@blamejs/blamejs-shop 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/README.md +1 -1
- package/lib/admin.js +274 -7
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +14 -0
- package/lib/vendor/blamejs/README.md +6 -5
- package/lib/vendor/blamejs/SECURITY.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +166 -3
- package/lib/vendor/blamejs/lib/cose.js +284 -10
- package/lib/vendor/blamejs/lib/crypto.js +119 -0
- package/lib/vendor/blamejs/lib/did.js +69 -20
- package/lib/vendor/blamejs/lib/mdoc.js +122 -0
- package/lib/vendor/blamejs/lib/network-dnssec.js +328 -0
- package/lib/vendor/blamejs/lib/network.js +1 -0
- package/lib/vendor/blamejs/lib/vc.js +231 -33
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.42.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.43.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.44.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.45.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.46.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.47.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.48.json +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +38 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +101 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-self-test.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/did.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +63 -0
- package/package.json +1 -1
|
@@ -170,12 +170,12 @@ async function testValidation() {
|
|
|
170
170
|
}
|
|
171
171
|
check("sign/verify: malformed args throw the right codes", ok);
|
|
172
172
|
|
|
173
|
-
//
|
|
173
|
+
// A detached (nil) payload without opts.externalPayload is refused.
|
|
174
174
|
var protBstr = b.cbor.encode(new Map([[1, -7]]));
|
|
175
175
|
var detached = b.cbor.encode(new b.cbor.Tag(18, [protBstr, new Map(), null, Buffer.from([0, 0])]));
|
|
176
176
|
var det = null;
|
|
177
177
|
try { await b.cose.verify(detached, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { det = e; }
|
|
178
|
-
check("verify: detached payload
|
|
178
|
+
check("verify: detached payload without externalPayload refused", det && det.code === "cose/detached-no-payload");
|
|
179
179
|
|
|
180
180
|
// Codex P2 on PR #184 — a non-byte payload (text string here) must
|
|
181
181
|
// be refused, not returned as a non-Buffer.
|
|
@@ -192,6 +192,61 @@ async function testValidation() {
|
|
|
192
192
|
check("verify: non-map unprotected header refused", bu && bu.code === "cose/malformed");
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
async function testDetachedPayload() {
|
|
196
|
+
var payload = Buffer.from("detached COSE payload", "utf8");
|
|
197
|
+
var det = await b.cose.sign(payload, { alg: "ES256", privateKey: EC.privateKey, detached: true });
|
|
198
|
+
// payload slot must be nil
|
|
199
|
+
var arr = b.cbor.decode(det, { allowedTags: [18] }).value;
|
|
200
|
+
check("detached: payload slot is nil", arr[2] === null);
|
|
201
|
+
var out = await b.cose.verify(det, { algorithms: ["ES256"], publicKey: EC.publicKey, externalPayload: payload });
|
|
202
|
+
check("detached: verify with externalPayload returns it", out.payload.equals(payload));
|
|
203
|
+
|
|
204
|
+
var noPay = null;
|
|
205
|
+
try { await b.cose.verify(det, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { noPay = e; }
|
|
206
|
+
check("detached: verify without externalPayload refused", noPay && noPay.code === "cose/detached-no-payload");
|
|
207
|
+
|
|
208
|
+
var wrong = null;
|
|
209
|
+
try { await b.cose.verify(det, { algorithms: ["ES256"], publicKey: EC.publicKey, externalPayload: Buffer.from("x") }); } catch (e) { wrong = e; }
|
|
210
|
+
check("detached: wrong externalPayload fails signature", wrong && wrong.code === "cose/bad-signature");
|
|
211
|
+
|
|
212
|
+
// attached token + externalPayload is ambiguous
|
|
213
|
+
var att = await b.cose.sign(payload, { alg: "ES256", privateKey: EC.privateKey });
|
|
214
|
+
var amb = null;
|
|
215
|
+
try { await b.cose.verify(att, { algorithms: ["ES256"], publicKey: EC.publicKey, externalPayload: payload }); } catch (e) { amb = e; }
|
|
216
|
+
check("detached: externalPayload on an attached token refused", amb && amb.code === "cose/payload-ambiguous");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function testImportKey() {
|
|
220
|
+
// EC2 P-256 COSE_Key built from the JWK, then used to verify.
|
|
221
|
+
var jwk = EC.publicKey.export({ format: "jwk" });
|
|
222
|
+
var coseKey = new Map([[1, 2], [-1, 1], [-2, Buffer.from(jwk.x, "base64url")], [-3, Buffer.from(jwk.y, "base64url")]]);
|
|
223
|
+
var imported = b.cose.importKey(coseKey);
|
|
224
|
+
check("importKey: EC2 P-256 matches the JWK x", imported.export({ format: "jwk" }).x === jwk.x);
|
|
225
|
+
var att = await b.cose.sign(Buffer.from("k"), { alg: "ES256", privateKey: EC.privateKey });
|
|
226
|
+
check("importKey: imported key verifies a COSE_Sign1", (await b.cose.verify(att, { algorithms: ["ES256"], publicKey: imported })).payload.toString() === "k");
|
|
227
|
+
|
|
228
|
+
// OKP Ed25519
|
|
229
|
+
var ejwk = ED.publicKey.export({ format: "jwk" });
|
|
230
|
+
var okp = new Map([[1, 1], [-1, 6], [-2, Buffer.from(ejwk.x, "base64url")]]);
|
|
231
|
+
check("importKey: OKP Ed25519", b.cose.importKey(okp).asymmetricKeyType === "ed25519");
|
|
232
|
+
|
|
233
|
+
// unsupported curve / kty refused
|
|
234
|
+
var bad = null;
|
|
235
|
+
try { b.cose.importKey(new Map([[1, 2], [-1, 99], [-2, Buffer.from(jwk.x, "base64url")], [-3, Buffer.from(jwk.y, "base64url")]])); } catch (e) { bad = e; }
|
|
236
|
+
check("importKey: unsupported EC2 curve refused", bad && bad.code === "cose/unsupported-key");
|
|
237
|
+
var badKty = null;
|
|
238
|
+
try { b.cose.importKey(new Map([[1, 4], [-2, Buffer.from(jwk.x, "base64url")]])); } catch (e) { badKty = e; }
|
|
239
|
+
check("importKey: unsupported kty refused", badKty && (badKty.code === "cose/unsupported-key" || badKty.code === "cose/bad-cose-key"));
|
|
240
|
+
|
|
241
|
+
// secp256k1 (crv id 8) is refused — b.cose has no ES256K path, so
|
|
242
|
+
// accepting it would let it verify under ES256 (alg/curve mis-binding).
|
|
243
|
+
var k1 = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "secp256k1" });
|
|
244
|
+
var k1jwk = k1.publicKey.export({ format: "jwk" });
|
|
245
|
+
var secpBad = null;
|
|
246
|
+
try { b.cose.importKey(new Map([[1, 2], [-1, 8], [-2, Buffer.from(k1jwk.x, "base64url")], [-3, Buffer.from(k1jwk.y, "base64url")]])); } catch (e) { secpBad = e; }
|
|
247
|
+
check("importKey: secp256k1 refused (no ES256K binding)", secpBad && secpBad.code === "cose/unsupported-key");
|
|
248
|
+
}
|
|
249
|
+
|
|
195
250
|
async function run() {
|
|
196
251
|
testSurface();
|
|
197
252
|
testEncrypt0();
|
|
@@ -201,6 +256,50 @@ async function run() {
|
|
|
201
256
|
await testTamperAndAllowlist();
|
|
202
257
|
await testCritBypassDefense();
|
|
203
258
|
await testValidation();
|
|
259
|
+
await testDetachedPayload();
|
|
260
|
+
await testImportKey();
|
|
261
|
+
testMac0();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function testMac0() {
|
|
265
|
+
var key = nodeCrypto.randomBytes(32);
|
|
266
|
+
var pt = Buffer.from("cose mac0 payload", "utf8");
|
|
267
|
+
var m = b.cose.mac0(pt, { alg: "HMAC-256/256", key: key });
|
|
268
|
+
check("mac0: tagged COSE_Mac0 (tag 17 → 0xd1)", m[0] === 0xd1);
|
|
269
|
+
var out = b.cose.macVerify0(m, { algorithms: ["HMAC-256/256"], key: key });
|
|
270
|
+
check("mac0: round-trips payload + alg", out.payload.equals(pt) && out.alg === "HMAC-256/256");
|
|
271
|
+
|
|
272
|
+
var wrongKey = null;
|
|
273
|
+
try { b.cose.macVerify0(m, { algorithms: ["HMAC-256/256"], key: nodeCrypto.randomBytes(32) }); } catch (e) { wrongKey = e; }
|
|
274
|
+
check("mac0: wrong key refused", wrongKey && wrongKey.code === "cose/bad-tag");
|
|
275
|
+
|
|
276
|
+
var tampered = Buffer.from(m); tampered[tampered.length - 1] ^= 0xff;
|
|
277
|
+
var tamp = null;
|
|
278
|
+
try { b.cose.macVerify0(tampered, { algorithms: ["HMAC-256/256"], key: key }); } catch (e) { tamp = e; }
|
|
279
|
+
check("mac0: tampered tag refused", tamp && tamp.code === "cose/bad-tag");
|
|
280
|
+
|
|
281
|
+
var notAllowed = null;
|
|
282
|
+
try { b.cose.macVerify0(m, { algorithms: ["HMAC-512/512"], key: key }); } catch (e) { notAllowed = e; }
|
|
283
|
+
check("mac0: alg not in allowlist refused", notAllowed && notAllowed.code === "cose/alg-not-allowed");
|
|
284
|
+
|
|
285
|
+
var md = b.cose.mac0(pt, { alg: "HMAC-384/384", key: key, detached: true });
|
|
286
|
+
check("mac0: detached verify with externalPayload", b.cose.macVerify0(md, { algorithms: ["HMAC-384/384"], key: key, externalPayload: pt }).payload.equals(pt));
|
|
287
|
+
var noPay = null;
|
|
288
|
+
try { b.cose.macVerify0(md, { algorithms: ["HMAC-384/384"], key: key }); } catch (e) { noPay = e; }
|
|
289
|
+
check("mac0: detached without externalPayload refused", noPay && noPay.code === "cose/detached-no-payload");
|
|
290
|
+
|
|
291
|
+
var ma = b.cose.mac0(pt, { alg: "HMAC-512/512", key: key, externalAad: Buffer.from("ctx-A", "utf8") });
|
|
292
|
+
var aadBad = null;
|
|
293
|
+
try { b.cose.macVerify0(ma, { algorithms: ["HMAC-512/512"], key: key, externalAad: Buffer.from("ctx-B", "utf8") }); } catch (e) { aadBad = e; }
|
|
294
|
+
check("mac0: external_aad mismatch refused", aadBad && aadBad.code === "cose/bad-tag");
|
|
295
|
+
|
|
296
|
+
// crit-bypass defense: a COSE_Mac0 marking an unknown critical header
|
|
297
|
+
// is refused (matching b.cose.verify).
|
|
298
|
+
var protCrit = b.cbor.encode(new Map([[1, 5], [2, [99]]]));
|
|
299
|
+
var macCrit = b.cbor.encode(new b.cbor.Tag(17, [protCrit, new Map(), Buffer.from("x", "utf8"), Buffer.alloc(32)]));
|
|
300
|
+
var critErr = null;
|
|
301
|
+
try { b.cose.macVerify0(macCrit, { algorithms: ["HMAC-256/256"], key: key }); } catch (e) { critErr = e; }
|
|
302
|
+
check("mac0: unknown crit header refused", critErr && critErr.code === "cose/crit-unknown");
|
|
204
303
|
}
|
|
205
304
|
|
|
206
305
|
module.exports = { run: run };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 0 — b.crypto.selfTest (FIPS 140-3-style power-on self-test).
|
|
4
|
+
* Confirms the KATs (NIST FIPS 202 SHA3/SHAKE vectors), the AEAD
|
|
5
|
+
* round-trip + tamper-detect, and the PQC pairwise-consistency +
|
|
6
|
+
* negative checks all run and pass, and that the report shape +
|
|
7
|
+
* throwOnFailure contract hold.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var b = require("../../index");
|
|
11
|
+
var helpers = require("../helpers");
|
|
12
|
+
var check = helpers.check;
|
|
13
|
+
var nodeCrypto = require("node:crypto");
|
|
14
|
+
|
|
15
|
+
var EXPECTED = [
|
|
16
|
+
"SHA3-512 KAT (FIPS 202)",
|
|
17
|
+
"SHA3-256 KAT (FIPS 202)",
|
|
18
|
+
"SHAKE256 KAT (FIPS 202)",
|
|
19
|
+
"HMAC-SHA3-512 determinism",
|
|
20
|
+
"XChaCha20-Poly1305 round-trip + tamper-detect",
|
|
21
|
+
"ML-KEM-1024 encaps/decaps pairwise consistency",
|
|
22
|
+
"ML-DSA-87 sign/verify + negative",
|
|
23
|
+
"SLH-DSA-SHAKE-256f sign/verify + negative",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function testSurface() {
|
|
27
|
+
check("b.crypto.selfTest is a function", typeof b.crypto.selfTest === "function");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function testAllPass() {
|
|
31
|
+
var r = b.crypto.selfTest();
|
|
32
|
+
check("selfTest: ok true", r.ok === true);
|
|
33
|
+
check("selfTest: no failures", r.failures.length === 0);
|
|
34
|
+
check("selfTest: ranAt is an ISO timestamp", typeof r.ranAt === "string" && !isNaN(Date.parse(r.ranAt)));
|
|
35
|
+
// every expected check ran and passed
|
|
36
|
+
var names = r.results.map(function (x) { return x.name; });
|
|
37
|
+
EXPECTED.forEach(function (name) {
|
|
38
|
+
check("selfTest ran + passed: " + name, names.indexOf(name) !== -1 && r.results[names.indexOf(name)].ok === true);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function testKatMatchesNistVectors() {
|
|
43
|
+
// Independent cross-check of the KAT references against node's own
|
|
44
|
+
// FIPS-validated SHA3 (the self-test's hash digests are the standard
|
|
45
|
+
// FIPS 202 answers, not framework-vs-framework).
|
|
46
|
+
check("SHA3-512(abc) is the FIPS 202 vector",
|
|
47
|
+
b.crypto.sha3Hash("abc") === nodeCrypto.createHash("sha3-512").update("abc").digest("hex"));
|
|
48
|
+
check("SHA3-512(abc) starts with the published prefix",
|
|
49
|
+
b.crypto.sha3Hash("abc").indexOf("b751850b1a57168a") === 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function testReportContract() {
|
|
53
|
+
// throwOnFailure:false always returns the structured report.
|
|
54
|
+
var r = b.crypto.selfTest({ throwOnFailure: false });
|
|
55
|
+
check("throwOnFailure:false returns a report", r && Array.isArray(r.results) && typeof r.ok === "boolean");
|
|
56
|
+
// each result carries a name + ok boolean
|
|
57
|
+
check("each result has name + ok", r.results.every(function (x) { return typeof x.name === "string" && typeof x.ok === "boolean"; }));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function run() {
|
|
61
|
+
testSurface();
|
|
62
|
+
testAllPass();
|
|
63
|
+
testKatMatchesNistVectors();
|
|
64
|
+
testReportContract();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { run: run };
|
|
68
|
+
|
|
69
|
+
if (require.main === module) {
|
|
70
|
+
run().then(
|
|
71
|
+
function () { console.log("[crypto-self-test] OK — " + helpers.getChecks() + " checks passed"); },
|
|
72
|
+
function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -128,12 +128,41 @@ function testRefusals() {
|
|
|
128
128
|
check("RSA key → unsupported", code(function () { b.did.keyToDid(rsa.publicKey); }) === "did/unsupported-key");
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function testDidJwk() {
|
|
132
|
+
var ec = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
133
|
+
var ed = nodeCrypto.generateKeyPairSync("ed25519");
|
|
134
|
+
|
|
135
|
+
var dj = b.did.keyToDid(ec.publicKey, { method: "jwk" });
|
|
136
|
+
check("did:jwk: keyToDid produces a did:jwk", dj.indexOf("did:jwk:") === 0);
|
|
137
|
+
check("did:jwk: parse method", b.did.parse(dj).method === "jwk");
|
|
138
|
+
var r = b.did.resolve(dj);
|
|
139
|
+
check("did:jwk: resolves to the key", _spkiEq(r.verificationMethods[0].publicKey, ec.publicKey));
|
|
140
|
+
check("did:jwk: vm type JsonWebKey2020 + publicKeyJwk", r.verificationMethods[0].type === "JsonWebKey2020" && !!r.didDocument.verificationMethod[0].publicKeyJwk);
|
|
141
|
+
check("did:jwk: Ed25519 round-trips", _spkiEq(b.did.resolve(b.did.keyToDid(ed.publicKey, { method: "jwk" })).verificationMethods[0].publicKey, ed.publicKey));
|
|
142
|
+
// keyToDid default is still did:key
|
|
143
|
+
check("did:jwk: keyToDid default is did:key", b.did.keyToDid(ec.publicKey).indexOf("did:key:") === 0);
|
|
144
|
+
// private member is stripped from the produced did:jwk
|
|
145
|
+
var decoded = JSON.parse(Buffer.from(dj.slice("did:jwk:".length), "base64url").toString("utf8"));
|
|
146
|
+
check("did:jwk: no private 'd' member encoded", decoded.d === undefined && typeof decoded.x === "string");
|
|
147
|
+
|
|
148
|
+
function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
|
|
149
|
+
check("did:jwk: non-JSON id refused", code(function () { b.did.resolve("did:jwk:" + Buffer.from("not json", "utf8").toString("base64url")); }) === "did/bad-jwk");
|
|
150
|
+
check("did:jwk: unsupported kty refused", code(function () {
|
|
151
|
+
b.did.resolve("did:jwk:" + Buffer.from(JSON.stringify({ kty: "RSA", n: "x", e: "AQAB" }), "utf8").toString("base64url"));
|
|
152
|
+
}) === "did/unsupported-key");
|
|
153
|
+
// keyToDid must also refuse an unsupported key when emitting did:jwk —
|
|
154
|
+
// generation and resolution share the allowlist so the DID round-trips.
|
|
155
|
+
var rsa = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
|
|
156
|
+
check("did:jwk: keyToDid refuses RSA (round-trip guarantee)", code(function () { b.did.keyToDid(rsa.publicKey, { method: "jwk" }); }) === "did/unsupported-key");
|
|
157
|
+
}
|
|
158
|
+
|
|
131
159
|
async function run() {
|
|
132
160
|
testSurface();
|
|
133
161
|
testSpecVector();
|
|
134
162
|
testRoundTrips();
|
|
135
163
|
await testCredentialIntegration();
|
|
136
164
|
testDidWeb();
|
|
165
|
+
testDidJwk();
|
|
137
166
|
testRefusals();
|
|
138
167
|
}
|
|
139
168
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 0 — b.network.dns.dnssec (local DNSSEC RRSIG verification).
|
|
4
|
+
* The oracles are REAL captured DNSKEY responses (Cloudflare DoH,
|
|
5
|
+
* application/dns-message) for an ECDSA-P256 zone (cloudflare.com) and
|
|
6
|
+
* an RSA/SHA-256 zone (verisign.com): a byte off in the RFC 4034
|
|
7
|
+
* canonicalisation would fail these real-world signatures. Verified at a
|
|
8
|
+
* fixed instant inside each RRSIG's window so the fixture never expires.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var b = require("../../index");
|
|
12
|
+
var helpers = require("../helpers");
|
|
13
|
+
var check = helpers.check;
|
|
14
|
+
|
|
15
|
+
// Real `cloudflare.com` DNSKEY response (ECDSA P-256 / alg 13), captured
|
|
16
|
+
// via Cloudflare DoH with the DO bit set.
|
|
17
|
+
var CF_HEX = "000081a000010003000000010a636c6f7564666c61726503636f6d0000300001c00c003000010000071200440100030da09311112cf9138818cd2feae970ebbd4d6a30f6088c25b325a39abbc5cd1197aa098283e5aaf421177c2aa5d714992a9957d1bcc18f98cd71f1f1806b65e148c00c003000010000071200440101030d99db2cc14cabdc33d6d77da63a2f15f71112584f234e8d1dc428e39e8a4a97e1aa271a555dc90701e17e2a4c4b6f120b7c32d44f4ac02bd894cf2d4be7778a19c00c002e000100000712006200300d0200000e106a604d3e6a0fe1be09430a636c6f7564666c61726503636f6d008a40d46e9ff1d7eae7a3824de100af4eba33c5cc88751c29fb7bf1a931d68ab1d25c19c8be0f06da464aebaad3f171be9ed92e20ae1b1bf25e54cc18939a2a5e00002904d0000080000000";
|
|
18
|
+
// Real `verisign.com` DNSKEY response (RSA/SHA-256 / alg 8).
|
|
19
|
+
var VS_HEX = "000081a0000100040000000108766572697369676e03636f6d0000300001c00c0030000100000cb900880100030803010001a24a87ea79dcc74adda5bad2b0ad3e5514b06c30dd14dcfe1ef3503e1591ccd67a486bf4d87e496c9c5f7009f6796eb54324157d5baa5815063816323bd2f1d2846d278c34c551cd4cf67c1adf21427836603e37c4230bc47ce59845fb697f53471a824be691e683580b92020f12c7a8efeb02f12fc475dbdeca65a0bb0a4f6fc00c0030000100000cb900880100030803010001cc288a535db6a1b542355d84ad07d77cd1729f6eb191b24698becb69ec43aaddbc1714102fd9a8745683d211a33bc88103e96a893e83cff7d27bce7cfeafc6ac2b3b85d0f95e2952d8c413a54aaeee378701f8627805ac97a778a0cd323ce1585139a3a84a9e5a28850ed427e8038fd2f0600f96e0188f46f33acede7811d711c00c0030000100000cb901080101030803010001bfb6a7aca1ef6f910e6dc358935f7132dd3a6fc716550739861b28f4c06ab0e6a4d8082bc7615fa898ed12594cb03e8c89d918d3c2c0d1036bfeae7b73f831ac49634c46cdb3d0c307dd53298f32c52fc16764729b133c105a4ec701ba2a3fbbb60ee6cf9c21dcc0ffbb270e3bc5e6bbf4cf1d07d1b00f50655c5d8e7724f951b3ac69d748265351aa014269ffd31678248ce15168aed3fcfbb1a32704743d76b15fc1abdb157c17b3d7deee5a6742019c32ee87a9bce449281122f586964d3f23cd502fbbb2c0504611876c50ca780ea958313dc9f7dec485aa90cd15bf42a66d80da99df2c46c3974ead4f88332a810b83d6e2a416d7ee8318f3c4c671eaa3c00c002e000100000cb901200030080200000e106a3b146f6a13876fd7a408766572697369676e03636f6d0075ae4e787092f2120d8592cf56d3cd87f06813e38aadd90111ba7e656e90ee1c969591cda2ed4838db2648a68326fa04cbd3886ff2fd48f954284bff78459c8a78c4ecb8b2462f0bd1636555dd96b1e83f7cf322d1a4806480eef57e16b65cf5d2229184cab0c573f30a16f5af94b4cd15c05a04c62cd2ca8afc2f39c6067ec2fed95cc044f88f4a746388de20fe58decccda4b1cbc50d8f011cd56055c56c375464b9999e3e04d6a7180ca5fce5801b445cfc9f33b6fdac4f5c9d9714deaa77420ee4f147f1fefb63187230cfb93c2a3c218130c707fb42dfb4d445a33197ebfbf00087d1012f2dc0f4b29857908a2de33aef8ebbcc326cf4e1ef60181cf82a00002904d0000080000000";
|
|
20
|
+
|
|
21
|
+
function _rdName(buf, off) {
|
|
22
|
+
var ls = [], jumped = false, end = off;
|
|
23
|
+
for (;;) {
|
|
24
|
+
var len = buf[off];
|
|
25
|
+
if (len === 0) { off++; if (!jumped) end = off; break; }
|
|
26
|
+
if ((len & 0xc0) === 0xc0) { if (!jumped) end = off + 2; off = ((len & 0x3f) << 8) | buf[off + 1]; jumped = true; continue; }
|
|
27
|
+
off++; ls.push(buf.slice(off, off + len).toString("ascii")); off += len;
|
|
28
|
+
}
|
|
29
|
+
return { name: ls.join(".") + (ls.length ? "." : ""), end: end };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Parse a DNSKEY DoH response → { keys: [{rdata, alg, pubkey}], rrsig }.
|
|
33
|
+
function _parse(hex) {
|
|
34
|
+
var buf = Buffer.from(hex, "hex");
|
|
35
|
+
var qd = buf.readUInt16BE(4), an = buf.readUInt16BE(6), off = 12;
|
|
36
|
+
for (var q = 0; q < qd; q++) off = _rdName(buf, off).end + 4;
|
|
37
|
+
var keys = [], rrsig = null;
|
|
38
|
+
for (var i = 0; i < an; i++) {
|
|
39
|
+
off = _rdName(buf, off).end;
|
|
40
|
+
var type = buf.readUInt16BE(off), rdlen = buf.readUInt16BE(off + 8);
|
|
41
|
+
off += 10;
|
|
42
|
+
var rd = buf.slice(off, off + rdlen); off += rdlen;
|
|
43
|
+
if (type === 48) keys.push({ rdata: rd, alg: rd[3], pubkey: rd.slice(4) });
|
|
44
|
+
else if (type === 46) {
|
|
45
|
+
var sn = _rdName(rd, 18);
|
|
46
|
+
rrsig = { algorithm: rd[2], labels: rd[3], originalTtl: rd.readUInt32BE(4), expiration: rd.readUInt32BE(8), inception: rd.readUInt32BE(12), keyTag: rd.readUInt16BE(16), signerName: sn.name, signature: rd.slice(sn.end) };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { keys: keys, rrsig: rrsig };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function _vector(hex, zone) {
|
|
53
|
+
var p = _parse(hex);
|
|
54
|
+
var ksk = p.keys.find(function (k) { return b.network.dns.dnssec.keyTag(k.rdata) === p.rrsig.keyTag; });
|
|
55
|
+
return { zone: zone, keys: p.keys, rrsig: p.rrsig, ksk: ksk, at: new Date(p.rrsig.inception * 1000 + 1000) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function testSurface() {
|
|
59
|
+
check("b.network.dns.dnssec.verifyRrset is a function", typeof b.network.dns.dnssec.verifyRrset === "function");
|
|
60
|
+
check("b.network.dns.dnssec.verifyDs is a function", typeof b.network.dns.dnssec.verifyDs === "function");
|
|
61
|
+
check("b.network.dns.dnssec.keyTag is a function", typeof b.network.dns.dnssec.keyTag === "function");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function testRealVectors() {
|
|
65
|
+
var cf = _vector(CF_HEX, "cloudflare.com");
|
|
66
|
+
check("keyTag computes the real RRSIG key tag (ECDSA)", cf.ksk !== undefined);
|
|
67
|
+
var ecOut = b.network.dns.dnssec.verifyRrset({ name: cf.zone, type: "DNSKEY", rdatas: cf.keys.map(function (k) { return k.rdata; }), rrsig: cf.rrsig, dnskey: { algorithm: cf.ksk.alg, publicKey: cf.ksk.pubkey }, at: cf.at });
|
|
68
|
+
check("verifyRrset: real cloudflare.com DNSKEY self-sig verifies (ECDSAP256SHA256)", ecOut.ok && ecOut.algorithm === "ECDSAP256SHA256");
|
|
69
|
+
|
|
70
|
+
var vs = _vector(VS_HEX, "verisign.com");
|
|
71
|
+
check("keyTag computes the real RRSIG key tag (RSA)", vs.ksk !== undefined);
|
|
72
|
+
var rsaOut = b.network.dns.dnssec.verifyRrset({ name: vs.zone, type: "DNSKEY", rdatas: vs.keys.map(function (k) { return k.rdata; }), rrsig: vs.rrsig, dnskey: { algorithm: vs.ksk.alg, publicKey: vs.ksk.pubkey }, at: vs.at });
|
|
73
|
+
check("verifyRrset: real verisign.com DNSKEY self-sig verifies (RSASHA256)", rsaOut.ok && rsaOut.algorithm === "RSASHA256");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function testRefusals() {
|
|
77
|
+
var cf = _vector(CF_HEX, "cloudflare.com");
|
|
78
|
+
function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
|
|
79
|
+
var base = { name: cf.zone, type: "DNSKEY", rrsig: cf.rrsig, dnskey: { algorithm: cf.ksk.alg, publicKey: cf.ksk.pubkey }, at: cf.at };
|
|
80
|
+
function withRdatas(rd) { return Object.assign({}, base, { rdatas: rd }); }
|
|
81
|
+
|
|
82
|
+
var tampered = cf.keys.map(function (k) { return Buffer.from(k.rdata); });
|
|
83
|
+
tampered[0][tampered[0].length - 1] ^= 0xff;
|
|
84
|
+
check("verifyRrset: tampered RRset refused", code(function () { b.network.dns.dnssec.verifyRrset(withRdatas(tampered)); }) === "dnssec/bad-signature");
|
|
85
|
+
|
|
86
|
+
var rd = cf.keys.map(function (k) { return k.rdata; });
|
|
87
|
+
check("verifyRrset: expired RRSIG refused", code(function () { b.network.dns.dnssec.verifyRrset(Object.assign({}, base, { rdatas: rd, at: new Date((cf.rrsig.expiration + 60) * 1000) })); }) === "dnssec/expired");
|
|
88
|
+
check("verifyRrset: not-yet-valid RRSIG refused", code(function () { b.network.dns.dnssec.verifyRrset(Object.assign({}, base, { rdatas: rd, at: new Date((cf.rrsig.inception - 60) * 1000) })); }) === "dnssec/not-yet-valid");
|
|
89
|
+
check("verifyRrset: invalid opts.at refused", code(function () { b.network.dns.dnssec.verifyRrset(Object.assign({}, base, { rdatas: rd, at: new Date("nope") })); }) === "dnssec/bad-at");
|
|
90
|
+
check("verifyRrset: name-bearing RR type refused (not mis-validated)", code(function () { b.network.dns.dnssec.verifyRrset(Object.assign({}, base, { type: "NS", rdatas: rd })); }) === "dnssec/uncanonicalizable-type");
|
|
91
|
+
check("verifyRrset: DNSKEY/RRSIG algorithm mismatch refused", code(function () { b.network.dns.dnssec.verifyRrset(Object.assign({}, base, { rdatas: rd, dnskey: { algorithm: 8, publicKey: cf.ksk.pubkey } })); }) === "dnssec/alg-mismatch");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function testVerifyDs() {
|
|
95
|
+
var cf = _vector(CF_HEX, "cloudflare.com");
|
|
96
|
+
var nodeCrypto = require("node:crypto");
|
|
97
|
+
// Build the SHA-256 DS digest over (canonical owner name || DNSKEY rdata),
|
|
98
|
+
// then confirm verifyDs accepts it and rejects a tampered digest / key tag.
|
|
99
|
+
function canonName(name) {
|
|
100
|
+
var n = name.replace(/\.$/, ""), parts = [];
|
|
101
|
+
n.split(".").forEach(function (l) { var b2 = Buffer.from(l.toLowerCase(), "ascii"); parts.push(Buffer.from([b2.length]), b2); });
|
|
102
|
+
parts.push(Buffer.from([0]));
|
|
103
|
+
return Buffer.concat(parts);
|
|
104
|
+
}
|
|
105
|
+
var tag = b.network.dns.dnssec.keyTag(cf.ksk.rdata);
|
|
106
|
+
var digest = nodeCrypto.createHash("sha256").update(Buffer.concat([canonName("cloudflare.com"), cf.ksk.rdata])).digest();
|
|
107
|
+
var ds = { keyTag: tag, algorithm: cf.ksk.alg, digestType: 2, digest: digest };
|
|
108
|
+
check("verifyDs: matching DS accepted", b.network.dns.dnssec.verifyDs({ ownerName: "cloudflare.com", dnskeyRdata: cf.ksk.rdata, ds: ds }).ok === true);
|
|
109
|
+
|
|
110
|
+
function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
|
|
111
|
+
var badDigest = Buffer.from(digest); badDigest[0] ^= 0xff;
|
|
112
|
+
check("verifyDs: tampered digest refused", code(function () { b.network.dns.dnssec.verifyDs({ ownerName: "cloudflare.com", dnskeyRdata: cf.ksk.rdata, ds: Object.assign({}, ds, { digest: badDigest }) }); }) === "dnssec/ds-mismatch");
|
|
113
|
+
check("verifyDs: key-tag mismatch refused", code(function () { b.network.dns.dnssec.verifyDs({ ownerName: "cloudflare.com", dnskeyRdata: cf.ksk.rdata, ds: Object.assign({}, ds, { keyTag: (tag + 1) & 0xffff }) }); }) === "dnssec/keytag-mismatch");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function run() {
|
|
117
|
+
testSurface();
|
|
118
|
+
testRealVectors();
|
|
119
|
+
testRefusals();
|
|
120
|
+
testVerifyDs();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { run: run };
|
|
124
|
+
|
|
125
|
+
if (require.main === module) {
|
|
126
|
+
run().then(
|
|
127
|
+
function () { console.log("[dnssec] OK — " + helpers.getChecks() + " checks passed"); },
|
|
128
|
+
function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -76,6 +76,14 @@ async function _makeMdoc(cert, opts) {
|
|
|
76
76
|
["validUntil", validUntilTag],
|
|
77
77
|
])],
|
|
78
78
|
]);
|
|
79
|
+
// Optional deviceKeyInfo.deviceKey (a COSE_Key) for device-auth tests.
|
|
80
|
+
if (opts.deviceJwk) {
|
|
81
|
+
mso.set("deviceKeyInfo", new Map([["deviceKey", new Map([
|
|
82
|
+
[1, 2], [-1, 1],
|
|
83
|
+
[-2, Buffer.from(opts.deviceJwk.x, "base64url")],
|
|
84
|
+
[-3, Buffer.from(opts.deviceJwk.y, "base64url")],
|
|
85
|
+
])]]));
|
|
86
|
+
}
|
|
79
87
|
var payload = cbor.encode(new cbor.Tag(24, cbor.encode(mso)));
|
|
80
88
|
var signed = await b.cose.sign(payload, { alg: opts.alg || "ES256", privateKey: cert.key, unprotectedHeaders: { 33: cert.certDer } });
|
|
81
89
|
var issuerAuth = cbor.decode(signed, { allowedTags: [18, 24] }).value;
|
|
@@ -212,12 +220,56 @@ async function testChainAndInputGuards() {
|
|
|
212
220
|
check("verify: missing algorithms refused", e4 && e4.code === "mdoc/algorithms-required");
|
|
213
221
|
}
|
|
214
222
|
|
|
223
|
+
async function testDeviceAuth() {
|
|
224
|
+
var cert = _makeCert();
|
|
225
|
+
var device = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
226
|
+
var deviceJwk = device.publicKey.export({ format: "jwk" });
|
|
227
|
+
// issuer-signed mdoc carrying the device key in the MSO
|
|
228
|
+
var mdoc = await _makeMdoc(cert, { deviceJwk: deviceJwk });
|
|
229
|
+
var issuer = await b.mdoc.verifyIssuerSigned(mdoc, { algorithms: ["ES256"] });
|
|
230
|
+
check("verifyIssuerSigned: returns the deviceKey", issuer.deviceKey instanceof Map || (issuer.deviceKey && typeof issuer.deviceKey === "object"));
|
|
231
|
+
|
|
232
|
+
// build a DeviceSigned: detached COSE_Sign1 over DeviceAuthentication
|
|
233
|
+
var sessionTranscript = ["DE-bytes", "ER-bytes", ["handover", 1]];
|
|
234
|
+
var deviceNsBytes = new cbor.Tag(24, cbor.encode(new Map()));
|
|
235
|
+
var da = ["DeviceAuthentication", sessionTranscript, DOCTYPE, deviceNsBytes];
|
|
236
|
+
var daBytes = cbor.encode(new cbor.Tag(24, cbor.encode(da)));
|
|
237
|
+
var sig = await b.cose.sign(daBytes, { alg: "ES256", privateKey: device.privateKey, detached: true });
|
|
238
|
+
var sigArr = cbor.decode(sig, { allowedTags: [18, 24] }).value;
|
|
239
|
+
var deviceSigned = new Map([["nameSpaces", deviceNsBytes], ["deviceAuth", new Map([["deviceSignature", sigArr]])]]);
|
|
240
|
+
|
|
241
|
+
var out = await b.mdoc.verifyDeviceAuth({ deviceKey: issuer.deviceKey, deviceSigned: deviceSigned, docType: DOCTYPE, sessionTranscript: sessionTranscript, algorithms: ["ES256"] });
|
|
242
|
+
check("verifyDeviceAuth: verifies with the MSO device key", out.docType === DOCTYPE && out.alg === "ES256");
|
|
243
|
+
|
|
244
|
+
// wrong session transcript → signature fails (binding works)
|
|
245
|
+
var e1 = null;
|
|
246
|
+
try { await b.mdoc.verifyDeviceAuth({ deviceKey: issuer.deviceKey, deviceSigned: deviceSigned, docType: DOCTYPE, sessionTranscript: ["tampered"], algorithms: ["ES256"] }); } catch (e) { e1 = e; }
|
|
247
|
+
check("verifyDeviceAuth: wrong sessionTranscript refused", e1 && e1.code === "cose/bad-signature");
|
|
248
|
+
|
|
249
|
+
// wrong docType → signature fails
|
|
250
|
+
var e2 = null;
|
|
251
|
+
try { await b.mdoc.verifyDeviceAuth({ deviceKey: issuer.deviceKey, deviceSigned: deviceSigned, docType: "org.iso.18013.5.1.other", sessionTranscript: sessionTranscript, algorithms: ["ES256"] }); } catch (e) { e2 = e; }
|
|
252
|
+
check("verifyDeviceAuth: wrong docType refused", e2 && e2.code === "cose/bad-signature");
|
|
253
|
+
|
|
254
|
+
// MAC variant → unsupported (deferred)
|
|
255
|
+
var dsMac = new Map([["nameSpaces", deviceNsBytes], ["deviceAuth", new Map([["deviceMac", [Buffer.alloc(3), new Map(), null, Buffer.alloc(8)]]])]]);
|
|
256
|
+
var e3 = null;
|
|
257
|
+
try { await b.mdoc.verifyDeviceAuth({ deviceKey: issuer.deviceKey, deviceSigned: dsMac, docType: DOCTYPE, sessionTranscript: sessionTranscript, algorithms: ["ES256"] }); } catch (e) { e3 = e; }
|
|
258
|
+
check("verifyDeviceAuth: MAC variant refused (deferred)", e3 && e3.code === "mdoc/device-mac-unsupported");
|
|
259
|
+
|
|
260
|
+
// missing sessionTranscript
|
|
261
|
+
var e4 = null;
|
|
262
|
+
try { await b.mdoc.verifyDeviceAuth({ deviceKey: issuer.deviceKey, deviceSigned: deviceSigned, docType: DOCTYPE, algorithms: ["ES256"] }); } catch (e) { e4 = e; }
|
|
263
|
+
check("verifyDeviceAuth: missing sessionTranscript refused", e4 && e4.code === "mdoc/no-session-transcript");
|
|
264
|
+
}
|
|
265
|
+
|
|
215
266
|
async function run() {
|
|
216
267
|
testSurface();
|
|
217
268
|
await testRoundTrip();
|
|
218
269
|
await testDigestAndSignatureRefusals();
|
|
219
270
|
await testValidityAndDocType();
|
|
220
271
|
await testChainAndInputGuards();
|
|
272
|
+
await testDeviceAuth();
|
|
221
273
|
}
|
|
222
274
|
|
|
223
275
|
module.exports = { run: run };
|
|
@@ -170,12 +170,75 @@ async function testTemporalAndStructural() {
|
|
|
170
170
|
check("verify: object issuer id extracted", oo.issuer === "did:example:obj");
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
async function testPresentation() {
|
|
174
|
+
var HOLDER = nodeCrypto.generateKeyPairSync("ed25519");
|
|
175
|
+
var holderId = "did:example:holder";
|
|
176
|
+
var jws = await b.vc.issue(_cred(), { securing: "jose", alg: "ES256", privateKey: EC.privateKey });
|
|
177
|
+
|
|
178
|
+
// jose VP wrapping a VC, with nonce + audience holder-binding
|
|
179
|
+
var vp = await b.vc.present({
|
|
180
|
+
credentials: [jws], holder: holderId, securing: "jose", alg: "EdDSA", privateKey: HOLDER.privateKey,
|
|
181
|
+
nonce: "chal-1", audience: "https://verifier.example",
|
|
182
|
+
});
|
|
183
|
+
check("present: VP is a compact JWS", typeof vp === "string" && vp.split(".").length === 3);
|
|
184
|
+
check("present: typ vp+jwt", JSON.parse(Buffer.from(vp.split(".")[0], "base64url").toString("utf8")).typ === "vp+jwt");
|
|
185
|
+
|
|
186
|
+
var out = await b.vc.verifyPresentation(vp, {
|
|
187
|
+
algorithms: ["EdDSA"], publicKey: HOLDER.publicKey, expectedHolder: holderId,
|
|
188
|
+
nonce: "chal-1", audience: "https://verifier.example",
|
|
189
|
+
verifyCredentials: true, credentialOpts: { algorithms: ["ES256"], publicKey: EC.publicKey },
|
|
190
|
+
});
|
|
191
|
+
check("verifyPresentation: holder returned", out.holder === holderId && out.securing === "jose");
|
|
192
|
+
check("verifyPresentation: enclosed VC verified", out.credentials.length === 1 && out.credentials[0].credential.credentialSubject.degree === "BS");
|
|
193
|
+
|
|
194
|
+
// cose VP
|
|
195
|
+
var coseVc = await b.vc.issue(_cred(), { securing: "cose", alg: "ES256", privateKey: EC.privateKey });
|
|
196
|
+
var vpc = await b.vc.present({ credentials: [coseVc], holder: holderId, securing: "cose", alg: "EdDSA", privateKey: HOLDER.privateKey });
|
|
197
|
+
var outc = await b.vc.verifyPresentation(vpc, { algorithms: ["EdDSA"], publicKey: HOLDER.publicKey, verifyCredentials: true, credentialOpts: { algorithms: ["ES256"], publicKey: EC.publicKey } });
|
|
198
|
+
check("cose VP: round-trips + enclosed VC verified", outc.holder === holderId && outc.credentials.length === 1);
|
|
199
|
+
|
|
200
|
+
// refusals
|
|
201
|
+
var e1 = null;
|
|
202
|
+
try { await b.vc.verifyPresentation(vp, { algorithms: ["EdDSA"], publicKey: HOLDER.publicKey, nonce: "wrong" }); } catch (e) { e1 = e; }
|
|
203
|
+
check("verifyPresentation: nonce mismatch refused", e1 && e1.code === "vc/nonce-mismatch");
|
|
204
|
+
var e2 = null;
|
|
205
|
+
try { await b.vc.verifyPresentation(vp, { algorithms: ["EdDSA"], publicKey: HOLDER.publicKey, audience: "https://other" }); } catch (e) { e2 = e; }
|
|
206
|
+
check("verifyPresentation: audience mismatch refused", e2 && e2.code === "vc/audience-mismatch");
|
|
207
|
+
var e3 = null;
|
|
208
|
+
try { await b.vc.verifyPresentation(vp, { algorithms: ["EdDSA"], publicKey: HOLDER.publicKey, expectedHolder: "did:example:other" }); } catch (e) { e3 = e; }
|
|
209
|
+
check("verifyPresentation: holder mismatch refused", e3 && e3.code === "vc/holder-mismatch");
|
|
210
|
+
// verifying the VP with the wrong typ (a VC) must fail typ check
|
|
211
|
+
var e4 = null;
|
|
212
|
+
try { await b.vc.verifyPresentation(jws, { algorithms: ["ES256"], publicKey: EC.publicKey }); } catch (e) { e4 = e; }
|
|
213
|
+
check("verifyPresentation: a VC (vc+jwt) is refused as a VP", e4 && e4.code === "vc/bad-typ");
|
|
214
|
+
// a VP cannot be verified as a VC
|
|
215
|
+
var e5 = null;
|
|
216
|
+
try { await b.vc.verify(vp, { algorithms: ["EdDSA"], publicKey: HOLDER.publicKey }); } catch (e) { e5 = e; }
|
|
217
|
+
check("verify: a VP (vp+jwt) is refused as a VC", e5 && e5.code === "vc/bad-typ");
|
|
218
|
+
// present requires credentials + holder
|
|
219
|
+
var e6 = null;
|
|
220
|
+
try { await b.vc.present({ credentials: [], holder: holderId, securing: "jose", alg: "EdDSA", privateKey: HOLDER.privateKey }); } catch (e) { e6 = e; }
|
|
221
|
+
check("present: empty credentials refused", e6 && e6.code === "vc/no-credentials");
|
|
222
|
+
|
|
223
|
+
// A holder-signed VP with a NON-ARRAY verifiableCredential must fail
|
|
224
|
+
// closed (not coerce to [] and skip credential verification).
|
|
225
|
+
var badVp = { "@context": ["https://www.w3.org/ns/credentials/v2"], type: ["VerifiablePresentation"], holder: holderId, verifiableCredential: { foo: "bar" } };
|
|
226
|
+
var h = Buffer.from(JSON.stringify({ alg: "EdDSA", typ: "vp+jwt" }), "utf8").toString("base64url");
|
|
227
|
+
var p = Buffer.from(JSON.stringify(badVp), "utf8").toString("base64url");
|
|
228
|
+
var si = h + "." + p;
|
|
229
|
+
var badVpJws = si + "." + nodeCrypto.sign(null, Buffer.from(si, "ascii"), HOLDER.privateKey).toString("base64url");
|
|
230
|
+
var e7 = null;
|
|
231
|
+
try { await b.vc.verifyPresentation(badVpJws, { algorithms: ["EdDSA"], publicKey: HOLDER.publicKey, verifyCredentials: true, credentialOpts: { algorithms: ["ES256"], publicKey: EC.publicKey } }); } catch (e) { e7 = e; }
|
|
232
|
+
check("verifyPresentation: non-array verifiableCredential refused (no bypass)", e7 && e7.code === "vc/bad-presentation");
|
|
233
|
+
}
|
|
234
|
+
|
|
173
235
|
async function run() {
|
|
174
236
|
testSurface();
|
|
175
237
|
await testJoseRoundTrip();
|
|
176
238
|
await testCoseRoundTrip();
|
|
177
239
|
await testRefusals();
|
|
178
240
|
await testTemporalAndStructural();
|
|
241
|
+
await testPresentation();
|
|
179
242
|
}
|
|
180
243
|
|
|
181
244
|
module.exports = { run: run };
|
package/package.json
CHANGED