From a3fe108b00c39e00953b3f174e5a8d6f5579ec06 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Mon, 22 Jul 2024 11:29:50 +0200 Subject: [PATCH 01/12] fix: openid4vc draft 13 support (credential-response format removed) Signed-off-by: Martin Auer --- .../openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index 6829c7cd24..d77bf9c029 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -424,6 +424,7 @@ export class OpenId4VciHolderService { const credential = await this.handleCredentialResponse(agentContext, credentialResponse, { verifyCredentialStatus: verifyCredentialStatus ?? false, credentialIssuerMetadata: metadata.credentialIssuerMetadata, + format: offeredCredentialConfiguration.format as OpenId4VciCredentialFormatProfile, }) this.logger.debug('Full credential', credential) @@ -615,6 +616,7 @@ export class OpenId4VciHolderService { options: { verifyCredentialStatus: boolean credentialIssuerMetadata: OpenId4VciIssuerMetadata + format: OpenId4VciCredentialFormatProfile } ): Promise { const { verifyCredentialStatus, credentialIssuerMetadata } = options @@ -635,7 +637,7 @@ export class OpenId4VciHolderService { } : undefined - const format = credentialResponse.successBody.format + const format = options.format if (format === OpenId4VciCredentialFormatProfile.SdJwtVc) { if (typeof credentialResponse.successBody.credential !== 'string') throw new CredoError( From 8765c0b4c85191964f6d2674ea239306c713d901 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Thu, 25 Jul 2024 14:21:14 +0200 Subject: [PATCH 02/12] feat: integrate dpop functionality Signed-off-by: Martin Auer --- .../core/src/modules/x509/X509ModuleConfig.ts | 4 + packages/openid4vc/package.json | 8 +- .../openid4vc-holder/OpenId4VcHolderApi.ts | 14 +- .../OpenId4VciHolderService.ts | 104 ++++++++++-- .../OpenId4VciHolderServiceOptions.ts | 5 +- .../openid4vc-issuer/OpenId4VcIssuerApi.ts | 3 +- .../OpenId4VcIssuerService.ts | 2 + .../OpenId4VcIssuerServiceOptions.ts | 10 +- .../repository/OpenId4VcIssuerRecord.ts | 10 +- .../router/accessTokenEndpoint.ts | 29 +++- .../router/credentialEndpoint.ts | 5 +- .../router/metadataEndpoint.ts | 1 + ...ccessToken.ts => verifyResourceRequest.ts} | 26 ++- .../__tests__/openid4vc-verifier.test.ts | 2 +- packages/openid4vc/src/shared/utils.ts | 44 ++++- .../openid4vc/tests/openid4vc.e2e.test.ts | 29 +++- pnpm-lock.yaml | 158 +----------------- 17 files changed, 252 insertions(+), 202 deletions(-) rename packages/openid4vc/src/openid4vc-issuer/router/{verifyAccessToken.ts => verifyResourceRequest.ts} (62%) diff --git a/packages/core/src/modules/x509/X509ModuleConfig.ts b/packages/core/src/modules/x509/X509ModuleConfig.ts index ac44bb02bc..5fcd99a076 100644 --- a/packages/core/src/modules/x509/X509ModuleConfig.ts +++ b/packages/core/src/modules/x509/X509ModuleConfig.ts @@ -1,4 +1,8 @@ export interface X509ModuleConfigOptions { + /** + * + * Array of trusted base64-encoded certificate strings in the DER-format. + */ trustedCertificates?: [string, ...string[]] } diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index 3995843668..8cff036238 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -27,10 +27,10 @@ }, "dependencies": { "@credo-ts/core": "workspace:*", - "@sphereon/did-auth-siop": "0.15.1-next.4", - "@sphereon/oid4vci-client": "0.15.1-next.4", - "@sphereon/oid4vci-common": "0.15.1-next.4", - "@sphereon/oid4vci-issuer": "0.15.1-next.4", + "@sphereon/did-auth-siop": "link:../../../../Documents/OID4VCI/packages/siop-oid4vp", + "@sphereon/oid4vci-client": "link:../../../../Documents/OID4VCI/packages/client", + "@sphereon/oid4vci-common": "link:../../../../Documents/OID4VCI/packages/common", + "@sphereon/oid4vci-issuer": "link:../../../../Documents/OID4VCI/packages/issuer", "@sphereon/ssi-types": "0.26.1-next.132", "class-transformer": "^0.5.1", "rxjs": "^7.8.0" diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts index 99461b9913..47d5b3f20e 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts @@ -146,11 +146,12 @@ export class OpenId4VcHolderApi { * @param options.code The authorization code obtained via the authorization request URI */ public async requestToken(options: OpenId4VciRequestTokenOptions): Promise { - const { access_token: accessToken, c_nonce: cNonce } = await this.openId4VciHolderService.requestAccessToken( - this.agentContext, - options - ) - return { accessToken, cNonce } + const { + access_token: accessToken, + c_nonce: cNonce, + dPoPJwk, + } = await this.openId4VciHolderService.requestAccessToken(this.agentContext, options) + return { accessToken, cNonce, dPoPJwk } } /** @@ -160,13 +161,14 @@ export class OpenId4VcHolderApi { * @param options.tokenResponse Obtained through @see requestAccessToken */ public async requestCredentials(options: OpenId4VciRequestCredentialOptions) { - const { resolvedCredentialOffer, cNonce, accessToken, ...credentialRequestOptions } = options + const { resolvedCredentialOffer, cNonce, accessToken, dPoPJwk, ...credentialRequestOptions } = options return this.openId4VciHolderService.acceptCredentialOffer(this.agentContext, { resolvedCredentialOffer, acceptCredentialOfferOptions: credentialRequestOptions, accessToken, cNonce, + dPoPJwk, }) } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index d77bf9c029..40c4504a58 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -17,19 +17,13 @@ import type { OpenId4VciCredentialSupported, OpenId4VciIssuerMetadata, } from '../shared' -import type { AgentContext, JwaSignatureAlgorithm, Key, JwkJson } from '@credo-ts/core' -import type { - AccessTokenResponse, - CredentialResponse, - Jwt, - OpenIDResponse, - AuthorizationDetails, - AuthorizationDetailsJwtVcJson, - CredentialIssuerMetadataV1_0_11, - CredentialIssuerMetadataV1_0_13, -} from '@sphereon/oid4vci-common' import { + AgentContext, + JwaSignatureAlgorithm, + Key, + JwkJson, + Jwk, SdJwtVcApi, getJwkFromJson, DidsApi, @@ -60,7 +54,23 @@ import { OpenID4VCIClientV1_0_11, OpenID4VCIClientV1_0_13, } from '@sphereon/oid4vci-client' -import { CodeChallengeMethod, OpenId4VCIVersion, PARMode, post } from '@sphereon/oid4vci-common' +import { + AccessTokenResponse, + CredentialResponse, + Jwt, + OpenIDResponse, + AuthorizationDetails, + AuthorizationDetailsJwtVcJson, + CredentialIssuerMetadataV1_0_11, + CredentialIssuerMetadataV1_0_13, + CodeChallengeMethod, + OpenId4VCIVersion, + PARMode, + post, + SigningAlgo, + EndpointMetadataResult, + CreateDPoPClientOptions, +} from '@sphereon/oid4vci-common' import { OpenId4VciCredentialFormatProfile } from '../shared' import { @@ -69,7 +79,7 @@ import { credentialsSupportedV11ToV13, } from '../shared/issuerMetadataUtils' import { OpenId4VciCredentialSupportedWithId } from '../shared/models/index' -import { getSupportedJwaSignatureAlgorithms, isCredentialOfferV1Draft13 } from '../shared/utils' +import { getCreateJwtCallback, getSupportedJwaSignatureAlgorithms, isCredentialOfferV1Draft13 } from '../shared/utils' import { openId4VciSupportedCredentialFormats, OpenId4VciNotificationMetadata } from './OpenId4VciHolderServiceOptions' @@ -263,6 +273,44 @@ export class OpenId4VciHolderService { } } + private async getCreateDPoPOptions( + agentContext: AgentContext, + metadata: Pick & { + credentialIssuerMetadata: OpenId4VciIssuerMetadata + } + ) { + const dPoPSigningAlgValuesSupported = + metadata.authorizationServerMetadata?.dpop_signing_alg_values_supported ?? + metadata.credentialIssuerMetadata.dpop_signing_alg_values_supported + + if (!dPoPSigningAlgValuesSupported) return undefined + + const alg = dPoPSigningAlgValuesSupported + .flatMap((alg) => alg) + .find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) + + const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined + + if (!JwkClass) { + throw new CredoError( + `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dPoPSigningAlgValuesSupported.join( + ', ' + )}'` + ) + } + + const key = await agentContext.wallet.createKey({ keyType: JwkClass.keyType }) + const jwk = getJwkFromKey(key) + + const createDPoPOptions: CreateDPoPClientOptions = { + jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, + dPoPSigningAlgValuesSupported, + jwtPayloadProps: {}, + createJwtCallback: getCreateJwtCallback(agentContext), + } + return createDPoPOptions + } + public async requestAccessToken(agentContext: AgentContext, options: OpenId4VciTokenRequestOptions) { const { resolvedCredentialOffer, txCode, resolvedAuthorizationRequest, code } = options const { metadata, credentialOfferRequestWithBaseUrl } = resolvedCredentialOffer @@ -271,6 +319,11 @@ export class OpenId4VciHolderService { let accessTokenResponse: OpenIDResponse const accessTokenClient = new AccessTokenClient() + + const createDPoPOptions = await this.getCreateDPoPOptions(agentContext, metadata) + const dPoPJwk = createDPoPOptions ? getJwkFromJson(createDPoPOptions.jwtIssuer.jwk) : undefined + + resolvedCredentialOffer.metadata.credentialIssuerMetadata if (resolvedAuthorizationRequest) { const { codeVerifier, redirectUri } = resolvedAuthorizationRequest accessTokenResponse = await accessTokenClient.acquireAccessToken({ @@ -280,12 +333,14 @@ export class OpenId4VciHolderService { code, codeVerifier, redirectUri, + createDPoPOptions, }) } else { accessTokenResponse = await accessTokenClient.acquireAccessToken({ metadata: metadata, credentialOffer: { credential_offer: credentialOfferRequestWithBaseUrl.credential_offer }, pin: txCode, + createDPoPOptions, }) } @@ -297,7 +352,7 @@ export class OpenId4VciHolderService { this.logger.debug('Requested OpenId4VCI Access Token.') - return accessTokenResponse.successBody + return { ...accessTokenResponse.successBody, dPoPJwk } } public async acceptCredentialOffer( @@ -308,6 +363,7 @@ export class OpenId4VciHolderService { resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode accessToken?: string cNonce?: string + dPoPJwk?: Jwk } ) { const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options @@ -320,7 +376,9 @@ export class OpenId4VciHolderService { return [] } - this.logger.info(`Accepting the following credential offers '${credentialsToRequest}'`) + this.logger.info( + `Accepting the following credential offers '${credentialsToRequest ? credentialsToRequest.join(', ') : 'all'}` + ) const supportedJwaSignatureAlgorithms = getSupportedJwaSignatureAlgorithms(agentContext) @@ -347,7 +405,7 @@ export class OpenId4VciHolderService { } as OpenId4VciTokenRequestOptions const tokenResponse = options.accessToken - ? { access_token: options.accessToken, c_nonce: options.cNonce } + ? { access_token: options.accessToken, c_nonce: options.cNonce, dPoPJwk: options.dPoPJwk } : await this.requestAccessToken(agentContext, tokenRequestOptions) const receivedCredentials: Array = [] @@ -412,10 +470,24 @@ export class OpenId4VciHolderService { .withToken(tokenResponse.access_token) const credentialRequestClient = credentialRequestBuilder.build() + + let createDPoPOptions: CreateDPoPClientOptions | undefined + if (options.dPoPJwk) { + const jwk = options.dPoPJwk + const alg = jwk.supportedSignatureAlgorithms[0] + + createDPoPOptions = { + jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, + jwtPayloadProps: { accessToken: options.accessToken }, + createJwtCallback: getCreateJwtCallback(agentContext), + } + } + const credentialResponse = await credentialRequestClient.acquireCredentialsUsingProof({ proofInput: proofOfPossession, credentialTypes: getTypesFromCredentialSupported(offeredCredentialConfiguration), format: offeredCredentialConfiguration.format, + createDPoPOptions, }) newCNonce = credentialResponse.successBody?.c_nonce diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index 4ef274ef15..109fa48d36 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -4,7 +4,7 @@ import type { OpenId4VciIssuerMetadata, OpenId4VciCredentialOfferPayload, } from '../shared' -import type { JwaSignatureAlgorithm, KeyType } from '@credo-ts/core' +import type { JwaSignatureAlgorithm, Jwk, KeyType } from '@credo-ts/core' import type { VerifiableCredential } from '@credo-ts/core/src/modules/dif-presentation-exchange/models/index' import type { AccessTokenResponse, @@ -42,7 +42,7 @@ export type OpenId4VciNotificationEvent = 'credential_accepted' | 'credential_fa export type OpenId4VciTokenResponse = Pick -export type OpenId4VciRequestTokenResponse = { accessToken: string; cNonce?: string } +export type OpenId4VciRequestTokenResponse = { accessToken: string; cNonce?: string; dPoPJwk?: Jwk } export interface OpenId4VciCredentialResponse { credential: VerifiableCredential @@ -112,6 +112,7 @@ export interface OpenId4VciCredentialRequestOptions extends Omit & + options: Pick & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) ) { const issuer = await this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, options.issuerId) @@ -76,6 +76,7 @@ export class OpenId4VcIssuerApi { issuer.credentialConfigurationsSupported = undefined } issuer.display = options.display + issuer.dPoPSigningAlgValuesSupported = options.dPoPSigningAlgValuesSupported return this.openId4VcIssuerService.updateIssuer(this.agentContext, issuer) } diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index ae26c6853d..7f76bda319 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -303,6 +303,7 @@ export class OpenId4VcIssuerService { const openId4VcIssuerBase = { issuerId: options.issuerId ?? utils.uuid(), display: options.display, + dPoPSigningAlgValuesSupported: options.dPoPSigningAlgValuesSupported, accessTokenPublicKeyFingerprint: accessTokenSignerKey.fingerprint, } as const @@ -344,6 +345,7 @@ export class OpenId4VcIssuerService { issuerRecord.credentialConfigurationsSupported ?? credentialsSupportedV11ToV13(agentContext, issuerRecord.credentialsSupported), issuerDisplay: issuerRecord.display, + dPoPSigningAlgValuesSupported: issuerRecord.dPoPSigningAlgValuesSupported, } satisfies OpenId4VcIssuerMetadata return issuerMetadata diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index 8914af5b5d..9a70f24eed 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -13,7 +13,13 @@ import type { OpenId4VciIssuerMetadataDisplay, OpenId4VciTxCode, } from '../shared' -import type { AgentContext, ClaimFormat, W3cCredential, SdJwtVcSignOptions } from '@credo-ts/core' +import type { + AgentContext, + ClaimFormat, + W3cCredential, + SdJwtVcSignOptions, + JwaSignatureAlgorithm, +} from '@credo-ts/core' export interface OpenId4VciPreAuthorizedCodeFlowConfig { preAuthorizedCode?: string @@ -39,6 +45,7 @@ export type OpenId4VcIssuerMetadata = { issuerDisplay?: OpenId4VciIssuerMetadataDisplay[] credentialsSupported: OpenId4VciCredentialSupportedWithId[] credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported + dPoPSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] } export interface OpenId4VciCreateCredentialOfferOptions { @@ -152,4 +159,5 @@ export type OpenId4VciCreateIssuerOptions = { issuerId?: string display?: OpenId4VciIssuerMetadataDisplay[] + dPoPSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] } & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts index b653265cde..a379baf2dc 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts @@ -3,7 +3,7 @@ import type { OpenId4VciCredentialConfigurationsSupported, OpenId4VciIssuerMetadataDisplay, } from '../../shared' -import type { RecordTags, TagsBase } from '@credo-ts/core' +import type { JwaSignatureAlgorithm, RecordTags, TagsBase } from '@credo-ts/core' import { BaseRecord, utils } from '@credo-ts/core' @@ -38,6 +38,12 @@ export type OpenId4VcIssuerRecordProps = { */ accessTokenPublicKeyFingerprint: string + /** + * The DPoP signing algorithms supported by this issuer. + * If not provided, dPoP is considered unsupported. + */ + dPoPSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] + display?: OpenId4VciIssuerMetadataDisplay[] } & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) @@ -56,6 +62,7 @@ export class OpenId4VcIssuerRecord extends BaseRecord { return async (jwtIssuer, jwt) => { const jwsService = agentContext.dependencyManager.resolve(JwsService) @@ -80,9 +89,10 @@ export function getCreateJwtCallback(agentContext: AgentContext): CreateJwtCallb return jws } else if (jwtIssuer.method === 'jwk') { - const key = getJwkFromJson(jwtIssuer.jwk).key + const jwk = getJwkFromJson(jwtIssuer.jwk) + const key = jwk.key const jws = await jwsService.createJwsCompact(agentContext, { - protectedHeaderOptions: jwt.header as JwsProtectedHeaderOptions, + protectedHeaderOptions: { ...jwt.header, jwk, alg: jwtIssuer.alg }, payload: JwtPayload.fromJson(jwt.payload), key, }) @@ -92,7 +102,7 @@ export function getCreateJwtCallback(agentContext: AgentContext): CreateJwtCallb const key = X509Service.getLeafCertificate(agentContext, { certificateChain: jwtIssuer.x5c }).publicKey const jws = await jwsService.createJwsCompact(agentContext, { - protectedHeaderOptions: jwt.header as JwsProtectedHeaderOptions, + protectedHeaderOptions: { ...jwt.header, alg: jwtIssuer.alg }, payload: JwtPayload.fromJson(jwt.payload), key, }) @@ -124,6 +134,12 @@ export async function openIdTokenIssuerToJwtIssuer( certificateChain: openId4VcTokenIssuer.x5c, }) + const jwk = getJwkFromKey(leafCertificate.publicKey) + const alg = jwk.supportedSignatureAlgorithms[0] + if (!alg) { + throw new CredoError(`No supported signature algorithms found key type: '${jwk.keyType}'`) + } + if (!issuer.startsWith('https://')) { throw new CredoError('The X509 certificate issuer must be a HTTPS URI.') } @@ -131,6 +147,7 @@ export async function openIdTokenIssuerToJwtIssuer( if (leafCertificate.sanUriNames?.includes(issuer)) { return { ...openId4VcTokenIssuer, + alg: alg as unknown as SigningAlgo, clientIdScheme: 'x509_san_uri', } } else { @@ -142,12 +159,23 @@ export async function openIdTokenIssuerToJwtIssuer( return { ...openId4VcTokenIssuer, + alg: alg as unknown as SigningAlgo, clientIdScheme: 'x509_san_dns', } } + } else if (openId4VcTokenIssuer.method === 'jwk') { + const alg = openId4VcTokenIssuer.jwk.supportedSignatureAlgorithms[0] + if (!alg) { + throw new CredoError(`No supported signature algorithms for key type: '${openId4VcTokenIssuer.jwk.keyType}'`) + } + return { + ...openId4VcTokenIssuer, + jwk: openId4VcTokenIssuer.jwk.toJson(), + alg: alg as unknown as SigningAlgo, + } } - return openId4VcTokenIssuer + throw new CredoError(`Unsupported jwt issuer method '${(openId4VcTokenIssuer as OpenId4VcJwtIssuer).method}'`) } export function getProofTypeFromKey(agentContext: AgentContext, key: Key) { diff --git a/packages/openid4vc/tests/openid4vc.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index 584c4b5947..cad61b49cf 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -16,6 +16,8 @@ import { W3cCredentialSubject, w3cDate, W3cIssuer, + Jwt, + Jwk, } from '@credo-ts/core' import express, { type Express } from 'express' @@ -190,11 +192,14 @@ describe('OpenId4Vc', () => { const issuerTenant2 = await issuer.agent.modules.tenants.getTenantAgent({ tenantId: issuer2.tenantId }) const openIdIssuerTenant1 = await issuerTenant1.modules.openId4VcIssuer.createIssuer({ + dPoPSigningAlgValuesSupported: ['EdDSA'], credentialConfigurationsSupported: { universityDegree: universityDegreeCredentialConfigurationSupported, }, }) const issuer1Record = await issuerTenant1.modules.openId4VcIssuer.getIssuerByIssuerId(openIdIssuerTenant1.issuerId) + expect(issuer1Record.dPoPSigningAlgValuesSupported).toEqual(['EdDSA']) + expect(issuer1Record.credentialsSupported).toEqual([ { id: 'universityDegree', @@ -218,6 +223,7 @@ describe('OpenId4Vc', () => { }, }) const openIdIssuerTenant2 = await issuerTenant2.modules.openId4VcIssuer.createIssuer({ + dPoPSigningAlgValuesSupported: ['EdDSA'], credentialsSupported: [universityDegreeCredentialSdJwt2], }) @@ -257,6 +263,9 @@ describe('OpenId4Vc', () => { credentialOffer1 ) + expect(resolvedCredentialOffer1.metadata.credentialIssuerMetadata?.dpop_signing_alg_values_supported).toEqual([ + 'EdDSA', + ]) expect(resolvedCredentialOffer1.offeredCredentials).toEqual([ { id: 'universityDegree', @@ -278,10 +287,20 @@ describe('OpenId4Vc', () => { ) // Bind to JWK - const credentialsTenant1 = await holderTenant1.modules.openId4VcHolder.acceptCredentialOfferUsingPreAuthorizedCode( - resolvedCredentialOffer1, - { credentialBindingResolver, userPin: issuanceSession1.userPin } - ) + const tokenResponseTenant1 = await holderTenant1.modules.openId4VcHolder.requestToken({ + resolvedCredentialOffer: resolvedCredentialOffer1, + }) + + expect(tokenResponseTenant1.accessToken).toBeDefined() + expect(tokenResponseTenant1.dPoPJwk).toBeInstanceOf(Jwk) + const { payload } = Jwt.fromSerializedJwt(tokenResponseTenant1.accessToken) + expect(payload.additionalClaims.token_type).toEqual('DPoP') + + const credentialsTenant1 = await holderTenant1.modules.openId4VcHolder.requestCredentials({ + resolvedCredentialOffer: resolvedCredentialOffer1, + ...tokenResponseTenant1, + credentialBindingResolver, + }) // Wait for all events await waitForCredentialIssuanceSessionRecordSubject(issuer.replaySubject, { @@ -306,7 +325,7 @@ describe('OpenId4Vc', () => { }) expect(credentialsTenant1).toHaveLength(1) - const compactSdJwtVcTenant1 = (credentialsTenant1[0] as SdJwtVc).compact + const compactSdJwtVcTenant1 = (credentialsTenant1[0].credential as SdJwtVc).compact const sdJwtVcTenant1 = holderTenant1.sdJwtVc.fromCompact(compactSdJwtVcTenant1) expect(sdJwtVcTenant1.payload.vct).toEqual('UniversityDegreeCredential') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37fb155cac..bcbd49e90b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -681,17 +681,17 @@ importers: specifier: workspace:* version: link:../core '@sphereon/did-auth-siop': - specifier: 0.15.1-next.4 - version: 0.15.1-next.4 + specifier: link:../../../../Documents/OID4VCI/packages/siop-oid4vp + version: link:../../../OID4VCI/packages/siop-oid4vp '@sphereon/oid4vci-client': - specifier: 0.15.1-next.4 - version: 0.15.1-next.4 + specifier: link:../../../../Documents/OID4VCI/packages/client + version: link:../../../OID4VCI/packages/client '@sphereon/oid4vci-common': - specifier: 0.15.1-next.4 - version: 0.15.1-next.4 + specifier: link:../../../../Documents/OID4VCI/packages/common + version: link:../../../OID4VCI/packages/common '@sphereon/oid4vci-issuer': - specifier: 0.15.1-next.4 - version: 0.15.1-next.4 + specifier: link:../../../../Documents/OID4VCI/packages/issuer + version: link:../../../OID4VCI/packages/issuer '@sphereon/ssi-types': specifier: 0.26.1-next.132 version: 0.26.1-next.132 @@ -2370,30 +2370,6 @@ packages: resolution: {integrity: sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw==} engines: {node: '>= 8'} - '@sphereon/did-auth-siop@0.15.1-next.4': - resolution: {integrity: sha512-Rg5O6A0P6uqf/a5o5lrCxt2WGlpzemyHCrkiOjvYAWGxGCxGctpKRVCNgwxDdFpIFCuXICNi0l/lk4iuZ1ofmQ==} - engines: {node: '>=18'} - - '@sphereon/did-uni-client@0.6.3': - resolution: {integrity: sha512-g7LD7ofbE36slHN7Bhr5dwUrj6t0BuZeXBYJMaVY/pOeL1vJxW1cZHbZqu0NSfOmzyBg4nsYVlgTjyi/Aua2ew==} - - '@sphereon/oid4vci-client@0.15.1-next.4': - resolution: {integrity: sha512-HfSD5aa1dTMlx3D5QpEkYwHxbbSnvb8eEmv4xno7b9/WahNp4xmQeP9jxUz9K0VjIYNvAtCROEEbrgEo9F6iQA==} - engines: {node: '>=18'} - - '@sphereon/oid4vci-common@0.15.1-next.4': - resolution: {integrity: sha512-X5NKqfc59+D+sql3QU91WJrbWHYDL8nfZCUT0X8UCTIcc5ioUlw1rWxv8CG2HUIuNXA2Uf/Nvm/9UZkinELxQQ==} - engines: {node: '>=18'} - - '@sphereon/oid4vci-issuer@0.15.1-next.4': - resolution: {integrity: sha512-2C4KlhQY55PcmBHI4CVVHMdP3QntmEUgxoheHw4kRLc5ntsmeLrUkO8ZcwHZybXb981xjboN37v8k01j2jSLcg==} - engines: {node: '>=18'} - peerDependencies: - awesome-qr: ^2.1.5-rc.0 - peerDependenciesMeta: - awesome-qr: - optional: true - '@sphereon/pex-models@2.2.4': resolution: {integrity: sha512-pGlp+wplneE1+Lk3U48/2htYKTbONMeG5/x7vhO6AnPUOsnOXeJdftPrBYWVSzz/JH5GJptAc6+pAyYE1zMu4Q==} @@ -2410,12 +2386,6 @@ packages: '@sphereon/ssi-types@0.26.1-next.132': resolution: {integrity: sha512-coqhozmVf2phy40l1htj/zbLJLJhyD69ZukBVR4gKn35t8q2B2EZhJbCIPWKk2HWT6+gV+JUBQ9n4v3cSHNCJg==} - '@sphereon/ssi-types@0.9.0': - resolution: {integrity: sha512-umCr/syNcmvMMbQ+i/r/mwjI1Qw2aFPp9AwBTvTo1ailAVaaJjJGPkkVz1K9/2NZATNdDiQ3A8yGzdVJoKh9pA==} - - '@sphereon/wellknown-dids-client@0.1.3': - resolution: {integrity: sha512-TAT24L3RoXD8ocrkTcsz7HuJmgjNjdoV6IXP1p3DdaI/GqkynytXE3J1+F7vUFMRYwY5nW2RaXSgDQhrFJemaA==} - '@stablelib/aead@1.0.1': resolution: {integrity: sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg==} @@ -4940,10 +4910,6 @@ packages: jwt-decode@3.1.2: resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} - jwt-decode@4.0.0: - resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} - engines: {node: '>=18'} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -4983,13 +4949,6 @@ packages: resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==} engines: {node: '>=14.16'} - language-subtag-registry@0.3.23: - resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} - leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -5394,10 +5353,6 @@ packages: resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==} engines: {node: '>= 6.0.0'} - multiformats@12.1.3: - resolution: {integrity: sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==} - engines: {node: '>=16.0.0', npm: '>=7.0.0'} - multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} @@ -5873,10 +5828,6 @@ packages: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} - qs@6.12.3: - resolution: {integrity: sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==} - engines: {node: '>=0.6'} - query-string@7.1.3: resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} engines: {node: '>=6'} @@ -6213,10 +6164,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true - shallow-clone@3.0.1: resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} engines: {node: '>=8'} @@ -9789,63 +9736,6 @@ snapshots: '@sovpro/delimited-stream@1.1.0': {} - '@sphereon/did-auth-siop@0.15.1-next.4': - dependencies: - '@astronautlabs/jsonpath': 1.1.2 - '@sphereon/did-uni-client': 0.6.3 - '@sphereon/pex': 3.3.3 - '@sphereon/pex-models': 2.2.4 - '@sphereon/ssi-types': 0.22.0 - '@sphereon/wellknown-dids-client': 0.1.3 - cross-fetch: 4.0.0 - events: 3.3.0 - jwt-decode: 4.0.0 - language-tags: 1.0.9 - multiformats: 12.1.3 - qs: 6.12.3 - sha.js: 2.4.11 - uint8arrays: 3.1.1 - uuid: 9.0.1 - transitivePeerDependencies: - - encoding - - '@sphereon/did-uni-client@0.6.3': - dependencies: - cross-fetch: 3.1.8 - did-resolver: 4.1.0 - transitivePeerDependencies: - - encoding - - '@sphereon/oid4vci-client@0.15.1-next.4': - dependencies: - '@sphereon/oid4vci-common': 0.15.1-next.4 - '@sphereon/ssi-types': 0.26.1-next.132 - cross-fetch: 3.1.8 - debug: 4.3.5 - transitivePeerDependencies: - - encoding - - supports-color - - '@sphereon/oid4vci-common@0.15.1-next.4': - dependencies: - '@sphereon/ssi-types': 0.26.1-next.132 - cross-fetch: 3.1.8 - jwt-decode: 4.0.0 - sha.js: 2.4.11 - uint8arrays: 3.1.1 - transitivePeerDependencies: - - encoding - - supports-color - - '@sphereon/oid4vci-issuer@0.15.1-next.4': - dependencies: - '@sphereon/oid4vci-common': 0.15.1-next.4 - '@sphereon/ssi-types': 0.26.1-next.132 - uuid: 9.0.1 - transitivePeerDependencies: - - encoding - - supports-color - '@sphereon/pex-models@2.2.4': {} '@sphereon/pex@3.3.3': @@ -9882,18 +9772,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/ssi-types@0.9.0': - dependencies: - jwt-decode: 3.1.2 - - '@sphereon/wellknown-dids-client@0.1.3': - dependencies: - '@sphereon/ssi-types': 0.9.0 - cross-fetch: 3.1.8 - jwt-decode: 3.1.2 - transitivePeerDependencies: - - encoding - '@stablelib/aead@1.0.1': {} '@stablelib/binary@1.0.1': @@ -11135,6 +11013,7 @@ snapshots: node-fetch: 2.7.0 transitivePeerDependencies: - encoding + optional: true cross-fetch@4.0.0: dependencies: @@ -13161,8 +13040,6 @@ snapshots: jwt-decode@3.1.2: {} - jwt-decode@4.0.0: {} - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -13193,12 +13070,6 @@ snapshots: ky@0.33.3: {} - language-subtag-registry@0.3.23: {} - - language-tags@1.0.9: - dependencies: - language-subtag-registry: 0.3.23 - leven@3.1.0: {} levn@0.3.0: @@ -13761,8 +13632,6 @@ snapshots: type-is: 1.6.18 xtend: 4.0.2 - multiformats@12.1.3: {} - multiformats@9.9.0: {} mute-stream@0.0.8: {} @@ -14279,10 +14148,6 @@ snapshots: dependencies: side-channel: 1.0.6 - qs@6.12.3: - dependencies: - side-channel: 1.0.6 - query-string@7.1.3: dependencies: decode-uri-component: 0.2.2 @@ -14700,11 +14565,6 @@ snapshots: setprototypeof@1.2.0: {} - sha.js@2.4.11: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - shallow-clone@3.0.1: dependencies: kind-of: 6.0.3 From cb3e749805968d8efc77a93ec95af82022e6974f Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Fri, 26 Jul 2024 08:14:37 +0200 Subject: [PATCH 03/12] fix: incorporate parts of the feedback --- .../openid4vc-holder/OpenId4VciHolderService.ts | 5 +---- .../router/accessTokenEndpoint.ts | 16 ++++++++++++++-- .../router/verifyResourceRequest.ts | 6 ++++-- packages/openid4vc/tests/openid4vc.e2e.test.ts | 4 ++-- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index 40c4504a58..d5f7b75671 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -285,9 +285,7 @@ export class OpenId4VciHolderService { if (!dPoPSigningAlgValuesSupported) return undefined - const alg = dPoPSigningAlgValuesSupported - .flatMap((alg) => alg) - .find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) + const alg = dPoPSigningAlgValuesSupported.find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined @@ -323,7 +321,6 @@ export class OpenId4VciHolderService { const createDPoPOptions = await this.getCreateDPoPOptions(agentContext, metadata) const dPoPJwk = createDPoPOptions ? getJwkFromJson(createDPoPOptions.jwtIssuer.jwk) : undefined - resolvedCredentialOffer.metadata.credentialIssuerMetadata if (resolvedAuthorizationRequest) { const { codeVerifier, redirectUri } = resolvedAuthorizationRequest accessTokenResponse = await accessTokenClient.acquireAccessToken({ diff --git a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts index dcd04e8e5b..4642ee3175 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts @@ -3,7 +3,16 @@ import type { AgentContext } from '@credo-ts/core' import type { AccessTokenRequest, JWK, JWTSignerCallback, SigningAlgo } from '@sphereon/oid4vci-common' import type { NextFunction, Response, Router } from 'express' -import { getJwkFromKey, CredoError, JwsService, JwtPayload, getJwkClassFromKeyType, Key } from '@credo-ts/core' +import { + getJwkFromKey, + CredoError, + JwsService, + JwtPayload, + getJwkClassFromKeyType, + Key, + joinUriParts, + Jwt, +} from '@credo-ts/core' import { GrantTypes, IssueStatus, @@ -17,6 +26,7 @@ import { assertValidAccessTokenRequest, createAccessTokenResponse } from '@spher import { getRequestContext, sendErrorResponse } from '../../shared/router' import { getVerifyJwtCallback } from '../../shared/utils' +import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' import { OpenId4VcCNonceStateManager } from '../repository/OpenId4VcCNonceStateManager' import { OpenId4VcCredentialOfferSessionStateManager } from '../repository/OpenId4VcCredentialOfferSessionStateManager' @@ -125,7 +135,9 @@ export function handleTokenRequest(config: OpenId4VciAccessTokenEndpointConfig) let dPoPJwk: JWK | undefined if (request.headers.dpop) { try { - const fullUrl = request.protocol + '://' + request.get('host') + request.originalUrl + const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) + const fullUrl = joinUriParts(issuerConfig.baseUrl, [requestContext.issuer.issuerId, request.url]) + dPoPJwk = await verifyDPoP( { method: request.method, headers: request.headers, fullUrl }, { diff --git a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts b/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts index 6b9d49f5c7..a3231ab77e 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts @@ -3,10 +3,11 @@ import type { OpenId4VcIssuerRecord } from '../repository' import type { AgentContext } from '@credo-ts/core' import type { SigningAlgo } from '@sphereon/oid4vci-common' -import { CredoError, JwsService, Jwt } from '@credo-ts/core' +import { CredoError, joinUriParts, JwsService, Jwt } from '@credo-ts/core' import { verifyResourceDPoP } from '@sphereon/oid4vci-common' import { getVerifyJwtCallback } from '../../shared/utils' +import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' import { OpenId4VcIssuerService } from '../OpenId4VcIssuerService' export async function verifyResourceRequest( @@ -36,7 +37,8 @@ export async function verifyResourceRequest( }, }) - const fullUrl = request.protocol + '://' + request.get('host') + request.originalUrl + const issuerConfig = agentContext.dependencyManager.resolve(OpenId4VcIssuerModuleConfig) + const fullUrl = joinUriParts(issuerConfig.baseUrl, [issuer.issuerId, request.url]) await verifyResourceDPoP( { method: request.method, headers: request.headers, fullUrl }, { diff --git a/packages/openid4vc/tests/openid4vc.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index cad61b49cf..2d72ad4790 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -192,7 +192,7 @@ describe('OpenId4Vc', () => { const issuerTenant2 = await issuer.agent.modules.tenants.getTenantAgent({ tenantId: issuer2.tenantId }) const openIdIssuerTenant1 = await issuerTenant1.modules.openId4VcIssuer.createIssuer({ - dPoPSigningAlgValuesSupported: ['EdDSA'], + dPoPSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], credentialConfigurationsSupported: { universityDegree: universityDegreeCredentialConfigurationSupported, }, @@ -223,7 +223,7 @@ describe('OpenId4Vc', () => { }, }) const openIdIssuerTenant2 = await issuerTenant2.modules.openId4VcIssuer.createIssuer({ - dPoPSigningAlgValuesSupported: ['EdDSA'], + dPoPSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], credentialsSupported: [universityDegreeCredentialSdJwt2], }) From c49466bc550b2660038168fcbcbec41eaf5abf02 Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Mon, 29 Jul 2024 15:38:14 +0200 Subject: [PATCH 04/12] fix: feedback --- packages/openid4vc/package.json | 3 +- .../openid4vc-holder/OpenId4VcHolderApi.ts | 8 ++-- .../OpenId4VciHolderService.ts | 47 ++++++++++--------- .../OpenId4VciHolderServiceOptions.ts | 4 +- .../openid4vc-issuer/OpenId4VcIssuerApi.ts | 18 +++---- .../OpenId4VcIssuerService.ts | 4 +- .../OpenId4VcIssuerServiceOptions.ts | 4 +- .../repository/OpenId4VcIssuerRecord.ts | 6 +-- .../router/accessTokenEndpoint.ts | 16 +++---- .../router/metadataEndpoint.ts | 2 +- .../router/verifyResourceRequest.ts | 6 +-- .../__tests__/openid4vc-verifier.test.ts | 2 +- packages/openid4vc/src/shared/utils.ts | 14 ++---- .../openid4vc/tests/openid4vc.e2e.test.ts | 8 ++-- pnpm-lock.yaml | 7 ++- 15 files changed, 76 insertions(+), 73 deletions(-) diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index 8cff036238..ec93f7aa1e 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -28,8 +28,9 @@ "dependencies": { "@credo-ts/core": "workspace:*", "@sphereon/did-auth-siop": "link:../../../../Documents/OID4VCI/packages/siop-oid4vp", + "@sphereon/common": "link:../../../../Documents/OID4VCI/packages/common", "@sphereon/oid4vci-client": "link:../../../../Documents/OID4VCI/packages/client", - "@sphereon/oid4vci-common": "link:../../../../Documents/OID4VCI/packages/common", + "@sphereon/oid4vci-common": "link:../../../../Documents/OID4VCI/packages/oid4vci-common", "@sphereon/oid4vci-issuer": "link:../../../../Documents/OID4VCI/packages/issuer", "@sphereon/ssi-types": "0.26.1-next.132", "class-transformer": "^0.5.1", diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts index 47d5b3f20e..ef0d4474a6 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VcHolderApi.ts @@ -149,9 +149,9 @@ export class OpenId4VcHolderApi { const { access_token: accessToken, c_nonce: cNonce, - dPoPJwk, + dpop, } = await this.openId4VciHolderService.requestAccessToken(this.agentContext, options) - return { accessToken, cNonce, dPoPJwk } + return { accessToken, cNonce, dpop } } /** @@ -161,14 +161,14 @@ export class OpenId4VcHolderApi { * @param options.tokenResponse Obtained through @see requestAccessToken */ public async requestCredentials(options: OpenId4VciRequestCredentialOptions) { - const { resolvedCredentialOffer, cNonce, accessToken, dPoPJwk, ...credentialRequestOptions } = options + const { resolvedCredentialOffer, cNonce, accessToken, dpop, ...credentialRequestOptions } = options return this.openId4VciHolderService.acceptCredentialOffer(this.agentContext, { resolvedCredentialOffer, acceptCredentialOfferOptions: credentialRequestOptions, accessToken, cNonce, - dPoPJwk, + dpop, }) } diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index d5f7b75671..4e99f3a9be 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -46,6 +46,7 @@ import { injectable, parseDid, } from '@credo-ts/core' +import { SigningAlgo, CreateDPoPClientOpts } from '@sphereon/common' import { AccessTokenClient, CredentialRequestClientBuilder, @@ -67,9 +68,7 @@ import { OpenId4VCIVersion, PARMode, post, - SigningAlgo, EndpointMetadataResult, - CreateDPoPClientOptions, } from '@sphereon/oid4vci-common' import { OpenId4VciCredentialFormatProfile } from '../shared' @@ -279,19 +278,19 @@ export class OpenId4VciHolderService { credentialIssuerMetadata: OpenId4VciIssuerMetadata } ) { - const dPoPSigningAlgValuesSupported = + const dpopSigningAlgValuesSupported = metadata.authorizationServerMetadata?.dpop_signing_alg_values_supported ?? metadata.credentialIssuerMetadata.dpop_signing_alg_values_supported - if (!dPoPSigningAlgValuesSupported) return undefined + if (!dpopSigningAlgValuesSupported) return undefined - const alg = dPoPSigningAlgValuesSupported.find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) + const alg = dpopSigningAlgValuesSupported.find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined if (!JwkClass) { throw new CredoError( - `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dPoPSigningAlgValuesSupported.join( + `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join( ', ' )}'` ) @@ -300,13 +299,13 @@ export class OpenId4VciHolderService { const key = await agentContext.wallet.createKey({ keyType: JwkClass.keyType }) const jwk = getJwkFromKey(key) - const createDPoPOptions: CreateDPoPClientOptions = { + const createDPoPOpts: CreateDPoPClientOpts = { jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, - dPoPSigningAlgValuesSupported, + dpopSigningAlgValuesSupported, jwtPayloadProps: {}, createJwtCallback: getCreateJwtCallback(agentContext), } - return createDPoPOptions + return createDPoPOpts } public async requestAccessToken(agentContext: AgentContext, options: OpenId4VciTokenRequestOptions) { @@ -318,8 +317,8 @@ export class OpenId4VciHolderService { const accessTokenClient = new AccessTokenClient() - const createDPoPOptions = await this.getCreateDPoPOptions(agentContext, metadata) - const dPoPJwk = createDPoPOptions ? getJwkFromJson(createDPoPOptions.jwtIssuer.jwk) : undefined + const createDPoPOpts = await this.getCreateDPoPOptions(agentContext, metadata) + const dpopJwk = createDPoPOpts ? getJwkFromJson(createDPoPOpts.jwtIssuer.jwk) : undefined if (resolvedAuthorizationRequest) { const { codeVerifier, redirectUri } = resolvedAuthorizationRequest @@ -330,14 +329,14 @@ export class OpenId4VciHolderService { code, codeVerifier, redirectUri, - createDPoPOptions, + createDPoPOpts, }) } else { accessTokenResponse = await accessTokenClient.acquireAccessToken({ metadata: metadata, credentialOffer: { credential_offer: credentialOfferRequestWithBaseUrl.credential_offer }, pin: txCode, - createDPoPOptions, + createDPoPOpts, }) } @@ -349,7 +348,7 @@ export class OpenId4VciHolderService { this.logger.debug('Requested OpenId4VCI Access Token.') - return { ...accessTokenResponse.successBody, dPoPJwk } + return { ...accessTokenResponse.successBody, dpop: { dpopJwk: dpopJwk } } } public async acceptCredentialOffer( @@ -360,7 +359,9 @@ export class OpenId4VciHolderService { resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode accessToken?: string cNonce?: string - dPoPJwk?: Jwk + dpop?: { + dpopJwk: Jwk + } } ) { const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options @@ -402,7 +403,11 @@ export class OpenId4VciHolderService { } as OpenId4VciTokenRequestOptions const tokenResponse = options.accessToken - ? { access_token: options.accessToken, c_nonce: options.cNonce, dPoPJwk: options.dPoPJwk } + ? { + access_token: options.accessToken, + c_nonce: options.cNonce, + ...(options.dpop && { dpop: { dpopJwk: options.dpop.dpopJwk } }), + } : await this.requestAccessToken(agentContext, tokenRequestOptions) const receivedCredentials: Array = [] @@ -468,12 +473,12 @@ export class OpenId4VciHolderService { const credentialRequestClient = credentialRequestBuilder.build() - let createDPoPOptions: CreateDPoPClientOptions | undefined - if (options.dPoPJwk) { - const jwk = options.dPoPJwk + let createDPoPOpts: CreateDPoPClientOpts | undefined + if (options.dpop) { + const jwk = options.dpop.dpopJwk const alg = jwk.supportedSignatureAlgorithms[0] - createDPoPOptions = { + createDPoPOpts = { jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, jwtPayloadProps: { accessToken: options.accessToken }, createJwtCallback: getCreateJwtCallback(agentContext), @@ -484,7 +489,7 @@ export class OpenId4VciHolderService { proofInput: proofOfPossession, credentialTypes: getTypesFromCredentialSupported(offeredCredentialConfiguration), format: offeredCredentialConfiguration.format, - createDPoPOptions, + createDPoPOpts, }) newCNonce = credentialResponse.successBody?.c_nonce diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index 109fa48d36..f88ef1076d 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -42,7 +42,7 @@ export type OpenId4VciNotificationEvent = 'credential_accepted' | 'credential_fa export type OpenId4VciTokenResponse = Pick -export type OpenId4VciRequestTokenResponse = { accessToken: string; cNonce?: string; dPoPJwk?: Jwk } +export type OpenId4VciRequestTokenResponse = { accessToken: string; cNonce?: string; dpop: { dpopJwk?: Jwk } } export interface OpenId4VciCredentialResponse { credential: VerifiableCredential @@ -112,7 +112,7 @@ export interface OpenId4VciCredentialRequestOptions extends Omit & + options: Pick & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) ) { - const issuer = await this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, options.issuerId) + const { issuerId, credentialConfigurationsSupported, credentialsSupported, ...issuerOptions } = options - if (options.credentialConfigurationsSupported) { - issuer.credentialConfigurationsSupported = options.credentialConfigurationsSupported - issuer.credentialsSupported = credentialsSupportedV13ToV11(options.credentialConfigurationsSupported) + const issuer = await this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, issuerId) + + if (credentialConfigurationsSupported) { + issuer.credentialConfigurationsSupported = credentialConfigurationsSupported + issuer.credentialsSupported = credentialsSupportedV13ToV11(credentialConfigurationsSupported) } else { - issuer.credentialsSupported = options.credentialsSupported + issuer.credentialsSupported = credentialsSupported issuer.credentialConfigurationsSupported = undefined } - issuer.display = options.display - issuer.dPoPSigningAlgValuesSupported = options.dPoPSigningAlgValuesSupported - return this.openId4VcIssuerService.updateIssuer(this.agentContext, issuer) + return this.openId4VcIssuerService.updateIssuer(this.agentContext, Object.assign(issuer, issuerOptions)) } /** diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts index 7f76bda319..235e233a6f 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerService.ts @@ -303,7 +303,7 @@ export class OpenId4VcIssuerService { const openId4VcIssuerBase = { issuerId: options.issuerId ?? utils.uuid(), display: options.display, - dPoPSigningAlgValuesSupported: options.dPoPSigningAlgValuesSupported, + dpopSigningAlgValuesSupported: options.dpopSigningAlgValuesSupported, accessTokenPublicKeyFingerprint: accessTokenSignerKey.fingerprint, } as const @@ -345,7 +345,7 @@ export class OpenId4VcIssuerService { issuerRecord.credentialConfigurationsSupported ?? credentialsSupportedV11ToV13(agentContext, issuerRecord.credentialsSupported), issuerDisplay: issuerRecord.display, - dPoPSigningAlgValuesSupported: issuerRecord.dPoPSigningAlgValuesSupported, + dpopSigningAlgValuesSupported: issuerRecord.dpopSigningAlgValuesSupported, } satisfies OpenId4VcIssuerMetadata return issuerMetadata diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index 9a70f24eed..d87b3863eb 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -45,7 +45,7 @@ export type OpenId4VcIssuerMetadata = { issuerDisplay?: OpenId4VciIssuerMetadataDisplay[] credentialsSupported: OpenId4VciCredentialSupportedWithId[] credentialConfigurationsSupported: OpenId4VciCredentialConfigurationsSupported - dPoPSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] + dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] } export interface OpenId4VciCreateCredentialOfferOptions { @@ -159,5 +159,5 @@ export type OpenId4VciCreateIssuerOptions = { issuerId?: string display?: OpenId4VciIssuerMetadataDisplay[] - dPoPSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] + dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] } & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) diff --git a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts index a379baf2dc..91e390bed3 100644 --- a/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts +++ b/packages/openid4vc/src/openid4vc-issuer/repository/OpenId4VcIssuerRecord.ts @@ -42,7 +42,7 @@ export type OpenId4VcIssuerRecordProps = { * The DPoP signing algorithms supported by this issuer. * If not provided, dPoP is considered unsupported. */ - dPoPSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] + dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] display?: OpenId4VciIssuerMetadataDisplay[] } & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) @@ -62,7 +62,7 @@ export class OpenId4VcIssuerRecord extends BaseRecord { const issuerTenant2 = await issuer.agent.modules.tenants.getTenantAgent({ tenantId: issuer2.tenantId }) const openIdIssuerTenant1 = await issuerTenant1.modules.openId4VcIssuer.createIssuer({ - dPoPSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], + dpopSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], credentialConfigurationsSupported: { universityDegree: universityDegreeCredentialConfigurationSupported, }, }) const issuer1Record = await issuerTenant1.modules.openId4VcIssuer.getIssuerByIssuerId(openIdIssuerTenant1.issuerId) - expect(issuer1Record.dPoPSigningAlgValuesSupported).toEqual(['EdDSA']) + expect(issuer1Record.dpopSigningAlgValuesSupported).toEqual(['EdDSA']) expect(issuer1Record.credentialsSupported).toEqual([ { @@ -223,7 +223,7 @@ describe('OpenId4Vc', () => { }, }) const openIdIssuerTenant2 = await issuerTenant2.modules.openId4VcIssuer.createIssuer({ - dPoPSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], + dpopSigningAlgValuesSupported: [JwaSignatureAlgorithm.EdDSA], credentialsSupported: [universityDegreeCredentialSdJwt2], }) @@ -292,7 +292,7 @@ describe('OpenId4Vc', () => { }) expect(tokenResponseTenant1.accessToken).toBeDefined() - expect(tokenResponseTenant1.dPoPJwk).toBeInstanceOf(Jwk) + expect(tokenResponseTenant1.dpop.dpopJwk).toBeInstanceOf(Jwk) const { payload } = Jwt.fromSerializedJwt(tokenResponseTenant1.accessToken) expect(payload.additionalClaims.token_type).toEqual('DPoP') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bcbd49e90b..d2859ccbb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -680,6 +680,9 @@ importers: '@credo-ts/core': specifier: workspace:* version: link:../core + '@sphereon/common': + specifier: link:../../../../Documents/OID4VCI/packages/common + version: link:../../../OID4VCI/packages/common '@sphereon/did-auth-siop': specifier: link:../../../../Documents/OID4VCI/packages/siop-oid4vp version: link:../../../OID4VCI/packages/siop-oid4vp @@ -687,8 +690,8 @@ importers: specifier: link:../../../../Documents/OID4VCI/packages/client version: link:../../../OID4VCI/packages/client '@sphereon/oid4vci-common': - specifier: link:../../../../Documents/OID4VCI/packages/common - version: link:../../../OID4VCI/packages/common + specifier: link:../../../../Documents/OID4VCI/packages/oid4vci-common + version: link:../../../OID4VCI/packages/oid4vci-common '@sphereon/oid4vci-issuer': specifier: link:../../../../Documents/OID4VCI/packages/issuer version: link:../../../OID4VCI/packages/issuer From c599fa9a9e85d2856e2126ce83903259a49d1b6b Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Mon, 29 Jul 2024 16:15:53 +0200 Subject: [PATCH 05/12] fix: types --- .../src/openid4vc-holder/OpenId4VciHolderService.ts | 8 +++----- .../openid4vc-holder/OpenId4VciHolderServiceOptions.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index 4e99f3a9be..2f2daf8fe2 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -301,7 +301,7 @@ export class OpenId4VciHolderService { const createDPoPOpts: CreateDPoPClientOpts = { jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, - dpopSigningAlgValuesSupported, + dPoPSigningAlgValuesSupported: dpopSigningAlgValuesSupported, jwtPayloadProps: {}, createJwtCallback: getCreateJwtCallback(agentContext), } @@ -348,7 +348,7 @@ export class OpenId4VciHolderService { this.logger.debug('Requested OpenId4VCI Access Token.') - return { ...accessTokenResponse.successBody, dpop: { dpopJwk: dpopJwk } } + return { ...accessTokenResponse.successBody, ...(dpopJwk && { dpop: { dpopJwk: dpopJwk } }) } } public async acceptCredentialOffer( @@ -359,9 +359,7 @@ export class OpenId4VciHolderService { resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode accessToken?: string cNonce?: string - dpop?: { - dpopJwk: Jwk - } + dpop?: { dpopJwk: Jwk } } ) { const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index f88ef1076d..053caa97b1 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -42,7 +42,7 @@ export type OpenId4VciNotificationEvent = 'credential_accepted' | 'credential_fa export type OpenId4VciTokenResponse = Pick -export type OpenId4VciRequestTokenResponse = { accessToken: string; cNonce?: string; dpop: { dpopJwk?: Jwk } } +export type OpenId4VciRequestTokenResponse = { accessToken: string; cNonce?: string; dpop?: { dpopJwk: Jwk } } export interface OpenId4VciCredentialResponse { credential: VerifiableCredential From 7a656c8abda8882f93820af03d03f8829ac46a4f Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 30 Jul 2024 13:56:43 +0200 Subject: [PATCH 06/12] fix: update lib --- packages/openid4vc/package.json | 2 +- .../OpenId4VciHolderService.ts | 20 ++++++++++++------- .../router/accessTokenEndpoint.ts | 4 ++-- .../router/verifyResourceRequest.ts | 4 ++-- .../__tests__/openid4vc-verifier.test.ts | 2 +- packages/openid4vc/src/shared/utils.ts | 9 ++++++--- .../openid4vc/tests/openid4vc.e2e.test.ts | 2 +- pnpm-lock.yaml | 6 +++--- 8 files changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index ec93f7aa1e..9a7c675b66 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -28,7 +28,7 @@ "dependencies": { "@credo-ts/core": "workspace:*", "@sphereon/did-auth-siop": "link:../../../../Documents/OID4VCI/packages/siop-oid4vp", - "@sphereon/common": "link:../../../../Documents/OID4VCI/packages/common", + "@sphereon/oid4vc-common": "link:../../../../Documents/OID4VCI/packages/common", "@sphereon/oid4vci-client": "link:../../../../Documents/OID4VCI/packages/client", "@sphereon/oid4vci-common": "link:../../../../Documents/OID4VCI/packages/oid4vci-common", "@sphereon/oid4vci-issuer": "link:../../../../Documents/OID4VCI/packages/issuer", diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index 2f2daf8fe2..0c903be6e5 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -46,7 +46,7 @@ import { injectable, parseDid, } from '@credo-ts/core' -import { SigningAlgo, CreateDPoPClientOpts } from '@sphereon/common' +import { SigningAlgo, CreateDPoPClientOpts } from '@sphereon/oid4vc-common' import { AccessTokenClient, CredentialRequestClientBuilder, @@ -318,8 +318,14 @@ export class OpenId4VciHolderService { const accessTokenClient = new AccessTokenClient() const createDPoPOpts = await this.getCreateDPoPOptions(agentContext, metadata) - const dpopJwk = createDPoPOpts ? getJwkFromJson(createDPoPOpts.jwtIssuer.jwk) : undefined + let dpopJwk: Jwk | undefined + if (createDPoPOpts) { + if (!createDPoPOpts.jwtIssuer.jwk.kty) { + throw new CredoError('Missing required key type (kty) in the jwk.') + } + dpopJwk = getJwkFromJson(createDPoPOpts.jwtIssuer.jwk as JwkJson) + } if (resolvedAuthorizationRequest) { const { codeVerifier, redirectUri } = resolvedAuthorizationRequest accessTokenResponse = await accessTokenClient.acquireAccessToken({ @@ -471,12 +477,12 @@ export class OpenId4VciHolderService { const credentialRequestClient = credentialRequestBuilder.build() - let createDPoPOpts: CreateDPoPClientOpts | undefined - if (options.dpop) { - const jwk = options.dpop.dpopJwk + let createDpopOpts: CreateDPoPClientOpts | undefined + if (tokenResponse.dpop) { + const jwk = tokenResponse.dpop.dpopJwk const alg = jwk.supportedSignatureAlgorithms[0] - createDPoPOpts = { + createDpopOpts = { jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, jwtPayloadProps: { accessToken: options.accessToken }, createJwtCallback: getCreateJwtCallback(agentContext), @@ -487,7 +493,7 @@ export class OpenId4VciHolderService { proofInput: proofOfPossession, credentialTypes: getTypesFromCredentialSupported(offeredCredentialConfiguration), format: offeredCredentialConfiguration.format, - createDPoPOpts, + createDPoPOpts: createDpopOpts, }) newCNonce = credentialResponse.successBody?.c_nonce diff --git a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts index 5d9443dbec..762ecb5cf3 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/accessTokenEndpoint.ts @@ -1,6 +1,6 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' import type { AgentContext } from '@credo-ts/core' -import type { JWK, SigningAlgo } from '@sphereon/common' +import type { JWK, SigningAlgo } from '@sphereon/oid4vc-common' import type { AccessTokenRequest, JWTSignerCallback } from '@sphereon/oid4vci-common' import type { NextFunction, Response, Router } from 'express' @@ -13,7 +13,7 @@ import { Key, joinUriParts, } from '@credo-ts/core' -import { verifyDPoP } from '@sphereon/common' +import { verifyDPoP } from '@sphereon/oid4vc-common' import { GrantTypes, IssueStatus, diff --git a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts b/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts index c6ff535f61..ff007ec7d6 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/verifyResourceRequest.ts @@ -1,10 +1,10 @@ import type { OpenId4VcIssuanceRequest } from './requestContext' import type { OpenId4VcIssuerRecord } from '../repository' import type { AgentContext } from '@credo-ts/core' -import type { SigningAlgo } from '@sphereon/common' +import type { SigningAlgo } from '@sphereon/oid4vc-common' import { CredoError, joinUriParts, JwsService, Jwt } from '@credo-ts/core' -import { verifyResourceDPoP } from '@sphereon/common' +import { verifyResourceDPoP } from '@sphereon/oid4vc-common' import { getVerifyJwtCallback } from '../../shared/utils' import { OpenId4VcIssuerModuleConfig } from '../OpenId4VcIssuerModuleConfig' diff --git a/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts b/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts index 32689957c7..cc25e0e8a1 100644 --- a/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts +++ b/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts @@ -1,5 +1,5 @@ import { Jwt } from '@credo-ts/core' -import { SigningAlgo } from '@sphereon/common' +import { SigningAlgo } from '@sphereon/oid4vc-common' import { cleanAll, enableNetConnect } from 'nock' import { AskarModule } from '../../../../askar/src' diff --git a/packages/openid4vc/src/shared/utils.ts b/packages/openid4vc/src/shared/utils.ts index e43055ff16..f170060f57 100644 --- a/packages/openid4vc/src/shared/utils.ts +++ b/packages/openid4vc/src/shared/utils.ts @@ -1,7 +1,7 @@ import type { OpenId4VcJwtIssuer } from './models' -import type { AgentContext, JwaSignatureAlgorithm, Key } from '@credo-ts/core' -import type { DPoPJwtIssuerWithContext, SigningAlgo, CreateJwtCallback, JwtIssuer } from '@sphereon/common' +import type { AgentContext, JwaSignatureAlgorithm, JwkJson, Key } from '@credo-ts/core' import type { JwtIssuerWithContext as VpJwtIssuerWithContext, VerifyJwtCallback } from '@sphereon/did-auth-siop' +import type { DPoPJwtIssuerWithContext, SigningAlgo, CreateJwtCallback, JwtIssuer } from '@sphereon/oid4vc-common' import type { CredentialOfferPayloadV1_0_11, CredentialOfferPayloadV1_0_13 } from '@sphereon/oid4vci-common' import { @@ -83,7 +83,10 @@ export function getCreateJwtCallback( return jws } else if (jwtIssuer.method === 'jwk') { - const jwk = getJwkFromJson(jwtIssuer.jwk) + if (!jwtIssuer.jwk.kty) { + throw new CredoError('Missing required key type (kty) in the jwk.') + } + const jwk = getJwkFromJson(jwtIssuer.jwk as JwkJson) const key = jwk.key const jws = await jwsService.createJwsCompact(agentContext, { protectedHeaderOptions: { ...jwt.header, jwk, alg: jwtIssuer.alg }, diff --git a/packages/openid4vc/tests/openid4vc.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index 2a07d835fb..4b0fc72336 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -292,7 +292,7 @@ describe('OpenId4Vc', () => { }) expect(tokenResponseTenant1.accessToken).toBeDefined() - expect(tokenResponseTenant1.dpop.dpopJwk).toBeInstanceOf(Jwk) + expect(tokenResponseTenant1.dpop?.dpopJwk).toBeInstanceOf(Jwk) const { payload } = Jwt.fromSerializedJwt(tokenResponseTenant1.accessToken) expect(payload.additionalClaims.token_type).toEqual('DPoP') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2859ccbb5..04cd1d1938 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -680,12 +680,12 @@ importers: '@credo-ts/core': specifier: workspace:* version: link:../core - '@sphereon/common': - specifier: link:../../../../Documents/OID4VCI/packages/common - version: link:../../../OID4VCI/packages/common '@sphereon/did-auth-siop': specifier: link:../../../../Documents/OID4VCI/packages/siop-oid4vp version: link:../../../OID4VCI/packages/siop-oid4vp + '@sphereon/oid4vc-common': + specifier: link:../../../../Documents/OID4VCI/packages/common + version: link:../../../OID4VCI/packages/common '@sphereon/oid4vci-client': specifier: link:../../../../Documents/OID4VCI/packages/client version: link:../../../OID4VCI/packages/client From f250d5730c5f9e8c0029e6e85eabac81d61fa28c Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Tue, 30 Jul 2024 17:26:46 +0200 Subject: [PATCH 07/12] feat: dpop retry mechanisms --- .../openid4vc-holder/OpenId4VciHolderService.ts | 16 ++++++++++------ .../OpenId4VciHolderServiceOptions.ts | 8 ++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index 0c903be6e5..644d2e51e6 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -69,6 +69,7 @@ import { PARMode, post, EndpointMetadataResult, + DPoPResponseParams, } from '@sphereon/oid4vci-common' import { OpenId4VciCredentialFormatProfile } from '../shared' @@ -313,7 +314,7 @@ export class OpenId4VciHolderService { const { metadata, credentialOfferRequestWithBaseUrl } = resolvedCredentialOffer // acquire the access token - let accessTokenResponse: OpenIDResponse + let accessTokenResponse: OpenIDResponse const accessTokenClient = new AccessTokenClient() @@ -354,7 +355,10 @@ export class OpenId4VciHolderService { this.logger.debug('Requested OpenId4VCI Access Token.') - return { ...accessTokenResponse.successBody, ...(dpopJwk && { dpop: { dpopJwk: dpopJwk } }) } + return { + ...accessTokenResponse.successBody, + ...(dpopJwk && { dpop: { dpopJwk: dpopJwk, dpopNonce: accessTokenResponse.params?.dpop?.dpopNonce } }), + } } public async acceptCredentialOffer( @@ -365,7 +369,7 @@ export class OpenId4VciHolderService { resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode accessToken?: string cNonce?: string - dpop?: { dpopJwk: Jwk } + dpop?: { dpopJwk: Jwk; dpopNonce?: string } } ) { const { resolvedCredentialOffer, acceptCredentialOfferOptions } = options @@ -410,7 +414,7 @@ export class OpenId4VciHolderService { ? { access_token: options.accessToken, c_nonce: options.cNonce, - ...(options.dpop && { dpop: { dpopJwk: options.dpop.dpopJwk } }), + ...(options.dpop && { dpop: { dpopJwk: options.dpop.dpopJwk, dpopNonce: options.dpop?.dpopNonce } }), } : await this.requestAccessToken(agentContext, tokenRequestOptions) @@ -484,7 +488,7 @@ export class OpenId4VciHolderService { createDpopOpts = { jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, - jwtPayloadProps: { accessToken: options.accessToken }, + jwtPayloadProps: { accessToken: tokenResponse.access_token, nonce: tokenResponse.dpop?.dpopNonce }, createJwtCallback: getCreateJwtCallback(agentContext), } } @@ -690,7 +694,7 @@ export class OpenId4VciHolderService { private async handleCredentialResponse( agentContext: AgentContext, - credentialResponse: OpenIDResponse, + credentialResponse: OpenIDResponse, options: { verifyCredentialStatus: boolean credentialIssuerMetadata: OpenId4VciIssuerMetadata diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index 053caa97b1..ffd26c6d06 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -42,7 +42,11 @@ export type OpenId4VciNotificationEvent = 'credential_accepted' | 'credential_fa export type OpenId4VciTokenResponse = Pick -export type OpenId4VciRequestTokenResponse = { accessToken: string; cNonce?: string; dpop?: { dpopJwk: Jwk } } +export type OpenId4VciRequestTokenResponse = { + accessToken: string + cNonce?: string + dpop?: { dpopJwk: Jwk; dpopNonce?: string } +} export interface OpenId4VciCredentialResponse { credential: VerifiableCredential @@ -112,7 +116,7 @@ export interface OpenId4VciCredentialRequestOptions extends Omit Date: Thu, 1 Aug 2024 20:17:00 +0200 Subject: [PATCH 08/12] feat: incorporate feedback --- .../OpenId4VciHolderService.ts | 60 ++++++++++--------- .../OpenId4VciHolderServiceOptions.ts | 4 +- .../openid4vc-issuer/OpenId4VcIssuerApi.ts | 19 +++--- .../OpenId4VcIssuerServiceOptions.ts | 7 +++ .../openid4vc/tests/openid4vc.e2e.test.ts | 2 +- 5 files changed, 49 insertions(+), 43 deletions(-) diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts index afe4b63edf..3fdaae3839 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderService.ts @@ -55,7 +55,7 @@ import { injectable, parseDid, } from '@credo-ts/core' -import { CreateDPoPClientOpts, SigningAlgo } from '@sphereon/oid4vc-common' +import { CreateDPoPClientOpts, CreateDPoPJwtPayloadProps, SigningAlgo } from '@sphereon/oid4vc-common' import { AccessTokenClient, CredentialRequestClientBuilder, @@ -275,10 +275,14 @@ export class OpenId4VciHolderService { } } - private async getCreateDPoPOptions( + private async getCreateDpopOptions( agentContext: AgentContext, metadata: Pick & { credentialIssuerMetadata: OpenId4VciIssuerMetadata + }, + resourceRequestOptions?: { + jwk: Jwk + jwtPayloadProps: Omit } ) { const dpopSigningAlgValuesSupported = @@ -289,23 +293,28 @@ export class OpenId4VciHolderService { const alg = dpopSigningAlgValuesSupported.find((alg) => getJwkClassFromJwaSignatureAlgorithm(alg)) - const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined + let jwk: Jwk + if (resourceRequestOptions) { + jwk = resourceRequestOptions.jwk + } else { + const JwkClass = alg ? getJwkClassFromJwaSignatureAlgorithm(alg) : undefined - if (!JwkClass) { - throw new CredoError( - `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join( - ', ' - )}'` - ) - } + if (!JwkClass) { + throw new CredoError( + `No supported dpop signature algorithms found in dpop_signing_alg_values_supported '${dpopSigningAlgValuesSupported.join( + ', ' + )}'` + ) + } - const key = await agentContext.wallet.createKey({ keyType: JwkClass.keyType }) - const jwk = getJwkFromKey(key) + const key = await agentContext.wallet.createKey({ keyType: JwkClass.keyType }) + jwk = getJwkFromKey(key) + } const createDPoPOpts: CreateDPoPClientOpts = { jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, dPoPSigningAlgValuesSupported: dpopSigningAlgValuesSupported, - jwtPayloadProps: {}, + jwtPayloadProps: resourceRequestOptions?.jwtPayloadProps ?? {}, createJwtCallback: getCreateJwtCallback(agentContext), } return createDPoPOpts @@ -320,7 +329,7 @@ export class OpenId4VciHolderService { const accessTokenClient = new AccessTokenClient() - const createDPoPOpts = await this.getCreateDPoPOptions(agentContext, metadata) + const createDPoPOpts = await this.getCreateDpopOptions(agentContext, metadata) let dpopJwk: Jwk | undefined if (createDPoPOpts) { @@ -359,7 +368,7 @@ export class OpenId4VciHolderService { return { ...accessTokenResponse.successBody, - ...(dpopJwk && { dpop: { dpopJwk: dpopJwk, dpopNonce: accessTokenResponse.params?.dpop?.dpopNonce } }), + ...(dpopJwk && { dpop: { jwk: dpopJwk, nonce: accessTokenResponse.params?.dpop?.dpopNonce } }), } } @@ -371,7 +380,7 @@ export class OpenId4VciHolderService { resolvedAuthorizationRequestWithCode?: OpenId4VciResolvedAuthorizationRequestWithCode accessToken?: string cNonce?: string - dpop?: { dpopJwk: Jwk; dpopNonce?: string } + dpop?: { jwk: Jwk; nonce?: string } clientId?: string } ) { @@ -417,7 +426,7 @@ export class OpenId4VciHolderService { ? { access_token: options.accessToken, c_nonce: options.cNonce, - ...(options.dpop && { dpop: { dpopJwk: options.dpop.dpopJwk, dpopNonce: options.dpop?.dpopNonce } }), + dpop: options.dpop, } : await this.requestAccessToken(agentContext, tokenRequestOptions) @@ -493,17 +502,12 @@ export class OpenId4VciHolderService { const credentialRequestClient = credentialRequestBuilder.build() - let createDpopOpts: CreateDPoPClientOpts | undefined - if (tokenResponse.dpop) { - const jwk = tokenResponse.dpop.dpopJwk - const alg = jwk.supportedSignatureAlgorithms[0] - - createDpopOpts = { - jwtIssuer: { alg: alg as unknown as SigningAlgo, jwk: jwk.toJson() }, - jwtPayloadProps: { accessToken: tokenResponse.access_token, nonce: tokenResponse.dpop?.dpopNonce }, - createJwtCallback: getCreateJwtCallback(agentContext), - } - } + const createDpopOpts = tokenResponse.dpop + ? await this.getCreateDpopOptions(agentContext, metadata, { + jwk: tokenResponse.dpop.jwk, + jwtPayloadProps: { accessToken: tokenResponse.access_token, nonce: tokenResponse.dpop?.nonce }, + }) + : undefined const credentialResponse = await credentialRequestClient.acquireCredentialsUsingProof({ proofInput: proofOfPossession, diff --git a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts index 109c4a61a2..990e31a861 100644 --- a/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-holder/OpenId4VciHolderServiceOptions.ts @@ -45,7 +45,7 @@ export type OpenId4VciTokenResponse = Pick & - (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) - ) { + public async updateIssuerMetadata(options: OpenId4VcUpdateIssuerRecordOptions) { const { issuerId, credentialConfigurationsSupported, credentialsSupported, ...issuerOptions } = options const issuer = await this.openId4VcIssuerService.getIssuerByIssuerId(this.agentContext, issuerId) @@ -77,8 +70,10 @@ export class OpenId4VcIssuerApi { issuer.credentialsSupported = credentialsSupported issuer.credentialConfigurationsSupported = undefined } + issuer.display = issuerOptions.display + issuer.dpopSigningAlgValuesSupported = issuerOptions.dpopSigningAlgValuesSupported - return this.openId4VcIssuerService.updateIssuer(this.agentContext, Object.assign(issuer, issuerOptions)) + return this.openId4VcIssuerService.updateIssuer(this.agentContext, issuer) } /** diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts index d87b3863eb..58c492abe8 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerServiceOptions.ts @@ -2,6 +2,7 @@ import type { OpenId4VcIssuanceSessionRecord, OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps, OpenId4VcIssuerRecordCredentialSupportedProps, + OpenId4VcIssuerRecordProps, } from './repository' import type { OpenId4VcCredentialHolderBinding, @@ -161,3 +162,9 @@ export type OpenId4VciCreateIssuerOptions = { display?: OpenId4VciIssuerMetadataDisplay[] dpopSigningAlgValuesSupported?: [JwaSignatureAlgorithm, ...JwaSignatureAlgorithm[]] } & (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) + +export type OpenId4VcUpdateIssuerRecordOptions = Pick< + OpenId4VcIssuerRecordProps, + 'issuerId' | 'display' | 'dpopSigningAlgValuesSupported' +> & + (OpenId4VcIssuerRecordCredentialSupportedProps | OpenId4VcIssuerRecordCredentialConfigurationsSupportedProps) diff --git a/packages/openid4vc/tests/openid4vc.e2e.test.ts b/packages/openid4vc/tests/openid4vc.e2e.test.ts index 4b0fc72336..6cad2cfeab 100644 --- a/packages/openid4vc/tests/openid4vc.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc.e2e.test.ts @@ -292,7 +292,7 @@ describe('OpenId4Vc', () => { }) expect(tokenResponseTenant1.accessToken).toBeDefined() - expect(tokenResponseTenant1.dpop?.dpopJwk).toBeInstanceOf(Jwk) + expect(tokenResponseTenant1.dpop?.jwk).toBeInstanceOf(Jwk) const { payload } = Jwt.fromSerializedJwt(tokenResponseTenant1.accessToken) expect(payload.additionalClaims.token_type).toEqual('DPoP') From bc926917c7b75bb83be28e2631e49654bdc71401 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sat, 3 Aug 2024 12:07:51 +0200 Subject: [PATCH 09/12] small type improvement Signed-off-by: Timo Glastra --- packages/openid4vc/src/shared/utils.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/openid4vc/src/shared/utils.ts b/packages/openid4vc/src/shared/utils.ts index f170060f57..5c50029da6 100644 --- a/packages/openid4vc/src/shared/utils.ts +++ b/packages/openid4vc/src/shared/utils.ts @@ -1,7 +1,7 @@ import type { 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, SigningAlgo, CreateJwtCallback, JwtIssuer } 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 { @@ -117,13 +117,13 @@ export async function openIdTokenIssuerToJwtIssuer( ): Promise { if (openId4VcTokenIssuer.method === 'did') { const key = await getKeyFromDid(agentContext, openId4VcTokenIssuer.didUrl) - const _alg = getJwkClassFromKeyType(key.keyType)?.supportedSignatureAlgorithms[0] - if (!_alg) throw new CredoError(`No supported signature algorithms for key type: ${key.keyType}`) + const alg = getJwkClassFromKeyType(key.keyType)?.supportedSignatureAlgorithms[0] + if (!alg) throw new CredoError(`No supported signature algorithms for key type: ${key.keyType}`) return { method: openId4VcTokenIssuer.method, didUrl: openId4VcTokenIssuer.didUrl, - alg: _alg as unknown as SigningAlgo, + alg, } } else if (openId4VcTokenIssuer.method === 'x5c') { const issuer = openId4VcTokenIssuer.issuer @@ -144,7 +144,7 @@ export async function openIdTokenIssuerToJwtIssuer( if (leafCertificate.sanUriNames?.includes(issuer)) { return { ...openId4VcTokenIssuer, - alg: alg as unknown as SigningAlgo, + alg, clientIdScheme: 'x509_san_uri', } } else { @@ -156,7 +156,7 @@ export async function openIdTokenIssuerToJwtIssuer( return { ...openId4VcTokenIssuer, - alg: alg as unknown as SigningAlgo, + alg, clientIdScheme: 'x509_san_dns', } } @@ -168,7 +168,7 @@ export async function openIdTokenIssuerToJwtIssuer( return { ...openId4VcTokenIssuer, jwk: openId4VcTokenIssuer.jwk.toJson(), - alg: alg as unknown as SigningAlgo, + alg, } } From 7273ca1492b4a29093b2aabf77519f51a07bf33f Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Mon, 5 Aug 2024 11:22:59 +0200 Subject: [PATCH 10/12] feat: update version Signed-off-by: Martin Auer --- packages/openid4vc/package.json | 10 +++--- pnpm-lock.yaml | 62 ++++++++++++++++----------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index a5a7db52e7..5b531fa8eb 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -27,11 +27,11 @@ }, "dependencies": { "@credo-ts/core": "workspace:*", - "@sphereon/did-auth-siop": "^0.16.0", - "@sphereon/oid4vc-common": "^0.16.0", - "@sphereon/oid4vci-client": "^0.16.0", - "@sphereon/oid4vci-common": "^0.16.0", - "@sphereon/oid4vci-issuer": "^0.16.0", + "@sphereon/did-auth-siop": "^0.16.1-next.3", + "@sphereon/oid4vc-common": "^0.16.1-next.3", + "@sphereon/oid4vci-client": "^0.16.1-next.3", + "@sphereon/oid4vci-common": "^0.16.1-next.3", + "@sphereon/oid4vci-issuer": "^0.16.1-next.3", "@sphereon/ssi-types": "0.28.0", "class-transformer": "^0.5.1", "rxjs": "^7.8.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12684cce04..8adf969371 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -684,20 +684,20 @@ importers: specifier: workspace:* version: link:../core '@sphereon/did-auth-siop': - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.16.1-next.3 + version: 0.16.1-unstable.5 '@sphereon/oid4vc-common': - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.16.1-next.3 + version: 0.16.1-unstable.5 '@sphereon/oid4vci-client': - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.16.1-next.3 + version: 0.16.1-unstable.5 '@sphereon/oid4vci-common': - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.16.1-next.3 + version: 0.16.1-unstable.5 '@sphereon/oid4vci-issuer': - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.16.1-next.3 + version: 0.16.1-unstable.5 '@sphereon/ssi-types': specifier: 0.28.0 version: 0.28.0 @@ -2383,27 +2383,27 @@ packages: resolution: {integrity: sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw==} engines: {node: '>= 8'} - '@sphereon/did-auth-siop@0.16.0': - resolution: {integrity: sha512-hWLzP+jv1Re/2s3Kkq5sBUhWKHPu6ptOcYWNCGIaYBS0SF8E86Bg5mPBbwq1tSr+/GapsxMcmL6QszQgJqJ+Cw==} + '@sphereon/did-auth-siop@0.16.1-unstable.5': + resolution: {integrity: sha512-2WDS8SRHUEYACnBbzNCnxnXJ26s0vuyzFrc/Va3LL6bN9s2gqajZlGFT953Nq0DomBMJujhE8gM6gvY0M61e4g==} engines: {node: '>=18'} '@sphereon/did-uni-client@0.6.3': resolution: {integrity: sha512-g7LD7ofbE36slHN7Bhr5dwUrj6t0BuZeXBYJMaVY/pOeL1vJxW1cZHbZqu0NSfOmzyBg4nsYVlgTjyi/Aua2ew==} - '@sphereon/oid4vc-common@0.16.0': - resolution: {integrity: sha512-EgT0gwMX5/MhTDAOEWqbx2c9azBSQAThL8EDu7wufGQxRFNAc3Ya8NXoCMIOfqIqbSLhmyCB0ANcdoHeJq2VdA==} + '@sphereon/oid4vc-common@0.16.1-unstable.5': + resolution: {integrity: sha512-ovAElg2JOn1b3XpzMZh8tKxapnpk0Sq8Ii/vKxpViD9cjf64O0LeaRrwkoBvuxSdPrmFhqqEiL8AP0oH7xTWQA==} engines: {node: '>=18'} - '@sphereon/oid4vci-client@0.16.0': - resolution: {integrity: sha512-lsuvuPkIdOtRLumZDyb06XipPiWWrwCAkVcqdJXEERn7W8DrHwnkyqVVmJvNZnx5DD4IlaSy7xLvw1H04rgczA==} + '@sphereon/oid4vci-client@0.16.1-unstable.5': + resolution: {integrity: sha512-aEpadqhHOesoWK9+1NY2BRs0RGdfDLjD1TrUE/P1Dzobsy27zdEPkIHBTikgDDmWHSNVkhiibqJD5qvyJftHnw==} engines: {node: '>=18'} - '@sphereon/oid4vci-common@0.16.0': - resolution: {integrity: sha512-Bf937YsOP7XkI8vgQSPh4e8bwxnkMQAanEtfPv27HSQPEcHpsFtrlQ9T5TRzmb6k66E+n1k0XJXK2WzOrOt7Eg==} + '@sphereon/oid4vci-common@0.16.1-unstable.5': + resolution: {integrity: sha512-hdfSGrjjb0rgXuoM3rHdWE360CXNjRHNbCCKEoik9ak24qvmWs6duMepz9R2dcE1KZz28F9qK3ZZyPMU73/Uxw==} engines: {node: '>=18'} - '@sphereon/oid4vci-issuer@0.16.0': - resolution: {integrity: sha512-Zd19OBWemBVQ98PMgPALViPJsOFb/irO4ElLF/tUXCKTApzUujtKFbaA54b4iRCnM2uBACuTBpzAxvqMsezO0w==} + '@sphereon/oid4vci-issuer@0.16.1-unstable.5': + resolution: {integrity: sha512-SrOjj7Pm6NDWPZuphtU6BeCYvCvMfD+XwayDqO8Oc6BBNwtgy4MHvF1CYjxKJWKabaRSNRRlwOCoAHLRJdW3uw==} engines: {node: '>=18'} peerDependencies: awesome-qr: ^2.1.5-rc.0 @@ -9770,11 +9770,11 @@ snapshots: '@sovpro/delimited-stream@1.1.0': {} - '@sphereon/did-auth-siop@0.16.0': + '@sphereon/did-auth-siop@0.16.1-unstable.5': dependencies: '@astronautlabs/jsonpath': 1.1.2 '@sphereon/did-uni-client': 0.6.3 - '@sphereon/oid4vc-common': 0.16.0 + '@sphereon/oid4vc-common': 0.16.1-unstable.5 '@sphereon/pex': 3.3.3 '@sphereon/pex-models': 2.2.4 '@sphereon/ssi-types': 0.22.0 @@ -9797,7 +9797,7 @@ snapshots: transitivePeerDependencies: - encoding - '@sphereon/oid4vc-common@0.16.0': + '@sphereon/oid4vc-common@0.16.1-unstable.5': dependencies: '@sphereon/ssi-types': 0.28.0 jwt-decode: 4.0.0 @@ -9807,10 +9807,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/oid4vci-client@0.16.0': + '@sphereon/oid4vci-client@0.16.1-unstable.5': dependencies: - '@sphereon/oid4vc-common': 0.16.0 - '@sphereon/oid4vci-common': 0.16.0 + '@sphereon/oid4vc-common': 0.16.1-unstable.5 + '@sphereon/oid4vci-common': 0.16.1-unstable.5 '@sphereon/ssi-types': 0.28.0 cross-fetch: 3.1.8 debug: 4.3.5 @@ -9818,9 +9818,9 @@ snapshots: - encoding - supports-color - '@sphereon/oid4vci-common@0.16.0': + '@sphereon/oid4vci-common@0.16.1-unstable.5': dependencies: - '@sphereon/oid4vc-common': 0.16.0 + '@sphereon/oid4vc-common': 0.16.1-unstable.5 '@sphereon/ssi-types': 0.28.0 cross-fetch: 3.1.8 jwt-decode: 4.0.0 @@ -9830,10 +9830,10 @@ snapshots: - encoding - supports-color - '@sphereon/oid4vci-issuer@0.16.0': + '@sphereon/oid4vci-issuer@0.16.1-unstable.5': dependencies: - '@sphereon/oid4vc-common': 0.16.0 - '@sphereon/oid4vci-common': 0.16.0 + '@sphereon/oid4vc-common': 0.16.1-unstable.5 + '@sphereon/oid4vci-common': 0.16.1-unstable.5 '@sphereon/ssi-types': 0.28.0 uuid: 9.0.1 transitivePeerDependencies: From f2d73af7b573347bdead0552315f68b217863be9 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 5 Aug 2024 11:39:41 +0200 Subject: [PATCH 11/12] pin version Signed-off-by: Timo Glastra --- packages/openid4vc/package.json | 10 +++--- pnpm-lock.yaml | 62 ++++++++++++++++----------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/openid4vc/package.json b/packages/openid4vc/package.json index 5b531fa8eb..fc72ee6d16 100644 --- a/packages/openid4vc/package.json +++ b/packages/openid4vc/package.json @@ -27,11 +27,11 @@ }, "dependencies": { "@credo-ts/core": "workspace:*", - "@sphereon/did-auth-siop": "^0.16.1-next.3", - "@sphereon/oid4vc-common": "^0.16.1-next.3", - "@sphereon/oid4vci-client": "^0.16.1-next.3", - "@sphereon/oid4vci-common": "^0.16.1-next.3", - "@sphereon/oid4vci-issuer": "^0.16.1-next.3", + "@sphereon/did-auth-siop": "0.16.1-next.3", + "@sphereon/oid4vc-common": "0.16.1-next.3", + "@sphereon/oid4vci-client": "0.16.1-next.3", + "@sphereon/oid4vci-common": "0.16.1-next.3", + "@sphereon/oid4vci-issuer": "0.16.1-next.3", "@sphereon/ssi-types": "0.28.0", "class-transformer": "^0.5.1", "rxjs": "^7.8.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8adf969371..b7a575c934 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -684,20 +684,20 @@ importers: specifier: workspace:* version: link:../core '@sphereon/did-auth-siop': - specifier: ^0.16.1-next.3 - version: 0.16.1-unstable.5 + specifier: 0.16.1-next.3 + version: 0.16.1-next.3 '@sphereon/oid4vc-common': - specifier: ^0.16.1-next.3 - version: 0.16.1-unstable.5 + specifier: 0.16.1-next.3 + version: 0.16.1-next.3 '@sphereon/oid4vci-client': - specifier: ^0.16.1-next.3 - version: 0.16.1-unstable.5 + specifier: 0.16.1-next.3 + version: 0.16.1-next.3 '@sphereon/oid4vci-common': - specifier: ^0.16.1-next.3 - version: 0.16.1-unstable.5 + specifier: 0.16.1-next.3 + version: 0.16.1-next.3 '@sphereon/oid4vci-issuer': - specifier: ^0.16.1-next.3 - version: 0.16.1-unstable.5 + specifier: 0.16.1-next.3 + version: 0.16.1-next.3 '@sphereon/ssi-types': specifier: 0.28.0 version: 0.28.0 @@ -2383,27 +2383,27 @@ packages: resolution: {integrity: sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw==} engines: {node: '>= 8'} - '@sphereon/did-auth-siop@0.16.1-unstable.5': - resolution: {integrity: sha512-2WDS8SRHUEYACnBbzNCnxnXJ26s0vuyzFrc/Va3LL6bN9s2gqajZlGFT953Nq0DomBMJujhE8gM6gvY0M61e4g==} + '@sphereon/did-auth-siop@0.16.1-next.3': + resolution: {integrity: sha512-PjE1n5oUPpLU7KCZ3pK1OOo+/DL+ASsJew5I2awt0HT7+mOEbXPhIWmnKjIcqPnXrw8O59m8uYi5MUrtASrIpg==} engines: {node: '>=18'} '@sphereon/did-uni-client@0.6.3': resolution: {integrity: sha512-g7LD7ofbE36slHN7Bhr5dwUrj6t0BuZeXBYJMaVY/pOeL1vJxW1cZHbZqu0NSfOmzyBg4nsYVlgTjyi/Aua2ew==} - '@sphereon/oid4vc-common@0.16.1-unstable.5': - resolution: {integrity: sha512-ovAElg2JOn1b3XpzMZh8tKxapnpk0Sq8Ii/vKxpViD9cjf64O0LeaRrwkoBvuxSdPrmFhqqEiL8AP0oH7xTWQA==} + '@sphereon/oid4vc-common@0.16.1-next.3': + resolution: {integrity: sha512-0bdCLsUtqzmn/Zks0RZNuO8H3s5Zc71x/o1WDXE4263REvO8zuzI4N6viNQZdM8vUxR99GnADQCxWCY2ZoVO8g==} engines: {node: '>=18'} - '@sphereon/oid4vci-client@0.16.1-unstable.5': - resolution: {integrity: sha512-aEpadqhHOesoWK9+1NY2BRs0RGdfDLjD1TrUE/P1Dzobsy27zdEPkIHBTikgDDmWHSNVkhiibqJD5qvyJftHnw==} + '@sphereon/oid4vci-client@0.16.1-next.3': + resolution: {integrity: sha512-b5xtDlQcEsg0W0In1lywUcvf3jJy/NhfvknbtYjRVtObhA2K/pRsb1yS+p54Vfc02enA+A/a2lVb6zI2peyF3w==} engines: {node: '>=18'} - '@sphereon/oid4vci-common@0.16.1-unstable.5': - resolution: {integrity: sha512-hdfSGrjjb0rgXuoM3rHdWE360CXNjRHNbCCKEoik9ak24qvmWs6duMepz9R2dcE1KZz28F9qK3ZZyPMU73/Uxw==} + '@sphereon/oid4vci-common@0.16.1-next.3': + resolution: {integrity: sha512-+0Cm/qWgQ2efs+vSFwf37Zji1k+oSs3pI7yuN0dGQF7iYG3whq+iNRfIKR5M9rYg8H4bITg0PSTrcFXz7VuWww==} engines: {node: '>=18'} - '@sphereon/oid4vci-issuer@0.16.1-unstable.5': - resolution: {integrity: sha512-SrOjj7Pm6NDWPZuphtU6BeCYvCvMfD+XwayDqO8Oc6BBNwtgy4MHvF1CYjxKJWKabaRSNRRlwOCoAHLRJdW3uw==} + '@sphereon/oid4vci-issuer@0.16.1-next.3': + resolution: {integrity: sha512-92D+dEqvEkyI6XKkjbZXSmZlqLNdl3vAd8BNDWM1+E2X337lFnT9cE0pF432YdOzvS/ZwdlmftGjq2ehi1nOgg==} engines: {node: '>=18'} peerDependencies: awesome-qr: ^2.1.5-rc.0 @@ -9770,11 +9770,11 @@ snapshots: '@sovpro/delimited-stream@1.1.0': {} - '@sphereon/did-auth-siop@0.16.1-unstable.5': + '@sphereon/did-auth-siop@0.16.1-next.3': dependencies: '@astronautlabs/jsonpath': 1.1.2 '@sphereon/did-uni-client': 0.6.3 - '@sphereon/oid4vc-common': 0.16.1-unstable.5 + '@sphereon/oid4vc-common': 0.16.1-next.3 '@sphereon/pex': 3.3.3 '@sphereon/pex-models': 2.2.4 '@sphereon/ssi-types': 0.22.0 @@ -9797,7 +9797,7 @@ snapshots: transitivePeerDependencies: - encoding - '@sphereon/oid4vc-common@0.16.1-unstable.5': + '@sphereon/oid4vc-common@0.16.1-next.3': dependencies: '@sphereon/ssi-types': 0.28.0 jwt-decode: 4.0.0 @@ -9807,10 +9807,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@sphereon/oid4vci-client@0.16.1-unstable.5': + '@sphereon/oid4vci-client@0.16.1-next.3': dependencies: - '@sphereon/oid4vc-common': 0.16.1-unstable.5 - '@sphereon/oid4vci-common': 0.16.1-unstable.5 + '@sphereon/oid4vc-common': 0.16.1-next.3 + '@sphereon/oid4vci-common': 0.16.1-next.3 '@sphereon/ssi-types': 0.28.0 cross-fetch: 3.1.8 debug: 4.3.5 @@ -9818,9 +9818,9 @@ snapshots: - encoding - supports-color - '@sphereon/oid4vci-common@0.16.1-unstable.5': + '@sphereon/oid4vci-common@0.16.1-next.3': dependencies: - '@sphereon/oid4vc-common': 0.16.1-unstable.5 + '@sphereon/oid4vc-common': 0.16.1-next.3 '@sphereon/ssi-types': 0.28.0 cross-fetch: 3.1.8 jwt-decode: 4.0.0 @@ -9830,10 +9830,10 @@ snapshots: - encoding - supports-color - '@sphereon/oid4vci-issuer@0.16.1-unstable.5': + '@sphereon/oid4vci-issuer@0.16.1-next.3': dependencies: - '@sphereon/oid4vc-common': 0.16.1-unstable.5 - '@sphereon/oid4vci-common': 0.16.1-unstable.5 + '@sphereon/oid4vc-common': 0.16.1-next.3 + '@sphereon/oid4vci-common': 0.16.1-next.3 '@sphereon/ssi-types': 0.28.0 uuid: 9.0.1 transitivePeerDependencies: From 33ed706a67100731c81b4a0128a7a9a5d5ac0ff7 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 5 Aug 2024 11:52:15 +0200 Subject: [PATCH 12/12] Create sharp-crews-sniff.md Signed-off-by: Timo Glastra --- .changeset/sharp-crews-sniff.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/sharp-crews-sniff.md diff --git a/.changeset/sharp-crews-sniff.md b/.changeset/sharp-crews-sniff.md new file mode 100644 index 0000000000..c6d48c6494 --- /dev/null +++ b/.changeset/sharp-crews-sniff.md @@ -0,0 +1,6 @@ +--- +"@credo-ts/core": patch +"@credo-ts/openid4vc": patch +--- + +Add support for Demonstrating Proof of Possesion (DPoP) when receiving credentials using OpenID4VCI