@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,158 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.scitt (SCITT signed statements) over b.cose.
4
+ * Covers the signed-statement round-trip, the integrity-protected
5
+ * CWT_Claims (iss/sub) binding, refusal of a bare COSE_Sign1 that
6
+ * carries no CWT_Claims, expected-issuer/subject enforcement, the
7
+ * reserved-claim guard, content-type media-type strings, and that the
8
+ * signature checks delegate to b.cose. Exercises the classical (ES256 /
9
+ * EdDSA) algorithms and the ML-DSA-87 PQC-forward path.
10
+ */
11
+
12
+ var b = require("../../index");
13
+ var helpers = require("../helpers");
14
+ var check = helpers.check;
15
+ var nodeCrypto = require("node:crypto");
16
+
17
+ var EC = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
18
+ var ED = nodeCrypto.generateKeyPairSync("ed25519");
19
+ var SBOM = Buffer.from('{"spdxVersion":"SPDX-2.3","name":"widget"}', "utf8");
20
+
21
+ function testSurface() {
22
+ check("b.scitt.signStatement exposed", typeof b.scitt.signStatement === "function");
23
+ check("b.scitt.verifyStatement exposed", typeof b.scitt.verifyStatement === "function");
24
+ check("b.scitt.CWT_CLAIMS_LABEL is RFC 9597 label 15", b.scitt.CWT_CLAIMS_LABEL === 15);
25
+ check("b.scitt.ScittError exposed", typeof b.scitt.ScittError === "function");
26
+ }
27
+
28
+ async function testRoundTrip() {
29
+ var stmt = await b.scitt.signStatement(SBOM, {
30
+ alg: "ES256", privateKey: EC.privateKey,
31
+ issuer: "https://builder.example", subject: "pkg:npm/widget@1.2.3",
32
+ contentType: "application/spdx+json", claims: { 6: 1700000000 },
33
+ });
34
+ check("signed statement is bytes", Buffer.isBuffer(stmt));
35
+
36
+ var out = await b.scitt.verifyStatement(stmt, {
37
+ algorithms: ["ES256"], publicKey: EC.publicKey,
38
+ expectedIssuer: "https://builder.example", expectedSubject: "pkg:npm/widget@1.2.3",
39
+ });
40
+ check("verify: payload round-trips", out.payload.equals(SBOM));
41
+ check("verify: issuer extracted from CWT_Claims", out.issuer === "https://builder.example");
42
+ check("verify: subject extracted from CWT_Claims", out.subject === "pkg:npm/widget@1.2.3");
43
+ check("verify: extra CWT claim (iat) preserved", out.cwtClaims.get(6) === 1700000000);
44
+ check("verify: alg reported", out.alg === "ES256");
45
+ // Content type is a media-type STRING in the protected header (label 3).
46
+ check("verify: content-type media-type string in protected header", out.protectedHeaders.get(3) === "application/spdx+json");
47
+ // CWT_Claims live in the INTEGRITY-PROTECTED header (label 15), not unprotected.
48
+ check("verify: CWT_Claims is in the protected header", out.protectedHeaders.get(15) instanceof Map);
49
+ }
50
+
51
+ async function testIdentityBindingRefusals() {
52
+ var stmt = await b.scitt.signStatement(SBOM, {
53
+ alg: "ES256", privateKey: EC.privateKey, issuer: "iss-a", subject: "sub-a",
54
+ });
55
+
56
+ var e1 = null;
57
+ try {
58
+ await b.scitt.verifyStatement(stmt, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedIssuer: "iss-b" });
59
+ } catch (e) { e1 = e; }
60
+ check("verify: issuer mismatch refused", e1 && e1.code === "scitt/issuer-mismatch");
61
+
62
+ var e2 = null;
63
+ try {
64
+ await b.scitt.verifyStatement(stmt, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedSubject: "sub-b" });
65
+ } catch (e) { e2 = e; }
66
+ check("verify: subject mismatch refused", e2 && e2.code === "scitt/subject-mismatch");
67
+
68
+ // A bare COSE_Sign1 (no CWT_Claims header) is NOT a SCITT statement.
69
+ var bare = await b.cose.sign(SBOM, { alg: "ES256", privateKey: EC.privateKey });
70
+ var e3 = null;
71
+ try { await b.scitt.verifyStatement(bare, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { e3 = e; }
72
+ check("verify: bare COSE_Sign1 (no CWT_Claims) refused", e3 && e3.code === "scitt/missing-cwt-claims");
73
+ }
74
+
75
+ async function testSignRefusals() {
76
+ var refusals = [
77
+ ["missing issuer", { alg: "ES256", privateKey: EC.privateKey, subject: "s" }, "scitt/bad-issuer"],
78
+ ["empty issuer", { alg: "ES256", privateKey: EC.privateKey, issuer: "", subject: "s" }, "scitt/bad-issuer"],
79
+ ["missing subject", { alg: "ES256", privateKey: EC.privateKey, issuer: "i" }, "scitt/bad-subject"],
80
+ ["iss override via claims", { alg: "ES256", privateKey: EC.privateKey, issuer: "i", subject: "s", claims: { 1: "evil" } }, "scitt/reserved-claim"],
81
+ ["sub override via claims", { alg: "ES256", privateKey: EC.privateKey, issuer: "i", subject: "s", claims: { 2: "evil" } }, "scitt/reserved-claim"],
82
+ ["non-integer claim label", { alg: "ES256", privateKey: EC.privateKey, issuer: "i", subject: "s", claims: { 1.5: "x" } }, "scitt/bad-claim-label"],
83
+ ];
84
+ for (var i = 0; i < refusals.length; i++) {
85
+ var err = null;
86
+ try { await b.scitt.signStatement(SBOM, refusals[i][1]); } catch (e) { err = e; }
87
+ check("signStatement refuses: " + refusals[i][0], err && err.code === refusals[i][2]);
88
+ }
89
+ }
90
+
91
+ async function testTamperAndDelegation() {
92
+ var stmt = await b.scitt.signStatement(SBOM, {
93
+ alg: "ES256", privateKey: EC.privateKey, issuer: "i", subject: "s",
94
+ });
95
+
96
+ // Tampering the bytes fails the underlying COSE signature check.
97
+ var bad = Buffer.from(stmt); bad[bad.length - 1] ^= 0xff;
98
+ var t = null;
99
+ try { await b.scitt.verifyStatement(bad, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { t = e; }
100
+ check("verify: tampered statement refused by COSE signature", t && t.code === "cose/bad-signature");
101
+
102
+ // The algorithm allowlist is mandatory and delegated to b.cose.verify.
103
+ var a = null;
104
+ try { await b.scitt.verifyStatement(stmt, { algorithms: ["EdDSA"], publicKey: EC.publicKey }); } catch (e) { a = e; }
105
+ check("verify: alg outside allowlist refused (delegated to cose)", a && a.code === "cose/alg-not-allowed");
106
+ }
107
+
108
+ async function testEdDsaAndPqc() {
109
+ // EdDSA (final COSE id, interoperable today).
110
+ var s1 = await b.scitt.signStatement(SBOM, { alg: "EdDSA", privateKey: ED.privateKey, issuer: "i", subject: "s" });
111
+ var o1 = await b.scitt.verifyStatement(s1, { algorithms: ["EdDSA"], publicKey: ED.publicKey });
112
+ check("EdDSA: round-trips with iss/sub", o1.issuer === "i" && o1.subject === "s");
113
+
114
+ // ML-DSA-87 (PQC-forward) — skip gracefully if the runtime lacks it.
115
+ var mldsa = null;
116
+ try { mldsa = nodeCrypto.generateKeyPairSync("ml-dsa-87"); } catch (_e) { mldsa = null; }
117
+ if (mldsa) {
118
+ var s2 = await b.scitt.signStatement(SBOM, { alg: "ML-DSA-87", privateKey: mldsa.privateKey, issuer: "pqc-iss", subject: "pqc-sub" });
119
+ var o2 = await b.scitt.verifyStatement(s2, { algorithms: ["ML-DSA-87"], publicKey: mldsa.publicKey });
120
+ check("ML-DSA-87: round-trips with iss/sub", o2.issuer === "pqc-iss" && o2.subject === "pqc-sub");
121
+ } else {
122
+ check("ML-DSA-87: runtime lacks ml-dsa-87 — classical path covers the contract", true);
123
+ }
124
+ }
125
+
126
+ async function testExternalAadBinding() {
127
+ var aad = Buffer.from("registration-context", "utf8");
128
+ var stmt = await b.scitt.signStatement(SBOM, {
129
+ alg: "ES256", privateKey: EC.privateKey, issuer: "i", subject: "s", externalAad: aad,
130
+ });
131
+ var ok = await b.scitt.verifyStatement(stmt, { algorithms: ["ES256"], publicKey: EC.publicKey, externalAad: aad });
132
+ check("externalAad: matching context verifies", ok.subject === "s");
133
+
134
+ var mism = null;
135
+ try {
136
+ await b.scitt.verifyStatement(stmt, { algorithms: ["ES256"], publicKey: EC.publicKey, externalAad: Buffer.from("other") });
137
+ } catch (e) { mism = e; }
138
+ check("externalAad: mismatched context refused", mism && mism.code === "cose/bad-signature");
139
+ }
140
+
141
+ async function run() {
142
+ testSurface();
143
+ await testRoundTrip();
144
+ await testIdentityBindingRefusals();
145
+ await testSignRefusals();
146
+ await testTamperAndDelegation();
147
+ await testEdDsaAndPqc();
148
+ await testExternalAadBinding();
149
+ }
150
+
151
+ module.exports = { run: run };
152
+
153
+ if (require.main === module) {
154
+ run().then(
155
+ function () { console.log("[scitt] OK — " + helpers.getChecks() + " checks passed"); },
156
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
157
+ );
158
+ }
@@ -0,0 +1,373 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.tsa (RFC 3161 timestamp client) over b.cms + asn1-der.
4
+ *
5
+ * The verify paths need real-shaped timestamp tokens. Two sources:
6
+ * (1) a pure-node "mock TSA" (cert + TimeStampToken built with the
7
+ * in-tree ASN.1 DER codec, signed with classical RSA / ECDSA via
8
+ * node:crypto) — hermetic, runs everywhere, and gives full control
9
+ * over the cert's extendedKeyUsage so the §2.3 refusal paths
10
+ * (non-critical / multi-purpose EKU) are exercised;
11
+ * (2) an OpenSSL `ts -reply` round-trip when openssl(1) is on PATH —
12
+ * an independent implementation, regression-guarding real-world
13
+ * interop (skipped gracefully where openssl is absent).
14
+ */
15
+
16
+ var b = require("../../index");
17
+ var helpers = require("../helpers");
18
+ var check = helpers.check;
19
+ var asn1 = require("../../lib/asn1-der");
20
+ var nodeCrypto = require("node:crypto");
21
+
22
+ var ID_CT_TST_INFO = "1.2.840.113549.1.9.16.1.4";
23
+ var ID_KP_TIMESTAMPING = "1.3.6.1.5.5.7.3.8";
24
+ var ID_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
25
+ var OID_CONTENT_TYPE_ATTR = "1.2.840.113549.1.9.3";
26
+ var OID_MESSAGE_DIGEST_ATTR = "1.2.840.113549.1.9.4";
27
+ var OID_SIGNED_DATA = "1.2.840.113549.1.7.2";
28
+ var OID_SHA256 = "2.16.840.1.101.3.4.2.1";
29
+ var OID_SHA512 = "2.16.840.1.101.3.4.2.3";
30
+ var OID_RSA_ENCRYPTION = "1.2.840.113549.1.1.1";
31
+ var OID_ECDSA_SHA256 = "1.2.840.10045.4.3.2";
32
+
33
+ function _utcTime(date) {
34
+ var s = date.toISOString().replace(/[-:T]/g, "").slice(2, 14) + "Z"; // YYMMDDHHMMSSZ
35
+ return asn1.writeNode(0x17, Buffer.from(s, "ascii"));
36
+ }
37
+ function _genTime(date) {
38
+ var s = date.toISOString().replace(/[-:T]/g, "").slice(0, 14) + "Z"; // YYYYMMDDHHMMSSZ
39
+ return asn1.writeNode(0x18, Buffer.from(s, "ascii"));
40
+ }
41
+ function _algId(oid, withNull) {
42
+ return withNull ? asn1.writeSequence([asn1.writeOid(oid), asn1.writeNull()])
43
+ : asn1.writeSequence([asn1.writeOid(oid)]);
44
+ }
45
+ function _name(cn) {
46
+ return asn1.writeSequence([asn1.writeSet([
47
+ asn1.writeSequence([asn1.writeOid("2.5.4.3"), asn1.writeUtf8String(cn)]),
48
+ ])]);
49
+ }
50
+
51
+ // Mint a minimal self-signed X.509v3 cert with a chosen extendedKeyUsage.
52
+ function _makeCert(opts) {
53
+ opts = opts || {};
54
+ var keyType = opts.keyType || "rsa";
55
+ var kp = keyType === "ec"
56
+ ? nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" })
57
+ : nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
58
+ var spki = kp.publicKey.export({ type: "spki", format: "der" });
59
+
60
+ var version = asn1.writeContextExplicit(0, asn1.writeInteger(Buffer.from([2]))); // v3
61
+ var serial = asn1.writeInteger(Buffer.from([0x2a]));
62
+ var sigOid = keyType === "ec" ? OID_ECDSA_SHA256 : "1.2.840.113549.1.1.11";
63
+ var sigAlgId = _algId(sigOid, keyType !== "ec");
64
+ var name = _name(opts.cn || "Test TSA");
65
+ var now = Date.now();
66
+ var validity = asn1.writeSequence([
67
+ _utcTime(new Date(now - 86400000)),
68
+ _utcTime(new Date(now + 86400000 * 3650)),
69
+ ]);
70
+
71
+ var extsList = [];
72
+ if (opts.ekuOids !== null) {
73
+ var ekuOids = opts.ekuOids || [ID_KP_TIMESTAMPING];
74
+ var ekuChildren = [asn1.writeOid("2.5.29.37")];
75
+ if (opts.ekuCritical !== false) ekuChildren.push(asn1.writeBoolean(true));
76
+ ekuChildren.push(asn1.writeOctetString(asn1.writeSequence(ekuOids.map(asn1.writeOid))));
77
+ extsList.push(asn1.writeSequence(ekuChildren));
78
+ }
79
+ var children = [version, serial, sigAlgId, name, validity, name, spki];
80
+ if (extsList.length) children.push(asn1.writeContextExplicit(3, asn1.writeSequence(extsList)));
81
+ var tbs = asn1.writeSequence(children);
82
+
83
+ var tbsSig = nodeCrypto.sign("sha256", tbs, kp.privateKey);
84
+ var certDer = asn1.writeSequence([tbs, sigAlgId, asn1.writeBitString(tbsSig, 0)]);
85
+ return { certDer: certDer, key: kp.privateKey, serial: Buffer.from([0x2a]), issuer: name };
86
+ }
87
+
88
+ // Build an RFC 3161 TimeStampToken (CMS SignedData over a TSTInfo).
89
+ function _makeToken(opts) {
90
+ var imprintOid = opts.imprintHashOid || OID_SHA512;
91
+ var imprintHash = opts.imprintHash;
92
+ var tstChildren = [
93
+ asn1.writeInteger(Buffer.from([1])), // version
94
+ asn1.writeOid(opts.policy || "1.2.3.4.1"), // policy
95
+ asn1.writeSequence([_algId(imprintOid, true), asn1.writeOctetString(imprintHash)]),
96
+ asn1.writeInteger(Buffer.from([0x05])), // serialNumber
97
+ _genTime(opts.genTime || new Date()),
98
+ ];
99
+ if (opts.nonce) tstChildren.push(asn1.writeInteger(opts.nonce));
100
+ var tstInfo = asn1.writeSequence(tstChildren);
101
+
102
+ // signed attributes (contentType + messageDigest), signed as a SET.
103
+ var digestNode = opts.signerDigest || "sha512";
104
+ var digestOid = digestNode === "sha256" ? OID_SHA256 : OID_SHA512;
105
+ var msgDigest = nodeCrypto.createHash(digestNode).update(tstInfo).digest();
106
+ var ctAttr = asn1.writeSequence([asn1.writeOid(OID_CONTENT_TYPE_ATTR),
107
+ asn1.writeSet([asn1.writeOid(ID_CT_TST_INFO)])]);
108
+ var mdAttr = asn1.writeSequence([asn1.writeOid(OID_MESSAGE_DIGEST_ATTR),
109
+ asn1.writeSet([asn1.writeOctetString(msgDigest)])]);
110
+ var signedSet = asn1.writeSet([ctAttr, mdAttr]); // 0x31… — the signed bytes
111
+ var sigAlgOid = opts.sigAlgOid || OID_RSA_ENCRYPTION;
112
+ // The signature hash is the one the signatureAlgorithm OID implies;
113
+ // hashless OIDs (rsaEncryption / id-ecPublicKey / PSS) take the
114
+ // digestAlgorithm hash — exactly what the verifier derives.
115
+ var SIGN_HASH = {
116
+ "1.2.840.10045.4.3.2": "sha256", "1.2.840.10045.4.3.3": "sha384", "1.2.840.10045.4.3.4": "sha512",
117
+ "1.2.840.113549.1.1.11": "sha256", "1.2.840.113549.1.1.12": "sha384", "1.2.840.113549.1.1.13": "sha512",
118
+ };
119
+ var signHash = SIGN_HASH[sigAlgOid] || digestNode;
120
+ var sig = nodeCrypto.sign(signHash, signedSet, opts.key);
121
+ var signedAttrsImplicit = Buffer.concat([Buffer.from([0xa0]), signedSet.slice(1)]); // [0] IMPLICIT
122
+
123
+ var sid = asn1.writeSequence([opts.issuer, asn1.writeInteger(opts.serial)]);
124
+ var signerInfo = asn1.writeSequence([
125
+ asn1.writeInteger(Buffer.from([1])), // version
126
+ sid,
127
+ _algId(digestOid, true),
128
+ signedAttrsImplicit,
129
+ _algId(sigAlgOid, sigAlgOid.indexOf("10045") === -1), // RSA: AlgId+NULL; EC: no NULL
130
+ asn1.writeOctetString(sig),
131
+ ]);
132
+
133
+ var encap = asn1.writeSequence([asn1.writeOid(ID_CT_TST_INFO),
134
+ asn1.writeContextExplicit(0, asn1.writeOctetString(tstInfo))]);
135
+ var certsField = asn1.writeContextImplicit(0, opts.certDer, { constructed: true });
136
+ var signedData = asn1.writeSequence([
137
+ asn1.writeInteger(Buffer.from([3])), // version
138
+ asn1.writeSet([_algId(digestOid, true)]),
139
+ encap,
140
+ certsField,
141
+ asn1.writeSet([signerInfo]),
142
+ ]);
143
+ return asn1.writeSequence([asn1.writeOid(OID_SIGNED_DATA), asn1.writeContextExplicit(0, signedData)]);
144
+ }
145
+
146
+ function _imprintOf(data, hashAlg) {
147
+ var node = hashAlg === "SHA-256" ? "sha256" : "sha512";
148
+ return nodeCrypto.createHash(node).update(data).digest();
149
+ }
150
+
151
+ function testSurface() {
152
+ check("b.tsa.buildRequest is a function", typeof b.tsa.buildRequest === "function");
153
+ check("b.tsa.parseResponse is a function", typeof b.tsa.parseResponse === "function");
154
+ check("b.tsa.verifyToken is a function", typeof b.tsa.verifyToken === "function");
155
+ check("b.tsa.IMPRINT_HASHES includes SHA-512", !!b.tsa.IMPRINT_HASHES["SHA-512"]);
156
+ check("b.tsa.TsaError is a class", typeof b.tsa.TsaError === "function");
157
+ }
158
+
159
+ function testBuildRequest() {
160
+ var req = b.tsa.buildRequest(Buffer.from("hello"), { hashAlg: "SHA-512" });
161
+ check("buildRequest returns der + nonce + imprint", Buffer.isBuffer(req.der) && Buffer.isBuffer(req.nonce) && Buffer.isBuffer(req.messageImprint));
162
+ check("buildRequest imprint is the SHA-512 of the data", req.messageImprint.equals(_imprintOf(Buffer.from("hello"), "SHA-512")));
163
+ // Decode the request and confirm structure.
164
+ var c = asn1.readSequence(asn1.readNode(req.der, 0).value);
165
+ check("request version is 1", asn1.readUnsignedInt(c[0]) === 1);
166
+ var mi = asn1.readSequence(c[1].value);
167
+ check("request imprint hash OID is SHA-512", asn1.readOid(asn1.readSequence(mi[0].value)[0]) === OID_SHA512);
168
+
169
+ // nonce:false omits it; certReq:false omits the boolean.
170
+ var noNonce = b.tsa.buildRequest(Buffer.from("x"), { nonce: false });
171
+ check("buildRequest nonce:false → null nonce", noNonce.nonce === null);
172
+
173
+ // pre-hashed input must match the algorithm length.
174
+ var ok = b.tsa.buildRequest(_imprintOf(Buffer.from("y"), "SHA-512"), { hashAlg: "SHA-512", hashed: true });
175
+ check("buildRequest hashed:true accepts a correct-length digest", ok.messageImprint.equals(_imprintOf(Buffer.from("y"), "SHA-512")));
176
+ var threw = null;
177
+ try { b.tsa.buildRequest(Buffer.from("short"), { hashAlg: "SHA-512", hashed: true }); } catch (e) { threw = e; }
178
+ check("buildRequest rejects wrong-length pre-hash", threw && threw.code === "tsa/bad-hash-length");
179
+ var badAlg = null;
180
+ try { b.tsa.buildRequest(Buffer.from("z"), { hashAlg: "MD5" }); } catch (e) { badAlg = e; }
181
+ check("buildRequest rejects unknown hashAlg", badAlg && badAlg.code === "tsa/bad-hash-alg");
182
+ }
183
+
184
+ function testVerifyHappyPath(keyType, sigAlgOid, label) {
185
+ var data = Buffer.from("artifact-bytes-" + label);
186
+ var nonce = nodeCrypto.randomBytes(8);
187
+ var cert = _makeCert({ keyType: keyType });
188
+ // Independent cross-check: node parses our minted cert and reports the EKU.
189
+ var x = new nodeCrypto.X509Certificate(cert.certDer);
190
+ check(label + ": minted cert carries timeStamping EKU (node-parsed)", (x.keyUsage || []).indexOf(ID_KP_TIMESTAMPING) !== -1);
191
+
192
+ var token = _makeToken({
193
+ certDer: cert.certDer, key: cert.key, issuer: cert.issuer, serial: cert.serial,
194
+ imprintHash: _imprintOf(data, "SHA-512"), nonce: nonce,
195
+ sigAlgOid: sigAlgOid, signerDigest: "sha512",
196
+ });
197
+ var out = b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", nonce: nonce });
198
+ check(label + ": verifyToken returns genTime Date", out.genTime instanceof Date);
199
+ check(label + ": verifyToken returns policy", out.policy === "1.2.3.4.1");
200
+ check(label + ": verifyToken reports hashAlg", out.hashAlg === "SHA-512");
201
+
202
+ // wrong data → imprint mismatch
203
+ var e1 = null;
204
+ try { b.tsa.verifyToken(token, { data: Buffer.from("other"), hashAlg: "SHA-512" }); } catch (e) { e1 = e; }
205
+ check(label + ": wrong data refused (imprint-mismatch)", e1 && e1.code === "tsa/imprint-mismatch");
206
+
207
+ // wrong nonce → nonce mismatch
208
+ var e2 = null;
209
+ try { b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", nonce: nodeCrypto.randomBytes(8) }); } catch (e) { e2 = e; }
210
+ check(label + ": wrong nonce refused", e2 && e2.code === "tsa/nonce-mismatch");
211
+
212
+ // wrong hashAlg → imprint-alg mismatch
213
+ var e3 = null;
214
+ try { b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-256" }); } catch (e) { e3 = e; }
215
+ check(label + ": wrong hashAlg refused (imprint-alg-mismatch)", e3 && e3.code === "tsa/imprint-alg-mismatch");
216
+
217
+ // tamper a byte in the token → signature fails
218
+ var bad = Buffer.from(token); bad[bad.length - 5] ^= 0xff;
219
+ var e4 = null;
220
+ try { b.tsa.verifyToken(bad, { data: data, hashAlg: "SHA-512" }); } catch (e) { e4 = e; }
221
+ check(label + ": tampered token refused", e4 && (e4.code === "tsa/bad-signature" || e4.code === "tsa/message-digest-mismatch" || e4.code === "tsa/not-cms"));
222
+ }
223
+
224
+ function testEkuRefusals() {
225
+ var data = Buffer.from("eku-test");
226
+ function tokenFor(certOpts) {
227
+ var cert = _makeCert(certOpts);
228
+ return _makeToken({ certDer: cert.certDer, key: cert.key, issuer: cert.issuer, serial: cert.serial,
229
+ imprintHash: _imprintOf(data, "SHA-512") });
230
+ }
231
+ // non-critical EKU
232
+ var e1 = null;
233
+ try { b.tsa.verifyToken(tokenFor({ ekuCritical: false }), { data: data, hashAlg: "SHA-512" }); } catch (e) { e1 = e; }
234
+ check("non-critical EKU refused", e1 && e1.code === "tsa/bad-eku");
235
+ // multi-purpose EKU (timeStamping + serverAuth) — not the sole purpose
236
+ var e2 = null;
237
+ try { b.tsa.verifyToken(tokenFor({ ekuOids: [ID_KP_TIMESTAMPING, ID_KP_SERVER_AUTH] }), { data: data, hashAlg: "SHA-512" }); } catch (e) { e2 = e; }
238
+ check("multi-purpose EKU refused (not sole)", e2 && e2.code === "tsa/bad-eku");
239
+ // wrong single purpose
240
+ var e3 = null;
241
+ try { b.tsa.verifyToken(tokenFor({ ekuOids: [ID_KP_SERVER_AUTH] }), { data: data, hashAlg: "SHA-512" }); } catch (e) { e3 = e; }
242
+ check("wrong EKU purpose refused", e3 && e3.code === "tsa/bad-eku");
243
+ // no EKU extension at all
244
+ var e4 = null;
245
+ try { b.tsa.verifyToken(tokenFor({ ekuOids: null }), { data: data, hashAlg: "SHA-512" }); } catch (e) { e4 = e; }
246
+ check("missing EKU refused", e4 && e4.code === "tsa/bad-eku");
247
+ }
248
+
249
+ function testChainVerify() {
250
+ var data = Buffer.from("chain-test");
251
+ var cert = _makeCert({});
252
+ var token = _makeToken({ certDer: cert.certDer, key: cert.key, issuer: cert.issuer, serial: cert.serial,
253
+ imprintHash: _imprintOf(data, "SHA-512") });
254
+ var anchorPem = new nodeCrypto.X509Certificate(cert.certDer).toString();
255
+ var out = b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", trustAnchorsPem: [anchorPem] });
256
+ check("chain verify accepts the self-signed anchor", out.policy === "1.2.3.4.1");
257
+
258
+ // a different, unrelated anchor → untrusted
259
+ var other = _makeCert({ cn: "Unrelated Root" });
260
+ var otherPem = new nodeCrypto.X509Certificate(other.certDer).toString();
261
+ var e1 = null;
262
+ try { b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", trustAnchorsPem: [otherPem] }); } catch (e) { e1 = e; }
263
+ check("chain verify refuses an unrelated anchor", e1 && e1.code === "tsa/untrusted-chain");
264
+
265
+ // A single PEM *string* anchor must enforce the chain — not silently
266
+ // skip it (the string shape previously bypassed the array-only guard).
267
+ var okStr = b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", trustAnchorsPem: anchorPem });
268
+ check("string trustAnchorsPem enforces + accepts the matching anchor", okStr.policy === "1.2.3.4.1");
269
+ var e2 = null;
270
+ try { b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", trustAnchorsPem: otherPem }); } catch (e) { e2 = e; }
271
+ check("string trustAnchorsPem refuses an unrelated anchor (no fail-open)", e2 && e2.code === "tsa/untrusted-chain");
272
+
273
+ // Empty / malformed anchor shapes are refused, never silently skipped.
274
+ var e3 = null;
275
+ try { b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", trustAnchorsPem: [] }); } catch (e) { e3 = e; }
276
+ check("empty trustAnchorsPem array refused", e3 && e3.code === "tsa/bad-trust-anchors");
277
+
278
+ // An Invalid Date for opts.at must throw, not silently disable the
279
+ // validity-window check (NaN comparisons).
280
+ var e4 = null;
281
+ try { b.tsa.verifyToken(token, { data: data, hashAlg: "SHA-512", trustAnchorsPem: [anchorPem], at: new Date("not-a-date") }); } catch (e) { e4 = e; }
282
+ check("invalid opts.at Date refused", e4 && e4.code === "tsa/bad-at");
283
+ }
284
+
285
+ function testParseResponseAndInputGuards() {
286
+ // garbage token → not CMS
287
+ var e1 = null;
288
+ try { b.tsa.verifyToken(Buffer.from([0x30, 0x01, 0x00]), { data: Buffer.from("x"), hashAlg: "SHA-512" }); } catch (e) { e1 = e; }
289
+ check("verifyToken refuses non-CMS bytes", e1 && (e1.code === "tsa/not-cms" || e1.code === "tsa/malformed"));
290
+ // no data
291
+ var e2 = null;
292
+ try { b.tsa.verifyToken(Buffer.from([0x30, 0x00]), { hashAlg: "SHA-512" }); } catch (e) { e2 = e; }
293
+ check("verifyToken requires data/hash", e2 && e2.code === "tsa/no-data");
294
+ // parseResponse on a hand-built rejection (status 2 + failInfo badRequest)
295
+ var statusInfo = asn1.writeSequence([
296
+ asn1.writeInteger(Buffer.from([0x02])), // rejection
297
+ asn1.writeNode(0x03, Buffer.from([0x05, 0x20])), // BIT STRING failInfo (bit 2 = badRequest)
298
+ ]);
299
+ var resp = asn1.writeSequence([statusInfo]);
300
+ var parsed = b.tsa.parseResponse(resp);
301
+ check("parseResponse decodes a rejection (not granted)", parsed.granted === false && parsed.status === 2);
302
+ check("parseResponse decodes failInfo bits", parsed.failInfo.indexOf("badRequest") !== -1);
303
+ check("parseResponse rejection carries no token", parsed.token === null);
304
+ // garbage response
305
+ var e3 = null;
306
+ try { b.tsa.parseResponse(Buffer.from([0x01, 0x02, 0x03])); } catch (e) { e3 = e; }
307
+ check("parseResponse refuses non-SEQUENCE", e3 && e3.code === "tsa/malformed");
308
+ }
309
+
310
+ // OpenSSL `ts -reply` interop — independent-implementation regression.
311
+ function testOpensslInterop() {
312
+ var cp = require("child_process");
313
+ var fs = require("fs");
314
+ var os = require("os");
315
+ var path = require("path");
316
+ var dir;
317
+ try { dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-tsa-")); } catch (_e) { dir = null; }
318
+ if (!dir) { check("openssl interop skipped (no tmpdir)", true); return; }
319
+ var cnf = path.join(dir, "ossl.cnf");
320
+ fs.writeFileSync(cnf, "[req]\ndistinguished_name=dn\nx509_extensions=v3_tsa\nprompt=no\n[dn]\nCN=Test TSA\n[v3_tsa]\nextendedKeyUsage=critical,timeStamping\nbasicConstraints=CA:false\n");
321
+ var env = Object.assign({}, process.env, { OPENSSL_CONF: cnf });
322
+ function ossl(args) { return cp.spawnSync("openssl", args, { cwd: dir, env: env, stdio: "ignore" }); }
323
+ var gen = ossl(["req", "-x509", "-newkey", "rsa:2048", "-keyout", "tsa.key", "-out", "tsa.crt", "-days", "3650", "-nodes", "-config", "ossl.cnf"]);
324
+ if (!gen || gen.status !== 0) {
325
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
326
+ check("openssl interop skipped (openssl(1) unavailable)", true);
327
+ return;
328
+ }
329
+ fs.writeFileSync(path.join(dir, "tsa.cnf"), "[c1]\nserial=./serial\ncrypto_device=builtin\nsigner_cert=./tsa.crt\nsigner_key=./tsa.key\ncerts=./tsa.crt\nsigner_digest=sha512\ndefault_policy=1.2.3.4.1\ndigests=sha256,sha384,sha512\naccuracy=secs:1\n");
330
+ fs.writeFileSync(path.join(dir, "serial"), "01\n");
331
+ var req = b.tsa.buildRequest(Buffer.from("hello world"), { hashAlg: "SHA-512" });
332
+ fs.writeFileSync(path.join(dir, "q.tsq"), req.der);
333
+ var reply = ossl(["ts", "-reply", "-queryfile", "q.tsq", "-config", "tsa.cnf", "-section", "c1", "-out", "r.tsr"]);
334
+ if (!reply || reply.status !== 0 || !fs.existsSync(path.join(dir, "r.tsr"))) {
335
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
336
+ check("openssl interop skipped (ts -reply unavailable)", true);
337
+ return;
338
+ }
339
+ var tsr = fs.readFileSync(path.join(dir, "r.tsr"));
340
+ var anchor = fs.readFileSync(path.join(dir, "tsa.crt"), "utf8");
341
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
342
+
343
+ var resp = b.tsa.parseResponse(tsr);
344
+ check("openssl interop: response granted", resp.granted === true && resp.status === 0);
345
+ var out = b.tsa.verifyToken(resp.token, { data: Buffer.from("hello world"), hashAlg: "SHA-512", nonce: req.nonce });
346
+ check("openssl interop: real token verifies", out.genTime instanceof Date && out.policy === "1.2.3.4.1");
347
+ var out2 = b.tsa.verifyToken(resp.token, { data: Buffer.from("hello world"), hashAlg: "SHA-512", trustAnchorsPem: [anchor] });
348
+ check("openssl interop: chain verifies to the TSA cert", out2.genTime instanceof Date);
349
+ var e1 = null;
350
+ try { b.tsa.verifyToken(resp.token, { data: Buffer.from("tampered"), hashAlg: "SHA-512" }); } catch (e) { e1 = e; }
351
+ check("openssl interop: wrong data refused", e1 && e1.code === "tsa/imprint-mismatch");
352
+ }
353
+
354
+ async function run() {
355
+ testSurface();
356
+ testBuildRequest();
357
+ testVerifyHappyPath("rsa", OID_RSA_ENCRYPTION, "RSA(rsaEncryption)");
358
+ testVerifyHappyPath("rsa", "1.2.840.113549.1.1.13", "RSA(sha512WithRSA)");
359
+ testVerifyHappyPath("ec", OID_ECDSA_SHA256, "ECDSA");
360
+ testEkuRefusals();
361
+ testChainVerify();
362
+ testParseResponseAndInputGuards();
363
+ testOpensslInterop();
364
+ }
365
+
366
+ module.exports = { run: run };
367
+
368
+ if (require.main === module) {
369
+ run().then(
370
+ function () { console.log("[tsa] OK — " + helpers.getChecks() + " checks passed"); },
371
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
372
+ );
373
+ }