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