@openeudi/openid4vp 0.10.1 → 0.11.1-rc.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 +37 -2
- package/dist/index.cjs +20 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,27 @@ OpenID4VP credential parsing and validation for EUDI Wallets. Supports SD-JWT VC
|
|
|
8
8
|
npm install @openeudi/openid4vp
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Security and maturity
|
|
12
|
+
|
|
13
|
+
This library verifies credentials for identity and age-assurance flows, so its posture is stated here rather than left to be inferred from the feature list.
|
|
14
|
+
|
|
15
|
+
**No independent third-party security audit has been carried out.** The code has not been reviewed by an external security firm.
|
|
16
|
+
|
|
17
|
+
**Two high-severity vulnerabilities have been found and fixed, both reported by outside researchers:**
|
|
18
|
+
|
|
19
|
+
| Advisory | Issue | Fixed in |
|
|
20
|
+
| -------- | ----- | -------- |
|
|
21
|
+
| [GHSA-4c2f-96cf-f5fc](https://github.com/openeudi/openid4vp/security/advisories/GHSA-4c2f-96cf-f5fc) (CVE-2026-75522) | X.509 chain building terminated on Subject DN string equality rather than cryptographic closure, so a forged certificate reusing a trusted anchor's DN was accepted without signature verification | 0.8.1 |
|
|
22
|
+
| [GHSA-h548-cr7v-4v97](https://github.com/openeudi/openid4vp/security/advisories/GHSA-h548-cr7v-4v97) (CVE-2026-77456) | Holder binding was not enforced: an SD-JWT VC's KB-JWT was optional, and mDOC device authentication was never verified, so a captured presentation could be replayed | 0.9.0 |
|
|
23
|
+
|
|
24
|
+
Both are fixed. Both were found externally rather than by this project's own testing — weigh that accordingly when deciding how much to rely on the library, and **always run the latest release**.
|
|
25
|
+
|
|
26
|
+
**Pre-1.0.** While the version is below 1.0, minor bumps may contain breaking changes; see the [migration list](#migration). Full EUDI ARF 1.4+ profile compliance is a 1.0 goal, not a current claim.
|
|
27
|
+
|
|
28
|
+
**What is independently exercised.** The OpenID Foundation conformance suite runs against this library in CI on every pull request, covering the 1.0-Final verifier happy flow for both `sd_jwt_vc` and `iso_mdl` plus the holder-binding negatives. Separately, a downstream deployment built on this library — [eudi-verify](https://github.com/eudi-verify/eudi-verify) 1.4.0 — is [OpenID Certified](https://openid.net/certification/certified-oid4vp-haip-final/) for the `OID4VP-1.0+HAIP-1.0 Verifier iso_mdl direct_post.jwt` profile (14 Aug 2026). That certification is the deployment's, not the library's, but it exercised this code.
|
|
29
|
+
|
|
30
|
+
Known functional gaps that bear on what you can rely on are listed under [Scope and limitations](#scope-and-limitations). To report a vulnerability, use private reporting as described in [SECURITY.md](./SECURITY.md) — please do not open a public issue.
|
|
31
|
+
|
|
11
32
|
## Quick start
|
|
12
33
|
|
|
13
34
|
Parse a Verifiable Presentation token and extract identity claims:
|
|
@@ -156,6 +177,20 @@ const req = await createSignedAuthorizationRequest({
|
|
|
156
177
|
|
|
157
178
|
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`.
|
|
158
179
|
|
|
180
|
+
### Self-signed verifier certificates are rejected
|
|
181
|
+
|
|
182
|
+
HAIP 1.0 Final requires the verifier certificate to chain to a trust anchor the wallet recognises. A self-signed leaf asserts an identity nothing vouches for, so a wallet enforcing the profile will reject the request object. `createSignedAuthorizationRequest` therefore **rejects a self-signed leaf by default** with `SignedRequestBuildError: self_signed_leaf`, failing at build time rather than emitting a request no conforming wallet will honour.
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// Local development or tests only — not valid under HAIP 1.0 Final.
|
|
186
|
+
await createSignedAuthorizationRequest({
|
|
187
|
+
...input,
|
|
188
|
+
allowSelfSignedCertificate: true,
|
|
189
|
+
}, dcqlQuery);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The check verifies the leaf's signature against its own public key rather than comparing Subject and Issuer DN strings. That distinction is deliberate: [GHSA-4c2f-96cf-f5fc](https://github.com/openeudi/openid4vp/security/advisories/GHSA-4c2f-96cf-f5fc) was a DN-string-equality bug, and a DN comparison here would both miss a re-signed certificate and wrongly reject a legitimate cross-signed one.
|
|
193
|
+
|
|
159
194
|
The emitted `client_metadata` carries the 1.0 Final shape: `encrypted_response_enc_values_supported: ["A128GCM", ...]`.
|
|
160
195
|
|
|
161
196
|
## Authorization responses (direct_post and direct_post.jwt)
|
|
@@ -393,7 +428,7 @@ This library implements the **verifier side** of OpenID4VP for SD-JWT VC and mDO
|
|
|
393
428
|
- **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**.
|
|
394
429
|
- **DCQL** — authorization request builder with DCQL query, matching via [@openeudi/dcql](https://www.npmjs.com/package/@openeudi/dcql), `verifyPresentation` for combined crypto + match.
|
|
395
430
|
- **HAIP** — `buildHaipQuery` / `validateHaipQuery` helpers for the High Assurance Interoperability Profile.
|
|
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.
|
|
431
|
+
- **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. Self-signed verifier leaf certificates are rejected by default per HAIP 1.0 Final.
|
|
397
432
|
- **Encrypted responses** — `decryptAuthorizationResponse` for `direct_post.jwt` (ECDH-ES + A128GCM/A256GCM), `verifyAuthorizationResponse` for the 1.0 §8.1 object-keyed `vp_token` envelope.
|
|
398
433
|
- **X.509 chain validation** — RFC 5280 chain building including `nameConstraints`, `StaticTrustStore`, `CompositeTrustStore`.
|
|
399
434
|
- **Revocation checking** — OCSP-first with CRL fallback (`revocationPolicy: 'skip' | 'prefer' | 'require'`).
|
|
@@ -404,7 +439,6 @@ This library implements the **verifier side** of OpenID4VP for SD-JWT VC and mDO
|
|
|
404
439
|
**What is NOT yet implemented** (planned for follow-up releases):
|
|
405
440
|
|
|
406
441
|
- Multi-credential DCQL queries (multiple query ids) and multi-presentation arrays per query id — currently rejected with `MultipleCredentialsNotSupportedError`.
|
|
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).
|
|
408
442
|
- SIOPv2 (Self-Issued OpenID Provider) identity flows.
|
|
409
443
|
|
|
410
444
|
EUDI Architecture Reference Framework (ARF) alignment: tracks OpenID4VP 1.0 final. Full ARF 1.4+ profile compliance will be added before a stable 1.0.
|
|
@@ -437,6 +471,7 @@ See [CHANGELOG.md](./CHANGELOG.md) for per-release changes. Key migration moment
|
|
|
437
471
|
- **0.7.0** — `createSignedAuthorizationRequest`, `decryptAuthorizationResponse`, `verifyAuthorizationResponse` for HAIP / 1.0 §8.1 envelopes.
|
|
438
472
|
- **0.8.0** — additive: ID3 `client_metadata` bridge, `trustedIssuerJwks` opt-in, transitive SD-JWT disclosure check.
|
|
439
473
|
- **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.
|
|
474
|
+
- **0.11.0** — **BREAKING:** `createSignedAuthorizationRequest` now rejects a self-signed leaf certificate with `SignedRequestBuildError: self_signed_leaf`, per HAIP 1.0 Final. Verifiers using a self-signed certificate for their own identity must either move to a CA-issued certificate (the correct fix) or pass `allowSelfSignedCertificate: true` to keep the previous behaviour. Verification of wallet presentations is unaffected — this concerns only the verifier's own request-signing certificate.
|
|
440
475
|
- **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.
|
|
441
476
|
|
|
442
477
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -8295,6 +8295,12 @@ async function createSignedAuthorizationRequest(input, query) {
|
|
|
8295
8295
|
"signer.publicKey SPKI does not match leaf certificate SPKI"
|
|
8296
8296
|
);
|
|
8297
8297
|
}
|
|
8298
|
+
if (input.allowSelfSignedCertificate !== true && await leafCert.isSelfSigned()) {
|
|
8299
|
+
throw new exports.SignedRequestBuildError(
|
|
8300
|
+
"self_signed_leaf",
|
|
8301
|
+
"leaf certificate is self-signed; HAIP 1.0 Final requires a certificate chaining to a trust anchor. Pass allowSelfSignedCertificate: true to override for local development or tests."
|
|
8302
|
+
);
|
|
8303
|
+
}
|
|
8298
8304
|
const responseMode = input.responseMode ?? "direct_post.jwt";
|
|
8299
8305
|
if (responseMode === "direct_post.jwt") {
|
|
8300
8306
|
if (!input.encryptionKey?.publicJwk) {
|
|
@@ -9695,7 +9701,8 @@ async function verifyPresentation(vpToken, query, options) {
|
|
|
9695
9701
|
const parsed = await parsePresentation(vpToken, options);
|
|
9696
9702
|
const dcqlFormat = INTERNAL_TO_DCQL_FORMAT[parsed.format] ?? parsed.format;
|
|
9697
9703
|
const decodedClaims = parsed.format === "mdoc" && parsed.namespacedClaims !== void 0 ? parsed.namespacedClaims : parsed.claims;
|
|
9698
|
-
const
|
|
9704
|
+
const sameFormat = query.credentials.filter((c) => c.format === dcqlFormat);
|
|
9705
|
+
const presentedCredentialQuery = sameFormat.find((c) => matchesCredentialType(c, parsed)) ?? sameFormat[0] ?? query.credentials[0];
|
|
9699
9706
|
const decoded = {
|
|
9700
9707
|
id: presentedCredentialQuery?.id ?? "presented",
|
|
9701
9708
|
format: dcqlFormat,
|
|
@@ -9719,6 +9726,17 @@ async function verifyPresentation(vpToken, query, options) {
|
|
|
9719
9726
|
valid: parsed.valid && match.satisfied
|
|
9720
9727
|
};
|
|
9721
9728
|
}
|
|
9729
|
+
function matchesCredentialType(credentialQuery, parsed) {
|
|
9730
|
+
if (parsed.format === "sd-jwt-vc") {
|
|
9731
|
+
const vctValues = credentialQuery.meta?.vct_values;
|
|
9732
|
+
return typeof parsed.vct === "string" && Array.isArray(vctValues) && vctValues.includes(parsed.vct);
|
|
9733
|
+
}
|
|
9734
|
+
if (parsed.format === "mdoc") {
|
|
9735
|
+
const doctype = credentialQuery.meta?.doctype_value;
|
|
9736
|
+
return typeof parsed.docType === "string" && doctype === parsed.docType;
|
|
9737
|
+
}
|
|
9738
|
+
return false;
|
|
9739
|
+
}
|
|
9722
9740
|
function isEncryptedResponse(x) {
|
|
9723
9741
|
return typeof x === "object" && x !== null && "response" in x && typeof x.response === "string" && Object.keys(x).length === 1;
|
|
9724
9742
|
}
|
|
@@ -10456,7 +10474,7 @@ function equalBytes(a, b) {
|
|
|
10456
10474
|
|
|
10457
10475
|
// src/index.ts
|
|
10458
10476
|
init_errors();
|
|
10459
|
-
var VERSION = "0.
|
|
10477
|
+
var VERSION = "0.11.1-rc.0";
|
|
10460
10478
|
/*! Bundled license information:
|
|
10461
10479
|
|
|
10462
10480
|
pvtsutils/build/index.es.js:
|