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