@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
@@ -18,9 +18,62 @@ var ML = nodeCrypto.generateKeyPairSync("ml-dsa-87");
18
18
  function testSurface() {
19
19
  check("b.cose.sign exposed", typeof b.cose.sign === "function");
20
20
  check("b.cose.verify exposed", typeof b.cose.verify === "function");
21
+ check("b.cose.encrypt0 exposed", typeof b.cose.encrypt0 === "function");
22
+ check("b.cose.decrypt0 exposed", typeof b.cose.decrypt0 === "function");
21
23
  check("b.cose.ALGORITHMS exposes COSE alg ids", b.cose.ALGORITHMS["ES256"] === -7 && b.cose.ALGORITHMS["ML-DSA-87"] === -50);
24
+ check("b.cose.AEAD_ALGORITHMS exposes AEAD ids", b.cose.AEAD_ALGORITHMS["ChaCha20-Poly1305"] === 24 && b.cose.AEAD_ALGORITHMS["A256GCM"] === 3);
22
25
  check("b.cose.COSE_SIGN1_TAG is 18", b.cose.COSE_SIGN1_TAG === 18);
23
- check("b.cose.CborError-style CoseError exposed", typeof b.cose.CoseError === "function");
26
+ check("b.cose.COSE_ENCRYPT0_TAG is 16", b.cose.COSE_ENCRYPT0_TAG === 16);
27
+ check("b.cose.CoseError exposed", typeof b.cose.CoseError === "function");
28
+ }
29
+
30
+ function testEncrypt0() {
31
+ var key = b.crypto.generateBytes(32);
32
+ var enc = b.cose.encrypt0(Buffer.from("secret-payload"), { alg: "ChaCha20-Poly1305", key: key });
33
+ check("encrypt0: tagged COSE_Encrypt0 (tag 16 → 0xd0)", enc[0] === 0xd0);
34
+ var d = b.cose.decrypt0(enc, { key: key, algorithms: ["ChaCha20-Poly1305"] });
35
+ check("encrypt0: round-trips plaintext + alg", d.plaintext.toString() === "secret-payload" && d.alg === "ChaCha20-Poly1305");
36
+
37
+ var wrongKey = null;
38
+ try { b.cose.decrypt0(enc, { key: b.crypto.generateBytes(32), algorithms: ["ChaCha20-Poly1305"] }); } catch (e) { wrongKey = e; }
39
+ check("decrypt0: wrong key refused", wrongKey && wrongKey.code === "cose/decrypt-failed");
40
+
41
+ var t = Buffer.from(enc); t[t.length - 1] ^= 0xff;
42
+ var tampered = null;
43
+ try { b.cose.decrypt0(t, { key: key, algorithms: ["ChaCha20-Poly1305"] }); } catch (e) { tampered = e; }
44
+ check("decrypt0: tampered ciphertext refused", tampered && tampered.code === "cose/decrypt-failed");
45
+
46
+ var notAllowed = null;
47
+ try { b.cose.decrypt0(enc, { key: key, algorithms: ["A256GCM"] }); } catch (e) { notAllowed = e; }
48
+ check("decrypt0: alg not in allowlist refused", notAllowed && notAllowed.code === "cose/alg-not-allowed");
49
+
50
+ // A256GCM opt-in round-trip.
51
+ var encG = b.cose.encrypt0(Buffer.from("g"), { alg: "A256GCM", key: key });
52
+ check("encrypt0: A256GCM round-trip", b.cose.decrypt0(encG, { key: key, algorithms: ["A256GCM"] }).plaintext.toString() === "g");
53
+
54
+ // external_aad must match.
55
+ var encA = b.cose.encrypt0(Buffer.from("z"), { key: key, externalAad: Buffer.from("ctx-A") });
56
+ var aadMismatch = null;
57
+ try { b.cose.decrypt0(encA, { key: key, algorithms: ["ChaCha20-Poly1305"], externalAad: Buffer.from("ctx-B") }); } catch (e) { aadMismatch = e; }
58
+ check("decrypt0: external_aad mismatch refused", aadMismatch && aadMismatch.code === "cose/decrypt-failed");
59
+
60
+ // Key-length + algorithms-required validation.
61
+ var badKey = null;
62
+ try { b.cose.encrypt0(Buffer.from("x"), { alg: "A128GCM", key: key }); } catch (e) { badKey = e; } // 32-byte key for A128GCM (needs 16)
63
+ check("encrypt0: wrong key length refused", badKey && badKey.code === "cose/bad-key");
64
+ var noAlgs = null;
65
+ try { b.cose.decrypt0(enc, { key: key }); } catch (e) { noAlgs = e; }
66
+ check("decrypt0: missing algorithms refused", noAlgs && noAlgs.code === "cose/algorithms-required");
67
+
68
+ // Codex P2 on PR #187 — an unprotectedHeaders override of label 5
69
+ // (IV) would emit a token whose stored IV disagrees with the AEAD
70
+ // IV (undecryptable); it must be refused.
71
+ var ivOverride = null;
72
+ try { b.cose.encrypt0(Buffer.from("x"), { key: key, unprotectedHeaders: { 5: Buffer.alloc(12) } }); } catch (e) { ivOverride = e; }
73
+ check("encrypt0: unprotectedHeaders IV override (label 5) refused", ivOverride && ivOverride.code === "cose/reserved-header");
74
+ // A non-IV unprotected header is still allowed + surfaced.
75
+ var withHdr = b.cose.encrypt0(Buffer.from("x"), { key: key, unprotectedHeaders: { 4: Buffer.from("kid-1") } });
76
+ check("encrypt0: non-IV unprotected header preserved", b.cose.decrypt0(withHdr, { key: key, algorithms: ["ChaCha20-Poly1305"] }).unprotectedHeaders.get(4).toString() === "kid-1");
24
77
  }
25
78
 
26
79
  async function testClassicalUseableToday() {
@@ -35,6 +88,34 @@ async function testClassicalUseableToday() {
35
88
  check("EdDSA: round-trips (string payload → bstr)", (await b.cose.verify(sed, { algorithms: ["EdDSA"], publicKey: ED.publicKey })).payload.toString() === "msg");
36
89
  }
37
90
 
91
+ async function testProtectedHeaders() {
92
+ // Extra integrity-protected headers (numeric labels) ride in the
93
+ // protected map and are covered by the signature — a CWT_Claims map
94
+ // (label 15) is the SCITT case.
95
+ var cwtClaims = new Map([[1, "iss.example"], [2, "subject-id"]]);
96
+ var s = await b.cose.sign(Buffer.from("artifact"), {
97
+ alg: "ES256", privateKey: EC.privateKey,
98
+ protectedHeaders: { 15: cwtClaims }, contentType: "application/spdx+json",
99
+ });
100
+ var v = await b.cose.verify(s, { algorithms: ["ES256"], publicKey: EC.publicKey });
101
+ check("protectedHeaders: label 15 (CWT_Claims) in protected map", v.protectedHeaders.get(15) instanceof Map && v.protectedHeaders.get(15).get(1) === "iss.example");
102
+ check("protectedHeaders: string content-type (label 3) preserved", v.protectedHeaders.get(3) === "application/spdx+json");
103
+
104
+ // alg (label 1) is reserved — protectedHeaders cannot override it.
105
+ var reserved = null;
106
+ try {
107
+ await b.cose.sign(Buffer.from("x"), { alg: "ES256", privateKey: EC.privateKey, protectedHeaders: { 1: -7 } });
108
+ } catch (e) { reserved = e; }
109
+ check("protectedHeaders: setting label 1 (alg) refused", reserved && reserved.code === "cose/reserved-header");
110
+
111
+ // A protected-header object accepts a Map too (integer keys preserved).
112
+ var sMap = await b.cose.sign(Buffer.from("y"), {
113
+ alg: "ES256", privateKey: EC.privateKey, protectedHeaders: new Map([[15, cwtClaims]]),
114
+ });
115
+ var vMap = await b.cose.verify(sMap, { algorithms: ["ES256"], publicKey: EC.publicKey });
116
+ check("protectedHeaders: Map input round-trips", vMap.protectedHeaders.get(15).get(2) === "subject-id");
117
+ }
118
+
38
119
  async function testPqcForward() {
39
120
  var s = await b.cose.sign(Buffer.from("pqc"), { alg: "ML-DSA-87", privateKey: ML.privateKey });
40
121
  var v = await b.cose.verify(s, { algorithms: ["ML-DSA-87"], publicKey: ML.publicKey });
@@ -113,7 +194,9 @@ async function testValidation() {
113
194
 
114
195
  async function run() {
115
196
  testSurface();
197
+ testEncrypt0();
116
198
  await testClassicalUseableToday();
199
+ await testProtectedHeaders();
117
200
  await testPqcForward();
118
201
  await testTamperAndAllowlist();
119
202
  await testCritBypassDefense();
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.cwt (RFC 8392 CBOR Web Token) = COSE_Sign1 over a CBOR
4
+ * claims map. Composes b.cose (signature + alg allowlist) + b.cbor.
5
+ * Covers round-trip + standard-claim mapping + exp/nbf/iss/aud
6
+ * enforcement + tag-61 + tamper (delegated to cose).
7
+ */
8
+
9
+ var b = require("../../index");
10
+ var helpers = require("../helpers");
11
+ var check = helpers.check;
12
+ var nodeCrypto = require("node:crypto");
13
+
14
+ var EC = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
15
+ function _now() { return Math.floor(Date.now() / 1000); }
16
+
17
+ function testSurface() {
18
+ check("b.cwt.sign exposed", typeof b.cwt.sign === "function");
19
+ check("b.cwt.verify exposed", typeof b.cwt.verify === "function");
20
+ check("b.cwt.CLAIM_LABELS maps standard claims", b.cwt.CLAIM_LABELS.iss === 1 && b.cwt.CLAIM_LABELS.exp === 4 && b.cwt.CLAIM_LABELS.cti === 7);
21
+ check("b.cwt.CwtError exposed", typeof b.cwt.CwtError === "function");
22
+ }
23
+
24
+ async function testRoundTrip() {
25
+ var now = _now();
26
+ var cwt = await b.cwt.sign(
27
+ { iss: "issuer.example", sub: "dev-42", aud: "svc", exp: now + 3600, nbf: now - 10, iat: now, scope: "telemetry" },
28
+ { alg: "ES256", privateKey: EC.privateKey, kid: "k1" });
29
+ check("sign: produces a COSE_Sign1 (tag 18 → 0xd2)", cwt[0] === 0xd2);
30
+ var v = await b.cwt.verify(cwt, { algorithms: ["ES256"], publicKey: EC.publicKey });
31
+ check("verify: standard claims mapped back to names", v.claims.iss === "issuer.example" && v.claims.sub === "dev-42" && v.claims.exp === now + 3600);
32
+ check("verify: custom claim preserved", v.claims.scope === "telemetry");
33
+ check("verify: raw Map uses integer labels", v.raw.get(1) === "issuer.example" && v.raw.get(4) === now + 3600);
34
+ check("verify: alg surfaced", v.alg === "ES256");
35
+ }
36
+
37
+ async function testTimeClaims() {
38
+ var now = _now();
39
+ var expired = await b.cwt.sign({ exp: now - 3600 }, { alg: "ES256", privateKey: EC.privateKey });
40
+ var e1 = null;
41
+ try { await b.cwt.verify(expired, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { e1 = e; }
42
+ check("verify: expired token refused", e1 && e1.code === "cwt/expired");
43
+
44
+ var future = await b.cwt.sign({ nbf: now + 3600 }, { alg: "ES256", privateKey: EC.privateKey });
45
+ var e2 = null;
46
+ try { await b.cwt.verify(future, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { e2 = e; }
47
+ check("verify: not-yet-valid (nbf) token refused", e2 && e2.code === "cwt/not-yet-valid");
48
+
49
+ // Clock skew tolerance: exp 30s in the past passes with 60s skew.
50
+ var justExpired = await b.cwt.sign({ exp: now - 30 }, { alg: "ES256", privateKey: EC.privateKey });
51
+ var skewOk = await b.cwt.verify(justExpired, { algorithms: ["ES256"], publicKey: EC.publicKey, clockSkewSec: 60 });
52
+ check("verify: clock skew tolerates marginally-expired exp", skewOk.claims.exp === now - 30);
53
+
54
+ // now override (testing): a token valid in the future verifies if now is advanced.
55
+ var fut = await b.cwt.sign({ nbf: now + 100, exp: now + 200 }, { alg: "ES256", privateKey: EC.privateKey });
56
+ var adv = await b.cwt.verify(fut, { algorithms: ["ES256"], publicKey: EC.publicKey, now: (now + 150) * 1000 });
57
+ check("verify: now override advances the clock", adv.claims.nbf === now + 100);
58
+ }
59
+
60
+ async function testIssuerAudience() {
61
+ var cwt = await b.cwt.sign({ iss: "iss-A", aud: ["svc-1", "svc-2"] }, { alg: "ES256", privateKey: EC.privateKey });
62
+ var ok = await b.cwt.verify(cwt, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedIssuer: "iss-A", expectedAudience: "svc-2" });
63
+ check("verify: matching iss + aud (array) accepted", ok.claims.iss === "iss-A");
64
+ var e1 = null;
65
+ try { await b.cwt.verify(cwt, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedIssuer: "iss-B" }); } catch (e) { e1 = e; }
66
+ check("verify: issuer mismatch refused", e1 && e1.code === "cwt/issuer-mismatch");
67
+ var e2 = null;
68
+ try { await b.cwt.verify(cwt, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedAudience: "svc-3" }); } catch (e) { e2 = e; }
69
+ check("verify: audience not in aud array refused", e2 && e2.code === "cwt/audience-mismatch");
70
+ }
71
+
72
+ async function testTaggedAndTamper() {
73
+ var tagged = await b.cwt.sign({ iss: "x" }, { alg: "ES256", privateKey: EC.privateKey, tagged: true });
74
+ check("sign: tagged wraps in CWT tag 61 (0xd8 0x3d)", tagged[0] === 0xd8 && tagged[1] === 0x3d);
75
+ var v = await b.cwt.verify(tagged, { algorithms: ["ES256"], publicKey: EC.publicKey });
76
+ check("verify: accepts tag-61-wrapped CWT", v.claims.iss === "x");
77
+
78
+ var cwt = await b.cwt.sign({ iss: "y" }, { alg: "ES256", privateKey: EC.privateKey });
79
+ var t = Buffer.from(cwt); t[t.length - 1] ^= 0xff;
80
+ var tampered = null;
81
+ try { await b.cwt.verify(t, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { tampered = e; }
82
+ check("verify: tampered token refused by the COSE signature check", tampered && tampered.code === "cose/bad-signature");
83
+ }
84
+
85
+ async function testRobustTagAndMalformedClaims() {
86
+ // Codex P1 on PR #185 — a non-minimal CBOR tag-61 encoding
87
+ // (0xd9 0x00 0x3d, the 2-byte-argument form) must still be unwrapped.
88
+ var bare = await b.cwt.sign({ iss: "x" }, { alg: "ES256", privateKey: EC.privateKey });
89
+ var nonMinimal = Buffer.concat([Buffer.from([0xd9, 0x00, 0x3d]), bare]);
90
+ var v = await b.cwt.verify(nonMinimal, { algorithms: ["ES256"], publicKey: EC.publicKey });
91
+ check("verify: non-minimal tag-61 encoding accepted (interop)", v.claims.iss === "x");
92
+
93
+ // Codex P2 on PR #185 — a present-but-non-numeric exp must be
94
+ // refused, not silently skipped (it would otherwise never expire).
95
+ var badExp = b.cbor.encode(new Map([[1, "iss-A"], [4, "whenever"]])); // label 4 = exp, string value
96
+ var badToken = await b.cose.sign(badExp, { alg: "ES256", privateKey: EC.privateKey });
97
+ var e1 = null;
98
+ try { await b.cwt.verify(badToken, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { e1 = e; }
99
+ check("verify: malformed (non-numeric) exp refused", e1 && e1.code === "cwt/malformed-claim");
100
+
101
+ var badNbf = b.cbor.encode(new Map([[5, "soon"]])); // label 5 = nbf
102
+ var badNbfToken = await b.cose.sign(badNbf, { alg: "ES256", privateKey: EC.privateKey });
103
+ var e2 = null;
104
+ try { await b.cwt.verify(badNbfToken, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { e2 = e; }
105
+ check("verify: malformed (non-numeric) nbf refused", e2 && e2.code === "cwt/malformed-claim");
106
+ }
107
+
108
+ async function testValidation() {
109
+ var bad = null;
110
+ try { await b.cwt.sign({ exp: "not-a-number" }, { alg: "ES256", privateKey: EC.privateKey }); } catch (e) { bad = e; }
111
+ check("sign: non-integer NumericDate refused", bad && bad.code === "cwt/bad-numeric-date");
112
+ var noAlg = null;
113
+ try { await b.cwt.verify(await b.cwt.sign({ iss: "z" }, { alg: "ES256", privateKey: EC.privateKey }), { publicKey: EC.publicKey }); } catch (e) { noAlg = e; }
114
+ check("verify: missing algorithms refused (delegated to cose)", noAlg !== null);
115
+ var badInput = null;
116
+ try { await b.cwt.verify("not-a-buffer", { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { badInput = e; }
117
+ check("verify: non-buffer input refused", badInput && badInput.code === "cwt/bad-input");
118
+ }
119
+
120
+ async function run() {
121
+ testSurface();
122
+ await testRoundTrip();
123
+ await testTimeClaims();
124
+ await testIssuerAudience();
125
+ await testTaggedAndTamper();
126
+ await testRobustTagAndMalformedClaims();
127
+ await testValidation();
128
+ }
129
+
130
+ module.exports = { run: run };
131
+
132
+ if (require.main === module) {
133
+ run().then(
134
+ function () { console.log("[cwt] OK — " + helpers.getChecks() + " checks passed"); },
135
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
136
+ );
137
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.eat (RFC 9711 Entity Attestation Token) over b.cwt.
4
+ * Covers EAT claim-label mapping, the verifier-nonce freshness
5
+ * binding, debug-status policy, profile pinning, and that the
6
+ * signature / time checks delegate to b.cwt / b.cose.
7
+ */
8
+
9
+ var b = require("../../index");
10
+ var helpers = require("../helpers");
11
+ var check = helpers.check;
12
+ var nodeCrypto = require("node:crypto");
13
+
14
+ var EC = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
15
+ function _now() { return Math.floor(Date.now() / 1000); }
16
+
17
+ function testSurface() {
18
+ check("b.eat.sign exposed", typeof b.eat.sign === "function");
19
+ check("b.eat.verify exposed", typeof b.eat.verify === "function");
20
+ check("b.eat.CLAIM_LABELS has RFC 9711 keys", b.eat.CLAIM_LABELS.nonce === 10 && b.eat.CLAIM_LABELS.dbgstat === 263 && b.eat.CLAIM_LABELS.eat_profile === 265);
21
+ check("b.eat.DBGSTAT enum", b.eat.DBGSTAT["disabled-permanently"] === 3 && b.eat.DBGSTAT.enabled === 0);
22
+ check("b.eat.EatError exposed", typeof b.eat.EatError === "function");
23
+ }
24
+
25
+ async function testRoundTripAndLabels() {
26
+ var nonce = b.crypto.generateBytes(16);
27
+ var eat = await b.eat.sign(
28
+ { nonce: nonce, ueid: Buffer.from([1, 2, 3, 4]), oemid: Buffer.from("acme"), dbgstat: "disabled-permanently",
29
+ eat_profile: "https://example.com/eat/p1", iss: "device-ca", iat: _now() },
30
+ { alg: "ES256", privateKey: EC.privateKey });
31
+ var v = await b.eat.verify(eat, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedNonce: nonce });
32
+ check("verify: friendly EAT claim names returned", Buffer.isBuffer(v.claims.ueid) && v.claims.eat_profile === "https://example.com/eat/p1");
33
+ check("verify: dbgstat decoded to enum name", v.claims.dbgstat === "disabled-permanently");
34
+ check("verify: standard claim (iss) still named", v.claims.iss === "device-ca");
35
+ check("verify: raw Map uses RFC 9711 integer labels", v.raw.get(10) !== undefined && v.raw.get(263) === 3 && v.raw.get(265) === "https://example.com/eat/p1");
36
+ }
37
+
38
+ async function testNonceBinding() {
39
+ var nonce = b.crypto.generateBytes(16);
40
+ var eat = await b.eat.sign({ nonce: nonce, ueid: Buffer.from([9]) }, { alg: "ES256", privateKey: EC.privateKey });
41
+ var ok = await b.eat.verify(eat, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedNonce: nonce });
42
+ check("nonce: matching expectedNonce accepted", Buffer.isBuffer(ok.claims.nonce));
43
+ var mismatch = null;
44
+ try { await b.eat.verify(eat, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedNonce: b.crypto.generateBytes(16) }); } catch (e) { mismatch = e; }
45
+ check("nonce: mismatch refused (replay/freshness defense)", mismatch && mismatch.code === "eat/nonce-mismatch");
46
+ // Token with no nonce but RP expects one → refused.
47
+ var noNonce = await b.eat.sign({ ueid: Buffer.from([1]) }, { alg: "ES256", privateKey: EC.privateKey });
48
+ var missing = null;
49
+ try { await b.eat.verify(noNonce, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedNonce: nonce }); } catch (e) { missing = e; }
50
+ check("nonce: expectedNonce but no eat_nonce claim refused", missing && missing.code === "eat/nonce-missing");
51
+ // Array-of-nonces (multi-verifier) membership.
52
+ var multi = await b.eat.sign({ nonce: [b.crypto.generateBytes(16), nonce] }, { alg: "ES256", privateKey: EC.privateKey });
53
+ var m = await b.eat.verify(multi, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedNonce: nonce });
54
+ check("nonce: array form matches when one entry equals expectedNonce", Array.isArray(m.claims.nonce));
55
+ }
56
+
57
+ async function testDebugStatusAndProfile() {
58
+ var enabled = await b.eat.sign({ dbgstat: "enabled" }, { alg: "ES256", privateKey: EC.privateKey });
59
+ var e1 = null;
60
+ try { await b.eat.verify(enabled, { algorithms: ["ES256"], publicKey: EC.publicKey, requireDebugDisabled: true }); } catch (e) { e1 = e; }
61
+ check("dbgstat: enabled refused under requireDebugDisabled", e1 && e1.code === "eat/debug-not-disabled");
62
+
63
+ var absent = await b.eat.sign({ ueid: Buffer.from([1]) }, { alg: "ES256", privateKey: EC.privateKey });
64
+ var e2 = null;
65
+ try { await b.eat.verify(absent, { algorithms: ["ES256"], publicKey: EC.publicKey, requireDebugDisabled: true }); } catch (e) { e2 = e; }
66
+ check("dbgstat: absent refused under requireDebugDisabled", e2 && e2.code === "eat/debug-not-disabled");
67
+
68
+ var disabled = await b.eat.sign({ dbgstat: "disabled-fully-and-permanently" }, { alg: "ES256", privateKey: EC.privateKey });
69
+ var ok = await b.eat.verify(disabled, { algorithms: ["ES256"], publicKey: EC.publicKey, requireDebugDisabled: true });
70
+ check("dbgstat: a disabled state passes", ok.claims.dbgstat === "disabled-fully-and-permanently");
71
+
72
+ var prof = await b.eat.sign({ eat_profile: "p-A" }, { alg: "ES256", privateKey: EC.privateKey });
73
+ var e3 = null;
74
+ try { await b.eat.verify(prof, { algorithms: ["ES256"], publicKey: EC.publicKey, expectedProfile: "p-B" }); } catch (e) { e3 = e; }
75
+ check("profile: expectedProfile mismatch refused", e3 && e3.code === "eat/profile-mismatch");
76
+ }
77
+
78
+ async function testValidationAndTamper() {
79
+ var bad = null;
80
+ try { await b.eat.sign({ dbgstat: "bogus" }, { alg: "ES256", privateKey: EC.privateKey }); } catch (e) { bad = e; }
81
+ check("sign: bad dbgstat enum refused", bad && bad.code === "eat/bad-dbgstat");
82
+
83
+ var eat = await b.eat.sign({ nonce: b.crypto.generateBytes(16) }, { alg: "ES256", privateKey: EC.privateKey });
84
+ var t = Buffer.from(eat); t[t.length - 1] ^= 0xff;
85
+ var tampered = null;
86
+ try { await b.eat.verify(t, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { tampered = e; }
87
+ check("verify: tampered token refused by COSE signature check", tampered && tampered.code === "cose/bad-signature");
88
+
89
+ // exp from the CWT layer still enforced through EAT.
90
+ var expired = await b.eat.sign({ exp: _now() - 3600, ueid: Buffer.from([1]) }, { alg: "ES256", privateKey: EC.privateKey });
91
+ var exp = null;
92
+ try { await b.eat.verify(expired, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { exp = e; }
93
+ check("verify: expired EAT refused (delegated to cwt)", exp && exp.code === "cwt/expired");
94
+ }
95
+
96
+ async function run() {
97
+ testSurface();
98
+ await testRoundTripAndLabels();
99
+ await testNonceBinding();
100
+ await testDebugStatusAndProfile();
101
+ await testValidationAndTamper();
102
+ }
103
+
104
+ module.exports = { run: run };
105
+
106
+ if (require.main === module) {
107
+ run().then(
108
+ function () { console.log("[eat] OK — " + helpers.getChecks() + " checks passed"); },
109
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
110
+ );
111
+ }
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.mdoc (ISO 18013-5 mdoc / mDL issuer-data verification).
4
+ * A pure-node mock issuer builds an IssuerSigned structure (COSE_Sign1
5
+ * IssuerAuth over a Tag-24 MobileSecurityObject, with the signer cert in
6
+ * the x5chain header) so the verifier's full path is exercised: COSE
7
+ * signature, MSO validity window, per-element digest matching against
8
+ * the MSO, selective disclosure, and the tamper / expiry / docType /
9
+ * malformed-validity refusal paths. The trust core (COSE_Sign1 + CBOR)
10
+ * is the framework's tested b.cose / b.cbor substrate.
11
+ */
12
+
13
+ var b = require("../../index");
14
+ var helpers = require("../helpers");
15
+ var check = helpers.check;
16
+ var asn1 = require("../../lib/asn1-der");
17
+ var cbor = b.cbor;
18
+ var nodeCrypto = require("node:crypto");
19
+
20
+ var NS = "org.iso.18013.5.1";
21
+ var DOCTYPE = "org.iso.18013.5.1.mDL";
22
+
23
+ function _algId(oid, withNull) {
24
+ return withNull ? asn1.writeSequence([asn1.writeOid(oid), asn1.writeNull()]) : asn1.writeSequence([asn1.writeOid(oid)]);
25
+ }
26
+ function _name(cn) {
27
+ return asn1.writeSequence([asn1.writeSet([asn1.writeSequence([asn1.writeOid("2.5.4.3"), asn1.writeUtf8String(cn)])])]);
28
+ }
29
+ function _utc(d) { return asn1.writeNode(0x17, Buffer.from(d.toISOString().replace(/[-:T]/g, "").slice(2, 14) + "Z", "ascii")); }
30
+
31
+ // Minimal self-signed EC cert (mdoc issuer-data verify reads the key,
32
+ // not an EKU). Returns { certDer, key, pem }.
33
+ function _makeCert(cn) {
34
+ var kp = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
35
+ var spki = kp.publicKey.export({ type: "spki", format: "der" });
36
+ var sa = _algId("1.2.840.10045.4.3.2", false);
37
+ var nm = _name(cn || "mDL Issuer");
38
+ var now = Date.now();
39
+ var tbs = asn1.writeSequence([
40
+ asn1.writeContextExplicit(0, asn1.writeInteger(Buffer.from([2]))),
41
+ asn1.writeInteger(Buffer.from([0x2a])), sa, nm,
42
+ asn1.writeSequence([_utc(new Date(now - 86400000)), _utc(new Date(now + 86400000 * 3650))]),
43
+ nm, spki,
44
+ ]);
45
+ var certDer = asn1.writeSequence([tbs, sa, asn1.writeBitString(nodeCrypto.sign("sha256", tbs, kp.privateKey), 0)]);
46
+ return { certDer: certDer, key: kp.privateKey, pem: new nodeCrypto.X509Certificate(certDer).toString() };
47
+ }
48
+
49
+ // Build an IssuerSigned (CBOR bytes). opts: { elements, validFrom,
50
+ // validUntil, docType, digestAlg } — defaults are a valid mDL.
51
+ async function _makeMdoc(cert, opts) {
52
+ opts = opts || {};
53
+ var elements = opts.elements || [["family_name", "Doe"], ["age_over_18", true], ["given_name", "Jane"]];
54
+ var digestNode = opts.digestNode || "sha256";
55
+ var digestAlg = opts.digestAlg || "SHA-256";
56
+ var now = Date.now();
57
+ var items = [];
58
+ var digests = new Map();
59
+ elements.forEach(function (el, i) {
60
+ var inner = cbor.encode(new Map([
61
+ ["digestID", i], ["random", nodeCrypto.randomBytes(32)],
62
+ ["elementIdentifier", el[0]], ["elementValue", el[1]],
63
+ ]));
64
+ items.push(new cbor.Tag(24, inner));
65
+ digests.set(i, nodeCrypto.createHash(digestNode).update(cbor.encode(new cbor.Tag(24, inner))).digest());
66
+ });
67
+ var validFromMs = opts.validFrom != null ? opts.validFrom : now - 86400000;
68
+ var validUntilMs = opts.validUntil != null ? opts.validUntil : now + 86400000 * 3650;
69
+ var validUntilTag = opts.validUntilRaw !== undefined ? opts.validUntilRaw : new cbor.Tag(0, new Date(validUntilMs).toISOString());
70
+ var mso = new Map([
71
+ ["version", "1.0"], ["digestAlgorithm", digestAlg], ["docType", opts.docType || DOCTYPE],
72
+ ["valueDigests", new Map([[NS, digests]])],
73
+ ["validityInfo", new Map([
74
+ ["signed", new cbor.Tag(0, new Date(now).toISOString())],
75
+ ["validFrom", new cbor.Tag(0, new Date(validFromMs).toISOString())],
76
+ ["validUntil", validUntilTag],
77
+ ])],
78
+ ]);
79
+ var payload = cbor.encode(new cbor.Tag(24, cbor.encode(mso)));
80
+ var signed = await b.cose.sign(payload, { alg: opts.alg || "ES256", privateKey: cert.key, unprotectedHeaders: { 33: cert.certDer } });
81
+ var issuerAuth = cbor.decode(signed, { allowedTags: [18, 24] }).value;
82
+ return cbor.encode(new Map([["nameSpaces", new Map([[NS, items]])], ["issuerAuth", issuerAuth]]));
83
+ }
84
+
85
+ function testSurface() {
86
+ check("b.mdoc.verifyIssuerSigned is a function", typeof b.mdoc.verifyIssuerSigned === "function");
87
+ check("b.mdoc.DIGEST_ALGS has SHA-256", b.mdoc.DIGEST_ALGS["SHA-256"] === "sha256");
88
+ check("b.mdoc.MdocError is a class", typeof b.mdoc.MdocError === "function");
89
+ }
90
+
91
+ async function testRoundTrip() {
92
+ var cert = _makeCert();
93
+ var mdoc = await _makeMdoc(cert);
94
+ var out = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], expectedDocType: DOCTYPE });
95
+ check("verify: docType", out.docType === DOCTYPE);
96
+ check("verify: alg reported", out.alg === "ES256");
97
+ check("verify: digestAlgorithm", out.digestAlgorithm === "SHA-256");
98
+ check("verify: disclosed elements extracted", out.namespaces[NS].family_name === "Doe" && out.namespaces[NS].age_over_18 === true && out.namespaces[NS].given_name === "Jane");
99
+ check("verify: validityInfo dates returned", out.validityInfo.validUntil instanceof Date);
100
+ check("verify: signerCert PEM returned", /BEGIN CERTIFICATE/.test(out.signerCert));
101
+
102
+ // chain verify against the self-signed issuer as anchor
103
+ var ok = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], trustAnchorsPem: cert.pem });
104
+ check("verify: chain to issuer anchor (string)", ok.docType === DOCTYPE);
105
+ }
106
+
107
+ async function testDigestAndSignatureRefusals() {
108
+ var cert = _makeCert();
109
+ var mdoc = await _makeMdoc(cert);
110
+
111
+ // Tamper a disclosed element's value: re-pack one IssuerSignedItem
112
+ // with a changed value but keep the MSO digest → digest mismatch.
113
+ var top = cbor.decode(mdoc, { allowedTags: [0, 1, 24, 1004] });
114
+ var items = top.get("nameSpaces").get(NS);
115
+ var inner0 = cbor.decode(items[0].value, { allowedTags: [0, 1, 24, 1004] });
116
+ inner0.set("elementValue", "Tampered");
117
+ items[0] = new cbor.Tag(24, cbor.encode(inner0));
118
+ var tampered = cbor.encode(top);
119
+ var e1 = null;
120
+ try { await b.mdoc.verifyIssuerSigned(tampered, { algorithms: ["ES256"] }); } catch (e) { e1 = e; }
121
+ check("verify: tampered element refused (digest-mismatch)", e1 && e1.code === "mdoc/digest-mismatch");
122
+
123
+ // Tamper the COSE signature deterministically (flip a byte in the
124
+ // issuerAuth signature element) → bad-signature.
125
+ var top2 = cbor.decode(mdoc, { allowedTags: [0, 1, 24, 1004] });
126
+ var ia = top2.get("issuerAuth");
127
+ ia[3] = Buffer.from(ia[3]); ia[3][0] ^= 0xff;
128
+ var bad = cbor.encode(top2);
129
+ var e2 = null;
130
+ try { await b.mdoc.verifyIssuerSigned(bad, { algorithms: ["ES256"] }); } catch (e) { e2 = e; }
131
+ check("verify: tampered COSE signature refused", e2 && e2.code === "cose/bad-signature");
132
+
133
+ // A malformed x5chain certificate surfaces a clean error (not raw OpenSSL).
134
+ var top3 = cbor.decode(mdoc, { allowedTags: [0, 1, 24, 1004] });
135
+ top3.get("issuerAuth")[1].set(33, Buffer.from([0x30, 0x03, 0x02, 0x01, 0x01]));
136
+ var e6 = null;
137
+ try { await b.mdoc.verifyIssuerSigned(cbor.encode(top3), { algorithms: ["ES256"] }); } catch (e) { e6 = e; }
138
+ check("verify: malformed x5chain cert refused cleanly", e6 && e6.code === "mdoc/bad-cert");
139
+
140
+ // alg outside allowlist
141
+ var e3 = null;
142
+ try { await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["EdDSA"] }); } catch (e) { e3 = e; }
143
+ check("verify: alg outside allowlist refused", e3 && (e3.code === "cose/alg-not-allowed"));
144
+ }
145
+
146
+ async function testValidityAndDocType() {
147
+ var cert = _makeCert();
148
+
149
+ // expired (valid window 10 days ago .. 1 day ago)
150
+ var expired = await _makeMdoc(cert, { validFrom: Date.now() - 86400000 * 10, validUntil: Date.now() - 86400000 });
151
+ var e1 = null;
152
+ try { await b.mdoc.verifyIssuerSigned(expired, { algorithms: ["ES256"] }); } catch (e) { e1 = e; }
153
+ check("verify: expired credential refused", e1 && e1.code === "mdoc/expired");
154
+
155
+ // not yet valid
156
+ var future = await _makeMdoc(cert, { validFrom: Date.now() + 86400000 * 30 });
157
+ var e2 = null;
158
+ try { await b.mdoc.verifyIssuerSigned(future, { algorithms: ["ES256"] }); } catch (e) { e2 = e; }
159
+ check("verify: not-yet-valid credential refused", e2 && e2.code === "mdoc/not-yet-valid");
160
+
161
+ // opts.at within window accepts an otherwise-expired credential
162
+ var ok = await b.mdoc.verifyIssuerSigned(expired, { algorithms: ["ES256"], at: new Date(Date.now() - 86400000 * 2) });
163
+ check("verify: opts.at within window accepts", ok.docType === DOCTYPE);
164
+
165
+ // docType mismatch
166
+ var e3 = null;
167
+ try { await b.mdoc.verifyIssuerSigned(await _makeMdoc(cert), { algorithms: ["ES256"], expectedDocType: "org.iso.18013.5.1.photoID" }); } catch (e) { e3 = e; }
168
+ check("verify: docType mismatch refused", e3 && e3.code === "mdoc/doctype-mismatch");
169
+
170
+ // malformed validUntil (a non-date) fails closed
171
+ var badValidity = await _makeMdoc(cert, { validUntilRaw: new cbor.Tag(0, "not-a-date") });
172
+ var e4 = null;
173
+ try { await b.mdoc.verifyIssuerSigned(badValidity, { algorithms: ["ES256"] }); } catch (e) { e4 = e; }
174
+ check("verify: malformed validUntil refused (fail closed)", e4 && e4.code === "mdoc/bad-validity");
175
+
176
+ // invalid opts.at refused (lesson carried from b.tsa / b.vc)
177
+ var e5 = null;
178
+ try { await b.mdoc.verifyIssuerSigned(await _makeMdoc(cert), { algorithms: ["ES256"], at: new Date("nope") }); } catch (e) { e5 = e; }
179
+ check("verify: invalid opts.at refused", e5 && e5.code === "mdoc/bad-at");
180
+
181
+ // Two signed IssuerSignedItems with the same elementIdentifier (each
182
+ // with a valid MSO digest) is ambiguous → fail closed, not last-wins.
183
+ var dup = await _makeMdoc(cert, { elements: [["family_name", "Doe"], ["family_name", "Roe"]] });
184
+ var e6 = null;
185
+ try { await b.mdoc.verifyIssuerSigned(dup, { algorithms: ["ES256"] }); } catch (e) { e6 = e; }
186
+ check("verify: duplicate elementIdentifier refused", e6 && e6.code === "mdoc/duplicate-element");
187
+ }
188
+
189
+ async function testChainAndInputGuards() {
190
+ var cert = _makeCert();
191
+ var mdoc = await _makeMdoc(cert);
192
+
193
+ // unrelated anchor → untrusted
194
+ var other = _makeCert("Unrelated Root");
195
+ var e1 = null;
196
+ try { await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], trustAnchorsPem: [other.pem] }); } catch (e) { e1 = e; }
197
+ check("verify: unrelated anchor refused", e1 && e1.code === "mdoc/untrusted-chain");
198
+
199
+ // empty trust-anchor shape refused (no fail-open)
200
+ var e2 = null;
201
+ try { await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"], trustAnchorsPem: [] }); } catch (e) { e2 = e; }
202
+ check("verify: empty trustAnchorsPem refused", e2 && e2.code === "mdoc/bad-trust-anchors");
203
+
204
+ // garbage input → not CBOR / malformed
205
+ var e3 = null;
206
+ try { await b.mdoc.verifyIssuerSigned(Buffer.from([0x00, 0x01]), { algorithms: ["ES256"] }); } catch (e) { e3 = e; }
207
+ check("verify: garbage input refused", e3 && (e3.code === "mdoc/malformed" || e3.code === "mdoc/bad-input" || /cbor/.test(e3.code || "")));
208
+
209
+ // missing algorithms
210
+ var e4 = null;
211
+ try { await b.mdoc.verifyIssuerSigned(mdoc, {}); } catch (e) { e4 = e; }
212
+ check("verify: missing algorithms refused", e4 && e4.code === "mdoc/algorithms-required");
213
+ }
214
+
215
+ async function run() {
216
+ testSurface();
217
+ await testRoundTrip();
218
+ await testDigestAndSignatureRefusals();
219
+ await testValidityAndDocType();
220
+ await testChainAndInputGuards();
221
+ }
222
+
223
+ module.exports = { run: run };
224
+
225
+ if (require.main === module) {
226
+ run().then(
227
+ function () { console.log("[mdoc] OK — " + helpers.getChecks() + " checks passed"); },
228
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
229
+ );
230
+ }