@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
package/lib/cmp-verify.js CHANGED
@@ -11,12 +11,12 @@
11
11
  // dependency set (the path-validate signature engine + full path building) that pki.cmp.build never
12
12
  // pulls, and re-exports the cmp-build producing surface so the whole pki.cmp namespace is wired here.
13
13
  //
14
- // The load-bearing rule: the protection is recomputed over the EXACT ProtectedPart bytes the builder
15
- // signed -- SEQUENCE { header, body } reconstructed from the parser-surfaced RAW headerBytes / bodyBytes
16
- // (schema-cmp), NEVER a re-serialization of the decoded structs. A re-encoder that normalized a malleable
14
+ // The load-bearing rule: the protection is recomputed over the exact ProtectedPart bytes the builder
15
+ // signed, a SEQUENCE { header, body } reconstructed from the parser-surfaced raw headerBytes / bodyBytes
16
+ // (schema-cmp), never a re-serialization of the decoded structs. A re-encoder that normalized a malleable
17
17
  // interior field would run the recompute over different bytes than a strict signer covered: both a false
18
- // reject for a canonical peer AND a bypass. The signature path routes through the ONE path-validate engine
19
- // (with the EdDSA low-order-point gate) injected via setEngine -- never build's self-check, which skips it.
18
+ // reject for a canonical peer and a bypass. The signature path routes through the single path-validate engine
19
+ // (with the EdDSA low-order-point gate) injected via setEngine, never build's self-check, which skips it.
20
20
  // RFC 9810 sec. 5.1.3, algorithms RFC 9481 / RFC 9579, out-of-path signer profile RFC 9483.
21
21
 
22
22
  var asn1 = require("./asn1-der");
@@ -40,7 +40,7 @@ function _err(code, message, cause) { return new CmpError(code, message, cause);
40
40
  var NS = pkix.makeNS("cmp", CmpError, oid);
41
41
  var PBMAC1_PARAMS = pkix.pbmac1Params(NS);
42
42
  // The shared RFC 5280 sec. 4.2.1 extension-value decoders (the acme / inspect home): the signer keyUsage gate
43
- // reads through the ONE structurally-strict decoder (X.690 sec. 11.2.2 minimal NamedBitList) rather than a
43
+ // reads through the single structurally-strict decoder (X.690 sec. 11.2.2 minimal NamedBitList), not a
44
44
  // hand-rolled bit test, so a malformed KeyUsage fails the gate independently of the path validator also rejecting it.
45
45
  var _certExtDecoders = pkix.certExtensionDecoders(NS).byOid;
46
46
 
@@ -50,9 +50,9 @@ var KNOWN_VERIFY_OPTS = {
50
50
  };
51
51
 
52
52
  // PBMAC1 PBKDF2-PRF / messageAuthScheme name -> WebCrypto hash. Only SHA-256/384/512 are supported
53
- // (RFC 9481 sec. 7 mandates SHA-256; RFC 9579 sec. 7 bars a <= 160-bit digest, so hmacWithSHA1 -- and an
54
- // omitted-PRF that resolves to it -- is unsupported, never MAC-verified under a weak digest).
55
- // Keyed by the IMMUTABLE dotted OID, never the display name: pki.oid.register() can rename hmacWithSHA256/384/512,
53
+ // (RFC 9481 sec. 7 mandates SHA-256; RFC 9579 sec. 7 bars a <= 160-bit digest, so hmacWithSHA1, and an
54
+ // omitted-PRF that resolves to it, is unsupported and never MAC-verified under a weak digest).
55
+ // Keyed by the immutable dotted OID, never the display name: pki.oid.register() can rename hmacWithSHA256/384/512,
56
56
  // so a name-keyed lookup would reject a valid PBMAC1 message under a renamed registry (the immutable-OID-dispatch rule).
57
57
  var PRF_HASH = {};
58
58
  PRF_HASH[oid.byName("hmacWithSHA256")] = "SHA-256";
@@ -69,8 +69,8 @@ var PBMAC1_KEYLEN_MAX = 1024;
69
69
  // PBKDF2 PRF output length (octets) -- the size of one derived block; keyLength beyond it costs extra HMAC rounds.
70
70
  var PRF_HLEN = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
71
71
 
72
- // The legacy / KEM MAC protection OIDs (RFC 9810 sec. 5.1.3.1/.2/.4) the v1 verifier recognizes and refuses
73
- // -- build emits only PBMAC1, so the verifier rejects a legacy/KEM MAC OID rather than accept a construction
72
+ // The legacy / KEM MAC protection OIDs (RFC 9810 sec. 5.1.3.1/.2/.4) the v1 verifier recognizes and refuses.
73
+ // Build emits only PBMAC1, so the verifier rejects a legacy/KEM MAC OID and never accepts a construction
74
74
  // it does not verify (a silent accept of an unverified algorithm would be fail-open).
75
75
  var UNSUPPORTED_MAC_OIDS = {};
76
76
  ["passwordBasedMac", "dhBasedMac", "kemBasedMac"].forEach(function (n) {
@@ -133,7 +133,7 @@ function _coerce(message) {
133
133
  }
134
134
 
135
135
  // Rebuild the PKIMessage DER from a parsed object's raw slices: SEQUENCE { header, body, protection [0]
136
- // EXPLICIT BIT STRING OPTIONAL, extraCerts [1] EXPLICIT SEQUENCE OPTIONAL } -- the exact inverse of the
136
+ // EXPLICIT BIT STRING OPTIONAL, extraCerts [1] EXPLICIT SEQUENCE OPTIONAL }, the exact inverse of the
137
137
  // parser's surfaced fields (cmp-build.js assembles the identical shape). Re-parsing this authenticates every
138
138
  // field against the raw bytes, discarding any mutated decoded field the caller may have carried.
139
139
  function _reassemble(m) {
@@ -166,7 +166,7 @@ function _certSki(parsed) {
166
166
  function _subjectDn(parsed) { return parsed.subject.dn || null; }
167
167
 
168
168
  // The decoded GeneralName nodes of a parsed certificate's subjectAltName (empty if none / malformed).
169
- // x509.parse surfaces `value` as the unwrapped extnValue content -- for SAN that is the SEQUENCE OF GeneralName.
169
+ // x509.parse surfaces `value` as the unwrapped extnValue content; for SAN that is the SEQUENCE OF GeneralName.
170
170
  function _sanGeneralNames(parsed) {
171
171
  var sanOid = oid.byName("subjectAltName");
172
172
  var exts = parsed.extensions;
@@ -189,7 +189,7 @@ function _sanGeneralNames(parsed) {
189
189
  // it equals the POPULATED subject DN (a [4] directoryName under the RFC 5280 sec. 7.1 canonical comparison) OR
190
190
  // a subjectAltName GeneralName entry -- checked regardless of whether the subject is empty, so a certificate
191
191
  // that carries both a subject and a SAN can be identified by either, and an empty subject (RFC 5280 sec.
192
- // 4.1.2.6 requires a critical SAN) is bound to its SAN identity rather than accepted with an anonymous sender.
192
+ // 4.1.2.6 requires a critical SAN) is bound to its SAN identity, never accepted with an anonymous sender.
193
193
  function _senderBoundToCert(sender, parsed) {
194
194
  if (!sender || !sender.bytes) return false;
195
195
  var subjectRdns = parsed.subject.rdns;
@@ -203,7 +203,7 @@ function _senderBoundToCert(sender, parsed) {
203
203
  }
204
204
 
205
205
  // GeneralName equality (RFC 5280 sec. 7) for the sender <-> SAN binding, applying the per-type comparison
206
- // rules rather than a universal byte compare: a dNSName [2] case-insensitively (sec. 4.2.1.6 / 7.2), an
206
+ // rules in place of a universal byte compare: a dNSName [2] case-insensitively (sec. 4.2.1.6 / 7.2), an
207
207
  // rfc822Name [1] with a case-insensitive domain (sec. 7.5), a uniformResourceIdentifier [6] with a
208
208
  // case-insensitive scheme + host (sec. 4.2.1.6 / RFC 3986 sec. 6.2.2.1), a directoryName [4] under the sec.
209
209
  // 7.1 canonical DN comparison, and every other type by exact DER (fail-closed). `sender` is the parsed [n]
@@ -213,9 +213,9 @@ function _generalNameMatches(sender, sanNode) {
213
213
  if (sanNode.tagClass === "context") {
214
214
  if (sanNode.tagNumber === 2 && sanNode.content) { // dNSName IA5String
215
215
  var dnsSan = sanNode.content.toString("latin1"), dnsSender = String(sender.value);
216
- // Case-fold ONLY a well-formed dNSName (RFC 5280 sec. 4.2.1.6 / RFC 1034, via the shared validator): a
216
+ // Case-fold only a well-formed dNSName (RFC 5280 sec. 4.2.1.6 / RFC 1034, via the shared validator): a
217
217
  // malformed name (an empty label like "victim..com", a leading/trailing dot, bad LDH) is compared
218
- // byte-exact rather than folded, so it cannot bind a byte-distinct identity.
218
+ // byte-exact and never folded, so it cannot bind a byte-distinct identity.
219
219
  if (pkix.dnsNameProblem(dnsSan) !== null || pkix.dnsNameProblem(dnsSender) !== null) return dnsSan === dnsSender;
220
220
  return dnsSender.toLowerCase() === dnsSan.toLowerCase();
221
221
  }
@@ -256,10 +256,10 @@ function _mailboxSplit(s) {
256
256
  sep = s.indexOf("@"); // an unquoted (Dot-string) local-part cannot contain a raw "@" -> exactly one total
257
257
  if (sep < 0 || sep !== s.lastIndexOf("@")) return -1;
258
258
  }
259
- // The Domain that follows the separator must be a well-formed FQDN (RFC 1034, via the shared validator) -- an
259
+ // The Domain that follows the separator must be a well-formed FQDN (RFC 1034, via the shared validator): an
260
260
  // empty / empty-label / extra-"@" domain is malformed. An unquoted Local-part must be a Dot-string: non-empty,
261
261
  // no leading/trailing dot, no empty atom ("a..b"). A quoted Local-part is already balanced above. A malformed
262
- // mailbox returns -1 so _rfc822Equal compares it byte-exact rather than case-folding a malformed domain.
262
+ // mailbox returns -1 so _rfc822Equal compares it byte-exact and never case-folds a malformed domain.
263
263
  var local = s.slice(0, sep), domain = s.slice(sep + 1);
264
264
  if (pkix.dnsNameProblem(domain) !== null) return -1;
265
265
  // An unquoted (Dot-string) Local-part is atext atoms joined by single dots (RFC 5321 sec. 4.1.2): every
@@ -275,7 +275,21 @@ function _mailboxSplit(s) {
275
275
  function _rfc822Equal(a, b) {
276
276
  var ai = _mailboxSplit(a), bi = _mailboxSplit(b);
277
277
  if (ai < 0 || bi < 0) return a === b;
278
- return a.slice(0, ai) === b.slice(0, bi) && a.slice(ai + 1).toLowerCase() === b.slice(bi + 1).toLowerCase();
278
+ return a.slice(0, ai) === b.slice(0, bi) && _lowerAsciiDomain(a.slice(ai + 1)) === _lowerAsciiDomain(b.slice(bi + 1));
279
+ }
280
+
281
+ // sec. 7.5 authorizes a case-insensitive ASCII comparison of the host-part, and nothing
282
+ // wider. String.toLowerCase() is Unicode-aware and folds well beyond A-Z: U+212A KELVIN
283
+ // SIGN lowercases to "k", so a host carrying it would compare EQUAL to the ASCII spelling
284
+ // and two distinct domains would read as one identity. Fold only A-Z and leave every other
285
+ // octet alone, so a non-ASCII host can differ from an ASCII one but never collide with it.
286
+ function _lowerAsciiDomain(h) {
287
+ var out = "";
288
+ for (var i = 0; i < h.length; i++) {
289
+ var c = h.charCodeAt(i);
290
+ out += (c >= 0x41 && c <= 0x5a) ? String.fromCharCode(c + 32) : h.charAt(i);
291
+ }
292
+ return out;
279
293
  }
280
294
 
281
295
  // RFC 5280 sec. 4.2.1.6 / RFC 3986 sec. 6.2.2.1 URI comparison: the scheme and the authority host are
@@ -284,15 +298,15 @@ function _rfc822Equal(a, b) {
284
298
  // -- never normalize an input we cannot confidently parse into a false identity match).
285
299
  function _normalizeUri(u) {
286
300
  // RFC 3986 syntax: every character must be a URI character and every "%" must introduce a valid pct-encoding
287
- // (%HH). A URI with an out-of-charset byte or a malformed percent-escape ("%zz") in ANY component (path,
288
- // query, fragment, userinfo) is not normalized -- return null so it is compared byte-exact.
301
+ // (%HH). A URI with an out-of-charset byte or a malformed percent-escape ("%zz") in any component (path,
302
+ // query, fragment, userinfo) is not normalized; return null so it is compared byte-exact.
289
303
  if (!/^[A-Za-z0-9._~:/?#@!$&'()*+,;=%[\]-]*$/.test(u) || /%(?![0-9A-Fa-f]{2})/.test(u)) return null;
290
304
  var m = /^([a-zA-Z][a-zA-Z0-9+.-]*):(.*)$/.exec(u); // scheme ":" rest (RFC 3986 sec. 3.1)
291
305
  if (!m) return null;
292
306
  var scheme = m[1].toLowerCase();
293
307
  var rest = m[2];
294
308
  // A URI SAN identity is host-based (RFC 5280 sec. 4.2.1.6): an authority-free URI (no "//", e.g. a URN) has
295
- // no host to anchor the comparison, so compare it byte-exact rather than case-fold its scheme -- the shared
309
+ // no host to anchor the comparison, so compare it byte-exact and never case-fold its scheme; the shared
296
310
  // GeneralName parser only enforces printable IA5, not a present authority.
297
311
  if (rest.slice(0, 2) !== "//") return null;
298
312
  var body = rest.slice(2);
@@ -306,7 +320,7 @@ function _normalizeUri(u) {
306
320
  var userinfo = at < 0 ? "" : authority.slice(0, at + 1);
307
321
  var hostport = at < 0 ? authority : authority.slice(at + 1);
308
322
  // Split the host from an optional ":port": an IPv6 literal "[...]" carries the port after the "]", otherwise
309
- // the port follows the FIRST ":". Case-fold ONLY the host (RFC 3986 sec. 6.2.2.1); the port stays byte-exact
323
+ // the port follows the first ":". Case-fold only the host (RFC 3986 sec. 6.2.2.1); the port stays byte-exact
310
324
  // so a malformed non-numeric port (":ADMIN" vs ":admin") is never folded into a false identity match.
311
325
  var host, port;
312
326
  if (hostport.charAt(0) === "[") {
@@ -317,13 +331,13 @@ function _normalizeUri(u) {
317
331
  var ci = hostport.indexOf(":");
318
332
  host = ci < 0 ? hostport : hostport.slice(0, ci); port = ci < 0 ? "" : hostport.slice(ci);
319
333
  }
320
- // The port must be numeric (RFC 3986 sec. 3.2.3 port = *DIGIT). A non-numeric port is a MALFORMED authority:
321
- // return null so the caller compares the raw values exactly, rather than case-fold the host of an input the
334
+ // The port must be numeric (RFC 3986 sec. 3.2.3 port = *DIGIT). A non-numeric port is a malformed authority:
335
+ // return null so the caller compares the raw values exactly and never case-folds the host of an input the
322
336
  // normalizer cannot confidently parse (which would bind two byte-distinct malformed authorities).
323
337
  if (port !== "" && !/^:[0-9]*$/.test(port)) return null;
324
338
  // The authority host must be a well-formed FQDN or IP literal (RFC 5280 sec. 4.2.1.6 requires a URI SAN
325
339
  // authority to carry an FQDN/IP host): an empty, malformed ("Victim..COM"), or bad IPv6-literal host is
326
- // compared byte-exact rather than case-folded, since the GeneralName parser only enforces printable IA5.
340
+ // compared byte-exact and never case-folded, since the GeneralName parser only enforces printable IA5.
327
341
  var hostOk = host.charAt(0) === "["
328
342
  ? (host.charAt(host.length - 1) === "]" && ipUtils.expandIpv6Hex(host.slice(1, -1)) !== null)
329
343
  : (pkix.dnsNameProblem(host) === null);
@@ -337,8 +351,8 @@ function _uriEqual(a, b) {
337
351
  }
338
352
 
339
353
  // M11/M14: resolve the signature-protection signer certificate. An explicit opts.signerCert wins (with the
340
- // senderKID SKI binding when present); else `extra` (the ALREADY dedup+validity+count-bounded extraCerts) by
341
- // senderKID, else RFC 9483 sec. 3.3 -- extraCerts[0] IS the protection certificate. `extra` is bounded BEFORE
354
+ // senderKID SKI binding when present); else `extra` (the already dedup+validity+count-bounded extraCerts) by
355
+ // senderKID, else RFC 9483 sec. 3.3, where extraCerts[0] is the protection certificate. `extra` is bounded before
342
356
  // this search so an unsigned flood cannot force unbounded X.509 parsing. Returns { der, spki, subject, parsed }
343
357
  // or null (never verify against an unauthenticated key).
344
358
  function _resolveSignerCert(m, opts, extra) {
@@ -359,8 +373,8 @@ function _resolveSignerCert(m, opts, extra) {
359
373
  // would change verdict.signer.cert/.spki). Parse it SEPARATELY: a corrupt / mistyped certificate is a
360
374
  // deployment error that THROWS cmp/bad-input, never a routine signer-cert-not-found verdict indistinguishable
361
375
  // from a message that simply omitted its signer (the nullable path is reserved for message candidates).
362
- // The caller EXPLICITLY selected this certificate, so the senderKID (a hint for narrowing a candidate search)
363
- // is NOT applied here -- the protection signature verification against this cert's key is the real gate; a
376
+ // The caller explicitly selected this certificate, so the senderKID (a hint for narrowing a candidate search)
377
+ // is not applied here: the protection signature verification against this cert's key is the real gate; a
364
378
  // valid message from a signer certificate that omits an SKI still resolves to its exact opts.signerCert.
365
379
  var scDer = Buffer.from(_certDer(opts.signerCert, "opts.signerCert"));
366
380
  var scParsed;
@@ -402,9 +416,9 @@ function _keyUsageAllowsSigning(parsed) {
402
416
  for (var i = 0; i < exts.length; i++) {
403
417
  if (exts[i].oid !== kuOid) continue;
404
418
  var ku;
405
- // Decode through the shared keyUsage decoder -- it enforces the RFC 5280 sec. 4.2.1.3 BIT STRING form + the
419
+ // Decode through the shared keyUsage decoder: it enforces the RFC 5280 sec. 4.2.1.3 BIT STRING form + the
406
420
  // X.690 sec. 11.2.2 minimal NamedBitList rule (rejecting a non-minimal 03 02 00 80). A malformed KeyUsage
407
- // fails this gate rather than a hand-rolled bit test authorizing it and leaning on path validation to reject it.
421
+ // fails this gate, so no hand-rolled bit test authorizes it and leans on path validation to reject it.
408
422
  try { ku = _certExtDecoders[kuOid](exts[i].value); }
409
423
  catch (_e) { return false; }
410
424
  return ku.digitalSignature === true;
@@ -430,7 +444,7 @@ async function _verifyMac(m, protectedPart, protectionAlg, protection, opts) {
430
444
  if (!prfHash) return _fail(m, "mac", protectionAlg, "cmp/unsupported-algorithm", "unsupported PBMAC1 PBKDF2 PRF " + JSON.stringify(kdf.prfName) + " (SHA-256/384/512 only; RFC 9481 sec. 7, RFC 9579 sec. 7)");
431
445
  if (!macHash) return _fail(m, "mac", protectionAlg, "cmp/unsupported-algorithm", "unsupported PBMAC1 messageAuthScheme " + JSON.stringify(params.schemeName) + " (SHA-256/384/512 only)");
432
446
 
433
- // M18: bound the attacker-controlled work factors BEFORE deriving (CWE-834/400). A config-tier throw.
447
+ // M18: bound the attacker-controlled work factors before deriving (CWE-834/400). A config-tier throw.
434
448
  _capWork(kdf.iterationCount, kdf.salt, kdf.keyLength, prfHash, opts);
435
449
 
436
450
  // M19: recompute the MAC over the reconstructed ProtectedPart and compare in constant time.
@@ -465,16 +479,16 @@ function _capWork(iterationCount, salt, keyLength, prfHash, opts) {
465
479
  if (iterationCount * blocks > cap) throw _err("cmp/bad-input", "the PBMAC1 combined work (iterationCount " + iterationCount + " x " + blocks + " derived blocks) exceeds the cap " + cap);
466
480
  }
467
481
 
468
- // M9-M14 + M20-M22: verify signature protection, then (with a trust store) FULL out-of-path cert validation.
482
+ // M9-M14 + M20-M22: verify signature protection, then (with a trust store) full out-of-path cert validation.
469
483
  async function _verifySignature(m, protectedPart, protectionAlg, protection, opts) {
470
- // Bound the UNSIGNED extraCerts ONCE (dedup + drop non-certificates + cap + scan cap) before either the
484
+ // Bound the unsigned extraCerts once (dedup + drop non-certificates + cap + scan cap) before either the
471
485
  // senderKID signer search or path building, so a hostile peer padding extraCerts cannot force unbounded
472
486
  // X.509 parsing or a candidate-pool overflow.
473
487
  var extra = _boundExtraCerts(m.extraCerts);
474
488
  var signer = _resolveSignerCert(m, opts, extra);
475
489
  if (!signer) return _fail(m, "signature", protectionAlg, "cmp/signer-cert-not-found", "no signer certificate resolved (opts.signerCert, senderKID, or extraCerts)");
476
490
 
477
- // M2/M9: verify over the reconstructed ProtectedPart through the ONE path-validate engine (the EdDSA
491
+ // M2/M9: verify over the reconstructed ProtectedPart through the single path-validate engine (the EdDSA
478
492
  // low-order-point gate + the sig-OID<->key-OID algorithm-confusion gate apply); fail-closed to false.
479
493
  var ok = await _engine.verifyWithSpki(protectionAlg, protection.bytes, signer.spki, protectedPart);
480
494
  if (ok !== true) return _fail(m, "signature", protectionAlg, "cmp/protection-failed", "the protection signature does not verify over the ProtectedPart under the declared protectionAlg", signer);
@@ -487,18 +501,18 @@ async function _verifySignature(m, protectedPart, protectionAlg, protection, opt
487
501
  return _fail(m, "signature", protectionAlg, "cmp/sender-mismatch", "the header sender field does not match the signer certificate subject, or (for an empty subject) a subjectAltName entry (RFC 9483 sec. 3.1, RFC 5280 sec. 7.1)", signer);
488
502
  }
489
503
 
490
- // A failed opt-in echo check is a REJECTION verdict (valid:false), consistent with the MAC path -- a caller
504
+ // A failed opt-in echo check is a rejection verdict (valid:false), consistent with the MAC path: a caller
491
505
  // that requested a transactionID / recipNonce binding must not see valid:true when it does not hold.
492
506
  var hc = _headerChecks(m, opts);
493
507
  if (hc) return _fail(m, "signature", protectionAlg, hc.code, hc.reason, signer);
494
508
 
495
509
  // M5/M12: with no trust store the verdict is crypto-only (trusted:false) and the signer is surfaced for
496
- // the caller to anchor. With a trust store the signer cert gets the FULL RFC 5280 sec. 6.1 path gates.
510
+ // the caller to anchor. With a trust store the signer cert gets the full RFC 5280 sec. 6.1 path gates.
497
511
  if (opts.trustAnchors == null) return _ok(m, "signature", protectionAlg, false, signer);
498
512
  var trust = await _chainSigner(signer, m, opts, extra);
499
- // Surface the ACTUAL validated chain (signer + the intermediates path.build used) on a trusted verdict, so a
500
- // caller can cache exactly the certificates that established trust rather than the UNSIGNED extraCerts a peer
501
- // can pad -- an appended-but-unused certificate never enters this chain.
513
+ // Surface the chain that actually validated (signer + the intermediates path.build used) on a trusted verdict,
514
+ // so a caller can cache exactly the certificates that established trust, not the unsigned extraCerts a peer
515
+ // can pad: an appended-but-unused certificate never enters this chain.
502
516
  if (trust.chain) signer.chain = trust.chain;
503
517
  return _verdict(m, "signature", protectionAlg, true, trust.trusted, trust.trusted ? null : "cmp/untrusted-signer", trust.reason, signer);
504
518
  }
@@ -526,26 +540,26 @@ async function _chainSigner(signer, m, opts, extra) {
526
540
  if (!_keyUsageAllowsSigning(signer.parsed)) {
527
541
  return { trusted: false, reason: "the signer certificate keyUsage does not assert digitalSignature (RFC 9483 sec. 3.2)" };
528
542
  }
529
- // Validate the signer path at a TRUSTED current time by default -- NOT the message's self-asserted
543
+ // Validate the signer path at a trusted current time by default, never the message's self-asserted
530
544
  // messageTime, which the sender controls: a holder of a now-expired but once-valid signer certificate
531
545
  // could otherwise backdate messageTime into the certificate's validity window and be reported trusted.
532
- // A caller doing historical verification opts into a specific instant via opts.time; a falsy but PRESENT
533
- // opts.time (0 / false / "") is NOT silently replaced -- it reaches path.build and is rejected as bad-input.
546
+ // A caller doing historical verification opts into a specific instant via opts.time; a falsy but present
547
+ // opts.time (0 / false / "") is never silently replaced: it reaches path.build and is rejected as bad-input.
534
548
  var time = opts.time != null ? opts.time : new Date();
535
549
  var anchors = _certList(opts.trustAnchors);
536
550
  // `extra` is the already dedup + validity + count-bounded extraCerts (unsigned pool material), so a flood
537
551
  // of duplicate / junk / non-certificate entries cannot exhaust the candidate-search budget or raise a
538
- // path/bad-input from a malformed pool certificate. Add it only up to the path-builder pool ceiling AFTER
552
+ // path/bad-input from a malformed pool certificate. Add it only up to the path-builder pool ceiling, after
539
553
  // the caller's intermediates, so unsigned extraCerts can never push a valid caller configuration over the
540
554
  // ceiling (if the caller's intermediates alone exceed it, that is a genuine config error path.build reports).
541
555
  var pool = _certList(opts.intermediates);
542
556
  var room = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES - pool.length;
543
557
  if (room > 0) {
544
558
  // Drop the signer leaf (already passed to build as the path TARGET) and any extraCert duplicating a caller
545
- // intermediate BEFORE truncating to the remaining slots, so a tight ceiling spends those slots on genuinely
546
- // useful embedded issuers rather than a redundant copy of the signer or a duplicate the pool already holds.
559
+ // intermediate before truncating to the remaining slots, so a tight ceiling spends those slots on genuinely
560
+ // useful embedded issuers, not on a redundant copy of the signer or a duplicate the pool already holds.
547
561
  // The identity is the canonical tbs+signature key so a Buffer extraCert dedups against a caller intermediate
548
- // supplied either as raw bytes OR as an already-parsed certificate object (path.build accepts both).
562
+ // supplied either as raw bytes or as an already-parsed certificate object (path.build accepts both).
549
563
  var seen = Object.create(null);
550
564
  var sk = _certKey(signer.parsed); if (sk) seen[sk] = 1;
551
565
  pool.forEach(function (c) { var k = _certKey(c); if (k) seen[k] = 1; });
@@ -560,9 +574,9 @@ async function _chainSigner(signer, m, opts, extra) {
560
574
  // res.path is the ordered certificates path.build assembled (signer + the intermediates it used), as PARSED
561
575
  // objects whose byte fields are subarrays of the (possibly multi-MB) response allocation. Map each back to its
562
576
  // source DER (the signer, or a DER pool entry) and return an INDEPENDENT copy, so a caller caching the chain
563
- // holds standalone buffers -- never slices pinning the whole response. Only certs on the trusted path enter it,
577
+ // holds standalone buffers, never slices pinning the whole response. Only certs on the trusted path enter it,
564
578
  // so unsigned extraCerts padding is excluded. (A path cert sourced from an already-parsed intermediate carries
565
- // no DER to copy and is omitted; for a Buffer/DER pool -- the cmp.session case -- the chain is complete.)
579
+ // no DER to copy and is omitted; for a Buffer/DER pool, the cmp.session case, the chain is complete.)
566
580
  var byKey = Object.create(null);
567
581
  var skey = _certKey(signer.parsed); if (skey) byKey[skey] = signer.der;
568
582
  pool.forEach(function (c) { if (Buffer.isBuffer(c) || c instanceof Uint8Array) { var k = _certKey(c); if (k && !byKey[k]) byKey[k] = c; } });
@@ -570,7 +584,7 @@ async function _chainSigner(signer, m, opts, extra) {
570
584
  return { trusted: true, reason: null, chain: chain };
571
585
  } catch (e) {
572
586
  // A config-tier fault from path.build (an invalid opts.time, an empty / malformed trustAnchors or
573
- // intermediate) is a DEPLOYMENT error, not an untrusted signer -- rethrow it as cmp/bad-input so it is
587
+ // intermediate) is a deployment error, not an untrusted signer, so rethrow it as cmp/bad-input and it is
574
588
  // not masked as a routine trust failure (the API contract: a bad required opt throws). A genuine
575
589
  // no-assemblable-path / validation failure collapses to an untrusted-signer verdict.
576
590
  if (e && e.code === "path/bad-input") {
@@ -643,7 +657,7 @@ async function _verify(message, opts) {
643
657
  return _fail(m, null, protectionAlg, "cmp/no-protection", "the PKIMessage carries no protection (RFC 9810 sec. 5.1.3); an unprotected message is never verified");
644
658
  }
645
659
 
646
- // M2: reconstruct the ProtectedPart from the RAW surfaced slices -- byte-identical to what build signed,
660
+ // M2: reconstruct the ProtectedPart from the raw surfaced slices, byte-identical to what build signed,
647
661
  // never a re-serialization of the decoded header/body structs.
648
662
  var protectedPart = b.sequence([b.raw(m.headerBytes), b.raw(m.bodyBytes)]);
649
663
 
@@ -678,7 +692,7 @@ async function _verify(message, opts) {
678
692
  * @related pki.cmp.build, pki.schema.cmp.parse
679
693
  * @defends cmp-unverified-protection (CWE-347), cmp-mac-timing (CWE-208)
680
694
  *
681
- * Verify the protection on an incoming RFC 9810 CMP `PKIMessage` -- the verify-inverse of
695
+ * Verify the protection on an incoming RFC 9810 CMP `PKIMessage`, the verify-inverse of
682
696
  * `pki.cmp.build`. `message` is a raw DER `Buffer`, a PEM `CMP` string, or an already-parsed
683
697
  * `pki.schema.cmp.parse` result (the protection is always recomputed from the parser-surfaced raw
684
698
  * `headerBytes` / `bodyBytes`, so a mutated display field on a parsed object cannot desync the crypto).
@@ -693,7 +707,7 @@ async function _verify(message, opts) {
693
707
  * protection is cryptographically intact under the declared algorithm; `trusted` is whether a MAC secret
694
708
  * matched or a signature signer certificate chained to a supplied trust anchor. On a trusted signature
695
709
  * verdict `signer.chain` is the validated certificate path as independent DER buffers (the signer plus the
696
- * intermediates that chained it to the anchor) -- the certificates actually used, never the unsigned
710
+ * intermediates that chained it to the anchor): the certificates actually used, never the unsigned
697
711
  * `extraCerts` a peer can pad, and never a slice pinning the response allocation. A
698
712
  * well-formed but unverifiable message is a `{ valid: false }` verdict carrying a `cmp/*` code, not a throw;
699
713
  * only malformed input (a non-PKIMessage, a bad required opt, a flavor/credential mismatch) throws a typed `CmpError`.
@@ -706,11 +720,11 @@ async function _verify(message, opts) {
706
720
  * (RFC 5280 sec. 6.1 plus the `keyUsage.digitalSignature` gate) to report `trusted`; absent -> the
707
721
  * verdict is crypto-only (`trusted: false`) and the signer certificate is surfaced for the caller to
708
722
  * anchor. The signature verify never routes through build's self-check (which skips the EdDSA
709
- * low-order-point gate) -- it uses the same engine `pki.crl.verify` / `pki.ocsp.verify` do.
723
+ * low-order-point gate); it uses the same engine `pki.crl.verify` / `pki.ocsp.verify` do.
710
724
  * - `intermediates` (Buffer|Buffer[]|PEM) -- extra untrusted pool certificates for path building
711
725
  * (`extraCerts` are added automatically, as untrusted pool material).
712
726
  * - `time` (Date) -- the validity instant for path validation. Defaults to the current time (the message's
713
- * self-asserted `messageTime` is NOT trusted for this); pass an explicit instant for historical verification.
727
+ * self-asserted `messageTime` is not trusted for this); pass an explicit instant for historical verification.
714
728
  * - `transactionID` (Buffer) -- opt-in: require `header.transactionID` to equal it (response-echo defense).
715
729
  * - `expectRecipNonce` (Buffer) -- opt-in: require `header.recipNonce` to echo the sent sender nonce.
716
730
  * - `revocationChecker` -- forwarded to `pki.path.validate` when chaining the signer certificate.
@@ -12,7 +12,7 @@
12
12
  // absent-or-NULL params (RFC 3274 sec. 2), and BOUNDED-inflates the eContent. There is NO crypto
13
13
  // here -- CompressedData provides no integrity / confidentiality / authentication (RFC 8551 sec.
14
14
  // 2.4.5); it is purely a size transform. The one load-bearing defense is the decompression-bomb
15
- // cap: inflate stops at C.LIMITS.COMPRESS_MAX_BYTES BEFORE the output is materialized (a
15
+ // cap: inflate stops at C.LIMITS.COMPRESS_MAX_BYTES before the output is materialized (a
16
16
  // resource-exhaustion defense, CWE-409), and every zlib fault collapses to a uniform typed error.
17
17
 
18
18
  var zlib = require("zlib");
@@ -70,7 +70,7 @@ async function decompress(input, opts) {
70
70
  if (parsed.contentTypeName !== "compressedData") throw _err("cms/unsupported-content-type", "input is not a CMS CompressedData (got " + parsed.contentTypeName + ")");
71
71
  var alg = parsed.compressionAlgorithm;
72
72
  if (alg.oid !== OID_ZLIB) throw _err("cms/unsupported-algorithm", "unsupported compressionAlgorithm " + (alg.name || alg.oid) + " (only id-alg-zlibCompress, RFC 3274 sec. 2)");
73
- // RFC 3274 sec. 2: the parameters SHOULD be omitted but MAY be NULL -- reject any other encoding.
73
+ // RFC 3274 sec. 2: the parameters SHOULD be omitted but MAY be NULL. Reject any other encoding.
74
74
  if (alg.parameters != null && Buffer.compare(alg.parameters, NULL_PARAMS_DER) !== 0) throw _err("cms/bad-algorithm-parameters", "id-alg-zlibCompress parameters must be absent or NULL (RFC 3274 sec. 2)");
75
75
  var eci = parsed.encapContentInfo;
76
76
  if (eci.eContent == null) throw _err("cms/no-encapsulated-content", "the CompressedData carries no encapsulated content (a detached CompressedData cannot be decompressed)");
@@ -92,12 +92,12 @@ function _toDer(input) {
92
92
  }
93
93
 
94
94
  // Inflate the RFC 1950 ZLIB stream through the shared bounded-decompression guard, which applies
95
- // the output cap at the decompressor (so a bomb is refused mid-stream rather than after it is
96
- // allocated) and requires the whole octet string to be exactly one compressed frame (so trailing
97
- // bytes cannot give one content a second encoding). This module keeps its own verdict codes: a cap
98
- // breach is cms/decompress-too-large, every other fault the uniform cms/decompress-failed.
95
+ // the output cap at the decompressor (so a bomb is refused mid-stream, before it is allocated) and
96
+ // requires the whole octet string to be exactly one compressed frame (so trailing bytes cannot give
97
+ // one content a second encoding). This module keeps its own verdict codes: a cap breach is
98
+ // cms/decompress-too-large, every other fault the uniform cms/decompress-failed.
99
99
  //
100
- // `_err` is a FACTORY, and the guard calls it as one -- handing it CmsError instead would throw
100
+ // `_err` is a factory and the guard calls it as one. Handing it CmsError instead would throw
101
101
  // "class cannot be invoked without new" on the rejection path, turning a refusal into a crash.
102
102
  function _inflateBounded(stream, cap) {
103
103
  var view = guard.bytes.view(stream, CmsError, "cms/decompress-failed", "the compressed content");