@pagopa/io-react-native-wallet 0.4.2 → 0.5.0

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 (81) hide show
  1. package/README.md +98 -22
  2. package/lib/commonjs/index.js +12 -8
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/pid/index.js +3 -8
  5. package/lib/commonjs/pid/index.js.map +1 -1
  6. package/lib/commonjs/pid/issuing.js +152 -168
  7. package/lib/commonjs/pid/issuing.js.map +1 -1
  8. package/lib/commonjs/pid/metadata.js +28 -25
  9. package/lib/commonjs/pid/metadata.js.map +1 -1
  10. package/lib/commonjs/rp/__test__/index.test.js +5 -3
  11. package/lib/commonjs/rp/__test__/index.test.js.map +1 -1
  12. package/lib/commonjs/rp/index.js +158 -154
  13. package/lib/commonjs/rp/index.js.map +1 -1
  14. package/lib/commonjs/trust/types.js +9 -7
  15. package/lib/commonjs/trust/types.js.map +1 -1
  16. package/lib/commonjs/utils/crypto.js +46 -0
  17. package/lib/commonjs/utils/crypto.js.map +1 -0
  18. package/lib/commonjs/utils/dpop.js +14 -7
  19. package/lib/commonjs/utils/dpop.js.map +1 -1
  20. package/lib/commonjs/wallet-instance-attestation/index.js +3 -3
  21. package/lib/commonjs/wallet-instance-attestation/issuing.js +50 -60
  22. package/lib/commonjs/wallet-instance-attestation/issuing.js.map +1 -1
  23. package/lib/module/index.js +4 -3
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/pid/index.js +1 -1
  26. package/lib/module/pid/index.js.map +1 -1
  27. package/lib/module/pid/issuing.js +151 -171
  28. package/lib/module/pid/issuing.js.map +1 -1
  29. package/lib/module/pid/metadata.js +28 -25
  30. package/lib/module/pid/metadata.js.map +1 -1
  31. package/lib/module/rp/__test__/index.test.js +1 -1
  32. package/lib/module/rp/__test__/index.test.js.map +1 -1
  33. package/lib/module/rp/index.js +155 -153
  34. package/lib/module/rp/index.js.map +1 -1
  35. package/lib/module/trust/types.js +7 -6
  36. package/lib/module/trust/types.js.map +1 -1
  37. package/lib/module/utils/crypto.js +40 -0
  38. package/lib/module/utils/crypto.js.map +1 -0
  39. package/lib/module/utils/dpop.js +13 -5
  40. package/lib/module/utils/dpop.js.map +1 -1
  41. package/lib/module/wallet-instance-attestation/index.js +2 -2
  42. package/lib/module/wallet-instance-attestation/index.js.map +1 -1
  43. package/lib/module/wallet-instance-attestation/issuing.js +48 -58
  44. package/lib/module/wallet-instance-attestation/issuing.js.map +1 -1
  45. package/lib/typescript/index.d.ts +4 -3
  46. package/lib/typescript/index.d.ts.map +1 -1
  47. package/lib/typescript/pid/index.d.ts +1 -1
  48. package/lib/typescript/pid/index.d.ts.map +1 -1
  49. package/lib/typescript/pid/issuing.d.ts +51 -87
  50. package/lib/typescript/pid/issuing.d.ts.map +1 -1
  51. package/lib/typescript/pid/metadata.d.ts +1338 -408
  52. package/lib/typescript/pid/metadata.d.ts.map +1 -1
  53. package/lib/typescript/rp/index.d.ts +48 -86
  54. package/lib/typescript/rp/index.d.ts.map +1 -1
  55. package/lib/typescript/rp/types.d.ts +413 -57
  56. package/lib/typescript/rp/types.d.ts.map +1 -1
  57. package/lib/typescript/sd-jwt/index.d.ts +1 -1
  58. package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
  59. package/lib/typescript/trust/types.d.ts +1000 -274
  60. package/lib/typescript/trust/types.d.ts.map +1 -1
  61. package/lib/typescript/utils/crypto.d.ts +10 -0
  62. package/lib/typescript/utils/crypto.d.ts.map +1 -0
  63. package/lib/typescript/utils/dpop.d.ts +10 -2
  64. package/lib/typescript/utils/dpop.d.ts.map +1 -1
  65. package/lib/typescript/wallet-instance-attestation/index.d.ts +2 -2
  66. package/lib/typescript/wallet-instance-attestation/index.d.ts.map +1 -1
  67. package/lib/typescript/wallet-instance-attestation/issuing.d.ts +17 -31
  68. package/lib/typescript/wallet-instance-attestation/issuing.d.ts.map +1 -1
  69. package/package.json +2 -2
  70. package/src/index.ts +5 -3
  71. package/src/pid/index.ts +1 -1
  72. package/src/pid/issuing.ts +233 -225
  73. package/src/pid/metadata.ts +32 -27
  74. package/src/rp/__test__/index.test.ts +1 -1
  75. package/src/rp/index.ts +180 -188
  76. package/src/sd-jwt/index.ts +1 -1
  77. package/src/trust/types.ts +39 -32
  78. package/src/utils/crypto.ts +41 -0
  79. package/src/utils/dpop.ts +17 -7
  80. package/src/wallet-instance-attestation/index.ts +2 -2
  81. package/src/wallet-instance-attestation/issuing.ts +55 -62
@@ -1,17 +1,20 @@
1
1
  import {
2
- decode as decodeJwt,
3
- verify as verifyJwt,
4
2
  sha256ToBase64,
3
+ type CryptoContext,
4
+ SignJWT,
5
+ thumbprint,
5
6
  } from "@pagopa/io-react-native-jwt";
6
-
7
- import { SignJWT, thumbprint } from "@pagopa/io-react-native-jwt";
8
7
  import { JWK } from "../utils/jwk";
9
8
  import uuid from "react-native-uuid";
10
- import { PidIssuingError, PidMetadataError } from "../utils/errors";
11
- import { getUnsignedDPop } from "../utils/dpop";
12
- import { sign, generate, deleteKey } from "@pagopa/io-react-native-crypto";
9
+ import { PidIssuingError } from "../utils/errors";
10
+ import { createDPopToken } from "../utils/dpop";
13
11
  import { PidIssuerEntityConfiguration } from "./metadata";
14
-
12
+ import {
13
+ createCryptoContextFor,
14
+ getEntityConfiguration as getGenericEntityConfiguration,
15
+ } from "..";
16
+ import { generate, deleteKey } from "@pagopa/io-react-native-crypto";
17
+ import { SdJwt } from ".";
15
18
  // This is a temporary type that will be used for demo purposes only
16
19
  export type CieData = {
17
20
  birthDate: string;
@@ -20,7 +23,15 @@ export type CieData = {
20
23
  surname: string;
21
24
  };
22
25
 
23
- export type TokenResponse = { access_token: string; c_nonce: string };
26
+ export type AuthorizationConf = {
27
+ accessToken: string;
28
+ nonce: string;
29
+ clientId: string;
30
+ authorizationCode: string;
31
+ codeVerifier: string;
32
+ walletProviderBaseUrl: string;
33
+ };
34
+
24
35
  export type PidResponse = {
25
36
  credential: string;
26
37
  c_nonce: string;
@@ -28,111 +39,93 @@ export type PidResponse = {
28
39
  format: string;
29
40
  };
30
41
 
31
- export class Issuing {
32
- pidProviderBaseUrl: string;
33
- walletProviderBaseUrl: string;
34
- walletInstanceAttestation: string;
35
- codeVerifier: string;
36
- clientId: string;
37
- state: string;
38
- authorizationCode: string;
39
- appFetch: GlobalFetch["fetch"];
42
+ /**
43
+ * Obtain the PID provider entity configuration.
44
+ */
45
+ export const getEntityConfiguration =
46
+ ({ appFetch = fetch }: { appFetch?: GlobalFetch["fetch"] } = {}) =>
47
+ async (
48
+ relyingPartyBaseUrl: string
49
+ ): Promise<PidIssuerEntityConfiguration> => {
50
+ return getGenericEntityConfiguration(relyingPartyBaseUrl, {
51
+ appFetch: appFetch,
52
+ }).then(PidIssuerEntityConfiguration.parse);
53
+ };
40
54
 
41
- constructor(
42
- pidProviderBaseUrl: string,
43
- walletProviderBaseUrl: string,
44
- walletInstanceAttestation: string,
55
+ /**
56
+ * Make a PAR request to the PID issuer and return the response url
57
+ */
58
+ const getPar =
59
+ ({
60
+ wiaCryptoContext,
61
+ appFetch = fetch,
62
+ }: {
63
+ wiaCryptoContext: CryptoContext;
64
+ appFetch?: GlobalFetch["fetch"];
65
+ }) =>
66
+ async (
45
67
  clientId: string,
46
- appFetch: GlobalFetch["fetch"] = fetch
47
- ) {
48
- this.pidProviderBaseUrl = pidProviderBaseUrl;
49
- this.walletProviderBaseUrl = walletProviderBaseUrl;
50
- this.state = `${uuid.v4()}`;
51
- this.codeVerifier = `${uuid.v4()}`;
52
- this.authorizationCode = `${uuid.v4()}`;
53
- this.walletInstanceAttestation = walletInstanceAttestation;
54
- this.clientId = clientId;
55
- this.appFetch = appFetch;
56
- }
68
+ codeVerifier: string,
69
+ walletProviderBaseUrl: string,
70
+ pidProviderEntityConfiguration: PidIssuerEntityConfiguration,
71
+ walletInstanceAttestation: string
72
+ ): Promise<string> => {
73
+ // Calculate the thumbprint of the public key of the Wallet Instance Attestation.
74
+ // The PAR request token is signed used the Wallet Instance Attestation key.
75
+ // The signature can be verified by reading the public key from the key set shippet with the it will ship the Wallet Instance Attestation;
76
+ // key is matched by its kid, which is supposed to be the thumbprint of its public key.
77
+ const keyThumbprint = await wiaCryptoContext
78
+ .getPublicKey()
79
+ .then(JWK.parse)
80
+ .then(thumbprint);
57
81
 
58
- /**
59
- * Return the unsigned jwt to call the PAR request.
60
- *
61
- * @function
62
- * @param jwk The wallet instance attestation public JWK
63
- *
64
- * @returns Unsigned jwt
65
- *
66
- */
67
- async getUnsignedJwtForPar(jwk: JWK): Promise<string> {
68
- const parsedJwk = JWK.parse(jwk);
69
- const keyThumbprint = await thumbprint(parsedJwk);
70
- const publicKey = { ...parsedJwk, kid: keyThumbprint };
71
- const codeChallenge = await sha256ToBase64(this.codeVerifier);
82
+ const codeChallenge = await sha256ToBase64(codeVerifier);
72
83
 
73
- const unsignedJwtForPar = new SignJWT({
74
- client_assertion_type:
75
- "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
76
- authorization_details: [
77
- {
78
- credentialDefinition: {
79
- type: ["eu.eudiw.pid.it"],
80
- },
81
- format: "vc+sd-jwt",
82
- type: "type",
83
- },
84
- ],
85
- response_type: "code",
86
- code_challenge_method: "s256",
87
- redirect_uri: this.walletProviderBaseUrl,
88
- state: this.state,
89
- client_id: this.clientId,
90
- code_challenge: codeChallenge,
91
- })
84
+ const signedJwtForPar = await new SignJWT(wiaCryptoContext)
92
85
  .setProtectedHeader({
93
- alg: "ES256",
94
- kid: publicKey.kid,
86
+ kid: keyThumbprint,
87
+ })
88
+ .setPayload({
89
+ client_assertion_type:
90
+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
91
+ authorization_details: [
92
+ {
93
+ credentialDefinition: {
94
+ type: ["eu.eudiw.pid.it"],
95
+ },
96
+ format: "vc+sd-jwt",
97
+ type: "type",
98
+ },
99
+ ],
100
+ response_type: "code",
101
+ code_challenge_method: "s256",
102
+ redirect_uri: walletProviderBaseUrl,
103
+ state: `${uuid.v4()}`,
104
+ client_id: clientId,
105
+ code_challenge: codeChallenge,
95
106
  })
96
107
  .setIssuedAt()
97
108
  .setExpirationTime("1h")
98
- .toSign();
99
-
100
- return unsignedJwtForPar;
101
- }
109
+ .sign();
102
110
 
103
- /**
104
- * Make a PAR request to the PID issuer and return the response url
105
- *
106
- * @function
107
- * @param unsignedJwtForPar The unsigned JWT for PAR
108
- * @param signature The JWT for PAR signature
109
- *
110
- * @returns Unsigned PAR url
111
- *
112
- */
113
- async getPar(unsignedJwtForPar: string, signature: string): Promise<string> {
114
- const codeChallenge = await sha256ToBase64(this.codeVerifier);
115
- const signedJwtForPar = await SignJWT.appendSignature(
116
- unsignedJwtForPar,
117
- signature
118
- );
119
-
120
- const parUrl = new URL("/as/par", this.pidProviderBaseUrl).href;
111
+ const parUrl =
112
+ pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
113
+ .pushed_authorization_request_endpoint;
121
114
 
122
115
  const requestBody = {
123
116
  response_type: "code",
124
- client_id: this.clientId,
117
+ client_id: clientId,
125
118
  code_challenge: codeChallenge,
126
119
  code_challenge_method: "S256",
127
120
  client_assertion_type:
128
121
  "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
129
- client_assertion: this.walletInstanceAttestation,
122
+ client_assertion: walletInstanceAttestation,
130
123
  request: signedJwtForPar,
131
124
  };
132
125
 
133
126
  var formBody = new URLSearchParams(requestBody);
134
127
 
135
- const response = await this.appFetch(parUrl, {
128
+ const response = await appFetch(parUrl, {
136
129
  method: "POST",
137
130
  headers: {
138
131
  "Content-Type": "application/x-www-form-urlencoded",
@@ -148,61 +141,76 @@ export class Issuing {
148
141
  throw new PidIssuingError(
149
142
  `Unable to obtain PAR. Response code: ${await response.text()}`
150
143
  );
151
- }
144
+ };
152
145
 
153
- /**
154
- * Return the unsigned jwt for a generic DPoP
155
- *
156
- * @function
157
- * @param jwk the public key for which the DPoP is to be created
158
- *
159
- * @returns Unsigned JWT for DPoP
160
- *
161
- */
162
- async getUnsignedDPoP(jwk: JWK): Promise<string> {
163
- const tokenUrl = new URL("/token", this.pidProviderBaseUrl).href;
164
- const dPop = getUnsignedDPop(jwk, {
165
- htm: "POST",
166
- htu: tokenUrl,
167
- jti: `${uuid.v4()}`,
168
- });
169
- return dPop;
170
- }
146
+ /**
147
+ * Start the issuing flow by generating an authorization request to the PID Provider. Obtain from the PID Provider an access token to be used to complete the issuing flow.
148
+ *
149
+ * @param params.wiaCryptoContext The key pair associated with the WIA. Will be use to prove the ownership of the attestation.
150
+ * @param params.appFetch (optional) Http client
151
+ * @param walletInstanceAttestation Wallet Instance Attestation token.
152
+ * @param walletProviderBaseUrl Base url for the Wallet Provider
153
+ * @param pidProviderEntityConfiguration The Entity Configuration of the PID Provider, from which discover public endooints.
154
+ * @returns The access token along with the values that identify the issuing session.
155
+ */
156
+ export const authorizeIssuing =
157
+ ({
158
+ wiaCryptoContext,
159
+ appFetch = fetch,
160
+ }: {
161
+ wiaCryptoContext: CryptoContext;
162
+ appFetch?: GlobalFetch["fetch"];
163
+ }) =>
164
+ async (
165
+ walletInstanceAttestation: string,
166
+ walletProviderBaseUrl: string,
167
+ pidProviderEntityConfiguration: PidIssuerEntityConfiguration
168
+ ): Promise<AuthorizationConf> => {
169
+ // FIXME: do better
170
+ const clientId = await wiaCryptoContext.getPublicKey().then((_) => _.kid);
171
+ const codeVerifier = `${uuid.v4()}`;
172
+ const authorizationCode = `${uuid.v4()}`;
173
+ const tokenUrl =
174
+ pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
175
+ .token_endpoint;
176
+
177
+ await getPar({ wiaCryptoContext, appFetch })(
178
+ clientId,
179
+ codeVerifier,
180
+ walletProviderBaseUrl,
181
+ pidProviderEntityConfiguration,
182
+ walletInstanceAttestation
183
+ );
171
184
 
172
- /**
173
- * Make an auth token request to the PID issuer
174
- *
175
- * @function
176
- * @returns a token response
177
- *
178
- */
179
- async getAuthToken(): Promise<TokenResponse> {
180
- //Generate fresh keys for DPoP
181
- const dPopKeyTag = `${uuid.v4()}`;
182
- const dPopKey = await generate(dPopKeyTag);
183
- const unsignedDPopForToken = await this.getUnsignedDPoP(dPopKey);
184
- const dPopTokenSignature = await sign(unsignedDPopForToken, dPopKeyTag);
185
- await deleteKey(dPopKeyTag);
185
+ // Use an ephemeral key to be destroyed after use
186
+ const keytag = `ephemeral-${uuid.v4()}`;
187
+ await generate(keytag);
188
+ const ephemeralContext = createCryptoContextFor(keytag);
186
189
 
187
- const signedDPop = await SignJWT.appendSignature(
188
- unsignedDPopForToken,
189
- dPopTokenSignature
190
+ const signedDPop = await createDPopToken(
191
+ {
192
+ htm: "POST",
193
+ htu: tokenUrl,
194
+ jti: `${uuid.v4()}`,
195
+ },
196
+ ephemeralContext
190
197
  );
191
- const decodedJwtDPop = decodeJwt(signedDPop);
192
- const tokenUrl = decodedJwtDPop.payload.htu as string;
198
+
199
+ await deleteKey(keytag);
200
+
193
201
  const requestBody = {
194
202
  grant_type: "authorization code",
195
- client_id: this.clientId,
196
- code: this.authorizationCode,
197
- code_verifier: this.codeVerifier,
203
+ client_id: clientId,
204
+ code: authorizationCode,
205
+ code_verifier: codeVerifier,
198
206
  client_assertion_type:
199
207
  "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
200
- client_assertion: this.walletInstanceAttestation,
201
- redirect_uri: this.walletProviderBaseUrl,
208
+ client_assertion: walletInstanceAttestation,
209
+ redirect_uri: walletProviderBaseUrl,
202
210
  };
203
211
  var formBody = new URLSearchParams(requestBody);
204
212
 
205
- const response = await this.appFetch(tokenUrl, {
213
+ const response = await appFetch(tokenUrl, {
206
214
  method: "POST",
207
215
  headers: {
208
216
  "Content-Type": "application/x-www-form-urlencoded",
@@ -212,70 +220,86 @@ export class Issuing {
212
220
  });
213
221
 
214
222
  if (response.status === 200) {
215
- return await response.json();
223
+ const { c_nonce, access_token } = await response.json();
224
+ return {
225
+ accessToken: access_token,
226
+ nonce: c_nonce,
227
+ clientId,
228
+ codeVerifier,
229
+ authorizationCode,
230
+ walletProviderBaseUrl,
231
+ };
216
232
  }
217
233
 
218
234
  throw new PidIssuingError(
219
235
  `Unable to obtain token. Response code: ${await response.text()}`
220
236
  );
221
- }
237
+ };
222
238
 
223
- /**
224
- * Return the unsigned jwt for nonce proof of possession
225
- *
226
- * @function
227
- * @param nonce the nonce
228
- *
229
- * @returns Unsigned JWT for nonce proof
230
- *
231
- */
232
- async getUnsignedNonceProof(nonce: string): Promise<string> {
233
- const unsignedProof = new SignJWT({
239
+ /**
240
+ * Return the signed jwt for nonce proof of possession
241
+ */
242
+ const createNonceProof = async (
243
+ nonce: string,
244
+ issuer: string,
245
+ audience: string,
246
+ ctx: CryptoContext
247
+ ): Promise<string> => {
248
+ return new SignJWT(ctx)
249
+ .setPayload({
234
250
  nonce,
235
251
  })
236
- .setProtectedHeader({
237
- alg: "ES256",
238
- type: "openid4vci-proof+jwt",
239
- })
240
- .setAudience(this.walletProviderBaseUrl)
241
- .setIssuer(this.clientId)
242
- .setIssuedAt()
243
- .setExpirationTime("1h")
244
- .toSign();
245
- return unsignedProof;
246
- }
252
+ .setProtectedHeader({
253
+ type: "openid4vci-proof+jwt",
254
+ })
255
+ .setAudience(audience)
256
+ .setIssuer(issuer)
257
+ .setIssuedAt()
258
+ .setExpirationTime("1h")
259
+ .sign();
260
+ };
247
261
 
248
- /**
249
- * Make the credential issuing request to the PID issuer
250
- *
251
- * @function
252
- * @param unsignedDPopForPid The unsigned JWT for PID DPoP
253
- * @param dPopPidSignature The JWT for PID DPoP signature
254
- * @param unsignedNonceProof The unsigned JWT for nonce proof
255
- * @param nonceProofSignature The JWT for nonce proof signature
256
- * @param accessToken The access token obtained with getAuthToken
257
- * @param cieData Personal data read by the CIE
258
- *
259
- * @returns a credential
260
- *
261
- */
262
- async getCredential(
263
- unsignedDPopForPid: string,
264
- dPopPidSignature: string,
265
- unsignedNonceProof: string,
266
- nonceProofSignature: string,
267
- accessToken: string,
262
+ /**
263
+ * Complete the issuing flow and get the PID credential.
264
+ *
265
+ * @param params.pidCryptoContext The key pair associated with the PID. Will be use to prove the ownership of the credential.
266
+ * @param params.appFetch (optional) Http client
267
+ * @param authConf The authorization configuration retrieved with the access token
268
+ * @param cieData Data red from the CIE login process
269
+ * @returns The PID credential token
270
+ */
271
+ export const getCredential =
272
+ ({
273
+ pidCryptoContext,
274
+ appFetch = fetch,
275
+ }: {
276
+ pidCryptoContext: CryptoContext;
277
+ appFetch?: GlobalFetch["fetch"];
278
+ }) =>
279
+ async (
280
+ { nonce, accessToken, clientId, walletProviderBaseUrl }: AuthorizationConf,
281
+ pidProviderEntityConfiguration: PidIssuerEntityConfiguration,
268
282
  cieData: CieData
269
- ): Promise<PidResponse> {
270
- const signedDPopForPid = await SignJWT.appendSignature(
271
- unsignedDPopForPid,
272
- dPopPidSignature
283
+ ): Promise<PidResponse> => {
284
+ const signedDPopForPid = await createDPopToken(
285
+ {
286
+ htm: "POST",
287
+ htu: pidProviderEntityConfiguration.payload.metadata
288
+ .openid_credential_issuer.token_endpoint,
289
+ jti: `${uuid.v4()}`,
290
+ },
291
+ pidCryptoContext
273
292
  );
274
- const signedNonceProof = await SignJWT.appendSignature(
275
- unsignedNonceProof,
276
- nonceProofSignature
293
+ const signedNonceProof = await createNonceProof(
294
+ nonce,
295
+ clientId,
296
+ walletProviderBaseUrl,
297
+ pidCryptoContext
277
298
  );
278
- const credentialUrl = new URL("/credential", this.pidProviderBaseUrl).href;
299
+
300
+ const credentialUrl =
301
+ pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
302
+ .credential_endpoint;
279
303
 
280
304
  const requestBody = {
281
305
  credential_definition: JSON.stringify({ type: ["eu.eudiw.pid.it"] }),
@@ -288,7 +312,7 @@ export class Issuing {
288
312
  };
289
313
  const formBody = new URLSearchParams(requestBody);
290
314
 
291
- const response = await this.appFetch(credentialUrl, {
315
+ const response = await appFetch(credentialUrl, {
292
316
  method: "POST",
293
317
  headers: {
294
318
  "Content-Type": "application/x-www-form-urlencoded",
@@ -299,44 +323,28 @@ export class Issuing {
299
323
  });
300
324
 
301
325
  if (response.status === 200) {
302
- return await response.json();
326
+ const pidResponse = (await response.json()) as PidResponse;
327
+ await validatePid(pidResponse.credential, pidCryptoContext);
328
+ return pidResponse;
303
329
  }
304
330
 
305
- throw new PidIssuingError(`Unable to obtain credential!`);
306
- }
307
-
308
- /**
309
- * Obtain the PID issuer metadata
310
- *
311
- * @function
312
- * @returns PID issuer metadata
313
- *
314
- */
315
- async getEntityConfiguration(): Promise<PidIssuerEntityConfiguration> {
316
- const metadataUrl = new URL(
317
- "ci/.well-known/openid-federation",
318
- this.pidProviderBaseUrl
319
- ).href;
320
-
321
- const response = await this.appFetch(metadataUrl);
331
+ throw new PidIssuingError(
332
+ `Unable to obtain credential! url=${credentialUrl} status=${
333
+ response.status
334
+ } body=${await response.text()}`
335
+ );
336
+ };
322
337
 
323
- if (response.status === 200) {
324
- const jwtMetadata = await response.text();
325
- const { payload } = decodeJwt(jwtMetadata);
326
- const result = PidIssuerEntityConfiguration.safeParse(payload);
327
- if (result.success) {
328
- const parsedMetadata = result.data;
329
- await verifyJwt(jwtMetadata, parsedMetadata.jwks.keys);
330
- return parsedMetadata;
331
- } else {
332
- throw new PidMetadataError(result.error.message);
333
- }
334
- }
338
+ const validatePid = async (pidJwt: string, pidCryptoContext: CryptoContext) => {
339
+ const decoded = SdJwt.decode(pidJwt);
340
+ const pidKey = await pidCryptoContext.getPublicKey();
341
+ const holderBindedKey = decoded.sdJwt.payload.cnf.jwk;
335
342
 
336
- throw new PidMetadataError(
337
- `Unable to obtain PID metadata. Response: ${await response.text()} with status: ${
338
- response.status
339
- }`
343
+ if ((await thumbprint(pidKey)) !== (await thumbprint(holderBindedKey))) {
344
+ throw new PidIssuingError(
345
+ `The obtained pid does not seem to be valid according to your configuration. Your PID public key is: ${JSON.stringify(
346
+ pidKey
347
+ )} but PID holder binded key is: ${JSON.stringify(holderBindedKey)}`
340
348
  );
341
349
  }
342
- }
350
+ };
@@ -1,3 +1,4 @@
1
+ import { EntityConfiguration } from "../trust/types";
1
2
  import { JWK } from "../utils/jwk";
2
3
  import { z } from "zod";
3
4
 
@@ -16,31 +17,35 @@ export const PidDisplayMetadata = z.object({
16
17
  export type PidIssuerEntityConfiguration = z.infer<
17
18
  typeof PidIssuerEntityConfiguration
18
19
  >;
19
- export const PidIssuerEntityConfiguration = z.object({
20
- jwks: z.object({ keys: z.array(JWK) }),
21
- metadata: z.object({
22
- openid_credential_issuer: z.object({
23
- credential_issuer: z.string(),
24
- authorization_endpoint: z.string(),
25
- token_endpoint: z.string(),
26
- pushed_authorization_request_endpoint: z.string(),
27
- dpop_signing_alg_values_supported: z.array(z.string()),
28
- credential_endpoint: z.string(),
29
- credentials_supported: z.array(
30
- z.object({
31
- format: z.literal("vc+sd-jwt"),
32
- cryptographic_binding_methods_supported: z.array(z.string()),
33
- cryptographic_suites_supported: z.array(z.string()),
34
- display: z.array(PidDisplayMetadata),
35
- })
36
- ),
20
+ export const PidIssuerEntityConfiguration = EntityConfiguration.and(
21
+ z.object({
22
+ payload: z.object({
23
+ jwks: z.object({ keys: z.array(JWK) }),
24
+ metadata: z.object({
25
+ openid_credential_issuer: z.object({
26
+ credential_issuer: z.string(),
27
+ authorization_endpoint: z.string(),
28
+ token_endpoint: z.string(),
29
+ pushed_authorization_request_endpoint: z.string(),
30
+ dpop_signing_alg_values_supported: z.array(z.string()),
31
+ credential_endpoint: z.string(),
32
+ credentials_supported: z.array(
33
+ z.object({
34
+ format: z.literal("vc+sd-jwt"),
35
+ cryptographic_binding_methods_supported: z.array(z.string()),
36
+ cryptographic_suites_supported: z.array(z.string()),
37
+ display: z.array(PidDisplayMetadata),
38
+ })
39
+ ),
40
+ }),
41
+ federation_entity: z.object({
42
+ organization_name: z.string(),
43
+ homepage_uri: z.string(),
44
+ policy_uri: z.string(),
45
+ tos_uri: z.string(),
46
+ logo_uri: z.string(),
47
+ }),
48
+ }),
37
49
  }),
38
- federation_entity: z.object({
39
- organization_name: z.string(),
40
- homepage_uri: z.string(),
41
- policy_uri: z.string(),
42
- tos_uri: z.string(),
43
- logo_uri: z.string(),
44
- }),
45
- }),
46
- });
50
+ })
51
+ );
@@ -1,4 +1,4 @@
1
- import { RelyingPartySolution } from "..";
1
+ import * as RelyingPartySolution from "..";
2
2
  import { AuthRequestDecodeError } from "../../utils/errors";
3
3
  import { RpEntityConfiguration } from "../types";
4
4