Skip to content

[WAL-896] feat: authenticated OpenID4VP presentation and signed DC API - #2141

Open
JakeFernandes98 wants to merge 18 commits into
mainfrom
feature/wal-896-final
Open

[WAL-896] feat: authenticated OpenID4VP presentation and signed DC API#2141
JakeFernandes98 wants to merge 18 commits into
mainfrom
feature/wal-896-final

Conversation

@JakeFernandes98

@JakeFernandes98 JakeFernandes98 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This is the WAL-896 identity PR on feature/wal-896-final. It supersedes walt-id/waltid-identity#2091 (feature/wal-896-dc-api), which is closed; that branch is kept.

Wallets and verifiers can complete authenticated OpenID4VP presentations: Verifier2 exposes signed Request Objects, Wallet2 and the mobile SDK authenticate them, and the first signed Digital Credentials API path (openid4vp-v1-signed with encrypted dc_api.jwt) is wired through shared KMP, Android, and Compose.

Default wallet behavior accepts signed Request Objects with a signable client ID, and also accepts unsigned JSON fetched via request_uri with redirect_uri (the Verifier2 bootstrap). There is no operator flag for HAIP signed-only; that can be added later.

Related PRs

OpenID4VP request authentication

Wallet2 authenticates the final Authorization Request before match/present. There is no allowUnsignedRequests (or similar) product config.

Request Client ID Default
Signed JAR x509_san_dns, x509_hash, DID, verifier_attestation, pre-registered Accept if trust matches (fail-closed without trust material)
Signed JAR redirect_uri Reject
Unsigned JSON from request_uri redirect_uri Accept
Unsigned JSON from request_uri any other prefix Reject
Query parameters / alg=none redirect_uri Accept
Query parameters / alg=none any other prefix Reject

DC API always advertises and accepts openid4vp-v1-signed and openid4vp-v1-unsigned. openid4vp-v1-multisigned stays rejected.

UnsignedRequestObjectPolicy remains inside AuthorizationRequestResolver (ALLOW_UNSIGNED default; REQUIRE_SIGNED for tests and a possible later HAIP profile). It is not threaded through Wallet2 routes, wallet-service.conf, or the mobile SDK.

What Changed

Verifier2

  • Signed cross-device sessions expose an authenticated compact Request Object on the full authorization URL (DC API stays request_uri).

Wallet2 and shared resolver

  • Final Request Objects are authenticated before match/present.
  • AuthorizationRequestResolver implements the table above.
  • Wallet API v2 loads clientIdTrust from wallet-service.conf (x509TrustAnchors as an in-memory PKIX trust store). X.509 prefixes fail closed when anchors are empty.

Mobile SDK

  • Shared KMP, Android Credential Manager, and iOS Swift bridge authenticate Request Objects the same way.
  • Android Credential Manager advertises signed and unsigned DC API protocols.
  • present(requestUrl) and previewPresentation(requestUrl) use the same resolver policy as DC API.

Trust material

  • Compose pins copyable Verifier2 X.509 example trust anchors so x509_san_dns / x509_hash are not empty-list fail-closed.
  • Wallet API v2 keeps the same PEM examples commented in wallet-service.conf.

Architecture Notes

  • Default HTTP OpenID4VP is signed client-ID prefixes or unsigned redirect_uri (including walt.id JSON request_uri bootstrap). It is not a HAIP signed-only profile.
  • Unsigned JSON at request_uri is the Verifier2 bootstrap path. OpenID4VP JAR request_uri is application/oauth-authz-req+jwt.
  • A later HAIP flag can flip the resolver to REQUIRE_SIGNED without adding a Wallet2 config today.

Caveats and Follow-Ups

  • iOS IdentityDocumentServices OpenID4VP DC API is out of scope.
  • openid4vp-v1-multisigned is out of scope.
  • haip-vp:// OS scheme registration is out of scope; protocol parse is scheme-agnostic and demos still register openid4vp.
  • Trust Registry–derived JAR anchors and live issuer resolveCertificateChain on receive are not in this PR.
  • This PR does not make a wallet HAIP-complete (HAIP still expects x509_hash for signed requests, encrypted direct_post.jwt, and a signed-only HTTP profile).

Breaking

  • Wallet2 now authenticates Request Objects. Signable prefixes (x509_*, DID, attestation, pre-registered) fail closed without matching trust material.
  • Unsigned redirect_uri via request_uri remains accepted; operators who want HAIP signed-only have no config switch yet.

@linear-code

linear-code Bot commented Aug 21, 2026

Copy link
Copy Markdown

WAL-896

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: ff928a42-d829-4806-b6f7-9fca3f058016

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

szijpeter and others added 18 commits September 1, 2026 18:10
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(verifier2): expose authenticated request objects

* feat(wallet): finalize authenticated OpenID4VP presentation flow
…ches

Keep WAL-896's plain-request RedirectUri gate, but refuse a contradicting
response destination with the same message as Request Object binding.
Android does not inherit jvmMain, so WAL-896's new expect had no actual
and compileAndroidMain failed during assembleDebug.
openid4vp-wallet is JVM-only, so Android loaded OrderedCertificateChain_jvmKt
for platformSupportsPkixCertificatePathValidation. Keep the actual in
jvmCommon so the JVM and Android artifacts expose the same class.
RFC 5280 treats a missing KeyUsage as unrestricted. Requiring
digitalSignature when the extension is absent rejected verifier
attestation JWTs that still sign the request correctly.
ClientIdTrustConfiguration now takes a trust store, not a list of
CertificateDer, so the demo PEMs need the same wrapping as the iOS bridge.
HAIP signed-only is the default: verifiers must send signed JARs, and redirect_uri is rejected. Unsigned JSON redirect_uri requests are accepted only when the flag is on.
Wallets only accept unsigned requests for that prefix; signed JARs keep
pre-registered/x509 client IDs, and public-demo signed E2E is skipped
until demo deploys inline request objects.
redirect_uri is only generated when both the per-session and service
client_id are empty. An explicit or configured id is left unchanged.
…ntials

A multiple=false query was collapsing the whole store to the first match,
so picking any other same-doctype credential failed after selection.
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants