@pagopa/io-react-native-wallet 0.16.3 → 0.17.1

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 (39) hide show
  1. package/README.md +146 -197
  2. package/lib/commonjs/cie/README.md +6 -0
  3. package/lib/commonjs/cie/component.js +3 -1
  4. package/lib/commonjs/cie/component.js.map +1 -1
  5. package/lib/commonjs/credential/issuance/06-obtain-credential.js +6 -0
  6. package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
  7. package/lib/commonjs/credential/issuance/README.md +323 -0
  8. package/lib/commonjs/credential/presentation/README.md +3 -0
  9. package/lib/commonjs/credential/status/README.md +64 -0
  10. package/lib/commonjs/utils/errors.js +20 -1
  11. package/lib/commonjs/utils/errors.js.map +1 -1
  12. package/lib/commonjs/wallet-instance/README.md +29 -0
  13. package/lib/commonjs/wallet-instance-attestation/README.md +35 -0
  14. package/lib/module/cie/README.md +6 -0
  15. package/lib/module/cie/component.js +2 -1
  16. package/lib/module/cie/component.js.map +1 -1
  17. package/lib/module/credential/issuance/06-obtain-credential.js +7 -1
  18. package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
  19. package/lib/module/credential/issuance/README.md +323 -0
  20. package/lib/module/credential/presentation/README.md +3 -0
  21. package/lib/module/credential/status/README.md +64 -0
  22. package/lib/module/utils/errors.js +18 -0
  23. package/lib/module/utils/errors.js.map +1 -1
  24. package/lib/module/wallet-instance/README.md +29 -0
  25. package/lib/module/wallet-instance-attestation/README.md +35 -0
  26. package/lib/typescript/cie/component.d.ts.map +1 -1
  27. package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
  28. package/lib/typescript/utils/errors.d.ts +9 -0
  29. package/lib/typescript/utils/errors.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/cie/README.md +6 -0
  32. package/src/cie/component.tsx +3 -1
  33. package/src/credential/issuance/06-obtain-credential.ts +10 -0
  34. package/src/credential/issuance/README.md +323 -0
  35. package/src/credential/presentation/README.md +3 -0
  36. package/src/credential/status/README.md +64 -0
  37. package/src/utils/errors.ts +18 -0
  38. package/src/wallet-instance/README.md +29 -0
  39. package/src/wallet-instance-attestation/README.md +35 -0
@@ -0,0 +1,323 @@
1
+ # Credential Issuance
2
+
3
+ This flow is used to obtain a credential from a credential issuer. Each step in the flow is imported from the related file which is named with a sequential number.
4
+
5
+ There's a fork in the flow which is based on the type of the credential that is being requested. If the credential is an eID, the flow takes a different path than if it is not an eID.
6
+ This is due to the fact that eID credentials require a different authorization flow than other credentials, which is accomplished by a strong authentication method like SPID or CIE.
7
+ Credentials instead require a simpler authorization flow and they require other credentials to be presented in order to be issued.
8
+
9
+ The supported credentials are defined in the entity configuration of the issuer which is evaluted and parsed in the `evaluateIssuerTrust` step.
10
+
11
+ ## Sequence Diagram
12
+
13
+ ```mermaid
14
+ graph TD;
15
+ 0[WalletInstanceAttestation.getAttestation]
16
+ 1[startFlow]
17
+ 2[evaluateIssuerTrust]
18
+ 3[startUserAuthorization]
19
+ C4[getRequestedCredentialToBePresented]
20
+ C4.1[completeUserAuthorizationWithFormPostJwtMode]
21
+ E4[completeUserAuthorizationWithQueryMode]
22
+ 5[authorizeAccess]
23
+ 6[obtainCredential]
24
+ 7[verifyAndParseCredential]
25
+ credSel{Is credential an eID?}
26
+
27
+ 0 --> 1
28
+ 1 --> 2
29
+ 2 --> 3
30
+ 3 --> credSel
31
+ credSel -->|Yes| E4
32
+ credSel -->|No| C4
33
+ C4 --> C4.1
34
+ C4.1 --> 5
35
+ E4 --> 5
36
+ 5 --> 6
37
+ 6 --> 7
38
+ ```
39
+
40
+ ## Mapped results
41
+
42
+ ### 404 Not Found (CredentialNotEntitledError)
43
+
44
+ A `404 Not Found` response is returned by the credential issuer when the authenticated user is not entitled to receive the requested credential.
45
+
46
+ ## Strong authentication for eID issuance (Query Mode)
47
+
48
+ The eID issuance requires a strong authentication method. Currently SPID (L2), CieID (L2) and CIE+PIN (L3) are supported. The strong authentication method is determined by the IDP hint which is passed to the `completeUserAuthorizationWithQueryMode` function.
49
+
50
+ For SPID in production the IDP hint can be found [here](https://registry.spid.gov.it/identity-providers), under the `entityId` field. For pre-production environment the IDP hint is `https://demo.spid.gov.it'`.
51
+
52
+ For CieID(L2) the IDP hint is `https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO"` for production and `https://collaudo.idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO` for pre-production.
53
+
54
+ CIE+PIN(L3) requires a different flow due to the physical card presence. Helper functions are exposed to handle it and the documentation can be found [here](../../cie/README.md).
55
+
56
+ The expected result from the authentication process is in provided in the query string as defined in the [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)](https://openid.net/specs/oauth-v2-jarm.html#name-response-mode-queryjwt).
57
+
58
+ ## Authentication through credentials (Form Post JWT Mode)
59
+
60
+ When the credential is different than an eID, the flow requires the user to present other credentials in order to obtain the requested one. This is done through the `getRequestedCredentialToBePresented` followed by the `completeUserAuthorizationWithFormPostJwtMode`.
61
+
62
+ The expected result from the authentication process is in `form_post.jwt` format as defined in [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)](https://openid.net/specs/oauth-v2-jarm.html#name-response-mode-form_postjwt).
63
+
64
+ ## Examples
65
+
66
+ <details>
67
+ <summary>Credential issuance flow</summary>
68
+
69
+ ```ts
70
+ // Retrieve the integrity key tag from the store and create its context
71
+ const integrityKeyTag = "example"; // Let's assume this is the key tag used to create the wallet instance
72
+ const integrityContext = getIntegrityContext(integrityKeyTag);
73
+
74
+ // generate Key for Wallet Instance Attestation
75
+ // ensure the key esists befor starting the issuing process
76
+ await regenerateCryptoKey(WIA_KEYTAG); // Let's assume this function regenerates this ephemeral key
77
+ const wiaCryptoContext = createCryptoContextFor(WIA_KEYTAG);
78
+
79
+ const { WALLET_PROVIDER_BASE_URL, WALLET_EAA_PROVIDER_BASE_URL, REDIRECT_URI } =
80
+ env; // Let's assume these are the environment variables
81
+
82
+ /**
83
+ * Obtains a new Wallet Instance Attestation.
84
+ * WARNING: The integrity context must be the same used when creating the Wallet Instance with the same keytag.
85
+ */
86
+ const walletInstanceAttestation =
87
+ await WalletInstanceAttestation.getAttestation({
88
+ wiaCryptoContext,
89
+ integrityContext,
90
+ walletProviderBaseUrl: WALLET_PROVIDER_BASE_URL,
91
+ appFetch,
92
+ });
93
+
94
+ const credentialType = "someCredential"; // Let's assume this is the credential type
95
+
96
+ const eid = {
97
+ credential: "example",
98
+ parsedCredential: "example"
99
+ keyTag: "example";
100
+ credentialType: "eid";
101
+ };
102
+
103
+ const eidCryptoContext = createCryptoContextFor(eid.keyTag);
104
+
105
+ // Create credential crypto context
106
+ const credentialKeyTag = uuid.v4().toString();
107
+ await generate(credentialKeyTag); // Let's assume this function generates a new hardware-backed key pair
108
+ const credentialCryptoContext = createCryptoContextFor(credentialKeyTag);
109
+
110
+ // Start the issuance flow
111
+ const startFlow: Credential.Issuance.StartFlow = () => ({
112
+ issuerUrl: WALLET_EAA_PROVIDER_BASE_URL,
113
+ credentialType,
114
+ });
115
+
116
+ const { issuerUrl } = startFlow();
117
+
118
+ // Evaluate issuer trust
119
+ const { issuerConf } = await Credential.Issuance.evaluateIssuerTrust(issuerUrl);
120
+
121
+ // Start user authorization
122
+ const { issuerRequestUri, clientId, codeVerifier, credentialDefinition } =
123
+ await Credential.Issuance.startUserAuthorization(issuerConf, credentialType, {
124
+ walletInstanceAttestation,
125
+ redirectUri,
126
+ wiaCryptoContext,
127
+ appFetch,
128
+ });
129
+
130
+ const requestObject =
131
+ await Credential.Issuance.getRequestedCredentialToBePresented(
132
+ issuerRequestUri,
133
+ clientId,
134
+ issuerConf,
135
+ appFetch
136
+ );
137
+
138
+ // The app here should ask the user to confirm the required data contained in the requestObject
139
+
140
+ // Complete the user authorization via form_post.jwt mode
141
+ const { code } =
142
+ await Credential.Issuance.completeUserAuthorizationWithFormPostJwtMode(
143
+ requestObject,
144
+ { wiaCryptoContext, pidCryptoContext, pid, walletInstanceAttestation }
145
+ );
146
+
147
+ // Generate the DPoP context which will be used for the whole issuance flow
148
+ await regenerateCryptoKey(DPOP_KEYTAG); // Let's assume this function regenerates this ephemeral key for the DPoP
149
+ const dPopCryptoContext = createCryptoContextFor(DPOP_KEYTAG);
150
+
151
+ const { accessToken } = await Credential.Issuance.authorizeAccess(
152
+ issuerConf,
153
+ code,
154
+ clientId,
155
+ redirectUri,
156
+ codeVerifier,
157
+ {
158
+ walletInstanceAttestation,
159
+ wiaCryptoContext,
160
+ dPopCryptoContext,
161
+ appFetch,
162
+ }
163
+ );
164
+
165
+ // Obtain the credential
166
+ const { credential, format } = await Credential.Issuance.obtainCredential(
167
+ issuerConf,
168
+ accessToken,
169
+ clientId,
170
+ credentialDefinition,
171
+ {
172
+ credentialCryptoContext,
173
+ dPopCryptoContext,
174
+ appFetch,
175
+ }
176
+ );
177
+
178
+ // Parse and verify the credential. The ignoreMissingAttributes flag must be set to false or omitted in production.
179
+ const { parsedCredential } = await Credential.Issuance.verifyAndParseCredential(
180
+ issuerConf,
181
+ credential,
182
+ format,
183
+ { credentialCryptoContext, ignoreMissingAttributes: true }
184
+ );
185
+
186
+ return {
187
+ parsedCredential,
188
+ credential,
189
+ keyTag: credentialKeyTag,
190
+ credentialType,
191
+ };
192
+ ```
193
+
194
+ </details>
195
+
196
+ <details>
197
+ <summary>eID issuance flow</summary>
198
+
199
+ ```ts
200
+ // Retrieve the integrity key tag from the store and create its context
201
+ const integrityKeyTag = "example"; // Let's assume this is the key tag used to create the wallet instance
202
+ const integrityContext = getIntegrityContext(integrityKeyTag);
203
+
204
+ // generate Key for Wallet Instance Attestation
205
+ // ensure the key esists befor starting the issuing process
206
+ await regenerateCryptoKey(WIA_KEYTAG); // Let's assume this function regenerates this ephemeral key
207
+ const wiaCryptoContext = createCryptoContextFor(WIA_KEYTAG);
208
+
209
+ const { WALLET_PROVIDER_BASE_URL, WALLET_EID_PROVIDER_BASE_URL, REDIRECT_URI } =
210
+ env; // Let's assume these are the environment variables
211
+
212
+ /**
213
+ * Obtains a new Wallet Instance Attestation.
214
+ * WARNING: The integrity context must be the same used when creating the Wallet Instance with the same keytag.
215
+ */
216
+ const walletInstanceAttestation =
217
+ await WalletInstanceAttestation.getAttestation({
218
+ wiaCryptoContext,
219
+ integrityContext,
220
+ walletProviderBaseUrl: WALLET_PROVIDER_BASE_URL,
221
+ appFetch,
222
+ });
223
+
224
+ const idpHit = "https://example.com"; // Let's assume this is the IDP hint
225
+
226
+ const authorizationContext = idpHint.includes("servizicie")
227
+ ? undefined
228
+ : {
229
+ authorize: openAuthenticationSession, // Let's assume this function opens the browser for the user to authenticate
230
+ };
231
+ /*
232
+ * Create credential crypto context for the PID
233
+ * WARNING: The eID keytag must be persisted and later used when requesting a credential which requires a eID presentation
234
+ */
235
+ const credentialKeyTag = uuid.v4().toString();
236
+ await generate(credentialKeyTag);
237
+ const credentialCryptoContext = createCryptoContextFor(credentialKeyTag);
238
+
239
+ // Start the issuance flow
240
+ const startFlow: Credential.Issuance.StartFlow = () => ({
241
+ issuerUrl: WALLET_EID_PROVIDER_BASE_URL,
242
+ credentialType: "PersonIdentificationData",
243
+ appFetch,
244
+ });
245
+
246
+ const { issuerUrl } = startFlow();
247
+
248
+ // Evaluate issuer trust
249
+ const { issuerConf } = await Credential.Issuance.evaluateIssuerTrust(
250
+ issuerUrl,
251
+ { appFetch }
252
+ );
253
+
254
+ // Start user authorization
255
+ const { issuerRequestUri, clientId, codeVerifier, credentialDefinition } =
256
+ await Credential.Issuance.startUserAuthorization(issuerConf, credentialType, {
257
+ walletInstanceAttestation,
258
+ redirectUri,
259
+ wiaCryptoContext,
260
+ appFetch,
261
+ });
262
+
263
+ // Complete the authroization process with query mode with the authorizationContext which opens the browser
264
+ const { code } =
265
+ await Credential.Issuance.completeUserAuthorizationWithQueryMode(
266
+ issuerRequestUri,
267
+ clientId,
268
+ issuerConf,
269
+ idpHint,
270
+ redirectUri,
271
+ authorizationContext
272
+ );
273
+
274
+ // Create DPoP context which will be used for the whole issuance flow
275
+ await regenerateCryptoKey(DPOP_KEYTAG);
276
+ const dPopCryptoContext = createCryptoContextFor(DPOP_KEYTAG);
277
+
278
+ const { accessToken } = await Credential.Issuance.authorizeAccess(
279
+ issuerConf,
280
+ code,
281
+ clientId,
282
+ redirectUri,
283
+ codeVerifier,
284
+ {
285
+ walletInstanceAttestation,
286
+ wiaCryptoContext,
287
+ dPopCryptoContext,
288
+ appFetch,
289
+ }
290
+ );
291
+
292
+ // Obtain che eID credential
293
+ const { credential, format } = await Credential.Issuance.obtainCredential(
294
+ issuerConf,
295
+ accessToken,
296
+ clientId,
297
+ credentialDefinition,
298
+ {
299
+ credentialCryptoContext,
300
+ dPopCryptoContext,
301
+ appFetch,
302
+ }
303
+ );
304
+
305
+ // Parse and verify the eID credential
306
+ const { parsedCredential } = await Credential.Issuance.verifyAndParseCredential(
307
+ issuerConf,
308
+ credential,
309
+ format,
310
+ { credentialCryptoContext }
311
+ );
312
+
313
+ return {
314
+ parsedCredential,
315
+ credential,
316
+ keyTag: credentialKeyTag,
317
+ credentialType,
318
+ };
319
+ ```
320
+
321
+ The result of this flow is a row credential and a parsed credential which must be stored securely in the wallet along with its crypto key.
322
+
323
+ </details>
@@ -0,0 +1,3 @@
1
+ # Credential presentation
2
+
3
+ Currently this flow is outdated.
@@ -0,0 +1,64 @@
1
+ # Credential Status Attestation
2
+
3
+ This flow is used to obtain a credential status attestation from its credential issuer. Each step in the flow is imported from the related file which is named with a sequential number.
4
+ The credential status attestation is a JWT which contains the credential status which indicates if the credential is valid or not.
5
+ The status attestation is supposed to be stored securely along with the credential. It has a limited lifetime and should be refreshed periodically according to the `exp` field in the JWT payload.
6
+
7
+ ## Sequence Diagram
8
+
9
+ ```mermaid
10
+ graph TD;
11
+ 0[startFlow]
12
+ 1[statusAttestation]
13
+ 2[verifyAndParseStatusAttestation]
14
+
15
+ 0 --> 1
16
+ 1 --> 2
17
+ ```
18
+
19
+ ## Mapped results
20
+
21
+ ### 404 Not Found (StatusAttestationInvalid)
22
+
23
+ A `404 Not Found` response is returned by the credential issuer when the status attestation is invalid.
24
+
25
+ ## Example
26
+
27
+ <details>
28
+ <summary>Credential status attestation flow</summary>
29
+
30
+ ```ts
31
+ // Start the issuance flow
32
+ const credentialIssuerUrl = "https://issuer.example.com";
33
+ const startFlow: Credential.Status.StartFlow = () => ({
34
+ issuerUrl: credentialIssuerUrl, // Let's assum
35
+ });
36
+
37
+ const { issuerUrl } = startFlow();
38
+
39
+ // Evaluate issuer trust
40
+ const { issuerConf } = await Credential.Status.evaluateIssuerTrust(issuerUrl);
41
+
42
+ // Get the credential attestation
43
+ const res = await Credential.Status.statusAttestation(
44
+ issuerConf,
45
+ credential,
46
+ credentialCryptoContext
47
+ );
48
+
49
+ // Verify and parse the status attestation
50
+ const { parsedStatusAttestation } =
51
+ await Credential.Status.verifyAndParseStatusAttestation(
52
+ issuerConf,
53
+ res.statusAttestation,
54
+ { credentialCryptoContext }
55
+ );
56
+
57
+ return {
58
+ statusAttestation: res.statusAttestation,
59
+ parsedStatusAttestation,
60
+ credentialType,
61
+ };
62
+ ```
63
+
64
+ </details>
@@ -462,3 +462,21 @@ export class CredentialRequestError extends IoWalletError {
462
462
  this.reason = reason;
463
463
  }
464
464
  }
465
+
466
+ /**
467
+ * Error subclass thrown when a credential cannot be issued immediately because it follows the async flow.
468
+ */
469
+ export class CredentialIssuingNotSynchronousError extends IoWalletError {
470
+ static get code(): "CREDENTIAL_ISSUING_NOT_SYNCHRONOUS_ERROR" {
471
+ return "CREDENTIAL_ISSUING_NOT_SYNCHRONOUS_ERROR";
472
+ }
473
+
474
+ code = "CREDENTIAL_ISSUING_NOT_SYNCHRONOUS_ERROR";
475
+
476
+ reason: string;
477
+
478
+ constructor(message: string, reason: string = "unspecified") {
479
+ super(serializeAttrs({ message, reason }));
480
+ this.reason = reason;
481
+ }
482
+ }
@@ -0,0 +1,29 @@
1
+ # Wallet Instance
2
+
3
+ This flow which consists of a single step, is used to create a wallet instance. The wallet provider must implement its endpoints based on the OpenAPI specification provided in the [wallet-instance.yaml](../../openapi/wallet-provider.yaml) file.
4
+ A service that is responsible for ensuring the integrity of the device where the app is running is required and it's supposed to use [Google Play Integrity API](https://developer.android.com/google/play/integrity/overview) and [Key Attestation](https://developer.android.com/privacy-and-security/security-key-attestation) on Android, [DCAppAttestService](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) on iOS.
5
+ The suggested way to implement this service is to use [io-react-native-integrity](https://github.com/pagopa/io-react-native-integrity) by providing an [IntegrityContext](../utils/integrity.ts) object.
6
+ An example is provided as follows:
7
+
8
+ ```ts
9
+ // Get env
10
+ const { GOOGLE_CLOUD_PROJECT_NUMBER, WALLET_PROVIDER_BASE_URL } = env; // Let's assume env is an object containing the environment variables
11
+
12
+ const googleCloudProjectNumber = isAndroid
13
+ ? GOOGLE_CLOUD_PROJECT_NUMBER
14
+ : undefined;
15
+
16
+ await ensureIntegrityServiceIsReady(googleCloudProjectNumber); // Required by io-react-native-integrity to ensure the service is ready
17
+ const integrityKeyTag = await generateIntegrityHardwareKeyTag();
18
+ const integrityContext = getIntegrityContext(integrityKeyTag); // This function is supposed to return an object as required by IntegrityContext.
19
+
20
+ await WalletInstance.createWalletInstance({
21
+ integrityContext,
22
+ walletProviderBaseUrl: WALLET_PROVIDER_BASE_URL,
23
+ appFetch,
24
+ });
25
+
26
+ return integrityKeyTag;
27
+ ```
28
+
29
+ The returned `integrityKeyTag` is supposed to be stored and used to verify the integrity of the device in the future when using an `IntegrityContext` object. It must be regenerated if another wallet instance is created.
@@ -0,0 +1,35 @@
1
+ # Wallet Instance Attestation
2
+
3
+ This flow consists of a single step and is used to obtain a Wallet Instance Attestation. The wallet provider must implement its endpoints based on the OpenAPI specification provided in the [wallet-instance.yaml](../../openapi/wallet-provider.yaml) file.
4
+ In order to require a status attestation the consumer application must provide:
5
+
6
+ - `wiaCryptoContext` object that is used to sign the attestation request. The key must be generated before creating the crypto context;
7
+ - `integrityContext` object that is used to verify the integrity of the device where the app is running. The key tag must be the same used when creating the Wallet Instance;
8
+
9
+ ```ts
10
+ // Retrieve the integrity key tag from the store and create its context
11
+ const integrityKeyTag = "example"; // Let's assume this is the same key used when creating the Wallet Instance
12
+ const integrityContext = getIntegrityContext(integrityKeyTag);
13
+
14
+ // generate Key for Wallet Instance Attestation
15
+ // ensure the key esists befor starting the issuing process
16
+ await regenerateCryptoKey(WIA_KEYTAG); // Let's assume WI_KEYTAG is a constant string and regenerateCryptoKey is a function that regenerates the key each time it is called
17
+ const wiaCryptoContext = createCryptoContextFor(WIA_KEYTAG);
18
+
19
+ // Get env URLs
20
+ const { WALLET_PROVIDER_BASE_URL } = env; // Let's assume env is an object containing the environment variables
21
+
22
+ /**
23
+ * Obtains a new Wallet Instance Attestation.
24
+ * WARNING: The integrity context must be the same used when creating the Wallet Instance with the same keytag.
25
+ */
26
+ const issuedAttestation = await WalletInstanceAttestation.getAttestation({
27
+ wiaCryptoContext,
28
+ integrityContext,
29
+ walletProviderBaseUrl: WALLET_PROVIDER_BASE_URL,
30
+ appFetch,
31
+ });
32
+ return issuedAttestation;
33
+ ```
34
+
35
+ The returned `issuedAttestation` is supposed to be stored and used for any future operation that requires a Wallet Instance Attestation. The wallet attestation has a limited validity and must be regenerated when it expires.