Skip to content

Commit

Permalink
fix: assert valid jarm response (#2117)
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Auer <[email protected]>
  • Loading branch information
auer-martin authored Dec 9, 2024
1 parent 6989c65 commit 07a60cd
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import type { AgentContext } from '@credo-ts/core'
import type { AuthorizationResponsePayload, DecryptCompact } from '@sphereon/did-auth-siop'
import type { Response, Router } from 'express'

import { Oauth2ErrorCodes, Oauth2ServerErrorResponseError } from '@animo-id/oauth2'
import { CredoError, Hasher, JsonEncoder, Key, TypedArrayEncoder } from '@credo-ts/core'
import { AuthorizationRequest, RP } from '@sphereon/did-auth-siop'

import { getRequestContext, sendErrorResponse, sendJsonResponse } from '../../shared/router'
import { getRequestContext, sendErrorResponse, sendJsonResponse, sendOauth2ErrorResponse } from '../../shared/router'
import { OpenId4VcSiopVerifierService } from '../OpenId4VcSiopVerifierService'

export interface OpenId4VcSiopAuthorizationEndpointConfig {
Expand Down Expand Up @@ -68,6 +69,8 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc
router.post(config.endpointPath, async (request: OpenId4VcVerificationRequest, response: Response, next) => {
const { agentContext, verifier } = getRequestContext(request)

let jarmResponseType: string | undefined

try {
const openId4VcVerifierService = agentContext.dependencyManager.resolve(OpenId4VcSiopVerifierService)

Expand Down Expand Up @@ -95,6 +98,8 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc
hasher: Hasher.hash,
})

jarmResponseType = res.type

const [header] = request.body.response.split('.')
jarmHeader = JsonEncoder.fromBase64(header)
// FIXME: verify the apv matches the nonce of the authorization reuqest
Expand Down Expand Up @@ -123,6 +128,29 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc
throw new CredoError('Missing verification session, cannot verify authorization response.')
}

const authorizationRequest = await AuthorizationRequest.fromUriOrJwt(verificationSession.authorizationRequestJwt)
const response_mode = await authorizationRequest.getMergedProperty<string>('response_mode')
if (response_mode?.includes('jwt') && !jarmResponseType) {
throw new Oauth2ServerErrorResponseError({
error: Oauth2ErrorCodes.InvalidRequest,
error_description: `JARM response is required for JWT response mode '${response_mode}'.`,
})
}

if (!response_mode?.includes('jwt') && jarmResponseType) {
throw new Oauth2ServerErrorResponseError({
error: Oauth2ErrorCodes.InvalidRequest,
error_description: `Recieved JARM response which is incompatible with response mode '${response_mode}'.`,
})
}

if (jarmResponseType && jarmResponseType !== 'encrypted') {
throw new Oauth2ServerErrorResponseError({
error: Oauth2ErrorCodes.InvalidRequest,
error_description: `Only encrypted JARM responses are supported, received '${jarmResponseType}'.`,
})
}

await openId4VcVerifierService.verifyAuthorizationResponse(agentContext, {
authorizationResponse: authorizationResponsePayload,
verificationSession,
Expand All @@ -133,6 +161,10 @@ export function configureAuthorizationEndpoint(router: Router, config: OpenId4Vc
presentation_during_issuance_session: verificationSession.presentationDuringIssuanceSession,
})
} catch (error) {
if (error instanceof Oauth2ServerErrorResponseError) {
return sendOauth2ErrorResponse(response, next, agentContext.config.logger, error)
}

return sendErrorResponse(response, next, agentContext.config.logger, 500, 'invalid_request', error)
}
})
Expand Down
2 changes: 1 addition & 1 deletion packages/openid4vc/src/shared/router/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function sendErrorResponse(
error: unknown,
additionalPayload?: Record<string, unknown>
) {
const body = { error: message, ...additionalPayload }
const body = { error: message, ...(error instanceof Error && { cause: error.message }), ...additionalPayload }
logger.warn(`[OID4VC] Sending error response: ${JSON.stringify(body)}`, {
error,
})
Expand Down
107 changes: 107 additions & 0 deletions packages/openid4vc/tests/openid4vc.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
JwsService,
JwtPayload,
} from '@credo-ts/core'
import { ResponseMode } from '@sphereon/did-auth-siop'
import express, { type Express } from 'express'

import { AskarModule } from '../../askar/src'
Expand Down Expand Up @@ -1857,6 +1858,112 @@ describe('OpenId4Vc', () => {
await holderTenant1.endSession()
})

it('e2e flow with verifier endpoints verifying a mdoc fails without direct_post.jwt', async () => {
const openIdVerifier = await verifier.agent.modules.openId4VcVerifier.createVerifier()

const selfSignedCertificate = await X509Service.createSelfSignedCertificate(issuer.agent.context, {
key: await issuer.agent.context.wallet.createKey({ keyType: KeyType.P256 }),
extensions: [],
name: 'C=DE',
})

await verifier.agent.x509.setTrustedCertificates([selfSignedCertificate.toString('pem')])

const holderKey = await holder.agent.context.wallet.createKey({ keyType: KeyType.P256 })
const signedMdoc = await issuer.agent.mdoc.sign({
docType: 'org.eu.university',
holderKey,
issuerCertificate: selfSignedCertificate.toString('pem'),
namespaces: {
'eu.europa.ec.eudi.pid.1': {
university: 'innsbruck',
degree: 'bachelor',
name: 'John Doe',
not: 'disclosed',
},
},
})

const certificate = await verifier.agent.x509.createSelfSignedCertificate({
key: await verifier.agent.wallet.createKey({ keyType: KeyType.Ed25519 }),
extensions: [[{ type: 'dns', value: 'localhost:1234' }]],
})

const rawCertificate = certificate.toString('base64')
await holder.agent.mdoc.store(signedMdoc)

await holder.agent.x509.addTrustedCertificate(rawCertificate)
await verifier.agent.x509.addTrustedCertificate(rawCertificate)

const presentationDefinition = {
id: 'mDL-sample-req',
input_descriptors: [
{
id: 'org.eu.university',
format: {
mso_mdoc: {
alg: ['ES256', 'ES384', 'ES512', 'EdDSA', 'ESB256', 'ESB320', 'ESB384', 'ESB512'],
},
},
constraints: {
fields: [
{
path: ["$['eu.europa.ec.eudi.pid.1']['name']"],
intent_to_retain: false,
},
{
path: ["$['eu.europa.ec.eudi.pid.1']['degree']"],
intent_to_retain: false,
},
],
limit_disclosure: 'required',
},
},
],
} satisfies DifPresentationExchangeDefinitionV2

const { authorizationRequest } = await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({
responseMode: 'direct_post.jwt',
verifierId: openIdVerifier.verifierId,
requestSigner: {
method: 'x5c',
x5c: [rawCertificate],
issuer: 'https://example.com/hakuna/matadata',
},
presentationExchange: { definition: presentationDefinition },
})

const resolvedAuthorizationRequest = await holder.agent.modules.openId4VcHolder.resolveSiopAuthorizationRequest(
authorizationRequest
)

if (!resolvedAuthorizationRequest.presentationExchange) {
throw new Error('Presentation exchange not defined')
}

const selectedCredentials = holder.agent.modules.openId4VcHolder.selectCredentialsForRequest(
resolvedAuthorizationRequest.presentationExchange.credentialsForRequest
)

const requestPayload =
await resolvedAuthorizationRequest.authorizationRequest.authorizationRequest.requestObject?.getPayload()
if (!requestPayload) {
throw new Error('No payload')
}

// setting this to direct_post to simulate the result of sending a non encrypted response to an authorization request that requires enryption
requestPayload.response_mode = ResponseMode.DIRECT_POST

await expect(
holder.agent.modules.openId4VcHolder.acceptSiopAuthorizationRequest({
authorizationRequest: resolvedAuthorizationRequest.authorizationRequest,
presentationExchange: {
credentials: selectedCredentials,
},
})
).rejects.toThrow(/JARM response is required/)
})

it('e2e flow with verifier endpoints verifying a mdoc and sd-jwt (jarm)', async () => {
const openIdVerifier = await verifier.agent.modules.openId4VcVerifier.createVerifier()

Expand Down

0 comments on commit 07a60cd

Please sign in to comment.