Skip to content

Commit

Permalink
nonce expiration verification
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra committed Nov 6, 2024
1 parent fbe5b7f commit 25c69c5
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 4 deletions.
4 changes: 2 additions & 2 deletions packages/oid4vci/src/Oid4vciClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ import {
type CreateCredentialRequestJwtProofOptions,
createCredentialRequestJwtProof,
} from './formats/proof-type/jwt/jwt-proof-type'
import { type IssuerMetadataResult, resolveIssuerMetadata } from './metadata/fetch-issuer-metadata'
import { type SendNotifcationOptions, sendNotifcation } from './notification/notification'
import { extractKnownCredentialConfigurationSupportedFormats } from './metadata/credential-issuer/credential-issuer-metadata'
import type { CredentialIssuerMetadata } from './metadata/credential-issuer/v-credential-issuer-metadata'
import { type IssuerMetadataResult, resolveIssuerMetadata } from './metadata/fetch-issuer-metadata'
import { type SendNotifcationOptions, sendNotifcation } from './notification/notification'

export enum AuthorizationFlow {
Oauth2Redirect = 'Oauth2Redirect',
Expand Down
5 changes: 4 additions & 1 deletion packages/oid4vci/src/Oid4vciIssuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ export class Oid4vciIssuer {
}

public async verifyCredentialRequestJwtProof(
options: Pick<VerifyCredentialRequestJwtProofOptions, 'clientId' | 'jwt' | 'now' | 'expectedNonce'> & {
options: Pick<
VerifyCredentialRequestJwtProofOptions,
'clientId' | 'jwt' | 'now' | 'expectedNonce' | 'nonceExpiresAt'
> & {
issuerMetadata: IssuerMetadataResult
}
) {
Expand Down
2 changes: 2 additions & 0 deletions packages/oid4vci/src/__tests__/Oid4vciIssuer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,14 @@ describe('Oid4vciIssuer', () => {
cNonce: 'some-new-nonce',
cNonceExpiresInSeconds: 500,
credential: 'the-credential',
credentialRequest: parsedCredentialRequest,
})

expect(credentialResponse).toEqual({
c_nonce: 'some-new-nonce',
c_nonce_expires_in: 500,
credential: 'the-credential',
format: 'vc+sd-jwt',
})
})
})
7 changes: 7 additions & 0 deletions packages/oid4vci/src/credential-offer/credential-offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ export async function createCredentialOffer(options: CreateCredentialOfferOption
'pre-authorized_code':
preAuthorizedCodeGrant['pre-authorized_code'] ?? encodeToBase64Url(await options.callbacks.generateRandom(32)),
}

// Draft 11 support
const txCode = grants[preAuthorizedCodeGrantIdentifier].tx_code
if (txCode && options.issuerMetadata.credentialIssuer.originalDraftVersion === Oid4vciDraftVersion.Draft11) {
grants[preAuthorizedCodeGrantIdentifier].user_pin_required = txCode !== undefined
}
}

const idsNotInMetadata = options.credentialConfigurationIds.filter(
Expand All @@ -216,6 +222,7 @@ export async function createCredentialOffer(options: CreateCredentialOfferOption
...options.additionalPayload,
} satisfies CredentialOfferObject)

// Draft 11 support
if (options.issuerMetadata.credentialIssuer.originalDraftVersion === Oid4vciDraftVersion.Draft11) {
credentialOfferObject.credentials = credentialOfferObject.credential_configuration_ids
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { parseWithErrorHandling } from '@animo-id/oauth2-utils'
import type { ParseCredentialRequestReturn } from './parse-credential-request'
import { type CredentialResponse, vCredentialResponse } from './v-credential-response'

export interface CreateCredentialResponseOptions {
credentialRequest: ParseCredentialRequestReturn

credential?: CredentialResponse['credential']
credentials?: CredentialResponse['credentials']

Expand All @@ -23,6 +26,10 @@ export function createCredentialResponse(options: CreateCredentialResponseOption
credential: options.credential,
credentials: options.credentials,
notification_id: options.notificationId,

// NOTE `format` is removed in draft 13. For now if a format was requested
// we just always return it in the response as well.
format: options.credentialRequest.format?.format,
...options.additionalPayload,
} satisfies CredentialResponse)

Expand Down
12 changes: 11 additions & 1 deletion packages/oid4vci/src/formats/proof-type/jwt/jwt-proof-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from './v-jwt-proof-type'

import { type CallbackContext, jwtSignerFromJwt, verifyJwt } from '@animo-id/oauth2'
import { Oid4vciError } from '../../../error/Oid4vcError'

export interface CreateCredentialRequestJwtProofOptions {
/**
Expand Down Expand Up @@ -61,10 +62,14 @@ export interface VerifyCredentialRequestJwtProofOptions {

/**
* Expected nonce. Should be a c_nonce previously shared with the wallet
* @todo: cNonceExpiresAt?
*/
expectedNonce: string

/**
* Date at which the nonce will expire
*/
nonceExpiresAt?: Date

/**
* The credential issuer identifier, will be matched against the `aud` claim.
*/
Expand Down Expand Up @@ -93,6 +98,11 @@ export async function verifyCredentialRequestJwtProof(options: VerifyCredentialR
payloadSchema: vCredentialRequestJwtProofTypePayload,
})

const now = options.now?.getTime() ?? Date.now()
if (options.nonceExpiresAt && now > options.nonceExpiresAt.getTime()) {
throw new Oid4vciError('Nonce used for credential request proof expired')
}

const signer = jwtSignerFromJwt({ header, payload })
await verifyJwt({
compact: options.jwt,
Expand Down
4 changes: 4 additions & 0 deletions packages/oid4vci/tests/full-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ describe('Full E2E test', () => {
cNonce: 'd9457e7c-4cf7-461c-a8d0-94221ba865e7',
cNonceExpiresInSeconds: 500,
notificationId: '3b926f09-d603-4e8b-a75d-eaa8965f0fe3',
credentialRequest: parsedCredentialRequest,
})

return HttpResponse.json(credentialResponse)
Expand Down Expand Up @@ -405,6 +406,7 @@ describe('Full E2E test', () => {
c_nonce_expires_in: 500,
credential: 'some-credential',
notification_id: '3b926f09-d603-4e8b-a75d-eaa8965f0fe3',
format: 'vc+sd-jwt',
})
})

Expand Down Expand Up @@ -637,6 +639,7 @@ describe('Full E2E test', () => {
cNonce: 'd9457e7c-4cf7-461c-a8d0-94221ba865e7',
cNonceExpiresInSeconds: 500,
notificationId: '3b926f09-d603-4e8b-a75d-eaa8965f0fe3',
credentialRequest: parsedCredentialRequest,
})

return HttpResponse.json(credentialResponse)
Expand Down Expand Up @@ -744,6 +747,7 @@ describe('Full E2E test', () => {
c_nonce_expires_in: 500,
credential: 'some-credential',
notification_id: '3b926f09-d603-4e8b-a75d-eaa8965f0fe3',
format: 'vc+sd-jwt',
})
})
})

0 comments on commit 25c69c5

Please sign in to comment.