@blamejs/pki 0.6.14 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +140 -286
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/pkcs12-build.js
CHANGED
|
@@ -16,22 +16,6 @@
|
|
|
16
16
|
* @spec RFC 7292, RFC 9579, RFC 8018
|
|
17
17
|
* @card Build a password-integrity PKCS#12 store (RFC 7292 / RFC 9579) and verify its MAC.
|
|
18
18
|
*/
|
|
19
|
-
//
|
|
20
|
-
// The PBES2 bag encryption and the PBMAC1 MAC compose the shared lib/pbes2.js home (the same PBKDF2 + AES-CBC
|
|
21
|
-
// + HMAC primitives pki.cms and pki.key use), fed PKCS#12 App. B.1-formatted password bytes, never the CMS
|
|
22
|
-
// UTF-8 encoding, the single most common PKCS#12 interop failure. The classic Appendix B.2 KDF (ID=3, the MAC
|
|
23
|
-
// integrity purpose) is bespoke PKCS#12 crypto with no in-tree equivalent, built here as one primitive and
|
|
24
|
-
// cross-checked against OpenSSL. The MAC is computed over the content octets of the id-data authSafe OCTET
|
|
25
|
-
// STRING (excluding its TLV header), exactly the parser's `macedBytes`, the canonical off-by-the-header
|
|
26
|
-
// trap designed out. Public-key integrity (an id-signedData authSafe, a CMS SignedData over the exact
|
|
27
|
-
// AuthenticatedSafe, no MacData) is produced by composing pki.cms.sign and verified on open by pki.cms.verify
|
|
28
|
-
// as the integrity gate before any bag is trusted; the signer is surfaced, never trust-chained (the caller's
|
|
29
|
-
// pki.path.validate step). Public-key PRIVACY (an id-envelopedData safe encrypting the SafeContents to a
|
|
30
|
-
// recipient public key, RFC 7292 sec. 3.1) is produced by composing pki.cms.encrypt (AES-CBC EnvelopedData,
|
|
31
|
-
// never GCM/AuthEnvelopedData) and opened by composing pki.cms.decrypt with the recipient key, after the
|
|
32
|
-
// integrity gate, with every recipient-side fault collapsed to the uniform pkcs12/decrypt-failed. Privacy is
|
|
33
|
-
// independent of integrity: all four combinations (password/public-key integrity x password/public-key privacy)
|
|
34
|
-
// are permitted, and the recipient key is a privacy credential only: never a MAC key, signer, or PBES2 password.
|
|
35
19
|
|
|
36
20
|
var nodeCrypto = require("crypto");
|
|
37
21
|
var asn1 = require("./asn1-der");
|
|
@@ -42,15 +26,15 @@ var pkcs8 = require("./schema-pkcs8");
|
|
|
42
26
|
var x509 = require("./schema-x509");
|
|
43
27
|
var schemaCrl = require("./schema-crl");
|
|
44
28
|
var key = require("./key");
|
|
45
|
-
var cms = require("./cms-verify");
|
|
46
|
-
var cmsSign = require("./cms-sign");
|
|
47
|
-
var cmsDecrypt = require("./cms-decrypt"); //
|
|
29
|
+
var cms = require("./cms-verify");
|
|
30
|
+
var cmsSign = require("./cms-sign");
|
|
31
|
+
var cmsDecrypt = require("./cms-decrypt"); // @internal
|
|
48
32
|
var schemaPkcs12 = require("./schema-pkcs12");
|
|
49
33
|
var pkix = require("./schema-pkix");
|
|
50
|
-
var pkiBuild = require("./pki-build");
|
|
34
|
+
var pkiBuild = require("./pki-build");
|
|
51
35
|
var guard = require("./guard-all");
|
|
52
36
|
var intrinsic = require("./guard-intrinsic");
|
|
53
|
-
var _toLower = intrinsic.uncurry(String.prototype.toLowerCase);
|
|
37
|
+
var _toLower = intrinsic.uncurry(String.prototype.toLowerCase);
|
|
54
38
|
var frameworkError = require("./framework-error");
|
|
55
39
|
var C = require("./constants");
|
|
56
40
|
|
|
@@ -60,108 +44,39 @@ var PemError = frameworkError.PemError;
|
|
|
60
44
|
function O(n) { return oid.byName(n); }
|
|
61
45
|
function _err(code, msg, cause) { return new Pkcs12Error(code, msg, cause); }
|
|
62
46
|
|
|
63
|
-
// PKCS#12 integrity/privacy defaults, named here so no call site writes them as literals (rule 12).
|
|
64
47
|
var DEFAULT_MAC_ITER = 2048;
|
|
65
48
|
var DEFAULT_PBMAC1_ITER = 2048;
|
|
66
49
|
var MAC_SALT_BYTES = 8;
|
|
67
|
-
var MAX_PBMAC1_KEYLEN = 1024;
|
|
68
|
-
// Both MAC paths cap iterations to bound the synchronous derivation that blocks the event loop, but at
|
|
69
|
-
// different counts, because the per-iteration cost differs by ~5x: the two ceilings target a comparable
|
|
70
|
-
// ~1-2 second wall-clock budget, not inconsistent DoS budgets. The classic Appendix B KDF is a hand-rolled
|
|
71
|
-
// JS hash loop (a createHash per iteration, ~1.1us each, so 1e6 ~= 1s); PBMAC1 derives via native
|
|
72
|
-
// crypto.pbkdf2Sync (one FFI call, ~0.2us per iteration, so the shared C.LIMITS.PBKDF2_MAX_ITERATIONS of 1e7
|
|
73
|
-
// ~= 2s). The 10x iteration-count gap is thus calibrated to wall clock; additionally the classic count is
|
|
74
|
-
// pkcs12-local (its KDF is bespoke here) while PBMAC1 reuses the toolkit-wide PBKDF2 ceiling. 1e6 is ~500x
|
|
75
|
-
// the OpenSSL default (2048) yet still bounds the loop to ~1 second.
|
|
50
|
+
var MAX_PBMAC1_KEYLEN = 1024;
|
|
76
51
|
var CLASSIC_MAC_MAX_ITERATIONS = 1000000;
|
|
77
|
-
// The aggregate cap on synchronous KDF work across a single open(): rounds summed over every encrypted
|
|
78
|
-
// bag and safe, whichever scheme it uses: the legacy App. B KDF (its block count x iterations) and PBKDF2
|
|
79
|
-
// alike. The per-bag iteration cap resets per bag, so a hostile store that duplicates a costly bag up to the
|
|
80
|
-
// parser's 1024-element limit would otherwise block the event loop for minutes; this bounds the total to
|
|
81
|
-
// ~the classic MAC ceiling. It covers both schemes because covering only the legacy one left the modern
|
|
82
|
-
// path (the one every current producer emits) free to multiply its cap by the element limit.
|
|
83
|
-
// A conforming store runs only a few thousand rounds (its handful of bags at ~2048 iterations).
|
|
84
52
|
var KDF_MAX_ROUNDS = CLASSIC_MAC_MAX_ITERATIONS;
|
|
85
53
|
|
|
86
|
-
// The classic App. B.2 KDF (u = hash output bytes, v = compression block bytes) per RFC 7292 App. B.2.
|
|
87
54
|
var P12_KDF_UV = {
|
|
88
55
|
sha1: { u: 20, v: 64 }, sha224: { u: 28, v: 64 }, sha256: { u: 32, v: 64 },
|
|
89
56
|
sha384: { u: 48, v: 128 }, sha512: { u: 64, v: 128 },
|
|
90
57
|
};
|
|
91
|
-
// PBMAC1 hash -> { prf AlgorithmIdentifier name, WebCrypto hash, HMAC key length }. RFC 9579 sec. 5/7 forbids
|
|
92
|
-
// a <= 160-bit digest, so SHA-1 is absent.
|
|
93
58
|
var PBMAC1_PRF = {
|
|
94
59
|
sha256: { prfName: "hmacWithSHA256", wc: "SHA-256", keyLen: 32 },
|
|
95
60
|
sha384: { prfName: "hmacWithSHA384", wc: "SHA-384", keyLen: 48 },
|
|
96
61
|
sha512: { prfName: "hmacWithSHA512", wc: "SHA-512", keyLen: 64 },
|
|
97
62
|
};
|
|
98
|
-
// PBMAC1 prf AlgorithmIdentifier name -> WebCrypto hash (verify direction).
|
|
99
63
|
var PRF_WC = { hmacWithSHA1: "SHA-1", hmacWithSHA256: "SHA-256", hmacWithSHA384: "SHA-384", hmacWithSHA512: "SHA-512" };
|
|
100
|
-
// operator cipher name -> AES-CBC registry name the PBES2 home speaks.
|
|
101
64
|
var CIPHER_NAME = { "aes-128-cbc": "aes128-CBC", "aes-192-cbc": "aes192-CBC", "aes-256-cbc": "aes256-CBC" };
|
|
102
|
-
// classic-MAC hash node name -> the registered digest OID name (identical spelling here).
|
|
103
65
|
var DIGEST_NAME = { sha1: "sha1", sha256: "sha256", sha384: "sha384", sha512: "sha512" };
|
|
104
66
|
|
|
105
|
-
// ---- password + KDF primitives ---------------------------------------------
|
|
106
67
|
|
|
107
|
-
// RFC 7292 App. B.1: a PKCS#12 password is the BMPString (UTF-16BE) encoding of the string plus a trailing
|
|
108
|
-
// 2-byte NULL terminator. "Beavis" -> 14 bytes. A non-BMP scalar (surrogate) is rejected. A Buffer/Uint8Array
|
|
109
|
-
// is taken verbatim as already-formatted bytes (an escape hatch for a caller that pre-encodes).
|
|
110
|
-
function _p12Password(pw) { return _p12PasswordOwned(pw).bytes; }
|
|
111
68
|
|
|
112
|
-
// An absent password is refused, never encoded as the empty one. The two are not the same
|
|
113
|
-
// credential, and the difference is invisible at the call site: a caller who misspells the option,
|
|
114
|
-
// or threads it through a layer that drops it, otherwise gets a store whose private key is
|
|
115
|
-
// protected by nothing and no error anywhere saying so. The empty password remains available:
|
|
116
|
-
// it just has to be asked for, as "".
|
|
117
69
|
var _MISSING_PASSWORD = "a password must be a string, Buffer, or Uint8Array -- an omitted password " +
|
|
118
70
|
"is not the empty password; pass \"\" to use the empty one deliberately";
|
|
119
71
|
|
|
120
|
-
// Every field opts.integrity reads. Adding one here is the only way to make it accepted, so a
|
|
121
|
-
// capability cannot arrive with its option silently ignored at this boundary.
|
|
122
72
|
var _INTEGRITY_OPTS = { mode: 1, signer: 1, signers: 1, certificates: 1, sid: 1, signingTime: 1 };
|
|
123
|
-
// The two caller-owned objects pki.pkcs12.build reads. The spec keys are what _normalizeSpec
|
|
124
|
-
// consumes -- the explicit `safeContents` form and the `{ key, cert, ca }` shorthand -- so the table
|
|
125
|
-
// is derived from that function rather than from the doc block. opts.integrity already had its own
|
|
126
|
-
// door; the objects containing it did not, so a misspelled `certificates` at the top level was
|
|
127
|
-
// dropped and the store built without them.
|
|
128
|
-
// The spec comes in two mutually exclusive FORMS, so it is checked against the one it selects.
|
|
129
|
-
// `_normalizeSpec` returns `spec.safeContents` the moment it is an array and never looks at the
|
|
130
|
-
// shorthand fields, so a union accepted `{ safeContents: [...], key }` and built a store with the
|
|
131
|
-
// key silently absent -- the same omission the door exists to refuse, reached by mixing the forms
|
|
132
|
-
// rather than by misspelling one.
|
|
133
73
|
var _BUILD_SPEC_FULL_KEYS = { safeContents: 1 };
|
|
134
74
|
var _BUILD_SPEC_SHORTHAND_KEYS = { key: 1, cert: 1, ca: 1, friendlyName: 1, localKeyId: 1 };
|
|
135
|
-
// `recipientCerts` selects the privacy recipients and is read by _normalizeSpec, which _build hands
|
|
136
|
-
// opts to -- it belongs here even though _build never names it. The three verbs this module exports
|
|
137
|
-
// have disjoint option sets (verifyMac reads maxIterations / recipientKey / allowUnauthenticated,
|
|
138
|
-
// open reads signerCerts / keys / importAlgorithm), so build gets its own table rather than a
|
|
139
|
-
// shared one that would admit theirs.
|
|
140
|
-
// Per spec FORM, because `recipientCerts` is read only by the shorthand branch of _normalizeSpec.
|
|
141
|
-
// Under the full form it is read by nothing: a caller who put a plaintext key bag in safeContents
|
|
142
|
-
// and supplied recipientCerts expecting public-key privacy got an id-data safe holding the private
|
|
143
|
-
// key in cleartext. The full form builds its own safes, so privacy there is `recipients` on the
|
|
144
|
-
// entry.
|
|
145
75
|
var _BUILD_OPTS_FULL = { integrity: 1, mac: 1, password: 1, pem: 1 };
|
|
146
76
|
var _BUILD_OPTS_SHORTHAND = { integrity: 1, mac: 1, password: 1, pem: 1, recipientCerts: 1 };
|
|
147
|
-
// The fields a safeContents entry carries. This entry is where the PRIVACY directive lives, which
|
|
148
|
-
// makes an unrecognized name here the costliest misspelling in the format: a misspelled `encrypt`
|
|
149
|
-
// is neither present nor falsy, so it passes the present-but-falsy guard in _buildAuthSafeElement,
|
|
150
|
-
// no privacy is selected, and the safe goes out as plaintext id-data -- an unshrouded keyBag in the
|
|
151
|
-
// clear, inside a PFX whose MAC still verifies and which opens without complaint.
|
|
152
|
-
// Per privacy branch, because the branches read different fields:
|
|
153
|
-
// `contentEncryptionAlgorithm` is the public-key branch's content cipher and is read by NOTHING on
|
|
154
|
-
// a password safe, whose cipher comes from `encrypt.cipher`. A union accepted an explicit cipher
|
|
155
|
-
// request on a password safe and encrypted under the default anyway.
|
|
156
77
|
var _SAFE_RECIPIENTS_KEYS = { bags: 1, recipients: 1, contentEncryptionAlgorithm: 1 };
|
|
157
78
|
var _SAFE_PASSWORD_KEYS = { bags: 1, encrypt: 1 };
|
|
158
79
|
var _SAFE_PLAINTEXT_KEYS = { bags: 1 };
|
|
159
|
-
// The fields a bag carries, PER TYPE rather than as one union. A union would admit a field that
|
|
160
|
-
// this bag's own arm never reads, which is worse here than a dropped attribute: `encrypt` on a
|
|
161
|
-
// `key` bag is read by nothing, so the private key is emitted as a plaintext keyBag while the
|
|
162
|
-
// caller supplied an explicit encryption directive and the outer MAC gives integrity only. Naming
|
|
163
|
-
// the fields per type makes that combination a refusal instead of a silent downgrade. Written out
|
|
164
|
-
// per type rather than merged at load, so each row reads as the whole contract for that bag.
|
|
165
80
|
var _BAG_KEYS_BY_TYPE = {
|
|
166
81
|
key: { type: 1, friendlyName: 1, localKeyId: 1, key: 1 },
|
|
167
82
|
shroudedKey: { type: 1, friendlyName: 1, localKeyId: 1, key: 1, encrypt: 1 },
|
|
@@ -170,76 +85,33 @@ var _BAG_KEYS_BY_TYPE = {
|
|
|
170
85
|
secret: { type: 1, friendlyName: 1, localKeyId: 1, secretTypeId: 1, secretValue: 1 },
|
|
171
86
|
safeContents: { type: 1, friendlyName: 1, localKeyId: 1, nested: 1 },
|
|
172
87
|
};
|
|
173
|
-
// The PBE descriptor both privacy levels share (_pbeOpts is the one place it is read, so one door
|
|
174
|
-
// covers the safe-level and bag-level spellings). A misspelled parameter reverts to the built-in
|
|
175
|
-
// default, so a caller who asked for a stronger KDF or a different cipher silently gets neither.
|
|
176
88
|
var _ENCRYPT_KEYS = { password: 1, cipher: 1, salt: 1, iterations: 1, prf: 1 };
|
|
177
|
-
// The MAC descriptor. Every field has a default, so a misspelled one reverts to the shipped value:
|
|
178
|
-
// a caller who asked for a higher iteration count, a different hash or a longer key gets the
|
|
179
|
-
// default instead, and the store carries no record of which was used.
|
|
180
|
-
// Per MAC algorithm: the classic Appendix B key derivation produces a key at the hash's own output
|
|
181
|
-
// length and has no keyLength to set, so admitting one there took an explicit security parameter and
|
|
182
|
-
// emitted the same MAC as a call that never named it. Only PBMAC1 (RFC 9579) derives to a length.
|
|
183
89
|
var _MAC_KEYS = { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 };
|
|
184
90
|
var _MAC_KEYS_HMAC = { algorithm: 1, hash: 1, salt: 1, iterations: 1, password: 1 };
|
|
185
91
|
var _MAC_KEYS_PBMAC1 = { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 };
|
|
186
|
-
// The reading verbs take their own option sets, disjoint from build's. `maxIterations` is the one
|
|
187
|
-
// that matters most: it caps the work a hostile store can demand, so a misspelling restores the
|
|
188
|
-
// built-in ceiling and the caller's tighter bound is never applied.
|
|
189
|
-
// The table is what the CALL reads, including through its delegations -- and those cross a module
|
|
190
|
-
// boundary here. recipientKey / recipientCert are read by _openEncryptedSafe; `recipientIndex` is
|
|
191
|
-
// read by cms-decrypt's decryptEnvelopedData, which _openEncryptedSafe hands this whole options
|
|
192
|
-
// object to, so a table built from this file's own `opts.` reads alone would refuse a documented
|
|
193
|
-
// option and break selecting a recipient by index in a multi-recipient store.
|
|
194
|
-
//
|
|
195
|
-
// One table here, not one per branch, and the distinction is who selects the branch. A builder's
|
|
196
|
-
// options are checked against the form the CALLER chose, because a field the chosen form never
|
|
197
|
-
// reads is a request that silently did not happen. Reading a store, the branch is chosen by the
|
|
198
|
-
// BYTES: which integrity mode it carries, and whether a safe is password- or recipient-encrypted,
|
|
199
|
-
// are not known until it is parsed. These options are the policy a caller declares in advance for
|
|
200
|
-
// whatever the store turns out to be, so one that goes unread means the store did not need it --
|
|
201
|
-
// signerCerts on a MAC'd store, recipientKey on one with no enveloped safe. Refusing those would
|
|
202
|
-
// make a correct, store-agnostic call fail on the shape of its input.
|
|
203
92
|
var _OPEN_OPTS = {
|
|
204
93
|
allowUnauthenticated: 1, importAlgorithm: 1, keys: 1, maxIterations: 1, signerCerts: 1,
|
|
205
94
|
recipientKey: 1, recipientCert: 1, recipientIndex: 1,
|
|
206
95
|
};
|
|
207
96
|
var _VERIFY_MAC_OPTS = { maxIterations: 1 };
|
|
208
97
|
|
|
209
|
-
// The same encoding, reporting ownership, mirroring pbes2.passwordBytesOwned. A caller-supplied
|
|
210
|
-
// Buffer is returned as-is and is borrowed: clearing it would destroy the caller's own credential,
|
|
211
|
-
// which is a worse defect than leaving a copy readable. Every other input is re-encoded into a
|
|
212
|
-
// buffer this module allocated, which it must clear once a derivation has consumed it.
|
|
213
98
|
function _p12PasswordOwned(pw) {
|
|
214
99
|
if (Buffer.isBuffer(pw)) return { bytes: guard.bytes.view(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: false };
|
|
215
100
|
return { bytes: _p12Encode(pw), owned: true };
|
|
216
101
|
}
|
|
217
102
|
function _p12Encode(pw) {
|
|
218
|
-
// A view first, so a detached backing store is a reject and never an empty password; then a copy,
|
|
219
|
-
// because everything this function returns is reported owned and gets zeroized after derivation,
|
|
220
|
-
// and handing back a view of a caller's Uint8Array would wipe the caller's own credential.
|
|
221
103
|
if (Buffer.isBuffer(pw) || pw instanceof Uint8Array) return guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password"); // allow:byte-source-narrow -- password on the ownership contract (string / Buffer / Uint8Array); widening passwords is a separate root decision
|
|
222
104
|
if (typeof pw !== "string") throw _err("pkcs12/bad-input", _MISSING_PASSWORD);
|
|
223
|
-
var out = Buffer.alloc(pw.length * 2 + 2);
|
|
105
|
+
var out = Buffer.alloc(pw.length * 2 + 2);
|
|
224
106
|
for (var i = 0; i < pw.length; i++) {
|
|
225
107
|
var u = pw.charCodeAt(i);
|
|
226
108
|
if (u >= 0xD800 && u <= 0xDFFF) throw _err("pkcs12/bad-input", "the password contains a non-BMP character (a surrogate code point cannot be BMPString-encoded)");
|
|
227
109
|
out[i * 2] = (u >> 8) & 0xFF;
|
|
228
110
|
out[i * 2 + 1] = u & 0xFF;
|
|
229
111
|
}
|
|
230
|
-
return out;
|
|
112
|
+
return out;
|
|
231
113
|
}
|
|
232
114
|
|
|
233
|
-
// The password bytes for the RFC 8018 PBES2 bag ciphers and the RFC 9579 PBMAC1 MAC. Although RFC 9579 sec. 6
|
|
234
|
-
// specifies the BMPString encoding here too, OpenSSL and NSS (and thus the interoperable ecosystem) feed
|
|
235
|
-
// PBKDF2 the raw UTF-8 password for these modern schemes (confirmed byte-for-byte against `openssl pkcs12`),
|
|
236
|
-
// reserving the BMPString+NULL form for the bespoke Appendix B KDF only. A file we emit must open in OpenSSL,
|
|
237
|
-
// so the modern schemes use UTF-8 here; only the classic Appendix B MAC uses `_p12Password`.
|
|
238
|
-
// The UTF-8 encoding, reporting ownership: the sibling of _p12PasswordOwned, and it exists
|
|
239
|
-
// for the same reason. A caller-supplied Buffer is borrowed and left alone; every other input is
|
|
240
|
-
// re-encoded into a buffer this module allocated, and a plaintext password copy this module made
|
|
241
|
-
// is cleared once the derivation has consumed it. Without it the App. B.1 copy taken from the same
|
|
242
|
-
// argument in the same call was wiped while this one was not.
|
|
243
115
|
function _pbePasswordOwned(pw) {
|
|
244
116
|
if (Buffer.isBuffer(pw)) return { bytes: guard.bytes.view(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: false };
|
|
245
117
|
if (pw instanceof Uint8Array) return { bytes: guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: true };
|
|
@@ -250,8 +122,6 @@ function _wipePw(owned) {
|
|
|
250
122
|
if (owned.owned) guard.secret.zeroize(owned.bytes, Pkcs12Error, "pkcs12/bad-input", "the password encoding");
|
|
251
123
|
}
|
|
252
124
|
|
|
253
|
-
// Concatenate copies of `src` to the smallest positive multiple of `blockSize` (>= src length), truncating
|
|
254
|
-
// the final copy. An empty source yields an empty buffer (RFC 7292 App. B.2 steps 2/3).
|
|
255
125
|
function _blockFill(src, blockSize) {
|
|
256
126
|
if (src.length === 0) return Buffer.alloc(0);
|
|
257
127
|
var total = blockSize * Math.ceil(src.length / blockSize);
|
|
@@ -260,58 +130,36 @@ function _blockFill(src, blockSize) {
|
|
|
260
130
|
return out;
|
|
261
131
|
}
|
|
262
132
|
|
|
263
|
-
// RFC 7292 Appendix B.2 KDF: derive `nBytes` of key material for purpose `id` (1 = cipher key, 2 = IV,
|
|
264
|
-
// 3 = MAC) from the App. B.1 password bytes + salt over `iterations` rounds of `hashName`. This is not PBKDF2. The
|
|
265
|
-
// block count c = ceil(nBytes/u): the MAC always requests exactly the hash output u (c = 1, one diversified,
|
|
266
|
-
// salted, iterated hash), while a legacy-PBE cipher key wider than u (e.g. 24-byte 3-key-3DES from SHA-1's
|
|
267
|
-
// 20-byte output, c = 2) exercises the multi-block feedback (step 6C: I = (I + B + 1) mod 2^(8v) between
|
|
268
|
-
// blocks). The single-block path is byte-identical to before (proven bidirectionally against OpenSSL); the
|
|
269
|
-
// multi-block path is proven against an `openssl pkcs12 -legacy` store (its own known-answer test).
|
|
270
133
|
function _p12Kdf(hashName, id, pwBytes, salt, iterations, nBytes) {
|
|
271
134
|
var uv = P12_KDF_UV[hashName], u = uv.u, v = uv.v;
|
|
272
|
-
var D = Buffer.alloc(v, id);
|
|
273
|
-
// Each fill is a full block-repeated copy of the salt / password; only their concatenation is
|
|
274
|
-
// used, so the sources are cleared and never abandoned.
|
|
135
|
+
var D = Buffer.alloc(v, id);
|
|
275
136
|
var sFill = _blockFill(salt, v), pFill = _blockFill(pwBytes, v);
|
|
276
|
-
var I = Buffer.concat([sFill, pFill]);
|
|
137
|
+
var I = Buffer.concat([sFill, pFill]);
|
|
277
138
|
guard.secret.zeroizeAll([sFill, pFill], Pkcs12Error, "pkcs12/bad-input", "a key-derivation fill");
|
|
278
|
-
var c = Math.ceil(nBytes / u);
|
|
279
|
-
// The accumulator is allocated ONCE at its final size. Growing it with Buffer.concat per round
|
|
280
|
-
// would abandon a password-derived copy of everything derived so far on every iteration, and
|
|
281
|
-
// those copies could not be reached to clear. Each intermediate digest is cleared as it is
|
|
282
|
-
// superseded, so only the value actually in use is ever readable.
|
|
139
|
+
var c = Math.ceil(nBytes / u);
|
|
283
140
|
var out = Buffer.alloc(c * u);
|
|
284
|
-
for (var i = 0; i < c; i++) {
|
|
141
|
+
for (var i = 0; i < c; i++) {
|
|
285
142
|
var A = Buffer.concat([D, I]);
|
|
286
|
-
for (var r = 0; r < iterations; r++) {
|
|
143
|
+
for (var r = 0; r < iterations; r++) {
|
|
287
144
|
var prev = A;
|
|
288
145
|
A = nodeCrypto.createHash(hashName).update(A).digest();
|
|
289
146
|
guard.secret.zeroize(prev, Pkcs12Error, "pkcs12/bad-input", "a key-derivation intermediate");
|
|
290
147
|
}
|
|
291
|
-
A.copy(out, i * u);
|
|
292
|
-
if (i < c - 1) {
|
|
293
|
-
var B = _blockFill(A, v);
|
|
148
|
+
A.copy(out, i * u);
|
|
149
|
+
if (i < c - 1) {
|
|
150
|
+
var B = _blockFill(A, v);
|
|
294
151
|
_kdfStepC(I, B, v);
|
|
295
152
|
guard.secret.zeroize(B, Pkcs12Error, "pkcs12/bad-input", "a key-derivation intermediate");
|
|
296
153
|
}
|
|
297
154
|
guard.secret.zeroize(A, Pkcs12Error, "pkcs12/bad-input", "a key-derivation block");
|
|
298
155
|
}
|
|
299
|
-
// I is S || P, so it carries the password bytes, and is finished with here.
|
|
300
156
|
guard.secret.zeroize(I, Pkcs12Error, "pkcs12/bad-input", "the key-derivation input block");
|
|
301
|
-
// Step 8 is "the first nBytes of A", but returning a SUBARRAY would hand back a view over a
|
|
302
|
-
// larger allocation: a caller clearing what it received would leave the rest of the final digest
|
|
303
|
-
// block -- password-derived material -- readable in the same backing buffer (15 unused bytes
|
|
304
|
-
// behind a 5-byte RC2 key, 16 behind a 24-byte 3DES key). Copy into an exact-sized buffer the
|
|
305
|
-
// caller wholly owns, and clear the oversized accumulator here, where it was allocated.
|
|
306
157
|
var exact = Buffer.alloc(nBytes);
|
|
307
158
|
out.copy(exact, 0, 0, nBytes);
|
|
308
159
|
guard.secret.zeroize(out, Pkcs12Error, "pkcs12/bad-input", "the key-derivation accumulator");
|
|
309
160
|
return exact;
|
|
310
161
|
}
|
|
311
162
|
|
|
312
|
-
// RFC 7292 App. B.2 step 6C: treat I as consecutive v-byte blocks and set each I_j = (I_j + B + 1) mod 2^(8v)
|
|
313
|
-
// via a big-endian byte-carry add (the +1 is the initial carry; carry out of the top byte is discarded, the
|
|
314
|
-
// mod). In place on I.
|
|
315
163
|
function _kdfStepC(I, B, v) {
|
|
316
164
|
for (var j = 0; j < I.length; j += v) {
|
|
317
165
|
var carry = 1;
|
|
@@ -323,7 +171,6 @@ function _kdfStepC(I, B, v) {
|
|
|
323
171
|
}
|
|
324
172
|
}
|
|
325
173
|
|
|
326
|
-
// ---- input coercion --------------------------------------------------------
|
|
327
174
|
|
|
328
175
|
function _coerceDer(input, what) {
|
|
329
176
|
return pkix.coerceToDer(input, { pemLabel: null, PemError: PemError, ErrorClass: Pkcs12Error, prefix: "pkcs12" });
|
|
@@ -338,10 +185,7 @@ function _assertMacIter(n, cap) {
|
|
|
338
185
|
return n;
|
|
339
186
|
}
|
|
340
187
|
|
|
341
|
-
// ---- bag builders ----------------------------------------------------------
|
|
342
188
|
|
|
343
|
-
// PKCS12Attribute SET OF: friendlyName (a single BMPString) + localKeyId (a single OCTET STRING). Each
|
|
344
|
-
// attribute's attrValues is a single-value SET OF (RFC 2985 SINGLE VALUE).
|
|
345
189
|
function _bagAttributes(bag) {
|
|
346
190
|
var attrs = [];
|
|
347
191
|
if (bag.friendlyName != null) attrs.push(b.sequence([b.oid(O("friendlyName")), b.set([b.bmpString(String(bag.friendlyName))])]));
|
|
@@ -352,14 +196,11 @@ function _bagAttributes(bag) {
|
|
|
352
196
|
return attrs.length ? b.set(attrs) : null;
|
|
353
197
|
}
|
|
354
198
|
|
|
355
|
-
// A secretBag secretTypeId is a caller-chosen content type: a registered OID name, or an arbitrary
|
|
356
|
-
// dotted-decimal OID string preserved verbatim (a name -> its OID; an already-dotted OID passes through).
|
|
357
199
|
function _secretTypeOid(id) {
|
|
358
|
-
try { return b.oid(O(id) || id); }
|
|
200
|
+
try { return b.oid(O(id) || id); }
|
|
359
201
|
catch (e) { throw _err("pkcs12/bad-input", "secretTypeId must be a registered name or a dotted-decimal OID string", e); }
|
|
360
202
|
}
|
|
361
203
|
|
|
362
|
-
// A SafeBag ::= SEQUENCE { bagId, bagValue [0] EXPLICIT DEFINED BY bagId, bagAttributes SET OF OPTIONAL }.
|
|
363
204
|
function _safeBag(bagName, bagValueDer, bag) {
|
|
364
205
|
var children = [b.oid(O(bagName)), b.explicit(0, bagValueDer)];
|
|
365
206
|
var attrs = _bagAttributes(bag);
|
|
@@ -369,8 +210,6 @@ function _safeBag(bagName, bagValueDer, bag) {
|
|
|
369
210
|
|
|
370
211
|
function _buildBag(bag, opts, depth) {
|
|
371
212
|
if (!bag || typeof bag !== "object") throw _err("pkcs12/bad-input", "each bag must be an object with a type");
|
|
372
|
-
// Checked against THIS type's table. An unrecognized type falls through to the switch below,
|
|
373
|
-
// which is the one place that names the permitted set.
|
|
374
213
|
if (intrinsic.hasOwn(_BAG_KEYS_BY_TYPE, bag.type)) {
|
|
375
214
|
guard.identifier.assertKnownKeys(bag, _BAG_KEYS_BY_TYPE[bag.type], _err, "pkcs12/bad-input", function (k) {
|
|
376
215
|
return "unknown field " + JSON.stringify(k) + " on a " + JSON.stringify(bag.type) +
|
|
@@ -387,14 +226,12 @@ function _buildBag(bag, opts, depth) {
|
|
|
387
226
|
var kDer = _coerceDer(bag.key, "shroudedKey key");
|
|
388
227
|
try { pkcs8.parse(kDer); } catch (e2) { throw _err("pkcs12/bad-input", "shroudedKey key is not a well-formed PKCS#8 PrivateKeyInfo", e2); }
|
|
389
228
|
var enc = bag.encrypt || {};
|
|
390
|
-
// The descriptor is checked BEFORE its password is read, so no field of an object carrying an
|
|
391
|
-
// unrecognized name reaches the KDF -- the same order the safe-level path uses below.
|
|
392
229
|
var bagPbe = _pbeOpts(enc);
|
|
393
230
|
var pw = _pbePasswordOwned(enc.password != null ? enc.password : opts.password);
|
|
394
231
|
var r;
|
|
395
232
|
try { r = pbes2.pbes2Encrypt(pw.bytes, kDer, bagPbe, _err, "pkcs12"); }
|
|
396
233
|
finally { _wipePw(pw); }
|
|
397
|
-
return _safeBag("pkcs8ShroudedKeyBag", b.sequence([r.algId, b.octetString(r.ct)]), bag);
|
|
234
|
+
return _safeBag("pkcs8ShroudedKeyBag", b.sequence([r.algId, b.octetString(r.ct)]), bag);
|
|
398
235
|
}
|
|
399
236
|
case "cert": {
|
|
400
237
|
var certDer = _coerceDer(bag.cert, "certBag cert");
|
|
@@ -420,17 +257,6 @@ function _buildBag(bag, opts, depth) {
|
|
|
420
257
|
}
|
|
421
258
|
}
|
|
422
259
|
|
|
423
|
-
// A pre-encoded DER value (one well-formed TLV, no trailing bytes) supplied verbatim (secretValue).
|
|
424
|
-
// The store, always parsed from the BYTES the caller handed over.
|
|
425
|
-
//
|
|
426
|
-
// Both verbs that use this decide integrity, and integrity is the one operation that must not read
|
|
427
|
-
// its inputs from two independently-chosen properties. Accepting a claimed-parsed store let
|
|
428
|
-
// `macedBytes` -- the range the MAC is verified over -- and `safeBags` / `encryptedSafes` -- the
|
|
429
|
-
// content returned as verified -- come from different sources: verify store A's MAC, hand back store
|
|
430
|
-
// B's bags. Parsing here binds them, because both are then derived from one byte string.
|
|
431
|
-
//
|
|
432
|
-
// `pki.schema.pkcs12.parse` remains the parse-only route for a caller who wants the structure
|
|
433
|
-
// without an integrity decision.
|
|
434
260
|
var _STORE_CLAIM = ["integrityMode", "mac", "macedBytes"];
|
|
435
261
|
function _storeFromBytes(pfx) {
|
|
436
262
|
return guard.parsed.fromTrustedSource(pfx, "pkcs12Store", _STORE_CLAIM, function (bytes) {
|
|
@@ -463,35 +289,17 @@ function _pbeOpts(enc) {
|
|
|
463
289
|
};
|
|
464
290
|
}
|
|
465
291
|
|
|
466
|
-
// SafeContents ::= SEQUENCE OF SafeBag (ordered, and not a SET OF).
|
|
467
292
|
function _buildSafeContents(bags, opts, depth) {
|
|
468
|
-
// The element cap is enforced BEFORE the density scan, so a huge dense array is rejected in O(1) rather
|
|
469
|
-
// than walked. reqDenseArray then rejects a non-array, or a sparse hole / nullish entry that
|
|
470
|
-
// Array.prototype.map would skip into Buffer.concat as a native error, testing own-property presence
|
|
471
|
-
// and failing at the first bad index.
|
|
472
293
|
if (intrinsic.isArray(bags) && bags.length > C.LIMITS.PKCS12_MAX_ELEMENTS) throw _err("pkcs12/bad-input", "a SafeContents exceeds the element cap " + C.LIMITS.PKCS12_MAX_ELEMENTS);
|
|
473
294
|
var dense = pkiBuild.reqDenseArray(bags, "bags", _err, "pkcs12/bad-input");
|
|
474
295
|
return b.sequence(dense.map(function (bag) { return _buildBag(bag, opts, depth); }));
|
|
475
296
|
}
|
|
476
297
|
|
|
477
|
-
// One AuthenticatedSafe element: a plaintext id-data ContentInfo, an id-encryptedData ContentInfo whose
|
|
478
|
-
// EncryptedData is the PBES2 encryption of the SafeContents (RFC 7292 sec. 5.1 step 2A/2B), or, for
|
|
479
|
-
// public-key privacy (sec. 3.1), an id-envelopedData ContentInfo whose CMS EnvelopedData encrypts the
|
|
480
|
-
// SafeContents to one or more recipient public keys. Async because the envelope path awaits pki.cms.encrypt.
|
|
481
298
|
async function _buildAuthSafeElement(sc, opts) {
|
|
482
299
|
if (!sc || typeof sc !== "object") throw _err("pkcs12/bad-input", "each safeContents entry must be an object");
|
|
483
|
-
// A privacy directive is honored whenever its field is present (even if falsy). A present-but-falsy `recipients`
|
|
484
|
-
// or `encrypt` (null / false / "") is a configuration error that intended privacy, so it must fail closed and never
|
|
485
|
-
// silently fall through to a plaintext id-data safe that would emit the bags in cleartext (privacy downgraded to
|
|
486
|
-
// none while still producing a valid MAC-protected PFX). Omit the field entirely for a genuinely plaintext safe.
|
|
487
300
|
var hasRecipients = sc.recipients !== undefined;
|
|
488
301
|
var hasEncrypt = sc.encrypt !== undefined;
|
|
489
302
|
if (hasEncrypt && hasRecipients) throw _err("pkcs12/bad-input", "a safeContents cannot combine encrypt (password) and recipients (public-key) -- one ContentInfo is one privacy type (RFC 7292 sec. 4.1)");
|
|
490
|
-
// Checked against the branch the directive above selects, and BEFORE that branch reads anything.
|
|
491
|
-
// The branches read different fields: `contentEncryptionAlgorithm` is the public-key branch's
|
|
492
|
-
// cipher and is read by nothing on a password safe, whose cipher comes from `encrypt.cipher` --
|
|
493
|
-
// so a union accepted an explicit cipher request on a password safe and encrypted under the
|
|
494
|
-
// default anyway.
|
|
495
303
|
guard.identifier.assertKnownKeys(sc, hasRecipients ? _SAFE_RECIPIENTS_KEYS : (hasEncrypt ? _SAFE_PASSWORD_KEYS : _SAFE_PLAINTEXT_KEYS),
|
|
496
304
|
_err, "pkcs12/bad-input", function (k) {
|
|
497
305
|
return "unknown safeContents field " + JSON.stringify(k) + " for a " +
|
|
@@ -502,41 +310,29 @@ async function _buildAuthSafeElement(sc, opts) {
|
|
|
502
310
|
var safeContentsDer = _buildSafeContents(sc.bags || [], opts, 0);
|
|
503
311
|
if (hasRecipients) {
|
|
504
312
|
if (!Array.isArray(sc.recipients) || !sc.recipients.length) throw _err("pkcs12/bad-input", "safeContents.recipients must be a non-empty array of recipient descriptors (RFC 5652 sec. 6.1)");
|
|
505
|
-
// Public-key privacy admits only certificate recipients (ktri / kari / kemri: RSA / ECDH / X25519 / X448 /
|
|
506
|
-
// ML-KEM, dispatched off the cert key). A password (pwri) or KEK (kekri) recipient is not public-key privacy
|
|
507
|
-
// and pki.pkcs12.open (which forwards only { recipientKey, recipientCert } to cms.decrypt) cannot reopen it,
|
|
508
|
-
// so reject it here and never emit a store the toolkit's own reader cannot process. (Re-open: pwri/kekri
|
|
509
|
-
// privacy when open forwards the full cms.decrypt key-material model.)
|
|
510
313
|
sc.recipients.forEach(function (r) {
|
|
511
314
|
if (!r || typeof r !== "object" || r.cert == null) throw _err("pkcs12/bad-input", "a public-key privacy recipient must be a certificate recipient { cert } (a password or KEK recipient is not public-key privacy and cannot be reopened by pkcs12.open)");
|
|
512
315
|
});
|
|
513
316
|
var alg = sc.contentEncryptionAlgorithm || "aes-256-cbc";
|
|
514
|
-
// RFC 7292 sec. 4.1 lists only id-envelopedData for a public-key privacy safe (never id-ct-authEnvelopedData);
|
|
515
|
-
// cms.encrypt DEFAULTS to aes-256-gcm (AEAD -> AuthEnvelopedData), so force a CBC content cipher and reject GCM.
|
|
516
317
|
if (alg !== "aes-128-cbc" && alg !== "aes-192-cbc" && alg !== "aes-256-cbc") throw _err("pkcs12/bad-input", "a public-key privacy safe requires an AES-CBC content cipher (aes-128|192|256-cbc), not " + JSON.stringify(alg) + " -- RFC 7292 sec. 4.1 admits only id-envelopedData, not GCM/AuthEnvelopedData");
|
|
517
318
|
var envCi = await cms.encrypt(safeContentsDer, sc.recipients, { contentEncryptionAlgorithm: alg, contentType: "data" });
|
|
518
|
-
return b.raw(envCi);
|
|
319
|
+
return b.raw(envCi);
|
|
519
320
|
}
|
|
520
321
|
if (!hasEncrypt) {
|
|
521
322
|
return b.sequence([b.oid(O("data")), b.explicit(0, b.octetString(safeContentsDer))]);
|
|
522
323
|
}
|
|
523
324
|
if (!sc.encrypt || typeof sc.encrypt !== "object") throw _err("pkcs12/bad-input", "safeContents.encrypt must be an object { password? } (RFC 7292 sec. 5.1) -- omit it entirely for a plaintext safe");
|
|
524
|
-
// Checked before the password is read, so no field of a descriptor carrying an unrecognized name
|
|
525
|
-
// reaches the KDF.
|
|
526
325
|
var safePbe = _pbeOpts(sc.encrypt);
|
|
527
326
|
var pw = _pbePasswordOwned(sc.encrypt.password != null ? sc.encrypt.password : opts.password);
|
|
528
327
|
var r;
|
|
529
328
|
try { r = pbes2.pbes2Encrypt(pw.bytes, safeContentsDer, safePbe, _err, "pkcs12"); }
|
|
530
329
|
finally { _wipePw(pw); }
|
|
531
|
-
var eci = b.sequence([b.oid(O("data")), r.algId, b.contextPrimitive(0, r.ct)]);
|
|
532
|
-
var encData = b.sequence([b.integer(0n), eci]);
|
|
330
|
+
var eci = b.sequence([b.oid(O("data")), r.algId, b.contextPrimitive(0, r.ct)]);
|
|
331
|
+
var encData = b.sequence([b.integer(0n), eci]);
|
|
533
332
|
return b.sequence([b.oid(O("encryptedData")), b.explicit(0, encData)]);
|
|
534
333
|
}
|
|
535
334
|
|
|
536
|
-
// ---- MacData ---------------------------------------------------------------
|
|
537
335
|
|
|
538
|
-
// MacData over the AuthenticatedSafe DER (= the parser's macedBytes). Classic App. B HMAC (default, max
|
|
539
|
-
// interop) or RFC 9579 PBMAC1. Returns the MacData DER.
|
|
540
336
|
async function _buildMacData(macOpts, sharedPassword, authSafeDer) {
|
|
541
337
|
var password = macOpts.password != null ? macOpts.password : sharedPassword;
|
|
542
338
|
var algorithm = macOpts.algorithm || "hmac";
|
|
@@ -549,10 +345,8 @@ async function _buildMacData(macOpts, sharedPassword, authSafeDer) {
|
|
|
549
345
|
var node = DIGEST_NAME[hash];
|
|
550
346
|
if (!node || !P12_KDF_UV[node]) throw _err("pkcs12/unsupported-algorithm", "unsupported classic MAC hash " + JSON.stringify(hash) + " (sha1 / sha256 / sha384 / sha512)");
|
|
551
347
|
var iter = _assertMacIter(macOpts.iterations == null ? DEFAULT_MAC_ITER : macOpts.iterations, CLASSIC_MAC_MAX_ITERATIONS);
|
|
552
|
-
// _p12Password always ALLOCATES (it re-encodes to BMPString + NULL), so its result is this
|
|
553
|
-
// module's own credential copy and is cleared once the derivation has consumed it.
|
|
554
348
|
var macPw = _p12PasswordOwned(password);
|
|
555
|
-
var macKey = _p12Kdf(node, 3, macPw.bytes, salt, iter, P12_KDF_UV[node].u);
|
|
349
|
+
var macKey = _p12Kdf(node, 3, macPw.bytes, salt, iter, P12_KDF_UV[node].u);
|
|
556
350
|
if (macPw.owned) guard.secret.zeroize(macPw.bytes, Pkcs12Error, "pkcs12/bad-input", "the password encoding");
|
|
557
351
|
try {
|
|
558
352
|
var digest = nodeCrypto.createHmac(node, macKey).update(authSafeDer).digest();
|
|
@@ -565,33 +359,21 @@ async function _buildMacData(macOpts, sharedPassword, authSafeDer) {
|
|
|
565
359
|
if (algorithm === "pbmac1") {
|
|
566
360
|
var prf = PBMAC1_PRF[hash];
|
|
567
361
|
if (!prf) throw _err("pkcs12/unsupported-algorithm", "PBMAC1 requires a SHA-256/384/512 digest (RFC 9579 sec. 5/7 forbids a <= 160-bit digest, e.g. SHA-1)");
|
|
568
|
-
// Native PBKDF2 (~0.2us/iter) tolerates the toolkit-wide PBKDF2 ceiling where the classic JS-loop KDF
|
|
569
|
-
// caps ~10x lower; both target a comparable ~1-2s wall clock (see CLASSIC_MAC_MAX_ITERATIONS).
|
|
570
362
|
var iter2 = _assertMacIter(macOpts.iterations == null ? DEFAULT_PBMAC1_ITER : macOpts.iterations, C.LIMITS.PBKDF2_MAX_ITERATIONS);
|
|
571
363
|
var keyLen = macOpts.keyLength != null ? macOpts.keyLength : prf.keyLen;
|
|
572
364
|
if (typeof keyLen !== "number" || !Number.isInteger(keyLen) || keyLen < 20 || keyLen > MAX_PBMAC1_KEYLEN) throw _err("pkcs12/bad-input", "PBMAC1 keyLength must be an integer in [20, " + MAX_PBMAC1_KEYLEN + "] (RFC 9579 sec. 9)");
|
|
573
365
|
var macPw2 = _pbePasswordOwned(password);
|
|
574
366
|
var mac;
|
|
575
|
-
try { mac = await pbes2.pbmac1(macPw2.bytes, salt, iter2, keyLen, prf.wc, prf.wc, authSafeDer); }
|
|
367
|
+
try { mac = await pbes2.pbmac1(macPw2.bytes, salt, iter2, keyLen, prf.wc, prf.wc, authSafeDer); }
|
|
576
368
|
finally { _wipePw(macPw2); }
|
|
577
369
|
var desc = { salt: salt, iterationCount: iter2, keyLength: keyLen, prfName: prf.prfName, macName: prf.prfName };
|
|
578
370
|
var digestInfo2 = b.sequence([pbes2.pbmac1AlgId(desc), b.octetString(mac)]);
|
|
579
|
-
// MacData.macSalt + iterations are ignored on a PBMAC1 verify but MUST be present + non-1 (RFC 9579 4c/4d).
|
|
580
371
|
return b.sequence([digestInfo2, b.octetString(salt), b.integer(BigInt(iter2))]);
|
|
581
372
|
}
|
|
582
373
|
throw _err("pkcs12/bad-input", "opts.mac.algorithm must be 'hmac' or 'pbmac1', got " + JSON.stringify(algorithm));
|
|
583
374
|
}
|
|
584
375
|
|
|
585
|
-
// ---- spec normalization ----------------------------------------------------
|
|
586
376
|
|
|
587
|
-
// Accept the OpenSSL-style convenience form ({ key, cert, ca, friendlyName, localKeyId }) or the full
|
|
588
|
-
// inverse-of-parse form ({ safeContents: SafeContentsSpec[] }). The convenience form -> one privacy safe of
|
|
589
|
-
// PBES2 cert bags + one shrouded-key safe.
|
|
590
|
-
// Which of the two forms a spec is gets decided ONCE, and that one decision is what both the door
|
|
591
|
-
// and this normalizer act on. Decided separately they can disagree: two spellings of "is this the
|
|
592
|
-
// full form" drift apart on their own, and a caller getter returning an array to one and something
|
|
593
|
-
// else to the other makes them disagree on demand -- the full form checked while the shorthand form
|
|
594
|
-
// is the one assembled.
|
|
595
377
|
function _specForm(spec) {
|
|
596
378
|
var isObject = !!spec && typeof spec === "object" && !Buffer.isBuffer(spec);
|
|
597
379
|
var safeContents = isObject ? spec.safeContents : null;
|
|
@@ -602,8 +384,6 @@ function _specForm(spec) {
|
|
|
602
384
|
function _normalizeSpec(spec, opts, form) {
|
|
603
385
|
if (form.full) return form.safeContents;
|
|
604
386
|
if (spec && (spec.key != null || spec.cert != null)) {
|
|
605
|
-
// One read of the caller's array, so the emptiness check and the recipients built from it cannot
|
|
606
|
-
// be handed different values.
|
|
607
387
|
var recipientCerts = opts.recipientCerts;
|
|
608
388
|
var enveloped = recipientCerts != null;
|
|
609
389
|
if (enveloped && (!intrinsic.isArray(recipientCerts) || !recipientCerts.length)) throw _err("pkcs12/bad-input", "opts.recipientCerts must be a non-empty array of recipient certificates");
|
|
@@ -613,9 +393,6 @@ function _normalizeSpec(spec, opts, form) {
|
|
|
613
393
|
(spec.ca || []).forEach(function (ca) { certBags.push({ type: "cert", cert: ca }); });
|
|
614
394
|
var sc = [];
|
|
615
395
|
if (enveloped) {
|
|
616
|
-
// Public-key-privacy convenience: the cert bags plus a plaintext keyBag in one recipient-enveloped safe (the
|
|
617
|
-
// envelope supplies confidentiality; there is no recipient-encrypted key-bag type, RFC 7292 sec. 4.2). The
|
|
618
|
-
// caller password, if any, remains the MAC (integrity) password; privacy and integrity are independent.
|
|
619
396
|
var bags = certBags.slice();
|
|
620
397
|
if (spec.key != null) bags.push({ type: "key", key: spec.key, friendlyName: spec.friendlyName, localKeyId: spec.localKeyId });
|
|
621
398
|
sc.push({ recipients: recipientCerts.map(function (c) { return { cert: c }; }), bags: bags });
|
|
@@ -628,7 +405,6 @@ function _normalizeSpec(spec, opts, form) {
|
|
|
628
405
|
throw _err("pkcs12/bad-input", "spec must be { safeContents: [...] } or { key, cert, ca? }");
|
|
629
406
|
}
|
|
630
407
|
|
|
631
|
-
// ---- public API ------------------------------------------------------------
|
|
632
408
|
|
|
633
409
|
/**
|
|
634
410
|
* @primitive pki.pkcs12.build
|
|
@@ -665,13 +441,6 @@ function _normalizeSpec(spec, opts, form) {
|
|
|
665
441
|
* { password: 'changeit', mac: { algorithm: 'hmac', hash: 'sha256' } });
|
|
666
442
|
*/
|
|
667
443
|
function build(spec, opts) {
|
|
668
|
-
// Both arguments copied at entry and released when the call settles -- see the note on the same
|
|
669
|
-
// call in x509-sign. A PKCS#12 file is assembled over many promise turns (a key derivation per
|
|
670
|
-
// bag, then the outer MAC) while the caller still owns the object holding the passwords and the
|
|
671
|
-
// bag contents. Rewriting a password buffer partway through produced a file whose MAC and whose
|
|
672
|
-
// bag encryption were keyed to two different values, so it opened with neither the password
|
|
673
|
-
// passed in nor the one written over it. The release is what keeps the copy of that password
|
|
674
|
-
// from outliving the call.
|
|
675
444
|
return guard.bytes.fixedCall(Pkcs12Error, "pkcs12/bad-input", [
|
|
676
445
|
[spec, "the PKCS#12 spec"], [opts, "pki.pkcs12.build options"],
|
|
677
446
|
], _build);
|
|
@@ -679,9 +448,6 @@ function build(spec, opts) {
|
|
|
679
448
|
|
|
680
449
|
async function _build(spec, opts) {
|
|
681
450
|
opts = opts || {};
|
|
682
|
-
// Before the spec is normalized, so a misspelled bag field is a refusal rather than a store built
|
|
683
|
-
// without it. A PKCS#12 file that silently omits the certificate or the key a caller asked for
|
|
684
|
-
// looks well-formed and opens cleanly; the omission surfaces wherever it is later imported.
|
|
685
451
|
var form = _specForm(spec);
|
|
686
452
|
if (form.isObject) {
|
|
687
453
|
guard.identifier.assertKnownKeys(spec, form.full ? _BUILD_SPEC_FULL_KEYS : _BUILD_SPEC_SHORTHAND_KEYS,
|
|
@@ -691,7 +457,6 @@ async function _build(spec, opts) {
|
|
|
691
457
|
: "shorthand form; pass safeContents instead to build the safes yourself");
|
|
692
458
|
});
|
|
693
459
|
}
|
|
694
|
-
// The options answer to the same form: recipientCerts is read only by the shorthand branch.
|
|
695
460
|
guard.identifier.assertKnownKeys(opts, form.full ? _BUILD_OPTS_FULL : _BUILD_OPTS_SHORTHAND, _err, "pkcs12/bad-input",
|
|
696
461
|
function (k) {
|
|
697
462
|
return "pki.pkcs12.build has an unknown option " + JSON.stringify(k) +
|
|
@@ -699,10 +464,6 @@ async function _build(spec, opts) {
|
|
|
699
464
|
? " for the safeContents form; the full form carries privacy per entry, as safeContents[i].recipients"
|
|
700
465
|
: "");
|
|
701
466
|
});
|
|
702
|
-
// The integrity mode is checked against the permitted set rather than compared to one literal.
|
|
703
|
-
// Compared, any other spelling reads as "not public-key" and silently selects password integrity,
|
|
704
|
-
// dropping the signer with it: a caller who wrote "publicKey" gets a MAC where they asked for a
|
|
705
|
-
// signature, and nothing in the store or the call says which they got.
|
|
706
467
|
if (opts.integrity != null) {
|
|
707
468
|
if (typeof opts.integrity !== "object" || Array.isArray(opts.integrity)) throw _err("pkcs12/bad-input", "opts.integrity must be an object { mode, signer|signers, ... }");
|
|
708
469
|
guard.identifier.assertKnownKeys(opts.integrity, _INTEGRITY_OPTS, _err, "pkcs12/bad-input", "opts.integrity has an unknown option ");
|
|
@@ -711,32 +472,19 @@ async function _build(spec, opts) {
|
|
|
711
472
|
}
|
|
712
473
|
}
|
|
713
474
|
var pubKey = opts.integrity != null;
|
|
714
|
-
// RFC 7292 sec. 4: public-key integrity OMITS MacData entirely -- a caller combining opts.mac with it is a
|
|
715
|
-
// config-time reject (the self-check re-parse would otherwise fail the coherence rule anyway).
|
|
716
475
|
if (pubKey && opts.mac != null && opts.mac !== false) throw _err("pkcs12/bad-integrity-mode", "public-key integrity has no MacData -- do not combine opts.mac with opts.integrity.mode 'public-key' (RFC 7292 sec. 4)");
|
|
717
476
|
var safeContentsSpecs = _normalizeSpec(spec, opts, form);
|
|
718
477
|
if (!Array.isArray(safeContentsSpecs) || !safeContentsSpecs.length) throw _err("pkcs12/bad-input", "the store has no safe contents");
|
|
719
478
|
var elements = [];
|
|
720
479
|
for (var i = 0; i < safeContentsSpecs.length; i++) elements.push(await _buildAuthSafeElement(safeContentsSpecs[i], opts));
|
|
721
|
-
var authSafeDer = b.sequence(elements);
|
|
480
|
+
var authSafeDer = b.sequence(elements);
|
|
722
481
|
|
|
723
482
|
var pfx;
|
|
724
483
|
if (pubKey) {
|
|
725
|
-
// Public-key integrity (RFC 7292 sec. 4 / sec. 5.1 step 5A): the authSafe is a CMS SignedData whose
|
|
726
|
-
// id-data eContent IS the byte-exact AuthenticatedSafe DER; the signature (from a keypair, not a
|
|
727
|
-
// password) provides the integrity, and there is NO MacData. Compose pki.cms.sign -- so every cms.sign
|
|
728
|
-
// signer algorithm (RSA / ECDSA / EdDSA / ML-DSA / SLH-DSA / composite) ships for free.
|
|
729
484
|
var ig = opts.integrity;
|
|
730
|
-
// `signer` is the one-signer spelling of `signers`, and the list wins outright, so supplying
|
|
731
|
-
// both signed the store under the list alone and dropped the single signer without a word.
|
|
732
485
|
if (ig.signers != null && ig.signer != null) throw _err("pkcs12/bad-input", "supply exactly one of opts.integrity.signer or opts.integrity.signers, not both");
|
|
733
486
|
var signers = ig.signers != null ? ig.signers : (ig.signer != null ? [ig.signer] : null);
|
|
734
487
|
if (!Array.isArray(signers) || !signers.length) throw _err("pkcs12/bad-input", "public-key integrity requires opts.integrity.signer or opts.integrity.signers (a cms.sign signer descriptor)");
|
|
735
|
-
// The signer descriptors are authoring input for this store, so they answer to the same rule as
|
|
736
|
-
// every other field the caller writes here, against the form each one selects. Every field
|
|
737
|
-
// beyond the identity has a default: without the door a misspelled `pss` or `digestAlgorithm`
|
|
738
|
-
// signed the store under PKCS#1 with SHA-256 while the caller had asked for something else, and
|
|
739
|
-
// the store records only what was used, never what was requested.
|
|
740
488
|
for (var sx = 0; sx < signers.length; sx++) {
|
|
741
489
|
var sd = signers[sx];
|
|
742
490
|
if (sd == null || typeof sd !== "object" || Buffer.isBuffer(sd)) throw _err("pkcs12/bad-input", "each opts.integrity signer must be a descriptor object");
|
|
@@ -749,17 +497,11 @@ async function _build(spec, opts) {
|
|
|
749
497
|
});
|
|
750
498
|
}
|
|
751
499
|
var authSafeCi = await cms.sign(authSafeDer, signers, { eContentType: "data", detached: false, certificates: ig.certificates !== false, sid: ig.sid, signingTime: ig.signingTime });
|
|
752
|
-
pfx = b.sequence([b.integer(3n), b.raw(authSafeCi)]);
|
|
500
|
+
pfx = b.sequence([b.integer(3n), b.raw(authSafeCi)]);
|
|
753
501
|
} else {
|
|
754
502
|
var pfxChildren = [b.integer(3n), b.sequence([b.oid(O("data")), b.explicit(0, b.octetString(authSafeDer))])];
|
|
755
|
-
// An array passes `typeof x === "object"`, and its own names are indices no table lists, so an
|
|
756
|
-
// array reached _buildMacData as an empty descriptor and produced the DEFAULT MAC. Excluded by
|
|
757
|
-
// the same test opts.integrity already applies, read through the load-time capture so a
|
|
758
|
-
// replaced Array.isArray cannot reopen it.
|
|
759
503
|
if (opts.mac != null && opts.mac !== false && (typeof opts.mac !== "object" || intrinsic.isArray(opts.mac))) throw _err("pkcs12/bad-input", "opts.mac must be false or a { algorithm, hash, salt, iterations, keyLength } object");
|
|
760
504
|
if (opts.mac != null && opts.mac !== false) {
|
|
761
|
-
// The algorithm selects the derivation, so the table that runs is the one belonging to it.
|
|
762
|
-
// An unrecognized spelling keeps the union; _buildMacData refuses it by name a moment later.
|
|
763
505
|
var macAlg = opts.mac.algorithm == null ? "hmac" : opts.mac.algorithm;
|
|
764
506
|
var macKeys = macAlg === "hmac" ? _MAC_KEYS_HMAC : macAlg === "pbmac1" ? _MAC_KEYS_PBMAC1 : _MAC_KEYS;
|
|
765
507
|
guard.identifier.assertKnownKeys(opts.mac, macKeys, _err, "pkcs12/bad-input", function (k) {
|
|
@@ -773,7 +515,6 @@ async function _build(spec, opts) {
|
|
|
773
515
|
pfx = b.sequence(pfxChildren);
|
|
774
516
|
}
|
|
775
517
|
|
|
776
|
-
// Self-check: the emitted store round-trips through the strict parser (structure + MAC/signedData coherence).
|
|
777
518
|
try { schemaPkcs12.parse(pfx); } catch (e) { throw _err("pkcs12/bad-input", "the produced PKCS#12 store did not re-parse (build bug)", e); }
|
|
778
519
|
return opts.pem ? schemaPkcs12.pemEncode(pfx, "PKCS12") : pfx;
|
|
779
520
|
}
|
|
@@ -812,9 +553,6 @@ async function verifyMac(pfx, password, opts) {
|
|
|
812
553
|
guard.identifier.assertKnownKeys(opts, _VERIFY_MAC_OPTS, _err, "pkcs12/bad-input", "pki.pkcs12.verifyMac has an unknown option ");
|
|
813
554
|
}
|
|
814
555
|
var m = _storeFromBytes(pfx);
|
|
815
|
-
// _verifyMacOfStore throws pkcs12/bad-input for a store carrying no password MAC, so m.mac is present
|
|
816
|
-
// once it returns. Surface the MAC algorithm the store used so a caller can reject an integrity
|
|
817
|
-
// algorithm it does not accept (e.g. a legacy SHA-1 classic MAC), instead of a bare boolean that hides it.
|
|
818
556
|
var valid = await _verifyMacOfStore(m, password, opts);
|
|
819
557
|
var isHmac = m.mac.kind === "hmac";
|
|
820
558
|
return {
|
|
@@ -825,9 +563,6 @@ async function verifyMac(pfx, password, opts) {
|
|
|
825
563
|
};
|
|
826
564
|
}
|
|
827
565
|
|
|
828
|
-
// The MAC computation over a store this module has ALREADY parsed from bytes. Separate from the
|
|
829
|
-
// public verb so `open` can verify the store it just parsed without going back through the door --
|
|
830
|
-
// the door's job is to establish that the store came from the caller's bytes, and by here it has.
|
|
831
566
|
async function _verifyMacOfStore(m, password, opts) {
|
|
832
567
|
opts = opts || {};
|
|
833
568
|
if (m.integrityMode !== "password" || !m.mac) throw _err("pkcs12/bad-input", "the store carries no password MAC (integrityMode " + m.integrityMode + ")");
|
|
@@ -836,34 +571,27 @@ async function _verifyMacOfStore(m, password, opts) {
|
|
|
836
571
|
if (m.mac.kind === "hmac") {
|
|
837
572
|
var node = _toLower(m.mac.hashName || "");
|
|
838
573
|
if (!P12_KDF_UV[node]) throw _err("pkcs12/unsupported-algorithm", "unsupported classic MAC hash " + m.mac.hashName);
|
|
839
|
-
// The iteration count and salt are attacker-controlled work factors: bound them BEFORE the KDF runs.
|
|
840
574
|
_capWork(m.mac.iterations, m.mac.macSalt, opts, undefined, CLASSIC_MAC_MAX_ITERATIONS);
|
|
841
575
|
var vMacPw = _p12PasswordOwned(password);
|
|
842
|
-
var macKey = _p12Kdf(node, 3, vMacPw.bytes, m.mac.macSalt, m.mac.iterations, P12_KDF_UV[node].u);
|
|
576
|
+
var macKey = _p12Kdf(node, 3, vMacPw.bytes, m.mac.macSalt, m.mac.iterations, P12_KDF_UV[node].u);
|
|
843
577
|
if (vMacPw.owned) guard.secret.zeroize(vMacPw.bytes, Pkcs12Error, "pkcs12/bad-input", "the password encoding");
|
|
844
578
|
try { computed = nodeCrypto.createHmac(node, macKey).update(m.macedBytes).digest(); }
|
|
845
579
|
finally { guard.secret.zeroize(macKey, Pkcs12Error, "pkcs12/bad-input", "the password-derived MAC key"); }
|
|
846
580
|
} else {
|
|
847
581
|
var kdf = m.mac.pbmac1.kdf;
|
|
848
582
|
var prfWc = PRF_WC[kdf.prfName];
|
|
849
|
-
// RFC 9579: HMAC under the messageAuthScheme, keyed by PBKDF2 under the (independent) prf.
|
|
850
583
|
var macWc = PRF_WC[m.mac.pbmac1.schemeName];
|
|
851
584
|
if (!prfWc) throw _err("pkcs12/unsupported-algorithm", "unsupported PBMAC1 prf " + kdf.prfName);
|
|
852
585
|
if (!macWc) throw _err("pkcs12/unsupported-algorithm", "unsupported PBMAC1 messageAuthScheme " + m.mac.pbmac1.schemeName);
|
|
853
|
-
// RFC 9579 sec. 5/7: a <= 160-bit digest (SHA-1) MUST NOT be used for PBMAC1 -- refuse it on verify, so a
|
|
854
|
-
// downgraded store cannot pass under a weak MAC even though the algorithm identifiers parse.
|
|
855
586
|
if (prfWc === "SHA-1" || macWc === "SHA-1") throw _err("pkcs12/unsupported-algorithm", "PBMAC1 with a <= 160-bit digest (SHA-1) is refused (RFC 9579 sec. 5/7)");
|
|
856
587
|
_capWork(kdf.iterationCount, kdf.salt, opts, kdf.keyLength, C.LIMITS.PBKDF2_MAX_ITERATIONS);
|
|
857
588
|
var vPw = _pbePasswordOwned(password);
|
|
858
|
-
try { computed = await pbes2.pbmac1(vPw.bytes, kdf.salt, kdf.iterationCount, kdf.keyLength, prfWc, macWc, m.macedBytes); }
|
|
589
|
+
try { computed = await pbes2.pbmac1(vPw.bytes, kdf.salt, kdf.iterationCount, kdf.keyLength, prfWc, macWc, m.macedBytes); }
|
|
859
590
|
finally { _wipePw(vPw); }
|
|
860
591
|
}
|
|
861
592
|
return computed.length === expected.length && guard.crypto.constantTimeEqual(computed, expected);
|
|
862
593
|
}
|
|
863
594
|
|
|
864
|
-
// Bound the attacker-controlled MAC work factors before a hostile store can force an expensive derivation:
|
|
865
|
-
// the iteration count (<= PBKDF2_MAX_ITERATIONS, downward-overridable via opts.maxIterations), the salt, and
|
|
866
|
-
// the PBMAC1 keyLength. A store that exceeds any cap is a typed reject, never a multi-second CPU burn.
|
|
867
595
|
function _capWork(iterations, salt, opts, keyLength, hardCap) {
|
|
868
596
|
var cap = hardCap;
|
|
869
597
|
if (opts.maxIterations != null) {
|
|
@@ -872,8 +600,6 @@ function _capWork(iterations, salt, opts, keyLength, hardCap) {
|
|
|
872
600
|
}
|
|
873
601
|
if (iterations > cap) throw _err("pkcs12/iteration-limit", "the MAC iteration count " + iterations + " exceeds the cap " + cap);
|
|
874
602
|
if (salt && salt.length > C.LIMITS.PBKDF2_MAX_SALT) throw _err("pkcs12/bad-input", "the MAC salt exceeds the " + C.LIMITS.PBKDF2_MAX_SALT + "-octet cap");
|
|
875
|
-
// RFC 9579 sec. 9: the PBMAC1 keyLength floor (>= 20) is enforced on verify too, so a downgraded store with
|
|
876
|
-
// a short derived key is refused rather than accepted under a weak MAC.
|
|
877
603
|
if (keyLength != null && (keyLength < 20 || keyLength > MAX_PBMAC1_KEYLEN)) throw _err("pkcs12/bad-input", "the PBMAC1 keyLength must be in [20, " + MAX_PBMAC1_KEYLEN + "] (RFC 9579 sec. 9)");
|
|
878
604
|
}
|
|
879
605
|
|
|
@@ -933,12 +659,6 @@ async function open(pfx, password, opts) {
|
|
|
933
659
|
var macVerified = false;
|
|
934
660
|
var signers = null;
|
|
935
661
|
if (m.integrityMode === "public-key") {
|
|
936
|
-
// RFC 7292 sec. 4 / sec. 5.1 step 5B -- INTEGRITY BEFORE USE: verify the CMS SignedData signature over
|
|
937
|
-
// the AuthenticatedSafe BEFORE decrypting or returning any bag (the signature is the integrity gate,
|
|
938
|
-
// exactly as the MAC is for password mode). cms.verify hashes the exact wire eContent the parser
|
|
939
|
-
// dispatched the bags from (m.authSafeSigned's attached content) -- no re-serialize. The signer is
|
|
940
|
-
// SURFACED as a per-signer verdict, NEVER trust-chained: the caller anchors signers[i].cert via
|
|
941
|
-
// pki.path.validate (the out-of-path signer contract). A cert-less store supplies opts.signerCerts.
|
|
942
662
|
var res = await cms.verify(m.authSafeSigned, { certs: opts.signerCerts });
|
|
943
663
|
if (!res.valid) throw _err("pkcs12/signature-invalid", "the PKCS#12 SignedData signature did not verify (an untrusted or tampered store)");
|
|
944
664
|
signers = res.signers;
|
|
@@ -948,16 +668,9 @@ async function open(pfx, password, opts) {
|
|
|
948
668
|
} else if (!opts.allowUnauthenticated) {
|
|
949
669
|
throw _err("pkcs12/no-integrity", "the store carries no integrity MAC (integrityMode " + m.integrityMode + "); set opts.allowUnauthenticated to open it anyway");
|
|
950
670
|
}
|
|
951
|
-
// The raw password is threaded to each bag/safe decrypt site, which picks the encoding its scheme needs:
|
|
952
|
-
// PBES2 uses UTF-8 (the pinned interop convention), a legacy App. C scheme uses the BMPString+NULL form (the
|
|
953
|
-
// App. B.1 encoding the classic MAC also uses) -- both distinct from the public-key integrity keypair. A
|
|
954
|
-
// wrong bag password fails at the first encrypted bag as the uniform pkcs12/decrypt-failed.
|
|
955
|
-
// `valid` is the integrity verdict: open THROWS on an integrity failure for both a password MAC and a
|
|
956
|
-
// public-key CMS signature, so a returned store with either mode is verified; only an allowUnauthenticated
|
|
957
|
-
// (MAC-less) store reaches here unverified. macVerified alone would read false for a valid public-key store.
|
|
958
671
|
var out = { valid: macVerified || m.integrityMode === "public-key", integrityMode: m.integrityMode, macVerified: macVerified, signers: signers, keys: [], certs: [], crls: [], secrets: [] };
|
|
959
672
|
var i;
|
|
960
|
-
var kdfBudget = { rounds: KDF_MAX_ROUNDS };
|
|
673
|
+
var kdfBudget = { rounds: KDF_MAX_ROUNDS };
|
|
961
674
|
for (i = 0; i < m.safeBags.length; i++) _openBag(m.safeBags[i], password, opts, out, 0, kdfBudget);
|
|
962
675
|
for (i = 0; i < m.encryptedSafes.length; i++) await _openEncryptedSafe(m.encryptedSafes[i], password, opts, out, 0, kdfBudget);
|
|
963
676
|
if (opts.keys === "crypto") {
|
|
@@ -969,9 +682,6 @@ async function open(pfx, password, opts) {
|
|
|
969
682
|
function _openBag(bag, password, opts, out, depth, budget) {
|
|
970
683
|
switch (bag.type) {
|
|
971
684
|
case "keyBag":
|
|
972
|
-
// The open contract surfaces keys[].pkcs8 as canonical PrivateKeyInfo DER (key.import re-decodes strict
|
|
973
|
-
// DER). The parser accepts a BER keyBag value, so re-parse it strictly here -- guaranteeing DER exactly as
|
|
974
|
-
// the shrouded path does via _decryptShroudedKey's pkcs8.parse. A non-DER (BER) plaintext key is refused.
|
|
975
685
|
try { pkcs8.parse(bag.keyDer); }
|
|
976
686
|
catch (e) { throw _err("pkcs12/bad-der", "a plaintext keyBag PrivateKeyInfo is not canonical DER", e); }
|
|
977
687
|
out.keys.push({ pkcs8: bag.keyDer, encrypted: false, friendlyName: bag.friendlyName, localKeyId: bag.localKeyId });
|
|
@@ -997,14 +707,6 @@ function _openBag(bag, password, opts, out, depth, budget) {
|
|
|
997
707
|
}
|
|
998
708
|
}
|
|
999
709
|
|
|
1000
|
-
// The RFC 7292 Appendix C / RFC 8018 PBES1 legacy PKCS#12 PBE schemes: the App. B.2 KDF (BMPString password)
|
|
1001
|
-
// derives a cipher key (id=1) + IV (id=2), then a legacy CBC cipher decrypts the bag. An `openssl pkcs12
|
|
1002
|
-
// -legacy` store uses pbeWithSHAAnd3-KeyTripleDES-CBC for keys and 40-bit RC2 for certs by default. RC2 and
|
|
1003
|
-
// RC4 were moved to OpenSSL 3.x's legacy provider (not loaded by node), so they are recognized but refused
|
|
1004
|
-
// with an actionable message rather than silently failing; the 3DES schemes decrypt via node.
|
|
1005
|
-
// Keyed by the IMMUTABLE dotted OID (resolved once at module load via oid.byName), NOT the display name --
|
|
1006
|
-
// so `pki.oid.register` renaming a built-in OID, or aliasing a legacy name onto an unrelated private OID,
|
|
1007
|
-
// cannot alter which cipher a bag decrypts under (the PBES2 branch dispatches on the OID the same way).
|
|
1008
710
|
var LEGACY_PBE = Object.create(null);
|
|
1009
711
|
LEGACY_PBE[O("pbeWithSHAAnd3-KeyTripleDES-CBC")] = { cipher: "des-ede3-cbc", keyLen: 24, ivLen: 8, hash: "sha1" };
|
|
1010
712
|
LEGACY_PBE[O("pbeWithSHAAnd2-KeyTripleDES-CBC")] = { cipher: "des-ede-cbc", keyLen: 16, ivLen: 8, hash: "sha1" };
|
|
@@ -1013,53 +715,30 @@ LEGACY_PBE[O("pbeWithSHAAnd40BitRC2-CBC")] = { rc2: 40, keyLen: 5, ivLen: 8, has
|
|
|
1013
715
|
LEGACY_PBE[O("pbeWithSHAAnd128BitRC4")] = { rc4: true };
|
|
1014
716
|
LEGACY_PBE[O("pbeWithSHAAnd40BitRC4")] = { rc4: true };
|
|
1015
717
|
|
|
1016
|
-
// Decrypt a legacy-PBE bag. pkcs-12PbeParams ::= SEQUENCE { salt OCTET STRING, iterations INTEGER }. Every
|
|
1017
|
-
// secret-dependent fault (a bad key -> a CBC unpad failure) collapses to the uniform pkcs12/decrypt-failed
|
|
1018
|
-
// (no padding oracle, RFC 8018 sec. 8). The iteration count is DoS-capped BEFORE the sync KDF runs.
|
|
1019
718
|
function _decryptLegacyPbe(ea, ct, password, opts, budget) {
|
|
1020
719
|
var scheme = LEGACY_PBE[ea.oid];
|
|
1021
720
|
if (!scheme) throw _err("pkcs12/unsupported-algorithm", "the bag uses " + (ea.name || ea.oid) + " (only RFC 8018 PBES2 and the RFC 7292 App. C 3DES / RC2 schemes are decrypted; re-export with -keypbe/-certpbe AES-256-CBC)");
|
|
1022
721
|
if (scheme.rc4) throw _err("pkcs12/unsupported-algorithm", "the bag uses " + ea.name + " -- RC4 legacy PBE is not supported; re-export with -keypbe/-certpbe AES-256-CBC or PBE-SHA1-3DES");
|
|
1023
|
-
// Decode + read pkcs-12PbeParams, wrapping EVERY fault in a typed pkcs12 error so a malformed legacy store is
|
|
1024
|
-
// classified like a malformed PBES2 one (pkcs12/bad-algorithm-parameters), never a leaked asn1/* code: omitted
|
|
1025
|
-
// parameters make asn1.decode throw asn1/not-buffer, a wrong-type salt/iterations makes asn1.read.* throw.
|
|
1026
|
-
// Content regions are normatively BER (RFC 7292 sec. 4.1) and the parser accepts BER anywhere, so a conforming
|
|
1027
|
-
// store may use an indefinite-length SEQUENCE or a constructed OCTET STRING salt -- decode in BER mode.
|
|
1028
722
|
var salt, iterations;
|
|
1029
723
|
try {
|
|
1030
724
|
var params = asn1.decode(ea.parameters, { ber: true });
|
|
1031
|
-
// Coverage residual -- the tagNumber!==SEQUENCE arm is exercised (a non-SEQUENCE params vector); the
|
|
1032
|
-
// !children and length<2 arms are defensive-unreachable via a conforming decode (asn1.decode of a
|
|
1033
|
-
// constructed node always sets a children array, and every real pkcs-12PbeParams carries salt+iterations).
|
|
1034
725
|
if (params.tagNumber !== asn1.TAGS.SEQUENCE || !params.children || params.children.length < 2) throw _err("pkcs12/bad-algorithm-parameters", "malformed pkcs-12PbeParams (RFC 7292 App. C)");
|
|
1035
726
|
salt = Buffer.from(asn1.read.octetString(params.children[0]));
|
|
1036
727
|
iterations = guard.range.positiveInt31(asn1.read.integer(params.children[1]), _err, "pkcs12/bad-algorithm-parameters", "legacy PBE iterations");
|
|
1037
728
|
} catch (e) {
|
|
1038
|
-
if (e && e.isPkcs12Error) throw e;
|
|
729
|
+
if (e && e.isPkcs12Error) throw e;
|
|
1039
730
|
throw _err("pkcs12/bad-algorithm-parameters", "malformed pkcs-12PbeParams (RFC 7292 App. C)", e);
|
|
1040
731
|
}
|
|
1041
732
|
var cap = opts.maxIterations != null ? Math.min(opts.maxIterations, CLASSIC_MAC_MAX_ITERATIONS) : CLASSIC_MAC_MAX_ITERATIONS;
|
|
1042
733
|
if (iterations > cap) throw _err("pkcs12/iteration-limit", "the legacy PBE iteration count " + iterations + " exceeds the cap " + cap);
|
|
1043
|
-
// Coverage residual -- the salt cap is the shared C.LIMITS.PBKDF2_MAX_SALT the classic MAC / PBES2 paths
|
|
1044
|
-
// already exercise; reaching this legacy-path duplicate needs a >1024-octet salt (a length-cascade-crafted
|
|
1045
|
-
// store no conforming producer emits), so the over-cap arm is documented rather than fixture-forced.
|
|
1046
734
|
if (salt.length > C.LIMITS.PBKDF2_MAX_SALT) throw _err("pkcs12/bad-input", "the legacy PBE salt exceeds the " + C.LIMITS.PBKDF2_MAX_SALT + "-octet cap");
|
|
1047
|
-
// Charge the aggregate KDF-work budget BEFORE deriving: the App. B KDF hashes `iterations` times per output
|
|
1048
|
-
// block, and this bag derives ceil(keyLen/u) key blocks + ceil(ivLen/u) IV blocks. A hostile many-bag store
|
|
1049
|
-
// exhausts the shared budget instead of resetting the per-bag cap each time.
|
|
1050
735
|
var u = P12_KDF_UV[scheme.hash].u;
|
|
1051
736
|
budget.rounds -= (Math.ceil(scheme.keyLen / u) + Math.ceil(scheme.ivLen / u)) * iterations;
|
|
1052
737
|
if (budget.rounds < 0) throw _err("pkcs12/iteration-limit", "the store's aggregate legacy PBE key-derivation work exceeds the budget (a hostile many-bag store)");
|
|
1053
|
-
var p12pw = _p12PasswordOwned(password);
|
|
738
|
+
var p12pw = _p12PasswordOwned(password);
|
|
1054
739
|
var keyM = _p12Kdf(scheme.hash, 1, p12pw.bytes, salt, iterations, scheme.keyLen);
|
|
1055
740
|
var iv = _p12Kdf(scheme.hash, 2, p12pw.bytes, salt, iterations, scheme.ivLen);
|
|
1056
741
|
if (p12pw.owned) guard.secret.zeroize(p12pw.bytes, Pkcs12Error, "pkcs12/bad-input", "the password encoding");
|
|
1057
|
-
// BOTH ciphers funnel through ONE uniform failure (same code AND message), so a bad-pad (secret-dependent)
|
|
1058
|
-
// and a valid-pad-wrong-content outcome are indistinguishable -- no CBC padding oracle (RFC 8018 sec. 8). RC2
|
|
1059
|
-
// is unavailable in node; the in-tree RFC 2268 primitive fills the gap and its typed reject is normalized here.
|
|
1060
|
-
// keyM is a password-derived key this function allocated -- the PBES2 arm of the same dispatch
|
|
1061
|
-
// clears its equivalent, so this arm owes it too, on the failing path an attacker drives. The
|
|
1062
|
-
// derived IV is not secret and is left alone.
|
|
1063
742
|
try {
|
|
1064
743
|
if (scheme.rc2) return rc2.cbcDecrypt(keyM, scheme.rc2, iv, ct, _err, "pkcs12/decrypt-failed");
|
|
1065
744
|
var d = nodeCrypto.createDecipheriv(scheme.cipher, keyM, iv);
|
|
@@ -1068,13 +747,7 @@ function _decryptLegacyPbe(ea, ct, password, opts, budget) {
|
|
|
1068
747
|
finally { guard.secret.zeroize(keyM, Pkcs12Error, "pkcs12/bad-input", "the password-derived decryption key"); }
|
|
1069
748
|
}
|
|
1070
749
|
|
|
1071
|
-
// Decrypt a PBES2 (RFC 8018) or legacy-PBE (RFC 7292 App. C) bag/safe -- dispatch on the encryptionAlgorithm
|
|
1072
|
-
// OID. PBES2 uses the UTF-8 password (the pinned interop convention); legacy PBE uses the App. B.1 BMPString.
|
|
1073
750
|
function _decryptBag(ea, ct, password, opts, budget) {
|
|
1074
|
-
// BOTH arms charge the one shared budget. Charging only the legacy arm left the modern one --
|
|
1075
|
-
// the arm every current producer emits -- able to reset its per-bag cap on every bag, so a store
|
|
1076
|
-
// that repeats a costly PBES2 bag up to the parser's element limit multiplied the cap by that
|
|
1077
|
-
// limit in blocking pbkdf2Sync work.
|
|
1078
751
|
if (ea.oid === O("pbes2")) {
|
|
1079
752
|
var pw = _pbePasswordOwned(password);
|
|
1080
753
|
try { return pbes2.pbes2Decrypt(pw.bytes, ea.parameters, ct, opts, _err, "pkcs12", budget); }
|
|
@@ -1083,42 +756,25 @@ function _decryptBag(ea, ct, password, opts, budget) {
|
|
|
1083
756
|
return _decryptLegacyPbe(ea, ct, password, opts, budget);
|
|
1084
757
|
}
|
|
1085
758
|
|
|
1086
|
-
// A pkcs8ShroudedKeyBag is a bare EncryptedPrivateKeyInfo (RFC 5958 sec. 3): decrypt to a PrivateKeyInfo,
|
|
1087
|
-
// re-validated (the re-parse is the MAC-less integrity check for the bag ciphertext).
|
|
1088
759
|
function _decryptShroudedKey(encrypted, password, opts, budget) {
|
|
1089
760
|
var der = _decryptBag(encrypted.encryptionAlgorithm, encrypted.encryptedData, password, opts, budget);
|
|
1090
761
|
try { pkcs8.parse(der); } catch (_e) { throw _err("pkcs12/decrypt-failed", "decryption failed"); }
|
|
1091
762
|
return der;
|
|
1092
763
|
}
|
|
1093
764
|
|
|
1094
|
-
// A secret-dependent recipient-decrypt fault (wrong recipientKey / a tampered envelope / a CBC unpad failure)
|
|
1095
|
-
// surfaces from cms.decrypt as the uniform cms/decrypt-failed -- wrap it to pkcs12/decrypt-failed so the public-
|
|
1096
|
-
// key privacy path shares ONE opaque verdict with the PBES2 path (no padding / recipient / structure oracle,
|
|
1097
|
-
// RFC 8018 sec. 8). A structural / selection fault (no-matching-recipient, unsupported-algorithm, iteration-
|
|
1098
|
-
// limit) is NOT secret-dependent and propagates as the delegated cms/* code.
|
|
1099
765
|
function _mapDecryptError(e) {
|
|
1100
766
|
if (e && e.code === "cms/decrypt-failed") return _err("pkcs12/decrypt-failed", "decryption failed", e);
|
|
1101
767
|
return e;
|
|
1102
768
|
}
|
|
1103
769
|
|
|
1104
|
-
// A privacy safe: id-encryptedData (PBES2 password) OR id-envelopedData (public-key, RFC 7292 sec. 3.1) --
|
|
1105
|
-
// decrypt the SafeContents, then re-walk it through the strict parser (same PKCS12_MAX_* caps) and open each
|
|
1106
|
-
// recovered bag. Async because the envelope path awaits pki.cms.decrypt.
|
|
1107
770
|
async function _openEncryptedSafe(encSafe, password, opts, out, depth, budget) {
|
|
1108
771
|
if (encSafe.type === "envelopedData") {
|
|
1109
|
-
// INTEGRITY-BEFORE-USE: open()'s pre-loop MAC / SignedData gate already ran over the WHOLE AuthenticatedSafe
|
|
1110
|
-
// (which covers this enveloped element), so the ciphertext reaching cms.decrypt is integrity-checked. The
|
|
1111
|
-
// recipient private key is a PRIVACY credential only -- never a MAC key, a signature input, or a PBES2 password.
|
|
1112
772
|
if (opts.recipientKey == null) throw _err("pkcs12/no-recipient-key", "the store has an id-envelopedData privacy safe -- supply opts.recipientKey (and opts.recipientCert) to decrypt it (RFC 7292 sec. 5.2)");
|
|
1113
|
-
//
|
|
1114
|
-
// @internal cms seam -- so a BER-encoded envelope the pkcs12 parser accepts opens without a strict-DER
|
|
1115
|
-
// re-parse of reconstructed bytes. The pkcs12 parser is the trusted source of this parsed structure.
|
|
773
|
+
// @internal
|
|
1116
774
|
var res;
|
|
1117
775
|
try { res = await cmsDecrypt.decryptEnvelopedData(encSafe.content, { key: opts.recipientKey, cert: opts.recipientCert }, opts, "envelopedData"); }
|
|
1118
776
|
catch (e) { throw _mapDecryptError(e); }
|
|
1119
777
|
var envBags;
|
|
1120
|
-
// A decrypt that unpads to non-SafeContents bytes collapses into the SAME uniform pkcs12/decrypt-failed as a
|
|
1121
|
-
// bad pad -- a distinguishable structural code here would be a padding oracle. The strict walk's caps fire.
|
|
1122
778
|
try { envBags = schemaPkcs12.walkSafeContents(res.content); }
|
|
1123
779
|
catch (_e) { throw _err("pkcs12/decrypt-failed", "decryption failed"); }
|
|
1124
780
|
for (var k = 0; k < envBags.length; k++) _openBag(envBags[k], password, opts, out, depth, budget);
|
|
@@ -1128,10 +784,6 @@ async function _openEncryptedSafe(encSafe, password, opts, out, depth, budget) {
|
|
|
1128
784
|
var eci = encSafe.content.encryptedContentInfo;
|
|
1129
785
|
if (eci.encryptedContent == null) throw _err("pkcs12/bad-input", "the encrypted privacy safe has no content");
|
|
1130
786
|
var safeContentsDer = _decryptBag(eci.contentEncryptionAlgorithm, eci.encryptedContent, password, opts, budget);
|
|
1131
|
-
// The bag/safe password MAY differ from the (already-verified) MAC password, so a decrypt under the MAC
|
|
1132
|
-
// password can succeed with a valid pad yet yield bytes that are not a SafeContents. Collapse THAT re-parse
|
|
1133
|
-
// failure into the same uniform pkcs12/decrypt-failed as a bad pad -- a distinguishable structural code here
|
|
1134
|
-
// would be a padding oracle (RFC 8018 sec. 8). The strict walk's caps still fire (bounding the work).
|
|
1135
787
|
var bags;
|
|
1136
788
|
try { bags = schemaPkcs12.walkSafeContents(safeContentsDer); }
|
|
1137
789
|
catch (_e) { throw _err("pkcs12/decrypt-failed", "decryption failed"); }
|