Skip to content

Commit

Permalink
fix: Search the jwk based on the kid
Browse files Browse the repository at this point in the history
Signed-off-by: Tom Lanser <tom@devv.nl>
  • Loading branch information
Tommylans committed Dec 5, 2024
1 parent 8628b5e commit 5f0b11a
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 70 deletions.
13 changes: 7 additions & 6 deletions packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
}

/**
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type {
OpenId4VcSiopAcceptAuthorizationRequestOptions,
OpenId4VcSiopFetchEntityConfigurationOptions,
OpenId4VcSiopGetOpenIdProviderOptions,
OpenId4VcSiopResolveAuthorizationRequestOptions,
OpenId4VcSiopResolvedAuthorizationRequest,
OpenId4VcSiopResolveTrustChainsOptions,
} from './OpenId4vcSiopHolderServiceOptions'
Expand Down Expand Up @@ -47,12 +45,9 @@ export class OpenId4VcSiopHolderService {

public async resolveAuthorizationRequest(
agentContext: AgentContext,
requestJwtOrUri: string,
options: OpenId4VcSiopResolveAuthorizationRequestOptions = {}
requestJwtOrUri: string
): Promise<OpenId4VcSiopResolvedAuthorizationRequest> {
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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[]]
Expand Down
48 changes: 15 additions & 33 deletions packages/openid4vc/src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) => {
Expand All @@ -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,
Expand All @@ -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}'`)
Expand Down

0 comments on commit 5f0b11a

Please sign in to comment.