@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
|
@@ -278,6 +278,7 @@ This is the minimum-viable security posture for a production deployment. The fra
|
|
|
278
278
|
- [ ] Confirm `vault: { mode: "wrapped" }` in the app's config (not `"plaintext"`)
|
|
279
279
|
- [ ] Store the passphrase in a secret manager (1Password / Vault / AWS Secrets Manager / sops) — never in git, never in shell history
|
|
280
280
|
- [ ] Rotate the vault passphrase quarterly: `blamejs vault rotate`
|
|
281
|
+
- [ ] In FIPS / regulated deployments, run `b.crypto.selfTest()` at start-up as a power-on integrity gate — it KATs SHA3/SHAKE against NIST FIPS 202 vectors and pairwise-tests ML-KEM-1024 / ML-DSA-87 / SLH-DSA-SHAKE-256f, throwing `crypto/self-test-failed` (fail closed) if the crypto stack is broken
|
|
281
282
|
|
|
282
283
|
**Audit chain**
|
|
283
284
|
- [ ] Run `blamejs audit verify-chain --db <path>` weekly via cron — walks the live audit chain end-to-end and reports tampering with `breakAt` / `breakRowId` / expected-vs-actual prevHash
|
|
@@ -350,6 +351,7 @@ This is the minimum-viable security posture for a production deployment. The fra
|
|
|
350
351
|
- [ ] At boot, before any outbound socket opens: call `b.network.bootFromEnv({ env: process.env, audit: b.audit })` so operator-supplied NTP / DNS / proxy / DPI-trust / TCP socket settings (`BLAMEJS_NTP_*`, `BLAMEJS_DNS_*`, `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`, `BLAMEJS_EXTRA_CA_CERTS`, `BLAMEJS_SOCKET_*`) apply uniformly
|
|
351
352
|
- [ ] If the deployment sits behind a deep-packet-inspection proxy with its own re-signing CA: install the CA via `b.network.tls.addCa("/path/to/corp-ca.pem", { label: "corp-mitm" })` and pass `allowDpiTrust: true` to `b.security.assertProduction` — every CA addition audits with subject + fingerprint so a forensic review can reconstruct the trust path
|
|
352
353
|
- [ ] For authenticated time (HIPAA / PCI / FIPS shops): use `b.network.ntp.nts.query({ host: ntsKeServer })` (RFC 8915) instead of plain SNTP; set `BLAMEJS_NTS_REQUIRE=1` to fail closed on negotiation failure
|
|
354
|
+
- [ ] When a DNS answer drives a trust decision (DANE / TLSA pinning, SSHFP, CAA enforcement, OPENPGPKEY lookup) and the upstream resolver isn't itself trusted: verify the answer's DNSSEC signature with `b.network.dns.dnssec.verifyRrset(...)` rather than trusting the resolver's AD bit — an on-path or compromised resolver can set AD on a forged answer, but cannot forge the RRSIG. Validate the DNSKEY against the parent's DS with `b.network.dns.dnssec.verifyDs(...)` up the chain to a trust anchor you pin
|
|
353
355
|
- [ ] At boot in production: call `await b.security.assertProduction({ vault: "wrapped", dbAtRest: "encrypted", auditSigning: "wrapped", ntpStrict: true, requireEnv: ["BLAMEJS_VAULT_PASSPHRASE"], dataDir: "./data" })` to refuse to start on weak posture instead of warning
|
|
354
356
|
- [ ] At boot: call `await b.configDrift.create({ dataDir, audit }).checkpoint({ allowedOrigins, csp, vaultMode, ... })` so the next boot detects + audits any silent runtime config change
|
|
355
357
|
- [ ] At boot, before any listener opens: call `b.configDrift.verifyVendorIntegrity({ manifestPath: "./lib/vendor/MANIFEST.json", audit: b.audit })` so a tampered `lib/vendor/*.cjs` artifact aborts start instead of running with a swapped crypto bundle
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"frameworkVersion": "0.12.
|
|
4
|
-
"createdAt": "2026-05-
|
|
3
|
+
"frameworkVersion": "0.12.48",
|
|
4
|
+
"createdAt": "2026-05-25T11:29:28.593Z",
|
|
5
5
|
"exports": {
|
|
6
6
|
"a2a": {
|
|
7
7
|
"type": "object",
|
|
@@ -13501,6 +13501,10 @@
|
|
|
13501
13501
|
"type": "primitive",
|
|
13502
13502
|
"valueType": "number"
|
|
13503
13503
|
},
|
|
13504
|
+
"COSE_MAC0_TAG": {
|
|
13505
|
+
"type": "primitive",
|
|
13506
|
+
"valueType": "number"
|
|
13507
|
+
},
|
|
13504
13508
|
"COSE_SIGN1_TAG": {
|
|
13505
13509
|
"type": "primitive",
|
|
13506
13510
|
"valueType": "number"
|
|
@@ -13509,6 +13513,23 @@
|
|
|
13509
13513
|
"type": "function",
|
|
13510
13514
|
"arity": 4
|
|
13511
13515
|
},
|
|
13516
|
+
"MAC_ALGORITHMS": {
|
|
13517
|
+
"type": "object",
|
|
13518
|
+
"members": {
|
|
13519
|
+
"HMAC-256/256": {
|
|
13520
|
+
"type": "primitive",
|
|
13521
|
+
"valueType": "number"
|
|
13522
|
+
},
|
|
13523
|
+
"HMAC-384/384": {
|
|
13524
|
+
"type": "primitive",
|
|
13525
|
+
"valueType": "number"
|
|
13526
|
+
},
|
|
13527
|
+
"HMAC-512/512": {
|
|
13528
|
+
"type": "primitive",
|
|
13529
|
+
"valueType": "number"
|
|
13530
|
+
}
|
|
13531
|
+
}
|
|
13532
|
+
},
|
|
13512
13533
|
"decrypt0": {
|
|
13513
13534
|
"type": "function",
|
|
13514
13535
|
"arity": 2
|
|
@@ -13517,6 +13538,18 @@
|
|
|
13517
13538
|
"type": "function",
|
|
13518
13539
|
"arity": 2
|
|
13519
13540
|
},
|
|
13541
|
+
"importKey": {
|
|
13542
|
+
"type": "function",
|
|
13543
|
+
"arity": 1
|
|
13544
|
+
},
|
|
13545
|
+
"mac0": {
|
|
13546
|
+
"type": "function",
|
|
13547
|
+
"arity": 2
|
|
13548
|
+
},
|
|
13549
|
+
"macVerify0": {
|
|
13550
|
+
"type": "function",
|
|
13551
|
+
"arity": 2
|
|
13552
|
+
},
|
|
13520
13553
|
"sign": {
|
|
13521
13554
|
"type": "function",
|
|
13522
13555
|
"arity": 2
|
|
@@ -13805,6 +13838,10 @@
|
|
|
13805
13838
|
"type": "function",
|
|
13806
13839
|
"arity": 2
|
|
13807
13840
|
},
|
|
13841
|
+
"selfTest": {
|
|
13842
|
+
"type": "function",
|
|
13843
|
+
"arity": 1
|
|
13844
|
+
},
|
|
13808
13845
|
"sha3Hash": {
|
|
13809
13846
|
"type": "function",
|
|
13810
13847
|
"arity": 1
|
|
@@ -14489,7 +14526,7 @@
|
|
|
14489
14526
|
},
|
|
14490
14527
|
"keyToDid": {
|
|
14491
14528
|
"type": "function",
|
|
14492
|
-
"arity":
|
|
14529
|
+
"arity": 2
|
|
14493
14530
|
},
|
|
14494
14531
|
"parse": {
|
|
14495
14532
|
"type": "function",
|
|
@@ -40433,6 +40470,10 @@
|
|
|
40433
40470
|
"type": "function",
|
|
40434
40471
|
"arity": 4
|
|
40435
40472
|
},
|
|
40473
|
+
"verifyDeviceAuth": {
|
|
40474
|
+
"type": "function",
|
|
40475
|
+
"arity": 1
|
|
40476
|
+
},
|
|
40436
40477
|
"verifyIssuerSigned": {
|
|
40437
40478
|
"type": "function",
|
|
40438
40479
|
"arity": 2
|
|
@@ -41537,6 +41578,120 @@
|
|
|
41537
41578
|
"type": "function",
|
|
41538
41579
|
"arity": 1
|
|
41539
41580
|
},
|
|
41581
|
+
"dnssec": {
|
|
41582
|
+
"type": "object",
|
|
41583
|
+
"members": {
|
|
41584
|
+
"ALGORITHMS": {
|
|
41585
|
+
"type": "object",
|
|
41586
|
+
"members": {
|
|
41587
|
+
"8": {
|
|
41588
|
+
"type": "object",
|
|
41589
|
+
"members": {
|
|
41590
|
+
"hash": {
|
|
41591
|
+
"type": "primitive",
|
|
41592
|
+
"valueType": "string"
|
|
41593
|
+
},
|
|
41594
|
+
"kind": {
|
|
41595
|
+
"type": "primitive",
|
|
41596
|
+
"valueType": "string"
|
|
41597
|
+
},
|
|
41598
|
+
"name": {
|
|
41599
|
+
"type": "primitive",
|
|
41600
|
+
"valueType": "string"
|
|
41601
|
+
}
|
|
41602
|
+
}
|
|
41603
|
+
},
|
|
41604
|
+
"13": {
|
|
41605
|
+
"type": "object",
|
|
41606
|
+
"members": {
|
|
41607
|
+
"coord": {
|
|
41608
|
+
"type": "primitive",
|
|
41609
|
+
"valueType": "number"
|
|
41610
|
+
},
|
|
41611
|
+
"crv": {
|
|
41612
|
+
"type": "primitive",
|
|
41613
|
+
"valueType": "string"
|
|
41614
|
+
},
|
|
41615
|
+
"hash": {
|
|
41616
|
+
"type": "primitive",
|
|
41617
|
+
"valueType": "string"
|
|
41618
|
+
},
|
|
41619
|
+
"kind": {
|
|
41620
|
+
"type": "primitive",
|
|
41621
|
+
"valueType": "string"
|
|
41622
|
+
},
|
|
41623
|
+
"name": {
|
|
41624
|
+
"type": "primitive",
|
|
41625
|
+
"valueType": "string"
|
|
41626
|
+
}
|
|
41627
|
+
}
|
|
41628
|
+
},
|
|
41629
|
+
"14": {
|
|
41630
|
+
"type": "object",
|
|
41631
|
+
"members": {
|
|
41632
|
+
"coord": {
|
|
41633
|
+
"type": "primitive",
|
|
41634
|
+
"valueType": "number"
|
|
41635
|
+
},
|
|
41636
|
+
"crv": {
|
|
41637
|
+
"type": "primitive",
|
|
41638
|
+
"valueType": "string"
|
|
41639
|
+
},
|
|
41640
|
+
"hash": {
|
|
41641
|
+
"type": "primitive",
|
|
41642
|
+
"valueType": "string"
|
|
41643
|
+
},
|
|
41644
|
+
"kind": {
|
|
41645
|
+
"type": "primitive",
|
|
41646
|
+
"valueType": "string"
|
|
41647
|
+
},
|
|
41648
|
+
"name": {
|
|
41649
|
+
"type": "primitive",
|
|
41650
|
+
"valueType": "string"
|
|
41651
|
+
}
|
|
41652
|
+
}
|
|
41653
|
+
},
|
|
41654
|
+
"15": {
|
|
41655
|
+
"type": "object",
|
|
41656
|
+
"members": {
|
|
41657
|
+
"crv": {
|
|
41658
|
+
"type": "primitive",
|
|
41659
|
+
"valueType": "string"
|
|
41660
|
+
},
|
|
41661
|
+
"hash": {
|
|
41662
|
+
"type": "primitive",
|
|
41663
|
+
"valueType": "null"
|
|
41664
|
+
},
|
|
41665
|
+
"kind": {
|
|
41666
|
+
"type": "primitive",
|
|
41667
|
+
"valueType": "string"
|
|
41668
|
+
},
|
|
41669
|
+
"name": {
|
|
41670
|
+
"type": "primitive",
|
|
41671
|
+
"valueType": "string"
|
|
41672
|
+
}
|
|
41673
|
+
}
|
|
41674
|
+
}
|
|
41675
|
+
}
|
|
41676
|
+
},
|
|
41677
|
+
"DnssecError": {
|
|
41678
|
+
"type": "function",
|
|
41679
|
+
"arity": 4
|
|
41680
|
+
},
|
|
41681
|
+
"keyTag": {
|
|
41682
|
+
"type": "function",
|
|
41683
|
+
"arity": 1
|
|
41684
|
+
},
|
|
41685
|
+
"verifyDs": {
|
|
41686
|
+
"type": "function",
|
|
41687
|
+
"arity": 1
|
|
41688
|
+
},
|
|
41689
|
+
"verifyRrset": {
|
|
41690
|
+
"type": "function",
|
|
41691
|
+
"arity": 1
|
|
41692
|
+
}
|
|
41693
|
+
}
|
|
41694
|
+
},
|
|
41540
41695
|
"getServers": {
|
|
41541
41696
|
"type": "function",
|
|
41542
41697
|
"arity": 0
|
|
@@ -49221,9 +49376,17 @@
|
|
|
49221
49376
|
"type": "function",
|
|
49222
49377
|
"arity": 2
|
|
49223
49378
|
},
|
|
49379
|
+
"present": {
|
|
49380
|
+
"type": "function",
|
|
49381
|
+
"arity": 1
|
|
49382
|
+
},
|
|
49224
49383
|
"verify": {
|
|
49225
49384
|
"type": "function",
|
|
49226
49385
|
"arity": 2
|
|
49386
|
+
},
|
|
49387
|
+
"verifyPresentation": {
|
|
49388
|
+
"type": "function",
|
|
49389
|
+
"arity": 2
|
|
49227
49390
|
}
|
|
49228
49391
|
}
|
|
49229
49392
|
},
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
|
|
54
54
|
var nodeCrypto = require("node:crypto");
|
|
55
55
|
var cbor = require("./cbor");
|
|
56
|
+
var bCrypto = require("./crypto");
|
|
56
57
|
var validateOpts = require("./validate-opts");
|
|
57
58
|
var { defineClass } = require("./framework-error");
|
|
58
59
|
|
|
@@ -143,6 +144,7 @@ function _toBeSigned(protectedBstr, externalAad, payload) {
|
|
|
143
144
|
* externalAad?: Buffer, // default empty — bound into the signature
|
|
144
145
|
* unprotectedHeaders?: object, // extra unprotected map entries (numeric keys)
|
|
145
146
|
* protectedHeaders?: object, // extra INTEGRITY-PROTECTED map entries (numeric keys); label 1 (alg) is reserved
|
|
147
|
+
* detached?: boolean, // emit a nil payload (RFC 9052 §4.1) — signature still covers it; caller transmits the payload separately
|
|
146
148
|
* }
|
|
147
149
|
*
|
|
148
150
|
* @example
|
|
@@ -152,7 +154,7 @@ function _toBeSigned(protectedBstr, externalAad, payload) {
|
|
|
152
154
|
*/
|
|
153
155
|
async function sign(payload, opts) {
|
|
154
156
|
validateOpts.requireObject(opts, "cose.sign", CoseError);
|
|
155
|
-
validateOpts(opts, ["alg", "privateKey", "kid", "contentType", "externalAad", "unprotectedHeaders", "protectedHeaders"], "cose.sign");
|
|
157
|
+
validateOpts(opts, ["alg", "privateKey", "kid", "contentType", "externalAad", "unprotectedHeaders", "protectedHeaders", "detached"], "cose.sign");
|
|
156
158
|
if (SIGNABLE.indexOf(opts.alg) === -1) {
|
|
157
159
|
throw new CoseError("cose/unsignable-alg",
|
|
158
160
|
"cose.sign: alg must be one of " + SIGNABLE.join(" / ") +
|
|
@@ -213,7 +215,10 @@ async function sign(payload, opts) {
|
|
|
213
215
|
? nodeCrypto.sign(null, toBeSigned, key)
|
|
214
216
|
: nodeCrypto.sign(params.nodeAlg, toBeSigned, { key: key, dsaEncoding: params.dsaEncoding });
|
|
215
217
|
|
|
216
|
-
|
|
218
|
+
// Detached payload (RFC 9052 §4.1): the COSE_Sign1 carries nil in the
|
|
219
|
+
// payload slot; the signature still covers the payload (above), and the
|
|
220
|
+
// caller transmits / re-supplies it out of band as externalPayload.
|
|
221
|
+
var sign1 = [protectedBstr, unprot, opts.detached ? null : payloadBytes, signature];
|
|
217
222
|
return cbor.encode(new cbor.Tag(COSE_SIGN1_TAG, sign1));
|
|
218
223
|
}
|
|
219
224
|
|
|
@@ -237,6 +242,7 @@ async function sign(payload, opts) {
|
|
|
237
242
|
* publicKey?: object, // the verification key (KeyObject / PEM)
|
|
238
243
|
* keyResolver?: function, // (protectedHeaders, unprotectedHeaders) → key
|
|
239
244
|
* externalAad?: Buffer, // must match what was signed
|
|
245
|
+
* externalPayload?: Buffer, // required when the COSE_Sign1 payload is detached (nil); bound into the Sig_structure
|
|
240
246
|
* maxBytes?: number, // forwarded to b.cbor.decode
|
|
241
247
|
* maxDepth?: number,
|
|
242
248
|
* }
|
|
@@ -247,7 +253,7 @@ async function sign(payload, opts) {
|
|
|
247
253
|
*/
|
|
248
254
|
async function verify(coseSign1, opts) {
|
|
249
255
|
validateOpts.requireObject(opts, "cose.verify", CoseError);
|
|
250
|
-
validateOpts(opts, ["algorithms", "publicKey", "keyResolver", "externalAad", "maxBytes", "maxDepth"], "cose.verify");
|
|
256
|
+
validateOpts(opts, ["algorithms", "publicKey", "keyResolver", "externalAad", "externalPayload", "maxBytes", "maxDepth"], "cose.verify");
|
|
251
257
|
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
252
258
|
throw new CoseError("cose/algorithms-required",
|
|
253
259
|
"cose.verify: opts.algorithms is required (no defaults — name the accepted algorithms)");
|
|
@@ -278,14 +284,23 @@ async function verify(coseSign1, opts) {
|
|
|
278
284
|
if (!Buffer.isBuffer(protectedBstr) || !Buffer.isBuffer(signature)) {
|
|
279
285
|
throw new CoseError("cose/malformed", "cose.verify: protected header and signature must be byte strings");
|
|
280
286
|
}
|
|
287
|
+
// Detached payload (RFC 9052 §4.1): a nil payload slot means the caller
|
|
288
|
+
// must supply the payload out of band via opts.externalPayload, which
|
|
289
|
+
// is then bound into the Sig_structure. Supplying externalPayload for
|
|
290
|
+
// an attached (non-nil) token is ambiguous and refused.
|
|
281
291
|
if (payload === null || payload === undefined) {
|
|
282
|
-
|
|
283
|
-
"cose
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
292
|
+
if (opts.externalPayload == null) {
|
|
293
|
+
throw new CoseError("cose/detached-no-payload",
|
|
294
|
+
"cose.verify: COSE_Sign1 has a detached (nil) payload — pass opts.externalPayload to verify it");
|
|
295
|
+
}
|
|
296
|
+
payload = _bstr(opts.externalPayload);
|
|
297
|
+
} else if (opts.externalPayload != null) {
|
|
298
|
+
throw new CoseError("cose/payload-ambiguous",
|
|
299
|
+
"cose.verify: opts.externalPayload was supplied but the COSE_Sign1 carries an attached payload");
|
|
300
|
+
} else if (!Buffer.isBuffer(payload)) {
|
|
301
|
+
// COSE_Sign1 payload is a bstr (RFC 9052 §4.2) — refuse a non-byte
|
|
302
|
+
// payload rather than return a value that violates the documented
|
|
303
|
+
// { payload: Buffer } shape.
|
|
289
304
|
throw new CoseError("cose/malformed", "cose.verify: payload must be a byte string (bstr)");
|
|
290
305
|
}
|
|
291
306
|
// The unprotected header is a CBOR map — refuse a non-map rather
|
|
@@ -534,12 +549,271 @@ function decrypt0(coseEncrypt0, opts) {
|
|
|
534
549
|
return { plaintext: pt, alg: algName, protectedHeaders: protMap, unprotectedHeaders: unprotected };
|
|
535
550
|
}
|
|
536
551
|
|
|
552
|
+
// ---- COSE_Mac0 (RFC 9052 §6.2) — single shared-key MAC ----
|
|
553
|
+
|
|
554
|
+
var COSE_MAC0_TAG = 17; // allow:raw-byte-literal — RFC 9052 COSE_Mac0 CBOR tag
|
|
555
|
+
// HMAC algorithms (RFC 9053 §3.1). Only the full-length tags are offered —
|
|
556
|
+
// the truncated HMAC 256/64 (id 4) is omitted. HMAC is symmetric, so its
|
|
557
|
+
// post-quantum strength is fine; these are the COSE-standard MAC algs.
|
|
558
|
+
var HMAC_NAME_TO_ID = { "HMAC-256/256": 5, "HMAC-384/384": 6, "HMAC-512/512": 7 }; // allow:raw-byte-literal — COSE HMAC algorithm ids (RFC 9053)
|
|
559
|
+
var HMAC_ID_TO_NAME = {};
|
|
560
|
+
Object.keys(HMAC_NAME_TO_ID).forEach(function (k) { HMAC_ID_TO_NAME[HMAC_NAME_TO_ID[k]] = k; });
|
|
561
|
+
function _hmacHash(algId) {
|
|
562
|
+
switch (algId) {
|
|
563
|
+
case 5: return "sha256";
|
|
564
|
+
case 6: return "sha384";
|
|
565
|
+
case 7: return "sha512";
|
|
566
|
+
default: throw new CoseError("cose/unknown-alg", "cose: unrecognized HMAC COSE alg id " + algId);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// MAC_structure (§6.3) = [ "MAC0", body_protected (bstr), external_aad (bstr), payload (bstr) ].
|
|
571
|
+
function _macStructure(protectedBstr, externalAad, payload) {
|
|
572
|
+
return cbor.encode(["MAC0", protectedBstr, externalAad, payload]);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* @primitive b.cose.mac0
|
|
577
|
+
* @signature b.cose.mac0(payload, opts)
|
|
578
|
+
* @since 0.12.47
|
|
579
|
+
* @status stable
|
|
580
|
+
* @related b.cose.macVerify0, b.cose.sign
|
|
581
|
+
*
|
|
582
|
+
* Produce a tagged COSE_Mac0 (RFC 9052 §6.2) — a single shared-key MAC
|
|
583
|
+
* over <code>payload</code>. The MAC is HMAC-SHA-256 / 384 / 512 (the
|
|
584
|
+
* COSE-standard MAC algorithms; HMAC is symmetric, so post-quantum
|
|
585
|
+
* strength is preserved). Use when both parties hold a shared key (e.g.
|
|
586
|
+
* an ECDH-derived key) and a non-repudiable signature is not wanted.
|
|
587
|
+
* <code>detached: true</code> emits a nil payload, verified later with
|
|
588
|
+
* <code>opts.externalPayload</code>.
|
|
589
|
+
*
|
|
590
|
+
* @opts
|
|
591
|
+
* {
|
|
592
|
+
* alg: string, // "HMAC-256/256" | "HMAC-384/384" | "HMAC-512/512"
|
|
593
|
+
* key: Buffer, // shared symmetric key
|
|
594
|
+
* externalAad?: Buffer, // bound into the MAC
|
|
595
|
+
* detached?: boolean, // emit a nil payload (caller re-supplies it on verify)
|
|
596
|
+
* unprotectedHeaders?: object,
|
|
597
|
+
* }
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* var mac = b.cose.mac0(Buffer.from("data"), { alg: "HMAC-256/256", key: sharedKey });
|
|
601
|
+
*/
|
|
602
|
+
function mac0(payload, opts) {
|
|
603
|
+
validateOpts.requireObject(opts, "cose.mac0", CoseError);
|
|
604
|
+
validateOpts(opts, ["alg", "key", "externalAad", "detached", "unprotectedHeaders"], "cose.mac0");
|
|
605
|
+
if (!(opts.alg in HMAC_NAME_TO_ID)) {
|
|
606
|
+
throw new CoseError("cose/unsignable-alg", "cose.mac0: alg must be one of " + Object.keys(HMAC_NAME_TO_ID).join(" / "));
|
|
607
|
+
}
|
|
608
|
+
var key = _bstr(opts.key);
|
|
609
|
+
var algId = HMAC_NAME_TO_ID[opts.alg];
|
|
610
|
+
var protMap = new Map();
|
|
611
|
+
protMap.set(HDR_ALG, algId);
|
|
612
|
+
var protectedBstr = cbor.encode(protMap);
|
|
613
|
+
|
|
614
|
+
var unprot = new Map();
|
|
615
|
+
if (opts.unprotectedHeaders && typeof opts.unprotectedHeaders === "object") {
|
|
616
|
+
var uk = Object.keys(opts.unprotectedHeaders);
|
|
617
|
+
for (var i = 0; i < uk.length; i++) unprot.set(Number(uk[i]), opts.unprotectedHeaders[uk[i]]);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
var payloadBytes = _bstr(payload);
|
|
621
|
+
var externalAad = opts.externalAad == null ? Buffer.alloc(0) : _bstr(opts.externalAad);
|
|
622
|
+
var tag = nodeCrypto.createHmac(_hmacHash(algId), key).update(_macStructure(protectedBstr, externalAad, payloadBytes)).digest();
|
|
623
|
+
|
|
624
|
+
var mac0arr = [protectedBstr, unprot, opts.detached ? null : payloadBytes, tag];
|
|
625
|
+
return cbor.encode(new cbor.Tag(COSE_MAC0_TAG, mac0arr));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* @primitive b.cose.macVerify0
|
|
630
|
+
* @signature b.cose.macVerify0(coseMac0, opts)
|
|
631
|
+
* @since 0.12.47
|
|
632
|
+
* @status stable
|
|
633
|
+
* @related b.cose.mac0
|
|
634
|
+
*
|
|
635
|
+
* Verify a COSE_Mac0 (RFC 9052 §6.2) and return its payload. The HMAC
|
|
636
|
+
* tag is recomputed over the MAC_structure and compared in constant
|
|
637
|
+
* time; the <code>alg</code> from the protected header must be in
|
|
638
|
+
* <code>opts.algorithms</code>. A detached (nil) payload is supplied via
|
|
639
|
+
* <code>opts.externalPayload</code>.
|
|
640
|
+
*
|
|
641
|
+
* @opts
|
|
642
|
+
* {
|
|
643
|
+
* algorithms: string[], // required — accepted HMAC alg names (allowlist)
|
|
644
|
+
* key: Buffer, // the shared symmetric key
|
|
645
|
+
* externalAad?: Buffer,
|
|
646
|
+
* externalPayload?: Buffer, // required for a detached payload
|
|
647
|
+
* maxBytes?: number,
|
|
648
|
+
* maxDepth?: number,
|
|
649
|
+
* }
|
|
650
|
+
*
|
|
651
|
+
* @example
|
|
652
|
+
* var out = b.cose.macVerify0(mac, { algorithms: ["HMAC-256/256"], key: sharedKey });
|
|
653
|
+
* // → { payload: <Buffer>, alg: "HMAC-256/256", protectedHeaders: Map, unprotectedHeaders: Map }
|
|
654
|
+
*/
|
|
655
|
+
function macVerify0(coseMac0, opts) {
|
|
656
|
+
validateOpts.requireObject(opts, "cose.macVerify0", CoseError);
|
|
657
|
+
validateOpts(opts, ["algorithms", "key", "externalAad", "externalPayload", "maxBytes", "maxDepth"], "cose.macVerify0");
|
|
658
|
+
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
659
|
+
throw new CoseError("cose/algorithms-required", "cose.macVerify0: opts.algorithms is required");
|
|
660
|
+
}
|
|
661
|
+
for (var ai = 0; ai < opts.algorithms.length; ai++) {
|
|
662
|
+
if (!(opts.algorithms[ai] in HMAC_NAME_TO_ID)) {
|
|
663
|
+
throw new CoseError("cose/unknown-alg", "cose.macVerify0: unknown algorithm '" + opts.algorithms[ai] + "'");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (opts.key == null) throw new CoseError("cose/no-key", "cose.macVerify0: opts.key is required");
|
|
667
|
+
var key = _bstr(opts.key);
|
|
668
|
+
|
|
669
|
+
var decoded = cbor.decode(_bstr(coseMac0), { allowedTags: [COSE_MAC0_TAG], maxBytes: opts.maxBytes, maxDepth: opts.maxDepth });
|
|
670
|
+
var arr = (decoded instanceof cbor.Tag && decoded.tag === COSE_MAC0_TAG) ? decoded.value : decoded;
|
|
671
|
+
if (!Array.isArray(arr) || arr.length !== 4) {
|
|
672
|
+
throw new CoseError("cose/malformed", "cose.macVerify0: not a COSE_Mac0 (expected a 4-element array)");
|
|
673
|
+
}
|
|
674
|
+
var protectedBstr = arr[0];
|
|
675
|
+
var unprotected = arr[1];
|
|
676
|
+
var payload = arr[2];
|
|
677
|
+
var tag = arr[3];
|
|
678
|
+
if (!Buffer.isBuffer(protectedBstr) || !Buffer.isBuffer(tag)) {
|
|
679
|
+
throw new CoseError("cose/malformed", "cose.macVerify0: protected header and tag must be byte strings");
|
|
680
|
+
}
|
|
681
|
+
if (!(unprotected instanceof Map)) {
|
|
682
|
+
throw new CoseError("cose/malformed", "cose.macVerify0: unprotected header must be a CBOR map");
|
|
683
|
+
}
|
|
684
|
+
if (payload === null || payload === undefined) {
|
|
685
|
+
if (opts.externalPayload == null) {
|
|
686
|
+
throw new CoseError("cose/detached-no-payload", "cose.macVerify0: detached (nil) payload — pass opts.externalPayload");
|
|
687
|
+
}
|
|
688
|
+
payload = _bstr(opts.externalPayload);
|
|
689
|
+
} else if (opts.externalPayload != null) {
|
|
690
|
+
throw new CoseError("cose/payload-ambiguous", "cose.macVerify0: externalPayload supplied but the COSE_Mac0 carries an attached payload");
|
|
691
|
+
} else if (!Buffer.isBuffer(payload)) {
|
|
692
|
+
throw new CoseError("cose/malformed", "cose.macVerify0: payload must be a byte string (bstr)");
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
var protMap = protectedBstr.length === 0 ? new Map()
|
|
696
|
+
: cbor.decode(protectedBstr, { maxBytes: opts.maxBytes, maxDepth: opts.maxDepth });
|
|
697
|
+
if (!(protMap instanceof Map)) {
|
|
698
|
+
throw new CoseError("cose/malformed", "cose.macVerify0: protected header is not a CBOR map");
|
|
699
|
+
}
|
|
700
|
+
// crit-bypass defense (RFC 9052 §3.1) — same as b.cose.verify: every
|
|
701
|
+
// label a crit array names must be one this verifier understands AND
|
|
702
|
+
// be present in the protected header.
|
|
703
|
+
if (protMap.has(HDR_CRIT)) {
|
|
704
|
+
var crit = protMap.get(HDR_CRIT);
|
|
705
|
+
if (!Array.isArray(crit)) {
|
|
706
|
+
throw new CoseError("cose/bad-crit", "cose.macVerify0: crit (label 2) must be an array");
|
|
707
|
+
}
|
|
708
|
+
for (var ci = 0; ci < crit.length; ci++) {
|
|
709
|
+
if (UNDERSTOOD_LABELS.indexOf(crit[ci]) === -1) {
|
|
710
|
+
throw new CoseError("cose/crit-unknown",
|
|
711
|
+
"cose.macVerify0: crit lists header label " + crit[ci] + " which is not understood (RFC 9052 §3.1)");
|
|
712
|
+
}
|
|
713
|
+
if (!protMap.has(crit[ci])) {
|
|
714
|
+
throw new CoseError("cose/crit-absent",
|
|
715
|
+
"cose.macVerify0: crit lists label " + crit[ci] + " not present in the protected header");
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
var algId = protMap.get(HDR_ALG);
|
|
720
|
+
var algName = HMAC_ID_TO_NAME[algId];
|
|
721
|
+
if (algName === undefined) {
|
|
722
|
+
throw new CoseError("cose/unknown-alg", "cose.macVerify0: unrecognized protected MAC alg id " + algId);
|
|
723
|
+
}
|
|
724
|
+
if (opts.algorithms.indexOf(algName) === -1) {
|
|
725
|
+
throw new CoseError("cose/alg-not-allowed", "cose.macVerify0: alg '" + algName + "' is not in the allowlist");
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
var externalAad = opts.externalAad == null ? Buffer.alloc(0) : _bstr(opts.externalAad);
|
|
729
|
+
var expected = nodeCrypto.createHmac(_hmacHash(algId), key).update(_macStructure(protectedBstr, externalAad, payload)).digest();
|
|
730
|
+
if (!bCrypto.timingSafeEqual(expected, tag)) {
|
|
731
|
+
throw new CoseError("cose/bad-tag", "cose.macVerify0: MAC tag verification failed");
|
|
732
|
+
}
|
|
733
|
+
return { payload: payload, alg: algName, protectedHeaders: protMap, unprotectedHeaders: unprotected };
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// ---- COSE_Key (RFC 9052 §7 / RFC 9053 §7) → KeyObject ----
|
|
737
|
+
|
|
738
|
+
// COSE_Key EC2 curve identifiers (RFC 9053 §7.1) → JWK crv names. Only
|
|
739
|
+
// the curves b.cose.verify has an algorithm for are accepted: P-256
|
|
740
|
+
// (ES256), P-384 (ES384), P-521 (ES512). secp256k1 is intentionally
|
|
741
|
+
// absent — there is no ES256K path here, so importing one would let a
|
|
742
|
+
// secp256k1 key be verified under ES256, breaking the COSE alg/curve
|
|
743
|
+
// binding (RFC 9053). Re-add with an explicit ES256K algorithm.
|
|
744
|
+
var COSE_EC2_CRV = { 1: "P-256", 2: "P-384", 3: "P-521" };
|
|
745
|
+
var COSE_KTY_OKP = 1;
|
|
746
|
+
var COSE_KTY_EC2 = 2;
|
|
747
|
+
var COSE_OKP_ED25519 = 6; // allow:raw-byte-literal — COSE OKP Ed25519 crv id (RFC 9053)
|
|
748
|
+
|
|
749
|
+
function _coseKeyBytes(v, what) {
|
|
750
|
+
if (Buffer.isBuffer(v)) return v;
|
|
751
|
+
if (v instanceof Uint8Array) return Buffer.from(v);
|
|
752
|
+
throw new CoseError("cose/bad-cose-key", "cose.importKey: COSE_Key " + what + " must be a byte string");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* @primitive b.cose.importKey
|
|
757
|
+
* @signature b.cose.importKey(coseKey)
|
|
758
|
+
* @since 0.12.45
|
|
759
|
+
* @status stable
|
|
760
|
+
* @related b.cose.verify, b.cbor.decode
|
|
761
|
+
*
|
|
762
|
+
* Import a COSE_Key (RFC 9052 §7) — a CBOR map keyed by integer labels —
|
|
763
|
+
* as a <code>node:crypto</code> public KeyObject for
|
|
764
|
+
* <code>b.cose.verify</code>. Accepts the EC2 (<code>kty</code> 2:
|
|
765
|
+
* P-256 / P-384 / P-521) and OKP (<code>kty</code> 1: Ed25519) key
|
|
766
|
+
* types — the curves <code>b.cose.verify</code> has an algorithm for;
|
|
767
|
+
* the curve is allowlisted, so an unexpected key type (including
|
|
768
|
+
* secp256k1, which has no ES256K path here) is refused rather than
|
|
769
|
+
* imported. The verification key embedded in an mdoc MSO or a COSE_Key
|
|
770
|
+
* header is consumed this way.
|
|
771
|
+
*
|
|
772
|
+
* @example
|
|
773
|
+
* var key = b.cose.importKey(coseKeyMap); // → public KeyObject
|
|
774
|
+
* var out = await b.cose.verify(sign1, { algorithms: ["ES256"], publicKey: key });
|
|
775
|
+
*/
|
|
776
|
+
function importKey(coseKey) {
|
|
777
|
+
if (!(coseKey instanceof Map)) {
|
|
778
|
+
if (coseKey && typeof coseKey === "object" && !Array.isArray(coseKey)) {
|
|
779
|
+
var m = new Map();
|
|
780
|
+
Object.keys(coseKey).forEach(function (k) { m.set(Number(k), coseKey[k]); });
|
|
781
|
+
coseKey = m;
|
|
782
|
+
} else {
|
|
783
|
+
throw new CoseError("cose/bad-cose-key", "cose.importKey: expected a COSE_Key map");
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
var kty = coseKey.get(1);
|
|
787
|
+
var x = _coseKeyBytes(coseKey.get(-2), "x");
|
|
788
|
+
var jwk;
|
|
789
|
+
if (kty === COSE_KTY_OKP) {
|
|
790
|
+
if (coseKey.get(-1) !== COSE_OKP_ED25519) {
|
|
791
|
+
throw new CoseError("cose/unsupported-key", "cose.importKey: only OKP curve Ed25519 is supported");
|
|
792
|
+
}
|
|
793
|
+
jwk = { kty: "OKP", crv: "Ed25519", x: x.toString("base64url") };
|
|
794
|
+
} else if (kty === COSE_KTY_EC2) {
|
|
795
|
+
var crvName = COSE_EC2_CRV[coseKey.get(-1)];
|
|
796
|
+
if (!crvName) throw new CoseError("cose/unsupported-key", "cose.importKey: unsupported EC2 curve id " + coseKey.get(-1));
|
|
797
|
+
var y = _coseKeyBytes(coseKey.get(-3), "y");
|
|
798
|
+
jwk = { kty: "EC", crv: crvName, x: x.toString("base64url"), y: y.toString("base64url") };
|
|
799
|
+
} else {
|
|
800
|
+
throw new CoseError("cose/unsupported-key", "cose.importKey: kty must be OKP (1) or EC2 (2), got " + kty);
|
|
801
|
+
}
|
|
802
|
+
try { return nodeCrypto.createPublicKey({ key: jwk, format: "jwk" }); }
|
|
803
|
+
catch (e) { throw new CoseError("cose/bad-cose-key", "cose.importKey: could not import COSE_Key: " + ((e && e.message) || e)); }
|
|
804
|
+
}
|
|
805
|
+
|
|
537
806
|
module.exports = {
|
|
538
807
|
sign: sign,
|
|
539
808
|
verify: verify,
|
|
540
809
|
encrypt0: encrypt0,
|
|
541
810
|
decrypt0: decrypt0,
|
|
811
|
+
mac0: mac0,
|
|
812
|
+
macVerify0: macVerify0,
|
|
813
|
+
importKey: importKey,
|
|
542
814
|
ALGORITHMS: ALG_NAME_TO_ID,
|
|
815
|
+
MAC_ALGORITHMS: HMAC_NAME_TO_ID,
|
|
816
|
+
COSE_MAC0_TAG: COSE_MAC0_TAG,
|
|
543
817
|
AEAD_ALGORITHMS: AEAD_NAME_TO_ID,
|
|
544
818
|
COSE_SIGN1_TAG: COSE_SIGN1_TAG,
|
|
545
819
|
COSE_ENCRYPT0_TAG: COSE_ENCRYPT0_TAG,
|