From 4060fe7f45a12e4eca2a8a321e219ee9ae7d7edf Mon Sep 17 00:00:00 2001 From: Zoe Maas Date: Tue, 26 Nov 2024 11:17:39 +0100 Subject: [PATCH] refactor: Added the OIDF client and check to the OID4VCI library --- packages/client/lib/OpenID4VCIClient.ts | 25 +++++++- packages/client/package.json | 5 ++ pnpm-lock.yaml | 79 +++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/packages/client/lib/OpenID4VCIClient.ts b/packages/client/lib/OpenID4VCIClient.ts index 0ec729d2..90337abd 100644 --- a/packages/client/lib/OpenID4VCIClient.ts +++ b/packages/client/lib/OpenID4VCIClient.ts @@ -1,4 +1,4 @@ -import { CreateDPoPClientOpts, JWK } from '@sphereon/oid4vc-common'; +import { CreateDPoPClientOpts, JWK, parseJWT } from '@sphereon/oid4vc-common'; import { AccessTokenRequestOpts, AccessTokenResponse, @@ -35,6 +35,7 @@ import { ProofOfPossessionCallbacks, toAuthorizationResponsePayload, } from '@sphereon/oid4vci-common'; +import { FederationClient } from '@sphereon/openid-federation-client'; import { CredentialFormat } from '@sphereon/ssi-types'; import Debug from 'debug'; @@ -54,7 +55,9 @@ import { generateMissingPKCEOpts, sendNotification } from './functions'; const debug = Debug('sphereon:oid4vci'); -export type OpenID4VCIClientState = OpenID4VCIClientStateV1_0_11 | OpenID4VCIClientStateV1_0_13; +export type OpenID4VCIClientState = + | (OpenID4VCIClientStateV1_0_11 & { trustChains?: Array }) + | (OpenID4VCIClientStateV1_0_13 & { trustChains?: Array }); export type EndpointMetadataResult = EndpointMetadataResultV1_0_11 | EndpointMetadataResultV1_0_13; @@ -76,6 +79,7 @@ export class OpenID4VCIClient { authorizationRequestOpts, authorizationCodeResponse, authorizationURL, + trustChains, }: { credentialOffer?: CredentialOfferRequestWithBaseUrl; kid?: string; @@ -91,6 +95,7 @@ export class OpenID4VCIClient { authorizationRequestOpts?: AuthorizationRequestOpts; authorizationCodeResponse?: AuthorizationResponse; authorizationURL?: string; + trustChains?: Array; }) { const issuer = credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : undefined); if (!issuer) { @@ -113,6 +118,7 @@ export class OpenID4VCIClient { : (endpointMetadata as EndpointMetadataResultV1_0_13 | undefined), accessTokenResponse, authorizationURL, + trustChains, } as OpenID4VCIClientState; // Running syncAuthorizationRequestOpts later as it is using the state if (!this._state.authorizationRequestOpts) { @@ -130,6 +136,7 @@ export class OpenID4VCIClient { pkce, authorizationRequest, createAuthorizationRequestURL, + trustChains, }: { credentialIssuer: string; kid?: string; @@ -139,6 +146,7 @@ export class OpenID4VCIClient { createAuthorizationRequestURL?: boolean; authorizationRequest?: AuthorizationRequestOpts; // Can be provided here, or when manually calling createAuthorizationUrl pkce?: PKCEOpts; + trustChains?: Array; }) { const client = new OpenID4VCIClient({ kid, @@ -147,6 +155,7 @@ export class OpenID4VCIClient { credentialIssuer, pkce, authorizationRequest, + trustChains, }); if (retrieveServerMetadata === undefined || retrieveServerMetadata) { await client.retrieveServerMetadata(); @@ -257,7 +266,11 @@ export class OpenID4VCIClient { if (this.credentialOffer) { this._state.endpointMetadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(this.credentialOffer); } else if (this._state.credentialIssuer) { - this._state.endpointMetadata = await MetadataClient.retrieveAllMetadata(this._state.credentialIssuer); + if (this._state.trustChains !== undefined && this._state.trustChains !== null && this._state.trustChains.length !== 0) { + this._state.endpointMetadata = await this.retrieveTrustChainMetadata(this._state.credentialIssuer, this._state.trustChains); + } else { + this._state.endpointMetadata = await MetadataClient.retrieveAllMetadata(this._state.credentialIssuer); + } } else { throw Error(`Cannot retrieve issuer metadata without either a credential offer, or issuer value`); } @@ -266,6 +279,12 @@ export class OpenID4VCIClient { return this.endpointMetadata; } + private async retrieveTrustChainMetadata(credentialIssuer: string, trustChains: Array): Promise { + const oidfClient = new FederationClient(null, null); + const resolvedTrustChain = await oidfClient.resolveTrustChain(credentialIssuer, trustChains); + return resolvedTrustChain?.trustChain?.asJsReadonlyArrayView().map((s) => parseJWT(s))[1].payload as EndpointMetadataResult | undefined; + } + private calculatePKCEOpts(pkce?: PKCEOpts) { this._state.pkce = generateMissingPKCEOpts({ ...this._state.pkce, ...pkce }); } diff --git a/packages/client/package.json b/packages/client/package.json index 42c78f5f..abb569e3 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -47,6 +47,11 @@ "uint8arrays": "3.1.1", "uuid": "^9.0.1" }, + "peerDependencies": { + "@sphereon/openid-federation-client": "^0.1.1-unstable.21e8440", + "@sphereon/openid-federation-common": "^0.1.1-unstable.21e8440", + "@sphereon/openid-federation-open-api": "^0.1.1-unstable.21e8440" + }, "engines": { "node": ">=18" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ac67b6b..5786947b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,6 +118,15 @@ importers: '@sphereon/oid4vci-common': specifier: workspace:* version: link:../oid4vci-common + '@sphereon/openid-federation-client': + specifier: ^0.1.1-unstable.21e8440 + version: 0.1.1-unstable.21e8440(encoding@0.1.13) + '@sphereon/openid-federation-common': + specifier: ^0.1.1-unstable.21e8440 + version: 0.1.1-unstable.21e8440(encoding@0.1.13) + '@sphereon/openid-federation-open-api': + specifier: ^0.1.1-unstable.21e8440 + version: 0.1.1-unstable.21e8440(encoding@0.1.13) '@sphereon/ssi-types': specifier: 0.30.2-feature.mdoc.funke2.367 version: 0.30.2-feature.mdoc.funke2.367 @@ -2116,6 +2125,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@js-joda/core@3.2.0': + resolution: {integrity: sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==} + '@js-joda/core@5.6.3': resolution: {integrity: sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA==} @@ -2542,6 +2554,16 @@ packages: '@sphereon/kmp-mdoc-core@0.2.0-SNAPSHOT.10': resolution: {integrity: sha512-mHH7I6fWdztaNjguGJOLaerXWnQymQ/xKQ8NqClIXoI2PJNgmpQG6DxFcLRs1aYyWg1iY8bPliLJi41u94KdCA==} + bundledDependencies: [] + + '@sphereon/openid-federation-client@0.1.1-unstable.21e8440': + resolution: {integrity: sha512-O1CW/t3Zan1wKQxdl6fzDXDrL/t+Ph9UB0rypJA4Pr9u4R+7nB+Z3KC/wphH2Q4NrJFP0sASKn0ER78Gc9onLw==} + + '@sphereon/openid-federation-common@0.1.1-unstable.21e8440': + resolution: {integrity: sha512-mumhjoqeP1qjy3SsT/3AExV+x+SwrO4On+ZrMTFNYjzY7zRKl6rKWkgA0iGbG6z2XR7xW3y1wzDqFxIGS94tXQ==} + + '@sphereon/openid-federation-open-api@0.1.1-unstable.21e8440': + resolution: {integrity: sha512-glePGlpbvZI+NhhsfoD44cFbamwIMIQF0xG1k1Ikr9i8XRZFBHz/w/WK9Hfc9dtP63Ry2Ckq8I4KlRZLL5OJ6g==} '@sphereon/pex-models@2.3.1': resolution: {integrity: sha512-SByU4cJ0XYA6VZQ/L6lsSiRcFtBPHbFioCeQ4GP7/W/jQ+PSBD7uK2oTnKQ9/0iEiMK/6JYqhKgLs4a9UX3UTQ==} @@ -5716,6 +5738,9 @@ packages: jose@4.15.9: resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + jose@5.9.4: + resolution: {integrity: sha512-WBBl6au1qg6OHj67yCffCgFR3BADJBXN8MdRvCgJDuMv3driV2nHr7jdGvaKX9IolosAsn+M0XRArqLXUhyJHQ==} + js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} @@ -8601,6 +8626,18 @@ packages: utf-8-validate: optional: true + ws@8.5.0: + resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + x25519-key-agreement-2020-context@1.0.0: resolution: {integrity: sha512-zblYd8oSg6hNAD+fA9X7ek1hJQRircl3jVlEVCaBTNN9Mv9b4G32uJvRZFMQEMmda8iaTtYo9i2dRMdXX8pjpA==} @@ -11142,6 +11179,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@js-joda/core@3.2.0': {} + '@js-joda/core@5.6.3': {} '@js-joda/timezone@2.3.0(@js-joda/core@5.6.3)': @@ -11878,6 +11917,42 @@ snapshots: '@js-joda/timezone': 2.3.0(@js-joda/core@5.6.3) format-util: 1.0.5 + '@sphereon/openid-federation-client@0.1.1-unstable.21e8440(encoding@0.1.13)': + dependencies: + '@js-joda/core': 3.2.0 + abort-controller: 3.0.0 + format-util: 1.0.5 + jose: 5.9.4 + node-fetch: 2.6.12(encoding@0.1.13) + ws: 8.5.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@sphereon/openid-federation-common@0.1.1-unstable.21e8440(encoding@0.1.13)': + dependencies: + abort-controller: 3.0.0 + format-util: 1.0.5 + node-fetch: 2.6.12(encoding@0.1.13) + typescript: 5.5.3 + ws: 8.5.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@sphereon/openid-federation-open-api@0.1.1-unstable.21e8440(encoding@0.1.13)': + dependencies: + abort-controller: 3.0.0 + format-util: 1.0.5 + node-fetch: 2.6.12(encoding@0.1.13) + ws: 8.5.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + '@sphereon/pex-models@2.3.1': {} '@sphereon/pex@5.0.0-unstable.28': @@ -16301,6 +16376,8 @@ snapshots: jose@4.15.9: {} + jose@5.9.4: {} + js-base64@3.7.7: {} js-binary-schema-parser@2.0.3: {} @@ -19772,6 +19849,8 @@ snapshots: ws@8.18.0: {} + ws@8.5.0: {} + x25519-key-agreement-2020-context@1.0.0: {} xcode@3.0.1: