diff --git a/CHANGELOG.md b/CHANGELOG.md index f2e852c..c3b90a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ VC Verifier Changelog WIP --- +- add support for SD-JWT +- add `did:jwk` support - introduce static contexts diff --git a/README.md b/README.md index f6b3aed..d66f91e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ This tool uses the libraries by [Digital Bazaar, Inc.](https://github.com/digita in order to verify signatures of [W3C conformal verifiable credentials](https://www.w3.org/TR/vc-data-model/) in JSON-LD form. +For SD-JWT the tool uses: + +- https://github.com/transmute-industries/vc-jwt-sd + ## Functionalities diff --git a/api/__tests__/presentation.test.ts b/api/__tests__/presentation.test.ts index 13ddabf..0e96a84 100644 --- a/api/__tests__/presentation.test.ts +++ b/api/__tests__/presentation.test.ts @@ -2,249 +2,268 @@ import request from "supertest"; import server from "../src/index"; -afterAll(done => { - server.close(); - done(); +afterAll((done) => { + server.close(); + done(); }); +const sdJWTPresentation: string = + "eyJ0eXAiOiJ2YytzZC1qd3QiLCJraWQiOiJlZWNmZDJmZC1kNTI3LTQxNTQtOWM0Mi0zYjM0Yjk0MTkyNmUiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE3MTM5NjE0NDU0MTIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMSIsInZjdCI6IklkZW50aXR5IiwianRpIjoiMjM0YWQ1NjQtMzUzYS00NWRkLTg2MTQtZDE1NTA2ZWU0NGRiIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsIngiOiJ0Y1N3b2hMYW56S0VYM0pwT01ET3N3cHc0bDVMSjR0RXZnTlU0cmRWbHlJIiwieSI6ImtiakpBZUoxcTlaaFFTTklWY2JLcFVucG9NRnB0TFlOZE1WbzliWURGaXMiLCJjcnYiOiJQLTI1NiIsImtpZCI6ImU1OGFhYmYxLTFmN2ItNDZmNC04YWIzLTg4MjZmYjVhNTEzYyJ9fSwiX3NkIjpbImNmZE5Zem5zaFdnTVJ3OXhwa0R6YzBMR1ZLSXRicElRSWE1aHg5OHFiR3ciLCJrUGkxVm1HRkVCT2dpb0xNU3hWVkdteWFUWlFmOGxROEE3STNGblN2OHM4Il0sIl9zZF9hbGciOiJTSEEtMjU2In0.AEedVXYUaNfp8Q3F6qx0WOjuFXkzGvlVDEM0ppgEsEj7wYGNod4QzelNyu09-B8o5q2kstDbjKod57rePAvu8w~WyJjYjY1MzEyMGMwMzYzOWVhIiwicHJlbmFtZSIsIk1heCJd~WyIyMzNmYTU2MDhiNjIwMzEyIiwic3VybmFtZSIsIk11c3Rlcm1hbm4iXQ~"; + const multiPresentation: any = { - "@context": [ + "@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", + "https://ref.gs1.org/gs1/vc/licence-context/", + "https://ssi.eecc.de/api/registry/context", + "https://w3id.org/security/suites/ed25519-2020/v1", + ], + id: "https://ssi.eecc.de/api/registry/vc/e8be5b36-f520-4bdf-ba34-1785b254f0a6", + type: ["VerifiableCredential", "GS1PrefixLicenceCredential"], + issuer: { + id: "did:web:ssi.eecc.de", + image: "https://id.eecc.de/assets/img/logo_big.png", + name: "EECC", + }, + issuanceDate: "2023-05-16T11:11:04Z", + credentialSubject: { + id: "did:web:ssi.eecc.de", + partyGLN: "4047111000006", + licenceValue: "4047111", + organizationName: "European EPC Competence Center", + }, + proof: { + type: "Ed25519Signature2020", + created: "2023-05-16T11:11:04Z", + proofPurpose: "assertionMethod", + verificationMethod: + "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", + proofValue: + "z4GKxTZzba6B4Bs2vCETRi3frX9vFn9m61ZsNKPaxH9yZBX382DLGY1yRvuKgwbxUag88eJQfALuBdBHhp3m2r5nA", + }, + }, + { + "@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", - "https://ref.gs1.org/gs1/vc/licence-context/", - "https://ssi.eecc.de/api/registry/context", - "https://w3id.org/security/suites/ed25519-2020/v1" - ], - "id": "https://ssi.eecc.de/api/registry/vc/e8be5b36-f520-4bdf-ba34-1785b254f0a6", - "type": [ - "VerifiableCredential", - "GS1PrefixLicenceCredential" - ], - "issuer": { - "id": "did:web:ssi.eecc.de", - "image": "https://id.eecc.de/assets/img/logo_big.png", - "name": "EECC" - }, - "issuanceDate": "2023-05-16T11:11:04Z", - "credentialSubject": { - "id": "did:web:ssi.eecc.de", - "partyGLN": "4047111000006", - "licenceValue": "4047111", - "organizationName": "European EPC Competence Center" - }, - "proof": { - "type": "Ed25519Signature2020", - "created": "2023-05-16T11:11:04Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", - "proofValue": "z4GKxTZzba6B4Bs2vCETRi3frX9vFn9m61ZsNKPaxH9yZBX382DLGY1yRvuKgwbxUag88eJQfALuBdBHhp3m2r5nA" - } - }, - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://ref.gs1.org/gs1/vc/licence-context/", - "https://ssi.eecc.de/api/registry/context", - "https://w3id.org/security/suites/ed25519-2020/v1" - ], - "id": "https://ssi.eecc.de/api/registry/vc/740fb8fb-b530-4807-8c8f-6e0943581e4c", - "type": [ - "VerifiableCredential", - "GS1PrefixLicenceCredential" - ], - "issuer": { - "id": "did:web:ssi.eecc.de", - "image": "https://id.eecc.de/assets/img/logo_big.png", - "name": "EECC" - }, - "issuanceDate": "2023-05-16T10:49:51Z", - "credentialSubject": { - "id": "did:web:eecc.de", - "partyGLN": "4047111000006", - "organizationName": "European EPC Competence Center", - "licenceValue": "4047111" - }, - "proof": { - "type": "Ed25519Signature2020", - "created": "2023-05-16T10:49:51Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", - "proofValue": "zmwT31nYTHhecy594pxMCMmuidTAEWsPSZsXAYj84bQfGSDUD8z5uCNy6WfyqwnkG58YQiZAYjevMdw6nBZaPTJ6" - } - } - ], - "holder": "did:web:ssi.eecc.de", - "proof": { - "type": "Ed25519Signature2020", - "created": "2023-06-14T14:23:34Z", - "verificationMethod": "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", - "proofPurpose": "authentication", - "challenge": "12345", - "proofValue": "z2aP6o5AeB9sD6N1H26X6bvwcLPjeRpAvBhYo6xixYX7AvieVNFH4K6brZxhdKnXCw7BKYnhf7wfqQTrmGugwL5hr" - } -} + "https://ref.gs1.org/gs1/vc/licence-context/", + "https://ssi.eecc.de/api/registry/context", + "https://w3id.org/security/suites/ed25519-2020/v1", + ], + id: "https://ssi.eecc.de/api/registry/vc/740fb8fb-b530-4807-8c8f-6e0943581e4c", + type: ["VerifiableCredential", "GS1PrefixLicenceCredential"], + issuer: { + id: "did:web:ssi.eecc.de", + image: "https://id.eecc.de/assets/img/logo_big.png", + name: "EECC", + }, + issuanceDate: "2023-05-16T10:49:51Z", + credentialSubject: { + id: "did:web:eecc.de", + partyGLN: "4047111000006", + organizationName: "European EPC Competence Center", + licenceValue: "4047111", + }, + proof: { + type: "Ed25519Signature2020", + created: "2023-05-16T10:49:51Z", + proofPurpose: "assertionMethod", + verificationMethod: + "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", + proofValue: + "zmwT31nYTHhecy594pxMCMmuidTAEWsPSZsXAYj84bQfGSDUD8z5uCNy6WfyqwnkG58YQiZAYjevMdw6nBZaPTJ6", + }, + }, + ], + holder: "did:web:ssi.eecc.de", + proof: { + type: "Ed25519Signature2020", + created: "2023-06-14T14:23:34Z", + verificationMethod: + "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", + proofPurpose: "authentication", + challenge: "12345", + proofValue: + "z2aP6o5AeB9sD6N1H26X6bvwcLPjeRpAvBhYo6xixYX7AvieVNFH4K6brZxhdKnXCw7BKYnhf7wfqQTrmGugwL5hr", + }, +}; const domainPresentation: any = { - "@context": [ + "@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", - "https://w3id.org/security/suites/ed25519-2020/v1" - ], - "type": [ - "VerifiablePresentation" - ], - "verifiableCredential": [ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "ipfs://QmUnuMAyvbhTsyvgcomPsDo8dx6K9bUsyo3EiFb8f1CfeY", - "https://w3id.org/security/suites/ed25519-2020/v1" - ], - "id": "https://ssi.eecc.de/api/registry/vc/ee1a4d4e-abb7-4b38-b9a2-037ba33d6193", - "type": [ - "VerifiableCredential", - "ProductPassportCredential" - ], - "issuer": { - "id": "did:web:ssi.eecc.de", - "image": "https://id.eecc.de/assets/img/logo_big.png", - "name": "EECC" - }, - "issuanceDate": "2023-02-27T22:55:55Z", - "credentialSubject": { - "id": "did:iota:ebfeb1f712ebc6f1c276e12ec21", - "brand": "Patek Philippe" - }, - "proof": { - "type": "Ed25519Signature2020", - "created": "2023-02-27T22:55:55Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", - "proofValue": "z5ti1RiEYjrc9177Cfir2jkmezMcBWr5GmeLo2MGaqoPcqxK8cRpg6Sx9WVMyDDGBNxkJPswTK9vaERhJLYD8jyLY" - } - } - ], - "holder": "did:web:ssi.eecc.de", - "proof": { - "type": "Ed25519Signature2020", - "created": "2023-06-14T15:23:16Z", - "verificationMethod": "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", - "proofPurpose": "authentication", - "challenge": "12345", - "domain": "ssi.eecc.de/verifier", - "proofValue": "z3tHBT6WT7dgULQiEH6CDdp3izjVCQUVGY1XSzMHMUq4PoPg3HnsYSFnkbdJsp1zE6yYUXkzfy4ceFM6qHpS7LTxk" - } -} + "ipfs://QmUnuMAyvbhTsyvgcomPsDo8dx6K9bUsyo3EiFb8f1CfeY", + "https://w3id.org/security/suites/ed25519-2020/v1", + ], + id: "https://ssi.eecc.de/api/registry/vc/ee1a4d4e-abb7-4b38-b9a2-037ba33d6193", + type: ["VerifiableCredential", "ProductPassportCredential"], + issuer: { + id: "did:web:ssi.eecc.de", + image: "https://id.eecc.de/assets/img/logo_big.png", + name: "EECC", + }, + issuanceDate: "2023-02-27T22:55:55Z", + credentialSubject: { + id: "did:iota:ebfeb1f712ebc6f1c276e12ec21", + brand: "Patek Philippe", + }, + proof: { + type: "Ed25519Signature2020", + created: "2023-02-27T22:55:55Z", + proofPurpose: "assertionMethod", + verificationMethod: + "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", + proofValue: + "z5ti1RiEYjrc9177Cfir2jkmezMcBWr5GmeLo2MGaqoPcqxK8cRpg6Sx9WVMyDDGBNxkJPswTK9vaERhJLYD8jyLY", + }, + }, + ], + holder: "did:web:ssi.eecc.de", + proof: { + type: "Ed25519Signature2020", + created: "2023-06-14T15:23:16Z", + verificationMethod: + "did:web:ssi.eecc.de#z6MkoHWsmSZnHisAxnVdokYHnXaVqWFZ4H33FnNg13zyymxd", + proofPurpose: "authentication", + challenge: "12345", + domain: "ssi.eecc.de/verifier", + proofValue: + "z3tHBT6WT7dgULQiEH6CDdp3izjVCQUVGY1XSzMHMUq4PoPg3HnsYSFnkbdJsp1zE6yYUXkzfy4ceFM6qHpS7LTxk", + }, +}; const statusPresentation: any = { - "@context": [ + "@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", - "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" - } -} + "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(server).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 presentation with status", async () => { + const res = await request(server) + .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(server).post("/api/verifier").query({ challenge: '12345' }).send([multiPresentation]); - 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(server) + .post("/api/verifier") + .query({ challenge: "12345" }) + .send([multiPresentation]); + 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 & domain", async () => { - const res = await request(server).post("/api/verifier").query({ challenge: '12345', domain: 'ssi.eecc.de/verifier' }).send([domainPresentation]); - 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 & domain", async () => { + const res = await request(server) + .post("/api/verifier") + .query({ challenge: "12345", domain: "ssi.eecc.de/verifier" }) + .send([domainPresentation]); + 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("Falsify single presentation with wrong challenge", async () => { - const res = await request(server).post("/api/verifier").query({ challenge: 'falseChallenge', domain: 'ssi.eecc.de/verifier' }).send([domainPresentation]); - expect(res.statusCode).toEqual(200); - expect(res.body[0]).toHaveProperty('verified'); - expect(res.body[0].verified).toBe(false); - expect(res.body[0]).toHaveProperty('error'); - expect(res.body[0].error.name).toBe('VerificationError'); - }); + test("Falsify single presentation with wrong challenge", async () => { + const res = await request(server) + .post("/api/verifier") + .query({ challenge: "falseChallenge", domain: "ssi.eecc.de/verifier" }) + .send([domainPresentation]); + expect(res.statusCode).toEqual(200); + expect(res.body[0]).toHaveProperty("verified"); + expect(res.body[0].verified).toBe(false); + expect(res.body[0]).toHaveProperty("error"); + expect(res.body[0].error.name).toBe("VerificationError"); + }); -}); \ No newline at end of file + test("Verify single SD-JWT presentation with challenge & domain", async () => { + const res = await request(server) + .post("/api/verifier") + .send([sdJWTPresentation]); + expect(res.statusCode).toEqual(200); + expect(res.body[0]).toHaveProperty("verified"); + console.log(res.body[0]); + expect(res.body[0].verified).toBe(true); + }); +}); diff --git a/api/package-lock.json b/api/package-lock.json index ebf19ca..52b700e 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "vc-verifier", - "version": "1.6.5", + "version": "1.7.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vc-verifier", - "version": "1.6.5", + "version": "1.7.4", "license": "AGPL-3.0", "dependencies": { "@digitalbazaar/data-integrity": "^1.4.1", @@ -17,7 +17,10 @@ "@digitalbazaar/vc": "^6.0.2", "@digitalbazaar/vc-revocation-list": "^5.0.0", "@digitalbazaar/vc-status-list": "^7.0.0", + "@or13/did-jwk": "^0.0.4", + "@transmute/vc-jwt-sd": "^0.0.4", "cors": "^2.8.5", + "crypto": "^1.0.1", "did-resolver": "^4.1.0", "dotenv": "^16.3.1", "express": "^4.18.2", @@ -25,6 +28,7 @@ "http-status-codes": "^2.2.0", "jsonld": "github:european-epc-competence-center/jsonld.js#cachefix", "jsonld-signatures": "^11.2.1", + "moment": "^2.29.4", "node-fetch": "^3.3.2", "parse-link-header": "^2.0.0", "web-did-resolver": "^2.0.27" @@ -1434,6 +1438,18 @@ } ] }, + "node_modules/@or13/did-jwk": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@or13/did-jwk/-/did-jwk-0.0.4.tgz", + "integrity": "sha512-iWAH7PeeI9YNZ9qQ5q9Nhz9ej4iJwZieEZbfVmPagWOuxRnpcu5umI5GYcGOB9oC+04SX1FMzwrC0U1mXKLngA==", + "dependencies": { + "jose": "^4.9.2", + "yargs": "^17.5.1" + }, + "bin": { + "did-jwk": "src/cli.js" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1458,6 +1474,35 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@transmute/cose": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@transmute/cose/-/cose-0.0.13.tgz", + "integrity": "sha512-v1GJOYWrX08cZCdl2Nfk4QAoZwssxmbj5VJ2MhTf9fg8c/Pb9XtNKVRodyzON8YARtKSomO0H9/GfUSydkzGyg==", + "dependencies": { + "@transmute/rfc9162": "^0.0.4", + "cbor-web": "^9.0.0", + "cose-js": "^0.8.4", + "jose": "^4.14.4" + } + }, + "node_modules/@transmute/rfc9162": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@transmute/rfc9162/-/rfc9162-0.0.4.tgz", + "integrity": "sha512-ChTvT9RN2MnQTvN56FhH6kCLwNRcnepeaSw9lXUdcP+2tD2/1yPrX7txzB8yhDnzG51QUej1vdKxXSpF1d4yIA==" + }, + "node_modules/@transmute/vc-jwt-sd": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@transmute/vc-jwt-sd/-/vc-jwt-sd-0.0.4.tgz", + "integrity": "sha512-vc1Fep2FPWApjxWxodcLGDr2tFr8Tqy4ZhMnMNPOrMK9pFiGKUaPfifPYM5mjzhtdEkwushJVBOVbo+EGxLyCw==", + "dependencies": { + "@transmute/cose": "^0.0.13", + "cbor-web": "9.0.0", + "jose": "^4.13.1", + "json-pointer": "^0.6.2", + "moment": "^2.29.4", + "yaml": "^2.3.1" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1771,6 +1816,11 @@ "node": ">=0.4.0" } }, + "node_modules/aes-cbc-mac": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/aes-cbc-mac/-/aes-cbc-mac-1.0.1.tgz", + "integrity": "sha512-F1U0qNBNyrW82LRdQvYWKOpljZFnJ9LBUGWNCzCBTC3/+Fki77KgDrfJ+rBVlCpcmMT3jDEGhG61RMVeyHAKog==" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1790,7 +1840,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1809,6 +1858,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -1848,6 +1902,14 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2002,6 +2064,11 @@ "node": ">=8" } }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -2046,6 +2113,11 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, "node_modules/browserslist": { "version": "4.21.10", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", @@ -2179,6 +2251,25 @@ "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.8.tgz", "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" }, + "node_modules/cbor": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", + "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=12.19" + } + }, + "node_modules/cbor-web": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cbor-web/-/cbor-web-9.0.0.tgz", + "integrity": "sha512-bTCCiR0brj9RShibl2wirK+y99JuZBhCLXo114N7HtwjKnLa43D14X9Ay0SdIslCYhyOH6kagtMp9HhVkqyPqQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/cborg": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/cborg/-/cborg-1.10.2.tgz", @@ -2263,7 +2354,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -2384,6 +2474,22 @@ "node": ">= 0.10" } }, + "node_modules/cose-js": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/cose-js/-/cose-js-0.8.4.tgz", + "integrity": "sha512-TYt82olRQS/iZyb/qchG4KZSnzVBlOVXJjTCCgwKZUIkqqFyUIA+JG8OQdX5+ZyiWLj9W118Kuf3/jII0Gb/Bg==", + "dependencies": { + "aes-cbc-mac": "^1.0.1", + "any-promise": "^1.3.0", + "cbor": "^8.1.0", + "elliptic": "^6.4.0", + "node-hkdf-sync": "^1.0.0", + "node-rsa": "^1.1.1" + }, + "engines": { + "node": ">=12.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2436,6 +2542,12 @@ "node": ">= 8" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." + }, "node_modules/crypto-ld": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/crypto-ld/-/crypto-ld-7.0.0.tgz", @@ -2608,6 +2720,20 @@ "integrity": "sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ==", "dev": true }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -2623,8 +2749,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -2647,7 +2772,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -2823,6 +2947,14 @@ "node": ">= 10.0.0" } }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2908,6 +3040,11 @@ "node": ">=8" } }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -3007,7 +3144,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -3134,6 +3270,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -3143,6 +3288,16 @@ "node": ">=8" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -3288,7 +3443,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4016,6 +4170,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.14.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz", + "integrity": "sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4053,6 +4215,14 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dependencies": { + "foreach": "^2.0.4" + } + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4323,6 +4493,16 @@ "node": ">=6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4334,6 +4514,14 @@ "node": "*" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4394,6 +4582,17 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-hkdf-sync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", + "integrity": "sha512-dKe4X44YGLxPITIMdbnVw0URGTLw2lUUKmClar5iz53ZRrl3xGKk3k7KsBASRMHGh6bJCE1Gmuirb/QaL7rJuw==", + "dependencies": { + "vows": "0.5.13" + }, + "engines": { + "node": ">= 0.6.5" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4406,6 +4605,14 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "dependencies": { + "asn1": "^0.2.4" + } + }, "node_modules/nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", @@ -4479,6 +4686,14 @@ "node": ">=4" } }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "engines": { + "node": ">=12.19" + } + }, "node_modules/nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -4897,7 +5112,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5235,7 +5449,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5249,7 +5462,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5756,6 +5968,20 @@ "node": ">= 0.8" } }, + "node_modules/vows": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/vows/-/vows-0.5.13.tgz", + "integrity": "sha512-m2+3s/ITbI95b7uzsBnA7oZg7/bJZFb+sKp2TUvomrIQxjtuNOivf/yOxAAyhEAX8gkIogoXfBJRmj9ys8r3gQ==", + "dependencies": { + "eyes": ">=0.1.6" + }, + "bin": { + "vows": "bin/vows" + }, + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -5815,7 +6041,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5858,7 +6083,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -5868,11 +6092,18 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -5890,7 +6121,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -6999,6 +7229,15 @@ "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" }, + "@or13/did-jwk": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@or13/did-jwk/-/did-jwk-0.0.4.tgz", + "integrity": "sha512-iWAH7PeeI9YNZ9qQ5q9Nhz9ej4iJwZieEZbfVmPagWOuxRnpcu5umI5GYcGOB9oC+04SX1FMzwrC0U1mXKLngA==", + "requires": { + "jose": "^4.9.2", + "yargs": "^17.5.1" + } + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -7023,6 +7262,35 @@ "@sinonjs/commons": "^3.0.0" } }, + "@transmute/cose": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@transmute/cose/-/cose-0.0.13.tgz", + "integrity": "sha512-v1GJOYWrX08cZCdl2Nfk4QAoZwssxmbj5VJ2MhTf9fg8c/Pb9XtNKVRodyzON8YARtKSomO0H9/GfUSydkzGyg==", + "requires": { + "@transmute/rfc9162": "^0.0.4", + "cbor-web": "^9.0.0", + "cose-js": "^0.8.4", + "jose": "^4.14.4" + } + }, + "@transmute/rfc9162": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@transmute/rfc9162/-/rfc9162-0.0.4.tgz", + "integrity": "sha512-ChTvT9RN2MnQTvN56FhH6kCLwNRcnepeaSw9lXUdcP+2tD2/1yPrX7txzB8yhDnzG51QUej1vdKxXSpF1d4yIA==" + }, + "@transmute/vc-jwt-sd": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@transmute/vc-jwt-sd/-/vc-jwt-sd-0.0.4.tgz", + "integrity": "sha512-vc1Fep2FPWApjxWxodcLGDr2tFr8Tqy4ZhMnMNPOrMK9pFiGKUaPfifPYM5mjzhtdEkwushJVBOVbo+EGxLyCw==", + "requires": { + "@transmute/cose": "^0.0.13", + "cbor-web": "9.0.0", + "jose": "^4.13.1", + "json-pointer": "^0.6.2", + "moment": "^2.29.4", + "yaml": "^2.3.1" + } + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -7321,6 +7589,11 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "aes-cbc-mac": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/aes-cbc-mac/-/aes-cbc-mac-1.0.1.tgz", + "integrity": "sha512-F1U0qNBNyrW82LRdQvYWKOpljZFnJ9LBUGWNCzCBTC3/+Fki77KgDrfJ+rBVlCpcmMT3jDEGhG61RMVeyHAKog==" + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -7333,8 +7606,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -7344,6 +7616,11 @@ "color-convert": "^2.0.1" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -7380,6 +7657,14 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7500,6 +7785,11 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -7537,6 +7827,11 @@ "fill-range": "^7.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, "browserslist": { "version": "4.21.10", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", @@ -7618,6 +7913,19 @@ "resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-1.0.8.tgz", "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" }, + "cbor": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", + "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", + "requires": { + "nofilter": "^3.1.0" + } + }, + "cbor-web": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cbor-web/-/cbor-web-9.0.0.tgz", + "integrity": "sha512-bTCCiR0brj9RShibl2wirK+y99JuZBhCLXo114N7HtwjKnLa43D14X9Ay0SdIslCYhyOH6kagtMp9HhVkqyPqQ==" + }, "cborg": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/cborg/-/cborg-1.10.2.tgz", @@ -7670,7 +7978,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -7766,6 +8073,19 @@ "vary": "^1" } }, + "cose-js": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/cose-js/-/cose-js-0.8.4.tgz", + "integrity": "sha512-TYt82olRQS/iZyb/qchG4KZSnzVBlOVXJjTCCgwKZUIkqqFyUIA+JG8OQdX5+ZyiWLj9W118Kuf3/jII0Gb/Bg==", + "requires": { + "aes-cbc-mac": "^1.0.1", + "any-promise": "^1.3.0", + "cbor": "^8.1.0", + "elliptic": "^6.4.0", + "node-hkdf-sync": "^1.0.0", + "node-rsa": "^1.1.1" + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -7806,6 +8126,11 @@ "which": "^2.0.1" } }, + "crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" + }, "crypto-ld": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/crypto-ld/-/crypto-ld-7.0.0.tgz", @@ -7934,6 +8259,20 @@ "integrity": "sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ==", "dev": true }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -7943,8 +8282,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -7963,8 +8301,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-html": { "version": "1.0.3", @@ -8100,6 +8437,11 @@ "swagger-ui-express": "^4.3.0" } }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==" + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8163,6 +8505,11 @@ "path-exists": "^4.0.0" } }, + "foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -8236,8 +8583,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.2.1", @@ -8319,12 +8665,31 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", "dev": true }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8436,8 +8801,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-fn": { "version": "2.1.0", @@ -8989,6 +9353,11 @@ } } }, + "jose": { + "version": "4.14.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.6.tgz", + "integrity": "sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9017,6 +9386,14 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "requires": { + "foreach": "^2.0.4" + } + }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9204,6 +9581,16 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9212,6 +9599,11 @@ "brace-expansion": "^1.1.7" } }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -9249,6 +9641,14 @@ "formdata-polyfill": "^4.0.10" } }, + "node-hkdf-sync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-hkdf-sync/-/node-hkdf-sync-1.0.0.tgz", + "integrity": "sha512-dKe4X44YGLxPITIMdbnVw0URGTLw2lUUKmClar5iz53ZRrl3xGKk3k7KsBASRMHGh6bJCE1Gmuirb/QaL7rJuw==", + "requires": { + "vows": "0.5.13" + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -9261,6 +9661,14 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, + "node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "requires": { + "asn1": "^0.2.4" + } + }, "nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", @@ -9317,6 +9725,11 @@ } } }, + "nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==" + }, "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -9614,8 +10027,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "resolve": { "version": "1.22.4", @@ -9867,7 +10279,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -9878,7 +10289,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -10202,6 +10612,14 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "vows": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/vows/-/vows-0.5.13.tgz", + "integrity": "sha512-m2+3s/ITbI95b7uzsBnA7oZg7/bJZFb+sKp2TUvomrIQxjtuNOivf/yOxAAyhEAX8gkIogoXfBJRmj9ys8r3gQ==", + "requires": { + "eyes": ">=0.1.6" + } + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -10252,7 +10670,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -10282,19 +10699,22 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==" + }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -10308,8 +10728,7 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, "yn": { "version": "3.1.1", diff --git a/api/package.json b/api/package.json index e4b73d3..513877a 100644 --- a/api/package.json +++ b/api/package.json @@ -34,7 +34,10 @@ "@digitalbazaar/vc": "^6.0.2", "@digitalbazaar/vc-revocation-list": "^5.0.0", "@digitalbazaar/vc-status-list": "^7.0.0", + "@or13/did-jwk": "^0.0.4", + "@transmute/vc-jwt-sd": "^0.0.4", "cors": "^2.8.5", + "crypto": "^1.0.1", "did-resolver": "^4.1.0", "dotenv": "^16.3.1", "express": "^4.18.2", @@ -42,6 +45,7 @@ "http-status-codes": "^2.2.0", "jsonld": "github:european-epc-competence-center/jsonld.js#cachefix", "jsonld-signatures": "^11.2.1", + "moment": "^2.29.4", "node-fetch": "^3.3.2", "parse-link-header": "^2.0.0", "web-did-resolver": "^2.0.27" @@ -64,4 +68,4 @@ "overrides": { "jsonld": "$jsonld" } -} \ No newline at end of file +} diff --git a/api/src/routes/verify/index.ts b/api/src/routes/verify/index.ts index daac82f..91b1346 100644 --- a/api/src/routes/verify/index.ts +++ b/api/src/routes/verify/index.ts @@ -1,98 +1,108 @@ -import { NextFunction, Request, response, Response } from 'express'; -import { StatusCodes } from 'http-status-codes'; +import { NextFunction, Request, response, Response } from "express"; +import { StatusCodes } from "http-status-codes"; -import { Verifier, fetch_json } from '../../services/index.js'; - -const VC_REGISTRY = process.env.VC_REGISTRY ? process.env.VC_REGISTRY : 'https://ssi.eecc.de/api/registry/vcs/'; +import { Verifier, fetch_json } from "../../services/index.js"; +const VC_REGISTRY = process.env.VC_REGISTRY + ? process.env.VC_REGISTRY + : "https://ssi.eecc.de/api/registry/vcs/"; export class VerifyRoutes { - - fetchAndVerify = async (req: Request, res: Response, next: NextFunction): Promise => { - - try { - - // fetch credential - let credential; - - try { - - credential = await fetch_json(decodeURIComponent(req.params.vcid)) as Verifiable; - - } catch (error) { - console.log(error) - return res.status(StatusCodes.NOT_FOUND).send('Credential not found!\n' + error); - } - - const result = await Verifier.verify(credential); - - return res.status(StatusCodes.OK).json(result); - - } catch (error) { - return res.status(StatusCodes.BAD_REQUEST).send('Something went wrong!\n' + error); - } - + fetchAndVerify = async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { + try { + // fetch credential + let credential; + + try { + credential = (await fetch_json( + decodeURIComponent(req.params.vcid) + )) as Verifiable; + } catch (error) { + console.log(error); + return res + .status(StatusCodes.NOT_FOUND) + .send("Credential not found!\n" + error); + } + + const result = await Verifier.verify(credential); + + return res.status(StatusCodes.OK).json(result); + } catch (error) { + return res + .status(StatusCodes.BAD_REQUEST) + .send("Something went wrong!\n" + error); } - - verify = async (req: Request, res: Response, next: NextFunction): Promise => { - - try { - - // Support W3C and JWT namespaces - const challenge = req.query.challenge || req.query.nonce; - const domain = req.query.domain || req.query.audience || req.query.aud; - - if (challenge && typeof challenge != 'string') throw new Error('The challenge/nonce must be provided as a string!'); - - if (domain && typeof domain != 'string') throw new Error('The domain/audience must be provided as a string!'); - - let tasks = Promise.all(req.body.map(function (verifialbe: Verifiable) { - - return Verifier.verify(verifialbe, challenge, domain); - - })); - - // wait for all verifiables to be verified - const results = await tasks; - - return res.status(StatusCodes.OK).json(results); - - } catch (error) { - return res.status(StatusCodes.BAD_REQUEST).send('Something went wrong verifying!\n' + error); - } - + }; + + verify = async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { + try { + // Support W3C and JWT namespaces + const challenge = req.query.challenge || req.query.nonce; + const domain = req.query.domain || req.query.audience || req.query.aud; + + if (challenge && typeof challenge != "string") + throw new Error("The challenge/nonce must be provided as a string!"); + + if (domain && typeof domain != "string") + throw new Error("The domain/audience must be provided as a string!"); + + let tasks = Promise.all( + req.body.map(function (verifiable: Verifiable) { + return Verifier.verify(verifiable, challenge, domain); + }) + ); + + // wait for all verifiables to be verified + const results = await tasks; + + return res.status(StatusCodes.OK).json(results); + } catch (error) { + console.error(error); + return res + .status(StatusCodes.BAD_REQUEST) + .send("Something went wrong verifying!\n" + error); } - - verifySubjectsVCs = async (req: Request, res: Response, next: NextFunction): Promise => { - - try { - - // fetch credentials - let credentials: Verifiable[]; - - try { - - credentials = await fetch_json(VC_REGISTRY + encodeURIComponent(req.params.subjectId)) as [Verifiable]; - - } catch (error) { - return res.status(StatusCodes.NOT_FOUND); - } - - let tasks = Promise.all(credentials.map(function (vc) { - - return Verifier.verify(vc); - - })); - - // wait for all vcs to be verified - const results = await tasks; - - return res.status(StatusCodes.OK).json(results); - - } catch (error) { - return res.status(StatusCodes.BAD_REQUEST).send('Something went wrong!\n' + error); - } - + }; + + verifySubjectsVCs = async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { + try { + // fetch credentials + let credentials: Verifiable[]; + + try { + credentials = (await fetch_json( + VC_REGISTRY + encodeURIComponent(req.params.subjectId) + )) as [Verifiable]; + } catch (error) { + return res.status(StatusCodes.NOT_FOUND); + } + + let tasks = Promise.all( + credentials.map(function (vc) { + return Verifier.verify(vc); + }) + ); + + // wait for all vcs to be verified + const results = await tasks; + + return res.status(StatusCodes.OK).json(results); + } catch (error) { + return res + .status(StatusCodes.BAD_REQUEST) + .send("Something went wrong!\n" + error); } - -} \ No newline at end of file + }; +} diff --git a/api/src/services/documentLoader/context/index.ts b/api/src/services/documentLoader/context/index.ts index ce02592..00d58b9 100644 --- a/api/src/services/documentLoader/context/index.ts +++ b/api/src/services/documentLoader/context/index.ts @@ -1,4 +1,4 @@ -import { statusList2021Context } from "./status-list-2021"; +import { statusList2021Context } from "./status-list-2021.js"; export const contexts = new Map([ ['https://w3id.org/vc/status-list/2021/v1', statusList2021Context] diff --git a/api/src/services/documentLoader/custom/jwk.ts b/api/src/services/documentLoader/custom/jwk.ts new file mode 100644 index 0000000..43f92b2 --- /dev/null +++ b/api/src/services/documentLoader/custom/jwk.ts @@ -0,0 +1,56 @@ +import { DIDDocument, DIDResolutionResult, DIDResolver, ParsedDID } from 'did-resolver'; +// @ts-ignore +import JWK from '@or13/did-jwk'; + + +export function getResolver(): Record { + async function resolve(did: string, parsed: ParsedDID): Promise { + + let err = null + + const didDocumentMetadata = {} + let didDocument: DIDDocument | null = null + + do { + try { + didDocument = JWK.resolve(did); + } catch (error) { + err = `resolver_error: DID must resolve to a valid https URL containing a JSON document: ${error}` + break + } + + // TODO: this excludes the use of query params + const docIdMatchesDid = didDocument?.id === did + if (!docIdMatchesDid) { + err = 'resolver_error: DID document id does not match requested did' + // break // uncomment this when adding more checks + } + // eslint-disable-next-line no-constant-condition + } while (false) + + const contentType = + typeof didDocument?.['@context'] !== 'undefined' ? 'application/did+ld+json' : 'application/did+json' + + + + if (err) { + return { + didDocument, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: err, + }, + } + } else { + return { + didDocument, + didDocumentMetadata, + didResolutionMetadata: { contentType }, + } + } + } + + return { jwk: resolve } +} + diff --git a/api/src/services/documentLoader/didresolver.ts b/api/src/services/documentLoader/didresolver.ts index 4538d0a..dfcc60f 100644 --- a/api/src/services/documentLoader/didresolver.ts +++ b/api/src/services/documentLoader/didresolver.ts @@ -1,14 +1,16 @@ import { Resolver } from 'did-resolver'; import * as key from './custom/key.js'; import * as web from 'web-did-resolver'; +import * as jwk from './custom/jwk.js'; export function getResolver() { return new Resolver({ + ...jwk.getResolver(), ...key.getResolver(), ...web.getResolver() //...you can flatten multiple resolver methods into the Resolver }, - { - cache: true - }) + { + cache: true + }) } \ No newline at end of file diff --git a/api/src/services/documentLoader/index.ts b/api/src/services/documentLoader/index.ts index 222c56d..fd393da 100644 --- a/api/src/services/documentLoader/index.ts +++ b/api/src/services/documentLoader/index.ts @@ -6,42 +6,46 @@ import { contexts } from './context/index.js'; const cache = contexts; -const documentLoader: Promise = jsonldSignatures.extendContextLoader(async (url: string) => { - - // Fetch did documents - if (url.startsWith('did:')) { - - const [did, verificationMethod] = url.split('#') +const dereferenceDID = async (url: string): Promise => { - // fetch document - const didDocument: any = (await getResolver().resolve(url)).didDocument + const [did, verificationMethod] = url.split('#') - // if a verifcation method of the DID document is queried (not yet implemented in the official resolver) - if (verificationMethod && didDocument) { + // fetch document + const didDocument: any = (await getResolver().resolve(url)).didDocument - const verificationMethodDoc: any | undefined = didDocument.verificationMethod.filter(function (method: any) { - return method.id === url || method.id === verificationMethod; - })[0]; + // if a verifcation method of the DID document is queried (not yet implemented in the official resolver) + if (verificationMethod && didDocument) { - if (!verificationMethodDoc) - throw new jsonldSignatures.VerificationError( - new Error(`${verificationMethod} is an unknown verification method for ${did}`) - ); + const verificationMethodDoc: any | undefined = didDocument.verificationMethod.filter(function (method: any) { + return method.id === url || method.id === '#' + verificationMethod; + })[0]; - return { - contextUrl: null, - documentUrl: url, - // deliver verification method with the DID doc context - document: Object.assign(verificationMethodDoc, { '@context': verificationMethodDoc['@context'] || didDocument['@context'] }), - }; - - } + if (!verificationMethodDoc) + throw new Error(`${verificationMethod} is an unknown verification method for ${did}`); return { contextUrl: null, documentUrl: url, - document: didDocument, + // deliver verification method with the DID doc context + document: Object.assign(verificationMethodDoc, { '@context': verificationMethodDoc['@context'] || didDocument['@context'] }), }; + + } + + return { + contextUrl: null, + documentUrl: url, + document: didDocument, + }; + +} + +const documentLoader: Promise = jsonldSignatures.extendContextLoader(async (url: string) => { + + // Fetch did documents + if (url.startsWith('did:')) { + + return await dereferenceDID(url); } let document = cache.get(url); @@ -71,4 +75,4 @@ const documentLoader: Promise = jsonldSignatures.extendContextLoader(async }); -export { documentLoader } \ No newline at end of file +export { documentLoader, dereferenceDID } \ No newline at end of file diff --git a/api/src/services/fetch/index.ts b/api/src/services/fetch/index.ts index 14d1dd2..19bc41b 100644 --- a/api/src/services/fetch/index.ts +++ b/api/src/services/fetch/index.ts @@ -8,6 +8,15 @@ const HEADERS = { const IPFS_GATEWAYS = ['ipfs.io', 'ipfs.ssi.eecc.de'].concat(process.env.IPFS_GATEWAYS ? process.env.IPFS_GATEWAYS.split(',') : []); +export function isURL(value: string | URL): boolean { + let url; + try { + url = new URL(value); + } catch (_) { + return false; + } + return url.protocol === "http:" || url.protocol === "https:"; +} export async function fetch_jsonld(url: string): Promise { diff --git a/api/src/services/verifier/dataintegrity.ts b/api/src/services/verifier/dataintegrity.ts new file mode 100644 index 0000000..e51bf88 --- /dev/null +++ b/api/src/services/verifier/dataintegrity.ts @@ -0,0 +1,153 @@ +// @ts-ignore +import { verifyCredential, verify } from '@digitalbazaar/vc'; +// @ts-ignore +import { Ed25519Signature2018 } from '@digitalbazaar/ed25519-signature-2018'; +// @ts-ignore +import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020'; +// @ts-ignore +import { checkStatus as checkStatus2020 } from '@digitalbazaar/vc-revocation-list'; +// @ts-ignore +import { checkStatus as checkStatus2021 } from '@digitalbazaar/vc-status-list'; +// @ts-ignore +import * as ecdsaSd2023Cryptosuite from '@digitalbazaar/ecdsa-sd-2023-cryptosuite'; +// @ts-ignore +import { DataIntegrityProof } from '@digitalbazaar/data-integrity'; +// @ts-ignore +import jsigs from 'jsonld-signatures'; + +import { documentLoader } from '../documentLoader/index.js'; + +const { createVerifyCryptosuite } = ecdsaSd2023Cryptosuite; +const { purposes: { AssertionProofPurpose } } = jsigs; + +function getSuite(proof: Proof): unknown[] { + + switch (proof.type) { + + case 'Ed25519Signature2018': return new Ed25519Signature2018(); + + case 'Ed25519Signature2020': return new Ed25519Signature2020(); + + case 'DataIntegrityProof': return new DataIntegrityProof({ + cryptosuite: createVerifyCryptosuite() + }); + + default: throw new Error(`${proof.type} not implemented`); + } + +} + +function getSuites(proof: Proof | Proof[]): unknown[] { + + var suites: unknown[] = [] + + if (Array.isArray(proof)) { + proof.forEach((proof: Proof) => suites.push(getSuite(proof))); + } else { + suites = [getSuite(proof)] + } + + return suites; + +} + +function getPresentationStatus(presentation: VerifiablePresentation): CredentialStatus[] | CredentialStatus | undefined { + + if (!presentation.verifiableCredential) return 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; + + let statusTypes = []; + + if (Array.isArray(credentialStatus)) { + statusTypes = credentialStatus.map(cs => cs.type); + } + else statusTypes = [credentialStatus.type] + + if (statusTypes.includes('StatusList2021Entry')) return checkStatus2021; + + if (statusTypes.includes('RevocationList2020Status')) return checkStatus2020; + + throw new Error(`${statusTypes} not implemented`); + +} + +export async function verifyDataIntegrityProof(verifiable: Verifiable, challenge?: string, domain?: string): Promise { + + const suite = getSuites(verifiable.proof); + + let result; + + if (verifiable.type.includes('VerifiableCredential')) { + + 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(credential, { + suite, + purpose: new AssertionProofPurpose(), + documentLoader, + // give it to jsigs anyway - does not get verified + checkStatus + }); + + } else { + + 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(presentation.proof) ? presentation.proof[0].challenge : presentation.proof.challenge); + + + const checkStatus = getCheckStatus( + getPresentationStatus(presentation) + ); + + result = await verify({ presentation, suite, documentLoader, challenge, domain, checkStatus }); + + } + + if (!result) throw Error('Provided verifiable object is of unknown type!'); + + // make non enumeratable errors enumeratable for the respsonse + if (result.error && !result.error.errors) result.error.name = result.error.message; + + return result; + +} \ No newline at end of file diff --git a/api/src/services/verifier/index.ts b/api/src/services/verifier/index.ts index 9e032fa..112179f 100644 --- a/api/src/services/verifier/index.ts +++ b/api/src/services/verifier/index.ts @@ -1,157 +1,20 @@ -// @ts-ignore -import { verifyCredential, verify } from '@digitalbazaar/vc'; -// @ts-ignore -import { Ed25519Signature2018 } from '@digitalbazaar/ed25519-signature-2018'; -// @ts-ignore -import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020'; -// @ts-ignore -import { checkStatus as checkStatus2020 } from '@digitalbazaar/vc-revocation-list'; -// @ts-ignore -import { checkStatus as checkStatus2021 } from '@digitalbazaar/vc-status-list'; -// @ts-ignore -import * as ecdsaSd2023Cryptosuite from '@digitalbazaar/ecdsa-sd-2023-cryptosuite'; -// @ts-ignore -import { DataIntegrityProof } from '@digitalbazaar/data-integrity'; -// @ts-ignore -import jsigs from 'jsonld-signatures'; - -import { documentLoader } from '../documentLoader/index.js'; - -const { createVerifyCryptosuite } = ecdsaSd2023Cryptosuite; -const { purposes: { AssertionProofPurpose } } = jsigs; - -function getSuite(proof: Proof): unknown[] { - - switch (proof.type) { - - case 'Ed25519Signature2018': return new Ed25519Signature2018(); - - case 'Ed25519Signature2020': return new Ed25519Signature2020(); - - case 'DataIntegrityProof': return new DataIntegrityProof({ - cryptosuite: createVerifyCryptosuite() - }); - - default: throw new Error(`${proof.type} not implemented`); - } - -} - -function getSuites(proof: Proof | Proof[]): unknown[] { - - var suites: unknown[] = [] - - if (Array.isArray(proof)) { - proof.forEach((proof: Proof) => suites.push(getSuite(proof))); - } else { - suites = [getSuite(proof)] - } - - return suites; - -} - -function getPresentationStatus(presentation: VerifiablePresentation): CredentialStatus[] | CredentialStatus | undefined { - - if (!presentation.verifiableCredential) return 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; - - let statusTypes = []; - - if (Array.isArray(credentialStatus)) { - statusTypes = credentialStatus.map(cs => cs.type); - } - else statusTypes = [credentialStatus.type] - - if (statusTypes.includes('StatusList2021Entry')) return checkStatus2021; - - if (statusTypes.includes('RevocationList2020Status')) return checkStatus2020; - - throw new Error(`${statusTypes} not implemented`); - -} - +import { verifyDataIntegrityProof } from "./dataintegrity.js"; +import { verifySDJWT } from "./sdjwt.js"; export class Verifier { - - static async verify(verifiable: Verifiable, challenge?: string, domain?: string): Promise { - - const suite = getSuites(verifiable.proof); - - let result; - - if (verifiable.type.includes('VerifiableCredential')) { - - 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(credential, { - suite, - purpose: new AssertionProofPurpose(), - documentLoader, - // give it to jsigs anyway - does not get verified - checkStatus - }); - - } else { - - 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(presentation.proof) ? presentation.proof[0].challenge : presentation.proof.challenge); - - - const checkStatus = getCheckStatus( - getPresentationStatus(presentation) - ); - - result = await verify({ presentation, suite, documentLoader, challenge, domain, checkStatus }); - - } - - if (!result) throw Error('Provided verifiable object is of unknown type!'); - - // make non enumeratable errors enumeratable for the respsonse - if (result.error && !result.error.errors) result.error.name = result.error.message; - - return result; - } - -} \ No newline at end of file + static async verify( + verifiable: Verifiable | string, + challenge?: string, + domain?: string + ): Promise { + // vc-jwt or sd-jwt + if (typeof verifiable == "string" && verifiable.startsWith("ey")) + return await verifySDJWT(verifiable, challenge, domain, false); + + // DataIntegrityProof + if (typeof verifiable == "object") + return await verifyDataIntegrityProof(verifiable, challenge, domain); + + throw new Error("Unrecognized credential type!"); + } +} diff --git a/api/src/services/verifier/sdjwt.ts b/api/src/services/verifier/sdjwt.ts new file mode 100644 index 0000000..6357510 --- /dev/null +++ b/api/src/services/verifier/sdjwt.ts @@ -0,0 +1,96 @@ +import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc"; +import type { Verifier, KbVerifier } from "@sd-jwt/types"; +import { ES256, digest } from "@sd-jwt/crypto-nodejs"; +import { importJWK, JWK, JWTPayload, jwtVerify } from "jose"; +import { dereferenceDID } from "../documentLoader/index.js"; +import { fetch_json, isURL } from "../fetch/index.js"; + +async function getPublicKey(issuer: string, kid: string): Promise { + if (isURL(issuer)) { + const response = await fetch_json(`${issuer}/.well-known/jwt-vc-issuer`) + .then((r: any) => r.data) + .catch(() => { + throw new Error("Error on fetching public key from " + issuer); + }); + const key = response.jwks.keys.find((key: any) => key.kid === kid); + if (!key) { + throw new Error("Key not found und well-known"); + } + return key; + } + if (kid && kid.startsWith("did:")) { + const absoluteDidUrl = + kid && kid.startsWith(issuer) ? kid : `${issuer}#${kid}`; + const { publicKeyJwk } = (await dereferenceDID(absoluteDidUrl))?.document; + if (!publicKeyJwk) { + throw new Error("Key not found in did"); + } + return publicKeyJwk; + } + throw new Error("Could not resolve public keys"); +} + +export async function verifySDJWT( + verifiable: string, + nonce?: string, + aud?: string, + enforceKeyBinding?: boolean +): Promise { + try { + let sdjwtInstance: SDJwtVcInstance; + /** + * The verifier function. This function will verify the signature of the vc. + * @param data encoded header and payload of the jwt + * @param signature signature of the jwt + * @returns true if the signature is valid + */ + const verifier: Verifier = async (data, signature) => { + const decodedVC = await sdjwtInstance.decode(`${data}.${signature}`); + const payload = decodedVC.jwt?.payload as JWTPayload; + const header = decodedVC.jwt?.header as JWK; + const publicKey = await getPublicKey( + payload.iss as string, + header.kid as string + ); + const verify = await ES256.getVerifier(publicKey); + if (nonce && payload.nonce !== nonce) { + return false; + } + if (aud && payload.aud !== aud) { + return false; + } + return verify(data, signature); + }; + + /** + * The kb verifier function. This function will verify the signature for the key binding + * @param data + * @param signature + * @param payload + * @returns + */ + const kbVerifier: KbVerifier = async (data, signature, payload) => { + if (!payload.cnf) { + throw new Error("No cnf found in the payload"); + } + const key = await importJWK(payload.cnf.jwk as JWK, "ES256"); + return jwtVerify(`${data}.${signature}`, key).then( + () => true, + () => false + ); + }; + + // initialize the sdjwt instance. + sdjwtInstance = new SDJwtVcInstance({ + hasher: digest, + verifier, + ...(enforceKeyBinding && { kbVerifier }), + }); + // verify the presentation. + await sdjwtInstance.verify(verifiable, [], false); + return Promise.resolve({ verified: true }); + } catch (e) { + console.error(e); + return Promise.reject({ verified: false, error: (e as Error).message }); + } +} diff --git a/api/src/types.d.ts b/api/src/types.d.ts index 1bbef57..4d080e7 100644 --- a/api/src/types.d.ts +++ b/api/src/types.d.ts @@ -35,4 +35,9 @@ type VerifiablePresentation = Verifiable & { id: string | URL; holder: string | any; verifiableCredential: VerifiableCredential | VerifiableCredential[]; +} + +type VerificationResult = { + verified: boolean; + error?: any; } \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bdec954..3fab5d8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,18 +1,19 @@ { "name": "verifier_frontend", - "version": "1.5.2", + "version": "1.7.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "verifier_frontend", - "version": "1.5.2", + "version": "1.7.4", "license": "AGPL-3.0", "dependencies": { "axios": "^1.1.3", "bootstrap": "^5.2.3", "bootstrap-icons": "^1.9.1", "export-from-json": "^1.7.0", + "jose": "^4.15.4", "jshashes": "^1.0.8", "jsonld": "^8.1.0", "pdfmake": "^0.2.7", @@ -12173,6 +12174,14 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-beautify": { "version": "1.14.7", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.7.tgz", @@ -27754,6 +27763,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==" + }, "js-beautify": { "version": "1.14.7", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.7.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3466c86..e9b47bb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,6 +28,7 @@ "bootstrap": "^5.2.3", "bootstrap-icons": "^1.9.1", "export-from-json": "^1.7.0", + "jose": "^4.15.4", "jshashes": "^1.0.8", "jsonld": "^8.1.0", "pdfmake": "^0.2.7", @@ -61,4 +62,4 @@ "sass": "^1.32.7", "sass-loader": "^12.0.0" } -} \ No newline at end of file +} diff --git a/frontend/src/api.js b/frontend/src/api.js index e067c0b..d115c9c 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1,6 +1,6 @@ import axios from 'axios' -const axiosInstance = axios.create({ - baseURL: process.env.VERIFIER_API || 'https://ssi.eecc.de/api/verifier', +const axiosInstance = axios.create({ + baseURL: process.env.VERIFIER_API || 'http://localhost:3000/api/verifier', timeout: 5000, headers: { 'Accept': 'application/ld+json,application/json,*/*' diff --git a/frontend/src/components/PresentationRequest.vue b/frontend/src/components/PresentationRequest.vue index af42e28..274ae71 100644 --- a/frontend/src/components/PresentationRequest.vue +++ b/frontend/src/components/PresentationRequest.vue @@ -1,167 +1,223 @@ \ No newline at end of file + diff --git a/frontend/src/utils.js b/frontend/src/utils.js index f47bb08..e587a8e 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,95 +1,122 @@ -import jsonld from 'jsonld'; -import { demoAuthPresentation } from './store/demoAuth'; +import jsonld from 'jsonld' +import { demoAuthPresentation } from './store/demoAuth' +import { decodeProtectedHeader, decodeJwt } from 'jose' export const VerifiableType = { - CREDENTIAL: 'VerifiableCredential', - PRESENTATION: 'VerifiablePresentation' -}; + CREDENTIAL: 'VerifiableCredential', + PRESENTATION: 'VerifiablePresentation', +} const IPFS_GATEWAYS = ['ipfs.io', 'ipfs.ssi.eecc.de'] export function isURL(url) { - if (typeof url != 'string') return false; - return url.startsWith('https://'); + if (typeof url != 'string') return false + return url.startsWith('https://') } export function getCredentialValue(value) { - return typeof value === 'object' ? value.value || value['@value'] || JSON.stringify(value, null, 2) : value; + return typeof value === 'object' + ? value.value || value['@value'] || JSON.stringify(value, null, 2) + : value } export function getPlainCredential(credential) { - var clean_credential = { ...credential }; - delete clean_credential.verified; - delete clean_credential.revoked; - delete clean_credential.suspended; - delete clean_credential.status; - delete clean_credential.presentation; - delete clean_credential.context; - return clean_credential; + var clean_credential = { ...credential } + delete clean_credential.verified + delete clean_credential.revoked + delete clean_credential.suspended + delete clean_credential.status + delete clean_credential.presentation + delete clean_credential.context + return clean_credential } export function getVerifiableType(verifiable) { - if (verifiable.type.includes(VerifiableType.PRESENTATION)) return VerifiableType.PRESENTATION; - return VerifiableType.CREDENTIAL; + if (verifiable.type.includes(VerifiableType.PRESENTATION)) + return VerifiableType.PRESENTATION + return VerifiableType.CREDENTIAL } export function getCredentialType(credential) { - return credential.type.length > 1 ? credential.type.filter((c) => c != 'VerifiableCredential')[0] : credential.type[0]; + return credential.type.length > 1 + ? credential.type.filter((c) => c != 'VerifiableCredential')[0] + : credential.type[0] } export function getHolder(presentation) { - if (presentation.holder) return presentation.holder; - const proof = Array.isArray(presentation.proof) ? presentation.proof[0] : presentation.proof - return proof.verificationMethod.split('#')[0]; + if (presentation.holder) return presentation.holder + const proof = Array.isArray(presentation.proof) + ? presentation.proof[0] + : presentation.proof + return proof.verificationMethod.split('#')[0] } -export async function fetchIPFS(IPFSUrl) { - - var document; - - await Promise.any(IPFS_GATEWAYS.map(async (gateway) => { - - return await fetch(`https://${gateway}/ipfs/${IPFSUrl.split('ipfs://')[1]}`); - - })) - .then((result) => { - - document = result; - - }) - .catch((error) => { - console.error(error) - }) - - if (!document) throw Error('Fetching from IPFS failed'); - - return document; - +export function SDJWTtoVP(jwt) { + console.log(jwt) + const decodedHeader = decodeProtectedHeader(jwt) + const decodedJWT = decodeJwt(jwt) + console.log(decodedHeader) + console.log(decodedJWT) + + if (!decodedHeader.kid) { + throw new Error('Missing kid in JWT header') + } + + return { + jwt, + type: [VerifiableType.PRESENTATION], + holder: decodedHeader.kid.split('#')[0], + issuer: decodedJWT.iss, + } } +export async function fetchIPFS(IPFSUrl) { + var document + + await Promise.any( + IPFS_GATEWAYS.map(async (gateway) => { + return await fetch( + `https://${gateway}/ipfs/${IPFSUrl.split('ipfs://')[1]}`, + ) + }), + ) + .then((result) => { + document = result + }) + .catch((error) => { + console.error(error) + }) + + if (!document) throw Error('Fetching from IPFS failed') + + return document +} const documentLoader = async (url) => { - let document; - if (url.startsWith('ipfs://')) { - - document = await fetchIPFS(url) - - } else document = await fetch(url); - - return { - contextUrl: null, - document: await document.json(), - documentUrl: url - }; -}; - + let document + if (url.startsWith('ipfs://')) { + document = await fetchIPFS(url) + } else document = await fetch(url) + + return { + contextUrl: null, + document: await document.json(), + documentUrl: url, + } +} export async function getContext(credential) { - const resolved = await jsonld.processContext(await jsonld.processContext(null, null), credential, { documentLoader }); - return resolved.mappings; + const resolved = await jsonld.processContext( + await jsonld.processContext(null, null), + credential, + { documentLoader }, + ) + return resolved.mappings } export function isDemoAuth(auth) { - return auth != undefined && JSON.stringify(auth) == JSON.stringify(demoAuthPresentation); + return ( + auth != undefined && + JSON.stringify(auth) == JSON.stringify(demoAuthPresentation) + ) } - diff --git a/frontend/src/views/Entry.vue b/frontend/src/views/Entry.vue index 609cbd0..80e0dd0 100644 --- a/frontend/src/views/Entry.vue +++ b/frontend/src/views/Entry.vue @@ -40,7 +40,7 @@
-
Credential Id
+
Credential Id / VC-JWT