<!-- PDF-show \newpage --> # PID Option C'': Signed Credential with eID Card ## Basic Idea In this design, PID credentials are issued on-demand in ISO mdoc / SD-JWT VC format using the OpenID4VCI protocol and directly presented using the OpenID4VP protocol. While this Option is similar to Option B, the PID credentials are signed by the PID Provider for the specific transaction. The user is authenticated by utilizing the eID card towards the PID Provider in every presentation. In this design, the device key for signing the presentation is generated by the PID Provider in order to achieve a high level of assurance and perform critical key management outside the Wallet. To ensure that the PID Provider does not get to know where a PID credential is used and which claims are provided, the payload of the presentation part (deviceAuth for mdoc or KB-JWT for SD-JWT) is created by the Wallet and only its hash is sent to the PID Provider for signing. This design therefore results in the following properties: - The complexity of implementation is lower for the Wallet, as it does not need to handle and secure the device key. - The PID Provider can enforce that there is exactly one presentation for the PID credential, ensuring a tight binding between the eID Card process and the PID presentation. - It is expected that certification for LoA High is easier for this design, as the PID Provider has full control over all keys and a secure authentication is guaranteed each time via the usage of the eID. Note that this design assumes that the signing algorithm performs a hashing step on the data first; this step is happening at the Wallet, while the rest of the signing is done at the PID Provider. In particular, this works well with ECDSA. ## Credential formats Two solutions are described: - **ISO mdoc:** The ISO mdoc credential format is used with - issuerAuth as issuer data authentication, a COSE_Sign1 signature over the MobileSecurityObject (see ISO 23220-4 7.1.3.4.2.1); containing - an ephemeral key in `deviceKeyInfo` - signed hashes in the `valueDigests` - deviceSignature as mdoc authentication method, a COSE_Sign1 signature over the deviceAuthentication data (see ISO 18013-5 9.1.3.6); containing - the PID data - **SD-JWT VC:** The SD-JWT VC credential format is used with - SD-JWT signed with JWS by the PID Provider; containing - an ephemeral key in `cnf` - the signed hashes in `_sd` arrays - KB-JWT signed by the PID Provider with the Hash; containing - nonce and audience of the Relying Party - a hash of the SD-JWT and the selected disclosures - Disclosures containing the PID data ## Cryptographic Formats (to be defined) ### Issuance Long-Term keys: Transaction-specific keys: Artifacts: ### Presentation Long-Term keys: Transaction-specific keys: Artifacts: ### Dependencies ## Sequence Diagram ### Issuance ```plantuml @startuml 'Lets define some common colors globally !$C_PRT = "#118888" !$C_ARG = "#daa520" !$C_VAR = "#daa520" 'Ensure messages are not too wide skinparam maxMessageSize 200 skinparam wrapWidth 300 'Macro for colored [TLS] block !function tls() !return "<color " + $C_PRT + ">[TLS]</color>" !endfunction 'Macro for colored [TLSPSK] block !function tlspsk() !return "<color " + $C_PRT + ">[TLS-PSK]</color>" !endfunction 'Macro for colored <Param1, Param2> block !function params($p) !return "<color " + $C_ARG + ">"+ $p + "</color>" !endfunction 'Align text on arrows to center skinparam sequenceMessageAlign center 'padding between boxes skinparam BoxPadding 100 autonumber "<b>(000)" title PID presentation over OpenID4VP and On-the-fly PID Issuance over OpenID4VCI with SD-JWT actor u as "User\nOpenID Holder" participant b as "Browser App\n(same device)" participant v [ Relying Party ---- ""Long-term Key: (//rp_pub//, //rp_priv//)"" ] participant w as "User's EUDI Wallet Instance\n(eID-Client)" participant i [ PID Provider (eService+eID Server) ---- ""Long-term Key: (//pp_pub//, //pp_priv//)"" ] u --> b : browse to application hnote over b #dfd: Screen: same_device_relying_party_start b -> v : tls() HTTP GET <rp-website> v -> v : generate ephemeral key pair (//rp_eph_pub//, //rp_eph_priv//) v -> v : create OpenID4VP Authorization Request,\n sign with //rp_priv//,\n store under <request_uri> note left: Authorization Request includes:\n- presentation_definition\n- purpose\n- state\n- nonce\n- //rp_eph_pub//\n- response_uri v -> v : generate new browser session <color:#909>session_id</color> and bind the authorization request to it v -> b : tls() HTTP 200 HTML containing wallet-link openid4vp://authorize?")\nclient_id=..&request_uri=<request_uri>\nSet-Cookie: sid=<color:#909>session_id</color> u --> b : action to start flow/launch wallet b -> w : launch with wallet-link openid4vp:// note right #fc7: Potential security risk: Wallet app may be spoofed by malicious app hnote over w #dfd: Screen: launch_wallet u --> w : unlock wallet note right: may be moved to later point in flow or removed, see notes. hnote over w #dfd: Screen: unlock_wallet 'newpage w -> v : tls() HTTP GET <request_uri> note right #fc7: Potential privacy risk: RP learns existence of wallet app and potentially identifying information (e.g., headers) v -> w : tls() HTTP 200 <JWT-Secured Authorization Request> w -> w : validate Authorization Request JWT using //rp_pub// u <--> w : user consent to present PID to Relying Party for given purpose hnote over w #dfd: Screen: consent_present_credential group PID provisioning note over w,i: PID Issuer and EUDI Wallet have inherent trust relationship, metadata may be pre-configured or retrieved w -> w : Wallet fetches fresh wallet attestation from backend w -> i : tls() HTTP POST </session_endpoint> wallet attestation nonce i -> i : generate and store nonce i -> w : tls() HTTP 200 <wallet attestation nonce> w -> w : sign wallet attestation PoP JWT (incl. wallet attestation nonce) w -> i : tls() HTTP POST PAR (PKCE code_challenge, wallet attestation JWT, wallet attestation PoP JWT, redirect_uri, either scope or authorization_details i -> i : verify wallet attestation & PoP \ncheck Wallet Provider solution status on trust list i -> w : tls() HTTP 200 request_uri note right : Attestation guarantees with high certainty that Wallet is trustworthy and not manipulated w -> i : tls() HTTP GET <OpenID4VCI Authorization Request(request_uri)> 'newpage hnote over w #dfd: Screen: eid_start group Read eID or Smart eID acc. to BSI TR-03130 i --> w : tls() HTTP 200 starting the eID Process w <-> i : eID Process u <--> w : <eID-PIN> hnote over w #dfd: Screen: eid_pin w <-> i : eID Process w -> i : tls() HTTP GET finishing the eID process with refreshUrl hnote over w #dfd: Screen: eid_nfc_data end i --> w : tls() HTTP 302 Authorization Response (code) group Generate initial DPoP nonce w -> w : generate ephemeral DPoP key pair //dpop_eph_pub//, //dpop_eph_priv// w -> w : generate placeholder DPoP proof using ephemeral DPoP key pair //dpop_eph_pub//, //dpop_eph_priv// w -> i : tls() HTTP POST <Token Request(DPoP Header with placeholder proof, authorization_code, PKCE code_verifier)> i -> i: generate and store dpop_nonce i -> w : tls() HTTP 400 <Bad Request (DPoP nonce Header with dpop_nonce, error: "use_dpop_nonce")> note left : The Wallet should check at this point, whether the Token Endpoint delivered the expected error and nonce. If not, this needs to be handled (retry or abort gracefully). w -> w: store dpop_nonce end w -> w: prepare DPoP proof JWT with //dpop_eph_pub//, dpop_nonce, iat and sign with //dpop_eph_priv// w -> i : tls() HTTP POST Token Request(code, code_verifier, DPoP header) i -> i : generate and store dpop_nonce i -> i : lookup authorization code\ngenerate Token Response with DPoP-bound access token\nverify PKCE challenge i --> w : tls() HTTP 200 <Token Response(DPoP nonce header with dpop_nonce, DPoP-bound access_token, c_nonce, optional authorization_details)> w -> w: prepare DPoP proof JWT with //dpop_eph_pub//, dpop_nonce, iat and sign with //dpop_eph_priv// 'newpage alt #ddf C'': ISO mdoc w -> i : HTTP POST <Credential Request(DPoP Header with proof, DPoP-bound access token)> i -> i : lookup access token i -> i : generate ephemeral device key pair (//device_pub//, //device_priv//) i -> i : create mdoc with all eID data attributes and //device_pub//, signed by //pp_priv//, and matching NameSpaceBytes i --> w : tls() HTTP 200 Credential Response(<color blue>issuerSigned</color>) note right: issuerSigned contains issuerAuth (the signed part, also known as MSO) and nameSpaces (the data elements) w -> w : calculate SessionTranscript (mDocGeneratedNonce, clientId, responseUri, nonce) w -> w : calculate hash of deviceAuth using SessionTranscript and including only selected data elements in nameSpaces w -> i : tls() HTTP POST Presentation Signing Request (deviceAuth hash, DPoP-bound access token) i -> i : create signature over deviceAuth hash using //device_priv// i --> w : tls() HTTP 200 Presentation Signing Response(<color blue>deviceAuth signature</color>) w -> w : assemble mdoc using deviceAuth and its signature note right: mdoc is ISO18013-5 Document containing issuerAuth and deviceAuth else #dfd C'': SD-JWT VC w -> i : tls() HTTP POST <Credential Request(DPoP Header with proof, DPoP-bound access token)> i -> i : lookup access token i -> i : generate ephemeral device key pair (//device_pub//, //device_priv//) i -> i : create SD-JWT with eID data and //device_pub//, signed by //pp_priv// and matching Disclosures i --> w : tls() HTTP 200 Credential Response(<color blue>SD-JWT</color>) w -> w : calculate hash of KB-JWT using nonce, audience, and sd_hash note right: the SD-JWT+Disclosures are required by the Wallet to calculate sd_hash w -> i : tls() HTTP POST Presentation Signing Request (kb_hash, DPoP-bound access token) note right: Issuer ensures that each device key is only used once and deletes it afterwards i -> i : create KB-JWT signature over kb_hash with //device_priv// i --> w : tls() HTTP 200 Presentation Signing Response(<color blue>KB-JWT signature</color>) w -> w : assemble KB-JWT using the payload and its signature end 'newpage end w -> w : create vp_token and presentation_submission w -> w : add mDL presentation according to <presentation_definition> with <nonce> to vp_token and presentation_submission note left #AAFFAA: Wallet may add presentations with keys under its own control as the \ncommunication channel between Relying Part and PID Provider is not E2EE w -> v : tls() HTTP POST encrypted <Authorization Response(presentation_submission, vp_token, state)> v -> v : look up state in existing sessions\ncreate & store response_code for session v --> w : tls() HTTP 200 <redirect_uri incl. response_code> 'newpage w -> b : launch browser with <redirect_uri with response_code> hnote over w #dfd: Screen: success_redirect b -> v : tls() HTTP GET <redirect_uri with response_code>\nCookie: sid=<color:#909>session_id</color> v -> v : look up session with <color:#909>session_id</color> and match response_code alt #ddf B.1.1: ISO mdoc v -> v : verify contents of <vp_token>:\n- verify mdoc issuerAuth PID\n- verify deviceAuth with //device_priv// from issuerAuth\n- calculate and validate correct SessionTranscript else #dfd B.1.2: SD-JWT VC v -> v : verify contents of <vp_token>:\n- verify SD-JWT PID\n- verify KB-JWT with //device_priv// from SD-JWT\n- validate nonce and audience from KB-JWT end v --> b : tls() HTTP 200 <HTML with continued UX flow> hnote over b #dfd: Screen: same_device_relying_party_identified @enduml ``` ## Step-by-Step Description Note: While certain assumptions about session management of the Relaying Party are made here, the concrete implementation is considered out of scope for this document. The usual security considerations for web session management apply. 1. User browses to Relying Party (RP) website 2. Browser app on the user's device opens the RP website 3. RP generates a key pair to be used for response encryption 4. RP generates an OpenID4VP Authorization Request and stores it under a `request_uri` (e.g., `https://rp.example.com/oidc/request/1234`); - The request is bound to the user's browser session - It is signed using a key bound to the RP's metadata that can be retrieved using the RP's client_id - It contains ephemeral key for response encryption - It contains RP's nonce and state - It contains the RP's response_uri endpoint for sending the Authorization Response over POST 5. RP generates a new browser session and binds the generated Authorization Request to it 6. RP returns an HTML page to the browser containing a link to the wallet app (e.g., `openid4vp://authorize?client_id=..&request_uri=https://rp.example.com/oidc/request/1234`); a cookie with the browser session id is set 7. The user clicks on the link 8. The RP website navigates to the custom scheme link to launch the wallet app 9. The user unlocks the wallet app (see notes below) 10. The wallet app retrieves the Authorization Request from the RP website (e.g., `https://rp.example.com/oidc/request/1234`) 11. The wallet app receives the Authorization Request 12. The wallet app validates the Authorization Request using the RP's public key - Was the signature valid and the key bound to the RP's metadata? - **Security:** This ensures that the Authorization Request was not tampered with; it does not ensure that the party that sent the Authorization Request is the RP. 13. The Wallet displays information about the identity of the Relying Party and the purpose, the user gives consent to present the PID. 14. The Wallet fetches fresh wallet attestation from the Wallet Provider backend. 15. The Wallet requests a fresh nonce for the wallet attestation nonce from the PID Provider (wallet attestation nonce). 16. The PID Provider generates a fresh nonce linked to the issuance session. 17. The PID Provider returns the wallet attestation nonce to the Wallet. 18. The Wallet generates a Wallet Attestation PoP and signs it with *dev_priv*; containing - audience - expiration time - wallet attestation nonce 19. The wallet sends the Pushed Authorization Request to the PID Provider; containing - PKCE code_challenge - wallet attestation + PoP - redirect_uri - either scope or an authorization_details parameter requesting the PID 20. The PID Provider verifies the wallet attestation and its proof of possession and validates the certification status of the Wallet Solution on a trust list. 21. The PID Provider returns a request_uri that is bound to the Pushed Authorization Request. 22. The Wallet sends the Authorization Request; containing - the PAR request_uri 23. The PID Provider responds with the first step to start the eID process with the wallet app, e.g. the tcToken. Note that this is the direct HTTP Response to Step 14. 24. Further communication is exchanged to perform the eID process 25. The user provides the eID PIN to the wallet app. 26. Further communication is exchanged to perform the eID process 27. The eID process is finished and as a final step the Wallet sends a request to the PID Provider calling the refreshURL. From now on Wallet and PID Provider are using the TLS-PSK channel generated by the eID flow. 28. The PID Provider responds to the Wallet with an Authorization Response; containing - the auth code 29. The wallet generates an ephemeral DPoP key pair *dpop_eph_pub*, *dpop_eph_priv*. 30. The Wallet generates a placeholder DPoP proof JWT using the ephemeral DPoP key pair to trigger an error response from the Token endpoint necessary to retrieve the `dpop_nonce`. 31. The Wallet sends a Token Request to the PID Provider, containing the placeholder DPoP proof JWT. 32. The PID Provider generates and stores a `dpop_nonce`. 33. The PID Provider responds with the expected error "use_dpop_nonce", containing the `dpop_nonce` to be used from now on in the DPoP nonce header. 34. The Wallet extracts and stores the `dpop_nonce`. 35. The Wallet now prepares the actual DPoP proof JWT for `dpop_eph_pub` including the `dpop_nonce` and `iat`. 36. The Wallet sends a Token Request to the PID Provider; containing: - the authorization_code from Authorization Response - the PKCE code_verifier matching the code_challenge from Authorization Request - the DPoP header 37. The PID Provider generates and stores a `dpop_nonce`. 38. The PID Provider matches the authorization_code and verifies the PKCE code_verifier to the previously received code_challenge. It then generates an access token bound to the DPoP key. 39. The PID Provider sends a Token Response; containing - DPoP-bound access token - a c_nonce - an authorization_details object, in case the authorization_details parameter was used in the Authorization Request - a fresh `dpop_nonce` in the DPoP nonce header 40. The Wallet prepares a DPoP proof JWT for `dpop_eph_pub` including the `dpop_nonce` and `iat`. 41. **(mdoc)** The Wallet send a Credential Request; containing - DPoP Header with proof - DPoP-bound access token - sessionTranscript - it does not contain a "proof" 42. **(mdoc)** The PID Provider looks up and validates the access token. 43. **(mdoc)** The PID Provider generate an ephemeral DeviceKey pair (*device_pub*,*device_priv*). 44. **(mdoc)** The PID Provider creates the mdoc issuerSigned; containing - issuerAuth (MSO) with the public part of DeviceKey and the hashes of the data elements and signs it with *pp_priv* - nameSpaces with the data elements 45. **(mdoc)** The PID Provider returns the issuerSigned to the Wallet. 46. **(mdoc)** The Wallet calculates the SessionTranscript according to ISO-18013-7 Annex B.4.4 from mDocGeneratedNonce, client_id, responseUri, nonce. The final result is a SHA-256 hash, thus not revealing the client_id and ResponseUri to the PID Provider. 47. **(mdoc)** The Wallet generates the deviceAuth utilizing the SessionTranscript and the requested data elements for nameSpaces and calculates the hash of it 48. **(mdoc)** The Wallet sends the hash of the deviceAuth to the Presentation Signing Endpoint of the PID Provider; the request is protected by a DPoP-bound access token. 49. **(mdoc)** The PID Provider creates the signature for the deviceAuth hash. 50. **(mdoc)** The PID Provider returns the signature to the Wallet. 51. **(mdoc)** The Wallet creates the mdoc using the issuerAuth, the deviceAuth payload and the deviceAuth signature returned by the PID Provider. 52. **(SD-JWT)** The Wallet send a Credential Request; containing - DPoP Header with proof - DPoP-bound access token - it does not contain a "proof" 53. **(SD-JWT)** The PID Provider looks up and validates the access token. 54. **(SD-JWT)** The PID Provider generate an ephemeral DeviceKey pair (*device_pub*,*device_priv*). 55. **(SD-JWT)** The PID Provider creates the issuer-signed part of the SD-JWT and signs it with *pp_priv*; containing - eID as the user claims - *device_pub* as cnf claim 56. **(SD-JWT)** The PID Provider sends the Credential Response; containing: - SD-JWT VC PID with Disclosures 57. **(SD-JWT)** The Wallet creates the header and payload for the KB-JWT from audience, nonce, and the hash of SD-JWT and selected disclosures and hashes it. The Wallet appends the KB-JWT to the SD-JWT. Note that this step can only happen after the SD-JWT has been issued and received by the Wallet, as the KB-JWT payload includes the *sd_hash* parameter, that is a hash over issuerSigned JWT and the Disclosures. 58. **(SD-JWT)** The Wallet sends a Request to the Presentation Signing Endpoint transmitting the hash of the KB-JWT. The request is protected by a DPoP-bound access token. 59. **(SD-JWT)** The PID Provider recognizes the device key from the provided public key reference and uses *device_priv* to create the signature of the KB-JWT using the hash of the KB-JWT. Afterwards, it must ensure that the device key pair is deleted and not used again. 60. **(SD-JWT)** The PID Provider sends the response containing the signature of KB-JWT. 61. **(SD-JWT)** The Wallet assembles the PID presentation from SD-JWT and KB-JWT payload/signature. 62. The wallet app creates a VP token and a presentation submission from the received SD-JWT PID. 63. Optional: The wallet app can add further presentations with keys under its own control as the communication channel between Relying Part and PID Provider is not E2EE 64. The wallet app sends the VP token and presentation submission to the RP (encrypted to the RP's public key *rp_eph_pub*). 65. The RP finds a session with the state and generates a response_code for this session. 66. The RP returns the redirect_uri with the response_code to the wallet app. 67. The wallet app launches the browser with the redirect_uri and response_code. 68. The browser sends the redirect_uri and response code to the RP, attaching the browser session id as a cookie. 69. The RP looks up whether there exists a session with the session id from the cookie and a matching response_code 70. **(mdoc)** The RP verifies the PID in the VP token with the MAC key and verifies the SessionTranscript calculated from nonce, mDocGeneratedNonce, clientID, response_uri. 71. **(SD-JWT)** The RP verifies the SD-JWT PID in the VP token with the MAC key, verifies the KB-JWT using the *kb_eph_pub* in the SD-JWT, and verifies the nonce and audience in the KB-JWT 72. The RP considers the user to be identified in the session context and continues the UX flow. ## Extensions to the Protocols ### Issuer Session Endpoint (at the PID Provider) Note that this extension is the same across multiple flows. This endpoint is used by the Wallet to obtain `session_id` from the PID Provider that is used to bind PoPs to the session and prove their freshness. Support for this endpoint is REQUIRED. To fetch the `session_id`, the Wallet MUST send an HTTP request using the POST method and the `application/json` media type. The PID Provider MUST return the HTTP Status Code 200 and a `session_id` parameter defined below. - `session_id`: REQUIRED. String that is a unique session identifier, chosen as a cryptographically random nonce with at least 128 bits of entropy. Communication with the Session Endpoint MUST utilize TLS. Below is a non-normative example of a request to a Session Endpoint: ```http POST /session_endpoint HTTP/1.1 Host: server.example.com Content-Type: application/json ``` Below is a non-normative example of a response from a Session Endpoint: ```http HTTP/1.1 200 OK Content-Type: application/json Cache-Control: no-store { "session_id": "iOiJSUzI1NiIsInR" } ``` ### Presentation Signing Endpoint This endpoint is used by the Wallet to obtain a signature over the KB-JWT or deviceAuth structure. Support for this endpoint is REQUIRED. Communication with this endpoint MUST use TLS and this endpoint MUST be protected using the DPoP-bound access token, similar to the Credential Endpoint. To fetch the bytes of the signature, the Wallet MUST send an HTTP request using the POST method and the `application/json` media type. The request contains a JSON-encoded object with the following parameters: - `hash_bytes`: REQUIRED. base64url-encoded bytes of the hash over the bytes of the deviceAuth structure or the KB-JWT structure, respectively. Note that the hash algorithm MUST be matched to the signing algorithm used by the PID Provider for signing the deviceAuth or KB-JWT structure. The PID Provider MUST return the HTTP Status Code 200 and a `signature_bytes` parameter defined below. - `signature_bytes`: REQUIRED. base64url-encoded bytes of the signature over the deviceAuth or KB-JWT, respectively. ### OpenID4VCI Credential Issuer Metadata Note that this extension is the same across multiple flows. This document defines the following additional Credential Issuer Metadata parameters: - `session_endpoint`: REQUIRED. URL of the Credential Issuer's Session Endpoint, as defined in a previous section. This URL MUST use the `https` scheme and MAY contain port, path, and query parameter components. - `presentation_signing_endpoint`: REQUIRED: URL of the Presentation Signing Endpoint, as defined above. This URL MUST use the `https` scheme and MAY contain port, path, and query parameter components. ## Usability Considerations ## Privacy Considerations ## Security Considerations ## Open Topics