From 2f6db155cb3b568ac709878af267ca7e7b181e96 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Fri, 22 May 2026 14:36:43 +0200 Subject: [PATCH] fix: add password parameter to pemToPfx and importCertificate functions for enhanced security --- src/lib/keyvault.ts | 3 ++- src/lib/pfx.ts | 4 ++-- src/lib/provisioner.ts | 16 ++++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib/keyvault.ts b/src/lib/keyvault.ts index 1dad298..1aceaf3 100644 --- a/src/lib/keyvault.ts +++ b/src/lib/keyvault.ts @@ -47,8 +47,9 @@ export class KeyVaultStore { return expiresOn.getTime() - Date.now() <= thresholdMs; } - async importCertificate(name: string, cert: string | Buffer, format: 'pem' | 'pfx' = 'pem'): Promise { + async importCertificate(name: string, cert: string | Buffer, format: 'pem' | 'pfx' = 'pem', password?: string): Promise { const options: ImportCertificateOptions = { + password, policy: { contentType: format === 'pfx' ? 'application/x-pkcs12' : 'application/x-pem-file', issuerName: 'Unknown', diff --git a/src/lib/pfx.ts b/src/lib/pfx.ts index 43e105e..59a0ba2 100644 --- a/src/lib/pfx.ts +++ b/src/lib/pfx.ts @@ -6,7 +6,7 @@ export interface PemBundle { chainPem: string; } -export function pemToPfx(privateKeyPem: string, certPem: string, chainPem: string): Buffer { +export function pemToPfx(privateKeyPem: string, certPem: string, chainPem: string, password: string): Buffer { const key = forge.pki.privateKeyFromPem(privateKeyPem); const cert = forge.pki.certificateFromPem(certPem); const chain = chainPem @@ -14,7 +14,7 @@ export function pemToPfx(privateKeyPem: string, certPem: string, chainPem: strin .filter(Boolean) .map(p => forge.pki.certificateFromPem(p)); - const p12 = forge.pkcs12.toPkcs12Asn1(key, [cert, ...chain], null); + const p12 = forge.pkcs12.toPkcs12Asn1(key, [cert, ...chain], password, { algorithm: '3des' }); const der = forge.asn1.toDer(p12).getBytes(); return Buffer.from(der, 'binary'); } diff --git a/src/lib/provisioner.ts b/src/lib/provisioner.ts index 59e5f4a..c977761 100644 --- a/src/lib/provisioner.ts +++ b/src/lib/provisioner.ts @@ -1,3 +1,4 @@ +import { randomUUID } from 'node:crypto'; import { DefaultAzureCredential } from '@azure/identity'; import { AcmeClient } from './acme.js'; import { ChallengeHandler } from './challenge.js'; @@ -105,10 +106,12 @@ export class Provisioner { const fqdns = group.map(d => d.fqdn); const issued = await this.acme.orderCertificate(fqdns, this.challengeHandler); - const [cert, format] = this.config.pfx - ? [pemToPfx(issued.privateKeyPem, issued.certificatePem, issued.chainPem), 'pfx' as const] - : [issued.privateKeyPem + issued.certificatePem + issued.chainPem, 'pem' as const]; - await this.store.importCertificate(certName, cert, format); + if (this.config.pfx) { + const password = randomUUID(); + await this.store.importCertificate(certName, pemToPfx(issued.privateKeyPem, issued.certificatePem, issued.chainPem, password), 'pfx', password); + } else { + await this.store.importCertificate(certName, issued.privateKeyPem + issued.certificatePem + issued.chainPem, 'pem'); + } if (status === 'missing') { result.certificatesIssued.push(primary.fqdn); @@ -157,9 +160,10 @@ export class Provisioner { if (currentFormat === 'pem') { const bundle = parsePemBundle(secretValue); this.log(`[convert] parsed blocks — key: ${bundle.privateKeyPem.length} chars, cert: ${bundle.certPem.length} chars, chain: ${bundle.chainPem.length} chars`); - const pfxBuffer = pemToPfx(bundle.privateKeyPem, bundle.certPem, bundle.chainPem); + const password = randomUUID(); + const pfxBuffer = pemToPfx(bundle.privateKeyPem, bundle.certPem, bundle.chainPem, password); this.log(`[convert] PFX buffer size: ${pfxBuffer.length} bytes`); - await this.store.importCertificate(certName, pfxBuffer, 'pfx'); + await this.store.importCertificate(certName, pfxBuffer, 'pfx', password); } else { const bundle = pfxToPem(Buffer.from(secretValue, 'base64')); this.log(`[convert] parsed PFX — key: ${bundle.privateKeyPem.length} chars, cert: ${bundle.certPem.length} chars, chain: ${bundle.chainPem.length} chars`);