From 333b3951ae23b445d9ef353b9b75ce5d0bfd3d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB=20Maas?= Date: Tue, 10 Sep 2024 00:40:59 +0200 Subject: [PATCH 1/5] feat: Created an issuer verification of an EBSI issued credential --- .../oid4vci-holder/src/agent/OID4VCIHolder.ts | 15 ++++++++++++- .../src/agent/OID4VCIHolderService.ts | 11 +++++++++- .../src/types/IOID4VCIHolder.ts | 22 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index 1880e064e..32d6f0e82 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -84,7 +84,7 @@ import { StartResult, StoreCredentialBrandingArgs, StoreCredentialsArgs, - VerificationResult, + VerificationResult, VerifyCredentialIssuerArgs, VerifyCredentialIssuerResult, } from '../types/IOID4VCIHolder' import { getBasicIssuerLocaleBranding, @@ -97,6 +97,7 @@ import { verifyCredentialToAccept, } from './OID4VCIHolderService' +import 'cross-fetch/polyfill' /** * {@inheritDoc IOID4VCIHolder} */ @@ -189,6 +190,14 @@ export function signCallback( } } +export async function verifyCredentialIssuer(args: VerifyCredentialIssuerArgs): Promise { + const { wrappedVc } = args + + const vc = wrappedVc.decoded?.iss ?? (typeof wrappedVc.decoded?.vc?.issuer === 'string' ? wrappedVc.decoded?.vc?.issuer : wrappedVc.decoded?.vc?.issuer?.existingInstanceId) + const url = `https://api-conformance.ebsi.eu/trusted-issuers-registry/v4/issuers/${vc.issuer}`; + return await (await fetch(url)).json() +} + export class OID4VCIHolder implements IAgentPlugin { readonly eventTypes: Array = [ OID4VCIHolderEvent.CONTACT_IDENTITY_CREATED, @@ -235,12 +244,14 @@ export class OID4VCIHolder implements IAgentPlugin { private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise private readonly onCredentialStored?: (args: OnCredentialStoredArgs) => Promise private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise + private readonly onVerifyIssuerType?: (args: VerifyCredentialIssuerArgs) => Promise constructor(options?: OID4VCIHolderOptions) { const { onContactIdentityCreated, onCredentialStored, onIdentifierCreated, + onVerifyIssuerType, vcFormatPreferences, jsonldCryptographicSuitePreferences, didMethodPreferences, @@ -266,6 +277,7 @@ export class OID4VCIHolder implements IAgentPlugin { this.onContactIdentityCreated = onContactIdentityCreated this.onCredentialStored = onCredentialStored this.onIdentifierCreated = onIdentifierCreated + this.onVerifyIssuerType = onVerifyIssuerType } public async onEvent(event: any, context: RequiredContext): Promise { @@ -745,6 +757,7 @@ export class OID4VCIHolder implements IAgentPlugin { credentialsToAccept.map((credentialToAccept) => verifyCredentialToAccept({ mappedCredential: credentialToAccept, + onVerifyIssuerType: this.onVerifyIssuerType, context, }), ), diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts index 14828a391..4fecb77fc 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts @@ -119,7 +119,7 @@ export const selectCredentialLocaleBranding = async ( } export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArgs): Promise => { - const { mappedCredential, hasher, context } = args + const { mappedCredential, hasher, onVerifyIssuerType, context } = args const credential = mappedCredential.credentialToAccept.credentialResponse.credential as OriginalVerifiableCredential if (!credential) { @@ -139,6 +139,15 @@ export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArg } } + if (onVerifyIssuerType) { + const issuer = await onVerifyIssuerType({ + wrappedVc: wrappedVC + }) + if (!issuer.attributes.some(a => ['RootTAO', 'TAO'].includes(a.issuerType))) { + throw Error('Credential must be issued by a Root TAO or TAO') + } + } + const verificationResult: VerificationResult = await verifyCredential( { credential, diff --git a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts index 149ff5acb..3f17b3f03 100644 --- a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts @@ -76,6 +76,7 @@ export type OID4VCIHolderOptions = { onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise onCredentialStored?: (args: OnCredentialStoredArgs) => Promise onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise + onVerifyIssuerType?: (args: VerifyCredentialIssuerArgs) => Promise vcFormatPreferences?: Array jsonldCryptographicSuitePreferences?: Array defaultAuthorizationRequestOptions?: AuthorizationRequestOpts @@ -164,6 +165,7 @@ export enum SupportedLanguage { export type VerifyCredentialToAcceptArgs = { mappedCredential: MappedCredentialToAccept + onVerifyIssuerType?: (args: VerifyCredentialIssuerArgs) => Promise hasher?: Hasher context: RequiredContext } @@ -602,4 +604,24 @@ export type RequiredContext = IAgentContext< IKeyManager & ISDJwtPlugin > + +export type IssuerType = 'RootTAO' | 'TAO' | 'TI' | 'Revoked or Undefined' + +export type VerifyCredentialIssuerArgs = { + wrappedVc: WrappedVerifiableCredential +} + +export type Attribute = { + hash: string + body: string + issuerType: IssuerType + tao: string + rootTao: string +} + +export type VerifyCredentialIssuerResult = { + did: string + attributes: Attribute[] +} + export type DidAgents = TAgent From 402e25032b39be6db18a3a9dc3378c6af7cab321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB=20Maas?= Date: Tue, 10 Sep 2024 13:30:28 +0200 Subject: [PATCH 2/5] refactor: Refactored the EBSI credential issuer verification --- .../oid4vci-holder/src/agent/OID4VCIHolder.ts | 36 +++++++++++++------ .../src/agent/OID4VCIHolderService.ts | 10 +++--- .../src/types/IOID4VCIHolder.ts | 8 ++--- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index 32d6f0e82..4b01faa88 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -59,7 +59,7 @@ import { OID4VCIMachine } from '../machine/oid4vciMachine' import { AddContactIdentityArgs, AddIssuerBrandingArgs, - AssertValidCredentialsArgs, + AssertValidCredentialsArgs, Attribute, createCredentialsToSelectFromArgs, CredentialToAccept, CredentialToSelectFromResult, @@ -84,7 +84,7 @@ import { StartResult, StoreCredentialBrandingArgs, StoreCredentialsArgs, - VerificationResult, VerifyCredentialIssuerArgs, VerifyCredentialIssuerResult, + VerificationResult, VerifyEBSICredentialIssuerArgs, VerifyEBSICredentialIssuerResult, } from '../types/IOID4VCIHolder' import { getBasicIssuerLocaleBranding, @@ -190,12 +190,28 @@ export function signCallback( } } -export async function verifyCredentialIssuer(args: VerifyCredentialIssuerArgs): Promise { +export async function verifyEBSICredentialIssuer(args: VerifyEBSICredentialIssuerArgs): Promise { const { wrappedVc } = args - const vc = wrappedVc.decoded?.iss ?? (typeof wrappedVc.decoded?.vc?.issuer === 'string' ? wrappedVc.decoded?.vc?.issuer : wrappedVc.decoded?.vc?.issuer?.existingInstanceId) - const url = `https://api-conformance.ebsi.eu/trusted-issuers-registry/v4/issuers/${vc.issuer}`; - return await (await fetch(url)).json() + const issuer = wrappedVc.decoded?.iss ?? (typeof wrappedVc.decoded?.vc?.issuer === 'string' ? wrappedVc.decoded?.vc?.issuer : wrappedVc.decoded?.vc?.issuer?.existingInstanceId) + + if (!issuer) { + return + } + + const url = `https://api-conformance.ebsi.eu/trusted-issuers-registry/v4/issuers/${issuer}`; + const response = await fetch(url) + if (response.status !== 200) { + return + } + + const payload = await response.json() + + if (!payload.attributes.some((a: Attribute) => ['RootTAO', 'TAO', 'TI'].includes(a.issuerType))) { + return + } + + return payload } export class OID4VCIHolder implements IAgentPlugin { @@ -244,14 +260,14 @@ export class OID4VCIHolder implements IAgentPlugin { private readonly onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise private readonly onCredentialStored?: (args: OnCredentialStoredArgs) => Promise private readonly onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise - private readonly onVerifyIssuerType?: (args: VerifyCredentialIssuerArgs) => Promise + private readonly onVerifyEBSICredentialIssuer?: (args: VerifyEBSICredentialIssuerArgs) => Promise constructor(options?: OID4VCIHolderOptions) { const { onContactIdentityCreated, onCredentialStored, onIdentifierCreated, - onVerifyIssuerType, + onVerifyEBSICredentialIssuer, vcFormatPreferences, jsonldCryptographicSuitePreferences, didMethodPreferences, @@ -277,7 +293,7 @@ export class OID4VCIHolder implements IAgentPlugin { this.onContactIdentityCreated = onContactIdentityCreated this.onCredentialStored = onCredentialStored this.onIdentifierCreated = onIdentifierCreated - this.onVerifyIssuerType = onVerifyIssuerType + this.onVerifyEBSICredentialIssuer = onVerifyEBSICredentialIssuer } public async onEvent(event: any, context: RequiredContext): Promise { @@ -757,7 +773,7 @@ export class OID4VCIHolder implements IAgentPlugin { credentialsToAccept.map((credentialToAccept) => verifyCredentialToAccept({ mappedCredential: credentialToAccept, - onVerifyIssuerType: this.onVerifyIssuerType, + onVerifyEBSICredentialIssuer: this.onVerifyEBSICredentialIssuer, context, }), ), diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts index 4fecb77fc..738a7cac0 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts @@ -119,7 +119,7 @@ export const selectCredentialLocaleBranding = async ( } export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArgs): Promise => { - const { mappedCredential, hasher, onVerifyIssuerType, context } = args + const { mappedCredential, hasher, onVerifyEBSICredentialIssuer, context } = args const credential = mappedCredential.credentialToAccept.credentialResponse.credential as OriginalVerifiableCredential if (!credential) { @@ -139,12 +139,12 @@ export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArg } } - if (onVerifyIssuerType) { - const issuer = await onVerifyIssuerType({ + if (onVerifyEBSICredentialIssuer) { + const response = await onVerifyEBSICredentialIssuer({ wrappedVc: wrappedVC }) - if (!issuer.attributes.some(a => ['RootTAO', 'TAO'].includes(a.issuerType))) { - throw Error('Credential must be issued by a Root TAO or TAO') + if (!response) { + return Promise.reject(Error('The issuer of the EBSI credential cannot be trusted.')) } } diff --git a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts index 3f17b3f03..c2cfa3465 100644 --- a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts @@ -76,7 +76,7 @@ export type OID4VCIHolderOptions = { onContactIdentityCreated?: (args: OnContactIdentityCreatedArgs) => Promise onCredentialStored?: (args: OnCredentialStoredArgs) => Promise onIdentifierCreated?: (args: OnIdentifierCreatedArgs) => Promise - onVerifyIssuerType?: (args: VerifyCredentialIssuerArgs) => Promise + onVerifyEBSICredentialIssuer?: (args: VerifyEBSICredentialIssuerArgs) => Promise vcFormatPreferences?: Array jsonldCryptographicSuitePreferences?: Array defaultAuthorizationRequestOptions?: AuthorizationRequestOpts @@ -165,7 +165,7 @@ export enum SupportedLanguage { export type VerifyCredentialToAcceptArgs = { mappedCredential: MappedCredentialToAccept - onVerifyIssuerType?: (args: VerifyCredentialIssuerArgs) => Promise + onVerifyEBSICredentialIssuer?: (args: VerifyEBSICredentialIssuerArgs) => Promise hasher?: Hasher context: RequiredContext } @@ -607,7 +607,7 @@ export type RequiredContext = IAgentContext< export type IssuerType = 'RootTAO' | 'TAO' | 'TI' | 'Revoked or Undefined' -export type VerifyCredentialIssuerArgs = { +export type VerifyEBSICredentialIssuerArgs = { wrappedVc: WrappedVerifiableCredential } @@ -619,7 +619,7 @@ export type Attribute = { rootTao: string } -export type VerifyCredentialIssuerResult = { +export type VerifyEBSICredentialIssuerResult = { did: string attributes: Attribute[] } From cab5464e5aae8e98b589e7094c11d3b809344eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB=20Maas?= Date: Wed, 11 Sep 2024 16:47:24 +0200 Subject: [PATCH 3/5] refactor: Added tests and fixed issues with the returned promises --- .../__tests__/EBSIFunctions.test.ts | 66 +++++++++++++++++++ .../oid4vci-holder/src/agent/OID4VCIHolder.ts | 6 +- 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts diff --git a/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts b/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts new file mode 100644 index 000000000..6fafd0a03 --- /dev/null +++ b/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts @@ -0,0 +1,66 @@ +import {verifyEBSICredentialIssuer} from "../src/agent/OID4VCIHolder"; +import {CredentialMapper} from "@sphereon/ssi-types"; + +const nock = require("nock") + +const BASE_URL = 'https://api-conformance.ebsi.eu' +const GET_VALID_ISSUER_URI = '/trusted-issuers-registry/v4/issuers/did:ebsi:ziDnioxYYLW1a3qUbqTFz4W' +const GET_INVALID_ISSUER_URI = '/trusted-issuers-registry/v4/issuers/invalid_issuer' + +describe('EBSI Functions', () => { + describe('verifyEBSICredentialIssuer', () => { + + const validIssuerResult = { + did: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W", + attributes: [{ + hash: "test", + body: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE", + issuerType: "RootTAO", + tao: "did:ebsi:zeybAiJxzUUrWQ1YM51SY35", + rootTao: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W" + }] + } + + const notIssuerResult = { + did: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W", + attributes: [{ + hash: "test", + body: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE", + issuerType: "Revoked or Undefined", + tao: "did:ebsi:zeybAiJxzUUrWQ1YM51SY35", + rootTao: "did:ebsi:ziDnioxYYLW1a3qUbqTFz4W" + }] + } + + it(`should return the issuer's did and attributes if the issuer is valid`, async () => { + nock(BASE_URL) + .get(GET_VALID_ISSUER_URI) + .reply(200, JSON.stringify(validIssuerResult)) + await expect(verifyEBSICredentialIssuer({ + wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE") + })).resolves.toEqual(validIssuerResult) + }) + + it(`should return undefined if the issuer type is not RootTAO, TAO or TI`, async () => { + nock(BASE_URL) + .get(GET_VALID_ISSUER_URI) + .reply(200, JSON.stringify(notIssuerResult)) + await expect(verifyEBSICredentialIssuer({ + wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE") + })).rejects.toBeUndefined() + }) + + it(`should return undefined if the issuer's did is not provided`, async () => { + await expect(verifyEBSICredentialIssuer({ + wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4M2RjYTg0NGI3Mzk4YzgxNDRlMmIzOTk5YzczMDZjYjc5NjMwMmFlZDE0N2Q2NjhmYjZmYjliYTQ5NmQ1MGQifQ.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6IiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmVic2k6emV5YkFpSnh6VVVyV1ExWU01MVNZMzUiLCJhY2NyZWRpdGVkRm9yIjpbXX0sInRlcm1zT2ZVc2UiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsInR5cGUiOiJJc3N1YW5jZUNlcnRpZmljYXRlIn0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS1waWxvdC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92Mi9zY2hlbWFzL3ozTWdVRlVrYjcyMnVxNHgzZHY1eUFKbW5ObXpERmVLNVVDOHg4M1FvZUxKTSIsInR5cGUiOiJGdWxsSnNvblNjaGVtYVZhbGlkYXRvcjIwMjEifX19.OFcZ-7iqoP_LmxTWqM9rR4aOK8VPyKyRJ2R8MD6m1jT2LzyqMVKzX__EF6e0ghs73l-nVtJBIu28QFsMFxAODg") + })).rejects.toBeUndefined() + }) + + it(`should return undefined if the issuer's did is invalid`, async () => { + nock(BASE_URL).get(GET_INVALID_ISSUER_URI).reply(400) + await expect(verifyEBSICredentialIssuer({ + wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4M2RjYTg0NGI3Mzk4YzgxNDRlMmIzOTk5YzczMDZjYjc5NjMwMmFlZDE0N2Q2NjhmYjZmYjliYTQ5NmQ1MGQifQ.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImludmFsaWRfaXNzdWVyIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.r0kAeMRrwn8lQNJakAmfWRtLmRQdRbULNjbbvTPsirpVGmN5O0V9O7eQ7_S4sHTF8p_AShSanv4MLvtRfCvg1A") + })).rejects.toBeUndefined() + }) + }) +}) diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index 4b01faa88..dc85fb2cb 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -196,19 +196,19 @@ export async function verifyEBSICredentialIssuer(args: VerifyEBSICredentialIssue const issuer = wrappedVc.decoded?.iss ?? (typeof wrappedVc.decoded?.vc?.issuer === 'string' ? wrappedVc.decoded?.vc?.issuer : wrappedVc.decoded?.vc?.issuer?.existingInstanceId) if (!issuer) { - return + return Promise.reject(undefined) } const url = `https://api-conformance.ebsi.eu/trusted-issuers-registry/v4/issuers/${issuer}`; const response = await fetch(url) if (response.status !== 200) { - return + return Promise.reject(undefined) } const payload = await response.json() if (!payload.attributes.some((a: Attribute) => ['RootTAO', 'TAO', 'TI'].includes(a.issuerType))) { - return + return Promise.reject(undefined) } return payload From bb7cee0ca2437dc32fa5ea577fb384e56c83cd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB=20Maas?= Date: Thu, 12 Sep 2024 10:49:50 +0200 Subject: [PATCH 4/5] refactor: Added tests and fixed issues with the returned promises --- packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts | 2 +- packages/oid4vci-holder/src/agent/OID4VCIHolder.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts b/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts index 6fafd0a03..67b4bbc20 100644 --- a/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts +++ b/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts @@ -53,7 +53,7 @@ describe('EBSI Functions', () => { it(`should return undefined if the issuer's did is not provided`, async () => { await expect(verifyEBSICredentialIssuer({ wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4M2RjYTg0NGI3Mzk4YzgxNDRlMmIzOTk5YzczMDZjYjc5NjMwMmFlZDE0N2Q2NjhmYjZmYjliYTQ5NmQ1MGQifQ.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6IiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmVic2k6emV5YkFpSnh6VVVyV1ExWU01MVNZMzUiLCJhY2NyZWRpdGVkRm9yIjpbXX0sInRlcm1zT2ZVc2UiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsInR5cGUiOiJJc3N1YW5jZUNlcnRpZmljYXRlIn0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS1waWxvdC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92Mi9zY2hlbWFzL3ozTWdVRlVrYjcyMnVxNHgzZHY1eUFKbW5ObXpERmVLNVVDOHg4M1FvZUxKTSIsInR5cGUiOiJGdWxsSnNvblNjaGVtYVZhbGlkYXRvcjIwMjEifX19.OFcZ-7iqoP_LmxTWqM9rR4aOK8VPyKyRJ2R8MD6m1jT2LzyqMVKzX__EF6e0ghs73l-nVtJBIu28QFsMFxAODg") - })).rejects.toBeUndefined() + })).rejects.toThrow(Error("The issuer of the VC is required")) }) it(`should return undefined if the issuer's did is invalid`, async () => { diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index dc85fb2cb..6a6c7cd6a 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -196,7 +196,7 @@ export async function verifyEBSICredentialIssuer(args: VerifyEBSICredentialIssue const issuer = wrappedVc.decoded?.iss ?? (typeof wrappedVc.decoded?.vc?.issuer === 'string' ? wrappedVc.decoded?.vc?.issuer : wrappedVc.decoded?.vc?.issuer?.existingInstanceId) if (!issuer) { - return Promise.reject(undefined) + return Promise.reject(Error("The issuer of the VC is required")) } const url = `https://api-conformance.ebsi.eu/trusted-issuers-registry/v4/issuers/${issuer}`; From 46624fe7dc3df35a7a93d3b54dbb39ca7e702e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB=20Maas?= Date: Thu, 12 Sep 2024 12:00:30 +0200 Subject: [PATCH 5/5] refactor: Refactored error handling and moved the issuer types to a parameter --- .../__tests__/EBSIFunctions.test.ts | 20 +++++++++++-------- .../oid4vci-holder/src/agent/OID4VCIHolder.ts | 12 +++++------ .../src/agent/OID4VCIHolderService.ts | 17 ++++++++-------- .../src/types/IOID4VCIHolder.ts | 3 ++- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts b/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts index 67b4bbc20..459d633d0 100644 --- a/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts +++ b/packages/oid4vci-holder/__tests__/EBSIFunctions.test.ts @@ -1,5 +1,6 @@ import {verifyEBSICredentialIssuer} from "../src/agent/OID4VCIHolder"; import {CredentialMapper} from "@sphereon/ssi-types"; +import {IssuerType} from "../src"; const nock = require("nock") @@ -37,30 +38,33 @@ describe('EBSI Functions', () => { .get(GET_VALID_ISSUER_URI) .reply(200, JSON.stringify(validIssuerResult)) await expect(verifyEBSICredentialIssuer({ - wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE") + wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE"), + issuerType: ['RootTAO'] })).resolves.toEqual(validIssuerResult) }) - it(`should return undefined if the issuer type is not RootTAO, TAO or TI`, async () => { + it(`should throw an Error if the issuer type is not RootTAO, TAO or TI`, async () => { + const issuerType: IssuerType[] = ['RootTAO', 'TAO', 'TI'] nock(BASE_URL) .get(GET_VALID_ISSUER_URI) .reply(200, JSON.stringify(notIssuerResult)) await expect(verifyEBSICredentialIssuer({ - wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE") - })).rejects.toBeUndefined() + wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJ0eXAiOiJKV1QiLCJraWQiOiIxODNkY2E4NDRiNzM5OGM4MTQ0ZTJiMzk5OWM3MzA2Y2I3OTYzMDJhZWQxNDdkNjY4ZmI2ZmI5YmE0OTZkNTBkIiwiYWxnIjoiRVMyNTZLIn0.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImRpZDplYnNpOnppRG5pb3hZWUxXMWEzcVVicVRGejRXIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.QWNWTWlrbUpLcFJaLVBGczQ0U3Mxb200Mk4yb3JzWndsTXp3REpHTTMxSUM2WG5ZVXJ0ZlY4RHFTbVQtaXBIMEdLSDZhclFEcGtrbXZTTy1NenYxWEE"), + issuerType: issuerType + })).rejects.toThrowError(Error(`The issuer type is required to be one of: ${issuerType.join(', ')}`)) }) - it(`should return undefined if the issuer's did is not provided`, async () => { + it(`should throw an Error if the issuer's did is not provided`, async () => { await expect(verifyEBSICredentialIssuer({ wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4M2RjYTg0NGI3Mzk4YzgxNDRlMmIzOTk5YzczMDZjYjc5NjMwMmFlZDE0N2Q2NjhmYjZmYjliYTQ5NmQ1MGQifQ.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6IiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmVic2k6emV5YkFpSnh6VVVyV1ExWU01MVNZMzUiLCJhY2NyZWRpdGVkRm9yIjpbXX0sInRlcm1zT2ZVc2UiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsInR5cGUiOiJJc3N1YW5jZUNlcnRpZmljYXRlIn0sImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL2FwaS1waWxvdC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92Mi9zY2hlbWFzL3ozTWdVRlVrYjcyMnVxNHgzZHY1eUFKbW5ObXpERmVLNVVDOHg4M1FvZUxKTSIsInR5cGUiOiJGdWxsSnNvblNjaGVtYVZhbGlkYXRvcjIwMjEifX19.OFcZ-7iqoP_LmxTWqM9rR4aOK8VPyKyRJ2R8MD6m1jT2LzyqMVKzX__EF6e0ghs73l-nVtJBIu28QFsMFxAODg") - })).rejects.toThrow(Error("The issuer of the VC is required")) + })).rejects.toThrowError(Error("The issuer of the VC is required to be present")) }) - it(`should return undefined if the issuer's did is invalid`, async () => { + it(`should throw an Error if the issuer's did is invalid`, async () => { nock(BASE_URL).get(GET_INVALID_ISSUER_URI).reply(400) await expect(verifyEBSICredentialIssuer({ wrappedVc: CredentialMapper.toWrappedVerifiableCredential("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE4M2RjYTg0NGI3Mzk4YzgxNDRlMmIzOTk5YzczMDZjYjc5NjMwMmFlZDE0N2Q2NjhmYjZmYjliYTQ5NmQ1MGQifQ.eyJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aURuaW94WVlMVzFhM3FVYnFURno0VyIsImlhdCI6MTcxNDQxMzA4OCwianRpIjoidXJuOnV1aWQ6NWZiN2Q5OGItMTA4Yy00YmMwLTlmZmMtYzY5Zjg0ZWQ3ODhmIiwibmJmIjoxNzE0NDEzMDg4LCJleHAiOjE3NDU5NDkwODgsInN1YiI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiJ1cm46dXVpZDo1ZmI3ZDk4Yi0xMDhjLTRiYzAtOWZmYy1jNjlmODRlZDc4OGYiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZUF1dGhvcmlzYXRpb25Ub09uYm9hcmQiXSwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNC0yOVQxNzo1MToyOFoiLCJpc3N1ZWQiOiIyMDI0LTA0LTI5VDE3OjUxOjI4WiIsInZhbGlkRnJvbSI6IjIwMjQtMDQtMjlUMTc6NTE6MjhaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDI1LTA0LTI5VDE3OjUxOjI4WiIsImlzc3VlciI6ImludmFsaWRfaXNzdWVyIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZWJzaTp6ZXliQWlKeHpVVXJXUTFZTTUxU1kzNSIsImFjY3JlZGl0ZWRGb3IiOltdfSwidGVybXNPZlVzZSI6eyJpZCI6ImRpZDplYnNpOnpleWJBaUp4elVVcldRMVlNNTFTWTM1IiwidHlwZSI6Iklzc3VhbmNlQ2VydGlmaWNhdGUifSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLXBpbG90LmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YyL3NjaGVtYXMvejNNZ1VGVWtiNzIydXE0eDNkdjV5QUptbk5tekRGZUs1VUM4eDgzUW9lTEpNIiwidHlwZSI6IkZ1bGxKc29uU2NoZW1hVmFsaWRhdG9yMjAyMSJ9fX0.r0kAeMRrwn8lQNJakAmfWRtLmRQdRbULNjbbvTPsirpVGmN5O0V9O7eQ7_S4sHTF8p_AShSanv4MLvtRfCvg1A") - })).rejects.toBeUndefined() + })).rejects.toThrowError('The issuer of the VC cannot be trusted') }) }) }) diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts index 6a6c7cd6a..06987b22f 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolder.ts @@ -190,25 +190,25 @@ export function signCallback( } } -export async function verifyEBSICredentialIssuer(args: VerifyEBSICredentialIssuerArgs): Promise { - const { wrappedVc } = args +export async function verifyEBSICredentialIssuer(args: VerifyEBSICredentialIssuerArgs): Promise { + const { wrappedVc, issuerType = ['TI'] } = args const issuer = wrappedVc.decoded?.iss ?? (typeof wrappedVc.decoded?.vc?.issuer === 'string' ? wrappedVc.decoded?.vc?.issuer : wrappedVc.decoded?.vc?.issuer?.existingInstanceId) if (!issuer) { - return Promise.reject(Error("The issuer of the VC is required")) + throw Error("The issuer of the VC is required to be present") } const url = `https://api-conformance.ebsi.eu/trusted-issuers-registry/v4/issuers/${issuer}`; const response = await fetch(url) if (response.status !== 200) { - return Promise.reject(undefined) + throw Error('The issuer of the VC cannot be trusted') } const payload = await response.json() - if (!payload.attributes.some((a: Attribute) => ['RootTAO', 'TAO', 'TI'].includes(a.issuerType))) { - return Promise.reject(undefined) + if (!payload.attributes.some((a: Attribute) => issuerType.includes(a.issuerType))) { + throw Error(`The issuer type is required to be one of: ${issuerType.join(', ')}`) } return payload diff --git a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts index 738a7cac0..9ae3bb542 100644 --- a/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts +++ b/packages/oid4vci-holder/src/agent/OID4VCIHolderService.ts @@ -135,16 +135,17 @@ export const verifyCredentialToAccept = async (args: VerifyCredentialToAcceptArg ) { // TODO: Skipping VC validation for EBSI conformance issued credential, as their Issuer is not present in the ledger (sigh) if (JSON.stringify(wrappedVC.decoded).includes('vc:ebsi:conformance')) { - return { source: wrappedVC, error: undefined, result: true, subResults: [] } satisfies VerificationResult + return {source: wrappedVC, error: undefined, result: true, subResults: []} satisfies VerificationResult } - } - if (onVerifyEBSICredentialIssuer) { - const response = await onVerifyEBSICredentialIssuer({ - wrappedVc: wrappedVC - }) - if (!response) { - return Promise.reject(Error('The issuer of the EBSI credential cannot be trusted.')) + if (onVerifyEBSICredentialIssuer) { + try { + await onVerifyEBSICredentialIssuer({ + wrappedVc: wrappedVC + }) + } catch(e) { + return {source: wrappedVC, error: e.message, result: true, subResults: []} satisfies VerificationResult + } } } diff --git a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts index c2cfa3465..6223ce5e9 100644 --- a/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts +++ b/packages/oid4vci-holder/src/types/IOID4VCIHolder.ts @@ -608,7 +608,8 @@ export type RequiredContext = IAgentContext< export type IssuerType = 'RootTAO' | 'TAO' | 'TI' | 'Revoked or Undefined' export type VerifyEBSICredentialIssuerArgs = { - wrappedVc: WrappedVerifiableCredential + wrappedVc: WrappedVerifiableCredential, + issuerType?: IssuerType[] } export type Attribute = {