@blamejs/pki 0.5.6 → 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 +419 -378
  2. package/MIGRATING.md +65 -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 +24 -20
  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 +104 -104
  11. package/lib/cmc-verify.js +108 -32
  12. package/lib/cmp-build.js +31 -26
  13. package/lib/cmp-session.js +74 -72
  14. package/lib/cmp-verify.js +72 -58
  15. package/lib/cms-compress.js +8 -9
  16. package/lib/cms-decrypt.js +92 -76
  17. package/lib/cms-encrypt.js +33 -34
  18. package/lib/cms-sign.js +100 -54
  19. package/lib/cms-verify.js +141 -82
  20. package/lib/composite-sig.js +13 -13
  21. package/lib/constants.js +4 -4
  22. package/lib/crl-sign.js +31 -25
  23. package/lib/crl-verify.js +7 -6
  24. package/lib/crmf-sign.js +19 -15
  25. package/lib/csr-sign.js +13 -9
  26. package/lib/ct.js +37 -37
  27. package/lib/edwards-point.js +7 -7
  28. package/lib/est.js +103 -59
  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 +378 -15
  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 +144 -75
  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 +32 -20
  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 +71 -56
  63. package/lib/pki-build.js +23 -22
  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 +169 -36
  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 +19 -16
  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 +22 -18
  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 +21 -20
  99. package/lib/webauthn-mds.js +67 -67
  100. package/lib/webauthn.js +34 -34
  101. package/lib/webcrypto.js +15 -15
  102. package/lib/x509-sign.js +24 -15
  103. package/package.json +3 -2
  104. package/sbom.cdx.json +6 -6
package/lib/cms-verify.js CHANGED
@@ -16,9 +16,9 @@
16
16
  * detached content, single and multiple signers, and RSA / RSASSA-PSS / ECDSA / EdDSA, the
17
17
  * post-quantum ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814),
18
18
  * and composite ML-DSA (pairing ML-DSA with a traditional RSA / ECDSA / EdDSA, accepted only when
19
- * BOTH components verify -- draft-ietf-lamps-cms-composite-sigs) are covered. It reports a
19
+ * both components verify, per draft-ietf-lamps-cms-composite-sigs) are covered. It reports a
20
20
  * per-signer verdict;
21
- * it does NOT chain the signer certificate to a trust anchor -- that is the caller's step through
21
+ * it does not chain the signer certificate to a trust anchor, which is the caller's step through
22
22
  * `pki.path.validate`.
23
23
  * @spec RFC 5652
24
24
  * @spec RFC 9882
@@ -63,11 +63,11 @@ var DIGEST_HASH = {
63
63
  // The SIGNATURE hash a bare-key-OID scheme (rsaEncryption, ecPublicKey) inherits from the
64
64
  // SignerInfo digestAlgorithm. Deliberately a SEPARATE table from DIGEST_HASH, and deliberately
65
65
  // without the extendable-output functions: RFC 8702 sec. 3.2 gives RSASSA-PKCS1-v1_5-with-SHAKE
66
- // and ECDSA-with-SHAKE their OWN signature OIDs and never pairs a bare key OID with a SHAKE
66
+ // and ECDSA-with-SHAKE their own signature OIDs and never pairs a bare key OID with a SHAKE
67
67
  // digestAlgorithm, so that combination is non-conformant and must keep its precise refusal.
68
68
  // Folding the two roles into one table would resolve a signature hash for it instead, letting
69
- // it past the fail-closed gate below and into the engine, where it surfaces as a relabeled
70
- // foreign fault rather than this module's own unsupported-algorithm verdict.
69
+ // it past the fail-closed gate below and into the engine, where the caller gets a relabeled
70
+ // foreign fault and loses this module's own unsupported-algorithm verdict.
71
71
  var SIG_HASH = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
72
72
  // Is `name` a message-digest algorithm this verifier supports?
73
73
  function _supportedDigest(name) { return !!DIGEST_HASH[name]; }
@@ -86,7 +86,7 @@ var SIG_SCHEME = {
86
86
  rsassaPss: { kind: "rsapss" },
87
87
  ecPublicKey: { kind: "ec", params: "absent" },
88
88
  // One-shot families (EdDSA, ML-DSA): the same OID identifies the key and the signature, so the
89
- // signer cert SPKI algorithm OID MUST equal the signatureAlgorithm OID -- `sameKeyOid` enables
89
+ // signer cert SPKI algorithm OID MUST equal the signatureAlgorithm OID. `sameKeyOid` enables
90
90
  // that agreement check (RFC 8410 / RFC 9882; enforced in _verifyAgainstCandidates).
91
91
  Ed25519: { kind: "eddsa", name: "Ed25519", params: "absent", sameKeyOid: true },
92
92
  Ed448: { kind: "eddsa", name: "Ed448", params: "absent", sameKeyOid: true },
@@ -104,7 +104,7 @@ var SIG_SCHEME = {
104
104
  };
105
105
  // SLH-DSA (RFC 9814): the twelve pure FIPS 205 sets, seeded like webcrypto's SLH_DSA_NODE. Each is
106
106
  // a one-shot family (kind "slhdsa"), params absent, sig-OID == key-OID (sameKeyOid). The WebCrypto
107
- // name is "SLH-DSA-"+SET, matching path-validate's transform (drift rule sec. 5 -- one name map).
107
+ // name is "SLH-DSA-"+SET, matching path-validate's transform (drift rule sec. 5: one name map).
108
108
  ["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
109
109
  "shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
110
110
  ].forEach(function (s) { SIG_SCHEME["id-slh-dsa-" + s] = { kind: "slhdsa", name: "SLH-DSA-" + s.toUpperCase(), params: "absent", sameKeyOid: true, digest: SLHDSA_BY_OID[oid.byName("id-slh-dsa-" + s)].digest }; });
@@ -119,8 +119,7 @@ EC_CURVE[oid.byName("secp384r1")] = { curve: "P-384", coordLen: 48 };
119
119
  EC_CURVE[oid.byName("secp521r1")] = { curve: "P-521", coordLen: 66 };
120
120
 
121
121
  function _toBuf(v, what) {
122
- if (Buffer.isBuffer(v)) return v;
123
- if (v instanceof Uint8Array) return Buffer.from(v);
122
+ if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmsError, "cms/bad-input", what);
124
123
  throw _err("cms/bad-input", what + " must be a Buffer");
125
124
  }
126
125
 
@@ -141,7 +140,7 @@ function _findSignerCerts(sid, parsedCerts) {
141
140
  return out;
142
141
  }
143
142
 
144
- // Verify `sigBytes` over `signedBytes` against EACH matching candidate certificate in turn; the
143
+ // Verify `sigBytes` over `signedBytes` against each matching candidate certificate in turn; the
145
144
  // signer is valid iff one candidate verifies. A candidate whose key is structurally unusable (a
146
145
  // low-order EdDSA point, an unsupported curve) or whose signature simply does not verify falls
147
146
  // through to the next -- so a colliding certificate cannot make a valid signature read invalid.
@@ -157,7 +156,7 @@ function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, sid, c
157
156
  var c = candidates[idx];
158
157
  // One-shot family (EdDSA/ML-DSA): the signer cert public-key algorithm OID MUST equal the
159
158
  // SignerInfo signatureAlgorithm OID. A candidate whose SPKI disagrees is skipped with a precise
160
- // verdict, rather than a foreign webcrypto/data throw from importing under the wrong name.
159
+ // verdict; importing it under the wrong name would surface a foreign webcrypto/data throw.
161
160
  if (expectedKeyOid && c.cert.subjectPublicKeyInfo.algorithm.oid !== expectedKeyOid) {
162
161
  lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
163
162
  return attempt(idx + 1);
@@ -207,11 +206,11 @@ function _hashAlgOid(seq) {
207
206
  }
208
207
 
209
208
  // Resolve RSASSA-PSS-params (RFC 4055) to { hash, saltLength }, or null if the parameters
210
- // deviate from the supported profile: an explicit SHA-2 hashAlgorithm, MGF1 keyed to the SAME
209
+ // deviate from the supported profile: an explicit SHA-2 hashAlgorithm, MGF1 keyed to the same
211
210
  // hash, saltLength equal to the hash length, and trailerField 1. The SHA-1 DEFAULTs are
212
211
  // rejected (params must be explicit), matching the certification-path validator, so a
213
- // non-conformant PSS AlgorithmIdentifier is a fail-closed verdict -- never verified under
214
- // WebCrypto's own defaults (a signatureAlgorithm bypass otherwise).
212
+ // non-conformant PSS AlgorithmIdentifier is a fail-closed verdict. Falling back to WebCrypto's
213
+ // own defaults would be a signatureAlgorithm bypass.
215
214
  function _resolvePss(paramsBytes) {
216
215
  if (!paramsBytes) return null;
217
216
  var n;
@@ -265,7 +264,7 @@ function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOi
265
264
  var ec = EC_CURVE[curveOid];
266
265
  if (!ec) throw _err("cms/unsupported-algorithm", "the signer key is on an unsupported EC curve");
267
266
  // The ORDER-AWARE gate: r,s in [1, n-1] per FIPS 186-5 sec. 6.4.2 (rejecting an out-of-range r/s >= the
268
- // curve order n, not only the r=s=0 forgery) -- a verifier that knows the curve MUST use it.
267
+ // curve order n, not only the r=s=0 forgery). A verifier that knows the curve MUST use it.
269
268
  var raw = validator.sig.ecdsaDerToP1363(sigBytes, ec.curve, CmsError, "cms/bad-signature");
270
269
  return subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"])
271
270
  .then(function (k) { return subtle.verify({ name: "ECDSA", hash: hashName }, k, raw, signedBytes); });
@@ -280,13 +279,13 @@ function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOi
280
279
  }
281
280
  // EdDSA -- the WebCrypto name follows the signing key's SPKI OID (Ed25519 / Ed448). node/
282
281
  // OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order (e.g.
283
- // all-zeroes) key verifies a forged signature -- so reject a non-full-order point first.
282
+ // all-zeroes) key verifies a forged signature, so reject a non-full-order point first.
284
283
  _requireValidEdPoint(spki, scheme.name);
285
284
  return subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"])
286
285
  .then(function (k) { return subtle.verify({ name: scheme.name }, k, sigBytes, signedBytes); });
287
286
  }
288
287
 
289
- // The EdDSA signer key's point MUST be a valid, canonical, full-order Edwards point -- reject an
288
+ // The EdDSA signer key's point MUST be a valid, canonical, full-order Edwards point: reject an
290
289
  // off-curve or low-order key before it verifies a signature (WebCrypto import does not check it).
291
290
  // Routed through the shared edwards-point gate every EdDSA verify path uses; curve from the name.
292
291
  function _requireValidEdPoint(spkiBytes, name) {
@@ -304,9 +303,9 @@ function _certCurveOid(cert) {
304
303
 
305
304
  // Decode the SignedAttributes SET OF Attribute from the exact bytes the signature covers
306
305
  // (the re-tagged signedAttrsBytes), returning [{ type, values: [valueNode] }]. Binding the
307
- // content-type / message-digest checks to THESE decoded attributes -- not the caller-mutable
308
- // parsed si.signedAttrs -- means a parsed-object input cannot desync the checked attributes
309
- // from the verified preimage (RFC 5652 sec. 5.4).
306
+ // content-type / message-digest checks to these decoded attributes means a parsed-object input
307
+ // cannot desync the checked attributes from the verified preimage (RFC 5652 sec. 5.4). The
308
+ // caller-mutable parsed si.signedAttrs is deliberately left out of that decision.
310
309
  function _decodeSignedAttrs(setOfBytes) {
311
310
  var set = asn1.decode(setOfBytes);
312
311
  if (set.tagClass !== "universal" || set.tagNumber !== asn1.TAGS.SET || !set.children) throw _err("cms/bad-signed-attrs", "signedAttrs is not a SET OF Attribute");
@@ -318,8 +317,8 @@ function _decodeSignedAttrs(setOfBytes) {
318
317
  });
319
318
  }
320
319
 
321
- // Verify one SignerInfo. Returns { ok, sid, cert, ... } -- a resolved false is a verdict; a
322
- // structural failure carries a code.
320
+ // Verify one SignerInfo. Returns { ok, sid, cert, ... }, where a resolved false is a verdict and
321
+ // a structural failure carries a code.
323
322
  // `csTarget`, when set (a Buffer), makes this a COUNTERSIGNATURE verify (RFC 5652 sec. 11.4): the
324
323
  // preimage is the countersigned signature octets, not the sec. 5.4 content preimage, and no
325
324
  // content-type attribute is required or checked (_computeCountersigBytes). content/eContentType are
@@ -344,7 +343,7 @@ function _verifyOne(si, content, eContentType, parsedCerts, csTarget) {
344
343
  // The digestAlgorithm parameters, when present, MUST be a DER NULL (RFC 5754 sec. 2 accepts
345
344
  // absent OR NULL; a present non-NULL is malformed and, being outside the signed preimage,
346
345
  // must fail closed like the signatureAlgorithm parameters above). EXCEPT for an ML-DSA
347
- // SignerInfo WITHOUT signed attributes: RFC 9882 sec. 3.3 says the whole digestAlgorithm field
346
+ // SignerInfo carrying no signed attributes: RFC 9882 sec. 3.3 says the whole digestAlgorithm field
348
347
  // has no meaning there and MUST be ignored, so neither its parameters nor its name may reject.
349
348
  var dp = si.digestAlgorithm.parameters;
350
349
  var mldsaNoAttrs = scheme.kind === "mldsa" && !si.signedAttrsBytes;
@@ -399,7 +398,7 @@ function _computeCountersigBytes(si, csTarget) {
399
398
  attrs = _decodeSignedAttrs(reTagged);
400
399
  } catch (e) { if (e instanceof CmsError) throw e; throw _err("cms/bad-signed-attrs", "the countersignature signedAttrs is not a valid SET OF Attribute", e); }
401
400
  // sec. 11.4: a content-type attribute MUST NOT appear (the parser already rejects it at parse, but
402
- // verify never relies on the parse alone -- a parsed-object input could carry one).
401
+ // verify never relies on the parse alone: a parsed-object input could carry one).
403
402
  if (attrs.filter(function (a) { return a.type === OID_CONTENT_TYPE; }).length) throw _err("cms/misplaced-attr", "a countersignature must not carry a content-type attribute (RFC 5652 sec. 11.4)");
404
403
  var mdAttr = attrs.filter(function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
405
404
  if (!mdAttr || mdAttr.values.length !== 1) throw _err("cms/bad-signed-attrs", "a countersignature's signedAttrs must carry exactly one message-digest attribute (RFC 5652 sec. 11.4)");
@@ -420,10 +419,27 @@ function _computeCountersigBytes(si, csTarget) {
420
419
  // content-type / message-digest attribute disagrees.
421
420
  function _computeSignedBytes(si, content, eContentType) {
422
421
  return Promise.resolve().then(function () {
423
- if (!si.signedAttrsBytes) return content; // no signed attributes: sign over the content directly
424
- // With signed attributes: decode them from the EXACT bytes the signature covers -- the
425
- // SignedAttributes SET OF, the on-wire [0] IMPLICIT tag replaced by a universal SET OF
426
- // (RFC 5652 sec. 5.4) -- so the content-type / message-digest checks bind the same bytes
422
+ if (!si.signedAttrsBytes) {
423
+ // No signed attributes: the signature is over the content itself (RFC 5652 sec. 5.4). That is
424
+ // also exactly what a stripped-attributes forgery looks like, because a CMS signature does not
425
+ // commit to whether attributes were present -- so a signature made over a SignedAttributes
426
+ // block re-presented as one made over content verifies, and with no attributes there is no
427
+ // message-digest or content-type attribute left to disagree. Refuse the shape: content that
428
+ // parses as a SignedAttributes block cannot be told apart from that forgery, and it is not
429
+ // the verifier's place to guess which one it is holding.
430
+ if (cms.looksLikeSignedAttributes(content)) {
431
+ return { mismatch: {
432
+ code: "cms/ambiguous-content",
433
+ message: "the content of a SignerInfo with no signed attributes is itself an encoded " +
434
+ "SignedAttributes block, which is indistinguishable from a signature over attributes " +
435
+ "re-presented as one over content (RFC 5652 sec. 5.4); sign such content WITH signed attributes",
436
+ } };
437
+ }
438
+ return content;
439
+ }
440
+ // With signed attributes: decode them from the EXACT bytes the signature covers, namely the
441
+ // SignedAttributes SET OF with the on-wire [0] IMPLICIT tag replaced by a universal SET OF
442
+ // (RFC 5652 sec. 5.4). The content-type / message-digest checks then bind the same bytes
427
443
  // that are verified. Reading the caller-mutable parsed si.signedAttrs instead would let a
428
444
  // parsed-object input desync the checked attributes from the verified preimage.
429
445
  var reTagged = Buffer.from(si.signedAttrsBytes);
@@ -462,7 +478,7 @@ function _computeSignedBytes(si, content, eContentType) {
462
478
  // (sec. 3.4, defense-in-depth over the parse-time _PARAMS_ABSENT gate), the unsupported-arm refusal,
463
479
  // and the sec. 3.4/sec. 5 coherence gate binding the SignerInfo digestAlgorithm to the arm's Table-1
464
480
  // pre-hash. It reuses the sec. 5.4 preimage + message-digest binding UNCHANGED, then dispatches
465
- // compositeVerify per candidate certificate (BOTH components must verify -- never an AND->OR
481
+ // compositeVerify per candidate certificate (both components must verify, with no AND->OR
466
482
  // downgrade).
467
483
  function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget) {
468
484
  if (si.signatureAlgorithm.parameters !== null && si.signatureAlgorithm.parameters !== undefined) {
@@ -474,8 +490,8 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
474
490
  return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "composite " + comp.name + ": " + comp.trad.unsupported });
475
491
  }
476
492
  // draft sec. 3.4: for a composite SignerInfo the digestAlgorithm parameters MUST be OMITTED
477
- // (id-sha256/id-sha512 and id-shake256) -- stricter than the generic RFC 5754 absent-OR-NULL rule
478
- // (which the classical/ML-DSA paths follow), because the composite draft explicitly requires the
493
+ // (id-sha256/id-sha512 and id-shake256). That is stricter than the generic RFC 5754 absent-OR-NULL
494
+ // rule (which the classical/ML-DSA paths follow), because the composite draft explicitly requires the
479
495
  // field omitted. A present parameter -- even a DER NULL -- is non-conformant and fails closed
480
496
  // (it is outside the signed preimage, so a parser differential must not verify).
481
497
  var dp = si.digestAlgorithm.parameters;
@@ -499,7 +515,7 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
499
515
  });
500
516
  }
501
517
 
502
- // Dispatch compositeVerify against EACH matching candidate; the signer is valid iff one candidate
518
+ // Dispatch compositeVerify against each matching candidate; the signer is valid iff one candidate
503
519
  // verifies both components. A candidate whose SPKI composite OID != the SignerInfo signatureAlgorithm
504
520
  // OID is skipped with a precise verdict (RFC 9814 sec. 4 key<->signature agreement); a candidate that
505
521
  // returns a structural fault code (bad split length, unsupported) falls through, its code surfaced if
@@ -526,9 +542,10 @@ function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, can
526
542
  return attempt(0);
527
543
  }
528
544
 
529
- // The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside the
530
- // signature) with each OID resolved to a name -- so a caller can read an attached RFC 3161 timestamp
531
- // token or inspect a countersignature attribute. NEVER counted toward a signer's ok / res.valid.
545
+ // The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside
546
+ // the signature) with each OID resolved to a name, so a caller can read an attached RFC 3161
547
+ // timestamp token or inspect a countersignature attribute. They are never counted toward a
548
+ // signer's ok / res.valid.
532
549
  function _surfaceUnsignedAttrs(si) {
533
550
  return (si.unsignedAttrs || []).map(function (a) {
534
551
  return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
@@ -537,17 +554,17 @@ function _surfaceUnsignedAttrs(si) {
537
554
 
538
555
  // Verify every countersignature attached to `si` (RFC 5652 sec. 11.4): each id-countersignature
539
556
  // value is a SignerInfo over `si`'s signature octets. Returns per-countersignature verdicts; a
540
- // countersignature's OWN countersignatures verify over ITS signature octets (recursive). A
541
- // countersignature that FAILS TO VERIFY is surfaced ok:false -- never silently dropped, and never
542
- // allowed to change the primary verdict. A countersignature value that is not a well-formed
557
+ // countersignature's own countersignatures verify over its signature octets (recursive). A
558
+ // countersignature that fails to verify is surfaced ok:false. It is never silently dropped, and
559
+ // never allowed to change the primary verdict. A countersignature value that is not a well-formed
543
560
  // SignerInfo does not reach here at all: the decoder validates every id-countersignature value by
544
- // content rather than on the attribute type, so such a message is refused whole. The recursion
545
- // terminates because it only walks the FINITE parsed structure: each nested countersignature value
546
- // is a sub-encoding of its parent, and the strict decoder already bounds total nesting by
547
- // C.LIMITS.DER_MAX_DEPTH at parse (CWE-834/770), so a hostile deep chain fails closed before verify.
561
+ // its content, so such a message is refused whole. The recursion terminates because it only walks
562
+ // the FINITE parsed structure: each nested countersignature value is a sub-encoding of its parent,
563
+ // and the strict decoder already bounds total nesting by C.LIMITS.DER_MAX_DEPTH at parse
564
+ // (CWE-834/770), so a hostile deep chain fails closed before verify.
548
565
  function _verifyCountersignatures(si, parsedCerts) {
549
566
  // si.signature is always a Buffer from the strict parser (and from walkCountersignature on the
550
- // recursive path), so _toBuf is a pass-through here -- the SignerInfo is already well-formed.
567
+ // recursive path), so _toBuf is a pass-through here. The SignerInfo is already well-formed.
551
568
  var targetSig = _toBuf(si.signature, "the countersigned signature");
552
569
  var values = [];
553
570
  (si.unsignedAttrs || []).forEach(function (a) {
@@ -559,6 +576,14 @@ function _verifyCountersignatures(si, parsedCerts) {
559
576
 
560
577
  function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
561
578
  var csSi;
579
+ // Coverage residual, and deliberately kept. Every value reaching here was already walked by the
580
+ // decoder, which validates an id-countersignature by content and not by the attribute type. A
581
+ // malformed one therefore refused the whole message before verify was entered, so this catch
582
+ // cannot be reached through the public path. That is settled behaviour: a message the decoder
583
+ // has found to be malformed has no sound remainder to report a verdict over, so it is refused
584
+ // whole instead of surfaced as one failed countersignature.
585
+ // cms-verify.test.js pins that through the shipped verbs; this stays because the walk is also
586
+ // reachable recursively and a backstop that returns a NEGATIVE verdict costs nothing.
562
587
  try { csSi = cms.walkCountersignature(asn1.decode(Buffer.isBuffer(vDer) ? vDer : Buffer.from(vDer))); }
563
588
  catch (e) { return Promise.resolve({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
564
589
  return _verifyOne(csSi, targetSig, null, parsedCerts, targetSig).then(function (verdict) {
@@ -574,7 +599,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
574
599
 
575
600
  /**
576
601
  * @primitive pki.cms.verify
577
- * @signature pki.cms.verify(input, opts?) -> Promise<{ valid, trusted, signers }>
602
+ * @signature pki.cms.verify(input, opts?) -> Promise<{ valid, trusted, eContentType, signers }>
578
603
  * @since 0.2.14
579
604
  * @status stable
580
605
  * @spec RFC 5652
@@ -584,31 +609,38 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
584
609
  * @related pki.schema.cms.parse, pki.path.validate
585
610
  *
586
611
  * Verify a CMS SignedData signature (RFC 5652 sec. 5). `input` is a PEM string, a DER
587
- * `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, signers }` where each
588
- * `signers[i]` is `{ ok, sid, cert, trusted }` (`cert` the matched signer certificate DER) or carries
589
- * a `code` on a structural failure; `valid` is true when there is at least one signer and
590
- * every signer verified.
612
+ * `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, eContentType, signers }`
613
+ * where each `signers[i]` is `{ ok, sid, cert, trusted, signedAttributesPresent }` (`cert` the
614
+ * matched signer certificate DER) or carries a `code` on a structural failure; `valid` is true when
615
+ * there is at least one signer and every signer verified.
616
+ *
617
+ * `eContentType` and `signedAttributesPresent` are there for a caller whose profile is stricter
618
+ * than RFC 5652's. Signing WITH attributes and signing the content directly are different claims --
619
+ * attributes bind a content type and a signing time alongside the digest, content-only binds
620
+ * nothing but the bytes -- and one message may carry a signer of each. A profile that requires
621
+ * attributes (RFC 8551 S/MIME does) or a particular content type can enforce it from the verdict,
622
+ * with no need to parse the message a second time.
591
623
  *
592
624
  * `valid` and `trusted` are DIFFERENT claims and neither implies the other. A SignedData carries
593
- * its own certificates, so `valid` establishes that the message is internally consistent -- the
625
+ * its own certificates, so `valid` establishes that the message is internally consistent: the
594
626
  * signature is sound under a certificate the message or `opts.certs` supplied. Anyone can mint a
595
627
  * certificate, sign with it, and embed it, so that says nothing about WHO signed. `trusted` says
596
628
  * every signer chained to a root named in `opts.trustAnchors`, validated through the same RFC 5280
597
629
  * path engine `pki.path.validate` uses. Without anchors there is nothing to chain to and `trusted`
598
630
  * is `false` -- a definite answer, not a missing one. Anchors that cannot be read are a
599
- * configuration fault and throw, rather than being absorbed into `trusted: false`, which would
600
- * report a verdict about the message for a check that never ran.
631
+ * configuration fault and throw. Absorbing them into `trusted: false` would report a verdict
632
+ * about the message for a check that never ran.
601
633
  *
602
- * Trust is decided from the certificate the SignerInfo selected -- the one reported as
603
- * `signers[i].cert` -- never from another certificate that happens to share its key. A
634
+ * Trust is decided from the certificate the SignerInfo selected, the one reported as
635
+ * `signers[i].cert`, never from another certificate that happens to share its key. A
604
636
  * `subjectKeyIdentifier` names a key, and several certificates can hold it with different
605
637
  * validity windows, key usage and policies; deciding from a sibling would let an expired or
606
638
  * wrong-purpose signer certificate be reported trusted because a different certificate chained.
607
639
  * Supply the certificate you want used. RSA (PKCS#1 v1.5 and RSASSA-PSS), ECDSA, EdDSA, and the post-quantum
608
- * ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814) -- pure mode,
609
- * empty context -- signatures are recognized, as is composite ML-DSA
610
- * (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional RSA / ECDSA / EdDSA
611
- * and verifies only when BOTH components pass (never an AND-to-OR downgrade).
640
+ * ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814) signatures
641
+ * are recognized, the post-quantum families in pure mode with an empty context, as is composite
642
+ * ML-DSA (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
643
+ * RSA / ECDSA / EdDSA and verifies only when both components pass (never an AND-to-OR downgrade).
612
644
  *
613
645
  * @opts content The detached content (a `Buffer`) when the SignedData carries no
614
646
  * encapsulated eContent. Required for a detached signature.
@@ -620,7 +652,7 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
620
652
  * @opts time The instant to validate the signer's chain at (default now). Only read when
621
653
  * `trustAnchors` is supplied.
622
654
  * @opts requiredEku Key purposes the SIGNER certificate must carry, as OID names or dotted OIDs.
623
- * @opts checkPurpose The purpose the ANCHOR's own trust metadata must permit -- a separate
655
+ * @opts checkPurpose The purpose the ANCHOR's own trust metadata must permit. This is a separate
624
656
  * question from `requiredEku`, since a root distributed with NSS trust bits can be
625
657
  * marked untrusted for one purpose and good for another. Those bits and
626
658
  * `distrustAfter` are consulted only when this names a purpose.
@@ -721,11 +753,25 @@ function _verify(input, opts) {
721
753
  return _verifyCountersignatures(si, parsedCerts).then(function (countersignatures) {
722
754
  verdict.countersignatures = countersignatures;
723
755
  verdict.unsignedAttrs = _surfaceUnsignedAttrs(si);
756
+ // Whether THIS signer signed attributes or signed the content directly. The two are
757
+ // different claims -- attributes bind a content type and a signing time alongside the
758
+ // digest, and content-only binds nothing but the bytes -- and RFC 5652 lets a message carry
759
+ // one signer of each. A caller whose profile requires attributes (RFC 8551 S/MIME does) can
760
+ // only enforce it if the verdict says which they got.
761
+ verdict.signedAttributesPresent = !!si.signedAttrsBytes;
724
762
  return verdict;
725
763
  });
726
764
  });
727
765
  })).then(function (signers) {
728
- var res = { valid: signers.length > 0 && signers.every(function (s) { return s.ok === true; }), signers: signers };
766
+ // The content type travels with the verdict. An operator applying a policy of their own -- "I
767
+ // only accept id-data", or a profile that names its own type -- otherwise had to parse the
768
+ // message a second time to learn it, and a check that needs a second parse is a check most
769
+ // callers will not write.
770
+ var res = {
771
+ valid: signers.length > 0 && signers.every(function (s) { return s.ok === true; }),
772
+ eContentType: parsed.encapContentInfo.eContentType,
773
+ signers: signers,
774
+ };
729
775
  return _applyTrust(res, parsedCerts, trustCfg).then(function () { return res; });
730
776
  });
731
777
  }
@@ -957,7 +1003,7 @@ function _addCert(out, der) {
957
1003
  * @spec RFC 9814
958
1004
  * @related pki.cms.verify, pki.schema.cms.parse
959
1005
  *
960
- * Produce a CMS SignedData (RFC 5652 sec. 5) over `content` (a `Buffer`) -- the structure
1006
+ * Produce a CMS SignedData (RFC 5652 sec. 5) over `content` (a `Buffer`): the structure
961
1007
  * S/MIME signed mail, RFC 3161 timestamp tokens, and code signing rest on, and exactly what
962
1008
  * `pki.cms.verify` consumes and OpenSSL `cms -verify` validates. Each `signers[i]` is
963
1009
  * `{ cert, key, digestAlgorithm?, pss? }`: `cert` the signer certificate (PEM or DER), `key`
@@ -967,9 +1013,10 @@ function _addCert(out, der) {
967
1013
  * message-digest attribute is bound to the content digest and the signature is over the
968
1014
  * canonical DER SET OF SignedAttributes; otherwise over the content directly. RSA (PKCS#1 v1.5
969
1015
  * and, with `pss`, RSASSA-PSS), ECDSA (P-256/384/521), Ed25519, Ed448, and the post-quantum ML-DSA
970
- * (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814 -- the message digest
971
- * pinned per parameter set) are covered, as is composite ML-DSA (draft-ietf-lamps-cms-composite-sigs)
972
- * -- the arm's pre-hash fixes the digestAlgorithm and the two component keys are signed together.
1016
+ * (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814, with the message
1017
+ * digest pinned per parameter set) are covered, as is composite ML-DSA
1018
+ * (draft-ietf-lamps-cms-composite-sigs), where the arm's pre-hash fixes the digestAlgorithm and
1019
+ * the two component keys are signed together.
973
1020
  *
974
1021
  * @opts detached Omit the encapsulated content (a detached signature; the verifier
975
1022
  * supplies the content). Default false.
@@ -982,7 +1029,7 @@ function _addCert(out, der) {
982
1029
  * @opts pem Return a PEM string (`-----BEGIN CMS-----`) instead of a DER Buffer.
983
1030
  * @opts unsignedAttributes Unsigned attributes for every SignerInfo, each `{ type, values }`
984
1031
  * (`type` an OID name or dotted string, `values` an array of DER value
985
- * `Buffer`s). Placed in the SignerInfo `[1]` unsignedAttrs -- OUTSIDE the
1032
+ * `Buffer`s). Placed in the SignerInfo `[1]` unsignedAttrs, outside the
986
1033
  * signature, so they carry no cryptographic assurance and a verifier never
987
1034
  * reports them authenticated. The vehicle for an RFC 3161 timestamp token
988
1035
  * (`timeStampToken`); content-type / message-digest / signing-time are
@@ -1008,12 +1055,12 @@ var sign = cmsSign.sign;
1008
1055
  *
1009
1056
  * Add one or more countersignatures to an existing CMS SignedData (RFC 5652 sec. 11.4). A
1010
1057
  * countersignature is itself a `SignerInfo` whose signature covers the CONTENTS of the countersigned
1011
- * SignerInfo's signature value (NOT the content), attached as the `id-countersignature` unsigned
1058
+ * SignerInfo's signature value (not the content), attached as the `id-countersignature` unsigned
1012
1059
  * attribute -- the construction Authenticode timestamps and signature-attestation workflows rest on.
1013
- * `cms` is the SignedData (a DER `Buffer` or a PEM `CMS` string -- the wire bytes are preserved, so
1060
+ * `cms` is the SignedData (a DER `Buffer` or a PEM `CMS` string; the wire bytes are preserved, so
1014
1061
  * the countersigned primary still verifies byte-for-byte). Each `signers[i]` is the same descriptor
1015
- * `pki.cms.sign` takes -- `{ cert, key, digestAlgorithm?, pss? }`, any RSA / RSASSA-PSS / ECDSA /
1016
- * EdDSA / ML-DSA / SLH-DSA / composite key -- and countersigns over the target signature octets;
1062
+ * `pki.cms.sign` takes (`{ cert, key, digestAlgorithm?, pss? }`, any RSA / RSASSA-PSS / ECDSA /
1063
+ * EdDSA / ML-DSA / SLH-DSA / composite key), and countersigns over the target signature octets;
1017
1064
  * `pki.cms.verify` returns each countersignature verdict under `signers[i].countersignatures`. A
1018
1065
  * countersignature never carries a content-type attribute (sec. 11.4); multiple countersignatures on
1019
1066
  * one signer are multiple values of the one id-countersignature attribute.
@@ -1022,7 +1069,7 @@ var sign = cmsSign.sign;
1022
1069
  * array of indices, or `"all"`.
1023
1070
  * @opts countersignatureOf Countersign the Nth existing countersignature of the target signer
1024
1071
  * instead of the primary signature (a nested countersignature).
1025
- * @opts signedAttributes Include signed attributes (message-digest + signing-time; NEVER a
1072
+ * @opts signedAttributes Include signed attributes (message-digest + signing-time; never a
1026
1073
  * content-type). Default true; false signs the target signature octets
1027
1074
  * directly.
1028
1075
  * @opts signingTime A `Date` for the countersignature's signing-time, or false to omit it.
@@ -1051,11 +1098,11 @@ var countersign = cmsSign.countersign;
1051
1098
  * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018
1052
1099
  * @related pki.cms.decrypt, pki.schema.cms.parse
1053
1100
  *
1054
- * Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content -- the
1101
+ * Encrypt content as a CMS EnvelopedData (CBC content), AuthEnvelopedData (AEAD content, the
1055
1102
  * default), or EncryptedData. `recipients` is an ARRAY of recipient descriptors for the enveloped
1056
- * family, each wrapping the SAME fresh content-encryption key: `{ cert }` auto-dispatches off the
1103
+ * family, each wrapping the same fresh content-encryption key: `{ cert }` auto-dispatches off the
1057
1104
  * recipient certificate's public-key algorithm -- RSA yields a ktri with RSAES-OAEP-SHA256 (PKCS#1
1058
- * v1.5 is NEVER emitted); EC P-256/384/521 a kari with ephemeral-static ECDH and the X9.63 KDF;
1105
+ * v1.5 is never emitted); EC P-256/384/521 a kari with ephemeral-static ECDH and the X9.63 KDF;
1059
1106
  * X25519/X448 a kari per RFC 8418 (HKDF); ML-KEM-512/768/1024 an ori/KEMRecipientInfo per RFC
1060
1107
  * 9629 + 9936. `{ password }` yields a pwri (PBKDF2 + RFC 3211 PWRI-KEK); `{ kek, kekId }` a kekri
1061
1108
  * (AES key wrap). For EncryptedData (no RecipientInfos), pass a single non-array `{ cek }` or
@@ -1089,8 +1136,8 @@ var encrypt = cmsEncrypt.encrypt;
1089
1136
  * @related pki.cms.decrypt, pki.schema.cms.parse
1090
1137
  *
1091
1138
  * Produce a CMS AuthenticatedData (RFC 5652 sec. 9): CLEARTEXT content plus a MAC (HMAC-SHA-2) --
1092
- * authenticated but NOT encrypted, the authenticated-only sibling of AuthEnvelopedData. A single
1093
- * fresh MAC key is minted and wrapped for every recipient with the SAME RecipientInfo model
1139
+ * authenticated but not encrypted, the authenticated-only sibling of AuthEnvelopedData. A single
1140
+ * fresh MAC key is minted and wrapped for every recipient with the same RecipientInfo model
1094
1141
  * `pki.cms.encrypt` uses for a content-encryption key, so `recipients` is the identical array of
1095
1142
  * descriptors: `{ cert }` (RSA -> ktri RSAES-OAEP, EC/X25519/X448 -> kari, ML-KEM -> ori/KEMRI),
1096
1143
  * `{ password }` (pwri), or `{ kek, kekId }` (kekri). By default the MAC covers the authenticated
@@ -1124,7 +1171,7 @@ var authenticate = cmsEncrypt.authenticate;
1124
1171
 
1125
1172
  /**
1126
1173
  * @primitive pki.cms.decrypt
1127
- * @signature pki.cms.decrypt(input, keyMaterial, opts?) -> Promise<{ content, contentType, contentTypeName, recipientType, recipientIndex, contentEncryptionAlgorithm, authenticated }>
1174
+ * @signature pki.cms.decrypt(input, keyMaterial, opts?) -> Promise<{ content, contentType, contentTypeName, recipientType, recipientIndex, contentEncryptionAlgorithm, authenticated, authenticatedBy, originAuthenticated, originatorInfo }>
1128
1175
  * @since 0.2.23
1129
1176
  * @status stable
1130
1177
  * @spec RFC 5652, RFC 5083, RFC 5084, RFC 3560, RFC 5753, RFC 8418, RFC 9629, RFC 9936, RFC 3211, RFC 8018, RFC 3218, RFC 2104
@@ -1136,16 +1183,28 @@ var authenticate = cmsEncrypt.authenticate;
1136
1183
  * ECDH / X25519 / X448; kekri; pwri; ori/ML-KEM), and decrypts (or MAC-verifies) the content.
1137
1184
  * `keyMaterial` is `{ key, cert }` (the recipient private key + its certificate, which drives recipient
1138
1185
  * matching), `{ password }`, `{ kek, kekId? }`, or `{ cek }` (EncryptedData raw-key mode). Fail-closed
1139
- * and oracle-free: every secret-dependent failure -- a bad key-wrap, a padding fault, a GCM tag
1140
- * mismatch, a PWRI check-byte mismatch, an AuthenticatedData MAC/message-digest mismatch -- collapses
1141
- * to the SINGLE uniform `cms/decrypt-failed` verdict (Bleichenbacher / EFAIL oracle freedom, and no
1142
- * unwrap-success bit for the MAC path), and the PKCS#1 v1.5 arm applies the RFC 3218 implicit-rejection
1143
- * countermeasure so its failure is indistinguishable. For an AuthenticatedData the MAC (HMAC-SHA-2)
1186
+ * and oracle-free: every secret-dependent failure collapses to the SINGLE uniform
1187
+ * `cms/decrypt-failed` verdict (Bleichenbacher / EFAIL oracle freedom, and no unwrap-success bit
1188
+ * for the MAC path). That covers a bad key-wrap, a padding fault, a GCM tag mismatch, a PWRI
1189
+ * check-byte mismatch, and an AuthenticatedData MAC/message-digest mismatch. The PKCS#1 v1.5 arm
1190
+ * applies the RFC 3218 implicit-rejection countermeasure so its failure is indistinguishable. For an AuthenticatedData the MAC (HMAC-SHA-2)
1144
1191
  * and, when authenticated attributes are present, the message-digest are verified before the content
1145
1192
  * is released, and the result carries `macAlgorithm` / `digestAlgorithm` in place of
1146
1193
  * `contentEncryptionAlgorithm`. `authenticated` is true for AuthEnvelopedData and AuthenticatedData; a
1147
1194
  * CBC EnvelopedData surfaces `authenticated: false` (the EFAIL caveat in the verdict itself).
1148
1195
  *
1196
+ * `authenticated` is a claim about the CONTENT and the key that opened it, never about who sent the
1197
+ * message, so the origin question is answered separately. `originAuthenticated` is `false` for every
1198
+ * recipient type: a `ktri` or ephemeral-static `kari` message is minted by anyone holding the recipient's
1199
+ * PUBLIC key, and a `pwri` or `kekri` message by any co-recipient sharing the secret. Read
1200
+ * `authenticated: true` as "these bytes were not altered after the key was chosen", and do not read it as
1201
+ * a sender identity -- to bind one, verify a signature (`pki.cms.verify`) over the plaintext.
1202
+ * `authenticatedBy` names what the integrity rests on (`"content-encryption-key"` for AEAD,
1203
+ * `"message-authentication-key"` for AuthenticatedData, `null` for an unauthenticated cipher).
1204
+ * `originatorInfo` is surfaced as the sender supplied it and is UNAUTHENTICATED: it sits outside the
1205
+ * AEAD's authenticated data, so it is a hint, never evidence, and any certificate it carries must be
1206
+ * validated before use.
1207
+ *
1149
1208
  * @opts recipientIndex Explicitly select the recipient by index (overrides key-material matching).
1150
1209
  * @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
1151
1210
  * @example
@@ -1171,7 +1230,7 @@ var decrypt = cmsDecrypt.decrypt;
1171
1230
  * ZLIB-compress `content` and wrap it as a CMS `CompressedData` ContentInfo (RFC 3274): version 0,
1172
1231
  * `compressionAlgorithm` = id-alg-zlibCompress (parameters omitted), `encapContentInfo` = the inner
1173
1232
  * content type plus the RFC 1950 ZLIB stream (RFC 1951 DEFLATE) as the eContent. There is exactly
1174
- * ONE compression algorithm in RFC 3274 -- ZLIB -- so there is no algorithm selector. CompressedData
1233
+ * one compression algorithm in RFC 3274, ZLIB, so there is no algorithm selector. CompressedData
1175
1234
  * is a size transform with NO integrity, confidentiality, or authentication (RFC 8551 sec. 2.4.5);
1176
1235
  * compress it, then sign or encrypt it if you need protection. Malformed input throws a typed `CmsError`.
1177
1236
  *
@@ -1194,7 +1253,7 @@ var compress = cmsCompress.compress;
1194
1253
  * Parse a CMS `CompressedData` (DER Buffer or PEM) and recover its content. It requires version 0,
1195
1254
  * `compressionAlgorithm` = id-alg-zlibCompress with absent-or-NULL parameters (RFC 3274 sec. 2), and a
1196
1255
  * present eContent, then inflates the RFC 1950 ZLIB stream. The inflate is BOUNDED at
1197
- * `C.LIMITS.COMPRESS_MAX_BYTES` (16 MiB) and stops before the output is materialized -- a
1256
+ * `C.LIMITS.COMPRESS_MAX_BYTES` (16 MiB) and stops before the output is materialized, a
1198
1257
  * decompression-bomb / resource-exhaustion defense (CWE-409): a cap breach throws
1199
1258
  * `cms/decompress-too-large`, and every malformed / truncated / corrupt stream collapses to the uniform
1200
1259
  * `cms/decompress-failed`. `opts.maxOutputBytes` tightens the cap DOWNWARD only. The verdict carries NO
@@ -13,9 +13,9 @@
13
13
  // mldsaSig || tradSig (sec. 4.3). Verification (sec. 2) reconstructs
14
14
  // M' = Prefix || Label || len(ctx) || ctx || PH(M),
15
15
  // verifies the ML-DSA component over M' with ctx = the composite Label, verifies the traditional
16
- // component over M' under its own hash, and accepts IFF BOTH pass (THREAT-MODEL: all components
17
- // must verify -- never an AND-to-OR downgrade). The ML-DSA component is the fixed-length FIRST
18
- // half; the split point is its length.
16
+ // component over M' under its own hash, and accepts if and only if both pass. Every component must
17
+ // verify; there is no AND-to-OR downgrade. The ML-DSA component is the fixed-length first half, and
18
+ // the split point is its length.
19
19
  //
20
20
  // Error-parameterized like the guard / validator families: each consumer passes its own typed
21
21
  // error CONSTRUCTOR `E` and its domain codes (path validation passes PathError + path/*; CMS
@@ -30,8 +30,8 @@ var edwardsPoint = require("./edwards-point");
30
30
  var subtle = webcrypto.webcrypto.subtle;
31
31
  var _b = asn1.build;
32
32
 
33
- // A caught error's OWN code if it shares the caller's domain prefix (the fallback's namespace,
34
- // e.g. path/ or cms/), else the fallback -- a foreign code (asn1/*) maps to the fallback so the
33
+ // A caught error's own code if it shares the caller's domain prefix (the fallback's namespace,
34
+ // e.g. path/ or cms/), else the fallback. A foreign code (asn1/*) maps to the fallback so the
35
35
  // verdict stays in the caller's error namespace. Mirrors path-validate's original pathCode when
36
36
  // the fallback is a path/* code.
37
37
  function _codeOf(e, fallback) {
@@ -55,8 +55,8 @@ var COMPOSITE_ALGS = {};
55
55
  // _comp(name, mldsa, ph, label, trad). `trad` is exactly one component shape:
56
56
  // { ec, hash } | { eddsa } | { rsaPss, hash, salt } | { rsaPkcs1, hash } |
57
57
  // { unsupported } for the arms Node's WebCrypto surface cannot verify (the two
58
- // brainpool curves) -- registered + params-guarded, deferred at verify to the
59
- // caller's unsupported-algorithm code rather than silently accepted.
58
+ // brainpool curves): registered and params-guarded, deferred at verify to the
59
+ // caller's unsupported-algorithm code instead of silently accepted.
60
60
  function _comp(name, mldsa, ph, label, trad) {
61
61
  var sz = MLDSA_COMPONENT[mldsa];
62
62
  COMPOSITE_ALGS[oid.byName(name)] = {
@@ -84,7 +84,7 @@ _comp("id-MLDSA87-RSA4096-PSS-SHA512", "ML-DSA-87", "SHA-512", "COMPSIG-MLDSA87-
84
84
  _comp("id-MLDSA87-ECDSA-P521-SHA512", "ML-DSA-87", "SHA-512", "COMPSIG-MLDSA87-ECDSA-P521-SHA512", { ec: "P-521", hash: "SHA-512" });
85
85
 
86
86
  // Wrap a raw component public key in the SPKI its WebCrypto import expects, so each half is
87
- // verified through the SAME import + verify seam the classical path uses (no second parallel
87
+ // verified through the same import and verify seam the classical path uses (no second parallel
88
88
  // verify path). tradPK for RSA is the RSAPublicKey DER; for EC the uncompressed point; for EdDSA
89
89
  // the raw public key.
90
90
  function _spkiFor(algNode, keyBytes) { return _b.sequence([algNode, _b.bitString(keyBytes, 0)]); }
@@ -120,10 +120,10 @@ function _verifyTradComponent(trad, tradPK, tradSig, mprime, E, badSig) {
120
120
  }
121
121
  if (trad.eddsa) {
122
122
  // node/OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order
123
- // (e.g. all-zeroes) key verifies a forged signature -- which would collapse the composite AND
124
- // to ML-DSA-only. Reject a non-full-order point before verify, through the same shared
123
+ // (e.g. all-zeroes) key verifies a forged signature, collapsing the composite conjunction to
124
+ // ML-DSA alone. Reject a non-full-order point before verify, through the same shared
125
125
  // edwards-point gate every other EdDSA verify path in the toolkit routes through, surfacing a
126
- // coded fault (like the ECDSA order-bound belt) rather than a silent false.
126
+ // coded fault (like the ECDSA order-bound belt) instead of a silent false.
127
127
  if (!edwardsPoint.validate(tradPK, trad.eddsa === "Ed25519" ? 6 : 7)) {
128
128
  throw new E(badSig, "the composite EdDSA component public key is not a valid, full-order Edwards point");
129
129
  }
@@ -132,7 +132,7 @@ function _verifyTradComponent(trad, tradPK, tradSig, mprime, E, badSig) {
132
132
  }
133
133
  if (trad.rsaPss || trad.rsaPkcs1) {
134
134
  // The composite OID fixes the RSA modulus size: a downgraded or mismatched modulus under the
135
- // declared OID (an id-MLDSA44-RSA2048-* whose component is really 1024-bit) is rejected BEFORE
135
+ // declared OID (an id-MLDSA44-RSA2048-* whose component is really 1024-bit) is rejected before
136
136
  // verify, so a weak RSA component cannot satisfy an arm that promises 2048/3072/4096 bits. A
137
137
  // malformed RSAPublicKey rejects the same way.
138
138
  var bits;
@@ -167,7 +167,7 @@ function compositeVerify(spkiBytes, sigBytes, message, d, E, unsupported, badSig
167
167
  if (bs.unusedBits !== 0) throw new E(badSig, "composite subjectPublicKey has unused bits");
168
168
  rawKey = bs.bytes;
169
169
  } catch (e) { return Promise.resolve({ ok: false, code: _codeOf(e, badSig), error: e }); }
170
- // The ML-DSA half is fixed-length and FIRST; the traditional half is the remainder. Both must be
170
+ // The ML-DSA half is fixed-length and comes first; the traditional half is the remainder. Both must be
171
171
  // non-empty for a well-formed composite.
172
172
  if (rawKey.length <= d.mldsaPk || sigBytes.length <= d.mldsaSig) {
173
173
  return Promise.resolve({ ok: false, code: badSig,
package/lib/constants.js CHANGED
@@ -19,7 +19,7 @@
19
19
  * Every scale helper is config-time / entry-point validation: it THROWS
20
20
  * `ConstantsError` on a non-finite or negative argument, and on a product
21
21
  * outside the safe-integer range, so an operator catches the typo at boot
22
- * rather than shipping a silently-wrong window or an Infinity that would
22
+ * instead of shipping a silently-wrong window or an Infinity that would
23
23
  * disable a size cap.
24
24
  *
25
25
  * @card
@@ -31,7 +31,7 @@ var frameworkError = require("./framework-error");
31
31
 
32
32
  var ConstantsError = frameworkError.ConstantsError;
33
33
 
34
- // _positive(n, who) -- the shared guard every scale helper runs. Config-
34
+ // _positive(n, who) is the shared guard every scale helper runs. Config-
35
35
  // time tier: a bad scale argument is an authoring bug, so it throws.
36
36
  function _positive(n, who) {
37
37
  if (typeof n !== "number" || !isFinite(n) || n < 0) {
@@ -43,10 +43,10 @@ function _positive(n, who) {
43
43
  return n;
44
44
  }
45
45
 
46
- // _scale(n, who, factor) -- validate the argument AND the product. A finite
46
+ // _scale(n, who, factor) validates the argument and the product. A finite
47
47
  // operand can still overflow the multiplication (days(1e304) -> Infinity),
48
48
  // and an Infinity handed onward silently disables any bound compared
49
- // against it (`len > Infinity` is always false) -- so a product outside the
49
+ // against it (`len > Infinity` is always false), so a product outside the
50
50
  // safe-integer range throws instead of returning.
51
51
  function _scale(n, who, factor) {
52
52
  var out = Math.round(_positive(n, who) * factor);