From c4391ee723da5ae2ce969e5708a7f86515348275 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. SecureContext::LoadPKCS12 now uses this same parsing logic. 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 Co-authored-by: Filip Skokan --- doc/api/crypto.md | 33 +++ lib/crypto.js | 2 + lib/internal/crypto/keys.js | 64 ++++++ node.gyp | 2 + src/crypto/crypto_context.cc | 103 ++++----- src/crypto/crypto_pkcs12.cc | 193 ++++++++++++++++ src/crypto/crypto_pkcs12.h | 54 +++++ 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 + 13 files changed, 643 insertions(+), 59 deletions(-) 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 c95104f4e16e..7f6fbf7ffd40 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -5518,6 +5518,39 @@ 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} A DER-encoded PKCS#12 + (`.p12` or `.pfx`) bundle. +* `options` {Object} + * `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} The passphrase + protecting the bundle. Omitting this option is equivalent to passing `''`. +* Returns: {Object} + * `privateKey` {KeyObject|null} The first private key in the bundle, or + `null` if none is present. + * `certificate` {X509Certificate|null} The certificate matching `privateKey`, + or `null` if no matching certificate is present. + * `additionalCertificates` {X509Certificate\[]} All other certificates in + the bundle. If there is no private key, this contains all certificates. + May be empty. + +Parses a PKCS#12 bundle, commonly stored with a `.p12` or `.pfx` extension, +and returns its private key and certificates. + +```mjs +import { parsePKCS12 } from 'node:crypto'; +import { readFileSync } from 'node:fs'; + +const { privateKey, certificate, additionalCertificates } = parsePKCS12( + readFileSync('bundle.p12'), + { passphrase: 'secret' }, +); +``` + ### `crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)`