import { readFileSync } from "node:fs"; import { KeyObject, createPrivateKey } from "node:crypto"; import { Injectable, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { EimsConfig } from "../../config/eims.config"; import { EimsConfigException } from "./eims.errors"; /** * Loads the INSA-issued EIMS credentials from disk, once, and keeps them in memory. * * The certificate is sent as base64 of the **exact bytes of the issued file** — it is deliberately * never parsed, re-encoded or re-exported, because that is what produced a working live login. * The private key never leaves this process: it is only ever used to produce a signature. */ @Injectable() export class EimsCredentialsProvider { private readonly logger = new Logger(EimsCredentialsProvider.name); private privateKey: KeyObject | null = null; private certificateBase64: string | null = null; constructor(private readonly config: ConfigService) {} private get cfg(): EimsConfig { return this.config.get("eims")!; } /** * RSA private key, parsed once. `privateKeyBase64` wins when set (no file I/O at all — for a * container that can't be given a host bind mount); otherwise falls back to `privateKeyPath`. * Throws a config error if neither is usable. */ getPrivateKey(): KeyObject { if (this.privateKey) return this.privateKey; const { privateKeyBase64, privateKeyPath: path } = this.cfg; const source = privateKeyBase64 ? "EIMS_PRIVATE_KEY_BASE64" : `EIMS_PRIVATE_KEY_PATH (${path})`; if (!privateKeyBase64 && !path) throw new EimsConfigException("EIMS_PRIVATE_KEY_PATH is not set"); let key: KeyObject; try { const bytes = privateKeyBase64 ? Buffer.from(privateKeyBase64, "base64") : readFileSync(path); key = createPrivateKey(bytes); } catch (err) { // The source is operational information, not a secret; the key material never appears. throw new EimsConfigException( `EIMS private key from ${source} could not be read or parsed: ${(err as Error).message}`, ); } if (key.asymmetricKeyType !== "rsa") { throw new EimsConfigException( `EIMS private key from ${source} is ${key.asymmetricKeyType ?? "of unknown type"}; EIMS requires RSA`, ); } this.privateKey = key; this.logger.log(`EIMS private key loaded (RSA-${key.asymmetricKeyDetails?.modulusLength ?? "?"})`); return key; } /** * Base64 of the certificate file's exact bytes. No parsing, no re-encoding. `certificateBase64` * config wins when set (already base64, used as-is); otherwise read from `certificatePath`. */ getCertificateBase64(): string { if (this.certificateBase64) return this.certificateBase64; const { certificateBase64: inline, certificatePath: path } = this.cfg; if (inline) { this.certificateBase64 = inline; this.logger.log(`EIMS certificate bundle loaded from EIMS_CERTIFICATE_BASE64`); return this.certificateBase64; } if (!path) throw new EimsConfigException("EIMS_CERTIFICATE_PATH is not set"); let bytes: Buffer; try { bytes = readFileSync(path); } catch (err) { throw new EimsConfigException( `EIMS certificate at ${path} could not be read: ${(err as Error).message}`, ); } if (bytes.length === 0) { throw new EimsConfigException(`EIMS certificate at ${path} is empty`); } this.certificateBase64 = bytes.toString("base64"); this.logger.log(`EIMS certificate bundle loaded (${bytes.length} bytes)`); return this.certificateBase64; } }