@pagopa/io-react-native-wallet 2.0.0 → 2.1.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 (33) hide show
  1. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +42 -128
  2. package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  3. package/lib/commonjs/sd-jwt/__test__/types.test.js +40 -0
  4. package/lib/commonjs/sd-jwt/__test__/types.test.js.map +1 -1
  5. package/lib/commonjs/sd-jwt/types.js +2 -1
  6. package/lib/commonjs/sd-jwt/types.js.map +1 -1
  7. package/lib/commonjs/trust/build-chain.js +22 -19
  8. package/lib/commonjs/trust/build-chain.js.map +1 -1
  9. package/lib/commonjs/utils/nestedProperty.js +142 -0
  10. package/lib/commonjs/utils/nestedProperty.js.map +1 -0
  11. package/lib/module/credential/issuance/07-verify-and-parse-credential.js +41 -127
  12. package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
  13. package/lib/module/sd-jwt/__test__/types.test.js +41 -1
  14. package/lib/module/sd-jwt/__test__/types.test.js.map +1 -1
  15. package/lib/module/sd-jwt/types.js +2 -1
  16. package/lib/module/sd-jwt/types.js.map +1 -1
  17. package/lib/module/trust/build-chain.js +22 -19
  18. package/lib/module/trust/build-chain.js.map +1 -1
  19. package/lib/module/utils/nestedProperty.js +136 -0
  20. package/lib/module/utils/nestedProperty.js.map +1 -0
  21. package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
  22. package/lib/typescript/sd-jwt/types.d.ts +5 -5
  23. package/lib/typescript/sd-jwt/types.d.ts.map +1 -1
  24. package/lib/typescript/trust/build-chain.d.ts +2 -3
  25. package/lib/typescript/trust/build-chain.d.ts.map +1 -1
  26. package/lib/typescript/utils/nestedProperty.d.ts +23 -0
  27. package/lib/typescript/utils/nestedProperty.d.ts.map +1 -0
  28. package/package.json +2 -2
  29. package/src/credential/issuance/07-verify-and-parse-credential.ts +2 -113
  30. package/src/sd-jwt/__test__/types.test.ts +49 -1
  31. package/src/sd-jwt/types.ts +2 -1
  32. package/src/trust/build-chain.ts +28 -25
  33. package/src/utils/nestedProperty.ts +198 -0
@@ -1,4 +1,3 @@
1
- import type { JWK } from "../utils/jwk";
2
1
  import {
3
2
  BuildTrustChainError,
4
3
  FederationListParseError,
@@ -266,39 +265,27 @@ export async function getFederationList(
266
265
  * Build a not-verified trust chain for a given Relying Party (RP) entity.
267
266
  *
268
267
  * @param relyingPartyEntityBaseUrl The base URL of the RP entity
269
- * @param trustAnchorKey The public key of the Trust Anchor (TA) entity
268
+ * @param trustAnchorConfig The entity configuration of the known trust anchor.
270
269
  * @param appFetch An optional instance of the http client to be used.
271
270
  * @returns A list of signed tokens that represent the trust chain, in the order of the chain (from the RP to the Trust Anchor)
272
271
  * @throws {FederationError} When an element of the chain fails to parse or other build steps fail.
273
272
  */
274
273
  export async function buildTrustChain(
275
274
  relyingPartyEntityBaseUrl: string,
276
- trustAnchorKey: JWK,
275
+ trustAnchorConfig: TrustAnchorEntityConfiguration,
277
276
  appFetch: GlobalFetch["fetch"] = fetch
278
277
  ): Promise<string[]> {
279
- // 1: Recursively gather the trust chain from the RP up to the Trust Anchor
280
- const trustChain = await gatherTrustChain(
281
- relyingPartyEntityBaseUrl,
282
- appFetch
283
- );
278
+ // 1: Verify if the RP is authorized by the Trust Anchor's federation list
279
+ // Extract the Trust Anchor's signing key and federation_list_endpoint
280
+ // (we assume the TA has only one key, as per spec)
281
+ const trustAnchorKey = trustAnchorConfig.payload.jwks.keys[0];
284
282
 
285
- // 2: Trust Anchor signature verification
286
- const trustAnchorJwt = trustChain[trustChain.length - 1];
287
- if (!trustAnchorJwt) {
283
+ if (!trustAnchorKey) {
288
284
  throw new BuildTrustChainError(
289
- "Cannot verify trust anchor: missing entity configuration in gathered chain.",
290
- { relyingPartyUrl: relyingPartyEntityBaseUrl }
285
+ "Cannot verify trust anchor: missing signing key in entity configuration."
291
286
  );
292
287
  }
293
288
 
294
- if (!trustAnchorKey.kid) {
295
- throw new TrustAnchorKidMissingError();
296
- }
297
-
298
- await verify(trustAnchorJwt, trustAnchorKey.kid, [trustAnchorKey]);
299
-
300
- // 3: Check the federation list
301
- const trustAnchorConfig = EntityConfiguration.parse(decode(trustAnchorJwt));
302
289
  const federationListEndpoint =
303
290
  trustAnchorConfig.payload.metadata.federation_entity
304
291
  .federation_list_endpoint;
@@ -316,6 +303,26 @@ export async function buildTrustChain(
316
303
  }
317
304
  }
318
305
 
306
+ // 1: Recursively gather the trust chain from the RP up to the Trust Anchor
307
+ const trustChain = await gatherTrustChain(
308
+ relyingPartyEntityBaseUrl,
309
+ appFetch
310
+ );
311
+ // 2: Trust Anchor signature verification
312
+ const chainTrustAnchorJwt = trustChain[trustChain.length - 1];
313
+ if (!chainTrustAnchorJwt) {
314
+ throw new BuildTrustChainError(
315
+ "Cannot verify trust anchor: missing entity configuration in gathered chain.",
316
+ { relyingPartyUrl: relyingPartyEntityBaseUrl }
317
+ );
318
+ }
319
+
320
+ if (!trustAnchorKey.kid) {
321
+ throw new TrustAnchorKidMissingError();
322
+ }
323
+
324
+ await verify(chainTrustAnchorJwt, trustAnchorKey.kid, [trustAnchorKey]);
325
+
319
326
  return trustChain;
320
327
  }
321
328
 
@@ -339,7 +346,6 @@ async function gatherTrustChain(
339
346
  appFetch,
340
347
  });
341
348
  const entityEC = EntityConfiguration.parse(decode(entityECJwt));
342
-
343
349
  if (isLeaf) {
344
350
  // Only push EC for the leaf
345
351
  chain.push(entityECJwt);
@@ -354,7 +360,6 @@ async function gatherTrustChain(
354
360
  }
355
361
  return chain;
356
362
  }
357
-
358
363
  const parentEntityBaseUrl = authorityHints[0]!;
359
364
 
360
365
  // Fetch parent EC
@@ -362,7 +367,6 @@ async function gatherTrustChain(
362
367
  appFetch,
363
368
  });
364
369
  const parentEC = EntityConfiguration.parse(decode(parentECJwt));
365
-
366
370
  // Fetch ES
367
371
  const federationFetchEndpoint =
368
372
  parentEC.payload.metadata.federation_entity.federation_fetch_endpoint;
@@ -372,7 +376,6 @@ async function gatherTrustChain(
372
376
  { entityBaseUrl, missingInEntityUrl: parentEntityBaseUrl }
373
377
  );
374
378
  }
375
-
376
379
  const entityStatementJwt = await getSignedEntityStatement(
377
380
  federationFetchEndpoint,
378
381
  entityBaseUrl,
@@ -0,0 +1,198 @@
1
+ import { isObject } from "./misc";
2
+
3
+ // The data used to create localized names
4
+ type DisplayData = { locale: string; name: string }[];
5
+
6
+ // The resulting object of localized names { en: "Name", it: "Nome" }
7
+ type LocalizedNames = Record<string, string>;
8
+
9
+ // The core structure being built: a node containing the actual value and its localized names
10
+ type PropertyNode<T> = {
11
+ value: T;
12
+ name: LocalizedNames;
13
+ };
14
+
15
+ // A path can consist of object keys, array indices, or null for mapping
16
+ type Path = (string | number | null)[];
17
+
18
+ // A union of all possible shapes. It can be a custom PropertyNode or a standard object/array structure
19
+ type NodeOrStructure = Partial<PropertyNode<any>> | Record<string, any> | any[];
20
+
21
+ // Helper to build localized names from the display data.
22
+ const buildName = (display: DisplayData): LocalizedNames =>
23
+ display.reduce(
24
+ (names, { locale, name }) => ({ ...names, [locale]: name }),
25
+ {}
26
+ );
27
+
28
+ // Handles the case where the path key is `null`
29
+ const handleNullKeyCase = (
30
+ currentObject: NodeOrStructure,
31
+ rest: Path,
32
+ sourceValue: unknown,
33
+ displayData: DisplayData
34
+ ): NodeOrStructure => {
35
+ if (!Array.isArray(sourceValue)) return currentObject;
36
+
37
+ // We assert the type here because we know this branch handles PropertyNodes
38
+ const node = currentObject as Partial<PropertyNode<unknown[]>>;
39
+ const existingValue = Array.isArray(node.value) ? node.value : [];
40
+
41
+ const mappedArray = sourceValue.map((item, idx) =>
42
+ createNestedProperty(existingValue[idx] || {}, rest, item, displayData)
43
+ );
44
+
45
+ return {
46
+ ...node,
47
+ value: mappedArray,
48
+ name: node.name ?? buildName(displayData),
49
+ };
50
+ };
51
+
52
+ // Handles the case where the path key is a string
53
+ const handleStringKeyCase = (
54
+ currentObject: NodeOrStructure,
55
+ key: string,
56
+ rest: Path,
57
+ sourceValue: unknown,
58
+ displayData: DisplayData
59
+ ): NodeOrStructure => {
60
+ let nextSourceValue = sourceValue;
61
+ const isLeaf = rest.length === 0;
62
+
63
+ if (isObject(sourceValue)) {
64
+ // Check if any remaining string keys in the path exist in current sourceValue
65
+ // This handles nested object paths (unlike arrays which use null in the path)
66
+ const hasRestKey = rest.some(
67
+ (r) => typeof r === "string" && r in sourceValue
68
+ );
69
+
70
+ if (hasRestKey) {
71
+ return handleRestKey(currentObject, key, rest, sourceValue, displayData);
72
+ }
73
+
74
+ // Skip processing when the key is not found within the claim object
75
+ if (!(key in sourceValue)) {
76
+ // Leaf node: create a node with an empty value and display name
77
+ if (isLeaf) {
78
+ return {
79
+ ...currentObject,
80
+ [key]: { value: {}, name: buildName(displayData) },
81
+ };
82
+ }
83
+ // Skip processing when the key is not found within the claim object
84
+ return currentObject;
85
+ }
86
+
87
+ nextSourceValue = sourceValue[key];
88
+ }
89
+
90
+ // base case
91
+ if (isLeaf) {
92
+ return {
93
+ ...currentObject,
94
+ [key]: { value: nextSourceValue, name: buildName(displayData) },
95
+ };
96
+ }
97
+
98
+ // recursive step
99
+ const nextObject =
100
+ (currentObject as Record<string, NodeOrStructure>)[key] || {};
101
+
102
+ return {
103
+ ...currentObject,
104
+ [key]: createNestedProperty(nextObject, rest, nextSourceValue, displayData),
105
+ };
106
+ };
107
+
108
+ // Handles the case where the path key is a number
109
+ const handleNumberKeyCase = (
110
+ currentObject: NodeOrStructure,
111
+ key: number,
112
+ rest: Path,
113
+ sourceValue: unknown,
114
+ displayData: DisplayData
115
+ ): NodeOrStructure => {
116
+ const newArray = Array.isArray(currentObject) ? [...currentObject] : [];
117
+ const nextValue = Array.isArray(sourceValue) ? sourceValue[key] : undefined;
118
+
119
+ newArray[key] = createNestedProperty(
120
+ newArray[key] || {},
121
+ rest,
122
+ nextValue,
123
+ displayData
124
+ );
125
+ return newArray;
126
+ };
127
+
128
+ /**
129
+ * Recursively constructs a nested object with descriptive properties from a path.
130
+ *
131
+ * @param currentObject - The object or array being built upon.
132
+ * @param path - The path segments to follow.
133
+ * @param sourceValue - The raw value to place at the end of the path.
134
+ * @param displayData - The data for generating localized names.
135
+ * @returns The new object or array structure.
136
+ */
137
+ export const createNestedProperty = (
138
+ currentObject: NodeOrStructure,
139
+ path: Path,
140
+ sourceValue: unknown, // Use `unknown` for type-safe input
141
+ displayData: DisplayData
142
+ ): NodeOrStructure => {
143
+ const [key, ...rest] = path;
144
+
145
+ switch (true) {
146
+ case key === null:
147
+ return handleNullKeyCase(currentObject, rest, sourceValue, displayData);
148
+
149
+ case typeof key === "string":
150
+ return handleStringKeyCase(
151
+ currentObject,
152
+ key as string,
153
+ rest,
154
+ sourceValue,
155
+ displayData
156
+ );
157
+
158
+ case typeof key === "number":
159
+ return handleNumberKeyCase(
160
+ currentObject,
161
+ key as number,
162
+ rest,
163
+ sourceValue,
164
+ displayData
165
+ );
166
+
167
+ default:
168
+ return currentObject;
169
+ }
170
+ };
171
+
172
+ // Handles the case where the next key in the path exists in the source object
173
+ const handleRestKey = (
174
+ currentObject: Record<string, any>,
175
+ key: string,
176
+ rest: Path,
177
+ sourceValue: Record<string, unknown>,
178
+ displayData: DisplayData
179
+ ): NodeOrStructure => {
180
+ const currentNode = currentObject[key] ?? {};
181
+ // Take the first key in the remaining path
182
+ const restKey = rest[0] as string;
183
+ const nextSourceValue = sourceValue[restKey];
184
+
185
+ // Merge the current node with the updated nested property for the remaining path.
186
+ return {
187
+ ...currentObject,
188
+ [key]: {
189
+ ...currentNode,
190
+ value: createNestedProperty(
191
+ currentNode.value ?? {},
192
+ rest,
193
+ nextSourceValue,
194
+ displayData
195
+ ),
196
+ },
197
+ };
198
+ };