@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
@@ -13,7 +13,7 @@
13
13
  // material (RSA -> ktri OAEP; EC -> kari stdDH; X25519/X448 -> kari HKDF; ML-KEM -> ori/KEMRI;
14
14
  // password -> pwri; symmetric KEK -> kekri) through the OID-keyed registry, never a hardcoded
15
15
  // switch. AEAD (AES-GCM) content is the default and yields AuthEnvelopedData; CBC yields
16
- // EnvelopedData. PKCS#1 v1.5 is NEVER emitted.
16
+ // EnvelopedData. PKCS#1 v1.5 is never emitted.
17
17
 
18
18
  var nodeCrypto = require("crypto");
19
19
  var asn1 = require("./asn1-der");
@@ -40,8 +40,7 @@ function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(nam
40
40
  // A certificate descriptor -> raw DER (the recipient cert is parsed for dispatch + rid; the caller
41
41
  // supplies bytes, not a re-encoded parse).
42
42
  function _normCertDer(cert, what) {
43
- if (Buffer.isBuffer(cert)) return cert;
44
- if (cert instanceof Uint8Array) return Buffer.from(cert);
43
+ if (Buffer.isBuffer(cert) || cert instanceof Uint8Array) return guard.bytes.snapshot(cert, CmsError, "cms/bad-input", what || "a certificate");
45
44
  if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("cms/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
46
45
  throw _err("cms/bad-input", (what || "a certificate") + " must be a DER Buffer, Uint8Array, or PEM string");
47
46
  }
@@ -62,13 +61,13 @@ function _wrapOidForKek(keyBytes) {
62
61
  if (keyBytes === 16) return "aes128-wrap";
63
62
  if (keyBytes === 24) return "aes192-wrap";
64
63
  if (keyBytes === 32) return "aes256-wrap";
65
- // Coverage residual: unreachable via the API -- a KEK/CEK is always an AES key size (16/24/32);
64
+ // Coverage residual: unreachable via the API, since a KEK/CEK is always an AES key size (16/24/32);
66
65
  // a defensive throw for a future caller that hands an off-size key.
67
66
  throw _err("cms/bad-input", "no AES key-wrap algorithm for a " + keyBytes + "-octet key-encryption key");
68
67
  }
69
68
 
70
- // GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen INTEGER DEFAULT 12 } -- the
71
- // DEFAULT 12 is OMITTED on emit (RFC 5084 sec. 3.2 / canonical DER).
69
+ // GCMParameters ::= SEQUENCE { aes-nonce OCTET STRING, aes-ICVlen INTEGER DEFAULT 12 }, where the
70
+ // DEFAULT 12 is omitted on emit (RFC 5084 sec. 3.2 / canonical DER).
72
71
  function _gcmParams(nonce, icvLen) {
73
72
  var kids = [b.octetString(nonce)];
74
73
  if (icvLen !== 12) kids.push(b.integer(BigInt(icvLen)));
@@ -76,7 +75,7 @@ function _gcmParams(nonce, icvLen) {
76
75
  }
77
76
 
78
77
  // The keyIdentifier option selects the RecipientIdentifier form. "issuerAndSerial" is the documented
79
- // default (its RFC name issuerAndSerialNumber is accepted too); reject anything else rather than
78
+ // default (its RFC name issuerAndSerialNumber is accepted too); reject anything else instead of
80
79
  // silently emitting issuerAndSerialNumber, so a typo surfaces instead of a rid the caller never asked for.
81
80
  function _assertKeyIdentifier(form) {
82
81
  if (form != null && form !== "issuerAndSerial" && form !== "issuerAndSerialNumber" && form !== "subjectKeyIdentifier") {
@@ -98,7 +97,7 @@ function _rid(cert, form) {
98
97
  // unsupported-curve / low-order / unsupported-KEM-cert throws further below): these validate the
99
98
  // CALLER's own recipient certificate at config time (tier-1 THROW). They fire only when a caller
100
99
  // supplies a certificate whose SKI/keyUsage extension is malformed, or whose key is an unsupported
101
- // curve / a low-order Montgomery point / an unsupported KEM -- inputs the toolkit never produces.
100
+ // curve, a low-order Montgomery point, or an unsupported KEM: inputs the toolkit never produces.
102
101
  function _skiOf(cert) {
103
102
  var exts = cert.extensions || [];
104
103
  for (var i = 0; i < exts.length; i++) if (exts[i].name === "subjectKeyIdentifier" && exts[i].value != null) {
@@ -121,7 +120,7 @@ var OAEP_HASH = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" };
121
120
  function _oaepParams(hashName) {
122
121
  var hAlg = _algId(hashName, "null");
123
122
  var mgf = b.sequence([b.oid(O("mgf1")), hAlg]);
124
- // pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty -- the empty-label default MUST be omitted (X.690).
123
+ // pSourceAlgorithm [2] DEFAULT pSpecifiedEmpty: the empty-label default MUST be omitted (X.690).
125
124
  return b.sequence([b.explicit(0, hAlg), b.explicit(1, mgf)]);
126
125
  }
127
126
  async function _buildKtri(cek, cert, opts) {
@@ -145,8 +144,8 @@ MONT_KA[O("X25519")] = { name: "X25519", hkdf: "SHA-256", scheme: "dhSinglePass-
145
144
  MONT_KA[O("X448")] = { name: "X448", hkdf: "SHA-512", scheme: "dhSinglePass-stdDH-hkdf-sha512-scheme" };
146
145
 
147
146
  // ECC-CMS-SharedInfo ::= SEQUENCE { keyInfo AlgorithmIdentifier (the wrap, params ABSENT),
148
- // entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, suppPubInfo [2] EXPLICIT OCTET STRING } --
149
- // suppPubInfo = the KEK length in BITS, 4-octet big-endian (RFC 5753 sec. 7.2). ONE builder,
147
+ // entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, suppPubInfo [2] EXPLICIT OCTET STRING }, where
148
+ // suppPubInfo is the KEK length in bits, 4-octet big-endian (RFC 5753 sec. 7.2). One builder,
150
149
  // shared by encrypt + decrypt so the two sides cannot diverge.
151
150
  function _eccSharedInfo(wrapName, ukm, kekBytes) {
152
151
  var kids = [_algId(wrapName, "absent")];
@@ -185,12 +184,12 @@ async function _buildKari(cek, cert, opts) {
185
184
  mz = Buffer.from(await subtle.deriveBits({ name: mka.name, public: rPub }, meph.privateKey, null));
186
185
  if (mz.every(function (x) { return x === 0; })) throw _err("cms/bad-input", "the X25519/X448 shared secret is all-zero (low-order point)");
187
186
  var mzKey = await subtle.importKey("raw", mz, { name: "HKDF" }, false, ["deriveBits"]);
188
- // RFC 8418 sec. 2.2: when a ukm is present it is used BOTH as the HKDF salt AND as the
189
- // ECC-CMS-SharedInfo entityUInfo (the HKDF info) -- omitting it from the info diverges the KEK
187
+ // RFC 8418 sec. 2.2: when a ukm is present it is used as both the HKDF salt and the
188
+ // ECC-CMS-SharedInfo entityUInfo (the HKDF info). Omitting it from the info diverges the KEK
190
189
  // from any conformant peer that reads the transmitted ukm.
191
190
  kek = Buffer.from(await subtle.deriveBits({ name: "HKDF", hash: mka.hkdf, salt: ukm || Buffer.alloc(0), info: _eccSharedInfo(wrapName, ukm, cek.length) }, mzKey, cek.length * 8));
192
191
  } else {
193
- // Coverage residual: unreachable -- _buildRecipient routes only ecPublicKey / X25519 / X448
192
+ // Coverage residual: unreachable, since _buildRecipient routes only ecPublicKey / X25519 / X448
194
193
  // keys into _buildKari; a defensive throw against a future dispatch change.
195
194
  throw _err("cms/unsupported-algorithm", "unsupported recipient key algorithm for kari");
196
195
  }
@@ -200,7 +199,7 @@ async function _buildKari(cek, cert, opts) {
200
199
  var origPubBits = origSpki.children[1]; // BIT STRING node
201
200
  var originatorKey = b.contextConstructed(1, Buffer.concat([origSpki.children[0].bytes, origPubBits.bytes]));
202
201
  // KeyAgreeRecipientIdentifier CHOICE { issuerAndSerialNumber, rKeyId [0] IMPLICIT
203
- // RecipientKeyIdentifier } -- the SKI form here wraps a SEQUENCE (rKeyId), unlike ktri's bare
202
+ // RecipientKeyIdentifier }, where the SKI form wraps a SEQUENCE (rKeyId), unlike ktri's bare
204
203
  // subjectKeyIdentifier [0] IMPLICIT OCTET STRING.
205
204
  var ridNode;
206
205
  _assertKeyIdentifier(opts.keyIdentifier);
@@ -235,13 +234,13 @@ async function _buildKekri(cek, desc) {
235
234
  return { tag: 2, node: b.sequence([b.integer(4n), kekid, _algId(wrapName, "absent"), b.octetString(encryptedKey)]) };
236
235
  }
237
236
 
238
- // A PBKDF2 iterationCount MUST be a positive integer within the same cap the decryptor enforces -- a
237
+ // A PBKDF2 iterationCount MUST be a positive integer within the same cap the decryptor enforces, so a
239
238
  // ---- pwri (password) : PBKDF2 + RFC 3211 double-CBC PWRI-KEK ---------------
240
239
  async function _buildPwri(cek, desc) {
241
- // A string / Uint8Array password is encoded into a buffer THIS toolkit allocated -- a credential
242
- // copy -- so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
240
+ // A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
241
+ // copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
243
242
  // borrowed and left intact.
244
- // The option validation runs BEFORE the password is encoded: a rejected iteration count or salt
243
+ // The option validation runs before the password is encoded: a rejected iteration count or salt
245
244
  // would otherwise abandon an owned credential copy on the way out.
246
245
  var iterations = pbes2.assertIterations(desc.iterations == null ? 600000 : desc.iterations, _err, "cms");
247
246
  var salt = desc.salt ? pbes2.assertSalt(guard.bytes.view(desc.salt, CmsError, "cms/bad-input", "salt"), _err, "cms") : nodeCrypto.randomBytes(16);
@@ -251,9 +250,9 @@ async function _buildPwri(cek, desc) {
251
250
  var pwOwn = pbes2.passwordBytesOwned(desc.password, _err, "cms");
252
251
  var password = pwOwn.bytes;
253
252
  var kekKey = await subtle.importKey("raw", password, { name: "PBKDF2" }, false, ["deriveBits"]);
254
- // The derived KEK is this function's allocation and is cleared below. `password` is NOT: when the
253
+ // The derived KEK is this function's allocation and is cleared below. `password` is not: when the
255
254
  // caller passes a Buffer, passwordBytes hands back that very buffer, so wiping it would destroy
256
- // the caller's own memory -- the one failure this rule must never cause.
255
+ // the caller's own memory, the one failure this rule must never cause.
257
256
  var kek = Buffer.from(await subtle.deriveBits({ name: "PBKDF2", hash: _prfHash(prf), salt: salt, iterations: iterations }, kekKey, innerKeyBytes * 8));
258
257
  if (pwOwn.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
259
258
  try {
@@ -279,7 +278,7 @@ KEM_WRAP[O("id-ml-kem-1024")] = "aes256-wrap";
279
278
  var KEM_WC = {}; KEM_WC[O("id-ml-kem-512")] = "ML-KEM-512"; KEM_WC[O("id-ml-kem-768")] = "ML-KEM-768"; KEM_WC[O("id-ml-kem-1024")] = "ML-KEM-1024";
280
279
 
281
280
  // CMSORIforKEMOtherInfo ::= SEQUENCE { wrap AlgorithmIdentifier, kekLength INTEGER,
282
- // ukm [0] EXPLICIT OCTET STRING OPTIONAL } -- the RFC 9629 sec. 5 KDF info, one builder both sides.
281
+ // ukm [0] EXPLICIT OCTET STRING OPTIONAL }, the RFC 9629 sec. 5 KDF info, one builder both sides.
283
282
  function _kemOtherInfo(wrapName, kekBytes, ukm) {
284
283
  var kids = [_algId(wrapName, "absent"), b.integer(BigInt(kekBytes))];
285
284
  if (ukm) kids.push(b.explicit(0, b.octetString(ukm)));
@@ -310,7 +309,7 @@ async function _buildKemri(cek, cert, opts) {
310
309
  return { tag: 4, node: b.sequence([b.oid(O("kem")), kemri]) };
311
310
  } finally {
312
311
  // RFC 9629 sec. 7 asks the SENDER to discard the shared secret and KEK once the recipient entry
313
- // is built -- and to use a fresh secret per recipient, so this runs per call rather than once at
312
+ // is built, and to use a fresh secret per recipient, so this runs per call and not once at
314
313
  // the end of a multi-recipient message. In a `finally`, so a wrap or encoding failure does not
315
314
  // leave them behind. The CEK is the caller's and is wiped by no one here; kemct is public.
316
315
  // kem.sharedKey is the ArrayBuffer the engine returned and ss is this function's copy of it;
@@ -341,7 +340,7 @@ function _pwriFormat(cek) {
341
340
  var padLen = body.length % blk === 0 ? 0 : blk - (body.length % blk);
342
341
  if (body.length + padLen < 2 * blk) padLen += (2 * blk - (body.length + padLen));
343
342
  // and each hold a plaintext copy of the CEK; only the padded result is returned, so
344
- // the intermediates are cleared here rather than abandoned.
343
+ // the intermediates are cleared here and never abandoned.
345
344
  var wrapped = Buffer.concat([body, nodeCrypto.randomBytes(padLen)]);
346
345
  guard.secret.zeroizeAll([check, body], CmsError, "cms/bad-input", "a PWRI formatting intermediate");
347
346
  return wrapped;
@@ -382,7 +381,7 @@ async function _buildRecipient(cek, desc, opts) {
382
381
  function mergeOpts(opts, desc) {
383
382
  return { oaepHash: desc.oaepHash || opts.oaepHash, keyIdentifier: desc.keyIdentifier || opts.keyIdentifier, ukm: desc.ukm != null ? desc.ukm : opts.ukm };
384
383
  }
385
- // RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4] -- all IMPLICIT,
384
+ // RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4], all IMPLICIT,
386
385
  // so the arm's SEQUENCE tag is replaced by the context tag.
387
386
  function _taggedRecipient(r) {
388
387
  if (r.tag == null) return r.node;
@@ -407,8 +406,8 @@ async function encrypt(content, recipients, opts) {
407
406
  var contentType = opts.contentType || "data";
408
407
  var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
409
408
 
410
- // The CEK protects the content for EVERY recipient, so unlike a per-recipient shared secret it is
411
- // cleared ONCE, after the last recipient entry is built and the content is encrypted -- wiping it
409
+ // The CEK protects the content for every recipient, so unlike a per-recipient shared secret it is
410
+ // cleared once, after the last recipient entry is built and the content is encrypted. Wiping it
412
411
  // inside the recipient loop would destroy the key the remaining recipients must be given.
413
412
  try {
414
413
  // EncryptedData: a single non-array { cek } or { password } descriptor, no RecipientInfos.
@@ -451,9 +450,9 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
451
450
  var setOf = b.setOf(opts.authAttrs);
452
451
  aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
453
452
  }
454
- // A 16-octet (128-bit) GCM tag -- the strongest ICV and what OpenSSL emits, so the message
453
+ // A 16-octet (128-bit) GCM tag, the strongest ICV and what OpenSSL emits, so the message
455
454
  // interops across OpenSSL 3.5 / 4.x. The aes-ICVlen (16) is carried explicitly (RFC 5084 sec. 3.2
456
- // omits it ONLY when it equals the DEFAULT 12); it MUST equal the mac octet length (M42).
455
+ // omits it only when it equals the DEFAULT 12); it MUST equal the mac octet length (M42).
457
456
  var g = _gcmEncrypt(cek, nonce, contentBytes, aad, ca.keyBits, 16);
458
457
  var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), _gcmParams(nonce, 16)]), b.contextPrimitive(0, g.ct)]);
459
458
  var kids = [b.integer(0n), b.setOf(riNodes), eci];
@@ -483,8 +482,8 @@ function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
483
482
  }
484
483
 
485
484
  function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
486
- // A string / Uint8Array password is encoded into a buffer THIS toolkit allocated -- a credential
487
- // copy -- so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
485
+ // A string / Uint8Array password is encoded into a buffer this toolkit allocated, a credential
486
+ // copy, so it is cleared once the derivation has consumed it. A caller-supplied Buffer is
488
487
  // borrowed and left intact.
489
488
  var pwOwn2 = pbes2.passwordBytesOwned(desc.password, _err, "cms");
490
489
  var password = pwOwn2.bytes;
@@ -555,7 +554,7 @@ async function authenticate(content, recipients, opts) {
555
554
  if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
556
555
 
557
556
  // The MAC key is this path's content-encryption key: every recipient is given it, so it is cleared
558
- // ONCE at the end rather than per recipient, and only after the MAC has been computed over it.
557
+ // once at the end and not per recipient, and only after the MAC has been computed over it.
559
558
  var macKey = nodeCrypto.randomBytes(MAC_KEY_OCTETS);
560
559
  try {
561
560
  var recips = [];
@@ -575,8 +574,8 @@ async function authenticate(content, recipients, opts) {
575
574
  ];
576
575
  if (opts.authAttrs && opts.authAttrs.length) pairs = pairs.concat(opts.authAttrs);
577
576
  // Every authAttr (auto-built or caller-supplied) MUST be a well-formed Attribute SEQUENCE
578
- // { type OBJECT IDENTIFIER, values SET OF } and each type appears at most once (RFC 5652) -- so a
579
- // malformed or duplicate caller attribute is rejected BEFORE it is MACed and emitted, never left to
577
+ // { type OBJECT IDENTIFIER, values SET OF } and each type appears at most once (RFC 5652), so a
578
+ // malformed or duplicate caller attribute is rejected before it is MACed and emitted, never left to
580
579
  // fail an operator's parser downstream.
581
580
  var seenTypes = {};
582
581
  pairs.forEach(function (p) {
package/lib/cms-sign.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
4
  //
5
- // @internal -- the pki.cms.sign implementation. So the pki.cms namespace has ONE @module home,
5
+ // @internal -- the pki.cms.sign implementation. So the pki.cms namespace has one @module home,
6
6
  // the operator-facing @module pki.cms + the @primitive pki.cms.sign documentation block live in
7
7
  // cms-verify.js, which re-exports this sign function.
8
8
  //
@@ -33,10 +33,27 @@ function _err(code, message, cause) { return new CmsError(code, message, cause);
33
33
  // cms/<kind>), so its faults keep the cms/* codes.
34
34
  function _signE(kind, message, cause) { return new CmsError("cms/" + kind, message, cause); }
35
35
  function O(name) { return oid.byName(name); }
36
+
37
+ // Every option each verb READS, and nothing else. Derived from the signing paths themselves rather
38
+ // than from what callers happen to pass: an option this list omits is one the verb would ignore in
39
+ // silence, and the ones that matter most are the ones a caller would most expect to work.
40
+ // A misspelled option name is the shape this closes: it does not select the mode the caller meant,
41
+ // and without the gate the message is signed the other way with nothing said about it.
42
+ var KNOWN_SIGN_OPTS = {
43
+ signedAttributes: 1, signingTime: 1, additionalSignedAttributes: 1, unsignedAttributes: 1,
44
+ sid: 1, eContentType: 1, detached: 1, certificates: 1, pem: 1,
45
+ };
46
+ // Countersign has its own set: it takes signerIndex and countersignatureOf to SELECT which
47
+ // signature it attaches to, and has no content of its own, so eContentType, detached and
48
+ // unsignedAttributes are not among the options it reads.
49
+ var KNOWN_COUNTERSIGN_OPTS = {
50
+ signerIndex: 1, countersignatureOf: 1, signingTime: 1, certificates: 1, pem: 1,
51
+ signedAttributes: 1, additionalSignedAttributes: 1, sid: 1,
52
+ };
36
53
  // The shared producing-side helpers, bound to this domain's error class. Only the
37
- // post-sign self-check is used here -- the same one every other signer in the
38
- // toolkit runs -- so a key-only signer's declared public key is bound by evidence
39
- // rather than by the caller's word.
54
+ // post-sign self-check is used here (the same one every other signer in the
55
+ // toolkit runs), so a key-only signer's declared public key is bound by evidence
56
+ // and never by the caller's word.
40
57
  var NS = pkix.makeNS("cms", CmsError, oid);
41
58
  var _b = pkiBuild.makeBuilder({
42
59
  ErrorClass: CmsError, prefix: "cms", O: O, NS: NS,
@@ -85,10 +102,10 @@ function _buildSid(cert, useSki) {
85
102
  // Assemble a SignedAttributes SET from resolved attribute pairs (each { type: <dotted OID>, values:
86
103
  // [<build node or DER Buffer>] }), returning { setOf, wire }: `setOf` is the canonical DER SET OF
87
104
  // (tag 0x31, build.set SET-OF-sorts) the signature covers (RFC 5652 sec. 5.4); `wire` is the same
88
- // bytes with the on-wire [0] IMPLICIT tag (0xA0). Each attribute type appears AT MOST ONCE (sec.
89
- // 5.3) -- a duplicate throws. Shared by the top-level signer (content-type + message-digest +
105
+ // bytes with the on-wire [0] IMPLICIT tag (0xA0). Each attribute type appears at most once (sec.
106
+ // 5.3), so a duplicate throws. Shared by the top-level signer (content-type + message-digest +
90
107
  // signing-time + caller attrs) and the countersignature builder (message-digest over the target
91
- // signature octets + signing-time, content-type OMITTED per sec. 11.4).
108
+ // signature octets + signing-time, content-type omitted per sec. 11.4).
92
109
  function _buildSignedAttrs(pairs) {
93
110
  var seenTypes = {};
94
111
  var attrs = pairs.map(function (p) {
@@ -112,16 +129,16 @@ function _resolveAttrPairs(list, what) {
112
129
  });
113
130
  }
114
131
 
115
- // content-type / message-digest / signing-time are content-binding SIGNED attributes and MUST NOT
116
- // appear as unsigned attributes (RFC 5652 sec. 11.1/11.2/11.3 -- the parser's ATTR_FORBIDDEN_IN).
132
+ // content-type / message-digest / signing-time are content-binding signed attributes and MUST NOT
133
+ // appear as unsigned attributes (RFC 5652 sec. 11.1/11.2/11.3, the parser's ATTR_FORBIDDEN_IN).
117
134
  var UNSIGNED_FORBIDDEN = {};
118
135
  UNSIGNED_FORBIDDEN[O("contentType")] = "content-type";
119
136
  UNSIGNED_FORBIDDEN[O("messageDigest")] = "message-digest";
120
137
  UNSIGNED_FORBIDDEN[O("signingTime")] = "signing-time";
121
138
 
122
139
  // Build the [1] IMPLICIT unsignedAttrs SET OF (on-wire tag 0xA1) from opts.unsignedAttributes
123
- // (each { type, values: [DER] } -- a countersignature, an RFC 3161 timestamp token), or null when
124
- // absent/empty. Unsigned attributes are OUTSIDE the signature; a placement-forbidden type or a
140
+ // (each { type, values: [DER] }, such as a countersignature or an RFC 3161 timestamp token), or null when
141
+ // absent/empty. Unsigned attributes sit outside the signature; a placement-forbidden type or a
125
142
  // duplicate type (RFC 5652 sec. 11 / sec. 5.3) is a config-time cms/bad-input.
126
143
  function _buildUnsignedAttrs(list) {
127
144
  if (list == null) return null;
@@ -144,9 +161,9 @@ function _buildUnsignedAttrs(list) {
144
161
  // SignedData digestAlgorithms + certificates sets).
145
162
  function _buildSignerInfo(signer, content, eContentType, opts) {
146
163
  var so = signer || {};
147
- // A KEY-ONLY signer: `{ key, spki, keyIdentifier }` with no certificate. RFC
164
+ // A key-only signer: `{ key, spki, keyIdentifier }` with no certificate. RFC
148
165
  // 5272 sec. 3.2 requires exactly this when a Full PKI Request is signed with the
149
- // key of a certification request it carries -- there is no certificate yet, the
166
+ // key of a certification request it carries: there is no certificate yet, the
150
167
  // sid MUST be the subjectKeyIdentifier form, and its value MUST be the SKI the
151
168
  // request itself declares. The scheme resolver only ever reads
152
169
  // `cert.subjectPublicKeyInfo.algorithm`, so the request's own parsed SPKI stands
@@ -217,9 +234,9 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
217
234
  // comparing it works only for key material this process can export, and the keys
218
235
  // this signer serves are often exactly the ones that cannot be: a non-extractable
219
236
  // CryptoKey, an HSM handle, or a composite `{ mldsa, trad }` pair that is two keys
220
- // rather than one. Every one of those can still be checked the direct way -- the
237
+ // and not one. Every one of those can still be checked the direct way: the
221
238
  // signature it just produced either verifies under the declared public key or it
222
- // does not -- so there is no key kind this has to take on trust.
239
+ // does not, so there is no key kind this has to take on trust.
223
240
  //
224
241
  // This is the same post-sign self-check every other signer in the toolkit runs,
225
242
  // through the same shared helper. It costs one verification per SignerInfo, which
@@ -227,8 +244,8 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
227
244
  function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert) {
228
245
  var declared = keyOnly ? soSpki : (cert && cert.subjectPublicKeyInfo && cert.subjectPublicKeyInfo.bytes);
229
246
  // A parsed certificate that does not retain its SPKI bytes cannot be checked this
230
- // way. That is not a shape x509.parse produces -- and if it ever were, refusing is
231
- // the answer rather than skipping, since a skip is the fail-open this prevents.
247
+ // way. That is not a shape x509.parse produces, and if it ever were, refusing is
248
+ // the answer, never skipping, since a skip is the fail-open this prevents.
232
249
  if (!declared) {
233
250
  throw _signE("bad-input",
234
251
  "a signer certificate did not surface its subjectPublicKeyInfo, so the signature it produced could " +
@@ -253,21 +270,21 @@ function _timeValue(when) {
253
270
 
254
271
  // Normalize a signer certificate input to its raw DER (DER Buffer / PEM string / Uint8Array).
255
272
  // The same bytes drive scheme resolution and the certificates [0] embedding, so a parsed
256
- // certificate (which does not retain its full DER) is rejected -- pass DER or PEM.
273
+ // certificate (which does not retain its full DER) is rejected; pass DER or PEM.
257
274
  // The stand-in a key-only signer resolves its signature scheme from: the parsed
258
275
  // SubjectPublicKeyInfo of the key that will sign. Only `.subjectPublicKeyInfo` is
259
- // read downstream, so this deliberately carries nothing else -- a fuller fake
276
+ // read downstream, so this deliberately carries nothing else; a fuller fake
260
277
  // would invite code to start trusting fields no certificate actually backs.
261
278
  function _keyOnlyCertStandIn(spkiDer) {
262
279
  var alg;
263
280
  try {
264
281
  // SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
265
- // The WHOLE structure, not just the field this function goes on to read. For an
266
- // OPAQUE key handle the derivation check downstream is deliberately skipped, so
267
- // this is the ONLY thing standing between a caller's bytes and a SignerInfo that
282
+ // The whole structure, not just the field this function goes on to read. For an
283
+ // opaque key handle the derivation check downstream is deliberately skipped, so
284
+ // this is the only thing standing between a caller's bytes and a SignerInfo that
268
285
  // declares them: a SEQUENCE carrying an algorithm and nothing else, or a third
269
286
  // field, would be emitted as the signer's public key and resolve to nothing for
270
- // anyone trying to verify. The key VALUE is not interpreted -- only that there
287
+ // anyone trying to verify. The key value is not interpreted, only that there
271
288
  // is one, in the field the structure reserves for it.
272
289
  var node = asn1.decode(spkiDer);
273
290
  if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children ||
@@ -288,7 +305,7 @@ function _keyOnlyCertStandIn(spkiDer) {
288
305
  "a key-only signer's spki algorithm is SEQUENCE { algorithm OID, parameters OPTIONAL } (RFC 5280 sec. 4.1.1.2)");
289
306
  }
290
307
  // The same shape schema-x509 surfaces: the algorithm OID plus its parameters
291
- // as the RAW TLV (or null when absent) -- the resolver re-decodes those bytes
308
+ // as the raw TLV (or null when absent); the resolver re-decodes those bytes
292
309
  // for an EC named curve and for the RSASSA-PSS hash pinning, so handing it a
293
310
  // different representation here would break exactly those two algorithms.
294
311
  alg = {
@@ -312,9 +329,9 @@ function _keyOnlyKeyId(so) {
312
329
  throw _err("cms/bad-input",
313
330
  "a key-only signer requires keyIdentifier -- the subjectKeyIdentifier the certification request declares (RFC 5272 sec. 3.2)");
314
331
  }
315
- // A key identifier is BYTES, so the type is checked rather than coerced. Buffer.from
316
- // ACCEPTS far more than it should mean here: Buffer.from(20) allocates twenty zero
317
- // octets and Buffer.from("a1b2") takes the ASCII of the text rather than the two
332
+ // A key identifier is bytes, so the type is checked and never coerced. Buffer.from
333
+ // accepts far more than it should mean here: Buffer.from(20) allocates twenty zero
334
+ // octets and Buffer.from("a1b2") takes the ASCII of the text, not the two
318
335
  // octets a reader means by it. Either would emit a structurally valid
319
336
  // SignerIdentifier carrying an identifier the caller never asked for, which no
320
337
  // verifier can match back to the certification request (RFC 5272 sec. 3.2).
@@ -325,8 +342,10 @@ function _keyOnlyKeyId(so) {
325
342
 
326
343
  function _normCertDer(c) {
327
344
  if (c == null) throw _err("cms/bad-input", "each signer requires a certificate (cert)");
328
- if (c instanceof Uint8Array && !Buffer.isBuffer(c)) c = Buffer.from(c); // a Uint8Array -> Buffer (below)
329
- if (Buffer.isBuffer(c)) return c[0] === 0x30 ? c : _pemToDer(c.toString("latin1")); // DER as-is, else PEM
345
+ if (c instanceof Uint8Array || Buffer.isBuffer(c)) {
346
+ c = guard.bytes.snapshot(c, CmsError, "cms/bad-input", "a signer certificate");
347
+ return c[0] === 0x30 ? c : _pemToDer(c.toString("latin1")); // DER as-is, else PEM
348
+ }
330
349
  if (typeof c === "string") return _pemToDer(c);
331
350
  throw _err("cms/bad-input", "a signer certificate must be a DER Buffer or a PEM string");
332
351
  }
@@ -340,12 +359,22 @@ function _pemToDer(text) {
340
359
  // Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
341
360
  // synchronous because they read the caller's mutable content and signer list.
342
361
  function sign(content, signers, opts) {
343
- return guard.async.deferred(function () { return _sign(content, signers, opts); });
362
+ // Every caller-owned argument copied at entry and released when the call settles; see the note
363
+ // on the same call in x509-sign. Here it is what makes the attribute-shaped-content refusal below
364
+ // hold: the value that decides it and the value that gets signed are now the same read.
365
+ return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
366
+ [content, "content"], [signers, "the signer list"], [opts, "pki.cms.sign options"],
367
+ ], _sign);
344
368
  }
345
369
 
346
370
  function _sign(content, signers, opts) {
347
371
  opts = opts || {};
348
372
  if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.sign options must be an object");
373
+ guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
374
+ // The arguments were copied at entry (see `sign` above), which is what makes the refusal below
375
+ // hold: flipping signedAttributes from true to false after the call returns would otherwise skip
376
+ // the attribute-shaped-content check while the signer signs that content directly, which is the
377
+ // very signature the stripping attack needs.
349
378
  var contentBuf = _toBuf(content, "content");
350
379
  var list = Array.isArray(signers) ? signers : [signers];
351
380
  if (!list.length) throw _err("cms/bad-input", "pki.cms.sign requires at least one signer");
@@ -363,13 +392,25 @@ function _sign(content, signers, opts) {
363
392
  "a key-only signer must be the ONLY SignerInfo in a Full PKI Request (RFC 5272 sec. 3.2)");
364
393
  }
365
394
  // RFC 5652 sec. 5.3: signed attributes MUST be present (carrying a content-type attribute)
366
- // whenever the encapsulated content type is not id-data -- so signedAttributes:false is only
395
+ // whenever the encapsulated content type is not id-data, so signedAttributes:false is only
367
396
  // valid for id-data content. Refusing it here keeps cms.sign from emitting a non-conformant
368
397
  // SignedData (e.g. a timestamp token, id-ct-TSTInfo, with no signed attributes).
369
398
  if (opts.signedAttributes === false && eContentType !== OID_DATA) {
370
399
  throw _err("cms/bad-input", "signed attributes are required when eContentType is not id-data (RFC 5652 sec. 5.3)");
371
400
  }
372
- // A supplied signing-time MUST be a valid Date (or false to omit the attribute) -- never a
401
+ // The signer's half of the signed-attribute stripping problem
402
+ // (draft-vangeest-lamps-cms-euf-cma-signeddata, Attack Type 2). Signing attribute-shaped content
403
+ // without attributes produces a signature that can afterwards be promoted into an
404
+ // attributes-present message, because the signature does not commit to which mode was used: the
405
+ // attacker attaches the signed bytes as the SignedAttributes and swaps in whatever content their
406
+ // message-digest attribute names. Refusing to mint the ambiguous signature is the only point at
407
+ // which this direction can be stopped -- by the time it is a message, the damage is done.
408
+ if (opts.signedAttributes === false && cms.looksLikeSignedAttributes(contentBuf)) {
409
+ throw _err("cms/ambiguous-content", "this content is itself an encoded SignedAttributes block, so signing " +
410
+ "it WITHOUT signed attributes would produce a signature that could be re-presented as one over " +
411
+ "attributes (RFC 5652 sec. 5.4); sign it with signed attributes instead");
412
+ }
413
+ // A supplied signing-time MUST be a valid Date (or false to omit the attribute), never a
373
414
  // silently-ignored non-Date or an Invalid Date that would encode a garbage Time.
374
415
  if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
375
416
 
@@ -403,7 +444,7 @@ function _sign(content, signers, opts) {
403
444
  });
404
445
  }
405
446
 
406
- // Dedupe certificate DERs (two signers may share a cert -- embed it once).
447
+ // Dedupe certificate DERs (two signers may share a cert, so embed it once).
407
448
  function _dedupe(ders) {
408
449
  var seen = {}, out = [];
409
450
  ders.forEach(function (d) { var k = d.toString("hex"); if (!seen[k]) { seen[k] = 1; out.push(d); } });
@@ -411,17 +452,16 @@ function _dedupe(ders) {
411
452
  }
412
453
 
413
454
  function _toBuf(v, what) {
414
- if (Buffer.isBuffer(v)) return v;
415
- if (v instanceof Uint8Array) return Buffer.from(v);
455
+ if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmsError, "cms/bad-input", what);
416
456
  throw _err("cms/bad-input", what + " must be a Buffer");
417
457
  }
418
458
 
419
459
  // ---- pki.cms.countersign (RFC 5652 sec. 11.4) ------------------------------
420
- // A countersignature is a SignerInfo (Countersignature ::= SignerInfo) over the CONTENTS of the
421
- // countersigned SignerInfo's signature OCTET STRING -- NOT the eContent -- attached as the
460
+ // A countersignature is a SignerInfo (Countersignature ::= SignerInfo) over the contents of the
461
+ // countersigned SignerInfo's signature OCTET STRING, never the eContent, attached as the
422
462
  // id-countersignature unsigned attribute. It reuses the whole build+sign flow (resolveSignScheme /
423
463
  // _buildSid / _buildSignedAttrs / signOverTbs); only the preimage (the target signature octets) and
424
- // the content-type OMISSION are the deltas, and the orchestrator splices the [1] unsignedAttrs into
464
+ // the omitted content-type are the deltas, and the orchestrator splices the [1] unsignedAttrs into
425
465
  // an existing SignedData while preserving the targeted SignerInfo's signed bytes BYTE-FOR-BYTE.
426
466
 
427
467
  // Resolve opts.signerIndex (a number, an array of numbers, or "all"; default 0) to primary indices.
@@ -435,8 +475,8 @@ function _resolveSignerIndices(spec, n) {
435
475
  }
436
476
 
437
477
  // Build one countersignature value (RFC 5652 sec. 11.4) over `targetSigOctets`: message-digest bound
438
- // to digest(targetSigOctets) under the countersignature's own digestAlgorithm, content-type OMITTED,
439
- // signed through the SAME per-algorithm scheme machinery as a top-level signer.
478
+ // to digest(targetSigOctets) under the countersignature's own digestAlgorithm, content-type omitted,
479
+ // signed through the same per-algorithm scheme machinery as a top-level signer.
440
480
  function _buildCountersignature(targetSigOctets, countersigner, opts) {
441
481
  var so = countersigner || {};
442
482
  var certDer = _normCertDer(so.cert);
@@ -463,9 +503,9 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
463
503
  });
464
504
  }
465
505
 
466
- // Build the [1] IMPLICIT unsignedAttrs bytes for a SignerInfo, merging `newCsValues` into the ONE
467
- // id-countersignature attribute (RFC 5652 sec. 11: single instance per type, MULTIPLE values), and
468
- // keeping every OTHER unsigned attribute and every EXISTING countersignature value verbatim.
506
+ // Build the [1] IMPLICIT unsignedAttrs bytes for a SignerInfo, merging `newCsValues` into the single
507
+ // id-countersignature attribute (RFC 5652 sec. 11: one instance per type, multiple values), and
508
+ // keeping every other unsigned attribute and every existing countersignature value verbatim.
469
509
  function _mergeCountersig(uaNode, newCsValues) {
470
510
  var CS = O("countersignature");
471
511
  var others = [], csValues = [];
@@ -539,14 +579,20 @@ function _targetPreimage(siNode, opts) {
539
579
  }
540
580
 
541
581
  // pki.cms.countersign -- documented by the @primitive block in cms-verify.js (the @module pki.cms home).
542
- // Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async).
582
+ // Documented `-> Promise`, so a fault leaves as a rejection (guard-async).
543
583
  function countersign(cmsInput, signers, opts) {
544
- return guard.async.deferred(function () { return _countersign(cmsInput, signers, opts); });
584
+ // Every caller-owned argument copied at entry and released when the call settles; see the note
585
+ // on the same call in x509-sign. `signerIndex` and `countersignatureOf` select which signature is
586
+ // countersigned, so a late read could attach the countersignature to a different one.
587
+ return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
588
+ [cmsInput, "the CMS message"], [signers, "the signer list"], [opts, "pki.cms.countersign options"],
589
+ ], _countersign);
545
590
  }
546
591
 
547
592
  function _countersign(cmsInput, signers, opts) {
548
593
  opts = opts || {};
549
594
  if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("cms/bad-input", "pki.cms.countersign options must be an object");
595
+ guard.identifier.assertKnownKeys(opts, KNOWN_COUNTERSIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
550
596
  var list = Array.isArray(signers) ? signers : [signers];
551
597
  if (!list.length) throw _err("cms/bad-input", "pki.cms.countersign requires at least one countersigner");
552
598
  if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
@@ -576,7 +622,7 @@ function _countersign(cmsInput, signers, opts) {
576
622
  return opts.countersignatureOf == null ? _appendCountersigs(siNode, byTarget[idx]) : _spliceNested(siNode, opts.countersignatureOf, byTarget[idx]);
577
623
  }));
578
624
 
579
- // Rebuild SignedData: version, digestAlgorithms (UNCHANGED -- a countersignature digest is not a
625
+ // Rebuild SignedData: version, digestAlgorithms (unchanged, since a countersignature digest is not a
580
626
  // SignedData digestAlgorithm), encapContentInfo, certificates [0]?, crls [1]?, the new signerInfos.
581
627
  var certsNode = null, crlsNode = null;
582
628
  for (var i = 3; i < sdKids.length - 1; i++) {
@@ -597,21 +643,21 @@ function _countersign(cmsInput, signers, opts) {
597
643
  });
598
644
  }
599
645
 
600
- // Coverage residual -- three defensive branches are unreachable through the shipped path:
601
- // * `_skiValue`'s `cert.extensions || []` fallback -- x509.parse always surfaces `extensions`
646
+ // Coverage residual: three defensive branches are unreachable through the shipped path.
647
+ // * `_skiValue`'s `cert.extensions || []` fallback. x509.parse always surfaces `extensions`
602
648
  // as an array (empty when absent), so the `|| []` never fires.
603
- // * `_assertKeyMatchesScheme`'s `key.algorithm || {}` -- a WebCrypto CryptoKey always carries
649
+ // * `_assertKeyMatchesScheme`'s `key.algorithm || {}`. A WebCrypto CryptoKey always carries
604
650
  // an `algorithm`, so the `|| {}` fallback never fires.
605
- // * `_assertKeyMatchesScheme`'s `!ka.hash` guard -- an `imp.hash` is set only for an RSA
651
+ // * `_assertKeyMatchesScheme`'s `!ka.hash` guard. An `imp.hash` is set only for an RSA
606
652
  // scheme, which requires `ka.name` to already equal the RSA name (else the earlier name
607
653
  // check throws); an RSA CryptoKey always carries a `hash`, so `!ka.hash` never fires.
608
654
  // Countersign-side residuals also unreachable through the shipped path:
609
- // * `_resolveSignerIndices`'s `n < 1` throw -- a parsed SignedData always carries at least one
655
+ // * `_resolveSignerIndices`'s `n < 1` throw. A parsed SignedData always carries at least one
610
656
  // SignerInfo, so the default index [0] is always in range.
611
- // * `_spliceNested`'s no-countersignature / index-out-of-range / not-found throws -- the same node
612
- // is validated by `_targetPreimage` FIRST (it computes the nested preimage before the build), so
657
+ // * `_spliceNested`'s no-countersignature / index-out-of-range / not-found throws. The same node
658
+ // is validated by `_targetPreimage` first (it computes the nested preimage before the build), so
613
659
  // by the time `_spliceNested` re-walks it those conditions cannot hold; the checks are
614
660
  // belt-and-suspenders against a future caller reordering the two.
615
- // * the `crls [1]` preservation branches in `countersign` -- pki.cms.sign never emits a crls field,
661
+ // * the `crls [1]` preservation branches in `countersign`. pki.cms.sign never emits a crls field,
616
662
  // so a store this producer countersigns never carries one to preserve.
617
663
  module.exports = { sign: sign, countersign: countersign };