diff --git a/integration-tests/e2e-tests/features/verify_anoncred_credential.feature b/integration-tests/e2e-tests/features/verify_anoncred_credential.feature new file mode 100644 index 000000000..d7b2e1bb2 --- /dev/null +++ b/integration-tests/e2e-tests/features/verify_anoncred_credential.feature @@ -0,0 +1,15 @@ +@anoncreds @credential @sdkverification +Feature: Verify Anoncreds presentation + The Edge Agent should be able to receive a verifiable credential from Cloud Agent and then send a presentation to another edge agent who will verify it + + Scenario: SDKs Anoncreds Verification + Given Cloud Agent is connected to Edge Agent + When Cloud Agent offers '1' anonymous credential + Then Edge Agent should receive the credentials offer from Cloud Agent + When Edge Agent accepts the credentials offer from Cloud Agent + And Cloud Agent should see all credentials were accepted + Then Edge Agent wait to receive issued credentials from Cloud Agent + And Edge Agent process issued credentials from Cloud Agent + Then Verifier Edge Agent will request Edge Agent to verify the anonymous credential + When Edge Agent sends the verification proof + Then Verifier Edge Agent should see the verification proof is verified \ No newline at end of file diff --git a/integration-tests/e2e-tests/features/verify_jwt_credential.feature b/integration-tests/e2e-tests/features/verify_jwt_credential.feature new file mode 100644 index 000000000..37554e5da --- /dev/null +++ b/integration-tests/e2e-tests/features/verify_jwt_credential.feature @@ -0,0 +1,23 @@ +@jwt @credential @sdkverification +Feature: Verify JWT presentation + The Edge Agent should be able to receive a verifiable credential from Cloud Agent and then send a presentation to another edge agent who will verify it + + Scenario: SDKs JWT Verification + Given Cloud Agent is connected to Edge Agent + When Cloud Agent offers '1' jwt credentials + Then Edge Agent should receive the credentials offer from Cloud Agent + When Edge Agent accepts the credentials offer from Cloud Agent + And Cloud Agent should see all credentials were accepted + Then Edge Agent wait to receive issued credentials from Cloud Agent + And Edge Agent process issued credentials from Cloud Agent + Then Verifier Edge Agent will request Edge Agent to verify the JWT credential + When Edge Agent sends the verification proof + Then Verifier Edge Agent should see the verification proof is verified + + Scenario: SDKs JWT Revoked Verification + Given Cloud Agent is connected to Edge Agent + And Edge Agent has '1' jwt credentials issued by Cloud Agent + When Cloud Agent revokes '1' credentials + Then Verifier Edge Agent will request Edge Agent to verify the JWT credential + When Edge Agent sends the verification proof + Then Verifier Edge Agent should see the verification proof is verified false \ No newline at end of file diff --git a/integration-tests/e2e-tests/package.json b/integration-tests/e2e-tests/package.json index 3c1019ce7..40362ad3a 100644 --- a/integration-tests/e2e-tests/package.json +++ b/integration-tests/e2e-tests/package.json @@ -48,4 +48,4 @@ "resolutions": { "wrap-ansi": "^7.0.0" } -} +} \ No newline at end of file diff --git a/integration-tests/e2e-tests/src/abilities/WalletSdk.ts b/integration-tests/e2e-tests/src/abilities/WalletSdk.ts index 1585082e3..43b59beca 100644 --- a/integration-tests/e2e-tests/src/abilities/WalletSdk.ts +++ b/integration-tests/e2e-tests/src/abilities/WalletSdk.ts @@ -47,11 +47,19 @@ export class WalletSdk extends Ability implements Initialisable, Discardable { }) } + static presentationStackSize(): QuestionAdapter { + return Question.about("presentation messages stack", actor => { + return WalletSdk.as(actor).messages.presentationMessagesStack.length + }) + } + static execute(callback: (sdk: SDK.Agent, messages: { credentialOfferStack: Message[]; issuedCredentialStack: Message[]; proofRequestStack: Message[]; revocationStack: Message[], + presentationMessagesStack: Message[] + }) => Promise): Interaction { return Interaction.where("#actor uses wallet sdk", async actor => { await callback(WalletSdk.as(actor).sdk, { @@ -59,6 +67,7 @@ export class WalletSdk extends Ability implements Initialisable, Discardable { issuedCredentialStack: WalletSdk.as(actor).messages.issuedCredentialStack, proofRequestStack: WalletSdk.as(actor).messages.proofRequestStack, revocationStack: WalletSdk.as(actor).messages.revocationStack, + presentationMessagesStack: WalletSdk.as(actor).messages.presentationMessagesStack }) }) } @@ -115,6 +124,8 @@ class MessageQueue { proofRequestStack: Message[] = [] issuedCredentialStack: Message[] = [] revocationStack: Message[] = [] + presentationMessagesStack: Message[] = []; + receivedMessages: string[] = [] enqueue(message: Message) { @@ -144,7 +155,8 @@ class MessageQueue { this.processingId = setInterval(() => { if (!this.isEmpty()) { const message: Message = this.dequeue() - + const piUri = message.piuri; + // checks if sdk already received message if (this.receivedMessages.includes(message.id)) { return @@ -152,14 +164,17 @@ class MessageQueue { this.receivedMessages.push(message.id) - if (message.piuri.includes("/offer-credential")) { + + if (piUri === SDK.ProtocolType.DidcommOfferCredential) { this.credentialOfferStack.push(message) - } else if (message.piuri.includes("/present-proof")) { + } else if (piUri === SDK.ProtocolType.DidcommRequestPresentation) { this.proofRequestStack.push(message) - } else if (message.piuri.includes("/issue-credential")) { + } else if (piUri === SDK.ProtocolType.DidcommIssueCredential) { this.issuedCredentialStack.push(message) - } else if (message.piuri.includes("/revoke")) { + } else if (piUri === SDK.ProtocolType.PrismRevocation) { this.revocationStack.push(message) + } else if (piUri === SDK.ProtocolType.DidcommPresentation) { + this.presentationMessagesStack.push(message) } } else { clearInterval(this.processingId!) diff --git a/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts b/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts index 17e5bdac9..22171dfb7 100644 --- a/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts +++ b/integration-tests/e2e-tests/src/steps/EdgeAgentSteps.ts @@ -1,9 +1,11 @@ +import SDK from "@atala/prism-wallet-sdk" import { Given, Then, When } from "@cucumber/cucumber" import { Actor, Notepad } from "@serenity-js/core" import { EdgeAgentWorkflow } from "../workflow/EdgeAgentWorkflow" import { CloudAgentWorkflow } from "../workflow/CloudAgentWorkflow" import { Utils } from "../Utils" + Given("{actor} has '{int}' jwt credentials issued by {actor}", async function (edgeAgent: Actor, numberOfIssuedCredentials: number, cloudAgent: Actor) { const recordIdList = [] @@ -38,19 +40,19 @@ Given("{actor} has '{int}' anonymous credentials issued by {actor}", ) Given("{actor} has created a backup", - async function(edgeAgent: Actor) { + async function (edgeAgent: Actor) { await EdgeAgentWorkflow.createBackup(edgeAgent) } ) Given("{actor} creates '{}' peer DIDs", - async function(edgeAgent: Actor, numberOfDids: number) { + async function (edgeAgent: Actor, numberOfDids: number) { await EdgeAgentWorkflow.createPeerDids(edgeAgent, numberOfDids) } ) Given("{actor} creates '{}' prism DIDs", - async function(edgeAgent: Actor, numberOfDids: number) { + async function (edgeAgent: Actor, numberOfDids: number) { await EdgeAgentWorkflow.createPrismDids(edgeAgent, numberOfDids) } ) @@ -148,31 +150,109 @@ Then("{actor} wait to receive issued credentials from {actor}", ) Then("a new SDK can be restored from {actor}", - async function(edgeAgent: Actor) { + async function (edgeAgent: Actor) { await EdgeAgentWorkflow.createNewWalletFromBackup(edgeAgent) } ) Then("a new SDK cannot be restored from {actor} with wrong seed", - async function(edgeAgent: Actor) { + async function (edgeAgent: Actor) { await EdgeAgentWorkflow.createNewWalletFromBackupWithWrongSeed(edgeAgent) } ) Then("a new {actor} is restored from {actor}", - async function(newAgent: Actor, edgeAgent: Actor) { + async function (newAgent: Actor, edgeAgent: Actor) { await EdgeAgentWorkflow.backupAndRestoreToNewAgent(newAgent, edgeAgent) } ) Then("{actor} should have the expected values from {actor}", - async function(copyEdgeAgent: Actor, originalEdgeAgent: Actor) { + async function (copyEdgeAgent: Actor, originalEdgeAgent: Actor) { await EdgeAgentWorkflow.copyAgentShouldMatchOriginalAgent(copyEdgeAgent, originalEdgeAgent) } ) Then("{actor} is dismissed", - async function(edgeAgent: Actor) { + async function (edgeAgent: Actor) { await edgeAgent.dismiss() } ) + +Then("{actor} will request {actor} to verify the anonymous credential", + async function (verifierEdgeAgent: Actor, holderEdgeAgent: Actor) { + + await EdgeAgentWorkflow.createPeerDids(holderEdgeAgent, 1) + const holderDID = await holderEdgeAgent.answer(Notepad.notes().get("lastPeerDID")); + + await EdgeAgentWorkflow.initiatePresentationRequest( + verifierEdgeAgent, + SDK.Domain.CredentialType.AnonCreds, + holderDID, + { + attributes: { + name: { + name: 'name', + restrictions: {} + } + } + } + ) + } +) + +Then("{actor} will request {actor} to verify the JWT credential", + async function (verifierEdgeAgent: Actor, holderEdgeAgent: Actor) { + + await EdgeAgentWorkflow.createPeerDids(holderEdgeAgent, 1) + const holderDID = await holderEdgeAgent.answer(Notepad.notes().get("lastPeerDID")); + + await EdgeAgentWorkflow.initiatePresentationRequest( + verifierEdgeAgent, + SDK.Domain.CredentialType.JWT, + holderDID, + { + claims: { + "automation-required": { + type: 'string', + pattern: 'required value' + } + } + } + ) + } +) + +When("{actor} sends the verification proof", async ( + edgeAgent: Actor, +) => { + await EdgeAgentWorkflow.waitForProofRequest( + edgeAgent + ) + await EdgeAgentWorkflow.presentVerificationRequest( + edgeAgent + ) +}) + +Then("{actor} should see the verification proof is verified", async ( + edgeAgent: Actor, +) => { + await EdgeAgentWorkflow.waitForPresentationMessage( + edgeAgent + ) + await EdgeAgentWorkflow.verifyPresentation( + edgeAgent + ) +}) + +Then("{actor} should see the verification proof is verified false", async ( + edgeAgent: Actor, +) => { + await EdgeAgentWorkflow.waitForPresentationMessage( + edgeAgent + ) + await EdgeAgentWorkflow.verifyPresentation( + edgeAgent, + false + ) +}) \ No newline at end of file diff --git a/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts b/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts index 0a508b166..c0cd04ca7 100644 --- a/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts +++ b/integration-tests/e2e-tests/src/workflow/EdgeAgentWorkflow.ts @@ -7,7 +7,7 @@ import { randomUUID } from "crypto" import _ from "lodash" import { assert } from "chai" -const { IssueCredential, OfferCredential, RequestPresentation, } = SDK +const { IssueCredential, OfferCredential, RequestPresentation, Presentation } = SDK export class EdgeAgentWorkflow { @@ -74,6 +74,54 @@ export class EdgeAgentWorkflow { ) } + static async presentVerificationRequest(edgeAgent: Actor) { + await edgeAgent.attemptsTo( + WalletSdk.execute(async (sdk, messages) => { + const credentials = await sdk.verifiableCredentials() + const credential = credentials[0] + const requestPresentationMessage = RequestPresentation.fromMessage( + messages.proofRequestStack.shift()!, + ) + const presentation = await sdk.createPresentationForRequestProof( + requestPresentationMessage, + credential, + ) + try { + await sdk.sendMessage(presentation.makeMessage()) + } catch (e) { + // + } + }) + ) + } + + static async verifyPresentation(edgeAgent: Actor, expected: boolean = true) { + await edgeAgent.attemptsTo( + WalletSdk.execute(async (sdk, messages) => { + const presentation = messages.presentationMessagesStack.shift()!; + + const presentationMessage = Presentation.fromMessage( + presentation, + ); + + try { + const verified = await sdk.handlePresentation( + presentationMessage + ) + if (!expected) assert.isFalse(verified) + else assert.isTrue(verified) + } catch (e) { + if (e.message.includes("credential is revoked")) { + assert.isTrue(expected === false) + } else { + throw e + } + } + + }) + ) + } + static async presentProof(edgeAgent: Actor) { await edgeAgent.attemptsTo( WalletSdk.execute(async (sdk, messages) => { @@ -96,6 +144,16 @@ export class EdgeAgentWorkflow { ) } + static async waitForPresentationMessage(edgeAgent: Actor, numberOfMessages: number = 1) { + await edgeAgent.attemptsTo( + Wait.upTo(Duration.ofSeconds(60)).until( + WalletSdk.presentationStackSize(), + equals(numberOfMessages) + ) + ) + } + + static async waitForCredentialRevocationMessage(edgeAgent: Actor, numberOfRevocation: number) { await edgeAgent.attemptsTo( Wait.upTo(Duration.ofSeconds(60)).until( @@ -114,7 +172,9 @@ export class EdgeAgentWorkflow { const credentials = await sdk.verifiableCredentials() const revokedCredentials = await Utils.asyncFilter(credentials, async credential => { // checks if it's revoked and part of the revoked ones - return credential.isRevoked() && revokedIdList.includes(credential.id) + return sdk.isCredentialRevoked(credential) && + credential.isRevoked() && + revokedIdList.includes(credential.id) }) await edgeAgent.attemptsTo( Ensure.that(revokedCredentials.length, equals(revokedRecordIdList.length)) @@ -123,16 +183,36 @@ export class EdgeAgentWorkflow { ) } - static async createPeerDids(edgeAgent: Actor, numberOfDids: number) { + static async createPeerDids(edgeAgent: Actor, numberOfDids: number = 1) { await edgeAgent.attemptsTo( WalletSdk.execute(async sdk => { await Utils.repeat(numberOfDids, async () => { - await sdk.createNewPeerDID() + const did = await sdk.createNewPeerDID() + await edgeAgent.attemptsTo( + Notepad.notes().set('lastPeerDID', did) + ) }) }) ) } + static async initiatePresentationRequest( + edgeAgent: Actor, + type: T, + toDiD: SDK.Domain.DID, + claims: SDK.Domain.PresentationClaims + ) { + await edgeAgent.attemptsTo( + WalletSdk.execute(async (sdk) => { + await sdk.initiatePresentationRequest( + type, + toDiD, + claims + ) + }) + ) + } + static async createPrismDids(edgeAgent: Actor, numberOfDids: number) { await edgeAgent.attemptsTo( WalletSdk.execute(async sdk => { @@ -171,7 +251,7 @@ export class EdgeAgentWorkflow { Ensure.that(prismDids.length, equals(expectedPrismDids.length)), Ensure.that(didPairs.length, equals(expectedDidPairs.length)), ) - + assert.isTrue(_.isEqual(expectedCredentials.map(it => it.id), credentials.map(it => it.id))) assert.isTrue(_.isEqual(expectedPeerDids.map(it => it.did.uuid), peerDids.map(it => it.did.uuid))) assert.isTrue(_.isEqual(expectedPrismDids.map(it => it.did.uuid), prismDids.map(it => it.did.uuid))) diff --git a/package-lock.json b/package-lock.json index 1e738db0e..c1e92832d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -131,38 +131,22 @@ } }, "externals/generated/anoncreds-wasm-browser": { - "name": "anoncreds-wasm", - "version": "0.1.0", "dev": true }, "externals/generated/anoncreds-wasm-node": { - "name": "anoncreds-wasm", - "version": "0.1.0", "dev": true }, "externals/generated/didcomm-wasm-browser": { - "name": "didcomm-js", - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "externals/generated/didcomm-wasm-node": { - "name": "didcomm-js", - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "externals/generated/jwe-wasm-browser": { - "name": "jwe-rust", - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "externals/generated/jwe-wasm-node": { - "name": "jwe-rust", - "version": "0.4.1", - "dev": true, - "license": "Apache-2.0" + "dev": true }, "node_modules/@ampproject/remapping": { "version": "2.3.0", diff --git a/rollup/rollup.node.mjs b/rollup/rollup.node.mjs index 0b73fcd31..deddd7c94 100644 --- a/rollup/rollup.node.mjs +++ b/rollup/rollup.node.mjs @@ -17,7 +17,6 @@ const nodePlugins = [ 'didcomm_js_bg.wasm': "../node-wasm/didcomm.wasm", 'anoncreds_wasm_bg.wasm': "../node-wasm/anoncreds.wasm", 'jwe_rust_bg.wasm': "../node-wasm/jwe.wasm", - } }), copy({ diff --git a/src/edge-agent/Agent.Credentials.ts b/src/edge-agent/Agent.Credentials.ts index 45270c965..da25cb8f2 100644 --- a/src/edge-agent/Agent.Credentials.ts +++ b/src/edge-agent/Agent.Credentials.ts @@ -388,7 +388,10 @@ export class AgentCredentials implements AgentCredentialsClass { if (!presentation.thid) { throw new AgentError.UnsupportedAttachmentType("Cannot find any message with that threadID"); } - const presentationSubmission = JSON.parse(attachment.payload) + const presentationSubmission = typeof attachment.payload === 'string' ? + JSON.parse(attachment.payload) : + attachment.payload; + const presentationDefinitionRequest = await this.getPresentationDefinitionByThid(presentation.thid); const options = { presentationDefinitionRequest diff --git a/src/index.ts b/src/index.ts index 831404c9a..e428840f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ export type { } from "./edge-agent/types"; export type { DIDCommProtocol } from "./mercury/DIDCommProtocol"; export * from "./edge-agent/protocols/types"; +export { ProtocolType } from "./edge-agent/protocols/ProtocolTypes"; export * from "./apollo/utils/Secp256k1PrivateKey"; export * from "./apollo/utils/Secp256k1PublicKey"; export * from "./apollo/utils/Secp256k1KeyPair"; diff --git a/src/pollux/Pollux.ts b/src/pollux/Pollux.ts index 37c0d53fc..c93f145c4 100644 --- a/src/pollux/Pollux.ts +++ b/src/pollux/Pollux.ts @@ -275,7 +275,7 @@ export default class Pollux implements IPollux { } const statusListDecoded = this.extractEncodedList(revocation) const bitstring = new Bitstring({ buffer: statusListDecoded }) - return bitstring.get(statusListIndex + 1) + return bitstring.get(statusListIndex) } throw new PolluxError.InvalidRevocationStatusResponse(`CredentialStatus proof type not supported`); } catch (err) { diff --git a/tests/pollux/Pollux.revocation.test.ts b/tests/pollux/Pollux.revocation.test.ts index 6608455a8..061a33a27 100644 --- a/tests/pollux/Pollux.revocation.test.ts +++ b/tests/pollux/Pollux.revocation.test.ts @@ -77,7 +77,7 @@ describe("Pollux", () => { //Workaround to hardcode the revocation index const vc = credential.properties.get(JWTVerifiableCredentialProperties.vc); - vc.credentialStatus.statusListIndex = 0; + vc.credentialStatus.statusListIndex = 1; credential.properties.set(JWTVerifiableCredentialProperties.vc, vc); const revoked = await pollux.isCredentialRevoked(credential)