@pagopa/io-react-native-wallet 2.0.0-next.2 → 2.0.0-next.4

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 (146) hide show
  1. package/lib/commonjs/credential/issuance/03-start-user-authorization.js +38 -24
  2. package/lib/commonjs/credential/issuance/03-start-user-authorization.js.map +1 -1
  3. package/lib/commonjs/credential/issuance/04-complete-user-authorization.js +75 -57
  4. package/lib/commonjs/credential/issuance/04-complete-user-authorization.js.map +1 -1
  5. package/lib/commonjs/credential/issuance/05-authorize-access.js +6 -10
  6. package/lib/commonjs/credential/issuance/05-authorize-access.js.map +1 -1
  7. package/lib/commonjs/credential/issuance/06-obtain-credential.js +43 -11
  8. package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
  9. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +51 -48
  10. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  11. package/lib/commonjs/credential/issuance/README.md +77 -45
  12. package/lib/commonjs/credential/issuance/const.js +1 -1
  13. package/lib/commonjs/credential/issuance/types.js +17 -10
  14. package/lib/commonjs/credential/issuance/types.js.map +1 -1
  15. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js +6 -13
  16. package/lib/commonjs/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  17. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js +10 -11
  18. package/lib/commonjs/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  19. package/lib/commonjs/credential/presentation/types.js +1 -1
  20. package/lib/commonjs/credential/presentation/types.js.map +1 -1
  21. package/lib/commonjs/credential/status/README.md +0 -1
  22. package/lib/commonjs/sd-jwt/__test__/index.test.js +11 -15
  23. package/lib/commonjs/sd-jwt/__test__/index.test.js.map +1 -1
  24. package/lib/commonjs/sd-jwt/__test__/types.test.js +5 -2
  25. package/lib/commonjs/sd-jwt/__test__/types.test.js.map +1 -1
  26. package/lib/commonjs/sd-jwt/__test__/utils.test.js +37 -0
  27. package/lib/commonjs/sd-jwt/__test__/utils.test.js.map +1 -0
  28. package/lib/commonjs/sd-jwt/index.js +26 -1
  29. package/lib/commonjs/sd-jwt/index.js.map +1 -1
  30. package/lib/commonjs/sd-jwt/types.js +71 -8
  31. package/lib/commonjs/sd-jwt/types.js.map +1 -1
  32. package/lib/commonjs/sd-jwt/utils.js +64 -0
  33. package/lib/commonjs/sd-jwt/utils.js.map +1 -0
  34. package/lib/commonjs/trust/types.js +18 -13
  35. package/lib/commonjs/trust/types.js.map +1 -1
  36. package/lib/commonjs/utils/par.js +32 -22
  37. package/lib/commonjs/utils/par.js.map +1 -1
  38. package/lib/commonjs/utils/pop.js +1 -1
  39. package/lib/commonjs/utils/pop.js.map +1 -1
  40. package/lib/commonjs/wallet-instance-attestation/types.js +4 -1
  41. package/lib/commonjs/wallet-instance-attestation/types.js.map +1 -1
  42. package/lib/module/credential/issuance/03-start-user-authorization.js +38 -24
  43. package/lib/module/credential/issuance/03-start-user-authorization.js.map +1 -1
  44. package/lib/module/credential/issuance/04-complete-user-authorization.js +76 -58
  45. package/lib/module/credential/issuance/04-complete-user-authorization.js.map +1 -1
  46. package/lib/module/credential/issuance/05-authorize-access.js +6 -10
  47. package/lib/module/credential/issuance/05-authorize-access.js.map +1 -1
  48. package/lib/module/credential/issuance/06-obtain-credential.js +44 -12
  49. package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
  50. package/lib/module/credential/issuance/07-verify-and-parse-credential.js +51 -48
  51. package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  52. package/lib/module/credential/issuance/README.md +77 -45
  53. package/lib/module/credential/issuance/const.js +1 -1
  54. package/lib/module/credential/issuance/types.js +13 -8
  55. package/lib/module/credential/issuance/types.js.map +1 -1
  56. package/lib/module/credential/presentation/07-evaluate-dcql-query.js +6 -13
  57. package/lib/module/credential/presentation/07-evaluate-dcql-query.js.map +1 -1
  58. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js +10 -11
  59. package/lib/module/credential/presentation/07-evaluate-input-descriptor.js.map +1 -1
  60. package/lib/module/credential/presentation/types.js +1 -1
  61. package/lib/module/credential/presentation/types.js.map +1 -1
  62. package/lib/module/credential/status/README.md +0 -1
  63. package/lib/module/sd-jwt/__test__/index.test.js +11 -16
  64. package/lib/module/sd-jwt/__test__/index.test.js.map +1 -1
  65. package/lib/module/sd-jwt/__test__/types.test.js +5 -2
  66. package/lib/module/sd-jwt/__test__/types.test.js.map +1 -1
  67. package/lib/module/sd-jwt/__test__/utils.test.js +35 -0
  68. package/lib/module/sd-jwt/__test__/utils.test.js.map +1 -0
  69. package/lib/module/sd-jwt/index.js +7 -1
  70. package/lib/module/sd-jwt/index.js.map +1 -1
  71. package/lib/module/sd-jwt/types.js +70 -7
  72. package/lib/module/sd-jwt/types.js.map +1 -1
  73. package/lib/module/sd-jwt/utils.js +57 -0
  74. package/lib/module/sd-jwt/utils.js.map +1 -0
  75. package/lib/module/trust/types.js +18 -13
  76. package/lib/module/trust/types.js.map +1 -1
  77. package/lib/module/utils/par.js +29 -20
  78. package/lib/module/utils/par.js.map +1 -1
  79. package/lib/module/utils/pop.js +1 -1
  80. package/lib/module/utils/pop.js.map +1 -1
  81. package/lib/module/wallet-instance-attestation/types.js +4 -1
  82. package/lib/module/wallet-instance-attestation/types.js.map +1 -1
  83. package/lib/typescript/client/generated/wallet-provider.d.ts +12 -12
  84. package/lib/typescript/credential/issuance/01-start-flow.d.ts +2 -2
  85. package/lib/typescript/credential/issuance/01-start-flow.d.ts.map +1 -1
  86. package/lib/typescript/credential/issuance/03-start-user-authorization.d.ts +7 -6
  87. package/lib/typescript/credential/issuance/03-start-user-authorization.d.ts.map +1 -1
  88. package/lib/typescript/credential/issuance/04-complete-user-authorization.d.ts +7 -14
  89. package/lib/typescript/credential/issuance/04-complete-user-authorization.d.ts.map +1 -1
  90. package/lib/typescript/credential/issuance/05-authorize-access.d.ts.map +1 -1
  91. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts +10 -5
  92. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
  93. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts +3 -2
  94. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
  95. package/lib/typescript/credential/issuance/const.d.ts +1 -1
  96. package/lib/typescript/credential/issuance/types.d.ts +49 -26
  97. package/lib/typescript/credential/issuance/types.d.ts.map +1 -1
  98. package/lib/typescript/credential/presentation/01-start-flow.d.ts +2 -2
  99. package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts +4 -3
  100. package/lib/typescript/credential/presentation/07-evaluate-dcql-query.d.ts.map +1 -1
  101. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts +9 -5
  102. package/lib/typescript/credential/presentation/07-evaluate-input-descriptor.d.ts.map +1 -1
  103. package/lib/typescript/credential/presentation/types.d.ts +3 -4
  104. package/lib/typescript/credential/presentation/types.d.ts.map +1 -1
  105. package/lib/typescript/credential/status/types.d.ts +4 -4
  106. package/lib/typescript/pid/sd-jwt/types.d.ts +7 -7
  107. package/lib/typescript/sd-jwt/__test__/utils.test.d.ts +2 -0
  108. package/lib/typescript/sd-jwt/__test__/utils.test.d.ts.map +1 -0
  109. package/lib/typescript/sd-jwt/index.d.ts +69 -28
  110. package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
  111. package/lib/typescript/sd-jwt/types.d.ts +264 -31
  112. package/lib/typescript/sd-jwt/types.d.ts.map +1 -1
  113. package/lib/typescript/sd-jwt/utils.d.ts +18 -0
  114. package/lib/typescript/sd-jwt/utils.d.ts.map +1 -0
  115. package/lib/typescript/trust/build-chain.d.ts +30 -14
  116. package/lib/typescript/trust/build-chain.d.ts.map +1 -1
  117. package/lib/typescript/trust/types.d.ts +322 -158
  118. package/lib/typescript/trust/types.d.ts.map +1 -1
  119. package/lib/typescript/utils/par.d.ts +29 -13
  120. package/lib/typescript/utils/par.d.ts.map +1 -1
  121. package/lib/typescript/wallet-instance-attestation/types.d.ts +9 -9
  122. package/lib/typescript/wallet-instance-attestation/types.d.ts.map +1 -1
  123. package/package.json +1 -1
  124. package/src/credential/issuance/01-start-flow.ts +2 -2
  125. package/src/credential/issuance/03-start-user-authorization.ts +57 -38
  126. package/src/credential/issuance/04-complete-user-authorization.ts +79 -85
  127. package/src/credential/issuance/05-authorize-access.ts +5 -11
  128. package/src/credential/issuance/06-obtain-credential.ts +56 -23
  129. package/src/credential/issuance/07-verify-and-parse-credential.ts +54 -62
  130. package/src/credential/issuance/README.md +77 -45
  131. package/src/credential/issuance/const.ts +1 -1
  132. package/src/credential/issuance/types.ts +19 -8
  133. package/src/credential/presentation/07-evaluate-dcql-query.ts +16 -17
  134. package/src/credential/presentation/07-evaluate-input-descriptor.ts +19 -16
  135. package/src/credential/presentation/types.ts +1 -2
  136. package/src/credential/status/README.md +0 -1
  137. package/src/sd-jwt/__test__/index.test.ts +8 -29
  138. package/src/sd-jwt/__test__/types.test.ts +6 -2
  139. package/src/sd-jwt/__test__/utils.test.ts +37 -0
  140. package/src/sd-jwt/index.ts +7 -1
  141. package/src/sd-jwt/types.ts +68 -7
  142. package/src/sd-jwt/utils.ts +73 -0
  143. package/src/trust/types.ts +23 -17
  144. package/src/utils/par.ts +37 -21
  145. package/src/utils/pop.ts +1 -1
  146. package/src/wallet-instance-attestation/types.ts +3 -1
@@ -10,16 +10,16 @@ import { IssuerResponseError, ValidationFailed } from "../../utils/errors";
10
10
  import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
11
11
  import {
12
12
  decode,
13
- encodeBase64,
14
13
  SignJWT,
15
14
  type CryptoContext,
16
15
  } from "@pagopa/io-react-native-jwt";
17
- import { RequestObject } from "../presentation/types";
18
- import { v4 as uuidv4 } from "uuid";
16
+ import { type RemotePresentation, RequestObject } from "../presentation/types";
19
17
  import { ResponseUriResultShape } from "./types";
20
18
  import { getJwtFromFormPost } from "../../utils/decoder";
21
19
  import { AuthorizationError, AuthorizationIdpError } from "./errors";
22
20
  import { LogLevel, Logger } from "../../utils/logging";
21
+ import { Presentation } from "..";
22
+ import type { DcqlQuery } from "dcql";
23
23
 
24
24
  /**
25
25
  * The interface of the phase to complete User authorization via strong identification when the response mode is "query" and the request credential is a PersonIdentificationData.
@@ -30,11 +30,10 @@ export type CompleteUserAuthorizationWithQueryMode = (
30
30
 
31
31
  export type CompleteUserAuthorizationWithFormPostJwtMode = (
32
32
  requestObject: Out<GetRequestedCredentialToBePresented>,
33
+ pid: string,
33
34
  context: {
34
35
  wiaCryptoContext: CryptoContext;
35
36
  pidCryptoContext: CryptoContext;
36
- pid: string;
37
- walletInstanceAttestation: string;
38
37
  appFetch?: GlobalFetch["fetch"];
39
38
  }
40
39
  ) => Promise<AuthorizationResult>;
@@ -158,103 +157,54 @@ export const getRequestedCredentialToBePresented: GetRequestedCredentialToBePres
158
157
  };
159
158
 
160
159
  /**
161
- * WARNING: This function must be called after {@link startUserAuthorization}. The next function to be called is {@link completeUserAuthorizationWithFormPostJwtMode}.
160
+ * WARNING: This function must be called after {@link getRequestedCredentialToBePresented}. The next function to be called is {@link authorizeAccess}.
162
161
  * The interface of the phase to complete User authorization via presentation of existing credentials when the response mode is "form_post.jwt".
163
- * It is used as a first step to complete the user authorization by obtaining the requested credential to be presented from the authorization server.
164
- * The information is obtained by performing a GET request to the authorization endpoint with request_uri and client_id parameters.
165
- * @param issuerRequestUri the URI of the issuer where the request is sent
166
- * @param clientId Identifies the current client across all the requests of the issuing flow returned by {@link startUserAuthorization}
167
- * @param issuerConf The issuer configuration returned by {@link evaluateIssuerTrust}
168
- * @param context.walletInstanceAccestation the Wallet Instance's attestation to be presented
169
- * @param context.pid the PID to be presented
170
- * @param context.wiaCryptoContext The Wallet Instance's crypto context associated with the walletInstanceAttestation parameter
171
- * @param context.pidCryptoContext The PID crypto context associated with the pid parameter
172
- * @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
162
+ * The information is obtained by performing a POST request to the endpoint received in the response_uri field of the requestObject, where the Authorization Response payload is posted.
163
+ * Following this,the redirect_uri from the response is used to obtain the final authorization response.
164
+ * @param requestObject - The request object containing the necessary parameters for authorization.
165
+ * @param pid The `PID` that must be presented for the issuance of credentials.
166
+ * @param appFetch (optional) fetch api implementation. Default: built-in fetch
173
167
  * @throws {ValidationFailed} if an error while validating the response
174
168
  * @returns the authorization response which contains code, state and iss
175
169
  */
176
170
  export const completeUserAuthorizationWithFormPostJwtMode: CompleteUserAuthorizationWithFormPostJwtMode =
177
- async (requestObject, ctx) => {
171
+ async (
172
+ requestObject,
173
+ pid,
174
+ { wiaCryptoContext, pidCryptoContext, appFetch = fetch }
175
+ ) => {
178
176
  Logger.log(
179
177
  LogLevel.DEBUG,
180
178
  `The requeste credential is not a PersonIdentificationData, completing the user authorization with form_post.jwt mode`
181
179
  );
182
180
 
183
- const {
184
- wiaCryptoContext,
185
- pidCryptoContext,
186
- pid,
187
- walletInstanceAttestation,
188
- appFetch = fetch,
189
- } = ctx;
181
+ if (!requestObject.dcql_query) {
182
+ throw new Error("Invalid request object");
183
+ }
190
184
 
191
- const wiaWpToken = await new SignJWT(wiaCryptoContext)
192
- .setProtectedHeader({
193
- alg: "ES256",
194
- typ: "JWT",
195
- })
196
- .setPayload({
197
- vp: walletInstanceAttestation,
198
- jti: uuidv4().toString(),
199
- nonce: requestObject.nonce,
200
- })
201
- .setIssuedAt()
202
- .setExpirationTime("5m")
203
- .setAudience(requestObject.response_uri)
204
- .sign();
185
+ const dcqlQueryResult = Presentation.evaluateDcqlQuery(
186
+ [[pidCryptoContext, pid]],
187
+ requestObject.dcql_query as DcqlQuery
188
+ );
205
189
 
206
- const pidWpToken = await new SignJWT(pidCryptoContext)
207
- .setProtectedHeader({
208
- alg: "ES256",
209
- typ: "JWT",
190
+ const credentialsToPresent = dcqlQueryResult.map(
191
+ ({ requiredDisclosures, ...rest }) => ({
192
+ ...rest,
193
+ requestedClaims: requiredDisclosures.map(([, claimName]) => claimName),
210
194
  })
211
- .setPayload({
212
- vp: pid,
213
- jti: uuidv4().toString(),
214
- nonce: requestObject.nonce,
215
- })
216
- .setIssuedAt()
217
- .setExpirationTime("5m")
218
- .setAudience(requestObject.response_uri)
219
- .sign();
220
-
221
- Logger.log(
222
- LogLevel.DEBUG,
223
- `Wallet instance attestation JWT token: ${wiaWpToken}`
224
195
  );
225
196
 
226
- /* The path parameter refers to the vp_token variable of the authzResponsePayload and must point to the plain credential which
227
- * is cointaned in the `vp` property of the signed jwt token payload
228
- */
229
- const presentationSubmission = {
230
- definition_id: `${uuidv4()}`,
231
- id: `${uuidv4()}`,
232
- descriptor_map: [
233
- {
234
- id: "PersonIdentificationData",
235
- path: "$.vp_token[0].vp",
236
- format: "vc+sd-jwt",
237
- },
238
- {
239
- id: "WalletAttestation",
240
- path: "$.vp_token[1].vp",
241
- format: "jwt",
242
- },
243
- ],
244
- };
245
-
246
- Logger.log(
247
- LogLevel.DEBUG,
248
- `Presentation submission: ${JSON.stringify(presentationSubmission)}`
197
+ const remotePresentations = await Presentation.prepareRemotePresentations(
198
+ credentialsToPresent,
199
+ requestObject.nonce,
200
+ requestObject.client_id
249
201
  );
250
202
 
251
- const authzResponsePayload = encodeBase64(
252
- JSON.stringify({
253
- state: requestObject.state,
254
- presentation_submission: presentationSubmission,
255
- vp_token: [pidWpToken, wiaWpToken],
256
- })
257
- );
203
+ const authzResponsePayload = await createAuthzResponsePayload({
204
+ state: requestObject.state,
205
+ remotePresentations,
206
+ wiaCryptoContext,
207
+ });
258
208
 
259
209
  Logger.log(
260
210
  LogLevel.DEBUG,
@@ -334,3 +284,47 @@ export const parseAuthorizationResponse = (
334
284
  }
335
285
  return authResParsed.data;
336
286
  };
287
+
288
+ /**
289
+ * Creates the authorization response payload to be sent.
290
+ * This payload includes the state and the VP tokens for the presented credentials.
291
+ * The payload is encoded in Base64.
292
+ * @param state - The state parameter from the request object (optional).
293
+ * @param remotePresentations - An array of remote presentations containing credential IDs and their corresponding VP tokens.
294
+ * @returns The Base64 encoded authorization response payload.
295
+ */
296
+ const createAuthzResponsePayload = async ({
297
+ state,
298
+ remotePresentations,
299
+ wiaCryptoContext,
300
+ }: {
301
+ state?: string;
302
+ remotePresentations: RemotePresentation[];
303
+ wiaCryptoContext: CryptoContext;
304
+ }): Promise<string> => {
305
+ const { kid } = await wiaCryptoContext.getPublicKey();
306
+
307
+ return new SignJWT(wiaCryptoContext)
308
+ .setProtectedHeader({
309
+ typ: "jwt",
310
+ kid,
311
+ })
312
+ .setPayload({
313
+ /**
314
+ * TODO [SIW-2264]: `state` coming from `requestObject` is marked as `optional`
315
+ * At the moment, it is not entirely clear whether this value can indeed be omitted
316
+ * and, if so, what the consequences of its absence might be.
317
+ */
318
+ ...(state ? { state } : {}),
319
+ vp_token: remotePresentations.reduce(
320
+ (vp_token, { credentialId, vpToken }) => ({
321
+ ...vp_token,
322
+ [credentialId]: vpToken,
323
+ }),
324
+ {}
325
+ ),
326
+ })
327
+ .setIssuedAt()
328
+ .setExpirationTime("1h")
329
+ .sign();
330
+ };
@@ -6,7 +6,6 @@ import { v4 as uuidv4 } from "uuid";
6
6
  import { createPopToken } from "../../utils/pop";
7
7
  import * as WalletInstanceAttestation from "../../wallet-instance-attestation";
8
8
  import type { CryptoContext } from "@pagopa/io-react-native-jwt";
9
- import { ASSERTION_TYPE } from "./const";
10
9
  import { TokenResponse } from "./types";
11
10
  import { IssuerResponseError, ValidationFailed } from "../../utils/errors";
12
11
  import type { CompleteUserAuthorizationWithQueryMode } from "./04-complete-user-authorization";
@@ -47,7 +46,7 @@ export type AuthorizeAccess = (
47
46
  export const authorizeAccess: AuthorizeAccess = async (
48
47
  issuerConf,
49
48
  code,
50
- clientId,
49
+ _,
51
50
  redirectUri,
52
51
  codeVerifier,
53
52
  context
@@ -58,11 +57,7 @@ export const authorizeAccess: AuthorizeAccess = async (
58
57
  wiaCryptoContext,
59
58
  dPopCryptoContext,
60
59
  } = context;
61
-
62
- const parEndpoint =
63
- issuerConf.oauth_authorization_server.pushed_authorization_request_endpoint;
64
- const parUrl = new URL(parEndpoint);
65
- const aud = `${parUrl.protocol}//${parUrl.hostname}`;
60
+ const aud = issuerConf.openid_credential_issuer.credential_issuer;
66
61
  const iss = WalletInstanceAttestation.decode(walletInstanceAttestation)
67
62
  .payload.cnf.jwk.kid;
68
63
 
@@ -92,12 +87,9 @@ export const authorizeAccess: AuthorizeAccess = async (
92
87
 
93
88
  const requestBody = {
94
89
  grant_type: "authorization_code",
95
- client_id: clientId,
96
90
  code,
97
- redirect_uri: redirectUri,
98
91
  code_verifier: codeVerifier,
99
- client_assertion_type: ASSERTION_TYPE,
100
- client_assertion: walletInstanceAttestation + "~" + signedWiaPoP,
92
+ redirect_uri: redirectUri,
101
93
  };
102
94
 
103
95
  const authorizationRequestFormBody = new URLSearchParams(requestBody);
@@ -112,6 +104,8 @@ export const authorizeAccess: AuthorizeAccess = async (
112
104
  headers: {
113
105
  "Content-Type": "application/x-www-form-urlencoded",
114
106
  DPoP: tokenRequestSignedDPop,
107
+ "OAuth-Client-Attestation": walletInstanceAttestation,
108
+ "OAuth-Client-Attestation-PoP": signedWiaPoP,
115
109
  },
116
110
  body: authorizationRequestFormBody.toString(),
117
111
  })
@@ -14,7 +14,7 @@ import {
14
14
  UnexpectedStatusCodeError,
15
15
  ValidationFailed,
16
16
  } from "../../utils/errors";
17
- import { CredentialResponse } from "./types";
17
+ import { CredentialResponse, NonceResponse } from "./types";
18
18
  import { createDPopToken } from "../../utils/dpop";
19
19
  import { v4 as uuidv4 } from "uuid";
20
20
  import { LogLevel, Logger } from "../../utils/logging";
@@ -23,14 +23,20 @@ export type ObtainCredential = (
23
23
  issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
24
24
  accessToken: Out<AuthorizeAccess>["accessToken"],
25
25
  clientId: Out<StartUserAuthorization>["clientId"],
26
- credentialDefinition: Out<StartUserAuthorization>["credentialDefinition"],
26
+ credentialDefinition: {
27
+ credential_configuration_id: string;
28
+ credential_identifier?: string;
29
+ },
27
30
  context: {
28
31
  dPopCryptoContext: CryptoContext;
29
32
  credentialCryptoContext: CryptoContext;
30
33
  appFetch?: GlobalFetch["fetch"];
31
34
  },
32
35
  operationType?: "reissuing"
33
- ) => Promise<CredentialResponse>;
36
+ ) => Promise<{
37
+ credential: string;
38
+ format: string;
39
+ }>;
34
40
 
35
41
  export const createNonceProof = async (
36
42
  nonce: string,
@@ -63,11 +69,11 @@ export const createNonceProof = async (
63
69
  * @param issuerConf The issuer configuration returned by {@link evaluateIssuerTrust}
64
70
  * @param accessToken The access token response returned by {@link authorizeAccess}
65
71
  * @param clientId The client id returned by {@link startUserAuthorization}
66
- * @param credentialDefinition The credential definition of the credential to be obtained returned by {@link startUserAuthorization}
67
- * @param tokenRequestSignedDPop The DPoP signed token request returned by {@link authorizeAccess}
72
+ * @param credentialDefinition The credential definition of the credential to be obtained returned by {@link authorizeAccess}
68
73
  * @param context.credentialCryptoContext The crypto context used to obtain the credential
69
74
  * @param context.dPopCryptoContext The DPoP crypto context
70
75
  * @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
76
+ * @param operationType Specify the type of credential issuance (used for reissuing)
71
77
  * @returns The credential response containing the credential
72
78
  */
73
79
  export const obtainCredential: ObtainCredential = async (
@@ -83,8 +89,21 @@ export const obtainCredential: ObtainCredential = async (
83
89
  appFetch = fetch,
84
90
  dPopCryptoContext,
85
91
  } = context;
92
+ const { credential_configuration_id, credential_identifier } =
93
+ credentialDefinition;
86
94
 
87
95
  const credentialUrl = issuerConf.openid_credential_issuer.credential_endpoint;
96
+ const issuerUrl = issuerConf.oauth_authorization_server.issuer;
97
+ const nonceUrl = issuerConf.openid_credential_issuer.nonce_endpoint;
98
+
99
+ // Fetch the nonce from the Credential Issuer
100
+ const { c_nonce } = await appFetch(nonceUrl, {
101
+ method: "POST",
102
+ headers: { "Content-Type": "application/json" },
103
+ })
104
+ .then(hasStatusOrThrow(200))
105
+ .then((res) => res.json())
106
+ .then((body) => NonceResponse.parse(body));
88
107
 
89
108
  /**
90
109
  * JWT proof token to bind the request nonce to the key that will bind the holder User with the Credential
@@ -92,9 +111,9 @@ export const obtainCredential: ObtainCredential = async (
92
111
  * @see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-proof-types
93
112
  */
94
113
  const signedNonceProof = await createNonceProof(
95
- accessToken.c_nonce,
114
+ c_nonce,
96
115
  clientId,
97
- credentialUrl,
116
+ issuerUrl,
98
117
  credentialCryptoContext
99
118
  );
100
119
 
@@ -103,10 +122,10 @@ export const obtainCredential: ObtainCredential = async (
103
122
  // Validation of accessTokenResponse.authorization_details if contain credentialDefinition
104
123
  const containsCredentialDefinition = accessToken.authorization_details.some(
105
124
  (c) =>
106
- c.credential_configuration_id ===
107
- credentialDefinition.credential_configuration_id &&
108
- c.format === credentialDefinition.format &&
109
- c.type === credentialDefinition.type
125
+ c.credential_configuration_id === credential_configuration_id &&
126
+ (credential_identifier
127
+ ? c.credential_identifiers.includes(credential_identifier)
128
+ : true)
110
129
  );
111
130
 
112
131
  if (!containsCredentialDefinition) {
@@ -120,17 +139,21 @@ export const obtainCredential: ObtainCredential = async (
120
139
  });
121
140
  }
122
141
 
123
- /** The credential request body */
124
- const credentialRequestFormBody = {
125
- credential_definition: {
126
- type: [credentialDefinition.credential_configuration_id],
127
- },
128
- format: credentialDefinition.format,
129
- proof: {
130
- jwt: signedNonceProof,
131
- proof_type: "jwt",
132
- },
133
- };
142
+ /**
143
+ * The credential request body.
144
+ * We accept both `credential_identifier` (recommended) and `credential_configuration_id`
145
+ * when the Authorization Server does not support `credential_identifier`.
146
+ * @see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-15.html#section-3.3.4
147
+ */
148
+ const credentialRequestFormBody = credential_identifier
149
+ ? {
150
+ credential_identifier: credential_identifier,
151
+ proof: { jwt: signedNonceProof, proof_type: "jwt" },
152
+ }
153
+ : {
154
+ credential_configuration_id: credential_configuration_id,
155
+ proof: { jwt: signedNonceProof, proof_type: "jwt" },
156
+ };
134
157
 
135
158
  Logger.log(
136
159
  LogLevel.DEBUG,
@@ -180,7 +203,17 @@ export const obtainCredential: ObtainCredential = async (
180
203
  `Credential Response: ${JSON.stringify(credentialRes.data)}`
181
204
  );
182
205
 
183
- return credentialRes.data;
206
+ // Extract the format corresponding to the credential_configuration_id used
207
+ const issuerCredentialConfig =
208
+ issuerConf.openid_credential_issuer.credential_configurations_supported[
209
+ credential_configuration_id
210
+ ];
211
+
212
+ // TODO: [SIW-2264] Handle multiple credentials
213
+ return {
214
+ credential: credentialRes.data.credentials.at(0)!.credential,
215
+ format: issuerCredentialConfig!.format,
216
+ };
184
217
  };
185
218
 
186
219
  /**
@@ -9,10 +9,14 @@ import type { JWK } from "../../utils/jwk";
9
9
  import type { ObtainCredential } from "./06-obtain-credential";
10
10
  import { LogLevel, Logger } from "../../utils/logging";
11
11
 
12
+ type IssuerConf = Out<EvaluateIssuerTrust>["issuerConf"];
13
+ type CredentialConf =
14
+ IssuerConf["openid_credential_issuer"]["credential_configurations_supported"][string];
15
+
12
16
  export type VerifyAndParseCredential = (
13
- issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
17
+ issuerConf: IssuerConf,
14
18
  credential: Out<ObtainCredential>["credential"],
15
- format: Out<ObtainCredential>["format"],
19
+ credentialConfigurationId: string,
16
20
  context: {
17
21
  credentialCryptoContext: CryptoContext;
18
22
  /**
@@ -54,54 +58,35 @@ type DecodedSdJwtCredential = Out<typeof verifySdJwt> & {
54
58
  };
55
59
 
56
60
  const parseCredentialSdJwt = (
57
- // the list of supported credentials, as defined in the issuer configuration
58
- credentials_supported: Out<EvaluateIssuerTrust>["issuerConf"]["openid_credential_issuer"]["credential_configurations_supported"],
61
+ // The credential configuration to use to parse the provided credential
62
+ credentialConfig: CredentialConf,
59
63
  { sdJwt, disclosures }: DecodedSdJwtCredential,
60
64
  ignoreMissingAttributes: boolean = false,
61
65
  includeUndefinedAttributes: boolean = false
62
66
  ): ParsedCredential => {
63
- const credentialSubject = credentials_supported[sdJwt.payload.vct];
64
-
65
- if (!credentialSubject) {
66
- Logger.log(
67
- LogLevel.ERROR,
68
- `Credential type not supported by the issuer: ${sdJwt.payload.vct}`
69
- );
70
- throw new IoWalletError("Credential type not supported by the issuer");
67
+ if (credentialConfig.format !== sdJwt.header.typ) {
68
+ const message = `Received credential is of an unknwown type. Expected one of [${credentialConfig.format}], received '${sdJwt.header.typ}'`;
69
+ Logger.log(LogLevel.ERROR, message);
70
+ throw new IoWalletError(message);
71
71
  }
72
72
 
73
- if (credentialSubject.format !== sdJwt.header.typ) {
74
- Logger.log(
75
- LogLevel.ERROR,
76
- `Received credential is of an unknwown type. Expected one of [${credentialSubject.format}], received '${sdJwt.header.typ}'`
77
- );
78
- throw new IoWalletError(
79
- `Received credential is of an unknwown type. Expected one of [${credentialSubject.format}], received '${sdJwt.header.typ}', `
80
- );
81
- }
82
-
83
- // transfrom a record { key: value } in an iterable of pairs [key, value]
84
- if (!credentialSubject.claims) {
73
+ if (!credentialConfig.claims) {
85
74
  Logger.log(LogLevel.ERROR, "Missing claims in the credential subject");
86
75
  throw new IoWalletError("Missing claims in the credential subject"); // TODO [SIW-1268]: should not be optional
87
76
  }
88
- const attrDefinitions = Object.entries(credentialSubject.claims);
77
+ const attrDefinitions = credentialConfig.claims;
89
78
 
90
79
  // the key of the attribute defintion must match the disclosure's name
91
80
  const attrsNotInDisclosures = attrDefinitions.filter(
92
- ([attrKey]) => !disclosures.some(([, name]) => name === attrKey)
81
+ (definition) => !disclosures.some(([, name]) => name === definition.path[0]) // Ignore nested paths for now, see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-15.html#name-claims-path-pointer
93
82
  );
94
83
  if (attrsNotInDisclosures.length > 0) {
95
- const missing = attrsNotInDisclosures.map((_) => _[0 /* key */]).join(", ");
84
+ const missing = attrsNotInDisclosures.map((_) => _.path[0]).join(", ");
96
85
  const received = disclosures.map((_) => _[1 /* name */]).join(", ");
97
86
  if (!ignoreMissingAttributes) {
98
- Logger.log(
99
- LogLevel.ERROR,
100
- `Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
101
- );
102
- throw new IoWalletError(
103
- `Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
104
- );
87
+ const message = `Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`;
88
+ Logger.log(LogLevel.ERROR, message);
89
+ throw new IoWalletError(message);
105
90
  }
106
91
  }
107
92
 
@@ -111,13 +96,13 @@ const parseCredentialSdJwt = (
111
96
  attrDefinitions
112
97
  // retrieve the value from the disclosure set
113
98
  .map(
114
- ([attrKey, definition]) =>
99
+ ({ path, ...definition }) =>
115
100
  [
116
- attrKey,
101
+ path[0],
117
102
  {
118
103
  ...definition,
119
104
  value: disclosures.find(
120
- (_) => _[1 /* name */] === attrKey
105
+ (_) => _[1 /* name */] === path[0]
121
106
  )?.[2 /* value */],
122
107
  },
123
108
  ] as const
@@ -186,30 +171,18 @@ async function verifyCredentialSdJwt(
186
171
  const { cnf } = decodedCredential.sdJwt.payload;
187
172
 
188
173
  if (!cnf.jwk.kid || cnf.jwk.kid !== holderBindingKey.kid) {
189
- Logger.log(
190
- LogLevel.ERROR,
191
- `Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${decodedCredential.sdJwt.payload.cnf.jwk.kid}`
192
- );
193
- throw new IoWalletError(
194
- `Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${decodedCredential.sdJwt.payload.cnf.jwk.kid}`
195
- );
174
+ const message = `Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${decodedCredential.sdJwt.payload.cnf.jwk.kid}`;
175
+ Logger.log(LogLevel.ERROR, message);
176
+ throw new IoWalletError(message);
196
177
  }
197
178
 
198
179
  return decodedCredential;
199
180
  }
200
181
 
201
- // utility type that specialize VerifyAndParseCredential for given format
202
- type WithFormat<Format extends Parameters<VerifyAndParseCredential>[2]> = (
203
- _0: Parameters<VerifyAndParseCredential>[0],
204
- _1: Parameters<VerifyAndParseCredential>[1],
205
- _2: Format,
206
- _3: Parameters<VerifyAndParseCredential>[3]
207
- ) => ReturnType<VerifyAndParseCredential>;
208
-
209
- const verifyAndParseCredentialSdJwt: WithFormat<"vc+sd-jwt"> = async (
182
+ const verifyAndParseCredentialSdJwt: VerifyAndParseCredential = async (
210
183
  issuerConf,
211
184
  credential,
212
- _,
185
+ credentialConfigurationId,
213
186
  {
214
187
  credentialCryptoContext,
215
188
  ignoreMissingAttributes,
@@ -224,8 +197,21 @@ const verifyAndParseCredentialSdJwt: WithFormat<"vc+sd-jwt"> = async (
224
197
 
225
198
  Logger.log(LogLevel.DEBUG, `Decoded credential: ${JSON.stringify(decoded)}`);
226
199
 
200
+ const credentialConfig =
201
+ issuerConf.openid_credential_issuer.credential_configurations_supported[
202
+ credentialConfigurationId
203
+ ];
204
+
205
+ if (!credentialConfig) {
206
+ Logger.log(
207
+ LogLevel.ERROR,
208
+ `Credential type not supported by the issuer: ${credentialConfigurationId}`
209
+ );
210
+ throw new IoWalletError("Credential type not supported by the issuer");
211
+ }
212
+
227
213
  const parsedCredential = parseCredentialSdJwt(
228
- issuerConf.openid_credential_issuer.credential_configurations_supported,
214
+ credentialConfig,
229
215
  decoded,
230
216
  ignoreMissingAttributes,
231
217
  includeUndefinedAttributes
@@ -251,7 +237,7 @@ const verifyAndParseCredentialSdJwt: WithFormat<"vc+sd-jwt"> = async (
251
237
  * Verify and parse an encoded credential.
252
238
  * @param issuerConf The Issuer configuration returned by {@link evaluateIssuerTrust}
253
239
  * @param credential The encoded credential returned by {@link obtainCredential}
254
- * @param format The format of the credentual returned by {@link obtainCredential}
240
+ * @param credentialConfigurationId The credential configuration ID that defines the provided credential
255
241
  * @param context.credentialCryptoContext The crypto context used to obtain the credential in {@link obtainCredential}
256
242
  * @param context.ignoreMissingAttributes Skip error when attributes declared in the issuer configuration are not found within disclosures
257
243
  * @param context.includeUndefinedAttributes Include attributes not explicitly declared in the issuer configuration
@@ -263,19 +249,25 @@ const verifyAndParseCredentialSdJwt: WithFormat<"vc+sd-jwt"> = async (
263
249
  export const verifyAndParseCredential: VerifyAndParseCredential = async (
264
250
  issuerConf,
265
251
  credential,
266
- format,
252
+ credentialConfigurationId,
267
253
  context
268
254
  ) => {
269
- if (format === "vc+sd-jwt") {
270
- Logger.log(LogLevel.DEBUG, "Parsing credential in vc+sd-jwt format");
255
+ const format =
256
+ issuerConf.openid_credential_issuer.credential_configurations_supported[
257
+ credentialConfigurationId
258
+ ]?.format;
259
+
260
+ if (format === "dc+sd-jwt") {
261
+ Logger.log(LogLevel.DEBUG, "Parsing credential in dc+sd-jwt format");
271
262
  return verifyAndParseCredentialSdJwt(
272
263
  issuerConf,
273
264
  credential,
274
- format,
265
+ credentialConfigurationId,
275
266
  context
276
267
  );
277
268
  }
278
269
 
279
- Logger.log(LogLevel.ERROR, `Unsupported credential format: ${format}`);
280
- throw new IoWalletError(`Unsupported credential format: ${format}`);
270
+ const message = `Unsupported credential format: ${format}`;
271
+ Logger.log(LogLevel.ERROR, message);
272
+ throw new IoWalletError(message);
281
273
  };