@blamejs/core 0.12.43 → 0.12.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/README.md +1 -1
- package/lib/did.js +69 -20
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.44 (2026-05-25) — **`b.did` adds the did:jwk method.** Completes b.did's method set with did:jwk alongside did:key and did:web. did:jwk encodes a public key as a base64url-encoded JWK directly in the identifier, so resolution is deterministic and offline — the same self-contained shape as did:key but in JWK form, which is what OpenID4VCI and the EU Digital Identity Wallet ecosystem commonly use. b.did.resolve("did:jwk:…") returns the verification key as a node:crypto KeyObject (kty/crv allowlisted — Ed25519 / P-256 / P-384 / secp256k1 — so an unexpected key type is refused, not blindly imported), and b.did.keyToDid(publicKey, { method: "jwk" }) produces a did:jwk from a key (the private member is stripped). No new runtime dependency. **Added:** *did:jwk in `b.did.resolve` / `b.did.keyToDid`* — `resolve` decodes the base64url JWK (bounded via `b.safeJson`), allowlists its `kty`/`crv`, and returns `{ didDocument, verificationMethods: [{ publicKey, … }] }` with the key as a KeyObject ready for `b.vc` / `b.mdoc` / `b.scitt`; `keyToDid(publicKey, { method: "jwk" })` encodes a public key as `did:jwk:<base64url-JWK>` (default remains `did:key`). Malformed base64url-JSON is refused with `did/bad-jwk` and an unsupported key type with `did/unsupported-key`.
|
|
12
|
+
|
|
11
13
|
- v0.12.43 (2026-05-25) — **`b.crypto.selfTest` — FIPS 140-3-style power-on self-test for the crypto stack.** A power-on self-test over the framework's cryptographic primitives — the integrity check a FIPS 140-3-validated module runs at start-up. The hash / XOF checks are known-answer tests against NIST FIPS 202 published vectors (SHA3-256 / SHA3-512 / SHAKE256), so they confirm the framework's hashing matches the standard rather than merely itself; the AEAD check round-trips XChaCha20-Poly1305 and confirms a tampered ciphertext is rejected; and the post-quantum checks run a pairwise-consistency + negative test for ML-KEM-1024, ML-DSA-87, and SLH-DSA-SHAKE-256f (a fresh keypair must encaps/decaps and sign/verify consistently and reject a tampered signature — FIPS 140-3 §10.3 pairwise consistency, since the runtime exposes no seed-injection API for a fixed-seed KAT). selfTest returns a structured report and, by default, throws on any failure so a broken crypto stack fails closed at boot rather than silently producing bad output. Operators in regulated deployments can run it at start-up as a self-integrity gate. **Added:** *`b.crypto.selfTest(opts?)`* — Runs eight checks — SHA3-512 / SHA3-256 / SHAKE256 known-answer tests (NIST FIPS 202), HMAC-SHA3-512 determinism, XChaCha20-Poly1305 round-trip + tamper-detect, and ML-KEM-1024 / ML-DSA-87 / SLH-DSA-SHAKE-256f pairwise-consistency + negative tests — and returns `{ ok, results: [{ name, ok, detail? }], failures, ranAt }`. Throws `crypto/self-test-failed` (with the report attached) on any failure unless `opts.throwOnFailure` is `false`. Exercises the framework's real primitive paths so a self-test failure means the shipped crypto is broken.
|
|
12
14
|
|
|
13
15
|
- v0.12.42 (2026-05-24) — **`b.vc.present` / `b.vc.verifyPresentation` — W3C Verifiable Presentations.** Completes b.vc with the holder side: a Verifiable Presentation is a holder-signed envelope wrapping one or more credentials, proving the presenter controls the key the credentials were issued to. b.vc.present builds and signs a VerifiablePresentation (each credential enveloped per VC-JOSE-COSE) as a compact JWS (vp+jwt) or COSE_Sign1 (application/vp+cose), matching b.vc.issue's algorithms; an optional nonce / audience is embedded in the signed presentation for holder-binding and replay protection. b.vc.verifyPresentation verifies the holder signature (auto-detected jose/cose, mandatory algorithm allowlist, JOSE none refused), the VCDM structure, and the embedded nonce / audience / expectedHolder when given, and — with verifyCredentials: true — verifies each enveloped credential through b.vc.verify and returns them. The holder is typically a DID, resolved to a key via b.did. Composes b.cose; no new runtime dependency. **Added:** *`b.vc.present(opts)` / `b.vc.verifyPresentation(secured, opts)`* — `present` wraps `opts.credentials` (secured VCs — compact-JWS strings or COSE_Sign1 bytes, each enveloped as an `EnvelopedVerifiableCredential` data: URI) in a `VerifiablePresentation` signed by the holder, with optional `nonce` / `audience` embedded for binding. `verifyPresentation` verifies the holder signature against the mandatory `opts.algorithms` allowlist (JOSE `none` always refused), re-checks the VCDM structure, enforces `expectedHolder` / `nonce` / `audience` when supplied, and with `verifyCredentials: true` verifies each enveloped credential through `b.vc.verify` (using `opts.credentialOpts`), returning `{ presentation, holder, credentials, securing, alg }`. The enveloped-credential count is bounded. A `vp+jwt` presentation is refused by `b.vc.verify` and a `vc+jwt` credential is refused by `verifyPresentation` — the media-type binding keeps the two surfaces distinct.
|
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
134
134
|
- **Trusted timestamping** — `b.tsa` RFC 3161 timestamp client: `buildRequest` a TimeStampReq, `parseResponse`, and `verifyToken` against your data — the message imprint, sent nonce, critical/sole `id-kp-timeStamping` EKU, and CMS signature are all checked, with optional certificate-chain verification. Timestamp a release artifact, audit checkpoint, or signed statement against any RFC 3161 TSA. Composes `b.cms` + the in-tree ASN.1 DER codec
|
|
135
135
|
- **Verifiable Credentials** — `b.vc` W3C Verifiable Credentials Data Model 2.0 (VC-JOSE-COSE): `issue` / `verify` a signed credential, and `present` / `verifyPresentation` a holder-signed Verifiable Presentation wrapping credentials (with `nonce`/`audience` holder-binding) — as a compact JWS (`vc+jwt` / `vp+jwt`, ES256/384/512 + EdDSA) or a COSE_Sign1 (`vc+cose` / `vp+cose`, + ML-DSA-87) over `b.cose`. VCDM structural + `validFrom`/`validUntil` checks; the JOSE `none` algorithm is always refused. The W3C model, distinct from the IETF SD-JWT VC at `b.auth.sdJwtVc`
|
|
136
136
|
- **Mobile credentials (mDL)** — `b.mdoc` ISO/IEC 18013-5 issuer-data verification: `verifyIssuerSigned` checks the COSE_Sign1 IssuerAuth (issuer cert from the `x5chain` header), the Mobile Security Object validity window, and every disclosed element's digest against the MSO `valueDigests` (the selective-disclosure integrity check), with optional issuer-chain verification. The ISO credential ecosystem alongside `b.vc` and `b.auth.sdJwtVc`. Composes `b.cose` + `b.cbor`
|
|
137
|
-
- **Decentralized Identifiers** — `b.did` W3C DID resolution (DID Core 1.0): `resolve` a `did:key` (deterministic, offline — Ed25519 / P-256 / P-384 / secp256k1) or `did:web` (operator-fetched document) to `node:crypto` verification keys, so a credential's issuer DID resolves to the key that verifies it (`b.vc` / `b.mdoc` / `b.scitt`). `keyToDid` names a key as a `did:key`; document
|
|
137
|
+
- **Decentralized Identifiers** — `b.did` W3C DID resolution (DID Core 1.0): `resolve` a `did:key` / `did:jwk` (deterministic, offline — Ed25519 / P-256 / P-384 / secp256k1) or `did:web` (operator-fetched document) to `node:crypto` verification keys, so a credential's issuer DID resolves to the key that verifies it (`b.vc` / `b.mdoc` / `b.scitt`). `keyToDid` names a key as a `did:key` or `did:jwk`; document/JWK keys are kty/crv-allowlisted before import
|
|
138
138
|
- **Document parsers** — `b.parsers` (XML / TOML / YAML / .env); `b.config` (schema-validated env)
|
|
139
139
|
- **File-type detection** — `b.fileType` magic-byte content classification with deny-on-upload categories (image / document / archive / executable / etc.)
|
|
140
140
|
### Content-safety gates
|
package/lib/did.js
CHANGED
|
@@ -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",
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:645939ff-86bb-46c2-a5af-52ac1e920cb5",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-25T08:20:43.623Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.12.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.12.44",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.12.
|
|
25
|
+
"version": "0.12.44",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.12.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.12.44",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.12.
|
|
57
|
+
"ref": "@blamejs/core@0.12.44",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|