@pagopa/io-react-native-wallet 0.4.2 → 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 -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