@blamejs/core 0.8.60 → 0.8.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +2 -2
- package/index.js +11 -0
- package/lib/audit.js +1 -0
- package/lib/auth/ciba.js +530 -0
- package/lib/auth/oauth.js +199 -11
- package/lib/auth/oid4vci.js +588 -0
- package/lib/auth/oid4vp.js +514 -0
- package/lib/auth/openid-federation.js +523 -0
- package/lib/auth/saml.js +636 -0
- package/lib/auth/sd-jwt-vc-holder.js +30 -8
- package/lib/auth/sd-jwt-vc.js +61 -7
- package/lib/db-collection.js +402 -105
- package/lib/db-file-lifecycle.js +333 -0
- package/lib/session-stores.js +138 -0
- package/lib/session.js +307 -20
- package/lib/validate-opts.js +41 -0
- package/lib/xml-c14n.js +499 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
|
@@ -128,16 +128,32 @@ function create(opts) {
|
|
|
128
128
|
throw new AuthError("auth-sd-jwt-vc/bad-token",
|
|
129
129
|
"holder.store: sdJwt is required");
|
|
130
130
|
}
|
|
131
|
+
if (spec.keyAttestation !== undefined && spec.keyAttestation !== null) {
|
|
132
|
+
// OpenID4VCI key-attestation extension — operator-supplied JWT
|
|
133
|
+
// signed by the holder-device attestation issuer (TEE / Apple
|
|
134
|
+
// App Attest / Play Integrity / FIDO MDS3 anchor). Stored
|
|
135
|
+
// verbatim; surfaced in present()'s key-binding header so the
|
|
136
|
+
// verifier can validate the holder-key provenance alongside
|
|
137
|
+
// the cnf-bound presentation. Refuse anything that doesn't
|
|
138
|
+
// look like a JWS (3 dot-separated segments).
|
|
139
|
+
if (typeof spec.keyAttestation !== "string" ||
|
|
140
|
+
spec.keyAttestation.split(".").length !== 3) {
|
|
141
|
+
throw new AuthError("auth-sd-jwt-vc/bad-key-attestation",
|
|
142
|
+
"holder.store: keyAttestation must be a JWS-compact-serialized JWT (3 dot-separated segments)");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
131
145
|
var record = {
|
|
132
|
-
id:
|
|
133
|
-
sdJwt:
|
|
134
|
-
vct:
|
|
135
|
-
issuer:
|
|
136
|
-
|
|
146
|
+
id: spec.id,
|
|
147
|
+
sdJwt: spec.sdJwt,
|
|
148
|
+
vct: spec.vct || null,
|
|
149
|
+
issuer: spec.issuer || null,
|
|
150
|
+
keyAttestation: spec.keyAttestation || null,
|
|
151
|
+
receivedAt: Date.now(),
|
|
137
152
|
};
|
|
138
153
|
await opts.storage.put(spec.id, record);
|
|
139
154
|
_emitAudit("auth.sdJwtVc.holder.stored", "success", {
|
|
140
155
|
id: spec.id, vct: record.vct, issuer: record.issuer,
|
|
156
|
+
hasKeyAttestation: !!record.keyAttestation,
|
|
141
157
|
});
|
|
142
158
|
_emitMetric("stored");
|
|
143
159
|
return record;
|
|
@@ -153,6 +169,10 @@ function create(opts) {
|
|
|
153
169
|
throw new AuthError("auth-sd-jwt-vc/credential-not-found",
|
|
154
170
|
"holder.present: credentialId \"" + spec.credentialId + "\" not found in storage");
|
|
155
171
|
}
|
|
172
|
+
// Operator may override the stored key_attestation per
|
|
173
|
+
// presentation (e.g., a fresh attestation token bound to the
|
|
174
|
+
// verifier nonce + audience for higher-AAL flows).
|
|
175
|
+
var keyAttestation = spec.keyAttestation || record.keyAttestation || null;
|
|
156
176
|
var presentation = sdJwtVcCore().present({
|
|
157
177
|
sdJwt: record.sdJwt,
|
|
158
178
|
disclosedClaimNames: spec.disclosedClaimNames || [],
|
|
@@ -160,11 +180,13 @@ function create(opts) {
|
|
|
160
180
|
nonce: spec.nonce || null,
|
|
161
181
|
holderKey: opts.holderKey,
|
|
162
182
|
algorithm: algorithm,
|
|
183
|
+
keyAttestation: keyAttestation,
|
|
163
184
|
});
|
|
164
185
|
_emitAudit("auth.sdJwtVc.holder.presented", "success", {
|
|
165
|
-
credentialId:
|
|
166
|
-
audience:
|
|
167
|
-
disclosed:
|
|
186
|
+
credentialId: spec.credentialId,
|
|
187
|
+
audience: spec.audience || null,
|
|
188
|
+
disclosed: (spec.disclosedClaimNames || []).length,
|
|
189
|
+
hasKeyAttestation: !!keyAttestation,
|
|
168
190
|
});
|
|
169
191
|
_emitMetric("presented");
|
|
170
192
|
return presentation;
|
package/lib/auth/sd-jwt-vc.js
CHANGED
|
@@ -271,6 +271,7 @@ function present(opts) {
|
|
|
271
271
|
validateOpts(opts, [
|
|
272
272
|
"sdJwt", "disclosedClaimNames", "audience",
|
|
273
273
|
"nonce", "holderKey", "algorithm", "issuedAt",
|
|
274
|
+
"keyAttestation",
|
|
274
275
|
], "auth.sdJwtVc.present");
|
|
275
276
|
|
|
276
277
|
validateOpts.requireNonEmptyString(opts.sdJwt,
|
|
@@ -324,6 +325,16 @@ function present(opts) {
|
|
|
324
325
|
sd_hash: sdHash,
|
|
325
326
|
};
|
|
326
327
|
var kbHeader = { alg: algorithm, typ: "kb+jwt" };
|
|
328
|
+
if (typeof opts.keyAttestation === "string" && opts.keyAttestation.length > 0) {
|
|
329
|
+
// OpenID4VCI key-attestation extension. The attestation JWT
|
|
330
|
+
// travels in the KB-JWT header so a verifier with no extra
|
|
331
|
+
// round-trip can validate the holder-key provenance (TEE /
|
|
332
|
+
// hardware-backed key) alongside the cnf-bound key-binding
|
|
333
|
+
// signature. Operators wanting per-presentation freshness
|
|
334
|
+
// mint a new attestation per audience+nonce on the holder
|
|
335
|
+
// device and pass it via opts.keyAttestation.
|
|
336
|
+
kbHeader.key_attestation = opts.keyAttestation;
|
|
337
|
+
}
|
|
327
338
|
var kbJwt = _signJwt(kbHeader, kbPayload, opts.holderKey, algorithm);
|
|
328
339
|
presentation += kbJwt;
|
|
329
340
|
}
|
|
@@ -343,6 +354,7 @@ async function verify(presentation, opts) {
|
|
|
343
354
|
"issuerKeyResolver", "audience", "nonce",
|
|
344
355
|
"now", "expectedVct", "maxClockSkewSec",
|
|
345
356
|
"requireKeyBinding",
|
|
357
|
+
"keyAttestationVerifier", "requireKeyAttestation",
|
|
346
358
|
], "auth.sdJwtVc.verify");
|
|
347
359
|
|
|
348
360
|
if (typeof presentation !== "string" || presentation.length === 0) {
|
|
@@ -438,6 +450,7 @@ async function verify(presentation, opts) {
|
|
|
438
450
|
// 4. Optionally verify Key Binding JWT
|
|
439
451
|
var kbValidated = false;
|
|
440
452
|
var holderKey = null;
|
|
453
|
+
var keyAttestationClaims = null;
|
|
441
454
|
if (jwtParsed.payload.cnf && jwtParsed.payload.cnf.jwk) {
|
|
442
455
|
holderKey = jwtParsed.payload.cnf.jwk;
|
|
443
456
|
}
|
|
@@ -488,6 +501,46 @@ async function verify(presentation, opts) {
|
|
|
488
501
|
"verify: KB-JWT iat is in the future");
|
|
489
502
|
}
|
|
490
503
|
kbValidated = true;
|
|
504
|
+
|
|
505
|
+
// OpenID4VCI key-attestation extension: the holder may include
|
|
506
|
+
// a key_attestation JWT in the KB-JWT header. The framework
|
|
507
|
+
// surfaces it for the operator-supplied verifier callback so
|
|
508
|
+
// policy decisions about TEE provenance / hardware-backed-key
|
|
509
|
+
// requirement / app-attest origin / FIDO MDS3 anchor stay in
|
|
510
|
+
// the operator's hands. The framework does NOT trust-anchor
|
|
511
|
+
// resolve attestation issuers itself — the operator's
|
|
512
|
+
// verifier picks the right anchor for the use case.
|
|
513
|
+
if (typeof kbHeaderObj.key_attestation === "string" &&
|
|
514
|
+
kbHeaderObj.key_attestation.length > 0) {
|
|
515
|
+
if (typeof opts.keyAttestationVerifier !== "function") {
|
|
516
|
+
if (opts.requireKeyAttestation === true) {
|
|
517
|
+
throw new AuthError("auth-sd-jwt-vc/no-attestation-verifier",
|
|
518
|
+
"verify: requireKeyAttestation=true but no keyAttestationVerifier supplied");
|
|
519
|
+
}
|
|
520
|
+
// No verifier — surface the raw token and let the caller
|
|
521
|
+
// skip; we don't trust an attestation we can't verify.
|
|
522
|
+
} else {
|
|
523
|
+
try {
|
|
524
|
+
keyAttestationClaims = await opts.keyAttestationVerifier({
|
|
525
|
+
jwt: kbHeaderObj.key_attestation,
|
|
526
|
+
holderKey: holderKey,
|
|
527
|
+
audience: opts.audience || null,
|
|
528
|
+
nonce: opts.nonce || null,
|
|
529
|
+
});
|
|
530
|
+
} catch (e) {
|
|
531
|
+
throw new AuthError("auth-sd-jwt-vc/attestation-verify-failed",
|
|
532
|
+
"verify: keyAttestationVerifier rejected the attestation: " +
|
|
533
|
+
((e && e.message) || String(e)));
|
|
534
|
+
}
|
|
535
|
+
if (!keyAttestationClaims || typeof keyAttestationClaims !== "object") {
|
|
536
|
+
throw new AuthError("auth-sd-jwt-vc/attestation-empty",
|
|
537
|
+
"verify: keyAttestationVerifier returned no claims (must return the verified attestation payload)");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} else if (opts.requireKeyAttestation === true) {
|
|
541
|
+
throw new AuthError("auth-sd-jwt-vc/missing-key-attestation",
|
|
542
|
+
"verify: requireKeyAttestation=true but KB-JWT carries no key_attestation header");
|
|
543
|
+
}
|
|
491
544
|
} else if (opts.requireKeyBinding) {
|
|
492
545
|
throw new AuthError("auth-sd-jwt-vc/missing-kb",
|
|
493
546
|
"verify: KB-JWT required (requireKeyBinding=true) but not present");
|
|
@@ -502,13 +555,14 @@ async function verify(presentation, opts) {
|
|
|
502
555
|
});
|
|
503
556
|
|
|
504
557
|
return {
|
|
505
|
-
valid:
|
|
506
|
-
claims:
|
|
507
|
-
disclosedClaims:
|
|
508
|
-
issuerHeader:
|
|
509
|
-
issuerPayload:
|
|
510
|
-
holderKey:
|
|
511
|
-
kbValidated:
|
|
558
|
+
valid: true,
|
|
559
|
+
claims: resolved,
|
|
560
|
+
disclosedClaims: disclosedClaims,
|
|
561
|
+
issuerHeader: jwtParsed.header,
|
|
562
|
+
issuerPayload: jwtParsed.payload,
|
|
563
|
+
holderKey: holderKey,
|
|
564
|
+
kbValidated: kbValidated,
|
|
565
|
+
keyAttestationClaims: keyAttestationClaims,
|
|
512
566
|
};
|
|
513
567
|
}
|
|
514
568
|
|