diff --git a/src/integration/signatures/signing.test.ts b/src/integration/signatures/signing.test.ts index 9277b76..8f13225 100644 --- a/src/integration/signatures/signing.test.ts +++ b/src/integration/signatures/signing.test.ts @@ -459,6 +459,8 @@ describe("signing integration", () => { const pdfStr = new TextDecoder().decode(bytes); expect(pdfStr).toContain("/Type /Sig"); + + await saveTestOutput("signatures/signed-aes128.pdf", bytes); }); it("signs with Triple DES P12", async () => { @@ -473,6 +475,8 @@ describe("signing integration", () => { const pdfStr = new TextDecoder().decode(bytes); expect(pdfStr).toContain("/Type /Sig"); + + await saveTestOutput("signatures/signed-3des.pdf", bytes); }); it("signs with legacy RC2 P12", async () => { @@ -487,6 +491,8 @@ describe("signing integration", () => { const pdfStr = new TextDecoder().decode(bytes); expect(pdfStr).toContain("/Type /Sig"); + + await saveTestOutput("signatures/signed-rc2.pdf", bytes); }); }); diff --git a/src/signatures/crypto/crypto-engine.ts b/src/signatures/crypto/crypto-engine.ts index 751724c..ad85d47 100644 --- a/src/signatures/crypto/crypto-engine.ts +++ b/src/signatures/crypto/crypto-engine.ts @@ -245,12 +245,18 @@ export function getCryptoEngine(): CryptoEngine { } /** - * Install the legacy crypto engine globally in pkijs. - * Call this once at startup to enable legacy P12 support. + * Get the pkijs Crypto object, ensuring our custom engine is installed. + * + * Handles cases where pkijs engine might not be set yet, or where another + * engine is already installed. */ -export function installCryptoEngine(): void { +export const getCrypto = () => { const engine = getCryptoEngine(); - // oxlint-disable-next-line typescript/no-unsafe-type-assertion - pkijs.setEngine(engine.name, engine as unknown as pkijs.ICryptoEngine); -} + if (!pkijs.engine || pkijs.engine.name !== engine.name) { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion + pkijs.setEngine(engine.name, engine as unknown as pkijs.ICryptoEngine); + } + + return pkijs.getCrypto(true); +}; diff --git a/src/signatures/crypto/index.ts b/src/signatures/crypto/index.ts index 2c12d67..bf76ee2 100644 --- a/src/signatures/crypto/index.ts +++ b/src/signatures/crypto/index.ts @@ -10,7 +10,7 @@ export { CryptoEngine, decryptLegacyPbe, getCryptoEngine, - installCryptoEngine, + getCrypto, isLegacyPbeOid, LEGACY_PBE_OIDS, } from "./crypto-engine"; diff --git a/src/signatures/revocation.ts b/src/signatures/revocation.ts index 9bf5991..3952234 100644 --- a/src/signatures/revocation.ts +++ b/src/signatures/revocation.ts @@ -12,6 +12,7 @@ import { fromBER, ObjectIdentifier, OctetString, Sequence } from "asn1js"; import * as pkijs from "pkijs"; import { toArrayBuffer } from "../helpers/buffer"; +import { getCrypto } from "./crypto"; import { OID_AD_OCSP, OID_AUTHORITY_INFO_ACCESS, @@ -245,7 +246,7 @@ export class DefaultRevocationProvider implements RevocationProvider { cert: pkijs.Certificate, issuer: pkijs.Certificate, ): Promise { - const crypto = pkijs.getCrypto(true); + const crypto = getCrypto(); // Use SHA-1 for OCSP certID hashes. // While SHA-1 is deprecated for signatures, it's widely required for OCSP diff --git a/src/signatures/signers/crypto-key.ts b/src/signatures/signers/crypto-key.ts index ca6f378..3564570 100644 --- a/src/signatures/signers/crypto-key.ts +++ b/src/signatures/signers/crypto-key.ts @@ -4,13 +4,11 @@ * Signs using a CryptoKey directly. */ -import * as pkijs from "pkijs"; import { createCMSECDSASignature } from "pkijs"; +import { getCrypto } from "../crypto"; import type { DigestAlgorithm, KeyType, SignatureAlgorithm, Signer } from "../types"; -const cryptoEngine = pkijs.getCrypto(true); - /** * Signer that uses a Web Crypto CryptoKey directly. * @@ -71,6 +69,8 @@ export class CryptoKeySigner implements Signer { * @returns Raw signature bytes */ async sign(data: Uint8Array, algorithm: DigestAlgorithm): Promise { + const crypto = getCrypto(); + let signAlgorithm: { name: string; saltLength?: number; hash?: { name: string } }; switch (this.signatureAlgorithm) { @@ -87,7 +87,7 @@ export class CryptoKeySigner implements Signer { break; } - const signature = await cryptoEngine.sign(signAlgorithm, this.privateKey, new Uint8Array(data)); + const signature = await crypto.sign(signAlgorithm, this.privateKey, new Uint8Array(data)); // WebCrypto ECDSA returns P1363 format (r || s), but CMS requires DER format if (this.signatureAlgorithm === "ECDSA") { diff --git a/src/signatures/signers/p12.ts b/src/signatures/signers/p12.ts index 899cec9..4878d98 100644 --- a/src/signatures/signers/p12.ts +++ b/src/signatures/signers/p12.ts @@ -10,7 +10,7 @@ import { createCMSECDSASignature } from "pkijs"; import { toArrayBuffer } from "../../helpers/buffer"; import { buildCertificateChain } from "../aia"; -import { decryptLegacyPbe, installCryptoEngine, isLegacyPbeOid, PKCS12KDF } from "../crypto"; +import { decryptLegacyPbe, getCrypto, isLegacyPbeOid, PKCS12KDF } from "../crypto"; import { OID_CERT_BAG, OID_EC_PUBLIC_KEY, @@ -24,12 +24,6 @@ import { import type { DigestAlgorithm, KeyType, SignatureAlgorithm, Signer } from "../types"; import { CertificateChainError, SignerError } from "../types"; -// Install our legacy crypto engine to handle 3DES/RC2 encrypted P12 files -installCryptoEngine(); - -// Get the crypto engine (now with legacy support) -const cryptoEngine = pkijs.getCrypto(true); - /** * Options for creating a P12Signer. */ @@ -252,6 +246,8 @@ export class P12Signer implements Signer { password: string, passwordBuffer: ArrayBuffer, ): Promise { + const crypto = getCrypto(); + // oxlint-disable-next-line typescript/no-unsafe-type-assertion const keyBag = safeBag.bagValue as pkijs.PKCS8ShroudedKeyBag; const algorithmId = keyBag.encryptionAlgorithm.algorithmId; @@ -300,7 +296,7 @@ export class P12Signer implements Signer { decryptedKey = toArrayBuffer(decrypted); } else { // Use pkijs/Web Crypto - decryptedKey = await cryptoEngine.decryptEncryptedContentInfo({ + decryptedKey = await crypto.decryptEncryptedContentInfo({ encryptedContentInfo: new pkijs.EncryptedContentInfo({ contentEncryptionAlgorithm: keyBag.encryptionAlgorithm, encryptedContent: keyBag.encryptedData, @@ -325,11 +321,13 @@ export class P12Signer implements Signer { * Import a PrivateKeyInfo into WebCrypto. */ private static async importPrivateKey(privateKeyInfo: pkijs.PrivateKeyInfo): Promise { + const crypto = getCrypto(); + const algorithmOid = privateKeyInfo.privateKeyAlgorithm.algorithmId; // RSA if (algorithmOid === OID_RSA_ENCRYPTION) { - return cryptoEngine.importKey( + return crypto.importKey( "pkcs8", privateKeyInfo.toSchema().toBER(false), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, @@ -355,7 +353,7 @@ export class P12Signer implements Signer { } } - return cryptoEngine.importKey( + return crypto.importKey( "pkcs8", privateKeyInfo.toSchema().toBER(false), { name: "ECDSA", namedCurve }, @@ -413,6 +411,8 @@ export class P12Signer implements Signer { * @returns The signature bytes */ async sign(data: Uint8Array, algorithm: DigestAlgorithm): Promise { + const crypto = getCrypto(); + let signAlgorithm: { name: string; saltLength?: number; hash?: { name: string } }; switch (this.signatureAlgorithm) { @@ -429,7 +429,7 @@ export class P12Signer implements Signer { break; } - const signature = await cryptoEngine.sign(signAlgorithm, this.privateKey, new Uint8Array(data)); + const signature = await crypto.sign(signAlgorithm, this.privateKey, new Uint8Array(data)); // WebCrypto ECDSA returns P1363 format (r || s), but CMS requires DER format if (this.signatureAlgorithm === "ECDSA") {