import { readFileSync } from 'node:fs'; import { EjbcaProvider } from './ejbca'; import { LocalDevCaProvider } from './local-dev-provider'; import type { CaProvider } from './types'; export * from './types'; /** * Build the configured provider, or `null` when S/MIME enrolment is not set up. * * `null` rather than a throw, so an unconfigured deployment answers 503 on the * enrolment route and is otherwise unaffected. Enrolment is an opt-in feature of * a mail client; a missing CA secret must not stop anyone reading their mail. */ let cached: CaProvider | null | undefined; export function getCaProvider(): CaProvider | null { if (cached !== undefined) return cached; cached = build(); return cached; } function build(): CaProvider | null { const baseUrl = process.env.SMIME_CA_URL; if (!baseUrl) { // Explicit opt-in only, never a silent fallback: the real EJBCA needs a // client mTLS credential this environment doesn't have and lives on the // private dev-k8s network, unreachable from here tonight - see // local-dev-provider.ts's module header for exactly what this is (and // isn't) a substitute for. if (process.env.SMIME_CA_DEV_LOCAL === 'true') { return new LocalDevCaProvider(); } return null; } // Read from the mounted secret by default (see deploy/k8s/ca/README.md ยง 5.3). // Paths are overridable for local development against a throwaway CA. const pfxPath = process.env.SMIME_CA_CLIENT_PFX_PATH ?? '/etc/smime-ca/client.p12'; const caPath = process.env.SMIME_CA_CHAIN_PATH ?? '/etc/smime-ca/ca-chain.pem'; const password = process.env.SMIME_CA_CLIENT_PFX_PASSWORD; if (!password) { console.error('[smime-ca] SMIME_CA_URL is set but SMIME_CA_CLIENT_PFX_PASSWORD is not'); return null; } let clientPfx: Buffer; let serverCaPem: string; try { clientPfx = readFileSync(pfxPath); serverCaPem = readFileSync(caPath, 'utf8'); } catch (cause) { // Loud, because the symptom otherwise is "enrolment returns 503" with no // indication that a file is simply not mounted. console.error(`[smime-ca] cannot read RA credential (${pfxPath} / ${caPath}):`, cause); return null; } return new EjbcaProvider({ id: process.env.SMIME_CA_ID ?? 'ejbca', baseUrl: baseUrl.replace(/\/$/, ''), clientPfx, clientPfxPassword: password, serverCaPem, caName: process.env.SMIME_CA_NAME ?? 'VNC S/MIME Issuing CA Sandbox R1', certificateProfile: process.env.SMIME_CA_CERT_PROFILE ?? 'VNC S/MIME 1y', endEntityProfile: process.env.SMIME_CA_EE_PROFILE ?? 'VNC S/MIME User', }); }