crypto: add crypto.parsePKCS12() · nodejs/node@ddbf974 · GitHub
Skip to content

Commit ddbf974

Browse files
bmuenzenmeyerpanva
authored andcommitted
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 <brian.muenzenmeyer@gmail.com> Co-authored-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #65627 Backport-PR-URL: #66233 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent e7ed436 commit ddbf974

13 files changed

Lines changed: 643 additions & 59 deletions

File tree

‎doc/api/crypto.md‎

Lines changed: 33 additions & 0 deletions

‎lib/crypto.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const {
8484
createSecretKey,
8585
createPublicKey,
8686
createPrivateKey,
87+
parsePKCS12,
8788
KeyObject,
8889
} = require('internal/crypto/keys');
8990
const {
@@ -216,6 +217,7 @@ module.exports = {
216217
getMacs,
217218
hkdf,
218219
hkdfSync,
220+
parsePKCS12,
219221
pbkdf2,
220222
pbkdf2Sync,
221223
generateKeyPair,

‎lib/internal/crypto/keys.js‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
'use strict';
22

33
const {
4+
ArrayPrototypeMap,
45
ArrayPrototypeSlice,
56
ObjectDefineProperties,
67
ObjectPrototypeHasOwnProperty,
78
ObjectSetPrototypeOf,
89
StringPrototypeIncludes,
910
StringPrototypeStartsWith,
1011
SymbolToStringTag,
12+
TypedArrayPrototypeIncludes,
1113
Uint8Array,
1214
} = primordials;
1315

@@ -36,6 +38,7 @@ const {
3638
kKeyEncodingPKCS8,
3739
kKeyEncodingSPKI,
3840
kKeyEncodingSEC1,
41+
parsePKCS12: _parsePKCS12,
3942
} = internalBinding('crypto');
4043

4144
const {
@@ -64,6 +67,7 @@ const {
6467

6568
const {
6669
getArrayBufferOrView,
70+
getBufferSourceBytes,
6771
bigIntArrayToUnsignedBigInt,
6872
normalizeAlgorithm,
6973
hasAnyNotIn,
@@ -812,6 +816,65 @@ function createPublicKey(key) {
812816
return new PublicKeyObject(handle);
813817
}
814818

819+
/**
820+
* Parses a PKCS#12 (.p12 / .pfx) bundle. Returns an object holding the first
821+
* private key as `privateKey`, the certificate associated with it as
822+
* `certificate`, and every other certificate in the bundle as an array in
823+
* `additionalCertificates`. `privateKey` and `certificate` are null when the
824+
* bundle contains none.
825+
* @param {ArrayBuffer|Buffer|TypedArray|DataView} bundle
826+
* @param {object} [options]
827+
* @returns {object}
828+
*/
829+
function parsePKCS12(bundle, options = kEmptyObject) {
830+
if (!isArrayBufferView(bundle) && !isAnyArrayBuffer(bundle)) {
831+
throw new ERR_INVALID_ARG_TYPE(
832+
'bundle',
833+
['ArrayBuffer', 'TypedArray', 'DataView', 'Buffer'],
834+
bundle);
835+
}
836+
837+
validateObject(options, 'options');
838+
const { passphrase } = options;
839+
840+
// `undefined` means no passphrase, '' a zero-length one. OpenSSL accepts
841+
// either PKCS#12 password encoding for both, so they behave the same.
842+
let passBuf;
843+
if (passphrase !== undefined) {
844+
passBuf = getArrayBufferOrView(passphrase, 'options.passphrase', 'utf8');
845+
// OpenSSL takes the passphrase as a NUL-terminated C string, so one
846+
// containing a NUL byte would be truncated there and the bundle opened
847+
// with only the bytes before it. A PKCS#12 password cannot represent an
848+
// embedded NUL anyway, so reject it outright.
849+
if (TypedArrayPrototypeIncludes(getBufferSourceBytes(passBuf), 0)) {
850+
throw new ERR_INVALID_ARG_VALUE(
851+
'options.passphrase', passphrase, 'must not contain null bytes');
852+
}
853+
// The binding reads the passphrase as a view; wrap a bare ArrayBuffer.
854+
if (isAnyArrayBuffer(passBuf)) passBuf = Buffer.from(passBuf);
855+
}
856+
857+
// Likewise, the binding reads the bundle as a view.
858+
const bundleBuf = isAnyArrayBuffer(bundle) ? Buffer.from(bundle) : bundle;
859+
860+
const {
861+
0: keyHandle,
862+
1: certHandle,
863+
2: otherHandles,
864+
} = _parsePKCS12(bundleBuf, passBuf);
865+
866+
// Required lazily: internal/crypto/x509 depends on this module.
867+
const { InternalX509Certificate } = require('internal/crypto/x509');
868+
869+
return {
870+
privateKey: keyHandle === null ? null : new PrivateKeyObject(keyHandle),
871+
certificate:
872+
certHandle === null ? null : new InternalX509Certificate(certHandle),
873+
additionalCertificates:
874+
ArrayPrototypeMap(otherHandles, (h) => new InternalX509Certificate(h)),
875+
};
876+
}
877+
815878
/**
816879
* Converts a secret KeyObjectHandle to a CryptoKey by dispatching to the
817880
* algorithm-specific Web Crypto import path.
@@ -1461,6 +1524,7 @@ module.exports = {
14611524
createSecretKey,
14621525
createPublicKey,
14631526
createPrivateKey,
1527+
parsePKCS12,
14641528
KeyObject,
14651529
CryptoKey,
14661530
InternalCryptoKey,

‎node.gyp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@
407407
'src/crypto/crypto_hash.cc',
408408
'src/crypto/crypto_keys.cc',
409409
'src/crypto/crypto_keygen.cc',
410+
'src/crypto/crypto_pkcs12.cc',
410411
'src/crypto/crypto_scrypt.cc',
411412
'src/crypto/crypto_tls.cc',
412413
'src/crypto/crypto_x509.cc',
@@ -426,6 +427,7 @@
426427
'src/crypto/crypto_hash.h',
427428
'src/crypto/crypto_keys.h',
428429
'src/crypto/crypto_keygen.h',
430+
'src/crypto/crypto_pkcs12.h',
429431
'src/crypto/crypto_scrypt.h',
430432
'src/crypto/crypto_tls.h',
431433
'src/crypto/crypto_context.h',

‎src/crypto/crypto_context.cc‎

Lines changed: 44 additions & 59 deletions

0 commit comments

Comments
 (0)