@blamejs/pki 0.5.31 → 0.5.33

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 (52) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +3 -3
  3. package/lib/acme.js +347 -90
  4. package/lib/asn1-der.js +93 -33
  5. package/lib/cmc-build.js +19 -8
  6. package/lib/cmc-verify.js +4 -4
  7. package/lib/cmp-build.js +36 -6
  8. package/lib/cmp-session.js +5 -5
  9. package/lib/cmp-verify.js +86 -16
  10. package/lib/cms-compress.js +2 -2
  11. package/lib/cms-decrypt.js +3 -3
  12. package/lib/cms-encrypt.js +1 -1
  13. package/lib/cms-sign.js +214 -93
  14. package/lib/cms-verify.js +363 -237
  15. package/lib/crl-sign.js +4 -4
  16. package/lib/crmf-sign.js +2 -1
  17. package/lib/csr-sign.js +1 -4
  18. package/lib/ct.js +8 -4
  19. package/lib/est.js +234 -34
  20. package/lib/framework-error.js +20 -6
  21. package/lib/guard-bytes.js +56 -4
  22. package/lib/guard-intrinsic.js +13 -0
  23. package/lib/http-digest.js +69 -16
  24. package/lib/http-retry-after.js +68 -24
  25. package/lib/http-transport.js +60 -6
  26. package/lib/inspect.js +13 -2
  27. package/lib/key.js +2 -2
  28. package/lib/lint.js +1 -1
  29. package/lib/mime.js +27 -4
  30. package/lib/ocsp.js +7 -7
  31. package/lib/oid.js +16 -0
  32. package/lib/path-validate.js +76 -18
  33. package/lib/pbes2.js +2 -2
  34. package/lib/pkcs12-build.js +2 -2
  35. package/lib/pki-build.js +6 -6
  36. package/lib/rfc3339.js +75 -17
  37. package/lib/schema-c509.js +36 -9
  38. package/lib/schema-cmc.js +3 -3
  39. package/lib/schema-pkix.js +174 -25
  40. package/lib/schema-tsp.js +10 -1
  41. package/lib/sign-scheme.js +1 -1
  42. package/lib/sigstore.js +52 -9
  43. package/lib/smime.js +86 -6
  44. package/lib/trust.js +114 -14
  45. package/lib/tsp-sign.js +39 -27
  46. package/lib/validator-tpm.js +2 -2
  47. package/lib/webauthn-mds.js +24 -6
  48. package/lib/webauthn.js +3 -3
  49. package/lib/webcrypto.js +80 -1
  50. package/lib/x509-sign.js +1 -1
  51. package/package.json +2 -2
  52. package/sbom.cdx.json +6 -6
package/lib/cms-verify.js CHANGED
@@ -52,6 +52,13 @@ var pkix = require("./schema-pkix");
52
52
 
53
53
  var CmsError = frameworkError.CmsError;
54
54
  function _err(code, message, cause) { return new CmsError(code, message, cause); }
55
+ // Promise.resolve captured at load and invoked through the captured Reflect.apply, so a streamed
56
+ // opts.content -- whose iterator runs during the digest await and can replace Promise.resolve -- cannot
57
+ // turn a resolved { ok: false } verdict into acceptance. The verify path resolves every value through
58
+ // this and awaits every step: Promise.all is deliberately NOT used, since its spec-internal element
59
+ // handling dispatches through the caller-replaceable Promise.prototype.then, which await does not.
60
+ var _Promise = Promise, _promiseResolveRaw = Promise.resolve;
61
+ function _pResolve(v) { return intrinsic.apply(_promiseResolveRaw, _Promise, [v]); }
55
62
 
56
63
  var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
57
64
  var OID_CONTENT_TYPE = oid.byName("contentType");
@@ -76,8 +83,8 @@ var SIG_HASH = { sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "S
76
83
  // Is `name` a message-digest algorithm this verifier supports?
77
84
  function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
78
85
  // The digest of `content` under the named algorithm, resolved to a Buffer.
79
- function _computeDigest(name, content) {
80
- return subtle.digest(DIGEST_HASH[name], content).then(function (d) { return Buffer.from(d); });
86
+ async function _computeDigest(name, content) {
87
+ return intrinsic.bufferFrom(await subtle.digest(DIGEST_HASH[name], content));
81
88
  }
82
89
  // A signatureAlgorithm name -> its verify scheme. A combined OID (sha256WithRSAEncryption,
83
90
  // ecdsaWithSHA256) carries its own hash; a bare key OID (rsaEncryption, ecPublicKey) takes the
@@ -123,7 +130,7 @@ EC_CURVE[oid.byName("secp384r1")] = { curve: "P-384", coordLen: 48 };
123
130
  EC_CURVE[oid.byName("secp521r1")] = { curve: "P-521", coordLen: 66 };
124
131
 
125
132
  function _toBuf(v, what) {
126
- if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, CmsError, "cms/bad-input", what);
133
+ if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, CmsError, "cms/bad-input", what);
127
134
  throw _err("cms/bad-input", what + " must be a Buffer");
128
135
  }
129
136
 
@@ -136,9 +143,11 @@ function _findSignerCerts(sid, parsedCerts) {
136
143
  for (var i = 0; i < parsedCerts.length; i++) {
137
144
  var c = parsedCerts[i];
138
145
  if (sid.subjectKeyIdentifier != null) {
139
- if (c.ski && c.ski.equals(_toBuf(sid.subjectKeyIdentifier, "sid.subjectKeyIdentifier"))) out.push(c);
146
+ // Captured equality: over a streamed content the untrusted iterator ran during the digest await
147
+ // and could replace Buffer.prototype.equals to match the wrong certificate to a signer.
148
+ if (c.ski && intrinsic.bufferEquals(c.ski, _toBuf(sid.subjectKeyIdentifier, "sid.subjectKeyIdentifier"))) intrinsic.push(out, c);
140
149
  } else if (sid.issuer && sid.serialNumberHex != null) {
141
- if (c.cert.serialNumberHex === sid.serialNumberHex && guard.name.dnEqual(c.cert.issuer.rdns, sid.issuer.rdns, _err, "cms/bad-name", "the signer certificate issuer")) out.push(c);
150
+ if (c.cert.serialNumberHex === sid.serialNumberHex && guard.name.dnEqual(c.cert.issuer.rdns, sid.issuer.rdns, _err, "cms/bad-name", "the signer certificate issuer")) intrinsic.push(out, c);
142
151
  }
143
152
  }
144
153
  return out;
@@ -150,27 +159,29 @@ function _findSignerCerts(sid, parsedCerts) {
150
159
  // through to the next -- so a colliding certificate cannot make a valid signature read invalid.
151
160
  // When no candidate verifies, a plain false is a code-less verdict; the last structural fault's
152
161
  // code is surfaced if one occurred (so the diagnostic is not lost).
153
- function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, sid, candidates, pssSalt, expectedKeyOid) {
162
+ async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, sid, candidates, pssSalt, expectedKeyOid) {
154
163
  var lastErr = null;
155
- function attempt(idx) {
156
- if (idx >= candidates.length) {
157
- return lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
158
- : { ok: false, sid: sid, cert: candidates[0].der };
159
- }
164
+ for (var idx = 0; idx < candidates.length; idx++) {
160
165
  var c = candidates[idx];
161
166
  // One-shot family (EdDSA/ML-DSA): the signer cert public-key algorithm OID MUST equal the
162
167
  // SignerInfo signatureAlgorithm OID. A candidate whose SPKI disagrees is skipped with a precise
163
168
  // verdict; importing it under the wrong name would surface a foreign webcrypto/data throw.
164
169
  if (expectedKeyOid && c.cert.subjectPublicKeyInfo.algorithm.oid !== expectedKeyOid) {
165
170
  lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
166
- return attempt(idx + 1);
171
+ continue;
172
+ }
173
+ // `await`, not `.then`: over a streamed content the untrusted iterator ran during the digest await
174
+ // and could replace Promise.prototype.then to return a fabricated `true`; await is immune (it does
175
+ // not dispatch through the caller-replaceable prototype method).
176
+ try {
177
+ var ok = await _verifySignature(scheme, sigHash, sigBytes, c.cert.subjectPublicKeyInfo.bytes, signedBytes, _certCurveOid(c.cert), pssSalt);
178
+ if (ok === true) return { ok: true, sid: sid, cert: c.der };
179
+ } catch (e) {
180
+ lastErr = (e instanceof CmsError) ? e : _err("cms/verify-error", "the SignerInfo signature could not be evaluated", e);
167
181
  }
168
- return Promise.resolve()
169
- .then(function () { return _verifySignature(scheme, sigHash, sigBytes, c.cert.subjectPublicKeyInfo.bytes, signedBytes, _certCurveOid(c.cert), pssSalt); })
170
- .then(function (ok) { return ok === true ? { ok: true, sid: sid, cert: c.der } : attempt(idx + 1); },
171
- function (e) { lastErr = (e instanceof CmsError) ? e : _err("cms/verify-error", "the SignerInfo signature could not be evaluated", e); return attempt(idx + 1); });
172
182
  }
173
- return attempt(0);
183
+ return lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
184
+ : { ok: false, sid: sid, cert: candidates[0].der };
174
185
  }
175
186
 
176
187
  // The cert's subjectKeyIdentifier extension value (the raw key id), or null.
@@ -255,14 +266,17 @@ function _resolvePss(paramsBytes) {
255
266
  // Verify the signature over `signedBytes` with the signer certificate's public key under the
256
267
  // resolved scheme + hash. Resolves true/false (a false verdict is a verdict); a structural
257
268
  // fault throws a typed CmsError.
258
- function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOid, pssSalt) {
269
+ async function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOid, pssSalt) {
270
+ // `await` throughout, never `.then`: a streamed content's untrusted iterator ran during the digest
271
+ // await and could replace Promise.prototype.then to answer a fabricated `true`; await does not
272
+ // dispatch through it.
259
273
  if (scheme.kind === "rsa") {
260
- return subtle.importKey("spki", spki, { name: "RSASSA-PKCS1-v1_5", hash: hashName }, false, ["verify"])
261
- .then(function (k) { return subtle.verify({ name: "RSASSA-PKCS1-v1_5" }, k, sigBytes, signedBytes); });
274
+ var kRsa = await subtle.importKey("spki", spki, { name: "RSASSA-PKCS1-v1_5", hash: hashName }, false, ["verify"]);
275
+ return subtle.verify({ name: "RSASSA-PKCS1-v1_5" }, kRsa, sigBytes, signedBytes);
262
276
  }
263
277
  if (scheme.kind === "rsapss") {
264
- return subtle.importKey("spki", spki, { name: "RSA-PSS", hash: hashName }, false, ["verify"])
265
- .then(function (k) { return subtle.verify({ name: "RSA-PSS", saltLength: pssSalt }, k, sigBytes, signedBytes); });
278
+ var kPss = await subtle.importKey("spki", spki, { name: "RSA-PSS", hash: hashName }, false, ["verify"]);
279
+ return subtle.verify({ name: "RSA-PSS", saltLength: pssSalt }, kPss, sigBytes, signedBytes);
266
280
  }
267
281
  if (scheme.kind === "ec") {
268
282
  var ec = EC_CURVE[curveOid];
@@ -270,23 +284,23 @@ function _verifySignature(scheme, hashName, sigBytes, spki, signedBytes, curveOi
270
284
  // 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
271
285
  // curve order n, not only the r=s=0 forgery). A verifier that knows the curve MUST use it.
272
286
  var raw = validator.sig.ecdsaDerToP1363(sigBytes, ec.curve, CmsError, "cms/bad-signature");
273
- return subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"])
274
- .then(function (k) { return subtle.verify({ name: "ECDSA", hash: hashName }, k, raw, signedBytes); });
287
+ var kEc = await subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: ec.curve }, false, ["verify"]);
288
+ return subtle.verify({ name: "ECDSA", hash: hashName }, kEc, raw, signedBytes);
275
289
  }
276
290
  if (scheme.kind === "mldsa" || scheme.kind === "slhdsa") {
277
291
  // ML-DSA (RFC 9882) / SLH-DSA (RFC 9814): a one-shot post-quantum verify, pure mode, empty
278
292
  // context -- no external signature hash and no Edwards-point validation (FIPS 204 sec. 5.3 /
279
293
  // FIPS 205 sec. 10.3; node structurally validates the SPKI on import). The signature is raw and
280
294
  // fixed-length (not ECDSA-DER).
281
- return subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"])
282
- .then(function (k) { return subtle.verify({ name: scheme.name }, k, sigBytes, signedBytes); });
295
+ var kPq = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
296
+ return subtle.verify({ name: scheme.name }, kPq, sigBytes, signedBytes);
283
297
  }
284
298
  // EdDSA -- the WebCrypto name follows the signing key's SPKI OID (Ed25519 / Ed448). node/
285
299
  // OpenSSL imports any Ed25519/Ed448 SPKI without validating the point, and a low-order (e.g.
286
300
  // all-zeroes) key verifies a forged signature, so reject a non-full-order point first.
287
301
  _requireValidEdPoint(spki, scheme.name);
288
- return subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"])
289
- .then(function (k) { return subtle.verify({ name: scheme.name }, k, sigBytes, signedBytes); });
302
+ var kEd = await subtle.importKey("spki", spki, { name: scheme.name }, false, ["verify"]);
303
+ return subtle.verify({ name: scheme.name }, kEd, sigBytes, signedBytes);
290
304
  }
291
305
 
292
306
  // The EdDSA signer key's point MUST be a valid, canonical, full-order Edwards point: reject an
@@ -313,7 +327,7 @@ function _certCurveOid(cert) {
313
327
  function _decodeSignedAttrs(setOfBytes) {
314
328
  var set = asn1.decode(setOfBytes);
315
329
  if (set.tagClass !== "universal" || set.tagNumber !== asn1.TAGS.SET || !set.children) throw _err("cms/bad-signed-attrs", "signedAttrs is not a SET OF Attribute");
316
- return set.children.map(function (attr) {
330
+ return intrinsic.map(set.children, function (attr) {
317
331
  if (attr.tagClass !== "universal" || attr.tagNumber !== asn1.TAGS.SEQUENCE || !attr.children || attr.children.length !== 2) throw _err("cms/bad-signed-attrs", "a signed Attribute is not a SEQUENCE { type, values }");
318
332
  var valuesSet = attr.children[1];
319
333
  if (valuesSet.tagClass !== "universal" || valuesSet.tagNumber !== asn1.TAGS.SET || !valuesSet.children) throw _err("cms/bad-signed-attrs", "a signed Attribute values field is not a SET OF");
@@ -327,23 +341,23 @@ function _decodeSignedAttrs(setOfBytes) {
327
341
  // preimage is the countersigned signature octets, not the sec. 5.4 content preimage, and no
328
342
  // content-type attribute is required or checked (_computeCountersigBytes). content/eContentType are
329
343
  // unused in that mode.
330
- function _verifyOne(si, content, eContentType, parsedCerts, csTarget) {
344
+ async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, digestByName) {
331
345
  // A composite ML-DSA SignerInfo (draft-ietf-lamps-cms-composite-sigs) is keyed by its composite
332
346
  // OID, not a SIG_SCHEME name; intercept it before the classical dispatch (its verify has no
333
347
  // single external signature hash, so it never routes through the SIG_SCHEME/sigHash gate below).
334
348
  var composite = compositeSig.COMPOSITE_ALGS[si.signatureAlgorithm.oid];
335
- if (composite) return _verifyComposite(si, composite, content, eContentType, parsedCerts, csTarget);
349
+ if (composite) return _verifyComposite(si, composite, content, eContentType, parsedCerts, csTarget, digestByName);
336
350
  var scheme = SIG_SCHEME[si.signatureAlgorithm.name];
337
- if (!scheme) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported signature algorithm " + JSON.stringify(si.signatureAlgorithm.name) });
351
+ if (!scheme) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported signature algorithm " + JSON.stringify(si.signatureAlgorithm.name) });
338
352
  var digestHash = SIG_HASH[si.digestAlgorithm.name];
339
353
  // RSASSA-PSS takes its signature hash and salt length from the RSASSA-PSS-params, not the
340
354
  // digestAlgorithm; a non-conformant parameter set is a fail-closed unsupported verdict.
341
355
  var pss = scheme.kind === "rsapss" ? _resolvePss(si.signatureAlgorithm.parameters) : null;
342
- if (scheme.kind === "rsapss" && !pss) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported or non-conformant RSASSA-PSS parameters (RFC 4055)" });
356
+ if (scheme.kind === "rsapss" && !pss) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported or non-conformant RSASSA-PSS parameters (RFC 4055)" });
343
357
  // signatureAlgorithm is outside the signed preimage, so a forbidden/missing parameters field
344
358
  // (a present parameter on ECDSA/EdDSA, a non-NULL or absent one on RSA) must fail closed --
345
359
  // otherwise it is a parser differential a caller trusting the verdict inherits (RFC 5754).
346
- if (scheme.params && !_algParamsOk(scheme.params, si.signatureAlgorithm.parameters)) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.signatureAlgorithm.name + " signature algorithm parameters must be " + (scheme.params === "null" ? "DER NULL (RFC 4055)" : "absent (RFC 5758/8410)") });
360
+ if (scheme.params && !_algParamsOk(scheme.params, si.signatureAlgorithm.parameters)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.signatureAlgorithm.name + " signature algorithm parameters must be " + (scheme.params === "null" ? "DER NULL (RFC 4055)" : "absent (RFC 5758/8410)") });
347
361
  // The digestAlgorithm parameters, when present, MUST be a DER NULL (RFC 5754 sec. 2 accepts
348
362
  // absent OR NULL; a present non-NULL is malformed and, being outside the signed preimage,
349
363
  // must fail closed like the signatureAlgorithm parameters above). EXCEPT for an ML-DSA
@@ -351,39 +365,40 @@ function _verifyOne(si, content, eContentType, parsedCerts, csTarget) {
351
365
  // has no meaning there and MUST be ignored, so neither its parameters nor its name may reject.
352
366
  var dp = si.digestAlgorithm.parameters;
353
367
  var mldsaNoAttrs = scheme.kind === "mldsa" && !si.signedAttrsBytes;
354
- if (!mldsaNoAttrs && dp !== null && dp !== undefined && !_isDerNull(dp)) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " digest algorithm parameters must be absent or DER NULL (RFC 5754 sec. 2)" });
368
+ if (!mldsaNoAttrs && dp !== null && dp !== undefined && !_isDerNull(dp)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " digest algorithm parameters must be absent or DER NULL (RFC 5754 sec. 2)" });
355
369
  // A SHAKE digest algorithm identifier (id-shake128 / id-shake256) carries NO parameters at all --
356
370
  // not even a DER NULL (RFC 8702 sec. 3.1) -- so for a one-shot PQC SignerInfo (ML-DSA per RFC 9882,
357
371
  // SLH-DSA per RFC 9814 sec. 4, both citing RFC 8702) with signed attributes a present SHAKE
358
372
  // parameter fails closed. The SHA-2 digests (id-sha256/384/512) follow RFC 5754, which requires a
359
373
  // verifier to accept absent OR NULL (the generic rule above); the RFCs' omit-on-encode rule binds
360
374
  // the signer, not the verifier, so a SHA-2 NULL is not tightened here.
361
- if ((scheme.kind === "mldsa" || scheme.kind === "slhdsa") && si.signedAttrsBytes && (si.digestAlgorithm.name === "shake128" || si.digestAlgorithm.name === "shake256") && dp !== null && dp !== undefined) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "a SHAKE digestAlgorithm carries no parameters (RFC 8702 sec. 3.1)" });
375
+ if ((scheme.kind === "mldsa" || scheme.kind === "slhdsa") && si.signedAttrsBytes && (si.digestAlgorithm.name === "shake128" || si.digestAlgorithm.name === "shake256") && dp !== null && dp !== undefined) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "a SHAKE digestAlgorithm carries no parameters (RFC 8702 sec. 3.1)" });
362
376
  var sigHash = pss ? pss.hash : (scheme.hash || digestHash);
363
377
  // The signature hash is required for every non-EdDSA scheme (EdDSA hashes internally);
364
378
  // the content digest is required whenever signed attributes are present, for every scheme
365
379
  // (the message-digest attribute is computed under digestAlgorithm). Either gap is a
366
380
  // fail-closed unsupported-algorithm verdict, never a foreign-domain throw from the digest.
367
- if ((scheme.kind !== "eddsa" && scheme.kind !== "mldsa" && scheme.kind !== "slhdsa" && !sigHash) || (si.signedAttrsBytes && !_supportedDigest(si.digestAlgorithm.name))) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported digest algorithm " + JSON.stringify(si.digestAlgorithm.name) });
381
+ if ((scheme.kind !== "eddsa" && scheme.kind !== "mldsa" && scheme.kind !== "slhdsa" && !sigHash) || (si.signedAttrsBytes && !_supportedDigest(si.digestAlgorithm.name))) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported digest algorithm " + JSON.stringify(si.digestAlgorithm.name) });
368
382
  // ML-DSA (RFC 9882 sec. 3.3): with signed attributes present, the message-digest algorithm MUST
369
383
  // meet the parameter set's security strength -- a below-strength digest is the weaker link and is
370
384
  // rejected fail-closed (the RFC's "verifiers MAY reject", taken by the strict-verifier posture).
371
385
  // With signed attributes absent the digestAlgorithm has no meaning (sec. 3.3) and is not checked.
372
- if (scheme.kind === "mldsa" && si.signedAttrsBytes && !_mldsaDigestSuitable(scheme.name, si.digestAlgorithm.name)) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " message digest is below the security strength of " + scheme.name + " (RFC 9882 sec. 3.3)" });
386
+ if (scheme.kind === "mldsa" && si.signedAttrsBytes && !_mldsaDigestSuitable(scheme.name, si.digestAlgorithm.name)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " message digest is below the security strength of " + scheme.name + " (RFC 9882 sec. 3.3)" });
373
387
  // RFC 9814 sec. 4: an SLH-DSA SignerInfo's message-digest algorithm is the one paired with the
374
388
  // parameter set (the sec. 4 list, always at least twice the tree-hash size); the signer emits it
375
389
  // and this strict verifier requires it, so a digest that does not match the set's paired hash fails
376
390
  // closed -- otherwise a signer could compute the message-digest attribute under a weaker hash the
377
391
  // set does not pair. Gated on signed attributes (absent -> the digest is not consulted, sec. 4).
378
- if (scheme.kind === "slhdsa" && si.signedAttrsBytes && si.digestAlgorithm.name !== scheme.digest) return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "SLH-DSA " + scheme.name + " requires the " + scheme.digest + " message digest (RFC 9814 sec. 4)" });
392
+ if (scheme.kind === "slhdsa" && si.signedAttrsBytes && si.digestAlgorithm.name !== scheme.digest) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "SLH-DSA " + scheme.name + " requires the " + scheme.digest + " message digest (RFC 9814 sec. 4)" });
379
393
  var signers = _findSignerCerts(si.sid, parsedCerts);
380
- if (!signers.length) return Promise.resolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
394
+ if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
381
395
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
382
396
 
383
- return (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType)).then(function (signedBytes) {
384
- if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
385
- return _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, si.sid, signers, pss ? pss.saltLength : 0, scheme.sameKeyOid ? oid.byName(si.signatureAlgorithm.name) : null);
386
- });
397
+ // `await`, not `.then`: see _verifyAgainstCandidates -- a streamed content's iterator could replace
398
+ // Promise.prototype.then to skip the signed-bytes computation and fabricate a passing verdict.
399
+ var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
400
+ if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
401
+ return _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes, si.sid, signers, pss ? pss.saltLength : 0, scheme.sameKeyOid ? oid.byName(si.signatureAlgorithm.name) : null);
387
402
  }
388
403
 
389
404
  // The exact bytes a countersignature covers (RFC 5652 sec. 11.4): the countersigned SignerInfo's
@@ -392,88 +407,102 @@ function _verifyOne(si, content, eContentType, parsedCerts, csTarget) {
392
407
  // covers the re-tagged SignedAttributes ([0] -> universal SET OF) -- the identical transform the
393
408
  // top-level signer uses, minus the content-type attribute (which sec. 11.4 FORBIDS). Resolves the
394
409
  // signed bytes, or { mismatch } when the message-digest disagrees.
395
- function _computeCountersigBytes(si, csTarget) {
396
- return Promise.resolve().then(function () {
397
- if (!si.signedAttrsBytes) return csTarget; // no signed attributes: sign over the target signature octets directly
398
- var reTagged = Buffer.from(si.signedAttrsBytes);
399
- reTagged[0] = 0x31; // [0] IMPLICIT -> universal SET OF
400
- var attrs;
401
- try {
402
- attrs = _decodeSignedAttrs(reTagged);
403
- } 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); }
404
- // sec. 11.4: a content-type attribute MUST NOT appear (the parser already rejects it at parse, but
405
- // verify never relies on the parse alone: a parsed-object input could carry one).
406
- 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)");
407
- var mdAttr = attrs.filter(function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
408
- 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)");
409
- var declared;
410
- try {
411
- declared = asn1.read.octetString(mdAttr.values[0]);
412
- } catch (e) { throw _err("cms/bad-signed-attrs", "the message-digest attribute value is not an OCTET STRING", e); }
413
- return _computeDigest(si.digestAlgorithm.name, csTarget).then(function (d) {
414
- if (!d.equals(declared)) return { mismatch: { code: "cms/message-digest-mismatch", message: "the countersignature message-digest does not match the countersigned signature" } };
415
- return reTagged;
416
- });
417
- });
410
+ async function _computeCountersigBytes(si, csTarget) {
411
+ // async, not `_pResolve().then`: a synchronous throw still becomes a rejection, and the digest await
412
+ // below dispatches through no caller-replaceable Promise.prototype.then (see _verifyAgainstCandidates).
413
+ if (!si.signedAttrsBytes) return csTarget; // no signed attributes: sign over the target signature octets directly
414
+ var reTagged = intrinsic.bufferFrom(si.signedAttrsBytes);
415
+ reTagged[0] = 0x31; // [0] IMPLICIT -> universal SET OF
416
+ var attrs;
417
+ try {
418
+ attrs = _decodeSignedAttrs(reTagged);
419
+ } 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); }
420
+ // sec. 11.4: a content-type attribute MUST NOT appear (the parser already rejects it at parse, but
421
+ // verify never relies on the parse alone: a parsed-object input could carry one).
422
+ if (intrinsic.filter(attrs, 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)");
423
+ var mdAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
424
+ 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)");
425
+ var declared;
426
+ try {
427
+ declared = asn1.read.octetString(mdAttr.values[0]);
428
+ } catch (e) { throw _err("cms/bad-signed-attrs", "the message-digest attribute value is not an OCTET STRING", e); }
429
+ var d = await _computeDigest(si.digestAlgorithm.name, csTarget);
430
+ if (!intrinsic.bufferEquals(d, declared)) return { mismatch: { code: "cms/message-digest-mismatch", message: "the countersignature message-digest does not match the countersigned signature" } };
431
+ return reTagged;
418
432
  }
419
433
 
420
434
  // The exact bytes the signature covers (RFC 5652 sec. 5.4), shared by the classical and composite
421
435
  // verify paths so both bind the message-digest / content-type checks to the VERIFIED preimage (not
422
436
  // the caller-mutable parsed si.signedAttrs). Resolves the signed bytes, or { mismatch } when a
423
437
  // content-type / message-digest attribute disagrees.
424
- function _computeSignedBytes(si, content, eContentType) {
425
- return Promise.resolve().then(function () {
426
- if (!si.signedAttrsBytes) {
427
- // No signed attributes: the signature is over the content itself (RFC 5652 sec. 5.4). That is
428
- // also exactly what a stripped-attributes forgery looks like, because a CMS signature does not
429
- // commit to whether attributes were present -- so a signature made over a SignedAttributes
430
- // block re-presented as one made over content verifies, and with no attributes there is no
431
- // message-digest or content-type attribute left to disagree. Refuse the shape: content that
432
- // parses as a SignedAttributes block cannot be told apart from that forgery, and it is not
433
- // the verifier's place to guess which one it is holding.
434
- if (cms.looksLikeSignedAttributes(content)) {
435
- return { mismatch: {
436
- code: "cms/ambiguous-content",
437
- message: "the content of a SignerInfo with no signed attributes is itself an encoded " +
438
- "SignedAttributes block, which is indistinguishable from a signature over attributes " +
439
- "re-presented as one over content (RFC 5652 sec. 5.4); sign such content WITH signed attributes",
440
- } };
441
- }
442
- return content;
438
+ async function _computeSignedBytes(si, content, eContentType, digestByName) {
439
+ // async, not `_pResolve().then`: a synchronous throw still becomes a rejection, and every digest step
440
+ // dispatches through no caller-replaceable Promise.prototype.then (see _verifyAgainstCandidates).
441
+ if (!si.signedAttrsBytes) {
442
+ // No signed attributes: the signature is over the content itself (RFC 5652 sec. 5.4). A streamed
443
+ // content is not retained, so there are no content bytes to verify that signature against; this
444
+ // is a per-signer verdict, not a whole-message abort, so the other signers of a mixed message
445
+ // still verify (the same shape the buffered path gives every per-signer condition). `digestByName`
446
+ // is the streaming marker: an object (possibly empty) when streamed, null when buffered.
447
+ if (digestByName) {
448
+ return { mismatch: {
449
+ code: "cms/streamed-content-unverifiable",
450
+ message: "a SignerInfo that signs the content directly (no signed attributes) cannot be " +
451
+ "verified against a streamed content, whose bytes are not retained; pass opts.content as a Buffer",
452
+ } };
443
453
  }
444
- // With signed attributes: decode them from the EXACT bytes the signature covers, namely the
445
- // SignedAttributes SET OF with the on-wire [0] IMPLICIT tag replaced by a universal SET OF
446
- // (RFC 5652 sec. 5.4). The content-type / message-digest checks then bind the same bytes
447
- // that are verified. Reading the caller-mutable parsed si.signedAttrs instead would let a
448
- // parsed-object input desync the checked attributes from the verified preimage.
449
- var reTagged = Buffer.from(si.signedAttrsBytes);
450
- reTagged[0] = 0x31; // [0] IMPLICIT -> universal SET OF
451
- var attrs;
452
- try {
453
- attrs = _decodeSignedAttrs(reTagged);
454
- } catch (e) { if (e instanceof CmsError) throw e; throw _err("cms/bad-signed-attrs", "signedAttrs is not a valid SET OF Attribute", e); }
455
- // The content-type attribute MUST be present, single-valued, and equal the eContentType
456
- // (RFC 5652 sec. 5.3).
457
- var ctAttr = attrs.filter(function (a) { return a.type === OID_CONTENT_TYPE; });
458
- if (ctAttr.length !== 1 || ctAttr[0].values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one content-type attribute (RFC 5652 sec. 5.3)");
459
- var ctOid;
460
- try {
461
- ctOid = asn1.read.oid(ctAttr[0].values[0]);
462
- } catch (e) { throw _err("cms/bad-signed-attrs", "the content-type attribute value is not an OBJECT IDENTIFIER", e); }
463
- if (ctOid !== eContentType) return { mismatch: { code: "cms/content-type-mismatch", message: "the content-type signed attribute does not match the SignedData eContentType (RFC 5652 sec. 5.3)" } };
464
- // The message-digest attribute MUST be present, single-valued, and equal the digest of the
465
- // content (RFC 5652 sec. 5.4).
466
- var mdAttr = attrs.filter(function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
467
- if (!mdAttr || mdAttr.values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one message-digest attribute (RFC 5652 sec. 5.4)");
468
- var declared;
469
- try {
470
- declared = asn1.read.octetString(mdAttr.values[0]);
471
- } catch (e) { throw _err("cms/bad-signed-attrs", "the message-digest attribute value is not an OCTET STRING", e); }
472
- return _computeDigest(si.digestAlgorithm.name, content).then(function (d) {
473
- if (!d.equals(declared)) return { mismatch: { code: "cms/message-digest-mismatch", message: "the message-digest attribute does not match the content digest" } };
474
- return reTagged;
475
- });
476
- });
454
+ // Signing content directly is also exactly what a stripped-attributes forgery looks like, because
455
+ // a CMS signature does not commit to whether attributes were present -- so a signature made over a
456
+ // SignedAttributes block re-presented as one made over content verifies, and with no attributes
457
+ // there is no message-digest or content-type attribute left to disagree. Refuse the shape: content
458
+ // that parses as a SignedAttributes block cannot be told apart from that forgery, and it is not
459
+ // the verifier's place to guess which one it is holding.
460
+ if (cms.looksLikeSignedAttributes(content)) {
461
+ return { mismatch: {
462
+ code: "cms/ambiguous-content",
463
+ message: "the content of a SignerInfo with no signed attributes is itself an encoded " +
464
+ "SignedAttributes block, which is indistinguishable from a signature over attributes " +
465
+ "re-presented as one over content (RFC 5652 sec. 5.4); sign such content WITH signed attributes",
466
+ } };
467
+ }
468
+ return content;
469
+ }
470
+ // With signed attributes: decode them from the EXACT bytes the signature covers, namely the
471
+ // SignedAttributes SET OF with the on-wire [0] IMPLICIT tag replaced by a universal SET OF
472
+ // (RFC 5652 sec. 5.4). The content-type / message-digest checks then bind the same bytes
473
+ // that are verified. Reading the caller-mutable parsed si.signedAttrs instead would let a
474
+ // parsed-object input desync the checked attributes from the verified preimage.
475
+ var reTagged = intrinsic.bufferFrom(si.signedAttrsBytes);
476
+ reTagged[0] = 0x31; // [0] IMPLICIT -> universal SET OF
477
+ var attrs;
478
+ try {
479
+ attrs = _decodeSignedAttrs(reTagged);
480
+ } catch (e) { if (e instanceof CmsError) throw e; throw _err("cms/bad-signed-attrs", "signedAttrs is not a valid SET OF Attribute", e); }
481
+ // The content-type attribute MUST be present, single-valued, and equal the eContentType
482
+ // (RFC 5652 sec. 5.3).
483
+ var ctAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_CONTENT_TYPE; });
484
+ if (ctAttr.length !== 1 || ctAttr[0].values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one content-type attribute (RFC 5652 sec. 5.3)");
485
+ var ctOid;
486
+ try {
487
+ ctOid = asn1.read.oid(ctAttr[0].values[0]);
488
+ } catch (e) { throw _err("cms/bad-signed-attrs", "the content-type attribute value is not an OBJECT IDENTIFIER", e); }
489
+ if (ctOid !== eContentType) return { mismatch: { code: "cms/content-type-mismatch", message: "the content-type signed attribute does not match the SignedData eContentType (RFC 5652 sec. 5.3)" } };
490
+ // The message-digest attribute MUST be present, single-valued, and equal the digest of the
491
+ // content (RFC 5652 sec. 5.4).
492
+ var mdAttr = intrinsic.filter(attrs, function (a) { return a.type === OID_MESSAGE_DIGEST; })[0];
493
+ if (!mdAttr || mdAttr.values.length !== 1) throw _err("cms/bad-signed-attrs", "signedAttrs must carry exactly one message-digest attribute (RFC 5652 sec. 5.4)");
494
+ var declared;
495
+ try {
496
+ declared = asn1.read.octetString(mdAttr.values[0]);
497
+ } catch (e) { throw _err("cms/bad-signed-attrs", "the message-digest attribute value is not an OCTET STRING", e); }
498
+ // Over a buffered content the digest is computed here; over a streamed content it was computed once,
499
+ // incrementally, before any signer was verified, and looked up by algorithm.
500
+ var d = digestByName ? digestByName[si.digestAlgorithm.name] : await _computeDigest(si.digestAlgorithm.name, content);
501
+ // Captured equality, not d.equals: over a streamed content the untrusted iterator's next() ran during
502
+ // the digest await and could have replaced Buffer.prototype.equals with one that always answers true,
503
+ // passing this message-digest check for content that was never signed (CWE-347).
504
+ if (!intrinsic.bufferEquals(d, declared)) return { mismatch: { code: "cms/message-digest-mismatch", message: "the message-digest attribute does not match the content digest" } };
505
+ return reTagged;
477
506
  }
478
507
 
479
508
  // Verify a composite ML-DSA SignerInfo (draft-ietf-lamps-cms-composite-sigs). The composite
@@ -484,14 +513,14 @@ function _computeSignedBytes(si, content, eContentType) {
484
513
  // pre-hash. It reuses the sec. 5.4 preimage + message-digest binding UNCHANGED, then dispatches
485
514
  // compositeVerify per candidate certificate (both components must verify, with no AND->OR
486
515
  // downgrade).
487
- function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget) {
516
+ async function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget, digestByName) {
488
517
  if (si.signatureAlgorithm.parameters !== null && si.signatureAlgorithm.parameters !== undefined) {
489
- return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite signatureAlgorithm parameters must be absent (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
518
+ return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite signatureAlgorithm parameters must be absent (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
490
519
  }
491
520
  // The 2 arms Node's WebCrypto surface cannot verify (the brainpool curves) fail closed --
492
521
  // never a silent single-component accept (the AND->OR downgrade this feature prevents).
493
522
  if (comp.trad.unsupported) {
494
- return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "composite " + comp.name + ": " + comp.trad.unsupported });
523
+ return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "composite " + comp.name + ": " + comp.trad.unsupported });
495
524
  }
496
525
  // draft sec. 3.4: for a composite SignerInfo the digestAlgorithm parameters MUST be OMITTED
497
526
  // (id-sha256/id-sha512 and id-shake256). That is stricter than the generic RFC 5754 absent-OR-NULL
@@ -500,7 +529,7 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
500
529
  // (it is outside the signed preimage, so a parser differential must not verify).
501
530
  var dp = si.digestAlgorithm.parameters;
502
531
  if (dp !== null && dp !== undefined) {
503
- return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite " + si.digestAlgorithm.name + " digestAlgorithm parameters must be omitted (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
532
+ return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the composite " + si.digestAlgorithm.name + " digestAlgorithm parameters must be omitted (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
504
533
  }
505
534
  // sec. 3.4 / sec. 5 (the one CMS-specific MUST the X.509 path does not cover): the SignerInfo
506
535
  // digestAlgorithm MUST equal the arm's pre-hash (Table 1) -- compositeVerify takes the pre-hash
@@ -508,15 +537,15 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
508
537
  // message-digest attribute under the WRONG algorithm. The sec. 5 SHOULD-reject is taken
509
538
  // fail-closed; the MAY-verify-anyway leniency is deliberately not taken.
510
539
  if (si.digestAlgorithm.name !== comp.phCms) {
511
- return Promise.resolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the SignerInfo digestAlgorithm " + JSON.stringify(si.digestAlgorithm.name) + " is not the composite " + comp.name + " pre-hash " + JSON.stringify(comp.phCms) + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
540
+ return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the SignerInfo digestAlgorithm " + JSON.stringify(si.digestAlgorithm.name) + " is not the composite " + comp.name + " pre-hash " + JSON.stringify(comp.phCms) + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
512
541
  }
513
542
  var signers = _findSignerCerts(si.sid, parsedCerts);
514
- if (!signers.length) return Promise.resolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
543
+ if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
515
544
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
516
- return (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType)).then(function (signedBytes) {
517
- if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
518
- return _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si.sid, signers, si.signatureAlgorithm.oid);
519
- });
545
+ // `await`, not `.then`: see _verifyAgainstCandidates.
546
+ var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
547
+ if (signedBytes && signedBytes.mismatch) return { ok: false, code: signedBytes.mismatch.code, sid: si.sid, cert: signers[0].der, message: signedBytes.mismatch.message };
548
+ return _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, si.sid, signers, si.signatureAlgorithm.oid);
520
549
  }
521
550
 
522
551
  // Dispatch compositeVerify against each matching candidate; the signer is valid iff one candidate
@@ -524,26 +553,21 @@ function _verifyComposite(si, comp, content, eContentType, parsedCerts, csTarget
524
553
  // OID is skipped with a precise verdict (RFC 9814 sec. 4 key<->signature agreement); a candidate that
525
554
  // returns a structural fault code (bad split length, unsupported) falls through, its code surfaced if
526
555
  // none verifies. A clean both-components-checked failure (the AND-downgrade) is a code-less false.
527
- function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, candidates, expectedKeyOid) {
556
+ async function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, candidates, expectedKeyOid) {
528
557
  var lastErr = null;
529
- function attempt(idx) {
530
- if (idx >= candidates.length) {
531
- return lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
532
- : { ok: false, sid: sid, cert: candidates[0].der };
533
- }
558
+ for (var idx = 0; idx < candidates.length; idx++) {
534
559
  var c = candidates[idx];
535
560
  if (c.cert.subjectPublicKeyInfo.algorithm.oid !== expectedKeyOid) {
536
561
  lastErr = _err("cms/unsupported-algorithm", "the signer certificate public-key algorithm does not match the SignerInfo signatureAlgorithm");
537
- return attempt(idx + 1);
562
+ continue;
538
563
  }
539
- return compositeSig.compositeVerify(c.cert.subjectPublicKeyInfo.bytes, sigBytes, signedBytes, comp, CmsError, "cms/unsupported-algorithm", "cms/bad-signature")
540
- .then(function (r) {
541
- if (r.ok === true) return { ok: true, sid: sid, cert: c.der };
542
- if (r.code) lastErr = (r.error instanceof CmsError) ? r.error : _err(r.code, r.error && r.error.message ? r.error.message : "the composite signature could not be evaluated");
543
- return attempt(idx + 1);
544
- });
564
+ // `await`, not `.then`: see _verifyAgainstCandidates.
565
+ var r = await compositeSig.compositeVerify(c.cert.subjectPublicKeyInfo.bytes, sigBytes, signedBytes, comp, CmsError, "cms/unsupported-algorithm", "cms/bad-signature");
566
+ if (r.ok === true) return { ok: true, sid: sid, cert: c.der };
567
+ if (r.code) lastErr = (r.error instanceof CmsError) ? r.error : _err(r.code, r.error && r.error.message ? r.error.message : "the composite signature could not be evaluated");
545
568
  }
546
- return attempt(0);
569
+ return lastErr ? { ok: false, code: lastErr.code, sid: sid, cert: candidates[0].der, message: lastErr.message }
570
+ : { ok: false, sid: sid, cert: candidates[0].der };
547
571
  }
548
572
 
549
573
  // The decoded unsigned attributes of a SignerInfo, surfaced UNAUTHENTICATED (they are outside
@@ -551,7 +575,7 @@ function _verifyCompositeAgainstCandidates(comp, sigBytes, signedBytes, sid, can
551
575
  // timestamp token or inspect a countersignature attribute. They are never counted toward a
552
576
  // signer's ok / res.valid.
553
577
  function _surfaceUnsignedAttrs(si) {
554
- return (si.unsignedAttrs || []).map(function (a) {
578
+ return intrinsic.map(si.unsignedAttrs || [], function (a) {
555
579
  return { type: a.type, typeName: oid.name(a.type) || null, values: a.values };
556
580
  });
557
581
  }
@@ -566,19 +590,26 @@ function _surfaceUnsignedAttrs(si) {
566
590
  // the FINITE parsed structure: each nested countersignature value is a sub-encoding of its parent,
567
591
  // and the strict decoder already bounds total nesting by C.LIMITS.DER_MAX_DEPTH at parse
568
592
  // (CWE-834/770), so a hostile deep chain fails closed before verify.
569
- function _verifyCountersignatures(si, parsedCerts) {
593
+ async function _verifyCountersignatures(si, parsedCerts) {
570
594
  // si.signature is always a Buffer from the strict parser (and from walkCountersignature on the
571
595
  // recursive path), so _toBuf is a pass-through here. The SignerInfo is already well-formed.
572
596
  var targetSig = _toBuf(si.signature, "the countersigned signature");
573
597
  var values = [];
574
- (si.unsignedAttrs || []).forEach(function (a) {
598
+ // Captured enumeration: over a streamed content the untrusted iterator's next() ran during the
599
+ // digest await and could replace Array.prototype.forEach / push to drop a failing countersignature.
600
+ intrinsic.forEach(si.unsignedAttrs || [], function (a) {
575
601
  if (a.type !== OID_COUNTERSIGNATURE) return;
576
- a.values.forEach(function (v) { values.push(v && v.bytes ? v.bytes : v); });
602
+ intrinsic.forEach(a.values, function (v) { intrinsic.push(values, v && v.bytes ? v.bytes : v); });
577
603
  });
578
- return Promise.all(values.map(function (vDer) { return _verifyOneCountersig(vDer, targetSig, parsedCerts); }));
604
+ // Sequential, started only as awaited (see _verify): not Promise.all (its element handling dispatches
605
+ // through the replaceable Promise.prototype.then), and not started-all-then-awaited (an early
606
+ // rejection would leave a later one unobserved).
607
+ var out = [];
608
+ for (var _cp = 0; _cp < values.length; _cp++) intrinsic.push(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts));
609
+ return out;
579
610
  }
580
611
 
581
- function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
612
+ async function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
582
613
  var csSi;
583
614
  // Coverage residual, and deliberately kept. Every value reaching here was already walked by the
584
615
  // decoder, which validates an id-countersignature by content and not by the attribute type. A
@@ -588,17 +619,16 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
588
619
  // whole instead of surfaced as one failed countersignature.
589
620
  // cms-verify.test.js pins that through the shipped verbs; this stays because the walk is also
590
621
  // reachable recursively and a backstop that returns a NEGATIVE verdict costs nothing.
591
- try { csSi = cms.walkCountersignature(asn1.decode(Buffer.isBuffer(vDer) ? vDer : Buffer.from(vDer))); }
592
- catch (e) { return Promise.resolve({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
593
- return _verifyOne(csSi, targetSig, null, parsedCerts, targetSig).then(function (verdict) {
594
- return _verifyCountersignatures(csSi, parsedCerts).then(function (nested) {
595
- var node = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
596
- if (verdict.code) node.code = verdict.code;
597
- node.unsignedAttrs = _surfaceUnsignedAttrs(csSi);
598
- node.countersignatures = nested;
599
- return node;
600
- });
601
- });
622
+ try { csSi = cms.walkCountersignature(asn1.decode(intrinsic.isBuffer(vDer) ? vDer : intrinsic.bufferFrom(vDer))); }
623
+ catch (e) { return { ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }; }
624
+ // `await`, not `.then`: see _verifyAgainstCandidates.
625
+ var verdict = await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig);
626
+ var nested = await _verifyCountersignatures(csSi, parsedCerts);
627
+ var node = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
628
+ if (verdict.code) node.code = verdict.code;
629
+ node.unsignedAttrs = _surfaceUnsignedAttrs(csSi);
630
+ node.countersignatures = nested;
631
+ return node;
602
632
  }
603
633
 
604
634
  /**
@@ -646,8 +676,11 @@ function _verifyOneCountersig(vDer, targetSig, parsedCerts) {
646
676
  * ML-DSA (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
647
677
  * RSA / ECDSA / EdDSA and verifies only when both components pass (never an AND-to-OR downgrade).
648
678
  *
649
- * @opts content The detached content (a `Buffer`) when the SignedData carries no
650
- * encapsulated eContent. Required for a detached signature.
679
+ * @opts content The detached content when the SignedData carries no encapsulated eContent, and
680
+ * required for a detached signature. A `Buffer`, or an async iterable of byte chunks
681
+ * to verify a large payload without holding it in memory. The streamed form is hashed
682
+ * once for every signer, so every signer must carry signed attributes; a signer that
683
+ * signs the content directly needs the content as a `Buffer`.
651
684
  * @opts certs Extra signer certificates (an array of DER `Buffer`s) to match against, in
652
685
  * addition to the certificates embedded in the SignedData.
653
686
  * @opts trustAnchors The roots the caller accepts (DER `Buffer`s or anchor tuples). Supplying
@@ -679,12 +712,51 @@ function _snapshotIfBytes(input, label) {
679
712
  // Every byte form the parser accepts -- an ArrayBuffer or a DataView reaches the
680
713
  // decoder as readily as a Buffer, and leaving those aliased would reopen the
681
714
  // window for exactly the inputs that came in by the wider door.
682
- if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer) {
715
+ if (guard.bytes.isByteSource(input)) {
683
716
  return guard.bytes.snapshotSource(input, CmsError, "cms/bad-input", label);
684
717
  }
685
718
  return input;
686
719
  }
687
720
 
721
+ // For a streamed (async-iterable) detached content: hash the stream ONCE, incrementally, under every
722
+ // distinct SUPPORTED SignerInfo digest algorithm, returning { <digestName>: Buffer } (streaming), or
723
+ // null (not streaming; digest per-signer over the buffered content). No per-signer condition aborts
724
+ // the whole message here, matching the buffered path's per-signer verdicts: a content-only signer
725
+ // (no signed attributes, whose signature is over content a stream does not retain) and an unsupported
726
+ // digest each contribute no name and are verdicted per-signer by _verifyOne / _computeSignedBytes.
727
+ // An empty map is still the streaming marker, so those per-signer verdicts are produced. `streamSource`
728
+ // is the lazy stream classified at the door (null when not streaming, buffered per-signer); its
729
+ // iterator is acquired only if a digest is actually needed, so an all-content-only message never opens it.
730
+ async function _resolveContentDigests(parsed, streamSource) {
731
+ if (streamSource == null) return null;
732
+ var names = [], seen = {};
733
+ intrinsic.forEach(parsed.signerInfos, function (si) {
734
+ var dn = si.digestAlgorithm.name;
735
+ if (si.signedAttrsBytes && _supportedDigest(dn) && !seen[dn]) { seen[dn] = 1; intrinsic.push(names, dn); }
736
+ });
737
+ // No signer needs a streamed digest (all content-only or unsupported): nothing to hash, but the
738
+ // empty map still marks streaming mode so each signer gets its per-signer verdict.
739
+ if (!names.length) return {};
740
+ // `await`, not `.then`: this is where the UNTRUSTED stream is consumed, so the very next promise step
741
+ // must not dispatch through a Promise.prototype.then the stream may have replaced.
742
+ var ds;
743
+ try {
744
+ ds = await subtle.digestStream(intrinsic.map(names, function (n) { return DIGEST_HASH[n]; }), streamSource);
745
+ } catch (e) {
746
+ // The engine reports malformed streamed content in its own domain; report it in this verb's domain
747
+ // instead, exactly as a buffered content's bad bytes are, rather than leaking the engine code. A
748
+ // non-byte chunk is webcrypto/data; a source whose async-iterator protocol is malformed (a
749
+ // non-callable next, or a factory returning a non-iterator) is webcrypto/syntax. The digest
750
+ // algorithm names are this module's own, so webcrypto/syntax here is never an algorithm fault.
751
+ if (e && e.code === "webcrypto/data") throw _err("cms/bad-input", "a streamed content chunk is not a byte source", e);
752
+ if (e && e.code === "webcrypto/syntax") throw _err("cms/bad-input", "the streamed content is not a valid async iterable of byte chunks", e);
753
+ throw e;
754
+ }
755
+ var byName = {};
756
+ intrinsic.forEach(names, function (n, i) { byName[n] = intrinsic.bufferFrom(ds[i]); });
757
+ return byName;
758
+ }
759
+
688
760
  // Every option pki.cms.verify reads. Adding one here is the only way to make it accepted, so a
689
761
  // capability cannot arrive with its option silently ignored at this boundary.
690
762
  var _VERIFY_OPTS = { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1 };
@@ -696,7 +768,7 @@ function verify(input, opts) {
696
768
  return guard.async.deferred(function () { return _verify(input, opts); });
697
769
  }
698
770
 
699
- function _verify(input, opts) {
771
+ async function _verify(input, opts) {
700
772
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.verify options");
701
773
  // An unrecognized option is refused, not swallowed. This is what kept the missing trust seam
702
774
  // silent: a caller writing `trustAnchors` before it existed -- or `trustAnchor` now -- got a
@@ -720,15 +792,43 @@ function _verify(input, opts) {
720
792
  return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
721
793
  }, _err, "cms/bad-input", "the SignedData");
722
794
  if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
795
+ // The trust configuration is snapshotted HERE, before opts.content is read at all. A streamed
796
+ // content's Symbol.asyncIterator accessor runs the moment asyncStreamOf reads it, one turn before a
797
+ // snapshot taken further down would run; from that accessor a caller can replace a global the snapshot
798
+ // itself relies on -- the Date it defaults the validation instant from, or the Array.isArray /
799
+ // Array.prototype.map / slice it copies the anchors and requiredEku with -- and steer the trust
800
+ // decision. Taken before the content object is touched, every value the snapshot reads comes from a
801
+ // pristine global. It also stays caller-owned across the later await: the array cannot be re-pointed,
802
+ // an anchor's DER rewritten, or the instant moved, once copied -- the same defense the input and the
803
+ // certificates already have.
804
+ var trustCfg = _snapshotTrust(opts);
805
+ // Pre-parse every candidate certificate (embedded + caller-supplied) once, BEFORE opts.content is
806
+ // touched. The caller-supplied set (opts.certs) is read here; a streamed content's accessor -- run the
807
+ // moment asyncStreamOf reads it below -- could otherwise mutate that array, or replace the
808
+ // Array.prototype.forEach this loop uses, and change which certificates the signer match and the trust
809
+ // build see. Taken first, the candidate pool is the one the caller passed.
810
+ var parsedCerts = [];
811
+ (parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
812
+ (opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
723
813
  var content = parsed.encapContentInfo.eContent;
814
+ var streamSource = null;
724
815
  if (content == null) {
725
816
  if (opts.content == null) throw _err("cms/detached-content-required", "this SignedData is detached; opts.content (the external content) is required");
726
- // Copied, not aliased: the digest over this content is computed in a later promise turn,
727
- // and a detached signature's content is the whole of what the signature speaks about. A
728
- // caller who rewrote the buffer in that gap would have the signature checked against the
729
- // original bytes while holding the replacement, and the verdict would report a valid
730
- // signature over content that was never signed.
731
- content = _toBuf(_snapshotIfBytes(opts.content, "opts.content"), "opts.content");
817
+ // A streamed (async-iterable) detached content is classified by asyncStreamOf, which reads
818
+ // Symbol.asyncIterator a single time and returns a LAZY stream whose iterator is acquired only when
819
+ // it is driven below, after the per-signer digest algorithms are known, so a single-use stream
820
+ // serves every signer in one pass and a message needing no streamed digest never acquires it. It is
821
+ // not snapshotted: a stream has no bytes to copy, and it is consumed exactly once. A byte source (or
822
+ // any non-iterable) yields null and is buffered.
823
+ streamSource = guard.bytes.asyncStreamOf(opts.content);
824
+ if (streamSource == null) {
825
+ // Copied, not aliased: the digest over this content is computed in a later promise turn,
826
+ // and a detached signature's content is the whole of what the signature speaks about. A
827
+ // caller who rewrote the buffer in that gap would have the signature checked against the
828
+ // original bytes while holding the replacement, and the verdict would report a valid
829
+ // signature over content that was never signed.
830
+ content = _toBuf(_snapshotIfBytes(opts.content, "opts.content"), "opts.content");
831
+ }
732
832
  } else if (opts.content != null) {
733
833
  // A caller supplied external content AND the SignedData is attached. A redundant COPY that equals
734
834
  // the encapsulated content is fine; content that DIFFERS is a substitution trap -- silently
@@ -738,47 +838,46 @@ function _verify(input, opts) {
738
838
  throw _err("cms/content-conflict", "opts.content disagrees with this SignedData's own encapsulated content (a detached signature must omit eContent)");
739
839
  }
740
840
  }
741
- // Pre-parse every candidate certificate (embedded + caller-supplied) once.
742
- var parsedCerts = [];
743
- (parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
744
- (opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
745
841
  var eContentType = parsed.encapContentInfo.eContentType;
746
- // The trust configuration is captured SYNCHRONOUSLY here, before any signature work yields. It is
747
- // read a promise turn later, and everything in it stays caller-owned across that gap: the array
748
- // can be re-pointed, an anchor's DER rewritten, the validation instant moved. Any of those would
749
- // have the chain judged against a configuration the caller never asked for while the verdict
750
- // reports the one they did. The same defense the input and the certificates already have.
751
- var trustCfg = _snapshotTrust(opts);
752
- return Promise.all(parsed.signerInfos.map(function (si) {
842
+ // `await` throughout the aggregation, never `.then`: the streamed content is consumed inside
843
+ // _resolveContentDigests, so from here on every promise step must dispatch through no
844
+ // caller-replaceable Promise.prototype.then. Captured enumeration (intrinsic.map) guards
845
+ // Array.prototype.map; awaiting each promise (never Promise.all) guards the chaining (CWE-347).
846
+ var digestByName = await _resolveContentDigests(parsed, streamSource);
847
+ // Each signer is verified SEQUENTIALLY -- started only as it is awaited. NOT Promise.all: its
848
+ // spec-internal element handling calls the caller-replaceable Promise.prototype.then, so a streamed
849
+ // content that replaced it could fabricate a resolved `{ ok: true }` for every signer; await
850
+ // dispatches through no such method. And NOT started-all-then-awaited-each: an earlier signer's
851
+ // rejection would leave a later one's rejection unobserved, which Node can escalate to a crash.
852
+ // Per-signer work is small, so the sequential cost is negligible.
853
+ var signers = [];
854
+ for (var _sp = 0; _sp < parsed.signerInfos.length; _sp++) {
855
+ var siOne = parsed.signerInfos[_sp];
753
856
  // res.valid reflects only the PRIMARY signerInfos; a countersignature / unsigned attribute is
754
857
  // outside the signature and NEVER flips the top-level verdict. Both are surfaced per signer.
755
- return _verifyOne(si, content, eContentType, parsedCerts).then(function (verdict) {
756
- return _verifyCountersignatures(si, parsedCerts).then(function (countersignatures) {
757
- verdict.countersignatures = countersignatures;
758
- verdict.unsignedAttrs = _surfaceUnsignedAttrs(si);
759
- // Whether THIS signer signed attributes or signed the content directly. The two are
760
- // different claims -- attributes bind a content type and a signing time alongside the
761
- // digest, and content-only binds nothing but the bytes -- and RFC 5652 lets a message carry
762
- // one signer of each. A caller whose profile requires attributes (RFC 8551 S/MIME does) can
763
- // only enforce it if the verdict says which they got.
764
- verdict.signedAttributesPresent = !!si.signedAttrsBytes;
765
- return verdict;
766
- });
767
- });
768
- })).then(function (signers) {
769
- // The content type travels with the verdict. An operator applying a policy of their own -- "I
770
- // only accept id-data", or a profile that names its own type -- otherwise had to parse the
771
- // message a second time to learn it, and a check that needs a second parse is a check most
772
- // callers will not write.
773
- var res = {
774
- // Signature soundness is decided by comparison for the same reason the trust verdict below
775
- // is: `every` replaced after load reports the whole set sound without reading one signer.
776
- valid: signers.length > 0 && guard.list.allMatch(signers, function (s) { return s.ok === true; }),
777
- eContentType: parsed.encapContentInfo.eContentType,
778
- signers: signers,
779
- };
780
- return _applyTrust(res, parsedCerts, trustCfg).then(function () { return res; });
781
- });
858
+ var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName);
859
+ verdict.countersignatures = await _verifyCountersignatures(siOne, parsedCerts);
860
+ verdict.unsignedAttrs = _surfaceUnsignedAttrs(siOne);
861
+ // Whether THIS signer signed attributes or signed the content directly. The two are different
862
+ // claims -- attributes bind a content type and a signing time alongside the digest, and content-only
863
+ // binds nothing but the bytes -- and RFC 5652 lets a message carry one signer of each. A caller
864
+ // whose profile requires attributes (RFC 8551 S/MIME does) can only enforce it if the verdict says
865
+ // which they got.
866
+ verdict.signedAttributesPresent = !!siOne.signedAttrsBytes;
867
+ intrinsic.push(signers, verdict);
868
+ }
869
+ // The content type travels with the verdict. An operator applying a policy of their own -- "I only
870
+ // accept id-data", or a profile that names its own type -- otherwise had to parse the message a
871
+ // second time to learn it, and a check that needs a second parse is a check most callers will not write.
872
+ var res = {
873
+ // Signature soundness is decided by comparison for the same reason the trust verdict below is:
874
+ // `every` replaced after load reports the whole set sound without reading one signer.
875
+ valid: signers.length > 0 && guard.list.allMatch(signers, function (s) { return s.ok === true; }),
876
+ eContentType: parsed.encapContentInfo.eContentType,
877
+ signers: signers,
878
+ };
879
+ await _applyTrust(res, parsedCerts, trustCfg);
880
+ return res;
782
881
  }
783
882
 
784
883
  // The full path build + validate, injected by path-validate the way it injects into crl-verify and
@@ -804,7 +903,7 @@ function setEngine(engine) { _engine = engine; }
804
903
  // rewritten. `requiredEku` is copied for the same reason as the array.
805
904
  var _ANCHOR_CLONE_DEPTH = 8;
806
905
  function _cloneAnchorValue(v, depth) {
807
- if (Buffer.isBuffer(v) || ArrayBuffer.isView(v) || v instanceof ArrayBuffer) {
906
+ if (guard.bytes.isByteSource(v)) {
808
907
  return _snapshotIfBytes(v, "opts.trustAnchors[]");
809
908
  }
810
909
  if (v === null || typeof v !== "object") return v;
@@ -816,7 +915,7 @@ function _cloneAnchorValue(v, depth) {
816
915
  // neither throw out of the snapshot nor put a different instant in the copy than the original
817
916
  // holds -- which would leave the anchor policy that was checked and the one that is applied two
818
917
  // different values.
819
- if (guard.time.isDate(v)) return new Date(guard.time.instantOf(v));
918
+ if (guard.time.isDate(v)) return new intrinsic.Date(guard.time.instantOf(v));
820
919
  // A structure too deep to copy is REFUSED, not shared. Returning it by reference at the cap
821
920
  // would leave part of the anchor caller-mutable across the chain walk while the rest was
822
921
  // snapshotted -- a guarantee that holds for the shallow fields and quietly lapses for the deep
@@ -844,7 +943,11 @@ function _snapshotTrust(opts) {
844
943
  }
845
944
  return {
846
945
  trustAnchors: anchors,
847
- time: guard.time.isDate(opts.time) ? new Date(guard.time.instantOf(opts.time)) : opts.time,
946
+ // The validation instant, read from the captured Date intrinsic, never the live global Date. The
947
+ // snapshot is taken before opts.content is touched (see _verify), so no content accessor has run to
948
+ // replace a global yet; reading the instant through the captured constructor holds even if that
949
+ // ordering regresses. A caller Date is cloned; undefined becomes now.
950
+ time: guard.time.isDate(opts.time) ? new intrinsic.Date(guard.time.instantOf(opts.time)) : (opts.time === undefined ? new intrinsic.Date() : opts.time),
848
951
  requiredEku: Array.isArray(opts.requiredEku) ? opts.requiredEku.slice() : opts.requiredEku,
849
952
  checkPurpose: opts.checkPurpose,
850
953
  };
@@ -864,7 +967,11 @@ function _snapshotTrust(opts) {
864
967
  var _CERT_EXT_DECODERS = pkix.certExtensionDecoders(pkix.makeNS("cms", CmsError, oid)).byOid;
865
968
 
866
969
  function _keyUsagePermitsSigning(parsedCerts, der) {
867
- var entry = parsedCerts.filter(function (c) { return c.der.equals(der); })[0];
970
+ // Captured filter + equality: this runs after the streamed content was consumed, and the fail-open
971
+ // `if (!entry) return true` below would otherwise be reachable by a hostile iterator that replaced
972
+ // Buffer.prototype.equals to return false, making the signer certificate un-findable and its
973
+ // keyUsage un-read -- a keyEncipherment-only certificate would then be reported trusted (CWE-347).
974
+ var entry = intrinsic.filter(parsedCerts, function (c) { return intrinsic.bufferEquals(c.der, der); })[0];
868
975
  if (!entry) return true; // not among the candidates: nothing to read
869
976
  var kuOid = oid.byName("keyUsage");
870
977
  var exts = entry.cert.extensions || [];
@@ -886,22 +993,27 @@ function _allTrusted(signers) {
886
993
 
887
994
  // `cfg` is verify's SNAPSHOT, never the caller's options object -- the parameter is named for that
888
995
  // so a future caller cannot hand it the live one without noticing.
889
- function _applyTrust(res, parsedCerts, cfg) {
996
+ async function _applyTrust(res, parsedCerts, cfg) {
890
997
  res.trusted = false;
891
998
  // Every signer carries the field too, whether or not anchors were supplied. A verdict whose
892
999
  // per-signer shape depends on which options were passed makes a caller iterating `signers` write
893
1000
  // a different loop for each case -- and the absent field reads as "unknown" where the answer is
894
1001
  // a definite "nothing anchored this".
895
- res.signers.forEach(function (s) { s.trusted = false; });
896
- if (cfg.trustAnchors == null) return Promise.resolve();
1002
+ // async + captured enumeration + await (see _verify): the trust verdict is decided after the streamed
1003
+ // content was consumed, so a hostile iterator must not steer it through a replaced Array.prototype or
1004
+ // Promise.prototype.then.
1005
+ intrinsic.forEach(res.signers, function (s) { s.trusted = false; });
1006
+ if (cfg.trustAnchors == null) return;
897
1007
  if (!_engine) {
898
1008
  throw _err("cms/bad-input", "opts.trustAnchors requires the path validator; load pki.path before verifying (require the toolkit through its index)");
899
1009
  }
900
1010
  // Every signer, not merely one: a multi-signer message's signers are independent claims, and
901
1011
  // reporting the whole as trusted because one of them anchored would let an unanchored signer ride
902
1012
  // out on another's chain -- the same rule the per-signer `ok` already follows for signatures.
903
- var pool = parsedCerts.map(function (c) { return c.der; });
904
- var at = cfg.time !== undefined ? cfg.time : new Date();
1013
+ var pool = intrinsic.map(parsedCerts, function (c) { return c.der; });
1014
+ // The default instant was captured at snapshot time (see _snapshotTrust), before the stream ran, so
1015
+ // no live `new Date()` is read here where a replaced Date could steer the validation instant.
1016
+ var at = cfg.time;
905
1017
  var anchors = cfg.trustAnchors;
906
1018
  // The configuration is checked HERE, once, before any signer is looked at. Leaving it to the
907
1019
  // chain walk would make a caller's mistake depend on the message: a SignedData whose every signer
@@ -927,7 +1039,7 @@ function _applyTrust(res, parsedCerts, cfg) {
927
1039
  // through the same definition the walk uses. Checking one and not the other is how this rule has
928
1040
  // repeatedly come back: each new part of the configuration has to join the preflight, not wait
929
1041
  // to be caught where it is consumed.
930
- anchors.forEach(function (a) {
1042
+ intrinsic.forEach(anchors, function (a) {
931
1043
  _engine.toAnchor(a);
932
1044
  if (purposes.checkPurpose != null) _engine.assertAnchorConstraints(a, purposes.checkPurpose);
933
1045
  });
@@ -936,9 +1048,10 @@ function _applyTrust(res, parsedCerts, cfg) {
936
1048
  // anchor's own constraint metadata -- all judged before a single signer is looked at, and all
937
1049
  // through the definitions the walk itself uses. A new option joins it here; validating one only
938
1050
  // where it is consumed is what made a caller's mistake depend on the message.
939
- return res.signers.reduce(function (p, s) {
940
- return p.then(function () {
941
- if (s.ok !== true || !s.cert) { s.trusted = false; return null; }
1051
+ for (var _si = 0; _si < res.signers.length; _si++) {
1052
+ var s = res.signers[_si];
1053
+ {
1054
+ if (s.ok !== true || !s.cert) { s.trusted = false; continue; }
942
1055
  var buildOpts = { trustAnchors: anchors, intermediates: pool, validate: true, time: at };
943
1056
  // A key purpose, when the caller names one. "Trusted" is not a property of a chain alone: a
944
1057
  // certificate restricted to serverAuth chains perfectly well and is still the wrong key to
@@ -963,22 +1076,29 @@ function _applyTrust(res, parsedCerts, cfg) {
963
1076
  // Permitted to have signed, as well as chained. Checked BEFORE the chain walk because a
964
1077
  // certificate that forbids signing cannot become trusted by chaining, so there is nothing to
965
1078
  // learn from walking it.
966
- if (!_keyUsagePermitsSigning(parsedCerts, s.cert)) { s.trusted = false; return null; }
967
- return _engine.build(s.cert, buildOpts)
968
- .then(function (r) { s.trusted = !!(r && r.valid); }, function (e) {
969
- // A CONFIG fault -- unusable anchors, an invalid time -- is the caller's mistake and must
970
- // not be absorbed into "untrusted", which would read as a verdict about the message. A
971
- // chain that simply does not reach an anchor is not that: it is the answer.
972
- if (e && e.code && /bad-input|bad-anchor|bad-time/.test(String(e.code))) throw e;
973
- s.trusted = false;
974
- });
975
- });
976
- }, Promise.resolve()).then(function () {
977
- // The trust verdict is decided by comparison, not by a prototype method: `every` replaced after
978
- // load answers true without consulting a single signer, and a message no anchor covers reports
979
- // trusted:true.
980
- res.trusted = res.valid && res.signers.length > 0 && _allTrusted(res.signers);
981
- });
1079
+ if (!_keyUsagePermitsSigning(parsedCerts, s.cert)) { s.trusted = false; continue; }
1080
+ // `await`, not `.then`: see _verify.
1081
+ try {
1082
+ var r = await _engine.build(s.cert, buildOpts);
1083
+ s.trusted = !!(r && r.valid);
1084
+ } catch (e) {
1085
+ // A CONFIG fault -- unusable anchors, an invalid time -- is the caller's mistake and must
1086
+ // not be absorbed into "untrusted", which would read as a verdict about the message. A
1087
+ // chain that simply does not reach an anchor is not that: it is the answer.
1088
+ if (e && e.code) {
1089
+ var code = intrinsic.String(e.code);
1090
+ if (intrinsic.stringIndexOf(code, "bad-input") !== -1 ||
1091
+ intrinsic.stringIndexOf(code, "bad-anchor") !== -1 ||
1092
+ intrinsic.stringIndexOf(code, "bad-time") !== -1) throw e;
1093
+ }
1094
+ s.trusted = false;
1095
+ }
1096
+ }
1097
+ }
1098
+ // The trust verdict is decided by comparison, not by a prototype method: `every` replaced after
1099
+ // load answers true without consulting a single signer, and a message no anchor covers reports
1100
+ // trusted:true.
1101
+ res.trusted = res.valid && res.signers.length > 0 && _allTrusted(res.signers);
982
1102
  }
983
1103
  // Parse a candidate cert DER and index its SKI; a cert that will not parse is skipped (it
984
1104
  // simply cannot be a signer match, and a malformed embedded cert must not fail the verify).
@@ -1036,6 +1156,12 @@ function _addCert(out, der) {
1036
1156
  * (draft-ietf-lamps-cms-composite-sigs), where the arm's pre-hash fixes the digestAlgorithm and
1037
1157
  * the two component keys are signed together.
1038
1158
  *
1159
+ * `content` may instead be an async iterable of byte chunks, such as an async generator, which signs
1160
+ * a large payload without holding it in memory. This form requires `detached: true` and signed
1161
+ * attributes. The payload is hashed incrementally to build the message-digest attribute, in a single
1162
+ * pass that serves every signer, so signers may use different digest algorithms. The signature covers
1163
+ * the same content as the buffered form.
1164
+ *
1039
1165
  * @opts detached Omit the encapsulated content (a detached signature; the verifier
1040
1166
  * supplies the content). Default false.
1041
1167
  * @opts eContentType The encapsulated content type (an OID name). Default `data`.