668f3c3e28
fix: modify pfxToPem function to export private key as PKCS#8 for Azure Key Vault compatibility
74 lines
2.6 KiB
TypeScript
74 lines
2.6 KiB
TypeScript
import { TokenCredential } from '@azure/identity';
|
|
import {
|
|
CertificateClient,
|
|
CertificatePolicy,
|
|
KeyVaultCertificateWithPolicy,
|
|
} from '@azure/keyvault-certificates';
|
|
import { SecretClient } from '@azure/keyvault-secrets';
|
|
|
|
export class KeyVaultStore {
|
|
private readonly secretClient: SecretClient;
|
|
private readonly certClient: CertificateClient;
|
|
|
|
constructor(credential: TokenCredential, keyVaultUrl: string) {
|
|
this.secretClient = new SecretClient(keyVaultUrl, credential);
|
|
this.certClient = new CertificateClient(keyVaultUrl, credential);
|
|
}
|
|
|
|
async getSecret(name: string): Promise<string | undefined> {
|
|
try {
|
|
const secret = await this.secretClient.getSecret(name);
|
|
return secret.value;
|
|
} catch (err: unknown) {
|
|
if (isNotFound(err)) return undefined;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async setSecret(name: string, value: string): Promise<void> {
|
|
await this.secretClient.setSecret(name, value);
|
|
}
|
|
|
|
async getCertificate(name: string): Promise<KeyVaultCertificateWithPolicy | undefined> {
|
|
try {
|
|
return await this.certClient.getCertificate(name);
|
|
} catch (err: unknown) {
|
|
if (isNotFound(err)) return undefined;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async certificateExpiresWithin(name: string, days: number): Promise<boolean | 'missing'> {
|
|
const cert = await this.getCertificate(name);
|
|
if (!cert) return 'missing';
|
|
const expiresOn = cert.properties.expiresOn;
|
|
if (!expiresOn) return false;
|
|
const thresholdMs = days * 24 * 60 * 60 * 1000;
|
|
return expiresOn.getTime() - Date.now() <= thresholdMs;
|
|
}
|
|
|
|
async importCertificate(name: string, cert: string | Buffer, format: 'pem' | 'pfx' = 'pem', password?: string): Promise<void> {
|
|
const certBuffer = typeof cert === 'string' ? Buffer.from(cert) : cert;
|
|
const contentType = format === 'pfx' ? 'application/x-pkcs12' : 'application/x-pem-file';
|
|
try {
|
|
// When a certificate already exists, Azure validates the incoming bytes against
|
|
// its stored policy's content_type. Updating the policy first tells Azure to
|
|
// expect the new format; without this, converting PEM→PFX (or vice-versa)
|
|
// fails because Azure tries to parse binary PFX data as PEM.
|
|
await this.certClient.updateCertificatePolicy(name, { contentType } as CertificatePolicy);
|
|
} catch {
|
|
// Certificate doesn't exist yet — no policy to update, proceed to import.
|
|
}
|
|
await this.certClient.importCertificate(name, certBuffer, { password });
|
|
}
|
|
}
|
|
|
|
function isNotFound(err: unknown): boolean {
|
|
return (
|
|
typeof err === 'object' &&
|
|
err !== null &&
|
|
'statusCode' in err &&
|
|
(err as { statusCode: number }).statusCode === 404
|
|
);
|
|
}
|