@blamejs/pki 0.6.14 → 0.6.15

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/lib/acme.js +59 -815
  3. package/lib/asn1-der.js +7 -245
  4. package/lib/attrcert-sign.js +12 -260
  5. package/lib/attrcert-verify.js +1 -7
  6. package/lib/byte-reader.js +0 -8
  7. package/lib/byte-writer.js +0 -11
  8. package/lib/cbor-det.js +0 -54
  9. package/lib/cmc-build.js +15 -357
  10. package/lib/cmc-verify.js +6 -239
  11. package/lib/cmp-build.js +41 -317
  12. package/lib/cmp-session.js +73 -760
  13. package/lib/cmp-verify.js +32 -488
  14. package/lib/cms-compress.js +3 -32
  15. package/lib/cms-decrypt.js +29 -294
  16. package/lib/cms-encrypt.js +14 -234
  17. package/lib/cms-sign.js +29 -324
  18. package/lib/cms-verify.js +9 -500
  19. package/lib/composite-kem.js +2 -102
  20. package/lib/composite-sig.js +1 -77
  21. package/lib/constants.js +0 -249
  22. package/lib/crl-sign.js +9 -237
  23. package/lib/crl-verify.js +1 -10
  24. package/lib/crmf-sign.js +19 -198
  25. package/lib/crmf-verify.js +1 -12
  26. package/lib/csr-sign.js +3 -34
  27. package/lib/csr-verify.js +1 -12
  28. package/lib/ct.js +28 -253
  29. package/lib/edwards-point.js +10 -44
  30. package/lib/est.js +28 -764
  31. package/lib/framework-error.js +4 -182
  32. package/lib/guard-all.js +1 -58
  33. package/lib/guard-async.js +1 -36
  34. package/lib/guard-bytes.js +6 -650
  35. package/lib/guard-compress.js +5 -102
  36. package/lib/guard-crypto.js +1 -40
  37. package/lib/guard-der.js +2 -93
  38. package/lib/guard-encoding.js +2 -60
  39. package/lib/guard-header.js +1 -36
  40. package/lib/guard-identifier.js +7 -531
  41. package/lib/guard-intrinsic.js +1 -150
  42. package/lib/guard-json.js +10 -73
  43. package/lib/guard-limits.js +1 -77
  44. package/lib/guard-list.js +1 -49
  45. package/lib/guard-name.js +3 -209
  46. package/lib/guard-parsed.js +7 -335
  47. package/lib/guard-range.js +1 -71
  48. package/lib/guard-secret.js +1 -102
  49. package/lib/guard-text.js +1 -64
  50. package/lib/guard-time.js +1 -97
  51. package/lib/hpke.js +3 -105
  52. package/lib/http-digest.js +14 -142
  53. package/lib/http-retry-after.js +1 -47
  54. package/lib/http-transport.js +21 -131
  55. package/lib/inspect.js +30 -284
  56. package/lib/ip-utils.js +6 -47
  57. package/lib/jose.js +7 -142
  58. package/lib/key.js +2 -67
  59. package/lib/lint.js +9 -194
  60. package/lib/merkle.js +1 -27
  61. package/lib/mime.js +9 -82
  62. package/lib/ocsp-request-verify.js +1 -14
  63. package/lib/ocsp-verify.js +1 -82
  64. package/lib/ocsp.js +11 -169
  65. package/lib/oid.js +5 -337
  66. package/lib/path-validate.js +103 -1492
  67. package/lib/pbes2.js +2 -89
  68. package/lib/pkcs12-build.js +31 -379
  69. package/lib/pki-build.js +64 -290
  70. package/lib/rc2.js +2 -18
  71. package/lib/rfc3339.js +4 -27
  72. package/lib/scep.js +8 -252
  73. package/lib/schema-all.js +0 -122
  74. package/lib/schema-attrcert.js +4 -146
  75. package/lib/schema-c509.js +153 -802
  76. package/lib/schema-cmc.js +5 -220
  77. package/lib/schema-cmp.js +2 -223
  78. package/lib/schema-cms.js +13 -427
  79. package/lib/schema-crl.js +5 -68
  80. package/lib/schema-crmf.js +6 -167
  81. package/lib/schema-csr.js +1 -54
  82. package/lib/schema-csrattrs.js +2 -87
  83. package/lib/schema-engine.js +0 -152
  84. package/lib/schema-ocsp.js +0 -117
  85. package/lib/schema-pkcs12.js +2 -93
  86. package/lib/schema-pkcs8.js +3 -35
  87. package/lib/schema-pkix.js +24 -655
  88. package/lib/schema-smime.js +0 -54
  89. package/lib/schema-tsp.js +3 -99
  90. package/lib/schema-x509.js +4 -63
  91. package/lib/shbs.js +11 -95
  92. package/lib/sign-scheme.js +2 -97
  93. package/lib/sigstore.js +9 -169
  94. package/lib/sleep.js +2 -8
  95. package/lib/smime.js +15 -408
  96. package/lib/tls-cert-compress.js +0 -62
  97. package/lib/trust.js +19 -185
  98. package/lib/tsp-sign.js +16 -270
  99. package/lib/validator-all.js +1 -30
  100. package/lib/validator-attcert.js +2 -46
  101. package/lib/validator-cose.js +2 -153
  102. package/lib/validator-keydesc.js +1 -37
  103. package/lib/validator-sig.js +1 -46
  104. package/lib/validator-tls.js +1 -88
  105. package/lib/validator-tpm.js +19 -122
  106. package/lib/verify-seam.js +1 -24
  107. package/lib/webauthn-mds.js +3 -400
  108. package/lib/webauthn.js +12 -833
  109. package/lib/webcrypto.js +5 -485
  110. package/lib/x509-sign.js +4 -108
  111. package/package.json +1 -1
  112. package/sbom.cdx.json +6 -6
@@ -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"); // pki.cms.sign (build the id-signedData authSafe) + pki.cms.verify (the open integrity gate) + cms.encrypt (the privacy envelope)
46
- var cmsSign = require("./cms-sign"); // the signer-descriptor field tables, for the integrity signers this verb authors
47
- var cmsDecrypt = require("./cms-decrypt"); // the @internal decryptEnvelopedData seam -- opens a privacy safe off the pkcs12 parser's already-walked (BER-capable) EnvelopedData node
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"); // reqDenseArray: reject a sparse/nullish caller array as a typed pkcs12/bad-input
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); // bound at load so a replaced String.prototype.toLowerCase cannot steer a hash-name comparison
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; // an HMAC key beyond a hash block is pointless, so bound the derived length
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); // + the 2-byte NULL terminator
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; // the final two bytes are already 0x00 0x00
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); // step 1: v copies of the ID diversifier
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]); // steps 2-4: I = S || P
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); // step 5
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++) { // step 6
141
+ for (var i = 0; i < c; i++) {
285
142
  var A = Buffer.concat([D, I]);
286
- for (var r = 0; r < iterations; r++) { // 6A: A = H^r(D||I)
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); // step 7 (accumulate A_1..A_c)
292
- if (i < c - 1) { // 6B+6C: modify I for the next block
293
- var B = _blockFill(A, v); // a full password-derived block
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); } // O() (oid.byName) throws OidError on a non-string -- keep it inside the domain wrap
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); // EncryptedPrivateKeyInfo
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); // cms.encrypt returns the whole id-envelopedData ContentInfo DER -- splice it verbatim
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)]); // EncryptedContentInfo, [0] IMPLICIT ct
532
- var encData = b.sequence([b.integer(0n), eci]); // EncryptedData { version 0, 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); // classic KDF -> BMPString
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); } // PBKDF2 -> UTF-8; prf == messageAuthScheme on build
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); // AuthenticatedSafe ::= SEQUENCE OF ContentInfo -- shared by both integrity modes
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)]); // PFX ::= SEQUENCE { version v3, authSafe (id-signedData) } -- NO MacData
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); // classic KDF -> BMPString
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); } // PBKDF2 -> UTF-8
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 }; // aggregate KDF work budget for this whole open(), both schemes
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; // an already-typed pkcs12 fault (the checks above) passes through
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); // App. B.1 BMPString + NULL (NOT the PBES2 UTF-8 encoding)
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
- // Drive the recipient decrypt off the parser's ALREADY-WALKED EnvelopedData (encSafe.content) via the
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"); }