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

Sign up to get free protection for your applications and to get access to all the features.
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 -169
  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 -172
  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 -226
  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,18 +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
- import { fixBase64EncodingOnKey } from "./../utils/jwk";
15
-
12
+ import {
13
+ createCryptoContextFor,
14
+ getEntityConfiguration as getGenericEntityConfiguration,
15
+ } from "..";
16
+ import { generate, deleteKey } from "@pagopa/io-react-native-crypto";
17
+ import { SdJwt } from ".";
16
18
  // This is a temporary type that will be used for demo purposes only
17
19
  export type CieData = {
18
20
  birthDate: string;
@@ -21,7 +23,15 @@ export type CieData = {
21
23
  surname: string;
22
24
  };
23
25
 
24
- 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
+
25
35
  export type PidResponse = {
26
36
  credential: string;
27
37
  c_nonce: string;
@@ -29,111 +39,93 @@ export type PidResponse = {
29
39
  format: string;
30
40
  };
31
41
 
32
- export class Issuing {
33
- pidProviderBaseUrl: string;
34
- walletProviderBaseUrl: string;
35
- walletInstanceAttestation: string;
36
- codeVerifier: string;
37
- clientId: string;
38
- state: string;
39
- authorizationCode: string;
40
- 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
+ };
41
54
 
42
- constructor(
43
- pidProviderBaseUrl: string,
44
- walletProviderBaseUrl: string,
45
- 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 (
46
67
  clientId: string,
47
- appFetch: GlobalFetch["fetch"] = fetch
48
- ) {
49
- this.pidProviderBaseUrl = pidProviderBaseUrl;
50
- this.walletProviderBaseUrl = walletProviderBaseUrl;
51
- this.state = `${uuid.v4()}`;
52
- this.codeVerifier = `${uuid.v4()}`;
53
- this.authorizationCode = `${uuid.v4()}`;
54
- this.walletInstanceAttestation = walletInstanceAttestation;
55
- this.clientId = clientId;
56
- this.appFetch = appFetch;
57
- }
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);
58
81
 
59
- /**
60
- * Return the unsigned jwt to call the PAR request.
61
- *
62
- * @function
63
- * @param jwk The wallet instance attestation public JWK
64
- *
65
- * @returns Unsigned jwt
66
- *
67
- */
68
- async getUnsignedJwtForPar(jwk: JWK): Promise<string> {
69
- const parsedJwk = JWK.parse(jwk);
70
- const keyThumbprint = await thumbprint(parsedJwk);
71
- const publicKey = { ...parsedJwk, kid: keyThumbprint };
72
- const codeChallenge = await sha256ToBase64(this.codeVerifier);
82
+ const codeChallenge = await sha256ToBase64(codeVerifier);
73
83
 
74
- const unsignedJwtForPar = new SignJWT({
75
- client_assertion_type:
76
- "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
77
- authorization_details: [
78
- {
79
- credentialDefinition: {
80
- type: ["eu.eudiw.pid.it"],
81
- },
82
- format: "vc+sd-jwt",
83
- type: "type",
84
- },
85
- ],
86
- response_type: "code",
87
- code_challenge_method: "s256",
88
- redirect_uri: this.walletProviderBaseUrl,
89
- state: this.state,
90
- client_id: this.clientId,
91
- code_challenge: codeChallenge,
92
- })
84
+ const signedJwtForPar = await new SignJWT(wiaCryptoContext)
93
85
  .setProtectedHeader({
94
- alg: "ES256",
95
- 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,
96
106
  })
97
107
  .setIssuedAt()
98
108
  .setExpirationTime("1h")
99
- .toSign();
100
-
101
- return unsignedJwtForPar;
102
- }
109
+ .sign();
103
110
 
104
- /**
105
- * Make a PAR request to the PID issuer and return the response url
106
- *
107
- * @function
108
- * @param unsignedJwtForPar The unsigned JWT for PAR
109
- * @param signature The JWT for PAR signature
110
- *
111
- * @returns Unsigned PAR url
112
- *
113
- */
114
- async getPar(unsignedJwtForPar: string, signature: string): Promise<string> {
115
- const codeChallenge = await sha256ToBase64(this.codeVerifier);
116
- const signedJwtForPar = await SignJWT.appendSignature(
117
- unsignedJwtForPar,
118
- signature
119
- );
120
-
121
- const parUrl = new URL("/as/par", this.pidProviderBaseUrl).href;
111
+ const parUrl =
112
+ pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
113
+ .pushed_authorization_request_endpoint;
122
114
 
123
115
  const requestBody = {
124
116
  response_type: "code",
125
- client_id: this.clientId,
117
+ client_id: clientId,
126
118
  code_challenge: codeChallenge,
127
119
  code_challenge_method: "S256",
128
120
  client_assertion_type:
129
121
  "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
130
- client_assertion: this.walletInstanceAttestation,
122
+ client_assertion: walletInstanceAttestation,
131
123
  request: signedJwtForPar,
132
124
  };
133
125
 
134
126
  var formBody = new URLSearchParams(requestBody);
135
127
 
136
- const response = await this.appFetch(parUrl, {
128
+ const response = await appFetch(parUrl, {
137
129
  method: "POST",
138
130
  headers: {
139
131
  "Content-Type": "application/x-www-form-urlencoded",
@@ -149,61 +141,76 @@ export class Issuing {
149
141
  throw new PidIssuingError(
150
142
  `Unable to obtain PAR. Response code: ${await response.text()}`
151
143
  );
152
- }
144
+ };
153
145
 
154
- /**
155
- * Return the unsigned jwt for a generic DPoP
156
- *
157
- * @function
158
- * @param jwk the public key for which the DPoP is to be created
159
- *
160
- * @returns Unsigned JWT for DPoP
161
- *
162
- */
163
- async getUnsignedDPoP(jwk: JWK): Promise<string> {
164
- const tokenUrl = new URL("/token", this.pidProviderBaseUrl).href;
165
- const dPop = getUnsignedDPop(fixBase64EncodingOnKey(jwk), {
166
- htm: "POST",
167
- htu: tokenUrl,
168
- jti: `${uuid.v4()}`,
169
- });
170
- return dPop;
171
- }
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
+ );
172
184
 
173
- /**
174
- * Make an auth token request to the PID issuer
175
- *
176
- * @function
177
- * @returns a token response
178
- *
179
- */
180
- async getAuthToken(): Promise<TokenResponse> {
181
- //Generate fresh keys for DPoP
182
- const dPopKeyTag = `${uuid.v4()}`;
183
- const dPopKey = await generate(dPopKeyTag);
184
- const unsignedDPopForToken = await this.getUnsignedDPoP(dPopKey);
185
- const dPopTokenSignature = await sign(unsignedDPopForToken, dPopKeyTag);
186
- 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);
187
189
 
188
- const signedDPop = await SignJWT.appendSignature(
189
- unsignedDPopForToken,
190
- dPopTokenSignature
190
+ const signedDPop = await createDPopToken(
191
+ {
192
+ htm: "POST",
193
+ htu: tokenUrl,
194
+ jti: `${uuid.v4()}`,
195
+ },
196
+ ephemeralContext
191
197
  );
192
- const decodedJwtDPop = decodeJwt(signedDPop);
193
- const tokenUrl = decodedJwtDPop.payload.htu as string;
198
+
199
+ await deleteKey(keytag);
200
+
194
201
  const requestBody = {
195
202
  grant_type: "authorization code",
196
- client_id: this.clientId,
197
- code: this.authorizationCode,
198
- code_verifier: this.codeVerifier,
203
+ client_id: clientId,
204
+ code: authorizationCode,
205
+ code_verifier: codeVerifier,
199
206
  client_assertion_type:
200
207
  "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
201
- client_assertion: this.walletInstanceAttestation,
202
- redirect_uri: this.walletProviderBaseUrl,
208
+ client_assertion: walletInstanceAttestation,
209
+ redirect_uri: walletProviderBaseUrl,
203
210
  };
204
211
  var formBody = new URLSearchParams(requestBody);
205
212
 
206
- const response = await this.appFetch(tokenUrl, {
213
+ const response = await appFetch(tokenUrl, {
207
214
  method: "POST",
208
215
  headers: {
209
216
  "Content-Type": "application/x-www-form-urlencoded",
@@ -213,70 +220,86 @@ export class Issuing {
213
220
  });
214
221
 
215
222
  if (response.status === 200) {
216
- 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
+ };
217
232
  }
218
233
 
219
234
  throw new PidIssuingError(
220
235
  `Unable to obtain token. Response code: ${await response.text()}`
221
236
  );
222
- }
237
+ };
223
238
 
224
- /**
225
- * Return the unsigned jwt for nonce proof of possession
226
- *
227
- * @function
228
- * @param nonce the nonce
229
- *
230
- * @returns Unsigned JWT for nonce proof
231
- *
232
- */
233
- async getUnsignedNonceProof(nonce: string): Promise<string> {
234
- 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({
235
250
  nonce,
236
251
  })
237
- .setProtectedHeader({
238
- alg: "ES256",
239
- type: "openid4vci-proof+jwt",
240
- })
241
- .setAudience(this.walletProviderBaseUrl)
242
- .setIssuer(this.clientId)
243
- .setIssuedAt()
244
- .setExpirationTime("1h")
245
- .toSign();
246
- return unsignedProof;
247
- }
252
+ .setProtectedHeader({
253
+ type: "openid4vci-proof+jwt",
254
+ })
255
+ .setAudience(audience)
256
+ .setIssuer(issuer)
257
+ .setIssuedAt()
258
+ .setExpirationTime("1h")
259
+ .sign();
260
+ };
248
261
 
249
- /**
250
- * Make the credential issuing request to the PID issuer
251
- *
252
- * @function
253
- * @param unsignedDPopForPid The unsigned JWT for PID DPoP
254
- * @param dPopPidSignature The JWT for PID DPoP signature
255
- * @param unsignedNonceProof The unsigned JWT for nonce proof
256
- * @param nonceProofSignature The JWT for nonce proof signature
257
- * @param accessToken The access token obtained with getAuthToken
258
- * @param cieData Personal data read by the CIE
259
- *
260
- * @returns a credential
261
- *
262
- */
263
- async getCredential(
264
- unsignedDPopForPid: string,
265
- dPopPidSignature: string,
266
- unsignedNonceProof: string,
267
- nonceProofSignature: string,
268
- 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,
269
282
  cieData: CieData
270
- ): Promise<PidResponse> {
271
- const signedDPopForPid = await SignJWT.appendSignature(
272
- unsignedDPopForPid,
273
- 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
274
292
  );
275
- const signedNonceProof = await SignJWT.appendSignature(
276
- unsignedNonceProof,
277
- nonceProofSignature
293
+ const signedNonceProof = await createNonceProof(
294
+ nonce,
295
+ clientId,
296
+ walletProviderBaseUrl,
297
+ pidCryptoContext
278
298
  );
279
- const credentialUrl = new URL("/credential", this.pidProviderBaseUrl).href;
299
+
300
+ const credentialUrl =
301
+ pidProviderEntityConfiguration.payload.metadata.openid_credential_issuer
302
+ .credential_endpoint;
280
303
 
281
304
  const requestBody = {
282
305
  credential_definition: JSON.stringify({ type: ["eu.eudiw.pid.it"] }),
@@ -289,7 +312,7 @@ export class Issuing {
289
312
  };
290
313
  const formBody = new URLSearchParams(requestBody);
291
314
 
292
- const response = await this.appFetch(credentialUrl, {
315
+ const response = await appFetch(credentialUrl, {
293
316
  method: "POST",
294
317
  headers: {
295
318
  "Content-Type": "application/x-www-form-urlencoded",
@@ -300,44 +323,28 @@ export class Issuing {
300
323
  });
301
324
 
302
325
  if (response.status === 200) {
303
- return await response.json();
326
+ const pidResponse = (await response.json()) as PidResponse;
327
+ await validatePid(pidResponse.credential, pidCryptoContext);
328
+ return pidResponse;
304
329
  }
305
330
 
306
- throw new PidIssuingError(`Unable to obtain credential!`);
307
- }
308
-
309
- /**
310
- * Obtain the PID issuer metadata
311
- *
312
- * @function
313
- * @returns PID issuer metadata
314
- *
315
- */
316
- async getEntityConfiguration(): Promise<PidIssuerEntityConfiguration> {
317
- const metadataUrl = new URL(
318
- "ci/.well-known/openid-federation",
319
- this.pidProviderBaseUrl
320
- ).href;
321
-
322
- 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
+ };
323
337
 
324
- if (response.status === 200) {
325
- const jwtMetadata = await response.text();
326
- const { payload } = decodeJwt(jwtMetadata);
327
- const result = PidIssuerEntityConfiguration.safeParse(payload);
328
- if (result.success) {
329
- const parsedMetadata = result.data;
330
- await verifyJwt(jwtMetadata, parsedMetadata.jwks.keys);
331
- return parsedMetadata;
332
- } else {
333
- throw new PidMetadataError(result.error.message);
334
- }
335
- }
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;
336
342
 
337
- throw new PidMetadataError(
338
- `Unable to obtain PID metadata. Response: ${await response.text()} with status: ${
339
- response.status
340
- }`
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)}`
341
348
  );
342
349
  }
343
- }
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