Skip to content

Commit

Permalink
feat: Littlebit of a cleanup for the verifier
Browse files Browse the repository at this point in the history
Signed-off-by: Tom Lanser <[email protected]>
  • Loading branch information
Tommylans committed Nov 7, 2024
1 parent b723485 commit bcaed4d
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 44 deletions.
2 changes: 1 addition & 1 deletion packages/openid4vc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@sphereon/oid4vci-common": "0.16.1-fix.173",
"@sphereon/oid4vci-issuer": "0.16.1-fix.173",
"@sphereon/ssi-types": "0.30.2-next.135",
"@openid-federation/core": "0.1.1-alpha.5",
"@openid-federation/core": "0.1.1-alpha.6",
"class-transformer": "^0.5.1",
"rxjs": "^7.8.0"
},
Expand Down
12 changes: 9 additions & 3 deletions packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import type {
OpenId4VciSendNotificationOptions,
OpenId4VciRequestTokenResponse,
} from './OpenId4VciHolderServiceOptions'
import type { OpenId4VcSiopAcceptAuthorizationRequestOptions } from './OpenId4vcSiopHolderServiceOptions'
import type {
OpenId4VcSiopAcceptAuthorizationRequestOptions,
OpenId4VcSiopResolveAuthorizationRequestOptions,
} from './OpenId4vcSiopHolderServiceOptions'

import { injectable, AgentContext } from '@credo-ts/core'

Expand Down Expand Up @@ -40,8 +43,11 @@ export class OpenId4VcHolderApi {
* @param requestJwtOrUri JWT or an SIOPv2 request URI
* @returns the resolved and verified authentication request.
*/
public async resolveSiopAuthorizationRequest(requestJwtOrUri: string) {
return this.openId4VcSiopHolderService.resolveAuthorizationRequest(this.agentContext, requestJwtOrUri)
public async resolveSiopAuthorizationRequest(
requestJwtOrUri: string,
options: OpenId4VcSiopResolveAuthorizationRequestOptions = {}
) {
return this.openId4VcSiopHolderService.resolveAuthorizationRequest(this.agentContext, requestJwtOrUri, options)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type {
OpenId4VcSiopAcceptAuthorizationRequestOptions,
OpenId4VcSiopGetOpenIdProviderOptions,
OpenId4VcSiopResolveAuthorizationRequestOptions,
OpenId4VcSiopResolvedAuthorizationRequest,
} from './OpenId4vcSiopHolderServiceOptions'
import type { OpenId4VcJwtIssuer } from '../shared'
Expand Down Expand Up @@ -38,9 +40,12 @@ export class OpenId4VcSiopHolderService {

public async resolveAuthorizationRequest(
agentContext: AgentContext,
requestJwtOrUri: string
requestJwtOrUri: string,
options: OpenId4VcSiopResolveAuthorizationRequestOptions = {}
): Promise<OpenId4VcSiopResolvedAuthorizationRequest> {
const openidProvider = await this.getOpenIdProvider(agentContext)
const openidProvider = await this.getOpenIdProvider(agentContext, {
federation: options.federation,
})

// parsing happens automatically in verifyAuthorizationRequest
const verifiedAuthorizationRequest = await openidProvider.verifyAuthorizationRequest(requestJwtOrUri)
Expand Down Expand Up @@ -231,7 +236,7 @@ export class OpenId4VcSiopHolderService {
}
}

private async getOpenIdProvider(agentContext: AgentContext) {
private async getOpenIdProvider(agentContext: AgentContext, options: OpenId4VcSiopGetOpenIdProviderOptions = {}) {
const builder = OP.builder()
.withExpiresIn(6000)
.withIssuer(ResponseIss.SELF_ISSUED_V2)
Expand All @@ -242,7 +247,11 @@ export class OpenId4VcSiopHolderService {
SupportedVersion.SIOPv2_D12_OID4VP_D20,
])
.withCreateJwtCallback(getCreateJwtCallback(agentContext))
.withVerifyJwtCallback(getVerifyJwtCallback(agentContext))
.withVerifyJwtCallback(
getVerifyJwtCallback(agentContext, {
federation: options.federation,
})
)
.withHasher(Hasher.hash)

const openidProvider = builder.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ export interface OpenId4VcSiopAcceptAuthorizationRequestOptions {
* The verified authorization request.
*/
authorizationRequest: OpenId4VcSiopVerifiedAuthorizationRequest

// 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[]
}
}

// FIXME: rethink properties
Expand All @@ -56,3 +67,12 @@ export interface OpenId4VcSiopAuthorizationResponseSubmission {
status: number
submittedResponse: OpenId4VcSiopAuthorizationResponsePayload
}

export interface OpenId4VcSiopGetOpenIdProviderOptions {
federation?: {
/**
* The entity IDs of the trusted issuers.
*/
trustedEntityIds?: string[]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export class OpenId4VcSiopVerifierService {
clientId = jwtIssuer.didUrl.split('#')[0]
clientIdScheme = 'did'
} else if (jwtIssuer.method === 'custom') {
// TODO: Currently used as openid federation, but the jwtIssuer should also be openid-federation
if (!jwtIssuer.options) throw new CredoError(`Custom jwtIssuer must have options defined.`)
if (!jwtIssuer.options.clientId) throw new CredoError(`Custom jwtIssuer must have clientId defined.`)
if (typeof jwtIssuer.options.clientId !== 'string')
Expand Down
35 changes: 23 additions & 12 deletions packages/openid4vc/src/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { OpenId4VcIssuerX5c, OpenId4VcJwtIssuer } from './models'
import type { AgentContext, JwaSignatureAlgorithm, JwkJson, Key } from '@credo-ts/core'
import type { JwtIssuerWithContext as VpJwtIssuerWithContext, VerifyJwtCallback } from '@sphereon/did-auth-siop'
import type { DPoPJwtIssuerWithContext, CreateJwtCallback, JwtIssuer, JwtIssuerBase } from '@sphereon/oid4vc-common'
import type { DPoPJwtIssuerWithContext, CreateJwtCallback, JwtIssuer } from '@sphereon/oid4vc-common'
import type { CredentialOfferPayloadV1_0_11, CredentialOfferPayloadV1_0_13 } from '@sphereon/oid4vci-common'

import {
Expand All @@ -18,7 +18,7 @@ import {
getJwkFromKey,
getKeyFromVerificationMethod,
} from '@credo-ts/core'
import { fetchEntityConfiguration, fetchEntityConfigurationChains } from '@openid-federation/core'
import { fetchEntityConfiguration, resolveTrustChains } from '@openid-federation/core'

/**
* Returns the JWA Signature Algorithms that are supported by the wallet.
Expand Down Expand Up @@ -52,7 +52,9 @@ async function getKeyFromDid(agentContext: AgentContext, didUrl: string) {
}

type VerifyJwtCallbackOptions = {
trustedEntityIds?: string[]
federation?: {
trustedEntityIds?: string[]
}
}

export function getVerifyJwtCallback(
Expand All @@ -61,23 +63,28 @@ export function getVerifyJwtCallback(
): VerifyJwtCallback {
return async (jwtVerifier, jwt) => {
const jwsService = agentContext.dependencyManager.resolve(JwsService)

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 })
return res.isValid
} else if (jwtVerifier.method === 'x5c' || jwtVerifier.method === 'jwk') {
}

if (jwtVerifier.method === 'x5c' || jwtVerifier.method === 'jwk') {
const res = await jwsService.verifyJws(agentContext, { jws: jwt.raw })
return res.isValid
} else if (jwtVerifier.method === 'openid-federation') {
}

if (jwtVerifier.method === 'openid-federation') {
const { entityId } = jwtVerifier
const trustedEntityIds = options.trustedEntityIds ?? [entityId] // TODO: Just for testing
const trustedEntityIds = options.federation?.trustedEntityIds
if (!trustedEntityIds)
throw new CredoError('No trusted entity ids provided but is required for the openid-federation method.')

const entityConfigurationChains = await fetchEntityConfigurationChains({
leafEntityId: entityId,
const validTrustChains = await resolveTrustChains({
entityId,
trustAnchorEntityIds: trustedEntityIds,
verifyJwtCallback: async ({ data, signature, jwk }) => {
const jws = `${TypedArrayEncoder.toUtf8String(data)}.${TypedArrayEncoder.toBase64URL(signature)}`
Expand All @@ -86,17 +93,20 @@ export function getVerifyJwtCallback(
jws,
jwkResolver: () => getJwkFromJson(jwk),
})

return res.isValid
},
})

// TODO: There is no check yet for the policies

// TODO: When this function results in a `false` it gives a really misleading error message: 'Error verifying the DID Auth Token signature.'

// TODO: I think this is correct but not sure?
return entityConfigurationChains.length > 0
} else {
throw new Error(`Unsupported jwt verifier method: '${jwtVerifier.method}'`)
return validTrustChains.length > 0
}

throw new Error(`Unsupported jwt verifier method: '${jwtVerifier.method}'`)
}
}

Expand Down Expand Up @@ -150,7 +160,7 @@ export function getCreateJwtCallback(
if (!options.clientId) throw new CredoError(`Custom jwtIssuer must have clientId defined.`)
if (typeof options.clientId !== 'string') throw new CredoError(`Custom jwtIssuer's clientId must be a string.`)

const clientId = options.clientId
const { clientId } = options

const entityConfiguration = await fetchEntityConfiguration({
entityId: clientId as string,
Expand All @@ -164,6 +174,7 @@ export function getCreateJwtCallback(
// TODO: Not 100% sure what key to pick here I think the one that matches the kid in the jwt header of the entity configuration or we should pass a alg and pick a jwk based on that?
const jwk = getJwkFromJson(entityConfiguration.jwks.keys[0])

// TODO: This gives a weird error when the private key is not available in the wallet
const jws = await jwsService.createJwsCompact(agentContext, {
protectedHeaderOptions: { ...jwt.header, jwk, alg: jwk.supportedSignatureAlgorithms[0] },
payload: JwtPayload.fromJson(jwt.payload),
Expand Down
18 changes: 12 additions & 6 deletions packages/openid4vc/tests/openid4vc-federation.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ describe('OpenId4Vc', () => {
tenants: TenantsModule<{ openId4VcIssuer: OpenId4VcIssuerModule }>
x509: X509Module
}>
let issuer1: TenantType
let issuer2: TenantType

let holder: AgentType<{
openId4VcHolder: OpenId4VcHolderModule
Expand Down Expand Up @@ -143,8 +141,6 @@ describe('OpenId4Vc', () => {
},
'96213c3d7fc8d4d6754c7a0fd969598g'
)) as unknown as typeof issuer
issuer1 = await createTenantForAgent(issuer.agent, 'iTenant1')
issuer2 = await createTenantForAgent(issuer.agent, 'iTenant2')

holder = (await createAgentFromModules(
'holder',
Expand Down Expand Up @@ -276,7 +272,12 @@ describe('OpenId4Vc', () => {
await verifierTenant2.endSession()

const resolvedProofRequest1 = await holderTenant.modules.openId4VcHolder.resolveSiopAuthorizationRequest(
authorizationRequestUri1
authorizationRequestUri1,
{
federation: {
trustedEntityIds: [`http://localhost:1234/oid4vp/${openIdVerifierTenant1.verifierId}`],
},
}
)

expect(resolvedProofRequest1.presentationExchange?.credentialsForRequest).toMatchObject({
Expand All @@ -302,7 +303,12 @@ describe('OpenId4Vc', () => {
})

const resolvedProofRequest2 = await holderTenant.modules.openId4VcHolder.resolveSiopAuthorizationRequest(
authorizationRequestUri2
authorizationRequestUri2,
{
federation: {
trustedEntityIds: [`http://localhost:1234/oid4vp/${openIdVerifierTenant2.verifierId}`],
},
}
)

expect(resolvedProofRequest2.presentationExchange?.credentialsForRequest).toMatchObject({
Expand Down
42 changes: 24 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bcaed4d

Please sign in to comment.