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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>
@@ -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.