diff --git a/.changeset/late-shirts-flash.md b/.changeset/late-shirts-flash.md new file mode 100644 index 0000000000..06b0c8b152 --- /dev/null +++ b/.changeset/late-shirts-flash.md @@ -0,0 +1,6 @@ +--- +'@credo-ts/openid4vc': minor +'@credo-ts/core': minor +--- + +feat: allow dynamicaly providing x509 certificates for all types of verifications diff --git a/demo-openid/src/Holder.ts b/demo-openid/src/Holder.ts index 7a3aa5a23b..453825ec68 100644 --- a/demo-openid/src/Holder.ts +++ b/demo-openid/src/Holder.ts @@ -9,6 +9,7 @@ import { DidKey, DidJwk, getJwkFromKey, + X509Module, } from '@credo-ts/core' import { authorizationCodeGrantIdentifier, @@ -19,12 +20,26 @@ import { import { ariesAskar } from '@hyperledger/aries-askar-nodejs' import { BaseAgent } from './BaseAgent' -import { Output } from './OutputClass' +import { greenText, Output } from './OutputClass' function getOpenIdHolderModules() { return { askar: new AskarModule({ ariesAskar }), openId4VcHolder: new OpenId4VcHolderModule(), + x509: new X509Module({ + getTrustedCertificatesForVerification: (agentContext, { certificateChain, verification }) => { + console.log( + greenText( + `dyncamically trusting certificate ${certificateChain[0].getIssuerNameField('C')} for verification of ${ + verification.type + }`, + true + ) + ) + + return [certificateChain[0].toString('pem')] + }, + }), } as const } @@ -41,9 +56,6 @@ export class Holder extends BaseAgent> public static async build(): Promise { const holder = new Holder(3000, 'OpenId4VcHolder ' + Math.random().toString()) await holder.initializeAgent('96213c3d7fc8d4d6754c7a0fd969598e') - await holder.agent.x509.addTrustedCertificate( - 'MIH7MIGioAMCAQICEFvUcSkwWUaPlEWnrOmu_EYwCgYIKoZIzj0EAwIwDTELMAkGA1UEBhMCREUwIBcNMDAwMTAxMDAwMDAwWhgPMjA1MDAxMDEwMDAwMDBaMA0xCzAJBgNVBAYTAkRFMDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgAC3A9V8ynqRcVjADqlfpZ9X8mwbew0TuQldH_QOpkadsWjAjAAMAoGCCqGSM49BAMCA0gAMEUCIQDXGNookSkHqRXiOP_0fVUdNIScY13h3DWkqSopFIYB2QIgUzNFnZ-SEdm-7UMzggaPiFgtznVzmHw2h4vVtuLzWlA' - ) return holder } diff --git a/jest.config.ts b/jest.config.ts index 286a152f79..7de18bcfe3 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -5,6 +5,7 @@ import base from './jest.config.base' const config: Config.InitialOptions = { ...base, roots: [''], + coverageReporters: ['text-summary', 'lcov', 'json'], coveragePathIgnorePatterns: ['/build/', '/node_modules/', '/__tests__/', 'tests'], coverageDirectory: '/coverage/', projects: [ diff --git a/packages/core/src/crypto/JwsService.ts b/packages/core/src/crypto/JwsService.ts index eed23e2df2..31f04163a5 100644 --- a/packages/core/src/crypto/JwsService.ts +++ b/packages/core/src/crypto/JwsService.ts @@ -12,7 +12,7 @@ import type { AgentContext } from '../agent' import type { Buffer } from '../utils' import { CredoError } from '../error' -import { X509ModuleConfig } from '../modules/x509' +import { EncodedX509Certificate, X509ModuleConfig } from '../modules/x509' import { injectable } from '../plugins' import { isJsonObject, JsonEncoder, TypedArrayEncoder } from '../utils' import { WalletError } from '../wallet/error' @@ -227,10 +227,16 @@ export class JwsService { protectedHeader: { alg: string; [key: string]: unknown } payload: string jwkResolver?: JwsJwkResolver - trustedCertificates?: [string, ...string[]] + trustedCertificates?: EncodedX509Certificate[] } ): Promise { - const { protectedHeader, jwkResolver, jws, payload, trustedCertificates: trustedCertificatesFromOptions } = options + const { + protectedHeader, + jwkResolver, + jws, + payload, + trustedCertificates: trustedCertificatesFromOptions = [], + } = options if ([protectedHeader.jwk, protectedHeader.kid, protectedHeader.x5c].filter(Boolean).length > 1) { throw new CredoError('Only one of jwk, kid and x5c headers can and must be provided.') @@ -244,8 +250,9 @@ export class JwsService { throw new CredoError('x5c header is not a valid JSON array of string.') } - const trustedCertificatesFromConfig = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates - const trustedCertificates = [...(trustedCertificatesFromConfig ?? []), ...(trustedCertificatesFromOptions ?? [])] + const trustedCertificatesFromConfig = + agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates ?? [] + const trustedCertificates = trustedCertificatesFromOptions ?? trustedCertificatesFromConfig if (trustedCertificates.length === 0) { throw new CredoError( `trustedCertificates is required when the JWS protected header contains an 'x5c' property.` @@ -254,7 +261,7 @@ export class JwsService { await X509Service.validateCertificateChain(agentContext, { certificateChain: protectedHeader.x5c, - trustedCertificates: trustedCertificates as [string, ...string[]], // Already validated that it has at least one certificate + trustedCertificates, }) const certificate = X509Service.getLeafCertificate(agentContext, { certificateChain: protectedHeader.x5c }) @@ -315,7 +322,7 @@ export interface VerifyJwsOptions { */ jwkResolver?: JwsJwkResolver - trustedCertificates?: [string, ...string[]] + trustedCertificates?: EncodedX509Certificate[] } export type JwsJwkResolver = (options: { diff --git a/packages/core/src/crypto/jose/jwt/Jwt.ts b/packages/core/src/crypto/jose/jwt/Jwt.ts index 6de8095c41..b55b77b0df 100644 --- a/packages/core/src/crypto/jose/jwt/Jwt.ts +++ b/packages/core/src/crypto/jose/jwt/Jwt.ts @@ -11,6 +11,7 @@ interface JwtHeader { alg: string kid?: string jwk?: JwkJson + x5c?: string[] [key: string]: unknown } diff --git a/packages/core/src/modules/mdoc/Mdoc.ts b/packages/core/src/modules/mdoc/Mdoc.ts index 1cf26c328d..ac8ec29b72 100644 --- a/packages/core/src/modules/mdoc/Mdoc.ts +++ b/packages/core/src/modules/mdoc/Mdoc.ts @@ -88,6 +88,10 @@ export class Mdoc { ) } + public get issuerSignedCertificateChain() { + return this.issuerSignedDocument.issuerSigned.issuerAuth.certificateChain + } + public get issuerSignedNamespaces(): MdocNameSpaces { return Object.fromEntries( Array.from(this.issuerSignedDocument.allIssuerSignedNamespaces.entries()).map(([namespace, value]) => [ @@ -156,19 +160,24 @@ export class Mdoc { agentContext: AgentContext, options?: MdocVerifyOptions ): Promise<{ isValid: true } | { isValid: false; error: string }> { - let trustedCerts: [string, ...string[]] | undefined - - if (options?.trustedCertificates) { - trustedCerts = options.trustedCertificates - } else if (options?.verificationContext) { - trustedCerts = await agentContext.dependencyManager - .resolve(X509ModuleConfig) - .getTrustedCertificatesForVerification?.(agentContext, options.verificationContext) - } else { - trustedCerts = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates + const x509ModuleConfig = agentContext.dependencyManager.resolve(X509ModuleConfig) + const certificateChain = this.issuerSignedDocument.issuerSigned.issuerAuth.certificateChain.map((certificate) => + X509Certificate.fromRawCertificate(certificate) + ) + + let trustedCertificates = options?.trustedCertificates + if (!trustedCertificates) { + trustedCertificates = + (await x509ModuleConfig.getTrustedCertificatesForVerification?.(agentContext, { + verification: { + type: 'credential', + credential: this, + }, + certificateChain, + })) ?? x509ModuleConfig.trustedCertificates } - if (!trustedCerts) { + if (!trustedCertificates) { throw new MdocError('No trusted certificates found. Cannot verify mdoc.') } @@ -177,7 +186,9 @@ export class Mdoc { const verifier = new Verifier() await verifier.verifyIssuerSignature( { - trustedCertificates: trustedCerts.map((cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate), + trustedCertificates: trustedCertificates.map( + (cert) => X509Certificate.fromEncodedCertificate(cert).rawCertificate + ), issuerAuth: this.issuerSignedDocument.issuerSigned.issuerAuth, disableCertificateChainValidation: false, now: options?.now, diff --git a/packages/core/src/modules/mdoc/MdocDeviceResponse.ts b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts index dda7266c86..92fcd28649 100644 --- a/packages/core/src/modules/mdoc/MdocDeviceResponse.ts +++ b/packages/core/src/modules/mdoc/MdocDeviceResponse.ts @@ -47,6 +47,7 @@ export class MdocDeviceResponse { docType ) }) + documents[0].deviceSignedNamespaces return new MdocDeviceResponse(base64Url, documents) } @@ -197,14 +198,34 @@ export class MdocDeviceResponse { public async verify(agentContext: AgentContext, options: Omit) { const verifier = new Verifier() const mdocContext = getMdocContext(agentContext) + const x509Config = agentContext.dependencyManager.resolve(X509ModuleConfig) - const x509ModuleConfig = agentContext.dependencyManager.resolve(X509ModuleConfig) - const getTrustedCertificatesForVerification = x509ModuleConfig.getTrustedCertificatesForVerification - - const trustedCertificates = - options.trustedCertificates ?? - (await getTrustedCertificatesForVerification?.(agentContext, options.verificationContext)) ?? - x509ModuleConfig?.trustedCertificates + // TODO: no way to currently have a per document x509 certificates in a presentation + // but this also the case for other formats + // FIXME: we can't pass multiple certificate chains. We should just verify each document separately + let trustedCertificates = options.trustedCertificates + if (!trustedCertificates) { + trustedCertificates = ( + await Promise.all( + this.documents.map((mdoc) => { + const certificateChain = mdoc.issuerSignedCertificateChain.map((cert) => + X509Certificate.fromRawCertificate(cert) + ) + return ( + x509Config.getTrustedCertificatesForVerification?.(agentContext, { + certificateChain, + verification: { + type: 'credential', + credential: mdoc, + }, + }) ?? x509Config.trustedCertificates + ) + }) + ) + ) + .filter((c): c is string[] => c !== undefined) + .flatMap((c) => c) + } if (!trustedCertificates) { throw new MdocError('No trusted certificates found. Cannot verify mdoc.') diff --git a/packages/core/src/modules/mdoc/MdocOptions.ts b/packages/core/src/modules/mdoc/MdocOptions.ts index 843b923915..a765c6760a 100644 --- a/packages/core/src/modules/mdoc/MdocOptions.ts +++ b/packages/core/src/modules/mdoc/MdocOptions.ts @@ -1,21 +1,14 @@ import type { Mdoc } from './Mdoc' import type { Key } from '../../crypto/Key' import type { DifPresentationExchangeDefinition } from '../dif-presentation-exchange' +import type { EncodedX509Certificate } from '../x509' import type { ValidityInfo } from '@animo-id/mdoc' export type MdocNameSpaces = Record> -export interface MdocVerificationContext { - /** - * The `id` of the `OpenId4VcVerificationSessionRecord` that this verification is bound to. - */ - openId4VcVerificationSessionId?: string -} - export type MdocVerifyOptions = { - trustedCertificates?: [string, ...string[]] + trustedCertificates?: EncodedX509Certificate[] now?: Date - verificationContext?: MdocVerificationContext } export type MdocOpenId4VpSessionTranscriptOptions = { @@ -33,14 +26,13 @@ export type MdocDeviceResponseOpenId4VpOptions = { } export type MdocDeviceResponseVerifyOptions = { - trustedCertificates?: [string, ...string[]] + trustedCertificates?: EncodedX509Certificate[] sessionTranscriptOptions: MdocOpenId4VpSessionTranscriptOptions /** * The base64Url-encoded device response string. */ deviceResponse: string now?: Date - verificationContext?: MdocVerificationContext } export type MdocSignOptions = { diff --git a/packages/core/src/modules/mdoc/__tests__/mdocServer.test.ts b/packages/core/src/modules/mdoc/__tests__/mdocServer.test.ts index 9f285b0f10..4caf90bea5 100644 --- a/packages/core/src/modules/mdoc/__tests__/mdocServer.test.ts +++ b/packages/core/src/modules/mdoc/__tests__/mdocServer.test.ts @@ -1,6 +1,6 @@ import type { AgentContext } from '../../..' -import { KeyType, X509Service } from '../../..' +import { KeyType, X509ModuleConfig, X509Service } from '../../..' import { InMemoryWallet } from '../../../../../../tests/InMemoryWallet' import { getAgentConfig, getAgentContext } from '../../../../tests' import { Mdoc } from '../Mdoc' @@ -14,7 +14,7 @@ describe('mdoc service test', () => { beforeAll(async () => { const agentConfig = getAgentConfig('mdoc') wallet = new InMemoryWallet() - agentContext = getAgentContext({ wallet }) + agentContext = getAgentContext({ wallet, registerInstances: [[X509ModuleConfig, new X509ModuleConfig()]] }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) diff --git a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts index 0a35d13af2..9010ecfcc7 100644 --- a/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/dif-presentation-exchange/DifPresentationExchangeProofFormatService.ts @@ -48,6 +48,7 @@ import { W3cJsonLdVerifiablePresentation, W3cJwtVerifiablePresentation, } from '../../../vc' +import { extractX509CertificatesFromJwt, X509ModuleConfig } from '../../../x509' import { ProofFormatSpec } from '../../models' const PRESENTATION_EXCHANGE_PRESENTATION_PROPOSAL = 'dif/presentation-exchange/definitions@v1.0' @@ -301,13 +302,31 @@ export class DifPresentationExchangeProofFormatService // whether it's a JWT or JSON-LD VP even though the input is the same. // Not sure how to fix if (parsedPresentation.claimFormat === ClaimFormat.JwtVp) { + const x509Config = agentContext.dependencyManager.resolve(X509ModuleConfig) + + const certificateChain = extractX509CertificatesFromJwt(parsedPresentation.jwt) + let trustedCertificates: string[] | undefined + + if (certificateChain && x509Config.getTrustedCertificatesForVerification) { + trustedCertificates = await x509Config.getTrustedCertificatesForVerification?.(agentContext, { + certificateChain, + verification: { + type: 'credential', + credential: parsedPresentation, + didcommProofRecordId: proofRecord.id, + }, + }) + } + + if (!trustedCertificates) { + trustedCertificates = x509Config.trustedCertificates ?? [] + } + verificationResult = await w3cCredentialService.verifyPresentation(agentContext, { presentation: parsedPresentation, challenge: request.options.challenge, domain: request.options.domain, - verificationContext: { - didcommProofRecordId: proofRecord.id, - }, + trustedCertificates, }) } else if (parsedPresentation.claimFormat === ClaimFormat.LdpVp) { if ( diff --git a/packages/core/src/modules/sd-jwt-vc/SdJwtVcOptions.ts b/packages/core/src/modules/sd-jwt-vc/SdJwtVcOptions.ts index 1167ddc973..cace5b596c 100644 --- a/packages/core/src/modules/sd-jwt-vc/SdJwtVcOptions.ts +++ b/packages/core/src/modules/sd-jwt-vc/SdJwtVcOptions.ts @@ -1,4 +1,5 @@ import type { JwkJson, Jwk, HashName } from '../../crypto' +import type { EncodedX509Certificate } from '../x509' // TODO: extend with required claim names for input (e.g. vct) export type SdJwtVcPayload = Record @@ -125,4 +126,6 @@ export type SdJwtVcVerifyOptions = { * It will will not influence the verification result if fetching of type metadata fails */ fetchTypeMetadata?: boolean + + trustedCertificates?: EncodedX509Certificate[] } diff --git a/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts b/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts index 346e512737..719e686343 100644 --- a/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts +++ b/packages/core/src/modules/sd-jwt-vc/SdJwtVcService.ts @@ -24,7 +24,7 @@ import { TypedArrayEncoder, nowInSeconds } from '../../utils' import { getDomainFromUrl } from '../../utils/domain' import { fetchWithTimeout } from '../../utils/fetch' import { DidResolverService, parseDid, getKeyFromVerificationMethod } from '../dids' -import { X509Certificate, X509ModuleConfig } from '../x509' +import { EncodedX509Certificate, X509Certificate, X509ModuleConfig } from '../x509' import { SdJwtVcError } from './SdJwtVcError' import { decodeSdJwtVc, sdJwtVcHasher } from './decodeSdJwtVc' @@ -191,7 +191,7 @@ export class SdJwtVcService { public async verify
( agentContext: AgentContext, - { compactSdJwtVc, keyBinding, requiredClaimKeys, fetchTypeMetadata }: SdJwtVcVerifyOptions + { compactSdJwtVc, keyBinding, requiredClaimKeys, fetchTypeMetadata, trustedCertificates }: SdJwtVcVerifyOptions ): Promise< | { isValid: true; verification: VerificationResult; sdJwtVc: SdJwtVc } | { isValid: false; verification: VerificationResult; sdJwtVc?: SdJwtVc; error: Error } @@ -229,7 +229,12 @@ export class SdJwtVcService { } satisfies SdJwtVc try { - const credentialIssuer = await this.parseIssuerFromCredential(agentContext, sdJwtVc) + const credentialIssuer = await this.parseIssuerFromCredential( + agentContext, + sdJwtVc, + returnSdJwtVc, + trustedCertificates + ) const issuer = await this.extractKeyFromIssuer(agentContext, credentialIssuer) const holderBinding = this.parseHolderBindingFromCredential(sdJwtVc) const holder = holderBinding ? await this.extractKeyFromHolderBinding(agentContext, holderBinding) : undefined @@ -455,8 +460,11 @@ export class SdJwtVcService { private async parseIssuerFromCredential
( agentContext: AgentContext, - sdJwtVc: SDJwt + sdJwtVc: SDJwt, + credoSdJwtVc: SdJwtVc, + _trustedCertificates?: EncodedX509Certificate[] ): Promise { + const x509Config = agentContext.dependencyManager.resolve(X509ModuleConfig) if (!sdJwtVc.jwt?.payload) { throw new SdJwtVcError('Credential not exist') } @@ -478,7 +486,20 @@ export class SdJwtVcService { throw new SdJwtVcError('Invalid x5c header in credential. Not an array of strings.') } - const trustedCertificates = agentContext.dependencyManager.resolve(X509ModuleConfig).trustedCertificates + let trustedCertificates = _trustedCertificates + const certificateChain = sdJwtVc.jwt.header.x5c.map((cert) => X509Certificate.fromEncodedCertificate(cert)) + + if (!trustedCertificates) { + trustedCertificates = + (await x509Config.getTrustedCertificatesForVerification?.(agentContext, { + certificateChain, + verification: { + type: 'credential', + credential: credoSdJwtVc, + }, + })) ?? x509Config.trustedCertificates + } + if (!trustedCertificates) { throw new SdJwtVcError( 'No trusted certificates configured for X509 certificate chain validation. Issuer cannot be verified.' diff --git a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts index 10e7679016..503d58818b 100644 --- a/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/W3cCredentialServiceOptions.ts @@ -7,6 +7,7 @@ import type { W3cCredential } from './models/credential/W3cCredential' import type { W3cPresentation } from './models/presentation/W3cPresentation' import type { JwaSignatureAlgorithm } from '../../crypto/jose/jwa' import type { SingleOrArray } from '../../utils/type' +import type { EncodedX509Certificate } from '../x509' export type W3cSignCredentialOptions = Format extends ClaimFormat.JwtVc @@ -179,22 +180,9 @@ interface W3cVerifyPresentationOptionsBase { verifyCredentialStatus?: boolean } -export interface VerificationContext { - /** - * The `id` of the `ProofRecord` that this verification is bound to. - */ - didcommProofRecordId?: string - - /** - * The `id` of the `OpenId4VcVerificationSessionRecord` that this verification is bound to. - */ - openId4VcVerificationSessionId?: string -} - export interface W3cJwtVerifyPresentationOptions extends W3cVerifyPresentationOptionsBase { presentation: W3cJwtVerifiablePresentation | string // string must be encoded VP JWT - trustedCertificates?: [string, ...string[]] - verificationContext?: VerificationContext + trustedCertificates?: EncodedX509Certificate[] } export interface W3cJsonLdVerifyPresentationOptions extends W3cVerifyPresentationOptionsBase { diff --git a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts index a03a07528d..47b5855834 100644 --- a/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts +++ b/packages/core/src/modules/vc/jwt-vc/W3cJwtCredentialService.ts @@ -16,6 +16,7 @@ import { injectable } from '../../../plugins' import { asArray, isDid, MessageValidator } from '../../../utils' import { getKeyDidMappingByKeyType, DidResolverService, getKeyFromVerificationMethod } from '../../dids' import { X509ModuleConfig } from '../../x509' +import { extractX509CertificatesFromJwt } from '../../x509/extraction' import { W3cJsonLdVerifiableCredential } from '../data-integrity' import { W3cJwtVerifiableCredential } from './W3cJwtVerifiableCredential' @@ -308,10 +309,19 @@ export class W3cJwtCredentialService { }) const proverPublicKey = getKeyFromVerificationMethod(proverVerificationMethod) const proverPublicJwk = getJwkFromKey(proverPublicKey) - - const getTrustedCertificatesForVerification = agentContext.dependencyManager.isRegistered(X509ModuleConfig) - ? agentContext.dependencyManager.resolve(X509ModuleConfig).getTrustedCertificatesForVerification - : undefined + const x509Config = agentContext.dependencyManager.resolve(X509ModuleConfig) + + let trustedCertificates = options.trustedCertificates + const certificateChain = extractX509CertificatesFromJwt(presentation.jwt) + if (certificateChain && !trustedCertificates) { + trustedCertificates = await x509Config.getTrustedCertificatesForVerification?.(agentContext, { + certificateChain, + verification: { + type: 'credential', + credential: presentation, + }, + }) + } let signatureResult: VerifyJwsResult | undefined = undefined try { @@ -320,9 +330,7 @@ export class W3cJwtCredentialService { jws: presentation.jwt.serializedJwt, // We have pre-fetched the key based on the singer/holder of the presentation jwkResolver: () => proverPublicJwk, - trustedCertificates: - options.trustedCertificates ?? - (await getTrustedCertificatesForVerification?.(agentContext, options.verificationContext)), + trustedCertificates, }) if (!signatureResult.isValid) { diff --git a/packages/core/src/modules/x509/X509Api.ts b/packages/core/src/modules/x509/X509Api.ts index a13392a771..49d77e4cc7 100644 --- a/packages/core/src/modules/x509/X509Api.ts +++ b/packages/core/src/modules/x509/X509Api.ts @@ -10,18 +10,14 @@ import { X509CreateSelfSignedCertificateOptions, X509ValidateCertificateChainOpt */ @injectable() export class X509Api { - public constructor( - private agentContext: AgentContext, - private x509ModuleConfig: X509ModuleConfig, - private x509Service: X509Service - ) {} + public constructor(private agentContext: AgentContext, private x509ModuleConfig: X509ModuleConfig) {} /** * Adds a trusted certificate to the X509 Module Config. * * @param certificate */ - public async addTrustedCertificate(certificate: string) { + public addTrustedCertificate(certificate: string) { this.x509ModuleConfig.addTrustedCertificate(certificate) } diff --git a/packages/core/src/modules/x509/X509ModuleConfig.ts b/packages/core/src/modules/x509/X509ModuleConfig.ts index 97ea419393..fff632092f 100644 --- a/packages/core/src/modules/x509/X509ModuleConfig.ts +++ b/packages/core/src/modules/x509/X509ModuleConfig.ts @@ -1,5 +1,47 @@ import type { AgentContext } from '../../agent' -import type { VerificationContext } from '../vc' +import type { JwtPayload } from '../../crypto' +import type { Mdoc } from '../mdoc/Mdoc' +import type { MdocDeviceResponse } from '../mdoc/MdocDeviceResponse' +import type { SdJwtVc } from '../sd-jwt-vc' +import type { W3cJwtVerifiableCredential, W3cJwtVerifiablePresentation } from '../vc' + +import { X509Certificate } from './X509Certificate' + +type X509VerificationTypeCredential = { + type: 'credential' + credential: SdJwtVc | Mdoc | MdocDeviceResponse | W3cJwtVerifiableCredential | W3cJwtVerifiablePresentation + + /** + * The `id` of the `DidCommProofRecord` that this verification is bound to. + */ + didcommProofRecordId?: string + + /** + * The `id` of the `OpenId4VcVerificationSessionRecord` that this verification is bound to. + */ + openId4VcVerificationSessionId?: string +} + +type X509VerificationTypeOauth2SecuredAuthorizationRequest = { + type: 'oauth2SecuredAuthorizationRequest' + authorizationRequest: { + jwt: string + payload: JwtPayload + } +} + +export interface X509VerificationContext { + /** + * The certificate chain provided with the data to be verified. The trusted certificates + * are determined before verification and thus it is not verified that the data was actually + * signed by the private key assocaited with the leaf certificate in the certificate chain, or + * whether the certificate chain is valid. However if the certificate + * does not match, or is not valid, verification will always fail at a later stage + */ + certificateChain: X509Certificate[] + + verification: X509VerificationTypeCredential | X509VerificationTypeOauth2SecuredAuthorizationRequest +} export interface X509ModuleConfigOptions { /** @@ -10,47 +52,56 @@ export interface X509ModuleConfigOptions { /** * Optional callback method that will be called to dynamically get trusted certificates for a verification. - * It will always provide the `agentContext` allowing to dynamically set the trusted certificates for a tenant. - * If available the associated record id is also provided allowing to filter down trusted certificates to a single - * exchange. + * It will provide the `agentContext` and `verificationContext` allowing to dynamically set the trusted certificates + * for a tenant or verificaiton context. + * + * If no certificaets should be trusted an empty array should be returned. If `undefined` is returned + * it will fallback to the globally registered trusted certificates * * @returns An array of base64-encoded certificate strings or PEM certificate strings. */ getTrustedCertificatesForVerification?( agentContext: AgentContext, - verificationContext?: VerificationContext - ): Promise<[string, ...string[]] | undefined> + verificationContext: X509VerificationContext + ): Promise | string[] | undefined } export class X509ModuleConfig { - private options: X509ModuleConfigOptions + #trustedCertificates?: X509Certificate[] + #getTrustedCertificatesForVerification?: X509ModuleConfigOptions['getTrustedCertificatesForVerification'] public constructor(options?: X509ModuleConfigOptions) { - this.options = options?.trustedCertificates ? { trustedCertificates: [...options.trustedCertificates] } : {} - this.options.getTrustedCertificatesForVerification = options?.getTrustedCertificatesForVerification + this.setTrustedCertificates(options?.trustedCertificates) + if (options?.getTrustedCertificatesForVerification) { + this.setTrustedCertificatesForVerification(options.getTrustedCertificatesForVerification) + } } public get trustedCertificates() { - return this.options.trustedCertificates + // TODO: we should probably update this API to return the instances, but don't want to + // break too much now + return this.#trustedCertificates?.map((cert) => cert.toString('pem')) } public get getTrustedCertificatesForVerification() { - return this.options.getTrustedCertificatesForVerification + return this.#getTrustedCertificatesForVerification } public setTrustedCertificatesForVerification(fn: X509ModuleConfigOptions['getTrustedCertificatesForVerification']) { - this.options.getTrustedCertificatesForVerification = fn + this.#getTrustedCertificatesForVerification = fn } public setTrustedCertificates(trustedCertificates?: [string, ...string[]]) { - this.options.trustedCertificates = trustedCertificates ? [...trustedCertificates] : undefined + this.#trustedCertificates = trustedCertificates + ? trustedCertificates.map((certificate) => X509Certificate.fromEncodedCertificate(certificate)) + : undefined } public addTrustedCertificate(trustedCertificate: string) { - if (!this.options.trustedCertificates) { - this.options.trustedCertificates = [trustedCertificate] + if (!this.#trustedCertificates) { + this.#trustedCertificates = [X509Certificate.fromEncodedCertificate(trustedCertificate)] return } - this.options.trustedCertificates.push(trustedCertificate) + this.#trustedCertificates.push(X509Certificate.fromEncodedCertificate(trustedCertificate)) } } diff --git a/packages/core/src/modules/x509/X509ServiceOptions.ts b/packages/core/src/modules/x509/X509ServiceOptions.ts index a653c1a0b1..84068af570 100644 --- a/packages/core/src/modules/x509/X509ServiceOptions.ts +++ b/packages/core/src/modules/x509/X509ServiceOptions.ts @@ -1,8 +1,14 @@ import type { ExtensionInput } from './X509Certificate' import type { Key } from '../../crypto/Key' +/** + * Base64 or PEM + */ +export type EncodedX509Certificate = string + export interface X509ValidateCertificateChainOptions { - certificateChain: Array + certificateChain: Array + certificate?: string /** * The date for which the certificate chain should be valid @@ -13,7 +19,8 @@ export interface X509ValidateCertificateChainOptions { * otherwise, the validation will fail */ verificationDate?: Date - trustedCertificates?: [string, ...string[]] + + trustedCertificates?: EncodedX509Certificate[] } export interface X509CreateSelfSignedCertificateOptions { diff --git a/packages/core/src/modules/x509/__tests__/X509ServiceModule.test.ts b/packages/core/src/modules/x509/__tests__/X509ServiceModule.test.ts index 7649c6a3cf..f54e224d94 100644 --- a/packages/core/src/modules/x509/__tests__/X509ServiceModule.test.ts +++ b/packages/core/src/modules/x509/__tests__/X509ServiceModule.test.ts @@ -11,7 +11,24 @@ const dependencyManager = { describe('X509ServiceModule', () => { test('registers dependencies on the dependency manager', async () => { - const options: X509ModuleConfigOptions = { trustedCertificates: ['certificate'] } + const options: X509ModuleConfigOptions = { + trustedCertificates: [ + `-----BEGIN CERTIFICATE----- +MIICKjCCAdCgAwIBAgIUV8bM0wi95D7KN0TyqHE42ru4hOgwCgYIKoZIzj0EAwIw +UzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBbGJh +bnkxDzANBgNVBAoMBk5ZIERNVjEPMA0GA1UECwwGTlkgRE1WMB4XDTIzMDkxNDE0 +NTUxOFoXDTMzMDkxMTE0NTUxOFowUzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5l +dyBZb3JrMQ8wDQYDVQQHDAZBbGJhbnkxDzANBgNVBAoMBk5ZIERNVjEPMA0GA1UE +CwwGTlkgRE1WMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiTwtg0eQbcbNabf2 +Nq9L/VM/lhhPCq2s0Qgw2kRx29tgrBcNHPxTT64tnc1Ij3dH/fl42SXqMenpCDw4 +K6ntU6OBgTB/MB0GA1UdDgQWBBSrbS4DuR1JIkAzj7zK3v2TM+r2xzAfBgNVHSME +GDAWgBSrbS4DuR1JIkAzj7zK3v2TM+r2xzAPBgNVHRMBAf8EBTADAQH/MCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAKBggqhkjO +PQQDAgNIADBFAiAJ/Qyrl7A+ePZOdNfc7ohmjEdqCvxaos6//gfTvncuqQIhANo4 +q8mKCA9J8k/+zh//yKbN1bLAtdqPx7dnrDqV3Lg+ +-----END CERTIFICATE-----`, + ], + } const x509Module = new X509Module(options) x509Module.register(dependencyManager) @@ -24,9 +41,24 @@ describe('X509ServiceModule', () => { expect(x509Module.config.trustedCertificates).toBeDefined() expect(x509Module.config.trustedCertificates).toHaveLength(1) - expect(x509Module.config.trustedCertificates).toContain('certificate') + expect(x509Module.config.trustedCertificates).toContain(`-----BEGIN CERTIFICATE----- +MIICKjCCAdCgAwIBAgIUV8bM0wi95D7KN0TyqHE42ru4hOgwCgYIKoZIzj0EAwIw +UzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMQ8wDQYDVQQHDAZBbGJh +bnkxDzANBgNVBAoMBk5ZIERNVjEPMA0GA1UECwwGTlkgRE1WMB4XDTIzMDkxNDE0 +NTUxOFoXDTMzMDkxMTE0NTUxOFowUzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5l +dyBZb3JrMQ8wDQYDVQQHDAZBbGJhbnkxDzANBgNVBAoMBk5ZIERNVjEPMA0GA1UE +CwwGTlkgRE1WMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiTwtg0eQbcbNabf2 +Nq9L/VM/lhhPCq2s0Qgw2kRx29tgrBcNHPxTT64tnc1Ij3dH/fl42SXqMenpCDw4 +K6ntU6OBgTB/MB0GA1UdDgQWBBSrbS4DuR1JIkAzj7zK3v2TM+r2xzAfBgNVHSME +GDAWgBSrbS4DuR1JIkAzj7zK3v2TM+r2xzAPBgNVHRMBAf8EBTADAQH/MCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAKBggqhkjO +PQQDAgNIADBFAiAJ/Qyrl7A+ePZOdNfc7ohmjEdqCvxaos6//gfTvncuqQIhANo4 +q8mKCA9J8k/+zh//yKbN1bLAtdqPx7dnrDqV3Lg+ +-----END CERTIFICATE-----`) - x509Module.config.addTrustedCertificate('certificate2') + x509Module.config.addTrustedCertificate( + 'MIIBEzCBu6ADAgECAhBwnaR5jboQ4R7Ne/k+sfhCMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAk5MMB4XDTcwMDEwMTAwMDAwMFoXDTI1MTEyMjA4MjIxMlowDTELMAkGA1UEBhMCTkwwOTATBgcqhkjOPQIBBggqhkjOPQMBBwMiAALcD1XzKepFxWMAOqV+ln1fybBt7DRO5CV0f9A6mRp2xaMdMBswGQYDVR0RBBIwEIIOZnVua2UuYW5pbW8uaWQwCgYIKoZIzj0EAwIDRwAwRAIgPM5ITfUD6VWLgRm8Eu1gw53Of+SUdjS+yRClR68m//4CIDxnng7NnJyGnpsKDuUqSIl/A0rRQCwTLBZw9Hx3MZnZ' + ) expect(x509Module.config.trustedCertificates).toHaveLength(2) x509Module.config.setTrustedCertificates(undefined) diff --git a/packages/core/src/modules/x509/extraction.ts b/packages/core/src/modules/x509/extraction.ts new file mode 100644 index 0000000000..4f31061b0b --- /dev/null +++ b/packages/core/src/modules/x509/extraction.ts @@ -0,0 +1,7 @@ +import type { Jwt } from '../../crypto' + +import { X509Certificate } from './X509Certificate' + +export function extractX509CertificatesFromJwt(jwt: Jwt) { + return jwt.header.x5c?.map((cert) => X509Certificate.fromEncodedCertificate(cert)) +} diff --git a/packages/core/src/modules/x509/index.ts b/packages/core/src/modules/x509/index.ts index 9291eac3d3..abe5a67c0d 100644 --- a/packages/core/src/modules/x509/index.ts +++ b/packages/core/src/modules/x509/index.ts @@ -5,3 +5,4 @@ export * from './X509Api' export * from './X509Module' export * from './X509ModuleConfig' export * from './X509ServiceOptions' +export * from './extraction' diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts index 122916e4b3..33f8071052 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts @@ -3,7 +3,7 @@ import type { OpenId4VcSiopResolvedAuthorizationRequest, } from './OpenId4vcSiopHolderServiceOptions' import type { OpenId4VcJwtIssuer } from '../shared' -import type { AgentContext, JwkJson, VerifiablePresentation } from '@credo-ts/core' +import type { AgentContext, EncodedX509Certificate, JwkJson, VerifiablePresentation } from '@credo-ts/core' import type { AuthorizationResponsePayload, PresentationExchangeResponseOpts, @@ -38,9 +38,10 @@ export class OpenId4VcSiopHolderService { public async resolveAuthorizationRequest( agentContext: AgentContext, - requestJwtOrUri: string + requestJwtOrUri: string, + trustedCertificates?: EncodedX509Certificate[] ): Promise { - const openidProvider = await this.getOpenIdProvider(agentContext) + const openidProvider = await this.getOpenIdProvider(agentContext, trustedCertificates) // parsing happens automatically in verifyAuthorizationRequest const verifiedAuthorizationRequest = await openidProvider.verifyAuthorizationRequest(requestJwtOrUri) @@ -244,7 +245,7 @@ export class OpenId4VcSiopHolderService { } as const } - private async getOpenIdProvider(agentContext: AgentContext) { + private async getOpenIdProvider(agentContext: AgentContext, trustedCertificates?: EncodedX509Certificate[]) { const builder = OP.builder() .withExpiresIn(6000) .withIssuer(ResponseIss.SELF_ISSUED_V2) @@ -255,7 +256,7 @@ export class OpenId4VcSiopHolderService { SupportedVersion.SIOPv2_D12_OID4VP_D20, ]) .withCreateJwtCallback(getCreateJwtCallback(agentContext)) - .withVerifyJwtCallback(getVerifyJwtCallback(agentContext)) + .withVerifyJwtCallback(getVerifyJwtCallback(agentContext, trustedCertificates)) .withHasher(Hasher.hash) const openidProvider = builder.build() diff --git a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts index 6d8196056b..258cf0fd23 100644 --- a/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts +++ b/packages/openid4vc/src/openid4vc-verifier/OpenId4VcSiopVerifierService.ts @@ -44,6 +44,10 @@ import { TypedArrayEncoder, Jwt, extractPresentationsWithDescriptorsFromSubmission, + X509ModuleConfig, + extractX509CertificatesFromJwt, + W3cJwtVerifiablePresentation, + X509Certificate, } from '@credo-ts/core' import { AuthorizationRequest, @@ -296,6 +300,7 @@ export class OpenId4VcSiopVerifierService { mdocGeneratedNonce: options.jarmHeader?.apu ? TypedArrayEncoder.toUtf8String(TypedArrayEncoder.fromBase64(options.jarmHeader.apu)) : undefined, + verificationSessionRecordId: options.verificationSession.id, }), }, }) @@ -627,6 +632,7 @@ export class OpenId4VcSiopVerifierService { correlationId: string responseUri?: string mdocGeneratedNonce?: string + verificationSessionRecordId: string } ): PresentationVerificationCallback { return async (encodedPresentation, presentationSubmission) => { @@ -635,6 +641,7 @@ export class OpenId4VcSiopVerifierService { this.logger.debug(`Presentation submission`, presentationSubmission) if (!encodedPresentation) throw new CredoError('Did not receive a presentation for verification.') + const x509Config = agentContext.dependencyManager.resolve(X509ModuleConfig) let isValid: boolean let reason: string | undefined = undefined @@ -642,15 +649,36 @@ export class OpenId4VcSiopVerifierService { if (typeof encodedPresentation === 'string' && encodedPresentation.includes('~')) { // TODO: it might be better here to look at the presentation submission to know // If presentation includes a ~, we assume it's an SD-JWT-VC - const sdJwtVcApi = agentContext.dependencyManager.resolve(SdJwtVcApi) + const jwt = Jwt.fromSerializedJwt(encodedPresentation.split('~')[0]) + const sdJwtVc = sdJwtVcApi.fromCompact(encodedPresentation) + const certificateChain = extractX509CertificatesFromJwt(jwt) + + let trustedCertificates: string[] | undefined = undefined + if (certificateChain && x509Config.getTrustedCertificatesForVerification) { + trustedCertificates = await x509Config.getTrustedCertificatesForVerification(agentContext, { + certificateChain, + verification: { + type: 'credential', + credential: sdJwtVc, + openId4VcVerificationSessionId: options.verificationSessionRecordId, + }, + }) + } + + if (!trustedCertificates) { + // We also take from the config here to avoid the callback being called again + trustedCertificates = x509Config.trustedCertificates ?? [] + } + const verificationResult = await sdJwtVcApi.verify({ compactSdJwtVc: encodedPresentation, keyBinding: { audience: options.audience, nonce: options.nonce, }, + trustedCertificates, }) isValid = verificationResult.verification.isValid @@ -661,6 +689,31 @@ export class OpenId4VcSiopVerifierService { reason = 'Mdoc device response verification failed. Response uri and the mdocGeneratedNonce are not set' } else { const mdocDeviceResponse = MdocDeviceResponse.fromBase64Url(encodedPresentation) + + const trustedCertificates = ( + await Promise.all( + mdocDeviceResponse.documents.map(async (mdoc) => { + const certificateChain = mdoc.issuerSignedCertificateChain.map((cert) => + X509Certificate.fromRawCertificate(cert) + ) + + const trustedCertificates = await x509Config.getTrustedCertificatesForVerification?.(agentContext, { + certificateChain, + verification: { + type: 'credential', + credential: mdoc, + openId4VcVerificationSessionId: options.verificationSessionRecordId, + }, + }) + + // TODO: could have some duplication but not a big issue + return trustedCertificates ?? x509Config.trustedCertificates + }) + ) + ) + .filter((c): c is string[] => c !== undefined) + .flatMap((c) => c) + await mdocDeviceResponse.verify(agentContext, { sessionTranscriptOptions: { clientId: options.audience, @@ -668,20 +721,35 @@ export class OpenId4VcSiopVerifierService { responseUri: options.responseUri, verifierGeneratedNonce: options.nonce, }, - verificationContext: { - openId4VcVerificationSessionId: options.correlationId, - }, + trustedCertificates, }) isValid = true } } else if (typeof encodedPresentation === 'string' && Jwt.format.test(encodedPresentation)) { + const presentation = W3cJwtVerifiablePresentation.fromSerializedJwt(encodedPresentation) + const certificateChain = extractX509CertificatesFromJwt(presentation.jwt) + + let trustedCertificates: string[] | undefined = undefined + if (certificateChain && x509Config.getTrustedCertificatesForVerification) { + trustedCertificates = await x509Config.getTrustedCertificatesForVerification?.(agentContext, { + certificateChain, + verification: { + type: 'credential', + credential: presentation, + openId4VcVerificationSessionId: options.verificationSessionRecordId, + }, + }) + } + + if (!trustedCertificates) { + trustedCertificates = x509Config.trustedCertificates ?? [] + } + const verificationResult = await this.w3cCredentialService.verifyPresentation(agentContext, { presentation: encodedPresentation, challenge: options.nonce, domain: options.audience, - verificationContext: { - openId4VcVerificationSessionId: options.correlationId, - }, + trustedCertificates, }) isValid = verificationResult.isValid diff --git a/packages/openid4vc/src/shared/utils.ts b/packages/openid4vc/src/shared/utils.ts index 9b15bd86ab..a2f5b5d6ad 100644 --- a/packages/openid4vc/src/shared/utils.ts +++ b/packages/openid4vc/src/shared/utils.ts @@ -1,5 +1,12 @@ import type { OpenId4VcIssuerX5c, OpenId4VcJwtIssuer } from './models' -import type { AgentContext, DidPurpose, JwaSignatureAlgorithm, JwkJson, Key } from '@credo-ts/core' +import type { + AgentContext, + DidPurpose, + EncodedX509Certificate, + JwaSignatureAlgorithm, + JwkJson, + Key, +} from '@credo-ts/core' import type { JwtIssuerWithContext as VpJwtIssuerWithContext, VerifyJwtCallback } from '@sphereon/did-auth-siop' import type { DPoPJwtIssuerWithContext, CreateJwtCallback, JwtIssuer } from '@sphereon/oid4vc-common' @@ -9,6 +16,8 @@ import { JwsService, JwtPayload, SignatureSuiteRegistry, + X509Certificate, + X509ModuleConfig, X509Service, getDomainFromUrl, getJwkClassFromKeyType, @@ -52,24 +61,61 @@ export async function getKeyFromDid( return getKeyFromVerificationMethod(verificationMethod) } -export function getVerifyJwtCallback(agentContext: AgentContext): VerifyJwtCallback { +export function getVerifyJwtCallback( + agentContext: AgentContext, + _trustedCertificates?: EncodedX509Certificate[] +): VerifyJwtCallback { return async (jwtVerifier, jwt) => { const jwsService = agentContext.dependencyManager.resolve(JwsService) + + let trustedCertificates = _trustedCertificates + if (jwtVerifier.method === 'did') { const key = await getKeyFromDid(agentContext, jwtVerifier.didUrl) const jwk = getJwkFromKey(key) - const res = await jwsService.verifyJws(agentContext, { jws: jwt.raw, jwkResolver: () => jwk }) + const res = await jwsService.verifyJws(agentContext, { + jws: jwt.raw, + jwkResolver: () => jwk, + // No certificates trusted + trustedCertificates: [], + }) return res.isValid } else if (jwtVerifier.method === 'x5c' || jwtVerifier.method === 'jwk') { - const res = await jwsService.verifyJws(agentContext, { jws: jwt.raw }) + if (jwtVerifier.type === 'request-object') { + const x509Config = agentContext.dependencyManager.resolve(X509ModuleConfig) + const certificateChain = jwt.header.x5c?.map((cert) => X509Certificate.fromEncodedCertificate(cert)) + + if (!trustedCertificates && certificateChain && x509Config.getTrustedCertificatesForVerification) { + trustedCertificates = await x509Config.getTrustedCertificatesForVerification(agentContext, { + certificateChain, + verification: { + type: 'oauth2SecuredAuthorizationRequest', + authorizationRequest: { + jwt: jwt.raw, + payload: JwtPayload.fromJson(jwt.payload), + }, + }, + }) + } + + if (!trustedCertificates) { + // We also take from the config here to avoid the callback being called again + trustedCertificates = x509Config.trustedCertificates ?? [] + } + } + + const res = await jwsService.verifyJws(agentContext, { + jws: jwt.raw, + // Only allowed for request object + trustedCertificates: jwtVerifier.type === 'request-object' ? trustedCertificates : [], + }) return res.isValid } else { throw new Error(`Unsupported jwt verifier method: '${jwtVerifier.method}'`) } } } - export function getCreateJwtCallback( agentContext: AgentContext ): CreateJwtCallback { diff --git a/packages/openid4vc/tests/openid4vc-presentation-during-issuance.e2e.test.ts b/packages/openid4vc/tests/openid4vc-presentation-during-issuance.e2e.test.ts index 329292773e..43b76f6b5a 100644 --- a/packages/openid4vc/tests/openid4vc-presentation-during-issuance.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc-presentation-during-issuance.e2e.test.ts @@ -157,8 +157,8 @@ describe('OpenId4Vc Presentation During Issuance', () => { askar: new AskarModule(askarModuleConfig), }) - await holder.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) - await issuer.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) + holder.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) + issuer.agent.x509.addTrustedCertificate(issuer.certificate.toString('base64')) // We let AFJ create the router, so we have a fresh one each time expressApp.use('/oid4vci', issuer.agent.modules.openId4VcIssuer.config.router)