Skip to content

Latest commit

 

History

History
856 lines (721 loc) · 32.7 KB

ewc-rfc001-issue-verifiable-credential.md

File metadata and controls

856 lines (721 loc) · 32.7 KB

EWC RFC001: Issue Verifiable Credential - v2.0

Authors:

  • Mr George Padaytti (iGrant.io, Sweden)
  • Mr Lal Chandran (iGrant.io, Sweden)
  • Dr Andreas Abraham (ValidatedID, Spain)

Reviewers:

  • Dr Nikos Triantafyllou (University of the Aegean, Greece)
  • Mr Florin Coptil (Bosch, Germany)
  • Mr Matteo Mirabelli (Infocert, Italy)
  • Dr Mikael Linden (Vero, Finland)
  • Mr Renaud Murat (Archipels, France)
  • Mr. Sebastian Bickerle (Lissi ID, Germany)
  • Mr. Quentin Drouot (Archipels, France)
  • Mr. Edward Curran (Lissi ID, Germany)
  • Mr. Björn Astrom (BankID, Sweden)
  • Mr. Björn Molin (DIGG, Sweden)

Status: Current: Draft 13 alignment 02-Sep-2024: Approved for v2.0 release

Table of Contents

1.0 Summary

This specification implements OID4VCI workflow for any issuer as per reference specification [1]. This minimises risks towards interoperability across the European Wallet Ecosystem with a standard specification in the EUDI wallet ecosystem as per the ARF [2] requirements.

2.0 Motivation

The EWC LSP must align with the standard protocol for issuing credentials. This is the basis of interoperability between Issuers and Holders across the EWC LSPs. The assumption is that the user is familiar with the EWC-chosen protocols and standards and can refer to original standards references when necessary.

3.0 Messages

The credential issuance can be an authorisation flow or a pre-authorised one. These are depicted in the following diagrams, the assumption is that credential offer is obtained by holder wallet prior to discovery using same device (i.e. clicking on a link) or cross device (i.e scanning a QR code) flows.

sequenceDiagram
  autonumber
  participant I as Individual using EUDI Wallet
  participant O as Organisational Wallet (Issuer)
  
  O->>I: GET: Credential Offer
  Note over I,O: Discovery issuer capabilities
  I->>O: GET: /.well-known/openid-credential-issuer
  O-->>I: OpenID credential issuer configuration
  I->>O: GET: /.well-known/oauth-authorization-server
  O-->>I: OAuth authorisation server metadata

  Note over I,O: Authenticate and Authorise
  I->>O: Authorisation request
  O-->>I: Authorisation response
  I->>O: Token request
  O-->>I: Token response

  Note over I,O: Issue credential
  I->>O: POST: Credential request with token
  O-->>I: Credential response with acceptance token
Loading

Figure 1: Issuance using Authorisation Code Flow based on [1]

sequenceDiagram
    autonumber
    participant I as Individual using EUDI Wallet
    participant O as Organisational Wallet (Issuer)

    O->>I: GET: Credential Offer
    
    Note over I,O: Discovery issuer capabilities
    I->>O: GET: /.well-known/openid-credential-issuer
    O-->>I: OpenID credential issuer configuration
    I->>O: GET: /.well-known/oauth-authorization-server
    O-->>I: OAuth authorisation server metadata

    Note over I,O: Authenticate and Authorise
    I->>O: POST: Pre-authorised token request with transaction code
    O-->>I: Token response

    Note over I,O: Issue credential
    I->>O: POST: Credential request with token
    O-->>I: Credential response with acceptance token
Loading

Figure 2: Issuance using Pre-Authorisation Code Flow based on [1]

3.1 Credential offer

The recommendation is to send the Credential Offer by Reference Using the credential_offer_uri Parameter to avoid QR Code data overload. The endpoint that is expected to be embedded in the QR code is:

openid-credential-offer://?credential_offer_uri=https://server.example.com/credential-offer

Here, the credential_offer_uri query param contains the URL in which the credential offer from the issuer can be resolved.

3.2 Credential offer response

Once the credential_offer_uri query param is resolved, the response can be either of the following formats.

For authorised code flow, the credential offer response is as given:

{
    "credential_issuer": "https://server.example.com",
    "credential_configuration_ids": [
        "VerifiablePortableDocumentA1"
    ],
    "grants": {
        "authorization_code": {
            "issuer_state": "eyJhbGciOiJSU0Et...FYUaBy"
        }
    }
}

For pre-authorised flow with a transaction code, the credential offer response is as given:

{
   "credential_issuer": "https://server.example.com",
   "credential_configuration_ids": [
      "VerifiablePortableDocumentA1",
   ],
   "grants": {
      "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
         "pre-authorized_code": "oaKazRN8I0IbtZ0C7JuMn5",
         "tx_code": {
            "length": 4,
            "input_mode": "numeric",
            "description": "Please provide the one-time code that was sent via e-mail or offline"
         }
      }
   }
}

For pre-authorised flow without a transaction code, the credential response is as given:

{
   "credential_issuer": "https://server.example.com",
   "credential_configuration_ids": [
      "VerifiablePortableDocumentA1",
   ],
   "grants": {
      "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
         "pre-authorized_code": "oaKazRN8I0IbtZ0C7JuMn5",
      }
   }
}

The following options are available for different grant types:

For authorization_code

Field Description
issuer_state OPTIONAL. String value created by the Credential Issuer and opaque to the Wallet. Used to bind the subsequent Authorization Request with the Credential Issuer to a context set up during previous steps. If received, it MUST be included in the subsequent Authorization Request as the issuer_state parameter value.
authorization_server OPTIONAL string that the Wallet can use to identify the Authorization Server when the authorization_servers parameter in the Credential Issuer metadata has multiple entries. It MUST match one of the values in the authorization_servers array.

For urn:ietf:params:oauth:grant-type:pre-authorized_code:

Field Description
pre-authorized_code REQUIRED. The code representing the Credential Issuer's authorisation for the Wallet to obtain Credentials of a certain type. This code MUST be short-lived and single-use. It MUST be included in the subsequent Token Request.
tx_code OPTIONAL. Object specifying whether the Authorization Server expects presentation of a Transaction Code by the End-User along with the Token Request. Intended to bind the Pre-Authorized Code to a certain transaction to prevent replay. If required, it MUST be sent in the tx_code parameter. Read more here.
    input_mode OPTIONAL. String specifying the input character set. Possible values are numeric (only digits) and text (any characters). The default is numeric.
    length OPTIONAL. Integer specifying the length of the Transaction Code. This helps the Wallet render the input screen and improve the user experience.
    description OPTIONAL. String containing guidance for the Holder of the Wallet on how to obtain the Transaction Code. It is RECOMMENDED to display this description next to the Transaction Code input screen. The length MUST NOT exceed 300 characters.
interval OPTIONAL. The minimum amount of time in seconds that the Wallet SHOULD wait between polling requests to the token endpoint. If no value is provided, Wallets MUST use 5 as the default.
authorization_server OPTIONAL string that the Wallet can use to identify the Authorization Server when the authorization_servers parameter in the Credential Issuer metadata has multiple entries. It MUST match one of the values in the authorization_servers array.

3.3 Discover request

Here, the holder wallet requests the issuer’s authorisation server configurations.

Resolve /.well-known/openid-credential-issuer endpoint for credential_issuer URI in the credential offer response.

GET https://server.example.com/.well-known/openid-credential-issuer

Resolve /.well-known/oauth-authorization-server endpoint for authorization_server URI present in the response for the above.

GET https://server.example.com/.well-known/oauth-authorization-server

3.4 Discover response

Once the well-known endpoint for issuer server configuration is resolved, the response is as given below with credentials_supported as defined by [6]:

{
  "credential_issuer": "https://server.example.com",
  "authorization_server": "https://server.example.com",
  "credential_endpoint": "https://server.example.com/credential",
  "deferred_credential_endpoint": "https://server.example.com/credential_deferred",
  "display": [
    {
      "name": "Issuer",
      "location": "Belgium",
      "locale": "en-GB",
      "description": "For queries about how we manage your data please contact the Data Protection Officer."
    }
  ],
  "credential_configurations_supported": {
    "VerifiablePortableDocumentA1": {
      "format": "vc+sd-jwt",
      "scope": "VerifiablePortableDocumentA1",
      "cryptographic_binding_methods_supported": [
        "jwk"
      ],
      "credential_signing_alg_values_supported": [
        "ES256"
      ],
      "display": [
        {
          "name": "Portable Document A1",
          "locale": "en-GB",
          "background_color": "#12107c",
          "text_color": "#FFFFFF"
        }
      ],
      "claims": {
        "given_name": {
          "display": [
            {
              "name": "Given Name",
              "locale": "en-GB"
            },
            {
              "name": "Vorname",
              "locale": "de-DE"
            }
          ]
        },
        "last_name": {
          "display": [
            {
              "name": "Surname",
              "locale": "en-GB"
            },
            {
              "name": "Nachname",
              "locale": "de-DE"
            }
          ]
        }
      }
    }
  }
}

Note

The credential_configurations_supported field and it's values change based on the supported credential formats 1) mso_mdoc 2) jwt_vc_json 3) vc+sd-jwt It is important to consult the relevant documentation for each format to ensure that all required fields and values are correctly configured. The supported credential format identifiers in the context of EWC LSPs, can be found here.

Once the well-known endpoint for authorisation server configuration is resolved, the response is as given below:

{
  "issuer": "https://server.example.com",
  "authorization_endpoint": "https://server.example.com/authorize",
  "pushed_authorization_request_endpoint": "https://server.example.com/par",
  "require_pushed_authorization_requests": true,
  "token_endpoint": "https://server.example.com/token",
  "jwks_uri": "https://server.example.com/.well-known/jwks.json",
  "response_types_supported": [
    "code",
    "vp_token",
    "id_token"
  ],
  "subject_types_supported": [
    "public",
    "pairwise"
  ],
  "id_token_signing_alg_values_supported": [
    "ES256"
  ],
  "pre-authorized_grant_anonymous_access_supported": true,
  "token_endpoint_auth_methods_supported": [
    "none"
  ]
}

3.5 Authorisation request

There are two possible ways to request the issuance of a specific Credential type in an Authorization Request. One way is to use the authorization_details request parameter with one or more authorization details objects of type openid_credential. The other method is through the use of scope.

Note

HAIP[6] chapter 4.2 mandatorily requires Pushed Authorisation Request as per [7]

3.5.1 Using authorization_details

The authorisation request is to grant access to the credential endpoint. Below is an example of such a request:

GET /authorize?
  response_type=code
  &client_id=s6BhdRkqt3
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &authorization_details=[{"type": "openid_credential", "
    credential_configuration_id": "VerifiablePortableDocumentA1"}]
  &redirect_uri=https://client.example.org/cb
Host: server.example.com

The query params for the authorisation request with authorization_details are as described below:

response_type REQUIRED. The value must be "code" to request an authorization code.
client_id REQUIRED. The identifier for the client making the request.
code_challenge REQUIRED. The code challenge used for Proof Key for Code Exchange (PKCE) as specified in OAuth 2.0 for Public Clients [5]
code_challenge_method OPTIONAL. The method used to transform the code verifier. Defaults to "plain" if not present. Possible values are "S256" or "plain", as defined in PKCE for OAuth 2.0.
authorization_details OPTIONAL. Provides fine-grained access details as specified in the OAuth 2.0 Rich Authorization Requests specification. The `authorization_details` parameter, as defined in Section 2 of [RFC9396], should be used to convey the specifics of the Credentials the Wallet intends to obtain. This specification introduces a new authorization details type, `openid_credential`. [4].

An example of W3C VC credential format is as given below:

 [
     {
         "type": "openid_credential",
         "credential_configuration_id": "VerifiablePortableDocumentA1",
         "credential_definition": {
             "credentialSubject": {}
         }
     }
 ]

The IETF SD-JWT VC is as given:

[
   {
       "type": "openid_credential",
       "format": "vc+sd-jwt",
       "vct": "VerifiablePortableDocumentA1"
   }
]
redirect_uri OPTIONAL. The redirection endpoint where the authorization server will send the user-agent after authorization is complete.
issuer_state OPTIONAL. A string value representing a specific processing context at the Credential Issuer. This value is usually provided in a Credential Offer from the Credential Issuer to the Wallet and is used to pass the issuer_state value back to the Credential Issuer.

3.5.2 Using scope

Below is an example of such a request using scope parameter. Here, the wallet gets the scope during discovery:

GET https://my-issuer.rocks/auth/authorize?
  response_type=code
  &scope=VerifiablePortableDocumentA1
  &resource=https%3A%2F%2Fcredential-issuer.example.com
  &client_id=s6BhdRkqt3
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
Host: server.example.com

The query params for the authorisation request with scope are as described below:

response_type REQUIRED. The value must be "code" to request an authorization code.
scope A JSON string that identifies the scope value supported by the Credential Issuer for a particular Credential. The value can be consistent across multiple credential configuration objects. The Authorization Server must uniquely identify the Credential Issuer based on the scope value. The Wallet can use this value in the Authorization Request. Scope values in this Credential Issuer metadata may overlap with those in the scopes_supported parameter of the Authorization Server.
client_id REQUIRED. The identifier for the client making the request.
redirect_uri OPTIONAL. The redirection endpoint where the authorization server will send the user-agent after authorization is complete.
code_challenge REQUIRED. The code challenge used for Proof Key for Code Exchange (PKCE) as specified in OAuth 2.0 for Public Clients [5]
code_challenge_method OPTIONAL. The method used to transform the code verifier. Defaults to "plain" if not present. Possible values are "S256" or "plain", as defined in PKCE for OAuth 2.0.
issuer_state OPTIONAL. A string value representing a specific processing context at the Credential Issuer. This value is usually provided in a Credential Offer from the Credential Issuer to the Wallet and is used to pass the issuer_state value back to the Credential Issuer.

3.6 Authorisation response

The credential issuer can optionally request additional details to authenticate the client e.g. DID authentication. In this case, the authorisation response will contain a response_type parameter with the value direct_post. A sample response is as given:

HTTP/1.1 302 Found
Location: http://localhost:8080?state=22857405-1a41-4db9-a638-a980484ecae1&client_id=https://example.server.com&redirect_uri=https://example.server.com/direct_post&response_type=id_token&response_mode=direct_post&scope=openid&nonce=a6f24536-b109-4623-a41a-7a9be932bdf6&request_uri=https://example.server.com/request_uri

Query params for the authorisation response are given below:

state The client uses an opaque value to maintain the state between the request and callback.
client_id Decentralised identifier
redirect_uri For redirection of the response
response_type The value must be id_token if the issuer requests DID authentication.
response_mode The value must be direct_post
scope The value must be openid
nonce A value used to associate a client session with an ID token and to mitigate replay attacks
request_uri The authorisation server’s private key signed the request.

The holder wallet then responds with an id_token signed by the DID to the direct post endpoint.

POST /direct_post
Content-Type: application/x-www-form-urlencoded
&id_token=eyJraWQiOiJkaW...a980484ecae1

If additional details are not requested, the credential issuer will send an authorisation response with a code query parameter containing the short-lived authorisation code. A sample response is given below:

HTTP/1.1 302 Found
Location: https://Wallet.example.org/cb?code=SplxlOBeZQQYbYS6WxSbIA

3.7 Token request

3.7.1 Authorisation code flow

The token request for authorised code flow is as given:

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

&grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&redirect_uri=https%3A%2F%2FWallet.example.org%2Fcb

This request is made with the following query params:

grant_type Grant type for authorisation. E.g. authorization_code
client_id Decentralised identifier
code Authorisation code
code_verifier Wallet-generated secure random token used to validate the original code_challenge provided in the initial Authorization Request
redirect_uri For redirection of the response as per IETF RFC6749 Section 3.1.2

3.7.2 Pre-authorised code flow

The token request for pre-authorised code flow is as given:

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

&grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA
&tx_code=493536

This request is made with the following query params:

grant_type Grant type for authorisation. E.g. urn:ietf:params:oauth:grant-type:pre-authorized_code
pre-authorized_code Code representing the Credential Issuer's authorisation for the Wallet to obtain Credentials of a certain type. This code must be short-lived and single-use.
tx_code Specifies if the Authorization Server expects the End-User to present a Transaction Code with the Token Request in a Pre-Authorized Code Flow. If not required, this object is absent by default. The Transaction Code binds the Pre-Authorized Code to a specific transaction, preventing replay attacks. The End-User PIN is set by the issuer and sent to the holder via email, SMS, or another out-of-band method.

3.8 Token response

3.8.1 With authorization_details

If authorization_details is used in authorisation request (refer chapter 3.5.1), the token response will be as given:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ",
  "token_type": "bearer",
  "expires_in": 86400,
  "c_nonce": "tZignsnFbp",
  "c_nonce_expires_in": 86400,
  "authorization_details": [
    {
      "type": "openid_credential",
      "credential_configuration_id": "VerifiablePortableDocumentA1",
      "credential_identifiers": [ "VerifiablePortableDocumentA1-Spain", "VerifiablePortableDocumentA1-Sweden", "VerifiablePortableDocumentA1-Germany" ]
    }
  ]
}

3.8.1 With scope

If scope is used in authorisation request (refer chapter 3.5.2), the token response will be as given:

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ",
    "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI4a5k..zEF",
    "token_type": "bearer",
    "expires_in": 86400,
    "id_token": "eyJodHRwOi8vbWF0dHIvdGVuYW50L..3Mz",
    "c_nonce": "PAPPf3h9lexTv3WYHZx8ajTe",
    "c_nonce_expires_in": 86400
}

3.9 Credential request

The Holder wallet makes a Credential Request to the Credential Endpoint as below:

For W3C VC with credential format identifier jwt_vc_json:

POST /credential
Content-Type: application/json
Authorization: Bearer eyJ0eXAi...KTjcrDMg

{
   "format": "jwt_vc_json",
   "credential_definition": {
      "type": [
         "VerifiableCredential",
         "VerifiablePortableDocumentA1"
      ]
   },
   "proof": {
      "proof_type": "jwt",
      "jwt":"eyJraWQiOiJkaWQ6ZX..zM"
   }
}

Note

In the above, the credentialSubject is optional and is not considered within the scope of EWC LSP.

For IETF SD-JWT VC with credential format identifier vc+sd-jwt:

POST /credential
Content-Type: application/json
Authorization: Bearer eyJ0eXAi...KTjcrDMg

{
   "format": "vc+sd-jwt",
   "vct": "SD_JWT_VC_example_in_OpenID4VCI",
   "proof": {
      "proof_type": "jwt",
      "jwt":"eyJ0eXAiOiJvc..1WlA"
   }
}

If specified, you can request specific fields to be included in the issued credential. If its not specified, all fields in the credential is included.

3.10 Credential response

The credential response can happen in-time or can be deferred as described below.

3.10.1 In-time

The In-time flow indicates that the credential is available immediately and the response format is as below:

{
  "credential": "eyJ0eXAiOi...F0YluuK2Cog",
  "c_nonce": "fGFF7UkhLa",
  "c_nonce_expires_in": 86400
}

3.10.2 Deferred

If the credential is unavailable, the issuer responds with an acceptance token, indicating credential issuance is deferred. The response is as below:

{
  "transaction_id": "8xLOxBtZp8",
  "c_nonce": "wlbQc6pCJp",
  "c_nonce_expires_in": 86400
}

The transaction_id is used to identity the deferred transaction when the credential is issued at a later time with the following deferred credential endpoint:

POST /deferred-credential
Authorization: BEARER eyJ0eXAiOiJKV1QiLCJhbGci..zaEhOOXcifQ
{
   "transaction_id": "8xLOxBtZp8"
}

Note

If the response contains transaction_id field, then it can be understood the credential is not available now and should be later available through the deferred credential endpoint. An example response is as given below:

{
  "transaction_id": "eyJ0eXAiOiJKV1QiLCJhbGci..zaEhOOXcifQ",
  "c_nonce": "wlbQc6pCJp",
  "c_nonce_expires_in": 86400
}

4.0 Alternate response format

Standard HTTP response codes shall be supported. Any additional ones can be formulated in the following format.

{
  "error": "invalid_request",
  "error_description": "The verifiable credential is expired"
}

The table below summarises the success/error responses that can be used:

Response format Description
invalid_request Request failed. E.g. The verifiable credential is expired
invalid_grant
  • The Authorization Server expects a PIN in the Pre-Authorized Code Flow but the Client provides the wrong PIN.
  • The end user provides the wrong Pre-Authorized Code or the Pre-Authorized Code has expired.
invalid_client The Client tried to send a Token Request with a Pre-Authorized Code without Client ID, but the Authorization Server does not support anonymous access

5.0 Implementers

Please refer to the implementers table.

6.0 Reference

  1. OpenID Foundation (2023), 'OpenID for Verifiable Credential Issuance', Available at: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-ID1.html (Accessed: July 11, 2024).
  2. European Commission (2023) The European Digital Identity Wallet Architecture and Reference Framework (2023-04, v1.1.0) [Online]. Available at: https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework/releases (Accessed: October 16, 2023).
  3. OpenID Foundation (2023), 'Self-Issued OpenID Provider v2 (SIOP v2)', Available at: https://openid.net/specs/openid-connect-self-issued-v2-1_0.html (Accessed: October 01, 2023)
  4. OAuth 2.0 Rich Authorization Requests, Available at: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-11 (Accessed: February 01, 2024)
  5. Proof Key for Code Exchange by OAuth Public Clients, Available at: https://datatracker.ietf.org/doc/html/rfc7636 (Accessed: February 01, 2024)
  6. OpenID4VC High Assurance Interoperability Profile with SD-JWT VC - draft 00, Available at https://openid.net/specs/openid4vc-high-assurance-interoperability-profile-sd-jwt-vc-1_0.html (Accessed: February 16, 2024)
  7. Lodderstedt, T., Campbell, B., Sakimura, N., Tonge, D., and F. Skokan, "OAuth 2.0 Pushed Authorization Requests", RFC 9126, DOI 10.17487/RFC9126, September 2021, https://www.rfc-editor.org/info/rfc9126.

Appendix A: Public key resolution

For a JWT there are multiple ways for resolving the public key using the kid header claim:

  • If the key identifier is a DID then use a DID resolver to obtain the public key
  • If the key identifier is not a DID, then resolve the JWKs endpoint in the AS configuration and match the public key from the JWK set using the key identifier.

Additionally, it is possible to specify JWK directly in the header using jwk header claim.