@blamejs/blamejs-shop 0.0.127 → 0.0.129
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 +4 -0
- package/lib/storefront.js +235 -6
- package/lib/vendor/MANIFEST.json +3 -3
- package/lib/vendor/blamejs/CHANGELOG.md +14 -0
- package/lib/vendor/blamejs/README.md +7 -1
- package/lib/vendor/blamejs/SECURITY.md +3 -0
- package/lib/vendor/blamejs/api-snapshot.json +408 -2
- package/lib/vendor/blamejs/index.js +6 -0
- package/lib/vendor/blamejs/lib/cose.js +212 -4
- package/lib/vendor/blamejs/lib/cwt.js +244 -0
- package/lib/vendor/blamejs/lib/eat.js +240 -0
- package/lib/vendor/blamejs/lib/mdoc.js +305 -0
- package/lib/vendor/blamejs/lib/scitt.js +243 -0
- package/lib/vendor/blamejs/lib/tsa.js +688 -0
- package/lib/vendor/blamejs/lib/vc.js +328 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.34.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.35.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.12.36.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.37.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.12.38.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.39.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.40.json +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +84 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/cwt.test.js +137 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/eat.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scitt.test.js +158 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +373 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +188 -0
- package/package.json +1 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.vc
|
|
4
|
+
* @nav Crypto
|
|
5
|
+
* @title Verifiable Credentials (W3C VCDM 2.0)
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Issue and verify W3C Verifiable Credentials (VC Data Model 2.0, a
|
|
9
|
+
* W3C Recommendation) secured per "Securing Verifiable Credentials
|
|
10
|
+
* using JOSE and COSE" (VC-JOSE-COSE, also a W3C Recommendation). A
|
|
11
|
+
* verifiable credential is a tamper-evident, cryptographically-signed
|
|
12
|
+
* set of claims an issuer makes about a subject — a diploma, a
|
|
13
|
+
* membership, a license, an age assertion.
|
|
14
|
+
*
|
|
15
|
+
* Two securing mechanisms are supported, both putting the credential
|
|
16
|
+
* itself (not a JWT/CWT claims wrapper) as the signed payload:
|
|
17
|
+
* <strong>JOSE</strong> produces a compact JWS with the <code>vc+jwt</code>
|
|
18
|
+
* media type (<code>typ</code> header <code>"vc+jwt"</code>), signed
|
|
19
|
+
* with the classical ES256 / 384 / 512 or EdDSA JOSE algorithms;
|
|
20
|
+
* <strong>COSE</strong> produces a COSE_Sign1 (<code>application/vc+cose</code>)
|
|
21
|
+
* over <code>b.cose</code>, adding ML-DSA-87 (PQC-forward) to that set.
|
|
22
|
+
* <code>b.vc.verify</code> auto-detects the form from the input (a
|
|
23
|
+
* compact-JWS string vs. COSE_Sign1 bytes).
|
|
24
|
+
*
|
|
25
|
+
* <code>b.vc.issue(credential, opts)</code> validates the credential
|
|
26
|
+
* against the VCDM 2.0 structural rules (the <code>credentials/v2</code>
|
|
27
|
+
* context first, a <code>VerifiableCredential</code> type, an issuer,
|
|
28
|
+
* a credential subject) and signs it. <code>b.vc.verify(secured, opts)</code>
|
|
29
|
+
* verifies the signature (the algorithm allowlist is mandatory; the
|
|
30
|
+
* JOSE <code>none</code> algorithm is always refused), re-checks the
|
|
31
|
+
* structural rules, and enforces the <code>validFrom</code> /
|
|
32
|
+
* <code>validUntil</code> validity window. This is the W3C model and
|
|
33
|
+
* is distinct from the IETF SD-JWT VC at <code>b.auth.sdJwtVc</code>.
|
|
34
|
+
*
|
|
35
|
+
* @card
|
|
36
|
+
* W3C Verifiable Credentials 2.0 (VC-JOSE-COSE) — issue / verify a
|
|
37
|
+
* signed credential as a compact JWS (vc+jwt) or a COSE_Sign1
|
|
38
|
+
* (vc+cose), with VCDM structural + validity-window checks. Composes
|
|
39
|
+
* b.cose; the JOSE alg `none` is always refused.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
var nodeCrypto = require("node:crypto");
|
|
43
|
+
var cose = require("./cose");
|
|
44
|
+
var safeJson = require("./safe-json");
|
|
45
|
+
var validateOpts = require("./validate-opts");
|
|
46
|
+
var { defineClass } = require("./framework-error");
|
|
47
|
+
|
|
48
|
+
var VcError = defineClass("VcError", { alwaysPermanent: true });
|
|
49
|
+
|
|
50
|
+
var VCDM_V2_CONTEXT = "https://www.w3.org/ns/credentials/v2";
|
|
51
|
+
var JOSE_TYP = "vc+jwt";
|
|
52
|
+
var COSE_TYP = "application/vc+cose";
|
|
53
|
+
var COSE_CONTENT_TYPE = "application/vc";
|
|
54
|
+
var HDR_COSE_TYP = 16; // allow:raw-byte-literal — COSE "typ" header label (RFC 9596)
|
|
55
|
+
|
|
56
|
+
// JOSE signature algorithms (final RFC 7518 / 8037), mapped to node
|
|
57
|
+
// verify parameters. ECDSA uses the IEEE-P1363 fixed-width encoding JOSE
|
|
58
|
+
// mandates (not ASN.1 DER). There is no signing default — the caller
|
|
59
|
+
// names the algorithm, mirroring b.cose.
|
|
60
|
+
var JOSE_ALGS = {
|
|
61
|
+
"ES256": { nodeHash: "sha256", dsaEncoding: "ieee-p1363" },
|
|
62
|
+
"ES384": { nodeHash: "sha384", dsaEncoding: "ieee-p1363" },
|
|
63
|
+
"ES512": { nodeHash: "sha512", dsaEncoding: "ieee-p1363" },
|
|
64
|
+
"EdDSA": { nodeHash: null },
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function _b64urlJson(obj) {
|
|
68
|
+
return Buffer.from(JSON.stringify(obj), "utf8").toString("base64url");
|
|
69
|
+
}
|
|
70
|
+
function _toKey(key, kind) {
|
|
71
|
+
if (key && typeof key === "object" && typeof key.asymmetricKeyType === "string") return key;
|
|
72
|
+
try {
|
|
73
|
+
return kind === "private" ? nodeCrypto.createPrivateKey(key) : nodeCrypto.createPublicKey(key);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
throw new VcError("vc/bad-key", "vc: could not load " + kind + " key: " + ((e && e.message) || e));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _issuerId(cred) {
|
|
80
|
+
if (typeof cred.issuer === "string") return cred.issuer;
|
|
81
|
+
if (cred.issuer && typeof cred.issuer === "object" && typeof cred.issuer.id === "string") return cred.issuer.id;
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// VCDM 2.0 structural rules; temporal checks only on verify.
|
|
86
|
+
function _validateVcdm(cred, opts) {
|
|
87
|
+
if (!cred || typeof cred !== "object" || Array.isArray(cred)) {
|
|
88
|
+
throw new VcError("vc/bad-credential", "vc: credential must be a JSON object");
|
|
89
|
+
}
|
|
90
|
+
var ctx = cred["@context"];
|
|
91
|
+
if (!Array.isArray(ctx) || ctx[0] !== VCDM_V2_CONTEXT) {
|
|
92
|
+
throw new VcError("vc/bad-context",
|
|
93
|
+
"vc: @context must be an array whose first element is '" + VCDM_V2_CONTEXT + "'");
|
|
94
|
+
}
|
|
95
|
+
var types = Array.isArray(cred.type) ? cred.type : [cred.type];
|
|
96
|
+
if (types.indexOf("VerifiableCredential") === -1) {
|
|
97
|
+
throw new VcError("vc/bad-type", "vc: type must include 'VerifiableCredential'");
|
|
98
|
+
}
|
|
99
|
+
if (_issuerId(cred) === undefined) {
|
|
100
|
+
throw new VcError("vc/no-issuer", "vc: issuer is required (a URL string or an object with an id)");
|
|
101
|
+
}
|
|
102
|
+
if (cred.credentialSubject === undefined || cred.credentialSubject === null) {
|
|
103
|
+
throw new VcError("vc/no-subject", "vc: credentialSubject is required");
|
|
104
|
+
}
|
|
105
|
+
// validFrom / validUntil, when present, MUST be valid XSD dateTimes
|
|
106
|
+
// (VCDM 2.0 §4.9). A malformed value fails closed at both issue and
|
|
107
|
+
// verify rather than silently skipping the window check.
|
|
108
|
+
var vf = _parseValidityField(cred, "validFrom");
|
|
109
|
+
var vu = _parseValidityField(cred, "validUntil");
|
|
110
|
+
if (opts && opts.temporal) {
|
|
111
|
+
var nowMs = opts.at.getTime();
|
|
112
|
+
if (vf !== null && nowMs < vf) {
|
|
113
|
+
throw new VcError("vc/not-yet-valid", "vc.verify: credential validFrom (" + cred.validFrom + ") is in the future");
|
|
114
|
+
}
|
|
115
|
+
if (vu !== null && nowMs > vu) {
|
|
116
|
+
throw new VcError("vc/expired", "vc.verify: credential validUntil (" + cred.validUntil + ") has passed");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function _parseValidityField(cred, name) {
|
|
122
|
+
if (cred[name] === undefined) return null;
|
|
123
|
+
if (typeof cred[name] !== "string") {
|
|
124
|
+
throw new VcError("vc/bad-validity", "vc: " + name + " must be an XSD dateTime string");
|
|
125
|
+
}
|
|
126
|
+
var ms = Date.parse(cred[name]);
|
|
127
|
+
if (!isFinite(ms)) {
|
|
128
|
+
throw new VcError("vc/bad-validity", "vc: " + name + " is not a valid dateTime: " + cred[name]);
|
|
129
|
+
}
|
|
130
|
+
return ms;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @primitive b.vc.issue
|
|
135
|
+
* @signature b.vc.issue(credential, opts)
|
|
136
|
+
* @since 0.12.39
|
|
137
|
+
* @status experimental
|
|
138
|
+
* @compliance gdpr, soc2
|
|
139
|
+
* @related b.vc.verify, b.cose.sign
|
|
140
|
+
*
|
|
141
|
+
* Validate a credential against the VCDM 2.0 structural rules and secure
|
|
142
|
+
* it. <code>securing: "jose"</code> returns a compact JWS string (media
|
|
143
|
+
* type <code>vc+jwt</code>) signed with an ES256/384/512 or EdDSA key;
|
|
144
|
+
* <code>securing: "cose"</code> returns COSE_Sign1 bytes (media type
|
|
145
|
+
* <code>application/vc+cose</code>) over <code>b.cose</code>, which also
|
|
146
|
+
* accepts <code>"ML-DSA-87"</code>. The credential itself is the signed
|
|
147
|
+
* payload — no JWT/CWT claims wrapper is added.
|
|
148
|
+
*
|
|
149
|
+
* @opts
|
|
150
|
+
* {
|
|
151
|
+
* securing: string, // "jose" (compact JWS) | "cose" (COSE_Sign1)
|
|
152
|
+
* alg: string, // JOSE: ES256/384/512 | EdDSA. COSE: + ML-DSA-87
|
|
153
|
+
* privateKey: object, // matching KeyObject or PEM
|
|
154
|
+
* kid: string, // optional key id (header)
|
|
155
|
+
* cty: string, // optional JOSE cty (e.g. "vc")
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* var jws = await b.vc.issue(credential, { securing: "jose", alg: "ES256", privateKey: key });
|
|
160
|
+
* // → a compact JWS string with typ "vc+jwt"
|
|
161
|
+
*/
|
|
162
|
+
async function issue(credential, opts) {
|
|
163
|
+
validateOpts.requireObject(opts, "vc.issue", VcError);
|
|
164
|
+
validateOpts(opts, ["securing", "alg", "privateKey", "kid", "cty"], "vc.issue");
|
|
165
|
+
_validateVcdm(credential, null);
|
|
166
|
+
if (!opts.privateKey) throw new VcError("vc/no-key", "vc.issue: opts.privateKey is required");
|
|
167
|
+
|
|
168
|
+
if (opts.securing === "cose") {
|
|
169
|
+
var protectedHeaders = {};
|
|
170
|
+
protectedHeaders[HDR_COSE_TYP] = COSE_TYP;
|
|
171
|
+
return cose.sign(Buffer.from(JSON.stringify(credential), "utf8"), {
|
|
172
|
+
alg: opts.alg,
|
|
173
|
+
privateKey: opts.privateKey,
|
|
174
|
+
kid: opts.kid,
|
|
175
|
+
contentType: COSE_CONTENT_TYPE,
|
|
176
|
+
protectedHeaders: protectedHeaders,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (opts.securing === "jose") {
|
|
180
|
+
var params = JOSE_ALGS[opts.alg];
|
|
181
|
+
if (!params) {
|
|
182
|
+
throw new VcError("vc/bad-alg", "vc.issue: JOSE securing requires alg ES256/384/512 or EdDSA (got " + opts.alg + ")");
|
|
183
|
+
}
|
|
184
|
+
var key = _toKey(opts.privateKey, "private");
|
|
185
|
+
var header = { alg: opts.alg, typ: JOSE_TYP };
|
|
186
|
+
if (typeof opts.kid === "string") header.kid = opts.kid;
|
|
187
|
+
if (typeof opts.cty === "string") header.cty = opts.cty;
|
|
188
|
+
var signingInput = _b64urlJson(header) + "." + _b64urlJson(credential);
|
|
189
|
+
var sig = params.nodeHash === null
|
|
190
|
+
? nodeCrypto.sign(null, Buffer.from(signingInput, "ascii"), key)
|
|
191
|
+
: nodeCrypto.sign(params.nodeHash, Buffer.from(signingInput, "ascii"), { key: key, dsaEncoding: params.dsaEncoding });
|
|
192
|
+
return signingInput + "." + sig.toString("base64url");
|
|
193
|
+
}
|
|
194
|
+
throw new VcError("vc/bad-securing", "vc.issue: securing must be 'jose' or 'cose'");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function _verifyJose(token, opts) {
|
|
198
|
+
var parts = token.split(".");
|
|
199
|
+
if (parts.length !== 3) {
|
|
200
|
+
throw new VcError("vc/malformed", "vc.verify: not a compact JWS (expected three dot-separated segments)");
|
|
201
|
+
}
|
|
202
|
+
var header;
|
|
203
|
+
try { header = safeJson.parse(Buffer.from(parts[0], "base64url").toString("utf8")); }
|
|
204
|
+
catch (_e) { throw new VcError("vc/malformed", "vc.verify: JWS header is not valid base64url-JSON"); }
|
|
205
|
+
if (!header || header.typ !== JOSE_TYP) {
|
|
206
|
+
throw new VcError("vc/bad-typ", "vc.verify: JWS typ must be '" + JOSE_TYP + "'");
|
|
207
|
+
}
|
|
208
|
+
// crit-bypass defense (RFC 7515 §4.1.11): a `crit` header marks
|
|
209
|
+
// extensions the verifier MUST understand and process. This verifier
|
|
210
|
+
// implements no critical extensions, so any `crit` is refused rather
|
|
211
|
+
// than ignored — accepting it would mean honoring a credential under
|
|
212
|
+
// weaker semantics than the issuer marked mandatory.
|
|
213
|
+
if (header.crit !== undefined) {
|
|
214
|
+
throw new VcError("vc/crit-unsupported",
|
|
215
|
+
"vc.verify: JWS 'crit' header lists extensions this verifier does not support (RFC 7515 §4.1.11)");
|
|
216
|
+
}
|
|
217
|
+
if (header.alg === "none" || !JOSE_ALGS[header.alg]) {
|
|
218
|
+
throw new VcError("vc/bad-alg", "vc.verify: unsupported or unsecured JWS alg '" + header.alg + "'");
|
|
219
|
+
}
|
|
220
|
+
if (opts.algorithms.indexOf(header.alg) === -1) {
|
|
221
|
+
throw new VcError("vc/alg-not-allowed", "vc.verify: alg '" + header.alg + "' is not in the allowlist");
|
|
222
|
+
}
|
|
223
|
+
var params = JOSE_ALGS[header.alg];
|
|
224
|
+
var pub = opts.publicKey ? _toKey(opts.publicKey, "public") : _toKey(opts.keyResolver(header), "public");
|
|
225
|
+
var signingInput = parts[0] + "." + parts[1];
|
|
226
|
+
var sig = Buffer.from(parts[2], "base64url");
|
|
227
|
+
var ok = params.nodeHash === null
|
|
228
|
+
? nodeCrypto.verify(null, Buffer.from(signingInput, "ascii"), pub, sig)
|
|
229
|
+
: nodeCrypto.verify(params.nodeHash, Buffer.from(signingInput, "ascii"), { key: pub, dsaEncoding: params.dsaEncoding }, sig);
|
|
230
|
+
if (!ok) throw new VcError("vc/bad-signature", "vc.verify: JWS signature did not verify");
|
|
231
|
+
var credential;
|
|
232
|
+
try { credential = safeJson.parse(Buffer.from(parts[1], "base64url").toString("utf8")); }
|
|
233
|
+
catch (_e) { throw new VcError("vc/malformed", "vc.verify: JWS payload is not valid base64url-JSON"); }
|
|
234
|
+
return { credential: credential, alg: header.alg };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function _verifyCose(bytes, opts) {
|
|
238
|
+
var algorithms = opts.algorithms.filter(function (a) { return a in cose.ALGORITHMS; });
|
|
239
|
+
if (!algorithms.length) {
|
|
240
|
+
throw new VcError("vc/no-cose-alg", "vc.verify: opts.algorithms has no COSE algorithm for a vc+cose credential");
|
|
241
|
+
}
|
|
242
|
+
var out = await cose.verify(bytes, {
|
|
243
|
+
algorithms: algorithms,
|
|
244
|
+
publicKey: opts.publicKey,
|
|
245
|
+
keyResolver: opts.keyResolver,
|
|
246
|
+
});
|
|
247
|
+
var typ = out.protectedHeaders.get(HDR_COSE_TYP);
|
|
248
|
+
if (typ !== undefined && typ !== COSE_TYP) {
|
|
249
|
+
throw new VcError("vc/bad-typ", "vc.verify: COSE typ header is '" + typ + "', expected '" + COSE_TYP + "'");
|
|
250
|
+
}
|
|
251
|
+
var credential;
|
|
252
|
+
try { credential = safeJson.parse(out.payload.toString("utf8")); }
|
|
253
|
+
catch (_e) { throw new VcError("vc/malformed", "vc.verify: COSE payload is not valid JSON"); }
|
|
254
|
+
return { credential: credential, alg: out.alg };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @primitive b.vc.verify
|
|
259
|
+
* @signature b.vc.verify(secured, opts)
|
|
260
|
+
* @since 0.12.39
|
|
261
|
+
* @status experimental
|
|
262
|
+
* @compliance gdpr, soc2
|
|
263
|
+
* @related b.vc.issue, b.cose.verify
|
|
264
|
+
*
|
|
265
|
+
* Verify a secured verifiable credential and return the credential. The
|
|
266
|
+
* securing form is auto-detected (a compact-JWS string vs. COSE_Sign1
|
|
267
|
+
* bytes); the algorithm allowlist is mandatory and the JOSE
|
|
268
|
+
* <code>none</code> algorithm is always refused. After the signature,
|
|
269
|
+
* the VCDM 2.0 structural rules are re-checked and the
|
|
270
|
+
* <code>validFrom</code> / <code>validUntil</code> window is enforced
|
|
271
|
+
* against <code>opts.at</code> (default: now).
|
|
272
|
+
*
|
|
273
|
+
* @opts
|
|
274
|
+
* {
|
|
275
|
+
* algorithms: string[], // required — accepted alg names (allowlist)
|
|
276
|
+
* publicKey: object, // verification key (KeyObject / PEM)
|
|
277
|
+
* keyResolver: function, // (header) → key (alternative to publicKey)
|
|
278
|
+
* expectedIssuer: string, // require the credential issuer (id) to match
|
|
279
|
+
* at: Date, // validity instant (default: now); must be a valid Date
|
|
280
|
+
* }
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* var out = await b.vc.verify(jws, { algorithms: ["ES256"], publicKey: issuerPub, expectedIssuer: "did:example:123" });
|
|
284
|
+
* // → { credential, securing: "jose", alg: "ES256", issuer: "did:example:123" }
|
|
285
|
+
*/
|
|
286
|
+
async function verify(secured, opts) {
|
|
287
|
+
validateOpts.requireObject(opts, "vc.verify", VcError);
|
|
288
|
+
validateOpts(opts, ["algorithms", "publicKey", "keyResolver", "expectedIssuer", "at"], "vc.verify");
|
|
289
|
+
if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
|
|
290
|
+
throw new VcError("vc/algorithms-required", "vc.verify: opts.algorithms is required (name the accepted algorithms)");
|
|
291
|
+
}
|
|
292
|
+
if (!opts.publicKey && typeof opts.keyResolver !== "function") {
|
|
293
|
+
throw new VcError("vc/no-key", "vc.verify: pass publicKey or keyResolver");
|
|
294
|
+
}
|
|
295
|
+
var at = new Date();
|
|
296
|
+
if (opts.at !== undefined && opts.at !== null) {
|
|
297
|
+
if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
|
|
298
|
+
throw new VcError("vc/bad-at", "vc.verify: opts.at must be a valid Date");
|
|
299
|
+
}
|
|
300
|
+
at = opts.at;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
var securing, result;
|
|
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
|
+
}
|
|
313
|
+
|
|
314
|
+
_validateVcdm(result.credential, { temporal: true, at: at });
|
|
315
|
+
var issuer = _issuerId(result.credential);
|
|
316
|
+
if (opts.expectedIssuer !== undefined && issuer !== opts.expectedIssuer) {
|
|
317
|
+
throw new VcError("vc/issuer-mismatch", "vc.verify: credential issuer does not match expectedIssuer");
|
|
318
|
+
}
|
|
319
|
+
return { credential: result.credential, securing: securing, alg: result.alg, issuer: issuer };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = {
|
|
323
|
+
issue: issue,
|
|
324
|
+
verify: verify,
|
|
325
|
+
JOSE_ALGS: JOSE_ALGS,
|
|
326
|
+
VCDM_V2_CONTEXT: VCDM_V2_CONTEXT,
|
|
327
|
+
VcError: VcError,
|
|
328
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.34",
|
|
4
|
+
"date": "2026-05-24",
|
|
5
|
+
"headline": "`b.cwt` — CBOR Web Token (RFC 8392) sign / verify over `b.cose`",
|
|
6
|
+
"summary": "A CWT is the CBOR-native counterpart to JWT — a signed claims set for constrained / IoT, FIDO attestation, and verifiable-credential contexts. `b.cwt` composes the v0.12.33 `b.cose` (COSE_Sign1 signature + mandatory algorithm allowlist) and v0.12.32 `b.cbor` (deterministic claims encoding) and layers the standard-claim handling on top: `sign` takes a friendly claims object, maps the standard claims to their RFC 8392 §3.1.1 integer labels (iss=1, sub=2, aud=3, exp=4, nbf=5, iat=6, cti=7), and signs; `verify` checks the COSE signature, decodes the claims, and enforces the time + identity claims — a passed `exp` (with clock-skew tolerance), a future `nbf`, and an `iss` / `aud` mismatch against the expected values are each refused. Signing algorithms follow `b.cose`: classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) and ML-DSA-87 (PQC-forward). RFC 8392 is a finalized standard, so CWTs produced here interoperate with other COSE/CWT implementations.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.cwt.sign(claims, opts)` / `b.cwt.verify(cwt, opts)`",
|
|
13
|
+
"body": "`sign` maps standard claim names to integer labels and keeps custom claims verbatim; `exp` / `nbf` / `iat` must be non-negative integer NumericDates. `opts.tagged` wraps the COSE_Sign1 in the CWT CBOR tag 61 (RFC 8392 §6); `verify` accepts tagged or bare input. `verify` returns `{ claims, raw, alg, protectedHeaders }` — `claims` is the friendly object (labels mapped back to names), `raw` the integer-keyed Map. Standard-claim enforcement: `exp` past `now + clockSkewSec` (default 60s) is refused with `cwt/expired`, `nbf` beyond `now - skew` with `cwt/not-yet-valid`, and `expectedIssuer` / `expectedAudience` mismatches with `cwt/issuer-mismatch` / `cwt/audience-mismatch` (aud may be a single value or an array). `opts.now` overrides the clock for testing. The signature itself is verified by `b.cose.verify`, so a tampered token fails there."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.35",
|
|
4
|
+
"date": "2026-05-24",
|
|
5
|
+
"headline": "`b.eat` — Entity Attestation Token (RFC 9711) over `b.cwt`",
|
|
6
|
+
"summary": "An EAT is the token a Relying Party asks a device or software entity to produce to prove what it is and what state it is in — a freshness nonce, a Universal Entity ID, OEM / hardware identifiers, debug status, software measurements, and nested submodule attestations. `b.eat` is the RFC 9711 profile over the v0.12.34 `b.cwt`: it maps the EAT claim names to their IANA CWT claim-key integer labels and adds the attestation-specific verification on top of the CWT signature + time checks. The central control is the verifier-nonce binding: when the Relying Party supplies a fresh `expectedNonce`, the token's `eat_nonce` (claim 10) must match it (constant-time compare) — without it a captured attestation replays forever. `verify` also enforces a debug-status policy (`requireDebugDisabled` refuses an `enabled` or absent `dbgstat`) and pins the `eat_profile`. RFC 9711 is a finalized standard; signing follows `b.cwt` / `b.cose` (ES256/384/512 + EdDSA interoperable today, ML-DSA-87 PQC-forward).",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.eat.sign(claims, opts)` / `b.eat.verify(eat, opts)`",
|
|
13
|
+
"body": "`sign` maps EAT claim names (`nonce`, `ueid`, `oemid`, `hwmodel`, `dbgstat`, `eat_profile`, `swname`/`swversion`, `measurements`, `submods`, …) to their RFC 9711 integer labels and accepts the `dbgstat` enum by name (`disabled-since-boot` → 2); standard CWT claims (`iss` / `exp` / …) pass through. `verify` returns `{ claims, raw, alg, protectedHeaders }` with the labels mapped back to friendly names and `dbgstat` decoded to its enum name. Attestation enforcement: `expectedNonce` requires a matching `eat_nonce` (refused `eat/nonce-mismatch`, missing `eat/nonce-missing` — `eat_nonce` may be a single byte string or an array for multiple verifiers), `requireDebugDisabled` refuses a non-disabled `dbgstat` (`eat/debug-not-disabled`), and `expectedProfile` pins `eat_profile`. The signature, algorithm allowlist, and `exp`/`nbf` checks delegate to `b.cwt` / `b.cose`."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "`b.cwt.sign` accepts a `Map`",
|
|
17
|
+
"body": "`b.cwt.sign` now takes either a plain object (string keys, standard claims mapped by name) or a `Map`, which preserves integer claim keys verbatim — profiles like `b.eat` resolve their claim names to integer labels and pass them through without the keys being stringified. The plain-object path is unchanged."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.36",
|
|
4
|
+
"date": "2026-05-24",
|
|
5
|
+
"headline": "`b.cose.encrypt0` / `b.cose.decrypt0` — COSE_Encrypt0 single-recipient AEAD (RFC 9052 §5.2)",
|
|
6
|
+
"summary": "Completes the COSE family with encryption alongside the v0.12.33 signing: COSE_Encrypt0 is the single-recipient AEAD container where the recipient already holds the symmetric key (direct mode). The default algorithm is ChaCha20/Poly1305 (COSE alg 24) — AES-GCM stays opt-in, since hard-rule #2 forbids AES-GCM as a default. The Enc_structure (`[\"Encrypt0\", protected, external_aad]`) is bound as the AEAD associated data so the algorithm + any external context are authenticated, and the authentication tag is appended to the ciphertext per COSE. Composes the in-tree `b.cbor` codec and `node:crypto` AEAD.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.cose.encrypt0(plaintext, opts)` / `b.cose.decrypt0(coseEncrypt0, opts)`",
|
|
13
|
+
"body": "`encrypt0` produces a tagged COSE_Encrypt0 with `alg` in the protected header and a random 12-byte IV in the unprotected header (label 5); `alg` is `\"ChaCha20-Poly1305\"` (default), `\"A256GCM\"`, or `\"A128GCM\"`, with the key length enforced (32 / 16 bytes). `decrypt0` reads the algorithm from the protected header (must be in the required `opts.algorithms` allowlist), reconstructs the Enc_structure as the AEAD AAD, and returns `{ plaintext, alg, protectedHeaders, unprotectedHeaders }`; a wrong key, tampered ciphertext, or `external_aad` mismatch fails AEAD authentication and is refused with `cose/decrypt-failed`. `external_aad` binds request context into the tag."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.37",
|
|
4
|
+
"date": "2026-05-24",
|
|
5
|
+
"headline": "`b.scitt.signStatement` / `b.scitt.verifyStatement` — SCITT signed statements over COSE (RFC 9052 + RFC 9597)",
|
|
6
|
+
"summary": "A SCITT signed statement is a signed, attributable claim about an artifact — a signed SBOM, a build attestation, a release approval. It is a COSE_Sign1 (b.cose) whose integrity-protected CWT_Claims header (label 15, RFC 9597) binds the issuer (who makes the statement) and the subject (the artifact it is about); the artifact, or a hash/reference to it, is the payload. signStatement places iss/sub in the protected header and declares the payload media type; verifyStatement checks the COSE signature (the algorithm allowlist is mandatory) and refuses any statement that lacks the iss/sub binding, with optional expected-issuer/subject matching. Signing uses the same algorithms as b.cose — classical ES256/384/512 + EdDSA (final COSE ids, interoperable today) plus ML-DSA-87 (PQC-forward). This is the issuer side of SCITT, buildable today on finalized RFCs; the transparency receipt (an inclusion proof from a transparency service, the COSE Receipts draft) is not yet shipped — a statement produced here is the input a transparency service registers, and the receipt format is the part still in flux. It opts in when COSE Receipts publishes.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.scitt.signStatement(payload, opts)` / `b.scitt.verifyStatement(statement, opts)`",
|
|
13
|
+
"body": "`signStatement` produces a COSE_Sign1 whose protected CWT_Claims header (label 15) carries `iss` (`opts.issuer`) and `sub` (`opts.subject`), with the payload media type declared via `opts.contentType` and extra CWT claims allowed by integer label (iss/sub cannot be overridden through `opts.claims`). `verifyStatement` verifies the signature through `b.cose.verify` (passing `opts.algorithms` as the mandatory allowlist), then requires a CWT_Claims header with both `iss` and `sub` — a bare COSE_Sign1 with no such binding is refused with `scitt/missing-cwt-claims` — and enforces `expectedIssuer` / `expectedSubject` when given. Returns `{ payload, issuer, subject, cwtClaims, alg, protectedHeaders, unprotectedHeaders }`. Because the identity binding lives in the integrity-protected header it is covered by the signature and cannot be substituted without detection."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"heading": "Changed",
|
|
19
|
+
"items": [
|
|
20
|
+
{
|
|
21
|
+
"title": "`b.cose.sign` accepts `protectedHeaders` and a media-type-string `contentType`",
|
|
22
|
+
"body": "`opts.protectedHeaders` (a numeric-keyed object or Map) adds extra integrity-protected header parameters — the CWT_Claims map (label 15) is the SCITT case. Label 1 (alg) is reserved and managed via `opts.alg`; setting it through `protectedHeaders` is refused with `cose/reserved-header`. `opts.contentType` now accepts a media-type string (RFC 9052 §3.1 tstr form, e.g. `\"application/spdx+json\"`) in addition to a CoAP Content-Format uint; a string was previously dropped."
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.38",
|
|
4
|
+
"date": "2026-05-24",
|
|
5
|
+
"headline": "`b.tsa` — RFC 3161 trusted timestamping client (build / parse / verify)",
|
|
6
|
+
"summary": "A timestamp authority binds a hash of your data to a trusted time, producing a token that proves the data existed at that instant — timestamp a release artifact, an audit-log checkpoint, a b.scitt signed statement, or a contract. b.tsa is the requester/verifier side of RFC 3161: buildRequest produces the DER TimeStampReq (the message imprint plus an optional nonce and a cert request), parseResponse reads the TimeStampResp (PKIStatus, failure-info bits, and the token), and verifyToken checks a token against your data and returns the asserted time. Verification is done in full per §2.4.2 / §2.3: the token is a CMS SignedData (b.cms) whose eContentType must be id-ct-TSTInfo; the message imprint must equal the hash of your data (constant-time); a sent nonce must round-trip; the signer certificate's extendedKeyUsage must be a critical, sole id-kp-timeStamping; and the CMS signature over the signed attributes must verify after the messageDigest attribute is matched to the recomputed eContent digest. An optional trust-anchor set verifies the certificate chain and validity at the asserted time. The HTTP transport to the TSA is the operator's to make. Composes b.cms and the in-tree ASN.1 DER codec; no new runtime dependency.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.tsa.buildRequest(data, opts?)` / `b.tsa.parseResponse(der)` / `b.tsa.verifyToken(token, opts)`",
|
|
13
|
+
"body": "`buildRequest` returns `{ der, nonce, hashAlg, messageImprint }`; the imprint hash defaults to SHA-512 and may be SHA-256/384/512 or SHA3-256/512, a random 64-bit nonce and a certificate request are included by default, and a pre-hashed input is accepted with `hashed: true`. `parseResponse` returns `{ granted, status, statusString, failInfo, token }`, decoding the PKIFailureInfo bits for a non-granted response rather than throwing. `verifyToken` enforces the imprint match (`opts.data` or `opts.hash`), the nonce round-trip, the critical/sole `id-kp-timeStamping` EKU, and the CMS signature, returning `{ genTime, policy, serialHex, accuracy, hashAlg, signerCertPem }`; pass `opts.trustAnchorsPem` to also verify the certificate chain and validity at the asserted time. Timestamp tokens are third-party artifacts, so verification accepts the classical RSA (PKCS#1 v1.5 and PSS) and ECDSA-over-SHA-2 signatures that public TSAs emit — the same consume-what-exists posture as `b.cose` verification, not a framework signing default."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.39",
|
|
4
|
+
"date": "2026-05-24",
|
|
5
|
+
"headline": "`b.vc` — W3C Verifiable Credentials 2.0 (issue / verify, JOSE + COSE securing)",
|
|
6
|
+
"summary": "Issue and verify W3C Verifiable Credentials (VC Data Model 2.0, a W3C Recommendation) secured per Securing Verifiable Credentials using JOSE and COSE (VC-JOSE-COSE, also a W3C Recommendation, May 2025). A verifiable credential is a tamper-evident, signed set of claims an issuer makes about a subject — a diploma, a membership, a license, an age assertion. Two securing mechanisms are supported, both signing the credential itself (no JWT/CWT claims wrapper): JOSE produces a compact JWS with the vc+jwt media type, signed with ES256/384/512 or EdDSA; COSE produces a COSE_Sign1 (application/vc+cose) over b.cose, which also accepts ML-DSA-87 for PQC-forward deployments. b.vc.verify auto-detects the form from the input, requires an algorithm allowlist, always refuses the JOSE none algorithm, re-checks the VCDM 2.0 structural rules, and enforces the validFrom / validUntil window. This is the W3C credential model, distinct from the IETF SD-JWT VC already at b.auth.sdJwtVc. Composes b.cose; no new runtime dependency.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.vc.issue(credential, opts)` / `b.vc.verify(secured, opts)`",
|
|
13
|
+
"body": "`issue` validates the credential against the VCDM 2.0 structural rules (the `credentials/v2` context first, a `VerifiableCredential` type, an issuer, a credential subject) and signs it: `securing: \"jose\"` returns a compact JWS string (`typ` header `vc+jwt`), `securing: \"cose\"` returns COSE_Sign1 bytes (`typ` header `application/vc+cose`, content type `application/vc`) via `b.cose`. The credential is the exact signed payload — no JWT/CWT claims are injected. `verify` auto-detects the securing form from the input (compact-JWS string vs. COSE_Sign1 bytes), verifies the signature against the mandatory `opts.algorithms` allowlist (the JOSE `none` algorithm is always refused), re-checks the structural rules, enforces the `validFrom` / `validUntil` window against `opts.at` (default now; must be a valid Date), and optionally matches `opts.expectedIssuer` against the credential issuer id. Returns `{ credential, securing, alg, issuer }`."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.40",
|
|
4
|
+
"date": "2026-05-24",
|
|
5
|
+
"headline": "`b.mdoc` — ISO 18013-5 mdoc / mDL issuer-data verification",
|
|
6
|
+
"summary": "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.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.mdoc.verifyIssuerSigned(issuerSigned, opts)`",
|
|
13
|
+
"body": "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."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -2252,6 +2252,33 @@ async function testNoDuplicateCodeBlocks() {
|
|
|
2252
2252
|
],
|
|
2253
2253
|
reason: "v0.12.33 — opts / structure validation prelude (`validateOpts(allowedKeys) + chained required-field + typeof guards + typed-error throw`). cose.verify validates a COSE_Sign1 opts blob + decoded structure (RFC 9052); the peers each validate a distinct spec's shape (SD-JWT-VC issuer opts / break-glass policy set / JSCalendar object / DDL dual-control declaration / DSR request / FedCM well-known manifest / Android Asset Links / heartbeat config). Each throws a primitive-specific typed error; the shingle is the validateOpts-then-guard idiom, not behaviour. Same family as the v0.12.29 input-shape-validation cluster.",
|
|
2254
2254
|
},
|
|
2255
|
+
{
|
|
2256
|
+
mode: "family-subset",
|
|
2257
|
+
files: [
|
|
2258
|
+
"lib/mdoc.js:verifyIssuerSigned",
|
|
2259
|
+
"lib/tsa.js:verifyToken",
|
|
2260
|
+
"lib/vc.js:verify",
|
|
2261
|
+
],
|
|
2262
|
+
reason: "v0.12.40 — signature-verify entry preamble shared by three credential / token verifiers: `validateOpts(allowedKeys) + mandatory algorithms-allowlist check + opts.at valid-Date guard + publicKey/keyResolver presence check`, then divergent domain logic. tsa.verifyToken verifies an RFC 3161 timestamp token (CMS SignedData + message-imprint + EKU); vc.verify verifies a W3C VC-JOSE-COSE credential (JWS/COSE + VCDM structural + validity window); mdoc.verifyIssuerSigned verifies an ISO 18013-5 mdoc (COSE_Sign1 IssuerAuth + MSO valueDigests matching). Each consumes a different wire format, returns a different shape, and throws a primitive-specific typed error — the shingle is the validate-then-guard preamble, not behaviour. Same family as the v0.12.33 cose.verify cluster.",
|
|
2263
|
+
},
|
|
2264
|
+
{
|
|
2265
|
+
mode: "family-subset",
|
|
2266
|
+
files: [
|
|
2267
|
+
"lib/dual-control.js:create",
|
|
2268
|
+
"lib/mdoc.js:verifyIssuerSigned",
|
|
2269
|
+
"lib/tsa.js:verifyToken",
|
|
2270
|
+
],
|
|
2271
|
+
reason: "v0.12.40 — validateOpts-then-guard prelude shared between a create-style validator (dual-control.create builds a two-person-rule grant after validating its opts) and the timestamp / mdoc verifiers. The common shingle is the `validateOpts(allowedKeys) + chained guard + typed-error` idiom; the bodies diverge entirely (dual-control persists a control record; tsa/mdoc verify cryptographic structures). Same validate-then-guard family as the v0.12.29 / v0.12.33 clusters.",
|
|
2272
|
+
},
|
|
2273
|
+
{
|
|
2274
|
+
mode: "family-subset",
|
|
2275
|
+
files: [
|
|
2276
|
+
"lib/eat.js:verify",
|
|
2277
|
+
"lib/auth/jar.js:parse",
|
|
2278
|
+
"lib/auth/status-list.js:fromJwt",
|
|
2279
|
+
],
|
|
2280
|
+
reason: "v0.12.35 — token-verify delegation prelude: each validates an opts blob, delegates the signature/structure verification to a lower primitive, then applies its own post-checks + builds a typed result. eat.verify delegates to b.cwt.verify then enforces nonce / dbgstat / profile (RFC 9711); jar.parse delegates to b.auth.jwt.verifyExternal then enforces iss / client_id / anti-nesting (RFC 9101); status-list.fromJwt delegates to a JWS verify then reads the status-list bits. The shingle is the validate→delegate→post-check shape; each enforces a distinct spec's claims.",
|
|
2281
|
+
},
|
|
2255
2282
|
{
|
|
2256
2283
|
mode: "family-subset",
|
|
2257
2284
|
files: [
|