@blamejs/core 0.12.41 → 0.12.43
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 -1
- package/lib/crypto.js +119 -0
- package/lib/vc.js +231 -33
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- 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
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
11
15
|
- v0.12.41 (2026-05-24) — **`b.did` — W3C DID resolution (did:key + did:web) feeding the credential verifiers.** Resolve W3C Decentralized Identifiers (DID Core 1.0) to verification keys — the link that lets a credential's issuer be named by a DID rather than a raw key. Resolve the issuer DID of a b.vc / b.mdoc / b.scitt credential to a node:crypto KeyObject and hand it to the verifier. did:key encodes the public key in the identifier (multicodec + base58btc), so resolution is deterministic and offline — Ed25519, P-256, P-384, and secp256k1 round-trip; did:web places the DID document at an HTTPS URL derived from the identifier, with the network fetch left to the operator (the framework parses the operator-fetched document and extracts its verification methods, as publicKeyMultibase or publicKeyJwk). b.did.keyToDid encodes a KeyObject as a did:key (an issuer naming itself), b.did.parse splits the identifier (and returns the did:web URL to fetch), and b.did.resolve returns the document and verification keys. DID Core 1.0 is a W3C Recommendation; the method specs (did:key W3C CCG report, did:web DID method registry — EUDI-mandated) are deployed-stable. Composes node:crypto; no new runtime dependency. **Added:** *`b.did.resolve(did, opts?)` / `b.did.keyToDid(publicKey)` / `b.did.parse(did)`* — `resolve` returns `{ didDocument, verificationMethods: [{ id, controller, type, publicKey }] }` with each `publicKey` a `node:crypto` KeyObject ready for `b.vc.verify` / `b.mdoc.verifyIssuerSigned` / `b.scitt.verifyStatement`. did:key resolves deterministically and offline (base58btc + multicodec → Ed25519 raw key or EC compressed point, rebuilt via SPKI); did:web requires the operator to pass the fetched DID document as `opts.document` (the URL to GET is on `b.did.parse(did).url`) and the document `id` must match the requested DID. A publicKeyJwk in a DID document is imported only after its `kty`/`crv` is allowlisted (Ed25519 / P-256 / P-384 / secp256k1) — an unexpected key type from an untrusted document is refused, not blindly imported. `keyToDid` encodes an Ed25519 / P-256 / P-384 / secp256k1 KeyObject as a did:key; `parse` derives the did:web HTTPS URL (`host[:port][:path]` → `https://host/path/did.json`, or `/.well-known/did.json`). Unknown methods, malformed base58, unsupported multicodec codes, and unsupported key types are each refused.
|
|
12
16
|
|
|
13
17
|
- v0.12.40 (2026-05-24) — **`b.mdoc` — ISO 18013-5 mdoc / mDL issuer-data verification.** Verify the issuer-signed data of an ISO/IEC 18013-5 mdoc — the credential format behind mobile driving licences (mDL) and the ISO track of the EU Digital Identity Wallet. This is the relying-party side: confirm that the data elements a holder presents were signed by the issuer and have not been altered. An mdoc's IssuerSigned carries the disclosed data elements and an issuerAuth that is a COSE_Sign1 (b.cose) over a Mobile Security Object (MSO) holding a per-element digest. b.mdoc.verifyIssuerSigned verifies the COSE signature with the issuer certificate from the COSE x5chain header, parses the MSO, enforces its validityInfo window, and recomputes each disclosed element's digest (the full Tag-24 IssuerSignedItemBytes) to match it against the MSO constant-time — the integrity check that makes selective disclosure trustworthy. An absent or mismatched digest is refused. Signing algorithms follow b.cose verification (the classical ES256/384/512 + EdDSA that real mDL issuers use; the caller names the allowlist); opts.trustAnchorsPem additionally verifies the issuer certificate chain. This completes the credential trio alongside W3C VCDM (b.vc) and IETF SD-JWT VC (b.auth.sdJwtVc). Composes b.cose + b.cbor; no new runtime dependency. **Added:** *`b.mdoc.verifyIssuerSigned(issuerSigned, opts)`* — Takes the CBOR `IssuerSigned` map (the operator extracts it from the device response / QR) and returns `{ docType, version, digestAlgorithm, validityInfo, namespaces, signerCert, alg }`. Verifies the COSE_Sign1 `issuerAuth` against the mandatory `opts.algorithms` allowlist using the issuer certificate from its `x5chain` (label 33) header; parses the Tag-24 Mobile Security Object; enforces the MSO `validityInfo` window against `opts.at` (default now; must be a valid Date; malformed dates fail closed); and recomputes the digest of every disclosed `IssuerSignedItem` (over the full Tag-24 bytes, with the MSO `digestAlgorithm` — SHA-256/384/512) to match the MSO `valueDigests` constant-time — an absent or mismatched digest is refused with `mdoc/digest-mismatch`. `opts.expectedDocType` pins the document type; `opts.trustAnchorsPem` (a PEM string or array) additionally verifies the issuer certificate chain and validity at the asserted time. A malformed `x5chain` certificate is refused with a clean `mdoc/bad-cert`. The mdoc device-authentication half (the SessionTranscript-bound holder-binding proof) is a presentation-protocol concern and is not part of issuer-data verification.
|
package/README.md
CHANGED
|
@@ -92,6 +92,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
92
92
|
### Crypto
|
|
93
93
|
|
|
94
94
|
- **At-rest envelope** — envelope-versioned PQC (ML-KEM-1024 + P-384 hybrid, XChaCha20-Poly1305, SHAKE256); vault sealing (`b.crypto`, `b.vault`)
|
|
95
|
+
- **Power-on self-test** — `b.crypto.selfTest()` runs FIPS 140-3-style integrity checks: NIST FIPS 202 known-answer tests (SHA3-256/512, SHAKE256), AEAD round-trip + tamper-detect, and ML-KEM-1024 / ML-DSA-87 / SLH-DSA-SHAKE-256f pairwise-consistency + negative tests; fails closed (throws) on any mismatch
|
|
95
96
|
- **Field-level + crypto-shred** — `b.cryptoField.eraseRow`; per-column data residency tagging + per-row keys (`K_row = HKDF(K_table, rowId)`) so erasing the per-row key makes WAL / replica residuals undecryptable (`b.cryptoField.declareColumnResidency`, `b.cryptoField.declarePerRowKey`)
|
|
96
97
|
- **AAD-bound sealed columns** — AEAD tag tied to `(table, rowId, column, schemaVersion)`; copy-paste between rows or schema-version replay surfaces as refused decrypt (`b.vault.aad`)
|
|
97
98
|
- **Signed webhooks + API encryption** — SLH-DSA-SHAKE-256f default; ML-DSA-65 opt-in; ECIES API encryption (`b.webhook`, `b.crypto`)
|
|
@@ -131,7 +132,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
131
132
|
- **Entity Attestation Token** — `b.eat` EAT sign/verify (RFC 9711) over `b.cwt`: device + software attestation claims (ueid / oemid / hwmodel / measurements / submods) with verifier-nonce freshness binding, `dbgstat` debug-status policy, and `eat_profile` pinning
|
|
132
133
|
- **SCITT signed statements** — `b.scitt` sign/verify a signed, attributable claim about an artifact (signed SBOM, build attestation, release approval) over `b.cose`: the issuer + subject bind in the integrity-protected CWT_Claims header (RFC 9597); verification refuses any statement missing the iss/sub binding. The issuer side, on finalized RFCs; the transparency receipt (COSE Receipts draft) opts in on publication
|
|
133
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
|
|
134
|
-
- **Verifiable Credentials** — `b.vc` W3C Verifiable Credentials Data Model 2.0 (VC-JOSE-COSE): `issue` / `verify` a signed credential as a compact JWS (`vc+jwt`, ES256/384/512 + EdDSA) or a COSE_Sign1 (`vc+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`
|
|
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`
|
|
135
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`
|
|
136
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 JWKs are kty/crv-allowlisted before import
|
|
137
138
|
- **Document parsers** — `b.parsers` (XML / TOML / YAML / .env); `b.config` (schema-validated env)
|
package/lib/crypto.js
CHANGED
|
@@ -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,
|
package/lib/vc.js
CHANGED
|
@@ -51,6 +51,11 @@ var VCDM_V2_CONTEXT = "https://www.w3.org/ns/credentials/v2";
|
|
|
51
51
|
var JOSE_TYP = "vc+jwt";
|
|
52
52
|
var COSE_TYP = "application/vc+cose";
|
|
53
53
|
var COSE_CONTENT_TYPE = "application/vc";
|
|
54
|
+
var VP_JOSE_TYP = "vp+jwt";
|
|
55
|
+
var VP_COSE_TYP = "application/vp+cose";
|
|
56
|
+
var VP_COSE_CONTENT_TYPE = "application/vp";
|
|
57
|
+
var MAX_PRESENTATION_CREDENTIALS = 64; // allow:raw-byte-literal — bounded count of enveloped VCs per presentation
|
|
58
|
+
var ENVELOPED_VC_TYPE = "EnvelopedVerifiableCredential";
|
|
54
59
|
var HDR_COSE_TYP = 16; // allow:raw-byte-literal — COSE "typ" header label (RFC 9596)
|
|
55
60
|
|
|
56
61
|
// JOSE signature algorithms (final RFC 7518 / 8037), mapped to node
|
|
@@ -165,36 +170,43 @@ async function issue(credential, opts) {
|
|
|
165
170
|
_validateVcdm(credential, null);
|
|
166
171
|
if (!opts.privateKey) throw new VcError("vc/no-key", "vc.issue: opts.privateKey is required");
|
|
167
172
|
|
|
173
|
+
return _sign(credential, opts, JOSE_TYP, COSE_TYP, COSE_CONTENT_TYPE, "vc.issue");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Secure a JSON document (credential or presentation) as a compact JWS
|
|
177
|
+
// (jose) or COSE_Sign1 (cose) with the given media-type headers. The
|
|
178
|
+
// document is the exact signed payload — no claims wrapper.
|
|
179
|
+
function _sign(doc, opts, joseTyp, coseTyp, coseContentType, fnName) {
|
|
168
180
|
if (opts.securing === "cose") {
|
|
169
181
|
var protectedHeaders = {};
|
|
170
|
-
protectedHeaders[HDR_COSE_TYP] =
|
|
171
|
-
return cose.sign(Buffer.from(JSON.stringify(
|
|
182
|
+
protectedHeaders[HDR_COSE_TYP] = coseTyp;
|
|
183
|
+
return cose.sign(Buffer.from(JSON.stringify(doc), "utf8"), {
|
|
172
184
|
alg: opts.alg,
|
|
173
185
|
privateKey: opts.privateKey,
|
|
174
186
|
kid: opts.kid,
|
|
175
|
-
contentType:
|
|
187
|
+
contentType: coseContentType,
|
|
176
188
|
protectedHeaders: protectedHeaders,
|
|
177
189
|
});
|
|
178
190
|
}
|
|
179
191
|
if (opts.securing === "jose") {
|
|
180
192
|
var params = JOSE_ALGS[opts.alg];
|
|
181
193
|
if (!params) {
|
|
182
|
-
throw new VcError("vc/bad-alg", "
|
|
194
|
+
throw new VcError("vc/bad-alg", fnName + ": JOSE securing requires alg ES256/384/512 or EdDSA (got " + opts.alg + ")");
|
|
183
195
|
}
|
|
184
196
|
var key = _toKey(opts.privateKey, "private");
|
|
185
|
-
var header = { alg: opts.alg, typ:
|
|
197
|
+
var header = { alg: opts.alg, typ: joseTyp };
|
|
186
198
|
if (typeof opts.kid === "string") header.kid = opts.kid;
|
|
187
199
|
if (typeof opts.cty === "string") header.cty = opts.cty;
|
|
188
|
-
var signingInput = _b64urlJson(header) + "." + _b64urlJson(
|
|
200
|
+
var signingInput = _b64urlJson(header) + "." + _b64urlJson(doc);
|
|
189
201
|
var sig = params.nodeHash === null
|
|
190
202
|
? nodeCrypto.sign(null, Buffer.from(signingInput, "ascii"), key)
|
|
191
203
|
: nodeCrypto.sign(params.nodeHash, Buffer.from(signingInput, "ascii"), { key: key, dsaEncoding: params.dsaEncoding });
|
|
192
204
|
return signingInput + "." + sig.toString("base64url");
|
|
193
205
|
}
|
|
194
|
-
throw new VcError("vc/bad-securing", "
|
|
206
|
+
throw new VcError("vc/bad-securing", fnName + ": securing must be 'jose' or 'cose'");
|
|
195
207
|
}
|
|
196
208
|
|
|
197
|
-
function _verifyJose(token, opts) {
|
|
209
|
+
function _verifyJose(token, opts, expectedTyp) {
|
|
198
210
|
var parts = token.split(".");
|
|
199
211
|
if (parts.length !== 3) {
|
|
200
212
|
throw new VcError("vc/malformed", "vc.verify: not a compact JWS (expected three dot-separated segments)");
|
|
@@ -202,8 +214,8 @@ function _verifyJose(token, opts) {
|
|
|
202
214
|
var header;
|
|
203
215
|
try { header = safeJson.parse(Buffer.from(parts[0], "base64url").toString("utf8")); }
|
|
204
216
|
catch (_e) { throw new VcError("vc/malformed", "vc.verify: JWS header is not valid base64url-JSON"); }
|
|
205
|
-
if (!header || header.typ !==
|
|
206
|
-
throw new VcError("vc/bad-typ", "vc.verify: JWS typ must be '" +
|
|
217
|
+
if (!header || header.typ !== expectedTyp) {
|
|
218
|
+
throw new VcError("vc/bad-typ", "vc.verify: JWS typ must be '" + expectedTyp + "'");
|
|
207
219
|
}
|
|
208
220
|
// crit-bypass defense (RFC 7515 §4.1.11): a `crit` header marks
|
|
209
221
|
// extensions the verifier MUST understand and process. This verifier
|
|
@@ -228,16 +240,16 @@ function _verifyJose(token, opts) {
|
|
|
228
240
|
? nodeCrypto.verify(null, Buffer.from(signingInput, "ascii"), pub, sig)
|
|
229
241
|
: nodeCrypto.verify(params.nodeHash, Buffer.from(signingInput, "ascii"), { key: pub, dsaEncoding: params.dsaEncoding }, sig);
|
|
230
242
|
if (!ok) throw new VcError("vc/bad-signature", "vc.verify: JWS signature did not verify");
|
|
231
|
-
var
|
|
232
|
-
try {
|
|
243
|
+
var payload;
|
|
244
|
+
try { payload = safeJson.parse(Buffer.from(parts[1], "base64url").toString("utf8")); }
|
|
233
245
|
catch (_e) { throw new VcError("vc/malformed", "vc.verify: JWS payload is not valid base64url-JSON"); }
|
|
234
|
-
return {
|
|
246
|
+
return { payload: payload, alg: header.alg };
|
|
235
247
|
}
|
|
236
248
|
|
|
237
|
-
async function _verifyCose(bytes, opts) {
|
|
249
|
+
async function _verifyCose(bytes, opts, expectedTyp) {
|
|
238
250
|
var algorithms = opts.algorithms.filter(function (a) { return a in cose.ALGORITHMS; });
|
|
239
251
|
if (!algorithms.length) {
|
|
240
|
-
throw new VcError("vc/no-cose-alg", "vc.verify: opts.algorithms has no COSE algorithm for a
|
|
252
|
+
throw new VcError("vc/no-cose-alg", "vc.verify: opts.algorithms has no COSE algorithm for a COSE-secured credential");
|
|
241
253
|
}
|
|
242
254
|
var out = await cose.verify(bytes, {
|
|
243
255
|
algorithms: algorithms,
|
|
@@ -245,13 +257,25 @@ async function _verifyCose(bytes, opts) {
|
|
|
245
257
|
keyResolver: opts.keyResolver,
|
|
246
258
|
});
|
|
247
259
|
var typ = out.protectedHeaders.get(HDR_COSE_TYP);
|
|
248
|
-
if (typ !== undefined && typ !==
|
|
249
|
-
throw new VcError("vc/bad-typ", "vc.verify: COSE typ header is '" + typ + "', expected '" +
|
|
260
|
+
if (typ !== undefined && typ !== expectedTyp) {
|
|
261
|
+
throw new VcError("vc/bad-typ", "vc.verify: COSE typ header is '" + typ + "', expected '" + expectedTyp + "'");
|
|
250
262
|
}
|
|
251
|
-
var
|
|
252
|
-
try {
|
|
263
|
+
var payload;
|
|
264
|
+
try { payload = safeJson.parse(out.payload.toString("utf8")); }
|
|
253
265
|
catch (_e) { throw new VcError("vc/malformed", "vc.verify: COSE payload is not valid JSON"); }
|
|
254
|
-
return {
|
|
266
|
+
return { payload: payload, alg: out.alg };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Verify a secured JSON document (the JOSE/COSE envelope) → { payload,
|
|
270
|
+
// alg, securing }. Shared by credential + presentation verification.
|
|
271
|
+
async function _verifySecured(secured, opts, joseTyp, coseTyp) {
|
|
272
|
+
if (typeof secured === "string") {
|
|
273
|
+
return Object.assign({ securing: "jose" }, _verifyJose(secured, opts, joseTyp));
|
|
274
|
+
}
|
|
275
|
+
if (Buffer.isBuffer(secured) || secured instanceof Uint8Array) {
|
|
276
|
+
return Object.assign({ securing: "cose" }, await _verifyCose(Buffer.from(secured), opts, coseTyp));
|
|
277
|
+
}
|
|
278
|
+
throw new VcError("vc/bad-input", "vc.verify: secured must be a compact-JWS string or COSE_Sign1 bytes");
|
|
255
279
|
}
|
|
256
280
|
|
|
257
281
|
/**
|
|
@@ -300,28 +324,202 @@ async function verify(secured, opts) {
|
|
|
300
324
|
at = opts.at;
|
|
301
325
|
}
|
|
302
326
|
|
|
303
|
-
var
|
|
304
|
-
if (typeof secured === "string") {
|
|
305
|
-
securing = "jose";
|
|
306
|
-
result = _verifyJose(secured, opts);
|
|
307
|
-
} else if (Buffer.isBuffer(secured) || secured instanceof Uint8Array) {
|
|
308
|
-
securing = "cose";
|
|
309
|
-
result = await _verifyCose(Buffer.from(secured), opts);
|
|
310
|
-
} else {
|
|
311
|
-
throw new VcError("vc/bad-input", "vc.verify: secured must be a compact-JWS string or COSE_Sign1 bytes");
|
|
312
|
-
}
|
|
327
|
+
var result = await _verifySecured(secured, opts, JOSE_TYP, COSE_TYP);
|
|
313
328
|
|
|
314
|
-
_validateVcdm(result.
|
|
315
|
-
var issuer = _issuerId(result.
|
|
329
|
+
_validateVcdm(result.payload, { temporal: true, at: at });
|
|
330
|
+
var issuer = _issuerId(result.payload);
|
|
316
331
|
if (opts.expectedIssuer !== undefined && issuer !== opts.expectedIssuer) {
|
|
317
332
|
throw new VcError("vc/issuer-mismatch", "vc.verify: credential issuer does not match expectedIssuer");
|
|
318
333
|
}
|
|
319
|
-
return { credential: result.
|
|
334
|
+
return { credential: result.payload, securing: result.securing, alg: result.alg, issuer: issuer };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// VCDM 2.0 presentation structural rules.
|
|
338
|
+
function _validateVp(vp) {
|
|
339
|
+
if (!vp || typeof vp !== "object" || Array.isArray(vp)) {
|
|
340
|
+
throw new VcError("vc/bad-presentation", "vc: presentation must be a JSON object");
|
|
341
|
+
}
|
|
342
|
+
var ctx = vp["@context"];
|
|
343
|
+
if (!Array.isArray(ctx) || ctx[0] !== VCDM_V2_CONTEXT) {
|
|
344
|
+
throw new VcError("vc/bad-context", "vc: presentation @context must start with '" + VCDM_V2_CONTEXT + "'");
|
|
345
|
+
}
|
|
346
|
+
var types = Array.isArray(vp.type) ? vp.type : [vp.type];
|
|
347
|
+
if (types.indexOf("VerifiablePresentation") === -1) {
|
|
348
|
+
throw new VcError("vc/bad-type", "vc: type must include 'VerifiablePresentation'");
|
|
349
|
+
}
|
|
350
|
+
// verifiableCredential, when present, MUST be an array — a non-array
|
|
351
|
+
// value must fail closed rather than coerce to empty (which would let
|
|
352
|
+
// a holder bypass credential verification with a malformed container).
|
|
353
|
+
if (vp.verifiableCredential !== undefined && !Array.isArray(vp.verifiableCredential)) {
|
|
354
|
+
throw new VcError("vc/bad-presentation", "vc: verifiableCredential must be an array");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// An enveloped VC (VC-JOSE-COSE §enveloping): a data: URI whose media
|
|
359
|
+
// type selects the securing and whose body is the secured credential.
|
|
360
|
+
function _envelopeVc(securedVc) {
|
|
361
|
+
if (typeof securedVc === "string") {
|
|
362
|
+
return { "@context": [VCDM_V2_CONTEXT], type: ENVELOPED_VC_TYPE, id: "data:application/vc+jwt," + securedVc };
|
|
363
|
+
}
|
|
364
|
+
if (Buffer.isBuffer(securedVc) || securedVc instanceof Uint8Array) {
|
|
365
|
+
return { "@context": [VCDM_V2_CONTEXT], type: ENVELOPED_VC_TYPE,
|
|
366
|
+
id: "data:application/vc+cose;base64," + Buffer.from(securedVc).toString("base64") };
|
|
367
|
+
}
|
|
368
|
+
throw new VcError("vc/bad-credential", "vc.present: each credential must be a compact-JWS string or COSE_Sign1 bytes");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function _parseEnvelopedVc(entry) {
|
|
372
|
+
if (!entry || typeof entry !== "object" || entry.type !== ENVELOPED_VC_TYPE || typeof entry.id !== "string") {
|
|
373
|
+
throw new VcError("vc/bad-enveloped", "vc.verifyPresentation: verifiableCredential entries must be EnvelopedVerifiableCredential data: URIs");
|
|
374
|
+
}
|
|
375
|
+
var comma = entry.id.indexOf(",");
|
|
376
|
+
if (entry.id.indexOf("data:") !== 0 || comma === -1) {
|
|
377
|
+
throw new VcError("vc/bad-enveloped", "vc.verifyPresentation: enveloped credential id is not a data: URI");
|
|
378
|
+
}
|
|
379
|
+
var meta = entry.id.slice("data:".length, comma);
|
|
380
|
+
var body = entry.id.slice(comma + 1);
|
|
381
|
+
if (meta.indexOf("application/vc+cose") === 0) return Buffer.from(body, "base64");
|
|
382
|
+
if (meta.indexOf("application/vc+jwt") === 0) return body;
|
|
383
|
+
throw new VcError("vc/bad-enveloped", "vc.verifyPresentation: unsupported enveloped media type '" + meta + "'");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* @primitive b.vc.present
|
|
388
|
+
* @signature b.vc.present(opts)
|
|
389
|
+
* @since 0.12.42
|
|
390
|
+
* @status experimental
|
|
391
|
+
* @compliance gdpr, soc2
|
|
392
|
+
* @related b.vc.verifyPresentation, b.vc.issue
|
|
393
|
+
*
|
|
394
|
+
* Build and sign a W3C Verifiable Presentation: a holder-signed envelope
|
|
395
|
+
* wrapping one or more secured credentials (each enveloped per
|
|
396
|
+
* VC-JOSE-COSE). <code>securing</code> and the algorithms match
|
|
397
|
+
* <code>b.vc.issue</code> (compact JWS <code>vp+jwt</code>, or COSE_Sign1
|
|
398
|
+
* <code>application/vp+cose</code>). Supply <code>nonce</code> /
|
|
399
|
+
* <code>audience</code> for holder-binding / replay protection — they
|
|
400
|
+
* are embedded in the signed presentation and checked at verification.
|
|
401
|
+
*
|
|
402
|
+
* @opts
|
|
403
|
+
* {
|
|
404
|
+
* credentials: array, // secured VCs (compact-JWS strings or COSE_Sign1 bytes)
|
|
405
|
+
* holder: string, // the presenter (a DID or other id)
|
|
406
|
+
* securing: string, // "jose" | "cose"
|
|
407
|
+
* alg: string, // JOSE: ES256/384/512 | EdDSA. COSE: + ML-DSA-87
|
|
408
|
+
* privateKey: object, // the holder's key
|
|
409
|
+
* kid: string, // optional key id
|
|
410
|
+
* nonce: string, // optional verifier challenge (embedded + checked)
|
|
411
|
+
* audience: string, // optional intended verifier (embedded + checked)
|
|
412
|
+
* }
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* var vp = await b.vc.present({ credentials: [jws], holder: holderDid, securing: "jose", alg: "ES256", privateKey: holderKey, nonce: challenge });
|
|
416
|
+
*/
|
|
417
|
+
async function present(opts) {
|
|
418
|
+
validateOpts.requireObject(opts, "vc.present", VcError);
|
|
419
|
+
validateOpts(opts, ["credentials", "holder", "securing", "alg", "privateKey", "kid", "nonce", "audience"], "vc.present");
|
|
420
|
+
if (!Array.isArray(opts.credentials) || opts.credentials.length === 0) {
|
|
421
|
+
throw new VcError("vc/no-credentials", "vc.present: opts.credentials must be a non-empty array");
|
|
422
|
+
}
|
|
423
|
+
if (opts.credentials.length > MAX_PRESENTATION_CREDENTIALS) {
|
|
424
|
+
throw new VcError("vc/too-many-credentials", "vc.present: at most " + MAX_PRESENTATION_CREDENTIALS + " credentials per presentation");
|
|
425
|
+
}
|
|
426
|
+
if (typeof opts.holder !== "string" || !opts.holder) {
|
|
427
|
+
throw new VcError("vc/no-holder", "vc.present: opts.holder is required (the presenter id / DID)");
|
|
428
|
+
}
|
|
429
|
+
if (!opts.privateKey) throw new VcError("vc/no-key", "vc.present: opts.privateKey is required");
|
|
430
|
+
|
|
431
|
+
var vp = {
|
|
432
|
+
"@context": [VCDM_V2_CONTEXT],
|
|
433
|
+
type: ["VerifiablePresentation"],
|
|
434
|
+
holder: opts.holder,
|
|
435
|
+
verifiableCredential: opts.credentials.map(_envelopeVc),
|
|
436
|
+
};
|
|
437
|
+
if (typeof opts.nonce === "string") vp.nonce = opts.nonce;
|
|
438
|
+
if (typeof opts.audience === "string") vp.audience = opts.audience;
|
|
439
|
+
|
|
440
|
+
return _sign(vp, opts, VP_JOSE_TYP, VP_COSE_TYP, VP_COSE_CONTENT_TYPE, "vc.present");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @primitive b.vc.verifyPresentation
|
|
445
|
+
* @signature b.vc.verifyPresentation(secured, opts)
|
|
446
|
+
* @since 0.12.42
|
|
447
|
+
* @status experimental
|
|
448
|
+
* @compliance gdpr, soc2
|
|
449
|
+
* @related b.vc.present, b.vc.verify
|
|
450
|
+
*
|
|
451
|
+
* Verify a Verifiable Presentation: the holder signature (auto-detected
|
|
452
|
+
* jose / cose, mandatory algorithm allowlist, JOSE <code>none</code>
|
|
453
|
+
* refused), the VCDM structure, and the embedded <code>nonce</code> /
|
|
454
|
+
* <code>audience</code> / <code>expectedHolder</code> when given. With
|
|
455
|
+
* <code>verifyCredentials: true</code> each enveloped credential is
|
|
456
|
+
* verified through <code>b.vc.verify</code> (using
|
|
457
|
+
* <code>opts.credentialOpts</code>) and returned.
|
|
458
|
+
*
|
|
459
|
+
* @opts
|
|
460
|
+
* {
|
|
461
|
+
* algorithms: string[], // required — holder-signature alg allowlist
|
|
462
|
+
* publicKey: object, // the holder verification key
|
|
463
|
+
* keyResolver: function, // (header) → holder key
|
|
464
|
+
* expectedHolder: string, // require presentation holder to match
|
|
465
|
+
* nonce: string, // require embedded nonce to match
|
|
466
|
+
* audience: string, // require embedded audience to match
|
|
467
|
+
* verifyCredentials: boolean, // verify each enveloped VC via b.vc.verify
|
|
468
|
+
* credentialOpts: object, // opts passed to b.vc.verify for each VC
|
|
469
|
+
* }
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* var out = await b.vc.verifyPresentation(vp, {
|
|
473
|
+
* algorithms: ["ES256"], publicKey: holderKey, nonce: challenge,
|
|
474
|
+
* verifyCredentials: true, credentialOpts: { algorithms: ["ES256"], publicKey: issuerKey },
|
|
475
|
+
* });
|
|
476
|
+
* // → { presentation, holder, credentials: [verified VCs], securing, alg }
|
|
477
|
+
*/
|
|
478
|
+
async function verifyPresentation(secured, opts) {
|
|
479
|
+
validateOpts.requireObject(opts, "vc.verifyPresentation", VcError);
|
|
480
|
+
validateOpts(opts, ["algorithms", "publicKey", "keyResolver", "expectedHolder", "nonce", "audience", "verifyCredentials", "credentialOpts"], "vc.verifyPresentation");
|
|
481
|
+
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
482
|
+
throw new VcError("vc/algorithms-required", "vc.verifyPresentation: opts.algorithms is required");
|
|
483
|
+
}
|
|
484
|
+
if (!opts.publicKey && typeof opts.keyResolver !== "function") {
|
|
485
|
+
throw new VcError("vc/no-key", "vc.verifyPresentation: pass publicKey or keyResolver");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
var result = await _verifySecured(secured, opts, VP_JOSE_TYP, VP_COSE_TYP);
|
|
489
|
+
var vp = result.payload;
|
|
490
|
+
_validateVp(vp);
|
|
491
|
+
|
|
492
|
+
if (opts.expectedHolder !== undefined && vp.holder !== opts.expectedHolder) {
|
|
493
|
+
throw new VcError("vc/holder-mismatch", "vc.verifyPresentation: presentation holder does not match expectedHolder");
|
|
494
|
+
}
|
|
495
|
+
if (opts.nonce !== undefined && vp.nonce !== opts.nonce) {
|
|
496
|
+
throw new VcError("vc/nonce-mismatch", "vc.verifyPresentation: presentation nonce does not match");
|
|
497
|
+
}
|
|
498
|
+
if (opts.audience !== undefined && vp.audience !== opts.audience) {
|
|
499
|
+
throw new VcError("vc/audience-mismatch", "vc.verifyPresentation: presentation audience does not match");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
var entries = vp.verifiableCredential || []; // _validateVp guarantees array-or-absent
|
|
503
|
+
if (entries.length > MAX_PRESENTATION_CREDENTIALS) {
|
|
504
|
+
throw new VcError("vc/too-many-credentials", "vc.verifyPresentation: presentation carries more than " + MAX_PRESENTATION_CREDENTIALS + " credentials");
|
|
505
|
+
}
|
|
506
|
+
var credentials = [];
|
|
507
|
+
if (opts.verifyCredentials) {
|
|
508
|
+
var credOpts = opts.credentialOpts;
|
|
509
|
+
validateOpts.requireObject(credOpts, "vc.verifyPresentation.credentialOpts", VcError);
|
|
510
|
+
for (var i = 0; i < entries.length; i += 1) {
|
|
511
|
+
credentials.push(await verify(_parseEnvelopedVc(entries[i]), credOpts));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return { presentation: vp, holder: vp.holder, credentials: credentials, securing: result.securing, alg: result.alg };
|
|
320
516
|
}
|
|
321
517
|
|
|
322
518
|
module.exports = {
|
|
323
519
|
issue: issue,
|
|
324
520
|
verify: verify,
|
|
521
|
+
present: present,
|
|
522
|
+
verifyPresentation: verifyPresentation,
|
|
325
523
|
JOSE_ALGS: JOSE_ALGS,
|
|
326
524
|
VCDM_V2_CONTEXT: VCDM_V2_CONTEXT,
|
|
327
525
|
VcError: VcError,
|
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:2659855c-303c-4b3d-931b-a39839633612",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-25T07:30:43.059Z",
|
|
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.43",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.12.
|
|
25
|
+
"version": "0.12.43",
|
|
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.43",
|
|
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.43",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|