From 9915eabb3fd9b0cf75ace1376a218281ffd1372f Mon Sep 17 00:00:00 2001 From: F-Node-Karlsruhe Date: Tue, 23 Apr 2024 14:29:54 +0200 Subject: [PATCH] init openid library Signed-off-by: F-Node-Karlsruhe --- api/__tests__/presentation.test.ts | 482 +++++++++++++++-------------- api/package-lock.json | 16 +- api/package.json | 1 + api/src/routes/verify/index.ts | 192 ++++++------ api/src/services/verifier/sdjwt.ts | 101 ++---- package-lock.json | 161 ++++++++++ package.json | 7 + 7 files changed, 559 insertions(+), 401 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/api/__tests__/presentation.test.ts b/api/__tests__/presentation.test.ts index 5699b93..7f19f09 100644 --- a/api/__tests__/presentation.test.ts +++ b/api/__tests__/presentation.test.ts @@ -2,259 +2,269 @@ import request from "supertest"; import server from "../src/index"; -afterAll(done => { - server.close(); - done(); +afterAll((done) => { + server.close(); + done(); }); -const sdJWTPresentation: string = "eyJhbGciOiJFUzM4NCIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpGUXlJc0luZ2lPaUpJWTNRd1gyTmliRzVzYkV0VFYxOWFVVnBtUlhkRGIwbFFaRlpWYW1kTlNXMW1OWEZzUkRKVWVEaG1TM0ZLUTFoeVYxSnlOMVZwU2xCWGVsTldhbXBHSWl3aWVTSTZJbTAxTlhka1ZUbG1hVXROWDFWQmR6WlpTMWs1Ym1kdGEyZzRlbWhKVG1JM1V6bFhTVzVtTFhvelIxaENOR3RST0dJNGVtOVBla3RvWjB0elRqTm9jRzBpTENKamNuWWlPaUpRTFRNNE5DSjkjMCJ9.eyJhcnJheV93aXRoX3JlY3Vyc2l2ZV9zZCI6WyJib3JpbmciLHsiZm9vIjoiYmFyIiwiX3NkIjpbInFnSG0yNjAyTlFZQjh4Z0RISmEwRTZIeDJxZnM0UWNzRURqREcyRDlaOUUiXX0sW3siLi4uIjoiNWExUFRkX0xJUVd0djVBMWdxM0R5QXZyQTh0QnlVZDJaMHF0SlJGSzF4RSJ9LHsiLi4uIjoiWW1tN0d4MnZNSlVHaDVjQ2tsZUpkaEV3MWVlTHNLRWF2SVZFT1hBMXVOUSJ9XV0sInRlc3QyIjpbeyIuLi4iOiJ2RDYzS2Z1bGFUT2NlNlh6WUlsXy1RSGFCUUVxR1lfVXA0S1lCell5QUJZIn0seyIuLi4iOiI1Z3Q4ZzlkMlNXVUZIZklZYWJXUFM5TlIxTTVNb3pKbnoyNUlGR01tdmhjIn1dLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpGUXlJc0luZ2lPaUpJWTNRd1gyTmliRzVzYkV0VFYxOWFVVnBtUlhkRGIwbFFaRlpWYW1kTlNXMW1OWEZzUkRKVWVEaG1TM0ZLUTFoeVYxSnlOMVZwU2xCWGVsTldhbXBHSWl3aWVTSTZJbTAxTlhka1ZUbG1hVXROWDFWQmR6WlpTMWs1Ym1kdGEyZzRlbWhKVG1JM1V6bFhTVzVtTFhvelIxaENOR3RST0dJNGVtOVBla3RvWjB0elRqTm9jRzBpTENKamNuWWlPaUpRTFRNNE5DSjkiLCJpYXQiOjE2OTQ3Njk1NDUsImV4cCI6MTcyNjM5MTk0NSwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0IiwieCI6InNoY24xeE43ODBLRWRuZVFVVXZ3bUhmQUM0Y0VWb2ZGSjlWc002RDlfLTdlaGtLdVdUN2NSUTZ3R0ZnU3hWbFEiLCJ5IjoiSHl2V1NYc25sT3RvRzJzNjB6a0ZTN2FmR01oU0pXUW5TX3BRaURfN0ptNHNQd3RMSnVnMy1OOW5pQjJOVl9DXyJ9fX0.JoD9gePif8jfnlf-45KwNMe1TuHWKWbWqbECzPHeY9vQVBMd3Sd0VrB2aXSlyKqDkZPPpQsVk5nSC3ofOEUAm7HHfHN42Vzr3-4XNPfoTJmarPZNFMtbjgWayBWyUuSX~WyI0SHdhRHk1dU9SczdMbjZ0b1NpR0N3IiwgImJheiIsIHsicXV4IjogInF1dXgifV0~WyJvdVl4cUZMRGZxZW5VUHptUWZBWEh3IiwgImJhciJd~WyJUMHpiWURfR1FSVVdocjJuYjc2SmpRIiwgImZvbyJd~WyJRRldFR1ZwNlNpZ19rdzJHYml1dnZBIiwgImJhciJd~eyJhbGciOiJFUzM4NCIsInR5cCI6ImtiK2p3dCJ9.eyJub25jZSI6Ijk4NzY1NDMyMTAiLCJhdWQiOiJkaWQ6d2ViOnZlcmlmaWVyLmV4YW1wbGUiLCJpYXQiOjE2OTQ3Njk1NDV9.zl9qYTnspJXq1EhMsQmgSfKixyVdruYX0JdGC26SygAKlvjL5UlUKb6LhjxF_tGw3MV5WIXdfGODlqjHmynPnRNAh8snXxn4j1ONUhuNHDqbxBEhGZKXIEJyIjj2Ibcf" +const sdJWTPresentation: string = + "eyJ0eXAiOiJzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJ0ZXN0Ijp7Il9zZCI6WyJqVEszMHNleDZhYV9kUk1KSWZDR056Q0FwbVB5MzRRNjNBa3QzS3hhSktzIl19LCJfc2QiOlsiME9nMi1ReG95eW1UOGNnVzZZUjVSSFpQLUJuR2tHUi1NM2otLV92RWlzSSIsIkcwZ3lHNnExVFMyUlQxMkZ3X2RRRDVVcjlZc1AwZlVWOXVtQWdGMC1jQ1EiXSwiX3NkX2FsZyI6InNoYS0yNTYifQ.ggEyE4SeDO2Hu3tol3VLmi7NQj56yKzKQDaafocgkLrUBdivghohtzrfcbrMN7CRufJ_Cnh0EL54kymXLGTdDQ~WyIwNGU0MjAzOWU4ZWFiOWRjIiwiYSIsIjEiXQ~WyIwOGE1Yjc5MjMyYjAzYzBhIiwiMSJd~WyJiNWE2YjUzZGQwYTFmMGIwIiwienp6IiwieHh4Il0~WyIxYzdmOTE4ZTE0MjA2NzZiIiwiZm9vIiwiYmFyIl0~WyJmZjYxYzQ5ZGU2NjFiYzMxIiwiYXJyIixbeyIuLi4iOiJTSG96VW5KNUpkd0ZtTjVCbXB5dXZCWGZfZWRjckVvcExPYThTVlBFUmg0In0sIjIiLHsiX3NkIjpbIkpuODNhZkp0OGx4NG1FMzZpRkZyS2U2R2VnN0dlVUQ4Z3UwdVo3NnRZcW8iXX1dXQ~"; 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://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/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", + }, +}; 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 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 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 & 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", 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("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("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 SD-JWT presentation with challenge & domain", async () => { - const res = await request(server).post("/api/verifier").query({ nonce: '9876543210', aud: 'did:web:verifier.example' }).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); - }); + 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") + .query({ nonce: "9876543210", aud: "did:web:verifier.example" }) + .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 64a0044..52b700e 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "vc-verifier", - "version": "1.7.2", + "version": "1.7.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vc-verifier", - "version": "1.7.2", + "version": "1.7.4", "license": "AGPL-3.0", "dependencies": { "@digitalbazaar/data-integrity": "^1.4.1", @@ -20,6 +20,7 @@ "@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", @@ -2541,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", @@ -8119,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", diff --git a/api/package.json b/api/package.json index 82cc6df..513877a 100644 --- a/api/package.json +++ b/api/package.json @@ -37,6 +37,7 @@ "@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", diff --git a/api/src/routes/verify/index.ts b/api/src/routes/verify/index.ts index b517687..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 (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) { - 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/verifier/sdjwt.ts b/api/src/services/verifier/sdjwt.ts index 218c4e1..ded5421 100644 --- a/api/src/services/verifier/sdjwt.ts +++ b/api/src/services/verifier/sdjwt.ts @@ -1,79 +1,36 @@ -import SD from '@transmute/vc-jwt-sd'; -import moment from 'moment'; -import crypto from "crypto"; -import { base64url, decodeProtectedHeader, decodeJwt } from "jose"; -import { dereferenceDID } from '../documentLoader/index.js'; - -const vcjwtsd = (SD as any).default; - -const digester = (name: 'sha-256' = 'sha-256'): any => { - if (name !== 'sha-256') { - throw new Error('hash function not supported') - } - return { - name, - digest: async (json: string) => { - return base64url.encode(crypto.createHash("sha256").update(json).digest()); - } - }; +import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc"; +import type { DisclosureFrame } from "@sd-jwt/types"; +import { ES256, digest, generateSalt } from "@sd-jwt/crypto-nodejs"; + +const createSignerVerifier = async () => { + const { privateKey, publicKey } = await ES256.generateKeyPair(); + return { + signer: await ES256.getSigner(privateKey), + verifier: await ES256.getVerifier(publicKey), + }; }; +const { signer, verifier } = await createSignerVerifier(); -export async function verifySDJWT(verifiable: string, nonce?: string, aud?: string): Promise { - - const parsed = vcjwtsd.Parse.compact(verifiable); - - const decodedHeader = decodeProtectedHeader(parsed.jwt) - - const decodedPayload = decodeJwt(parsed.jwt) - - try { - - const alg = decodedHeader.alg; - - if (!alg) throw new Error('No alg specified in the jwt header!'); - - // TODO support urls and plain keys as well - const iss = (decodedHeader.iss || decodedPayload.iss) as string - - const kid = decodedHeader.kid as string - - const absoluteDidUrl = kid && kid.startsWith(iss) ? kid : `${iss}#${kid}` - - let { publicKeyJwk } = (await dereferenceDID(absoluteDidUrl)).document; - - // in case of did:key use jwk from jwt - if (!publicKeyJwk) publicKeyJwk = (decodedPayload.cnf as any).jwk; - - const verifier = new vcjwtsd.Verifier({ - alg, - digester: digester(), - verifier: { - verify: async (token: string) => { - const parsed = vcjwtsd.Parse.compact(token) - const verifier = await vcjwtsd.JWS.verifier(publicKeyJwk) - return verifier.verify(parsed.jwt) - } - } - }) - const verified = await verifier.verify({ - presentation: verifiable, - nonce, - aud - }) - - const now = moment().unix() - - if (now < verified.claimset.iat) throw new Error('Credential is not yet valid!') - if (verified.claimset.exp && now > verified.claimset.exp) throw new Error('Credential expired!') +export async function verifySDJWT( + verifiable: string, + nonce?: string, + aud?: string +): Promise { + const sdjwt = new SDJwtVcInstance({ + signer, + verifier, + signAlg: "EdDSA", + hasher: digest, + hashAlg: "SHA-256", + saltGenerator: generateSalt, + }); - console.log(verified.claimset) + const decodedObject = await sdjwt.decode(verifiable); - } catch (error: any) { - console.log(error) - return { verified: false, error: error.message }; - } + const verified = await sdjwt.verify(verifiable, []); - return { verified: true }; + console.log(verified); -} \ No newline at end of file + return { verified: true }; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b1876a9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,161 @@ +{ + "name": "vc-verifier", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "@sd-jwt/crypto-nodejs": "^0.6.1", + "@sd-jwt/sd-jwt-vc": "^0.6.1", + "@sd-jwt/types": "^0.6.1" + } + }, + "node_modules/@sd-jwt/core": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/core/-/core-0.6.1.tgz", + "integrity": "sha512-egFTb23o6BGWF93vnjopN02rSiC1HOOnkk9BI8Kao3jz9ipZAHdO6wF7gwfZm5Nlol/Kd1/KSLhbOUPYt++FjA==", + "dependencies": { + "@sd-jwt/decode": "0.6.1", + "@sd-jwt/present": "0.6.1", + "@sd-jwt/types": "0.6.1", + "@sd-jwt/utils": "0.6.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@sd-jwt/crypto-nodejs": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/crypto-nodejs/-/crypto-nodejs-0.6.1.tgz", + "integrity": "sha512-dkzZYNPlswCCnn4PfR/+GFeo/tI7QEZv4vqzpAp8/7v62Cpi/VjdyMmW5+SlHg3sTw82eL/B9aFTcFlnbQuvPA==", + "engines": { + "node": ">=16" + } + }, + "node_modules/@sd-jwt/decode": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/decode/-/decode-0.6.1.tgz", + "integrity": "sha512-QgTIoYd5zyKKLgXB4xEYJTrvumVwtsj5Dog0v0L9UH9ZvHekDaeexS247X7A4iSdzTvmZzUpGskgABOa4D8NmQ==", + "dependencies": { + "@sd-jwt/types": "0.6.1", + "@sd-jwt/utils": "0.6.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@sd-jwt/present": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/present/-/present-0.6.1.tgz", + "integrity": "sha512-QRD3TUDLj4PqQNZ70bBxh8FLLrOE9mY8V9qiZrJSsaDOLFs2p1CtZG+v9ig62fxFYJZMf4bWKwYjz+qqGAtxCg==", + "dependencies": { + "@sd-jwt/decode": "0.6.1", + "@sd-jwt/types": "0.6.1", + "@sd-jwt/utils": "0.6.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/sd-jwt-vc/-/sd-jwt-vc-0.6.1.tgz", + "integrity": "sha512-eF7NAFvedBCx+vrw4TVY3evUz5rAG8/FtB/CUudYEigKcpanLgfuNGhk93D45k+lLDG0b24w+qorqbpLZzHA2g==", + "dependencies": { + "@sd-jwt/core": "0.6.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@sd-jwt/types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/types/-/types-0.6.1.tgz", + "integrity": "sha512-LKpABZJGT77jNhOLvAHIkNNmGqXzyfwBT+6r+DN9zNzMx1CzuNR0qXk1GMUbast9iCfPkGbnEpUv/jHTBvlIvg==", + "engines": { + "node": ">=16" + } + }, + "node_modules/@sd-jwt/utils": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/utils/-/utils-0.6.1.tgz", + "integrity": "sha512-1NHZ//+GecGQJb+gSdDicnrHG0DvACUk9jTnXA5yLZhlRjgkjyfJLNsCZesYeCyVp/SiyvIC9B+JwoY4kI0TwQ==", + "dependencies": { + "@sd-jwt/types": "0.6.1", + "js-base64": "^3.7.6" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + } + }, + "dependencies": { + "@sd-jwt/core": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/core/-/core-0.6.1.tgz", + "integrity": "sha512-egFTb23o6BGWF93vnjopN02rSiC1HOOnkk9BI8Kao3jz9ipZAHdO6wF7gwfZm5Nlol/Kd1/KSLhbOUPYt++FjA==", + "requires": { + "@sd-jwt/decode": "0.6.1", + "@sd-jwt/present": "0.6.1", + "@sd-jwt/types": "0.6.1", + "@sd-jwt/utils": "0.6.1" + } + }, + "@sd-jwt/crypto-nodejs": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/crypto-nodejs/-/crypto-nodejs-0.6.1.tgz", + "integrity": "sha512-dkzZYNPlswCCnn4PfR/+GFeo/tI7QEZv4vqzpAp8/7v62Cpi/VjdyMmW5+SlHg3sTw82eL/B9aFTcFlnbQuvPA==" + }, + "@sd-jwt/decode": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/decode/-/decode-0.6.1.tgz", + "integrity": "sha512-QgTIoYd5zyKKLgXB4xEYJTrvumVwtsj5Dog0v0L9UH9ZvHekDaeexS247X7A4iSdzTvmZzUpGskgABOa4D8NmQ==", + "requires": { + "@sd-jwt/types": "0.6.1", + "@sd-jwt/utils": "0.6.1" + } + }, + "@sd-jwt/present": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/present/-/present-0.6.1.tgz", + "integrity": "sha512-QRD3TUDLj4PqQNZ70bBxh8FLLrOE9mY8V9qiZrJSsaDOLFs2p1CtZG+v9ig62fxFYJZMf4bWKwYjz+qqGAtxCg==", + "requires": { + "@sd-jwt/decode": "0.6.1", + "@sd-jwt/types": "0.6.1", + "@sd-jwt/utils": "0.6.1" + } + }, + "@sd-jwt/sd-jwt-vc": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/sd-jwt-vc/-/sd-jwt-vc-0.6.1.tgz", + "integrity": "sha512-eF7NAFvedBCx+vrw4TVY3evUz5rAG8/FtB/CUudYEigKcpanLgfuNGhk93D45k+lLDG0b24w+qorqbpLZzHA2g==", + "requires": { + "@sd-jwt/core": "0.6.1" + } + }, + "@sd-jwt/types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/types/-/types-0.6.1.tgz", + "integrity": "sha512-LKpABZJGT77jNhOLvAHIkNNmGqXzyfwBT+6r+DN9zNzMx1CzuNR0qXk1GMUbast9iCfPkGbnEpUv/jHTBvlIvg==" + }, + "@sd-jwt/utils": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@sd-jwt/utils/-/utils-0.6.1.tgz", + "integrity": "sha512-1NHZ//+GecGQJb+gSdDicnrHG0DvACUk9jTnXA5yLZhlRjgkjyfJLNsCZesYeCyVp/SiyvIC9B+JwoY4kI0TwQ==", + "requires": { + "@sd-jwt/types": "0.6.1", + "js-base64": "^3.7.6" + } + }, + "js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c0df970 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@sd-jwt/crypto-nodejs": "^0.6.1", + "@sd-jwt/sd-jwt-vc": "^0.6.1", + "@sd-jwt/types": "^0.6.1" + } +}