@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
package/src/rp/index.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  SignJWT,
11
11
  EncryptJwe,
12
12
  verify,
13
+ type CryptoContext,
13
14
  } from "@pagopa/io-react-native-jwt";
14
15
  import {
15
16
  QRCodePayload,
@@ -21,102 +22,111 @@ import {
21
22
  import uuid from "react-native-uuid";
22
23
  import type { JWK } from "@pagopa/io-react-native-jwt/lib/typescript/types";
23
24
  import { disclose } from "../sd-jwt";
24
- import { getEntityConfiguration } from "../trust";
25
+ import { getEntityConfiguration as getGenericEntityConfiguration } from "../trust";
26
+ import { createDPopToken } from "../utils/dpop";
27
+ import { WalletInstanceAttestation } from "..";
25
28
 
26
- export class RelyingPartySolution {
27
- relyingPartyBaseUrl: string;
28
- walletInstanceAttestation: string;
29
- appFetch: GlobalFetch["fetch"];
29
+ /**
30
+ * Select a RSA public key from those provided by the RP to encrypt.
31
+ *
32
+ * @param entity The RP entity configuration
33
+ * @returns A suitable public key with its compatible encryption algorithm
34
+ * @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
35
+ */
36
+ const chooseRSAPublicKeyToEncrypt = (entity: RpEntityConfiguration): JWK => {
37
+ const [usingRsa256] =
38
+ entity.payload.metadata.wallet_relying_party.jwks.filter(
39
+ (jwk) => jwk.use === "enc" && jwk.kty === "RSA"
40
+ );
30
41
 
31
- constructor(
32
- relyingPartyBaseUrl: string,
33
- walletInstanceAttestation: string,
34
- appFetch: GlobalFetch["fetch"] = fetch
35
- ) {
36
- this.relyingPartyBaseUrl = relyingPartyBaseUrl;
37
- this.walletInstanceAttestation = walletInstanceAttestation;
38
- this.appFetch = appFetch;
42
+ if (usingRsa256) {
43
+ return usingRsa256;
39
44
  }
40
45
 
41
- /**
42
- * Decode a QR code content to an authentication request url.
43
- * @function
44
- * @param qrcode QR code content
45
- *
46
- * @returns The authentication request url
47
- *
48
- */
49
- static decodeAuthRequestQR(qrcode: string): QRCodePayload {
50
- const decoded = decodeBase64(qrcode);
51
- const decodedUrl = new URL(decoded);
52
- const protocol = decodedUrl.protocol;
53
- const resource = decodedUrl.hostname;
54
- const requestURI = decodedUrl.searchParams.get("request_uri");
55
- const clientId = decodedUrl.searchParams.get("client_id");
46
+ // No suitable key has been found
47
+ throw new NoSuitableKeysFoundInEntityConfiguration(
48
+ "Encrypt with RP public key"
49
+ );
50
+ };
56
51
 
57
- const result = QRCodePayload.safeParse({
58
- protocol,
59
- resource,
60
- requestURI,
61
- clientId,
62
- });
52
+ /**
53
+ * Obtain the relying party entity configuration.
54
+ */
55
+ export const getEntityConfiguration =
56
+ ({ appFetch = fetch }: { appFetch?: GlobalFetch["fetch"] } = {}) =>
57
+ async (relyingPartyBaseUrl: string): Promise<RpEntityConfiguration> => {
58
+ return getGenericEntityConfiguration(relyingPartyBaseUrl, {
59
+ appFetch: appFetch,
60
+ }).then(RpEntityConfiguration.parse);
61
+ };
63
62
 
64
- if (result.success) {
65
- return result.data;
66
- } else {
67
- throw new AuthRequestDecodeError(result.error.message, `${decodedUrl}`);
68
- }
69
- }
70
- /**
71
- * Obtain the unsigned wallet instance DPoP for authentication request
72
- *
73
- * @function
74
- * @param walletInstanceAttestationJwk JWT of the Wallet Instance Attestation
75
- * @param authRequestUrl authentication request url
76
- *
77
- * @returns The unsigned wallet instance DPoP
78
- *
79
- */
80
- async getUnsignedWalletInstanceDPoP(
81
- walletInstanceAttestationJwk: any,
82
- authRequestUrl: string
83
- ): Promise<string> {
84
- return await new SignJWT({
85
- jti: `${uuid.v4()}`,
86
- htm: "GET",
87
- htu: authRequestUrl,
88
- ath: await sha256ToBase64(this.walletInstanceAttestation),
89
- })
90
- .setProtectedHeader({
91
- alg: "ES256",
92
- jwk: walletInstanceAttestationJwk,
93
- typ: "dpop+jwt",
94
- })
95
- .setIssuedAt()
96
- .setExpirationTime("1h")
97
- .toSign();
63
+ /**
64
+ * Decode a QR code content to an authentication request url.
65
+ * @function
66
+ * @param qrcode QR code content
67
+ *
68
+ * @returns The authentication request url
69
+ *
70
+ */
71
+ export const decodeAuthRequestQR = (qrcode: string): QRCodePayload => {
72
+ const decoded = decodeBase64(qrcode);
73
+ const decodedUrl = new URL(decoded);
74
+ const protocol = decodedUrl.protocol;
75
+ const resource = decodedUrl.hostname;
76
+ const requestURI = decodedUrl.searchParams.get("request_uri");
77
+ const clientId = decodedUrl.searchParams.get("client_id");
78
+
79
+ const result = QRCodePayload.safeParse({
80
+ protocol,
81
+ resource,
82
+ requestURI,
83
+ clientId,
84
+ });
85
+
86
+ if (result.success) {
87
+ return result.data;
88
+ } else {
89
+ throw new AuthRequestDecodeError(result.error.message, `${decodedUrl}`);
98
90
  }
91
+ };
92
+
93
+ export type RequestObjectConf = {
94
+ requestObject: RequestObject;
95
+ rpEntityConfiguration: RpEntityConfiguration;
96
+ walletInstanceAttestation: string;
97
+ };
99
98
 
100
- /**
101
- * Obtain the Request Object for RP authentication
102
- * @see https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/relying-party-solution.html
103
- *
104
- * @async @function
105
- * @param signedWalletInstanceDPoP JWT of the Wallet Instance Attestation DPoP
106
- *
107
- * @returns The Request Object JWT
108
- * @throws {NoSuitableKeysFoundInEntityConfiguration} When the Request Object is signed with a key not listed in RP's entity configuration
109
- *
110
- */
111
- async getRequestObject(
112
- signedWalletInstanceDPoP: string,
99
+ /**
100
+ * Obtain the Request Object for RP authentication
101
+ * @see https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/relying-party-solution.html
102
+ */
103
+ export const getRequestObject =
104
+ ({
105
+ wiaCryptoContext,
106
+ appFetch = fetch,
107
+ }: {
108
+ wiaCryptoContext: CryptoContext;
109
+ appFetch?: GlobalFetch["fetch"];
110
+ }) =>
111
+ async (
112
+ walletInstanceAttestation: string,
113
113
  requestUri: string,
114
- entity: RpEntityConfiguration
115
- ): Promise<RequestObject> {
116
- const response = await this.appFetch(requestUri, {
114
+ rpEntityConfiguration: RpEntityConfiguration
115
+ ): Promise<RequestObjectConf> => {
116
+ const signedWalletInstanceDPoP = await createDPopToken(
117
+ {
118
+ jti: `${uuid.v4()}`,
119
+ htm: "GET",
120
+ htu: requestUri,
121
+ ath: await sha256ToBase64(walletInstanceAttestation),
122
+ },
123
+ wiaCryptoContext
124
+ );
125
+
126
+ const response = await appFetch(requestUri, {
117
127
  method: "GET",
118
128
  headers: {
119
- Authorization: `DPoP ${this.walletInstanceAttestation}`,
129
+ Authorization: `DPoP ${walletInstanceAttestation}`,
120
130
  DPoP: signedWalletInstanceDPoP,
121
131
  },
122
132
  });
@@ -130,9 +140,10 @@ export class RelyingPartySolution {
130
140
  // verify token signature according to RP's entity configuration
131
141
  // to ensure the request object is authentic
132
142
  {
133
- const pubKey = entity.payload.metadata.wallet_relying_party.jwks.find(
134
- ({ kid }) => kid === responseJwt.protectedHeader.kid
135
- );
143
+ const pubKey =
144
+ rpEntityConfiguration.payload.metadata.wallet_relying_party.jwks.find(
145
+ ({ kid }) => kid === responseJwt.protectedHeader.kid
146
+ );
136
147
  if (!pubKey) {
137
148
  throw new NoSuitableKeysFoundInEntityConfiguration(
138
149
  "Request Object signature verification"
@@ -142,67 +153,68 @@ export class RelyingPartySolution {
142
153
  }
143
154
 
144
155
  // parse request object it has the expected shape by specification
145
- const requestObj = RequestObject.parse({
156
+ const requestObject = RequestObject.parse({
146
157
  header: responseJwt.protectedHeader,
147
158
  payload: responseJwt.payload,
148
159
  });
149
160
 
150
- return requestObj;
161
+ return {
162
+ requestObject,
163
+ rpEntityConfiguration,
164
+ walletInstanceAttestation,
165
+ };
151
166
  }
152
167
 
153
168
  throw new IoWalletError(
154
- `Unable to obtain Request Object. Response code: ${response.status}`
169
+ `Unable to obtain Request Object. Response code: ${response.status}
170
+ ${await response.text()}`
155
171
  );
156
- }
172
+ };
157
173
 
158
- /**
159
- * Prepare the Verified Presentation token for a received request object in the context of an authorization request flow.
160
- * The presentation is prepared by disclosing data from provided credentials, according to requested claims
161
- * Each Verified Credential come along with the claims the user accepts to disclose from it.
162
- *
163
- * The returned token is unsigned (sign should be apply by the caller).
164
- *
165
- * @todo accept more than a Verified Credential
166
- *
167
- * @param requestObj The incoming request object, which the requirements for the requested authorization
168
- * @param walletInstanceIdentifier The identifies of the wallt instance that is presenting
169
- * @param presentation The Verified Credential containing user data along with the list of claims to be disclosed.
170
- * @param signKeyId The kid of the key that will be used to sign
171
- * @returns The unsigned Verified Presentation token
172
- * @throws {ClaimsNotFoundBetweenDislosures} If the Verified Credential does not contain one or more requested claims.
173
- *
174
- */
175
- async prepareVpToken(
176
- requestObj: RequestObject,
177
- walletInstanceIdentifier: string,
178
- [vc, claims]: Presentation, // TODO: [SIW-353] support multiple presentations,
179
- signKeyId: string
174
+ /**
175
+ * Prepare the Verified Presentation token for a received request object in the context of an authorization request flow.
176
+ * The presentation is prepared by disclosing data from provided credentials, according to requested claims
177
+ * Each Verified Credential come along with the claims the user accepts to disclose from it.
178
+ *
179
+ * @todo accept more than a Verified Credential
180
+ */
181
+ const prepareVpToken =
182
+ ({ pidCryptoContext }: { pidCryptoContext: CryptoContext }) =>
183
+ async (
184
+ { requestObject, walletInstanceAttestation }: RequestObjectConf,
185
+ [vc, claims]: Presentation // TODO: [SIW-353] support multiple presentations,
180
186
  ): Promise<{
181
187
  vp_token: string;
182
188
  presentation_submission: Record<string, unknown>;
183
- }> {
189
+ }> => {
184
190
  // this throws if vc cannot satisfy all the requested claims
185
191
  const { token: vp, paths } = await disclose(vc, claims);
186
192
 
187
- // TODO: [SIW-359] check all requeste claims of the requestedObj are satisfied
193
+ // obtain issuer from Wallet Instance
194
+ const {
195
+ payload: { iss },
196
+ } = WalletInstanceAttestation.decode(walletInstanceAttestation);
188
197
 
189
- const vp_token = new SignJWT({
190
- vp: vp,
191
- jti: `${uuid.v4()}`,
192
- iss: walletInstanceIdentifier,
193
- nonce: requestObj.payload.nonce,
194
- })
195
- .setAudience(requestObj.payload.response_uri)
196
- .setIssuedAt()
197
- .setExpirationTime("1h")
198
+ const pidKid = await pidCryptoContext.getPublicKey().then((_) => _.kid);
199
+
200
+ // TODO: [SIW-359] check all requeste claims of the requestedObj are satisfied
201
+ const vp_token = await new SignJWT(pidCryptoContext)
198
202
  .setProtectedHeader({
199
203
  typ: "JWT",
200
- alg: "ES256",
201
- kid: signKeyId,
204
+ kid: pidKid,
205
+ })
206
+ .setPayload({
207
+ vp: vp,
208
+ jti: `${uuid.v4()}`,
209
+ iss,
210
+ nonce: requestObject.payload.nonce,
202
211
  })
203
- .toSign();
212
+ .setAudience(requestObject.payload.response_uri)
213
+ .setIssuedAt()
214
+ .setExpirationTime("1h")
215
+ .sign();
204
216
 
205
- const vc_scope = requestObj.payload.scope;
217
+ const vc_scope = requestObject.payload.scope;
206
218
  const presentation_submission = {
207
219
  definition_id: `${uuid.v4()}`,
208
220
  id: `${uuid.v4()}`,
@@ -214,36 +226,49 @@ export class RelyingPartySolution {
214
226
  };
215
227
 
216
228
  return { vp_token, presentation_submission };
217
- }
229
+ };
218
230
 
219
- /**
220
- * Compose and send an Authorization Response in the context of an authorization request flow.
221
- *
222
- * @todo MUST add presentation_submission
223
- *
224
- * @param requestObj The incoming request object, which the requirements for the requested authorization
225
- * @param vp_token The signed Verified Presentation token with data to send.
226
- * @param presentation_submission
227
- * @param entity The RP entity configuration
228
- * @returns The response from the RP
229
- * @throws {IoWalletError} if the submission fails.
230
- * @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key
231
- *
232
- */
233
- async sendAuthorizationResponse(
234
- requestObj: RequestObject,
235
- vp_token: string,
236
- presentation_submission: Record<string, unknown>,
237
- entity: RpEntityConfiguration
238
- ): Promise<string> {
231
+ /**
232
+ * Compose and send an Authorization Response in the context of an authorization request flow.
233
+ *
234
+ * @todo MUST add presentation_submission
235
+ *
236
+ */
237
+ export const sendAuthorizationResponse =
238
+ ({
239
+ pidCryptoContext,
240
+ appFetch = fetch,
241
+ }: {
242
+ pidCryptoContext: CryptoContext;
243
+ appFetch?: GlobalFetch["fetch"];
244
+ }) =>
245
+ async (
246
+ {
247
+ requestObject,
248
+ rpEntityConfiguration,
249
+ walletInstanceAttestation,
250
+ }: RequestObjectConf,
251
+ presentation: Presentation // TODO: [SIW-353] support multiple presentations,
252
+ ): Promise<string> => {
239
253
  // the request is an unsigned jws without iss, aud, exp
240
254
  // https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-signed-and-encrypted-respon
241
- const jwk = this.chooseRSAPublicKeyToEncrypt(entity);
255
+ const jwk = chooseRSAPublicKeyToEncrypt(rpEntityConfiguration);
256
+
257
+ const { vp_token, presentation_submission } = await prepareVpToken({
258
+ pidCryptoContext,
259
+ })(
260
+ {
261
+ requestObject,
262
+ rpEntityConfiguration,
263
+ walletInstanceAttestation,
264
+ },
265
+ presentation
266
+ );
242
267
 
243
268
  const authzResponsePayload = JSON.stringify({
244
- state: requestObj.payload.state,
269
+ state: requestObject.payload.state,
245
270
  presentation_submission,
246
- nonce: requestObj.payload.nonce,
271
+ nonce: requestObject.payload.nonce,
247
272
  vp_token,
248
273
  });
249
274
 
@@ -256,7 +281,7 @@ export class RelyingPartySolution {
256
281
  const formBody = new URLSearchParams({ response: encrypted });
257
282
  const body = formBody.toString();
258
283
 
259
- const response = await this.appFetch(requestObj.payload.response_uri, {
284
+ const response = await appFetch(requestObject.payload.response_uri, {
260
285
  method: "POST",
261
286
  headers: {
262
287
  "Content-Type": "application/x-www-form-urlencoded",
@@ -273,37 +298,4 @@ export class RelyingPartySolution {
273
298
  response.status
274
299
  }`
275
300
  );
276
- }
277
-
278
- /**
279
- * Select a RSA public key from those provided by the RP to encrypt.
280
- *
281
- * @param entity The RP entity configuration
282
- * @returns A suitable public key with its compatible encryption algorithm
283
- * @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
284
- */
285
- private chooseRSAPublicKeyToEncrypt(entity: RpEntityConfiguration): JWK {
286
- const [usingRsa256] =
287
- entity.payload.metadata.wallet_relying_party.jwks.filter(
288
- (jwk) => jwk.use === "enc" && jwk.kty === "RSA"
289
- );
290
-
291
- if (usingRsa256) {
292
- return usingRsa256;
293
- }
294
-
295
- // No suitable key has been found
296
- throw new NoSuitableKeysFoundInEntityConfiguration(
297
- "Encrypt with RP public key"
298
- );
299
- }
300
-
301
- /**
302
- * Obtain the relying party entity configuration.
303
- */
304
- async getEntityConfiguration(): Promise<RpEntityConfiguration> {
305
- return getEntityConfiguration(this.relyingPartyBaseUrl, {
306
- appFetch: this.appFetch,
307
- }).then(RpEntityConfiguration.parse);
308
- }
309
- }
301
+ };
@@ -7,7 +7,7 @@ import { sha256ToBase64 } from "@pagopa/io-react-native-jwt";
7
7
  import { decodeBase64 } from "@pagopa/io-react-native-jwt";
8
8
  import { Disclosure, SdJwt4VC, type DisclosureWithEncoded } from "./types";
9
9
  import { verifyDisclosure } from "./verifier";
10
- import type { JWK } from "src/utils/jwk";
10
+ import type { JWK } from "../utils/jwk";
11
11
  import {
12
12
  ClaimsNotFoundBetweenDislosures,
13
13
  ClaimsNotFoundInToken,
@@ -22,40 +22,47 @@ export const EntityStatement = z.object({
22
22
  }),
23
23
  });
24
24
 
25
+ export type EntityConfigurationHeader = z.infer<
26
+ typeof EntityConfigurationHeader
27
+ >;
28
+ export const EntityConfigurationHeader = z.object({
29
+ typ: z.literal("entity-statement+jwt"),
30
+ alg: z.string(),
31
+ kid: z.string(),
32
+ });
33
+
25
34
  export type EntityConfiguration = z.infer<typeof EntityConfiguration>;
26
35
  export const EntityConfiguration = z.object({
27
- header: z.object({
28
- typ: z.literal("entity-statement+jwt"),
29
- alg: z.string(),
30
- kid: z.string(),
31
- }),
32
- payload: z.object({
33
- exp: UnixTime,
34
- iat: UnixTime,
35
- iss: z.string(),
36
- sub: z.string(),
37
- jwks: z.object({
38
- keys: z.array(JWK),
39
- }),
40
- metadata: z
41
- .object({
42
- federation_entity: z
43
- .object({
44
- federation_fetch_endpoint: z.string().optional(),
45
- federation_list_endpoint: z.string().optional(),
46
- federation_resolve_endpoint: z.string().optional(),
47
- federation_trust_mark_status_endpoint: z.string().optional(),
48
- federation_trust_mark_list_endpoint: z.string().optional(),
49
- homepage_uri: z.string().optional(),
50
- policy_uri: z.string().optional(),
51
- logo_uri: z.string().optional(),
52
- contacts: z.array(z.string()).optional(),
53
- })
54
- .passthrough(),
55
- })
56
- .passthrough(),
57
- authority_hints: z.array(z.string()).optional(),
58
- }),
36
+ header: EntityConfigurationHeader,
37
+ payload: z
38
+ .object({
39
+ exp: UnixTime,
40
+ iat: UnixTime,
41
+ iss: z.string(),
42
+ sub: z.string(),
43
+ jwks: z.object({
44
+ keys: z.array(JWK),
45
+ }),
46
+ metadata: z
47
+ .object({
48
+ federation_entity: z
49
+ .object({
50
+ federation_fetch_endpoint: z.string().optional(),
51
+ federation_list_endpoint: z.string().optional(),
52
+ federation_resolve_endpoint: z.string().optional(),
53
+ federation_trust_mark_status_endpoint: z.string().optional(),
54
+ federation_trust_mark_list_endpoint: z.string().optional(),
55
+ homepage_uri: z.string().optional(),
56
+ policy_uri: z.string().optional(),
57
+ logo_uri: z.string().optional(),
58
+ contacts: z.array(z.string()).optional(),
59
+ })
60
+ .passthrough(),
61
+ })
62
+ .passthrough(),
63
+ authority_hints: z.array(z.string()).optional(),
64
+ })
65
+ .passthrough(),
59
66
  });
60
67
 
61
68
  export type TrustAnchorEntityConfiguration = z.infer<
@@ -0,0 +1,41 @@
1
+ import { getPublicKey, sign } from "@pagopa/io-react-native-crypto";
2
+ import { thumbprint, type CryptoContext } from "@pagopa/io-react-native-jwt";
3
+ import { fixBase64EncodingOnKey } from "./jwk";
4
+
5
+ /**
6
+ * Create a CryptoContext bound to a key pair.
7
+ * Key pair is supposed to exist already in the device's keychain.
8
+ * It's identified by its unique keytag.
9
+ *
10
+ * @returns the crypto context
11
+ */
12
+ export const createCryptoContextFor = (keytag: string): CryptoContext => {
13
+ return {
14
+ /**
15
+ * Retrieve the public key of the pair.
16
+ * If the key pair doesn't exist yet, an error is raised
17
+ * @returns The public key.
18
+ */
19
+ async getPublicKey() {
20
+ return getPublicKey(keytag)
21
+ .then(fixBase64EncodingOnKey)
22
+ .then(async (jwk) => ({
23
+ ...jwk,
24
+ // Keys in the TEE are not stored with their KID, which is supposed to be assigned when they are included in JWK sets.
25
+ // (that is, KID is not a propoerty of the key itself, but it's property used to identify a key in a set).
26
+ // We assume the convention we use the thumbprint of the public key as KID, thus for easy development we decided to evaluate KID here
27
+ // However the values is an arbitrary string that might be anything
28
+ kid: await thumbprint(jwk),
29
+ }));
30
+ },
31
+ /**
32
+ * Get a signature for a provided value.
33
+ * If the key pair doesn't exist yet, an error is raised.
34
+ * @param value
35
+ * @returns The signature for the value
36
+ */
37
+ async getSignature(value: string) {
38
+ return sign(value, keytag);
39
+ },
40
+ };
41
+ };
package/src/utils/dpop.ts CHANGED
@@ -1,19 +1,29 @@
1
1
  import * as z from "zod";
2
2
 
3
- import { SignJWT } from "@pagopa/io-react-native-jwt";
4
- import type { JWK } from "./jwk";
3
+ import { SignJWT, type CryptoContext } from "@pagopa/io-react-native-jwt";
5
4
 
6
- export const getUnsignedDPop = (jwk: JWK, payload: DPoPPayload): string => {
7
- const dPop = new SignJWT(payload)
5
+ /**
6
+ * Create a signed DPoP token
7
+ *
8
+ * @param payload The payload to be included in the token.
9
+ * @param crypto The crypto context that handles the key bound to the DPoP.
10
+ *
11
+ * @returns The signed crypto token.
12
+ */
13
+ export const createDPopToken = async (
14
+ payload: DPoPPayload,
15
+ crypto: CryptoContext
16
+ ): Promise<string> => {
17
+ const jwk = await crypto.getPublicKey();
18
+ return new SignJWT(crypto)
19
+ .setPayload(payload)
8
20
  .setProtectedHeader({
9
- alg: "ES256",
10
21
  typ: "dpop+jwt",
11
22
  jwk,
12
23
  })
13
24
  .setIssuedAt()
14
25
  .setExpirationTime("1h")
15
- .toSign();
16
- return dPop;
26
+ .sign();
17
27
  };
18
28
 
19
29
  export type DPoPPayload = z.infer<typeof DPoPPayload>;
@@ -2,8 +2,8 @@ import { WalletInstanceAttestationJwt } from "./types";
2
2
  import { decode as decodeJwt } from "@pagopa/io-react-native-jwt";
3
3
  import { verify as verifyJwt } from "@pagopa/io-react-native-jwt";
4
4
 
5
- import { Issuing } from "./issuing";
6
- export { Issuing };
5
+ import { getAttestation } from "./issuing";
6
+ export { getAttestation };
7
7
  /**
8
8
  * Decode a given JWT to get the parsed Wallet Instance Attestation object they define.
9
9
  * It ensures provided data is in a valid shape.