Skip to content

Conversation

berendsliedrecht
Copy link
Contributor

Signed-off-by: Berend Sliedrecht berend@animo.id

…the holder side for verification

Signed-off-by: Berend Sliedrecht <berend@animo.id>
@berendsliedrecht berendsliedrecht requested a review from a team as a code owner May 27, 2025 12:03
Copy link

changeset-bot bot commented May 27, 2025

🦋 Changeset detected

Latest commit: 03b0011

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@credo-ts/openid4vc Patch
@credo-ts/action-menu Patch
@credo-ts/anoncreds Patch
@credo-ts/askar Patch
@credo-ts/cheqd Patch
@credo-ts/core Patch
@credo-ts/didcomm Patch
@credo-ts/drpc Patch
@credo-ts/indy-sdk-to-askar-migration Patch
@credo-ts/indy-vdr Patch
@credo-ts/node Patch
@credo-ts/question-answer Patch
@credo-ts/react-native Patch
@credo-ts/redis-cache Patch
@credo-ts/tenants Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@TimoGlastra
Copy link
Contributor

Chore is generally for non feature related changes (e.g. preparing for a release). This is more a feature (as it allows you to do something that wasn't possible before)

Comment on lines 149 to 151
if (!result) {
throw new CredoError('verificationAuthorizationCallback returned false. User-provided validation failed.')
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will swallow any errors. I think it might be better to make the inner method throw. Or we at least return the error so we can add it as cause. i think requiring the callback to throw has my preference

@TimoGlastra
Copy link
Contributor

can you add a changeset (just know i will keep commenting this on every PR, so might be good to just add it to every PR by default 😅)

export interface ResolveOpenId4VpAuthorizationRequestOptions {
trustedCertificates?: EncodedX509Certificate[]
origin?: string
verifyAuthorizationRequestCallback?: VerifyAuthorizationRequestCallback
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soo looking at it like this, what does the callback provide over just doing

  • resolve
  • call custom callback method

The callback just adds another layer of indirection.

For the verifier side the callback makes more sense since we automatically send a http response (and you might want to block it).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also thinking whether the name of the callback may mislead into thinking it will override the verification (which it doesn't).

To continue on my previous comment. The input to the verify is just the authorization request, which is not always the full context (which you get returned from the method). So I'd actually lean to not merge this, and just call our custom callback after calling the credo resolve method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It enforces it slightly more, the callback is optional so it is not enforced strictly. The API will be extended before merging to get more values next to the authorizationRequest in the callback. I just need to find out everything that is required or can be nice to have in the verification context.

@berendsliedrecht
Copy link
Contributor Author

can you add a changeset (just know i will keep commenting this on every PR, so might be good to just add it to every PR by default 😅)

Haha yes, I will!

@berendsliedrecht berendsliedrecht changed the title chore: custom verification callback for the authorization request on the holder side for verification feat: custom verification callback for the authorization request on the holder side for verification May 27, 2025
…s the holder to do additional validation on the authorization request when resolving it

Signed-off-by: Berend Sliedrecht <berend@animo.id>
…sed on the registration_certificate

Signed-off-by: Berend Sliedrecht <berend@animo.id>
@berendsliedrecht berendsliedrecht force-pushed the authorization-request-verification-callback branch from 45d4b54 to ae03ac5 Compare May 28, 2025 10:53
const holderTenant = await holder.agent.modules.tenants.getTenantAgent({ tenantId: holder1.tenantId })

await expect(
holderTenant.modules.openId4VcHolder.resolveOpenId4VpAuthorizationRequest(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main interesting code, i.e. validation the registration certificate, happens here.


export type VerifyAuthorizationRequestOptions = {
authorizationRequest: Openid4vpAuthorizationRequest | Openid4vpAuthorizationRequestDcApi
jwsService: JwsService
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to pass the agentContext as the first param to the callback. The user can then inject anything they need. Attestations could be other things than JWTs as well, so it's too focused on a specific case.

If the context is passed the user can just do agentContext.resolve(JwsService)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense

Comment on lines 468 to 505
for (const va of authorizationRequest.verifier_attestations) {
// Here we verify it as a registration certificate according to
// https://bmi.usercontent.opencode.de/eudi-wallet/eidas-2.0-architekturkonzept/flows/Wallet-Relying-Party-Authentication/#registration-certificate
if (va.format === 'jwt') {
if (typeof va.data !== 'string') {
throw new Error('Only inline JWTs are supported')
}

const { isValid } = await jwsService.verifyJws(holder.agent.context, { jws: va.data })
const jwt = Jwt.fromSerializedJwt(va.data)

if (!isValid) {
throw new Error('Invalid signature on JWT provided in the verifier_attestations')
}

if (jwt.header.typ !== 'rc-rp+jwt') {
throw new Error(`only 'rc-rp+jwt' is supported as header typ. Request included: ${jwt.header.typ}`)
}

const registrationCertificateHeaderSchema = z
.object({
typ: z.literal('rc-rp+jwt'),
alg: z.string(),
// sprin-d did not define this
x5u: z.string().url().optional(),
// sprin-d did not define this
'x5t#s256': z.string().optional(),
})
.passthrough()

// TODO: does not support intermediaries
const registrationCertificatePayloadSchema = z
.object({
credentials: z.array(
z.object({
id: z.string().optional(),
format: z.string(),
multiple: z.boolean().default(false),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is too complex for what's being tested. We're testing the callback, and this adds the very complex structure of a registration certificate. I would like to keep this test simple, and add the EUDI specific validations to a new repository.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we are not testing what is relevant here. I can move it to another repo, that is fine.

// TODO: export from oid4vp
export type ParsedTransactionDataEntry = NonNullable<ResolvedOpenid4vpAuthorizationRequest['transactionData']>[number]

export type VerifyAuthorizationRequestOptions = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel we're still missing relevant information from the OpenId4VpResolvedAuthorizationRequest interface here.

Also we should try to stay as consistent as possible with OpenId4VpResolvedAuthorizationRequest.

So

authorizationRequestPayload: OpenId4VpAuthorizationRequestPayload

and ideally:

  /**
   * Metadata about the signed authorization request.
   *
   * Only present if the authorization request was signed
   */
  signedAuthorizationRequest?: {
    signer: VerifiedJarRequest['signer']
    payload: VerifiedJarRequest['jwt']['payload']
    header: VerifiedJarRequest['jwt']['header']
  }

also add the origin (in case of DC-API):

  /**
   * Origin of the request, to be used with Digital Credentials API
   */
  origin?: string

Maybe we can just pass the same interface? Or do we call this before the DCQL and Presentation Exchange Queries are run? (in that case we can omit these from the interface). But i would rather not add client here if it's not also available in the resolved value.

https://github.com/openwallet-foundation/credo-ts/blob/main/packages/openid4vc/src/openid4vc-holder/OpenId4vpHolderServiceOptions.ts#L22

})
} catch (e) {
throw new CredoError(
`verificationAuthorizationCallback returned false. User-provided validation failed. cause: ${e}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method does not return false anymore

Suggested change
`verificationAuthorizationCallback returned false. User-provided validation failed. cause: ${e}`,
`error during call to user-provided verifyAuthorizationRequestCallback. cause: ${e}`,

… request

Signed-off-by: Berend Sliedrecht <berend@animo.id>
@berendsliedrecht berendsliedrecht force-pushed the authorization-request-verification-callback branch from 775bf07 to cc916d4 Compare May 28, 2025 13:36
Signed-off-by: Berend Sliedrecht <berend@animo.id>
Copy link
Contributor

@TimoGlastra TimoGlastra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One remaining comment


export type VerifyAuthorizationRequestOptions = {
agentContext: AgentContext
} & OpenId4VpResolvedAuthorizationRequest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transactionData, pex and dcql will never be defined, so just using the interface is misleading. Can we do a Pick on the interface?

Signed-off-by: Berend Sliedrecht <berend@animo.id>
@berendsliedrecht berendsliedrecht force-pushed the authorization-request-verification-callback branch from f36d781 to 03b0011 Compare May 30, 2025 12:46
@berendsliedrecht berendsliedrecht merged commit 66d3384 into openwallet-foundation:main Jun 2, 2025
34 of 38 checks passed
@berendsliedrecht berendsliedrecht deleted the authorization-request-verification-callback branch June 2, 2025 09:34
berendsliedrecht added a commit to animo/credo-ts that referenced this pull request Jun 4, 2025
…he holder side for verification (openwallet-foundation#2303)

Signed-off-by: Berend Sliedrecht <berend@animo.id>
genaris pushed a commit to genaris/credo-ts that referenced this pull request Oct 9, 2025
…he holder side for verification (openwallet-foundation#2303)

Signed-off-by: Berend Sliedrecht <berend@animo.id>
Signed-off-by: Ariel Gentile <gentilester@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants