From b12315ca59aed8ed4290f11f5b93d817af63a019 Mon Sep 17 00:00:00 2001 From: mix irving Date: Tue, 19 Nov 2024 21:05:20 +1300 Subject: [PATCH 1/4] docs: connectionless issue (#1447) Signed-off-by: mixmix --- .../credentials/connectionless/issue.md | 450 ++++++++++++++++++ .../connectionless/present-proof.md | 0 .../{ => didcomm}/anoncreds-setup.png | Bin .../{ => didcomm}/anoncreds-setup.puml | 0 .../issue-flow.anoncreds.png} | Bin .../issue-flow.anoncreds.puml} | 0 .../issue-flow.jwt.png} | Bin .../issue-flow.jwt.puml} | 0 .../credentials/{ => didcomm}/issue.md | 28 +- .../present-proof-flow.anoncreds.png} | Bin .../present-proof-flow.anoncreds.puml} | 0 .../present-proof-flow.jwt.png} | Bin .../present-proof-flow.jwt.puml} | 0 .../{ => didcomm}/present-proof.md | 15 +- .../{oid4vci.md => oid4cv/issue.md} | 2 +- docs/docusaurus/sidebars.js | 7 +- 16 files changed, 474 insertions(+), 28 deletions(-) create mode 100644 docs/docusaurus/credentials/connectionless/issue.md create mode 100644 docs/docusaurus/credentials/connectionless/present-proof.md rename docs/docusaurus/credentials/{ => didcomm}/anoncreds-setup.png (100%) rename docs/docusaurus/credentials/{ => didcomm}/anoncreds-setup.puml (100%) rename docs/docusaurus/credentials/{anoncreds-issue-flow.png => didcomm/issue-flow.anoncreds.png} (100%) rename docs/docusaurus/credentials/{anoncreds-issue-flow.puml => didcomm/issue-flow.anoncreds.puml} (100%) rename docs/docusaurus/credentials/{issue-flow.png => didcomm/issue-flow.jwt.png} (100%) rename docs/docusaurus/credentials/{issue-flow.puml => didcomm/issue-flow.jwt.puml} (100%) rename docs/docusaurus/credentials/{ => didcomm}/issue.md (94%) rename docs/docusaurus/credentials/{anoncreds-present-proof-flow.png => didcomm/present-proof-flow.anoncreds.png} (100%) rename docs/docusaurus/credentials/{anoncreds-present-proof-flow.puml => didcomm/present-proof-flow.anoncreds.puml} (100%) rename docs/docusaurus/credentials/{present-proof-flow.png => didcomm/present-proof-flow.jwt.png} (100%) rename docs/docusaurus/credentials/{present-proof-flow.puml => didcomm/present-proof-flow.jwt.puml} (100%) rename docs/docusaurus/credentials/{ => didcomm}/present-proof.md (98%) rename docs/docusaurus/credentials/{oid4vci.md => oid4cv/issue.md} (99%) diff --git a/docs/docusaurus/credentials/connectionless/issue.md b/docs/docusaurus/credentials/connectionless/issue.md new file mode 100644 index 0000000000..fc853fc42b --- /dev/null +++ b/docs/docusaurus/credentials/connectionless/issue.md @@ -0,0 +1,450 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Issue credentials (Connectionless) + +In the Identus Platform, the [Issue Credentials Protocol](/docs/concepts/glossary#issue-credential-protocol) allows you to create, retrieve, and manage issued [verifiable credentials (VCs)](/docs/concepts/glossary#verifiable-credentials) between a VC issuer and a VC holder. + +## Roles + +In the Issue Credentials Protocol, there are two roles: + +1. The [Issuer](/docs/concepts/glossary#issuer) is responsible for creating a new credential offer, sending it to a Holder, and issuing the VC once the offer is accepted. +2. The [Holder](/docs/concepts/glossary#holder) is responsible for accepting a credential offer from an issuer and receiving the VC. + +The Issuer and Holder interact with the Identus Cloud Agent API to perform the operations defined in the protocol. + + +## Prerequisites + +Before using the "Connectionless" Issuing Credentials protocol, the following conditions must be present: + + + + +1. Issuer Cloud Agents is up and running +2. The Issuer has a published PRISM DID, and the [DID document](/docs/concepts/glossary#did-document) must have at least one `assertionMethod` key for issuing credentials (see [Create DID](../../dids/create.md) and [Publish DID](../../dids/publish.md)) +3. The Holder is either another Cloud Agent or Edge Agent SDK + + + + +1. Issuer Cloud Agents is up and running +2. The Issuer has a published PRISM DID, and the [DID document](/docs/concepts/glossary#did-document) must have at least one `assertionMethod` key for issuing credentials (see [Create DID](../../dids/create.md) and [Publish DID](../../dids/publish.md)) +3. The Issuer must have created an AnonCreds Credential Definition as described [here](../../credentialdefinition/create.md). +4. The Holder is either another Cloud Agent or Edge Agent SDK + + + + +- 📌 **Note:** Currently we only support `Ed25519` curve +1. Issuer Cloud Agents is up and running +2. The Issuer has a published PRISM DID, and the [DID document](/docs/concepts/glossary#did-document) must have at least one `assertionMethod` key for issuing credentials (see [Create DID](../../dids/create.md) and [Publish DID](../../dids/publish.md)) +3. The Holder is either another Cloud Agent or Edge Agent SDK +4. The Holder must have a PRISM DID, and the DID document must have at least one `authentication` key for presenting the proof and the curve must be `Ed25519`. + + + + +## Overview + +The protocol described is a VC issuance process between an Issuer (Identus Cloud Agent) and Holder (Identus Edge Agent SDK). + +The protocol consists of the following main parts: + +1. The Issuer creates a new credential offer using the [`/issue-credentials/credential-offers/invitation`](/identus-docs/agent-api/#tag/Issue-Credentials-Protocol/operation/createCredentialOffer) endpoint, which includes information such as the schema identifier and claims. This returns a unique OOB (out-of-band) invitate code for the prospective Holder. +2. The Holder accepts the OOB invite (see SDK `acceptInvitation`) +3. The Issuer responds by sending the actual Credential Offer +4. The Holder accepts the Credential Offer +5. The Issuer sends the Verifiable Credential + +The claims provide specific information about the individual, such as their name or qualifications. + +This protocol is applicable in various real-life scenarios, such as educational credentialing, employment verification, and more. +In these scenarios, the Issuer could be a school, an employer, etc., and the Holder could be a student or an employee. +The VCs issued during this protocol could represent a diploma, a certificate of employment, etc. + +## Endpoints + +| Endpoint | Description | Role | +|--------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|----------------| +| [`/issue-credentials/credential-offers/invitation`](/agent-api/#tag/Issue-Credentials-Protocol/operation/createCredentialOffer) | This endpoint allows you to create a new credential offer invitation | Issuer | +| [`/issue-credentials/credential-offers/accept-invitation`](/agent-api/#tag/Issue-Credentials-Protocol/operation/acceptCredentialOfferInvitation) | This endpoint allows you to accept the invitation | Holder | +| [`/issue-credentials/records`](/agent-api/#tag/Issue-Credentials-Protocol/operation/getCredentialRecords) | This endpoint allows you to retrieve a collection of all the existing credential records | Issuer, Holder | +| [`/issue-credentials/records/{recordId}`](/agent-api/#tag/Issue-Credentials-Protocol/operation/getCredentialRecord) | This endpoint allows you to retrieve a specific credential record by its `id` | Issuer, Holder | +| [`/issue-credentials/records/{recordId}/accept-offer`](/agent-api/#tag/Issue-Credentials-Protocol/operation/acceptCredentialOffer) | This endpoint allows you to accept a credential offer | Holder | +| [`/issue-credentials/records/{recordId}/issue-credential`](/agent-api/#tag/Issue-Credentials-Protocol/operation/issueCredential) | This endpoint allows you to issue a VC for a specific credential record. | Issuer | + + +:::info +Please check the full [Cloud Agent API](/agent-api) specification for more detailed information. +::: + +## Issuer interactions + +This section describes the Issuer role's available interactions with the Cloud Agent. + +### Creating a Credential Offer + +To start the process, the issuer needs to create a credential offer invitation. +To do this, make a `POST` request to the [`/issue-credentials/credential-offers/invitation`](/agent-api/#tag/Issue-Credentials-Protocol/operation/createCredentialOffer) endpoint with a JSON payload that includes the following information: + + + + +1. `claims`: The data stored in a verifiable credential. Claims get expressed in a key-value format. The claims contain the data that the issuer attests to, such as name, address, date of birth, and so on. +2. `issuingDID`: The DID referring to the issuer to issue this credential from +3. `schemaId`: An optional field that, if specified, contains a valid URL to an existing VC schema. + The Cloud Agent must be able to dereference the specified URL (i.e. fetch the VC schema content from it), in order to validate the provided claims against it. + When not specified, the claims fields is not validated and can be any valid JSON object. + Please refer to the [Create VC schema](../../schemas/create.md) doc for details on how to create a VC schema. +4. `credentialFormat`: The format of the credential that will be issued - `JWT` in this case. When not specified, the default value is `JWT`. + +:::note +The `issuingDID` property comes from completing the pre-requisite steps listed above +::: + +Once the request initiates, a new credential record for the issuer gets created with a unique ID. The state of this record is now `OfferPending`. + +```shell +# Issuer POST request to create a new credential offer +curl -X 'POST' \ + 'http://localhost:8080/cloud-agent/issue-credentials/credential-offers/invitation' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "claims": { + "emailAddress": "alice@wonderland.com", + "givenName": "Alice", + "familyName": "Wonderland", + "dateOfIssuance": "2020-11-13T20:20:39+00:00", + "drivingLicenseID": "12345", + "drivingClass": 3 + }, + "credentialFormat": "JWT", + "issuingDID": "did:prism:9f847f8bbb66c112f71d08ab39930d468ccbfe1e0e1d002be53d46c431212c26", + "schemaId": "http://localhost:8080/cloud-agent/schema-registry/schemas/3f86a73f-5b78-39c7-af77-0c16123fa9c2" + }' +``` + + + + +1. `claims`: The data stored in a verifiable credential. AnonCreds claims get expressed in a flat, "string -> string", key-value pair format. The claims contain the data that the issuer attests to, such as name, address, date of birth, and so on. +2. `issuingDID`: The DID referring to the issuer to issue this credential from +3. `credentialDefinitionId`: The unique ID of the [credential definition](../../credentialdefinition/credential-definition.md) that has been created by the issuer as a prerequisite. Please refer to the [Create AnonCreds Credential Definition](../../credentialdefinition/credential-definition.md) doc for details on how to create a credential definition. +:::note +📌 Note: If the credential definition was created via HTTP URL endpoint, then this credential definition will be referenced to that credential via HTTP URL, and if this credential definition was created via DID URL endpoint, then it will be referenced via DID URL, How to create credential definition for HTTP URL or DID URL is explained in [credential definition creation guide](../../credentialdefinition/create.md) +::: +4. `credentialFormat`: The format of the credential that will be issued - `AnonCreds` in this case. + +:::note +The `issuingDID` and `credentialDefinitionId` properties come from completing the pre-requisite steps listed above +::: + +Once the request initiates, a new credential record for the issuer gets created with a unique ID. The state of this record is now `OfferPending`. + +```shell +# Issuer POST request to create a new credential offer +curl -X 'POST' \ + 'http://localhost:8080/cloud-agent/issue-credentials/credential-offers/invitation' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "claims": { + "emailAddress": "alice@wonderland.com", + "givenName": "Alice", + "familyName": "Wonderland", + "dateOfIssuance": "2020-11-13T20:20:39+00:00", + "drivingLicenseID": "12345", + "drivingClass": "3" + }, + "credentialFormat": "AnonCreds", + "issuingDID": "did:prism:9f847f8bbb66c112f71d08ab39930d468ccbfe1e0e1d002be53d46c431212c26", + "credentialDefinitionId": "5d737816-8fe8-3492-bfe3-1b3e2b67220b" + }' +``` + + + + + +1. `claims`: The data stored in a verifiable credential. Claims get expressed in a key-value format. The claims contain the data that the issuer attests to, such as name, address, date of birth, and so on. +2. `issuingDID`: The DID referring to the issuer to issue this credential from +4. `schemaId`: An optional field that, if specified, contains a valid URL to an existing VC schema. + The Cloud Agent must be able to dereference the specified URL (i.e. fetch the VC schema content from it), in order to validate the provided claims against it. + When not specified, the claims fields is not validated and can be any valid JSON object. + Please refer to the [Create VC schema](../../schemas/create.md) doc for details on how to create a VC schema. +5. `credentialFormat`: The format of the credential that will be issued - `SDJWT` in this case. + +:::note +The `issuingDID` property comes from completing the pre-requisite steps listed above +::: + +- 📌 **Note:** Claims can also include the `exp` Expiration Time attribute, which is part of JWT claims. `exp` attribute is disclosable if specified and can have a value in epoch time (in seconds), indicating when the SDJWT credential expires for more details +[RFC5719](https://datatracker.ietf.org/doc/html/rfc7519#page-9) + +Once the request initiates, a new credential record for the issuer gets created with a unique ID. The state of this record is now `OfferPending`. + +```shell +# Issuer POST request to create a new credential offer +curl -X 'POST' \ + 'http://localhost:8080/cloud-agent/issue-credentials/credential-offers/invitation' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "claims": { + "emailAddress": "alice@wonderland.com", + "givenName": "Alice", + "familyName": "Wonderland", + "dateOfIssuance": "2020-11-13T20:20:39+00:00", + "drivingLicenseID": "12345", + "drivingClass": 3, + "exp" : 1883000000 + }, + "credentialFormat": "SDJWT", + "issuingDID": "did:prism:9f847f8bbb66c112f71d08ab39930d468ccbfe1e0e1d002be53d46c431212c26", + "schemaId": "http://localhost:8080/cloud-agent/schema-registry/schemas/3f86a73f-5b78-39c7-af77-0c16123fa9c2" + }' +``` + + + + + +### Sending the Offer to the Holder + +The next step for the Issuer is to send the OOB invite Holder (by definition, this is "out of band", so not handled by Identus). +Common ways to convey such OOB invites might be a QR code that is scanned, or via an existing channel of connection in an application. + + +### Issuing the Credential + +Once the holder has approved the offer and sent a request to the Issuer, +the Issuer will receive the request via DIDComm and update the record state to `RequestReceived.` + +The Issuer can then use the [`/issue-credentials/records/{recordId}/issue-credential`](/agent-api/#tag/Issue-Credentials-Protocol/operation/issueCredential) endpoint to issue the credential to the holder. + +```shell +# Issuer POST request to issue the credential +# make sure you have `issuer_record_id` extracted from created credential offer +# and the record achieved `RequestReceived` state +curl -X POST \ + "http://localhost:8080/cloud-agent/issue-credentials/records/$issuer_record_id/issue-credential" \ + -H "Content-Type: application/json" \ + -H "apikey: $API_KEY" +``` + +When this endpoint gets called, the state of the record will change to `CredentialPending,` and after processing, it will change to `CredentialGenerated.` + +Finally, the Issuer agent will send the credential to the holder via DIDComm, +and the state of the record will change to `CredentialSent`. +At this point, the Issuer's interactions with the holder are complete. + +```mermaid +--- +title: Issuer flow +--- +stateDiagram-v2 + [*] --> OfferPending: create credential offer (`/issue-credentials/credential-offers`) + OfferPending --> OfferSent: send offer (auto via PRISM Agent DIDComm) + OfferSent --> RequestReceived: receive request (auto via PRISM Agent DIDComm) + RequestReceived --> CredentialPending: issue credential (`/issue-credentials/records/{recordId}/issue-credential`) + CredentialPending --> CredentialGenerated: process issued credential (auto via PRISM Agent) + CredentialGenerated --> CredentialSent: send credential (auto via PRISM Agent) +``` + +## Holder interactions + +This section describes the Holder role's available interactions with the Cloud Agent. + +### Receiving the VC Offer + +The Holder will receive the offer from the Issuer via DIDComm, +and a new credential record with a unique ID gets created in the `OfferReceived` state. + +This process is automatic for the Cloud Agent. + +You could check if a new credential offer is available using [`/issue-credentials/records`](/#tag/Issue-Credentials-Protocol/operation/getCredentialRecords) request and check for any records available in `OfferReceived` state: +```shell +# Holder GET request to retrieve credential records +curl "http://localhost:8090/cloud-agent/issue-credentials/records" \ + -H "Content-Type: application/json" \ + -H "apikey: $API_KEY" +``` + + +### Approving the VC Offer + +To accept the offer, the Holder can make a `POST` request to the [`/issue-credentials/records/{recordId}/accept-offer`](/agent-api/#tag/Issue-Credentials-Protocol/operation/acceptCredentialOffer) endpoint with a JSON payload that includes the following information: + + + + +1. `holder_record_id`: The unique identifier of the issue credential record known by the holder's Cloud Agent. +2. `subjectId`: This field represents the unique identifier for the subject of the verifiable credential. It is a short-form PRISM [DID](/docs/concepts/glossary#decentralized-identifier) string, such as `did:prism:subjectIdentifier`. + +```shell +# Holder POST request to accept the credential offer +curl -X POST "http://localhost:8090/cloud-agent/issue-credentials/records/$holder_record_id/accept-offer" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "subjectId": "did:prism:subjectIdentifier" + }' +``` + + + + +1. `holder_record_id`: The unique identifier of the issue credential record known by the holder's Cloud Agent. + +```shell +# Holder POST request to accept the credential offer +curl -X POST "http://localhost:8090/cloud-agent/issue-credentials/records/$holder_record_id/accept-offer" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{}' +``` + + + + + +1. `holder_record_id`: The unique identifier of the issue credential record known by the holder's Cloud Agent. +2. `subjectId`: This field represents the unique identifier for the subject of the verifiable credential. It is a short-form PRISM [DID](/docs/concepts/glossary#decentralized-identifier) string, such as `did:prism:subjectIdentifier`. +3. `keyId` Option parameter + 1. when keyId is not provided the SDJWT VC is not binded to Holder/Prover key + ```shell + # Holder POST request to accept the credential offer + curl -X POST "http://localhost:8090/cloud-agent/issue-credentials/records/$holder_record_id/accept-offer" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "subjectId": "did:prism:subjectIdentifier" + }' + ``` + A SD-JWT Verifiable Credential (VC) without a `cnf` key could possibly look like below + + ```json + { + "_sd": [ + "CrQe7S5kqBAHt-nMYXgc6bdt2SH5aTY1sU_M-PgkjPI", + "JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE", + "PorFbpKuVu6xymJagvkFsFXAbRoc2JGlAUA2BA4o7cI", + "TGf4oLbgwd5JQaHyKVQZU9UdGE0w5rtDsrZzfUaomLo", + "XQ_3kPKt1XyX7KANkqVR6yZ2Va5NrPIvPYbyMvRKBMM", + "XzFrzwscM6Gn6CJDc6vVK8BkMnfG8vOSKfpPIZdAfdE", + "gbOsI4Edq2x2Kw-w5wPEzakob9hV1cRD0ATN3oQL9JM", + "jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4" + ], + "iss": "https://issuer.example.com", + "iat": 1683000000, + "exp": 1883000000, + "sub": "user_42", + "_sd_alg": "sha-256" + } + ``` + 2. `keyId`: This is optional field but must be specified to choose which key bounds to the verifiable credential. + For more information on key-binding, [ietf-oauth-selective-disclosure-jwt](https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt). + Currently, we only support the EdDSA algorithm and curve Ed25519. + The specified keyId should be of type Ed25519. + The purpose of the keyId should be authentication. + + ```shell + # Holder POST request to accept the credential offer with keyId + curl -X POST "http://localhost:8090/cloud-agent/issue-credentials/records/$holder_record_id/accept-offer" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -H "apikey: $API_KEY" \ + -d '{ + "subjectId": "did:prism:subjectIdentifier", + "keyId": "key-1" + }' + ``` + A SD-JWT Verifiable Credential (VC) that includes a `cnf` key could possibly look like below + ```json + { + "_sd": [ + "CrQe7S5kqBAHt-nMYXgc6bdt2SH5aTY1sU_M-PgkjPI", + "JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE", + "PorFbpKuVu6xymJagvkFsFXAbRoc2JGlAUA2BA4o7cI", + "TGf4oLbgwd5JQaHyKVQZU9UdGE0w5rtDsrZzfUaomLo", + "XQ_3kPKt1XyX7KANkqVR6yZ2Va5NrPIvPYbyMvRKBMM", + "XzFrzwscM6Gn6CJDc6vVK8BkMnfG8vOSKfpPIZdAfdE", + "gbOsI4Edq2x2Kw-w5wPEzakob9hV1cRD0ATN3oQL9JM", + "jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4" + ], + "iss": "https://issuer.example.com", + "iat": 1683000000, + "exp": 1883000000, + "sub": "user_42", + "_sd_alg": "sha-256", + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ" + } + } + } + ``` + + + + + +This request will change the state of the record to `RequestPending`. + +### Receiving the VC Credential + +Once the Holder has approved the offer and sent a request to the Issuer, the Holder agent will process the request and send it to the Issuer agent. +The state of the Holder's record will change to `RequestSent`. + +After the Issuer has issued the credential, the Holder will receive the credential via DIDComm, and the state of the Holder's record will change to `CredentialReceived`. +This process is automatic for the Cloud Agent. + +The Holder can check the achieved credential using a GET request to [`/issue-credentials/records/{recordId}/`](/agent-api/#tag/Issue-Credentials-Protocol/operation/getCredentialRecord) endpoint. + +```mermaid +--- +title: Holder Flow +--- +stateDiagram-v2 + [*] --> OfferReceived: receive offer (auto via PRISM Agent) + OfferReceived --> RequestPending: accept offer (`/issue-credentials/records/{recordId}/accept-offer`) + RequestPending --> RequestSent: send request (auto via PRISM Agent) + RequestSent --> CredentialReceived: receive credential (auto via PRISM Agent) +``` + +## Sequence diagram + +TODO + + diff --git a/docs/docusaurus/credentials/connectionless/present-proof.md b/docs/docusaurus/credentials/connectionless/present-proof.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/docusaurus/credentials/anoncreds-setup.png b/docs/docusaurus/credentials/didcomm/anoncreds-setup.png similarity index 100% rename from docs/docusaurus/credentials/anoncreds-setup.png rename to docs/docusaurus/credentials/didcomm/anoncreds-setup.png diff --git a/docs/docusaurus/credentials/anoncreds-setup.puml b/docs/docusaurus/credentials/didcomm/anoncreds-setup.puml similarity index 100% rename from docs/docusaurus/credentials/anoncreds-setup.puml rename to docs/docusaurus/credentials/didcomm/anoncreds-setup.puml diff --git a/docs/docusaurus/credentials/anoncreds-issue-flow.png b/docs/docusaurus/credentials/didcomm/issue-flow.anoncreds.png similarity index 100% rename from docs/docusaurus/credentials/anoncreds-issue-flow.png rename to docs/docusaurus/credentials/didcomm/issue-flow.anoncreds.png diff --git a/docs/docusaurus/credentials/anoncreds-issue-flow.puml b/docs/docusaurus/credentials/didcomm/issue-flow.anoncreds.puml similarity index 100% rename from docs/docusaurus/credentials/anoncreds-issue-flow.puml rename to docs/docusaurus/credentials/didcomm/issue-flow.anoncreds.puml diff --git a/docs/docusaurus/credentials/issue-flow.png b/docs/docusaurus/credentials/didcomm/issue-flow.jwt.png similarity index 100% rename from docs/docusaurus/credentials/issue-flow.png rename to docs/docusaurus/credentials/didcomm/issue-flow.jwt.png diff --git a/docs/docusaurus/credentials/issue-flow.puml b/docs/docusaurus/credentials/didcomm/issue-flow.jwt.puml similarity index 100% rename from docs/docusaurus/credentials/issue-flow.puml rename to docs/docusaurus/credentials/didcomm/issue-flow.jwt.puml diff --git a/docs/docusaurus/credentials/issue.md b/docs/docusaurus/credentials/didcomm/issue.md similarity index 94% rename from docs/docusaurus/credentials/issue.md rename to docs/docusaurus/credentials/didcomm/issue.md index ff9beeae99..dfe4a887a5 100644 --- a/docs/docusaurus/credentials/issue.md +++ b/docs/docusaurus/credentials/didcomm/issue.md @@ -3,7 +3,7 @@ import TabItem from '@theme/TabItem'; # Issue credentials (DIDComm) -In the Identus Platform, the [Issue Credentials Protocol](/docs/concepts/glossary#issue-credentials-protocol) allows you to create, retrieve, and manage issued [verifiable credentials (VCs)](/docs/concepts/glossary#verifiable-credentials) between a VC issuer and a VC holder. +In the Identus Platform, the [Issue Credentials Protocol](/docs/concepts/glossary#issue-credential-protocol) allows you to create, retrieve, and manage issued [verifiable credentials (VCs)](/docs/concepts/glossary#verifiable-credentials) between a VC issuer and a VC holder. ## Roles @@ -23,24 +23,24 @@ Before using the Issuing Credentials protocol, the following conditions must be 1. Issuer and Holder Cloud Agents up and running -2. A connection must be established between the Issuer and Holder Cloud Agents (see [Connections](../connections/connection.md)) -3. The Issuer must have a published PRISM DID, and the [DID document](/docs/concepts/glossary#did-document) must have at least one `assertionMethod` key for issuing credentials (see [Create DID](../dids/create.md) and [Publish DID](../dids/publish.md)) +2. A connection must be established between the Issuer and Holder Cloud Agents (see [Connections](../../connections/connection.md)) +3. The Issuer must have a published PRISM DID, and the [DID document](/docs/concepts/glossary#did-document) must have at least one `assertionMethod` key for issuing credentials (see [Create DID](../../dids/create.md) and [Publish DID](../../dids/publish.md)) 4. The Holder must have a PRISM DID, and the DID document must have at least one `authentication` key for presenting the proof. 1. Issuer and Holder Cloud Agents up and running -2. A connection must be established between the Issuer and Holder Cloud Agents (see [Connections](../connections/connection.md)) -3. The Issuer must have created an AnonCreds Credential Definition as described [here](../credentialdefinition/create.md). +2. A connection must be established between the Issuer and Holder Cloud Agents (see [Connections](../../connections/connection.md)) +3. The Issuer must have created an AnonCreds Credential Definition as described [here](../../credentialdefinition/create.md). - 📌 **Note:** Currently we only support `Ed25519` curve 1. Issuer and Holder Cloud Agents up and running -2. A connection must be established between the Issuer and Holder Cloud Agents (see [Connections](../connections/connection.md)) -3. The Issuer must have a published PRISM DID, and the [DID document](/docs/concepts/glossary#did-document) must have at least one `assertionMethod` key for issuing credentials and the curve must be `Ed25519` (see [Create DID](../dids/create.md) and [Publish DID](../dids/publish.md)) +2. A connection must be established between the Issuer and Holder Cloud Agents (see [Connections](../../connections/connection.md)) +3. The Issuer must have a published PRISM DID, and the [DID document](/docs/concepts/glossary#did-document) must have at least one `assertionMethod` key for issuing credentials and the curve must be `Ed25519` (see [Create DID](../../dids/create.md) and [Publish DID](../../dids/publish.md)) 4. The Holder must have a PRISM DID, and the DID document must have at least one `authentication` key for presenting the proof and the curve must be `Ed25519`. @@ -95,7 +95,7 @@ To do this, make a `POST` request to the [`/issue-credentials/credential-offers` 4. `schemaId`: An optional field that, if specified, contains a valid URL to an existing VC schema. The Cloud Agent must be able to dereference the specified URL (i.e. fetch the VC schema content from it), in order to validate the provided claims against it. When not specified, the claims fields is not validated and can be any valid JSON object. - Please refer to the [Create VC schema](../schemas/create.md) doc for details on how to create a VC schema. + Please refer to the [Create VC schema](../../schemas/create.md) doc for details on how to create a VC schema. 5. `credentialFormat`: The format of the credential that will be issued - `JWT` in this case. When not specified, the default value is `JWT`. @@ -133,9 +133,9 @@ curl -X 'POST' \ 1. `claims`: The data stored in a verifiable credential. AnonCreds claims get expressed in a flat, "string -> string", key-value pair format. The claims contain the data that the issuer attests to, such as name, address, date of birth, and so on. 2. `connectionId`: The unique ID of the connection between the holder and the issuer to offer this credential over. -3. `credentialDefinitionId`: The unique ID of the [credential definition](../credentialdefinition/credential-definition.md) that has been created by the issuer as a prerequisite. Please refer to the [Create AnonCreds Credential Definition](../credentialdefinition/credential-definition.md) doc for details on how to create a credential definition. +3. `credentialDefinitionId`: The unique ID of the [credential definition](../../credentialdefinition/credential-definition.md) that has been created by the issuer as a prerequisite. Please refer to the [Create AnonCreds Credential Definition](../../credentialdefinition/credential-definition.md) doc for details on how to create a credential definition. :::note -📌 Note: If the credential definition was created via HTTP URL endpoint, then this credential definition will be referenced to that credential via HTTP URL, and if this credential definition was created via DID URL endpoint, then it will be referenced via DID URL, How to create credential definition for HTTP URL or DID URL is explained in [credential definition creation guide](../credentialdefinition/create.md) +📌 Note: If the credential definition was created via HTTP URL endpoint, then this credential definition will be referenced to that credential via HTTP URL, and if this credential definition was created via DID URL endpoint, then it will be referenced via DID URL, How to create credential definition for HTTP URL or DID URL is explained in [credential definition creation guide](../../credentialdefinition/create.md) ::: 4. `credentialFormat`: The format of the credential that will be issued - `AnonCreds` in this case. 5. `issuingDID`: The DID referring to the issuer to issue this credential from @@ -178,7 +178,7 @@ curl -X 'POST' \ 4. `schemaId`: An optional field that, if specified, contains a valid URL to an existing VC schema. The Cloud Agent must be able to dereference the specified URL (i.e. fetch the VC schema content from it), in order to validate the provided claims against it. When not specified, the claims fields is not validated and can be any valid JSON object. - Please refer to the [Create VC schema](../schemas/create.md) doc for details on how to create a VC schema. + Please refer to the [Create VC schema](../../schemas/create.md) doc for details on how to create a VC schema. 5. `credentialFormat`: The format of the credential that will be issued - `SDJWT` in this case. @@ -435,12 +435,12 @@ The following diagram shows the end-to-end flow for an issuer to issue a VC to a -![](issue-flow.png) +![](issue-flow.jwt.png) -![](anoncreds-issue-flow.png) +![](issue-flow.anoncreds.png) - \ No newline at end of file + diff --git a/docs/docusaurus/credentials/anoncreds-present-proof-flow.png b/docs/docusaurus/credentials/didcomm/present-proof-flow.anoncreds.png similarity index 100% rename from docs/docusaurus/credentials/anoncreds-present-proof-flow.png rename to docs/docusaurus/credentials/didcomm/present-proof-flow.anoncreds.png diff --git a/docs/docusaurus/credentials/anoncreds-present-proof-flow.puml b/docs/docusaurus/credentials/didcomm/present-proof-flow.anoncreds.puml similarity index 100% rename from docs/docusaurus/credentials/anoncreds-present-proof-flow.puml rename to docs/docusaurus/credentials/didcomm/present-proof-flow.anoncreds.puml diff --git a/docs/docusaurus/credentials/present-proof-flow.png b/docs/docusaurus/credentials/didcomm/present-proof-flow.jwt.png similarity index 100% rename from docs/docusaurus/credentials/present-proof-flow.png rename to docs/docusaurus/credentials/didcomm/present-proof-flow.jwt.png diff --git a/docs/docusaurus/credentials/present-proof-flow.puml b/docs/docusaurus/credentials/didcomm/present-proof-flow.jwt.puml similarity index 100% rename from docs/docusaurus/credentials/present-proof-flow.puml rename to docs/docusaurus/credentials/didcomm/present-proof-flow.jwt.puml diff --git a/docs/docusaurus/credentials/present-proof.md b/docs/docusaurus/credentials/didcomm/present-proof.md similarity index 98% rename from docs/docusaurus/credentials/present-proof.md rename to docs/docusaurus/credentials/didcomm/present-proof.md index 280fea81c5..406f9d1cd9 100644 --- a/docs/docusaurus/credentials/present-proof.md +++ b/docs/docusaurus/credentials/didcomm/present-proof.md @@ -1,7 +1,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Present proof +# Present proof (DIDComm) The [Present Proof Protocol](/docs/concepts/glossary#present-proof-protocol) allows: - a [Verifier](/docs/concepts/glossary#verifier) to request a verifiable credential presentation from a Holder/Prover @@ -21,7 +21,7 @@ The present proof protocol has two roles: Before using the Proof Presentation protocol, the following conditions must be present: 1. Holder/Prover and Verifier Cloud Agents must be up and running -2. A connection must be established between the Holder/Prover and Verifier Cloud Agents (see [Connections](../connections/connection.md)) +2. A connection must be established between the Holder/Prover and Verifier Cloud Agents (see [Connections](../../connections/connection.md)) 3. The Holder/Prover should hold a [verifiable credential (VC)](/docs/concepts/glossary#verifiable-credential) received from an [Issuer](/docs/concepts/glossary#issuer) see [Issue](./issue.md). ## Overview @@ -323,20 +323,15 @@ stateDiagram-v2 The following diagram shows the end-to-end flow for a verifier to request and verify a proof presentation from a Holder/prover. -### JWT Present Proof Flow Diagram -![](present-proof-flow.png) -### Anoncreds Present Proof Flow Diagram -![](anoncreds-present-proof-flow.png) - -![](present-proof-flow.png) +![](present-proof-flow.jwt.png) -![](anoncreds-present-proof-flow.png) +![](present-proof-flow.anoncreds.png) - \ No newline at end of file + diff --git a/docs/docusaurus/credentials/oid4vci.md b/docs/docusaurus/credentials/oid4cv/issue.md similarity index 99% rename from docs/docusaurus/credentials/oid4vci.md rename to docs/docusaurus/credentials/oid4cv/issue.md index a84b7c7dfe..8bcf839b1c 100644 --- a/docs/docusaurus/credentials/oid4vci.md +++ b/docs/docusaurus/credentials/oid4cv/issue.md @@ -1,4 +1,4 @@ -# Issue credentials (OID4VCI) +# Issue credentials (OID4VC) [OID4VCI](/docs/concepts/glossary#oid4vci) (OpenID for Verifiable Credential Issuance) is a protocol that extends OAuth2 to issue credentials. It involves a Credential Issuer server and an Authorization server working together, diff --git a/docs/docusaurus/sidebars.js b/docs/docusaurus/sidebars.js index b746ea93f5..56d5dfca37 100644 --- a/docs/docusaurus/sidebars.js +++ b/docs/docusaurus/sidebars.js @@ -19,9 +19,10 @@ const sidebars = { description: 'Credentials tutorials' }, items: [ - 'credentials/issue', - 'credentials/oid4vci', - 'credentials/present-proof', + 'credentials/didcomm/issue', + 'credentials/connectionless/issue', + 'credentials/oid4ci/issue', + 'credentials/didcomm/present-proof', 'credentials/revocation' ] }, From b0effec9bdc48f7daad94a159c78766c6ed1c903 Mon Sep 17 00:00:00 2001 From: bvoiturier Date: Tue, 19 Nov 2024 09:25:50 +0100 Subject: [PATCH 2/4] fix: Kafka consumer not picking messages (#1441) Signed-off-by: Benjamin Voiturier --- cloud-agent/service/server/src/main/resources/logback.xml | 3 +++ .../shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cloud-agent/service/server/src/main/resources/logback.xml b/cloud-agent/service/server/src/main/resources/logback.xml index 9121c1c22d..a91551a3d1 100644 --- a/cloud-agent/service/server/src/main/resources/logback.xml +++ b/cloud-agent/service/server/src/main/resources/logback.xml @@ -13,6 +13,9 @@ + + + diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala index 9180fc4d62..14e5ab0491 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/messaging/kafka/ZKafkaMessagingServiceImpl.scala @@ -9,6 +9,7 @@ import zio.kafka.consumer.{ ConsumerSettings as ZKConsumerSettings, Subscription as ZKSubscription } +import zio.kafka.consumer.Consumer.{AutoOffsetStrategy, OffsetRetrieval} import zio.kafka.producer.{Producer as ZKProducer, ProducerSettings as ZKProducerSettings} import zio.kafka.serde.{Deserializer as ZKDeserializer, Serializer as ZKSerializer} @@ -80,7 +81,7 @@ class ZKafkaConsumerImpl[K, V]( .withMaxPollInterval(maxPollInterval) // Should be max.poll.records x 'max processing time per record' // 'pollTimeout' default is 50 millis. This is a ZIO Kafka property. .withPollTimeout(pollTimeout) - // .withOffsetRetrieval(OffsetRetrieval.Auto(AutoOffsetStrategy.Earliest)) + .withOffsetRetrieval(OffsetRetrieval.Auto(AutoOffsetStrategy.Earliest)) .withRebalanceSafeCommits(rebalanceSafeCommits) // .withMaxRebalanceDuration(30.seconds) ) From 7839b95ad423556129a4d7c1bf90b75dfa799db6 Mon Sep 17 00:00:00 2001 From: Shailesh Patil Date: Wed, 20 Nov 2024 11:34:15 +0000 Subject: [PATCH 3/4] fix: proof presentation request (#1453) Signed-off-by: mineme0110 Signed-off-by: Yurii Shynbuiev Co-authored-by: Yurii Shynbuiev --- .mega-linter.yml | 3 +- .../server/jobs/PresentBackgroundJobs.scala | 117 +++++++++-------- .../reportproblem/v2/ReportProblem.scala | 2 +- .../core/model/error/PresentationError.scala | 6 + .../core/service/CredentialServiceImpl.scala | 3 +- .../PresentationSubmissionVerification.scala | 4 +- ...ialSchemaAndTrustedIssuersConstraint.scala | 6 + .../jwt/VerifiablePresentationPayload.scala | 123 ++++++++++++++---- .../pollux/vc/jwt/JwtPresentationTest.scala | 99 ++++++++++++++ .../test/kotlin/common/CredentialSchema.kt | 32 ++++- .../connectionless/ConnectionLessSteps.kt | 7 +- .../test/kotlin/steps/proofs/JwtProofSteps.kt | 30 ++++- .../credential/jwt/present_proof.feature | 20 ++- .../src/common/ProofsService.ts | 9 +- 14 files changed, 361 insertions(+), 100 deletions(-) create mode 100644 pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/CredentialSchemaAndTrustedIssuersConstraint.scala create mode 100644 pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JwtPresentationTest.scala diff --git a/.mega-linter.yml b/.mega-linter.yml index 786cf36edd..6736cb65d1 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -28,6 +28,7 @@ DISABLE_LINTERS: - PYTHON_MYPY - PYTHON_PYRIGHT - PYTHON_RUFF + - TYPESCRIPT_STANDARD DISABLE_ERRORS_LINTERS: - KOTLIN_KTLINT @@ -65,5 +66,5 @@ YAML_PRETTIER_FILTER_REGEX_EXCLUDE: "infrastructure/charts/agent/*|cloud-agent/s YAML_V8R_FILTER_REGEX_EXCLUDE: "infrastructure/charts/agent/*" JAVASCRIPT_STANDARD_FILTER_REGEX_EXCLUDE: "tests/performance-tests/agent-performance-tests-k6/src/k6chaijs.js\ - |tests/didcomm-tests/docker/initdb.js" + |tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts|tests/didcomm-tests/docker/initdb.js" BASH_SHELLCHECK_FILTER_REGEX_EXCLUDE: "infrastructure/*" diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala index 4bfb247176..a6f2bfae67 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala @@ -20,7 +20,7 @@ import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.mercury.protocol.presentproof.* import org.hyperledger.identus.mercury.protocol.reportproblem.v2.{ProblemCode, ReportProblem} -import org.hyperledger.identus.pollux.core.model.* +import org.hyperledger.identus.pollux.core.model.{presentation, *} import org.hyperledger.identus.pollux.core.model.error.{CredentialServiceError, PresentationError} import org.hyperledger.identus.pollux.core.model.error.PresentationError.* import org.hyperledger.identus.pollux.core.model.presentation.Options @@ -28,6 +28,7 @@ import org.hyperledger.identus.pollux.core.service.{CredentialService, Presentat import org.hyperledger.identus.pollux.core.service.serdes.AnoncredCredentialProofsV1 import org.hyperledger.identus.pollux.sdjwt.{HolderPrivateKey, IssuerPublicKey, PresentationCompact, SDJWT} import org.hyperledger.identus.pollux.vc.jwt.{DidResolver as JwtDidResolver, Issuer as JwtIssuer, JWT, JwtPresentation} +import org.hyperledger.identus.pollux.vc.jwt.CredentialSchemaAndTrustedIssuersConstraint import org.hyperledger.identus.resolvers.DIDResolver import org.hyperledger.identus.shared.http.* import org.hyperledger.identus.shared.messaging @@ -37,7 +38,7 @@ import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds import zio.* import zio.metrics.* -import zio.prelude.Validation +import zio.prelude.{Validation, ZValidation} import zio.prelude.ZValidation.{Failure as ZFailure, *} import java.time.{Instant, ZoneId} @@ -947,7 +948,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { object Verifier { - def handleRequestPending(id: DidCommID, record: RequestPresentation): ZIO[ + def handleRequestPending(id: DidCommID, requestPresentation: RequestPresentation): ZIO[ JwtDidResolver & COMMON_RESOURCES & MESSAGING_RESOURCES, Failure, Unit @@ -978,17 +979,20 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { val verifierReqPendingToSentFlow = for { _ <- ZIO.log(s"PresentationRecord: RequestPending (Send Message)") - walletAccessContext <- buildWalletAccessContextLayer( - record.from.getOrElse(throw new RuntimeException("from is None is not possible")) - ) + walletAccessContext <- ZIO + .fromOption(requestPresentation.from) + .mapError(_ => RequestPresentationMissingField(id.value, "sender")) + .flatMap(buildWalletAccessContextLayer) + result <- for { didOps <- ZIO.service[DidOps] - didCommAgent <- buildDIDCommAgent( - record.from.getOrElse(throw new RuntimeException("from is None is not possible")) - ).provideSomeLayer(ZLayer.succeed(walletAccessContext)) + didCommAgent <- ZIO + .fromOption(requestPresentation.from) + .mapError(_ => RequestPresentationMissingField(id.value, "sender")) + .flatMap(buildDIDCommAgent(_).provideSomeLayer(ZLayer.succeed(walletAccessContext))) resp <- MessagingService - .send(record.makeMessage) + .send(requestPresentation.makeMessage) .provideSomeLayer(didCommAgent) @@ Metric .gauge("present_proof_flow_verifier_send_presentation_request_msg_ms_gauge") @@ -1069,6 +1073,16 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { } } + private def buildReportProblem(presentation: Presentation, error: String): ReportProblem = { + ReportProblem.build( + fromDID = presentation.to, + toDID = presentation.from, + pthid = presentation.thid.getOrElse(presentation.id), + code = ProblemCode("e.p.presentation-verification-failed"), + comment = Some(error) + ) + } + private def handleJWT( id: DidCommID, requestPresentation: RequestPresentation, @@ -1085,7 +1099,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ <- checkInvitationExpiry(id, invitation).provideSomeLayer(ZLayer.succeed(walletAccessContext)) result <- for { didResolverService <- ZIO.service[JwtDidResolver] - credentialsClaimsValidationResult <- presentation.attachments.head.data match { + claimsValidationResult <- presentation.attachments.head.data match { case Base64(data) => val base64Decoded = new String(java.util.Base64.getUrlDecoder.decode(data)) val maybePresentationOptions: Either[PresentationError, Option[Options]] = @@ -1103,17 +1117,33 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ) ) .getOrElse(Right(None)) + val schemaIdAndTrustedIssuers = requestPresentation.body.proof_types.map { proofType => + CredentialSchemaAndTrustedIssuersConstraint( + proofType.schema, + proofType.trustIssuers.map(_.map(_.value)) + ) + } val presentationClaimsValidationResult = for { - _ <- ZIO.fromEither(maybePresentationOptions.map { + validationResult: Validation[String, Unit] <- ZIO.fromEither(maybePresentationOptions.map { case Some(options) => - JwtPresentation.validatePresentation( - JWT(base64Decoded), - options.domain, - options.challenge - ) - case _ => Validation.unit + JwtPresentation + .validatePresentation( + JWT(base64Decoded), + Some(options.domain), + Some(options.challenge), + schemaIdAndTrustedIssuers + ) + case _ => + JwtPresentation + .validatePresentation( + JWT(base64Decoded), + None, + None, + schemaIdAndTrustedIssuers + ) }) + verificationConfig <- ZIO.service[AppConfig].map(_.agent.verification) _ <- ZIO.log(s"VerificationConfig: ${verificationConfig}") @@ -1121,18 +1151,21 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { // A proof is typically attached to a verifiable presentation for authentication purposes // and to a verifiable credential as a method of assertion. uriResolver <- ZIO.service[UriResolver] - result <- JwtPresentation + result: Validation[String, Unit] <- JwtPresentation .verify( JWT(base64Decoded), verificationConfig.toPresentationVerificationOptions() )(didResolverService, uriResolver)(clock) .mapError(error => PresentationError.PresentationVerificationError(error.mkString)) - } yield result + } yield Seq(validationResult, result) presentationClaimsValidationResult case any => ZIO.fail(PresentationReceivedError("Only Base64 Supported")) } + credentialsClaimsValidationResult = ZValidation + .validateAll(claimsValidationResult) + .map(_ => ()) _ <- credentialsClaimsValidationResult match case l @ ZFailure(_, _) => ZIO.logError(s"CredentialsClaimsValidationResult: $l") case l @ Success(_, _) => ZIO.logInfo(s"CredentialsClaimsValidationResult: $l") @@ -1151,19 +1184,13 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ <- service .markPresentationVerificationFailed(id) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ presReceivedToProcessedAspect - didCommAgent <- buildDIDCommAgent(presentation.from).provideSomeLayer( + didCommAgent <- buildDIDCommAgent(presentation.to).provideSomeLayer( ZLayer.succeed(walletAccessContext) ) - reportproblem = ReportProblem.build( - fromDID = presentation.to, - toDID = presentation.from, - pthid = presentation.thid.getOrElse(presentation.id), - code = ProblemCode("e.p.presentation-verification-failed"), - comment = Some(error.mkString) - ) + reportProblem = buildReportProblem(presentation, error.mkString) _ <- MessagingService - .send(reportproblem.toMessage) + .send(reportProblem.toMessage) .provideSomeLayer(didCommAgent) _ <- ZIO.log(s"CredentialsClaimsValidationResult: $error") } yield () @@ -1219,19 +1246,13 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ <- service .markPresentationVerificationFailed(id) .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ presReceivedToProcessedAspect - didCommAgent <- buildDIDCommAgent(presentation.from).provideSomeLayer( + didCommAgent <- buildDIDCommAgent(presentation.to).provideSomeLayer( ZLayer.succeed(walletAccessContext) ) - reportproblem = ReportProblem.build( - fromDID = presentation.to, - toDID = presentation.from, - pthid = presentation.thid.getOrElse(presentation.id), - code = ProblemCode("e.p.presentation-verification-failed"), - comment = Some(invalid.toString) - ) + reportProblem = buildReportProblem(presentation, invalid.toString) resp <- MessagingService - .send(reportproblem.toMessage) + .send(reportProblem.toMessage) .provideSomeLayer(didCommAgent) _ <- ZIO.log(s"CredentialsClaimsValidationResult: ${invalid.toString}") } yield () @@ -1263,19 +1284,13 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { .provideSomeLayer(ZLayer.succeed(walletAccessContext)) @@ presReceivedToProcessedAspect) .flatMapError(e => for { - didCommAgent <- buildDIDCommAgent(presentation.from).provideSomeLayer( + didCommAgent <- buildDIDCommAgent(presentation.to).provideSomeLayer( ZLayer.succeed(walletAccessContext) ) - reportproblem = ReportProblem.build( - fromDID = presentation.to, - toDID = presentation.from, - pthid = presentation.thid.getOrElse(presentation.id), - code = ProblemCode("e.p.presentation-verification-failed"), - comment = Some(e.toString) - ) + reportProblem = buildReportProblem(presentation, e.toString) _ <- MessagingService - .send(reportproblem.toMessage) + .send(reportProblem.toMessage) .provideSomeLayer(didCommAgent) _ <- ZIO.log(s"CredentialsClaimsValidationResult: ${e.toString}") } yield () @@ -1286,12 +1301,4 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { } } } - -// val syncDIDPublicationStateFromDlt: ZIO[WalletAccessContext & ManagedDIDService, GetManagedDIDError, Unit] = -// for { -// managedDidService <- ZIO.service[ManagedDIDService] -// _ <- managedDidService.syncManagedDIDState -// _ <- managedDidService.syncUnconfirmedUpdateOperations -// } yield () - } diff --git a/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala b/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala index 4c6867e52a..22e64637ef 100644 --- a/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala +++ b/mercury/protocol-report-problem/src/main/scala/org/hyperledger/identus/mercury/protocol/reportproblem/v2/ReportProblem.scala @@ -50,7 +50,7 @@ object ReportProblem { given Encoder[ReportProblem] = deriveEncoder[ReportProblem] given Decoder[ReportProblem] = deriveDecoder[ReportProblem] - def readFromMessage(message: Message): ReportProblem = + def fromMessage(message: Message): ReportProblem = val body = message.body.asJson.as[ReportProblem.Body].toOption.get // TODO get ReportProblem( id = message.id, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala index 4f4a1d7eef..4ea28a3359 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala @@ -198,6 +198,12 @@ object PresentationError { msg ) + final case class PresentationValidationError(msg: String) + extends PresentationError( + StatusCode.BadRequest, + msg + ) + final case class PresentationReceivedError(msg: String) extends PresentationError( StatusCode.InternalServerError, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala index bdd741e155..8fdcc7cd57 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala @@ -26,6 +26,7 @@ import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, Cre import org.hyperledger.identus.pollux.prex.{ClaimFormat, Jwt, PresentationDefinition} import org.hyperledger.identus.pollux.sdjwt.* import org.hyperledger.identus.pollux.vc.jwt.{Issuer as JwtIssuer, *} +import org.hyperledger.identus.pollux.vc.jwt.PresentationPayload.Implicits.* import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair} import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.messaging.{Producer, WalletIdAndRecordId} @@ -1505,7 +1506,7 @@ class CredentialServiceImpl( ZIO.fail(CredentialRequestValidationFailed(s"JWT presentation verification failed: $error")) jwtPresentation <- ZIO - .fromTry(JwtPresentation.decodeJwt(jwt)) + .fromTry(JwtPresentation.decodeJwt[JwtPresentationPayload](jwt)) .mapError(t => CredentialRequestValidationFailed(s"JWT presentation decoding failed: ${t.getMessage}")) } yield jwtPresentation } diff --git a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala index fe167a1122..cf816034a5 100644 --- a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala +++ b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationSubmissionVerification.scala @@ -13,7 +13,7 @@ import org.hyperledger.identus.pollux.prex.PresentationSubmissionError.{ JsonPathNotFound, SubmissionNotSatisfyInputDescriptors } -import org.hyperledger.identus.pollux.vc.jwt.{JWT, JwtCredential, JwtPresentation} +import org.hyperledger.identus.pollux.vc.jwt.{JWT, JwtCredential, JwtPresentation, JwtPresentationPayload} import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* import org.hyperledger.identus.pollux.vc.jwt.PresentationPayload.Implicits.* import org.hyperledger.identus.shared.json.{JsonInterop, JsonPath, JsonPathError, JsonSchemaValidatorImpl} @@ -220,7 +220,7 @@ object PresentationSubmissionVerification { .map(JWT(_)) .mapError(_ => InvalidDataTypeForClaimFormat(format, path, "string")) payload <- ZIO - .fromTry(JwtPresentation.decodeJwt(jwt)) + .fromTry(JwtPresentation.decodeJwt[JwtPresentationPayload](jwt)) .mapError(e => ClaimDecodeFailure(format, path, e.getMessage())) _ <- formatVerification(jwt) .mapError(errors => ClaimFormatVerificationFailure(format, path, errors.mkString)) diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/CredentialSchemaAndTrustedIssuersConstraint.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/CredentialSchemaAndTrustedIssuersConstraint.scala new file mode 100644 index 0000000000..0f8f0e68e0 --- /dev/null +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/CredentialSchemaAndTrustedIssuersConstraint.scala @@ -0,0 +1,6 @@ +package org.hyperledger.identus.pollux.vc.jwt + +case class CredentialSchemaAndTrustedIssuersConstraint( + schemaId: String, + trustedIssuers: Option[Seq[String]] +) diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala index 9cada23670..ffe96ca470 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala @@ -337,6 +337,12 @@ object JwtPresentation { .flatMap(decode[JwtPresentationPayload](_).toTry) } + def decodeJwt[A](jwt: JWT)(using decoder: io.circe.Decoder[A]): Try[A] = { + JwtCirce + .decodeRaw(jwt.value, options = JwtOptions(signature = false, expiration = false, notBefore = false)) + .flatMap(decode[A](_).toTry) + } + def decodeJwt(jwt: JWT, publicKey: PublicKey): Try[JwtPresentationPayload] = { JwtCirce .decodeRaw(jwt.value, publicKey, JwtOptions(expiration = false, notBefore = false)) @@ -370,7 +376,7 @@ object JwtPresentation { )(didResolver: DidResolver, uriResolver: UriResolver)(implicit clock: Clock ): IO[List[String], Validation[String, Unit]] = { - val validateJwtPresentation = Validation.fromTry(decodeJwt(jwt)).mapError(_.toString) + val validateJwtPresentation = Validation.fromTry(decodeJwt[JwtPresentationPayload](jwt)).mapError(_.toString) val credentialValidationZIO = ValidationUtils.foreach( @@ -405,12 +411,94 @@ object JwtPresentation { domain: String, challenge: String ): Validation[String, Unit] = { - val validateJwtPresentation = Validation.fromTry(decodeJwt(jwt)).mapError(_.toString) + val validateJwtPresentation = Validation.fromTry(decodeJwt[JwtPresentationPayload](jwt)).mapError(_.toString) for { decodeJwtPresentation <- validateJwtPresentation - aud <- validateAudience(decodeJwtPresentation, domain) + aud <- validateAudience(decodeJwtPresentation, Some(domain)) result <- validateNonce(decodeJwtPresentation, Some(challenge)) } yield result + } + + def validatePresentation( + jwt: JWT, + domain: Option[String], + challenge: Option[String], + schemaIdAndTrustedIssuers: Seq[CredentialSchemaAndTrustedIssuersConstraint] + ): Validation[String, Unit] = { + val validateJwtPresentation = Validation.fromTry(decodeJwt[JwtPresentationPayload](jwt)).mapError(_.toString) + for { + decodeJwtPresentation <- validateJwtPresentation + aud <- validateAudience(decodeJwtPresentation, domain) + nonce <- validateNonce(decodeJwtPresentation, challenge) + result <- validateSchemaIdAndTrustedIssuers(decodeJwtPresentation, schemaIdAndTrustedIssuers) + } yield { + result + } + } + + def validateSchemaIdAndTrustedIssuers( + decodedJwtPresentation: JwtPresentationPayload, + schemaIdAndTrustedIssuers: Seq[CredentialSchemaAndTrustedIssuersConstraint] + ): Validation[String, Unit] = { + import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* + + val vcList = decodedJwtPresentation.vp.verifiableCredential + val expectedSchemaIds = schemaIdAndTrustedIssuers.map(_.schemaId) + val trustedIssuers = schemaIdAndTrustedIssuers.flatMap(_.trustedIssuers).flatten + ZValidation + .validateAll( + vcList.map { + case (w3cVerifiableCredentialPayload: W3cVerifiableCredentialPayload) => + val credentialSchemas = w3cVerifiableCredentialPayload.payload.maybeCredentialSchema + val issuer = w3cVerifiableCredentialPayload.payload.issuer + for { + s <- validateSchemaIds(credentialSchemas, expectedSchemaIds) + i <- validateIsTrustedIssuer(issuer, trustedIssuers) + } yield i + + case (jwtVerifiableCredentialPayload: JwtVerifiableCredentialPayload) => + for { + jwtCredentialPayload <- Validation + .fromTry(decodeJwt[JwtCredentialPayload](jwtVerifiableCredentialPayload.jwt)) + .mapError(_.toString) + issuer = jwtCredentialPayload.issuer + credentialSchemas = jwtCredentialPayload.maybeCredentialSchema + s <- validateSchemaIds(credentialSchemas, expectedSchemaIds) + i <- validateIsTrustedIssuer(issuer, trustedIssuers) + } yield i + } + ) + .map(_ => ()) + } + def validateSchemaIds( + credentialSchemas: Option[CredentialSchema | List[CredentialSchema]], + expectedSchemaIds: Seq[String] + ): Validation[String, Unit] = { + if (expectedSchemaIds.nonEmpty) { + val isValidSchema = credentialSchemas match { + case Some(schema: CredentialSchema) => expectedSchemaIds.contains(schema.id) + case Some(schemaList: List[CredentialSchema]) => expectedSchemaIds.intersect(schemaList.map(_.id)).nonEmpty + case _ => false + } + if (!isValidSchema) { + Validation.fail(s"SchemaId expected =$expectedSchemaIds actual found =$credentialSchemas") + } else Validation.unit + } else Validation.unit + + } + + def validateIsTrustedIssuer( + credentialIssuer: String | CredentialIssuer, + trustedIssuers: Seq[String] + ): Validation[String, Unit] = { + if (trustedIssuers.nonEmpty) { + val isValidIssuer = credentialIssuer match + case issuer: String => trustedIssuers.contains(issuer) + case issuer: CredentialIssuer => trustedIssuers.contains(issuer.id) + if (!isValidIssuer) { + Validation.fail(s"TrustedIssuers = ${trustedIssuers.mkString(",")} actual issuer = $credentialIssuer") + } else Validation.unit + } else Validation.unit } @@ -424,19 +512,15 @@ object JwtPresentation { } def validateAudience( decodedJwtPresentation: JwtPresentationPayload, - domain: String + domain: Option[String] ): Validation[String, Unit] = { - if (!decodedJwtPresentation.aud.contains(domain)) { + if (!domain.forall(domain => decodedJwtPresentation.aud.contains(domain))) { Validation.fail(s"domain/Audience dont match doamin=$domain, exp=${decodedJwtPresentation.aud}") } else Validation.unit } def verifyHolderBinding(jwt: JWT): Validation[String, Unit] = { import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* - val decodeJWT = (jwt: JWT) => - Validation - .fromTry(JwtCirce.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) - .mapError(_.getMessage) def validateCredentialSubjectId( vcList: IndexedSeq[VerifiableCredentialPayload], @@ -459,10 +543,9 @@ object JwtPresentation { case (jwtVerifiableCredentialPayload: JwtVerifiableCredentialPayload) => for { - jwtCredentialDecoded <- decodeJWT(jwtVerifiableCredentialPayload.jwt) jwtCredentialPayload <- Validation - .fromEither(decode[JwtCredentialPayload](jwtCredentialDecoded)) - .mapError(_.getMessage) + .fromTry(decodeJwt[JwtCredentialPayload](jwtVerifiableCredentialPayload.jwt)) + .mapError(_.toString) mayBeSubjectDid = jwtCredentialPayload.maybeSub x <- if (mayBeSubjectDid.contains(iss)) { @@ -477,20 +560,15 @@ object JwtPresentation { .map(_ => ()) } for { - decodedJWT <- decodeJWT(jwt) - jwtPresentationPayload <- Validation.fromEither(decode[JwtPresentationPayload](decodedJWT)).mapError(_.getMessage) + jwtPresentationPayload <- Validation + .fromTry(decodeJwt[JwtPresentationPayload](jwt)) + .mapError(_.toString) result <- validateCredentialSubjectId(jwtPresentationPayload.vp.verifiableCredential, jwtPresentationPayload.iss) } yield result } def verifyDates(jwt: JWT, leeway: TemporalAmount)(implicit clock: Clock): Validation[String, Unit] = { val now = clock.instant() - - val decodeJWT = - Validation - .fromTry(JwtCirce.decodeRaw(jwt.value, options = JwtOptions(false, false, false))) - .mapError(_.getMessage) - def validateNbfNotAfterExp(maybeNbf: Option[Instant], maybeExp: Option[Instant]): Validation[String, Unit] = { val maybeResult = for { @@ -525,8 +603,9 @@ object JwtPresentation { } for { - decodedJWT <- decodeJWT - jwtCredentialPayload <- Validation.fromEither(decode[JwtPresentationPayload](decodedJWT)).mapError(_.getMessage) + jwtCredentialPayload <- Validation + .fromTry(decodeJwt[JwtPresentationPayload](jwt)) + .mapError(_.toString) maybeNbf = jwtCredentialPayload.maybeNbf maybeExp = jwtCredentialPayload.maybeExp result <- Validation.validateWith( diff --git a/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JwtPresentationTest.scala b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JwtPresentationTest.scala new file mode 100644 index 0000000000..7122818572 --- /dev/null +++ b/pollux/vc-jwt/src/test/scala/org/hyperledger/identus/pollux/vc/jwt/JwtPresentationTest.scala @@ -0,0 +1,99 @@ +package org.hyperledger.identus.pollux.vc.jwt + +import zio.test.* +import zio.test.ZIOSpecDefault + +object JwtPresentationTest extends ZIOSpecDefault { + val jwt = JWT( + "eyJraWQiOiJteS1hdXRoLWtleSIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2SyJ9.eyJpc3MiOiJkaWQ6cHJpc206YjgwZjAxMTZhYWY2OTI5MGRkMzJiZDE0OTNmN2IxYWJhMDM5OTYyM2JkNDk5Mzk3NTRjNThhNGNmZTU4M2QwYzpDcGNDQ3BRQ0VqOEtDMjE1TFdGMWRHZ3RhMlY1RUFSS0xnb0pjMlZqY0RJMU5tc3hFaUVDQnViQkpoNDhRNjhqOTJZS3NjMVFqQ0prOHFvRXBMamRoejNRRVFzRWpWRVNTZ29XYlhrdGEyVjVMV0Z6YzJWeWRHbHZiazFsZEdodlpCQUNTaTRLQ1hObFkzQXlOVFpyTVJJaEFoTENSMkhTa3NOWUh2Y0dCUWZzQVZrdDNFa1pMSVpMVEhYcFc4ckJRRHI5RWpzS0IyMWhjM1JsY2pBUUFVb3VDZ2x6WldOd01qVTJhekVTSVFMRC10d3c1SklVbzA2dXQ5MDQwaTZ5dTdhQUhMcWdxajdUcHBZNlUtQzhrQnBJQ2c1aFoyVnVkQzFpWVhObExYVnliQklRVEdsdWEyVmtVbVZ6YjNWeVkyVldNUm9rYUhSMGNEb3ZMekU1TWk0eE5qZ3VNUzQ0TmpvNU1EQXdMMk5zYjNWa0xXRm5aVzUwIiwiYXVkIjoiaHR0cHM6XC9cL3ByaXNtLXZlcmlmaWVyLmNvbSIsInZwIjp7InR5cGUiOlsiVmVyaWZpYWJsZVByZXNlbnRhdGlvbiJdLCJAY29udGV4dCI6WyJodHRwczpcL1wvd3d3LnczLm9yZ1wvMjAxOFwvcHJlc2VudGF0aW9uc1wvdjEiXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKRlpFUlRRU0o5LmV5SnBjM01pT2lKa2FXUTZjSEpwYzIwNk1HWmxaVFF4Wm1Sa05qZ3dOemMyTTJOall6VXpPV015T1RnME1XRTBNVGhqTnpaaE9EbGlNMlk0WTJRMlpHRTRPV1JsTW1FelpERmhNRFExTnpGbU5UcERjSE5EUTNCblEwVnJXVXRHVnpFMVRGZDBiR1ZUTVdoa1dGSnZXbGMxTUdGWFRtaGtSMngyWW1oQlJWTnBjMHRDTUZaclRXcFZNVTFVYTFOSlJIVjVSVlF4YUVOQmRVTjJhek5QYm05bmJETTRZMDFwU201NFgycHJURmxUVG5kWFRVbEJiblJzVEVWclkwdEdiVEUxVEZkMGJHVlRNV2hqTTA1c1kyNVNjR0l5TlU1YVdGSnZZakpSVVVGcmIzSkRaMlJHV2tSSk1VNVVSVFZGYVVOYVFWSkpibE5rVWtwRVN5MUZUSEp4VjI5a01YZE9UMGhvZFhSM1dsSkVTMG90YnpWQlpraHlZbkpTU1RkRFoyUjBXVmhPTUZwWVNYZEZRVVpMVEdkdlNtTXlWbXBqUkVreFRtMXplRVZwUlVSSE1XMXpkV056VlhOa1VVOHRibGx3Y2xGWU5HOUJXRnBJVnpKak9GQlNWM3BTWkZOMGF6WXdPRFZKWVZOQmIwOVpWMlJzWW01UmRGbHRSbnBhVXpFeFkyMTNVMFZGZUhCaWJYUnNXa1pLYkdNeU9URmpiVTVzVm1wRllVcEhhREJrU0VFMlRIazRlRTlVU1hWTlZGazBUR3BGZFU5RVdUWlBSRUYzVFVNNWFtSkhPVEZhUXpGb1dqSldkV1JCSWl3aWMzVmlJam9pWkdsa09uQnlhWE50T21JNE1HWXdNVEUyWVdGbU5qa3lPVEJrWkRNeVltUXhORGt6WmpkaU1XRmlZVEF6T1RrMk1qTmlaRFE1T1RNNU56VTBZelU0WVRSalptVTFPRE5rTUdNNlEzQmpRME53VVVORmFqaExRekl4TlV4WFJqRmtSMmQwWVRKV05VVkJVa3RNWjI5S1l6SldhbU5FU1RGT2JYTjRSV2xGUTBKMVlrSkthRFE0VVRZNGFqa3lXVXR6WXpGUmFrTkthemh4YjBWd1RHcGthSG96VVVWUmMwVnFWa1ZUVTJkdlYySllhM1JoTWxZMVRGZEdlbU15Vm5sa1IyeDJZbXN4YkdSSGFIWmFRa0ZEVTJrMFMwTllUbXhaTTBGNVRsUmFjazFTU1doQmFFeERVakpJVTJ0elRsbElkbU5IUWxGbWMwRldhM1F6Uld0YVRFbGFURlJJV0hCWE9ISkNVVVJ5T1VWcWMwdENNakZvWXpOU2JHTnFRVkZCVlc5MVEyZHNlbHBYVG5kTmFsVXlZWHBGVTBsUlRFUXRkSGQzTlVwSlZXOHdOblYwT1RBME1HazJlWFUzWVVGSVRIRm5jV28zVkhCd1dUWlZMVU00YTBKd1NVTm5OV2hhTWxaMVpFTXhhVmxZVG14TVdGWjVZa0pKVVZSSGJIVmhNbFpyVlcxV2VtSXpWbmxaTWxaWFRWSnZhMkZJVWpCalJHOTJUSHBGTlUxcE5IaE9hbWQxVFZNME5FNXFielZOUkVGM1RESk9jMkl6Vm10TVYwWnVXbGMxTUNJc0ltNWlaaUk2TVRjek1qQXhOVEl6TVN3aVpYaHdJam94TnpNeU1ERTRPRE14TENKMll5STZleUpqY21Wa1pXNTBhV0ZzVTJOb1pXMWhJanBiZXlKcFpDSTZJbWgwZEhBNlhDOWNMekU1TWk0eE5qZ3VNUzQ0TmpvNE1EQXdYQzlqYkc5MVpDMWhaMlZ1ZEZ3dmMyTm9aVzFoTFhKbFoybHpkSEo1WEM5elkyaGxiV0Z6WEM4Mk16TmhOamhtTnkwMFpUZGlMVE13TnpNdFlUbGhNQzA0WVdVNU5qUmtZVFU1TmpjaUxDSjBlWEJsSWpvaVEzSmxaR1Z1ZEdsaGJGTmphR1Z0WVRJd01qSWlmVjBzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltVnRZV2xzUVdSa2NtVnpjeUk2SW1Gc2FXTmxRSGR2Ym1SbGNteGhibVF1WTI5dElpd2laSEpwZG1sdVowTnNZWE56SWpvekxDSm1ZVzFwYkhsT1lXMWxJam9pVjI5dVpHVnliR0Z1WkNJc0ltZHBkbVZ1VG1GdFpTSTZJa0ZzYVdObElpd2laSEpwZG1sdVoweHBZMlZ1YzJWSlJDSTZJakV5TXpRMUlpd2lhV1FpT2lKa2FXUTZjSEpwYzIwNllqZ3daakF4TVRaaFlXWTJPVEk1TUdSa016SmlaREUwT1RObU4ySXhZV0poTURNNU9UWXlNMkprTkRrNU16azNOVFJqTlRoaE5HTm1aVFU0TTJRd1l6cERjR05EUTNCUlEwVnFPRXRETWpFMVRGZEdNV1JIWjNSaE1sWTFSVUZTUzB4bmIwcGpNbFpxWTBSSk1VNXRjM2hGYVVWRFFuVmlRa3BvTkRoUk5qaHFPVEpaUzNOak1WRnFRMHByT0hGdlJYQk1hbVJvZWpOUlJWRnpSV3BXUlZOVFoyOVhZbGhyZEdFeVZqVk1WMFo2WXpKV2VXUkhiSFppYXpGc1pFZG9kbHBDUVVOVGFUUkxRMWhPYkZrelFYbE9WRnB5VFZKSmFFRm9URU5TTWtoVGEzTk9XVWgyWTBkQ1VXWnpRVlpyZERORmExcE1TVnBNVkVoWWNGYzRja0pSUkhJNVJXcHpTMEl5TVdoak0xSnNZMnBCVVVGVmIzVkRaMng2V2xkT2QwMXFWVEpoZWtWVFNWRk1SQzEwZDNjMVNrbFZiekEyZFhRNU1EUXdhVFo1ZFRkaFFVaE1jV2R4YWpkVWNIQlpObFV0UXpoclFuQkpRMmMxYUZveVZuVmtRekZwV1ZoT2JFeFlWbmxpUWtsUlZFZHNkV0V5Vm10VmJWWjZZak5XZVZreVZsZE5VbTlyWVVoU01HTkViM1pNZWtVMVRXazBlRTVxWjNWTlV6UTBUbXB2TlUxRVFYZE1NazV6WWpOV2EweFhSbTVhVnpVd0lpd2laR0YwWlU5bVNYTnpkV0Z1WTJVaU9pSXlNREl3TFRFeExURXpWREl3T2pJd09qTTVLekF3T2pBd0luMHNJblI1Y0dVaU9sc2lWbVZ5YVdacFlXSnNaVU55WldSbGJuUnBZV3dpWFN3aVFHTnZiblJsZUhRaU9sc2lhSFIwY0hNNlhDOWNMM2QzZHk1M015NXZjbWRjTHpJd01UaGNMMk55WldSbGJuUnBZV3h6WEM5Mk1TSmRMQ0pwYzNOMVpYSWlPbnNpYVdRaU9pSmthV1E2Y0hKcGMyMDZNR1psWlRReFptUmtOamd3TnpjMk0yTmpZelV6T1dNeU9UZzBNV0UwTVRoak56WmhPRGxpTTJZNFkyUTJaR0U0T1dSbE1tRXpaREZoTURRMU56Rm1OVHBEY0hORFEzQm5RMFZyV1V0R1Z6RTFURmQwYkdWVE1XaGtXRkp2V2xjMU1HRlhUbWhrUjJ4MlltaEJSVk5wYzB0Q01GWnJUV3BWTVUxVWExTkpSSFY1UlZReGFFTkJkVU4yYXpOUGJtOW5iRE00WTAxcFNtNTRYMnByVEZsVFRuZFhUVWxCYm5Sc1RFVnJZMHRHYlRFMVRGZDBiR1ZUTVdoak0wNXNZMjVTY0dJeU5VNWFXRkp2WWpKUlVVRnJiM0pEWjJSR1drUkpNVTVVUlRWRmFVTmFRVkpKYmxOa1VrcEVTeTFGVEhKeFYyOWtNWGRPVDBob2RYUjNXbEpFUzBvdGJ6VkJaa2h5WW5KU1NUZERaMlIwV1ZoT01GcFlTWGRGUVVaTFRHZHZTbU15Vm1walJFa3hUbTF6ZUVWcFJVUkhNVzF6ZFdOelZYTmtVVTh0Ymxsd2NsRllORzlCV0ZwSVZ6SmpPRkJTVjNwU1pGTjBhell3T0RWSllWTkJiMDlaVjJSc1ltNVJkRmx0Um5wYVV6RXhZMjEzVTBWRmVIQmliWFJzV2taS2JHTXlPVEZqYlU1c1ZtcEZZVXBIYURCa1NFRTJUSGs0ZUU5VVNYVk5WRmswVEdwRmRVOUVXVFpQUkVGM1RVTTVhbUpIT1RGYVF6Rm9XakpXZFdSQklpd2lkSGx3WlNJNklsQnliMlpwYkdVaWZTd2lZM0psWkdWdWRHbGhiRk4wWVhSMWN5STZleUp6ZEdGMGRYTlFkWEp3YjNObElqb2lVbVYyYjJOaGRHbHZiaUlzSW5OMFlYUjFjMHhwYzNSSmJtUmxlQ0k2TVN3aWFXUWlPaUpvZEhSd09sd3ZYQzh4T1RJdU1UWTRMakV1T0RZNk9EQXdNRnd2WTJ4dmRXUXRZV2RsYm5SY0wyTnlaV1JsYm5ScFlXd3RjM1JoZEhWelhDODBaRGN4TmpZM01pMDFNekpqTFRRM056Y3RZVEJoTmkxbU56azJNR1F3TlRKbFpHVWpNU0lzSW5SNWNHVWlPaUpUZEdGMGRYTk1hWE4wTWpBeU1VVnVkSEo1SWl3aWMzUmhkSFZ6VEdsemRFTnlaV1JsYm5ScFlXd2lPaUpvZEhSd09sd3ZYQzh4T1RJdU1UWTRMakV1T0RZNk9EQXdNRnd2WTJ4dmRXUXRZV2RsYm5SY0wyTnlaV1JsYm5ScFlXd3RjM1JoZEhWelhDODBaRGN4TmpZM01pMDFNekpqTFRRM056Y3RZVEJoTmkxbU56azJNR1F3TlRKbFpHVWlmWDE5LmpiRE02NHQ1N3JoTXktNEt5ZnlsR3FCZGdzMUtJbXZpa0QzblFuVFRPbF9YcXV6UThTWGpZVEYyTWREbXJGRzAtSUk5NGo4ZmlUS0xYcWRpQ1NXNEN3Il19LCJub25jZSI6IjExYzkxNDkzLTAxYjMtNGM0ZC1hYzM2LWIzMzZiYWI1YmRkZiJ9.d5bzOLV-kQZvCceoFppqlPRG7aK3mo9sZVCj5_sPqIMvOqzAbxTOPyfI459GFKpeF8ApsNwJyx9jED_cRaqqiQ" + ) + val domain = Some("https://prism-verifier.com") + val challenge = Some("11c91493-01b3-4c4d-ac36-b336bab5bddf") + val schemaIdAndTrustedIssuers = Seq( + CredentialSchemaAndTrustedIssuersConstraint( + schemaId = "http://192.168.1.86:8000/cloud-agent/schema-registry/schemas/633a68f7-4e7b-3073-a9a0-8ae964da5967", + trustedIssuers = Some( + Seq( + "did:prism:0fee41fdd6807763ccc539c29841a418c76a89b3f8cd6da89de2a3d1a04571f5:CpsCCpgCEkYKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESisKB0VkMjU1MTkSIDuyET1hCAuCvk3Onogl38cMiJnx_jkLYSNwWMIAntlLEkcKFm15LWtleS1hc3NlcnRpb25NZXRob2QQAkorCgdFZDI1NTE5EiCZARInSdRJDK-ELrqWod1wNOHhutwZRDKJ-o5AfHrbrRI7CgdtYXN0ZXIwEAFKLgoJc2VjcDI1NmsxEiEDG1msucsUsdQO-nYprQX4oAXZHW2c8PRWzRdStk6085IaSAoOYWdlbnQtYmFzZS11cmwSEExpbmtlZFJlc291cmNlVjEaJGh0dHA6Ly8xOTIuMTY4LjEuODY6ODAwMC9jbG91ZC1hZ2VudA" + ) + ) + ) + ) + override def spec = suite("JWTVerificationSpec")( + test("validate true when issuer is trusted") { + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => false, _ => true)) + }, + test("fail when issuer is not trusted") { + val trustedIssuer = "did:prism:issuer" + val schemaIdAndTrustedIssuers = Seq( + CredentialSchemaAndTrustedIssuersConstraint( + schemaId = + "http://192.168.1.86:8000/cloud-agent/schema-registry/schemas/633a68f7-4e7b-3073-a9a0-8ae964da5967", + trustedIssuers = Some( + Seq( + trustedIssuer + ) + ) + ) + ) + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains(trustedIssuer), _ => false)) + }, + test("fail when schema ID doesn't match") { + val expectedSchemaId = "http://192.168.1.86:8000/cloud-agent/schema-registry/schemas/schemaId" + val schemaIdAndTrustedIssuers = Seq( + CredentialSchemaAndTrustedIssuersConstraint( + schemaId = expectedSchemaId, + trustedIssuers = Some( + Seq( + "did:prism:0fee41fdd6807763ccc539c29841a418c76a89b3f8cd6da89de2a3d1a04571f5:CpsCCpgCEkYKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESisKB0VkMjU1MTkSIDuyET1hCAuCvk3Onogl38cMiJnx_jkLYSNwWMIAntlLEkcKFm15LWtleS1hc3NlcnRpb25NZXRob2QQAkorCgdFZDI1NTE5EiCZARInSdRJDK-ELrqWod1wNOHhutwZRDKJ-o5AfHrbrRI7CgdtYXN0ZXIwEAFKLgoJc2VjcDI1NmsxEiEDG1msucsUsdQO-nYprQX4oAXZHW2c8PRWzRdStk6085IaSAoOYWdlbnQtYmFzZS11cmwSEExpbmtlZFJlc291cmNlVjEaJGh0dHA6Ly8xOTIuMTY4LjEuODY6ODAwMC9jbG91ZC1hZ2VudA" + ) + ) + ) + ) + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains(expectedSchemaId), _ => false)) + }, + test("fail when domain validation fails") { + val domain = Some("domain") + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains("domain/Audience dont match"), _ => false)) + }, + test("fail when challenge validation fails") { + val challenge = Some("challenge") + val validation = JwtPresentation.validatePresentation( + jwt, + domain, + challenge, + schemaIdAndTrustedIssuers + ) + assertTrue(validation.fold(_ => true, _ => false)) + assertTrue(validation.fold(chunk => chunk.mkString.contains("Challenge/Nonce dont match"), _ => false)) + } + ) + +} diff --git a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt index 05a6bbf37c..b128ddce39 100644 --- a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt +++ b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt @@ -34,7 +34,37 @@ enum class CredentialSchema { "name" to "Name", "age" to 18, ) - }, ; + }, + EMPLOYEE_SCHEMA { + override val credentialSchemaType: String = + "https://w3c-ccg.github.io/vc-json-schemas/schema/2.0/schema.json" + override val schemaType: String = "https://json-schema.org/draft/2020-12/schema" + override val schema: JsonSchema = JsonSchema( + id = "https://example.com/employee-schema-1.0", + schema = schemaType, + description = "Employee schema", + type = "object", + properties = mutableMapOf( + "name" to JsonSchemaProperty(type = "string"), + "age" to JsonSchemaProperty(type = "integer"), + ), + required = listOf("name", "age"), + ) + override val credentialSchema: CredentialSchemaInput = CredentialSchemaInput( + author = "did:prism:agent", + name = UUID.randomUUID().toString(), + description = "Simple employee credentials schema", + type = credentialSchemaType, + schema = schema, + tags = listOf("employee", "employees"), + version = "1.0.0", + ) + override val claims: Map = linkedMapOf( + "name" to "Name", + "age" to 18, + ) + }, + ; abstract val credentialSchema: CredentialSchemaInput abstract val schema: JsonSchema diff --git a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt index ec7a49a0df..2547e1e123 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt @@ -90,12 +90,7 @@ class ConnectionLessSteps { challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf", domain = "https://example-verifier.com", ), - proofs = listOf( - ProofRequestAux( - schemaId = "https://schema.org/Person", - trustIssuers = listOf("did:web:atalaprism.io/users/testUser"), - ), - ), + proofs = emptyList(), ) verifier.attemptsTo( diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt index 78985e62b0..7e70708b41 100644 --- a/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt @@ -1,5 +1,6 @@ package steps.proofs +import common.CredentialSchema import interactions.* import io.cucumber.java.en.When import io.iohk.atala.automation.extensions.get @@ -15,6 +16,31 @@ class JwtProofSteps { @When("{actor} sends a request for jwt proof presentation to {actor}") fun verifierSendsARequestForJwtProofPresentationToHolder(verifier: Actor, holder: Actor) { val verifierConnectionToHolder = verifier.recall("connection-with-${holder.name}").connectionId + val presentationRequest = RequestPresentationInput( + connectionId = verifierConnectionToHolder, + options = Options( + challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf", + domain = "https://example-verifier.com", + ), + proofs = emptyList(), + ) + verifier.attemptsTo( + Post.to("/present-proof/presentations").body(presentationRequest), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + ) + val presentationStatus = SerenityRest.lastResponse().get() + verifier.remember("thid", presentationStatus.thid) + holder.remember("thid", presentationStatus.thid) + } + + @When("{actor} sends a request for jwt proof from trustedIssuer {actor} using {} schema presentation to {actor}") + fun verifierSendsARequestForJwtProofPresentationToHolderUsingSchemaFromTrustedIssuer(verifier: Actor, issuer: Actor, schema: CredentialSchema, holder: Actor) { + val verifierConnectionToHolder = verifier.recall("connection-with-${holder.name}").connectionId + val trustIssuer = issuer.recall("longFormDid") + val baseUrl = issuer.recall("baseUrl") + val schemaGuid = issuer.recall(schema.name)!! + val schemaId = "$baseUrl/schema-registry/schemas/$schemaGuid" + val presentationRequest = RequestPresentationInput( connectionId = verifierConnectionToHolder, options = Options( @@ -23,8 +49,8 @@ class JwtProofSteps { ), proofs = listOf( ProofRequestAux( - schemaId = "https://schema.org/Person", - trustIssuers = listOf("did:web:atalaprism.io/users/testUser"), + schemaId = schemaId, + trustIssuers = listOf(trustIssuer), ), ), ) diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature index 43612873d6..04e7773873 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature @@ -31,4 +31,22 @@ Feature: Present Proof Protocol And Holder accepts the OOB invitation request for JWT proof presentation from Verifier And Holder receives the presentation proof request And Holder makes the jwt presentation of the proof - Then Verifier has the proof verified \ No newline at end of file + Then Verifier has the proof verified + + + Scenario: Verifier request for jwt proof presentation to Holder from trusted issuer using specified schema + Given Verifier and Holder have an existing connection + And Holder has a jwt issued credential with STUDENT_SCHEMA schema from Issuer + When Verifier sends a request for jwt proof from trustedIssuer Issuer using STUDENT_SCHEMA schema presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Verifier has the proof verified + + Scenario: Verifier request for jwt proof presentation to Holder from trusted issuer using specified schema + Given Verifier and Holder have an existing connection + And Holder has a jwt issued credential with STUDENT_SCHEMA schema from Issuer + And Holder has a jwt issued credential with EMPLOYEE_SCHEMA schema from Issuer + When Verifier sends a request for jwt proof from trustedIssuer Issuer using STUDENT_SCHEMA schema presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Verifier sees the proof returned verification failed \ No newline at end of file diff --git a/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts b/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts index 2624de0713..2be18cce6f 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts @@ -24,14 +24,7 @@ export class ProofsService extends HttpService { "challenge": "11c91493-01b3-4c4d-ac36-b336bab5bddf", "domain": "https://example-verifier.com" }, - "proofs":[ - { - "schemaId": "https://schema.org/${vu.vu.idInInstance}-${vu.vu.idInTest}-${vu.vu.iterationInScenario}", - "trustIssuers": [ - "did:web:atalaprism.io/users/testUser" - ] - } - ] + "proofs":[] }` const res = this.post("present-proof/presentations", payload); try { From 8781d3dfce1efffbd3b89712b3961b035271c729 Mon Sep 17 00:00:00 2001 From: Allain Magyar Date: Wed, 20 Nov 2024 16:02:06 -0300 Subject: [PATCH 4/4] test: add new tests and refactoring (#1442) Signed-off-by: Hyperledger Bot Signed-off-by: Allain Magyar Co-authored-by: Hyperledger Bot --- .../test/kotlin/abilities/ListenToEvents.kt | 83 ++++++------ .../test/kotlin/common/DidDocumentTemplate.kt | 9 ++ .../src/test/kotlin/common/DidPurpose.kt | 41 ------ .../src/test/kotlin/common/DidType.kt | 50 +++++++ .../test/kotlin/steps/common/CommonSteps.kt | 38 +++--- .../steps/connection/ConnectionSteps.kt | 22 ++-- .../connectionless/ConnectionLessSteps.kt | 2 +- .../steps/credentials/CredentialSteps.kt | 3 - .../steps/credentials/JwtCredentialSteps.kt | 27 ++-- .../{PublishDidSteps.kt => CreateDidSteps.kt} | 122 ++++++++++-------- .../kotlin/steps/multitenancy/WalletsSteps.kt | 4 +- .../steps/oid4vci/IssueCredentialSteps.kt | 2 +- .../oid4vci/ManageCredentialConfigSteps.kt | 14 +- .../steps/schemas/CredentialSchemasSteps.kt | 4 +- .../steps/verification/VcVerificationSteps.kt | 4 +- .../features/connection/connection.feature | 12 +- .../credential/anoncred/issuance.feature | 4 +- .../credential/anoncred/present_proof.feature | 4 +- .../features/credential/jwt/issuance.feature | 70 +++++----- .../credential/jwt/present_proof.feature | 30 ++++- .../credential/jwt/revocation.feature | 5 +- .../credential/sdjwt/issuance.feature | 37 +++--- .../credential/sdjwt/present_proof.feature | 2 +- .../resources/features/did/create_did.feature | 6 +- .../features/did/deactivate_did.feature | 2 +- .../resources/features/did/update_did.feature | 4 +- .../features/multitenancy/wallets.feature | 4 +- .../features/oid4vci/issue_jwt.feature | 10 +- .../oid4vci/manage_credential_config.feature | 18 +-- .../features/oid4vci/manage_issuer.feature | 18 +-- .../schemas/credential_schemas.feature | 6 +- .../verificationapi/vc_verification.feature | 16 +-- 32 files changed, 355 insertions(+), 318 deletions(-) create mode 100644 tests/integration-tests/src/test/kotlin/common/DidDocumentTemplate.kt delete mode 100644 tests/integration-tests/src/test/kotlin/common/DidPurpose.kt create mode 100644 tests/integration-tests/src/test/kotlin/common/DidType.kt rename tests/integration-tests/src/test/kotlin/steps/did/{PublishDidSteps.kt => CreateDidSteps.kt} (63%) diff --git a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt index e2116d6793..3450e30dd5 100644 --- a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt +++ b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt @@ -3,26 +3,37 @@ package abilities import com.google.gson.GsonBuilder import common.TestConstants import io.iohk.atala.automation.restassured.CustomGsonObjectMapperFactory -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import models.* +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.engine.ApplicationEngine +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty +import io.ktor.server.request.receiveText +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import models.ConnectionEvent +import models.CredentialEvent +import models.DidEvent +import models.Event +import models.PresentationEvent +import models.PresentationStatusAdapter import net.serenitybdd.screenplay.Ability import net.serenitybdd.screenplay.Actor import net.serenitybdd.screenplay.HasTeardown import net.serenitybdd.screenplay.Question -import org.hyperledger.identus.client.models.* +import org.hyperledger.identus.client.models.Connection +import org.hyperledger.identus.client.models.IssueCredentialRecord import java.net.URL import java.time.OffsetDateTime open class ListenToEvents( private val url: URL, webhookPort: Int?, -) : Ability, HasTeardown { +) : Ability, + HasTeardown { private val server: ApplicationEngine private val gson = GsonBuilder() @@ -88,48 +99,36 @@ open class ListenToEvents( } companion object { - fun at(url: URL, webhookPort: Int?): ListenToEvents { - return ListenToEvents(url, webhookPort) - } + fun at(url: URL, webhookPort: Int?): ListenToEvents = ListenToEvents(url, webhookPort) - fun with(actor: Actor): ListenToEvents { - return actor.abilityTo(ListenToEvents::class.java) - } + fun with(actor: Actor): ListenToEvents = actor.abilityTo(ListenToEvents::class.java) - fun presentationProofStatus(actor: Actor): Question { - return Question.about("presentation status").answeredBy { - val proofEvent = with(actor).presentationEvents.lastOrNull { - it.data.thid == actor.recall("thid") - } - proofEvent?.data?.status + fun presentationProofStatus(actor: Actor): Question = Question.about("presentation status").answeredBy { + val proofEvent = with(actor).presentationEvents.lastOrNull { + it.data.thid == actor.recall("thid") } + proofEvent?.data?.status } - fun connectionState(actor: Actor): Question { - return Question.about("connection state").answeredBy { - val lastEvent = with(actor).connectionEvents.lastOrNull { - it.data.thid == actor.recall("connection").thid - } - lastEvent?.data?.state + fun connectionState(actor: Actor): Question = Question.about("connection state").answeredBy { + val lastEvent = with(actor).connectionEvents.lastOrNull { + it.data.thid == actor.recall("connection").thid } + lastEvent?.data?.state } - fun credentialState(actor: Actor): Question { - return Question.about("credential state").answeredBy { - val credentialEvent = ListenToEvents.with(actor).credentialEvents.lastOrNull { - it.data.thid == actor.recall("thid") - } - credentialEvent?.data?.protocolState + fun credentialState(actor: Actor): Question = Question.about("credential state").answeredBy { + val credentialEvent = ListenToEvents.with(actor).credentialEvents.lastOrNull { + it.data.thid == actor.recall("thid") } + credentialEvent?.data?.protocolState } - fun didStatus(actor: Actor): Question { - return Question.about("did status").answeredBy { - val didEvent = ListenToEvents.with(actor).didEvents.lastOrNull { - it.data.did == actor.recall("shortFormDid") - } - didEvent?.data?.status + fun didStatus(actor: Actor): Question = Question.about("did status").answeredBy { + val didEvent = ListenToEvents.with(actor).didEvents.lastOrNull { + it.data.did == actor.recall("shortFormDid") } + didEvent?.data?.status } } @@ -143,9 +142,7 @@ open class ListenToEvents( .start(wait = false) } - override fun toString(): String { - return "Listen HTTP port at $url" - } + override fun toString(): String = "Listen HTTP port at $url" override fun tearDown() { server.stop() diff --git a/tests/integration-tests/src/test/kotlin/common/DidDocumentTemplate.kt b/tests/integration-tests/src/test/kotlin/common/DidDocumentTemplate.kt new file mode 100644 index 0000000000..a82f4cbced --- /dev/null +++ b/tests/integration-tests/src/test/kotlin/common/DidDocumentTemplate.kt @@ -0,0 +1,9 @@ +package common + +import org.hyperledger.identus.client.models.ManagedDIDKeyTemplate +import org.hyperledger.identus.client.models.Service + +data class DidDocumentTemplate( + val publicKeys: MutableList, + val services: MutableList, +) diff --git a/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt b/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt deleted file mode 100644 index c4c30ca83c..0000000000 --- a/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt +++ /dev/null @@ -1,41 +0,0 @@ -package common - -import org.hyperledger.identus.client.models.* - -enum class DidPurpose { - CUSTOM { - override val publicKeys = mutableListOf() - override val services = mutableListOf() - }, - SD_JWT { - override val publicKeys = mutableListOf( - ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.ED25519), - ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.ED25519), - ) - override val services = mutableListOf() - }, - JWT { - override val publicKeys = mutableListOf( - ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), - ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), - ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), - ManagedDIDKeyTemplate("assertion-2", Purpose.ASSERTION_METHOD, Curve.ED25519), - ) - override val services = mutableListOf() - }, - OIDC_JWT { - override val publicKeys = mutableListOf( - ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), - ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), - ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), - ) - override val services = mutableListOf() - }, - ANONCRED { - override val publicKeys = mutableListOf() - override val services = mutableListOf() - }, ; - - abstract val publicKeys: MutableList - abstract val services: MutableList -} diff --git a/tests/integration-tests/src/test/kotlin/common/DidType.kt b/tests/integration-tests/src/test/kotlin/common/DidType.kt new file mode 100644 index 0000000000..38f2c2b792 --- /dev/null +++ b/tests/integration-tests/src/test/kotlin/common/DidType.kt @@ -0,0 +1,50 @@ +package common + +import org.hyperledger.identus.client.models.* + +enum class DidType { + CUSTOM { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf(), + services = mutableListOf(), + ) + }, + SD_JWT { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf( + ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.ED25519), + ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.ED25519), + ), + services = mutableListOf(), + ) + }, + JWT { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf( + ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), + ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), + ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), + ManagedDIDKeyTemplate("assertion-2", Purpose.ASSERTION_METHOD, Curve.ED25519), + ), + services = mutableListOf(), + ) + }, + OIDC_JWT { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf( + ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1), + ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519), + ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1), + ), + services = mutableListOf(), + ) + }, + ANONCRED { + override val documentTemplate get() = DidDocumentTemplate( + publicKeys = mutableListOf(), + services = mutableListOf(), + ) + }, ; + + abstract val documentTemplate: DidDocumentTemplate +} diff --git a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt index d5c8428b13..06aaab22bf 100644 --- a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt @@ -1,7 +1,7 @@ package steps.common import common.CredentialSchema -import common.DidPurpose +import common.DidType import interactions.Get import io.cucumber.java.en.Given import io.iohk.atala.automation.extensions.get @@ -13,7 +13,7 @@ import org.hyperledger.identus.client.models.Connection import org.hyperledger.identus.client.models.ConnectionsPage import steps.connection.ConnectionSteps import steps.credentials.* -import steps.did.PublishDidSteps +import steps.did.CreateDidSteps import steps.schemas.CredentialSchemasSteps class CommonSteps { @@ -21,21 +21,21 @@ class CommonSteps { fun holderHasIssuedJwtCredentialFromIssuer(holder: Actor, issuer: Actor) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.JWT) val jwtCredentialSteps = JwtCredentialSteps() val credentialSteps = CredentialSteps() jwtCredentialSteps.issuerOffersAJwtCredential(issuer, holder, "short") credentialSteps.holderReceivesCredentialOffer(holder) - jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder) + jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder, "auth-1") credentialSteps.issuerIssuesTheCredential(issuer) credentialSteps.holderReceivesTheIssuedCredential(holder) } - @Given("{actor} has a jwt issued credential with {} schema from {actor}") + @Given("{actor} has a jwt issued credential with '{}' schema from {actor}") fun holderHasIssuedJwtCredentialFromIssuerWithSchema( holder: Actor, schema: CredentialSchema, @@ -43,9 +43,9 @@ class CommonSteps { ) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.JWT) val schemaSteps = CredentialSchemasSteps() schemaSteps.agentHasAPublishedSchema(issuer, schema) @@ -54,7 +54,7 @@ class CommonSteps { val credentialSteps = CredentialSteps() jwtCredentialSteps.issuerOffersJwtCredentialToHolderUsingSchema(issuer, holder, "short", schema) credentialSteps.holderReceivesCredentialOffer(holder) - jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder) + jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder, "auth-1") credentialSteps.issuerIssuesTheCredential(issuer) credentialSteps.holderReceivesTheIssuedCredential(holder) } @@ -63,9 +63,9 @@ class CommonSteps { fun holderHasIssuedSdJwtCredentialFromIssuer(holder: Actor, issuer: Actor) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.SD_JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.SD_JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.SD_JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.SD_JWT) val sdJwtCredentialSteps = SdJwtCredentialSteps() val credentialSteps = CredentialSteps() @@ -80,9 +80,9 @@ class CommonSteps { fun holderHasIssuedSdJwtCredentialFromIssuerWithKeyBind(holder: Actor, issuer: Actor) { actorsHaveExistingConnection(issuer, holder) - val publishDidSteps = PublishDidSteps() - publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.SD_JWT) - publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.SD_JWT) + val createDidSteps = CreateDidSteps() + createDidSteps.agentHasAnUnpublishedDID(holder, DidType.SD_JWT) + createDidSteps.agentHasAPublishedDID(issuer, DidType.SD_JWT) val sdJwtCredentialSteps = SdJwtCredentialSteps() val credentialSteps = CredentialSteps() @@ -97,8 +97,6 @@ class CommonSteps { fun actorsHaveExistingConnection(inviter: Actor, invitee: Actor) { inviter.attemptsTo( Get.resource("/connections"), - ) - inviter.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), ) val inviterConnection = SerenityRest.lastResponse().get().contents!!.firstOrNull { @@ -109,8 +107,6 @@ class CommonSteps { if (inviterConnection != null) { invitee.attemptsTo( Get.resource("/connections"), - ) - invitee.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), ) inviteeConnection = SerenityRest.lastResponse().get().contents!!.firstOrNull { diff --git a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt index 17df7e4bf3..1c3e2e0e94 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt @@ -15,9 +15,12 @@ import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK import org.assertj.core.api.Assertions.assertThat import org.hamcrest.CoreMatchers -import org.hyperledger.identus.client.models.* +import org.hyperledger.identus.client.models.AcceptConnectionInvitationRequest +import org.hyperledger.identus.client.models.Connection import org.hyperledger.identus.client.models.Connection.State.CONNECTION_RESPONSE_RECEIVED import org.hyperledger.identus.client.models.Connection.State.CONNECTION_RESPONSE_SENT +import org.hyperledger.identus.client.models.Connection.State.INVITATION_GENERATED +import org.hyperledger.identus.client.models.CreateConnectionRequest class ConnectionSteps { @@ -29,12 +32,11 @@ class ConnectionSteps { inviter.attemptsTo( Post.to("/connections").body(CreateConnectionRequest(label = connectionLabel)), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), ) val connection = SerenityRest.lastResponse().get() - inviter.attemptsTo( - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), Ensure.that(connection.label!!).isEqualTo(connectionLabel), Ensure.that(connection.state).isEqualTo(Connection.State.INVITATION_GENERATED), Ensure.that(connection.role).isEqualTo(Connection.Role.INVITER), @@ -48,20 +50,14 @@ class ConnectionSteps { fun inviteeSendsAConnectionRequestToInviter(invitee: Actor, inviter: Actor) { // Bob accepts connection using achieved out-of-band invitation val inviterConnection = inviter.recall("connection") + val body = AcceptConnectionInvitationRequest(inviterConnection.invitation.invitationUrl.split("=")[1]) invitee.attemptsTo( - Post.to("/connection-invitations") - .with { - it.body( - AcceptConnectionInvitationRequest( - inviterConnection.invitation.invitationUrl.split("=")[1], - ), - ) - }, + Post.to("/connection-invitations").body(body), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), ) - val inviteeConnection = SerenityRest.lastResponse().get() + val inviteeConnection = SerenityRest.lastResponse().get() invitee.attemptsTo( - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), Ensure.that(inviteeConnection.invitation.from).isEqualTo(inviterConnection.invitation.from), Ensure.that(inviteeConnection.invitation.id).isEqualTo(inviterConnection.invitation.id), Ensure.that(inviteeConnection.invitation.invitationUrl) diff --git a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt index 2547e1e123..04d6772c38 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt @@ -14,7 +14,7 @@ import org.hyperledger.identus.client.models.* class ConnectionLessSteps { - @When("{actor} creates a {string} credential offer invitation with {string} form DID") + @When("{actor} creates a '{}' credential offer invitation with '{}' form DID") fun inviterGeneratesACredentialOfferInvitation(issuer: Actor, credentialFormat: String, didForm: String) { val claims = linkedMapOf( "firstName" to "Automation", diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt index 7c2251ec83..d3aac42ea4 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt @@ -48,9 +48,6 @@ class CredentialSteps { issuer.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), - ) - - issuer.attemptsTo( PollingWait.until( ListenToEvents.credentialState(issuer), equalTo(CREDENTIAL_SENT), diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt index 5ca7c1d96b..551662d448 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt @@ -62,7 +62,7 @@ class JwtCredentialSteps { holder.remember("thid", credentialRecord.thid) } - @When("{actor} offers a jwt credential to {actor} with {string} form DID") + @When("{actor} offers a jwt credential to {actor} with '{}' form DID") fun issuerOffersAJwtCredential(issuer: Actor, holder: Actor, format: String) { val claims = linkedMapOf( "firstName" to "FirstName", @@ -72,7 +72,7 @@ class JwtCredentialSteps { saveCredentialOffer(issuer, holder) } - @When("{actor} offers a jwt credential to {actor} with {string} form DID using issuingKid {string}") + @When("{actor} offers a jwt credential to {actor} with '{}' form DID using issuingKid '{}'") fun issuerOffersAJwtCredentialWithIssuingKeyId(issuer: Actor, holder: Actor, format: String, issuingKid: String?) { val claims = linkedMapOf( "firstName" to "FirstName", @@ -82,7 +82,7 @@ class JwtCredentialSteps { saveCredentialOffer(issuer, holder) } - @When("{actor} offers a jwt credential to {actor} with {} form using {} schema") + @When("{actor} offers a jwt credential to {actor} with '{}' form using '{}' schema") fun issuerOffersJwtCredentialToHolderUsingSchema( issuer: Actor, holder: Actor, @@ -95,7 +95,7 @@ class JwtCredentialSteps { saveCredentialOffer(issuer, holder) } - @When("{actor} offers a jwt credential to {actor} with {} form DID with wrong claims structure using {} schema") + @When("{actor} offers a jwt credential to {actor} with '{}' form DID with wrong claims structure using '{}' schema") fun issuerOffersJwtCredentialToHolderWithWrongClaimStructure( issuer: Actor, holder: Actor, @@ -110,22 +110,13 @@ class JwtCredentialSteps { sendCredentialOffer(issuer, holder, format, schemaGuid, claims, "assertion-1") } - @When("{actor} accepts jwt credential offer") - fun holderAcceptsJwtCredentialOfferForJwt(holder: Actor) { + @When("{actor} accepts jwt credential offer using '{}' key id") + fun holderAcceptsJwtCredentialOfferForJwt(holder: Actor, keyId: String) { val recordId = holder.recall("recordId") + val longFormDid = holder.recall("longFormDid") + val acceptRequest = AcceptCredentialOfferRequest(longFormDid, keyId) holder.attemptsTo( - Post.to("/issue-credentials/records/$recordId/accept-offer") - .body(AcceptCredentialOfferRequest(holder.recall("longFormDid"), holder.recall("kidSecp256K1"))), - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), - ) - } - - @When("{actor} accepts jwt credential offer with keyId {string}") - fun holderAcceptsJwtCredentialOfferForJwtWithKeyId(holder: Actor, keyId: String?) { - val recordId = holder.recall("recordId") - holder.attemptsTo( - Post.to("/issue-credentials/records/$recordId/accept-offer") - .body(AcceptCredentialOfferRequest(holder.recall("longFormDid"), keyId)), + Post.to("/issue-credentials/records/$recordId/accept-offer").body(acceptRequest), Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), ) } diff --git a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/CreateDidSteps.kt similarity index 63% rename from tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt rename to tests/integration-tests/src/test/kotlin/steps/did/CreateDidSteps.kt index 8bff650f21..78ea918abf 100644 --- a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/did/CreateDidSteps.kt @@ -1,11 +1,15 @@ package steps.did import abilities.ListenToEvents -import common.DidPurpose +import common.DidDocumentTemplate +import common.DidType +import common.DidType.CUSTOM import interactions.Get import interactions.Post import interactions.body -import io.cucumber.java.en.* +import io.cucumber.java.en.Given +import io.cucumber.java.en.Then +import io.cucumber.java.en.When import io.iohk.atala.automation.extensions.get import io.iohk.atala.automation.serenity.ensure.Ensure import io.iohk.atala.automation.serenity.interactions.PollingWait @@ -15,84 +19,64 @@ import org.apache.http.HttpStatus import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK import org.hamcrest.CoreMatchers.equalTo -import org.hyperledger.identus.client.models.* - -class PublishDidSteps { - - @Given("{actor} has a published DID for {}") - fun agentHasAPublishedDID(agent: Actor, didPurpose: DidPurpose) { - if (agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didPurpose)) { +import org.hyperledger.identus.client.models.CreateManagedDidRequest +import org.hyperledger.identus.client.models.CreateManagedDidRequestDocumentTemplate +import org.hyperledger.identus.client.models.Curve +import org.hyperledger.identus.client.models.DIDOperationResponse +import org.hyperledger.identus.client.models.DIDResolutionResult +import org.hyperledger.identus.client.models.ManagedDID +import org.hyperledger.identus.client.models.ManagedDIDKeyTemplate +import org.hyperledger.identus.client.models.Purpose + +class CreateDidSteps { + + @Given("{actor} has a published DID for '{}'") + fun agentHasAPublishedDID(agent: Actor, didType: DidType) { + if (agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didType)) { return } - agentHasAnUnpublishedDID(agent, didPurpose) + agentHasAnUnpublishedDID(agent, didType) hePublishesDidToLedger(agent) } - @Given("{actor} has an unpublished DID for {}") - fun agentHasAnUnpublishedDID(agent: Actor, didPurpose: DidPurpose) { + @Given("{actor} has an unpublished DID for '{}'") + fun agentHasAnUnpublishedDID(agent: Actor, didType: DidType) { if (agent.recallAll().containsKey("shortFormDid") || agent.recallAll().containsKey("longFormDid")) { // is not published and has the same purpose - if (!agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didPurpose)) { + if (!agent.recallAll().containsKey("hasPublishedDid") && actualDidHasSamePurpose(agent, didType)) { return } } - agentCreatesUnpublishedDid(agent, didPurpose) - } - - private fun actualDidHasSamePurpose(agent: Actor, didPurpose: DidPurpose): Boolean { - val actualPurpose: DidPurpose = agent.recall("didPurpose") ?: return false - return actualPurpose == didPurpose + agentCreatesUnpublishedDid(agent, didType) } - @Given("{actor} creates unpublished DID") + @Given("{actor} creates empty unpublished DID") fun agentCreatesEmptyUnpublishedDid(actor: Actor) { - agentCreatesUnpublishedDid(actor, DidPurpose.CUSTOM) + agentCreatesUnpublishedDid(actor, CUSTOM) } - @Given("{actor} creates unpublished DID for {}") - fun agentCreatesUnpublishedDid(actor: Actor, didPurpose: DidPurpose) { - val createDidRequest = CreateManagedDidRequest( - CreateManagedDidRequestDocumentTemplate(didPurpose.publicKeys, services = didPurpose.services), - ) - actor.attemptsTo( - Post.to("/did-registrar/dids").body(createDidRequest), - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), - ) - - val managedDid = SerenityRest.lastResponse().get() - - actor.attemptsTo( - Ensure.that(managedDid.longFormDid!!).isNotEmpty(), - Get.resource("/did-registrar/dids/${managedDid.longFormDid}"), - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), - ) - - val did = SerenityRest.lastResponse().get() - - actor.remember("longFormDid", managedDid.longFormDid) - actor.remember("kidSecp256K1", "auth-1") - actor.remember("kidEd25519", "auth-2") - actor.remember("shortFormDid", did.did) - actor.remember("didPurpose", didPurpose) - actor.forget("hasPublishedDid") + @Given("{actor} creates unpublished DID for '{}'") + fun agentCreatesUnpublishedDid(actor: Actor, didType: DidType) { + createDid(actor, didType, didType.documentTemplate) } @When("{actor} prepares a custom PRISM DID") fun actorPreparesCustomDid(actor: Actor) { - val customDid = DidPurpose.CUSTOM + val customDid = CUSTOM.documentTemplate actor.remember("customDid", customDid) } @When("{actor} adds a '{curve}' key for '{purpose}' purpose with '{}' name to the custom PRISM DID") fun actorAddsKeyToCustomDid(actor: Actor, curve: Curve, purpose: Purpose, name: String) { - val customDid = actor.recall("customDid") - customDid.publicKeys.add(ManagedDIDKeyTemplate(name, purpose, curve)) + val documentTemplate = actor.recall("customDid") + documentTemplate.publicKeys.add(ManagedDIDKeyTemplate(name, purpose, curve)) + actor.remember("customDid", documentTemplate) } @When("{actor} creates the custom PRISM DID") fun actorCreatesTheCustomPrismDid(actor: Actor) { - val customDid = actor.recall("customDid") - agentCreatesUnpublishedDid(actor, customDid) + val documentTemplate = actor.recall("customDid") + createDid(actor, CUSTOM, documentTemplate) } @When("{actor} publishes DID to ledger") @@ -131,4 +115,38 @@ class PublishDidSteps { Ensure.that(didResolutionResult.didDocumentMetadata.deactivated!!).isFalse(), ) } + + private fun actualDidHasSamePurpose(agent: Actor, didType: DidType): Boolean { + val actualPurpose: DidType = agent.recall("didPurpose") ?: return false + return actualPurpose == didType + } + + private fun createDid(actor: Actor, didType: DidType, documentTemplate: DidDocumentTemplate) { + val createDidRequest = CreateManagedDidRequest( + CreateManagedDidRequestDocumentTemplate( + publicKeys = documentTemplate.publicKeys, + services = documentTemplate.services, + ), + ) + + actor.attemptsTo( + Post.to("/did-registrar/dids").body(createDidRequest), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + ) + + val managedDid = SerenityRest.lastResponse().get() + + actor.attemptsTo( + Ensure.that(managedDid.longFormDid!!).isNotEmpty(), + Get.resource("/did-registrar/dids/${managedDid.longFormDid}"), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), + ) + + val did = SerenityRest.lastResponse().get() + + actor.remember("longFormDid", managedDid.longFormDid) + actor.remember("shortFormDid", did.did) + actor.remember("didPurpose", didType) + actor.forget("hasPublishedDid") + } } diff --git a/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt b/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt index c2eb65cebb..9fbf303e51 100644 --- a/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/multitenancy/WalletsSteps.kt @@ -41,7 +41,7 @@ class WalletsSteps { return SerenityRest.lastResponse().get() } - @When("{actor} creates new wallet with name {string}") + @When("{actor} creates new wallet with name '{}'") fun iCreateNewWalletWithName(acme: Actor, name: String) { val wallet = createNewWallet(acme, name) acme.attemptsTo( @@ -88,7 +88,7 @@ class WalletsSteps { ) } - @Then("{actor} should have a wallet with name {string}") + @Then("{actor} should have a wallet with name '{}'") fun iShouldHaveAWalletWithName(acme: Actor, name: String) { acme.attemptsTo( Get.resource("/wallets/${acme.recall("walletId")}") diff --git a/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt index 6a2e906361..e639fda97a 100644 --- a/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt @@ -28,7 +28,7 @@ import java.net.URI import java.net.URL class IssueCredentialSteps { - @When("{actor} creates an offer using {string} configuration with {string} form DID") + @When("{actor} creates an offer using '{}' configuration with '{}' form DID") fun issuerCreateCredentialOffer(issuer: Actor, configurationId: String, didForm: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") val claims = linkedMapOf( diff --git a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt index 8a53c311f9..7dbc0f41f6 100644 --- a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt @@ -22,7 +22,7 @@ import org.hyperledger.identus.client.models.IssuerMetadata import java.util.UUID class ManageCredentialConfigSteps { - @Given("{actor} has {string} credential configuration created from {}") + @Given("{actor} has '{}' credential configuration created from '{}'") fun issuerHasExistingCredentialConfig(issuer: Actor, configurationId: String, schema: CredentialSchema) { ManageIssuerSteps().issuerHasExistingCredentialIssuer(issuer) val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") @@ -34,7 +34,7 @@ class ManageCredentialConfigSteps { } } - @When("{actor} uses {} to create a credential configuration {string}") + @When("{actor} uses '{}' to create a credential configuration '{}'") fun issuerCreateCredentialConfiguration(issuer: Actor, schema: CredentialSchema, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") val schemaGuid = issuer.recall(schema.name) @@ -52,7 +52,7 @@ class ManageCredentialConfigSteps { ) } - @When("{actor} deletes {string} credential configuration") + @When("{actor} deletes '{}' credential configuration") fun issuerDeletesCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( @@ -61,7 +61,7 @@ class ManageCredentialConfigSteps { ) } - @When("{actor} deletes a non existent {} credential configuration") + @When("{actor} deletes a non existent '{}' credential configuration") fun issuerDeletesANonExistentCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( @@ -75,7 +75,7 @@ class ManageCredentialConfigSteps { issuer.remember("credentialConfiguration", credentialConfiguration) } - @When("{actor} uses {} issuer id for credential configuration") + @When("{actor} uses '{}' issuer id for credential configuration") fun issuerUsesIssuerId(issuer: Actor, issuerId: String) { if (issuerId == "existing") { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") @@ -129,7 +129,7 @@ class ManageCredentialConfigSteps { ) } - @Then("{actor} sees the {string} configuration on IssuerMetadata endpoint") + @Then("{actor} sees the '{}' configuration on IssuerMetadata endpoint") fun issuerSeesCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( @@ -143,7 +143,7 @@ class ManageCredentialConfigSteps { ) } - @Then("{actor} cannot see the {string} configuration on IssuerMetadata endpoint") + @Then("{actor} cannot see the '{}' configuration on IssuerMetadata endpoint") fun issuerCannotSeeCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( diff --git a/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt b/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt index ae14368831..37511cd35f 100644 --- a/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/schemas/CredentialSchemasSteps.kt @@ -16,7 +16,7 @@ import java.util.UUID class CredentialSchemasSteps { - @Given("{actor} has published {} schema") + @Given("{actor} has published '{}' schema") fun agentHasAPublishedSchema(agent: Actor, schema: CredentialSchema) { if (agent.recallAll().containsKey(schema.name)) { return @@ -24,7 +24,7 @@ class CredentialSchemasSteps { agentCreatesANewCredentialSchema(agent, schema) } - @When("{actor} creates a new credential {} schema") + @When("{actor} creates a new credential '{}' schema") fun agentCreatesANewCredentialSchema(actor: Actor, schema: CredentialSchema) { actor.attemptsTo( Post.to("/schema-registry/schemas").body( diff --git a/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt b/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt index 32f285a197..174aed265d 100644 --- a/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/verification/VcVerificationSteps.kt @@ -47,7 +47,7 @@ class VcVerificationSteps { holder.remember("issuerDid", "did:prism:issuer") } - @Given("{actor} has a {} problem in the Verifiable Credential") + @Given("{actor} has a '{}' problem in the Verifiable Credential") fun holderHasProblemInTheVerifiableCredential(holder: Actor, problem: JwtCredentialProblem) { val jwt = problem.jwt() holder.remember("jwt", jwt) @@ -64,7 +64,7 @@ class VcVerificationSteps { holder.remember("checks", checks) } - @Then("{actor} should see that verification has failed with {} problem") + @Then("{actor} should see that verification has failed with '{}' problem") fun holderShouldSeeThatVerificationHasFailedWithProblem(holder: Actor, problem: JwtCredentialProblem) { } diff --git a/tests/integration-tests/src/test/resources/features/connection/connection.feature b/tests/integration-tests/src/test/resources/features/connection/connection.feature index 329f9115bd..0b742d2fb7 100644 --- a/tests/integration-tests/src/test/resources/features/connection/connection.feature +++ b/tests/integration-tests/src/test/resources/features/connection/connection.feature @@ -1,9 +1,9 @@ @connection @create Feature: Agents connection -Scenario: Establish a connection between two agents - When Issuer generates a connection invitation to Holder - And Holder sends a connection request to Issuer - And Issuer receives the connection request and sends back the response - And Holder receives the connection response - Then Issuer and Holder have a connection + Scenario: Establish a connection between two agents + When Issuer generates a connection invitation to Holder + And Holder sends a connection request to Issuer + And Issuer receives the connection request and sends back the response + And Holder receives the connection response + Then Issuer and Holder have a connection diff --git a/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature index 712d6d7db6..3011d2cb2e 100644 --- a/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature @@ -3,8 +3,8 @@ Feature: Issue Anoncred credential Background: Given Issuer and Holder have an existing connection - And Issuer has a published DID for ANONCRED - And Holder has an unpublished DID for ANONCRED + And Issuer has a published DID for 'ANONCRED' + And Holder has an unpublished DID for 'ANONCRED' Scenario: Issuing anoncred with published PRISM DID Given Issuer has an anoncred schema definition diff --git a/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature index 8bc05395c0..539bc1f0f7 100644 --- a/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature @@ -4,8 +4,8 @@ Feature: Present Proof Protocol Scenario: Holder presents anoncreds credential proof to verifier Given Issuer and Holder have an existing connection And Verifier and Holder have an existing connection - And Issuer has a published DID for ANONCRED - And Holder has an unpublished DID for ANONCRED + And Issuer has a published DID for 'ANONCRED' + And Holder has an unpublished DID for 'ANONCRED' And Issuer has an anoncred schema definition And Issuer offers anoncred to Holder And Holder receives the credential offer diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature index 6258cf5fdb..5e15b98f8f 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature @@ -1,61 +1,73 @@ @jwt @issuance Feature: Issue JWT credential - - Scenario: Issuing jwt credential with published PRISM DID + Scenario Outline: Issuing jwt credential using assertion Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form DID + And Holder creates unpublished DID for 'JWT' + When Issuer prepares a custom PRISM DID + And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID + And Issuer creates the custom PRISM DID + And Issuer publishes DID to ledger + When Issuer offers a jwt credential to Holder with 'short' form DID using issuingKid '' And Holder receives the credential offer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential + When Issuer revokes the credential issued to Holder + Then Issuer should see the credential was revoked + When Issuer sends a request for jwt proof presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Issuer sees the proof returned verification failed + Examples: + | assertionMethod | assertionName | + | secp256k1 | assert-1 | + | ed25519 | assert-1 | - Scenario: Issuing jwt credential with published PRISM DID using Ed25519 + Scenario: Issuing jwt credential with published PRISM DID Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form DID using issuingKid "assertion-2" + And Issuer has a published DID for 'JWT' + And Holder has an unpublished DID for 'JWT' + When Issuer offers a jwt credential to Holder with 'short' form DID And Holder receives the credential offer - And Holder accepts jwt credential offer with keyId "auth-2" + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential Scenario: Issuing jwt credential with a schema Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Issuer has published STUDENT_SCHEMA schema - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form using STUDENT_SCHEMA schema + And Issuer has a published DID for 'JWT' + And Issuer has published 'STUDENT_SCHEMA' schema + And Holder has an unpublished DID for 'JWT' + When Issuer offers a jwt credential to Holder with 'short' form using 'STUDENT_SCHEMA' schema And Holder receives the credential offer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential Scenario: Issuing jwt credential with wrong claim structure for schema Given Issuer and Holder have an existing connection - And Issuer has a published DID for JWT - And Issuer has published STUDENT_SCHEMA schema - And Holder has an unpublished DID for JWT - When Issuer offers a jwt credential to Holder with "short" form DID with wrong claims structure using STUDENT_SCHEMA schema + And Issuer has a published DID for 'JWT' + And Issuer has published 'STUDENT_SCHEMA' schema + And Holder has an unpublished DID for 'JWT' + When Issuer offers a jwt credential to Holder with 'short' form DID with wrong claims structure using 'STUDENT_SCHEMA' schema Then Issuer should see that credential issuance has failed Scenario: Issuing jwt credential with unpublished PRISM DID Given Issuer and Holder have an existing connection - And Issuer has an unpublished DID for JWT - And Holder has an unpublished DID for JWT - And Issuer offers a jwt credential to Holder with "long" form DID + And Issuer has an unpublished DID for 'JWT' + And Holder has an unpublished DID for 'JWT' + And Issuer offers a jwt credential to Holder with 'long' form DID And Holder receives the credential offer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential Then Holder receives the issued credential Scenario: Connectionless issuance of JWT credential using OOB invitation - Given Issuer has a published DID for JWT - And Holder has an unpublished DID for JWT - When Issuer creates a "JWT" credential offer invitation with "short" form DID + Given Issuer has a published DID for 'JWT' + And Holder has an unpublished DID for 'JWT' + When Issuer creates a 'JWT' credential offer invitation with 'short' form DID And Holder accepts the credential offer invitation from Issuer - And Holder accepts jwt credential offer + And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential - Then Holder receives the issued credential \ No newline at end of file + Then Holder receives the issued credential diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature index 04e7773873..8c627d03d9 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature @@ -25,6 +25,27 @@ Feature: Present Proof Protocol And Holder rejects the proof Then Holder sees the proof is rejected + Scenario Outline: Verifying jwt credential using assertion + Given Issuer and Holder have an existing connection + And Verifier and Holder have an existing connection + And Holder creates unpublished DID for 'JWT' + When Issuer prepares a custom PRISM DID + And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID + And Issuer creates the custom PRISM DID + When Issuer offers a jwt credential to Holder with 'long' form DID using issuingKid '' + And Holder receives the credential offer + And Holder accepts jwt credential offer using 'auth-1' key id + And Issuer issues the credential + Then Holder receives the issued credential + When Verifier sends a request for jwt proof presentation to Holder + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Verifier has the proof verified + Examples: + | assertionMethod | assertionName | + | secp256k1 | assert-1 | + | ed25519 | assert-1 | + Scenario: Connectionless Verification Holder presents jwt credential proof to verifier Given Holder has a jwt issued credential from Issuer When Verifier creates a OOB Invitation request for JWT proof presentation @@ -33,10 +54,9 @@ Feature: Present Proof Protocol And Holder makes the jwt presentation of the proof Then Verifier has the proof verified - Scenario: Verifier request for jwt proof presentation to Holder from trusted issuer using specified schema Given Verifier and Holder have an existing connection - And Holder has a jwt issued credential with STUDENT_SCHEMA schema from Issuer + And Holder has a jwt issued credential with 'STUDENT_SCHEMA' schema from Issuer When Verifier sends a request for jwt proof from trustedIssuer Issuer using STUDENT_SCHEMA schema presentation to Holder And Holder receives the presentation proof request And Holder makes the jwt presentation of the proof @@ -44,9 +64,9 @@ Feature: Present Proof Protocol Scenario: Verifier request for jwt proof presentation to Holder from trusted issuer using specified schema Given Verifier and Holder have an existing connection - And Holder has a jwt issued credential with STUDENT_SCHEMA schema from Issuer - And Holder has a jwt issued credential with EMPLOYEE_SCHEMA schema from Issuer + And Holder has a jwt issued credential with 'STUDENT_SCHEMA' schema from Issuer + And Holder has a jwt issued credential with 'EMPLOYEE_SCHEMA' schema from Issuer When Verifier sends a request for jwt proof from trustedIssuer Issuer using STUDENT_SCHEMA schema presentation to Holder And Holder receives the presentation proof request And Holder makes the jwt presentation of the proof - Then Verifier sees the proof returned verification failed \ No newline at end of file + Then Verifier sees the proof returned verification failed diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature index 508380402d..284f008a60 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature @@ -1,10 +1,8 @@ @jwt @revocation Feature: JWT Credential revocation - Background: - Given Holder has a jwt issued credential from Issuer - Scenario: Revoke jwt issued credential + Given Holder has a jwt issued credential from Issuer When Issuer revokes the credential issued to Holder Then Issuer should see the credential was revoked When Issuer sends a request for jwt proof presentation to Holder @@ -13,6 +11,7 @@ Feature: JWT Credential revocation Then Issuer sees the proof returned verification failed Scenario: Holder tries to revoke jwt credential from issuer + Given Holder has a jwt issued credential from Issuer When Holder tries to revoke credential from Issuer And Issuer sends a request for jwt proof presentation to Holder And Holder receives the presentation proof request diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature index a96c24f04f..2edc63f073 100644 --- a/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature @@ -1,21 +1,28 @@ @sdjwt @issuance Feature: Issue SD-JWT credential - Scenario: Issuing sd-jwt credential + Scenario Outline: Issuing sd-jwt credential Given Issuer and Holder have an existing connection - And Issuer has a published DID for SD_JWT - And Holder has an unpublished DID for SD_JWT + And Holder has an unpublished DID for 'SD_JWT' + When Issuer prepares a custom PRISM DID + And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID + And Issuer adds a '' key for 'authentication' purpose with '' name to the custom PRISM DID + And Issuer creates the custom PRISM DID + And Issuer publishes DID to ledger When Issuer offers a sd-jwt credential to Holder And Holder receives the credential offer And Holder accepts credential offer for sd-jwt And Issuer issues the credential Then Holder receives the issued credential And Holder checks the sd-jwt credential contents + Examples: + | assertionMethod | assertionName | authentication | authenticationName | + | ed25519 | assert-1 | ed25519 | auth-1 | Scenario: Issuing sd-jwt credential with holder binding Given Issuer and Holder have an existing connection - And Issuer has a published DID for SD_JWT - And Holder has an unpublished DID for SD_JWT + And Issuer has a published DID for 'SD_JWT' + And Holder has an unpublished DID for 'SD_JWT' When Issuer offers a sd-jwt credential to Holder And Holder receives the credential offer And Holder accepts credential offer for sd-jwt with 'auth-1' key binding @@ -24,25 +31,11 @@ Feature: Issue SD-JWT credential Then Holder checks the sd-jwt credential contents with holder binding Scenario: Connectionless issuance of sd-jwt credential with holder binding - And Issuer has a published DID for SD_JWT - And Holder has an unpublished DID for SD_JWT - When Issuer creates a "SDJWT" credential offer invitation with "short" form DID + And Issuer has a published DID for 'SD_JWT' + And Holder has an unpublished DID for 'SD_JWT' + When Issuer creates a 'SDJWT' credential offer invitation with 'short' form DID And Holder accepts the credential offer invitation from Issuer And Holder accepts credential offer for sd-jwt with 'auth-1' key binding And Issuer issues the credential Then Holder receives the issued credential Then Holder checks the sd-jwt credential contents with holder binding - - -# Scenario: Issuing sd-jwt with wrong algorithm -# Given Issuer and Holder have an existing connection -# When Issuer prepares a custom PRISM DID -# And Issuer adds a 'secp256k1' key for 'assertionMethod' purpose with 'assert-1' name to the custom PRISM DID -# And Issuer adds a 'secp256k1' key for 'authentication' purpose with 'auth-1' name to the custom PRISM DID -# And Issuer creates the custom PRISM DID -# And Holder has an unpublished DID for SD_JWT -# And Issuer offers a sd-jwt credential to Holder -# And Holder receives the credential offer -# And Holder accepts credential offer for sd-jwt -# And Issuer tries to issue the credential -# Then Issuer should see that credential issuance has failed diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature index 96e8f4e961..d0d3ca9dd5 100644 --- a/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature @@ -25,7 +25,7 @@ Feature: Present SD-JWT Proof Protocol | Verifier | | Issuer | - Scenario Outline: Holder presents sd-jwt proof to + Scenario Outline: Holder presents sd-jwt out-of-band proof to Given Holder has a sd-jwt issued credential from Issuer When creates a OOB Invitation request for sd-jwt proof presentation requesting [firstName] claims And Holder accepts the OOB invitation request for JWT proof presentation from diff --git a/tests/integration-tests/src/test/resources/features/did/create_did.feature b/tests/integration-tests/src/test/resources/features/did/create_did.feature index 55ebe81a21..172fc301b0 100644 --- a/tests/integration-tests/src/test/resources/features/did/create_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/create_did.feature @@ -1,7 +1,7 @@ @DLT @did @create Feature: Create and publish DID - Scenario Outline: Create PRISM DID + Scenario Outline: Create PRISM DID with for When Issuer creates PRISM DID with key having purpose Then He sees PRISM DID was created successfully And He sees PRISM DID data was stored correctly with and @@ -13,7 +13,7 @@ Feature: Create and publish DID | Ed25519 | assertionMethod | | X25519 | keyAgreement | - Scenario Outline: Create PRISM DID with disallowed key purpose + Scenario Outline: Create PRISM DID with for should not work When Issuer creates PRISM DID with key having purpose Then He sees PRISM DID was not successfully created Examples: @@ -23,6 +23,6 @@ Feature: Create and publish DID | X25519 | assertionMethod | Scenario: Successfully publish DID to ledger - Given Issuer creates unpublished DID + Given Issuer creates empty unpublished DID When He publishes DID to ledger Then He resolves DID document corresponds to W3C standard diff --git a/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature b/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature index 13b3ea03e3..37a1c3bebb 100644 --- a/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature @@ -2,7 +2,7 @@ Feature: Deactivate DID Scenario: Deactivate DID - Given Issuer creates unpublished DID + Given Issuer creates empty unpublished DID And Issuer publishes DID to ledger When Issuer deactivates PRISM DID Then He sees that PRISM DID is successfully deactivated diff --git a/tests/integration-tests/src/test/resources/features/did/update_did.feature b/tests/integration-tests/src/test/resources/features/did/update_did.feature index 9aeb2e122f..6e3166fd78 100644 --- a/tests/integration-tests/src/test/resources/features/did/update_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/update_did.feature @@ -2,7 +2,7 @@ Feature: Update DID Background: Published DID is created - Given Issuer has a published DID for JWT + Given Issuer has a published DID for 'JWT' Scenario: Update PRISM DID services When Issuer updates PRISM DID with new services @@ -17,7 +17,7 @@ Feature: Update DID Then He sees the PRISM DID should have been updated successfully And He sees the PRISM DID should have the service removed - Scenario Outline: Update PRISM DID keys + Scenario Outline: Update PRISM DID keys using for When Issuer updates PRISM DID by adding new key with curve and purpose Then He sees PRISM DID was successfully updated with new keys of purpose diff --git a/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature b/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature index f6f0cdf0c7..83ba9f0459 100644 --- a/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature +++ b/tests/integration-tests/src/test/resources/features/multitenancy/wallets.feature @@ -2,8 +2,8 @@ Feature: Wallets management Scenario Outline: Successful creation of a new wallet - When Admin creates new wallet with name - Then Admin should have a wallet with name + When Admin creates new wallet with name '' + Then Admin should have a wallet with name '' Examples: | name | | "wallet-1" | diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature index 00c7bf2f45..a48859a8e5 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature @@ -2,20 +2,20 @@ Feature: Issue JWT Credentials using OID4VCI authorization code flow Background: - Given Issuer has a published DID for OIDC_JWT - And Issuer has published STUDENT_SCHEMA schema + Given Issuer has a published DID for 'OIDC_JWT' + And Issuer has published 'STUDENT_SCHEMA' schema And Issuer has an existing oid4vci issuer - And Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA + And Issuer has 'StudentProfile' credential configuration created from 'STUDENT_SCHEMA' Scenario: Issuing credential with published PRISM DID - When Issuer creates an offer using "StudentProfile" configuration with "short" form DID + When Issuer creates an offer using 'StudentProfile' configuration with 'short' form DID And Holder receives oid4vci offer from Issuer And Holder resolves oid4vci issuer metadata and login via front-end channel And Holder presents the access token with JWT proof on CredentialEndpoint Then Holder sees credential issued successfully from CredentialEndpoint Scenario: Issuing credential with unpublished PRISM DID - When Issuer creates an offer using "StudentProfile" configuration with "long" form DID + When Issuer creates an offer using 'StudentProfile' configuration with 'long' form DID And Holder receives oid4vci offer from Issuer And Holder resolves oid4vci issuer metadata and login via front-end channel And Holder presents the access token with JWT proof on CredentialEndpoint diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature b/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature index 669fe6977e..991f93ea24 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature @@ -2,22 +2,22 @@ Feature: Manage OID4VCI credential configuration Background: - Given Issuer has a published DID for OIDC_JWT - And Issuer has published STUDENT_SCHEMA schema + Given Issuer has a published DID for 'OIDC_JWT' + And Issuer has published 'STUDENT_SCHEMA' schema And Issuer has an existing oid4vci issuer Scenario: Successfully create credential configuration - Given Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA - Then Issuer sees the "StudentProfile" configuration on IssuerMetadata endpoint + Given Issuer has 'StudentProfile' credential configuration created from 'STUDENT_SCHEMA' + Then Issuer sees the 'StudentProfile' configuration on IssuerMetadata endpoint Scenario: Successfully delete credential configuration - Given Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA - When Issuer deletes "StudentProfile" credential configuration - Then Issuer cannot see the "StudentProfile" configuration on IssuerMetadata endpoint + Given Issuer has 'StudentProfile' credential configuration created from 'STUDENT_SCHEMA' + When Issuer deletes 'StudentProfile' credential configuration + Then Issuer cannot see the 'StudentProfile' configuration on IssuerMetadata endpoint Scenario Outline: Create configuration with expect code When Issuer creates a new credential configuration request - And Issuer uses issuer id for credential configuration + And Issuer uses '' issuer id for credential configuration And Issuer adds '' configuration id for credential configuration request And Issuer adds '' format for credential configuration request And Issuer adds '' schemaId for credential configuration request @@ -35,5 +35,5 @@ Feature: Manage OID4VCI credential configuration | existing | StudentProfile | jwt_vc_json | STUDENT_SCHEMA | 409 | Duplicated credential | duplicated configuration id | Scenario: Delete non existent credential configuration - When Issuer deletes a non existent "NonExistentProfile" credential configuration + When Issuer deletes a non existent 'NonExistentProfile' credential configuration Then Issuer should see that create credential configuration has failed with '404' status code and 'There is no credential configuration' detail diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature b/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature index 6259934824..4ba9f9bbf7 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature @@ -18,18 +18,18 @@ Feature: Manage OID4VCI credential issuer Then Issuer cannot see the oid4vci issuer on the agent And Issuer cannot see the oid4vci IssuerMetadata endpoint - Scenario Outline: Create issuer with expect response + Scenario Outline: Create issuer with [id=; url=; clientId=; clientSecret=] expect response When Issuer tries to create oid4vci issuer with '', '', '' and '' Then Issuer should see the oid4vci '' http status response with '' detail Examples: - | id | url | clientId | clientSecret | httpStatus | errorDetail | description | - | null | null | null | null | 400 | authorizationServer.url | null values | - | null | malformed | id | secret | 400 | Relative URL 'malformed' is not | malformed url | - | null | http://example.com | id | null | 400 | authorizationServer.clientSecret | null client secret | - | null | http://example.com | null | secret | 400 | authorizationServer.clientId | null client id | - | null | null | id | secret | 400 | authorizationServer.url | null url | - | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 201 | | right values | - | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 500 | | duplicated id | + | id | url | clientId | clientSecret | httpStatus | errorDetail | + | null | null | null | null | 400 | authorizationServer.url | + | null | malformed | id | secret | 400 | Relative URL 'malformed' is not | + | null | http://example.com | id | null | 400 | authorizationServer.clientSecret | + | null | http://example.com | null | secret | 400 | authorizationServer.clientId | + | null | null | id | secret | 400 | authorizationServer.url | + | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 201 | | + | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 500 | | Scenario Outline: Update issuer with expect response Given Issuer has an existing oid4vci issuer diff --git a/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature b/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature index fdbbbc911f..d06b5a0090 100644 --- a/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature +++ b/tests/integration-tests/src/test/resources/features/schemas/credential_schemas.feature @@ -2,10 +2,10 @@ Feature: Credential schemas Background: - Given Issuer creates unpublished DID + Given Issuer creates empty unpublished DID Scenario: Successful schema creation - When Issuer creates a new credential STUDENT_SCHEMA schema + When Issuer creates a new credential 'STUDENT_SCHEMA' schema Then He sees new credential schema is available Scenario Outline: Multiple schema creation @@ -15,7 +15,7 @@ Feature: Credential schemas | schemas | | 4 | - Scenario Outline: Schema creation should fail for cases + Scenario Outline: Schema creation should fail for When Issuer creates a schema containing '' issue Then Issuer should see the schema creation failed Examples: diff --git a/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature b/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature index 3db147f66d..6b74dc000c 100644 --- a/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature +++ b/tests/integration-tests/src/test/resources/features/verificationapi/vc_verification.feature @@ -1,8 +1,8 @@ @verification @api -Feature: Vc Verification schemas +Feature: Verification API - Scenario: Receive a jwt vc from cloud-agent and verify it - Given Holder has a jwt issued credential with STUDENT_SCHEMA schema from Issuer + Scenario: Verify a jwt credential using verification api + Given Holder has a jwt issued credential with 'STUDENT_SCHEMA' schema from Issuer And Holder uses that JWT VC issued from Issuer for Verification API And Holder sends the JWT Credential to Issuer Verification API | ALGORITHM_VERIFICATION | true | @@ -14,7 +14,7 @@ Feature: Vc Verification schemas | SEMANTIC_CHECK_OF_CLAIMS | true | Then Holder should see that all checks have passed - Scenario: Expected checks for generated JWT VC + Scenario: Verify a pre-generated jwt credential using verification api Given Holder has a JWT VC for Verification API When Holder sends the JWT Credential to Issuer Verification API | ALGORITHM_VERIFICATION | true | @@ -26,11 +26,11 @@ Feature: Vc Verification schemas | SEMANTIC_CHECK_OF_CLAIMS | true | Then Holder should see that all checks have passed - Scenario Outline: Expected failures - Given Holder has a problem in the Verifiable Credential + Scenario Outline: Verify credential with problem + Given Holder has a '' problem in the Verifiable Credential When Holder sends the JWT Credential to Issuer Verification API | | false | - Then Holder should see that verification has failed with problem + Then Holder should see that verification has failed with '' problem Examples: | problem | | ALGORITHM_VERIFICATION | @@ -41,7 +41,7 @@ Feature: Vc Verification schemas | SIGNATURE_VERIFICATION | | SEMANTIC_CHECK_OF_CLAIMS | - Scenario Outline: Unsupported verification check should fail + Scenario Outline: Unsupported verification check should fail Given Holder has a JWT VC for Verification API When Holder sends the JWT Credential to Issuer Verification API | | false |