feat(openid4vp): serve sessions over the W3C Digital Credentials API - #648
Draft
dobby-coder[bot] wants to merge 2 commits into
Draft
feat(openid4vp): serve sessions over the W3C Digital Credentials API#648dobby-coder[bot] wants to merge 2 commits into
dobby-coder[bot] wants to merge 2 commits into
Conversation
Adds the OpenID4VP 1.0 Appendix A profile to the OpenID4VP client, so a wallet can answer a presentation request the OS handed it through the platform credential chooser rather than through a custom scheme or a universal link. - Client.NewDcApiSession starts a session from the protocol identifier, the caller origin and the raw `data` member the platform reported, instead of from a URL with a request_uri to fetch. - Both openid4vp-v1-unsigned (parameters as members of `data`) and openid4vp-v1-signed (a JWS in `data.request`, verified against the existing trust models) are accepted. openid4vp-v1-multisigned is rejected with an explicit error. - Response modes dc_api and dc_api.jwt build the JSON the wallet hands back to the platform and deliver it through the new Handler.DeliverDcApiResponse; nothing is POSTed to a response_uri. - Signed requests must list the caller origin in expected_origins, and presentations are bound to `origin:<origin>` for both request types, so a Key Binding JWT's aud is never the client identifier. - client_id and expected_origins in an unsigned request, and response_uri and redirect_uri in either, are dropped during parsing. On the wallet-facing client API, SessionRequestData.DcApi starts such a session and SessionState.DcApiResponse carries the response back out. Part of privacybydesign/irmamobile#677 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolved against the locale-aware client (#633): - eudi/openid4vp/client.go: master's requestor-building block in handleSessionAsync moved into verifySignedAuthorizationRequest by this branch. Kept the extraction and adopted master's change inside it, so the requestor name is resolved via clientmodels.Resolve against client.currentLocale. - unsignedDcApiRequestor now sets TrustedParty.Name to a plain resolved string. client_name is a single string and an origin is not localized, so there is nothing for the locale to resolve there. - CHANGELOG.md: kept both Unreleased > Added entries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
|
Merged
|
Contributor
Author
|
/dobby fix rebase this PR onto the latest master and resolve any conflicts |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
privacybydesign/irmamobile#677 asks for Yivi to serve OpenID4VP presentation requests through the W3C Digital Credentials API, so the OS credential chooser routes a request straight to the wallet instead of a custom scheme or a universal link.
That issue flags one open question: does irmago support the DC API request/response profile? It does not.
eudi/openid4vpknows onlydirect_postanddirect_post.jwt, always fetches the request object from arequest_uri, and always POSTs the response itself. This PR adds the core half (OpenID4VP 1.0 Appendix A) so the Android and Dart work can build on it.What it adds
openid4vp.Client.NewDcApiSession(request, handler)starts a session from the protocol identifier, the caller origin and the rawdatamember the platform reported, instead of from a URL.dc_apianddc_api.jwt. The response is built as the JSONdataobject the platform hands back to the verifier and delivered through the newHandler.DeliverDcApiResponse. Nothing is POSTed.openid4vp-v1-unsigned): the request parameters arrive as members ofdata.client_idandexpected_originsare dropped, because the verifier authenticated neither. The verifier is shown by its origin host and never as verified.openid4vp-v1-signed): the JWS indata.requestgoes through the existing X.509 and DID trust models. The caller origin must match one of the signedexpected_origins, or the session fails.origin:<origin>for both request types, so a Key Binding JWT'saudis the origin and never the client identifier (Appendix A.4).response_uriandredirect_uriare dropped during parsing, so no code further down can transmit the response anywhere but back through the platform.SessionRequestData.DcApistarts the session,SessionState.DcApiResponsecarries the response back out.direct_postanddirect_post.jwtbehaviour is unchanged. The JWE builder is now shared betweendirect_post.jwtanddc_api.jwt, withstatepassed in as a response-mode-specific member (stateis not defined for the DC API, so the DC API responses omit it).Breaking, internal API:
openid4vp.HandlergainsDeliverDcApiResponse(response string). The one implementer in this repo is updated.Not in this PR
androidx.credentials.registry:registry-digitalcredentials-sdjwtvc(1.0.0-alpha04) looks like the right matcher for Yivi's SD-JWT VC credentials, but that is the app repo's call.openid4vp-v1-multisigned(JWS JSON Serialization), which needs multi-client-identifier verification. Rejected with an explicit error rather than silently misparsed.FailureandCancelledtell the app what happened and the app decides what to return to the platform.Testing
eudi/openid4vp/dc_api_test.goadds 20 test functions (43 cases counting table subtests):response_type,response_mode, nonce,scopeand an emptydcql_queryclient_id,expected_origins,response_uriandredirect_urinever survive an unsigned requestdc_api.jwtJWE and asserting thevp_tokenis not also sent in the clear and thatstateis absentdirect_post.jwtstill carriesstateClient.NewDcApiSessionasserting the response comes back viaDeliverDcApiResponseand the audience is the origin, for a signed request as well as an unsigned oneRan locally on go1.26.4:
go test ./eudi/... ./client/... ./common/...,gofmt -d -e .,go vet ./...,go fix -diff ./...,staticcheckandineffassignare all clean. The Postgres/SMTP/Redis-dependent packages underinternal/sessiontestwere not run locally; they compile (go vet ./internal/...) and CI covers the rest.Part of privacybydesign/irmamobile#677
🤖 Generated with Claude Code