diff --git a/__tests__/shared/credentialStatus.ts b/__tests__/shared/credentialStatus.ts index 4dcda0b9b..9f66f8e03 100644 --- a/__tests__/shared/credentialStatus.ts +++ b/__tests__/shared/credentialStatus.ts @@ -102,9 +102,9 @@ export default (testContext: { }) expect(vc).toHaveProperty('proof.jwt') - const verified = await agent.verifyCredential({ credential: vc }) + const result = await agent.verifyCredential({ credential: vc }) expect(callsCounter).toHaveBeenCalledTimes(1) - expect(verified).toBeTruthy() + expect(result.verified).toEqual(true) }) it('should check credentialStatus for revoked JWT credential', async () => { @@ -114,9 +114,9 @@ export default (testContext: { }) expect(vc).toHaveProperty('proof.jwt') - const verified = await agent.verifyCredential({ credential: vc }) + const result = await agent.verifyCredential({ credential: vc }) expect(callsCounter).toHaveBeenCalledTimes(1) - expect(verified).toBeFalsy() + expect(result.verified).toEqual(false) }) it('should fail checking credentialStatus with exception during verification', async () => { @@ -152,9 +152,9 @@ export default (testContext: { }) expect(vc).toHaveProperty('proof.jws') - const verified = await agent.verifyCredential({ credential: vc }) + const result = await agent.verifyCredential({ credential: vc }) expect(callsCounter).toHaveBeenCalledTimes(1) - expect(verified).toBeTruthy() + expect(result.verified).toEqual(true) }) it('should check credentialStatus for revoked JSON-LD credential', async () => { @@ -164,9 +164,9 @@ export default (testContext: { }) expect(vc).toHaveProperty('proof.jws') - const verified = await agent.verifyCredential({ credential: vc }) + const result = await agent.verifyCredential({ credential: vc }) expect(callsCounter).toHaveBeenCalledTimes(1) - expect(verified).toBeFalsy() + expect(result.verified).toEqual(false) }) it('should check credentialStatus for EIP712 credential', async () => { @@ -176,9 +176,9 @@ export default (testContext: { }) expect(vc).toHaveProperty('proof.proofValue') - const verified = await agent.verifyCredential({ credential: vc }) + const result = await agent.verifyCredential({ credential: vc }) expect(callsCounter).toHaveBeenCalledTimes(1) - expect(verified).toBeTruthy() + expect(result.verified).toEqual(true) }) it('should check credentialStatus for revoked EIP712 credential', async () => { @@ -188,9 +188,9 @@ export default (testContext: { }) expect(vc).toHaveProperty('proof.proofValue') - const verified = await agent.verifyCredential({ credential: vc }) + const result = await agent.verifyCredential({ credential: vc }) expect(callsCounter).toHaveBeenCalledTimes(1) - expect(verified).toBeFalsy() + expect(result.verified).toEqual(false) }) }) @@ -235,7 +235,7 @@ export default (testContext: { // TODO It`s an exception flow an it'd be better to throw an exception instead of returning false await expect(agent.verifyCredential({ credential: vc })).rejects.toThrow( - `The credential status can't be verified by the agent`, + `invalid_setup: The credential status can't be verified because there is no ICredentialStatusVerifier plugin installed.`, ) }) }) diff --git a/__tests__/shared/verifiableDataJWT.ts b/__tests__/shared/verifiableDataJWT.ts index 70f61c359..694dfbf27 100644 --- a/__tests__/shared/verifiableDataJWT.ts +++ b/__tests__/shared/verifiableDataJWT.ts @@ -7,9 +7,12 @@ import { IIdentifier, TAgent, TKeyType, + VerifiableCredential, + VerifiablePresentation, } from '../../packages/core/src' import { ICredentialIssuer } from '../../packages/credential-w3c/src' import { decodeJWT } from 'did-jwt' +import { VC_JWT_ERROR } from 'did-jwt-vc' type ConfiguredAgent = TAgent @@ -25,14 +28,10 @@ export default (testContext: { beforeAll(async () => { await testContext.setup() agent = testContext.getAgent() + identifier = await agent.didManagerCreate({ kms: 'local', provider: 'did:key' }) }) afterAll(testContext.tearDown) - it('should create identifier', async () => { - identifier = await agent.didManagerCreate({ kms: 'local' }) - expect(identifier).toHaveProperty('did') - }) - it('should create verifiable credential in JWT', async () => { const verifiableCredential = await agent.createVerifiableCredential({ credential: { @@ -274,7 +273,7 @@ export default (testContext: { } beforeAll(async () => { - const imported = await agent.didManagerImport(importedDID) + await agent.didManagerImport(importedDID) }) it('signs JWT with ES256K', async () => { @@ -299,5 +298,172 @@ export default (testContext: { }) }) }) + + describe('credential verification policies', () => { + let credential: VerifiableCredential + + beforeAll(async () => { + const issuanceDate = '2019-08-19T09:15:20.000Z' // 1566206120 + const expirationDate = '2019-08-20T10:42:31.000Z' // 1566297751 + credential = await agent.createVerifiableCredential({ + proofFormat: 'jwt', + credential: { + issuer: identifier.did, + issuanceDate, + expirationDate, + credentialSubject: { + hello: 'world', + }, + }, + }) + }) + + it('can verify credential at a particular time', async () => { + const result = await agent.verifyCredential({ credential }) + expect(result.verified).toBe(false) + expect(result?.error?.errorCode).toEqual(VC_JWT_ERROR.INVALID_JWT) + + const result2 = await agent.verifyCredential({ + credential, + policies: { now: 1566297000 }, + }) + expect(result2.verified).toBe(true) + }) + + it('can override expiry check', async () => { + const result = await agent.verifyCredential({ + credential, + policies: { expirationDate: false }, + }) + expect(result.verified).toBe(true) + }) + + it('can override issuance check', async () => { + const result = await agent.verifyCredential({ + credential, + policies: { issuanceDate: false, now: 1565000000 }, + }) + expect(result.verified).toBe(true) + }) + + it('can override audience check', async () => { + const cred = await agent.createVerifiableCredential({ + proofFormat: 'jwt', + credential: { + issuer: identifier.did, + aud: 'override me', + credentialSubject: { + hello: 'world', + }, + }, + }) + const result = await agent.verifyCredential({ credential: cred }) + expect(result.verified).toBe(false) + expect(result.error?.errorCode).toEqual(VC_JWT_ERROR.INVALID_AUDIENCE) + + const result2 = await agent.verifyCredential({ credential: cred, policies: { audience: false } }) + expect(result2.verified).toBe(true) + }) + + it('can override credentialStatus check', async () => { + const cred = await agent.createVerifiableCredential({ + proofFormat: 'jwt', + credential: { + issuer: identifier.did, + credentialSubject: { + hello: 'world', + }, + credentialStatus: { + id: 'override me', + type: 'ThisMethodDoesNotExist2022', + }, + }, + }) + await expect(agent.verifyCredential({ credential: cred })).rejects.toThrow(/^invalid_setup:/) + + const result2 = await agent.verifyCredential({ + credential: cred, + policies: { credentialStatus: false }, + }) + expect(result2.verified).toBe(true) + }) + }) + + describe('presentation verification policies', () => { + let credential: VerifiableCredential + let presentation: VerifiablePresentation + + beforeAll(async () => { + const issuanceDate = '2019-08-19T09:15:20.000Z' // 1566206120 + const expirationDate = '2019-08-20T10:42:31.000Z' // 1566297751 + credential = await agent.createVerifiableCredential({ + proofFormat: 'jwt', + credential: { + issuer: identifier.did, + credentialSubject: { + hello: 'world', + }, + }, + }) + presentation = await agent.createVerifiablePresentation({ + proofFormat: 'jwt', + presentation: { + holder: identifier.did, + verifiableCredential: [credential], + issuanceDate, + expirationDate, + }, + }) + }) + + it('can verify presentation at a particular time', async () => { + const result = await agent.verifyPresentation({ presentation }) + expect(result.verified).toBe(false) + expect(result?.error?.errorCode).toEqual(VC_JWT_ERROR.INVALID_JWT) + + const result2 = await agent.verifyPresentation({ + presentation, + policies: { now: 1566297000 }, + }) + expect(result2.verified).toBe(true) + }) + + it('can override expiry check', async () => { + const result = await agent.verifyPresentation({ + presentation, + policies: { expirationDate: false }, + }) + expect(result.verified).toBe(true) + }) + + it('can override issuance check', async () => { + const result = await agent.verifyPresentation({ + presentation, + policies: { issuanceDate: false, now: 1565000000 }, + }) + expect(result.verified).toBe(true) + }) + + it('can override audience check', async () => { + const pres = await agent.createVerifiablePresentation({ + proofFormat: 'jwt', + presentation: { + holder: identifier.did, + verifiableCredential: [credential], + }, + challenge: '1234', + domain: 'example.com', + }) + const result = await agent.verifyPresentation({ presentation: pres }) + expect(result.verified).toBe(false) + expect(result.error?.errorCode).toEqual(VC_JWT_ERROR.INVALID_AUDIENCE) + + const result2 = await agent.verifyPresentation({ + presentation: pres, + policies: { audience: false }, + }) + expect(result2.verified).toBe(true) + }) + }) }) } diff --git a/__tests__/shared/verifiableDataLD.ts b/__tests__/shared/verifiableDataLD.ts index 778221f89..ca16e9615 100644 --- a/__tests__/shared/verifiableDataLD.ts +++ b/__tests__/shared/verifiableDataLD.ts @@ -1,6 +1,14 @@ // noinspection ES6PreferShortImport -import { IDataStore, IDataStoreORM, IDIDManager, IIdentifier, TAgent } from '../../packages/core/src' +import { + IDataStore, + IDataStoreORM, + IDIDManager, + IIdentifier, + TAgent, + VerifiableCredential, + VerifiablePresentation, +} from '../../packages/core/src' import { ICredentialIssuer } from '../../packages/credential-w3c/src' import { IDIDComm } from '../../packages/did-comm/src' @@ -72,7 +80,7 @@ export default (testContext: { credential: verifiableCredential, }) - expect(result).toEqual(true) + expect(result.verified).toEqual(true) }) it('should handleMessage with VC (non-JWT)', async () => { @@ -98,20 +106,19 @@ export default (testContext: { hash: storedCredentialHash, }) + // tamper with credential verifiableCredential.credentialSubject.name = 'Martin, the not so greats' - try { - await agent.handleMessage({ + await expect( + agent.handleMessage({ raw: JSON.stringify({ body: verifiableCredential, type: 'w3c.vc', }), save: false, metaData: [{ type: 'LDS' }], - }) - } catch (e) { - expect(e).toEqual(Error('Error verifying LD Verifiable Credential')) - } + }), + ).rejects.toThrow(/Verification error/) }) it('should sign a verifiable presentation in LD', async () => { @@ -164,7 +171,7 @@ export default (testContext: { domain, }) - expect(result).toBeTruthy() + expect(result.verified).toEqual(true) }) it('should handleMessage with VPs (non-JWT)', async () => { @@ -244,7 +251,65 @@ export default (testContext: { credential: verifiableCredential, }) - expect(result).toEqual(true) + expect(result.verified).toEqual(true) + }) + + describe('credential verification policies', () => { + it('can verify credential at a particular time', async () => { + const issuanceDate = '2019-08-19T09:15:20.000Z' // 1566206120 + const expirationDate = '2019-08-20T10:42:31.000Z' // 1566297751 + let credential = await agent.createVerifiableCredential({ + proofFormat: 'lds', + credential: { + issuer: { id: didKeyIdentifier.did }, + '@context': ['https://veramo.io/contexts/profile/v1'], + type: ['Profile'], + issuanceDate, + expirationDate, + credentialSubject: { + id: didKeyIdentifier.did, + name: 'hello', + }, + }, + now: 1566206120, + }) + + const result = await agent.verifyCredential({ credential }) + expect(result.verified).toBe(false) + + const result2 = await agent.verifyCredential({ + credential, + policies: { now: 1566297000 }, + }) + expect(result2.verified).toBe(true) + }) + + it('can override credentialStatus check', async () => { + const cred = await agent.createVerifiableCredential({ + proofFormat: 'lds', + credential: { + issuer: { id: didKeyIdentifier.did }, + '@context': ['https://veramo.io/contexts/profile/v1'], + type: ['Profile'], + credentialSubject: { + id: didKeyIdentifier.did, + name: 'hello', + }, + credentialStatus: { + id: 'override me', + type: 'ThisMethodDoesNotExist2022', + }, + }, + now: 1566206120, + }) + await expect(agent.verifyCredential({ credential: cred })).rejects.toThrow(/^invalid_setup:/) + + const result2 = await agent.verifyCredential({ + credential: cred, + policies: { credentialStatus: false }, + }) + expect(result2.verified).toBe(true) + }) }) }) } diff --git a/packages/cli/src/credential.ts b/packages/cli/src/credential.ts index fe6806f77..aec46f380 100644 --- a/packages/cli/src/credential.ts +++ b/packages/cli/src/credential.ts @@ -191,7 +191,7 @@ credential } try { const result = await agent.verifyCredential({ credential: credentialAsJSON }) - if (result === true) { + if (result.verified === true) { console.log('Credential was verified successfully.') } else { console.error('Credential could not be verified.') diff --git a/packages/core/package.json b/packages/core/package.json index 5ec14e564..797ebf007 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -23,14 +23,14 @@ }, "dependencies": { "debug": "^4.3.3", - "did-jwt-vc": "^3.0.0", + "did-jwt-vc": "^3.1.0", "events": "^3.2.0", "z-schema": "^5.0.2" }, "devDependencies": { "@types/debug": "4.1.7", - "did-resolver": "^4.0.0", - "credential-status": "^2.0.5", + "credential-status": "2.0.5", + "did-resolver": "4.0.0", "typescript": "4.7.3" }, "files": [ diff --git a/packages/core/plugin.schema.json b/packages/core/plugin.schema.json index aaebcc62f..d6f646ccf 100644 --- a/packages/core/plugin.schema.json +++ b/packages/core/plugin.schema.json @@ -1119,7 +1119,8 @@ { "type": "object" } - ] + ], + "description": "Represents a service endpoint URL or a map of URLs" }, "IDIDManagerCreateArgs": { "type": "object", @@ -2347,7 +2348,8 @@ { "type": "object" } - ] + ], + "description": "Represents a service endpoint URL or a map of URLs" }, "FindMessagesArgs": { "$ref": "#/components/schemas/FindArgs-TMessageColumns", diff --git a/packages/core/src/types/IIdentifier.ts b/packages/core/src/types/IIdentifier.ts index d037b375c..8ada26a66 100644 --- a/packages/core/src/types/IIdentifier.ts +++ b/packages/core/src/types/IIdentifier.ts @@ -131,4 +131,10 @@ export interface IService { description?: string } -export type IServiceEndpoint = string | Record \ No newline at end of file +/** + * Represents a service endpoint URL or a map of URLs + * @see {@link https://www.w3.org/TR/did-core/#dfn-serviceendpoint | serviceEndpoint data model} + * + * @public + */ +export type IServiceEndpoint = string | Record diff --git a/packages/credential-eip712/src/agent/CredentialEIP712.ts b/packages/credential-eip712/src/agent/CredentialEIP712.ts index d7da2be91..fdfa50ba0 100644 --- a/packages/credential-eip712/src/agent/CredentialEIP712.ts +++ b/packages/credential-eip712/src/agent/CredentialEIP712.ts @@ -1,13 +1,21 @@ import { - CredentialPayload, IAgentPlugin, IIdentifier, PresentationPayload, VerifiableCredential, VerifiablePresentation -} from "@veramo/core" + CredentialPayload, + IAgentPlugin, + IIdentifier, + PresentationPayload, + VerifiableCredential, + VerifiablePresentation, +} from '@veramo/core' import { - extractIssuer, getChainIdForDidEthr, + extractIssuer, + getChainIdForDidEthr, getEthereumAddress, isDefined, - MANDATORY_CREDENTIAL_CONTEXT, mapIdentifierKeysToDoc, processEntryToArray, - resolveDidOrThrow -} from "@veramo/utils" + MANDATORY_CREDENTIAL_CONTEXT, + mapIdentifierKeysToDoc, + processEntryToArray, + resolveDidOrThrow, +} from '@veramo/utils' import { schema } from '../index' import { recoverTypedSignature, SignTypedDataVersion } from '@metamask/eth-sig-util' @@ -17,10 +25,10 @@ import { ICredentialIssuerEIP712, IRequiredContext, IVerifyCredentialEIP712Args, - IVerifyPresentationEIP712Args + IVerifyPresentationEIP712Args, } from '../types/ICredentialEIP712' -import { getEthTypesFromInputDoc } from "eip-712-types-generation" +import { getEthTypesFromInputDoc } from 'eip-712-types-generation' /** * A Veramo plugin that implements the {@link ICredentialIssuerEIP712} methods. @@ -36,7 +44,7 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { createVerifiableCredentialEIP712: this.createVerifiableCredentialEIP712.bind(this), createVerifiablePresentationEIP712: this.createVerifiablePresentationEIP712.bind(this), verifyCredentialEIP712: this.verifyCredentialEIP712.bind(this), - verifyPresentationEIP712: this.verifyPresentationEIP712.bind(this) + verifyPresentationEIP712: this.verifyPresentationEIP712.bind(this), } } @@ -57,7 +65,7 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { const issuer = extractIssuer(args.credential) if (!issuer || typeof issuer === 'undefined') { - throw new Error('invalid_argument: args.credential.issuer must not be empty') + throw new Error('invalid_argument: credential.issuer must not be empty') } let keyRef = args.keyRef @@ -65,14 +73,17 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { const identifier = await context.agent.didManagerGet({ did: issuer }) if (!keyRef) { - const key = identifier.keys.find((k) => k.type === 'Secp256k1' && k.meta?.algorithms?.includes('eth_signTypedData')) - if (!key) throw Error('No signing key for ' + identifier.did) + const key = identifier.keys.find( + (k) => k.type === 'Secp256k1' && k.meta?.algorithms?.includes('eth_signTypedData'), + ) + if (!key) throw Error('key_not_found: No suitable signing key is known for ' + identifier.did) keyRef = key.kid } const extendedKeys = await mapIdentifierKeysToDoc(identifier, 'verificationMethod', context) - const extendedKey = extendedKeys.find(key => key.kid === keyRef) - if (!extendedKey) throw Error('Key not found') + const extendedKey = extendedKeys.find((key) => key.kid === keyRef) + if (!extendedKey) + throw Error('key_not_found: The signing key is not available in the issuer DID document') const chainId = getChainIdForDidEthr(extendedKey.meta.verificationMethod) @@ -84,72 +95,73 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { proof: { verificationMethod: extendedKey.meta.verificationMethod.id, created: issuanceDate, - proofPurpose: "assertionMethod", - type: "EthereumEip712Signature2021", - } + proofPurpose: 'assertionMethod', + type: 'EthereumEip712Signature2021', + }, } - const message = credential; + const message = credential const domain = { chainId, - name: "VerifiableCredential", - version: "1", - }; + name: 'VerifiableCredential', + version: '1', + } - const primaryType = "VerifiableCredential" - const allTypes = getEthTypesFromInputDoc(credential, primaryType); + const primaryType = 'VerifiableCredential' + const allTypes = getEthTypesFromInputDoc(credential, primaryType) const types = { ...allTypes } const data = JSON.stringify({ domain, types, message, primaryType }) const signature = await context.agent.keyManagerSign({ keyRef, data, algorithm: 'eth_signTypedData' }) - credential['proof']['proofValue'] = signature; + credential['proof']['proofValue'] = signature credential['proof']['eip712'] = { domain, messageSchema: allTypes, primaryType, } - return credential as VerifiableCredential; + return credential as VerifiableCredential } /** {@inheritdoc ICredentialIssuerEIP712.verifyCredentialEIP712} */ - private async verifyCredentialEIP712(args: IVerifyCredentialEIP712Args, context: IRequiredContext): Promise { + private async verifyCredentialEIP712( + args: IVerifyCredentialEIP712Args, + context: IRequiredContext, + ): Promise { const { credential } = args - if (!credential.proof || !credential.proof.proofValue) throw new Error("Proof is undefined") - if ( - !credential.proof.eip712 || - !credential.proof.eip712.messageSchema || - !credential.proof.eip712.domain - ) throw new Error("eip712 is undefined"); + if (!credential.proof || !credential.proof.proofValue) + throw new Error('invalid_argument: proof is undefined') + if (!credential.proof.eip712 || !credential.proof.eip712.messageSchema || !credential.proof.eip712.domain) + throw new Error('invalid_argument: proof.eip712 is missing expected properties') - const { proof, ...signingInput } = credential; - const { proofValue, eip712, ...verifyInputProof } = proof; + const { proof, ...signingInput } = credential + const { proofValue, eip712, ...verifyInputProof } = proof const verificationMessage = { ...signingInput, - proof: verifyInputProof + proof: verifyInputProof, } const objectToVerify = { message: verificationMessage, domain: eip712.domain, types: eip712.messageSchema, - primaryType: eip712.primaryType + primaryType: eip712.primaryType, } const recovered = recoverTypedSignature({ data: objectToVerify, signature: proofValue, - version: SignTypedDataVersion.V4 + version: SignTypedDataVersion.V4, }) const issuer = extractIssuer(credential) if (!issuer || typeof issuer === 'undefined') { - throw new Error('invalid_argument: args.credential.issuer must not be empty') + throw new Error('invalid_argument: credential.issuer must not be empty') } - const didDocument = await resolveDidOrThrow(issuer, context); + const didDocument = await resolveDidOrThrow(issuer, context) if (didDocument.verificationMethod) { for (const verificationMethod of didDocument.verificationMethod) { @@ -157,12 +169,11 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { return true } } - } - else { - throw new Error("Recovered Address does not match issuer") + } else { + throw new Error('resolver_error: issuer DIDDocument does not contain any verificationMethods') } - return true; + return false } /** {@inheritdoc ICredentialIssuerEIP712.createVerifiablePresentationEIP712} */ @@ -188,7 +199,7 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { } if (!isDefined(args.presentation.holder)) { - throw new Error('invalid_argument: args.presentation.holder must not be empty') + throw new Error('invalid_argument: presentation.holder must not be empty') } if (args.presentation.verifiableCredential) { @@ -207,105 +218,109 @@ export class CredentialIssuerEIP712 implements IAgentPlugin { try { identifier = await context.agent.didManagerGet({ did: presentation.holder }) } catch (e) { - throw new Error('invalid_argument: args.presentation.holder must be a DID managed by this agent') + throw new Error('invalid_argument: presentation.holder must be a DID managed by this agent') } let keyRef = args.keyRef if (!keyRef) { - const key = identifier.keys.find((k) => k.type === 'Secp256k1' && k.meta?.algorithms?.includes('eth_signTypedData')) - if (!key) throw Error('No signing key for ' + identifier.did) + const key = identifier.keys.find( + (k) => k.type === 'Secp256k1' && k.meta?.algorithms?.includes('eth_signTypedData'), + ) + if (!key) throw Error('key_not_found: No suitable signing key is known for ' + identifier.did) keyRef = key.kid } const extendedKeys = await mapIdentifierKeysToDoc(identifier, 'verificationMethod', context) - const extendedKey = extendedKeys.find(key => key.kid === keyRef) - if (!extendedKey) throw Error('Key not found') + const extendedKey = extendedKeys.find((key) => key.kid === keyRef) + if (!extendedKey) + throw Error('key_not_found: The signing key is not available in the issuer DID document') const chainId = getChainIdForDidEthr(extendedKey.meta.verificationMethod) presentation['proof'] = { verificationMethod: extendedKey.meta.verificationMethod.id, created: issuanceDate, - proofPurpose: "assertionMethod", - type: "EthereumEip712Signature2021", + proofPurpose: 'assertionMethod', + type: 'EthereumEip712Signature2021', } - const message = presentation; + const message = presentation const domain = { chainId, - name: "VerifiablePresentation", - version: "1", - }; + name: 'VerifiablePresentation', + version: '1', + } const primaryType = 'VerifiablePresentation' - const allTypes = getEthTypesFromInputDoc(presentation, primaryType); + const allTypes = getEthTypesFromInputDoc(presentation, primaryType) const types = { ...allTypes } const data = JSON.stringify({ domain, types, message }) const signature = await context.agent.keyManagerSign({ keyRef, data, algorithm: 'eth_signTypedData' }) - - presentation.proof.proofValue = signature; + presentation.proof.proofValue = signature presentation.proof.eip712 = { domain, messageSchema: allTypes, primaryType, - }; + } return presentation as VerifiablePresentation } /** {@inheritdoc ICredentialIssuerEIP712.verifyPresentationEIP712} */ - private async verifyPresentationEIP712(args: IVerifyPresentationEIP712Args, context: IRequiredContext): Promise { - try { - const { presentation } = args - if (!presentation.proof || !presentation.proof.proofValue) throw new Error("Proof is undefined") - if ( - !presentation.proof.eip712 || - !presentation.proof.eip712.messageSchema || - !presentation.proof.eip712.domain - ) throw new Error("eip712 is undefined"); - - const { proof, ...signingInput } = presentation; - const { proofValue, eip712, ...verifyInputProof } = proof; - const verificationMessage = { - ...signingInput, - proof: verifyInputProof - } + private async verifyPresentationEIP712( + args: IVerifyPresentationEIP712Args, + context: IRequiredContext, + ): Promise { + const { presentation } = args + if (!presentation.proof || !presentation.proof.proofValue) throw new Error('Proof is undefined') + if ( + !presentation.proof.eip712 || + !presentation.proof.eip712.messageSchema || + !presentation.proof.eip712.domain + ) + throw new Error('proof.eip712 is undefined') - const objectToVerify = { - message: verificationMessage, - domain: eip712.domain, - types: eip712.messageSchema, - primaryType: eip712.primaryType - } + const { proof, ...signingInput } = presentation + const { proofValue, eip712, ...verifyInputProof } = proof + const verificationMessage = { + ...signingInput, + proof: verifyInputProof, + } - const recovered = recoverTypedSignature({ - data: objectToVerify, - signature: proofValue, - version: SignTypedDataVersion.V4 - }) + const objectToVerify = { + message: verificationMessage, + domain: eip712.domain, + types: eip712.messageSchema, + primaryType: eip712.primaryType, + } - const issuer = extractIssuer(presentation) - if (!issuer || typeof issuer === 'undefined') { - throw new Error('invalid_argument: args.presentation.issuer must not be empty') - } + const recovered = recoverTypedSignature({ + data: objectToVerify, + signature: proofValue, + version: SignTypedDataVersion.V4, + }) - const didDocument = await resolveDidOrThrow(issuer, context); + const issuer = extractIssuer(presentation) + if (!issuer || typeof issuer === 'undefined') { + throw new Error('invalid_argument: args.presentation.issuer must not be empty') + } + + const didDocument = await resolveDidOrThrow(issuer, context) - if (didDocument.verificationMethod) { - for (const verificationMethod of didDocument.verificationMethod) { - if (getEthereumAddress(verificationMethod)?.toLowerCase() === recovered.toLowerCase()) { - return true - } + if (didDocument.verificationMethod) { + for (const verificationMethod of didDocument.verificationMethod) { + if (getEthereumAddress(verificationMethod)?.toLowerCase() === recovered.toLowerCase()) { + return true } } - - throw new Error("Recovered Address does not match issuer") - } catch (e: any) { - throw new Error(e); + } else { + throw new Error('resolver_error: holder DIDDocument does not contain any verificationMethods') } + + return false } } diff --git a/packages/credential-ld/plugin.schema.json b/packages/credential-ld/plugin.schema.json index 97528e09e..17ad4bf32 100644 --- a/packages/credential-ld/plugin.schema.json +++ b/packages/credential-ld/plugin.schema.json @@ -12,11 +12,18 @@ "keyRef": { "type": "string", "description": "Optional. The key handle ( {@link @veramo/core#IKey.kid | IKey.kid } ) from the internal database." + }, + "fetchRemoteContexts": { + "type": "boolean", + "description": "Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded.\n\nDefaults to `false`" } }, "required": [ "credential" ], + "additionalProperties": { + "description": "Any other options that can be forwarded to the lower level libraries" + }, "description": "Encapsulates the parameters required to create a\n {@link https://www.w3.org/TR/vc-data-model/#credentials | W3C Verifiable Credential }" }, "CredentialPayload": { @@ -193,11 +200,18 @@ "keyRef": { "type": "string", "description": "Optional. The key handle ( {@link @veramo/core#IKey.kid | IKey.kid } ) from the internal database." + }, + "fetchRemoteContexts": { + "type": "boolean", + "description": "Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded.\n\nDefaults to `false`" } }, "required": [ "presentation" ], + "additionalProperties": { + "description": "Any other options that can be forwarded to the lower level libraries" + }, "description": "Encapsulates the parameters required to create a\n {@link https://www.w3.org/TR/vc-data-model/#presentations | W3C Verifiable Presentation }" }, "PresentationPayload": { @@ -333,12 +347,15 @@ }, "fetchRemoteContexts": { "type": "boolean", - "description": "Set this to true if you want the `@context` URLs to be fetched in case they are not pre-loaded.\n\nDefaults to `false`" + "description": "Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded.\n\nDefaults to `false`" } }, "required": [ "credential" ], + "additionalProperties": { + "description": "Any other options that can be forwarded to the lower level libraries" + }, "description": "Encapsulates the parameters required to verify a\n {@link https://www.w3.org/TR/vc-data-model/#credentials | W3C Verifiable Credential }" }, "IVerifyPresentationLDArgs": { @@ -358,12 +375,15 @@ }, "fetchRemoteContexts": { "type": "boolean", - "description": "Set this to true if you want the `@context` URLs to be fetched in case they are not pre-loaded.\n\nDefaults to `false`" + "description": "Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded.\n\nDefaults to `false`" } }, "required": [ "presentation" ], + "additionalProperties": { + "description": "Any other options that can be forwarded to the lower level libraries" + }, "description": "Encapsulates the parameters required to verify a\n {@link https://www.w3.org/TR/vc-data-model/#presentations | W3C Verifiable Presentation }" } }, diff --git a/packages/credential-ld/src/__tests__/issue-verify-flow.test.ts b/packages/credential-ld/src/__tests__/issue-verify-flow.test.ts index 22303c8da..7f57baa48 100644 --- a/packages/credential-ld/src/__tests__/issue-verify-flow.test.ts +++ b/packages/credential-ld/src/__tests__/issue-verify-flow.test.ts @@ -91,11 +91,11 @@ describe('credential-LD full flow', () => { expect(verifiableCredential).toBeDefined() - const verified = await agent.verifyCredential({ + const result = await agent.verifyCredential({ credential: verifiableCredential, }) - expect(verified).toBe(true) + expect(result.verified).toBe(true) }) it('works with EcdsaSecp256k1RecoveryMethod2020 credentials', async () => { @@ -113,11 +113,11 @@ describe('credential-LD full flow', () => { expect(verifiableCredential).toBeDefined() - const verified = await agent.verifyCredential({ + const result = await agent.verifyCredential({ credential: verifiableCredential, }) - expect(verified).toBe(true) + expect(result.verified).toBe(true) }) it('works with Ed25519Signature2018 credential and presentation', async () => { @@ -144,12 +144,12 @@ describe('credential-LD full flow', () => { expect(verifiablePresentation).toBeDefined() - const verified = await agent.verifyPresentation({ + const result = await agent.verifyPresentation({ presentation: verifiablePresentation, challenge: 'VERAMO', }) - expect(verified).toBe(true) + expect(result.verified).toBe(true) }) it('works with EcdsaSecp256k1RecoveryMethod2020 credential and presentation', async () => { @@ -179,11 +179,11 @@ describe('credential-LD full flow', () => { expect(verifiablePresentation).toBeDefined() - const verified = await agent.verifyPresentation({ + const result = await agent.verifyPresentation({ presentation: verifiablePresentation, challenge: 'VERAMO', }) - expect(verified).toBe(true) + expect(result.verified).toBe(true) }) }) diff --git a/packages/credential-ld/src/action-handler.ts b/packages/credential-ld/src/action-handler.ts index 14ae63061..c3f8549ae 100644 --- a/packages/credential-ld/src/action-handler.ts +++ b/packages/credential-ld/src/action-handler.ts @@ -110,13 +110,19 @@ export class CredentialIssuerLD implements IAgentPlugin { args.keyRef, ) + let { now } = args + if (typeof now === 'number') { + now = new Date(now * 1000) + } + return await this.ldCredentialModule.signLDVerifiablePresentation( presentation, identifier.did, signingKey, verificationMethodId, - args.challenge, - args.domain, + args.challenge || '', + args.domain || '', + { ...args, now }, context, ) } catch (error) { @@ -135,15 +141,10 @@ export class CredentialIssuerLD implements IAgentPlugin { MANDATORY_CREDENTIAL_CONTEXT, ) const credentialType = processEntryToArray(args?.credential?.type, 'VerifiableCredential') - let issuanceDate = args?.credential?.issuanceDate || new Date().toISOString() - if (issuanceDate instanceof Date) { - issuanceDate = issuanceDate.toISOString() - } const credential: CredentialPayload = { ...args?.credential, '@context': credentialContext, type: credentialType, - issuanceDate, } const issuer = extractIssuer(credential) @@ -164,11 +165,17 @@ export class CredentialIssuerLD implements IAgentPlugin { args.keyRef, ) + let { now } = args + if (typeof now === 'number') { + now = new Date(now * 1000) + } + return await this.ldCredentialModule.issueLDVerifiableCredential( credential, identifier.did, signingKey, verificationMethodId, + { ...args.options, now }, context, ) } catch (error) { @@ -183,7 +190,18 @@ export class CredentialIssuerLD implements IAgentPlugin { context: IRequiredContext, ): Promise { const credential = args.credential - return this.ldCredentialModule.verifyCredential(credential, args.fetchRemoteContexts || false, context) + + let { now } = args + if (typeof now === 'number') { + now = new Date(now * 1000) + } + + return this.ldCredentialModule.verifyCredential( + credential, + args.fetchRemoteContexts || false, + { ...args.options, now }, + context, + ) } /** {@inheritdoc ICredentialIssuerLD.verifyPresentationLD} */ @@ -192,11 +210,16 @@ export class CredentialIssuerLD implements IAgentPlugin { context: IRequiredContext, ): Promise { const presentation = args.presentation + let { now } = args + if (typeof now === 'number') { + now = new Date(now * 1000) + } return this.ldCredentialModule.verifyPresentation( presentation, args.challenge, args.domain, args.fetchRemoteContexts || false, + { ...args.options, now }, context, ) } diff --git a/packages/credential-ld/src/ld-credential-module.ts b/packages/credential-ld/src/ld-credential-module.ts index 217c83191..0347d1438 100644 --- a/packages/credential-ld/src/ld-credential-module.ts +++ b/packages/credential-ld/src/ld-credential-module.ts @@ -28,6 +28,7 @@ export class LdCredentialModule { private ldContextLoader: LdContextLoader ldSuiteLoader: LdSuiteLoader + constructor(options: { ldContextLoader: LdContextLoader; ldSuiteLoader: LdSuiteLoader }) { this.ldContextLoader = options.ldContextLoader this.ldSuiteLoader = options.ldSuiteLoader @@ -97,15 +98,17 @@ export class LdCredentialModule { issuerDid: string, key: IKey, verificationMethodId: string, + options: any, context: IAgentContext, ): Promise { const suite = this.ldSuiteLoader.getSignatureSuiteForKeyType(key.type) - const documentLoader = this.getDocumentLoader(context) + const documentLoader = this.getDocumentLoader(context, options.fetchRemoteContexts) // some suites can modify the incoming credential (e.g. add required contexts) suite.preSigningCredModification(credential) return await vc.issue({ + ...options, credential, suite: suite.getSuiteForSigning(key, issuerDid, verificationMethodId, context), documentLoader, @@ -120,14 +123,16 @@ export class LdCredentialModule { verificationMethodId: string, challenge: string | undefined, domain: string | undefined, + options: any, context: IAgentContext, ): Promise { const suite = this.ldSuiteLoader.getSignatureSuiteForKeyType(key.type) - const documentLoader = this.getDocumentLoader(context) + const documentLoader = this.getDocumentLoader(context, options.fetchRemoteContexts) suite.preSigningPresModification(presentation) return await vc.signPresentation({ + ...options, presentation, suite: suite.getSuiteForSigning(key, holderDid, verificationMethodId, context), challenge, @@ -140,9 +145,11 @@ export class LdCredentialModule { async verifyCredential( credential: VerifiableCredential, fetchRemoteContexts: boolean = false, + options: any, context: IAgentContext, ): Promise { const result = await vc.verifyCredential({ + ...options, credential, suite: this.ldSuiteLoader.getAllSignatureSuites().map((x) => x.getSuiteForVerification()), documentLoader: this.getDocumentLoader(context, fetchRemoteContexts), @@ -150,13 +157,12 @@ export class LdCredentialModule { checkStatus: async () => Promise.resolve({ verified: true }), // Fake method }) - if (result.verified) return true + if (!result.verified) { + // result can include raw Error + debug(`Error verifying LD Credential: ${JSON.stringify(result, null, 2)}`) + } - // NOT verified. - // result can include raw Error - debug(`Error verifying LD Verifiable Credential: ${JSON.stringify(result, null, 2)}`) - // console.log(JSON.stringify(result, null, 2)); - throw Error('Error verifying LD Verifiable Credential') + return result } async verifyPresentation( @@ -164,9 +170,11 @@ export class LdCredentialModule { challenge: string | undefined, domain: string | undefined, fetchRemoteContexts: boolean = false, + options: any, context: IAgentContext, ): Promise { const result = await vc.verify({ + ...options, presentation, suite: this.ldSuiteLoader.getAllSignatureSuites().map((x) => x.getSuiteForVerification()), documentLoader: this.getDocumentLoader(context, fetchRemoteContexts), @@ -175,13 +183,10 @@ export class LdCredentialModule { compactProof: false, }) - if (result.verified) return true - - // NOT verified. - - // result can include raw Error - console.log(`Error verifying LD Verifiable Presentation`) - console.log(JSON.stringify(result, null, 2)) - throw Error('Error verifying LD Verifiable Presentation') + if (!result.verified) { + // result can include raw Error + debug(`Error verifying LD Presentation: ${JSON.stringify(result, null, 2)}`) + } + return result } } diff --git a/packages/credential-ld/src/types.ts b/packages/credential-ld/src/types.ts index cd5ad5ce7..5c45e1517 100644 --- a/packages/credential-ld/src/types.ts +++ b/packages/credential-ld/src/types.ts @@ -119,6 +119,18 @@ export interface ICreateVerifiablePresentationLDArgs { * Optional. The key handle ({@link @veramo/core#IKey.kid | IKey.kid}) from the internal database. */ keyRef?: string + + /** + * Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded. + * + * Defaults to `false` + */ + fetchRemoteContexts?: boolean + + /** + * Any other options that can be forwarded to the lower level libraries + */ + [x:string]: any } /** @@ -142,7 +154,19 @@ export interface ICreateVerifiableCredentialLDArgs { /** * Optional. The key handle ({@link @veramo/core#IKey.kid | IKey.kid}) from the internal database. */ - keyRef?: string + keyRef?: string, + + /** + * Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded. + * + * Defaults to `false` + */ + fetchRemoteContexts?: boolean + + /** + * Any other options that can be forwarded to the lower level libraries + */ + [x:string]: any } /** @@ -163,11 +187,16 @@ export interface IVerifyCredentialLDArgs { credential: VerifiableCredential /** - * Set this to true if you want the `@context` URLs to be fetched in case they are not pre-loaded. + * Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded. * * Defaults to `false` */ fetchRemoteContexts?: boolean + + /** + * Any other options that can be forwarded to the lower level libraries + */ + [x:string]: any } /** @@ -198,11 +227,16 @@ export interface IVerifyPresentationLDArgs { domain?: string /** - * Set this to true if you want the `@context` URLs to be fetched in case they are not pre-loaded. + * Set this to true if you want the `@context` URLs to be fetched in case they are not preloaded. * * Defaults to `false` */ fetchRemoteContexts?: boolean + + /** + * Any other options that can be forwarded to the lower level libraries + */ + [x:string]: any } /** diff --git a/packages/credential-status/package.json b/packages/credential-status/package.json index ce0bf5797..5715a3df9 100644 --- a/packages/credential-status/package.json +++ b/packages/credential-status/package.json @@ -12,7 +12,7 @@ "@veramo/core": "^3.1.0", "@veramo/utils": "^3.1.0", "credential-status": "^2.0.5", - "did-jwt": "^6.3.0", + "did-jwt": "^6.6.0", "did-resolver": "^4.0.0" }, "devDependencies": { diff --git a/packages/credential-w3c/package.json b/packages/credential-w3c/package.json index acfc464e5..b4adce6fb 100644 --- a/packages/credential-w3c/package.json +++ b/packages/credential-w3c/package.json @@ -20,7 +20,7 @@ "@veramo/message-handler": "^3.1.4", "@veramo/utils": "^3.1.4", "debug": "^4.3.3", - "did-jwt-vc": "^3.0.0", + "did-jwt-vc": "^3.1.0", "did-resolver": "^4.0.0", "uint8arrays": "^3.0.0", "uuid": "^8.3.0" diff --git a/packages/credential-w3c/plugin.schema.json b/packages/credential-w3c/plugin.schema.json index 6a6ebcd4d..8863d0bf4 100644 --- a/packages/credential-w3c/plugin.schema.json +++ b/packages/credential-w3c/plugin.schema.json @@ -25,12 +25,20 @@ "keyRef": { "type": "string", "description": "[Optional] The ID of the key that should sign this credential. If this is not specified, the first matching key will be used." + }, + "fetchRemoteContexts": { + "type": "boolean", + "description": "When dealing with JSON-LD you also MUST provide the proper contexts. Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. The context definitions SHOULD rather be provided at startup instead of being fetched.", + "default": false } }, "required": [ "credential", "proofFormat" ], + "additionalProperties": { + "description": "Any other options that can be forwarded to the lower level libraries" + }, "description": "Encapsulates the parameters required to create a\n {@link https://www.w3.org/TR/vc-data-model/#credentials | W3C Verifiable Credential }" }, "CredentialPayload": { @@ -229,12 +237,20 @@ "keyRef": { "type": "string", "description": "[Optional] The ID of the key that should sign this presentation. If this is not specified, the first matching key will be used." + }, + "fetchRemoteContexts": { + "type": "boolean", + "description": "When dealing with JSON-LD you also MUST provide the proper contexts. Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. The context definitions SHOULD rather be provided at startup instead of being fetched.", + "default": false } }, "required": [ "presentation", "proofFormat" ], + "additionalProperties": { + "description": "Any other options that can be forwarded to the lower level libraries" + }, "description": "Encapsulates the parameters required to create a\n {@link https://www.w3.org/TR/vc-data-model/#presentations | W3C Verifiable Presentation }" }, "PresentationPayload": { @@ -370,86 +386,50 @@ }, "fetchRemoteContexts": { "type": "boolean", - "description": "When dealing with JSON-LD you also MUST provide the proper contexts. Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not pre-loaded. The context definitions SHOULD rather be provided at startup instead of being fetched.", - "default": false - } - }, - "required": [ - "credential" - ], - "additionalProperties": { - "description": "Other options can be specified for verification. They will be forwarded to the lower level modules. that performt the checks" - }, - "description": "Encapsulates the parameters required to verify a\n {@link https://www.w3.org/TR/vc-data-model/#credentials | W3C Verifiable Credential }" - }, - "IVerifyPresentationArgs": { - "type": "object", - "properties": { - "presentation": { - "$ref": "#/components/schemas/W3CVerifiablePresentation", - "description": "The Verifiable Presentation object according to the\n {@link https://www.w3.org/TR/vc-data-model/#presentations | canonical model } or the JWT representation.\n\nThe signer of the Presentation is verified based on the `holder` property of the `presentation` or the `iss` property of the JWT payload respectively" - }, - "challenge": { - "type": "string", - "description": "Optional (only for JWT) string challenge parameter to verify the verifiable presentation against" - }, - "domain": { - "type": "string", - "description": "Optional (only for JWT) string domain parameter to verify the verifiable presentation against" - }, - "fetchRemoteContexts": { - "type": "boolean", - "description": "When dealing with JSON-LD you also MUST provide the proper contexts. Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not pre-loaded. The context definitions SHOULD rather be provided at startup instead of being fetched.", + "description": "When dealing with JSON-LD you also MUST provide the proper contexts. Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. The context definitions SHOULD rather be provided at startup instead of being fetched.", "default": false }, "policies": { - "$ref": "#/components/schemas/VerifyPresentationPolicies", - "description": "Verification Policies for the verifiable presentation These will also be forwarded to the lower level module" + "$ref": "#/components/schemas/VerificationPolicies", + "description": "Overrides specific aspects of credential verification, where possible." } }, "required": [ - "presentation" + "credential" ], "additionalProperties": { - "description": "Other options can be specified for verification. They will be forwarded to the lower level modules. that performt the checks" + "description": "Other options can be specified for verification. They will be forwarded to the lower level modules. that perform the checks" }, - "description": "Encapsulates the parameters required to verify a\n {@link https://www.w3.org/TR/vc-data-model/#presentations | W3C Verifiable Presentation }" - }, - "W3CVerifiablePresentation": { - "anyOf": [ - { - "$ref": "#/components/schemas/VerifiablePresentation" - }, - { - "$ref": "#/components/schemas/CompactJWT" - } - ], - "description": "Represents a signed Verifiable Presentation (includes proof) in either JSON or compact JWT format. See {@link https://www.w3.org/TR/vc-data-model/#credentials | VC data model }" + "description": "Encapsulates the parameters required to verify a\n {@link https://www.w3.org/TR/vc-data-model/#credentials | W3C Verifiable Credential }" }, - "VerifyPresentationPolicies": { + "VerificationPolicies": { "type": "object", "properties": { "now": { "type": "number", - "description": "policy to over the now (current time) during the verification check" + "description": "policy to over the now (current time) during the verification check (UNIX time in seconds)" }, "issuanceDate": { "type": "boolean", - "description": "policy to override the issuanceDate (nbf) timestamp check" + "description": "policy to skip the issuanceDate (nbf) timestamp check when set to `false`" }, - "issuedAtDate": { + "expirationDate": { "type": "boolean", - "description": "policy to override the issuedAtDate (iat) timestamp check" + "description": "policy to skip the expirationDate (exp) timestamp check when set to `false`" }, - "expirationDate": { + "audience": { "type": "boolean", - "description": "policy to override the expirationDate (exp) timestamp check" + "description": "policy to skip the audience check when set to `false`" + }, + "credentialStatus": { + "type": "boolean", + "description": "policy to skip the revocation check (credentialStatus) when set to `false`" } }, "additionalProperties": { "description": "Other options can be specified for verification. They will be forwarded to the lower level modules that perform the checks" }, - "description": "These optional settings can be used to override some of the default checks that are performed on Presentations during verification." + "description": "These optional settings can be used to override some default checks that are performed on Presentations during verification." }, "IVerifyResult": { "type": "object", @@ -484,6 +464,50 @@ } }, "description": "An error object, which can contain a code." + }, + "IVerifyPresentationArgs": { + "type": "object", + "properties": { + "presentation": { + "$ref": "#/components/schemas/W3CVerifiablePresentation", + "description": "The Verifiable Presentation object according to the\n {@link https://www.w3.org/TR/vc-data-model/#presentations | canonical model } or the JWT representation.\n\nThe signer of the Presentation is verified based on the `holder` property of the `presentation` or the `iss` property of the JWT payload respectively" + }, + "challenge": { + "type": "string", + "description": "Optional (only for JWT) string challenge parameter to verify the verifiable presentation against" + }, + "domain": { + "type": "string", + "description": "Optional (only for JWT) string domain parameter to verify the verifiable presentation against" + }, + "fetchRemoteContexts": { + "type": "boolean", + "description": "When dealing with JSON-LD you also MUST provide the proper contexts. Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. The context definitions SHOULD rather be provided at startup instead of being fetched.", + "default": false + }, + "policies": { + "$ref": "#/components/schemas/VerificationPolicies", + "description": "Overrides specific aspects of credential verification, where possible." + } + }, + "required": [ + "presentation" + ], + "additionalProperties": { + "description": "Other options can be specified for verification. They will be forwarded to the lower level modules. that perform the checks" + }, + "description": "Encapsulates the parameters required to verify a\n {@link https://www.w3.org/TR/vc-data-model/#presentations | W3C Verifiable Presentation }" + }, + "W3CVerifiablePresentation": { + "anyOf": [ + { + "$ref": "#/components/schemas/VerifiablePresentation" + }, + { + "$ref": "#/components/schemas/CompactJWT" + } + ], + "description": "Represents a signed Verifiable Presentation (includes proof) in either JSON or compact JWT format. See {@link https://www.w3.org/TR/vc-data-model/#credentials | VC data model }" } }, "methods": { @@ -511,7 +535,7 @@ "$ref": "#/components/schemas/IVerifyCredentialArgs" }, "returnType": { - "type": "boolean" + "$ref": "#/components/schemas/IVerifyResult" } }, "verifyPresentation": { diff --git a/packages/credential-w3c/src/action-handler.ts b/packages/credential-w3c/src/action-handler.ts index 2d1f7593e..fcbd3ebed 100644 --- a/packages/credential-w3c/src/action-handler.ts +++ b/packages/credential-w3c/src/action-handler.ts @@ -39,7 +39,7 @@ import Debug from 'debug' import { Resolvable } from 'did-resolver' import { schema } from './' -const enum CredentialType { +const enum DocumentFormat { JWT, JSONLD, EIP712, @@ -111,6 +111,20 @@ export interface ICreateVerifiablePresentationArgs { * If this is not specified, the first matching key will be used. */ keyRef?: string + + /** + * When dealing with JSON-LD you also MUST provide the proper contexts. + * Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. + * The context definitions SHOULD rather be provided at startup instead of being fetched. + * + * @default false + */ + fetchRemoteContexts?: boolean + + /** + * Any other options that can be forwarded to the lower level libraries + */ + [x: string]: any } /** @@ -157,6 +171,20 @@ export interface ICreateVerifiableCredentialArgs { * If this is not specified, the first matching key will be used. */ keyRef?: string + + /** + * When dealing with JSON-LD you also MUST provide the proper contexts. + * Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. + * The context definitions SHOULD rather be provided at startup instead of being fetched. + * + * @default false + */ + fetchRemoteContexts?: boolean + + /** + * Any other options that can be forwarded to the lower level libraries + */ + [x: string]: any } /** @@ -178,16 +206,21 @@ export interface IVerifyCredentialArgs { /** * When dealing with JSON-LD you also MUST provide the proper contexts. - * Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not pre-loaded. + * Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. * The context definitions SHOULD rather be provided at startup instead of being fetched. * * @default false */ fetchRemoteContexts?: boolean + /** + * Overrides specific aspects of credential verification, where possible. + */ + policies?: VerificationPolicies + /** * Other options can be specified for verification. - * They will be forwarded to the lower level modules. that performt the checks + * They will be forwarded to the lower level modules. that perform the checks */ [x: string]: any } @@ -221,58 +254,61 @@ export interface IVerifyPresentationArgs { /** * When dealing with JSON-LD you also MUST provide the proper contexts. - * Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not pre-loaded. + * Set this to `true` ONLY if you want the '@context' URLs to be fetched in case they are not preloaded. * The context definitions SHOULD rather be provided at startup instead of being fetched. * * @default false */ fetchRemoteContexts?: boolean - /** - * Verification Policies for the verifiable presentation - * These will also be forwarded to the lower level module + * Overrides specific aspects of credential verification, where possible. */ - policies?: VerifyPresentationPolicies + policies?: VerificationPolicies /** * Other options can be specified for verification. - * They will be forwarded to the lower level modules. that performt the checks - */ + * They will be forwarded to the lower level modules. that perform the checks + */ [x: string]: any } /** - * These optional settings can be used to override some of the default checks that are performed on - * Presentations during verification. + * These optional settings can be used to override some default checks that are performed on Presentations during + * verification. * * @beta */ -export interface VerifyPresentationPolicies { +export interface VerificationPolicies { /** - * policy to over the now (current time) during the verification check + * policy to over the now (current time) during the verification check (UNIX time in seconds) */ now?: number /** - * policy to override the issuanceDate (nbf) timestamp check + * policy to skip the issuanceDate (nbf) timestamp check when set to `false` */ issuanceDate?: boolean /** - * policy to override the issuedAtDate (iat) timestamp check + * policy to skip the expirationDate (exp) timestamp check when set to `false` */ - issuedAtDate?: boolean + expirationDate?: boolean /** - * policy to override the expirationDate (exp) timestamp check + * policy to skip the audience check when set to `false` */ - expirationDate?: boolean + audience?: boolean + + /** + * policy to skip the revocation check (credentialStatus) when set to `false` + */ + credentialStatus?: boolean /** * Other options can be specified for verification. * They will be forwarded to the lower level modules that perform the checks - */ + */ [x: string]: any } @@ -332,11 +368,12 @@ export interface ICredentialIssuer extends IPluginMethodMap { * @param args - Arguments necessary to verify a VerifiableCredential * @param context - This reserved param is automatically added and handled by the framework, *do not override* * - * @returns - a promise that resolves to the boolean true on successful verification or rejects on error + * @returns - a promise that resolves to an object containing a `verified` boolean property and an optional `error` + * for details * * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#credentials | Verifiable Credential data model} */ - verifyCredential(args: IVerifyCredentialArgs, context: IContext): Promise + verifyCredential(args: IVerifyCredentialArgs, context: IContext): Promise /** * Verifies a Verifiable Presentation JWT or LDS Format. @@ -344,7 +381,8 @@ export interface ICredentialIssuer extends IPluginMethodMap { * @param args - Arguments necessary to verify a VerifiableCredential * @param context - This reserved param is automatically added and handled by the framework, *do not override* * - * @returns - a promise that resolves to the boolean true on successful verification or rejects on error + * @returns - a promise that resolves to an object containing a `verified` boolean property and an optional `error` + * for details * * @remarks Please see {@link https://www.w3.org/TR/vc-data-model/#presentations | Verifiable Credential data model} */ @@ -387,24 +425,34 @@ export class CredentialIssuer implements IAgentPlugin { args: ICreateVerifiablePresentationArgs, context: IContext, ): Promise { - const presentationContext = processEntryToArray( + let { + presentation, + proofFormat, + domain, + challenge, + removeOriginalFields, + keyRef, + save, + now, + ...otherOptions + } = args + const presentationContext: string[] = processEntryToArray( args?.presentation?.['@context'], MANDATORY_CREDENTIAL_CONTEXT, ) const presentationType = processEntryToArray(args?.presentation?.type, 'VerifiablePresentation') - const presentation: PresentationPayload = { - ...args?.presentation, + presentation = { + ...presentation, '@context': presentationContext, type: presentationType, - issuanceDate: args?.presentation?.issuanceDate || new Date(), } if (!isDefined(presentation.holder)) { - throw new Error('invalid_argument: args.presentation.holder must not be empty') + throw new Error('invalid_argument: presentation.holder must not be empty') } - if (args.presentation.verifiableCredential) { - const credentials = args.presentation.verifiableCredential.map((cred) => { + if (presentation.verifiableCredential) { + presentation.verifiableCredential = presentation.verifiableCredential.map((cred) => { // map JWT credentials to their canonical form if (typeof cred !== 'string' && cred.proof.jwt) { return cred.proof.jwt @@ -412,65 +460,66 @@ export class CredentialIssuer implements IAgentPlugin { return cred } }) - presentation.verifiableCredential = credentials } let identifier: IIdentifier try { identifier = await context.agent.didManagerGet({ did: presentation.holder }) } catch (e) { - throw new Error('invalid_argument: args.presentation.holder must be a DID managed by this agent') + throw new Error('invalid_argument: presentation.holder must be a DID managed by this agent') } - try { - //FIXME: `args` should allow picking a key or key type - const key = identifier.keys.find((k) => k.type === 'Secp256k1' || k.type === 'Ed25519') - if (!key) throw Error('No signing key for ' + identifier.did) + //FIXME: `args` should allow picking a key or key type + const key = identifier.keys.find((k) => k.type === 'Secp256k1' || k.type === 'Ed25519') + if (!key) throw Error('key_not_found: No signing key for ' + identifier.did) - let verifiablePresentation: VerifiablePresentation + let verifiablePresentation: VerifiablePresentation - if (args.proofFormat === 'lds') { - if (typeof context.agent.createVerifiablePresentationLD === 'function') { - verifiablePresentation = await context.agent.createVerifiablePresentationLD(args) - } else { - throw new Error( - 'invalid_configuration: your agent does not seem to have ICredentialIssuerLD plugin installed', - ) - } - } else if (args.proofFormat === 'EthereumEip712Signature2021') { - if (typeof context.agent.createVerifiablePresentationEIP712 === 'function') { - verifiablePresentation = await context.agent.createVerifiablePresentationEIP712(args) - } else { - throw new Error( - 'invalid_configuration: your agent does not seem to have ICredentialIssuerEIP712 plugin installed', - ) - } + if (proofFormat === 'lds') { + if (typeof context.agent.createVerifiablePresentationLD === 'function') { + verifiablePresentation = await context.agent.createVerifiablePresentationLD({ ...args, presentation }) } else { - // only add issuanceDate for JWT - presentation.issuanceDate = args.presentation.issuanceDate || new Date().toISOString() - debug('Signing VP with', identifier.did) - let alg = 'ES256K' - if (key.type === 'Ed25519') { - alg = 'EdDSA' - } - const signer = wrapSigner(context, key, alg) - - const jwt = await createVerifiablePresentationJwt( - presentation as any, - { did: identifier.did, signer, alg }, - { removeOriginalFields: args.removeOriginalFields, challenge: args.challenge, domain: args.domain }, + throw new Error( + 'invalid_setup: your agent does not seem to have ICredentialIssuerLD plugin installed', ) - //FIXME: flagging this as a potential privacy leak. - debug(jwt) - verifiablePresentation = normalizePresentation(jwt) } - if (args.save) { - await context.agent.dataStoreSaveVerifiablePresentation({ verifiablePresentation }) + } else if (proofFormat === 'EthereumEip712Signature2021') { + if (typeof context.agent.createVerifiablePresentationEIP712 === 'function') { + verifiablePresentation = await context.agent.createVerifiablePresentationEIP712({ + ...args, + presentation, + }) + } else { + throw new Error( + 'invalid_setup: your agent does not seem to have ICredentialIssuerEIP712 plugin installed', + ) } - return verifiablePresentation - } catch (error) { - debug(error) - return Promise.reject(error) + } else { + // only add issuanceDate for JWT + now = typeof now === 'number' ? new Date(now * 1000) : now + if (!Object.getOwnPropertyNames(presentation).includes('issuanceDate')) { + presentation.issuanceDate = (now instanceof Date ? now : new Date()).toISOString() + } + + debug('Signing VP with', identifier.did) + let alg = 'ES256K' + if (key.type === 'Ed25519') { + alg = 'EdDSA' + } + const signer = wrapSigner(context, key, alg) + + const jwt = await createVerifiablePresentationJwt( + presentation as any, + { did: identifier.did, signer, alg }, + { removeOriginalFields, challenge, domain, ...otherOptions }, + ) + //FIXME: flagging this as a potential privacy leak. + debug(jwt) + verifiablePresentation = normalizePresentation(jwt) + } + if (save) { + await context.agent.dataStoreSaveVerifiablePresentation({ verifiablePresentation }) } + return verifiablePresentation } /** {@inheritdoc ICredentialIssuer.createVerifiableCredential} */ @@ -478,46 +527,50 @@ export class CredentialIssuer implements IAgentPlugin { args: ICreateVerifiableCredentialArgs, context: IContext, ): Promise { - const credentialContext = processEntryToArray( - args?.credential?.['@context'], - MANDATORY_CREDENTIAL_CONTEXT, - ) - const credentialType = processEntryToArray(args?.credential?.type, 'VerifiableCredential') - const credential: CredentialPayload = { - ...args?.credential, + let { credential, proofFormat, keyRef, removeOriginalFields, save, now, ...otherOptions } = args + const credentialContext = processEntryToArray(credential['@context'], MANDATORY_CREDENTIAL_CONTEXT) + const credentialType = processEntryToArray(credential.type, 'VerifiableCredential') + + // only add issuanceDate for JWT + now = typeof now === 'number' ? new Date(now * 1000) : now + if (!Object.getOwnPropertyNames(credential).includes('issuanceDate')) { + credential.issuanceDate = (now instanceof Date ? now : new Date()).toISOString() + } + + credential = { + ...credential, '@context': credentialContext, type: credentialType, - issuanceDate: args?.credential?.issuanceDate || new Date().toISOString(), } //FIXME: if the identifier is not found, the error message should reflect that. const issuer = extractIssuer(credential) if (!issuer || typeof issuer === 'undefined') { - throw new Error('invalid_argument: args.credential.issuer must not be empty') + throw new Error('invalid_argument: credential.issuer must not be empty') } let identifier: IIdentifier try { identifier = await context.agent.didManagerGet({ did: issuer }) } catch (e) { - throw new Error(`invalid_argument: args.credential.issuer must be a DID managed by this agent. ${e}`) + throw new Error(`invalid_argument: credential.issuer must be a DID managed by this agent. ${e}`) } try { let verifiableCredential: VerifiableCredential - if (args.proofFormat === 'lds') { + if (proofFormat === 'lds') { if (typeof context.agent.createVerifiableCredentialLD === 'function') { - verifiableCredential = await context.agent.createVerifiableCredentialLD(args as any) + verifiableCredential = await context.agent.createVerifiableCredentialLD({ ...args, credential }) } else { throw new Error( - 'invalid_configuration: your agent does not seem to have ICredentialIssuerLD plugin installed', + 'invalid_setup: your agent does not seem to have ICredentialIssuerLD plugin installed', ) } - } else if (args.proofFormat === 'EthereumEip712Signature2021') { + } else if (proofFormat === 'EthereumEip712Signature2021') { if (typeof context.agent.createVerifiableCredentialEIP712 === 'function') { - verifiableCredential = await context.agent.createVerifiableCredentialEIP712(args as any) + verifiableCredential = await context.agent.createVerifiableCredentialEIP712({ ...args, credential }) } else { throw new Error( - 'invalid_configuration: your agent does not seem to have ICredentialIssuerEIP712 plugin installed', + 'invalid_setup: your agent does not seem to have ICredentialIssuerEIP712 plugin installed', ) } } else { @@ -534,13 +587,13 @@ export class CredentialIssuer implements IAgentPlugin { const jwt = await createVerifiableCredentialJwt( credential as any, { did: identifier.did, signer, alg }, - { removeOriginalFields: args.removeOriginalFields }, + { removeOriginalFields, ...otherOptions }, ) //FIXME: flagging this as a potential privacy leak. debug(jwt) verifiableCredential = normalizeCredential(jwt) } - if (args.save) { + if (save) { await context.agent.dataStoreSaveVerifiableCredential({ verifiableCredential }) } @@ -552,59 +605,102 @@ export class CredentialIssuer implements IAgentPlugin { } /** {@inheritdoc ICredentialIssuer.verifyCredential} */ - async verifyCredential(args: IVerifyCredentialArgs, context: IContext): Promise { - const credential = args.credential + async verifyCredential(args: IVerifyCredentialArgs, context: IContext): Promise { + let { credential, policies, ...otherOptions } = args let verifiedCredential: VerifiableCredential + let verificationResult: IVerifyResult = { verified: false } - const type: CredentialType = detectCredentialType(credential) - if (type == CredentialType.JWT) { + const type: DocumentFormat = detectDocumentType(credential) + if (type == DocumentFormat.JWT) { let jwt: string = typeof credential === 'string' ? credential : credential.proof.jwt const resolver = { resolve: (didUrl: string) => context.agent.resolveDid({ didUrl }) } as Resolvable try { - const verification = await verifyCredentialJWT(jwt, resolver) - verifiedCredential = verification.verifiableCredential + verificationResult = await verifyCredentialJWT(jwt, resolver, { + ...otherOptions, + policies: { + ...policies, + nbf: policies?.nbf ?? policies?.issuanceDate, + iat: policies?.iat ?? policies?.issuanceDate, + exp: policies?.exp ?? policies?.expirationDate, + aud: policies?.aud ?? policies?.audience, + }, + }) + verifiedCredential = verificationResult.verifiableCredential } catch (e: any) { - //TODO: return a more detailed reason for failure - return false + let { message, errorCode } = e + return { + verified: false, + error: { + message, + errorCode: errorCode ? errorCode : message.split(':')[0], + }, + } } - } else if (type == CredentialType.EIP712) { + } else if (type == DocumentFormat.EIP712) { if (typeof context.agent.verifyCredentialEIP712 !== 'function') { throw new Error( - 'invalid_configuration: your agent does not seem to have ICredentialIssuerEIP712 plugin installed', + 'invalid_setup: your agent does not seem to have ICredentialIssuerEIP712 plugin installed', ) } - if (!(await context.agent.verifyCredentialEIP712(args))) { - return false + try { + const result = await context.agent.verifyCredentialEIP712(args) + if (result) { + verificationResult = { + verified: true, + } + } else { + verificationResult = { + verified: false, + error: { + message: 'invalid_signature: The signature does not match any of the issuer signing keys', + errorCode: 'invalid_signature', + }, + } + } + verifiedCredential = credential + } catch (e: any) { + const { message, errorCode } = e + return { + verified: false, + error: { + message, + errorCode: errorCode ? errorCode : e.message.split(':')[0], + }, + } } - - verifiedCredential = credential - } else if (type == CredentialType.JSONLD) { + } else if (type == DocumentFormat.JSONLD) { if (typeof context.agent.verifyCredentialLD !== 'function') { throw new Error( - 'invalid_configuration: your agent does not seem to have ICredentialIssuerLD plugin installed', + 'invalid_setup: your agent does not seem to have ICredentialIssuerLD plugin installed', ) } - if (!(await context.agent.verifyCredentialLD(args))) return false - + verificationResult = await context.agent.verifyCredentialLD({ ...args, now: policies?.now }) verifiedCredential = credential } else { - throw new Error('Unknown credential type.') + throw new Error('invalid_argument: Unknown credential type.') } - if (await isRevoked(verifiedCredential, context)) { - return false + if (policies?.credentialStatus !== false && (await isRevoked(verifiedCredential, context))) { + verificationResult = { + verified: false, + error: { + message: 'revoked: The credential was revoked by the issuer', + errorCode: 'revoked', + }, + } } - return true + return verificationResult } /** {@inheritdoc ICredentialIssuer.verifyPresentation} */ async verifyPresentation(args: IVerifyPresentationArgs, context: IContext): Promise { - const presentation = args.presentation - if (typeof presentation === 'string' || (presentation)?.proof?.jwt) { + let { presentation, domain, challenge, fetchRemoteContexts, policies, ...otherOptions } = args + const type: DocumentFormat = detectDocumentType(presentation) + if (type === DocumentFormat.JWT) { // JWT let jwt: string if (typeof presentation === 'string') { @@ -614,7 +710,7 @@ export class CredentialIssuer implements IAgentPlugin { } const resolver = { resolve: (didUrl: string) => context.agent.resolveDid({ didUrl }) } as Resolvable - let audience = args.domain + let audience = domain if (!audience) { const { payload } = await decodeJWT(jwt) if (payload.aud) { @@ -629,32 +725,69 @@ export class CredentialIssuer implements IAgentPlugin { } try { - const verification = await verifyPresentationJWT(jwt, resolver, { - challenge: args.challenge, - domain: args.domain, + return await verifyPresentationJWT(jwt, resolver, { + challenge, + domain, audience, policies: { - nbf: args.policies?.issuanceDate, - iat: args.policies?.issuedAtDate, - now: args.policies?.now, - exp: args.policies?.expirationDate - } + ...policies, + nbf: policies?.nbf ?? policies?.issuanceDate, + iat: policies?.iat ?? policies?.issuanceDate, + exp: policies?.exp ?? policies?.expirationDate, + aud: policies?.aud ?? policies?.audience, + }, + ...otherOptions }) - return { verified: true } } catch (e: any) { - - // Need this logic for the errorCode because ErrorCodes are not being exported from did-jwt - // Uncase the code is not present the ErrorCode property will be undefined - return { verified: false, error: { message: e.message, errorCode: e.message.split(':')[0] } } + let { message, errorCode } = e + return { + verified: false, + error: { + message, + errorCode: errorCode ? errorCode : message.split(':')[0], + }, + } + } + } else if (type === DocumentFormat.EIP712) { + // JSON-LD + if (typeof context.agent.verifyPresentationEIP712 !== 'function') { + throw new Error( + 'invalid_setup: your agent does not seem to have ICredentialIssuerEIP712 plugin installed', + ) + } + try { + const result = await context.agent.verifyPresentationEIP712(args) + if (result) { + return { + verified: true, + } + } else { + return { + verified: false, + error: { + message: 'invalid_signature: The signature does not match any of the issuer signing keys', + errorCode: 'invalid_signature', + }, + } + } + } catch (e: any) { + const { message, errorCode } = e + return { + verified: false, + error: { + message, + errorCode: errorCode ? errorCode : e.message.split(':')[0], + }, + } } } else { // JSON-LD if (typeof context.agent.verifyPresentationLD === 'function') { - const result = await context.agent.verifyPresentationLD(args) + const result = await context.agent.verifyPresentationLD({ ...args, now: policies?.now }) return result } else { throw new Error( - 'invalid_configuration: your agent does not seem to have ICredentialIssuerLD plugin installed', + 'invalid_setup: your agent does not seem to have ICredentialIssuerLD plugin installed', ) } } @@ -672,21 +805,22 @@ function wrapSigner( } } -function detectCredentialType(credential: W3CVerifiableCredential): CredentialType { - if (typeof credential === 'string' || (credential)?.proof?.jwt) - return CredentialType.JWT - if ((credential)?.proof?.type === 'EthereumEip712Signature2021') - return CredentialType.EIP712 - return CredentialType.JSONLD +function detectDocumentType(document: W3CVerifiableCredential | W3CVerifiablePresentation): DocumentFormat { + if (typeof document === 'string' || (document)?.proof?.jwt) return DocumentFormat.JWT + if ((document)?.proof?.type === 'EthereumEip712Signature2021') + return DocumentFormat.EIP712 + return DocumentFormat.JSONLD } -async function isRevoked(credential: VerifiableCredential, context: IAgentContext): Promise { +async function isRevoked(credential: VerifiableCredential, context: IContext): Promise { if (!credential.credentialStatus) return false if (typeof context.agent.checkCredentialStatus === 'function') { const status = await context.agent.checkCredentialStatus({ credential }) - return status?.revoked == true + return status?.revoked == true || status?.verified === false } - throw new Error(`invalid_config: The credential status can't be verified by the agent`) + throw new Error( + `invalid_setup: The credential status can't be verified because there is no ICredentialStatusVerifier plugin installed.`, + ) } diff --git a/packages/credential-w3c/src/message-handler.ts b/packages/credential-w3c/src/message-handler.ts index c55335918..4d28176ab 100644 --- a/packages/credential-w3c/src/message-handler.ts +++ b/packages/credential-w3c/src/message-handler.ts @@ -6,7 +6,9 @@ import { normalizePresentation, validateJwtCredentialPayload, validateJwtPresentationPayload, + VC_ERROR, } from 'did-jwt-vc' +import { JWT_ERROR } from 'did-jwt' import { ICredentialIssuer } from './action-handler' import { v4 as uuidv4 } from 'uuid' import Debug from 'debug' @@ -105,20 +107,23 @@ export class W3cMessageHandler extends AbstractMessageHandler { // verify credential const credential = message.data as VerifiableCredential - // throws on error. - await context.agent.verifyCredential({ credential }) - message.id = computeEntryHash(message.raw || message.id || uuidv4()) - message.type = MessageTypes.vc - message.from = extractIssuer(credential) - message.to = credential.credentialSubject.id - - if (credential.tag) { - message.threadId = credential.tag - } + const result = await context.agent.verifyCredential({ credential }) + if (result.verified) { + message.id = computeEntryHash(message.raw || message.id || uuidv4()) + message.type = MessageTypes.vc + message.from = extractIssuer(credential) + message.to = credential.credentialSubject.id + + if (credential.tag) { + message.threadId = credential.tag + } - message.createdAt = credential.issuanceDate - message.credentials = [credential] - return message + message.createdAt = credential.issuanceDate + message.credentials = [credential] + return message + } else { + throw new Error(result.error?.message) + } } if (message.type === MessageTypes.vp && message.data) { @@ -126,26 +131,29 @@ export class W3cMessageHandler extends AbstractMessageHandler { const presentation = message.data as VerifiablePresentation // throws on error. - await context.agent.verifyPresentation({ + const result = await context.agent.verifyPresentation({ presentation, // FIXME: HARDCODED CHALLENGE VERIFICATION FOR NOW challenge: 'VERAMO', domain: 'VERAMO', }) + if (result.verified) { + message.id = computeEntryHash(message.raw || message.id || uuidv4()) + message.type = MessageTypes.vp + message.from = presentation.holder + // message.to = presentation.verifier?.[0] - message.id = computeEntryHash(message.raw || message.id || uuidv4()) - message.type = MessageTypes.vp - message.from = presentation.holder - // message.to = presentation.verifier?.[0] + if (presentation.tag) { + message.threadId = presentation.tag + } - if (presentation.tag) { - message.threadId = presentation.tag + // message.createdAt = presentation.issuanceDate + message.presentations = [presentation] + message.credentials = asArray(presentation.verifiableCredential).map(decodeCredentialToObject) + return message + } else { + throw new Error(result.error?.message) } - - // message.createdAt = presentation.issuanceDate - message.presentations = [presentation] - message.credentials = asArray(presentation.verifiableCredential).map(decodeCredentialToObject) - return message } return super.handle(message, context) diff --git a/packages/did-comm/package.json b/packages/did-comm/package.json index 95da054e4..aeaccfb1a 100644 --- a/packages/did-comm/package.json +++ b/packages/did-comm/package.json @@ -21,7 +21,7 @@ "@veramo/utils": "^3.1.4", "cross-fetch": "^3.1.4", "debug": "^4.3.3", - "did-jwt": "^6.3.0", + "did-jwt": "^6.6.0", "did-resolver": "^4.0.0", "uint8arrays": "^3.0.0", "uuid": "^8.3.0" diff --git a/packages/did-jwt/package.json b/packages/did-jwt/package.json index a07bdbee0..a10e1bc9f 100644 --- a/packages/did-jwt/package.json +++ b/packages/did-jwt/package.json @@ -12,7 +12,7 @@ "@veramo/core": "^3.1.4", "@veramo/message-handler": "^3.1.4", "debug": "^4.3.3", - "did-jwt": "^6.3.0", + "did-jwt": "^6.6.0", "did-resolver": "^4.0.0" }, "devDependencies": { diff --git a/packages/did-jwt/src/message-handler.ts b/packages/did-jwt/src/message-handler.ts index f020803b1..00a9aed38 100644 --- a/packages/did-jwt/src/message-handler.ts +++ b/packages/did-jwt/src/message-handler.ts @@ -3,6 +3,7 @@ import { AbstractMessageHandler, Message } from '@veramo/message-handler' import { verifyJWT, decodeJWT } from 'did-jwt' import Debug from 'debug' import { Resolvable } from 'did-resolver' + const debug = Debug('veramo:did-jwt:message-handler') export type IContext = IAgentContext @@ -18,10 +19,14 @@ export class JwtMessageHandler extends AbstractMessageHandler { const decoded = decodeJWT(message.raw) const audience = Array.isArray(decoded.payload.aud) ? decoded.payload.aud[0] : decoded.payload.aud const resolver = { resolve: (didUrl: string) => context.agent.resolveDid({ didUrl }) } as Resolvable - const verified = await verifyJWT(message.raw, { resolver, audience }) - debug('Message.raw is a valid JWT') - message.addMetaData({ type: decoded.header.typ || 'JWT', value: decoded.header.alg }) - message.data = verified.payload + const result = await verifyJWT(message.raw, { resolver, audience }) + if (result.verified) { + debug('Message.raw is a valid JWT') + message.addMetaData({ type: decoded.header.typ || 'JWT', value: decoded.header.alg }) + message.data = result.payload + } else { + debug(result) + } } catch (e: any) { debug(e.message) } diff --git a/packages/did-provider-ethr/package.json b/packages/did-provider-ethr/package.json index bf6863d24..567bad0e2 100644 --- a/packages/did-provider-ethr/package.json +++ b/packages/did-provider-ethr/package.json @@ -1,6 +1,6 @@ { "name": "@veramo/did-provider-ethr", - "description": "Veramo ehtr-did based identity controller plugin.", + "description": "Veramo ethr-did based identity controller plugin.", "version": "3.1.4", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/packages/did-provider-ethr/src/ethr-did-provider.ts b/packages/did-provider-ethr/src/ethr-did-provider.ts index 7019a52aa..af9ba751f 100644 --- a/packages/did-provider-ethr/src/ethr-did-provider.ts +++ b/packages/did-provider-ethr/src/ethr-did-provider.ts @@ -190,7 +190,7 @@ export class EthrDIDProvider extends AbstractIdentifierProvider { const network = this.getNetworkFor(networkSpecifier) if (!network) { throw new Error( - `invalid_config: Cannot create did:ethr. There is no known configuration for network=${networkSpecifier}'`, + `invalid_setup: Cannot create did:ethr. There is no known configuration for network=${networkSpecifier}'`, ) } if (typeof networkSpecifier === 'number') { diff --git a/packages/did-resolver/src/resolver.ts b/packages/did-resolver/src/resolver.ts index 14540f770..724c92dca 100644 --- a/packages/did-resolver/src/resolver.ts +++ b/packages/did-resolver/src/resolver.ts @@ -36,7 +36,7 @@ export class DIDResolverPlugin implements IAgentPlugin { this.didResolver = new Resolver(resolverMap as Record) } else { throw Error( - 'invalid_config: The DIDResolverPlugin must be initialized with a Resolvable or a map of methods to DIDResolver implementations', + 'invalid_setup: The DIDResolverPlugin must be initialized with a Resolvable or a map of methods to DIDResolver implementations', ) } diff --git a/packages/key-manager/package.json b/packages/key-manager/package.json index bf032a3c9..45fcaf900 100644 --- a/packages/key-manager/package.json +++ b/packages/key-manager/package.json @@ -14,12 +14,12 @@ "@ethersproject/transactions": "^5.6.2", "@stablelib/ed25519": "^1.0.2", "@veramo/core": "^3.1.4", - "did-jwt": "^6.3.0", + "did-jwt": "^6.6.0", "uint8arrays": "^3.0.0", "uuid": "^8.3.2" }, "devDependencies": { - "@ethersproject/abstract-signer": "^5.6.2", + "@ethersproject/abstract-signer": "5.6.2", "typescript": "4.7.3" }, "files": [ diff --git a/packages/kms-local/package.json b/packages/kms-local/package.json index 79ec08fbb..fe4116bdd 100644 --- a/packages/kms-local/package.json +++ b/packages/kms-local/package.json @@ -22,7 +22,7 @@ "@veramo/key-manager": "^3.1.4", "base-58": "^0.0.1", "debug": "^4.3.3", - "did-jwt": "^6.3.0", + "did-jwt": "^6.6.0", "uint8arrays": "^3.0.0" }, "devDependencies": { diff --git a/packages/selective-disclosure/package.json b/packages/selective-disclosure/package.json index f44a6a354..3dc06d434 100644 --- a/packages/selective-disclosure/package.json +++ b/packages/selective-disclosure/package.json @@ -19,7 +19,7 @@ "@veramo/did-jwt": "^3.1.4", "@veramo/message-handler": "^3.1.4", "debug": "^4.3.3", - "did-jwt": "^6.3.0", + "did-jwt": "^6.6.0", "uuid": "^8.3.0" }, "devDependencies": { diff --git a/packages/utils/package.json b/packages/utils/package.json index 05ef1e80a..752c84910 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -15,8 +15,8 @@ "blakejs": "^1.1.1", "cross-fetch": "^3.1.4", "debug": "^4.3.3", - "did-jwt": "^6.3.0", - "did-jwt-vc": "^3.0.0", + "did-jwt": "^6.6.0", + "did-jwt-vc": "^3.1.0", "did-resolver": "^4.0.0", "uint8arrays": "^3.0.0", "uuid": "^8.3.0" diff --git a/yarn.lock b/yarn.lock index 40cf53cfc..61a09a9bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7403,7 +7403,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -credential-status@^2.0.5: +credential-status@2.0.5, credential-status@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/credential-status/-/credential-status-2.0.5.tgz#59e34d30b85664160dd77a4357f1a1237cb7b2bc" integrity sha512-hh0pOcRidROn4MC1wF3vNURhPEMSzm3RcpFIl5PFVj5HWgCaZy16nXmrOl5cmr50Jhp2WV48cWbNMxh4OFWU+w== @@ -7958,12 +7958,12 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" -did-jwt-vc@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/did-jwt-vc/-/did-jwt-vc-3.0.1.tgz#d390957ac54fbc6504cba5dbad6df410c54136f1" - integrity sha512-M4GvWC7WFXjyPXXVdxD+IP8+KUbyizxPFB2z8fErnYQfdQzWuPqz4PM4MkgI3I4V66lNKfn6bgUivXVTuP8i2Q== +did-jwt-vc@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/did-jwt-vc/-/did-jwt-vc-3.1.0.tgz#aa7877c4c1f26ba11883604ac0ece30ca4fe78a4" + integrity sha512-8N54No9RQpbDM4a/aMiGc/tZWubtH8bqi7DLnO6B62AdWaNVKeS9ddcuANztSS1yTuypyzlyeeEtCTqEzpYgjA== dependencies: - did-jwt "^6.5.0" + did-jwt "^6.6.0" did-resolver "^4.0.0" did-jwt@^6.3.0: @@ -7984,10 +7984,10 @@ did-jwt@^6.3.0: multiformats "^9.6.5" uint8arrays "^3.0.0" -did-jwt@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/did-jwt/-/did-jwt-6.5.0.tgz#0179ed25db32c111a667563c0d7b54e5d6ecb939" - integrity sha512-yfdqk2N6+161Yeay4HMC5daic/HRIsc+W1r7JQlNtL14fmQyaPgJxnxpXdc7Qmwd+pMlfpi1oOEtraExDE6MzQ== +did-jwt@^6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/did-jwt/-/did-jwt-6.6.0.tgz#e7c932f7e3ff992b15aef7db3d530c81fb34902d" + integrity sha512-qSjXEEHS4fSbBHRCC/ObDzPVkCVvuXIfIiGWa03HNRr85gGkbxzBrxUsUbXfXo1CuzeCqmmZpK4nM4VWlZh6bQ== dependencies: "@stablelib/ed25519" "^1.0.2" "@stablelib/random" "^1.0.1" @@ -8002,7 +8002,7 @@ did-jwt@^6.5.0: multiformats "^9.6.5" uint8arrays "^3.0.0" -did-resolver@^4.0.0: +did-resolver@4.0.0, did-resolver@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.0.0.tgz#fc8f657b4cd7f44c2921051fb046599fbe7d4b31" integrity sha512-/roxrDr9EnAmLs+s9T+8+gcpilMo+IkeytcsGO7dcxvTmVJ+0Rt60HtV8o0UXHhGBo0Q+paMH/0ffXz1rqGFYg==