Skip to content

Commit

Permalink
feat: Processed feedback and used the right keys 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 18, 2024
1 parent 1743fb1 commit dcd810d
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 217 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.6",
"@openid-federation/core": "0.1.1-alpha.12",
"class-transformer": "^0.5.1",
"rxjs": "^7.8.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
OpenId4VcSiopResolveAuthorizationRequestOptions,
OpenId4VcSiopResolvedAuthorizationRequest,
} from './OpenId4vcSiopHolderServiceOptions'
import type { OpenId4VcJwtIssuer } from '../shared'
import type { OpenId4VcJwtIssuer, OpenId4VcJwtIssuerFederation } from '../shared'
import type { AgentContext, JwkJson, VerifiablePresentation } from '@credo-ts/core'
import type {
AuthorizationResponsePayload,
Expand All @@ -28,7 +28,9 @@ import {
injectable,
parseDid,
MdocDeviceResponse,
JwsService,
} from '@credo-ts/core'
import { fetchEntityConfiguration } from '@openid-federation/core'
import { OP, ResponseIss, ResponseMode, ResponseType, SupportedVersion, VPTokenLocation } from '@sphereon/did-auth-siop'

import { getSphereonVerifiablePresentation } from '../shared/transform'
Expand Down Expand Up @@ -64,6 +66,34 @@ export class OpenId4VcSiopHolderService {

const presentationDefinition = verifiedAuthorizationRequest.presentationDefinitions?.[0]?.definition

if (verifiedAuthorizationRequest.clientIdScheme === 'entity_id') {
const clientId = verifiedAuthorizationRequest.authorizationRequestPayload.client_id
if (!clientId) {
throw new CredoError("Unable to extract 'client_id' from authorization request")
}

const jwsService = agentContext.dependencyManager.resolve(JwsService)

const entityConfiguration = await fetchEntityConfiguration({
entityId: clientId,
verifyJwtCallback: async ({ jwt, jwk }) => {
const res = await jwsService.verifyJws(agentContext, {
jws: jwt,
jwkResolver: () => getJwkFromJson(jwk),
})

return res.isValid
},
})
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
}
}

return {
authorizationRequest: verifiedAuthorizationRequest,

Expand Down Expand Up @@ -261,7 +291,7 @@ export class OpenId4VcSiopHolderService {

private getOpenIdTokenIssuerFromVerifiablePresentation(
verifiablePresentation: VerifiablePresentation
): OpenId4VcJwtIssuer {
): Exclude<OpenId4VcJwtIssuer, OpenId4VcJwtIssuerFederation> {
let openIdTokenIssuer: OpenId4VcJwtIssuer

if (verifiablePresentation instanceof W3cJsonLdVerifiablePresentation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
OpenId4VcJwtIssuer,
OpenId4VcSiopVerifiedAuthorizationRequest,
OpenId4VcSiopAuthorizationResponsePayload,
OpenId4VcJwtIssuerFederation,
} from '../shared'
import type {
DifPexCredentialsForRequest,
Expand Down Expand Up @@ -42,7 +43,7 @@ export interface OpenId4VcSiopAcceptAuthorizationRequestOptions {
* In case presentation exchange is used, and `openIdTokenIssuer` is not provided, the issuer of the ID Token
* will be extracted from the signer of the first verifiable presentation.
*/
openIdTokenIssuer?: OpenId4VcJwtIssuer
openIdTokenIssuer?: Exclude<OpenId4VcJwtIssuer, OpenId4VcJwtIssuerFederation>

/**
* The verified authorization request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,7 @@ export class OpenId4VcIssuerModule implements Module {
configureCredentialOfferEndpoint(endpointRouter, this.config.credentialOfferEndpoint)
configureAccessTokenEndpoint(endpointRouter, this.config.accessTokenEndpoint)
configureCredentialEndpoint(endpointRouter, this.config.credentialEndpoint)

// The federation endpoint is optional
if (this.config.federationEndpoint) {
configureFederationEndpoint(endpointRouter, this.config.federationEndpoint)
}
configureFederationEndpoint(endpointRouter)

// First one will be called for all requests (when next is called)
contextRouter.use(async (req: OpenId4VcIssuanceRequest, _res: unknown, next) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
import type { OpenId4VcIssuanceRequest } from './requestContext'
import type { FederationKeyCallback } from '../../shared/federation'
import type { Buffer } from '@credo-ts/core'
import type { Router, Response } from 'express'

import { getJwkFromKey } from '@credo-ts/core'
import { Key, getJwkFromKey, KeyType } from '@credo-ts/core'
import { createEntityConfiguration } from '@openid-federation/core'

import { getRequestContext, sendErrorResponse } from '../../shared/router'
import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService'

export interface OpenId4VcSiopFederationEndpointConfig {
/**
* The path at which the credential endpoint should be made available. Note that it will be
* hosted at a subpath to take into account multiple tenants and issuers.
*
* @default /.well-known/openid-federation
*/
endpointPath: string

// TODO: Not sure about the property name yet.
//TODO: More information is needed than only the key also the client id etc
keyCallback: FederationKeyCallback<{
issuerId: string
}>
}

// TODO: It's also possible that the issuer and the verifier can have the same openid-federation endpoint. In that case we need to combine them.

export function configureFederationEndpoint(router: Router, config: OpenId4VcSiopFederationEndpointConfig) {
router.get(config.endpointPath, async (request: OpenId4VcIssuanceRequest, response: Response, next) => {
export function configureFederationEndpoint(router: Router) {
// TODO: this whole result needs to be cached and the ttl should be the expires of this node

router.get('/.well-known/openid-federation', async (request: OpenId4VcIssuanceRequest, response: Response, next) => {
const { agentContext, issuer } = getRequestContext(request)
const openId4VcIssuerService = agentContext.dependencyManager.resolve(OpenId4VcIssuerService)

try {
// TODO: Should be only created once per issuer and be used between instances
const federationKey = await agentContext.wallet.createKey({
keyType: KeyType.Ed25519,
})

const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer)
// TODO: Use a type here from sphreon
const transformedMetadata = {
Expand All @@ -50,16 +40,17 @@ export function configureFederationEndpoint(router: Router, config: OpenId4VcSio
const now = new Date()
const expires = new Date(now.getTime() + 1000 * 60 * 60 * 24) // 1 day from now

const { key } = await config.keyCallback(agentContext, {
issuerId: issuer.issuerId,
})
// TODO: We need to generate a key and always use that for the entity configuration

const jwk = getJwkFromKey(federationKey)

const jwk = getJwkFromKey(key)
const kid = 'key-1'
const kid = federationKey.fingerprint
const alg = jwk.supportedSignatureAlgorithms[0]

const issuerDisplay = issuerMetadata.issuerDisplay?.[0]

const accessTokenSigningKey = Key.fromFingerprint(issuer.accessTokenPublicKeyFingerprint)

const entityConfiguration = await createEntityConfiguration({
claims: {
sub: issuerMetadata.issuerUrl,
Expand All @@ -72,11 +63,23 @@ export function configureFederationEndpoint(router: Router, config: OpenId4VcSio
metadata: {
federation_entity: issuerDisplay
? {
organization_name: issuerDisplay.organization_name,
logo_uri: issuerDisplay.logo_uri,
organization_name: issuerDisplay.name,
logo_uri: issuerDisplay.logo?.url,
}
: undefined,
openid_credential_issuer: transformedMetadata,
openid_provider: {
...transformedMetadata,
client_registration_types_supported: ['automatic'],
jwks: {
keys: [
{
// TODO: Not 100% sure if this is the right key that we want to expose here or a different one
kid: accessTokenSigningKey.fingerprint,
...getJwkFromKey(accessTokenSigningKey).toJson(),
},
],
},
},
},
},
header: {
Expand All @@ -87,12 +90,15 @@ export function configureFederationEndpoint(router: Router, config: OpenId4VcSio
signJwtCallback: ({ toBeSigned }) =>
agentContext.wallet.sign({
data: toBeSigned as Buffer,
key,
key: federationKey,
}),
})

response.writeHead(200, { 'Content-Type': 'application/entity-statement+jwt' }).end(entityConfiguration)
} catch (error) {
agentContext.config.logger.error('Failed to create entity configuration', {
error,
})
sendErrorResponse(response, agentContext.config.logger, 500, 'invalid_request', error)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,19 @@ export class OpenId4VcSiopVerifierService {
this.config.authorizationEndpoint.endpointPath,
])

const federationClientId = joinUriParts(this.config.baseUrl, [options.verifier.verifierId])

const jwtIssuer =
options.requestSigner.method === 'x5c'
? await openIdTokenIssuerToJwtIssuer(agentContext, {
...options.requestSigner,
issuer: authorizationResponseUrl,
})
: options.requestSigner.method === 'openid-federation'
? await openIdTokenIssuerToJwtIssuer(agentContext, {
...options.requestSigner,
clientId: federationClientId,
})
: await openIdTokenIssuerToJwtIssuer(agentContext, options.requestSigner)

let clientIdScheme: ClientIdScheme
Expand Down Expand Up @@ -144,16 +151,12 @@ export class OpenId4VcSiopVerifierService {
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')
throw new CredoError(`Custom jwtIssuer's clientId must be a string.`)

clientIdScheme = 'entity_id'
clientId = jwtIssuer.options.clientId
clientId = federationClientId
} else {
throw new CredoError(
`Unsupported jwt issuer method '${options.requestSigner.method}'. Only 'did' and 'x5c' are supported.`
`Unsupported jwt issuer method '${options.requestSigner.method}'. Only 'did', 'x5c' and 'custom' are supported.`
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,10 @@ export class OpenId4VcVerifierModule implements Module {
// Configure endpoints
configureAuthorizationEndpoint(endpointRouter, this.config.authorizationEndpoint)
configureAuthorizationRequestEndpoint(endpointRouter, this.config.authorizationRequestEndpoint)
if (this.config.federationEndpoint) {
configureFederationEndpoint(endpointRouter, this.config.federationEndpoint)
}

// TODO: The keys needs to be passed down to the federation endpoint to be used in the entity configuration for the openid relying party
// TODO: But the keys also needs to be available for the request signing. They also needs to get saved because it needs to survive a restart of the agent.
configureFederationEndpoint(endpointRouter)

// First one will be called for all requests (when next is called)
contextRouter.use(async (req: OpenId4VcVerificationRequest, _res: unknown, next) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { OpenId4VcSiopAuthorizationEndpointConfig } from './router/authorizationEndpoint'
import type { OpenId4VcSiopAuthorizationRequestEndpointConfig } from './router/authorizationRequestEndpoint'
import type { OpenId4VcSiopFederationEndpointConfig } from './router/federationEndpoint'
import type { Optional } from '@credo-ts/core'
import type { Router } from 'express'

Expand All @@ -25,7 +24,6 @@ export interface OpenId4VcVerifierModuleConfigOptions {
endpoints?: {
authorization?: Optional<OpenId4VcSiopAuthorizationEndpointConfig, 'endpointPath'>
authorizationRequest?: Optional<OpenId4VcSiopAuthorizationRequestEndpointConfig, 'endpointPath'>
federation?: Optional<OpenId4VcSiopFederationEndpointConfig, 'endpointPath'>
}
}

Expand Down Expand Up @@ -62,15 +60,4 @@ export class OpenId4VcVerifierModuleConfig {
endpointPath: userOptions?.endpointPath ?? '/authorize',
}
}

public get federationEndpoint(): OpenId4VcSiopFederationEndpointConfig | undefined {
// Use user supplied options, or return defaults.
const userOptions = this.options.endpoints?.federation
if (!userOptions) return undefined

return {
...userOptions,
endpointPath: userOptions.endpointPath ?? '/.well-known/openid-federation',
}
}
}
Loading

0 comments on commit dcd810d

Please sign in to comment.