@pagopa/io-react-native-wallet 0.27.1 → 0.28.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.
- package/lib/commonjs/client/generated/wallet-provider.js +27 -19
- package/lib/commonjs/client/generated/wallet-provider.js.map +1 -1
- package/lib/commonjs/trust/chain.js +35 -50
- package/lib/commonjs/trust/chain.js.map +1 -1
- package/lib/commonjs/trust/index.js +139 -16
- package/lib/commonjs/trust/index.js.map +1 -1
- package/lib/commonjs/trust/types.js +10 -6
- package/lib/commonjs/trust/types.js.map +1 -1
- package/lib/commonjs/trust/utils.js +36 -0
- package/lib/commonjs/trust/utils.js.map +1 -0
- package/lib/commonjs/wallet-instance/index.js +10 -0
- package/lib/commonjs/wallet-instance/index.js.map +1 -1
- package/lib/module/client/generated/wallet-provider.js +22 -15
- package/lib/module/client/generated/wallet-provider.js.map +1 -1
- package/lib/module/trust/chain.js +32 -46
- package/lib/module/trust/chain.js.map +1 -1
- package/lib/module/trust/index.js +139 -18
- package/lib/module/trust/index.js.map +1 -1
- package/lib/module/trust/types.js +8 -5
- package/lib/module/trust/types.js.map +1 -1
- package/lib/module/trust/utils.js +28 -0
- package/lib/module/trust/utils.js.map +1 -0
- package/lib/module/wallet-instance/index.js +9 -0
- package/lib/module/wallet-instance/index.js.map +1 -1
- package/lib/typescript/client/generated/wallet-provider.d.ts +91 -54
- package/lib/typescript/client/generated/wallet-provider.d.ts.map +1 -1
- package/lib/typescript/credential/status/types.d.ts +6 -6
- package/lib/typescript/sd-jwt/index.d.ts +12 -12
- package/lib/typescript/sd-jwt/types.d.ts +6 -6
- package/lib/typescript/trust/chain.d.ts +4 -9
- package/lib/typescript/trust/chain.d.ts.map +1 -1
- package/lib/typescript/trust/index.d.ts +101 -53
- package/lib/typescript/trust/index.d.ts.map +1 -1
- package/lib/typescript/trust/types.d.ts +761 -64
- package/lib/typescript/trust/types.d.ts.map +1 -1
- package/lib/typescript/trust/utils.d.ts +12 -0
- package/lib/typescript/trust/utils.d.ts.map +1 -0
- package/lib/typescript/utils/errors.d.ts.map +1 -1
- package/lib/typescript/utils/misc.d.ts.map +1 -1
- package/lib/typescript/wallet-instance/index.d.ts +8 -0
- package/lib/typescript/wallet-instance/index.d.ts.map +1 -1
- package/lib/typescript/wallet-instance-attestation/types.d.ts +20 -20
- package/package.json +10 -11
- package/src/client/generated/wallet-provider.ts +28 -19
- package/src/credential/presentation/types.ts +1 -1
- package/src/trust/chain.ts +48 -68
- package/src/trust/index.ts +185 -20
- package/src/trust/types.ts +9 -5
- package/src/trust/utils.ts +32 -0
- package/src/utils/errors.ts +2 -2
- package/src/utils/misc.ts +2 -2
- package/src/wallet-instance/index.ts +13 -0
package/src/trust/index.ts
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
+
import { decode, verify } from "./utils";
|
1
2
|
import { decode as decodeJwt } from "@pagopa/io-react-native-jwt";
|
2
3
|
import {
|
3
|
-
WalletProviderEntityConfiguration,
|
4
|
-
TrustAnchorEntityConfiguration,
|
5
4
|
CredentialIssuerEntityConfiguration,
|
6
|
-
RelyingPartyEntityConfiguration,
|
7
5
|
EntityConfiguration,
|
8
6
|
EntityStatement,
|
7
|
+
FederationListResponse,
|
8
|
+
RelyingPartyEntityConfiguration,
|
9
|
+
TrustAnchorEntityConfiguration,
|
10
|
+
WalletProviderEntityConfiguration,
|
9
11
|
} from "./types";
|
10
|
-
import {
|
12
|
+
import { renewTrustChain, validateTrustChain } from "./chain";
|
11
13
|
import { hasStatusOrThrow } from "../utils/misc";
|
14
|
+
import { IoWalletError } from "../utils/errors";
|
15
|
+
import type { JWK } from "../utils/jwk";
|
12
16
|
|
13
17
|
export type {
|
14
18
|
WalletProviderEntityConfiguration,
|
@@ -24,9 +28,9 @@ export type {
|
|
24
28
|
* It can handle fast chain renewal, which means we try to fetch a fresh version of each statement.
|
25
29
|
*
|
26
30
|
* @param trustAnchorEntity The entity configuration of the known trust anchor
|
27
|
-
* @param chain The chain of statements to be
|
28
|
-
* @param
|
29
|
-
* @param
|
31
|
+
* @param chain The chain of statements to be validated
|
32
|
+
* @param renewOnFail Whether to renew the provided chain if the validation fails at first. Default: true
|
33
|
+
* @param appFetch Fetch api implementation. Default: the built-in implementation
|
30
34
|
* @returns The result of the chain validation
|
31
35
|
* @throws {IoWalletError} When either validation or renewal fail
|
32
36
|
*/
|
@@ -54,7 +58,7 @@ export async function verifyTrustChain(
|
|
54
58
|
* Fetch the signed entity configuration token for an entity
|
55
59
|
*
|
56
60
|
* @param entityBaseUrl The url of the entity to fetch
|
57
|
-
* @param
|
61
|
+
* @param appFetch (optional) fetch api implementation
|
58
62
|
* @returns The signed Entity Configuration token
|
59
63
|
*/
|
60
64
|
export async function getSignedEntityConfiguration(
|
@@ -86,6 +90,7 @@ export async function getSignedEntityConfiguration(
|
|
86
90
|
*
|
87
91
|
* @param entityBaseUrl The base url of the entity.
|
88
92
|
* @param schema The expected schema of the entity configuration, according to the kind of entity we are fetching from.
|
93
|
+
* @param options An optional object with additional options.
|
89
94
|
* @param options.appFetch An optional instance of the http client to be used.
|
90
95
|
* @returns The parsed entity configuration object
|
91
96
|
* @throws {IoWalletError} If the http request fails
|
@@ -200,9 +205,9 @@ export const getEntityConfiguration = (
|
|
200
205
|
/**
|
201
206
|
* Fetch and parse the entity statement document for a given federation entity.
|
202
207
|
*
|
203
|
-
* @param accreditationBodyBaseUrl The base url of the
|
208
|
+
* @param accreditationBodyBaseUrl The base url of the accreditation body which holds and signs the required entity statement
|
204
209
|
* @param subordinatedEntityBaseUrl The url that identifies the subordinate entity
|
205
|
-
* @param
|
210
|
+
* @param appFetch An optional instance of the http client to be used.
|
206
211
|
* @returns The parsed entity configuration object
|
207
212
|
* @throws {IoWalletError} If the http request fails
|
208
213
|
* @throws Parse error if the document is not in the expected shape.
|
@@ -234,14 +239,14 @@ export async function getEntityStatement(
|
|
234
239
|
/**
|
235
240
|
* Fetch the entity statement document for a given federation entity.
|
236
241
|
*
|
237
|
-
* @param
|
238
|
-
* @param subordinatedEntityBaseUrl The url that identifies the subordinate entity
|
239
|
-
* @param
|
240
|
-
* @returns The signed entity statement token
|
241
|
-
* @throws {IoWalletError} If the http request fails
|
242
|
+
* @param federationFetchEndpoint The exact endpoint provided by the parent EC's metadata.
|
243
|
+
* @param subordinatedEntityBaseUrl The url that identifies the subordinate entity.
|
244
|
+
* @param appFetch An optional instance of the http client to be used.
|
245
|
+
* @returns The signed entity statement token.
|
246
|
+
* @throws {IoWalletError} If the http request fails.
|
242
247
|
*/
|
243
248
|
export async function getSignedEntityStatement(
|
244
|
-
|
249
|
+
federationFetchEndpoint: string,
|
245
250
|
subordinatedEntityBaseUrl: string,
|
246
251
|
{
|
247
252
|
appFetch = fetch,
|
@@ -249,13 +254,173 @@ export async function getSignedEntityStatement(
|
|
249
254
|
appFetch?: GlobalFetch["fetch"];
|
250
255
|
} = {}
|
251
256
|
) {
|
252
|
-
const url =
|
253
|
-
|
254
|
-
})}`;
|
257
|
+
const url = new URL(federationFetchEndpoint);
|
258
|
+
url.searchParams.set("sub", subordinatedEntityBaseUrl);
|
255
259
|
|
256
|
-
return await appFetch(url, {
|
260
|
+
return await appFetch(url.toString(), {
|
257
261
|
method: "GET",
|
258
262
|
})
|
259
263
|
.then(hasStatusOrThrow(200))
|
260
264
|
.then((res) => res.text());
|
261
265
|
}
|
266
|
+
|
267
|
+
/**
|
268
|
+
* Fetch the federation list document from a given endpoint.
|
269
|
+
*
|
270
|
+
* @param federationListEndpoint The URL of the federation list endpoint.
|
271
|
+
* @param appFetch An optional instance of the http client to be used.
|
272
|
+
* @returns The federation list as an array of strings.
|
273
|
+
* @throws {IoWalletError} If the HTTP request fails or the response cannot be parsed.
|
274
|
+
*/
|
275
|
+
export async function getFederationList(
|
276
|
+
federationListEndpoint: string,
|
277
|
+
{
|
278
|
+
appFetch = fetch,
|
279
|
+
}: {
|
280
|
+
appFetch?: GlobalFetch["fetch"];
|
281
|
+
} = {}
|
282
|
+
): Promise<string[]> {
|
283
|
+
return await appFetch(federationListEndpoint, {
|
284
|
+
method: "GET",
|
285
|
+
})
|
286
|
+
.then(hasStatusOrThrow(200))
|
287
|
+
.then((res) => res.json())
|
288
|
+
.then((json) => {
|
289
|
+
const result = FederationListResponse.safeParse(json);
|
290
|
+
if (!result.success) {
|
291
|
+
throw new IoWalletError(
|
292
|
+
`Invalid federation list format received from Trust Anchor: ${result.error.message}`
|
293
|
+
);
|
294
|
+
}
|
295
|
+
return result.data;
|
296
|
+
});
|
297
|
+
}
|
298
|
+
|
299
|
+
/**
|
300
|
+
* Build a not-verified trust chain for a given Relying Party (RP) entity.
|
301
|
+
*
|
302
|
+
* @param relyingPartyEntityBaseUrl The base URL of the RP entity
|
303
|
+
* @param trustAnchorKey The public key of the Trust Anchor (TA) entity
|
304
|
+
* @param appFetch An optional instance of the http client to be used.
|
305
|
+
* @returns A list of signed tokens that represent the trust chain, in the order of the chain (from the RP to the Trust Anchor)
|
306
|
+
* @throws {IoWalletError} When an element of the chain fails to parse
|
307
|
+
* The result of this function can be used to validate the trust chain with {@link verifyTrustChain}
|
308
|
+
*/
|
309
|
+
export async function buildTrustChain(
|
310
|
+
relyingPartyEntityBaseUrl: string,
|
311
|
+
trustAnchorKey: JWK,
|
312
|
+
appFetch: GlobalFetch["fetch"] = fetch
|
313
|
+
): Promise<string[]> {
|
314
|
+
// 1: Recursively gather the trust chain from the RP up to the Trust Anchor
|
315
|
+
const trustChain = await gatherTrustChain(
|
316
|
+
relyingPartyEntityBaseUrl,
|
317
|
+
appFetch
|
318
|
+
);
|
319
|
+
|
320
|
+
// 2: Trust Anchor signature verification
|
321
|
+
const trustAnchorJwt = trustChain[trustChain.length - 1];
|
322
|
+
if (!trustAnchorJwt) {
|
323
|
+
throw new IoWalletError(
|
324
|
+
"Cannot verify trust anchor: missing entity configuration."
|
325
|
+
);
|
326
|
+
}
|
327
|
+
|
328
|
+
if (!trustAnchorKey.kid) {
|
329
|
+
throw new IoWalletError("Missing 'kid' in provided Trust Anchor key.");
|
330
|
+
}
|
331
|
+
|
332
|
+
await verify(trustAnchorJwt, trustAnchorKey.kid, [trustAnchorKey]);
|
333
|
+
|
334
|
+
// 3: Check the federation list
|
335
|
+
const trustAnchorConfig = EntityConfiguration.parse(decode(trustAnchorJwt));
|
336
|
+
const federationListEndpoint =
|
337
|
+
trustAnchorConfig.payload.metadata.federation_entity
|
338
|
+
.federation_list_endpoint;
|
339
|
+
|
340
|
+
if (federationListEndpoint) {
|
341
|
+
const federationList = await getFederationList(federationListEndpoint, {
|
342
|
+
appFetch,
|
343
|
+
});
|
344
|
+
|
345
|
+
if (!federationList.includes(relyingPartyEntityBaseUrl)) {
|
346
|
+
throw new IoWalletError(
|
347
|
+
"Relying Party entity base URL is not authorized by the Trust Anchor's federation list."
|
348
|
+
);
|
349
|
+
}
|
350
|
+
}
|
351
|
+
|
352
|
+
return trustChain;
|
353
|
+
}
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Recursively gather the trust chain for an entity and all its superiors.
|
357
|
+
* @param entityBaseUrl The base URL of the entity for which to gather the chain.
|
358
|
+
* @param appFetch An optional instance of the http client to be used.
|
359
|
+
* @param isLeaf Whether the current entity is the leaf of the chain.
|
360
|
+
* @returns A full ordered list of JWTs (ECs and ESs) forming the trust chain.
|
361
|
+
*/
|
362
|
+
async function gatherTrustChain(
|
363
|
+
entityBaseUrl: string,
|
364
|
+
appFetch: GlobalFetch["fetch"],
|
365
|
+
isLeaf: boolean = true
|
366
|
+
): Promise<string[]> {
|
367
|
+
const chain: string[] = [];
|
368
|
+
|
369
|
+
// Fetch self-signed EC (only needed for the leaf)
|
370
|
+
const entityECJwt = await getSignedEntityConfiguration(entityBaseUrl, {
|
371
|
+
appFetch,
|
372
|
+
});
|
373
|
+
const entityEC = EntityConfiguration.parse(decode(entityECJwt));
|
374
|
+
|
375
|
+
if (isLeaf) {
|
376
|
+
// Only push EC for the leaf
|
377
|
+
chain.push(entityECJwt);
|
378
|
+
}
|
379
|
+
|
380
|
+
// Find authority_hints (parent, if any)
|
381
|
+
const authorityHints = entityEC.payload.authority_hints ?? [];
|
382
|
+
if (authorityHints.length === 0) {
|
383
|
+
// This is the Trust Anchor (no parent)
|
384
|
+
if (!isLeaf) {
|
385
|
+
chain.push(entityECJwt);
|
386
|
+
}
|
387
|
+
return chain;
|
388
|
+
}
|
389
|
+
|
390
|
+
const parentEntityBaseUrl = authorityHints[0]!;
|
391
|
+
|
392
|
+
// Fetch parent EC
|
393
|
+
const parentECJwt = await getSignedEntityConfiguration(parentEntityBaseUrl, {
|
394
|
+
appFetch,
|
395
|
+
});
|
396
|
+
const parentEC = EntityConfiguration.parse(decode(parentECJwt));
|
397
|
+
|
398
|
+
// Fetch ES
|
399
|
+
const federationFetchEndpoint =
|
400
|
+
parentEC.payload.metadata.federation_entity.federation_fetch_endpoint;
|
401
|
+
if (!federationFetchEndpoint) {
|
402
|
+
throw new IoWalletError(
|
403
|
+
"Missing federation_fetch_endpoint in parent's configuration."
|
404
|
+
);
|
405
|
+
}
|
406
|
+
|
407
|
+
const entityStatementJwt = await getSignedEntityStatement(
|
408
|
+
federationFetchEndpoint,
|
409
|
+
entityBaseUrl,
|
410
|
+
{ appFetch }
|
411
|
+
);
|
412
|
+
// Validate the ES
|
413
|
+
EntityStatement.parse(decode(entityStatementJwt));
|
414
|
+
|
415
|
+
// Push this ES into the chain
|
416
|
+
chain.push(entityStatementJwt);
|
417
|
+
|
418
|
+
// Recurse into the parent
|
419
|
+
const parentChain = await gatherTrustChain(
|
420
|
+
parentEntityBaseUrl,
|
421
|
+
appFetch,
|
422
|
+
false
|
423
|
+
);
|
424
|
+
|
425
|
+
return chain.concat(parentChain);
|
426
|
+
}
|
package/src/trust/types.ts
CHANGED
@@ -12,7 +12,6 @@ const RelyingPartyMetadata = z.object({
|
|
12
12
|
jwks: z.object({ keys: z.array(JWK) }),
|
13
13
|
contacts: z.array(z.string()).optional(),
|
14
14
|
});
|
15
|
-
//.passthrough();
|
16
15
|
|
17
16
|
// Display metadata for a credential, used by the issuer to
|
18
17
|
// instruct the Wallet Solution on how to render the credential correctly
|
@@ -50,7 +49,7 @@ const IssuanceErrorSupported = z.object({
|
|
50
49
|
),
|
51
50
|
});
|
52
51
|
|
53
|
-
// Metadata for a
|
52
|
+
// Metadata for a credential which is supported by an Issuer
|
54
53
|
type SupportedCredentialMetadata = z.infer<typeof SupportedCredentialMetadata>;
|
55
54
|
const SupportedCredentialMetadata = z.object({
|
56
55
|
format: z.union([z.literal("vc+sd-jwt"), z.literal("vc+mdoc-cbor")]),
|
@@ -74,7 +73,7 @@ export const EntityStatement = z.object({
|
|
74
73
|
iss: z.string(),
|
75
74
|
sub: z.string(),
|
76
75
|
jwks: z.object({ keys: z.array(JWK) }),
|
77
|
-
trust_marks: z.array(TrustMark),
|
76
|
+
trust_marks: z.array(TrustMark).optional(),
|
78
77
|
iat: z.number(),
|
79
78
|
exp: z.number(),
|
80
79
|
}),
|
@@ -90,7 +89,7 @@ export const EntityConfigurationHeader = z.object({
|
|
90
89
|
});
|
91
90
|
|
92
91
|
/**
|
93
|
-
* @see https://openid.net/specs/openid-
|
92
|
+
* @see https://openid.net/specs/openid-federation-1_0-41.html
|
94
93
|
*/
|
95
94
|
const FederationEntityMetadata = z
|
96
95
|
.object({
|
@@ -99,6 +98,9 @@ const FederationEntityMetadata = z
|
|
99
98
|
federation_resolve_endpoint: z.string().optional(),
|
100
99
|
federation_trust_mark_status_endpoint: z.string().optional(),
|
101
100
|
federation_trust_mark_list_endpoint: z.string().optional(),
|
101
|
+
federation_trust_mark_endpoint: z.string().optional(),
|
102
|
+
federation_historical_keys_endpoint: z.string().optional(),
|
103
|
+
endpoint_auth_signing_alg_values_supported: z.string().optional(),
|
102
104
|
organization_name: z.string().optional(),
|
103
105
|
homepage_uri: z.string().optional(),
|
104
106
|
policy_uri: z.string().optional(),
|
@@ -107,7 +109,7 @@ const FederationEntityMetadata = z
|
|
107
109
|
})
|
108
110
|
.passthrough();
|
109
111
|
|
110
|
-
//
|
112
|
+
// Structure common to every Entity Configuration document
|
111
113
|
const BaseEntityConfiguration = z.object({
|
112
114
|
header: EntityConfigurationHeader,
|
113
115
|
payload: z
|
@@ -232,3 +234,5 @@ export const EntityConfiguration = z.union(
|
|
232
234
|
description: "Any kind of Entity Configuration allowed in the ecosystem",
|
233
235
|
}
|
234
236
|
);
|
237
|
+
|
238
|
+
export const FederationListResponse = z.array(z.string());
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import {
|
2
|
+
decode as decodeJwt,
|
3
|
+
verify as verifyJwt,
|
4
|
+
} from "@pagopa/io-react-native-jwt";
|
5
|
+
|
6
|
+
import type { JWK } from "../utils/jwk";
|
7
|
+
import type { JWTDecodeResult } from "@pagopa/io-react-native-jwt/lib/typescript/types";
|
8
|
+
|
9
|
+
export type ParsedToken = {
|
10
|
+
header: JWTDecodeResult["protectedHeader"];
|
11
|
+
payload: JWTDecodeResult["payload"];
|
12
|
+
};
|
13
|
+
|
14
|
+
// Verify a token signature
|
15
|
+
// The kid is extracted from the token header
|
16
|
+
export const verify = async (
|
17
|
+
token: string,
|
18
|
+
kid: string,
|
19
|
+
jwks: JWK[]
|
20
|
+
): Promise<ParsedToken> => {
|
21
|
+
const jwk = jwks.find((k) => k.kid === kid);
|
22
|
+
if (!jwk) {
|
23
|
+
throw new Error(`Invalid kid: ${kid}, token: ${token}`);
|
24
|
+
}
|
25
|
+
const { protectedHeader: header, payload } = await verifyJwt(token, jwk);
|
26
|
+
return { header, payload };
|
27
|
+
};
|
28
|
+
|
29
|
+
export const decode = (token: string) => {
|
30
|
+
const { protectedHeader: header, payload } = decodeJwt(token);
|
31
|
+
return { header, payload };
|
32
|
+
};
|
package/src/utils/errors.ts
CHANGED
@@ -225,8 +225,8 @@ export const isWalletProviderResponseError = (
|
|
225
225
|
type ErrorCodeMap<T> = T extends typeof IssuerResponseError
|
226
226
|
? IssuerResponseErrorCode
|
227
227
|
: T extends typeof WalletProviderResponseError
|
228
|
-
|
229
|
-
|
228
|
+
? WalletProviderResponseErrorCode
|
229
|
+
: never;
|
230
230
|
|
231
231
|
type ErrorCase<T> = {
|
232
232
|
code: ErrorCodeMap<T>;
|
package/src/utils/misc.ts
CHANGED
@@ -37,8 +37,8 @@ export const parseRawHttpResponse = <T extends Record<string, unknown>>(
|
|
37
37
|
export type Out<FN> = FN extends (...args: any[]) => Promise<any>
|
38
38
|
? Awaited<ReturnType<FN>>
|
39
39
|
: FN extends (...args: any[]) => any
|
40
|
-
|
41
|
-
|
40
|
+
? ReturnType<FN>
|
41
|
+
: never;
|
42
42
|
|
43
43
|
/**
|
44
44
|
* TODO [SIW-1310]: replace this function with a cryptographically secure one.
|
@@ -87,3 +87,16 @@ export async function getWalletInstanceStatus(context: {
|
|
87
87
|
path: { id: context.id },
|
88
88
|
});
|
89
89
|
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* Get the status of the current Wallet Instance.
|
93
|
+
* @returns Details on the status of the current Wallet Instance
|
94
|
+
*/
|
95
|
+
export async function getCurrentWalletInstanceStatus(context: {
|
96
|
+
walletProviderBaseUrl: string;
|
97
|
+
appFetch?: GlobalFetch["fetch"];
|
98
|
+
}): Promise<WalletInstanceData> {
|
99
|
+
const api = getWalletProviderClient(context);
|
100
|
+
|
101
|
+
return api.get("/wallet-instances/current/status");
|
102
|
+
}
|