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

Commit c0a42d2

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 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 6ddbb99 commit c0a42d2

13 files changed

Lines changed: 644 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: 65 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

@@ -35,6 +37,7 @@ const {
3537
kKeyEncodingPKCS8,
3638
kKeyEncodingSPKI,
3739
kKeyEncodingSEC1,
40+
parsePKCS12: _parsePKCS12,
3841
} = internalBinding('crypto');
3942

4043
const {
@@ -63,6 +66,7 @@ const {
6366

6467
const {
6568
getArrayBufferOrView,
69+
getBufferSourceBytes,
6670
bigIntArrayToUnsignedBigInt,
6771
normalizeAlgorithm,
6872
hasAnyNotIn,
@@ -92,6 +96,7 @@ const {
9296
} = require('internal/util');
9397

9498
const { inspect } = require('internal/util/inspect');
99+
const { Buffer } = require('buffer');
95100

96101
const emitDEP0203 = getDeprecationWarningEmitter(
97102
'DEP0203',
@@ -789,6 +794,65 @@ function createPublicKey(key) {
789794
return new PublicKeyObject(handle);
790795
}
791796

797+
/**
798+
* Parses a PKCS#12 (.p12 / .pfx) bundle. Returns an object holding the first
799+
* private key as `privateKey`, the certificate associated with it as
800+
* `certificate`, and every other certificate in the bundle as an array in
801+
* `additionalCertificates`. `privateKey` and `certificate` are null when the
802+
* bundle contains none.
803+
* @param {ArrayBuffer|Buffer|TypedArray|DataView} bundle
804+
* @param {object} [options]
805+
* @returns {object}
806+
*/
807+
function parsePKCS12(bundle, options = kEmptyObject) {
808+
if (!isArrayBufferView(bundle) && !isAnyArrayBuffer(bundle)) {
809+
throw new ERR_INVALID_ARG_TYPE(
810+
'bundle',
811+
['ArrayBuffer', 'TypedArray', 'DataView', 'Buffer'],
812+
bundle);
813+
}
814+
815+
validateObject(options, 'options');
816+
const { passphrase } = options;
817+
818+
// `undefined` means no passphrase, '' a zero-length one. OpenSSL accepts
819+
// either PKCS#12 password encoding for both, so they behave the same.
820+
let passBuf;
821+
if (passphrase !== undefined) {
822+
passBuf = getArrayBufferOrView(passphrase, 'options.passphrase', 'utf8');
823+
// OpenSSL takes the passphrase as a NUL-terminated C string, so one
824+
// containing a NUL byte would be truncated there and the bundle opened
825+
// with only the bytes before it. A PKCS#12 password cannot represent an
826+
// embedded NUL anyway, so reject it outright.
827+
if (TypedArrayPrototypeIncludes(getBufferSourceBytes(passBuf), 0)) {
828+
throw new ERR_INVALID_ARG_VALUE(
829+
'options.passphrase', passphrase, 'must not contain null bytes');
830+
}
831+
// The binding reads the passphrase as a view; wrap a bare ArrayBuffer.
832+
if (isAnyArrayBuffer(passBuf)) passBuf = Buffer.from(passBuf);
833+
}
834+
835+
// Likewise, the binding reads the bundle as a view.
836+
const bundleBuf = isAnyArrayBuffer(bundle) ? Buffer.from(bundle) : bundle;
837+
838+
const {
839+
0: keyHandle,
840+
1: certHandle,
841+
2: otherHandles,
842+
} = _parsePKCS12(bundleBuf, passBuf);
843+
844+
// Required lazily: internal/crypto/x509 depends on this module.
845+
const { InternalX509Certificate } = require('internal/crypto/x509');
846+
847+
return {
848+
privateKey: keyHandle === null ? null : new PrivateKeyObject(keyHandle),
849+
certificate:
850+
certHandle === null ? null : new InternalX509Certificate(certHandle),
851+
additionalCertificates:
852+
ArrayPrototypeMap(otherHandles, (h) => new InternalX509Certificate(h)),
853+
};
854+
}
855+
792856
/**
793857
* Converts a secret KeyObjectHandle to a CryptoKey by dispatching to the
794858
* algorithm-specific Web Crypto import path.
@@ -1438,6 +1502,7 @@ module.exports = {
14381502
createSecretKey,
14391503
createPublicKey,
14401504
createPrivateKey,
1505+
parsePKCS12,
14411506
KeyObject,
14421507
CryptoKey,
14431508
InternalCryptoKey,

‎node.gyp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@
412412
'src/crypto/crypto_hash.cc',
413413
'src/crypto/crypto_keys.cc',
414414
'src/crypto/crypto_keygen.cc',
415+
'src/crypto/crypto_pkcs12.cc',
415416
'src/crypto/crypto_scrypt.cc',
416417
'src/crypto/crypto_tls.cc',
417418
'src/crypto/crypto_x509.cc',
@@ -432,6 +433,7 @@
432433
'src/crypto/crypto_hash.h',
433434
'src/crypto/crypto_keys.h',
434435
'src/crypto/crypto_keygen.h',
436+
'src/crypto/crypto_pkcs12.h',
435437
'src/crypto/crypto_scrypt.h',
436438
'src/crypto/crypto_tls.h',
437439
'src/crypto/crypto_context.h',

‎src/crypto/crypto_context.cc‎

Lines changed: 44 additions & 59 deletions

0 commit comments

Comments
 (0)