@openeudi/openid4vp 0.10.0 → 0.11.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 CHANGED
@@ -156,6 +156,20 @@ const req = await createSignedAuthorizationRequest({
156
156
 
157
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`.
158
158
 
159
+ ### Self-signed verifier certificates are rejected
160
+
161
+ 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.
162
+
163
+ ```ts
164
+ // Local development or tests only — not valid under HAIP 1.0 Final.
165
+ await createSignedAuthorizationRequest({
166
+ ...input,
167
+ allowSelfSignedCertificate: true,
168
+ }, dcqlQuery);
169
+ ```
170
+
171
+ 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.
172
+
159
173
  The emitted `client_metadata` carries the 1.0 Final shape: `encrypted_response_enc_values_supported: ["A128GCM", ...]`.
160
174
 
161
175
  ## Authorization responses (direct_post and direct_post.jwt)
@@ -393,7 +407,7 @@ This library implements the **verifier side** of OpenID4VP for SD-JWT VC and mDO
393
407
  - **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
408
  - **DCQL** — authorization request builder with DCQL query, matching via [@openeudi/dcql](https://www.npmjs.com/package/@openeudi/dcql), `verifyPresentation` for combined crypto + match.
395
409
  - **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.
410
+ - **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
411
  - **Encrypted responses** — `decryptAuthorizationResponse` for `direct_post.jwt` (ECDH-ES + A128GCM/A256GCM), `verifyAuthorizationResponse` for the 1.0 §8.1 object-keyed `vp_token` envelope.
398
412
  - **X.509 chain validation** — RFC 5280 chain building including `nameConstraints`, `StaticTrustStore`, `CompositeTrustStore`.
399
413
  - **Revocation checking** — OCSP-first with CRL fallback (`revocationPolicy: 'skip' | 'prefer' | 'require'`).
@@ -404,7 +418,6 @@ This library implements the **verifier side** of OpenID4VP for SD-JWT VC and mDO
404
418
  **What is NOT yet implemented** (planned for follow-up releases):
405
419
 
406
420
  - 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
421
  - SIOPv2 (Self-Issued OpenID Provider) identity flows.
409
422
 
410
423
  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 +450,7 @@ See [CHANGELOG.md](./CHANGELOG.md) for per-release changes. Key migration moment
437
450
  - **0.7.0** — `createSignedAuthorizationRequest`, `decryptAuthorizationResponse`, `verifyAuthorizationResponse` for HAIP / 1.0 §8.1 envelopes.
438
451
  - **0.8.0** — additive: ID3 `client_metadata` bridge, `trustedIssuerJwks` opt-in, transitive SD-JWT disclosure check.
439
452
  - **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.
453
+ - **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
454
  - **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
455
 
442
456
  ## 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,8 +9701,9 @@ 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;
9704
+ const presentedCredentialQuery = query.credentials.find((c) => c.format === dcqlFormat) ?? query.credentials[0];
9698
9705
  const decoded = {
9699
- id: query.credentials[0]?.id ?? "presented",
9706
+ id: presentedCredentialQuery?.id ?? "presented",
9700
9707
  format: dcqlFormat,
9701
9708
  claims: decodedClaims
9702
9709
  };
@@ -9817,8 +9824,12 @@ async function verifyAuthorizationResponse(envelope, query, options) {
9817
9824
  if (queryIds.length > 1 || presentationCount > 1) {
9818
9825
  throw new exports.MultipleCredentialsNotSupportedError(queryIds.length, presentationCount);
9819
9826
  }
9820
- const presentation = vpToken[queryIds[0]][0];
9821
- const decodedPresentation = query.credentials[0]?.format === "mso_mdoc" && typeof presentation === "string" ? base64UrlToBytes(presentation) : presentation;
9827
+ const presentedQueryId = queryIds[0];
9828
+ const presentation = vpToken[presentedQueryId][0];
9829
+ const presentedCredentialQuery = query.credentials.find(
9830
+ (c) => c.id === presentedQueryId
9831
+ );
9832
+ const decodedPresentation = presentedCredentialQuery?.format === "mso_mdoc" && typeof presentation === "string" ? base64UrlToBytes(presentation) : presentation;
9822
9833
  return verifyPresentation(decodedPresentation, query, options);
9823
9834
  }
9824
9835
 
@@ -10451,7 +10462,7 @@ function equalBytes(a, b) {
10451
10462
 
10452
10463
  // src/index.ts
10453
10464
  init_errors();
10454
- var VERSION = "0.10.0";
10465
+ var VERSION = "0.11.0";
10455
10466
  /*! Bundled license information:
10456
10467
 
10457
10468
  pvtsutils/build/index.es.js: