From 73cd4f1dde7d5d713b76873cbd745d8ec67762cc Mon Sep 17 00:00:00 2001 From: Brian Muenzenmeyer Date: Fri, 28 Aug 2026 06:10:13 -0500 Subject: [PATCH] crypto: add crypto.parsePKCS12() Return the private key, end-entity certificate, and any other non-matching certificates from a PKCS#12 (.p12/.pfx) bundle as a KeyObject and X509Certificate instances. Node.js already parses PKCS#12 in SecureContext::LoadPKCS12, which backs tls's `pfx` option, but the results are consumed directly into an SSL_CTX and never reach JavaScript. Callers who need the key or the certificates for anything other than an immediate TLS connection have to shell out to `openssl pkcs12` or take a userland dependency. The binding wraps d2i_PKCS12_bio() and PKCS12_parse() and follows their semantics, matching the existing TLS path: the first private key is returned, the end-entity certificate is the one associated with that key, and any remaining certificates are returned through `additionalCertificates`. A bundle containing no private key reports `certificate` as null and returns its certificates through `additionalCertificates`. Absent and empty passphrases are kept distinct, since OpenSSL treats them differently. Bundles that require OpenSSL's legacy provider throw ERR_CRYPTO_UNSUPPORTED_OPERATION, reusing the error added for the TLS path. Signed-off-by: bmuenzenmeyer --- doc/api/crypto.md | 63 ++++++ lib/crypto.js | 2 + lib/internal/crypto/keys.js | 66 ++++++ node.gyp | 2 + src/crypto/crypto_pkcs12.cc | 220 ++++++++++++++++++ src/crypto/crypto_pkcs12.h | 23 ++ src/node_crypto.cc | 1 + src/node_crypto.h | 1 + test/fixtures/keys/Makefile | 13 ++ test/fixtures/keys/cert-without-key-fips.pfx | Bin 0 -> 1296 bytes test/parallel/test-crypto-pkcs12.js | 226 +++++++++++++++++++ typings/internalBinding/crypto.d.ts | 10 + 12 files changed, 627 insertions(+) create mode 100644 src/crypto/crypto_pkcs12.cc create mode 100644 src/crypto/crypto_pkcs12.h create mode 100644 test/fixtures/keys/cert-without-key-fips.pfx create mode 100644 test/parallel/test-crypto-pkcs12.js diff --git a/doc/api/crypto.md b/doc/api/crypto.md index a08519a5768..e60e31e7909 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -5516,6 +5516,65 @@ const derivedKey = hkdfSync('sha512', 'key', 'salt', 'info', 64); console.log(Buffer.from(derivedKey).toString('hex')); // '24156e2...5391653' ``` +### `crypto.parsePKCS12(bundle[, options])` + + + +* `bundle` {ArrayBuffer|Buffer|TypedArray|DataView} The DER-encoded PKCS#12 + bundle. +* `options` {Object} + * `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} The passphrase + protecting the bundle. Omit for bundles with no passphrase. PKCS#12 + encodes an absent and an empty passphrase differently, but OpenSSL tries + both, so omitting this option and passing `''` behave the same. The + passphrase must not contain a NUL byte; PKCS#12 passwords cannot + represent one, and passing one throws [`ERR_INVALID_ARG_VALUE`][]. +* Returns: {Object} + * `privateKey` {KeyObject|null} The private key, or `null` if the bundle + contains none. + * `certificate` {X509Certificate|null} The certificate associated with + `privateKey`, or `null` if the bundle contains none. + * `additionalCertificates` {X509Certificate\[]} Every other certificate in + the bundle. These are not necessarily certificate authorities; this is + whatever remains once `certificate` has been taken out. May be empty. + +Parses a PKCS#12 bundle — commonly seen with the `.p12` or `.pfx` extension — +and returns its contents. + +```mjs +import { parsePKCS12 } from 'node:crypto'; +import { readFileSync } from 'node:fs'; + +const { privateKey, certificate } = parsePKCS12( + readFileSync('bundle.p12'), + { passphrase: 'secret' }, +); + +console.log(certificate.subject); +console.log(privateKey.export({ type: 'pkcs8', format: 'pem' })); +``` + +A PKCS#12 bundle may technically contain more than one private key. This API +returns only the first, matching the behavior of OpenSSL's `PKCS12_parse()`. + +`certificate` is identified by its association with the private key. A bundle +containing no private key therefore reports `certificate` as `null` and returns +all of its certificates through `additionalCertificates`, including any +end-entity certificate the bundle holds. + +Bundles encrypted with older algorithms — notably RC2 and PBE-SHA1 variants +produced by legacy Windows tooling and older versions of `keytool` — require +OpenSSL's legacy provider. Reading these throws an error with the code +[`ERR_CRYPTO_UNSUPPORTED_OPERATION`][]; starting Node.js with +[`--openssl-legacy-provider`][] may allow them to be read, subject to the +security implications of enabling that provider. + +To use a PKCS#12 bundle directly for a TLS connection, prefer the `pfx` option +of [`tls.createSecureContext()`][] rather than parsing and re-supplying the +parts. + ### `crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)`