@blamejs/blamejs-shop 0.0.127 → 0.0.129

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 (33) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +4 -0
  3. package/lib/storefront.js +235 -6
  4. package/lib/vendor/MANIFEST.json +3 -3
  5. package/lib/vendor/blamejs/CHANGELOG.md +14 -0
  6. package/lib/vendor/blamejs/README.md +7 -1
  7. package/lib/vendor/blamejs/SECURITY.md +3 -0
  8. package/lib/vendor/blamejs/api-snapshot.json +408 -2
  9. package/lib/vendor/blamejs/index.js +6 -0
  10. package/lib/vendor/blamejs/lib/cose.js +212 -4
  11. package/lib/vendor/blamejs/lib/cwt.js +244 -0
  12. package/lib/vendor/blamejs/lib/eat.js +240 -0
  13. package/lib/vendor/blamejs/lib/mdoc.js +305 -0
  14. package/lib/vendor/blamejs/lib/scitt.js +243 -0
  15. package/lib/vendor/blamejs/lib/tsa.js +688 -0
  16. package/lib/vendor/blamejs/lib/vc.js +328 -0
  17. package/lib/vendor/blamejs/package.json +1 -1
  18. package/lib/vendor/blamejs/release-notes/v0.12.34.json +18 -0
  19. package/lib/vendor/blamejs/release-notes/v0.12.35.json +22 -0
  20. package/lib/vendor/blamejs/release-notes/v0.12.36.json +18 -0
  21. package/lib/vendor/blamejs/release-notes/v0.12.37.json +27 -0
  22. package/lib/vendor/blamejs/release-notes/v0.12.38.json +18 -0
  23. package/lib/vendor/blamejs/release-notes/v0.12.39.json +18 -0
  24. package/lib/vendor/blamejs/release-notes/v0.12.40.json +18 -0
  25. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +27 -0
  26. package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +84 -1
  27. package/lib/vendor/blamejs/test/layer-0-primitives/cwt.test.js +137 -0
  28. package/lib/vendor/blamejs/test/layer-0-primitives/eat.test.js +111 -0
  29. package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +230 -0
  30. package/lib/vendor/blamejs/test/layer-0-primitives/scitt.test.js +158 -0
  31. package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +373 -0
  32. package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +188 -0
  33. package/package.json +1 -1
@@ -0,0 +1,688 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.tsa
4
+ * @nav Crypto
5
+ * @title Timestamping (RFC 3161)
6
+ *
7
+ * @intro
8
+ * An RFC 3161 Time-Stamp Protocol client — the requester / verifier
9
+ * side, not a TSA. A timestamp authority binds a hash of your data to
10
+ * a trusted time, producing a timestamp token that proves the data
11
+ * existed at that instant: timestamp a release artifact, an audit-log
12
+ * checkpoint, a <code>b.scitt</code> signed statement, a contract.
13
+ *
14
+ * <code>b.tsa.buildRequest(data, opts)</code> produces the DER
15
+ * TimeStampReq (a message imprint = the hash of your data, plus an
16
+ * optional nonce and a request for the TSA's certificate);
17
+ * <code>b.tsa.parseResponse(der)</code> reads the TimeStampResp,
18
+ * surfacing the PKIStatus (and any failure-info bits) and the token;
19
+ * <code>b.tsa.verifyToken(token, opts)</code> verifies the token
20
+ * against your data and returns the asserted time. The transport (an
21
+ * HTTP POST of <code>application/timestamp-query</code> to the TSA's
22
+ * URL) is the operator's to make — the framework builds the request
23
+ * and verifies the response.
24
+ *
25
+ * <strong>Verification</strong> (RFC 3161 §2.4.2 / §2.3) is the
26
+ * security-bearing part and is done in full: the token is a CMS
27
+ * SignedData (<code>b.cms</code>) whose eContentType must be
28
+ * <code>id-ct-TSTInfo</code>; the message imprint inside the TSTInfo
29
+ * must equal the hash of your data (constant-time compare); a sent
30
+ * nonce must round-trip; the signer's certificate must carry the
31
+ * <code>id-kp-timeStamping</code> extended key usage, marked critical
32
+ * and as the <em>only</em> EKU; and the CMS signature over the signed
33
+ * attributes must verify (the <code>messageDigest</code> attribute is
34
+ * checked against the recomputed eContent digest first). An optional
35
+ * trust-anchor set verifies the certificate chain and validity at the
36
+ * asserted time.
37
+ *
38
+ * <strong>Algorithms.</strong> Timestamp tokens are third-party
39
+ * artifacts: public TSAs sign with classical RSA (PKCS#1 v1.5 or PSS)
40
+ * or ECDSA over SHA-2, so verification accepts those — the same
41
+ * consume-what-exists stance as <code>b.cose</code> verification. This
42
+ * is not a signing default (the framework is not the TSA); it is
43
+ * verification of externally-produced tokens. The message-imprint
44
+ * hash you request defaults to SHA-512 and may be any of SHA-256 /
45
+ * 384 / 512 or SHA3-256 / 512 the TSA supports.
46
+ *
47
+ * @card
48
+ * RFC 3161 timestamp client — build a TimeStampReq, parse the
49
+ * response, and verify a timestamp token in full (imprint match,
50
+ * nonce, id-kp-timeStamping EKU critical+sole, CMS signature, optional
51
+ * chain). Composes b.cms + the in-tree ASN.1 DER codec.
52
+ */
53
+
54
+ var nodeCrypto = require("node:crypto");
55
+ var bCrypto = require("./crypto");
56
+ var asn1 = require("./asn1-der");
57
+ var cms = require("./cms-codec");
58
+ var validateOpts = require("./validate-opts");
59
+ var { defineClass } = require("./framework-error");
60
+
61
+ var TsaError = defineClass("TsaError", { alwaysPermanent: true });
62
+
63
+ // id-ct-TSTInfo (RFC 3161 §2.4.2) — the eContentType of a timestamp token.
64
+ var OID_TST_INFO = "1.2.840.113549.1.9.16.1.4";
65
+ // id-kp-timeStamping (RFC 3161 §2.3) — the required, critical, sole EKU.
66
+ var OID_KP_TIMESTAMPING = "1.3.6.1.5.5.7.3.8";
67
+ var OID_EXT_EKU = "2.5.29.37"; // certificate extendedKeyUsage extension
68
+ var OID_MESSAGE_DIGEST = "1.2.840.113549.1.9.4"; // RFC 5652 messageDigest signed attribute
69
+ var OID_CONTENT_TYPE = "1.2.840.113549.1.9.3"; // RFC 5652 contentType signed attribute
70
+
71
+ // Message-imprint hash algorithms: name to { oid, nodeHash }.
72
+ var IMPRINT_HASHES = {
73
+ "SHA-256": { oid: "2.16.840.1.101.3.4.2.1", nodeHash: "sha256" },
74
+ "SHA-384": { oid: "2.16.840.1.101.3.4.2.2", nodeHash: "sha384" },
75
+ "SHA-512": { oid: "2.16.840.1.101.3.4.2.3", nodeHash: "sha512" },
76
+ "SHA3-256": { oid: "2.16.840.1.101.3.4.2.8", nodeHash: "sha3-256" },
77
+ "SHA3-512": { oid: "2.16.840.1.101.3.4.2.10", nodeHash: "sha3-512" },
78
+ };
79
+ var OID_TO_IMPRINT_HASH = {};
80
+ Object.keys(IMPRINT_HASHES).forEach(function (n) { OID_TO_IMPRINT_HASH[IMPRINT_HASHES[n].oid] = n; });
81
+
82
+ // Signer signature algorithms a real TSA emits to node verify parameters.
83
+ // scheme "rsa" = PKCS#1 v1.5, "pss" = RSASSA-PSS, "ecdsa" = ECDSA(DER).
84
+ var SIG_ALGS = {
85
+ // Bare rsaEncryption — RFC 8933 / common CMS producers (incl. OpenSSL
86
+ // ts) put this in SignerInfo.signatureAlgorithm and take the hash from
87
+ // the separate digestAlgorithm field.
88
+ "1.2.840.113549.1.1.1": { hash: null, scheme: "rsa" }, // rsaEncryption (hash from digestAlg)
89
+ "1.2.840.113549.1.1.11": { hash: "sha256", scheme: "rsa" }, // sha256WithRSAEncryption
90
+ "1.2.840.113549.1.1.12": { hash: "sha384", scheme: "rsa" }, // sha384WithRSAEncryption
91
+ "1.2.840.113549.1.1.13": { hash: "sha512", scheme: "rsa" }, // sha512WithRSAEncryption
92
+ "1.2.840.113549.1.1.10": { hash: null, scheme: "pss" }, // RSASSA-PSS (hash from signerInfo digestAlg)
93
+ // Bare id-ecPublicKey appears in some producers' SignerInfo too.
94
+ "1.2.840.10045.2.1": { hash: null, scheme: "ecdsa" }, // id-ecPublicKey (hash from digestAlg)
95
+ "1.2.840.10045.4.3.2": { hash: "sha256", scheme: "ecdsa" }, // ecdsa-with-SHA256
96
+ "1.2.840.10045.4.3.3": { hash: "sha384", scheme: "ecdsa" }, // ecdsa-with-SHA384
97
+ "1.2.840.10045.4.3.4": { hash: "sha512", scheme: "ecdsa" }, // ecdsa-with-SHA512
98
+ };
99
+ var DIGEST_OID_TO_NODE = {
100
+ "2.16.840.1.101.3.4.2.1": "sha256",
101
+ "2.16.840.1.101.3.4.2.2": "sha384",
102
+ "2.16.840.1.101.3.4.2.3": "sha512",
103
+ "2.16.840.1.101.3.4.2.8": "sha3-256",
104
+ "2.16.840.1.101.3.4.2.10": "sha3-512",
105
+ };
106
+
107
+ function _bytes(x, what) {
108
+ if (Buffer.isBuffer(x)) return x;
109
+ if (x instanceof Uint8Array) return Buffer.from(x);
110
+ if (typeof x === "string") return Buffer.from(x, "utf8");
111
+ throw new TsaError("tsa/bad-bytes", "tsa: " + what + " must be Buffer / Uint8Array / string");
112
+ }
113
+
114
+ function _normHex(h) {
115
+ return String(h).replace(/[^0-9a-fA-F]/g, "").replace(/^0+(?=.)/, "").toUpperCase();
116
+ }
117
+
118
+ // Resolve the message imprint: hash the data, or use a pre-computed hash.
119
+ function _imprint(data, opts, fnName) {
120
+ var hashName = opts.hashAlg || "SHA-512";
121
+ var h = IMPRINT_HASHES[hashName];
122
+ if (!h) {
123
+ throw new TsaError("tsa/bad-hash-alg",
124
+ fnName + ": hashAlg must be one of " + Object.keys(IMPRINT_HASHES).join(" / "));
125
+ }
126
+ var digest;
127
+ if (opts.hashed) {
128
+ digest = _bytes(data, "hash");
129
+ var expectLen = nodeCrypto.createHash(h.nodeHash).update(Buffer.alloc(0)).digest().length;
130
+ if (digest.length !== expectLen) {
131
+ throw new TsaError("tsa/bad-hash-length",
132
+ fnName + ": pre-hashed input is " + digest.length + " bytes, expected " +
133
+ expectLen + " for " + hashName);
134
+ }
135
+ } else {
136
+ digest = nodeCrypto.createHash(h.nodeHash).update(_bytes(data, "data")).digest();
137
+ }
138
+ return { hashName: hashName, hashOid: h.oid, digest: digest };
139
+ }
140
+
141
+ /**
142
+ * @primitive b.tsa.buildRequest
143
+ * @signature b.tsa.buildRequest(data, opts?)
144
+ * @since 0.12.38
145
+ * @status experimental
146
+ * @compliance soc2
147
+ * @related b.tsa.parseResponse, b.tsa.verifyToken
148
+ *
149
+ * Build a DER-encoded RFC 3161 TimeStampReq for <code>data</code>. POST
150
+ * the returned bytes to the TSA as <code>application/timestamp-query</code>;
151
+ * keep the returned <code>nonce</code> to pass to
152
+ * <code>verifyToken</code>. By default a random 64-bit nonce is included
153
+ * and the TSA is asked to return its certificate.
154
+ *
155
+ * @opts
156
+ * {
157
+ * hashAlg: string, // "SHA-512" (default) | "SHA-256" | "SHA-384" | "SHA3-256" | "SHA3-512"
158
+ * hashed: boolean, // true means `data` is already the digest (must match hashAlg length)
159
+ * reqPolicy: string, // request a specific TSA policy OID (dotted)
160
+ * nonce: Buffer, // explicit nonce bytes, or false to omit (default: random 8 bytes)
161
+ * certReq: boolean, // ask the TSA to include its cert (default true)
162
+ * }
163
+ *
164
+ * @example
165
+ * var req = b.tsa.buildRequest(releaseTarball, { hashAlg: "SHA-512" });
166
+ * // POST req.der to the TSA; keep req.nonce for verifyToken
167
+ * // → { der, nonce, hashAlg, messageImprint }
168
+ */
169
+ function buildRequest(data, opts) {
170
+ opts = opts || {};
171
+ validateOpts.requireObject(opts, "tsa.buildRequest", TsaError);
172
+ validateOpts(opts, ["hashAlg", "hashed", "reqPolicy", "nonce", "certReq"], "tsa.buildRequest");
173
+ var imp = _imprint(data, opts, "tsa.buildRequest");
174
+
175
+ var algId = asn1.writeSequence([asn1.writeOid(imp.hashOid), asn1.writeNull()]);
176
+ var messageImprint = asn1.writeSequence([algId, asn1.writeOctetString(imp.digest)]);
177
+
178
+ var children = [asn1.writeInteger(Buffer.from([1])), messageImprint]; // version 1
179
+ if (typeof opts.reqPolicy === "string") children.push(asn1.writeOid(opts.reqPolicy));
180
+
181
+ var nonce = null;
182
+ if (opts.nonce !== false) {
183
+ nonce = Buffer.isBuffer(opts.nonce) ? opts.nonce : nodeCrypto.randomBytes(8); // allow:raw-byte-literal — RFC 3161 nonce: 64-bit random
184
+ children.push(asn1.writeInteger(nonce));
185
+ }
186
+ // certReq DEFAULT FALSE — only encode when true (the default we want).
187
+ var certReq = opts.certReq !== false;
188
+ if (certReq) children.push(asn1.writeBoolean(true));
189
+
190
+ return {
191
+ der: asn1.writeSequence(children),
192
+ nonce: nonce,
193
+ hashAlg: imp.hashName,
194
+ messageImprint: imp.digest,
195
+ };
196
+ }
197
+
198
+ /**
199
+ * @primitive b.tsa.parseResponse
200
+ * @signature b.tsa.parseResponse(der)
201
+ * @since 0.12.38
202
+ * @status experimental
203
+ * @compliance soc2
204
+ * @related b.tsa.buildRequest, b.tsa.verifyToken
205
+ *
206
+ * Parse a DER RFC 3161 TimeStampResp. Returns the PKIStatus and, when
207
+ * the request was granted, the timestamp token (the DER ContentInfo to
208
+ * pass to <code>verifyToken</code>). A non-granted status surfaces the
209
+ * status integer, any free-text, and the decoded failure-info flags
210
+ * rather than throwing — the caller decides how to react.
211
+ *
212
+ * @example
213
+ * var resp = b.tsa.parseResponse(httpBodyBytes);
214
+ * if (resp.granted) { var out = b.tsa.verifyToken(resp.token, { data: tarball, nonce: req.nonce }); }
215
+ * // → { granted: true, status: 0, token, statusString: null, failInfo: [] }
216
+ */
217
+ function parseResponse(der) {
218
+ var buf = _bytes(der, "der");
219
+ var root;
220
+ try { root = asn1.readNode(buf, 0); } catch (e) {
221
+ throw new TsaError("tsa/malformed", "tsa.parseResponse: not DER: " + ((e && e.message) || e));
222
+ }
223
+ if (root.tag !== asn1.TAG.SEQUENCE || root.tagClass !== asn1.TAG_CLASS.UNIVERSAL) {
224
+ throw new TsaError("tsa/malformed", "tsa.parseResponse: TimeStampResp must be a SEQUENCE");
225
+ }
226
+ var children = asn1.readSequence(root.value);
227
+ if (children.length < 1 || children[0].tag !== asn1.TAG.SEQUENCE) {
228
+ throw new TsaError("tsa/malformed", "tsa.parseResponse: missing PKIStatusInfo");
229
+ }
230
+ var statusInfo = asn1.readSequence(children[0].value);
231
+ if (statusInfo.length < 1 || statusInfo[0].tag !== asn1.TAG.INTEGER) {
232
+ throw new TsaError("tsa/malformed", "tsa.parseResponse: PKIStatusInfo missing status INTEGER");
233
+ }
234
+ var status = asn1.readUnsignedInt(statusInfo[0]);
235
+ if (typeof status !== "number") {
236
+ throw new TsaError("tsa/malformed", "tsa.parseResponse: status integer out of range");
237
+ }
238
+
239
+ var statusString = null;
240
+ var failInfo = [];
241
+ for (var i = 1; i < statusInfo.length; i += 1) {
242
+ var n = statusInfo[i];
243
+ if (n.tag === asn1.TAG.SEQUENCE && statusString === null) {
244
+ var texts = asn1.readSequence(n.value).map(function (t) { return t.value.toString("utf8"); });
245
+ statusString = texts.join("; ");
246
+ } else if (n.tag === asn1.TAG.BIT_STRING) {
247
+ failInfo = _decodeFailInfo(n);
248
+ }
249
+ }
250
+
251
+ // granted(0) / grantedWithMods(1) carry a token; 2..5 are failures.
252
+ var granted = status === 0 || status === 1;
253
+ var token = null;
254
+ if (granted) {
255
+ var tokNode = children[1];
256
+ if (!tokNode) {
257
+ throw new TsaError("tsa/no-token",
258
+ "tsa.parseResponse: status granted but no timeStampToken present");
259
+ }
260
+ token = tokNode.raw;
261
+ }
262
+ return { granted: granted, status: status, statusString: statusString, failInfo: failInfo, token: token };
263
+ }
264
+
265
+ // PKIFailureInfo bit names (RFC 3161 §2.4.2 / RFC 2510).
266
+ var FAIL_INFO_BITS = { // allow:raw-byte-literal — RFC 3161 PKIFailureInfo bit positions
267
+ 0: "badAlg", 2: "badRequest", 5: "badDataFormat", 14: "timeNotAvailable",
268
+ 15: "unacceptedPolicy", 16: "unacceptedExtension", 17: "addInfoNotAvailable", 25: "systemFailure",
269
+ };
270
+ function _decodeFailInfo(bitStringNode) {
271
+ var out = [];
272
+ var v = bitStringNode.value;
273
+ if (v.length <= 1) return out; // first byte = unused-bit count
274
+ var bits = v.slice(1);
275
+ for (var byteIdx = 0; byteIdx < bits.length; byteIdx += 1) {
276
+ for (var b = 0; b < 8; b += 1) { // allow:raw-byte-literal — 8 bits per byte
277
+ if (bits[byteIdx] & (0x80 >> b)) {
278
+ var pos = byteIdx * 8 + b; // allow:raw-byte-literal — 8 bits per byte
279
+ out.push(FAIL_INFO_BITS[pos] || ("bit" + pos));
280
+ }
281
+ }
282
+ }
283
+ return out;
284
+ }
285
+
286
+ // Parse GeneralizedTime "YYYYMMDDHHMMSS[.fff]Z" to Date (UTC).
287
+ function _parseGeneralizedTime(node) {
288
+ var s = node.value.toString("ascii");
289
+ var m = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d+)?Z$/.exec(s);
290
+ if (!m) {
291
+ throw new TsaError("tsa/bad-gentime", "tsa: genTime is not a 'Z'-terminated GeneralizedTime: " + s);
292
+ }
293
+ // Fractional seconds (rare in timestamps) → milliseconds from the
294
+ // first three fractional digits, zero-padded; no float arithmetic.
295
+ var frac = m[7] ? m[7].slice(1) : "";
296
+ var ms = frac ? parseInt((frac + "000").slice(0, 3), 10) : 0;
297
+ var t = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6], ms);
298
+ return new Date(t);
299
+ }
300
+
301
+ // TSTInfo (RFC 3161 §2.4.2) to fields. messageImprint stays raw so
302
+ // verifyToken can compare hash-alg OID + hashed bytes.
303
+ function _parseTstInfo(eContent) {
304
+ var root = asn1.readNode(eContent, 0);
305
+ if (root.tag !== asn1.TAG.SEQUENCE) {
306
+ throw new TsaError("tsa/malformed", "tsa: TSTInfo must be a SEQUENCE");
307
+ }
308
+ var c = asn1.readSequence(root.value);
309
+ if (c.length < 5) throw new TsaError("tsa/malformed", "tsa: TSTInfo too short");
310
+ var idx = 0;
311
+ idx += 1; // version
312
+ var policy = asn1.readOid(c[idx]); idx += 1;
313
+ var miNode = c[idx]; idx += 1;
314
+ var serialNode = c[idx]; idx += 1;
315
+ var genTime = _parseGeneralizedTime(c[idx]); idx += 1;
316
+
317
+ // Parse messageImprint { hashAlgorithm AlgId, hashedMessage OCTET STRING }.
318
+ var mi = asn1.readSequence(miNode.value);
319
+ var miAlg = asn1.readSequence(mi[0].value);
320
+ var miHashOid = asn1.readOid(miAlg[0]);
321
+ var miHash = asn1.readOctetString(mi[1]);
322
+
323
+ var accuracy = null;
324
+ var nonce = null;
325
+ for (; idx < c.length; idx += 1) {
326
+ var n = c[idx];
327
+ if (n.tagClass === asn1.TAG_CLASS.UNIVERSAL && n.tag === asn1.TAG.SEQUENCE && accuracy === null) {
328
+ accuracy = _parseAccuracy(n);
329
+ } else if (n.tagClass === asn1.TAG_CLASS.UNIVERSAL && n.tag === asn1.TAG.INTEGER) {
330
+ nonce = n.value; // raw INTEGER bytes
331
+ }
332
+ // ordering BOOLEAN, [0] tsa, [1] extensions are read but not surfaced in v1.
333
+ }
334
+
335
+ var serialHex = Buffer.isBuffer(serialNode.value) ? serialNode.value.toString("hex") : null;
336
+ return {
337
+ policy: policy,
338
+ genTime: genTime,
339
+ accuracy: accuracy,
340
+ nonce: nonce,
341
+ serialHex: serialHex,
342
+ imprintHashOid: miHashOid,
343
+ imprintHash: miHash,
344
+ };
345
+ }
346
+
347
+ function _parseAccuracy(node) {
348
+ var c = asn1.readSequence(node.value);
349
+ var out = { seconds: 0, millis: 0, micros: 0 };
350
+ for (var i = 0; i < c.length; i += 1) {
351
+ var n = c[i];
352
+ if (n.tagClass === asn1.TAG_CLASS.UNIVERSAL && n.tag === asn1.TAG.INTEGER) {
353
+ out.seconds = asn1.readUnsignedInt(n);
354
+ } else if (n.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && n.tag === 0) {
355
+ out.millis = _ctxInt(n);
356
+ } else if (n.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && n.tag === 1) {
357
+ out.micros = _ctxInt(n);
358
+ }
359
+ }
360
+ return out;
361
+ }
362
+ function _ctxInt(node) {
363
+ // [n] IMPLICIT INTEGER — value bytes are the integer content directly.
364
+ var v = node.value, n = 0;
365
+ for (var i = 0; i < v.length; i += 1) n = (n * 256) + v[i]; // allow:raw-byte-literal — base-256 integer accumulation
366
+ return n;
367
+ }
368
+
369
+ // Walk a certificate's extensions for one OID to { critical, valueBytes } or null.
370
+ function _certExtension(certDer, wantOid) {
371
+ var cert = asn1.readNode(certDer, 0);
372
+ var top = asn1.readSequence(cert.value); // [ tbs, sigAlg, sigValue ]
373
+ var tbs = asn1.readSequence(top[0].value);
374
+ // Find the [3] EXPLICIT extensions wrapper.
375
+ var extsWrapper = asn1.findChild(tbs, function (n) {
376
+ return n.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && n.tag === 3;
377
+ });
378
+ if (!extsWrapper) return null;
379
+ var extsSeq = asn1.unwrapExplicit(extsWrapper, 3);
380
+ var exts = asn1.readSequence(extsSeq.value);
381
+ for (var i = 0; i < exts.length; i += 1) {
382
+ var ec = asn1.readSequence(exts[i].value);
383
+ var oid = asn1.readOid(ec[0]);
384
+ if (oid !== wantOid) continue;
385
+ var critical = false;
386
+ var valueNode = null;
387
+ for (var j = 1; j < ec.length; j += 1) {
388
+ if (ec[j].tag === asn1.TAG.BOOLEAN) critical = ec[j].value[0] !== 0;
389
+ else if (ec[j].tag === asn1.TAG.OCTET_STRING) valueNode = ec[j];
390
+ }
391
+ if (!valueNode) return null;
392
+ return { critical: critical, valueBytes: asn1.readOctetString(valueNode) };
393
+ }
394
+ return null;
395
+ }
396
+
397
+ // RFC 3161 §2.3: the signing cert's EKU MUST contain id-kp-timeStamping,
398
+ // MUST be critical, and MUST be the only key purpose.
399
+ function _checkTimestampingEku(certDer) {
400
+ var ext = _certExtension(certDer, OID_EXT_EKU);
401
+ if (!ext) {
402
+ throw new TsaError("tsa/bad-eku",
403
+ "tsa.verifyToken: signer certificate has no extendedKeyUsage (RFC 3161 §2.3 requires id-kp-timeStamping)");
404
+ }
405
+ if (!ext.critical) {
406
+ throw new TsaError("tsa/bad-eku",
407
+ "tsa.verifyToken: extendedKeyUsage is not marked critical (RFC 3161 §2.3)");
408
+ }
409
+ var purposes = asn1.readSequence(asn1.readNode(ext.valueBytes, 0).value).map(asn1.readOid);
410
+ if (purposes.length !== 1 || purposes[0] !== OID_KP_TIMESTAMPING) {
411
+ throw new TsaError("tsa/bad-eku",
412
+ "tsa.verifyToken: extendedKeyUsage must be exactly { id-kp-timeStamping } (got " +
413
+ purposes.join(", ") + ")");
414
+ }
415
+ }
416
+
417
+ // Pick the signing cert from the token: prefer the one whose serial
418
+ // matches the SignerInfo sid (IssuerAndSerialNumber); else fall through
419
+ // to all certs (the EKU + signature checks remain authoritative).
420
+ function _candidateSigners(sidRaw, certs) {
421
+ var wantSerial = null;
422
+ try {
423
+ var sid = asn1.readNode(sidRaw, 0);
424
+ if (sid.tag === asn1.TAG.SEQUENCE && sid.tagClass === asn1.TAG_CLASS.UNIVERSAL) {
425
+ var parts = asn1.readSequence(sid.value); // IssuerAndSerialNumber { issuer, serial }
426
+ var serialNode = parts[parts.length - 1];
427
+ if (serialNode && serialNode.tag === asn1.TAG.INTEGER) {
428
+ wantSerial = _normHex(serialNode.value.toString("hex"));
429
+ }
430
+ }
431
+ } catch (_e) { wantSerial = null; }
432
+ if (wantSerial === null) return certs.slice();
433
+ var matched = certs.filter(function (d) {
434
+ try { return _normHex(new nodeCrypto.X509Certificate(d).serialNumber) === wantSerial; }
435
+ catch (_e) { return false; }
436
+ });
437
+ return matched.length ? matched : certs.slice();
438
+ }
439
+
440
+ function _verifyCmsSignature(si, eContent, signerCertDer) {
441
+ var alg = SIG_ALGS[si.sigAlgOid];
442
+ if (!alg) {
443
+ throw new TsaError("tsa/bad-sig-alg",
444
+ "tsa.verifyToken: signer signature algorithm " + si.sigAlgOid +
445
+ " is not a supported RSA / ECDSA timestamp algorithm");
446
+ }
447
+ if (!si.signedAttrsRaw) {
448
+ throw new TsaError("tsa/no-signed-attrs",
449
+ "tsa.verifyToken: SignerInfo has no signed attributes (RFC 3161 tokens always sign attributes)");
450
+ }
451
+ var digestNode = DIGEST_OID_TO_NODE[si.digestAlgOid];
452
+ if (!digestNode) {
453
+ throw new TsaError("tsa/bad-digest",
454
+ "tsa.verifyToken: SignerInfo digest algorithm " + si.digestAlgOid + " unsupported");
455
+ }
456
+ // The eContent digest must equal the messageDigest signed attribute,
457
+ // and the contentType attribute must be id-ct-TSTInfo.
458
+ var attrs = _parseSignedAttrs(si.signedAttrsRaw);
459
+ if (!attrs.messageDigest) {
460
+ throw new TsaError("tsa/no-message-digest", "tsa.verifyToken: signed attrs missing messageDigest");
461
+ }
462
+ var actual = nodeCrypto.createHash(digestNode).update(eContent).digest();
463
+ if (!bCrypto.timingSafeEqual(actual, attrs.messageDigest)) {
464
+ throw new TsaError("tsa/message-digest-mismatch",
465
+ "tsa.verifyToken: recomputed eContent digest does not match the messageDigest attribute");
466
+ }
467
+ if (attrs.contentType && attrs.contentType !== OID_TST_INFO) {
468
+ throw new TsaError("tsa/bad-content-type-attr",
469
+ "tsa.verifyToken: signed contentType attribute is " + attrs.contentType + ", expected id-ct-TSTInfo");
470
+ }
471
+
472
+ var pubKey = new nodeCrypto.X509Certificate(signerCertDer).publicKey;
473
+ // For algorithm ids that don't name the hash (bare rsaEncryption /
474
+ // id-ecPublicKey / RSASSA-PSS), the hash comes from the digestAlgorithm.
475
+ var hashName = alg.hash || digestNode;
476
+ var keyParam = pubKey;
477
+ if (alg.scheme === "pss") {
478
+ keyParam = { key: pubKey, padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING,
479
+ saltLength: nodeCrypto.constants.RSA_PSS_SALTLEN_AUTO };
480
+ }
481
+ var ok;
482
+ try { ok = nodeCrypto.verify(hashName, si.signedAttrsRaw, keyParam, si.signature); }
483
+ catch (e) {
484
+ throw new TsaError("tsa/verify-threw",
485
+ "tsa.verifyToken: signature verification threw: " + ((e && e.message) || e));
486
+ }
487
+ if (!ok) {
488
+ throw new TsaError("tsa/bad-signature",
489
+ "tsa.verifyToken: CMS signature over the signed attributes did not verify");
490
+ }
491
+ }
492
+
493
+ // Parse the universal-SET signedAttrs bytes for contentType + messageDigest.
494
+ function _parseSignedAttrs(signedAttrsRaw) {
495
+ var set = asn1.readNode(signedAttrsRaw, 0);
496
+ var attrs = asn1.readSequence(set.value);
497
+ var out = { contentType: null, messageDigest: null };
498
+ for (var i = 0; i < attrs.length; i += 1) {
499
+ var a = asn1.readSequence(attrs[i].value); // Attribute { type OID, values SET }
500
+ var type = asn1.readOid(a[0]);
501
+ var valueSet = asn1.readSequence(a[1].value);
502
+ if (!valueSet.length) continue;
503
+ if (type === OID_MESSAGE_DIGEST) out.messageDigest = asn1.readOctetString(valueSet[0]);
504
+ else if (type === OID_CONTENT_TYPE) out.contentType = asn1.readOid(valueSet[0]);
505
+ }
506
+ return out;
507
+ }
508
+
509
+ // Optional: verify the signer chains to a trust anchor and is valid at `at`.
510
+ function _verifyChain(signerCertDer, tokenCerts, trustAnchorsPem, at) {
511
+ var anchors = trustAnchorsPem.map(function (p) { return new nodeCrypto.X509Certificate(p); });
512
+ var pool = tokenCerts.map(function (d) { return new nodeCrypto.X509Certificate(d); });
513
+ var current = new nodeCrypto.X509Certificate(signerCertDer);
514
+ var seen = 0;
515
+ var atTime = at.getTime();
516
+ while (seen <= pool.length + 1) {
517
+ _assertValidAt(current, atTime);
518
+ // Anchor reached?
519
+ for (var a = 0; a < anchors.length; a += 1) {
520
+ if (_issued(anchors[a], current)) { _assertValidAt(anchors[a], atTime); return; }
521
+ if (current.fingerprint256 === anchors[a].fingerprint256) return;
522
+ }
523
+ // Walk up through the token's intermediates.
524
+ var parent = null;
525
+ for (var p = 0; p < pool.length; p += 1) {
526
+ if (pool[p].fingerprint256 !== current.fingerprint256 && _issued(pool[p], current)) {
527
+ parent = pool[p]; break;
528
+ }
529
+ }
530
+ if (!parent) {
531
+ throw new TsaError("tsa/untrusted-chain",
532
+ "tsa.verifyToken: signer certificate does not chain to any supplied trust anchor");
533
+ }
534
+ current = parent;
535
+ seen += 1;
536
+ }
537
+ throw new TsaError("tsa/chain-loop", "tsa.verifyToken: certificate chain did not terminate");
538
+ }
539
+ function _issued(issuer, subject) {
540
+ try { return subject.checkIssued(issuer) && subject.verify(issuer.publicKey); }
541
+ catch (_e) { return false; }
542
+ }
543
+ function _assertValidAt(cert, atMs) {
544
+ if (atMs < cert.validFromDate.getTime() || atMs > cert.validToDate.getTime()) {
545
+ throw new TsaError("tsa/cert-expired",
546
+ "tsa.verifyToken: certificate '" + cert.subject + "' is not valid at the asserted time");
547
+ }
548
+ }
549
+
550
+ /**
551
+ * @primitive b.tsa.verifyToken
552
+ * @signature b.tsa.verifyToken(token, opts)
553
+ * @since 0.12.38
554
+ * @status experimental
555
+ * @compliance soc2
556
+ * @related b.tsa.buildRequest, b.cms.parseSignedData
557
+ *
558
+ * Verify an RFC 3161 timestamp token against your data and return the
559
+ * asserted time. Performs the full §2.4.2 / §2.3 check: eContentType is
560
+ * <code>id-ct-TSTInfo</code>, the message imprint equals the hash of
561
+ * <code>opts.data</code> (or <code>opts.hash</code>), a sent nonce
562
+ * round-trips, the signer cert's extendedKeyUsage is a critical, sole
563
+ * <code>id-kp-timeStamping</code>, and the CMS signature verifies. Pass
564
+ * <code>opts.trustAnchorsPem</code> to also verify the certificate
565
+ * chain and validity at the asserted time.
566
+ *
567
+ * @opts
568
+ * {
569
+ * data: Buffer, // the timestamped data (hashed with hashAlg)
570
+ * hash: Buffer, // OR a pre-computed digest (with hashAlg)
571
+ * hashAlg: string, // default "SHA-512" — must match the imprint
572
+ * nonce: Buffer, // require the token nonce to match (from buildRequest)
573
+ * trustAnchorsPem: string|string[], // PEM root(s) — enables chain + validity verification
574
+ * at: Date, // validity instant for chain check (default: genTime); must be a valid Date
575
+ * }
576
+ *
577
+ * @example
578
+ * var out = b.tsa.verifyToken(resp.token, { data: tarball, hashAlg: "SHA-512", nonce: req.nonce });
579
+ * // → { genTime, policy, serialHex, accuracy, hashAlg, signerCertPem }
580
+ */
581
+ function verifyToken(token, opts) {
582
+ validateOpts.requireObject(opts, "tsa.verifyToken", TsaError);
583
+ validateOpts(opts, ["data", "hash", "hashAlg", "nonce", "trustAnchorsPem", "at"], "tsa.verifyToken");
584
+ if (opts.data == null && opts.hash == null) {
585
+ throw new TsaError("tsa/no-data", "tsa.verifyToken: pass opts.data or opts.hash to bind the token");
586
+ }
587
+ var imp = _imprint(opts.hash != null ? opts.hash : opts.data,
588
+ { hashAlg: opts.hashAlg, hashed: opts.hash != null }, "tsa.verifyToken");
589
+
590
+ var sd;
591
+ try { sd = cms.parseSignedData(_bytes(token, "token")); }
592
+ catch (e) {
593
+ throw new TsaError("tsa/not-cms", "tsa.verifyToken: token is not CMS SignedData: " + ((e && e.message) || e));
594
+ }
595
+ if (sd.encapContent.eContentType !== OID_TST_INFO) {
596
+ throw new TsaError("tsa/not-tst",
597
+ "tsa.verifyToken: eContentType is " + sd.encapContent.eContentType + ", expected id-ct-TSTInfo");
598
+ }
599
+ if (!sd.encapContent.eContent) {
600
+ throw new TsaError("tsa/detached", "tsa.verifyToken: timestamp token has no embedded TSTInfo (detached not allowed)");
601
+ }
602
+ if (!sd.signerInfos.length) {
603
+ throw new TsaError("tsa/no-signer", "tsa.verifyToken: token has no SignerInfo");
604
+ }
605
+ if (!sd.certificates.length) {
606
+ throw new TsaError("tsa/no-cert",
607
+ "tsa.verifyToken: token carries no certificate — request one with certReq (the default)");
608
+ }
609
+
610
+ var tst = _parseTstInfo(sd.encapContent.eContent);
611
+
612
+ // (3) message imprint must match the data.
613
+ if (OID_TO_IMPRINT_HASH[tst.imprintHashOid] !== imp.hashName) {
614
+ throw new TsaError("tsa/imprint-alg-mismatch",
615
+ "tsa.verifyToken: token imprint hash (" + tst.imprintHashOid + ") differs from " + imp.hashName);
616
+ }
617
+ if (!bCrypto.timingSafeEqual(tst.imprintHash, imp.digest)) {
618
+ throw new TsaError("tsa/imprint-mismatch",
619
+ "tsa.verifyToken: token message imprint does not match the supplied data");
620
+ }
621
+
622
+ // (4) nonce round-trip.
623
+ if (opts.nonce != null) {
624
+ var want = _bytes(opts.nonce, "nonce");
625
+ var got = tst.nonce == null ? Buffer.alloc(0) : Buffer.from(tst.nonce);
626
+ // Compare as unsigned integers (ignore a DER sign-pad byte difference).
627
+ if (_normHex(want.toString("hex")) !== _normHex(got.toString("hex"))) {
628
+ throw new TsaError("tsa/nonce-mismatch", "tsa.verifyToken: token nonce does not match the request nonce");
629
+ }
630
+ }
631
+
632
+ // (5)+(6) signer cert + EKU + CMS signature.
633
+ var si = sd.signerInfos[0];
634
+ var candidates = _candidateSigners(si.sid, sd.certificates);
635
+ var signerCertDer = null;
636
+ var lastErr = null;
637
+ for (var i = 0; i < candidates.length; i += 1) {
638
+ try {
639
+ _checkTimestampingEku(candidates[i]);
640
+ _verifyCmsSignature(si, sd.encapContent.eContent, candidates[i]);
641
+ signerCertDer = candidates[i];
642
+ break;
643
+ } catch (e) { lastErr = e; }
644
+ }
645
+ if (!signerCertDer) {
646
+ throw lastErr || new TsaError("tsa/no-valid-signer",
647
+ "tsa.verifyToken: no certificate in the token both carries the timestamping EKU and verifies the signature");
648
+ }
649
+
650
+ // (7) optional chain + validity. Accept a single PEM string or an
651
+ // array — never silently skip chain verification when the caller
652
+ // supplied an anchor in an unexpected shape (a fail-open).
653
+ if (opts.trustAnchorsPem !== undefined && opts.trustAnchorsPem !== null) {
654
+ var anchors = typeof opts.trustAnchorsPem === "string" ? [opts.trustAnchorsPem] : opts.trustAnchorsPem;
655
+ if (!Array.isArray(anchors) || anchors.length === 0 ||
656
+ !anchors.every(function (a) { return typeof a === "string" && a.length > 0; })) {
657
+ throw new TsaError("tsa/bad-trust-anchors",
658
+ "tsa.verifyToken: trustAnchorsPem must be a non-empty PEM string or array of PEM strings");
659
+ }
660
+ // A supplied opts.at must be a valid Date — an Invalid Date would
661
+ // make every validity-window comparison NaN (silently disabling it).
662
+ var at = tst.genTime;
663
+ if (opts.at !== undefined && opts.at !== null) {
664
+ if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
665
+ throw new TsaError("tsa/bad-at", "tsa.verifyToken: opts.at must be a valid Date");
666
+ }
667
+ at = opts.at;
668
+ }
669
+ _verifyChain(signerCertDer, sd.certificates, anchors, at);
670
+ }
671
+
672
+ return {
673
+ genTime: tst.genTime,
674
+ policy: tst.policy,
675
+ serialHex: tst.serialHex,
676
+ accuracy: tst.accuracy,
677
+ hashAlg: imp.hashName,
678
+ signerCertPem: new nodeCrypto.X509Certificate(signerCertDer).toString(),
679
+ };
680
+ }
681
+
682
+ module.exports = {
683
+ buildRequest: buildRequest,
684
+ parseResponse: parseResponse,
685
+ verifyToken: verifyToken,
686
+ IMPRINT_HASHES: IMPRINT_HASHES,
687
+ TsaError: TsaError,
688
+ };