From 376386777860e6d028d9af4506e899c6ebaebda0 Mon Sep 17 00:00:00 2001 From: F-Node-Karlsruhe Date: Mon, 11 Sep 2023 11:55:44 +0200 Subject: [PATCH] fix status list in presentation Signed-off-by: F-Node-Karlsruhe --- CHANGELOG.md | 3 ++ api/__tests__/presentation.test.ts | 69 ++++++++++++++++++++++++++++++ api/src/services/verifier/index.ts | 49 ++++++++++++++++++--- 3 files changed, 114 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 916b7af..2803128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ VC Verifier Changelog WIP --- +- fix statusList in presentation + + 1.7.1 (2023-09-08) --- diff --git a/api/__tests__/presentation.test.ts b/api/__tests__/presentation.test.ts index dae448c..e85dfa5 100644 --- a/api/__tests__/presentation.test.ts +++ b/api/__tests__/presentation.test.ts @@ -138,8 +138,77 @@ const domainPresentation: any = { } } +const statusPresentation: any = { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": [ + "VerifiablePresentation" + ], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "ipfs://QmY9CDY2PoXLgHr2vG4u8mj27cfAyuzVxE2swt8wwFn7Rt", + "https://w3id.org/vc-revocation-list-2020/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "https://ssi.eecc.de/api/registry/vc/b7440bf7-2317-43e4-9857-4c85032ac4a1", + "type": [ + "VerifiableCredential", + "EECCAccessCredential" + ], + "issuer": { + "id": "did:web:ssi.eecc.de", + "image": "https://id.eecc.de/assets/img/logo_big.png", + "name": "EECC" + }, + "issuanceDate": "2023-06-19T09:58:08Z", + "credentialStatus": { + "id": "https://ssi.eecc.de/api/registry/vc/status/did:web:ssi.eecc.de/1#1", + "type": "RevocationList2020Status", + "revocationListIndex": 1, + "revocationListCredential": "https://ssi.eecc.de/api/registry/vc/status/did:web:ssi.eecc.de/1" + }, + "credentialSubject": { + "id": "https://ssi.eecc.de/verifier", + "customer_name": "VC Verifier" + }, + "proof": { + "type": "Ed25519Signature2020", + "created": "2023-06-19T09:58:08Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", + "proofValue": "z247VYAm5GbG1sAAcqW7QwEL1mfXxaSa3N93EuLc4HeTUm219UjrPMC5gyq5xTAaDQCVMUEVTouyboY4UMTxzjekN" + } + } + ], + "holder": "did:web:ssi.eecc.de", + "proof": { + "type": "Ed25519Signature2020", + "created": "2023-06-19T10:19:04Z", + "verificationMethod": "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", + "proofPurpose": "authentication", + "challenge": "demochallenge", + "proofValue": "z3BCT7df4bRXXTa9i79apmiou76M8LVZXGFYHA18xT6wX3PfcpjfWaMc9xhFwmGi9XTj8tth1ai1ASeQoHiGWMn1V" + } +} + describe("Verifier API Test for Presentations", () => { + test("Verify presentation with status", async () => { + const res = await request(app).post("/api/verifier").send([statusPresentation]); + expect(res.statusCode).toEqual(200); + expect(res.body[0]).toHaveProperty('verified'); + expect(res.body[0].verified).toBe(true); + expect(res.body[0]).toHaveProperty('credentialResults'); + res.body[0].credentialResults.forEach((el: any) => { + expect(el).toHaveProperty('verified'); + expect(el.verified).toBe(true); + }); + }); + test("Verify single presentation with challenge", async () => { const res = await request(app).post("/api/verifier").query({ challenge: '12345' }).send([multiPresentation]); expect(res.statusCode).toEqual(200); diff --git a/api/src/services/verifier/index.ts b/api/src/services/verifier/index.ts index 8ae8a28..00a40c8 100644 --- a/api/src/services/verifier/index.ts +++ b/api/src/services/verifier/index.ts @@ -51,6 +51,32 @@ function getSuites(proof: Proof | Proof[]): unknown[] { } +function getPresentationStatus(presentation: VerifiablePresentation): CredentialStatus[] | CredentialStatus | undefined { + + const credentials = ( + Array.isArray(presentation.verifiableCredential) + ? presentation.verifiableCredential + : [presentation.verifiableCredential] + ) + .filter((credential: VerifiableCredential) => credential.credentialStatus); + + if (credentials.length == 0) return undefined; + + if (credentials.length == 1) return credentials[0].credentialStatus; + + const statusTypes = credentials.map((credential: VerifiableCredential) => { + return Array.isArray(credential.credentialStatus) + ? credential.credentialStatus.map((credentialStatus: CredentialStatus) => credentialStatus.type) + : credential.credentialStatus.type + }); + + // disallow multiple status types + if (new Set(statusTypes.flat(1)).size > 1) throw new Error('Currently only on status type is allowed within one presentation!'); + + return credentials[0].credentialStatus; + +} + function getCheckStatus(credentialStatus?: CredentialStatus[] | CredentialStatus): any | undefined { // no status provided if (!credentialStatus) return undefined; @@ -79,13 +105,15 @@ export class Verifier { let result; - const checkStatus = getCheckStatus(verifiable.credentialStatus); - if (verifiable.type.includes('VerifiableCredential')) { - if ((Array.isArray(verifiable.proof) ? verifiable.proof[0].type : verifiable.proof.type) == 'DataIntegrityProof') { + const credential = verifiable as VerifiableCredential; + + const checkStatus = getCheckStatus(credential.credentialStatus); + + if ((Array.isArray(credential.proof) ? credential.proof[0].type : credential.proof.type) == 'DataIntegrityProof') { - result = await jsigs.verify(verifiable, { + result = await jsigs.verify(credential, { suite, purpose: new AssertionProofPurpose(), documentLoader, @@ -95,17 +123,24 @@ export class Verifier { } else { - result = await verifyCredential({ credential: verifiable, suite, documentLoader, checkStatus }); + result = await verifyCredential({ credential, suite, documentLoader, checkStatus }); } } if (verifiable.type.includes('VerifiablePresentation')) { + const presentation = verifiable as VerifiablePresentation; + // try to use challenge in proof if not provided in case no exchange protocol is used - if (!challenge) challenge = (Array.isArray(verifiable.proof) ? verifiable.proof[0].challenge : verifiable.proof.challenge); + if (!challenge) challenge = (Array.isArray(presentation.proof) ? presentation.proof[0].challenge : presentation.proof.challenge); + + + const checkStatus = getCheckStatus( + getPresentationStatus(presentation) + ); - result = await verify({ presentation: verifiable, suite, documentLoader, challenge, domain, checkStatus }); + result = await verify({ presentation, suite, documentLoader, challenge, domain, checkStatus }); }