From cb6d70ffa2d466e2c283432bc6331adf8fe2a2a5 Mon Sep 17 00:00:00 2001 From: Tom Lanser Date: Mon, 18 Nov 2024 14:51:43 +0100 Subject: [PATCH] feat: Added more logging and added unhappy tests Signed-off-by: Tom Lanser --- .../OpenId4VcIssuerModuleConfig.ts | 10 -- .../router/federationEndpoint.ts | 26 +++--- .../__tests__/openid4vc-verifier.test.ts | 39 -------- packages/openid4vc/src/shared/utils.ts | 18 +++- .../tests/openid4vc-federation.e2e.test.ts | 92 +++++++++++++++++++ 5 files changed, 118 insertions(+), 67 deletions(-) diff --git a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts index 7697cc637..ae8f7418f 100644 --- a/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts +++ b/packages/openid4vc/src/openid4vc-issuer/OpenId4VcIssuerModuleConfig.ts @@ -96,14 +96,4 @@ export class OpenId4VcIssuerModuleConfig { endpointPath: userOptions.endpointPath ?? '/offers', } } - - public get federationEndpoint(): OpenId4VcSiopFederationEndpointConfig | undefined { - const userOptions = this.options.endpoints.federation - if (!userOptions) return undefined - - return { - ...userOptions, - endpointPath: userOptions.endpointPath ?? '/.well-known/openid-federation', - } - } } diff --git a/packages/openid4vc/src/openid4vc-issuer/router/federationEndpoint.ts b/packages/openid4vc/src/openid4vc-issuer/router/federationEndpoint.ts index dacab4c2e..788dea94f 100644 --- a/packages/openid4vc/src/openid4vc-issuer/router/federationEndpoint.ts +++ b/packages/openid4vc/src/openid4vc-issuer/router/federationEndpoint.ts @@ -24,18 +24,6 @@ export function configureFederationEndpoint(router: Router) { }) const issuerMetadata = openId4VcIssuerService.getIssuerMetadata(agentContext, issuer) - // TODO: Use a type here from sphreon - const transformedMetadata = { - credential_issuer: issuerMetadata.issuerUrl, - token_endpoint: issuerMetadata.tokenEndpoint, - credential_endpoint: issuerMetadata.credentialEndpoint, - authorization_server: issuerMetadata.authorizationServer, - authorization_servers: issuerMetadata.authorizationServer ? [issuerMetadata.authorizationServer] : undefined, - credentials_supported: issuerMetadata.credentialsSupported, - credential_configurations_supported: issuerMetadata.credentialConfigurationsSupported, - display: issuerMetadata.issuerDisplay, - dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, - } as const const now = new Date() const expires = new Date(now.getTime() + 1000 * 60 * 60 * 24) // 1 day from now @@ -68,7 +56,19 @@ export function configureFederationEndpoint(router: Router) { } : undefined, openid_provider: { - ...transformedMetadata, + // TODO: The type isn't correct yet down the line so that needs to be updated before + // credential_issuer: issuerMetadata.issuerUrl, + // token_endpoint: issuerMetadata.tokenEndpoint, + // credential_endpoint: issuerMetadata.credentialEndpoint, + // authorization_server: issuerMetadata.authorizationServer, + // authorization_servers: issuerMetadata.authorizationServer + // ? [issuerMetadata.authorizationServer] + // : undefined, + // credentials_supported: issuerMetadata.credentialsSupported, + // credential_configurations_supported: issuerMetadata.credentialConfigurationsSupported, + // display: issuerMetadata.issuerDisplay, + // dpop_signing_alg_values_supported: issuerMetadata.dpopSigningAlgValuesSupported, + client_registration_types_supported: ['automatic'], jwks: { keys: [ 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 883b5cb8a..e40ef7057 100644 --- a/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts +++ b/packages/openid4vc/src/openid4vc-verifier/__tests__/openid4vc-verifier.test.ts @@ -113,44 +113,5 @@ describe('OpenId4VcVerifier', () => { expect(jwt.payload.iss).toEqual(verifier.did) expect(jwt.payload.sub).toEqual(verifier.did) }) - - it('check openid proof request format (entity id)', async () => { - const openIdVerifier = await verifier.agent.modules.openId4VcVerifier.createVerifier() - const { authorizationRequest, verificationSession } = - await verifier.agent.modules.openId4VcVerifier.createAuthorizationRequest({ - requestSigner: { - method: 'openid-federation', - clientId: 'http://localhost:3001/verifier', - }, - verifierId: openIdVerifier.verifierId, - }) - - expect( - authorizationRequest.startsWith( - `openid://?client_id=${encodeURIComponent(verifier.did)}&request_uri=http%3A%2F%2Fredirect-uri%2F${ - openIdVerifier.verifierId - }%2Fauthorization-requests%2F` - ) - ).toBe(true) - - const jwt = Jwt.fromSerializedJwt(verificationSession.authorizationRequestJwt) - - expect(jwt.header.kid) - - expect(jwt.header.kid).toEqual(verifier.kid) - expect(jwt.header.alg).toEqual(SigningAlgo.EDDSA) - expect(jwt.header.typ).toEqual('JWT') - expect(jwt.payload.additionalClaims.scope).toEqual('openid') - expect(jwt.payload.additionalClaims.client_id).toEqual(verifier.did) - expect(jwt.payload.additionalClaims.response_uri).toEqual( - `http://redirect-uri/${openIdVerifier.verifierId}/authorize` - ) - expect(jwt.payload.additionalClaims.response_mode).toEqual('direct_post') - expect(jwt.payload.additionalClaims.nonce).toBeDefined() - expect(jwt.payload.additionalClaims.state).toBeDefined() - expect(jwt.payload.additionalClaims.response_type).toEqual('id_token') - expect(jwt.payload.iss).toEqual(verifier.did) - expect(jwt.payload.sub).toEqual(verifier.did) - }) }) }) diff --git a/packages/openid4vc/src/shared/utils.ts b/packages/openid4vc/src/shared/utils.ts index d85f82306..fb6bbf3eb 100644 --- a/packages/openid4vc/src/shared/utils.ts +++ b/packages/openid4vc/src/shared/utils.ts @@ -60,6 +60,8 @@ export function getVerifyJwtCallback( agentContext: AgentContext, options: VerifyJwtCallbackOptions = {} ): VerifyJwtCallback { + const logger = agentContext.config.logger + return async (jwtVerifier, jwt) => { const jwsService = agentContext.dependencyManager.resolve(JwsService) @@ -79,8 +81,10 @@ export function getVerifyJwtCallback( if (jwtVerifier.method === 'openid-federation') { const { entityId } = jwtVerifier const trustedEntityIds = options.federation?.trustedEntityIds - if (!trustedEntityIds) - throw new CredoError('No trusted entity ids provided but is required for the openid-federation method.') + if (!trustedEntityIds) { + logger.error('No trusted entity ids provided but is required for the "openid-federation" method.') + return false + } const validTrustChains = await resolveTrustChains({ entityId, @@ -95,7 +99,10 @@ export function getVerifyJwtCallback( }, }) // When the chain is already invalid we can return false immediately - if (validTrustChains.length === 0) return false + if (validTrustChains.length === 0) { + logger.error(`${entityId} is not part of a trusted federation.`) + return false + } // Pick the first valid trust chain for validation of the leaf entity jwks const { entityConfiguration } = validTrustChains[0] @@ -108,11 +115,12 @@ export function getVerifyJwtCallback( jws: jwt.raw, jwkResolver: () => getJwkFromJson(rpSigningKeys[0]), }) + if (!res.isValid) { + logger.error(`${entityId} does not match the expected signing key.`) + } // TODO: There is no check yet for the policies - // TODO: When this function results in a `false` it gives a really misleading error message: 'Error verifying the DID Auth Token signature.' - return res.isValid } diff --git a/packages/openid4vc/tests/openid4vc-federation.e2e.test.ts b/packages/openid4vc/tests/openid4vc-federation.e2e.test.ts index 3abd06dc9..f0f7f874b 100644 --- a/packages/openid4vc/tests/openid4vc-federation.e2e.test.ts +++ b/packages/openid4vc/tests/openid4vc-federation.e2e.test.ts @@ -424,4 +424,96 @@ describe('OpenId4Vc', () => { ], }) }) + + it('e2e flow with tenants, unhappy flow', async () => { + const holderTenant = await holder.agent.modules.tenants.getTenantAgent({ tenantId: holder1.tenantId }) + const verifierTenant1 = await verifier.agent.modules.tenants.getTenantAgent({ tenantId: verifier1.tenantId }) + const verifierTenant2 = await verifier.agent.modules.tenants.getTenantAgent({ tenantId: verifier2.tenantId }) + + const openIdVerifierTenant1 = await verifierTenant1.modules.openId4VcVerifier.createVerifier() + const openIdVerifierTenant2 = await verifierTenant2.modules.openId4VcVerifier.createVerifier() + + const signedCredential1 = await issuer.agent.w3cCredentials.signCredential({ + format: ClaimFormat.JwtVc, + credential: new W3cCredential({ + type: ['VerifiableCredential', 'OpenBadgeCredential'], + issuer: new W3cIssuer({ id: issuer.did }), + credentialSubject: new W3cCredentialSubject({ id: holder1.did }), + issuanceDate: w3cDate(Date.now()), + }), + alg: JwaSignatureAlgorithm.EdDSA, + verificationMethod: issuer.verificationMethod.id, + }) + + const signedCredential2 = await issuer.agent.w3cCredentials.signCredential({ + format: ClaimFormat.JwtVc, + credential: new W3cCredential({ + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: new W3cIssuer({ id: issuer.did }), + credentialSubject: new W3cCredentialSubject({ id: holder1.did }), + issuanceDate: w3cDate(Date.now()), + }), + alg: JwaSignatureAlgorithm.EdDSA, + verificationMethod: issuer.verificationMethod.id, + }) + + await holderTenant.w3cCredentials.storeCredential({ credential: signedCredential1 }) + await holderTenant.w3cCredentials.storeCredential({ credential: signedCredential2 }) + + const { authorizationRequest: authorizationRequestUri1, verificationSession: verificationSession1 } = + await verifierTenant1.modules.openId4VcVerifier.createAuthorizationRequest({ + verifierId: openIdVerifierTenant1.verifierId, + requestSigner: { + method: 'openid-federation', + }, + presentationExchange: { + definition: openBadgePresentationDefinition, + }, + }) + + expect(authorizationRequestUri1).toEqual( + `openid4vp://?client_id=${encodeURIComponent( + `http://localhost:1234/oid4vp/${openIdVerifierTenant1.verifierId}` + )}&request_uri=${encodeURIComponent(verificationSession1.authorizationRequestUri)}` + ) + + const { authorizationRequest: authorizationRequestUri2, verificationSession: verificationSession2 } = + await verifierTenant2.modules.openId4VcVerifier.createAuthorizationRequest({ + requestSigner: { + method: 'openid-federation', + }, + presentationExchange: { + definition: universityDegreePresentationDefinition, + }, + verifierId: openIdVerifierTenant2.verifierId, + }) + + expect(authorizationRequestUri2).toEqual( + `openid4vp://?client_id=${encodeURIComponent( + `http://localhost:1234/oid4vp/${openIdVerifierTenant2.verifierId}` + )}&request_uri=${encodeURIComponent(verificationSession2.authorizationRequestUri)}` + ) + + await verifierTenant1.endSession() + await verifierTenant2.endSession() + + const resolvedProofRequestWithFederationPromise = + holderTenant.modules.openId4VcHolder.resolveSiopAuthorizationRequest(authorizationRequestUri1, { + federation: { + // This will look for a whole different trusted entity + trustedEntityIds: [`http://localhost:1234/oid4vp/${openIdVerifierTenant2.verifierId}`], + }, + }) + + // TODO: Look into this error see if we can make it more specific + await expect(resolvedProofRequestWithFederationPromise).rejects.toThrow( + `Error verifying the DID Auth Token signature.` + ) + + const resolvedProofRequestWithoutFederationPromise = + holderTenant.modules.openId4VcHolder.resolveSiopAuthorizationRequest(authorizationRequestUri2) + await expect(resolvedProofRequestWithoutFederationPromise).rejects.toThrow( + `Error verifying the DID Auth Token signature.` + ) + }) })