@blamejs/pki 0.6.14 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/lib/acme.js +59 -815
  3. package/lib/asn1-der.js +7 -245
  4. package/lib/attrcert-sign.js +12 -260
  5. package/lib/attrcert-verify.js +1 -7
  6. package/lib/byte-reader.js +0 -8
  7. package/lib/byte-writer.js +0 -11
  8. package/lib/cbor-det.js +0 -54
  9. package/lib/cmc-build.js +15 -357
  10. package/lib/cmc-verify.js +6 -239
  11. package/lib/cmp-build.js +41 -317
  12. package/lib/cmp-session.js +73 -760
  13. package/lib/cmp-verify.js +32 -488
  14. package/lib/cms-compress.js +3 -32
  15. package/lib/cms-decrypt.js +29 -294
  16. package/lib/cms-encrypt.js +14 -234
  17. package/lib/cms-sign.js +29 -324
  18. package/lib/cms-verify.js +9 -500
  19. package/lib/composite-kem.js +2 -102
  20. package/lib/composite-sig.js +1 -77
  21. package/lib/constants.js +0 -249
  22. package/lib/crl-sign.js +9 -237
  23. package/lib/crl-verify.js +1 -10
  24. package/lib/crmf-sign.js +19 -198
  25. package/lib/crmf-verify.js +1 -12
  26. package/lib/csr-sign.js +3 -34
  27. package/lib/csr-verify.js +1 -12
  28. package/lib/ct.js +28 -253
  29. package/lib/edwards-point.js +10 -44
  30. package/lib/est.js +28 -764
  31. package/lib/framework-error.js +4 -182
  32. package/lib/guard-all.js +1 -58
  33. package/lib/guard-async.js +1 -36
  34. package/lib/guard-bytes.js +6 -650
  35. package/lib/guard-compress.js +5 -102
  36. package/lib/guard-crypto.js +1 -40
  37. package/lib/guard-der.js +2 -93
  38. package/lib/guard-encoding.js +2 -60
  39. package/lib/guard-header.js +1 -36
  40. package/lib/guard-identifier.js +7 -531
  41. package/lib/guard-intrinsic.js +1 -150
  42. package/lib/guard-json.js +10 -73
  43. package/lib/guard-limits.js +1 -77
  44. package/lib/guard-list.js +1 -49
  45. package/lib/guard-name.js +3 -209
  46. package/lib/guard-parsed.js +7 -335
  47. package/lib/guard-range.js +1 -71
  48. package/lib/guard-secret.js +1 -102
  49. package/lib/guard-text.js +1 -64
  50. package/lib/guard-time.js +1 -97
  51. package/lib/hpke.js +3 -105
  52. package/lib/http-digest.js +14 -142
  53. package/lib/http-retry-after.js +1 -47
  54. package/lib/http-transport.js +21 -131
  55. package/lib/inspect.js +30 -284
  56. package/lib/ip-utils.js +6 -47
  57. package/lib/jose.js +7 -142
  58. package/lib/key.js +2 -67
  59. package/lib/lint.js +9 -194
  60. package/lib/merkle.js +1 -27
  61. package/lib/mime.js +9 -82
  62. package/lib/ocsp-request-verify.js +1 -14
  63. package/lib/ocsp-verify.js +1 -82
  64. package/lib/ocsp.js +11 -169
  65. package/lib/oid.js +5 -337
  66. package/lib/path-validate.js +103 -1492
  67. package/lib/pbes2.js +2 -89
  68. package/lib/pkcs12-build.js +31 -379
  69. package/lib/pki-build.js +64 -290
  70. package/lib/rc2.js +2 -18
  71. package/lib/rfc3339.js +4 -27
  72. package/lib/scep.js +8 -252
  73. package/lib/schema-all.js +0 -122
  74. package/lib/schema-attrcert.js +4 -146
  75. package/lib/schema-c509.js +153 -802
  76. package/lib/schema-cmc.js +5 -220
  77. package/lib/schema-cmp.js +2 -223
  78. package/lib/schema-cms.js +13 -427
  79. package/lib/schema-crl.js +5 -68
  80. package/lib/schema-crmf.js +6 -167
  81. package/lib/schema-csr.js +1 -54
  82. package/lib/schema-csrattrs.js +2 -87
  83. package/lib/schema-engine.js +0 -152
  84. package/lib/schema-ocsp.js +0 -117
  85. package/lib/schema-pkcs12.js +2 -93
  86. package/lib/schema-pkcs8.js +3 -35
  87. package/lib/schema-pkix.js +24 -655
  88. package/lib/schema-smime.js +0 -54
  89. package/lib/schema-tsp.js +3 -99
  90. package/lib/schema-x509.js +4 -63
  91. package/lib/shbs.js +11 -95
  92. package/lib/sign-scheme.js +2 -97
  93. package/lib/sigstore.js +9 -169
  94. package/lib/sleep.js +2 -8
  95. package/lib/smime.js +15 -408
  96. package/lib/tls-cert-compress.js +0 -62
  97. package/lib/trust.js +19 -185
  98. package/lib/tsp-sign.js +16 -270
  99. package/lib/validator-all.js +1 -30
  100. package/lib/validator-attcert.js +2 -46
  101. package/lib/validator-cose.js +2 -153
  102. package/lib/validator-keydesc.js +1 -37
  103. package/lib/validator-sig.js +1 -46
  104. package/lib/validator-tls.js +1 -88
  105. package/lib/validator-tpm.js +19 -122
  106. package/lib/verify-seam.js +1 -24
  107. package/lib/webauthn-mds.js +3 -400
  108. package/lib/webauthn.js +12 -833
  109. package/lib/webcrypto.js +5 -485
  110. package/lib/x509-sign.js +4 -108
  111. package/package.json +1 -1
  112. package/sbom.cdx.json +6 -6
package/lib/tsp-sign.js CHANGED
@@ -36,58 +36,43 @@ var _NS = pkix.makeNS("tsp", TspError, oid);
36
36
  var b = asn1.build;
37
37
  function _err(code, message, cause) { return new TspError(code, message, cause); }
38
38
 
39
- // Any ASCII digit 1-9 present (a non-zero fractional digit), by character scan -- no regex.
40
39
  function _hasNonZeroDigit(s) {
41
40
  for (var i = 0; i < s.length; i++) { var c = _charCodeAt(s, i); if (c >= 49 && c <= 57) return true; }
42
41
  return false;
43
42
  }
44
43
  function O(name) { return oid.byName(name); }
45
44
 
46
- // The shared authoring-side builder, for the same pre-encoded-Extension validation every other
47
- // request builder uses (a CSR's extensionRequest, a CRMF CertTemplate, a CRL's entry extensions).
48
45
  var _b = pkiBuild.makeBuilder({
49
46
  ErrorClass: TspError, prefix: "tsp", O: O, NS: _NS,
50
47
  NAME_SCHEMA: pkix.name(_NS), SPKI_SCHEMA: pkix.spki(_NS),
51
48
  });
52
49
 
53
- // Digest names whose imprint / certHash this producer supports (the SHA-2 family).
54
50
  var NODE_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512" };
55
- // The message-imprint hash MUST be exactly the digest algorithm's output length (RFC 3161 sec.
56
- // 2.4.1 -- hashedMessage is "the hash of the datum to be time-stamped").
57
51
  var HASH_LEN = { sha256: 32, sha384: 48, sha512: 64 };
58
52
 
59
- // How much a `pki.path.validate` revocationChecked outcome ESTABLISHED, ordered. `false` is "no
60
- // checker ran at all"; "undetermined" is "one ran and could not answer"; "waived" is an
61
- // undetermined one a caller chose to pass; "determined" is an explicit good-or-revoked answer.
62
- // Ranking them lets a caller trying several candidate paths keep the most established outcome any
63
- // of them reached. Order of evaluation then does not decide the answer.
64
53
  var _REVOCATION_RANK = { "false": 0, "undetermined": 1, "waived": 2, "determined": 3 };
65
54
  function _rankRevocation(v) {
66
55
  var r = _REVOCATION_RANK[String(v)];
67
- return r === undefined ? 0 : r; // an unrecognized value establishes nothing
56
+ return r === undefined ? 0 : r;
68
57
  }
69
58
  function _moreEstablished(candidate, current) {
70
59
  return _rankRevocation(candidate) > _rankRevocation(current);
71
60
  }
72
61
 
73
- // A hash AlgorithmIdentifier { OID, NULL }: messageImprint and ESSCertIDv2 hash algorithms
74
- // carry an explicit NULL parameter (the form RFC 3161 / RFC 5035 producers emit).
75
62
  function _hashAlgId(name) {
76
63
  if (!NODE_DIGEST[name]) throw _err("tsp/unsupported-algorithm", "unsupported hash algorithm " + JSON.stringify(name));
77
64
  return b.sequence([b.oid(O(name)), b.nullValue()]);
78
65
  }
79
- // A policy identifier: an OID name or a dotted-decimal string.
80
66
  function _policy(p) {
81
67
  if (typeof p !== "string") throw _err("tsp/bad-input", "the timestamp policy must be an OID name or dotted string");
82
68
  return oid.isDottedDecimal(p) ? b.oid(p) : b.oid(O(p));
83
69
  }
84
- // The signer certificate DER (a DER BufferSource / PEM string) -- for the ESSCertIDv2 hash.
85
70
  function _certDer(c) {
86
71
  if (c == null) throw _err("tsp/bad-input", "the TSA signer requires a certificate (cert)");
87
- if (guard.bytes.isByteSource(c)) { // any BufferSource -> a byte view
72
+ if (guard.bytes.isByteSource(c)) {
88
73
  var buf = guard.bytes.source(c, TspError, "tsp/bad-input", "the TSA certificate");
89
- if (buf[0] === 0x30) return buf; // DER as-is
90
- c = buf.toString("latin1"); // else decode as PEM
74
+ if (buf[0] === 0x30) return buf;
75
+ c = buf.toString("latin1");
91
76
  }
92
77
  if (typeof c !== "string") throw _err("tsp/bad-input", "the TSA certificate must be a DER BufferSource or a PEM string");
93
78
  var der = pkix.pemDecodeLenient(c, "CERTIFICATE");
@@ -95,14 +80,10 @@ function _certDer(c) {
95
80
  return der;
96
81
  }
97
82
 
98
- // The RFC 5035 SigningCertificateV2 signed-attribute value binding the token to the TSA cert:
99
- // SigningCertificateV2 ::= SEQUENCE { certs SEQUENCE OF ESSCertIDv2 }, ESSCertIDv2 ::= SEQUENCE {
100
- // hashAlgorithm DEFAULT sha256, certHash OCTET STRING, issuerSerial OPTIONAL }. The default
101
- // sha256 hashAlgorithm is omitted; certHash is the digest of the certificate.
102
83
  function _signingCertV2(certDer, hashName) {
103
84
  var certHash = nodeCrypto.createHash(NODE_DIGEST[hashName]).update(certDer).digest();
104
85
  var essCertId = hashName === "sha256"
105
- ? b.sequence([b.octetString(certHash)]) // hashAlgorithm DEFAULT sha256 omitted
86
+ ? b.sequence([b.octetString(certHash)])
106
87
  : b.sequence([_hashAlgId(hashName), b.octetString(certHash)]);
107
88
  return b.sequence([b.sequence([essCertId])]);
108
89
  }
@@ -143,26 +124,12 @@ function _signingCertV2(certDer, hashName) {
143
124
  * var token = await pki.tsp.sign(imprint, { cert: signerCertDer, key: signerKeyPkcs8 }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
144
125
  * (await pki.cms.verify(token)).valid; // true
145
126
  */
146
- // Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
147
- // synchronous because they read the caller's mutable imprint, TSA material and options.
148
127
  function sign(messageImprint, tsa, opts) {
149
- // Every caller-owned argument copied at entry and released when the call settles -- see the note
150
- // on the same call in x509-sign. The imprint matters most: it is the digest the token attests to.
151
128
  return guard.bytes.fixedCall(TspError, "tsp/bad-input", [
152
129
  [messageImprint, "the messageImprint"], [tsa, "the TSA"], [opts, "pki.tsp.sign options"],
153
130
  ], _sign);
154
131
  }
155
132
 
156
- // The three caller-owned objects pki.tsp.sign reads. A dropped option here is a token that quietly
157
- // lacks what was asked for -- `odering` for `ordering` emitted a token with the ordering flag unset,
158
- // which is a claim about the timestamp's orderability that the caller believes they made.
159
- // The TSA is gated too, and the reason it needs to be is not the one it looks like. Both fields it
160
- // carries are REQUIRED, so a MISSPELLING of either is already refused for what it leaves missing --
161
- // but that is only half the class. A name belonging on the OPTIONS, written on the TSA instead, is
162
- // read by nothing here: `ordering` placed on the TSA emits a token the same size as one that never
163
- // asked for it, with the flag unset, while the caller believes they set it. Requiring the argument
164
- // to carry only what it reads is what closes that, so all three caller-owned arguments answer the
165
- // same question -- is this a name I read? -- rather than two of them answering it.
166
133
  var _TSA_KEYS = { cert: 1, key: 1 };
167
134
  var _IMPRINT_KEYS = { hashAlgorithm: 1, hashedMessage: 1 };
168
135
  var _SIGN_OPTS = {
@@ -197,16 +164,9 @@ function _sign(messageImprint, tsa, opts) {
197
164
  var certHashAlg = opts.certHashAlgorithm || "sha256";
198
165
  if (!NODE_DIGEST[certHashAlg]) throw _err("tsp/unsupported-algorithm", "unsupported certHashAlgorithm " + JSON.stringify(certHashAlg));
199
166
 
200
- // TSTInfo (RFC 3161 sec. 2.4.2): version(1), policy, messageImprint, serialNumber, genTime,
201
- // then the OPTIONAL accuracy / ordering / nonce in ascending order.
202
167
  var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage"))]);
203
- // genTime defaults to now; a supplied value MUST be a valid Date (never a silently-ignored
204
- // non-Date or an Invalid Date that would encode a garbage GeneralizedTime).
205
168
  if (opts.genTime != null) guard.time.assertValid(opts.genTime, _err, "tsp/bad-input", "genTime");
206
169
  var genTime = guard.time.isDate(opts.genTime) ? opts.genTime : new Date();
207
- // A caller-authored INTEGER is coerced through the shared guard, so a value that is not one is a
208
- // typed tsp/bad-input rather than a raw SyntaxError or RangeError out of BigInt() -- an untyped
209
- // fault escaping a public verb, which a caller cannot catch by code.
210
170
  var serial = guard.range.authoredInteger(opts.serialNumber, _err, "tsp/bad-input", "serialNumber");
211
171
  var fields = [b.integer(1n), _policy(opts.policy), imprint, b.integer(serial), b.generalizedTime(genTime)];
212
172
  if (opts.accuracy) fields.push(_accuracy(opts.accuracy));
@@ -225,14 +185,6 @@ function _sign(messageImprint, tsa, opts) {
225
185
  });
226
186
  }
227
187
 
228
- // Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] INTEGER (1..999) OPTIONAL,
229
- // micros [1] INTEGER (1..999) OPTIONAL } (RFC 3161 sec. 2.4.2). seconds is a non-negative
230
- // integer; millis and micros MUST each be in 1..999 -- enforced before encoding.
231
- // Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] OPTIONAL, micros [1] OPTIONAL }
232
- // (RFC 3161 sec. 2.4.2). All three are OPTIONAL and encoded only when present, so a misspelling is
233
- // missed by nothing: `milis` emitted an EMPTY Accuracy that parses back as zero seconds, millis and
234
- // micros, and the token then understates its own precision -- a claim about the timestamp that the
235
- // caller believed they had made.
236
188
  var _ACCURACY_KEYS = { seconds: 1, millis: 1, micros: 1 };
237
189
 
238
190
  function _accuracy(a) {
@@ -249,25 +201,15 @@ function _accuracy(a) {
249
201
  if (a.micros != null) f.push(b.contextPrimitive(1, _subMilliBytes(a.micros, "micros")));
250
202
  return b.sequence(f);
251
203
  }
252
- // The content octets of an Accuracy millis/micros field: an INTEGER in 1..999 (RFC 3161 sec.
253
- // 2.4.2), without the universal INTEGER tag (the field is IMPLICIT [0]/[1]).
254
204
  function _subMilliBytes(n, label) {
255
205
  var v = Number(n);
256
206
  if (!Number.isInteger(v) || v < 1 || v > 999) throw _err("tsp/bad-input", "Accuracy " + label + " must be an integer in 1..999 (RFC 3161 sec. 2.4.2)");
257
- return b.integer(BigInt(v)).subarray(2); // strip the universal INTEGER tag+length
207
+ return b.integer(BigInt(v)).subarray(2);
258
208
  }
259
209
 
260
- // Coverage residual -- `_hashAlgId`'s unsupported-hash throw is unreachable through the shipped
261
- // path: both callers (the messageImprint hash and the ESSCertIDv2 certHashAlgorithm) validate
262
- // the name against NODE_DIGEST before `_hashAlgId` runs, so the guard is belt-and-suspenders.
263
210
 
264
- // PKIFailureInfo name -> NamedBitList bit index (RFC 3161 sec. 2.4.2; the reverse of the
265
- // schema-tsp FAILURE_BITS decode map). Build maps names -> bits; an unknown name fails closed.
266
211
  var FAILINFO_BIT = { badAlg: 0, badRequest: 2, badDataFormat: 5, timeNotAvailable: 14, unacceptedPolicy: 15, unacceptedExtension: 16, addInfoNotAvailable: 17, systemFailure: 25 };
267
212
 
268
- // Encode a PKIFailureInfo as a minimal DER NamedBitList BIT STRING from a set of names: set each
269
- // named bit (bit 0 = MSB of byte 0), strip trailing zero octets, and place unusedBits exactly
270
- // below the lowest set bit (X.690 sec. 11.2.2).
271
213
  function _failInfoBits(names) {
272
214
  if (!Array.isArray(names)) throw _err("tsp/bad-input", "failInfo must be an array of PKIFailureInfo names");
273
215
  var idxs = names.map(function (n) {
@@ -275,11 +217,9 @@ function _failInfoBits(names) {
275
217
  if (i == null) throw _err("tsp/bad-input", "unknown PKIFailureInfo name " + JSON.stringify(n) + " (RFC 3161 sec. 2.4.2)");
276
218
  return i;
277
219
  });
278
- return b.namedBitString(idxs); // minimal NamedBitList DER (X.690 sec. 11.2.2), empty when no bits set
220
+ return b.namedBitString(idxs);
279
221
  }
280
222
 
281
- // Coerce a timeStampToken (a CMS ContentInfo the token producer emits) to DER for embedding in a
282
- // TimeStampResp -- a DER Buffer as-is, a Uint8Array copied, or a PEM string de-armored.
283
223
  function _tokenDer(t) {
284
224
  if (guard.bytes.isByteSource(t)) { var u = guard.bytes.source(t, TspError, "tsp/bad-input", "the timeStampToken"); if (u[0] === 0x30) return u; }
285
225
  if (typeof t === "string") return schemaTsp.pemDecode(t);
@@ -315,10 +255,6 @@ function _assertImprint(mi) {
315
255
  * var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
316
256
  * var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: sha256Digest }, { nonce: 0x0102030405060708n, certReq: true });
317
257
  */
318
- // The options pki.tsp.request encodes into the TimeStampReq. Every one is OPTIONAL in the
319
- // structure, so a misspelled name is missed by nothing and the request goes out claiming less than
320
- // was asked for. `nonce` is the one that costs most: misspelled, the request carries no nonce at
321
- // all, and the caller matches a reply against a replay defense that was never asked for.
322
258
  var _REQUEST_OPTS = { reqPolicy: 1, nonce: 1, certReq: 1, extensions: 1, pem: 1 };
323
259
 
324
260
  function request(messageImprint, opts) {
@@ -326,12 +262,6 @@ function request(messageImprint, opts) {
326
262
  if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.request options must be an object");
327
263
  guard.identifier.assertKnownKeys(opts, _REQUEST_OPTS, _err, "tsp/bad-input", "pki.tsp.request has an unknown option ");
328
264
  var mi = messageImprint || {};
329
- // The same table pki.tsp.sign holds its imprint to. The request's own options live in the second
330
- // argument, so `nonce` written on the imprint reaches nothing and the request goes out without
331
- // the replay defense the caller believes they asked for.
332
- // The same table pki.tsp.sign holds its imprint to. The request's own options live in the second
333
- // argument, so `nonce` written on the imprint reaches nothing and the request goes out without
334
- // the replay defense the caller believes they asked for.
335
265
  if (mi && typeof mi === "object" && !Buffer.isBuffer(mi)) {
336
266
  guard.identifier.assertKnownKeys(mi, _IMPRINT_KEYS, _err, "tsp/bad-input", function (k) {
337
267
  return "unknown messageImprint field " + JSON.stringify(k) +
@@ -340,8 +270,6 @@ function request(messageImprint, opts) {
340
270
  }
341
271
  _assertImprint(mi);
342
272
  var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage"))]);
343
- // TimeStampReq: version(1), messageImprint, then OPTIONAL reqPolicy / nonce / certReq /
344
- // extensions in schema order. certReq DEFAULT FALSE -> only an explicit TRUE is encoded (DER).
345
273
  var fields = [b.integer(1n), imprint];
346
274
  if (opts.reqPolicy != null) fields.push(_policy(opts.reqPolicy));
347
275
  if (opts.nonce != null) fields.push(b.integer(guard.range.authoredInteger(opts.nonce, _err, "tsp/bad-input", "nonce")));
@@ -349,11 +277,6 @@ function request(messageImprint, opts) {
349
277
  if (opts.certReq === true) fields.push(b.boolean(true));
350
278
  if (opts.extensions != null) {
351
279
  if (!Array.isArray(opts.extensions) || !guard.list.allMatch(opts.extensions, function (e) { return guard.bytes.isByteSource(e); })) throw _err("tsp/bad-input", "extensions must be an array of encoded Extension DER buffers");
352
- // Each pre-encoded Extension is validated as one, and its extnID must not repeat -- the same
353
- // gate every other request builder applies. Spliced in unchecked, a caller relaying a blob it
354
- // did not author put fully chosen bytes inside [0] and this encoder emitted DER its own
355
- // parseRequest refuses: an undecodable value, a duplicate extnID, an explicit critical=FALSE
356
- // the DER DEFAULT rule forbids.
357
280
  var seenExt = {};
358
281
  var encoded = opts.extensions.map(function (e, i) {
359
282
  var der = guard.bytes.source(e, TspError, "tsp/bad-input", "an extension");
@@ -419,9 +342,6 @@ function parseRequest(input) { return schemaTsp.parseRequest(input); }
419
342
  * var resp = pki.tsp.response(token, {}); // granted
420
343
  * var rej = pki.tsp.response(null, { status: 2, failInfo: ["badAlg"] }); // rejection
421
344
  */
422
- // The options pki.tsp.response encodes into the TimeStampResp. `status` carries the verdict and
423
- // defaults to granted(0), so a misspelled name does not lose a detail -- it INVERTS the answer: a
424
- // TSA refusing a request emits a granted response instead, over a token it did mean to withhold.
425
345
  var _RESPONSE_OPTS = { status: 1, statusString: 1, failInfo: 1, pem: 1 };
426
346
 
427
347
  function response(token, opts) {
@@ -436,8 +356,6 @@ function response(token, opts) {
436
356
  var siFields = [b.integer(BigInt(status))];
437
357
  if (opts.statusString != null) {
438
358
  var texts = Array.isArray(opts.statusString) ? opts.statusString : [opts.statusString];
439
- // PKIFreeText is SEQUENCE SIZE (1..MAX) OF UTF8String -- an empty statusString would build an
440
- // empty SEQUENCE the parser (seqOf min:1) rejects, so reject it at the builder instead.
441
359
  if (!texts.length) throw _err("tsp/bad-input", "statusString must carry at least one PKIFreeText element (RFC 4210 SEQUENCE SIZE 1..MAX)");
442
360
  siFields.push(b.sequence(texts.map(function (t) { return b.utf8(String(t)); })));
443
361
  }
@@ -478,61 +396,22 @@ function response(token, opts) {
478
396
  */
479
397
  function parseResponse(input) { return schemaTsp.parse(input); }
480
398
 
481
- // Coverage residual -- the null-guards and decode/parse catches in the verify helpers below are
482
- // belt-and-suspenders on values already guaranteed upstream, so their failure arms are unreachable
483
- // through the shipped path: parseToken has already proven the token is a single-signer SignedData
484
- // carrying a signing-certificate attribute, so `signerInfo`/`signedAttrs`/the signing-cert attr are
485
- // present; cms.verify returns a parsed, matched signer certificate, so `tsaCertDer` is set and
486
- // re-parsing it (or decoding its extensions) cannot fail here; decodeAttribute yields at least one
487
- // ESSCertID (an empty SEQUENCE OF is rejected at decode -> the `!essCert` guard is defensive); and
488
- // path.validate returns a fail-closed verdict for a bad anchor rather than throwing, so its catch
489
- // is defensive. Likewise, in the out-of-path chain step, `parsed.certificates` is always an array
490
- // (empty when absent), the embedded certs the chain walk parses are the X.509 (universal) choices,
491
- // and guard.name.dnEqual's control-byte reject is defensive on a cert x509.parse already accepted --
492
- // so the `|| []`, the non-universal filter arm, and that throw are unreachable through the shipped
493
- // path. In the tsa-hint check, the tsa GeneralName's decode catch is likewise defensive: parseToken
494
- // eager-decodes the token, so a constructed GeneralName that would fail decodeGeneralName has already
495
- // failed there, and a primitive one decodes -- the catch only guards decodeGeneralName's standalone
496
- // use and yields the same safe no-match. A malformed ESS value, a malformed EKU/keyUsage extnValue,
497
- // and a malformed subjectAltName ARE reachable (a signed-but-broken cert/attribute) and ARE covered;
498
- // the reachable verdicts (unsupported hash, EKU shape/wrapper, keyUsage, imprint/nonce/policy/binding
499
- // mismatch, tsa-hint match + mismatch, unknown critical extension, untrusted TSA, embedded-chain
500
- // ordering) are each driven by a RED conformance vector.
501
-
502
- // The imprint / ESSCertID hash algorithms a verifier can recompute: the SHA-2 family, plus SHA-1
503
- // for MATCHING a legacy token's imprint (a verify-side digest, never a signing choice). An unknown
504
- // or weak (e.g. MD5) algorithm cannot be recomputed, so the check fails closed rather than assume.
399
+
505
400
  var _VERIFY_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512", sha1: "sha1" };
506
- // The digest output length each verify hash MUST produce: a messageImprint or precomputed imprint
507
- // of any other length is not a real digest and can never correspond to data (RFC 3161 sec. 2.4.1).
508
401
  var _VERIFY_DIGEST_LEN = { sha1: 20, sha256: 32, sha384: 48, sha512: 64 };
509
- // The ESSCertID(V2) certificate binding is a pure identity pin, so its digest MUST be
510
- // collision-resistant: a SHA-1 certHash would let a chosen-prefix collision substitute the TSA
511
- // certificate. RFC 5816 moved this binding from the SHA-1 ESSCertID to the SHA-2 ESSCertIDv2 for
512
- // exactly this reason -- accept only SHA-2 here (the messageImprint stays algorithm-agile, since
513
- // that hash is the requester's documented RFC 3161 choice, not an identity pin).
514
402
  var _BIND_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512" };
515
403
 
516
- // M10 -- recompute the imprint over `data` under the token's messageImprint hash algorithm (read
517
- // from the verified eContent) and compare to hashedMessage. `data` is raw bytes (hashed) or a
518
- // precomputed { hashAlgorithm, hashedMessage } (algorithm-exact + byte-exact). Returns true or a
519
- // tsp/* verdict code; a config-shape error throws (the caller's contract).
520
404
  function _imprintMatches(mi, data) {
521
405
  var name = mi.hashAlgorithm && mi.hashAlgorithm.name;
522
406
  var nodeAlg = (name && intrinsic.hasOwn(_VERIFY_DIGEST, name)) ? _VERIFY_DIGEST[name] : null;
523
- if (!nodeAlg) return "tsp/unsupported-algorithm"; // cannot recompute -> never assume a match
407
+ if (!nodeAlg) return "tsp/unsupported-algorithm";
524
408
  var wantLen = _VERIFY_DIGEST_LEN[name];
525
- // The token's messageImprint MUST be a full-length digest for its algorithm. A short/empty
526
- // hashedMessage is not a hash and cannot correspond to any data, so it fails closed here -- a
527
- // matching short precomputed imprint must never make it verify (RFC 3161 sec. 2.4.1).
528
409
  if (mi.hashedMessage.length !== wantLen) return "tsp/imprint-mismatch";
529
410
  var actual;
530
411
  if (guard.bytes.isByteSource(data)) {
531
412
  actual = nodeCrypto.createHash(nodeAlg).update(guard.bytes.source(data, TspError, "tsp/bad-input", "data")).digest();
532
413
  } else if (data && typeof data === "object" && guard.bytes.isByteSource(data.hashedMessage)) {
533
- if (data.hashAlgorithm !== name) return "tsp/imprint-mismatch"; // a precomputed imprint under a different algorithm
534
- // a caller-supplied precomputed imprint of the wrong length is a config error (the sign path
535
- // rejects it identically) -- throw rather than silently mismatch (three-tier: caller input).
414
+ if (data.hashAlgorithm !== name) return "tsp/imprint-mismatch";
536
415
  var dhmLen = guard.bytes.source(data.hashedMessage, TspError, "tsp/bad-input", "precomputed imprint hashedMessage").length;
537
416
  if (dhmLen !== wantLen) throw _err("tsp/bad-input", "precomputed imprint hashedMessage length (" + dhmLen + ") does not match the " + name + " digest length (" + wantLen + ")");
538
417
  actual = guard.bytes.source(data.hashedMessage, TspError, "tsp/bad-input", "precomputed imprint hashedMessage");
@@ -542,41 +421,29 @@ function _imprintMatches(mi, data) {
542
421
  return Buffer.compare(actual, Buffer.from(mi.hashedMessage)) === 0 ? true : "tsp/imprint-mismatch";
543
422
  }
544
423
 
545
- // M13 -- the token MUST be bound to the exact signer certificate via the ESS SigningCertificate(V2)
546
- // signed attribute (RFC 3161 sec. 2.4.2 / RFC 5816): recompute hash(tsaCertDer) under the
547
- // ESSCertID(V2) hashAlgorithm (v1 implies SHA-1, v2 defaults SHA-256) and compare to certHash.
548
424
  function _checkCertBinding(signerInfo, tsaCertDer) {
549
425
  var attrs = (signerInfo && signerInfo.signedAttrs) || [];
550
- // Prefer the RFC 5816 SigningCertificateV2 (SHA-2) binding over the legacy RFC 2634
551
- // SigningCertificate (SHA-1). A token may carry both for compatibility, and the SHA-1 attribute
552
- // sorts first in the signed-attribute SET; picking it would fail the SHA-2-only binding pin even
553
- // though a usable v2 binding is present. Fall back to the legacy attribute only when no v2 exists.
554
426
  var sc = null, scV1 = null;
555
427
  for (var i = 0; i < attrs.length; i++) {
556
428
  if (attrs[i].type === O("signingCertificateV2")) { sc = attrs[i]; break; }
557
429
  if (attrs[i].type === O("signingCertificate") && !scV1) scV1 = attrs[i];
558
430
  }
559
431
  if (!sc) sc = scV1;
560
- if (!sc) return "tsp/missing-signing-certificate"; // parseToken guarantees presence; belt-and-suspenders
432
+ if (!sc) return "tsp/missing-signing-certificate";
561
433
  var decoded;
562
434
  try { decoded = smime.decodeAttribute(sc); }
563
435
  catch (_e) { return "tsp/bad-signing-certificate"; }
564
436
  var essCert = decoded.certs && decoded.certs[0];
565
437
  if (!essCert) return "tsp/bad-signing-certificate";
566
438
  var nodeAlg = essCert.hashAlgorithm && _BIND_DIGEST[essCert.hashAlgorithm.name];
567
- if (!nodeAlg) return "tsp/unsupported-algorithm"; // SHA-1 (or an unknown hash) is not a valid identity pin
439
+ if (!nodeAlg) return "tsp/unsupported-algorithm";
568
440
  var actual = nodeCrypto.createHash(nodeAlg).update(tsaCertDer).digest();
569
441
  if (Buffer.compare(actual, Buffer.from(essCert.certHash)) !== 0) return "tsp/cert-binding-mismatch";
570
- // RFC 5035 sec. 5: if the ESSCertID(V2) carries the OPTIONAL issuerSerial, it MUST identify the
571
- // signer certificate -- confirm its serialNumber. The certHash already pins the exact cert
572
- // cryptographically; the serialNumber is that cert's unambiguous identity field.
573
442
  if (essCert.issuerSerial) {
574
443
  var signerCert;
575
444
  try { signerCert = pkiX509.parse(tsaCertDer); }
576
445
  catch (_e) { return "tsp/bad-signing-certificate"; }
577
446
  if (essCert.issuerSerial.serialNumber !== signerCert.serialNumber) return "tsp/cert-binding-mismatch";
578
- // the issuerSerial issuer is a single directoryName (RFC 5035 sec. 5) that MUST canonically
579
- // equal the signer cert's issuer DN -- decode it and compare through the shared name guard.
580
447
  var names = essCert.issuerSerial.issuer.names || [];
581
448
  var gn = null;
582
449
  try { if (names.length === 1) gn = schemaTsp.decodeGeneralName(names[0].bytes); }
@@ -588,33 +455,20 @@ function _checkCertBinding(signerInfo, tsaCertDer) {
588
455
  return true;
589
456
  }
590
457
 
591
- // M11 -- RFC 3161 sec. 2.3: the TSA certificate MUST contain exactly ONE extendedKeyUsage instance
592
- // whose sole KeyPurposeID is id-kp-timeStamping, and that extension MUST be CRITICAL. path.validate's
593
- // requiredEku only checks the purpose is ASSERTED (an absent EKU is unrestricted), so this
594
- // single-purpose + critical gate is layered on top -- the OCSP-delegate precedent for a format gate
595
- // above the path checks. Additionally (RFC 5280 sec. 4.2.1.3) a keyUsage extension, if present, MUST
596
- // permit signing for the cert to sign the token. Returns true or a tsp/* code.
597
458
  function _checkTsaCertUsage(tsaCertDer) {
598
459
  var cert;
599
460
  try { cert = pkiX509.parse(tsaCertDer); }
600
461
  catch (_e) { return "tsp/bad-tsa-certificate"; }
601
462
  var exts = (cert.extensions || []).filter(function (e) { return e.oid === O("extKeyUsage"); });
602
- if (exts.length !== 1) return "tsp/bad-eku"; // absent, or a duplicate extension (RFC 5280 -- one instance)
463
+ if (exts.length !== 1) return "tsp/bad-eku";
603
464
  if (!exts[0].critical) return "tsp/eku-not-critical";
604
465
  var purposes, ekuNode;
605
466
  try { ekuNode = asn1.decode(exts[0].value); }
606
467
  catch (_e) { return "tsp/bad-eku"; }
607
- // the extnValue MUST be a universal SEQUENCE OF KeyPurposeId -- a non-SEQUENCE constructed wrapper
608
- // (e.g. a SET) carrying OID children would otherwise slip through the bare children walk.
609
468
  if (ekuNode.tagClass !== "universal" || ekuNode.tagNumber !== asn1.TAGS.SEQUENCE || !ekuNode.children) return "tsp/bad-eku";
610
469
  try { purposes = ekuNode.children.map(function (c) { return asn1.read.oid(c); }); }
611
470
  catch (_e) { return "tsp/bad-eku"; }
612
471
  if (purposes.length !== 1 || purposes[0] !== O("timeStamping")) return "tsp/eku-not-exclusive";
613
- // A keyUsage that forbids signing cannot mint a token (RFC 5280 sec. 4.2.1.3); an absent keyUsage
614
- // is unrestricted. Require digitalSignature (bit 0) or nonRepudiation/contentCommitment (bit 1) --
615
- // the signing bits, the TSA analogue of the OCSP-responder keyUsage gate.
616
- // Through the shared reader, which applies the NamedBitList rules (X.690 sec. 11.2.2 minimal
617
- // encoding, sec. 4.2.1.3 at least one bit set) this boundary was applying only half of.
618
472
  var ku;
619
473
  try { ku = pkix.keyUsageOf(_NS, cert, _err, "tsp/bad-key-usage", "TSA certificate"); }
620
474
  catch (_e) { return "tsp/bad-key-usage"; }
@@ -622,9 +476,6 @@ function _checkTsaCertUsage(tsaCertDer) {
622
476
  return true;
623
477
  }
624
478
 
625
- // RFC 5816 sec. 2.2.2: if TSTInfo.tsa is present, it is a hint that MUST correspond to one of the
626
- // signing certificate's subject names -- its subject DN (a directoryName, compared canonically) or a
627
- // subjectAltName entry (a byte-exact GeneralName match). `tsaBytes` is the raw tsa GeneralName TLV.
628
479
  function _tsaMatchesCert(tsaBytes, signerCert) {
629
480
  var gn;
630
481
  try { gn = schemaTsp.decodeGeneralName(tsaBytes); }
@@ -635,8 +486,6 @@ function _tsaMatchesCert(tsaBytes, signerCert) {
635
486
  var san;
636
487
  try { san = asn1.decode(sanExt.value); }
637
488
  catch (_e) { return false; }
638
- // subjectAltName MUST be a universal SEQUENCE OF GeneralName -- a malformed wrapper (e.g. a SET)
639
- // carrying a byte-equal child must not contribute an identity (mirrors the EKU wrapper gate).
640
489
  if (san.tagClass === "universal" && san.tagNumber === asn1.TAGS.SEQUENCE && san.children) {
641
490
  for (var i = 0; i < san.children.length; i++) {
642
491
  if (san.children[i].bytes.equals(tsaBytes)) return true;
@@ -646,28 +495,15 @@ function _tsaMatchesCert(tsaBytes, signerCert) {
646
495
  return false;
647
496
  }
648
497
 
649
- // Bound on the candidate certification paths enumerated from a token's certificate pool -- a guard
650
- // against a hostile embed of many same-subject certificates blowing up the search (CWE-834).
651
498
  var MAX_TSA_CHAINS = 32;
652
499
 
653
- // Enumerate candidate certification paths for the TSA leaf from a pool of embedded and caller-supplied
654
- // certificates. path.validate validates a FIXED ordered path (it is not a path builder), so the
655
- // ordering + backtracking lives here: at every step EVERY unused pool certificate whose subject DN
656
- // equals the working issuer DN is a candidate issuer -- CA rollover / cross-certification embed
657
- // several with the same subject, and a same-subject wrong-key certificate must not shadow the real
658
- // issuer -- and each accumulated prefix is itself a candidate (the anchor may have issued the leaf
659
- // directly or any intermediate). A certificate is never chained to itself (RFC 5280 identity is
660
- // issuer + serialNumber, never the serial alone). Each chain is returned anchor-down (highest issuer
661
- // first, the leaf last) as path.validate takes it; longer chains first so a fully-embedded path is
662
- // preferred. Bounded by MAX_TSA_CHAINS. The caller (verify) tries each until one validates.
663
500
  function _buildTsaChains(leaf, pool) {
664
501
  var chains = [];
665
502
  function dfs(current, acc, used) {
666
503
  if (chains.length >= MAX_TSA_CHAINS) return;
667
- chains.push(acc.slice()); // acc is leaf..current; the anchor may validate the path at this length
504
+ chains.push(acc.slice());
668
505
  for (var i = 0; i < pool.length && chains.length < MAX_TSA_CHAINS; i++) {
669
506
  if (used[i]) continue;
670
- // never chain a certificate to itself (issuer + serialNumber identity, not the serial alone).
671
507
  if (pool[i].serialNumberHex === current.serialNumberHex && guard.name.dnEqual(pool[i].issuer.rdns, current.issuer.rdns, _err, "tsp/bad-tsa-certificate", "the TSA certificate issuer")) continue;
672
508
  if (guard.name.dnEqual(pool[i].subject.rdns, current.issuer.rdns, _err, "tsp/bad-tsa-certificate", "the TSA certificate subject")) {
673
509
  used[i] = true; acc.push(pool[i]);
@@ -742,11 +578,8 @@ function _buildTsaChains(leaf, pool) {
742
578
  * res.valid; // boolean; pass opts.trustAnchors to also chain the TSA cert to a root
743
579
  * res.genTime; // Date, read from the verified eContent
744
580
  */
745
- // Every option pki.tsp.verify reads. Adding one here is the only way to make it accepted.
746
581
  var _VERIFY_OPTS = { certs: 1, trustAnchors: 1, nonce: 1, reqPolicy: 1, revocationChecker: 1 };
747
582
 
748
- // Does any extension carry the critical flag? The answer IS the unknown-critical-extension gate, so
749
- // it is decided by comparison instead of by a `some`. See guard-list.
750
583
  function _anyCritical(extensions) {
751
584
  return guard.list.anyMatches(extensions, function (e) { return !!e && !!e.critical; });
752
585
  }
@@ -754,136 +587,60 @@ function _anyCritical(extensions) {
754
587
  async function verify(token, data, opts) {
755
588
  opts = opts || {};
756
589
  if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.verify options must be an object");
757
- // An unrecognized option is refused rather than ignored. The anchor option is `trustAnchors` (an
758
- // anchor tuple, or a non-empty array of them), the same spelling pki.cms.verify / pki.cmp.verify use.
759
- // A caller who mistyped it would otherwise get no anchoring and no error: the TSA certificate
760
- // unchained, `valid: true`, and nothing to notice it by. Naming it at the boundary is cheap.
761
590
  guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "tsp/bad-input",
762
591
  "pki.tsp.verify has an unknown option (the anchor option is `trustAnchors`) ");
763
592
  if (opts.certs != null && (!Array.isArray(opts.certs) || !guard.list.allMatch(opts.certs, function (c) { return guard.bytes.isByteSource(c); }))) {
764
- throw _err("tsp/bad-input", "pki.tsp.verify opts.certs must be an array of DER certificate Buffers"); // a bad element is a caller error, never silently dropped
593
+ throw _err("tsp/bad-input", "pki.tsp.verify opts.certs must be an array of DER certificate Buffers");
765
594
  }
766
- var tokenDer = _tokenDer(token); // DER Buffer / PEM -> DER; a non-token input throws tsp/bad-input
767
- // M7 -- parseToken enforces the SignedData + id-ct-TSTInfo + single-signer + signing-cert-present
768
- // structure and decodes the TSTInfo FROM the raw eContent; a structural defect throws.
595
+ var tokenDer = _tokenDer(token);
769
596
  var parsed = schemaTsp.parseToken(tokenDer);
770
597
  var tst = parsed.tstInfo;
771
- // `trusted: false` on every refusal too, not only on the accepting return. A caller reading
772
- // `res.trusted` must get an answer on both branches -- an undefined on the failure path is the
773
- // same "cannot tell what was checked" the field was added to remove, and `!res.trusted` reading
774
- // true by accident is not the same as its reading true because nothing anchored the TSA.
775
- // What a path established travels with the REFUSAL as well. Hardcoding these to false/null here
776
- // would misreport the one case the field matters most for: a path that ran, established the TSA
777
- // REVOKED, and refused on that basis did check revocation, and saying it did not is the same
778
- // "cannot tell what was checked" the field was added to remove. They stay false/null until a path
779
- // actually produces them, which is the honest answer before one runs.
780
598
  var revocationChecked = false;
781
599
  var anchorConstraints = null;
782
600
  function fail(code, reason) {
783
601
  return { valid: false, trusted: false, revocationChecked: revocationChecked, anchorConstraints: anchorConstraints, code: code, reason: reason || null, genTime: tst.genTime, accuracy: tst.accuracy, serialNumber: tst.serialNumber, serialNumberHex: tst.serialNumberHex, policy: tst.policy, nonce: tst.nonce, tsa: tst.tsa, tstInfo: tst, signer: null };
784
602
  }
785
- // M12 -- the CMS signature over the exact RFC 5652 sec. 5.4 preimage (message-digest bound to the
786
- // authenticated eContent). cms.verify re-parses the same bytes; a failure is a fail-closed verdict.
787
- // Caller-supplied out-of-band certificates (opts.certs) join the signer-certificate candidates, so
788
- // a cert-less token (RFC 3161 -- the TSA omits its certificate when certReq was false) can verify.
789
603
  var cmsRes = await cmsVerify.verify(tokenDer, { certs: opts.certs });
790
604
  var signer = cmsRes.signers[0];
791
605
  if (!cmsRes.valid) return fail("tsp/bad-signature", signer && (signer.code || signer.message));
792
606
  var tsaCertDer = signer && signer.cert;
793
607
  if (!tsaCertDer) return fail("tsp/bad-signature", "the TSA signer certificate was not found");
794
- // M10 -- what was timestamped MUST correspond to the data (imprint recompute + match).
795
608
  var mi = _imprintMatches(tst.messageImprint, data);
796
609
  if (mi !== true) return fail(mi);
797
- // M14 -- if a request nonce is supplied, the token MUST echo it (BigInt-exact).
798
610
  if (opts.nonce != null) {
799
611
  var wantNonce = guard.range.authoredInteger(opts.nonce, _err, "tsp/bad-input", "opts.nonce");
800
612
  if (tst.nonce == null || tst.nonce !== wantNonce) return fail("tsp/nonce-mismatch");
801
613
  }
802
- // M15 -- if the requested policy is supplied, the token's policy MUST equal it.
803
614
  if (opts.reqPolicy != null) {
804
615
  var wantPolicy = oid.isDottedDecimal(opts.reqPolicy) ? opts.reqPolicy : O(opts.reqPolicy);
805
616
  if (tst.policy !== wantPolicy) return fail("tsp/policy-mismatch");
806
617
  }
807
- // A TSTInfo carrying a CRITICAL extension the verifier cannot process is unusable: RFC 3161 sec.
808
- // 2.4.2 defers extension handling to RFC 5280 sec. 4.2, and defines no TSTInfo extension, so any
809
- // critical one is unrecognized (the unknown-critical-extension gate the cert path also applies).
810
- // The scan for a critical extension IS the gate, so it is a loop rather than a `some`: one
811
- // answering false makes the unrecognized critical extension invisible and the token is accepted.
812
618
  if (tst.extensions && _anyCritical(tst.extensions)) {
813
619
  return fail("tsp/unknown-critical-extension");
814
620
  }
815
- // M13 -- the ESSCertID(V2) binding: the verifying cert MUST be the one the signer identified.
816
621
  var bind = _checkCertBinding(parsed.signerInfo, tsaCertDer);
817
622
  if (bind !== true) return fail(bind);
818
- // M11 -- the RFC 3161 sec. 2.3 critical single-timeStamping EKU gate + the keyUsage-permits-signing check.
819
623
  var usage = _checkTsaCertUsage(tsaCertDer);
820
624
  if (usage !== true) return fail(usage);
821
- // RFC 5816 sec. 2.2.2 -- the tsa hint, when present, MUST name the signer certificate.
822
625
  if (tst.tsa) {
823
626
  var sc;
824
627
  try { sc = pkiX509.parse(tsaCertDer); }
825
628
  catch (_e) { return fail("tsp/bad-tsa-certificate"); }
826
629
  if (!_tsaMatchesCert(tst.tsa.bytes, sc)) return fail("tsp/tsa-mismatch");
827
630
  }
828
- // M18 -- full out-of-path TSA-cert validation (issuer-sig, validity at genTime, unknown-critical,
829
- // key-param inheritance, requiredEku, optional revocation), only when trustAnchors is supplied.
830
- // The path is ordered from the token's embedded certificates (leaf + any intermediates), so a TSA
831
- // issued under an intermediate CA -- not just directly under the anchor -- validates.
832
- // `trusted` is the SECOND claim, kept apart from `valid`. `valid` says the token's signature and
833
- // its structural bindings hold; whether the timestamp authority is one this caller accepts is
834
- // answered only by the chain below, and only when an anchor was supplied. Collapsing the two into
835
- // one boolean meant an archived verdict could not be re-read to tell whether the TSA was ever
836
- // trusted -- which is exactly what a timestamp is archived to answer. Without an anchor there is
837
- // nothing to chain to and `trusted` is false: a definite answer rather than a missing one, the
838
- // same shape pki.cms.verify and pki.cmp.verify return.
839
631
  var trusted = false;
840
632
  if (opts.trustAnchors) {
841
633
  var pathRes = null;
842
- // tst.genTime floors to millisecond precision. When genTime carries sub-millisecond digits the true
843
- // instant lies in (floor, floor+1ms], so fail closed on BOTH validity boundaries: require each
844
- // candidate path to validate at the floor AND the ceil. A token minted just after the certificate
845
- // expired is rejected at the ceil (floor <= notAfter but ceil > notAfter); one minted just before
846
- // notBefore is rejected at the floor (ceil >= notBefore but floor < notBefore). Certificate
847
- // validity is seconds-precise (RFC 5280 sec. 4.1.2.5), so this never over-rejects a valid token.
848
634
  var floorT = tst.genTime;
849
635
  var ceilT = (tst.genTimeFraction != null && _hasNonZeroDigit(tst.genTimeFraction.slice(3))) ? new Date(guard.time.instantOf(floorT) + 1) : floorT;
850
636
  try {
851
- // parseToken surfaces each embedded cert as { bytes, tagClass, ... }; take the X.509 certs
852
- // (universal SEQUENCE), skipping any attribute-certificate choices, and parse their raw bytes.
853
- // Caller out-of-band certificates (opts.certs) join the pool so an intermediate absent from the
854
- // token can still complete the path.
855
637
  var pool = (parsed.certificates || []).filter(function (c) { return c.tagClass === "universal"; }).map(function (c) { return pkiX509.parse(c.bytes); });
856
638
  (opts.certs || []).forEach(function (c) { pool.push(pkiX509.parse(c)); });
857
- // path.validate validates a FIXED path, so backtracking over same-subject issuer candidates
858
- // happens here -- accept the TSA certificate if ANY enumerated chain validates to the anchor at
859
- // both window endpoints.
860
- //
861
- // checkPurpose names timeStamping alongside requiredEku, and the pairing is the point. The EKU
862
- // constrains the TSA CERTIFICATE; checkPurpose selects the ANCHOR's own trust metadata, which
863
- // pki.path consults only when a purpose is named -- so asking one without the other checks one
864
- // end of the chain and not the other, and a root explicitly distrusted for timestamping would
865
- // still answer trusted. The purpose is not a caller choice here: this verb validates timestamp
866
- // tokens and nothing else, so there is exactly one purpose its anchors can be judged under.
867
- // EVERY path result goes through this one function, so none can be produced without being
868
- // accumulated. The alternative -- validating in several places and accumulating afterwards --
869
- // is what let a result be overwritten before it was counted, first across candidate chains and
870
- // then across the two endpoints of one candidate. There is now one door and it always counts.
871
- //
872
- // The two fields accumulate INDEPENDENTLY because they are independent facts. Whether
873
- // revocation was established and which anchor constraints were consulted do not imply each
874
- // other: an anchor that rejects the TSA on its own purposes or distrustAfter metadata, with no
875
- // revocationChecker configured, establishes constraints while establishing nothing about
876
- // revocation.
877
639
  async function validateAt(chain, when) {
878
640
  var res = await pathValidate.validate(chain, {
879
641
  time: when, trustAnchors: opts.trustAnchors, requiredEku: ["timeStamping"], checkPurpose: "timeStamping", revocationChecker: opts.revocationChecker,
880
642
  });
881
- // The most any attempt ESTABLISHED, kept across every attempt: backtracking tries several
882
- // chains for one TSA certificate and a fractional genTime validates each at two endpoints,
883
- // so an attempt that fails earlier than another must not erase what that other established.
884
643
  if (_moreEstablished(res.revocationChecked, revocationChecked)) revocationChecked = res.revocationChecked;
885
- // Every attempt validates against the SAME opts.trustAnchor under the same purpose, so any
886
- // non-null value describes that one anchor; the first to report them is as good as the last.
887
644
  if (anchorConstraints == null && res.anchorConstraints != null) anchorConstraints = res.anchorConstraints;
888
645
  return res;
889
646
  }
@@ -893,20 +650,9 @@ async function verify(token, data, opts) {
893
650
  if (pathRes.valid && ceilT !== floorT) pathRes = await validateAt(chains[ci], ceilT);
894
651
  }
895
652
  } catch (e) { return fail("tsp/untrusted-tsa", (e && e.message) || String(e)); }
896
- // On a REFUSAL the question is whether ANYTHING established the TSA's revocation status, so the
897
- // accumulated best answers it: a candidate that reached the checker and was rejected as revoked
898
- // did check revocation, and saying otherwise would misdescribe the reason for the refusal.
899
653
  if (!pathRes || !pathRes.valid) return fail("tsp/untrusted-tsa", "the TSA certificate did not validate to the trust anchor at genTime");
900
- // On ACCEPTANCE the question is narrower and the accumulated best would OVERCLAIM: what matters
901
- // is what was checked on the path actually accepted, not what some rejected candidate managed.
902
- // The loop stops at the first valid path, so `pathRes` is that path.
903
654
  revocationChecked = pathRes.revocationChecked;
904
655
  anchorConstraints = pathRes.anchorConstraints;
905
- // Reduced to a bare `trusted: true`, this answered the same way whether the TSA was established
906
- // un-revoked or revocation was never consulted at all -- and revocation only runs when a
907
- // revocationChecker is supplied, so the second case is the DEFAULT. A caller archiving a
908
- // timestamp verdict could not tell the two apart later, which is the distinction the
909
- // valid/trusted split exists to keep.
910
656
  trusted = true;
911
657
  }
912
658
  return {