PKCS#12

The PKCS#12 (.p12 / .pfx) producing side (RFC 7292, RFC 9579). pki.pkcs12.build assembles a password-integrity PFX -- key, certificate, CRL, and secret bags (shrouded keys and cert safes encrypted under RFC 8018 PBES2) wrapped in an AuthenticatedSafe, protected by either a classic Appendix B HMAC or an RFC 9579 PBMAC1 (PBKDF2 + HMAC) MAC, over AES-128/192/256-CBC and SHA-256/384/512. Every password is encoded the PKCS#12 way (BMPString + NULL, Appendix B.1), so a file it emits opens in OpenSSL and NSS. pki.pkcs12.verifyMac recomputes a store's MAC over the exact AuthenticatedSafe byte range and constant-time-compares it. Parsing lives at pki.schema.pkcs12.parse.

pki.pkcs12.build

since 0.3.11 experimental
pki.pkcs12.build(spec, opts?) -> Promise<Buffer|string>

Build a PKCS#12 (.p12 / .pfx) store with password OR public-key integrity. spec is either the OpenSSL-style convenience form { key, cert, ca?, friendlyName?, localKeyId? } (one PBES2-encrypted cert safe plus one shrouded-key safe) or the full form { safeContents: [...] }, where each element is a plaintext or PBES2-encrypted SafeContents of key / shroudedKey / cert / crl / secret / nested safeContents bags. Keys and certs are validated before wrapping. Password integrity (the default) MACs the AuthenticatedSafe with a classic Appendix B HMAC or an RFC 9579 PBMAC1. Public-key integrity (opts.integrity.mode: "public-key") instead wraps the AuthenticatedSafe in a CMS SignedData -- a signature from a keypair, no MacData (RFC 7292 sec. 4). Privacy (PBES2 bag encryption via password) is independent of the integrity mode. The store is re-parsed before return.

Options

- `password` -- the shared privacy + integrity password (string / Buffer / Uint8Array).
- `mac` -- `false` for a MAC-less store, or `{ algorithm: 'hmac'(default)|'pbmac1', hash: 'sha256'(default)|'sha1'|'sha384'|'sha512', salt?, iterations?, keyLength? }`.
- `integrity` -- `{ mode: 'public-key', signer: { cert, key, digestAlgorithm?, pss? } | signers: [ ... ], sid?, signingTime?, certificates? }` for public-key integrity (a CMS SignedData authSafe over any `pki.cms.sign` signer algorithm, no MacData). Combining it with a truthy `mac` is rejected.
- `recipientCerts` -- (public-key privacy convenience) `[certDer|PEM, ...]`; the convenience-form cert + key are placed in one recipient-enveloped safe (a plaintext keyBag). For the full form, set per-`safeContents` `recipients: [{ cert }, ...]` -- CERTIFICATE recipients only (RSA-OAEP / ECDH / X25519 / X448 / ML-KEM, dispatched off the cert key); a password or KEK recipient is not public-key privacy and is rejected -- with an optional `contentEncryptionAlgorithm` (`aes-128|192|256-cbc`, default 256; GCM/AEAD rejected). `encrypt` (password) and `recipients` (public-key) on the same safe is rejected; privacy is independent of integrity.
- `pem` (boolean) -- return a PEM `PKCS12` string instead of DER.

Example

async function example() {
  var p12 = await pki.pkcs12.build({ safeContents: [{ bags: [
    { type: 'cert', cert: signerCertDer },
    { type: 'shroudedKey', key: signerKeyPkcs8, encrypt: { password: 'changeit' } } ] }] },
    { password: 'changeit', mac: { algorithm: 'hmac', hash: 'sha256' } });
}
example();

References

pki.pkcs12.verifyMac

since 0.3.11 experimental
pki.pkcs12.verifyMac(pfx, password, opts?) -> Promise<boolean>

Verify a password-integrity PKCS#12 store's MAC. pfx is a pki.schema.pkcs12.parse result, a DER Buffer, or a PEM string. The password is BMPString+NULL encoded (RFC 7292 App. B.1), the MAC is recomputed over the store's exact AuthenticatedSafe byte range (macedBytes) using the store's own MAC parameters -- the classic Appendix B (ID=3) HMAC or the RFC 9579 PBMAC1 -- and constant-time-compared to the stored MAC value. Returns true / false for the password match; throws Pkcs12Error on a MAC-less or public-key-integrity store, or an unsupported MAC algorithm (never a falsy verdict standing in for an error).

Example

async function example() {
  var p12 = await pki.pkcs12.build({ key: signerKeyPkcs8, cert: signerCertDer }, { password: 'changeit' });
  var ok = await pki.pkcs12.verifyMac(p12, 'changeit');
}
example();

References

pki.pkcs12.open

since 0.3.12 experimental
pki.pkcs12.open(pfx, password, opts?) -> Promise<OpenResult>

Read a PKCS#12 store: verify its integrity FIRST -- a password store's MAC, or a public-key store's CMS SignedData signature (RFC 7292 sec. 4 / 5.1 -- never trust a bag from a store whose integrity check fails) -- then decrypt every PBES2 privacy safe and pkcs8ShroudedKeyBag with the password -- and every id-envelopedData (public-key privacy) safe with opts.recipientKey (RFC 7292 sec. 3.1, via pki.cms.decrypt) -- returning a structured bundle { integrityMode, macVerified, signers, keys, certs, crls, secrets } -- each private key as PKCS#8 PrivateKeyInfo DER (re-validated), each certificate / CRL / secret as raw DER, all carrying their friendlyName / localKeyId for pairing. pfx is a DER Buffer, PEM string, or a pki.schema.pkcs12.parse result.

A MAC-less store is refused (pkcs12/no-integrity) unless opts.allowUnauthenticated is set. A public-key integrity store is verified through pki.cms.verify before any bag is trusted; a signature failure is pkcs12/signature-invalid, and signers carries the per-signer verdict [{ ok, sid, cert }] (null in password / MAC-less mode). The signer is surfaced, NEVER trust-chained -- anchoring signers[i].cert to a trust root is the caller's pki.path.validate step (the out-of-path signer contract). Privacy is independent of integrity, so the bag password still decrypts a public-key store's bags; a wrong bag password there is the uniform pkcs12/decrypt-failed (no MAC to catch it first). Bags encrypted under PBES2 (AES-CBC), or the RFC 7292 App. C legacy schemes an openssl pkcs12 -legacy store uses -- 3-key / 2-key Triple-DES-CBC and 40-/128-bit RC2-CBC (the App. B KDF over the BMPString password, RC2 via an in-tree RFC 2268 cipher) -- are decrypted; the legacy RC4 schemes are named and refused.

Options

- `allowUnauthenticated` (boolean) -- open a MAC-less store anyway (result carries `macVerified: false`).
- `signerCerts` (array of cert DER) -- signer certificate(s) for a public-key store built with `certificates: false` (forwarded to `pki.cms.verify`).
- `recipientKey` (PKCS#8 DER|PEM) + `recipientCert` (cert DER|PEM, or `recipientIndex`) -- decrypt an id-envelopedData (public-key privacy) safe. The recipient key is a privacy credential only; a wrong key / tampered envelope is the uniform `pkcs12/decrypt-failed`, and an enveloped safe with no `recipientKey` is `pkcs12/no-recipient-key`.
- `maxIterations` (number) -- lower the PBKDF2 / MAC iteration cap for this call (downward-only).
- `keys` (string) -- `der` (default) or `crypto` (also `pki.key.import` each private key to a CryptoKey).
- `importAlgorithm` -- forwarded to `pki.key.import` for the ambiguous RSA / EC arms when `keys: crypto`.

Example

async function example() {
  var p12 = await pki.pkcs12.build({ key: signerKeyPkcs8, cert: signerCertDer }, { password: 'changeit' });
  var store = await pki.pkcs12.open(p12, 'changeit');
  var keyDer = store.keys[0].pkcs8, certDer = store.certs[0].cert;
}
example();

References