@blamejs/pki 0.5.7 → 0.5.8

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 (104) hide show
  1. package/CHANGELOG.md +405 -386
  2. package/MIGRATING.md +43 -0
  3. package/README.md +12 -12
  4. package/lib/acme.js +31 -31
  5. package/lib/asn1-der.js +10 -10
  6. package/lib/attrcert-sign.js +19 -19
  7. package/lib/byte-reader.js +6 -6
  8. package/lib/byte-writer.js +5 -5
  9. package/lib/cbor-det.js +27 -24
  10. package/lib/cmc-build.js +97 -92
  11. package/lib/cmc-verify.js +106 -30
  12. package/lib/cmp-build.js +25 -25
  13. package/lib/cmp-session.js +70 -70
  14. package/lib/cmp-verify.js +71 -57
  15. package/lib/cms-compress.js +7 -7
  16. package/lib/cms-decrypt.js +90 -72
  17. package/lib/cms-encrypt.js +32 -32
  18. package/lib/cms-sign.js +74 -55
  19. package/lib/cms-verify.js +97 -75
  20. package/lib/composite-sig.js +13 -13
  21. package/lib/constants.js +4 -4
  22. package/lib/crl-sign.js +22 -22
  23. package/lib/crl-verify.js +7 -6
  24. package/lib/crmf-sign.js +14 -14
  25. package/lib/csr-sign.js +8 -8
  26. package/lib/ct.js +37 -37
  27. package/lib/edwards-point.js +7 -7
  28. package/lib/est.js +98 -55
  29. package/lib/framework-error.js +5 -5
  30. package/lib/guard-all.js +3 -3
  31. package/lib/guard-async.js +4 -4
  32. package/lib/guard-bytes.js +79 -79
  33. package/lib/guard-compress.js +17 -17
  34. package/lib/guard-crypto.js +1 -1
  35. package/lib/guard-encoding.js +15 -15
  36. package/lib/guard-header.js +3 -3
  37. package/lib/guard-identifier.js +16 -16
  38. package/lib/guard-json.js +15 -15
  39. package/lib/guard-limits.js +7 -7
  40. package/lib/guard-name.js +81 -16
  41. package/lib/guard-parsed.js +80 -80
  42. package/lib/guard-range.js +19 -19
  43. package/lib/guard-secret.js +11 -10
  44. package/lib/guard-text.js +6 -6
  45. package/lib/guard-time.js +10 -10
  46. package/lib/hpke.js +18 -17
  47. package/lib/http-digest.js +35 -35
  48. package/lib/http-retry-after.js +13 -13
  49. package/lib/http-transport.js +20 -19
  50. package/lib/inspect.js +53 -53
  51. package/lib/ip-utils.js +2 -2
  52. package/lib/jose.js +13 -13
  53. package/lib/key.js +16 -16
  54. package/lib/lint.js +51 -51
  55. package/lib/merkle.js +51 -36
  56. package/lib/mime.js +18 -18
  57. package/lib/ocsp-verify.js +10 -10
  58. package/lib/ocsp.js +13 -13
  59. package/lib/oid.js +29 -29
  60. package/lib/path-validate.js +114 -113
  61. package/lib/pbes2.js +16 -16
  62. package/lib/pkcs12-build.js +53 -53
  63. package/lib/pki-build.js +21 -19
  64. package/lib/rc2.js +1 -1
  65. package/lib/rfc3339.js +5 -5
  66. package/lib/schema-all.js +31 -31
  67. package/lib/schema-attrcert.js +12 -12
  68. package/lib/schema-c509.js +144 -142
  69. package/lib/schema-cmc.js +58 -58
  70. package/lib/schema-cmp.js +43 -43
  71. package/lib/schema-cms.js +45 -45
  72. package/lib/schema-crl.js +7 -7
  73. package/lib/schema-crmf.js +28 -28
  74. package/lib/schema-csr.js +12 -12
  75. package/lib/schema-csrattrs.js +16 -16
  76. package/lib/schema-engine.js +18 -18
  77. package/lib/schema-ocsp.js +15 -15
  78. package/lib/schema-pkcs12.js +20 -20
  79. package/lib/schema-pkcs8.js +2 -2
  80. package/lib/schema-pkix.js +131 -126
  81. package/lib/schema-smime.js +19 -19
  82. package/lib/schema-tsp.js +12 -12
  83. package/lib/schema-x509.js +3 -3
  84. package/lib/shbs.js +18 -18
  85. package/lib/sign-scheme.js +13 -13
  86. package/lib/sigstore.js +10 -11
  87. package/lib/sleep.js +1 -1
  88. package/lib/smime.js +308 -96
  89. package/lib/tls-cert-compress.js +18 -18
  90. package/lib/trust.js +27 -27
  91. package/lib/tsp-sign.js +17 -17
  92. package/lib/validator-all.js +1 -1
  93. package/lib/validator-attcert.js +1 -1
  94. package/lib/validator-cose.js +43 -44
  95. package/lib/validator-keydesc.js +3 -3
  96. package/lib/validator-sig.js +13 -13
  97. package/lib/validator-tls.js +11 -11
  98. package/lib/validator-tpm.js +20 -19
  99. package/lib/webauthn-mds.js +66 -66
  100. package/lib/webauthn.js +33 -33
  101. package/lib/webcrypto.js +15 -15
  102. package/lib/x509-sign.js +13 -13
  103. package/package.json +3 -2
  104. package/sbom.cdx.json +6 -6
@@ -6,10 +6,10 @@
6
6
  // @primitive pki.cms.decrypt block live in cms-verify.js, which re-exports this function.
7
7
  //
8
8
  // CMS EnvelopedData / AuthEnvelopedData / EncryptedData decryption (RFC 5652/5083/5084/3560/5753/
9
- // 8418/9629/9936/3211/8018), the consuming side of pki.cms.encrypt. Three stages behind ONE
10
- // oracle-free choke point: (1) SELECT the recipient (identifier matching -- typed + distinct),
11
- // (2) ACQUIRE the CEK (per-arm unwrap/decap/derive), (3) OPEN the content (AEAD verify / CBC
12
- // decrypt+unpad). Every secret-dependent failure in stages 2-3 collapses to the SINGLE uniform
9
+ // 8418/9629/9936/3211/8018), the consuming side of pki.cms.encrypt. Three stages behind one
10
+ // oracle-free choke point: (1) select the recipient (identifier matching, typed + distinct),
11
+ // (2) acquire the CEK (per-arm unwrap/decap/derive), (3) open the content (AEAD verify / CBC
12
+ // decrypt+unpad). Every secret-dependent failure in stages 2-3 collapses to the single uniform
13
13
  // verdict `cms/decrypt-failed` (Bleichenbacher / EFAIL / PWRI-check-byte oracle freedom); the
14
14
  // ktri v1.5 arm substitutes a random CEK on any decode fault (RFC 3218 implicit rejection) so its
15
15
  // failure emerges at stage 3 identically to every other bad-key path.
@@ -32,7 +32,7 @@ var KEM_CT_LENGTHS = schemaCms.KEM_CT_LENGTHS;
32
32
 
33
33
  function O(n) { return oid.byName(n); }
34
34
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
35
- // The ONE uniform secret-dependent-failure verdict. No cause chaining that distinguishes the site.
35
+ // The single uniform secret-dependent-failure verdict. No cause chaining that distinguishes the site.
36
36
  function _fail() { return new CmsError("cms/decrypt-failed", "the CMS content could not be decrypted (uniform by design -- padding / integrity / key-unwrap failures are indistinguishable to defeat oracles)"); }
37
37
 
38
38
  var CONTENT_KEYBITS = pbes2.CONTENT_KEYBITS; // content-encryption OID -> key bits (the shared PBES2 table)
@@ -50,13 +50,31 @@ async function decrypt(input, keyMaterial, opts) {
50
50
  return decryptEnvelopedData(parsed, keyMaterial, opts, ct);
51
51
  }
52
52
 
53
- // The post-parse decrypt of an ALREADY-PARSED EnvelopedData / AuthEnvelopedData: select a recipient, acquire
54
- // the CEK, open the content -- oracle-free (every secret-dependent failure collapses to cms/decrypt-failed).
55
- // @internal -- NOT re-exported on pki.cms. Taking the parsed structure (rather than DER) is a DELIBERATE seam
56
- // for a sibling composer that already holds a strictly-walked content node it must NOT re-serialize/re-parse:
53
+ // The post-parse decrypt of an already-parsed EnvelopedData / AuthEnvelopedData: select a recipient, acquire
54
+ // the CEK, open the content, oracle-free (every secret-dependent failure collapses to cms/decrypt-failed).
55
+ // @internal -- not re-exported on pki.cms. Taking the parsed structure in place of DER is a deliberate seam
56
+ // for a sibling composer that already holds a strictly-walked content node it must not re-serialize/re-parse:
57
57
  // pki.pkcs12.open drives this off the pkcs12 parser's walkEnvelopedData result so an RFC 7292 privacy safe
58
- // whose EnvelopedData is BER-encoded (indefinite lengths, some NSS output -- accepted by the pkcs12 parser)
59
- // opens without hitting the public decrypt()'s strict-DER re-parse. Callers pass a TRUSTED parse result only.
58
+ // whose EnvelopedData is BER-encoded (indefinite lengths, some NSS output the pkcs12 parser accepts)
59
+ // opens without hitting the public decrypt()'s strict-DER re-parse. Callers pass a trusted parse result only.
60
+ // `authenticated` answers a question about the content and the key that opened it. It
61
+ // cannot answer the question an operator hears in the word -- who sent this -- and no
62
+ // recipient type this toolkit supports authenticates an individual originator: a ktri or
63
+ // ephemeral-static kari message is minted by anyone holding the recipient's PUBLIC key,
64
+ // and a password or KEK message is minted by any co-recipient sharing the secret. The
65
+ // origin question therefore gets its own field instead of being left to the reader's
66
+ // inference. originatorInfo rides outside the AEAD's authenticated data, so it is a hint
67
+ // the sender chose, never evidence, and it is surfaced saying so rather than dropped.
68
+ // Every verdict this module returns carries all three, including the unauthenticated
69
+ // ciphers: a field present on some returns and absent on others is read as a capability
70
+ // difference, and the caller cannot tell "no origin" from "this path forgot to say".
71
+ function _originFields(res, authenticatedBy, originatorInfo) {
72
+ res.authenticatedBy = authenticatedBy || null;
73
+ res.originAuthenticated = false;
74
+ res.originatorInfo = originatorInfo || null;
75
+ return res;
76
+ }
77
+
60
78
  async function decryptEnvelopedData(parsed, keyMaterial, opts, contentTypeName) {
61
79
  opts = opts || {};
62
80
  if (keyMaterial == null || typeof keyMaterial !== "object") throw _err("cms/bad-input", "decrypt requires a key-material object");
@@ -64,8 +82,8 @@ async function decryptEnvelopedData(parsed, keyMaterial, opts, contentTypeName)
64
82
  var recips = parsed.recipientInfos || [];
65
83
  var candidates = _selectCandidates(recips, keyMaterial, opts); // stage 1 (typed, distinct)
66
84
  var eci = parsed.encryptedContentInfo;
67
- // The content cipher's MODE is a structural property of the message -- readable from the algorithm
68
- // identifier with no key material at all -- so it is decided HERE, before any recipient is tried.
85
+ // The content cipher's mode is a structural property of the message, readable from the algorithm
86
+ // identifier with no key material at all, so it is decided here, before any recipient is tried.
69
87
  // Deciding it per candidate would make a structural verdict depend on key acquisition succeeding
70
88
  // first: several password or KEK recipients can match the same key material, so the distinct
71
89
  // cms/unsupported-algorithm would be swallowed by the ambiguous-candidate loop below and the caller
@@ -78,18 +96,18 @@ async function decryptEnvelopedData(parsed, keyMaterial, opts, contentTypeName)
78
96
  try {
79
97
  _assertSupported(candidates[ci].ri, keyMaterial); // distinct-code reject (MQV / non-KEM ori)
80
98
  var cek = await _acquireCek(candidates[ci].ri, keyMaterial, opts); // stage 2 (uniform)
81
- // The recovered CEK is cleared only AFTER the content is open -- it is the key the next stage
82
- // needs, so an earlier wipe would break decryption rather than protect it. The returned object
99
+ // The recovered CEK is cleared only after the content is open: it is the key the next stage
100
+ // needs, so an earlier wipe would break decryption instead of protecting it. The returned object
83
101
  // is built before the `finally` runs and holds the plaintext, not the key.
84
102
  try {
85
103
  var content = await _openContent(parsed, eci, cek, ct); // stage 3 (uniform)
86
- return {
104
+ return _originFields({
87
105
  content: content,
88
106
  contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType,
89
107
  recipientType: candidates[ci].ri.type, recipientIndex: candidates[ci].index,
90
108
  contentEncryptionAlgorithm: eci.contentEncryptionAlgorithm.name || eci.contentEncryptionAlgorithm.oid,
91
109
  authenticated: ct === "authEnvelopedData",
92
- };
110
+ }, ct === "authEnvelopedData" ? "content-encryption-key" : null, parsed.originatorInfo);
93
111
  } finally {
94
112
  guard.secret.zeroize(cek, CmsError, "cms/bad-input", "the recovered content-encryption key");
95
113
  }
@@ -101,7 +119,7 @@ async function decryptEnvelopedData(parsed, keyMaterial, opts, contentTypeName)
101
119
  }
102
120
 
103
121
  function _parse(input) {
104
- // Always drive the strict parser over caller-supplied bytes -- an arbitrary object that merely
122
+ // Always drive the strict parser over caller-supplied bytes: an arbitrary object that merely
105
123
  // carries a contentTypeName is not a trusted parse result and must not bypass DER validation.
106
124
  return schemaCms.parse(_toDer(input));
107
125
  }
@@ -111,7 +129,7 @@ function _toDer(input) {
111
129
  throw _err("cms/bad-input", "input must be a DER Buffer, Uint8Array, or PEM string");
112
130
  }
113
131
 
114
- // ---- stage 1: recipient selection (NOT secret-dependent) -------------------
132
+ // ---- stage 1: recipient selection (not secret-dependent) -------------------
115
133
  // Returns every recipient the key material may target. An explicit recipientIndex or an rid match
116
134
  // yields exactly one; unlabelled pwri / kekri recipients yield all matches for the caller to try.
117
135
  function _selectCandidates(recips, km, opts) {
@@ -198,12 +216,12 @@ async function _acquireCek(ri, km, opts) {
198
216
  if (ri.type === "pwri") return await _pwriCek(ri, km, opts);
199
217
  if (ri.type === "ori") return await _kemriCek(ri, km);
200
218
  } catch (e) {
201
- // Structural / resource / config faults keep their own code -- only SECRET-DEPENDENT failures
219
+ // Structural / resource / config faults keep their own code; only secret-dependent failures
202
220
  // (a bad unwrap, a wrong key, a padding fault) collapse to the uniform verdict.
203
221
  if (e instanceof CmsError && _passThrough[e.code]) throw e;
204
222
  throw _fail();
205
223
  }
206
- // Coverage residual: unreachable -- _selectCandidates only returns one of the five handled types.
224
+ // Coverage residual: unreachable, since _selectCandidates only returns one of the five handled types.
207
225
  throw _err("cms/unsupported-recipient-type", "unsupported recipient type " + ri.type);
208
226
  }
209
227
 
@@ -229,7 +247,7 @@ async function _ktriCek(ri, km) {
229
247
  }
230
248
  }
231
249
  // Coverage residual: reachable only from a hostile message (our encrypt emits only OAEP; OpenSSL
232
- // emits OAEP or rsaEncryption) -- a fail-closed reject the fuzz harness exercises.
250
+ // emits OAEP or rsaEncryption): a fail-closed reject the fuzz harness exercises.
233
251
  throw _err("cms/unsupported-algorithm", "unsupported ktri keyEncryptionAlgorithm " + kea.oid);
234
252
  } finally {
235
253
  _releaseKeyDer(k);
@@ -242,7 +260,7 @@ async function _kariCek(ri, km) {
242
260
  // otherwise be an unprotected window, and it is not a narrow one: reading the wrap algorithm, the
243
261
  // originator's key, the recipient's certificate and the matching RecipientEncryptedKey all parse
244
262
  // attacker-supplied structure and all throw on malformed input. A message crafted to fail any one
245
- // of them would be a way to leave the key copy in the heap -- the exact outcome the wipe is for.
263
+ // of them would be a way to leave the key copy in the heap, the exact outcome the wipe is for.
246
264
  var k = _normKeyDer(km.key);
247
265
  // The agreement secret and the KEK derived from it are both allocated here; one `finally` clears
248
266
  // whichever branch produced them, including when the unwrap below throws on a tampered key.
@@ -253,7 +271,7 @@ async function _kariCek(ri, km) {
253
271
  var wrapAlg = _kariWrap(kea);
254
272
  var scheme = kea.oid;
255
273
  var origSpki = _originatorSpki(ri.originator);
256
- // Unwrap THIS recipient's RecipientEncryptedKey (matched by rid), not element 0 -- a kari may list
274
+ // Unwrap this recipient's own RecipientEncryptedKey (matched by rid), not element 0; a kari may list
257
275
  // several recipients under one ephemeral key.
258
276
  var rek = (km.cert != null && _kariRekFor(ri, x509.parse(_normCertDer(km.cert)))) || ri.recipientEncryptedKeys[0];
259
277
  var kekBytes = WRAP_KEK_LENGTHS[wrapAlg.oid];
@@ -266,8 +284,8 @@ async function _kariCek(ri, km) {
266
284
  mz = Buffer.from(await subtle.deriveBits({ name: mont.name, public: origPub }, recipPriv, null));
267
285
  if (mz.every(function (x) { return x === 0; })) throw _fail();
268
286
  var mzKey = await subtle.importKey("raw", mz, { name: "HKDF" }, false, ["deriveBits"]);
269
- // RFC 8418 sec. 2.2: a present ukm is used BOTH as the HKDF salt AND as the ECC-CMS-SharedInfo
270
- // entityUInfo -- mirror the producer so both sides derive the same KEK as a conformant peer.
287
+ // RFC 8418 sec. 2.2: a present ukm serves as both the HKDF salt and the ECC-CMS-SharedInfo
288
+ // entityUInfo, so mirror the producer and both sides derive the same KEK as a conformant peer.
271
289
  kek = Buffer.from(await subtle.deriveBits({ name: "HKDF", hash: mont.hkdf, salt: ukm || Buffer.alloc(0), info: _eccSharedInfo(wrapAlg.name, ukm, kekBytes) }, mzKey, kekBytes * 8));
272
290
  } else {
273
291
  // RFC 5753 sec. 7.1 permits the originator EC key to omit its curve parameters, inheriting the
@@ -313,11 +331,11 @@ async function _pwriCek(ri, km, opts) {
313
331
  // override a built-in name, and a name-matched mode check would then admit a non-CBC inner cipher.
314
332
  if (!innerBits || CONTENT_MODE[innerOid] !== "cbc") throw _err("cms/unsupported-algorithm", "unsupported pwri inner cipher");
315
333
  var iv = asn1.read.octetString(inner.children[1]);
316
- // The derived KEK is ours and is cleared once the unwrap has consumed it -- on the failing path
334
+ // The derived KEK is ours and is cleared once the unwrap has consumed it, on the failing path
317
335
  // too, which is the one an attacker induces by tampering with the wrapped key. The caller's
318
336
  // password is not touched: passwordBytes returns a caller-supplied Buffer as-is.
319
- // A string / Uint8Array password is encoded into a buffer THIS toolkit allocated -- a credential
320
- // copy -- so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
337
+ // A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
338
+ // copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
321
339
  // borrowed and left intact.
322
340
  var pw = pbes2.passwordBytesOwned(km.password, _err, "cms");
323
341
  var kek;
@@ -336,18 +354,18 @@ async function _kemriCek(ri, km) {
336
354
  var wcName = _mlkemName(k.kem.oid);
337
355
  if (!wcName) throw _err("cms/unsupported-algorithm", "unsupported KEM " + k.kem.oid);
338
356
  // Honor the declared key-derivation function: we implement HKDF-SHA256 (RFC 9629), so a message
339
- // naming any other KDF is rejected rather than silently derived with the wrong one.
357
+ // naming any other KDF is rejected, never silently derived with the wrong one.
340
358
  if (k.kdf.oid !== O("hkdfWithSha256")) throw _err("cms/unsupported-algorithm", "unsupported KEM key-derivation " + k.kdf.oid);
341
359
  var wantCt = KEM_CT_LENGTHS[k.kem.oid];
342
360
  var kemct = k.kemct;
343
- // Coverage residual (both _fail re-checks): unreachable via a parsed message -- the strict parser
361
+ // Coverage residual (both _fail re-checks): unreachable via a parsed message, because the strict parser
344
362
  // already rejects a wrong kemct length (cms/bad-kem-ciphertext) and a kekLength != wrap size
345
363
  // (cms/kek-length-mismatch). These are defense-in-depth on the consumer path (M32 / M29).
346
- if (wantCt && kemct.length !== wantCt) throw _fail(); // M32: exact ct length BEFORE decap
364
+ if (wantCt && kemct.length !== wantCt) throw _fail(); // M32: exact ct length before decap
347
365
  var kekBytes = Number(k.kekLength);
348
366
  var wrapAlg = k.wrap;
349
367
  if (WRAP_KEK_LENGTHS[wrapAlg.oid] !== kekBytes) throw _fail(); // M29 re-check on the consumer path
350
- // The decapsulation key copy is released as soon as the engine has imported it -- the import is
368
+ // The decapsulation key copy is released as soon as the engine has imported it: the import is
351
369
  // the only thing that reads it, so its lifetime does not need to span the decapsulation below.
352
370
  var keyCopy = _normKeyDer(km.key);
353
371
  var priv;
@@ -366,8 +384,8 @@ async function _kemriCek(ri, km) {
366
384
  return await _aesKwUnwrap(kek, k.encryptedKey);
367
385
  } finally {
368
386
  // NIST SP 800-227 RS5 / sec. 4.2 and RFC 9629 sec. 7: the shared secret and the KEK it derives
369
- // are destroyed as soon as they stop being needed. In a `finally`, so an unwrap that FAILS --
370
- // the common case under a wrong key or a tampered encryptedKey -- leaves nothing behind either;
387
+ // are destroyed as soon as they stop being needed. In a `finally`, so an unwrap that fails
388
+ // (the common case under a wrong key or a tampered encryptedKey) leaves nothing behind either;
371
389
  // a wipe on the success path alone would keep the secret exactly when an attacker made it fail.
372
390
  // Only buffers allocated here are wiped: km.key, the message bytes and the returned CEK are the
373
391
  // caller's or the caller's to consume.
@@ -381,7 +399,7 @@ async function _kemriCek(ri, km) {
381
399
  // The declared cipher MODE must match the container's authentication model: an AEAD cipher belongs in
382
400
  // AuthEnvelopedData and a plain CBC cipher in EnvelopedData (RFC 5083 sec. 2.1 / RFC 5084 sec. 3). Key
383
401
  // length alone does not separate them, so without this an EnvelopedData naming AES-GCM was opened as
384
- // unauthenticated CBC while the result still reported the AEAD algorithm -- telling the caller the
402
+ // unauthenticated CBC while the result still reported the AEAD algorithm, telling the caller the
385
403
  // content was authenticated when nothing had authenticated it. The mirror case dereferenced AEAD
386
404
  // parameters the parser leaves null for a non-AEAD algorithm, faulting instead of failing closed.
387
405
  // An unresolvable OID is left to _openContent, which names it as the unsupported algorithm it is.
@@ -403,10 +421,10 @@ async function _openContent(parsed, eci, cek, ct) {
403
421
  // A null CEK (v1.5 implicit rejection) or a wrong-length CEK -> a fresh random key of the right
404
422
  // length, so the failure surfaces here as the uniform verdict, never earlier.
405
423
  //
406
- // The substitute is THIS function's allocation, so this function clears it: reassigning the
424
+ // The substitute is this function's own allocation, so this function clears it: reassigning the
407
425
  // parameter does not change the caller's variable, so the caller's `finally` would wipe the
408
426
  // original null / wrong-length value and leave the substitute behind. It is held separately
409
- // rather than wiping `cek` here, because on the normal path `cek` is the CALLER's recovered key
427
+ // and `cek` is left alone here, because on the normal path `cek` is the caller's recovered key
410
428
  // and is still in use after this returns.
411
429
  var substitute = null;
412
430
  if (cek == null || cek.length !== keyBits / 8) { substitute = nodeCrypto.randomBytes(keyBits / 8); cek = substitute; }
@@ -426,7 +444,7 @@ async function _openContent(parsed, eci, cek, ct) {
426
444
  }
427
445
  }
428
446
  function _gcmOpen(cek, nonce, ct, tag, aad, keyBits, icvLen) {
429
- // Coverage residual: unreachable via a parsed message -- the strict parser rejects any
447
+ // Coverage residual: unreachable via a parsed message, since the strict parser rejects any
430
448
  // AuthEnvelopedData whose mac length != aes-ICVlen (cms/mac-length-mismatch) before we get here.
431
449
  if (!tag || tag.length !== icvLen) throw _fail();
432
450
  var d = nodeCrypto.createDecipheriv("aes-" + keyBits + "-gcm", cek, nonce, { authTagLength: icvLen });
@@ -447,14 +465,14 @@ async function _decryptEncryptedData(parsed, km, opts) {
447
465
  var cek = guard.bytes.view(km.cek, CmsError, "cms/bad-input", "cek");
448
466
  if (cek.length !== keyBits / 8) throw _err("cms/bad-input", "the supplied cek length does not match the content algorithm");
449
467
  var iv = asn1.read.octetString(asn1.decode(alg.parameters));
450
- try { return { content: pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "cek", recipientIndex: -1, contentEncryptionAlgorithm: alg.name || alg.oid, authenticated: false }; }
468
+ try { return _originFields({ content: pbes2.cbcDecrypt(cek, iv, eci.encryptedContent, keyBits), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "cek", recipientIndex: -1, contentEncryptionAlgorithm: alg.name || alg.oid, authenticated: false }, null, null); }
451
469
  catch (_e) { throw _fail(); }
452
470
  }
453
471
  async function _decryptPbes2(parsed, eci, km, opts) {
454
472
  if (km.password == null) throw _err("cms/bad-input", "this EncryptedData needs a { password }");
455
473
  // The PBES2 parameters are attacker-controlled structure the strict parser surfaces raw (PBES2 is
456
474
  // not an AEAD it validates): parse them behind a structural guard so a malformed shape is a typed
457
- // cms/bad-input, never a raw dereference fault (the PBES2 structure is public -- not a decrypt oracle).
475
+ // cms/bad-input, never a raw dereference fault (the PBES2 structure is public, not a decrypt oracle).
458
476
  var kdf, encOid, iv, pb;
459
477
  try {
460
478
  var params = pbes2.seqChildren(eci.contentEncryptionAlgorithm.parameters, 2, "PBES2 parameters", _err, "cms");
@@ -472,14 +490,14 @@ async function _decryptPbes2(parsed, eci, km, opts) {
472
490
  if (!keyBits) throw _err("cms/unsupported-algorithm", "unsupported PBES2 content cipher " + encOid);
473
491
  // The password-derived content key is ours; the caller's password buffer is not (passwordBytes
474
492
  // passes a supplied Buffer straight through) and is left intact.
475
- // A string / Uint8Array password is encoded into a buffer THIS toolkit allocated -- a credential
476
- // copy -- so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
493
+ // A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
494
+ // copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
477
495
  // borrowed and left intact.
478
496
  var pwE = pbes2.passwordBytesOwned(km.password, _err, "cms");
479
497
  var key;
480
498
  try { key = nodeCrypto.pbkdf2Sync(pwE.bytes, pb.salt, pb.iterations, keyBits / 8, pb.prfNode); }
481
499
  finally { if (pwE.owned) guard.secret.zeroize(pwE.bytes, CmsError, "cms/bad-input", "the password encoding"); }
482
- try { return { content: pbes2.cbcDecrypt(key, iv, eci.encryptedContent, keyBits), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "password", recipientIndex: -1, contentEncryptionAlgorithm: oid.name(encOid) || encOid, authenticated: false }; }
500
+ try { return _originFields({ content: pbes2.cbcDecrypt(key, iv, eci.encryptedContent, keyBits), contentType: eci.contentType, contentTypeName: oid.name(eci.contentType) || eci.contentType, recipientType: "password", recipientIndex: -1, contentEncryptionAlgorithm: oid.name(encOid) || encOid, authenticated: false }, null, null); }
483
501
  catch (_e) { throw _fail(); }
484
502
  finally { guard.secret.zeroize(key, CmsError, "cms/bad-input", "the password-derived content-encryption key"); }
485
503
  }
@@ -487,19 +505,19 @@ async function _decryptPbes2(parsed, eci, km, opts) {
487
505
  // ---- shared helpers (mirror cms-encrypt's builders) ------------------------
488
506
  async function _aesKwUnwrap(kek, wrapped) {
489
507
  var kekKey = await subtle.importKey("raw", kek, { name: "AES-KW" }, false, ["unwrapKey"]);
490
- // Import the unwrapped key as HMAC, not AES-CBC: this function EXPORTS to raw bytes, so the CryptoKey
491
- // type is transient and the exported bytes are identical for an AES CEK -- but HMAC accepts any wrap
508
+ // Import the unwrapped key as HMAC, not AES-CBC: this function exports to raw bytes, so the CryptoKey
509
+ // type is transient and the exported bytes are identical for an AES CEK, while HMAC accepts any wrap
492
510
  // length, so an AuthenticatedData MAC key that is not 16/24/32 octets (e.g. a 64-octet HMAC-SHA-512
493
511
  // key from another implementation) is recovered instead of rejected before the MAC is even checked.
494
512
  var raw = await subtle.unwrapKey("raw", wrapped, kekKey, { name: "AES-KW" }, { name: "HMAC", hash: "SHA-256" }, true, ["sign"]);
495
513
  // exportKey allocates an ArrayBuffer holding the unwrapped key; the Buffer below is a copy of it,
496
514
  // and the original would otherwise stay readable with nothing referencing it. That is a
497
515
  // controllable allocation, not one of the runtime-internal copies the best-effort caveat covers.
498
- // Only the intermediate is wiped -- the returned Buffer IS the key the caller must use to open
499
- // the content, so wiping it here would destroy the result rather than protect it.
516
+ // Only the intermediate is wiped: the returned Buffer is the key the caller must use to open
517
+ // the content, so wiping it here would destroy the result instead of protecting it.
500
518
  var rawAb = await subtle.exportKey("raw", raw);
501
519
  try {
502
- // An explicit COPY, not Buffer.from(arrayBuffer) -- that form returns a VIEW sharing the
520
+ // An explicit copy, not Buffer.from(arrayBuffer): that form returns a view sharing the
503
521
  // ArrayBuffer's memory, so wiping the intermediate below would zero the key being returned.
504
522
  var view = new Uint8Array(rawAb);
505
523
  var out = Buffer.alloc(view.length);
@@ -522,14 +540,14 @@ function _kemOtherInfo(wrapName, kekBytes, ukm) {
522
540
  return b.sequence(kids);
523
541
  }
524
542
  // RFC 3211 double-CBC unwrap (M26): decrypt pass 2 (IV = last block of pass 1), then pass 1
525
- // (IV = first block), validate the count + complement check bytes -- uniform failure on mismatch.
543
+ // (IV = first block), validate the count + complement check bytes, with uniform failure on mismatch.
526
544
  function _pwriUnwrap(kek, wrapped, iv, keyBits) {
527
545
  var blk = 16, alg = "aes-" + keyBits + "-cbc";
528
546
  if (wrapped.length < 2 * blk || wrapped.length % blk !== 0) throw _fail();
529
547
  var n = wrapped.length;
530
548
  // pass2 = CBC(kek, iv2 = pass1[last block], pass1). Recover pass1's last block by decrypting the
531
- // last ciphertext block (ECB) and XORing the previous ciphertext block -- a standard CBC last-
532
- // block decrypt that needs no IV -- then CBC-decrypt pass2 under that recovered iv2 to get pass1.
549
+ // last ciphertext block (ECB) and XORing the previous ciphertext block, a standard CBC last-
550
+ // block decrypt that needs no IV, then CBC-decrypt pass2 under that recovered iv2 to get pass1.
533
551
  var ecb = nodeCrypto.createDecipheriv("aes-" + keyBits + "-ecb", kek, Buffer.alloc(0)); ecb.setAutoPadding(false);
534
552
  var lastDec = Buffer.concat([ecb.update(wrapped.subarray(n - blk)), ecb.final()]);
535
553
  var iv2 = Buffer.alloc(blk);
@@ -538,7 +556,7 @@ function _pwriUnwrap(kek, wrapped, iv, keyBits) {
538
556
  var pass1 = Buffer.concat([d1.update(wrapped), d1.final()]);
539
557
  var d2 = nodeCrypto.createDecipheriv(alg, kek, iv); d2.setAutoPadding(false);
540
558
  // body is the recovered plaintext block: it holds the CEK. The caller receives an independent
541
- // COPY, so the block is cleared on every exit -- including the two validation rejects, which are
559
+ // copy, so the block is cleared on every exit, including the two validation rejects, which are
542
560
  // the paths an attacker induces by tampering with the wrapped key.
543
561
  var body = Buffer.concat([d2.update(pass1), d2.final()]);
544
562
  try {
@@ -553,7 +571,7 @@ function _pwriUnwrap(kek, wrapped, iv, keyBits) {
553
571
  guard.secret.zeroizeAll([body, pass1], CmsError, "cms/bad-input", "the PWRI plaintext block");
554
572
  }
555
573
  }
556
- // Coverage residual (the unsupported-algorithm throw arm of each lookup below -- _hashW3c,
574
+ // Coverage residual (the unsupported-algorithm throw arm of each lookup below: _hashW3c,
557
575
  // _x963Hash, and _originatorSpki that follow): reachable only from a fully well-formed recipient that
558
576
  // names an inner algorithm we do not implement (a non-registry OAEP hash / kari scheme / originator
559
577
  // form). The producer never emits one; the fuzz harness (fuzz/cms-decrypt.fuzz.js) drives these arms
@@ -572,8 +590,8 @@ function _oaepHashFromParams(paramsBytes) {
572
590
  mgfHash = _hashW3c(asn1.read.oid(mg.children[1].children[0]));
573
591
  } else if (ch.tagNumber === 2) { label = asn1.read.octetString(ch.children[0].children[1]); } // pSourceAlgorithm [2]
574
592
  });
575
- // WebCrypto RSA-OAEP ties the MGF1 hash to the OAEP hash and supports only an empty label -- reject,
576
- // rather than silently ignore, any parameter set we cannot faithfully honor.
593
+ // WebCrypto RSA-OAEP ties the MGF1 hash to the OAEP hash and supports only an empty label, so any
594
+ // parameter set we cannot faithfully honor is rejected, never silently ignored.
577
595
  if (mgfHash != null && mgfHash !== hashName) throw _err("cms/unsupported-algorithm", "the OAEP MGF1 hash must equal the OAEP hash");
578
596
  if (label != null && label.length > 0) throw _err("cms/unsupported-algorithm", "a non-empty OAEP label is not supported");
579
597
  return hashName;
@@ -593,7 +611,7 @@ function _kariWrap(kea) {
593
611
  }
594
612
  function _originatorSpki(originator) {
595
613
  // originator OriginatorIdentifierOrKey CHOICE; we require originatorKey [1] (RFC 5753 MUST for
596
- // ephemeral-static). The parser surfaces { form, value:{ algorithm, publicKey } } -- reconstruct
614
+ // ephemeral-static). The parser surfaces { form, value:{ algorithm, publicKey } }, so reconstruct
597
615
  // the SPKI (SEQUENCE { algorithm, subjectPublicKey BIT STRING }) for importKey.
598
616
  if (!originator || originator.form !== "originatorKey") throw _err("cms/unsupported-algorithm", "kari requires an originatorKey (ephemeral-static ECDH)");
599
617
  var v = originator.value;
@@ -639,11 +657,11 @@ var DIGEST_WC = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
639
657
  var MAC_KEY_MIN_OCTETS = 16;
640
658
  function _isDerNull(p) { return Buffer.isBuffer(p) && p.length === 2 && p[0] === 0x05 && p[1] === 0x00; }
641
659
 
642
- // Verify a CMS AuthenticatedData (RFC 5652 sec. 9): recover the MAC key through the SAME RecipientInfo
660
+ // Verify a CMS AuthenticatedData (RFC 5652 sec. 9): recover the MAC key through the same RecipientInfo
643
661
  // unwrap decrypt uses, recompute the HMAC over the sec. 9.2 preimage (the re-tagged [2] authAttrs SET
644
- // OF when present, else the eContent value octets), constant-time-compare to the transmitted mac, and
645
- // -- when authAttrs are present -- INDEPENDENTLY confirm message-digest == digest(content) (sec. 9.3
646
- // do-not-trust-originator). The content is returned ONLY after every check passes. Every
662
+ // OF when present, else the eContent value octets), constant-time-compare to the transmitted mac, and,
663
+ // when authAttrs are present, independently confirm message-digest == digest(content) (sec. 9.3
664
+ // do-not-trust-originator). The content is returned only after every check passes. Every
647
665
  // secret-dependent failure collapses to the uniform cms/decrypt-failed (no unwrap-success oracle).
648
666
  async function _verifyAuthenticatedData(parsed, km, opts) {
649
667
  var macAlg = parsed.macAlgorithm;
@@ -674,10 +692,10 @@ async function _verifyAuthenticatedData(parsed, km, opts) {
674
692
  _assertSupported(candidates[ci].ri, km);
675
693
  var macKey = await _acquireCek(candidates[ci].ri, km, opts); // recover the MAC key (uniform failure)
676
694
  // RFC 3218 implicit rejection: _ktriCek returns null on an RSA v1.5 unwrap fault, and a hostile
677
- // ktri/pwri could convey a below-floor (128-bit) key; in EITHER case substitute a fresh random
678
- // key so the MAC verify still RUNS and fails uniformly -- never a fast-path that distinguishes an
695
+ // ktri/pwri could convey a below-floor (128-bit) key; in either case substitute a fresh random
696
+ // key so the MAC verify still runs and fails uniformly, never a fast-path that distinguishes an
679
697
  // invalid/short unwrap from a MAC mismatch (Bleichenbacher / weak-key oracle freedom).
680
- // The substitute is held SEPARATELY rather than overwriting macKey: a hostile recipient can
698
+ // The substitute is held separately, never overwriting macKey: a hostile recipient can
681
699
  // unwrap to a non-null key that is merely too short, and assigning over it would drop the only
682
700
  // reference to that recovered secret, leaving it readable while the cleanup cleared the random
683
701
  // replacement instead. Both are this module's allocations and both are cleared below.
@@ -693,14 +711,14 @@ async function _verifyAuthenticatedData(parsed, km, opts) {
693
711
  var actual = Buffer.from(await subtle.digest(mdCheck.hash, content));
694
712
  if (!actual.equals(mdCheck.declared)) throw _fail(); // sec. 9.3: recompute, never trust the originator's digest
695
713
  }
696
- return {
714
+ return _originFields({
697
715
  content: Buffer.from(content),
698
716
  contentType: parsed.encapContentInfo.eContentType, contentTypeName: oid.name(parsed.encapContentInfo.eContentType) || parsed.encapContentInfo.eContentType,
699
717
  recipientType: candidates[ci].ri.type, recipientIndex: candidates[ci].index,
700
718
  macAlgorithm: macAlg.name || macAlg.oid,
701
719
  digestAlgorithm: parsed.digestAlgorithm ? (parsed.digestAlgorithm.name || parsed.digestAlgorithm.oid) : null,
702
720
  authenticated: true,
703
- };
721
+ }, "message-authentication-key", parsed.originatorInfo);
704
722
  } finally {
705
723
  guard.secret.zeroizeAll([macKey, macSubstitute], CmsError, "cms/bad-input", "the message-authentication key");
706
724
  }
@@ -717,7 +735,7 @@ async function _verifyAuthenticatedData(parsed, km, opts) {
717
735
  // the frozen cms/unsupported-algorithm code, not a round-trip.
718
736
  // * the detached (absent eContent) reject is the deferred-with-condition path (this producer always
719
737
  // attaches the content); re-open when a detached AuthenticatedData caller appears.
720
- // * `parsed.authAttrs || []` / `parsed.recipientInfos || []` -- the parser always surfaces these as
738
+ // * `parsed.authAttrs || []` / `parsed.recipientInfos || []`: the parser always surfaces these as
721
739
  // arrays (or rejects), so the `|| []` fallbacks never fire.
722
740
  // * `oid.name(eContentType) || eContentType` fires only for an unregistered content type; `macAlg.name
723
741
  // || macAlg.oid` and `digestAlgorithm.name || .oid` never reach the `.oid` fallback because the
@@ -725,17 +743,17 @@ async function _verifyAuthenticatedData(parsed, km, opts) {
725
743
  // * the `macKey == null` half of the random-key substitution fires only for a hand-crafted RSA v1.5
726
744
  // ktri (this producer emits OAEP); its behaviour is identical to the tested below-floor path (a
727
745
  // random key -> the MAC verify fails uniformly), so the < 16 vector covers the substitution.
728
- // _normKeyDer(key) -> { der, owned } -- the recipient private key as PKCS#8 DER, and whether the
729
- // buffer is one THIS module made.
746
+ // _normKeyDer(key) -> { der, owned }: the recipient private key as PKCS#8 DER, and whether the
747
+ // buffer is one this module made.
730
748
  //
731
749
  // The distinction decides who may wipe it. A caller handing in their own Buffer keeps a live
732
750
  // reference and will use it again; wiping that would destroy the key out from under them. The other
733
- // two forms produce a NEW buffer here -- a Uint8Array is copied, a PEM string is decoded -- and that
751
+ // two forms produce a new buffer here (a Uint8Array is copied, a PEM string is decoded), and that
734
752
  // buffer is a second copy of a private key which nothing else can reach, so it lives until the
735
753
  // garbage collector happens to reuse the page unless this module clears it.
736
754
  //
737
- // Returning the flag rather than always copying keeps the caller's buffer un-duplicated: making our
738
- // own copy of every key so we could uniformly wipe it would ADD a copy of the secret to solve the
755
+ // Returning the flag, in place of always copying, keeps the caller's buffer un-duplicated: making our
756
+ // own copy of every key so we could uniformly wipe it would add a copy of the secret to solve the
739
757
  // problem of having one.
740
758
  function _normKeyDer(key) {
741
759
  if (Buffer.isBuffer(key)) return { der: key, owned: false };