@openeudi/openid4vp 0.9.3 → 0.10.0
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/README.md +10 -11
- package/dist/index.cjs +19 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +19 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ Mismatches return `valid: false` — they do not throw. Only crypto/structural f
|
|
|
124
124
|
|
|
125
125
|
> **Privacy — diagnostics are verifier-internal.** `match.unmatched[].reason` and `detail` (including `value_mismatch`) are intended for verifier-side logging, debugging, and admin UIs. OpenID4VP §11 warns that per-claim verification outcomes can reveal wallet contents to observers. Do NOT echo these diagnostics into the OpenID4VP wire response sent back to the wallet, into end-user-visible error messages that another party could correlate, or into public analytics/third-party logs. The protocol's own error codes are the public interface; these fields are your internal instrumentation.
|
|
126
126
|
|
|
127
|
-
## Signed authorization requests (x509_san_dns)
|
|
127
|
+
## Signed authorization requests (x509_san_dns, x509_hash)
|
|
128
128
|
|
|
129
129
|
For flows that require a signed request object (JAR) per OpenID4VP 1.0 §5.10, use `createSignedAuthorizationRequest`:
|
|
130
130
|
|
|
@@ -132,12 +132,13 @@ For flows that require a signed request object (JAR) per OpenID4VP 1.0 §5.10, u
|
|
|
132
132
|
import { createSignedAuthorizationRequest } from "@openeudi/openid4vp";
|
|
133
133
|
|
|
134
134
|
const req = await createSignedAuthorizationRequest({
|
|
135
|
-
|
|
135
|
+
clientIdPrefix: "x509_san_dns", // or "x509_hash"; defaults to "x509_san_dns"
|
|
136
|
+
hostname: "verifier.example.com", // required for x509_san_dns; optional for x509_hash
|
|
136
137
|
requestUri: "https://verifier.example.com/request.jwt",
|
|
137
138
|
responseUri: "https://verifier.example.com/response",
|
|
138
139
|
nonce,
|
|
139
140
|
signer: verifierKeyPair, // CryptoKeyPair with public+private
|
|
140
|
-
certificateChain: [leafCertDer], // DER-encoded, leaf SAN DNSName must
|
|
141
|
+
certificateChain: [leafCertDer], // DER-encoded, leaf SAN DNSName must include hostname when one is given
|
|
141
142
|
encryptionKey: {
|
|
142
143
|
publicJwk: encryptionPublicJwk, // must include alg, e.g. "ECDH-ES"
|
|
143
144
|
},
|
|
@@ -151,14 +152,11 @@ const req = await createSignedAuthorizationRequest({
|
|
|
151
152
|
// (Content-Type: application/oauth-authz-req+jwt)
|
|
152
153
|
```
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
The emitted `client_metadata` carries both shapes for compatibility:
|
|
155
|
+
`clientIdPrefix: "x509_hash"` sets `client_id` to `x509_hash:` followed by the base64url-encoded SHA-256 hash of the DER-encoded leaf certificate, per OpenID4VP 1.0 §5.9.3 (referenced by HAIP 1.0 Final for its mandated Client Identifier Prefix). `hostname` is not required in this mode, since the `client_id` is derived from the certificate rather than the host. If you do supply one it is still checked against the leaf SAN DNSName values — a supplied hostname that the certificate does not cover is treated as a misconfiguration (`hostname_cert_mismatch`) rather than silently ignored.
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
- ID3 singular — `authorization_encrypted_response_alg: "ECDH-ES"`, `authorization_encrypted_response_enc: "A128GCM"`
|
|
157
|
+
The caller hosts `req.requestObject` at `requestUri` (the library does not host HTTP). The library verifies that the signing key's public SPKI matches the leaf certificate's public key — an attempt to sign with a mismatched key fails with `SignedRequestBuildError: signing_key_cert_mismatch`.
|
|
160
158
|
|
|
161
|
-
|
|
159
|
+
The emitted `client_metadata` carries the 1.0 Final shape: `encrypted_response_enc_values_supported: ["A128GCM", ...]`.
|
|
162
160
|
|
|
163
161
|
## Authorization responses (direct_post and direct_post.jwt)
|
|
164
162
|
|
|
@@ -395,7 +393,7 @@ This library implements the **verifier side** of OpenID4VP for SD-JWT VC and mDO
|
|
|
395
393
|
- **mDOC / ISO 18013-5** `mso_mdoc` format — CBOR decoding, claim extraction, COSE_Sign1 signature verification, MobileSecurityObject validity enforcement, IssuerSignedItem digest verification. Device authentication (ISO 18013-5 §9.1.3) is **mandatory and fails closed**: the `DeviceSignature` (COSE_Sign1) over `DeviceAuthentication`/`SessionTranscript` is verified against the MSO-committed device key. `DeviceMac` (COSE_Mac0) is **not supported and is rejected**.
|
|
396
394
|
- **DCQL** — authorization request builder with DCQL query, matching via [@openeudi/dcql](https://www.npmjs.com/package/@openeudi/dcql), `verifyPresentation` for combined crypto + match.
|
|
397
395
|
- **HAIP** — `buildHaipQuery` / `validateHaipQuery` helpers for the High Assurance Interoperability Profile.
|
|
398
|
-
- **Signed authorization requests (JAR)** — `createSignedAuthorizationRequest` per RFC 9101 / OpenID4VP 1.0 §5.10 with `x509_san_dns` client-id binding. Emits
|
|
396
|
+
- **Signed authorization requests (JAR)** — `createSignedAuthorizationRequest` per RFC 9101 / OpenID4VP 1.0 §5.10 with `x509_san_dns` or `x509_hash` client-id binding. Emits the OpenID4VP 1.0 Final `client_metadata` shape.
|
|
399
397
|
- **Encrypted responses** — `decryptAuthorizationResponse` for `direct_post.jwt` (ECDH-ES + A128GCM/A256GCM), `verifyAuthorizationResponse` for the 1.0 §8.1 object-keyed `vp_token` envelope.
|
|
400
398
|
- **X.509 chain validation** — RFC 5280 chain building including `nameConstraints`, `StaticTrustStore`, `CompositeTrustStore`.
|
|
401
399
|
- **Revocation checking** — OCSP-first with CRL fallback (`revocationPolicy: 'skip' | 'prefer' | 'require'`).
|
|
@@ -406,7 +404,6 @@ This library implements the **verifier side** of OpenID4VP for SD-JWT VC and mDO
|
|
|
406
404
|
**What is NOT yet implemented** (planned for follow-up releases):
|
|
407
405
|
|
|
408
406
|
- Multi-credential DCQL queries (multiple query ids) and multi-presentation arrays per query id — currently rejected with `MultipleCredentialsNotSupportedError`.
|
|
409
|
-
- `client_id_scheme: x509_hash` (HAIP 1.0 final's mandated scheme) — only `x509_san_dns` is supported today.
|
|
410
407
|
- Self-signed-leaf rejection per HAIP 1.0 final's strict constraint (current behaviour accepts self-signed leaves for the verifier's own identity).
|
|
411
408
|
- SIOPv2 (Self-Issued OpenID Provider) identity flows.
|
|
412
409
|
|
|
@@ -439,6 +436,8 @@ See [CHANGELOG.md](./CHANGELOG.md) for per-release changes. Key migration moment
|
|
|
439
436
|
- **0.6.0** — DCQL surfaces specific `UnmatchedReason` values via `@openeudi/dcql@0.2.0` (BREAKING for callers reading `match.unmatched[].reason`).
|
|
440
437
|
- **0.7.0** — `createSignedAuthorizationRequest`, `decryptAuthorizationResponse`, `verifyAuthorizationResponse` for HAIP / 1.0 §8.1 envelopes.
|
|
441
438
|
- **0.8.0** — additive: ID3 `client_metadata` bridge, `trustedIssuerJwks` opt-in, transitive SD-JWT disclosure check.
|
|
439
|
+
- **0.9.3** — `reflect-metadata` is loaded by the package itself in both ESM and CJS builds. If you added a manual `import "reflect-metadata"` before importing this library to work around the 0.9.2 bug, you can drop it.
|
|
440
|
+
- **0.10.0** — `createSignedAuthorizationRequest` gains `clientIdPrefix` (`x509_san_dns` default, or `x509_hash`), and `hostname` becomes optional — required only for `x509_san_dns`, but still validated against the leaf SAN whenever supplied. **Wire change for existing callers:** the emitted `client_metadata` no longer carries the singular ID3 `authorization_encrypted_response_alg` / `authorization_encrypted_response_enc` fields added in 0.8.0. Verifiers that read the 1.0 Final `encrypted_response_enc_values_supported` array (and `alg` from `client_metadata.jwks`) are unaffected; anything still reading the singular fields needs updating.
|
|
442
441
|
|
|
443
442
|
## License
|
|
444
443
|
|
package/dist/index.cjs
CHANGED
|
@@ -8268,14 +8268,23 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8268
8268
|
"vpFormatsSupported is required and must be non-empty"
|
|
8269
8269
|
);
|
|
8270
8270
|
}
|
|
8271
|
+
const clientIdPrefix = input.clientIdPrefix ?? "x509_san_dns";
|
|
8271
8272
|
const leafCert = new x509.X509Certificate(toArrayBuffer(input.certificateChain[0]));
|
|
8272
|
-
|
|
8273
|
-
if (!sanDnsNames.includes(input.hostname)) {
|
|
8273
|
+
if (clientIdPrefix === "x509_san_dns" && !input.hostname) {
|
|
8274
8274
|
throw new exports.SignedRequestBuildError(
|
|
8275
|
-
"
|
|
8276
|
-
|
|
8275
|
+
"missing_hostname",
|
|
8276
|
+
'hostname is required for clientIdPrefix "x509_san_dns"'
|
|
8277
8277
|
);
|
|
8278
8278
|
}
|
|
8279
|
+
if (input.hostname) {
|
|
8280
|
+
const sanDnsNames = extractDnsNames(leafCert);
|
|
8281
|
+
if (!sanDnsNames.includes(input.hostname)) {
|
|
8282
|
+
throw new exports.SignedRequestBuildError(
|
|
8283
|
+
"hostname_cert_mismatch",
|
|
8284
|
+
`leaf cert SAN DNSName values [${sanDnsNames.join(", ")}] do not include hostname "${input.hostname}"`
|
|
8285
|
+
);
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
8279
8288
|
const signerPublicSpki = new Uint8Array(
|
|
8280
8289
|
await crypto.subtle.exportKey("spki", input.signer.publicKey)
|
|
8281
8290
|
);
|
|
@@ -8302,7 +8311,7 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8302
8311
|
}
|
|
8303
8312
|
}
|
|
8304
8313
|
const state = input.state ?? uuid.v4();
|
|
8305
|
-
const clientId = `x509_san_dns:${input.hostname}`;
|
|
8314
|
+
const clientId = clientIdPrefix === "x509_hash" ? `x509_hash:${await sha256Base64Url(input.certificateChain[0])}` : `x509_san_dns:${input.hostname}`;
|
|
8306
8315
|
const now = Math.floor(Date.now() / 1e3);
|
|
8307
8316
|
const clientMetadata = {
|
|
8308
8317
|
vp_formats_supported: input.vpFormatsSupported
|
|
@@ -8319,8 +8328,6 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8319
8328
|
clientMetadata.jwks = { keys: [jwk] };
|
|
8320
8329
|
const encValues = enc3 ?? [...DEFAULT_SUPPORTED_ENC_VALUES];
|
|
8321
8330
|
clientMetadata.encrypted_response_enc_values_supported = encValues;
|
|
8322
|
-
clientMetadata.authorization_encrypted_response_alg = "ECDH-ES";
|
|
8323
|
-
clientMetadata.authorization_encrypted_response_enc = encValues[0];
|
|
8324
8331
|
}
|
|
8325
8332
|
const payload = {
|
|
8326
8333
|
iss: clientId,
|
|
@@ -8365,6 +8372,10 @@ function bytesEqual(a, b) {
|
|
|
8365
8372
|
}
|
|
8366
8373
|
return true;
|
|
8367
8374
|
}
|
|
8375
|
+
async function sha256Base64Url(derCert) {
|
|
8376
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", toArrayBuffer(derCert)));
|
|
8377
|
+
return bytesToBase64(digest).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
8378
|
+
}
|
|
8368
8379
|
function bytesToBase64(bytes) {
|
|
8369
8380
|
let s = "";
|
|
8370
8381
|
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
@@ -10440,7 +10451,7 @@ function equalBytes(a, b) {
|
|
|
10440
10451
|
|
|
10441
10452
|
// src/index.ts
|
|
10442
10453
|
init_errors();
|
|
10443
|
-
var VERSION = "0.
|
|
10454
|
+
var VERSION = "0.10.0";
|
|
10444
10455
|
/*! Bundled license information:
|
|
10445
10456
|
|
|
10446
10457
|
pvtsutils/build/index.es.js:
|