Introduction
The module tests the Verifiable Credential Issuance flows without EBSI as a trust anchor. The Credential Issuer DID and Holder Wallet DID are based on did:key
; the trust framework is not defined, and the Credential Offering is given by the Conformance UI instead of the Credential Issuer (test subject).
Guidelines
All tests start with a Credential Offering initiated by the Conformance UI on behalf of the Credential Issuer. The pre-authorized flow requires a pre-authorized code and pin, which must be inserted through the UI. The Credential issuer may have split responsibilities for issuance and authentication, or both functionalities may be included in the same service. After the discovery process is complete, the test case will proceed.
Please see Wallets Metadata specification for further details.
Please see VCI - Credential Offering for further details.
Tests
In-time Issuance
The user initiates the Credential Offering through the Conformance UI, which will offer CTWalletSameInTime to Holder Wallet on behalf of the Credential Issuer. The Credential Issuer must request an ID Token and issue CTWalletSameInTime synchronously.
Non-normative examples
The client proceeds with the Verifiable Credential Issuance flow by requesting access for the required credential from the Authorisation Server. The instructions for the request are contained in the Credential Offering.
The Authorisation Request is plain, contains PKCE challenge, and the Client is identified (client_id
) with DID.
Authorisation Request
GET from https://my-issuer.rocks/auth/authorize?
response_type=code
&scope=openid
&issuer_state=tracker%3Dvcfghhj
&state=client-state
&client_id=did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r
&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22format%22%3A%22jwt_vc%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22VerifiableAttestation%22%2C%22CTWalletSameInTime%22%5D%7D%5D
&redirect_uri=openid%3A
&nonce=glkFFoisdfEui43
&code_challenge=YjI0ZTQ4NTBhMzJmMmZhNjZkZDFkYzVhNzlhNGMyZDdjZDlkMTM4YTY4NjcyMTA5M2Q2OWQ3YjNjOGJlZDBlMSAgLQo%3D
&code_challenge_method=S256
&client_metadata=%7B%22vp_formats_supported%22%3A%7B%22jwt_vp%22%3A%7B%22alg%22%3A%5B%22ES256%22%5D%7D%2C%22jwt_vc%22%3A%7B%22alg%22%3A%5B%22ES256%22%5D%7D%7D%2C%22response_types_supported%22%3A%5B%22vp_token%22%2C%22id_token%22%5D%2C%22authorization_endpoint%22%3A%22openid%3A%2F%2F%22%7D
The Issuer's Authorisation Server validates the request and proceeds by requesting authentication of a DID from the client. The ID Token Request also serves as an Authorisation Request, and it MUST be a signed Request Object.
The Request Object is signed with the Issuer's Authorisation Server's private keys, which are discoverable through jwks_uri
parameter in ./well-known/openid-credential-issuer. The request must use response_mode=direct_post
, and the response location is delivered in the redirect_uri
. The redirect location is defined by the Authorisation Request's client_metadata.authorization_endpoint
, or defaults to openid:
if missing.
ID Token Request
HTTP 302 Location: openid://
client_id=https%3A%2F%2Fmy-issuer.rocks%2Fauth
&response_type=id_token
&scope=openid
&redirect_uri=https%3A%2F%2Fmy-issuer.rocks%2Fauth%2Fdirect_post
&request=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImM0S3JlcEpYem1CTVctcW8ybnREQ3drVGdMbTJDYl81ZWFiemtsalRoXzAifQ.eyJpc3MiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwiYXVkIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JzRVl2ZHJqeE1qUTR0cG5qZTlCREJUenVORFAza25uNnFMWkVyemQ0Yko1Z28yQ0Nob1BqZDVHQUgzenBGSlA1ZnV3U2s2NlU1UHE2RWhGNG5Lbkh6RG56bkVQOGZYOTluWkdnd2JBaDFvN0dqMVg1MlRkaGY3VTRLVGs2NnhzQTVyIiwiZXhwIjoxNTg5Njk5MTYyLCJyZXNwb25zZV90eXBlIjoiaWRfdG9rZW4iLCJyZXNwb25zZV9tb2RlIjoiZGlyZWN0X3Bvc3QiLCJjbGllbnRfaWQiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwicmVkaXJlY3RfdXJpIjoiaHR0cHM6Ly9teS1pc3N1ZXIucm9ja3MvYXV0aC9kaXJlY3RfcG9zdCIsInNjb3BlIjoib3BlbmlkIiwibm9uY2UiOiJuLTBTNl9XekEyTWoifQ.Vg615ydUGWQBM_o0mSoBePYTPyplbmcFv1oWa2mF3K-CeB9n6biCqmP-1w2jBLxSHVbIJlz_Ta0hc9pFWsewRQ
JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'c4KrepJXzmBMW-qo2ntDCwkTgLm2Cb_5eabzkljTh_0'
}
JWT Payload:
{
iss: 'https://my-issuer.rocks/auth',
aud: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
exp: 1589699162,
response_type: 'id_token',
response_mode: 'direct_post',
client_id: 'https://my-issuer.rocks/auth',
redirect_uri: 'https://my-issuer.rocks/auth/direct_post',
scope: 'openid',
nonce: 'n-0S6_WzA2Mj'
}
The client proceeds by issuing an ID Token signed by the DID document's authentication key. This will be used to prove the control of the DID.
The state
parameter is mandatory for the ID Token Response when it is present in the ID Token Request sent by the "Authorization Server." In such cases, the Client must ensure that the values of the state
parameter are identical in both.
ID Token Response
POST into https://my-issuer.rocks/auth/direct_post
Content-Type: application/x-www-form-urlencoded
id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUtic0VZdmRyanhNalE0dHBuamU5QkRCVHp1TkRQM2tubjZxTFpFcnpkNGJKNWdvMkNDaG9QamQ1R0FIM3pwRkpQNWZ1d1NrNjZVNVBxNkVoRjRuS25IekRuem5FUDhmWDk5blpHZ3diQWgxbzdHajFYNTJUZGhmN1U0S1RrNjZ4c0E1ciJ9.eyJpc3MiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIiLCJzdWIiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIiLCJhdWQiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwiZXhwIjoxNTg5Njk5MzYwLCJpYXQiOjE1ODk2OTkyNjAsIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.7EfM_Ne0Oa-pAMSehOxRDygP_zASUgw31Q7oLBsAXKAFNPtN9-Xwfl0LoB0BbjQAYPGOjQJbGlz0pCwAqBX-7Q
JWT Header:
{
typ: 'JWT',
alg: 'ES256',
kid: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r'
}
JWT Payload:
{
iss: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
sub: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
aud: 'https://my-issuer.rocks/auth',
exp: 1589699360,
iat: 1589699260,
nonce: 'n-0S6_WzA2Mj'
}
The Authorisation Server evaluates the authentication response and original authorisation request to assert if access should be granted. Upon successful authentication, the direct_post endpoint returns a redirect to the originally requested redirect_uri
with a code
.
Authorisation Response
HTTP/1.1 302 Found
Location: openid://?
code=SplxlOBeZQQYbYS6WxSbIA
&state=client-state
The client proceeds with the code
flow by calling the Token Endpoint with the required details and providing a code_verifier
corresponding to the initial Authorisation Request code_challenge
.
Token Request
POST into https://my-issuer.rocks/auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r
&code=SplxlOBeZQQYbYS6WxSbIA
&code_verifier=random-secret
The Access Token is delivered as a response payload from a successful Token Endpoint initiation. The c_nonce
(Challenge Nonce) must be stored by the client until a new one is provided.
Token Response
HTTP Response Payload: Content-Type application/json
{
access_token: 'eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ',
token_type: 'bearer',
expires_in: 86400,
id_token: 'eyJodHRwOi8vbWF0dHIvdGVuYW50L..3Mz',
c_nonce: 'PAPPf3h9lexTv3WYHZx8ajTe',
c_nonce_expires_in: 86400
}
At this point, the client has successfully obtained a valid access_token, which can be used to gain access into the Credential Issuer's Credential Endpoint.
The client proceeds by requesting issuance of the Verifiable Credential from the Issuer Mock. The requested Credential must be equal to the granted access. The DID document's authentication key must be used for signing the JWT proof, and the DID must equal the same as the one used in authentication.
Credential Request
POST into https://my-issuer.rocks/auth/credentials
Content-Type: application/json
Authorization: BEARER eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ
{
types: [
'VerifiableCredential',
'VerifiableAttestation',
'CTWalletSameInTime'
],
format: 'jwt_vc',
proof: {
proof_type: 'jwt',
jwt: 'eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2Iiwia2lkIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JzRVl2ZHJqeE1qUTR0cG5qZTlCREJUenVORFAza25uNnFMWkVyemQ0Yko1Z28yQ0Nob1BqZDVHQUgzenBGSlA1ZnV3U2s2NlU1UHE2RWhGNG5Lbkh6RG56bkVQOGZYOTluWkdnd2JBaDFvN0dqMVg1MlRkaGY3VTRLVGs2NnhzQTVyI3oyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIifQ.eyJpc3MiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnNFWXZkcmp4TWpRNHRwbmplOUJEQlR6dU5EUDNrbm42cUxaRXJ6ZDRiSjVnbzJDQ2hvUGpkNUdBSDN6cEZKUDVmdXdTazY2VTVQcTZFaEY0bktuSHpEbnpuRVA4Zlg5OW5aR2d3YkFoMW83R2oxWDUyVGRoZjdVNEtUazY2eHNBNXIiLCJhdWQiOiJodHRwczovL215LWlzc3Vlci5yb2Nrcy9hdXRoIiwiaWF0IjoxNTg5Njk5NTYyLCJleHAiOjE1ODk2OTk5NjIsIm5vbmNlIjoiUEFQUGYzaDlsZXhUdjNXWUhaeDhhalRlIn0.Oa2Mk135r_bxTrJWFOVRwOXiQrE2Vm3bVilmBCLnQhIdAFJ30qFb4d31yCC9ZNCmgzhYGzp39wqJBt5z3jMJ0Q'
}
}
JWT Header:
{
typ: 'openid4vci-proof+jwt',
alg: 'ES256',
kid: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r'
}
JWT Payload:
{
iss: 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9KbsEYvdrjxMjQ4tpnje9BDBTzuNDP3knn6qLZErzd4bJ5go2CChoPjd5GAH3zpFJP5fuwSk66U5Pq6EhF4nKnHzDnznEP8fX99nZGgwbAh1o7Gj1X52Tdhf7U4KTk66xsA5r',
aud: 'https://my-issuer.rocks/auth',
iat: 1589699562,
exp: 1589699962,
nonce: 'PAPPf3h9lexTv3WYHZx8ajTe'
}
After the successful request, the response payload will contain the requested credential.
Credential Response
HTTP Response Payload: Content-Type application/json
{
format: 'jwt_vc',
credential: 'LUpixVCWJk0eOt4CXQe1NXK....WZwmhmn9OQp6YxX0a2L',
c_nonce: 'fGFF7UkhLa',
c_nonce_expires_in: 86400
}
Deferred Issuance
The user initiates the Credential Offering through the Conformance UI, which offers CTWalletSameDeferred to the Holder Wallet on behalf of the Credential Issuer. The Credential Issuer must request an ID Token and defer the CTWalletSameDeferred issuance. For the test case, there is no need to have any delay for the deferred flow; however, the maximum allowed delay is one minute before the test case will fail.
Non-normative examples
The requests and responses used are semantically the same as those in the in-time flow. The last Credential Response is also semantically identical to the in-time Credential Response.
In the deferred flow, a Deferred Credential Response is returned in place of a Credential Response. The deferred response contains an "acceptance_token" to be used in the deferred endpoint.
Credential Response with acceptance_token
HTTP Response Payload: Content-Type application/json
{
acceptance_token: 'eyJ0eXAiOiJKV1QiLCJhbGci..zaEhOOXcifQ',
c_nonce: 'fGFF7UkhLa',
c_nonce_expires_in: 86400
}
Deferred credential request
POST https://my-issuer.rocks/auth/credential_deferred
Authorization: BEARER eyJ0eXAiOiJKV1QiLCJhbGci..zaEhOOXcifQ
Pre-authorised Issuance
The user must input pin-code and the pre-authorised code used in the Credential Offering through the Conformance UI, which will offer CTWalletSamePreAuthorised to the Holder Wallet on behalf of the Credential Issuer. The Credential Issuer must issue CTWalletSamePreAuthorised synchronously.
Non-normative examples
All other payloads are semantically the same with the In-time flow.
Token Request
POST into https://my-issuer.rocks/auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code
&user_pin=1234
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA
Applying for Qualification
After all tests has been passed, the Issuer may apply for a qualification credential. The credential is requested from the Conformance Issuer with the credential type of CTIssueQualificationCredential, which follows the in-time Verifiable Credential Issuance process.