@blamejs/pki 0.8.51 → 0.8.53

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.
package/lib/cms-verify.js CHANGED
@@ -31,6 +31,7 @@ var asn1 = require("./asn1-der");
31
31
  var oid = require("./oid");
32
32
  var x509 = require("./schema-x509");
33
33
  var cms = require("./schema-cms");
34
+ var smime = require("./schema-smime");
34
35
  var webcrypto = require("./webcrypto");
35
36
  var subtle = webcrypto.webcrypto.subtle;
36
37
  var edwardsPoint = require("./edwards-point");
@@ -46,6 +47,7 @@ var validator = require("./validator-all");
46
47
  var compositeSig = require("./composite-sig");
47
48
  var guard = require("./guard-all");
48
49
  var intrinsic = require("./guard-intrinsic");
50
+ var constants = require("./constants");
49
51
  var frameworkError = require("./framework-error");
50
52
  var pkix = require("./schema-pkix");
51
53
 
@@ -69,6 +71,15 @@ function _row(o) { return guard.verdict.of(o); }
69
71
  var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
70
72
  var OID_CONTENT_TYPE = oid.byName("contentType");
71
73
  var OID_COUNTERSIGNATURE = oid.byName("countersignature");
74
+ /** @internal `id-aa-signatureTimeStampToken`, which the registry already carries under the S/MIME
75
+ * authenticated-attribute arc as `timeStampToken`: `oid.byName("timeStampToken")` is
76
+ * `1.2.840.113549.1.9.16.2.14`, the OID RFC 5126 clause 6.1.1 assigns. A second name for one OID
77
+ * would give `oid.name()` two answers, so the existing name is used rather than a CAdES alias. */
78
+ var OID_TIMESTAMP_TOKEN = oid.byName("timeStampToken");
79
+ var OID_ID_DATA = oid.byName("data");
80
+ var OID_SIGNING_CERTIFICATE = oid.byName("signingCertificate");
81
+ var OID_SIGNING_CERTIFICATE_V2 = oid.byName("signingCertificateV2");
82
+ var OID_SIGNING_TIME = oid.byName("signingTime");
72
83
 
73
84
  var DIGEST_HASH = intrinsic.assign(intrinsic.create(null), {
74
85
  sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512",
@@ -83,9 +94,16 @@ function _digestOidTable(named) {
83
94
  });
84
95
  return t;
85
96
  }
86
- var WEAK_DIGEST_OIDS = _digestOidTable([["sha1", "SHA-1"]]);
97
+ /** @internal Both tables read the one membership list in `C.NAMES.WEAK_DIGESTS`, keyed the two ways
98
+ * the two checks ask the question: by the digest OID a SignerInfo declares, and by the WebCrypto
99
+ * hash name a signature algorithm resolves to. A digest added to that list reaches both. */
100
+ var _WEAK_DIGEST_NAMES = intrinsic.map(intrinsic.keys(constants.NAMES.WEAK_DIGESTS), function (n) {
101
+ return [n, constants.NAMES.WEAK_DIGESTS[n]];
102
+ });
103
+ var WEAK_DIGEST_OIDS = _digestOidTable(_WEAK_DIGEST_NAMES);
87
104
  var UNVERIFIABLE_DIGEST_OIDS = _digestOidTable([["md5", "MD5"], ["md4", "MD4"], ["md2", "MD2"]]);
88
- var WEAK_SIGNATURE_HASHES = intrinsic.assign(intrinsic.create(null), { "SHA-1": 1 });
105
+ var WEAK_SIGNATURE_HASHES = intrinsic.create(null);
106
+ intrinsic.forEach(_WEAK_DIGEST_NAMES, function (pair) { WEAK_SIGNATURE_HASHES[pair[1]] = 1; });
89
107
  function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
90
108
  async function _computeDigest(name, content) {
91
109
  return intrinsic.bufferFrom(await subtle.digest(DIGEST_HASH[name], content));
@@ -155,6 +173,35 @@ async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes,
155
173
  : { ok: false, sid: sid, cert: candidates[0].der });
156
174
  }
157
175
 
176
+ /** @internal Where a signer identifier matches several certificates, the signing-certificate
177
+ * attribute is what says which of them made the signature, so one it names is tried first. A
178
+ * subjectKeyIdentifier names a KEY: two certificates holding that key verify the same signature
179
+ * equally, and without this the one reported as `signers[i].cert` would be whichever candidate came
180
+ * first, taking with it every decision keyed to that certificate, its validity window, its key
181
+ * usage, and the chain built from it. RFC 5035 clause 5.4 makes the first identifier "the
182
+ * certificate used to verify the signature", which is exactly this question.
183
+ *
184
+ * Order is all that changes: a candidate the attribute names still has to verify the signature, and
185
+ * where none is named or none matches, the list is returned as it came. */
186
+ function _preferBoundCandidate(si, candidates) {
187
+ if (candidates.length < 2) return candidates;
188
+ /** @internal The attribute is read ONCE and each candidate compared against what it read. Reading
189
+ * it per candidate would multiply its size by the candidate count, and both are attacker-chosen:
190
+ * a message can embed many certificates carrying one subject key identifier and a long
191
+ * certificate list inside the attribute, so the work would be their product, spent before the
192
+ * signature is checked. */
193
+ var claims = smime.certBindingClaims(si.signedAttrs || []);
194
+ if (!claims.present) return candidates;
195
+ var bound = [];
196
+ var rest = [];
197
+ intrinsic.forEach(candidates, function (c) {
198
+ var entry = smime.certBindingAgainst(claims.chosen, c.der);
199
+ var names = entry.matches === true && entry.issuerSerialMatches !== false;
200
+ guard.list.append(names ? bound : rest, c);
201
+ });
202
+ return bound.length ? intrinsic.concat(bound, rest) : candidates;
203
+ }
204
+
158
205
  function _certSki(cert) {
159
206
  var ext = (cert.extensions || []).filter(function (e) { return e.oid === oid.byName("subjectKeyIdentifier"); })[0];
160
207
  if (!ext) return null;
@@ -185,23 +232,22 @@ function _hashAlgOid(seq) {
185
232
  return o;
186
233
  }
187
234
 
235
+ /** @internal The parameter fields come from `cms.taggedParameterFields`, which holds the DER rule
236
+ * for a SEQUENCE of tagged OPTIONAL and DEFAULT fields: one element per tag, ascending, no repeat,
237
+ * exactly one value each. The comparison that reads these same fields for their defaults walks the
238
+ * same reader, so the verifier and the comparison cannot disagree about what a field is. */
188
239
  function _resolvePss(paramsBytes) {
189
240
  if (!paramsBytes) return null;
190
- var n;
191
- try {
192
- n = asn1.decode(paramsBytes);
193
- } catch (_e) { return null; }
194
- if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.SEQUENCE || !n.children) return null;
195
- var hash = null, saltLen = null, mgfNode = null, trailer = 1n, last = -1;
196
- for (var i = 0; i < n.children.length; i++) {
197
- var f = n.children[i];
198
- if (f.tagClass !== "context" || f.tagNumber > 3 || f.tagNumber <= last || !f.children || f.children.length !== 1) return null;
199
- last = f.tagNumber;
241
+ var fields = cms.taggedParameterFields(paramsBytes, 3);
242
+ if (fields === null) return null;
243
+ var hash = null, saltLen = null, mgfNode = null, trailer = 1n;
244
+ for (var i = 0; i < fields.length; i++) {
245
+ var f = fields[i];
200
246
  try {
201
- if (f.tagNumber === 0) { hash = PSS_HASH[_hashAlgOid(f.children[0])]; if (!hash) return null; }
202
- else if (f.tagNumber === 1) { mgfNode = f.children[0]; }
203
- else if (f.tagNumber === 2) { saltLen = asn1.read.integer(f.children[0]); }
204
- else { trailer = asn1.read.integer(f.children[0]); }
247
+ if (f.tag === 0) { hash = PSS_HASH[_hashAlgOid(f.node)]; if (!hash) return null; }
248
+ else if (f.tag === 1) { mgfNode = f.node; }
249
+ else if (f.tag === 2) { saltLen = asn1.read.integer(f.node); }
250
+ else { trailer = asn1.read.integer(f.node); }
205
251
  } catch (_e3) { return null; }
206
252
  }
207
253
  if (hash === null || mgfNode === null) return null;
@@ -295,7 +341,7 @@ async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, dige
295
341
  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) });
296
342
  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)" });
297
343
  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)" });
298
- var signers = _findSignerCerts(si.sid, parsedCerts);
344
+ var signers = _preferBoundCandidate(si, _findSignerCerts(si.sid, parsedCerts));
299
345
  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" });
300
346
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
301
347
 
@@ -381,7 +427,7 @@ async function _verifyComposite(si, comp, content, eContentType, parsedCerts, cs
381
427
  if (si.digestAlgorithm.name !== comp.phCms) {
382
428
  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)" });
383
429
  }
384
- var signers = _findSignerCerts(si.sid, parsedCerts);
430
+ var signers = _preferBoundCandidate(si, _findSignerCerts(si.sid, parsedCerts));
385
431
  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" });
386
432
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
387
433
  var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
@@ -417,7 +463,375 @@ function _surfaceSignedAttrs(si) {
417
463
  });
418
464
  }
419
465
 
420
- async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests) {
466
+ /** @internal What the RFC 6211 algorithm protection attribute says about the structure carrying it,
467
+ * read ONCE per signer and used for both the verdict and the comparison, so the row an operator
468
+ * reads and the decision the verdict rests on cannot disagree.
469
+ *
470
+ * The attribute is a copy of the algorithm identifiers the structure already names, placed where
471
+ * the signature covers them. Sec. 3.1 makes both comparisons mandatory for a validator that
472
+ * supports the attribute, and support is claimed the moment this runs: "If the fields are not the
473
+ * same (modulo encoding), then signature validation MUST fail."
474
+ *
475
+ * Both arms compare modulo encoding, in the two ways sec. 3 names. A field with a default compares
476
+ * the same written out as omitted, which the clause makes a MUST and says a binary compare cannot
477
+ * satisfy; RSASSA-PSS is where that arises, since RFC 4055 clause 3.1 defaults all four of its
478
+ * parameter fields. Absent and DER NULL parameters read as one algorithm on the digest arm, because
479
+ * RFC 3370 clause 2.1 and RFC 5754 clause 2 require both encodings to be ACCEPTED; sec. 3 leaves
480
+ * that case to the implementer. Anything else in a parameter field is read as written: that is
481
+ * where RSASSA-PSS puts its hash, mask generator, salt length and trailer field, which the
482
+ * signature itself does not cover, so collapsing two values there would admit the substitution the
483
+ * attribute exists to refuse.
484
+ *
485
+ * A `[2]` MAC arm inside `signedAttrs` is a mismatch rather than an absent signature arm: sec. 2
486
+ * populates that arm "only if the attribute is placed in an AuthenticatedData.authAttrs sequence",
487
+ * so the attribute is making a claim about a structure this is not. */
488
+ function _algorithmProtectionReport(si, armName, armIdentifier) {
489
+ return cms.algorithmProtectionReport(si.signedAttrs || si.authAttrs || [],
490
+ si.digestAlgorithm, armName, armIdentifier);
491
+ }
492
+
493
+ /** @internal The report applied to a verdict. A mismatch fails the signer, and the option that
494
+ * requires the attribute fails an absent one; a verdict that already failed keeps its own code,
495
+ * since the signature's verdict is the stronger statement and masking it would tell an operator the
496
+ * wrong thing about why the message was refused. */
497
+ function _applyAlgorithmProtection(verdict, report, required) {
498
+ var fields = { algorithmProtection: guard.verdict.of(report) };
499
+ if (verdict.ok !== true) return guard.verdict.of(verdict, fields);
500
+ if (report.mismatch !== null) {
501
+ return guard.verdict.of(verdict, intrinsic.assign(fields,
502
+ { ok: false, code: report.mismatch.code, message: report.mismatch.message }));
503
+ }
504
+ if (required && !report.present) {
505
+ return guard.verdict.of(verdict, intrinsic.assign(fields, { ok: false,
506
+ code: "cms/missing-algorithm-protection",
507
+ message: "this signature carries no RFC 6211 algorithm protection attribute, which " +
508
+ "opts.requireAlgorithmProtection asks for; RFC 8933 sec. 4.1 makes including it a SHOULD, " +
509
+ "so a message without one is not malformed" }));
510
+ }
511
+ return guard.verdict.of(verdict, fields);
512
+ }
513
+
514
+ /** @internal The CAdES baseline requirements this verb decides from one SignerInfo's own signed
515
+ * attributes, reported beside the signature verdict rather than folded into it: a signature can be
516
+ * cryptographically sound and still not be a CAdES signature, and the two answers are separate.
517
+ *
518
+ * `requirementsChecked` names the scope in the data, so a caller reads which requirements were
519
+ * evaluated instead of taking `conformant` for a full ETSI EN 319 122-1 clause 6.3 audit. Each
520
+ * finding carries the `level` of the requirement it is against, and `conformant` answers for the
521
+ * shall-level ones alone, so a SHOULD the document states as guidance is reported without being
522
+ * reported as non-conformance.
523
+ *
524
+ * - requirement f, shall: "The content-type attribute shall have value id-data".
525
+ * - clauses 5.2.2.2 and 5.2.2.3, shall: the signing certificate is protected by a
526
+ * `signing-certificate` or `signing-certificate-v2` attribute, and the identifier it carries
527
+ * names the certificate the signature resolved to. Clause 5.2.2.2 NOTE 2: "the binding
528
+ * information is the digest of the certificate".
529
+ * - requirement h, shall: "The ESS signing-certificate attribute shall be used if the SHA-1 hash
530
+ * algorithm is used". The two attributes are defined BY their hash algorithm, clause 5.2.2.2 as
531
+ * "a signing certificate attribute using the SHA-1 hash algorithm" and clause 5.2.2.3 as one
532
+ * "using a hash algorithm different from SHA-1", so what the requirement reads is the digest the
533
+ * identifier binds with and not the digest the signature was computed under. Its sibling
534
+ * requirement i, the v2 form "when another hash algorithm than SHA-1 is used", has no verifiable
535
+ * counterexample: a v1 ESSCertID carries no algorithm field, so a non-SHA-1 binding cannot be
536
+ * expressed in one, and it is not claimed as checked.
537
+ * - requirement g, should: "The issuerSerial field should not be included in the encoding of the
538
+ * ESSCertID, ESSCertIDv2 or OtherCertID type".
539
+ * - requirement j, should: "The generator should migrate to the use of ESS signing-certificate-v2
540
+ * in preference to ESS signing-certificate in line with the guidance regarding limited lifetime
541
+ * for the use of SHA-1", which is what a SHA-1 binding is reported against.
542
+ */
543
+ var CADES_BASELINE_CHECKED = intrinsic.freeze([
544
+ "ETSI EN 319 122-1 clause 6.3 requirement f",
545
+ "ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3",
546
+ "ETSI EN 319 122-1 clause 6.3 requirement g",
547
+ "ETSI EN 319 122-1 clause 6.3 requirement h",
548
+ "ETSI EN 319 122-1 clause 6.3 requirement j",
549
+ ]);
550
+
551
+ function _cadesBaseline(si, signerCert) {
552
+ var attrs = si.signedAttrs || [];
553
+ var findings = [];
554
+ var contentType = null;
555
+ intrinsic.forEach(attrs, function (a) {
556
+ if (a.type !== OID_CONTENT_TYPE || contentType !== null || !a.values || a.values.length !== 1) return;
557
+ try { contentType = asn1.read.oid(asn1.decode(a.values[0])); }
558
+ // allow:swallow-unverified a content-type that does not read is reported as the unmet requirement below
559
+ catch (_e) { contentType = null; }
560
+ });
561
+ if (contentType !== OID_ID_DATA) {
562
+ guard.list.append(findings, {
563
+ code: "cms/cades-content-type", level: "shall",
564
+ message: "the content-type attribute shall have value id-data (ETSI EN 319 122-1 clause 6.3 " +
565
+ "requirement f), and this signature carries " +
566
+ (contentType === null ? "no readable content-type attribute" : (oid.name(contentType) || contentType)),
567
+ });
568
+ }
569
+ var v1 = null;
570
+ var v2 = null;
571
+ intrinsic.forEach(attrs, function (a) {
572
+ if (a.type === OID_SIGNING_CERTIFICATE && v1 === null && a.values && a.values.length === 1) v1 = a.values[0];
573
+ if (a.type === OID_SIGNING_CERTIFICATE_V2 && v2 === null && a.values && a.values.length === 1) v2 = a.values[0];
574
+ });
575
+ if (v1 === null && v2 === null) {
576
+ guard.list.append(findings, {
577
+ code: "cms/cades-no-signing-certificate", level: "shall",
578
+ message: "the signing certificate shall be protected by a signing-certificate or " +
579
+ "signing-certificate-v2 signed attribute (ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3)",
580
+ });
581
+ } else {
582
+ intrinsic.forEach(_bindingFindings(attrs, signerCert), function (f) {
583
+ guard.list.append(findings, f);
584
+ });
585
+ }
586
+ return guard.verdict.of({
587
+ conformant: guard.list.allMatch(findings, function (f) { return f.level !== "shall"; }),
588
+ signingCertificateAttribute: v2 !== null ? "v2" : (v1 !== null ? "v1" : null),
589
+ findings: findings,
590
+ requirementsChecked: CADES_BASELINE_CHECKED,
591
+ });
592
+ }
593
+
594
+ /** @internal What the signing-certificate attribute claims, checked against the certificate this
595
+ * SignerInfo selected. The requirement is that the certificate IS protected, so the attribute's
596
+ * presence is not the answer: an identifier naming other bytes binds the signature to a certificate
597
+ * nobody resolved it to, which is the substitution the attribute exists to prevent (RFC 5035
598
+ * clause 5.4: "The first certificate identified in the sequence of certificate identifiers MUST be
599
+ * the certificate used to verify the signature").
600
+ *
601
+ * The claims are read by `pki.schema.smime.certBindingFacts`, the one implementation of "does this
602
+ * identifier name that certificate", which `pki.tsp.verify` holds a timestamp token to. What differs
603
+ * is the policy over the facts, not the facts: that verb refuses a token on the first failing claim,
604
+ * and this one reports every claim that failed, because a conformance verdict is read to find out
605
+ * what is wrong rather than to decide one thing. A superseded v1 attribute is held to the same claim
606
+ * as the v2 that supersedes it, so a structure naming one certificate in each is reported rather
607
+ * than resolved in favor of whichever a reader prefers. An identifier this build cannot recompute,
608
+ * and a signature whose certificate was never matched, are reported as the undecided claims they
609
+ * are rather than passed over. */
610
+ function _bindingFindings(attrs, signerCert) {
611
+ if (signerCert == null) {
612
+ return [{ code: "cms/cades-signing-certificate-unverifiable", level: "shall",
613
+ message: "no certificate was matched for this signer, so what the signing-certificate " +
614
+ "attribute binds cannot be checked" }];
615
+ }
616
+ var facts = smime.certBindingFacts(attrs, signerCert);
617
+ var out = [];
618
+ var entries = [facts.chosen];
619
+ intrinsic.forEach(facts.superseded, function (s) { guard.list.append(entries, s); });
620
+ intrinsic.forEach(entries, function (e) {
621
+ var which = e.form === "v2" ? "signing-certificate-v2" : "signing-certificate";
622
+ if (!e.readable) {
623
+ guard.list.append(out, { code: "cms/cades-signing-certificate-unreadable", level: "shall",
624
+ message: "the " + which + " attribute value does not decode, so what it binds cannot be checked" });
625
+ return;
626
+ }
627
+ if (!e.supported) {
628
+ guard.list.append(out, { code: "cms/cades-signing-certificate-unreadable", level: "shall",
629
+ message: "the " + which + " identifier names the digest " + e.digestName +
630
+ ", which this build does not bind a certificate with, so what it binds cannot be checked" });
631
+ return;
632
+ }
633
+ /** @internal The form is held to the digest it binds with, which is what defines each of the two
634
+ * attributes: a v2 identifier naming SHA-1 is the case requirement h sends to the v1 attribute.
635
+ * A v1 identifier carries no algorithm field, so it is SHA-1 by the type and can never be the
636
+ * wrong form for its own digest; what it draws is requirement j's migration guidance. */
637
+ if (e.weak && e.form === "v2") {
638
+ guard.list.append(out, { code: "cms/cades-signing-certificate-form", level: "shall",
639
+ message: "the signing-certificate attribute shall be used if the SHA-1 hash algorithm is " +
640
+ "used (ETSI EN 319 122-1 clause 6.3 requirement h), and this signature carries a " +
641
+ "signing-certificate-v2 identifier binding with " + e.digestName });
642
+ } else if (e.weak) {
643
+ guard.list.append(out, { code: "cms/cades-weak-certificate-binding", level: "should",
644
+ message: "the " + which + " identifier binds the certificate with " + e.digestName +
645
+ ", and a generator should migrate to signing-certificate-v2 in preference to " +
646
+ "signing-certificate given the limited lifetime for the use of SHA-1 (ETSI EN 319 122-1 " +
647
+ "clause 6.3 requirement j)" });
648
+ }
649
+ if (e.issuerSerialPresent) {
650
+ guard.list.append(out, { code: "cms/cades-issuer-serial-included", level: "should",
651
+ message: "the issuerSerial field should not be included in the encoding of the " +
652
+ (e.form === "v2" ? "ESSCertIDv2" : "ESSCertID") + " type (ETSI EN 319 122-1 clause 6.3 " +
653
+ "requirement g); clause 5.2.2.2 NOTE 2 has it as a hint, the binding being the digest of " +
654
+ "the certificate" });
655
+ }
656
+ if (e.certUnreadable) {
657
+ guard.list.append(out, { code: "cms/cades-signing-certificate-unverifiable", level: "shall",
658
+ message: "the " + which + " identifier states an issuer and serial, and the certificate " +
659
+ "this signer resolved to does not parse, so that claim cannot be checked" });
660
+ }
661
+ if (e.matches !== true || e.issuerSerialMatches === false) {
662
+ guard.list.append(out, { code: "cms/cades-signing-certificate-mismatch", level: "shall",
663
+ message: "the first " + which + " identifier does not name the certificate this signature " +
664
+ "was resolved to: " + (e.matches !== true ? "the certificate hash disagrees"
665
+ : "the hash agrees but the issuer and serial name another certificate") +
666
+ " (RFC 5035 clause 5.4), so the signing certificate is not protected (ETSI EN 319 122-1 " +
667
+ "clauses 5.2.2.2 and 5.2.2.3)" });
668
+ }
669
+ });
670
+ return out;
671
+ }
672
+
673
+ /** @internal The CAdES signature timestamps on one SignerInfo, each as its own row.
674
+ *
675
+ * `id-aa-signatureTimeStampToken` is `{ 1 2 840 113549 1 9 16 2 14 }` (RFC 5126 clause 6.1.1; ETSI
676
+ * EN 319 122-1 clause 5.3 and Annex D) and its imprint is "the hash value of the signature field
677
+ * (without the ASN.1 tag and length) within SignerInfo for which the signature-time-stamp attribute
678
+ * is created" (EN 319 122-1 clause 5.3).
679
+ *
680
+ * Read HERE, in the loop that already holds `targetSig`, rather than in a verb of its own. The
681
+ * imprint preimage is the same octets the countersignature row covers, and a separate verb would
682
+ * re-derive it from its own parse: two derivations of one preimage that can disagree is a silent
683
+ * fail-open, since the one that disagrees is the one that accepts.
684
+ *
685
+ * Plurality is the opposite of countersignature: EN 319 122-1 clause 5.3 has "exactly one component
686
+ * of AttributeValue type" per attribute, and RFC 5126 clause 6.1.1 allows "several instances of this
687
+ * attribute ... from different TSAs", so each instance is one row and an instance carrying two values
688
+ * is refused rather than read as two timestamps.
689
+ *
690
+ * Every failure is a ROW verdict and never a throw: a signature whose timestamp cannot be read still
691
+ * has its own signature verdict, and collapsing the two would lose it.
692
+ *
693
+ * `0.4.0.1733.2.4`, the archive timestamp, is NOT read here. EN 319 122-1 clause 5.5.3 contains the
694
+ * sentence "The archive-time-stamp-v3 attribute shall be identified by the
695
+ * id-aa-signatureTimeStampToken OID" and then defines `id-aa-ets-archiveTimestampV3` under the ETSI
696
+ * arc, as does Annex D. The ASN.1 is followed: the two attributes hash different preimages, and
697
+ * clause 5.5.3 step 3 has the archive timestamp cover six SignerInfo fields "including the tag,
698
+ * length and value octets" where this one excludes the tag and length of one of them, so a dispatcher
699
+ * following that sentence would hash the wrong octets for every B-T signature. */
700
+ async function _verifySignatureTimestamps(si, targetSig, opts) {
701
+ var rows = [];
702
+ var attrs = [];
703
+ intrinsic.forEach(si.unsignedAttrs || [], function (a) { if (a.type === OID_TIMESTAMP_TOKEN) guard.list.append(attrs, a); });
704
+ if (!attrs.length) return rows;
705
+ /** @internal The signer's own claimed signing time and certificate window, read once here and
706
+ * passed to every row, so each row answers the coherence questions against the same values. */
707
+ var claimed = _signingTimeOf(si);
708
+ for (var i = 0; i < attrs.length; i++) {
709
+ guard.list.append(rows, await _verifyOneTimestamp(attrs[i], targetSig, claimed, opts));
710
+ }
711
+ return rows;
712
+ }
713
+
714
+ /** @internal The signer's `signing-time` attribute as a Date, or null where it carries none. It is the
715
+ * signer's own assertion and is not evidence of the real time, but it sits inside the signature, so a
716
+ * timestamp that precedes it is a contradiction within one signature. */
717
+ function _signingTimeOf(si) {
718
+ var found = null;
719
+ intrinsic.forEach(si.signedAttrs || [], function (a) {
720
+ if (a.type !== OID_SIGNING_TIME || found !== null || !a.values || !a.values.length) return;
721
+ /** @internal Read without a guard around it: `pki.schema.cms.parse` holds a signing-time
722
+ * attribute's value to a Time through this same reader before any of this runs, so a value
723
+ * that reaches here reads. A parser that stopped checking would surface the failure rather
724
+ * than have this quietly report no claimed time. */
725
+ found = asn1.read.time(asn1.decode(a.values[0]));
726
+ });
727
+ return found;
728
+ }
729
+
730
+ /** @internal The instant a Date names, or null where the value is absent or names no instant. Every
731
+ * ordering rule below reads its operands through this, so "no value" and "a value that cannot be
732
+ * ordered" reach the comparison as the same absence rather than as a comparison that is false both
733
+ * ways. */
734
+ function _instantOrNull(value) {
735
+ if (!guard.time.isDate(value)) return null;
736
+ var at = guard.time.instantOf(value);
737
+ return intrinsic.numberIsNaN(at) ? null : at;
738
+ }
739
+
740
+ async function _verifyOneTimestamp(attr, targetSig, claimedSigningTime, opts) {
741
+ if (!attr.values || attr.values.length !== 1) {
742
+ return guard.verdict.of({ valid: false, trusted: false, genTime: null,
743
+ code: "cms/timestamp-multi-valued",
744
+ message: "a signature-time-stamp attribute shall contain exactly one component of AttributeValue " +
745
+ "type (ETSI EN 319 122-1 clause 5.3), got " + ((attr.values && attr.values.length) || 0) });
746
+ }
747
+ var tokenDer = attr.values[0];
748
+ tokenDer = tokenDer && tokenDer.bytes ? tokenDer.bytes : tokenDer;
749
+ var res;
750
+ try {
751
+ /** @internal Reached inside the function rather than at the top of the file: `tsp-sign` requires
752
+ * THIS module to verify a token's own CMS signature, so a top-level require here is a cycle and
753
+ * one of the two would see a half-built export object. */
754
+ var tspVerify = require("./tsp-sign"); // allow:inline-require -- circular load: tsp-sign requires this module
755
+ res = await tspVerify.verify(tokenDer, targetSig, {
756
+ certs: opts.timestampCerts,
757
+ trustAnchors: opts.timestampTrustAnchors,
758
+ revocationChecker: opts.timestampRevocationChecker,
759
+ allowWeakDigests: opts.allowWeakDigests === true,
760
+ });
761
+ } catch (e) {
762
+ return guard.verdict.of({ valid: false, trusted: false, genTime: null,
763
+ code: (e && e.code) || "cms/bad-timestamp", message: e && e.message });
764
+ }
765
+ if (res.valid !== true) {
766
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime || null,
767
+ code: res.code === "tsp/imprint-mismatch" ? "cms/timestamp-imprint-mismatch" : res.code,
768
+ message: res.reason || null });
769
+ }
770
+ /** @internal Two coherence rules the token alone cannot answer, each against a value read from the
771
+ * signature this attribute hangs on. The first is the signer's claimed time; the second is
772
+ * EN 319 122-1 clause 6.3 requirement m, "the tokens ... shall be created before the signing
773
+ * certificate has been revoked or has expired", whose expiry half is decidable from the signer
774
+ * certificate alone and whose revocation half needs status this verb does not fetch.
775
+ *
776
+ * Each instant is taken once, through the captured read, and a genTime that is not a usable
777
+ * instant is refused rather than compared: an ordering test against it answers false in both
778
+ * directions, so a token carrying one would pass both rules by being uncomparable. */
779
+ var genAt = _instantOrNull(res.genTime);
780
+ if (res.genTime != null && genAt === null) {
781
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: null,
782
+ code: "cms/bad-timestamp-gentime",
783
+ message: "the token's genTime is not a usable instant, so the times this attribute is held " +
784
+ "to cannot be ordered against it" });
785
+ }
786
+ var claimedAt = _instantOrNull(claimedSigningTime);
787
+ if (claimedAt !== null && genAt !== null && genAt < claimedAt) {
788
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime,
789
+ code: "cms/timestamp-before-signing-time",
790
+ message: "the token's genTime " + guard.time.isoOf(res.genTime) + " precedes the signing-time " +
791
+ "the signature itself asserts, " + guard.time.isoOf(claimedSigningTime) });
792
+ }
793
+ /** @internal The upper bound is the LATEST instant the token could have been created at, not the
794
+ * instant it names. Two things widen it past a `Date`: RFC 3161 clause 2.4.2 lets genTime carry a
795
+ * fraction finer than a Date holds, so a time one microsecond past a notAfter truncates to the
796
+ * notAfter itself, and the accuracy the token states is a window around genTime rather than a
797
+ * point, so a genTime half a second inside the window with two seconds of accuracy establishes
798
+ * nothing about which side of it the token was made on. Requirement m asks that the token WAS
799
+ * created before the certificate expired, so a bound that cannot establish it refuses. */
800
+ var notAfterAt = _instantOrNull(opts.signerNotAfter);
801
+ var genUpper = genAt === null ? null
802
+ : guard.time.latestInstantOf(res.genTime, res.tstInfo && res.tstInfo.genTimeFraction, res.accuracy || null);
803
+ if (notAfterAt !== null && genUpper !== null && genUpper > notAfterAt) {
804
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime,
805
+ code: "cms/timestamp-after-signer-expiry",
806
+ /** @internal The message names the BOUND, not just the genTime: this verdict is reached
807
+ * whenever the latest instant the token could have been created at is past the notAfter, and
808
+ * with a wide accuracy or a sub-millisecond fraction that is true of tokens whose stated
809
+ * genTime is inside the window. Reporting the genTime alone would give an operator a reason
810
+ * they can check and find false. */
811
+ message: "the token's genTime " + guard.time.isoOf(res.genTime) + ", with the accuracy it " +
812
+ "states, puts the latest it could have been created at " +
813
+ guard.time.isoOf(new intrinsic.Date(genUpper)) + ", past the signing certificate's notAfter " +
814
+ guard.time.isoOf(opts.signerNotAfter) + ", so it does not establish that the token was " +
815
+ "created before that certificate expired (ETSI EN 319 122-1 clause 6.3 requirement m)" });
816
+ }
817
+ return guard.verdict.of({
818
+ valid: true, trusted: res.trusted === true, genTime: res.genTime,
819
+ accuracy: res.accuracy, serialNumber: res.serialNumber, policy: res.policy,
820
+ revocationChecked: res.revocationChecked, tsa: res.tsa, signer: res.signer,
821
+ });
822
+ }
823
+
824
+ /** @internal The signing certificate's `notAfter`, where the signer row matched one. The expiry half
825
+ * of EN 319 122-1 clause 6.3 requirement m is decidable from this certificate alone; the revocation
826
+ * half needs status this verb does not fetch and is not claimed. */
827
+ function _signerNotAfter(verdict) {
828
+ if (!verdict || !verdict.cert) return null;
829
+ try { return x509.parse(verdict.cert).validity.notAfter; }
830
+ // allow:swallow-unverified a signer certificate that does not parse already failed its own verdict
831
+ catch (_e) { return null; }
832
+ }
833
+
834
+ async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests, requireProtection) {
421
835
  var targetSig = _toBuf(si.signature, "the countersigned signature");
422
836
  var values = [];
423
837
  intrinsic.forEach(si.unsignedAttrs || [], function (a) {
@@ -425,17 +839,24 @@ async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests) {
425
839
  intrinsic.forEach(a.values, function (v) { guard.list.append(values, v && v.bytes ? v.bytes : v); });
426
840
  });
427
841
  var out = [];
428
- for (var _cp = 0; _cp < values.length; _cp++) guard.list.append(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts, allowWeakDigests));
842
+ for (var _cp = 0; _cp < values.length; _cp++) guard.list.append(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts, allowWeakDigests, requireProtection));
429
843
  return out;
430
844
  }
431
845
 
432
- async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDigests) {
846
+ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDigests, requireProtection) {
433
847
  var csSi;
434
848
  try { csSi = cms.walkCountersignature(asn1.decode(intrinsic.isBuffer(vDer) ? vDer : intrinsic.bufferFrom(vDer))); }
435
849
  catch (e) { return _row({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
436
- var verdict = await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig, undefined, allowWeakDigests);
437
- var nested = await _verifyCountersignatures(csSi, parsedCerts, allowWeakDigests);
438
- var head = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name };
850
+ /** @internal A countersignature is its own SignerInfo with its own algorithm identifiers, so the
851
+ * attribute inside it protects those and is compared against them rather than against the
852
+ * signature it countersigns. */
853
+ var apReport = _algorithmProtectionReport(csSi, "signatureAlgorithm", csSi.signatureAlgorithm);
854
+ var verdict = _applyAlgorithmProtection(
855
+ await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig, undefined, allowWeakDigests),
856
+ apReport, requireProtection === true);
857
+ var nested = await _verifyCountersignatures(csSi, parsedCerts, allowWeakDigests, requireProtection);
858
+ var head = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name,
859
+ algorithmProtection: verdict.algorithmProtection };
439
860
  return guard.verdict.of(
440
861
  verdict.code ? guard.verdict.of(head, { code: verdict.code }) : head,
441
862
  { unsignedAttrs: _surfaceUnsignedAttrs(csSi), countersignatures: nested });
@@ -455,7 +876,7 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
455
876
  * Verify a CMS SignedData signature (RFC 5652 sec. 5). `input` is a PEM string, a DER
456
877
  * `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, eContentType, eContent, signers }`
457
878
  * (`eContent` the encapsulated content when present, else null) where each `signers[i]` is
458
- * `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures }`
879
+ * `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures, signatureTimeStamps, cadesBaseline, algorithmProtection }`
459
880
  * (`cert` the matched signer certificate DER) or carries a `code` on a structural failure; `valid` is true when
460
881
  * there is at least one signer and every signer verified.
461
882
  *
@@ -480,12 +901,52 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
480
901
  * `subjectKeyIdentifier` names a key, and several certificates can hold it with different
481
902
  * validity windows, key usage and policies; deciding from a sibling would let an expired or
482
903
  * wrong-purpose signer certificate be reported trusted because a different certificate chained.
483
- * Supply the certificate you want used. RSA (PKCS#1 v1.5 and RSASSA-PSS), ECDSA, EdDSA, and the post-quantum
904
+ * Supply the certificate you want used. Where several candidates match the identifier and the
905
+ * signature carries an ESS signing-certificate attribute, the candidate that attribute names is the
906
+ * one selected, since RFC 5035 clause 5.4 makes its first identifier "the certificate used to verify
907
+ * the signature"; a named candidate must still verify the signature like any other. RSA (PKCS#1 v1.5 and RSASSA-PSS), ECDSA, EdDSA, and the post-quantum
484
908
  * ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814) signatures
485
909
  * are recognized, the post-quantum families in pure mode with an empty context, as is composite
486
910
  * ML-DSA (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
487
911
  * RSA / ECDSA / EdDSA and verifies only when both components pass (never an AND-to-OR downgrade).
488
912
  *
913
+ * `signatureTimeStamps` is one row per `id-aa-signatureTimeStampToken` unsigned attribute (RFC 5126
914
+ * clause 6.1.1, ETSI EN 319 122-1 clause 5.3), each `{ valid, trusted, genTime, accuracy,
915
+ * serialNumber, policy, revocationChecked, tsa, signer }` or `{ valid: false, code, message }`. A
916
+ * token that does not verify is a row verdict and does not take the signer's own `ok` down: the
917
+ * signature and the time it was made at are separate claims. Each row's imprint is checked against
918
+ * the signature octets this verb just verified, and a row is refused when its `genTime` precedes the
919
+ * signer's own `signing-time` attribute or falls after the signing certificate's `notAfter`
920
+ * (EN 319 122-1 clause 6.3 requirement m).
921
+ *
922
+ * What a `valid: true` row establishes is that the token was issued over the signature octets THIS
923
+ * SignerInfo carries. It does not say that signature is sound, which is `signers[i].ok`, and rows are
924
+ * reported beside a failed signature rather than withheld, so a caller acting on a timestamp reads
925
+ * both. The imprint names signature octets rather than a signer, which is what EN 319 122-1
926
+ * clause 5.3 specifies: two certificates holding one key produce one signature value over one
927
+ * content, and a token over it answers for either. `pki.cms.attachTimestamp` adds such an attribute to an
928
+ * existing message and `pki.cms.timestampImprint` produces the imprint a TSA signs.
929
+ *
930
+ * `algorithmProtection` reports the RFC 6211 attribute, `{ present, oid, compared, mismatch }`. The
931
+ * attribute is a copy of the digest and signature algorithm identifiers this signer names, placed
932
+ * where the signature covers them, and `compared` lists the identifiers checked against it. A
933
+ * disagreement fails the signer with `cms/algorithm-protection-mismatch`, which sec. 3.1 requires of
934
+ * any validator that supports the attribute: the identifiers the attribute protects are otherwise
935
+ * covered by nothing, and RFC 8933 sec. 6 names RSASSA-PSS as the case where the signature itself
936
+ * pins no digest. An absent attribute is not a failure unless `requireAlgorithmProtection` asks for
937
+ * one, and `present: false` says so rather than looking like a comparison that passed.
938
+ *
939
+ * `cadesBaseline` is a third claim, `{ conformant, signingCertificateAttribute, findings,
940
+ * requirementsChecked }`: a signature can be sound and still not be a CAdES signature.
941
+ * `requirementsChecked` lists the EN 319 122-1 requirements decided here, each unmet one carrying a
942
+ * `{ code, message }` finding, so `conformant` is read against that list and not as a full
943
+ * clause 6.3 audit. The signing-certificate requirement is the attribute's CLAIM checked against the
944
+ * certificate this SignerInfo selected, the one reported as `signers[i].cert`, not the attribute's
945
+ * presence. Like the timestamp rows it is reported beside a failed signature rather than withheld,
946
+ * so `conformant: true` on a signer whose `ok` is false says the CAdES attributes are in order and
947
+ * nothing about the signature. `pki.cms.sign` reaches `conformant: true` with a
948
+ * `signingCertificateV2` attribute built by `pki.schema.smime.buildSigningCertificateV2`.
949
+ *
489
950
  * @opts content The detached content when the SignedData carries no encapsulated eContent, and
490
951
  * required for a detached signature. A `Buffer`, or an async iterable of byte chunks
491
952
  * to verify a large payload without holding it in memory. The streamed form is hashed
@@ -503,6 +964,21 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
503
964
  * question from `requiredEku`, since a root distributed with NSS trust bits can be
504
965
  * marked untrusted for one purpose and good for another. Those bits and
505
966
  * `distrustAfter` are consulted only when this names a purpose.
967
+ * @opts timestampCerts Extra TSA certificates for the signature-timestamp rows, in addition to the
968
+ * ones each token embeds.
969
+ * @opts timestampTrustAnchors The roots a time-stamping authority must chain to for a timestamp row
970
+ * to report `trusted`. Named apart from `trustAnchors` because a TSA is a different
971
+ * trust decision from the signer: an operator may anchor one and not the other.
972
+ * @opts timestampRevocationChecker A revocation checker for the TSA chain, as `pki.path.validate`
973
+ * takes one. Each row reports what it established in `revocationChecked`.
974
+ * @opts requireAlgorithmProtection Fail a signer that carries no RFC 6211
975
+ * `CMSAlgorithmProtection` attribute, with `cms/missing-algorithm-protection`. Off
976
+ * by default: RFC 6211 says nothing about absence and RFC 8933 sec. 4.1 makes
977
+ * including it a SHOULD, so a message without one is not malformed and most CMS in
978
+ * the field carries none. A signer that DOES carry one is always held to it, with no
979
+ * option, because sec. 3.1's comparisons are MUSTs once a validator supports the
980
+ * attribute. The option is the boolean it documents: `false`, `null` and an absent
981
+ * option are the off forms, and any other value is refused `cms/bad-input`.
506
982
  * @example
507
983
  * var pair = await pki.key.generate("Ed25519");
508
984
  * var key = await pki.key.export(pair.privateKey);
@@ -540,7 +1016,13 @@ async function _resolveContentDigests(parsed, streamSource) {
540
1016
  return byName;
541
1017
  }
542
1018
 
543
- var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, allowWeakDigests: 1 });
1019
+ var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, allowWeakDigests: 1,
1020
+ /** @internal The timestamp inputs are named apart from the signer's own, because a time-stamping
1021
+ * authority is a different trust decision from the signer's: an operator may anchor the TSA while
1022
+ * leaving the signer unanchored, or the reverse, and one `trustAnchors` for both would make those
1023
+ * two answers one. */
1024
+ timestampCerts: 1, timestampTrustAnchors: 1, timestampRevocationChecker: 1,
1025
+ requireAlgorithmProtection: 1 });
544
1026
 
545
1027
  function verify(input, opts) {
546
1028
  return guard.async.deferred(function () { return _verify(input, opts); });
@@ -549,12 +1031,20 @@ function verify(input, opts) {
549
1031
  async function _verify(input, opts) {
550
1032
  opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.verify options");
551
1033
  guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "cms/bad-input", "pki.cms.verify has an unknown option ");
1034
+ /** @internal The two policy switches are taken FIRST, ahead of everything below that can run the
1035
+ * caller's own code. Deriving the message runs accessors on a parsed input, copying the trust
1036
+ * material runs them on each anchor, and a streamed detached content runs the generator while
1037
+ * this verb is pending; any of those can write to an options object the caller still holds, so a
1038
+ * switch read after them is read from whatever they left behind rather than from the call. */
1039
+ var allowWeakDigests = opts.allowWeakDigests === true;
1040
+ var requireProtection = guard.identifier.booleanOption(opts.requireAlgorithmProtection, _err,
1041
+ "cms/bad-input", "pki.cms.verify opts.requireAlgorithmProtection");
552
1042
  var parsed = guard.parsed.acceptDerived(input, "cms", function (bytes) {
553
1043
  return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
554
1044
  }, _err, "cms/bad-input", "the SignedData");
555
1045
  if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
556
1046
  var trustCfg = _snapshotTrust(opts);
557
- var allowWeakDigests = opts.allowWeakDigests === true;
1047
+ var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests);
558
1048
  var parsedCerts = [];
559
1049
  (parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
560
1050
  (opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
@@ -580,13 +1070,25 @@ async function _verify(input, opts) {
580
1070
  var signers = [];
581
1071
  for (var _sp = 0; _sp < parsed.signerInfos.length; _sp++) {
582
1072
  var siOne = parsed.signerInfos[_sp];
1073
+ /** @internal Read once, before the signature is checked, and used for both the verdict row and
1074
+ * the decision: the attribute is an independent requirement on the same signer, the way trust
1075
+ * is, so it composes onto the verdict rather than being folded into the signature check. */
1076
+ var apReport = _algorithmProtectionReport(siOne, "signatureAlgorithm", siOne.signatureAlgorithm);
583
1077
  var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName, allowWeakDigests);
584
- var counters = await _verifyCountersignatures(siOne, parsedCerts, allowWeakDigests);
1078
+ verdict = _applyAlgorithmProtection(verdict, apReport, requireProtection);
1079
+ var counters = await _verifyCountersignatures(siOne, parsedCerts, allowWeakDigests, requireProtection);
1080
+ /** @internal The timestamp rows are built from the same `signature` octets the signature verdict
1081
+ * above was computed over, which is why they are here and not in a verb of their own. The
1082
+ * signer's own certificate supplies the expiry half of EN 319 122-1 clause 6.3 requirement m. */
1083
+ var stamps = await _verifySignatureTimestamps(siOne, _toBuf(siOne.signature, "the timestamped signature"),
1084
+ intrinsic.assign(intrinsic.create(null), tsTrustCfg, { signerNotAfter: _signerNotAfter(verdict) }));
585
1085
  guard.list.append(signers, guard.verdict.of(verdict, {
586
1086
  countersignatures: counters,
1087
+ signatureTimeStamps: stamps,
587
1088
  unsignedAttrs: _surfaceUnsignedAttrs(siOne),
588
1089
  signedAttributesPresent: !!siOne.signedAttrsBytes,
589
1090
  signedAttributes: _surfaceSignedAttrs(siOne),
1091
+ cadesBaseline: _cadesBaseline(siOne, verdict && verdict.cert ? verdict.cert : null),
590
1092
  digestAlgorithm: siOne.digestAlgorithm.name,
591
1093
  trusted: false,
592
1094
  }));
@@ -640,6 +1142,30 @@ function _snapshotTrust(opts) {
640
1142
  };
641
1143
  }
642
1144
 
1145
+ /** @internal The time-stamping authority's trust inputs, taken at the call like the signer's own and
1146
+ * for the same reason: they are first read inside the per-signer loop, after the signature and
1147
+ * countersignature checks have awaited, so an options object that changes between rows, or one
1148
+ * whose properties are accessors, would have two rows of one verdict judged against two different
1149
+ * authorities. The certificates are copied by value, since a caller holding the array could swap an
1150
+ * entry after the row that read it. The checker is a function and is captured as the one reference
1151
+ * every row calls. */
1152
+ function _snapshotTimestampTrust(opts, allowWeakDigests) {
1153
+ var certs = opts.timestampCerts;
1154
+ var anchors = opts.timestampTrustAnchors;
1155
+ return {
1156
+ timestampCerts: certs == null ? certs
1157
+ : guard.list.copyMap(intrinsic.isArray(certs) ? certs : [certs], function (c) {
1158
+ return _snapshotIfBytes(c, "opts.timestampCerts[]");
1159
+ }),
1160
+ timestampTrustAnchors: anchors == null ? anchors
1161
+ : guard.list.copyMap(intrinsic.isArray(anchors) ? anchors : [anchors], function (a) {
1162
+ return typeof a === "string" ? a : _cloneAnchorValue(a, 0);
1163
+ }),
1164
+ timestampRevocationChecker: opts.timestampRevocationChecker,
1165
+ allowWeakDigests: allowWeakDigests,
1166
+ };
1167
+ }
1168
+
643
1169
  /** @internal A signature over content is authorized by digitalSignature, contentCommitment or
644
1170
  * nonRepudiation alike; the shared helper carries the rest of the rule. */
645
1171
  var _permitsSigning = pkix.keyUsagePermits(pkix.makeNS("cms", CmsError, oid),
@@ -762,6 +1288,17 @@ function _addCert(out, der) {
762
1288
  * (`timeStampToken`); content-type / message-digest / signing-time are
763
1289
  * rejected here (RFC 5652 sec. 11). Use `pki.cms.countersign` for a
764
1290
  * countersignature specifically.
1291
+ * @opts algorithmProtection Emit the RFC 6211 `CMSAlgorithmProtection` signed attribute, a copy of
1292
+ * this signer's digest and signature algorithm identifiers placed where the
1293
+ * signature covers them. RFC 8933 sec. 4.1 makes including it a SHOULD, and
1294
+ * RFC 9882 sec. 4 and RFC 9814 sec. 5 repeat that for ML-DSA and SLH-DSA,
1295
+ * which carry no digest identifier inside the signature. Off by default,
1296
+ * because turning it on changes the bytes of every signature. `true` emits
1297
+ * the identifier RFC 6211 sec. 2 states, which OpenSSL and Bouncy Castle
1298
+ * use; `"registry"` emits the S/MIME arc value the IANA registry and
1299
+ * draft-ietf-lamps-rfc6211-update give it. A verifier here reads both.
1300
+ * `false`, `null` and an absent option emit none, and any other value is
1301
+ * refused `cms/bad-input`.
765
1302
  * @example
766
1303
  * var pair = await pki.key.generate("Ed25519");
767
1304
  * var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
@@ -804,6 +1341,10 @@ var sign = cmsSign.sign;
804
1341
  * is rejected, RFC 5652 sec. 11.4).
805
1342
  * @opts sid `"issuerAndSerial"` (default) or `"ski"`.
806
1343
  * @opts certificates Embed the countersigner certificate(s) in the SignedData. Default true.
1344
+ * @opts algorithmProtection Emit the RFC 6211 `CMSAlgorithmProtection` attribute on the
1345
+ * countersignature, copying ITS own digest and signature algorithm
1346
+ * identifiers rather than the countersigned signature's. Off by
1347
+ * default; `"registry"` selects the IANA registry identifier.
807
1348
  * @opts pem Return a PEM string instead of a DER Buffer.
808
1349
  * @example
809
1350
  * var pair = await pki.key.generate("Ed25519");
@@ -817,6 +1358,12 @@ var sign = cmsSign.sign;
817
1358
  */
818
1359
  var countersign = cmsSign.countersign;
819
1360
 
1361
+ /** @internal The two CAdES signature-timestamp verbs, re-exported here with `sign` and `countersign`
1362
+ * because this module is the `pki.cms` namespace's assembly point. Their documentation blocks live
1363
+ * with their implementations in `cms-sign.js`. */
1364
+ var timestampImprint = cmsSign.timestampImprint;
1365
+ var attachTimestamp = cmsSign.attachTimestamp;
1366
+
820
1367
  /**
821
1368
  * @primitive pki.cms.encrypt
822
1369
  * @signature pki.cms.encrypt(content, recipients, opts?) -> Promise<Buffer | string>
@@ -893,6 +1440,18 @@ var encrypt = cmsEncrypt.encrypt;
893
1440
  * @opts keyIdentifier `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
894
1441
  * @opts oaepHash The RSAES-OAEP hash for ktri recipients wrapping the MAC key: `"sha256"` (default) / `"sha384"` / `"sha512"`.
895
1442
  * @opts ukm User keying material (a Buffer) for kari / kemri recipients.
1443
+ * @opts algorithmProtection Emit the RFC 6211 `CMSAlgorithmProtection` authenticated attribute, a
1444
+ * copy of the digest and MAC algorithm identifiers this structure names. It matters more here
1445
+ * than on a signature: RFC 5652 sec. 9.2 makes the MAC input the DER encoding of `authAttrs`
1446
+ * alone, so `digestAlgorithm` and `macAlgorithm` are covered by nothing, and rewriting the
1447
+ * digest algorithm leaves the MAC verifying while the recipient recomputes the content digest
1448
+ * under the attacker's choice, no key required. `true` emits the identifier RFC 6211 sec. 2
1449
+ * states, `"registry"` the IANA registry value. Off by default, because it changes the bytes
1450
+ * of every message. `false`, `null` and an absent option emit none, and any other value is
1451
+ * refused `cms/bad-input`. With `authenticatedAttributes: false` the MAC covers the content
1452
+ * octets directly and no attribute set is built, so asking for the attribute there is refused
1453
+ * while the off forms are still accepted. `pki.cms.decrypt` reads both identifiers and
1454
+ * compares the copies it finds.
896
1455
  * @opts pem Return a PEM string (`-----BEGIN CMS-----`) instead of a DER Buffer.
897
1456
  * @example
898
1457
  * // authenticate a message to an RSA recipient, then verify the MAC via decrypt
@@ -955,6 +1514,19 @@ var authenticate = cmsEncrypt.authenticate;
955
1514
  * @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
956
1515
  * @opts stream Return `content` as an async iterable of plaintext `Buffer` chunks instead of a single `Buffer`.
957
1516
  * @opts allowUnauthenticatedRsa15 Read a message that uses RSAES-PKCS1-v1_5 key transport over a content with no integrity tag, such as AES-CBC. Refused by default with `cms/unauthenticated-rsa-v15`: the content padding is that combination's only integrity check, so decrypting it either returns the plaintext of a substituted key or tells an attacker submitting chosen ciphertexts that theirs decoded (RFC 8017 sec. 7.2). `openssl cms -encrypt` emits exactly this combination when no algorithm is named, so reading one of its messages needs this option. An AEAD content or an RSAES-OAEP recipient needs none.
1517
+ * @opts requireAlgorithmProtection For an AuthenticatedData, refuse one that carries no RFC 6211
1518
+ * `CMSAlgorithmProtection` authenticated attribute, with `cms/missing-algorithm-protection`.
1519
+ * Off by default, since RFC 8933 sec. 4.1 makes including it a SHOULD. A message that DOES
1520
+ * carry one is always held to it, with no option: its `digestAlgorithm` and `macAlgorithm`
1521
+ * copies are compared against the fields of the `AuthenticatedData` itself, and a disagreement
1522
+ * is `cms/algorithm-protection-mismatch`. That comparison runs before the MAC, because a
1523
+ * mismatch is a statement about the message rather than about the key. The result's
1524
+ * `algorithmProtection` field reports what was compared. The option is the boolean it
1525
+ * documents: `false`, `null` and an absent option are the off forms, and any other value is
1526
+ * refused `cms/bad-input`. Asking for it while decrypting any other content type is refused
1527
+ * the same way: RFC 8933 sec. 6 gives the attribute a place in an `AuthenticatedData` alone,
1528
+ * so an `EnvelopedData`, `AuthEnvelopedData` or `EncryptedData` cannot satisfy the
1529
+ * requirement and returning its plaintext would answer neither question.
958
1530
  * @example
959
1531
  * var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
960
1532
  * var pair = await pki.key.generate(rsa);
@@ -1126,4 +1698,4 @@ function isCertsOnly(input) {
1126
1698
  }
1127
1699
 
1128
1700
  // @internal
1129
- module.exports = { verify: verify, sign: sign, countersign: countersign, encrypt: encrypt, authenticate: authenticate, decrypt: decrypt, compress: compress, decompress: decompress, digest: cmsDigest.digest, verifyDigest: cmsDigest.verifyDigest, certsOnly: certsOnly, parseCertsOnly: parseCertsOnly, isCertsOnly: isCertsOnly, setEngine: setEngine };
1701
+ module.exports = { verify: verify, sign: sign, countersign: countersign, encrypt: encrypt, authenticate: authenticate, decrypt: decrypt, compress: compress, decompress: decompress, digest: cmsDigest.digest, verifyDigest: cmsDigest.verifyDigest, certsOnly: certsOnly, parseCertsOnly: parseCertsOnly, isCertsOnly: isCertsOnly, setEngine: setEngine, timestampImprint: timestampImprint, attachTimestamp: attachTimestamp };