@sphereon/oid4vci-client 0.8.2-next.12 → 0.8.2-next.26

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.
Files changed (35) hide show
  1. package/dist/AccessTokenClient.d.ts.map +1 -1
  2. package/dist/AccessTokenClient.js +8 -8
  3. package/dist/AccessTokenClient.js.map +1 -1
  4. package/dist/CredentialRequestClient.d.ts.map +1 -1
  5. package/dist/CredentialRequestClient.js +4 -5
  6. package/dist/CredentialRequestClient.js.map +1 -1
  7. package/dist/CredentialRequestClientBuilder.d.ts +6 -0
  8. package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
  9. package/dist/CredentialRequestClientBuilder.js +9 -0
  10. package/dist/CredentialRequestClientBuilder.js.map +1 -1
  11. package/dist/OpenID4VCIClient.d.ts +15 -5
  12. package/dist/OpenID4VCIClient.d.ts.map +1 -1
  13. package/dist/OpenID4VCIClient.js +99 -38
  14. package/dist/OpenID4VCIClient.js.map +1 -1
  15. package/dist/ProofOfPossessionBuilder.d.ts +3 -1
  16. package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
  17. package/dist/ProofOfPossessionBuilder.js +5 -0
  18. package/dist/ProofOfPossessionBuilder.js.map +1 -1
  19. package/dist/functions/ProofUtil.d.ts +2 -1
  20. package/dist/functions/ProofUtil.d.ts.map +1 -1
  21. package/dist/functions/ProofUtil.js +6 -4
  22. package/dist/functions/ProofUtil.js.map +1 -1
  23. package/lib/AccessTokenClient.ts +10 -8
  24. package/lib/CredentialRequestClient.ts +4 -5
  25. package/lib/CredentialRequestClientBuilder.ts +19 -0
  26. package/lib/OpenID4VCIClient.ts +130 -38
  27. package/lib/ProofOfPossessionBuilder.ts +8 -0
  28. package/lib/__tests__/CredentialRequestClient.spec.ts +9 -2
  29. package/lib/__tests__/EBSIE2E.spec.test.ts +138 -0
  30. package/lib/__tests__/MetadataClient.spec.ts +4 -1
  31. package/lib/__tests__/OpenID4VCIClient.spec.ts +5 -0
  32. package/lib/__tests__/SdJwt.spec.ts +161 -0
  33. package/lib/__tests__/data/VciDataFixtures.ts +1 -1
  34. package/lib/functions/ProofUtil.ts +18 -4
  35. package/package.json +6 -4
@@ -8,15 +8,18 @@ import {
8
8
  CredentialResponse,
9
9
  CredentialSupported,
10
10
  EndpointMetadataResult,
11
+ getIssuerFromCredentialOfferPayload,
12
+ getSupportedCredentials,
13
+ getTypesFromCredentialSupported,
11
14
  JsonURIMode,
15
+ JWK,
16
+ KID_JWK_X5C_ERROR,
12
17
  OID4VCICredentialFormat,
13
18
  OpenId4VCIVersion,
14
19
  ProofOfPossessionCallbacks,
15
20
  PushedAuthorizationResponse,
16
21
  ResponseType,
17
22
  } from '@sphereon/oid4vci-common';
18
- import { getSupportedCredentials, getTypesFromCredentialSupported } from '@sphereon/oid4vci-common/dist/functions/IssuerMetadataUtils';
19
- import { CredentialSupportedTypeV1_0_08 } from '@sphereon/oid4vci-common/dist/types/v1_0_08.types';
20
23
  import { CredentialFormat } from '@sphereon/ssi-types';
21
24
  import Debug from 'debug';
22
25
 
@@ -39,27 +42,66 @@ interface AuthDetails {
39
42
 
40
43
  interface AuthRequestOpts {
41
44
  codeChallenge: string;
42
- codeChallengeMethod: CodeChallengeMethod;
45
+ codeChallengeMethod?: CodeChallengeMethod;
43
46
  authorizationDetails?: AuthDetails | AuthDetails[];
44
47
  redirectUri: string;
45
48
  scope?: string;
46
49
  }
47
50
 
48
51
  export class OpenID4VCIClient {
49
- private readonly _credentialOffer: CredentialOfferRequestWithBaseUrl;
52
+ private readonly _credentialOffer?: CredentialOfferRequestWithBaseUrl;
53
+ private _credentialIssuer: string;
50
54
  private _clientId?: string;
51
55
  private _kid: string | undefined;
56
+ private _jwk: JWK | undefined;
52
57
  private _alg: Alg | string | undefined;
53
58
  private _endpointMetadata: EndpointMetadataResult | undefined;
54
59
  private _accessTokenResponse: AccessTokenResponse | undefined;
55
60
 
56
- private constructor(credentialOffer: CredentialOfferRequestWithBaseUrl, kid?: string, alg?: Alg | string, clientId?: string) {
61
+ private constructor({
62
+ credentialOffer,
63
+ clientId,
64
+ kid,
65
+ alg,
66
+ credentialIssuer,
67
+ }: {
68
+ credentialOffer?: CredentialOfferRequestWithBaseUrl;
69
+ kid?: string;
70
+ alg?: Alg | string;
71
+ clientId?: string;
72
+ credentialIssuer?: string;
73
+ }) {
57
74
  this._credentialOffer = credentialOffer;
75
+ const issuer = credentialIssuer ?? (credentialOffer ? getIssuerFromCredentialOfferPayload(credentialOffer.credential_offer) : undefined);
76
+ if (!issuer) {
77
+ throw Error('No credential issuer supplied or deduced from offer');
78
+ }
79
+ this._credentialIssuer = issuer;
58
80
  this._kid = kid;
59
81
  this._alg = alg;
60
82
  this._clientId = clientId;
61
83
  }
62
84
 
85
+ public static async fromCredentialIssuer({
86
+ kid,
87
+ alg,
88
+ retrieveServerMetadata,
89
+ clientId,
90
+ credentialIssuer,
91
+ }: {
92
+ credentialIssuer: string;
93
+ kid?: string;
94
+ alg?: Alg | string;
95
+ retrieveServerMetadata?: boolean;
96
+ clientId?: string;
97
+ }) {
98
+ const client = new OpenID4VCIClient({ kid, alg, clientId, credentialIssuer });
99
+ if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
100
+ await client.retrieveServerMetadata();
101
+ }
102
+ return client;
103
+ }
104
+
63
105
  public static async fromURI({
64
106
  uri,
65
107
  kid,
@@ -75,7 +117,12 @@ export class OpenID4VCIClient {
75
117
  resolveOfferUri?: boolean;
76
118
  clientId?: string;
77
119
  }): Promise<OpenID4VCIClient> {
78
- const client = new OpenID4VCIClient(await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }), kid, alg, clientId);
120
+ const client = new OpenID4VCIClient({
121
+ credentialOffer: await CredentialOfferClient.fromURI(uri, { resolve: resolveOfferUri }),
122
+ kid,
123
+ alg,
124
+ clientId,
125
+ });
79
126
 
80
127
  if (retrieveServerMetadata === undefined || retrieveServerMetadata) {
81
128
  await client.retrieveServerMetadata();
@@ -86,16 +133,41 @@ export class OpenID4VCIClient {
86
133
  public async retrieveServerMetadata(): Promise<EndpointMetadataResult> {
87
134
  this.assertIssuerData();
88
135
  if (!this._endpointMetadata) {
89
- this._endpointMetadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
136
+ if (this.credentialOffer) {
137
+ this._endpointMetadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(this.credentialOffer);
138
+ } else if (this._credentialIssuer) {
139
+ this._endpointMetadata = await MetadataClient.retrieveAllMetadata(this._credentialIssuer);
140
+ } else {
141
+ throw Error(`Cannot retrieve issuer metadata without either a credential offer, or issuer value`);
142
+ }
90
143
  }
91
144
  return this.endpointMetadata;
92
145
  }
93
146
 
147
+ // todo: Unify this method with the par method
148
+
94
149
  public createAuthorizationRequestUrl({ codeChallengeMethod, codeChallenge, authorizationDetails, redirectUri, scope }: AuthRequestOpts): string {
95
150
  // Scope and authorization_details can be used in the same authorization request
96
151
  // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rar-23#name-relationship-to-scope-param
97
152
  if (!scope && !authorizationDetails) {
98
- throw Error('Please provide a scope or authorization_details');
153
+ if (!this.credentialOffer) {
154
+ throw Error('Please provide a scope or authorization_details');
155
+ }
156
+ const creds = this.credentialOffer.credential_offer.credentials;
157
+
158
+ authorizationDetails = creds
159
+ .flatMap((cred) => (typeof cred === 'string' ? this.getCredentialsSupported(true) : (cred as CredentialSupported)))
160
+ .map((cred) => {
161
+ return {
162
+ ...cred,
163
+ type: 'openid_credential',
164
+ locations: [this._credentialIssuer],
165
+ format: cred.format,
166
+ } satisfies AuthDetails;
167
+ });
168
+ if (authorizationDetails.length === 0) {
169
+ throw Error(`Could not create authorization details from credential offer. Please pass in explicit details`);
170
+ }
99
171
  }
100
172
  // todo: Probably can go with current logic in MetadataClient who will always set the authorization_endpoint when found
101
173
  // handling this because of the support for v1_0-08
@@ -117,7 +189,7 @@ export class OpenID4VCIClient {
117
189
 
118
190
  const queryObj: { [key: string]: string } = {
119
191
  response_type: ResponseType.AUTH_CODE,
120
- code_challenge_method: codeChallengeMethod,
192
+ code_challenge_method: codeChallengeMethod ?? CodeChallengeMethod.SHA256,
121
193
  code_challenge: codeChallenge,
122
194
  authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)),
123
195
  redirect_uri: redirectUri,
@@ -128,7 +200,7 @@ export class OpenID4VCIClient {
128
200
  queryObj['client_id'] = this.clientId;
129
201
  }
130
202
 
131
- if (this.credentialOffer.issuerState) {
203
+ if (this.credentialOffer?.issuerState) {
132
204
  queryObj['issuer_state'] = this.credentialOffer.issuerState;
133
205
  }
134
206
 
@@ -140,6 +212,7 @@ export class OpenID4VCIClient {
140
212
  });
141
213
  }
142
214
 
215
+ // todo: Unify this method with the create auth request url method
143
216
  public async acquirePushedAuthorizationRequestURI({
144
217
  codeChallengeMethod,
145
218
  codeChallenge,
@@ -173,7 +246,7 @@ export class OpenID4VCIClient {
173
246
 
174
247
  const queryObj: { [key: string]: string } = {
175
248
  response_type: ResponseType.AUTH_CODE,
176
- code_challenge_method: codeChallengeMethod,
249
+ code_challenge_method: codeChallengeMethod ?? CodeChallengeMethod.SHA256,
177
250
  code_challenge: codeChallenge,
178
251
  authorization_details: JSON.stringify(this.handleAuthorizationDetails(authorizationDetails)),
179
252
  redirect_uri: redirectUri,
@@ -184,7 +257,7 @@ export class OpenID4VCIClient {
184
257
  queryObj['client_id'] = this.clientId;
185
258
  }
186
259
 
187
- if (this.credentialOffer.issuerState) {
260
+ if (this.credentialOffer?.issuerState) {
188
261
  queryObj['issuer_state'] = this.credentialOffer.issuerState;
189
262
  }
190
263
 
@@ -249,6 +322,7 @@ export class OpenID4VCIClient {
249
322
  const response = await accessTokenClient.acquireAccessToken({
250
323
  credentialOffer: this.credentialOffer,
251
324
  metadata: this.endpointMetadata,
325
+ credentialIssuer: this.getIssuer(),
252
326
  pin,
253
327
  codeVerifier,
254
328
  code,
@@ -281,6 +355,7 @@ export class OpenID4VCIClient {
281
355
  proofCallbacks,
282
356
  format,
283
357
  kid,
358
+ jwk,
284
359
  alg,
285
360
  jti,
286
361
  }: {
@@ -288,25 +363,34 @@ export class OpenID4VCIClient {
288
363
  proofCallbacks: ProofOfPossessionCallbacks<any>;
289
364
  format?: CredentialFormat | OID4VCICredentialFormat;
290
365
  kid?: string;
366
+ jwk?: JWK;
291
367
  alg?: Alg | string;
292
368
  jti?: string;
293
369
  }): Promise<CredentialResponse> {
294
- if (alg) {
295
- this._alg = alg;
296
- }
297
- if (kid) {
298
- this._kid = kid;
370
+ if ([jwk, kid].filter((v) => v !== undefined).length > 1) {
371
+ throw new Error(KID_JWK_X5C_ERROR + `. jwk: ${jwk !== undefined}, kid: ${kid !== undefined}`);
299
372
  }
300
373
 
301
- const requestBuilder = CredentialRequestClientBuilder.fromCredentialOffer({
302
- credentialOffer: this.credentialOffer,
303
- metadata: this.endpointMetadata,
304
- });
374
+ if (alg) this._alg = alg;
375
+ if (jwk) this._jwk = jwk;
376
+ if (kid) this._kid = kid;
377
+
378
+ const requestBuilder = this.credentialOffer
379
+ ? CredentialRequestClientBuilder.fromCredentialOffer({
380
+ credentialOffer: this.credentialOffer,
381
+ metadata: this.endpointMetadata,
382
+ })
383
+ : CredentialRequestClientBuilder.fromCredentialIssuer({
384
+ credentialIssuer: this.getIssuer(),
385
+ credentialTypes,
386
+ metadata: this.endpointMetadata,
387
+ version: this.version(),
388
+ });
305
389
 
306
390
  requestBuilder.withTokenFromResponse(this.accessTokenResponse);
307
391
  if (this.endpointMetadata?.credentialIssuerMetadata) {
308
392
  const metadata = this.endpointMetadata.credentialIssuerMetadata;
309
- const types = Array.isArray(credentialTypes) ? credentialTypes.sort() : [credentialTypes];
393
+ const types = Array.isArray(credentialTypes) ? [...credentialTypes].sort() : [credentialTypes];
310
394
 
311
395
  if (metadata.credentials_supported && Array.isArray(metadata.credentials_supported)) {
312
396
  let typeSupported = false;
@@ -322,10 +406,11 @@ export class OpenID4VCIClient {
322
406
  });
323
407
 
324
408
  if (!typeSupported) {
325
- throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
409
+ console.log(`Not all credential types ${JSON.stringify(credentialTypes)} are present in metadata for ${this.getIssuer()}`);
410
+ // throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
326
411
  }
327
412
  } else if (metadata.credentials_supported && !Array.isArray(metadata.credentials_supported)) {
328
- const credentialsSupported = metadata.credentials_supported as CredentialSupportedTypeV1_0_08;
413
+ const credentialsSupported = metadata.credentials_supported;
329
414
  if (types.some((type) => !metadata.credentials_supported || !credentialsSupported[type])) {
330
415
  throw Error(`Not all credential types ${JSON.stringify(credentialTypes)} are supported by issuer ${this.getIssuer()}`);
331
416
  }
@@ -339,8 +424,14 @@ export class OpenID4VCIClient {
339
424
  version: this.version(),
340
425
  })
341
426
  .withIssuer(this.getIssuer())
342
- .withAlg(this.alg)
343
- .withKid(this.kid);
427
+ .withAlg(this.alg);
428
+
429
+ if (this._jwk) {
430
+ proofBuilder.withJWK(this._jwk);
431
+ }
432
+ if (this._kid) {
433
+ proofBuilder.withKid(this._kid);
434
+ }
344
435
 
345
436
  if (this.clientId) {
346
437
  proofBuilder.withClientId(this.clientId);
@@ -354,7 +445,7 @@ export class OpenID4VCIClient {
354
445
  format,
355
446
  });
356
447
  if (response.errorBody) {
357
- debug(`Credential request error:\r\n${response.errorBody}`);
448
+ debug(`Credential request error:\r\n${JSON.stringify(response.errorBody)}`);
358
449
  throw Error(
359
450
  `Retrieving a credential from ${this._endpointMetadata?.credential_endpoint} for issuer ${this.getIssuer()} failed with status: ${
360
451
  response.origResponse.status
@@ -387,7 +478,9 @@ export class OpenID4VCIClient {
387
478
  }
388
479
 
389
480
  getCredentialOfferTypes(): string[][] {
390
- if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
481
+ if (!this.credentialOffer) {
482
+ return [];
483
+ } else if (this.credentialOffer.version < OpenId4VCIVersion.VER_1_0_11) {
391
484
  const orig = this.credentialOffer.original_credential_offer as CredentialOfferPayloadV1_0_08;
392
485
  const types: string[] = typeof orig.credential_type === 'string' ? [orig.credential_type] : orig.credential_type;
393
486
  const result: string[][] = [];
@@ -399,8 +492,8 @@ export class OpenID4VCIClient {
399
492
  return [c];
400
493
  } else if ('types' in c) {
401
494
  return c.types;
402
- } else if ('vct' in c.credential_definition) {
403
- return [c.credential_definition.vct];
495
+ } else if ('vct' in c) {
496
+ return [c.vct];
404
497
  } else {
405
498
  return c.credential_definition.types;
406
499
  }
@@ -409,15 +502,15 @@ export class OpenID4VCIClient {
409
502
  }
410
503
 
411
504
  issuerSupportedFlowTypes(): AuthzFlowType[] {
412
- return this.credentialOffer.supportedFlows;
505
+ return this.credentialOffer?.supportedFlows ?? [AuthzFlowType.AUTHORIZATION_CODE_FLOW];
413
506
  }
414
507
 
415
- get credentialOffer(): CredentialOfferRequestWithBaseUrl {
508
+ get credentialOffer(): CredentialOfferRequestWithBaseUrl | undefined {
416
509
  return this._credentialOffer;
417
510
  }
418
511
 
419
512
  public version(): OpenId4VCIVersion {
420
- return this.credentialOffer.version;
513
+ return this.credentialOffer?.version ?? OpenId4VCIVersion.VER_1_0_11;
421
514
  }
422
515
 
423
516
  public get endpointMetadata(): EndpointMetadataResult {
@@ -443,9 +536,6 @@ export class OpenID4VCIClient {
443
536
  }
444
537
 
445
538
  get clientId(): string | undefined {
446
- /*if (!this._clientId) {
447
- throw Error('No client id present');
448
- }*/
449
539
  return this._clientId;
450
540
  }
451
541
 
@@ -457,7 +547,7 @@ export class OpenID4VCIClient {
457
547
 
458
548
  public getIssuer(): string {
459
549
  this.assertIssuerData();
460
- return this._endpointMetadata ? this.endpointMetadata.issuer : this.getIssuer();
550
+ return this._credentialIssuer!;
461
551
  }
462
552
 
463
553
  public getAccessTokenEndpoint(): string {
@@ -473,8 +563,10 @@ export class OpenID4VCIClient {
473
563
  }
474
564
 
475
565
  private assertIssuerData(): void {
476
- if (!this._credentialOffer) {
566
+ if (!this._credentialOffer && this.issuerSupportedFlowTypes().includes(AuthzFlowType.PRE_AUTHORIZED_CODE_FLOW)) {
477
567
  throw Error(`No issuance initiation or credential offer present`);
568
+ } else if (!this._credentialIssuer) {
569
+ throw Error(`No credential issuer value present`);
478
570
  }
479
571
  }
480
572
 
@@ -2,6 +2,7 @@ import {
2
2
  AccessTokenResponse,
3
3
  Alg,
4
4
  EndpointMetadata,
5
+ JWK,
5
6
  Jwt,
6
7
  NO_JWT_PROVIDED,
7
8
  OpenId4VCIVersion,
@@ -19,6 +20,7 @@ export class ProofOfPossessionBuilder<DIDDoc> {
19
20
  private readonly version: OpenId4VCIVersion;
20
21
 
21
22
  private kid?: string;
23
+ private jwk?: JWK;
22
24
  private clientId?: string;
23
25
  private issuer?: string;
24
26
  private jwt?: Jwt;
@@ -91,6 +93,11 @@ export class ProofOfPossessionBuilder<DIDDoc> {
91
93
  return this;
92
94
  }
93
95
 
96
+ withJWK(jwk: JWK): this {
97
+ this.jwk = jwk;
98
+ return this;
99
+ }
100
+
94
101
  withIssuer(issuer: string): this {
95
102
  this.issuer = issuer;
96
103
  return this;
@@ -182,6 +189,7 @@ export class ProofOfPossessionBuilder<DIDDoc> {
182
189
  {
183
190
  typ: this.typ ?? (this.version < OpenId4VCIVersion.VER_1_0_11 ? 'jwt' : 'openid4vci-proof+jwt'),
184
191
  kid: this.kid,
192
+ jwk: this.jwk,
185
193
  jti: this.jti,
186
194
  alg: this.alg,
187
195
  issuer: this.issuer,
@@ -128,7 +128,7 @@ describe('Credential Request Client ', () => {
128
128
  version: OpenId4VCIVersion.VER_1_0_08,
129
129
  });
130
130
  expect(credentialRequest.proof?.jwt?.includes(partialJWT)).toBeTruthy();
131
- expect(credentialRequest.format).toEqual('jwt_vc_json');
131
+ expect(credentialRequest.format).toEqual('jwt_vc');
132
132
  const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
133
133
  expect(result?.successBody?.credential).toEqual(mockedVC);
134
134
  });
@@ -157,8 +157,15 @@ describe('Credential Request Client ', () => {
157
157
  });
158
158
 
159
159
  describe('Credential Request Client with Walt.id ', () => {
160
- it('should have correct metadata endpoints', async function () {
160
+ beforeAll(() => {
161
+ nock.cleanAll();
162
+ });
163
+
164
+ afterEach(() => {
161
165
  nock.cleanAll();
166
+ });
167
+ it('should have correct metadata endpoints', async function () {
168
+ // nock.cleanAll();
162
169
  const WALT_IRR_URI =
163
170
  'openid-initiate-issuance://?issuer=https%3A%2F%2Fjff.walt.id%2Fissuer-api%2Foidc%2F&credential_type=OpenBadgeCredential&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhOTUyZjUxNi1jYWVmLTQ4YjMtODIxYy00OTRkYzgyNjljZjAiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.YE5DlalcLC2ChGEg47CQDaN1gTxbaQqSclIVqsSAUHE&user_pin_required=false';
164
171
  const credentialOffer = await CredentialOfferClient.fromURI(WALT_IRR_URI);
@@ -0,0 +1,138 @@
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: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossAuthorisedInTime') {
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 !== 'CTWalletCrossPreAuthorisedInTime') {
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
+ // Current conformance tests is not stable as changes are being applied it seems
104
+
105
+ it.skip(
106
+ 'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json',
107
+ async () => {
108
+ await test('CTWalletCrossPreAuthorisedInTime');
109
+ // await test('CTWalletCrossAuthorisedInTime');
110
+ },
111
+ UNIT_TEST_TIMEOUT,
112
+ );
113
+ });
114
+
115
+ async function getCredentialOffer(credentialType: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossAuthorisedInTime'): Promise<string> {
116
+ const credentialOffer = await fetch(
117
+ `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`,
118
+ {
119
+ method: 'GET',
120
+ headers: {
121
+ Accept: 'application/json',
122
+ 'Content-Type': 'application/json',
123
+ },
124
+ },
125
+ );
126
+
127
+ return await credentialOffer.text();
128
+ }
129
+
130
+ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
131
+ const importedJwk = await importJWK(jwk);
132
+ return await new SignJWT({ ...args.payload })
133
+ .setProtectedHeader({ ...args.header, kid: kid! })
134
+ .setIssuer(DID)
135
+ .setIssuedAt()
136
+ .setExpirationTime('2m')
137
+ .sign(importedJwk);
138
+ }
@@ -1,4 +1,6 @@
1
1
  import { getIssuerFromCredentialOfferPayload, WellKnownEndpoints } from '@sphereon/oid4vci-common';
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
+ // @ts-ignore
2
4
  import nock from 'nock';
3
5
 
4
6
  import { CredentialOfferClient } from '../CredentialOfferClient';
@@ -211,7 +213,8 @@ describe('Metadataclient with Walt-id should', () => {
211
213
  });
212
214
  });
213
215
 
214
- describe('Metadataclient with SpruceId should', () => {
216
+ // Spruce gives back 404's these days, so test is disabled
217
+ describe.skip('Metadataclient with SpruceId should', () => {
215
218
  beforeAll(() => {
216
219
  nock.cleanAll();
217
220
  });
@@ -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`;