Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/integration/signatures/signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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);
});
});

Expand Down
18 changes: 12 additions & 6 deletions src/signatures/crypto/crypto-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
2 changes: 1 addition & 1 deletion src/signatures/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export {
CryptoEngine,
decryptLegacyPbe,
getCryptoEngine,
installCryptoEngine,
getCrypto,
isLegacyPbeOid,
LEGACY_PBE_OIDS,
} from "./crypto-engine";
Expand Down
3 changes: 2 additions & 1 deletion src/signatures/revocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -245,7 +246,7 @@ export class DefaultRevocationProvider implements RevocationProvider {
cert: pkijs.Certificate,
issuer: pkijs.Certificate,
): Promise<Uint8Array> {
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
Expand Down
8 changes: 4 additions & 4 deletions src/signatures/signers/crypto-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -71,6 +69,8 @@ export class CryptoKeySigner implements Signer {
* @returns Raw signature bytes
*/
async sign(data: Uint8Array, algorithm: DigestAlgorithm): Promise<Uint8Array> {
const crypto = getCrypto();

let signAlgorithm: { name: string; saltLength?: number; hash?: { name: string } };

switch (this.signatureAlgorithm) {
Expand All @@ -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") {
Expand Down
22 changes: 11 additions & 11 deletions src/signatures/signers/p12.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
*/
Expand Down Expand Up @@ -252,6 +246,8 @@ export class P12Signer implements Signer {
password: string,
passwordBuffer: ArrayBuffer,
): Promise<CryptoKey> {
const crypto = getCrypto();

// oxlint-disable-next-line typescript/no-unsafe-type-assertion
const keyBag = safeBag.bagValue as pkijs.PKCS8ShroudedKeyBag;
const algorithmId = keyBag.encryptionAlgorithm.algorithmId;
Expand Down Expand Up @@ -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,
Expand All @@ -325,11 +321,13 @@ export class P12Signer implements Signer {
* Import a PrivateKeyInfo into WebCrypto.
*/
private static async importPrivateKey(privateKeyInfo: pkijs.PrivateKeyInfo): Promise<CryptoKey> {
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" },
Expand All @@ -355,7 +353,7 @@ export class P12Signer implements Signer {
}
}

return cryptoEngine.importKey(
return crypto.importKey(
"pkcs8",
privateKeyInfo.toSchema().toBER(false),
{ name: "ECDSA", namedCurve },
Expand Down Expand Up @@ -413,6 +411,8 @@ export class P12Signer implements Signer {
* @returns The signature bytes
*/
async sign(data: Uint8Array, algorithm: DigestAlgorithm): Promise<Uint8Array> {
const crypto = getCrypto();

let signAlgorithm: { name: string; saltLength?: number; hash?: { name: string } };

switch (this.signatureAlgorithm) {
Expand All @@ -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") {
Expand Down