@blamejs/blamejs-shop 0.2.9 → 0.2.11

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.
@@ -82,13 +82,56 @@ async function testWrapRequiresRecipient() {
82
82
  refused && /no-recipient/.test(refused.code || refused.message));
83
83
  }
84
84
 
85
- async function testTenantStrategyDeferred() {
86
- var refused = null;
85
+ async function testTenantStrategyRoundTrip() {
86
+ var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-aw-tenant-"));
87
87
  try {
88
- b.archive.wrap(Buffer.from("bytes"), { recipient: "tenant", tenantId: "alpha" });
89
- } catch (e) { refused = e; }
90
- check("archive.wrap: tenant strategy deferred to v0.12.11 with explicit message",
91
- refused && /tenant-strategy-deferred/.test(refused.code || refused.message));
88
+ await helpers.setupVaultOnly(tmpDir);
89
+ var src = Buffer.from("per-tenant sealed archive bytes ".repeat(40));
90
+
91
+ // wrap → unwrap round-trips for the same tenant, no key-pair managed.
92
+ var sealed = b.archive.wrap(src, { recipient: "tenant", tenantId: "alpha" });
93
+ check("archive.wrap tenant: BAWRP magic",
94
+ sealed.slice(0, 5).toString("ascii") === "BAWRP");
95
+ check("archive.wrap tenant: version byte is 0x02 (tenant)",
96
+ sealed[5] === 0x02);
97
+ var recovered = b.archive.unwrap(sealed, { recipient: "tenant", tenantId: "alpha" });
98
+ check("archive.wrap tenant: round-trips losslessly", recovered.equals(src));
99
+ // recipient may be omitted on unwrap (version byte selects the path).
100
+ var recovered2 = b.archive.unwrap(sealed, { tenantId: "alpha" });
101
+ check("archive.wrap tenant: unwrap works with tenantId alone", recovered2.equals(src));
102
+
103
+ // Cross-tenant isolation — a different tenant's derived key (and AAD)
104
+ // cannot open tenant alpha's envelope.
105
+ var crossErr = null;
106
+ try { b.archive.unwrap(sealed, { recipient: "tenant", tenantId: "beta" }); }
107
+ catch (e) { crossErr = e; }
108
+ check("archive.wrap tenant: another tenant cannot decrypt",
109
+ crossErr && /decrypt-failed/.test(crossErr.code || crossErr.message));
110
+
111
+ // Missing tenantId on wrap throws a clear config error.
112
+ var noIdErr = null;
113
+ try { b.archive.wrap(src, { recipient: "tenant" }); }
114
+ catch (e) { noIdErr = e; }
115
+ check("archive.wrap tenant: missing tenantId throws no-tenant-id",
116
+ noIdErr && /no-tenant-id/.test(noIdErr.code || noIdErr.message));
117
+
118
+ // Passing a key-pair recipient to a tenant envelope is refused.
119
+ var mismatchErr = null;
120
+ try { b.archive.unwrap(sealed, { recipient: { privateKey: "x", ecPrivateKey: "y" } }); }
121
+ catch (e) { mismatchErr = e; }
122
+ check("archive.wrap tenant: key-pair recipient on tenant envelope refused",
123
+ mismatchErr && /recipient-mismatch/.test(mismatchErr.code || mismatchErr.message));
124
+
125
+ // Determinism — re-wrapping the same bytes for the same tenant yields
126
+ // a DIFFERENT envelope (fresh nonce) that still opens to the same plaintext.
127
+ var sealed2 = b.archive.wrap(src, { recipient: "tenant", tenantId: "alpha" });
128
+ check("archive.wrap tenant: fresh nonce per wrap (envelopes differ)",
129
+ !sealed2.equals(sealed));
130
+ check("archive.wrap tenant: second envelope also round-trips",
131
+ b.archive.unwrap(sealed2, { tenantId: "alpha" }).equals(src));
132
+ } finally {
133
+ helpers.teardownVaultOnly(tmpDir);
134
+ }
92
135
  }
93
136
 
94
137
  async function testBackupRecipientRoundTrip() {
@@ -159,7 +202,7 @@ async function run() {
159
202
  await testWrapRefusesWrongKey();
160
203
  await testWrapRefusesPartialStaticRecipient();
161
204
  await testWrapRequiresRecipient();
162
- await testTenantStrategyDeferred();
205
+ await testTenantStrategyRoundTrip();
163
206
  await testBackupRecipientRoundTrip();
164
207
  await testBackupRecipientStrategyRequiresKeys();
165
208
  await testBackupRecipientDirectoryRefused();
@@ -2387,6 +2387,16 @@ async function testNoDuplicateCodeBlocks() {
2387
2387
  ],
2388
2388
  reason: "v0.12.48 / v0.12.51 / v0.12.52 / v0.12.53 / v0.13.x — Buffer-coercion guard (`if (Buffer.isBuffer(x)) return x; if (x instanceof Uint8Array) return Buffer.from(x); ...`) repeats across byte-string-consuming primitives. The throw-on-unknown variants (cose / mdoc / dnssec / dane / tsa) each raise a MODULE-LOCAL typed error code naming the local argument; the JSON-fallback variants (eat._toBuf serializing a CBOR/EAT claims payload, worm._toBytes serializing a record to hash) instead JSON.stringify a non-bytes value. The duplicated prefix is the symptom; the cause is that JS can't throw a caller-namespaced ErrorClass (or choose the domain's serialization) without the local closure. Same documented exception as the v0.12.7 require-non-empty-string cluster — the per-domain error code / serialization is the divergence the dup detector can't see.",
2389
2389
  },
2390
+ {
2391
+ mode: "family-subset",
2392
+ files: [
2393
+ "lib/cose.js:importKey",
2394
+ "lib/cose.js:exportKey",
2395
+ "lib/did.js:_jwkToKey",
2396
+ "lib/network-dnssec.js:_jwkKey",
2397
+ ],
2398
+ reason: "v0.13.20 — EC/OKP JWK-coordinate handling (`kty` + `crv` switch over P-256/P-384/P-521 + Ed25519, base64url x/y, `createPublicKey({ format: 'jwk' })`) is coincidentally similar across three unrelated wire formats: cose.importKey/exportKey map a COSE_Key (RFC 9052 §7 — INTEGER crv ids -1/-2/-3 and labels) to/from a KeyObject; did._jwkToKey resolves a W3C DID verification method's JWK; network-dnssec._jwkKey reconstructs a DNSKEY's public key. The curve identifiers, the surrounding map shape, and the direction (import builds a key from a map, export emits a map from a key) all differ per spec — extracting a shared helper would couple three independent standards on a syntactic accident. The shingle is the JWK-coordinate idiom, not behaviour; same documented exception as the v0.12.40 signature-verify-preamble cluster.",
2399
+ },
2390
2400
  {
2391
2401
  mode: "family-subset",
2392
2402
  files: [
@@ -247,6 +247,47 @@ async function testImportKey() {
247
247
  check("importKey: secp256k1 refused (no ES256K binding)", secpBad && secpBad.code === "cose/unsupported-key");
248
248
  }
249
249
 
250
+ async function testExportKey() {
251
+ // EC2 P-256 → COSE_Key bytes → decode → importKey round-trips, and
252
+ // the re-imported key verifies a COSE_Sign1 the original signed.
253
+ var bytes = b.cose.exportKey(EC.publicKey, { alg: "ES256", kid: "key-1" });
254
+ check("exportKey: returns CBOR bytes", Buffer.isBuffer(bytes));
255
+ var map = b.cbor.decode(bytes);
256
+ check("exportKey: kty=EC2 (2)", map.get(1) === 2);
257
+ check("exportKey: crv=P-256 (1)", map.get(-1) === 1);
258
+ check("exportKey: x matches the JWK", map.get(-2).toString("base64url") === EC.publicKey.export({ format: "jwk" }).x);
259
+ check("exportKey: alg label 3 = ES256 (-7)", map.get(3) === -7);
260
+ check("exportKey: kid label 2 = 'key-1'", Buffer.isBuffer(map.get(2)) && map.get(2).toString("utf8") === "key-1");
261
+ var reimported = b.cose.importKey(map);
262
+ var att = await b.cose.sign(Buffer.from("rt"), { alg: "ES256", privateKey: EC.privateKey });
263
+ check("exportKey → importKey: round-trips for verification",
264
+ (await b.cose.verify(att, { algorithms: ["ES256"], publicKey: reimported })).payload.toString() === "rt");
265
+
266
+ // OKP Ed25519 round-trip.
267
+ var edBytes = b.cose.exportKey(ED.publicKey);
268
+ var edMap = b.cbor.decode(edBytes);
269
+ check("exportKey: OKP kty=1 + crv Ed25519 (6)", edMap.get(1) === 1 && edMap.get(-1) === 6);
270
+ check("exportKey: OKP re-imports to ed25519",
271
+ b.cose.importKey(edMap).asymmetricKeyType === "ed25519");
272
+
273
+ // A private key exports its PUBLIC half (never the secret).
274
+ var fromPriv = b.cbor.decode(b.cose.exportKey(EC.privateKey));
275
+ check("exportKey: private key exports the public coordinates",
276
+ fromPriv.get(-2).toString("base64url") === EC.publicKey.export({ format: "jwk" }).x);
277
+
278
+ // Unsupported curve / key type / bad opts refused.
279
+ var k1 = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "secp256k1" });
280
+ var secpBad = null;
281
+ try { b.cose.exportKey(k1.publicKey); } catch (e) { secpBad = e; }
282
+ check("exportKey: secp256k1 refused", secpBad && secpBad.code === "cose/unsupported-key");
283
+ var notKey = null;
284
+ try { b.cose.exportKey({ not: "a key" }); } catch (e) { notKey = e; }
285
+ check("exportKey: non-KeyObject refused", notKey && notKey.code === "cose/bad-key");
286
+ var badAlg = null;
287
+ try { b.cose.exportKey(EC.publicKey, { alg: "NOPE" }); } catch (e) { badAlg = e; }
288
+ check("exportKey: unknown alg refused", badAlg && badAlg.code === "cose/unknown-alg");
289
+ }
290
+
250
291
  async function run() {
251
292
  testSurface();
252
293
  testEncrypt0();
@@ -258,6 +299,7 @@ async function run() {
258
299
  await testValidation();
259
300
  await testDetachedPayload();
260
301
  await testImportKey();
302
+ await testExportKey();
261
303
  testMac0();
262
304
  }
263
305
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {