@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
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.