@blamejs/pki 0.8.51 → 0.8.52

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;
@@ -295,7 +342,7 @@ async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, dige
295
342
  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
343
  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
344
  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);
345
+ var signers = _preferBoundCandidate(si, _findSignerCerts(si.sid, parsedCerts));
299
346
  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
347
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
301
348
 
@@ -381,7 +428,7 @@ async function _verifyComposite(si, comp, content, eContentType, parsedCerts, cs
381
428
  if (si.digestAlgorithm.name !== comp.phCms) {
382
429
  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
430
  }
384
- var signers = _findSignerCerts(si.sid, parsedCerts);
431
+ var signers = _preferBoundCandidate(si, _findSignerCerts(si.sid, parsedCerts));
385
432
  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
433
  var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
387
434
  var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
@@ -417,6 +464,326 @@ function _surfaceSignedAttrs(si) {
417
464
  });
418
465
  }
419
466
 
467
+ /** @internal The CAdES baseline requirements this verb decides from one SignerInfo's own signed
468
+ * attributes, reported beside the signature verdict rather than folded into it: a signature can be
469
+ * cryptographically sound and still not be a CAdES signature, and the two answers are separate.
470
+ *
471
+ * `requirementsChecked` names the scope in the data, so a caller reads which requirements were
472
+ * evaluated instead of taking `conformant` for a full ETSI EN 319 122-1 clause 6.3 audit. Each
473
+ * finding carries the `level` of the requirement it is against, and `conformant` answers for the
474
+ * shall-level ones alone, so a SHOULD the document states as guidance is reported without being
475
+ * reported as non-conformance.
476
+ *
477
+ * - requirement f, shall: "The content-type attribute shall have value id-data".
478
+ * - clauses 5.2.2.2 and 5.2.2.3, shall: the signing certificate is protected by a
479
+ * `signing-certificate` or `signing-certificate-v2` attribute, and the identifier it carries
480
+ * names the certificate the signature resolved to. Clause 5.2.2.2 NOTE 2: "the binding
481
+ * information is the digest of the certificate".
482
+ * - requirement h, shall: "The ESS signing-certificate attribute shall be used if the SHA-1 hash
483
+ * algorithm is used". The two attributes are defined BY their hash algorithm, clause 5.2.2.2 as
484
+ * "a signing certificate attribute using the SHA-1 hash algorithm" and clause 5.2.2.3 as one
485
+ * "using a hash algorithm different from SHA-1", so what the requirement reads is the digest the
486
+ * identifier binds with and not the digest the signature was computed under. Its sibling
487
+ * requirement i, the v2 form "when another hash algorithm than SHA-1 is used", has no verifiable
488
+ * counterexample: a v1 ESSCertID carries no algorithm field, so a non-SHA-1 binding cannot be
489
+ * expressed in one, and it is not claimed as checked.
490
+ * - requirement g, should: "The issuerSerial field should not be included in the encoding of the
491
+ * ESSCertID, ESSCertIDv2 or OtherCertID type".
492
+ * - requirement j, should: "The generator should migrate to the use of ESS signing-certificate-v2
493
+ * in preference to ESS signing-certificate in line with the guidance regarding limited lifetime
494
+ * for the use of SHA-1", which is what a SHA-1 binding is reported against.
495
+ */
496
+ var CADES_BASELINE_CHECKED = intrinsic.freeze([
497
+ "ETSI EN 319 122-1 clause 6.3 requirement f",
498
+ "ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3",
499
+ "ETSI EN 319 122-1 clause 6.3 requirement g",
500
+ "ETSI EN 319 122-1 clause 6.3 requirement h",
501
+ "ETSI EN 319 122-1 clause 6.3 requirement j",
502
+ ]);
503
+
504
+ function _cadesBaseline(si, signerCert) {
505
+ var attrs = si.signedAttrs || [];
506
+ var findings = [];
507
+ var contentType = null;
508
+ intrinsic.forEach(attrs, function (a) {
509
+ if (a.type !== OID_CONTENT_TYPE || contentType !== null || !a.values || a.values.length !== 1) return;
510
+ try { contentType = asn1.read.oid(asn1.decode(a.values[0])); }
511
+ // allow:swallow-unverified a content-type that does not read is reported as the unmet requirement below
512
+ catch (_e) { contentType = null; }
513
+ });
514
+ if (contentType !== OID_ID_DATA) {
515
+ guard.list.append(findings, {
516
+ code: "cms/cades-content-type", level: "shall",
517
+ message: "the content-type attribute shall have value id-data (ETSI EN 319 122-1 clause 6.3 " +
518
+ "requirement f), and this signature carries " +
519
+ (contentType === null ? "no readable content-type attribute" : (oid.name(contentType) || contentType)),
520
+ });
521
+ }
522
+ var v1 = null;
523
+ var v2 = null;
524
+ intrinsic.forEach(attrs, function (a) {
525
+ if (a.type === OID_SIGNING_CERTIFICATE && v1 === null && a.values && a.values.length === 1) v1 = a.values[0];
526
+ if (a.type === OID_SIGNING_CERTIFICATE_V2 && v2 === null && a.values && a.values.length === 1) v2 = a.values[0];
527
+ });
528
+ if (v1 === null && v2 === null) {
529
+ guard.list.append(findings, {
530
+ code: "cms/cades-no-signing-certificate", level: "shall",
531
+ message: "the signing certificate shall be protected by a signing-certificate or " +
532
+ "signing-certificate-v2 signed attribute (ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3)",
533
+ });
534
+ } else {
535
+ intrinsic.forEach(_bindingFindings(attrs, signerCert), function (f) {
536
+ guard.list.append(findings, f);
537
+ });
538
+ }
539
+ return guard.verdict.of({
540
+ conformant: guard.list.allMatch(findings, function (f) { return f.level !== "shall"; }),
541
+ signingCertificateAttribute: v2 !== null ? "v2" : (v1 !== null ? "v1" : null),
542
+ findings: findings,
543
+ requirementsChecked: CADES_BASELINE_CHECKED,
544
+ });
545
+ }
546
+
547
+ /** @internal What the signing-certificate attribute claims, checked against the certificate this
548
+ * SignerInfo selected. The requirement is that the certificate IS protected, so the attribute's
549
+ * presence is not the answer: an identifier naming other bytes binds the signature to a certificate
550
+ * nobody resolved it to, which is the substitution the attribute exists to prevent (RFC 5035
551
+ * clause 5.4: "The first certificate identified in the sequence of certificate identifiers MUST be
552
+ * the certificate used to verify the signature").
553
+ *
554
+ * The claims are read by `pki.schema.smime.certBindingFacts`, the one implementation of "does this
555
+ * identifier name that certificate", which `pki.tsp.verify` holds a timestamp token to. What differs
556
+ * is the policy over the facts, not the facts: that verb refuses a token on the first failing claim,
557
+ * and this one reports every claim that failed, because a conformance verdict is read to find out
558
+ * what is wrong rather than to decide one thing. A superseded v1 attribute is held to the same claim
559
+ * as the v2 that supersedes it, so a structure naming one certificate in each is reported rather
560
+ * than resolved in favor of whichever a reader prefers. An identifier this build cannot recompute,
561
+ * and a signature whose certificate was never matched, are reported as the undecided claims they
562
+ * are rather than passed over. */
563
+ function _bindingFindings(attrs, signerCert) {
564
+ if (signerCert == null) {
565
+ return [{ code: "cms/cades-signing-certificate-unverifiable", level: "shall",
566
+ message: "no certificate was matched for this signer, so what the signing-certificate " +
567
+ "attribute binds cannot be checked" }];
568
+ }
569
+ var facts = smime.certBindingFacts(attrs, signerCert);
570
+ var out = [];
571
+ var entries = [facts.chosen];
572
+ intrinsic.forEach(facts.superseded, function (s) { guard.list.append(entries, s); });
573
+ intrinsic.forEach(entries, function (e) {
574
+ var which = e.form === "v2" ? "signing-certificate-v2" : "signing-certificate";
575
+ if (!e.readable) {
576
+ guard.list.append(out, { code: "cms/cades-signing-certificate-unreadable", level: "shall",
577
+ message: "the " + which + " attribute value does not decode, so what it binds cannot be checked" });
578
+ return;
579
+ }
580
+ if (!e.supported) {
581
+ guard.list.append(out, { code: "cms/cades-signing-certificate-unreadable", level: "shall",
582
+ message: "the " + which + " identifier names the digest " + e.digestName +
583
+ ", which this build does not bind a certificate with, so what it binds cannot be checked" });
584
+ return;
585
+ }
586
+ /** @internal The form is held to the digest it binds with, which is what defines each of the two
587
+ * attributes: a v2 identifier naming SHA-1 is the case requirement h sends to the v1 attribute.
588
+ * A v1 identifier carries no algorithm field, so it is SHA-1 by the type and can never be the
589
+ * wrong form for its own digest; what it draws is requirement j's migration guidance. */
590
+ if (e.weak && e.form === "v2") {
591
+ guard.list.append(out, { code: "cms/cades-signing-certificate-form", level: "shall",
592
+ message: "the signing-certificate attribute shall be used if the SHA-1 hash algorithm is " +
593
+ "used (ETSI EN 319 122-1 clause 6.3 requirement h), and this signature carries a " +
594
+ "signing-certificate-v2 identifier binding with " + e.digestName });
595
+ } else if (e.weak) {
596
+ guard.list.append(out, { code: "cms/cades-weak-certificate-binding", level: "should",
597
+ message: "the " + which + " identifier binds the certificate with " + e.digestName +
598
+ ", and a generator should migrate to signing-certificate-v2 in preference to " +
599
+ "signing-certificate given the limited lifetime for the use of SHA-1 (ETSI EN 319 122-1 " +
600
+ "clause 6.3 requirement j)" });
601
+ }
602
+ if (e.issuerSerialPresent) {
603
+ guard.list.append(out, { code: "cms/cades-issuer-serial-included", level: "should",
604
+ message: "the issuerSerial field should not be included in the encoding of the " +
605
+ (e.form === "v2" ? "ESSCertIDv2" : "ESSCertID") + " type (ETSI EN 319 122-1 clause 6.3 " +
606
+ "requirement g); clause 5.2.2.2 NOTE 2 has it as a hint, the binding being the digest of " +
607
+ "the certificate" });
608
+ }
609
+ if (e.certUnreadable) {
610
+ guard.list.append(out, { code: "cms/cades-signing-certificate-unverifiable", level: "shall",
611
+ message: "the " + which + " identifier states an issuer and serial, and the certificate " +
612
+ "this signer resolved to does not parse, so that claim cannot be checked" });
613
+ }
614
+ if (e.matches !== true || e.issuerSerialMatches === false) {
615
+ guard.list.append(out, { code: "cms/cades-signing-certificate-mismatch", level: "shall",
616
+ message: "the first " + which + " identifier does not name the certificate this signature " +
617
+ "was resolved to: " + (e.matches !== true ? "the certificate hash disagrees"
618
+ : "the hash agrees but the issuer and serial name another certificate") +
619
+ " (RFC 5035 clause 5.4), so the signing certificate is not protected (ETSI EN 319 122-1 " +
620
+ "clauses 5.2.2.2 and 5.2.2.3)" });
621
+ }
622
+ });
623
+ return out;
624
+ }
625
+
626
+ /** @internal The CAdES signature timestamps on one SignerInfo, each as its own row.
627
+ *
628
+ * `id-aa-signatureTimeStampToken` is `{ 1 2 840 113549 1 9 16 2 14 }` (RFC 5126 clause 6.1.1; ETSI
629
+ * EN 319 122-1 clause 5.3 and Annex D) and its imprint is "the hash value of the signature field
630
+ * (without the ASN.1 tag and length) within SignerInfo for which the signature-time-stamp attribute
631
+ * is created" (EN 319 122-1 clause 5.3).
632
+ *
633
+ * Read HERE, in the loop that already holds `targetSig`, rather than in a verb of its own. The
634
+ * imprint preimage is the same octets the countersignature row covers, and a separate verb would
635
+ * re-derive it from its own parse: two derivations of one preimage that can disagree is a silent
636
+ * fail-open, since the one that disagrees is the one that accepts.
637
+ *
638
+ * Plurality is the opposite of countersignature: EN 319 122-1 clause 5.3 has "exactly one component
639
+ * of AttributeValue type" per attribute, and RFC 5126 clause 6.1.1 allows "several instances of this
640
+ * attribute ... from different TSAs", so each instance is one row and an instance carrying two values
641
+ * is refused rather than read as two timestamps.
642
+ *
643
+ * Every failure is a ROW verdict and never a throw: a signature whose timestamp cannot be read still
644
+ * has its own signature verdict, and collapsing the two would lose it.
645
+ *
646
+ * `0.4.0.1733.2.4`, the archive timestamp, is NOT read here. EN 319 122-1 clause 5.5.3 contains the
647
+ * sentence "The archive-time-stamp-v3 attribute shall be identified by the
648
+ * id-aa-signatureTimeStampToken OID" and then defines `id-aa-ets-archiveTimestampV3` under the ETSI
649
+ * arc, as does Annex D. The ASN.1 is followed: the two attributes hash different preimages, and
650
+ * clause 5.5.3 step 3 has the archive timestamp cover six SignerInfo fields "including the tag,
651
+ * length and value octets" where this one excludes the tag and length of one of them, so a dispatcher
652
+ * following that sentence would hash the wrong octets for every B-T signature. */
653
+ async function _verifySignatureTimestamps(si, targetSig, opts) {
654
+ var rows = [];
655
+ var attrs = [];
656
+ intrinsic.forEach(si.unsignedAttrs || [], function (a) { if (a.type === OID_TIMESTAMP_TOKEN) guard.list.append(attrs, a); });
657
+ if (!attrs.length) return rows;
658
+ /** @internal The signer's own claimed signing time and certificate window, read once here and
659
+ * passed to every row, so each row answers the coherence questions against the same values. */
660
+ var claimed = _signingTimeOf(si);
661
+ for (var i = 0; i < attrs.length; i++) {
662
+ guard.list.append(rows, await _verifyOneTimestamp(attrs[i], targetSig, claimed, opts));
663
+ }
664
+ return rows;
665
+ }
666
+
667
+ /** @internal The signer's `signing-time` attribute as a Date, or null where it carries none. It is the
668
+ * signer's own assertion and is not evidence of the real time, but it sits inside the signature, so a
669
+ * timestamp that precedes it is a contradiction within one signature. */
670
+ function _signingTimeOf(si) {
671
+ var found = null;
672
+ intrinsic.forEach(si.signedAttrs || [], function (a) {
673
+ if (a.type !== OID_SIGNING_TIME || found !== null || !a.values || !a.values.length) return;
674
+ /** @internal Read without a guard around it: `pki.schema.cms.parse` holds a signing-time
675
+ * attribute's value to a Time through this same reader before any of this runs, so a value
676
+ * that reaches here reads. A parser that stopped checking would surface the failure rather
677
+ * than have this quietly report no claimed time. */
678
+ found = asn1.read.time(asn1.decode(a.values[0]));
679
+ });
680
+ return found;
681
+ }
682
+
683
+ /** @internal The instant a Date names, or null where the value is absent or names no instant. Every
684
+ * ordering rule below reads its operands through this, so "no value" and "a value that cannot be
685
+ * ordered" reach the comparison as the same absence rather than as a comparison that is false both
686
+ * ways. */
687
+ function _instantOrNull(value) {
688
+ if (!guard.time.isDate(value)) return null;
689
+ var at = guard.time.instantOf(value);
690
+ return intrinsic.numberIsNaN(at) ? null : at;
691
+ }
692
+
693
+ async function _verifyOneTimestamp(attr, targetSig, claimedSigningTime, opts) {
694
+ if (!attr.values || attr.values.length !== 1) {
695
+ return guard.verdict.of({ valid: false, trusted: false, genTime: null,
696
+ code: "cms/timestamp-multi-valued",
697
+ message: "a signature-time-stamp attribute shall contain exactly one component of AttributeValue " +
698
+ "type (ETSI EN 319 122-1 clause 5.3), got " + ((attr.values && attr.values.length) || 0) });
699
+ }
700
+ var tokenDer = attr.values[0];
701
+ tokenDer = tokenDer && tokenDer.bytes ? tokenDer.bytes : tokenDer;
702
+ var res;
703
+ try {
704
+ /** @internal Reached inside the function rather than at the top of the file: `tsp-sign` requires
705
+ * THIS module to verify a token's own CMS signature, so a top-level require here is a cycle and
706
+ * one of the two would see a half-built export object. */
707
+ var tspVerify = require("./tsp-sign"); // allow:inline-require -- circular load: tsp-sign requires this module
708
+ res = await tspVerify.verify(tokenDer, targetSig, {
709
+ certs: opts.timestampCerts,
710
+ trustAnchors: opts.timestampTrustAnchors,
711
+ revocationChecker: opts.timestampRevocationChecker,
712
+ allowWeakDigests: opts.allowWeakDigests === true,
713
+ });
714
+ } catch (e) {
715
+ return guard.verdict.of({ valid: false, trusted: false, genTime: null,
716
+ code: (e && e.code) || "cms/bad-timestamp", message: e && e.message });
717
+ }
718
+ if (res.valid !== true) {
719
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime || null,
720
+ code: res.code === "tsp/imprint-mismatch" ? "cms/timestamp-imprint-mismatch" : res.code,
721
+ message: res.reason || null });
722
+ }
723
+ /** @internal Two coherence rules the token alone cannot answer, each against a value read from the
724
+ * signature this attribute hangs on. The first is the signer's claimed time; the second is
725
+ * EN 319 122-1 clause 6.3 requirement m, "the tokens ... shall be created before the signing
726
+ * certificate has been revoked or has expired", whose expiry half is decidable from the signer
727
+ * certificate alone and whose revocation half needs status this verb does not fetch.
728
+ *
729
+ * Each instant is taken once, through the captured read, and a genTime that is not a usable
730
+ * instant is refused rather than compared: an ordering test against it answers false in both
731
+ * directions, so a token carrying one would pass both rules by being uncomparable. */
732
+ var genAt = _instantOrNull(res.genTime);
733
+ if (res.genTime != null && genAt === null) {
734
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: null,
735
+ code: "cms/bad-timestamp-gentime",
736
+ message: "the token's genTime is not a usable instant, so the times this attribute is held " +
737
+ "to cannot be ordered against it" });
738
+ }
739
+ var claimedAt = _instantOrNull(claimedSigningTime);
740
+ if (claimedAt !== null && genAt !== null && genAt < claimedAt) {
741
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime,
742
+ code: "cms/timestamp-before-signing-time",
743
+ message: "the token's genTime " + guard.time.isoOf(res.genTime) + " precedes the signing-time " +
744
+ "the signature itself asserts, " + guard.time.isoOf(claimedSigningTime) });
745
+ }
746
+ /** @internal The upper bound is the LATEST instant the token could have been created at, not the
747
+ * instant it names. Two things widen it past a `Date`: RFC 3161 clause 2.4.2 lets genTime carry a
748
+ * fraction finer than a Date holds, so a time one microsecond past a notAfter truncates to the
749
+ * notAfter itself, and the accuracy the token states is a window around genTime rather than a
750
+ * point, so a genTime half a second inside the window with two seconds of accuracy establishes
751
+ * nothing about which side of it the token was made on. Requirement m asks that the token WAS
752
+ * created before the certificate expired, so a bound that cannot establish it refuses. */
753
+ var notAfterAt = _instantOrNull(opts.signerNotAfter);
754
+ var genUpper = genAt === null ? null
755
+ : guard.time.latestInstantOf(res.genTime, res.tstInfo && res.tstInfo.genTimeFraction, res.accuracy || null);
756
+ if (notAfterAt !== null && genUpper !== null && genUpper > notAfterAt) {
757
+ return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime,
758
+ code: "cms/timestamp-after-signer-expiry",
759
+ /** @internal The message names the BOUND, not just the genTime: this verdict is reached
760
+ * whenever the latest instant the token could have been created at is past the notAfter, and
761
+ * with a wide accuracy or a sub-millisecond fraction that is true of tokens whose stated
762
+ * genTime is inside the window. Reporting the genTime alone would give an operator a reason
763
+ * they can check and find false. */
764
+ message: "the token's genTime " + guard.time.isoOf(res.genTime) + ", with the accuracy it " +
765
+ "states, puts the latest it could have been created at " +
766
+ guard.time.isoOf(new intrinsic.Date(genUpper)) + ", past the signing certificate's notAfter " +
767
+ guard.time.isoOf(opts.signerNotAfter) + ", so it does not establish that the token was " +
768
+ "created before that certificate expired (ETSI EN 319 122-1 clause 6.3 requirement m)" });
769
+ }
770
+ return guard.verdict.of({
771
+ valid: true, trusted: res.trusted === true, genTime: res.genTime,
772
+ accuracy: res.accuracy, serialNumber: res.serialNumber, policy: res.policy,
773
+ revocationChecked: res.revocationChecked, tsa: res.tsa, signer: res.signer,
774
+ });
775
+ }
776
+
777
+ /** @internal The signing certificate's `notAfter`, where the signer row matched one. The expiry half
778
+ * of EN 319 122-1 clause 6.3 requirement m is decidable from this certificate alone; the revocation
779
+ * half needs status this verb does not fetch and is not claimed. */
780
+ function _signerNotAfter(verdict) {
781
+ if (!verdict || !verdict.cert) return null;
782
+ try { return x509.parse(verdict.cert).validity.notAfter; }
783
+ // allow:swallow-unverified a signer certificate that does not parse already failed its own verdict
784
+ catch (_e) { return null; }
785
+ }
786
+
420
787
  async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests) {
421
788
  var targetSig = _toBuf(si.signature, "the countersigned signature");
422
789
  var values = [];
@@ -455,7 +822,7 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
455
822
  * Verify a CMS SignedData signature (RFC 5652 sec. 5). `input` is a PEM string, a DER
456
823
  * `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, eContentType, eContent, signers }`
457
824
  * (`eContent` the encapsulated content when present, else null) where each `signers[i]` is
458
- * `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures }`
825
+ * `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures, signatureTimeStamps, cadesBaseline }`
459
826
  * (`cert` the matched signer certificate DER) or carries a `code` on a structural failure; `valid` is true when
460
827
  * there is at least one signer and every signer verified.
461
828
  *
@@ -480,12 +847,43 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
480
847
  * `subjectKeyIdentifier` names a key, and several certificates can hold it with different
481
848
  * validity windows, key usage and policies; deciding from a sibling would let an expired or
482
849
  * 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
850
+ * Supply the certificate you want used. Where several candidates match the identifier and the
851
+ * signature carries an ESS signing-certificate attribute, the candidate that attribute names is the
852
+ * one selected, since RFC 5035 clause 5.4 makes its first identifier "the certificate used to verify
853
+ * 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
854
  * ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814) signatures
485
855
  * are recognized, the post-quantum families in pure mode with an empty context, as is composite
486
856
  * ML-DSA (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
487
857
  * RSA / ECDSA / EdDSA and verifies only when both components pass (never an AND-to-OR downgrade).
488
858
  *
859
+ * `signatureTimeStamps` is one row per `id-aa-signatureTimeStampToken` unsigned attribute (RFC 5126
860
+ * clause 6.1.1, ETSI EN 319 122-1 clause 5.3), each `{ valid, trusted, genTime, accuracy,
861
+ * serialNumber, policy, revocationChecked, tsa, signer }` or `{ valid: false, code, message }`. A
862
+ * token that does not verify is a row verdict and does not take the signer's own `ok` down: the
863
+ * signature and the time it was made at are separate claims. Each row's imprint is checked against
864
+ * the signature octets this verb just verified, and a row is refused when its `genTime` precedes the
865
+ * signer's own `signing-time` attribute or falls after the signing certificate's `notAfter`
866
+ * (EN 319 122-1 clause 6.3 requirement m).
867
+ *
868
+ * What a `valid: true` row establishes is that the token was issued over the signature octets THIS
869
+ * SignerInfo carries. It does not say that signature is sound, which is `signers[i].ok`, and rows are
870
+ * reported beside a failed signature rather than withheld, so a caller acting on a timestamp reads
871
+ * both. The imprint names signature octets rather than a signer, which is what EN 319 122-1
872
+ * clause 5.3 specifies: two certificates holding one key produce one signature value over one
873
+ * content, and a token over it answers for either. `pki.cms.attachTimestamp` adds such an attribute to an
874
+ * existing message and `pki.cms.timestampImprint` produces the imprint a TSA signs.
875
+ *
876
+ * `cadesBaseline` is a third claim, `{ conformant, signingCertificateAttribute, findings,
877
+ * requirementsChecked }`: a signature can be sound and still not be a CAdES signature.
878
+ * `requirementsChecked` lists the EN 319 122-1 requirements decided here, each unmet one carrying a
879
+ * `{ code, message }` finding, so `conformant` is read against that list and not as a full
880
+ * clause 6.3 audit. The signing-certificate requirement is the attribute's CLAIM checked against the
881
+ * certificate this SignerInfo selected, the one reported as `signers[i].cert`, not the attribute's
882
+ * presence. Like the timestamp rows it is reported beside a failed signature rather than withheld,
883
+ * so `conformant: true` on a signer whose `ok` is false says the CAdES attributes are in order and
884
+ * nothing about the signature. `pki.cms.sign` reaches `conformant: true` with a
885
+ * `signingCertificateV2` attribute built by `pki.schema.smime.buildSigningCertificateV2`.
886
+ *
489
887
  * @opts content The detached content when the SignedData carries no encapsulated eContent, and
490
888
  * required for a detached signature. A `Buffer`, or an async iterable of byte chunks
491
889
  * to verify a large payload without holding it in memory. The streamed form is hashed
@@ -503,6 +901,13 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
503
901
  * question from `requiredEku`, since a root distributed with NSS trust bits can be
504
902
  * marked untrusted for one purpose and good for another. Those bits and
505
903
  * `distrustAfter` are consulted only when this names a purpose.
904
+ * @opts timestampCerts Extra TSA certificates for the signature-timestamp rows, in addition to the
905
+ * ones each token embeds.
906
+ * @opts timestampTrustAnchors The roots a time-stamping authority must chain to for a timestamp row
907
+ * to report `trusted`. Named apart from `trustAnchors` because a TSA is a different
908
+ * trust decision from the signer: an operator may anchor one and not the other.
909
+ * @opts timestampRevocationChecker A revocation checker for the TSA chain, as `pki.path.validate`
910
+ * takes one. Each row reports what it established in `revocationChecked`.
506
911
  * @example
507
912
  * var pair = await pki.key.generate("Ed25519");
508
913
  * var key = await pki.key.export(pair.privateKey);
@@ -540,7 +945,12 @@ async function _resolveContentDigests(parsed, streamSource) {
540
945
  return byName;
541
946
  }
542
947
 
543
- var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, allowWeakDigests: 1 });
948
+ var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, allowWeakDigests: 1,
949
+ /** @internal The timestamp inputs are named apart from the signer's own, because a time-stamping
950
+ * authority is a different trust decision from the signer's: an operator may anchor the TSA while
951
+ * leaving the signer unanchored, or the reverse, and one `trustAnchors` for both would make those
952
+ * two answers one. */
953
+ timestampCerts: 1, timestampTrustAnchors: 1, timestampRevocationChecker: 1 });
544
954
 
545
955
  function verify(input, opts) {
546
956
  return guard.async.deferred(function () { return _verify(input, opts); });
@@ -555,6 +965,7 @@ async function _verify(input, opts) {
555
965
  if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
556
966
  var trustCfg = _snapshotTrust(opts);
557
967
  var allowWeakDigests = opts.allowWeakDigests === true;
968
+ var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests);
558
969
  var parsedCerts = [];
559
970
  (parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
560
971
  (opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
@@ -582,11 +993,18 @@ async function _verify(input, opts) {
582
993
  var siOne = parsed.signerInfos[_sp];
583
994
  var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName, allowWeakDigests);
584
995
  var counters = await _verifyCountersignatures(siOne, parsedCerts, allowWeakDigests);
996
+ /** @internal The timestamp rows are built from the same `signature` octets the signature verdict
997
+ * above was computed over, which is why they are here and not in a verb of their own. The
998
+ * signer's own certificate supplies the expiry half of EN 319 122-1 clause 6.3 requirement m. */
999
+ var stamps = await _verifySignatureTimestamps(siOne, _toBuf(siOne.signature, "the timestamped signature"),
1000
+ intrinsic.assign(intrinsic.create(null), tsTrustCfg, { signerNotAfter: _signerNotAfter(verdict) }));
585
1001
  guard.list.append(signers, guard.verdict.of(verdict, {
586
1002
  countersignatures: counters,
1003
+ signatureTimeStamps: stamps,
587
1004
  unsignedAttrs: _surfaceUnsignedAttrs(siOne),
588
1005
  signedAttributesPresent: !!siOne.signedAttrsBytes,
589
1006
  signedAttributes: _surfaceSignedAttrs(siOne),
1007
+ cadesBaseline: _cadesBaseline(siOne, verdict && verdict.cert ? verdict.cert : null),
590
1008
  digestAlgorithm: siOne.digestAlgorithm.name,
591
1009
  trusted: false,
592
1010
  }));
@@ -640,6 +1058,30 @@ function _snapshotTrust(opts) {
640
1058
  };
641
1059
  }
642
1060
 
1061
+ /** @internal The time-stamping authority's trust inputs, taken at the call like the signer's own and
1062
+ * for the same reason: they are first read inside the per-signer loop, after the signature and
1063
+ * countersignature checks have awaited, so an options object that changes between rows, or one
1064
+ * whose properties are accessors, would have two rows of one verdict judged against two different
1065
+ * authorities. The certificates are copied by value, since a caller holding the array could swap an
1066
+ * entry after the row that read it. The checker is a function and is captured as the one reference
1067
+ * every row calls. */
1068
+ function _snapshotTimestampTrust(opts, allowWeakDigests) {
1069
+ var certs = opts.timestampCerts;
1070
+ var anchors = opts.timestampTrustAnchors;
1071
+ return {
1072
+ timestampCerts: certs == null ? certs
1073
+ : guard.list.copyMap(intrinsic.isArray(certs) ? certs : [certs], function (c) {
1074
+ return _snapshotIfBytes(c, "opts.timestampCerts[]");
1075
+ }),
1076
+ timestampTrustAnchors: anchors == null ? anchors
1077
+ : guard.list.copyMap(intrinsic.isArray(anchors) ? anchors : [anchors], function (a) {
1078
+ return typeof a === "string" ? a : _cloneAnchorValue(a, 0);
1079
+ }),
1080
+ timestampRevocationChecker: opts.timestampRevocationChecker,
1081
+ allowWeakDigests: allowWeakDigests,
1082
+ };
1083
+ }
1084
+
643
1085
  /** @internal A signature over content is authorized by digitalSignature, contentCommitment or
644
1086
  * nonRepudiation alike; the shared helper carries the rest of the rule. */
645
1087
  var _permitsSigning = pkix.keyUsagePermits(pkix.makeNS("cms", CmsError, oid),
@@ -817,6 +1259,12 @@ var sign = cmsSign.sign;
817
1259
  */
818
1260
  var countersign = cmsSign.countersign;
819
1261
 
1262
+ /** @internal The two CAdES signature-timestamp verbs, re-exported here with `sign` and `countersign`
1263
+ * because this module is the `pki.cms` namespace's assembly point. Their documentation blocks live
1264
+ * with their implementations in `cms-sign.js`. */
1265
+ var timestampImprint = cmsSign.timestampImprint;
1266
+ var attachTimestamp = cmsSign.attachTimestamp;
1267
+
820
1268
  /**
821
1269
  * @primitive pki.cms.encrypt
822
1270
  * @signature pki.cms.encrypt(content, recipients, opts?) -> Promise<Buffer | string>
@@ -1126,4 +1574,4 @@ function isCertsOnly(input) {
1126
1574
  }
1127
1575
 
1128
1576
  // @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 };
1577
+ 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 };
package/lib/constants.js CHANGED
@@ -293,6 +293,21 @@ var NAMES = Object.assign(Object.create(null), {
293
293
  * this length: an OCSP CertID's issuer hashes, a timestamp's messageImprint. The XOFs (SHAKE) have
294
294
  * no single length and are absent. */
295
295
  DIGEST_OCTETS: { sha1: 20, sha256: 32, sha384: 48, sha512: 64, "sha3-256": 32, "sha3-512": 64 },
296
+ /** @internal The digests a verify path refuses by default and admits only under an explicit
297
+ * opt-in, by the OID registry's name, with the WebCrypto hash name each maps to. A digest is weak
298
+ * once a collision is reachable, which breaks the binding of every structure that carries "the
299
+ * hash of X": a signature's message digest, a timestamp's messageImprint, an ESS certificate
300
+ * identifier. Membership is defined once here, so a path that refuses one refuses all of them and
301
+ * a path that admits them under an opt-in admits the same set. A digest no path will compute at
302
+ * all is not listed; it is absent from DIGEST_HASH instead. */
303
+ WEAK_DIGESTS: { sha1: "SHA-1" },
304
+ /** @internal The digests an RFC 3161 message imprint may name in this build, by the OID registry's
305
+ * name, with the node name each computes under. One table for the whole timestamp round trip: a
306
+ * verb that TAKES an imprint, a verb that attaches a token over one, and the verifier that reads
307
+ * it all answer from this, so none of them can produce an imprint another would refuse. SHA-1 is
308
+ * listed because archived tokens carry it and the verifier admits it under an explicit opt-in;
309
+ * WEAK_DIGESTS is what decides that, not absence from here. */
310
+ TIMESTAMP_IMPRINT_DIGESTS: { sha1: "sha1", sha256: "sha256", sha384: "sha384", sha512: "sha512" },
296
311
  });
297
312
 
298
313
  /** @internal Each inner table is looked up by a name a certificate supplies, so it answers from