@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.
Files changed (31) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +1 -1
  3. package/lib/admin.js +274 -7
  4. package/lib/vendor/MANIFEST.json +2 -2
  5. package/lib/vendor/blamejs/CHANGELOG.md +14 -0
  6. package/lib/vendor/blamejs/README.md +6 -5
  7. package/lib/vendor/blamejs/SECURITY.md +2 -0
  8. package/lib/vendor/blamejs/api-snapshot.json +166 -3
  9. package/lib/vendor/blamejs/lib/cose.js +284 -10
  10. package/lib/vendor/blamejs/lib/crypto.js +119 -0
  11. package/lib/vendor/blamejs/lib/did.js +69 -20
  12. package/lib/vendor/blamejs/lib/mdoc.js +122 -0
  13. package/lib/vendor/blamejs/lib/network-dnssec.js +328 -0
  14. package/lib/vendor/blamejs/lib/network.js +1 -0
  15. package/lib/vendor/blamejs/lib/vc.js +231 -33
  16. package/lib/vendor/blamejs/package.json +1 -1
  17. package/lib/vendor/blamejs/release-notes/v0.12.42.json +18 -0
  18. package/lib/vendor/blamejs/release-notes/v0.12.43.json +18 -0
  19. package/lib/vendor/blamejs/release-notes/v0.12.44.json +18 -0
  20. package/lib/vendor/blamejs/release-notes/v0.12.45.json +18 -0
  21. package/lib/vendor/blamejs/release-notes/v0.12.46.json +18 -0
  22. package/lib/vendor/blamejs/release-notes/v0.12.47.json +18 -0
  23. package/lib/vendor/blamejs/release-notes/v0.12.48.json +22 -0
  24. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +38 -1
  25. package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +101 -2
  26. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-self-test.test.js +74 -0
  27. package/lib/vendor/blamejs/test/layer-0-primitives/did.test.js +29 -0
  28. package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +130 -0
  29. package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +52 -0
  30. package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +63 -0
  31. 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
- * Two methods are supported. <strong>did:key</strong> encodes a public
16
- * key directly in the identifier (multicodec + base58btc multibase),
17
- * so resolution is deterministic and offline Ed25519, P-256, P-384,
18
- * and secp256k1 keys round-trip. <strong>did:web</strong> places the
19
- * DID document at an HTTPS URL derived from the identifier; the network
20
- * fetch is the operator's to make (the same operator-supplied-input
21
- * stance as the rest of the framework), and <code>resolve</code> takes
22
- * the fetched document and extracts its verification methods.
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 KeyObjects for
40
- * the credential verifiers. did:key is deterministic + offline
41
- * (Ed25519 / P-256 / P-384 / secp256k1); did:web parses an
42
- * operator-fetched DID document. Composes node:crypto; no new dep.
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
- * <code>did:key</code> — the inverse of resolution, for an issuer that
234
- * names itself by its key. Ed25519, P-256, P-384, and secp256k1 are
235
- * supported.
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); // → "did:key:z6Mk…"
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> resolves deterministically and offline.
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
  };