@sphereon/oid4vci-client 0.8.2-next.6 → 0.8.2-unstable.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,9 @@ import {
8
8
  CredentialResponse,
9
9
  CredentialSupported,
10
10
  EndpointMetadataResult,
11
+ getIssuerFromCredentialOfferPayload,
12
+ getSupportedCredentials,
13
+ getTypesFromCredentialSupported,
11
14
  JsonURIMode,
12
15
  OID4VCICredentialFormat,
13
16
  OpenId4VCIVersion,
@@ -15,8 +18,6 @@ import {
15
18
  PushedAuthorizationResponse,
16
19
  ResponseType,
17
20
  } from '@sphereon/oid4vci-common';
18
- import { getSupportedCredentials } from '@sphereon/oid4vci-common/dist/functions/IssuerMetadataUtils';
19
- import { CredentialSupportedTypeV1_0_08 } from '@sphereon/oid4vci-common/dist/types/v1_0_08.types';
20
21
  import { CredentialFormat } from '@sphereon/ssi-types';
21
22
  import Debug from 'debug';
22
23
 
@@ -39,27 +40,65 @@ interface AuthDetails {
39
40
 
40
41
  interface AuthRequestOpts {
41
42
  codeChallenge: string;
42
- codeChallengeMethod: CodeChallengeMethod;
43
+ codeChallengeMethod?: CodeChallengeMethod;
43
44
  authorizationDetails?: AuthDetails | AuthDetails[];
44
45
  redirectUri: string;
45
46
  scope?: string;
46
47
  }
47
48
 
48
49
  export class OpenID4VCIClient {
49
- private readonly _credentialOffer: CredentialOfferRequestWithBaseUrl;
50
+ private readonly _credentialOffer?: CredentialOfferRequestWithBaseUrl;
51
+ private _credentialIssuer: string;
50
52
  private _clientId?: string;
51
53
  private _kid: string | undefined;
52
54
  private _alg: Alg | string | undefined;
53
55
  private _endpointMetadata: EndpointMetadataResult | undefined;
54
56
  private _accessTokenResponse: AccessTokenResponse | undefined;
55
57
 
56
- private constructor(credentialOffer: CredentialOfferRequestWithBaseUrl, kid?: string, alg?: Alg | string, clientId?: string) {
58
+ private constructor({
59
+ credentialOffer,
60
+ clientId,
61
+ kid,
62
+ alg,
63
+ credentialIssuer,
64
+ }: {
65
+ credentialOffer?: CredentialOfferRequestWithBaseUrl;
66
+ kid?: string;
67
+ alg?: Alg | string;
68
+ clientId?: string;
69
+ credentialIssuer?: string;
70
+ }) {
57
71
  this._credentialOffer = credentialOffer;
72
+ const issuer = credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : undefined);
73
+ if (!issuer) {
74
+ throw Error('No credential issuer supplied or deduced from offer');
75
+ }
76
+ this._credentialIssuer = issuer;
58
77
  this._kid = kid;
59
78
  this._alg = alg;
60
79
  this._clientId = clientId;
61
80
  }
62
81
 
82
+ public static async fromCredentialIssuer({
83
+ kid,
84
+ alg,
85
+ retrieveServerMetadata,
86
+ clientId,
87
+ credentialIssuer,
88
+ }: {
89
+ credentialIssuer: string;
90
+ kid?: string;
91
+ alg?: Alg | string;
92
+ retrieveServerMetadata?: boolean;
93
+ clientId?: string;
94
+ }) {
95
+ const client = new OpenID4VCIClient({ kid, alg, clientId, credentialIssuer });
96
+ if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
97
+ await client.retrieveServerMetadata();
98
+ }
99
+ return client;
100
+ }
101
+
63
102
  public static async fromURI({
64
103
  uri,
65
104
  kid,
@@ -75,7 +114,12 @@ export class OpenID4VCIClient {
75
114
  resolveOfferUri?: boolean;
76
115
  clientId?: string;
77
116
  }): Promise<OpenID4VCIClient> {
78
- const client = new OpenID4VCIClient(await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }), kid, alg, clientId);
117
+ const client = new OpenID4VCIClient({
118
+ credentialOffer: await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }),
119
+ kid,
120
+ alg,
121
+ clientId,
122
+ });
79
123
 
80
124
  if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
81
125
  await client.retrieveServerMetadata();
@@ -86,16 +130,41 @@ export class OpenID4VCIClient {
86
130
  public async retrieveServerMetadata(): Promise<EndpointMetadataResult> {
87
131
  this.assertIssuerData();
88
132
  if (!this._endpointMetadata) {
89
- this._endpointMetadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
133
+ if (this.credentialOffer) {
134
+ this._endpointMetadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
135
+ } else if (this._credentialIssuer) {
136
+ this._endpointMetadata = await MetadataClient.retrieveAllMetadata(this._credentialIssuer);
137
+ } else {
138
+ throw Error(`Cannot retrieve issuer metadata without either a credential offer, or issuer value`);
139
+ }
90
140
  }
91
141
  return this.endpointMetadata;
92
142
  }
93
143
 
144
+ // todo: Unify this method with the par method
145
+
94
146
  public createAuthorizationRequestUrl({ codeChallengeMethod, codeChallenge, authorizationDetails, redirectUri, scope }: AuthRequestOpts): string {
95
147
  // Scope and authorization_details can be used in the same authorization request
96
148
  // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
97
149
  if (!scope && !authorizationDetails) {
98
- throw Error('Please provide a scope or authorization_details');
150
+ if (!this.credentialOffer) {
151
+ throw Error('Please provide a scope or authorization_details');
152
+ }
153
+ const creds = this.credentialOffer.credential_offer.credentials;
154
+
155
+ authorizationDetails = creds
156
+ .flatMap((cred) => (typeof cred === 'string' ? this.getCredentialsSupported(true) : (cred as CredentialSupported)))
157
+ .map((cred) => {
158
+ return {
159
+ ...cred,
160
+ type: 'openid_credential',
161
+ locations: [this._credentialIssuer],
162
+ format: cred.format,
163
+ } satisfies AuthDetails;
164
+ });
165
+ if (authorizationDetails.length === 0) {
166
+ throw Error(`Could not create authorization details from credential offer. Please pass in explicit details`);
167
+ }
99
168
  }
100
169
  // todo: Probably can go with current logic in MetadataClient who will always set the authorization_endpoint when found
101
170
  // handling this because of the support for v1_0-08
@@ -117,7 +186,7 @@ export class OpenID4VCIClient {
117
186
 
118
187
  const queryObj: { [key: string]: string } = {
119
188
  response_type: ResponseType.AUTH_CODE,
120
- code_challenge_method: codeChallengeMethod,
189
+ code_challenge_method: codeChallengeMethod ?? CodeChallengeMethod.SHA256,
121
190
  code_challenge: codeChallenge,
122
191
  authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)),
123
192
  redirect_uri: redirectUri,
@@ -128,7 +197,7 @@ export class OpenID4VCIClient {
128
197
  queryObj['client_id'] = this.clientId;
129
198
  }
130
199
 
131
- if (this.credentialOffer.issuerState) {
200
+ if (this.credentialOffer?.issuerState) {
132
201
  queryObj['issuer_state'] = this.credentialOffer.issuerState;
133
202
  }
134
203
 
@@ -140,6 +209,7 @@ export class OpenID4VCIClient {
140
209
  });
141
210
  }
142
211
 
212
+ // todo: Unify this method with the create auth request url method
143
213
  public async acquirePushedAuthorizationRequestURI({
144
214
  codeChallengeMethod,
145
215
  codeChallenge,
@@ -173,7 +243,7 @@ export class OpenID4VCIClient {
173
243
 
174
244
  const queryObj: { [key: string]: string } = {
175
245
  response_type: ResponseType.AUTH_CODE,
176
- code_challenge_method: codeChallengeMethod,
246
+ code_challenge_method: codeChallengeMethod ?? CodeChallengeMethod.SHA256,
177
247
  code_challenge: codeChallenge,
178
248
  authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)),
179
249
  redirect_uri: redirectUri,
@@ -184,7 +254,7 @@ export class OpenID4VCIClient {
184
254
  queryObj['client_id'] = this.clientId;
185
255
  }
186
256
 
187
- if (this.credentialOffer.issuerState) {
257
+ if (this.credentialOffer?.issuerState) {
188
258
  queryObj['issuer_state'] = this.credentialOffer.issuerState;
189
259
  }
190
260
 
@@ -249,6 +319,7 @@ export class OpenID4VCIClient {
249
319
  const response = await accessTokenClient.acquireAccessToken({
250
320
  credentialOffer: this.credentialOffer,
251
321
  metadata: this.endpointMetadata,
322
+ credentialIssuer: this.getIssuer(),
252
323
  pin,
253
324
  codeVerifier,
254
325
  code,
@@ -298,36 +369,42 @@ export class OpenID4VCIClient {
298
369
  this._kid = kid;
299
370
  }
300
371
 
301
- const requestBuilder = CredentialRequestClientBuilder.fromCredentialOffer({
302
- credentialOffer: this.credentialOffer,
303
- metadata: this.endpointMetadata,
304
- });
372
+ const requestBuilder = this.credentialOffer
373
+ ? CredentialRequestClientBuilder.fromCredentialOffer({
374
+ credentialOffer: this.credentialOffer,
375
+ metadata: this.endpointMetadata,
376
+ })
377
+ : CredentialRequestClientBuilder.fromCredentialIssuer({
378
+ credentialIssuer: this.getIssuer(),
379
+ credentialTypes,
380
+ metadata: this.endpointMetadata,
381
+ version: this.version(),
382
+ });
305
383
 
306
384
  requestBuilder.withTokenFromResponse(this.accessTokenResponse);
307
385
  if (this.endpointMetadata?.credentialIssuerMetadata) {
308
386
  const metadata = this.endpointMetadata.credentialIssuerMetadata;
309
- const types = Array.isArray(credentialTypes) ? credentialTypes.sort() : [credentialTypes];
387
+ const types = Array.isArray(credentialTypes) ? [...credentialTypes].sort() : [credentialTypes];
310
388
 
311
389
  if (metadata.credentials_supported && Array.isArray(metadata.credentials_supported)) {
312
390
  let typeSupported = false;
313
391
 
314
392
  metadata.credentials_supported.forEach((supportedCredential) => {
315
- if (!supportedCredential.types || supportedCredential.types.length === 0) {
316
- throw Error('types is required in the credentials supported');
317
- }
393
+ const subTypes = getTypesFromCredentialSupported(supportedCredential);
318
394
  if (
319
- supportedCredential.types.sort().every((t, i) => types[i] === t) ||
320
- (types.length === 1 && (types[0] === supportedCredential.id || supportedCredential.types.includes(types[0])))
395
+ subTypes.sort().every((t, i) => types[i] === t) ||
396
+ (types.length === 1 && (types[0] === supportedCredential.id || subTypes.includes(types[0])))
321
397
  ) {
322
398
  typeSupported = true;
323
399
  }
324
400
  });
325
401
 
326
402
  if (!typeSupported) {
327
- throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
403
+ console.log(`Not all credential types ${JSON.stringify(credentialTypes)} are present in metadata for ${this.getIssuer()}`);
404
+ // throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
328
405
  }
329
406
  } else if (metadata.credentials_supported && !Array.isArray(metadata.credentials_supported)) {
330
- const credentialsSupported = metadata.credentials_supported as CredentialSupportedTypeV1_0_08;
407
+ const credentialsSupported = metadata.credentials_supported;
331
408
  if (types.some((type) => !metadata.credentials_supported || !credentialsSupported[type])) {
332
409
  throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
333
410
  }
@@ -356,7 +433,7 @@ export class OpenID4VCIClient {
356
433
  format,
357
434
  });
358
435
  if (response.errorBody) {
359
- debug(`Credential request error:\r\n${response.errorBody}`);
436
+ debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
360
437
  throw Error(
361
438
  `Retrieving a credential from ${this._endpointMetadata?.credential_endpoint} for issuer ${this.getIssuer()} failed with status: ${
362
439
  response.origResponse.status
@@ -389,7 +466,9 @@ export class OpenID4VCIClient {
389
466
  }
390
467
 
391
468
  getCredentialOfferTypes(): string[][] {
392
- if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
469
+ if (!this.credentialOffer) {
470
+ return [];
471
+ } else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
393
472
  const orig = this.credentialOffer.original_credential_offer as CredentialOfferPayloadV1_0_08;
394
473
  const types: string[] = typeof orig.credential_type === 'string' ? [orig.credential_type] : orig.credential_type;
395
474
  const result: string[][] = [];
@@ -397,21 +476,29 @@ export class OpenID4VCIClient {
397
476
  return result;
398
477
  } else {
399
478
  return this.credentialOffer.credential_offer.credentials.map((c) => {
400
- return typeof c === 'string' ? [c] : c.types;
479
+ if (typeof c === 'string') {
480
+ return [c];
481
+ } else if ('types' in c) {
482
+ return c.types;
483
+ } else if ('vct' in c.credential_definition) {
484
+ return [c.credential_definition.vct];
485
+ } else {
486
+ return c.credential_definition.types;
487
+ }
401
488
  });
402
489
  }
403
490
  }
404
491
 
405
492
  issuerSupportedFlowTypes(): AuthzFlowType[] {
406
- return this.credentialOffer.supportedFlows;
493
+ return this.credentialOffer?.supportedFlows ?? [AuthzFlowType.AUTHORIZATION_CODE_FLOW];
407
494
  }
408
495
 
409
- get credentialOffer(): CredentialOfferRequestWithBaseUrl {
496
+ get credentialOffer(): CredentialOfferRequestWithBaseUrl | undefined {
410
497
  return this._credentialOffer;
411
498
  }
412
499
 
413
500
  public version(): OpenId4VCIVersion {
414
- return this.credentialOffer.version;
501
+ return this.credentialOffer?.version ?? OpenId4VCIVersion.VER_1_0_11;
415
502
  }
416
503
 
417
504
  public get endpointMetadata(): EndpointMetadataResult {
@@ -437,9 +524,6 @@ export class OpenID4VCIClient {
437
524
  }
438
525
 
439
526
  get clientId(): string | undefined {
440
- /*if (!this._clientId) {
441
- throw Error('No client id present');
442
- }*/
443
527
  return this._clientId;
444
528
  }
445
529
 
@@ -451,7 +535,7 @@ export class OpenID4VCIClient {
451
535
 
452
536
  public getIssuer(): string {
453
537
  this.assertIssuerData();
454
- return this._endpointMetadata ? this.endpointMetadata.issuer : this.getIssuer();
538
+ return this._credentialIssuer!;
455
539
  }
456
540
 
457
541
  public getAccessTokenEndpoint(): string {
@@ -467,8 +551,10 @@ export class OpenID4VCIClient {
467
551
  }
468
552
 
469
553
  private assertIssuerData(): void {
470
- if (!this._credentialOffer) {
554
+ if (!this._credentialOffer && this.issuerSupportedFlowTypes().includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
471
555
  throw Error(`No issuance initiation or credential offer present`);
556
+ } else if (!this._credentialIssuer) {
557
+ throw Error(`No credential issuer value present`);
472
558
  }
473
559
  }
474
560
 
@@ -3,6 +3,7 @@ import { KeyObject } from 'crypto';
3
3
  import {
4
4
  Alg,
5
5
  EndpointMetadata,
6
+ getCredentialRequestForVersion,
6
7
  getIssuerFromCredentialOfferPayload,
7
8
  Jwt,
8
9
  OpenId4VCIVersion,
@@ -127,7 +128,7 @@ describe('Credential Request Client ', () => {
127
128
  version: OpenId4VCIVersion.VER_1_0_08,
128
129
  });
129
130
  expect(credentialRequest.proof?.jwt?.includes(partialJWT)).toBeTruthy();
130
- expect(credentialRequest.format).toEqual('jwt_vc_json');
131
+ expect(credentialRequest.format).toEqual('jwt_vc');
131
132
  const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
132
133
  expect(result?.successBody?.credential).toEqual(mockedVC);
133
134
  });
@@ -149,9 +150,7 @@ describe('Credential Request Client ', () => {
149
150
  .withKid(kid)
150
151
  .withClientId('sphereon:wallet')
151
152
  .build();
152
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
153
- // @ts-ignore
154
- await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json-ld', types: ['random'], proof })).rejects.toThrow(
153
+ await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json', types: ['random'], proof })).rejects.toThrow(
155
154
  Error(URL_NOT_VALID),
156
155
  );
157
156
  });
@@ -194,10 +193,11 @@ describe('Credential Request Client with different issuers ', () => {
194
193
  jwt: getMockData('spruce')?.credential.request.proof.jwt as string,
195
194
  },
196
195
  credentialTypes: ['OpenBadgeCredential'],
197
- format: 'jwt_vc_json-ld',
196
+ format: 'jwt_vc',
198
197
  version: OpenId4VCIVersion.VER_1_0_08,
199
198
  });
200
- expect(credentialRequest).toEqual(getMockData('spruce')?.credential.request);
199
+ const draft8CredentialRequest = getCredentialRequestForVersion(credentialRequest, OpenId4VCIVersion.VER_1_0_08);
200
+ expect(draft8CredentialRequest).toEqual(getMockData('spruce')?.credential.request);
201
201
  });
202
202
 
203
203
  it('should create correct CredentialRequest for Walt', async () => {
@@ -264,7 +264,8 @@ describe('Credential Request Client with different issuers ', () => {
264
264
  format: 'ldp_vc',
265
265
  version: OpenId4VCIVersion.VER_1_0_08,
266
266
  });
267
- expect(credentialOffer).toEqual(getMockData('mattr')?.credential.request);
267
+ const credentialRequest = getCredentialRequestForVersion(credentialOffer, OpenId4VCIVersion.VER_1_0_08);
268
+ expect(credentialRequest).toEqual(getMockData('mattr')?.credential.request);
268
269
  });
269
270
 
270
271
  it('should create correct CredentialRequest for diwala', async () => {
@@ -286,6 +287,10 @@ describe('Credential Request Client with different issuers ', () => {
286
287
  format: 'ldp_vc',
287
288
  version: OpenId4VCIVersion.VER_1_0_08,
288
289
  });
289
- expect(credentialOffer).toEqual(getMockData('diwala')?.credential.request);
290
+
291
+ // createCredentialRequest returns uniform format in draft 11
292
+ const credentialRequest = getCredentialRequestForVersion(credentialOffer, OpenId4VCIVersion.VER_1_0_08);
293
+
294
+ expect(credentialRequest).toEqual(getMockData('diwala')?.credential.request);
290
295
  });
291
296
  });
@@ -0,0 +1,136 @@
1
+ import { Alg, CodeChallengeMethod, Jwt } from '@sphereon/oid4vci-common';
2
+ import { toJwk } from '@sphereon/ssi-sdk-ext.key-utils';
3
+ import { CredentialMapper } from '@sphereon/ssi-types';
4
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5
+ //@ts-ignore
6
+ import { from } from '@trust/keyto';
7
+ import { fetch } from 'cross-fetch';
8
+ import debug from 'debug';
9
+ import { base64url, importJWK, JWK, SignJWT } from 'jose';
10
+ import * as u8a from 'uint8arrays';
11
+
12
+ import { OpenID4VCIClient } from '..';
13
+
14
+ export const UNIT_TEST_TIMEOUT = 30000;
15
+
16
+ const ISSUER_URL = 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock';
17
+ const AUTH_URL = 'https://conformance-test.ebsi.eu/conformance/v3/auth-mock';
18
+
19
+ const jwk: JWK = {
20
+ alg: 'ES256',
21
+ use: 'sig',
22
+ kty: 'EC',
23
+ crv: 'P-256',
24
+ x: 'hUWYK06qFvdudydiqnEhVJhZ-73jcLtuzH8kIyNOSHE',
25
+ y: 'UZf7oUkJdo65SQekMD5ssiRclEimG2SmlsjXf3QwQJo',
26
+ d: 'zDeeo3K0Pk8dofeKcajvJYxMZ1vijx_cVDJQl1IpbAM',
27
+ };
28
+
29
+ console.log(`JWK (private/orig): ${JSON.stringify(jwk, null, 2)}`);
30
+
31
+ const privateKey = from(jwk, 'jwk').toString('blk', 'private');
32
+ const publicKey = from(jwk, 'jwk').toString('blk', 'public');
33
+ console.log(`Private key: ${privateKey}`);
34
+ console.log(`Public key: ${publicKey}`);
35
+ console.log(`Private key (b64): ${base64url.encode(u8a.fromString(privateKey, 'base16'))}`);
36
+ console.log(`JWK (private 2) ${JSON.stringify(toJwk(privateKey, 'Secp256r1', { isPrivateKey: true }))}`);
37
+ console.log(`JWK (public 2) ${JSON.stringify(toJwk(publicKey, 'Secp256r1', { isPrivateKey: false }))}`);
38
+
39
+ // const DID_METHOD = 'did:key'
40
+ const DID =
41
+ 'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
42
+ const DID_URL_ENCODED =
43
+ 'did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
44
+ // const PRIVATE_KEY_HEX = '7dd923e40f4615ac496119f7e793cc2899e99b64b88ca8603db986700089532b'
45
+
46
+ // const PUBLIC_KEY_HEX =
47
+ // '04a23cb4c83901acc2eb0f852599610de0caeac260bf8ed05e7f902eaac0f9c8d74dd4841b94d13424d32af8ec0e9976db9abfa7e3a59e10d565c5d4d901b4be63'
48
+
49
+ // pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9
50
+ // priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
51
+ // const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
52
+ const kid = `${DID}#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE`;
53
+
54
+ // const jw = jose.importKey()
55
+ describe('OID4VCI-Client using Sphereon issuer should', () => {
56
+ async function test(credentialType: 'CTWalletCrossPreAuthorised' | 'CTWalletCrossInTime') {
57
+ debug.enable('*');
58
+ const offer = await getCredentialOffer(credentialType);
59
+ const client = await OpenID4VCIClient.fromURI({
60
+ uri: offer,
61
+ kid,
62
+ alg: Alg.ES256,
63
+ clientId: DID_URL_ENCODED,
64
+ });
65
+ expect(client.credentialOffer).toBeDefined();
66
+ expect(client.endpointMetadata).toBeDefined();
67
+ expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credential`);
68
+ expect(client.getAccessTokenEndpoint()).toEqual(`${AUTH_URL}/token`);
69
+
70
+ if (credentialType !== 'CTWalletCrossPreAuthorised') {
71
+ const url = client.createAuthorizationRequestUrl({
72
+ redirectUri: 'openid4vc%3A',
73
+ codeChallenge: 'mE2kPHmIprOqtkaYmESWj35yz-PB5vzdiSu0tAZ8sqs',
74
+ codeChallengeMethod: CodeChallengeMethod.SHA256,
75
+ });
76
+ const result = await fetch(url);
77
+ console.log(result.text());
78
+ }
79
+
80
+ const accessToken = await client.acquireAccessToken({ pin: '0891' });
81
+ // console.log(accessToken);
82
+ expect(accessToken).toMatchObject({
83
+ expires_in: 86400,
84
+ // scope: 'GuestCredential',
85
+ token_type: 'Bearer',
86
+ });
87
+
88
+ const format = 'jwt_vc';
89
+ const credentialResponse = await client.acquireCredentials({
90
+ credentialTypes: client.getCredentialOfferTypes()[0],
91
+ format,
92
+ proofCallbacks: {
93
+ signCallback: proofOfPossessionCallbackFunction,
94
+ },
95
+ kid,
96
+ });
97
+ console.log(JSON.stringify(credentialResponse, null, 2));
98
+ expect(credentialResponse.credential).toBeDefined();
99
+ const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!);
100
+ expect(format.startsWith(wrappedVC.format)).toEqual(true);
101
+ }
102
+
103
+ it(
104
+ 'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json',
105
+ async () => {
106
+ await test('CTWalletCrossPreAuthorised');
107
+ // await test('CTWalletCrossInTime');
108
+ },
109
+ UNIT_TEST_TIMEOUT,
110
+ );
111
+ });
112
+
113
+ async function getCredentialOffer(credentialType: 'CTWalletCrossPreAuthorised' | 'CTWalletCrossInTime'): Promise<string> {
114
+ const credentialOffer = await fetch(
115
+ `https://conformance-test.ebsi.eu/conformance/v3/issuer-mock/initiate-credential-offer?credential_type=${credentialType}&client_id=${DID_URL_ENCODED}&credential_offer_endpoint=openid-credential-offer%3A%2F%2F`,
116
+ {
117
+ method: 'GET',
118
+ headers: {
119
+ Accept: 'application/json',
120
+ 'Content-Type': 'application/json',
121
+ },
122
+ },
123
+ );
124
+
125
+ return await credentialOffer.text();
126
+ }
127
+
128
+ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
129
+ const importedJwk = await importJWK(jwk);
130
+ return await new SignJWT({ ...args.payload })
131
+ .setProtectedHeader({ ...args.header, kid: kid! })
132
+ .setIssuer(DID)
133
+ .setIssuedAt()
134
+ .setExpirationTime('2m')
135
+ .sign(importedJwk);
136
+ }
@@ -68,6 +68,11 @@ describe('OpenID4VCIClient should', () => {
68
68
  expect(scope?.[0]).toBe('openid');
69
69
  });
70
70
  it('throw an error if no scope and no authorization_details is provided', async () => {
71
+ nock(MOCK_URL).get(/.*/).reply(200, {});
72
+ nock(MOCK_URL).get(WellKnownEndpoints.OAUTH_AS).reply(404, {});
73
+ nock(MOCK_URL).get(WellKnownEndpoints.OPENID_CONFIGURATION).reply(404, {});
74
+ // Use a client with issuer only to trigger the error
75
+ client = await OpenID4VCIClient.fromCredentialIssuer({ credentialIssuer: 'https://server.example.com' });
71
76
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
72
77
  // @ts-ignore
73
78
  client._endpointMetadata?.credentialIssuerMetadata.authorization_endpoint = `${MOCK_URL}v1/auth/authorize`;
@@ -1,4 +1,4 @@
1
- import { CredentialSupportedBrief, IssuerCredentialSubjectDisplay, IssuerMetadataV1_0_08 } from '@sphereon/oid4vci-common';
1
+ import { CredentialSupportedFormatV1_0_08, IssuerCredentialSubjectDisplay, IssuerMetadataV1_0_08 } from '@sphereon/oid4vci-common';
2
2
  import { ICredentialStatus, W3CVerifiableCredential } from '@sphereon/ssi-types';
3
3
 
4
4
  export function getMockData(issuerName: string): IssuerMockData | null {
@@ -42,7 +42,8 @@ export interface IssuerMockData {
42
42
  url: string;
43
43
  deeplink: string;
44
44
  request: {
45
- types: [string];
45
+ types?: [string];
46
+ type?: string;
46
47
  format: 'jwt_vc' | 'ldp_vc' | 'jwt_vc_json-ld' | string;
47
48
  proof: {
48
49
  proof_type: 'jwt' | string;
@@ -110,8 +111,8 @@ const mockData: VciMockDataStructure = {
110
111
  deeplink:
111
112
  'openid-initiate-issuance://?issuer=https%3A%2F%2Fngi%2Doidc4vci%2Dtest%2Espruceid%2Exyz&credential_type=OpenBadgeCredential&pre-authorized_code=eyJhbGciOiJFUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOlsiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJleHAiOiIyMDIzLTA0LTIwVDA5OjA0OjM2WiIsIm5vbmNlIjoibWFibmVpT0VSZVB3V3BuRFFweEt3UnRsVVRFRlhGUEwifQ.qOZRPN8sTv_knhp7WaWte2-aDULaPZX--2i9unF6QDQNUllqDhvxgIHMDCYHCV8O2_Gj-T2x1J84fDMajE3asg&user_pin_required=false',
112
113
  request: {
113
- types: ['OpenBadgeCredential'],
114
- format: 'jwt_vc_json-ld',
114
+ type: 'OpenBadgeCredential',
115
+ format: 'jwt_vc',
115
116
  proof: {
116
117
  proof_type: 'jwt',
117
118
  jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOa3NpTENKMWMyVWlPaUp6YVdjaUxDSnJkSGtpT2lKRlF5SXNJbU55ZGlJNkluTmxZM0F5TlRack1TSXNJbmdpT2lKclpuVmpTa0V0VEhKck9VWjBPRmx5TFVkMlQzSmpia3N3YjNkc2RqUlhNblUwU3pJeFNHZHZTVlIzSWl3aWVTSTZJalozY0ZCUE1rOUNRVXBTU0ZFMVRXdEtXVlJaV0dsQlJFUXdOMU5OTlV0amVXcDNYMkUzVUUxWmVGa2lmUSMwIn0.eyJhdWQiOiJodHRwczovL25naS1vaWRjNHZjaS10ZXN0LnNwcnVjZWlkLnh5eiIsImlhdCI6MTY4MTkxMTA2MC45NDIsImV4cCI6MTY4MTkxMTcyMC45NDIsImlzcyI6InNwaGVyZW9uOnNzaS13YWxsZXQiLCJqdGkiOiJhNjA4MzMxZi02ZmE0LTQ0ZjAtYWNkZWY5NmFjMjdmNmQ3MCJ9.NwF3_41gwnlIdd_6Uk9CczeQHzIQt6UcvTT5Cxv72j9S1vNwiY9annA2kLsjsTiR5-WMBdUhJCO7wYCtZ15mxw',
@@ -357,7 +358,7 @@ const mockData: VciMockDataStructure = {
357
358
  url: 'https://jff.walt.id/issuer-api/default/oidc/credential',
358
359
  request: {
359
360
  types: ['OpenBadgeCredential'],
360
- format: 'jwt_vc_json',
361
+ format: 'jwt_vc',
361
362
  proof: {
362
363
  proof_type: 'jwt',
363
364
  jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOa3NpTENKMWMyVWlPaUp6YVdjaUxDSnJkSGtpT2lKRlF5SXNJbU55ZGlJNkluTmxZM0F5TlRack1TSXNJbmdpT2lKclpuVmpTa0V0VEhKck9VWjBPRmx5TFVkMlQzSmpia3N3YjNkc2RqUlhNblUwU3pJeFNHZHZTVlIzSWl3aWVTSTZJalozY0ZCUE1rOUNRVXBTU0ZFMVRXdEtXVlJaV0dsQlJFUXdOMU5OTlV0amVXcDNYMkUzVUUxWmVGa2lmUSMwIn0.eyJhdWQiOiJodHRwczovL2pmZi53YWx0LmlkL2lzc3Vlci1hcGkvZGVmYXVsdC9vaWRjLyIsImlhdCI6MTY4MTkxMTk0Mi4yMzgsImV4cCI6MTY4MTkxMjYwMi4yMzgsIm5vbmNlIjoiZjA2YTMxMDUtYTJlZC00NGZjLTk1NGItNGEyNTk3MDM0OTNiIiwiaXNzIjoic3BoZXJlb246c3NpLXdhbGxldCIsImp0aSI6IjA1OWM3ODA5LTlmOGYtNGE3ZS1hZDI4YTNhMTNhMGIzNmViIn0.RfiWyybxpe3nkx3b0yIsqDHQtvB1WwhDW4t0X-kijy2dsSfv2cYhSEmAzs1shg7OV4EW8fSzt_Te79xiVl6jCw',
@@ -514,7 +515,7 @@ const mockData: VciMockDataStructure = {
514
515
  types: ['PermanentResidentCard'],
515
516
  binding_methods_supported: ['did'],
516
517
  cryptographic_suites_supported: ['Ed25519Signature2018'],
517
- } as CredentialSupportedBrief,
518
+ } as CredentialSupportedFormatV1_0_08,
518
519
  },
519
520
  },
520
521
  AcademicAward: {
@@ -525,7 +526,7 @@ const mockData: VciMockDataStructure = {
525
526
  types: ['AcademicAward'],
526
527
  binding_methods_supported: ['did'],
527
528
  cryptographic_suites_supported: ['Ed25519Signature2018'],
528
- } as CredentialSupportedBrief,
529
+ } as CredentialSupportedFormatV1_0_08,
529
530
  },
530
531
  },
531
532
  LearnerProfile: {
@@ -536,7 +537,7 @@ const mockData: VciMockDataStructure = {
536
537
  types: ['LearnerProfile'],
537
538
  binding_methods_supported: ['did'],
538
539
  cryptographic_suites_supported: ['Ed25519Signature2018'],
539
- } as CredentialSupportedBrief,
540
+ } as CredentialSupportedFormatV1_0_08,
540
541
  },
541
542
  },
542
543
  OpenBadgeCredential: {
@@ -547,7 +548,7 @@ const mockData: VciMockDataStructure = {
547
548
  types: ['OpenBadgeCredential'],
548
549
  binding_methods_supported: ['did'],
549
550
  cryptographic_suites_supported: ['Ed25519Signature2018'],
550
- } as CredentialSupportedBrief,
551
+ } as CredentialSupportedFormatV1_0_08,
551
552
  },
552
553
  },
553
554
  },
@@ -573,8 +574,8 @@ const mockData: VciMockDataStructure = {
573
574
  'openid-initiate-issuance://?issuer=https://launchpad.mattrlabs.com&credential_type=OpenBadgeCredential&pre-authorized_code=g0UCOj6RAN5AwHU6gczm_GzB4_lH6GW39Z0Dl2DOOiO',
574
575
  url: 'https://launchpad.vii.electron.mattrlabs.io/oidc/v1/auth/credential',
575
576
  request: {
576
- types: ['OpenBadgeCredential'],
577
- format: 'jwt_vc_json-ld',
577
+ type: 'OpenBadgeCredential',
578
+ format: 'ldp_vc',
578
579
  proof: {
579
580
  proof_type: 'jwt',
580
581
  jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3AxM3N6QUFMVFN0cDV1OGtMcnl5YW5vYWtrVWtFUGZXazdvOHY3dms0RW1KI3o2TWtwMTNzekFBTFRTdHA1dThrTHJ5eWFub2Fra1VrRVBmV2s3bzh2N3ZrNEVtSiJ9.eyJhdWQiOiJodHRwczovL2xhdW5jaHBhZC5tYXR0cmxhYnMuY29tIiwiaWF0IjoxNjgxOTE0NDgyLjUxOSwiZXhwIjoxNjgxOTE1MTQyLjUxOSwiaXNzIjoic3BoZXJlb246c3NpLXdhbGxldCIsImp0aSI6ImI5NDY1ZGE5LTY4OGYtNDdjNi04MjUwNDA0ZGNiOWI5Y2E5In0.uQ8ewOfIjy_1p_Gk6PjeEWccBJnjOca1pwbTWiCAFMQX9wlIsfeUdGtXUoHjH5_PQtpwytodx7WU456_CT9iBQ',
@@ -687,8 +688,8 @@ const mockData: VciMockDataStructure = {
687
688
  'openid-initiate-issuance://?issuer=https://oidc4vc.diwala.io&amp;credential_type=OpenBadgeCredential&amp;pre-authorized_code=eyJhbGciOiJIUzI1NiJ9.eyJjcmVkZW50aWFsX3R5cGUiOiJPcGVuQmFkZ2VDcmVkZW50aWFsIiwiZXhwIjoxNjgxOTg0NDY3fQ.fEAHKz2nuWfiYHw406iNxr-81pWkNkbi31bWsYSf6Ng',
688
689
  url: 'https://oidc4vc.diwala.io/credential',
689
690
  request: {
690
- types: ['OpenBadgeCredential'],
691
- format: 'jwt_vc_json-ld',
691
+ type: 'OpenBadgeCredential',
692
+ format: 'ldp_vc',
692
693
  proof: {
693
694
  proof_type: 'jwt',
694
695
  jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3AxM3N6QUFMVFN0cDV1OGtMcnl5YW5vYWtrVWtFUGZXazdvOHY3dms0RW1KI3o2TWtwMTNzekFBTFRTdHA1dThrTHJ5eWFub2Fra1VrRVBmV2s3bzh2N3ZrNEVtSiJ9.eyJhdWQiOiJodHRwczovL29pZGM0dmMuZGl3YWxhLmlvIiwiaWF0IjoxNjgxOTE1MDk1LjIwMiwiZXhwIjoxNjgxOTE1NzU1LjIwMiwiaXNzIjoic3BoZXJlb246c3NpLXdhbGxldCIsImp0aSI6IjYxN2MwM2EzLTM3MTUtNGJlMy1hYjkxNzM4MTlmYzYxNTYzIn0.KA-cHjecaYp9FSaWHkz5cqtNyhBIVT_0I7cJnpHn03T4UWFvdhjhn8Hpe-BU247enFyWOWJ6v3NQZyZgle7xBA',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphereon/oid4vci-client",
3
- "version": "0.8.2-next.6+094ebfc",
3
+ "version": "0.8.2-unstable.16+a504fb2",
4
4
  "description": "OpenID for Verifiable Credential Issuance (OpenID4VCI) client",
5
5
  "source": "lib/index.ts",
6
6
  "main": "dist/index.js",
@@ -15,13 +15,15 @@
15
15
  "build": "tsc"
16
16
  },
17
17
  "dependencies": {
18
- "@sphereon/oid4vci-common": "0.8.2-next.6+094ebfc",
19
- "@sphereon/ssi-types": "0.17.2",
18
+ "@sphereon/oid4vci-common": "0.8.2-unstable.16+a504fb2",
19
+ "@sphereon/ssi-types": "0.17.6-unstable.23",
20
20
  "cross-fetch": "^3.1.8",
21
21
  "debug": "^4.3.4"
22
22
  },
23
23
  "devDependencies": {
24
+ "@sphereon/ssi-sdk-ext.key-utils": "^0.15.1-next.7",
24
25
  "@transmute/did-key.js": "^0.3.0-unstable.10",
26
+ "@trust/keyto": "^2.0.0-alpha1",
25
27
  "@types/jest": "^29.5.3",
26
28
  "@types/node": "^18.17.4",
27
29
  "@types/uuid": "^9.0.6",
@@ -67,5 +69,5 @@
67
69
  "OIDC4VCI",
68
70
  "OID4VCI"
69
71
  ],
70
- "gitHead": "094ebfc61e1650e3c7bb32e4a22ec576d7049f18"
72
+ "gitHead": "a504fb24a8d6127624e3e5497f1a6633e816253d"
71
73
  }