diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts index 87d6af88d..5106a2f10 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts @@ -9,8 +9,8 @@ import type { } from './OpenId4VciHolderServiceOptions' import type { OpenId4VcSiopAcceptAuthorizationRequestOptions, - OpenId4VcSiopResolveAuthorizationRequestOptions, OpenId4VcSiopResolveTrustChainsOptions, + OpenId4VcSiopFetchEntityConfigurationOptions, } from './OpenId4vcSiopHolderServiceOptions' import { injectable, AgentContext, DifPresentationExchangeService, DifPexCredentialsForRequest } from '@credo-ts/core' @@ -46,11 +46,8 @@ export class OpenId4VcHolderApi { * @param requestJwtOrUri JWT or an SIOPv2 request URI * @returns the resolved and verified authentication request. */ - public async resolveSiopAuthorizationRequest( - requestJwtOrUri: string, - options: OpenId4VcSiopResolveAuthorizationRequestOptions = {} - ) { - return this.openId4VcSiopHolderService.resolveAuthorizationRequest(this.agentContext, requestJwtOrUri, options) + public async resolveSiopAuthorizationRequest(requestJwtOrUri: string) { + return this.openId4VcSiopHolderService.resolveAuthorizationRequest(this.agentContext, requestJwtOrUri) } /** @@ -176,4 +173,8 @@ export class OpenId4VcHolderApi { public async resolveOpenIdFederationChains(options: OpenId4VcSiopResolveTrustChainsOptions) { return this.openId4VcSiopHolderService.resolveOpenIdFederationChains(this.agentContext, options) } + + public async fetchOpenIdFederationEntityConfiguration(options: OpenId4VcSiopFetchEntityConfigurationOptions) { + return this.openId4VcSiopHolderService.fetchOpenIdFederationEntityConfiguration(this.agentContext, options) + } } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts index 710aab493..5250a0bef 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderService.ts @@ -1,8 +1,6 @@ import type { OpenId4VcSiopAcceptAuthorizationRequestOptions, OpenId4VcSiopFetchEntityConfigurationOptions, - OpenId4VcSiopGetOpenIdProviderOptions, - OpenId4VcSiopResolveAuthorizationRequestOptions, OpenId4VcSiopResolvedAuthorizationRequest, OpenId4VcSiopResolveTrustChainsOptions, } from './OpenId4vcSiopHolderServiceOptions' @@ -47,12 +45,9 @@ export class OpenId4VcSiopHolderService { public async resolveAuthorizationRequest( agentContext: AgentContext, - requestJwtOrUri: string, - options: OpenId4VcSiopResolveAuthorizationRequestOptions = {} + requestJwtOrUri: string ): Promise { - const openidProvider = await this.getOpenIdProvider(agentContext, { - federation: options.federation, - }) + const openidProvider = await this.getOpenIdProvider(agentContext) // parsing happens automatically in verifyAuthorizationRequest const verifiedAuthorizationRequest = await openidProvider.verifyAuthorizationRequest(requestJwtOrUri) @@ -93,10 +88,14 @@ export class OpenId4VcSiopHolderService { if (!entityConfiguration) throw new CredoError(`Unable to fetch entity configuration for entityId '${clientId}'`) const openidRelyingPartyMetadata = entityConfiguration.metadata?.openid_relying_party + // When the metadata is present in the federation we want to use that instead of what is passed with the request if (openidRelyingPartyMetadata) { verifiedAuthorizationRequest.authorizationRequestPayload.client_metadata = openidRelyingPartyMetadata + verifiedAuthorizationRequest.authorizationRequest.payload.client_metadata = openidRelyingPartyMetadata } + + // TODO: Do we want to do something with the real chain of do we want to give the user the possibility to do that somewhere else with the risk of being forgotten or that it doesn't have enough information at that place? } return { @@ -284,7 +283,7 @@ export class OpenId4VcSiopHolderService { } as const } - private async getOpenIdProvider(agentContext: AgentContext, options: OpenId4VcSiopGetOpenIdProviderOptions = {}) { + private async getOpenIdProvider(agentContext: AgentContext) { const builder = OP.builder() .withExpiresIn(6000) .withIssuer(ResponseIss.SELF_ISSUED_V2) @@ -295,11 +294,7 @@ export class OpenId4VcSiopHolderService { SupportedVersion.SIOPv2_D12_OID4VP_D20, ]) .withCreateJwtCallback(getCreateJwtCallback(agentContext)) - .withVerifyJwtCallback( - getVerifyJwtCallback(agentContext, { - federation: options.federation, - }) - ) + .withVerifyJwtCallback(getVerifyJwtCallback(agentContext)) .withHasher(Hasher.hash) const openidProvider = builder.build() diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts index 0b31cfc5c..25cf6569e 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4vcSiopHolderServiceOptions.ts @@ -52,24 +52,6 @@ export interface OpenId4VcSiopAcceptAuthorizationRequestOptions { // TODO: Not sure if this also needs the federation because the validation of the authorization is already done with the ResolveAuthorizationRequest } -export interface OpenId4VcSiopResolveAuthorizationRequestOptions { - federation?: { - /** - * The entity IDs of the trusted issuers. - */ - trustedEntityIds?: string[] - } -} - -export interface OpenId4VcSiopGetOpenIdProviderOptions { - federation?: { - /** - * The entity IDs of the trusted issuers. - */ - trustedEntityIds?: string[] - } -} - export interface OpenId4VcSiopResolveTrustChainsOptions { entityId: string trustAnchorEntityIds: [string, ...string[]] diff --git a/packages/openid4vc/src/shared/utils.ts b/packages/openid4vc/src/shared/utils.ts index 7b97e32c7..1b2ddd57e 100644 --- a/packages/openid4vc/src/shared/utils.ts +++ b/packages/openid4vc/src/shared/utils.ts @@ -16,7 +16,7 @@ import { getJwkFromKey, getKeyFromVerificationMethod, } from '@credo-ts/core' -import { fetchEntityConfiguration, resolveTrustChains } from '@openid-federation/core' +import { fetchEntityConfiguration } from '@openid-federation/core' /** * Returns the JWA Signature Algorithms that are supported by the wallet. @@ -53,16 +53,7 @@ export async function getKeyFromDid( return getKeyFromVerificationMethod(verificationMethod) } -type VerifyJwtCallbackOptions = { - federation?: { - trustedEntityIds?: string[] - } -} - -export function getVerifyJwtCallback( - agentContext: AgentContext, - options: VerifyJwtCallbackOptions = {} -): VerifyJwtCallback { +export function getVerifyJwtCallback(agentContext: AgentContext): VerifyJwtCallback { const logger = agentContext.config.logger return async (jwtVerifier, jwt) => { @@ -83,15 +74,9 @@ export function getVerifyJwtCallback( if (jwtVerifier.method === 'openid-federation') { const { entityId } = jwtVerifier - const trustedEntityIds = options.federation?.trustedEntityIds - if (!trustedEntityIds) { - logger.error('No trusted entity ids provided but is required for the "openid-federation" method.') - return false - } - const validTrustChains = await resolveTrustChains({ + const entityConfiguration = await fetchEntityConfiguration({ entityId, - trustAnchorEntityIds: trustedEntityIds, verifyJwtCallback: async ({ jwt, jwk }) => { const res = await jwsService.verifyJws(agentContext, { jws: jwt, @@ -101,30 +86,27 @@ export function getVerifyJwtCallback( return res.isValid }, }) - // When the chain is already invalid we can return false immediately - if (validTrustChains.length === 0) { - logger.error(`${entityId} is not part of a trusted federation.`) - return false - } - // Pick the first valid trust chain for validation of the leaf entity jwks - const { leafEntityConfiguration } = validTrustChains[0] - // TODO: No support yet for signed jwks and external jwks - const rpSigningKeys = leafEntityConfiguration?.metadata?.openid_relying_party?.jwks?.keys + // TODO: Not really sure if we can use the kid of the jwt header for finding the federation key. And if it even has a kid in the jwt header. + const kid = jwt.header.kid + if (!kid) throw new CredoError('No kid found in the jwt header.') + + const rpSigningKeys = entityConfiguration.metadata?.openid_relying_party?.jwks?.keys if (!rpSigningKeys || rpSigningKeys.length === 0) throw new CredoError('No rp signing keys found in the entity configuration.') - const res = await jwsService.verifyJws(agentContext, { + const jwk = rpSigningKeys.find((key) => key.kid === kid) + if (!jwk) throw new CredoError(`No rp signing key found in the entity configuration with kid: ${kid}.`) + + const result = await jwsService.verifyJws(agentContext, { jws: jwt.raw, - jwkResolver: () => getJwkFromJson(rpSigningKeys[0]), + jwkResolver: () => getJwkFromJson(jwk), }) - if (!res.isValid) { + if (!result.isValid) { logger.error(`${entityId} does not match the expected signing key.`) } - // TODO: There is no check yet for the policies - - return res.isValid + return result.isValid } throw new Error(`Unsupported jwt verifier method: '${jwtVerifier.method}'`)