@blamejs/blamejs-shop 0.1.0 → 0.1.2
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 +4 -0
- package/README.md +2 -2
- package/lib/admin.js +299 -7
- package/lib/payment.js +27 -0
- package/lib/storefront.js +25 -6
- 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
|
@@ -62,6 +62,9 @@ var audit = lazyRequire(function () { return require("./audit"); });
|
|
|
62
62
|
// loaded because safe-buffer.js itself imports b.crypto for
|
|
63
63
|
// hex-compare helpers (circular).
|
|
64
64
|
var safeBuffer = lazyRequire(function () { return require("./safe-buffer"); });
|
|
65
|
+
// pqc-software requires this module (b.crypto) — lazy-load to break the
|
|
66
|
+
// cycle. Only the power-on self-test needs it here.
|
|
67
|
+
var pqcSoftware = lazyRequire(function () { return require("./pqc-software"); });
|
|
65
68
|
|
|
66
69
|
// Streaming-hash algorithm allowlist. Mirrors the framework's PQC-
|
|
67
70
|
// first crypto policy: SHA3 / SHAKE family is the default surface;
|
|
@@ -1873,8 +1876,124 @@ var SUPPORTED_KEM_ALGORITHMS = Object.freeze([
|
|
|
1873
1876
|
// var fixtures = require("blamejs/lib/_test/crypto-fixtures");
|
|
1874
1877
|
// var blob = fixtures.mintLegacyEnvelope0xE1(plaintext, recipient);
|
|
1875
1878
|
|
|
1879
|
+
// ---- FIPS 140-3-style power-on self-test ----
|
|
1880
|
+
//
|
|
1881
|
+
// Known-answer tests (KATs) for the deterministic primitives — the
|
|
1882
|
+
// hash / XOF digests are NIST FIPS 202 published vectors, so this
|
|
1883
|
+
// confirms the framework's hashing matches the standard, not merely
|
|
1884
|
+
// itself. The PQC algorithms have no seed-injection API (node generates
|
|
1885
|
+
// the keypair randomness internally), so they get FIPS 140-3 §10.3
|
|
1886
|
+
// pairwise-consistency + negative tests (a fresh keypair must
|
|
1887
|
+
// sign->verify / encaps->decaps consistently, and a tampered signature
|
|
1888
|
+
// must be rejected) rather than a fixed-seed KAT.
|
|
1889
|
+
var KAT_SHA3_512_ABC = "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0";
|
|
1890
|
+
var KAT_SHA3_256_ABC = "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532";
|
|
1891
|
+
var KAT_SHAKE256_ABC_32 = "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739";
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* @primitive b.crypto.selfTest
|
|
1895
|
+
* @signature b.crypto.selfTest(opts?)
|
|
1896
|
+
* @since 0.12.43
|
|
1897
|
+
* @status stable
|
|
1898
|
+
* @compliance soc2, hipaa, pci-dss
|
|
1899
|
+
* @related b.crypto.sha3Hash, b.crypto.encrypt
|
|
1900
|
+
*
|
|
1901
|
+
* Run a power-on self-test over the framework's cryptographic
|
|
1902
|
+
* primitives — the integrity check FIPS 140-3 requires of a validated
|
|
1903
|
+
* module. The hash / XOF checks are known-answer tests against NIST FIPS
|
|
1904
|
+
* 202 vectors (SHA3-256 / SHA3-512 / SHAKE256); the AEAD check
|
|
1905
|
+
* round-trips XChaCha20-Poly1305 and confirms a tampered ciphertext is
|
|
1906
|
+
* rejected; the post-quantum checks run a pairwise-consistency +
|
|
1907
|
+
* negative test for ML-KEM-1024, ML-DSA-87, and SLH-DSA-SHAKE-256f.
|
|
1908
|
+
* Returns a structured report and, by default, throws on any failure so
|
|
1909
|
+
* a broken crypto stack fails closed at boot rather than silently
|
|
1910
|
+
* producing bad output.
|
|
1911
|
+
*
|
|
1912
|
+
* @opts
|
|
1913
|
+
* {
|
|
1914
|
+
* throwOnFailure?: boolean, // default true — throw if any check fails
|
|
1915
|
+
* }
|
|
1916
|
+
*
|
|
1917
|
+
* @example
|
|
1918
|
+
* var report = b.crypto.selfTest();
|
|
1919
|
+
* // -> { ok: true, results: [ { name, ok }, ... ], failures: [], ranAt }
|
|
1920
|
+
*/
|
|
1921
|
+
function selfTest(opts) {
|
|
1922
|
+
opts = opts || {};
|
|
1923
|
+
var results = [];
|
|
1924
|
+
function record(name, fn) {
|
|
1925
|
+
try { fn(); results.push({ name: name, ok: true }); }
|
|
1926
|
+
catch (e) { results.push({ name: name, ok: false, detail: (e && e.message) || String(e) }); }
|
|
1927
|
+
}
|
|
1928
|
+
function assert(cond, msg) { if (!cond) throw new Error(msg); }
|
|
1929
|
+
|
|
1930
|
+
record("SHA3-512 KAT (FIPS 202)", function () {
|
|
1931
|
+
assert(sha3Hash("abc") === KAT_SHA3_512_ABC, "SHA3-512(\"abc\") does not match the FIPS 202 vector");
|
|
1932
|
+
});
|
|
1933
|
+
record("SHA3-256 KAT (FIPS 202)", function () {
|
|
1934
|
+
assert(hash("abc", "sha3-256").toString("hex") === KAT_SHA3_256_ABC, "SHA3-256(\"abc\") does not match the FIPS 202 vector");
|
|
1935
|
+
});
|
|
1936
|
+
record("SHAKE256 KAT (FIPS 202)", function () {
|
|
1937
|
+
assert(hash("abc", "shake256", C.BYTES.bytes(32)).toString("hex") === KAT_SHAKE256_ABC_32, "SHAKE256(\"abc\") does not match the FIPS 202 vector");
|
|
1938
|
+
});
|
|
1939
|
+
record("HMAC-SHA3-512 determinism", function () {
|
|
1940
|
+
var k = Buffer.from("self-test-hmac-key", "utf8");
|
|
1941
|
+
assert(timingSafeEqual(hmacSha3(k, "abc"), hmacSha3(k, "abc")), "HMAC-SHA3-512 is not deterministic");
|
|
1942
|
+
assert(!timingSafeEqual(hmacSha3(k, "abc"), hmacSha3(k, "abd")), "HMAC-SHA3-512 collided on distinct inputs");
|
|
1943
|
+
});
|
|
1944
|
+
record("XChaCha20-Poly1305 round-trip + tamper-detect", function () {
|
|
1945
|
+
var key = generateBytes(C.BYTES.bytes(32));
|
|
1946
|
+
var pt = Buffer.from("blamejs crypto self-test plaintext", "utf8");
|
|
1947
|
+
var packed = encryptPacked(pt, key);
|
|
1948
|
+
assert(decryptPacked(packed, key).equals(pt), "AEAD round-trip did not recover the plaintext");
|
|
1949
|
+
var bad = Buffer.from(packed); bad[bad.length - 1] ^= 0xff;
|
|
1950
|
+
var rejected = false;
|
|
1951
|
+
try { decryptPacked(bad, key); } catch (_e) { rejected = true; }
|
|
1952
|
+
assert(rejected, "AEAD accepted a tampered ciphertext");
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
// Post-quantum pairwise-consistency + negative tests. PQC is an
|
|
1956
|
+
// optional vendored dependency — a load failure surfaces as a failed
|
|
1957
|
+
// check rather than a silent skip.
|
|
1958
|
+
var pqc = pqcSoftware();
|
|
1959
|
+
record("ML-KEM-1024 encaps/decaps pairwise consistency", function () {
|
|
1960
|
+
var kp = pqc.ml_kem_1024.keygen();
|
|
1961
|
+
var enc = pqc.ml_kem_1024.encapsulate(kp.publicKey);
|
|
1962
|
+
var ss = pqc.ml_kem_1024.decapsulate(enc.cipherText, kp.secretKey);
|
|
1963
|
+
assert(timingSafeEqual(Buffer.from(ss), Buffer.from(enc.sharedSecret)), "ML-KEM-1024 decapsulated secret does not match");
|
|
1964
|
+
});
|
|
1965
|
+
record("ML-DSA-87 sign/verify + negative", function () {
|
|
1966
|
+
var kp = pqc.ml_dsa_87.keygen();
|
|
1967
|
+
var msg = Buffer.from("blamejs ML-DSA self-test", "utf8");
|
|
1968
|
+
var sig = pqc.ml_dsa_87.sign(msg, kp.secretKey);
|
|
1969
|
+
assert(pqc.ml_dsa_87.verify(sig, msg, kp.publicKey), "ML-DSA-87 rejected a valid signature");
|
|
1970
|
+
var bad = Buffer.from(sig); bad[0] ^= 0xff;
|
|
1971
|
+
assert(!pqc.ml_dsa_87.verify(bad, msg, kp.publicKey), "ML-DSA-87 accepted a tampered signature");
|
|
1972
|
+
});
|
|
1973
|
+
record("SLH-DSA-SHAKE-256f sign/verify + negative", function () {
|
|
1974
|
+
var kp = pqc.slh_dsa_shake_256f.keygen();
|
|
1975
|
+
var msg = Buffer.from("blamejs SLH-DSA self-test", "utf8");
|
|
1976
|
+
var sig = pqc.slh_dsa_shake_256f.sign(msg, kp.secretKey);
|
|
1977
|
+
assert(pqc.slh_dsa_shake_256f.verify(sig, msg, kp.publicKey), "SLH-DSA-SHAKE-256f rejected a valid signature");
|
|
1978
|
+
var bad = Buffer.from(sig); bad[0] ^= 0xff;
|
|
1979
|
+
assert(!pqc.slh_dsa_shake_256f.verify(bad, msg, kp.publicKey), "SLH-DSA-SHAKE-256f accepted a tampered signature");
|
|
1980
|
+
});
|
|
1981
|
+
|
|
1982
|
+
var failures = results.filter(function (r) { return !r.ok; });
|
|
1983
|
+
var report = { ok: failures.length === 0, results: results, failures: failures, ranAt: new Date().toISOString() };
|
|
1984
|
+
if (failures.length && opts.throwOnFailure !== false) {
|
|
1985
|
+
var err = new Error("crypto.selfTest: " + failures.length + " self-test(s) failed: " +
|
|
1986
|
+
failures.map(function (f) { return f.name; }).join("; "));
|
|
1987
|
+
err.code = "crypto/self-test-failed";
|
|
1988
|
+
err.report = report;
|
|
1989
|
+
throw err;
|
|
1990
|
+
}
|
|
1991
|
+
return report;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1876
1994
|
module.exports = {
|
|
1877
1995
|
sri: sri,
|
|
1996
|
+
selfTest: selfTest,
|
|
1878
1997
|
// Hashing
|
|
1879
1998
|
sha3Hash: sha3Hash,
|
|
1880
1999
|
hmacSha3: hmacSha3,
|
|
@@ -12,14 +12,16 @@
|
|
|
12
12
|
* <code>b.scitt</code> credential to a <code>node:crypto</code>
|
|
13
13
|
* KeyObject, then hand that key to the verifier.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
* key directly in the identifier (multicodec + base58btc
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
15
|
+
* Three methods are supported. <strong>did:key</strong> encodes a
|
|
16
|
+
* public key directly in the identifier (multicodec + base58btc
|
|
17
|
+
* multibase) and <strong>did:jwk</strong> encodes it as a base64url
|
|
18
|
+
* public JWK — both resolve deterministically and offline (Ed25519,
|
|
19
|
+
* P-256, P-384, and secp256k1 round-trip). <strong>did:web</strong>
|
|
20
|
+
* places the DID document at an HTTPS URL derived from the identifier;
|
|
21
|
+
* the network fetch is the operator's to make (the same
|
|
22
|
+
* operator-supplied-input stance as the rest of the framework), and
|
|
23
|
+
* <code>resolve</code> takes the fetched document and extracts its
|
|
24
|
+
* verification methods.
|
|
23
25
|
*
|
|
24
26
|
* <code>b.did.keyToDid(publicKey)</code> produces a did:key from a
|
|
25
27
|
* KeyObject (an issuer naming itself); <code>b.did.parse(did)</code>
|
|
@@ -36,13 +38,15 @@
|
|
|
36
38
|
* deployed and interoperable today; pin the dependency deliberately.
|
|
37
39
|
*
|
|
38
40
|
* @card
|
|
39
|
-
* W3C DID resolution (did:key + did:web) → verification
|
|
40
|
-
* the credential verifiers. did:key
|
|
41
|
-
* (Ed25519 / P-256 / P-384 / secp256k1);
|
|
42
|
-
* operator-fetched DID document. Composes
|
|
41
|
+
* W3C DID resolution (did:key + did:jwk + did:web) → verification
|
|
42
|
+
* KeyObjects for the credential verifiers. did:key + did:jwk are
|
|
43
|
+
* deterministic + offline (Ed25519 / P-256 / P-384 / secp256k1);
|
|
44
|
+
* did:web parses an operator-fetched DID document. Composes
|
|
45
|
+
* node:crypto; no new dep.
|
|
43
46
|
*/
|
|
44
47
|
|
|
45
48
|
var nodeCrypto = require("node:crypto");
|
|
49
|
+
var safeJson = require("./safe-json");
|
|
46
50
|
var validateOpts = require("./validate-opts");
|
|
47
51
|
var { defineClass } = require("./framework-error");
|
|
48
52
|
|
|
@@ -55,6 +59,7 @@ var B58_MAP = (function () {
|
|
|
55
59
|
return m;
|
|
56
60
|
})();
|
|
57
61
|
var MAX_MULTIBASE_CHARS = 1024; // allow:raw-byte-literal — bounded did:key multibase length (DoS cap)
|
|
62
|
+
var MAX_JWK_B64_CHARS = 8192; // allow:raw-byte-literal — bounded did:jwk encoded-JWK length (DoS cap)
|
|
58
63
|
|
|
59
64
|
// multicodec public-key codes (unsigned-varint) → curve descriptor.
|
|
60
65
|
// keyLen is the multicodec payload: Ed25519 raw 32; EC compressed point.
|
|
@@ -224,23 +229,43 @@ function _didWebUrl(id) {
|
|
|
224
229
|
|
|
225
230
|
/**
|
|
226
231
|
* @primitive b.did.keyToDid
|
|
227
|
-
* @signature b.did.keyToDid(publicKey)
|
|
232
|
+
* @signature b.did.keyToDid(publicKey, opts?)
|
|
228
233
|
* @since 0.12.41
|
|
229
234
|
* @status experimental
|
|
230
235
|
* @related b.did.resolve
|
|
231
236
|
*
|
|
232
237
|
* Encode a public key (a <code>node:crypto</code> KeyObject or PEM) as a
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
238
|
+
* DID — the inverse of resolution, for an issuer that names itself by
|
|
239
|
+
* its key. Defaults to <code>did:key</code> (multicodec + base58btc);
|
|
240
|
+
* pass <code>opts.method = "jwk"</code> for <code>did:jwk</code>
|
|
241
|
+
* (base64url-encoded public JWK). Ed25519, P-256, P-384, and secp256k1
|
|
242
|
+
* are supported.
|
|
243
|
+
*
|
|
244
|
+
* @opts
|
|
245
|
+
* {
|
|
246
|
+
* method: string, // "key" (default) | "jwk"
|
|
247
|
+
* }
|
|
236
248
|
*
|
|
237
249
|
* @example
|
|
238
|
-
* var did = b.did.keyToDid(issuerPublicKey);
|
|
250
|
+
* var did = b.did.keyToDid(issuerPublicKey); // → "did:key:z6Mk…"
|
|
251
|
+
* var dj = b.did.keyToDid(issuerPublicKey, { method: "jwk" }); // → "did:jwk:eyJr…"
|
|
239
252
|
*/
|
|
240
|
-
function keyToDid(publicKey) {
|
|
253
|
+
function keyToDid(publicKey, opts) {
|
|
241
254
|
var key = (publicKey && typeof publicKey === "object" && publicKey.asymmetricKeyType)
|
|
242
255
|
? publicKey : nodeCrypto.createPublicKey(publicKey);
|
|
243
256
|
var jwk = key.export({ format: "jwk" });
|
|
257
|
+
if (opts && opts.method === "jwk") {
|
|
258
|
+
// did:jwk — base64url(UTF-8(JSON of the PUBLIC JWK)). Strip any
|
|
259
|
+
// private member defensively (a public KeyObject has none, but a
|
|
260
|
+
// caller could pass a private key by mistake).
|
|
261
|
+
var pub = {};
|
|
262
|
+
Object.keys(jwk).forEach(function (k) { if (k !== "d") pub[k] = jwk[k]; });
|
|
263
|
+
// Gate on the same kty/crv allowlist resolution enforces, so a
|
|
264
|
+
// produced did:jwk always round-trips (no generate-succeeds /
|
|
265
|
+
// resolve-fails RSA-style identifiers).
|
|
266
|
+
_jwkToKey(pub);
|
|
267
|
+
return "did:jwk:" + Buffer.from(JSON.stringify(pub), "utf8").toString("base64url");
|
|
268
|
+
}
|
|
244
269
|
var code, payload;
|
|
245
270
|
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") {
|
|
246
271
|
code = NAME_TO_CODE["Ed25519"];
|
|
@@ -266,8 +291,8 @@ function keyToDid(publicKey) {
|
|
|
266
291
|
*
|
|
267
292
|
* Resolve a DID to its document and verification methods (each with a
|
|
268
293
|
* <code>node:crypto</code> public KeyObject ready for a verifier).
|
|
269
|
-
* <code>did:key</code>
|
|
270
|
-
* <code>did:web</code> requires the operator to supply the fetched DID
|
|
294
|
+
* <code>did:key</code> and <code>did:jwk</code> resolve deterministically
|
|
295
|
+
* and offline. <code>did:web</code> requires the operator to supply the fetched DID
|
|
271
296
|
* document as <code>opts.document</code> (the network fetch is the
|
|
272
297
|
* operator's; the URL to fetch is on <code>b.did.parse(did).url</code>).
|
|
273
298
|
*
|
|
@@ -304,6 +329,30 @@ function resolve(did, opts) {
|
|
|
304
329
|
return { didDocument: doc, verificationMethods: [vm] };
|
|
305
330
|
}
|
|
306
331
|
|
|
332
|
+
if (parsed.method === "jwk") {
|
|
333
|
+
if (parsed.id.length > MAX_JWK_B64_CHARS) {
|
|
334
|
+
throw new DidError("did/too-long", "did:jwk: encoded JWK exceeds the " + MAX_JWK_B64_CHARS + "-char cap");
|
|
335
|
+
}
|
|
336
|
+
var jwkJson = Buffer.from(parsed.id, "base64url").toString("utf8");
|
|
337
|
+
var jwk;
|
|
338
|
+
try { jwk = safeJson.parse(jwkJson, { maxBytes: MAX_JWK_B64_CHARS }); } catch (_e) {
|
|
339
|
+
throw new DidError("did/bad-jwk", "did:jwk: method-specific id is not base64url-encoded JSON");
|
|
340
|
+
}
|
|
341
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
342
|
+
throw new DidError("did/bad-jwk", "did:jwk: decoded value is not a JWK object");
|
|
343
|
+
}
|
|
344
|
+
var jwkKey = _jwkToKey(jwk); // kty/crv allowlisted
|
|
345
|
+
var jwkVmId = did + "#0";
|
|
346
|
+
var jwkVm = { id: jwkVmId, controller: did, type: "JsonWebKey2020", publicKey: jwkKey };
|
|
347
|
+
var jwkDoc = {
|
|
348
|
+
"@context": ["https://www.w3.org/ns/did/v1"],
|
|
349
|
+
id: did,
|
|
350
|
+
verificationMethod: [{ id: jwkVmId, controller: did, type: "JsonWebKey2020", publicKeyJwk: jwk }],
|
|
351
|
+
assertionMethod: [jwkVmId], authentication: [jwkVmId],
|
|
352
|
+
};
|
|
353
|
+
return { didDocument: jwkDoc, verificationMethods: [jwkVm] };
|
|
354
|
+
}
|
|
355
|
+
|
|
307
356
|
if (parsed.method === "web") {
|
|
308
357
|
if (!opts.document || typeof opts.document !== "object") {
|
|
309
358
|
throw new DidError("did/document-required",
|
|
@@ -251,17 +251,138 @@ async function verifyIssuerSigned(issuerSigned, opts) {
|
|
|
251
251
|
_verifyChain(chain, anchors, at);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// The device key (MSO deviceKeyInfo.deviceKey, a COSE_Key) binds the
|
|
255
|
+
// holder — surfaced for b.mdoc.verifyDeviceAuth.
|
|
256
|
+
var deviceKeyInfo = _mapGet(mso, "deviceKeyInfo");
|
|
257
|
+
var deviceKey = deviceKeyInfo ? _mapGet(deviceKeyInfo, "deviceKey") : undefined;
|
|
258
|
+
|
|
254
259
|
return {
|
|
255
260
|
docType: docType,
|
|
256
261
|
version: _mapGet(mso, "version"),
|
|
257
262
|
digestAlgorithm: digestAlgName,
|
|
258
263
|
validityInfo: { validFrom: new Date(validFromMs), validUntil: new Date(validUntilMs) },
|
|
259
264
|
namespaces: out,
|
|
265
|
+
deviceKey: deviceKey,
|
|
260
266
|
signerCert: signerCert.toString(),
|
|
261
267
|
alg: verified.alg,
|
|
262
268
|
};
|
|
263
269
|
}
|
|
264
270
|
|
|
271
|
+
/**
|
|
272
|
+
* @primitive b.mdoc.verifyDeviceAuth
|
|
273
|
+
* @signature b.mdoc.verifyDeviceAuth(opts)
|
|
274
|
+
* @since 0.12.46
|
|
275
|
+
* @status experimental
|
|
276
|
+
* @compliance gdpr, soc2
|
|
277
|
+
* @related b.mdoc.verifyIssuerSigned, b.cose.verify
|
|
278
|
+
*
|
|
279
|
+
* Verify the device-authentication half of an ISO 18013-5 mdoc (§9.1.3,
|
|
280
|
+
* signature variant) — the proof that the holder controls the device key
|
|
281
|
+
* the issuer bound into the MSO, which stops a captured issuer-signed
|
|
282
|
+
* document from being replayed by anyone else. The device's COSE_Sign1
|
|
283
|
+
* (<code>deviceSigned.deviceAuth.deviceSignature</code>) is verified over
|
|
284
|
+
* the detached DeviceAuthentication structure
|
|
285
|
+
* (<code>["DeviceAuthentication", SessionTranscript, DocType,
|
|
286
|
+
* DeviceNameSpacesBytes]</code>) with the device key from the issuer-signed
|
|
287
|
+
* MSO (<code>verifyIssuerSigned(...).deviceKey</code>). The
|
|
288
|
+
* <code>sessionTranscript</code> binds the proof to this exact exchange
|
|
289
|
+
* and is supplied by the operator (the presentation protocol — e.g.
|
|
290
|
+
* OpenID4VP — defines it). The MAC variant (<code>deviceMac</code> /
|
|
291
|
+
* COSE_Mac0, used in proximity flows with a reader ephemeral key) is not
|
|
292
|
+
* yet supported and is refused with <code>mdoc/device-mac-unsupported</code>.
|
|
293
|
+
*
|
|
294
|
+
* @opts
|
|
295
|
+
* {
|
|
296
|
+
* deviceKey: object, // COSE_Key (from verifyIssuerSigned().deviceKey) or a KeyObject / PEM
|
|
297
|
+
* deviceSigned: object, // the DeviceSigned structure (CBOR bytes or decoded)
|
|
298
|
+
* docType: string, // the document type (must match the issuer-signed docType)
|
|
299
|
+
* sessionTranscript: any, // the SessionTranscript (CBOR bytes or decoded) bound by the protocol
|
|
300
|
+
* algorithms: string[], // required — accepted COSE alg names (ES256/384/512, EdDSA)
|
|
301
|
+
* maxBytes: number, // forwarded to b.cbor.decode
|
|
302
|
+
* maxDepth: number,
|
|
303
|
+
* }
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* var issuer = await b.mdoc.verifyIssuerSigned(issuerSignedBytes, { algorithms: ["ES256"] });
|
|
307
|
+
* var dev = await b.mdoc.verifyDeviceAuth({ deviceKey: issuer.deviceKey, deviceSigned: deviceSignedBytes, docType: issuer.docType, sessionTranscript: transcript, algorithms: ["ES256"] });
|
|
308
|
+
* // → { docType, alg, deviceNamespaces }
|
|
309
|
+
*/
|
|
310
|
+
async function verifyDeviceAuth(opts) {
|
|
311
|
+
validateOpts.requireObject(opts, "mdoc.verifyDeviceAuth", MdocError);
|
|
312
|
+
validateOpts(opts, ["deviceKey", "deviceSigned", "docType", "sessionTranscript", "algorithms", "maxBytes", "maxDepth"], "mdoc.verifyDeviceAuth");
|
|
313
|
+
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
314
|
+
throw new MdocError("mdoc/algorithms-required", "mdoc.verifyDeviceAuth: opts.algorithms is required");
|
|
315
|
+
}
|
|
316
|
+
if (typeof opts.docType !== "string" || !opts.docType) {
|
|
317
|
+
throw new MdocError("mdoc/bad-input", "mdoc.verifyDeviceAuth: opts.docType is required");
|
|
318
|
+
}
|
|
319
|
+
if (opts.sessionTranscript === undefined || opts.sessionTranscript === null) {
|
|
320
|
+
throw new MdocError("mdoc/no-session-transcript", "mdoc.verifyDeviceAuth: opts.sessionTranscript is required (the protocol-bound transcript)");
|
|
321
|
+
}
|
|
322
|
+
var decodeOpts = { allowedTags: ALLOWED_TAGS, maxBytes: opts.maxBytes, maxDepth: opts.maxDepth };
|
|
323
|
+
|
|
324
|
+
// Device key → KeyObject. Accept a COSE_Key (Map/object) via importKey,
|
|
325
|
+
// or an already-loaded KeyObject / PEM.
|
|
326
|
+
var deviceKeyObj;
|
|
327
|
+
if (opts.deviceKey && typeof opts.deviceKey === "object" && typeof opts.deviceKey.asymmetricKeyType === "string") {
|
|
328
|
+
deviceKeyObj = opts.deviceKey;
|
|
329
|
+
} else if (opts.deviceKey instanceof Map || (opts.deviceKey && typeof opts.deviceKey === "object")) {
|
|
330
|
+
deviceKeyObj = cose.importKey(opts.deviceKey);
|
|
331
|
+
} else if (typeof opts.deviceKey === "string") {
|
|
332
|
+
deviceKeyObj = opts.deviceKey; // PEM, resolved by b.cose
|
|
333
|
+
} else {
|
|
334
|
+
throw new MdocError("mdoc/no-device-key", "mdoc.verifyDeviceAuth: opts.deviceKey is required (a COSE_Key or KeyObject)");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
var ds = (Buffer.isBuffer(opts.deviceSigned) || opts.deviceSigned instanceof Uint8Array)
|
|
338
|
+
? cbor.decode(_bytes(opts.deviceSigned, "deviceSigned"), decodeOpts) : opts.deviceSigned;
|
|
339
|
+
var deviceNameSpaces = _mapGet(ds, "nameSpaces");
|
|
340
|
+
var deviceAuth = _mapGet(ds, "deviceAuth");
|
|
341
|
+
if (!deviceNameSpaces || !deviceAuth) {
|
|
342
|
+
throw new MdocError("mdoc/malformed", "mdoc.verifyDeviceAuth: deviceSigned must have nameSpaces + deviceAuth");
|
|
343
|
+
}
|
|
344
|
+
if (!(deviceNameSpaces instanceof cbor.Tag) || deviceNameSpaces.tag !== TAG_ENCODED_CBOR) {
|
|
345
|
+
throw new MdocError("mdoc/malformed", "mdoc.verifyDeviceAuth: deviceSigned.nameSpaces must be a Tag-24 DeviceNameSpacesBytes");
|
|
346
|
+
}
|
|
347
|
+
var deviceSignature = _mapGet(deviceAuth, "deviceSignature");
|
|
348
|
+
if (!deviceSignature) {
|
|
349
|
+
if (_mapGet(deviceAuth, "deviceMac") !== undefined) {
|
|
350
|
+
throw new MdocError("mdoc/device-mac-unsupported",
|
|
351
|
+
"mdoc.verifyDeviceAuth: the MAC variant (deviceMac / COSE_Mac0) is not supported — only deviceSignature");
|
|
352
|
+
}
|
|
353
|
+
throw new MdocError("mdoc/no-device-signature", "mdoc.verifyDeviceAuth: deviceAuth has no deviceSignature");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
var st = (Buffer.isBuffer(opts.sessionTranscript) || opts.sessionTranscript instanceof Uint8Array)
|
|
357
|
+
? cbor.decode(_bytes(opts.sessionTranscript, "sessionTranscript"), decodeOpts) : opts.sessionTranscript;
|
|
358
|
+
|
|
359
|
+
// DeviceAuthentication (ISO 18013-5 §9.1.3.4); the detached payload is
|
|
360
|
+
// its Tag-24-wrapped CBOR.
|
|
361
|
+
var deviceAuthentication = ["DeviceAuthentication", st, opts.docType, deviceNameSpaces];
|
|
362
|
+
var deviceAuthBytes = cbor.encode(new cbor.Tag(TAG_ENCODED_CBOR, cbor.encode(deviceAuthentication)));
|
|
363
|
+
|
|
364
|
+
var coseBytes = Array.isArray(deviceSignature) ? cbor.encode(deviceSignature) : _bytes(deviceSignature, "deviceSignature");
|
|
365
|
+
var out = await cose.verify(coseBytes, {
|
|
366
|
+
algorithms: opts.algorithms,
|
|
367
|
+
keyResolver: function () { return deviceKeyObj; },
|
|
368
|
+
externalPayload: deviceAuthBytes,
|
|
369
|
+
maxBytes: opts.maxBytes,
|
|
370
|
+
maxDepth: opts.maxDepth,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
var deviceNamespaces = {};
|
|
374
|
+
try {
|
|
375
|
+
var dns = cbor.decode(deviceNameSpaces.value, decodeOpts);
|
|
376
|
+
if (dns instanceof Map) {
|
|
377
|
+
dns.forEach(function (items, ns) {
|
|
378
|
+
deviceNamespaces[ns] = items instanceof Map ? Object.fromEntries(items) : items;
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
} catch (_e) { /* device-released namespaces are optional + advisory */ }
|
|
382
|
+
|
|
383
|
+
return { docType: opts.docType, alg: out.alg, deviceNamespaces: deviceNamespaces };
|
|
384
|
+
}
|
|
385
|
+
|
|
265
386
|
// Verify the leaf (chain[0]) chains to a supplied anchor and every cert
|
|
266
387
|
// is valid at `at`. Intermediates in the x5chain are consulted.
|
|
267
388
|
function _verifyChain(chainDer, anchorsPem, at) {
|
|
@@ -300,6 +421,7 @@ function _assertValidAt(cert, atMs) {
|
|
|
300
421
|
|
|
301
422
|
module.exports = {
|
|
302
423
|
verifyIssuerSigned: verifyIssuerSigned,
|
|
424
|
+
verifyDeviceAuth: verifyDeviceAuth,
|
|
303
425
|
DIGEST_ALGS: DIGEST_ALGS,
|
|
304
426
|
MdocError: MdocError,
|
|
305
427
|
};
|