@pagopa/io-react-native-wallet 2.0.0-next.0 → 2.0.0-next.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/lib/commonjs/trust/README.md +147 -0
  2. package/lib/commonjs/trust/chain.js +47 -10
  3. package/lib/commonjs/trust/chain.js.map +1 -1
  4. package/lib/commonjs/trust/errors.js +24 -1
  5. package/lib/commonjs/trust/errors.js.map +1 -1
  6. package/lib/commonjs/trust/index.js +11 -5
  7. package/lib/commonjs/trust/index.js.map +1 -1
  8. package/lib/commonjs/trust/utils.js +30 -1
  9. package/lib/commonjs/trust/utils.js.map +1 -1
  10. package/lib/module/trust/README.md +147 -0
  11. package/lib/module/trust/chain.js +49 -12
  12. package/lib/module/trust/chain.js.map +1 -1
  13. package/lib/module/trust/errors.js +23 -2
  14. package/lib/module/trust/errors.js.map +1 -1
  15. package/lib/module/trust/index.js +11 -5
  16. package/lib/module/trust/index.js.map +1 -1
  17. package/lib/module/trust/utils.js +27 -0
  18. package/lib/module/trust/utils.js.map +1 -1
  19. package/lib/typescript/client/generated/wallet-provider.d.ts +12 -12
  20. package/lib/typescript/credential/presentation/types.d.ts +4 -4
  21. package/lib/typescript/credential/status/types.d.ts +6 -6
  22. package/lib/typescript/sd-jwt/index.d.ts +12 -12
  23. package/lib/typescript/sd-jwt/types.d.ts +6 -6
  24. package/lib/typescript/trust/chain.d.ts +8 -6
  25. package/lib/typescript/trust/chain.d.ts.map +1 -1
  26. package/lib/typescript/trust/errors.d.ts +22 -0
  27. package/lib/typescript/trust/errors.d.ts.map +1 -1
  28. package/lib/typescript/trust/index.d.ts +208 -206
  29. package/lib/typescript/trust/index.d.ts.map +1 -1
  30. package/lib/typescript/trust/types.d.ts +559 -559
  31. package/lib/typescript/trust/utils.d.ts +10 -0
  32. package/lib/typescript/trust/utils.d.ts.map +1 -1
  33. package/lib/typescript/wallet-instance-attestation/types.d.ts +25 -25
  34. package/package.json +2 -2
  35. package/src/trust/README.md +147 -0
  36. package/src/trust/chain.ts +91 -15
  37. package/src/trust/errors.ts +32 -1
  38. package/src/trust/index.ts +11 -4
  39. package/src/trust/utils.ts +35 -0
@@ -1,4 +1,5 @@
1
1
  import type { JWK, JWTDecodeResult } from "../utils/jwk";
2
+ import type { TrustAnchorEntityConfiguration } from "./types";
2
3
  export type ParsedToken = {
3
4
  header: JWTDecodeResult["protectedHeader"];
4
5
  payload: JWTDecodeResult["payload"];
@@ -9,4 +10,13 @@ export declare const verify: (token: string, kid: string, jwks: JWK[]) => Promis
9
10
  * It seems like typescript can't correctly infer the return type of the function.
10
11
  */
11
12
  export declare const decode: (token: string) => ParsedToken;
13
+ /**
14
+ * Extracts the X.509 Trust Anchor certificate (Base64 encoded) from the
15
+ * Trust Anchor's Entity Configuration.
16
+ *
17
+ * @param trustAnchorEntity The entity configuration of the known trust anchor.
18
+ * @returns The Base64 encoded X.509 certificate string.
19
+ * @throws {FederationError} If the certificate cannot be derived.
20
+ */
21
+ export declare function getTrustAnchorX509Certificate(trustAnchorEntity: TrustAnchorEntityConfiguration): string;
12
22
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/trust/utils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,eAAe,CAAC,iBAAiB,CAAC,CAAC;IAC3C,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;CACrC,CAAC;AAIF,eAAO,MAAM,MAAM,UACV,MAAM,OACR,MAAM,QACL,GAAG,EAAE,KACV,QAAQ,WAAW,CAOrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,UAAW,MAAM,KAAG,WAGtC,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/trust/utils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,SAAS,CAAC;AAE9D,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,eAAe,CAAC,iBAAiB,CAAC,CAAC;IAC3C,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;CACrC,CAAC;AAIF,eAAO,MAAM,MAAM,UACV,MAAM,OACR,MAAM,QACL,GAAG,EAAE,KACV,QAAQ,WAAW,CAOrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,UAAW,MAAM,KAAG,WAGtC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,iBAAiB,EAAE,8BAA8B,GAChD,MAAM,CAqBR"}
@@ -246,6 +246,15 @@ export declare const WalletInstanceAttestationRequestJwt: z.ZodObject<{
246
246
  integrity_assertion: string;
247
247
  }>>;
248
248
  }, "strip", z.ZodTypeAny, {
249
+ header: {
250
+ alg: string;
251
+ kid: string;
252
+ typ: string;
253
+ x5c?: string[] | undefined;
254
+ trust_chain?: string[] | undefined;
255
+ } & {
256
+ typ: "wp-war+jwt";
257
+ };
249
258
  payload: {
250
259
  iss: string;
251
260
  iat: number;
@@ -285,6 +294,7 @@ export declare const WalletInstanceAttestationRequestJwt: z.ZodObject<{
285
294
  hardware_signature: string;
286
295
  integrity_assertion: string;
287
296
  };
297
+ }, {
288
298
  header: {
289
299
  alg: string;
290
300
  kid: string;
@@ -294,7 +304,6 @@ export declare const WalletInstanceAttestationRequestJwt: z.ZodObject<{
294
304
  } & {
295
305
  typ: "wp-war+jwt";
296
306
  };
297
- }, {
298
307
  payload: {
299
308
  iss: string;
300
309
  iat: number;
@@ -334,15 +343,6 @@ export declare const WalletInstanceAttestationRequestJwt: z.ZodObject<{
334
343
  hardware_signature: string;
335
344
  integrity_assertion: string;
336
345
  };
337
- header: {
338
- alg: string;
339
- kid: string;
340
- typ: string;
341
- x5c?: string[] | undefined;
342
- trust_chain?: string[] | undefined;
343
- } & {
344
- typ: "wp-war+jwt";
345
- };
346
346
  }>;
347
347
  export type WalletInstanceAttestationJwt = z.infer<typeof WalletInstanceAttestationJwt>;
348
348
  export declare const WalletInstanceAttestationJwt: z.ZodObject<{
@@ -591,6 +591,16 @@ export declare const WalletInstanceAttestationJwt: z.ZodObject<{
591
591
  wallet_name?: string | undefined;
592
592
  }>>;
593
593
  }, "strip", z.ZodTypeAny, {
594
+ header: {
595
+ alg: string;
596
+ kid: string;
597
+ typ: string;
598
+ x5c?: string[] | undefined;
599
+ trust_chain?: string[] | undefined;
600
+ } & {
601
+ typ: "oauth-client-attestation+jwt";
602
+ trust_chain: string[];
603
+ };
594
604
  payload: {
595
605
  iss: string;
596
606
  iat: number;
@@ -629,6 +639,7 @@ export declare const WalletInstanceAttestationJwt: z.ZodObject<{
629
639
  wallet_link?: string | undefined;
630
640
  wallet_name?: string | undefined;
631
641
  };
642
+ }, {
632
643
  header: {
633
644
  alg: string;
634
645
  kid: string;
@@ -639,7 +650,6 @@ export declare const WalletInstanceAttestationJwt: z.ZodObject<{
639
650
  typ: "oauth-client-attestation+jwt";
640
651
  trust_chain: string[];
641
652
  };
642
- }, {
643
653
  payload: {
644
654
  iss: string;
645
655
  iat: number;
@@ -678,16 +688,6 @@ export declare const WalletInstanceAttestationJwt: z.ZodObject<{
678
688
  wallet_link?: string | undefined;
679
689
  wallet_name?: string | undefined;
680
690
  };
681
- header: {
682
- alg: string;
683
- kid: string;
684
- typ: string;
685
- x5c?: string[] | undefined;
686
- trust_chain?: string[] | undefined;
687
- } & {
688
- typ: "oauth-client-attestation+jwt";
689
- trust_chain: string[];
690
- };
691
691
  }>;
692
692
  export type WalletAttestationResponse = z.infer<typeof WalletAttestationResponse>;
693
693
  export declare const WalletAttestationResponse: z.ZodObject<{
@@ -695,21 +695,21 @@ export declare const WalletAttestationResponse: z.ZodObject<{
695
695
  wallet_attestation: z.ZodString;
696
696
  format: z.ZodEnum<["jwt", "dc+sd-jwt", "mso_mdoc"]>;
697
697
  }, "strip", z.ZodTypeAny, {
698
- format: "jwt" | "dc+sd-jwt" | "mso_mdoc";
699
698
  wallet_attestation: string;
700
- }, {
701
699
  format: "jwt" | "dc+sd-jwt" | "mso_mdoc";
700
+ }, {
702
701
  wallet_attestation: string;
702
+ format: "jwt" | "dc+sd-jwt" | "mso_mdoc";
703
703
  }>, "many">;
704
704
  }, "strip", z.ZodTypeAny, {
705
705
  wallet_attestations: {
706
- format: "jwt" | "dc+sd-jwt" | "mso_mdoc";
707
706
  wallet_attestation: string;
707
+ format: "jwt" | "dc+sd-jwt" | "mso_mdoc";
708
708
  }[];
709
709
  }, {
710
710
  wallet_attestations: {
711
- format: "jwt" | "dc+sd-jwt" | "mso_mdoc";
712
711
  wallet_attestation: string;
712
+ format: "jwt" | "dc+sd-jwt" | "mso_mdoc";
713
713
  }[];
714
714
  }>;
715
715
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/io-react-native-wallet",
3
- "version": "2.0.0-next.0",
3
+ "version": "2.0.0-next.1",
4
4
  "description": "Provide data structures, helpers and API for IO Wallet",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -53,7 +53,7 @@
53
53
  "registry": "https://registry.npmjs.org/"
54
54
  },
55
55
  "devDependencies": {
56
- "@pagopa/io-react-native-crypto": "^0.2.3",
56
+ "@pagopa/io-react-native-crypto": "^1.2.2",
57
57
  "@pagopa/io-react-native-jwt": "^2.1.0",
58
58
  "@react-native/eslint-config": "^0.75.5",
59
59
  "@rushstack/eslint-patch": "^1.3.2",
@@ -0,0 +1,147 @@
1
+ # Trust Chain Validation
2
+
3
+ This module implements **Trust Chain validation** for Entity Configurations and Entity Statements in line with the [IT Wallet Federation Specifications](https://italia.github.io/eid-wallet-it-docs/). It ensures that an entity's metadata is trusted by validating a chain of signed JWTs up to a known Trust Anchor.
4
+
5
+ The validation covers:
6
+
7
+ * JWT signature verification (using the next entity's JWKS)
8
+ * Trust chain ordering (leaf → parent → Trust Anchor)
9
+ * Optional X.509 CRL-based certificate validation
10
+
11
+ ## Sequence Diagram
12
+
13
+ ```mermaid
14
+ sequenceDiagram
15
+ autonumber
16
+ participant A as Leaf Entity
17
+ participant B as Intermediate (Federation Authority)
18
+ participant C as Trust Anchor
19
+
20
+ A->>A: Self-issued Entity Configuration (JWT)
21
+ B->>A: Signed Entity Statement (JWT)
22
+ C->>B: Signed Entity Statement (JWT or self-issued EC)
23
+
24
+ Note over A,C: Each JWT is validated with the next issuer's public keys
25
+ ```
26
+
27
+ ## Errors
28
+
29
+ | Error | Description |
30
+ | ----------------------------- | ------------------------------------------------------------------ |
31
+ | `TrustChainEmptyError` | The input chain is empty. |
32
+ | `TrustChainTokenMissingError` | One of the JWTs in the chain is missing. |
33
+ | `X509ValidationError` | X.509 certificate validation failed (e.g. revocation, expiration). |
34
+ | `FederationError` | Generic federation processing error. |
35
+
36
+ ## Usage
37
+
38
+ ### Validate a trust chain
39
+
40
+ ```ts
41
+ import { validateTrustChain } from "./trust";
42
+ import { trustAnchorEntityConfiguration } from "./your-data";
43
+ import { chain } from "./your-data"; // array of JWTs, starting from leaf
44
+
45
+ const result = await validateTrustChain(trustAnchorEntityConfiguration, chain, {
46
+ connectTimeout: 3000,
47
+ readTimeout: 3000,
48
+ requireCrl: false,
49
+ });
50
+ ```
51
+
52
+ * The `chain` must be an array of signed JWT strings.
53
+ * The first JWT must be a self-issued `EntityConfiguration`.
54
+ * The last JWT must be an `EntityStatement` or a self-issued Trust Anchor `EntityConfiguration`.
55
+
56
+ ### Renew a trust chain
57
+
58
+ ```ts
59
+ import { renewTrustChain } from "./trust";
60
+
61
+ const newChain = await renewTrustChain(chain);
62
+ ```
63
+
64
+ This will fetch updated JWTs from each authority in the chain.
65
+
66
+ ### Build a trust chain
67
+
68
+ ```ts
69
+ import { buildTrustChain } from "./trust";
70
+
71
+ const chain = await buildTrustChain({
72
+ leaf: "https://example-leaf",
73
+ trustAnchor: trustAnchorEntityConfiguration,
74
+ });
75
+ ```
76
+
77
+ * **leaf**: the entity URL of the subject to be trusted.
78
+ * **trustAnchor**: the known trust anchor configuration.
79
+ * Returns a list of JWT strings ordered from leaf to trust anchor.
80
+
81
+
82
+ ## Trust Chain Structure
83
+
84
+ | Position | JWT Type | Requirements |
85
+ | -------- | ----------------------------------- |-------------------------------|
86
+ | First | Entity Configuration | `iss === sub` (self-issued) |
87
+ | Middle | Entity Statement | `iss ≠ sub`, signed by parent |
88
+ | Last | Entity Statement or Trust Anchor EC | Trust Anchor must be known |
89
+
90
+ ### Build and Validate Example
91
+
92
+ ```ts
93
+ import {
94
+ buildTrustChain,
95
+ validateTrustChain,
96
+ } from "./trust";
97
+ import { trustAnchorEntityConfiguration } from "./your-data";
98
+
99
+ const chain = await buildTrustChain({
100
+ leaf: "https://example-leaf",
101
+ trustAnchor: trustAnchorEntityConfiguration,
102
+ });
103
+
104
+ const result = await validateTrustChain(trustAnchorEntityConfiguration, chain, {
105
+ connectTimeout: 3000,
106
+ readTimeout: 3000,
107
+ requireCrl: true,
108
+ });
109
+ ```
110
+
111
+ * This example fetches and builds the full trust chain dynamically, then validates it end-to-end.
112
+
113
+ ## Example Trust Chain
114
+
115
+ ```ts
116
+ [
117
+ {
118
+ header: { alg: "ES256", kid: "leaf-kid" },
119
+ payload: { iss: "https://leaf", sub: "https://leaf", jwks: { keys: [...] } }
120
+ },
121
+ {
122
+ header: { alg: "ES256", kid: "intermediate-kid" },
123
+ payload: { iss: "https://intermediate", sub: "https://leaf", jwks: { keys: [...] } }
124
+ },
125
+ {
126
+ header: { alg: "ES256", kid: "ta-kid" },
127
+ payload: { iss: "https://ta", sub: "https://ta", jwks: { keys: [...] } }
128
+ }
129
+ ]
130
+ ```
131
+
132
+ ## Mocking in Tests
133
+
134
+ If you're testing in Node (not in React Native), you need to mock X.509 and crypto-native dependencies:
135
+
136
+ ```ts
137
+ jest.mock("@pagopa/io-react-native-crypto", () => ({
138
+ verifyCertificateChain: jest.fn().mockResolvedValue({
139
+ isValid: true,
140
+ validationStatus: "VALID",
141
+ errorMessage: undefined,
142
+ }),
143
+ generate: jest.fn().mockResolvedValue({ ... }),
144
+ }));
145
+ ```
146
+
147
+ Ensure mocked `JWK`s contain an `x5c` array to trigger certificate validation logic during tests.
@@ -6,13 +6,26 @@ import {
6
6
  import { JWK } from "../utils/jwk";
7
7
  import * as z from "zod";
8
8
  import { getSignedEntityConfiguration, getSignedEntityStatement } from ".";
9
- import { decode, type ParsedToken, verify } from "./utils";
10
9
  import {
10
+ decode,
11
+ getTrustAnchorX509Certificate,
12
+ type ParsedToken,
13
+ verify,
14
+ } from "./utils";
15
+ import {
16
+ FederationError,
11
17
  MissingFederationFetchEndpointError,
18
+ MissingX509CertsError,
12
19
  TrustChainEmptyError,
13
20
  TrustChainRenewalError,
14
21
  TrustChainTokenMissingError,
22
+ X509ValidationError,
15
23
  } from "./errors";
24
+ import {
25
+ type CertificateValidationResult,
26
+ verifyCertificateChain,
27
+ type X509CertificateOptions,
28
+ } from "@pagopa/io-react-native-crypto";
16
29
 
17
30
  // The first element of the chain is supposed to be the Entity Configuration for the document issuer
18
31
  const FirstElementShape = EntityConfiguration;
@@ -26,16 +39,18 @@ const LastElementShape = z.union([
26
39
  ]);
27
40
 
28
41
  /**
29
- * Validates a provided trust chain against a known trust
42
+ * Validates a provided trust chain against a known trust anchor, including X.509 certificate checks.
30
43
  *
31
- * @param trustAnchorEntity The entity configuration of the known trust anchor
32
- * @param chain The chain of statements to be validated
33
- * @returns The list of parsed token representing the chain
34
- * @throws {FederationError} If the chain is not valid
44
+ * @param trustAnchorEntity The entity configuration of the known trust anchor (for JWT validation).
45
+ * @param chain The chain of statements to be validated.
46
+ * @param x509Options Options for X.509 certificate validation.
47
+ * @returns The list of parsed tokens representing the chain.
48
+ * @throws {FederationError} If the chain is not valid (JWT or X.509). Specific errors like TrustChainEmptyError, X509ValidationError may be thrown.
35
49
  */
36
50
  export async function validateTrustChain(
37
51
  trustAnchorEntity: TrustAnchorEntityConfiguration,
38
- chain: string[]
52
+ chain: string[],
53
+ x509Options: X509CertificateOptions
39
54
  ): Promise<ParsedToken[]> {
40
55
  // If the chain is empty, fail
41
56
  if (chain.length === 0) {
@@ -50,7 +65,7 @@ export async function validateTrustChain(
50
65
  ? LastElementShape
51
66
  : MiddleElementShape;
52
67
 
53
- // select the kid from the current index
68
+ // Select the kid from the current index
54
69
  const selectKid = (currentIndex: number): string => {
55
70
  const token = chain[currentIndex];
56
71
  if (!token) {
@@ -63,8 +78,8 @@ export async function validateTrustChain(
63
78
  return shape.parse(decode(token)).header.kid;
64
79
  };
65
80
 
66
- // select keys from the next token
67
- // if the current token is the last, keys from trust anchor will be used
81
+ // Select keys from the next token
82
+ // If the current token is the last, keys from trust anchor will be used
68
83
  const selectKeys = (currentIndex: number): JWK[] => {
69
84
  if (currentIndex === chain.length - 1) {
70
85
  return trustAnchorEntity.payload.jwks.keys;
@@ -82,13 +97,74 @@ export async function validateTrustChain(
82
97
  return shape.parse(decode(nextToken)).payload.jwks.keys;
83
98
  };
84
99
 
100
+ const x509TrustAnchorCertBase64 =
101
+ getTrustAnchorX509Certificate(trustAnchorEntity);
102
+
85
103
  // Iterate the chain and validate each element's signature against the public keys of its next
86
104
  // If there is no next, hence it's the end of the chain, and it must be verified by the Trust Anchor
87
- return Promise.all(
88
- chain
89
- .map((token, i) => [token, selectKid(i), selectKeys(i)] as const)
90
- .map((args) => verify(...args))
91
- );
105
+ const validationPromises = chain.map(async (tokenString, i) => {
106
+ const kidFromTokenHeader = selectKid(i);
107
+ const signerJwks = selectKeys(i);
108
+
109
+ // Step 1: Verify JWT signature
110
+ const parsedToken = await verify(
111
+ tokenString,
112
+ kidFromTokenHeader,
113
+ signerJwks
114
+ );
115
+
116
+ // Step 2: X.509 Certificate Chain Validation
117
+ const jwkUsedForVerification = signerJwks.find(
118
+ (k) => k.kid === kidFromTokenHeader
119
+ );
120
+
121
+ if (!jwkUsedForVerification) {
122
+ throw new FederationError(
123
+ `JWK with kid '${kidFromTokenHeader}' was not found in signer's JWKS for token at index ${i}, though JWT verification passed.`,
124
+ { tokenIndex: i, kid: kidFromTokenHeader }
125
+ );
126
+ }
127
+
128
+ if (
129
+ !jwkUsedForVerification.x5c ||
130
+ jwkUsedForVerification.x5c.length === 0
131
+ ) {
132
+ throw new MissingX509CertsError(
133
+ `JWK with kid '${kidFromTokenHeader}' does not contain an X.509 certificate chain (x5c) for token at index ${i}.`
134
+ );
135
+ }
136
+
137
+ // If the chain has more than one certificate AND
138
+ // the last certificate in the x5c chain is the same as the trust anchor,
139
+ // remove the anchor from the chain being passed, as it's supplied separately.
140
+ const certChainBase64 =
141
+ jwkUsedForVerification.x5c.length > 1 &&
142
+ jwkUsedForVerification.x5c.at(-1) === x509TrustAnchorCertBase64
143
+ ? jwkUsedForVerification.x5c.slice(0, -1)
144
+ : jwkUsedForVerification.x5c;
145
+
146
+ const x509ValidationResult: CertificateValidationResult =
147
+ await verifyCertificateChain(
148
+ certChainBase64,
149
+ x509TrustAnchorCertBase64,
150
+ x509Options
151
+ );
152
+
153
+ if (!x509ValidationResult.isValid) {
154
+ throw new X509ValidationError(
155
+ `X.509 certificate chain validation failed for token at index ${i} (kid: ${kidFromTokenHeader}). Status: ${x509ValidationResult.validationStatus}. Error: ${x509ValidationResult.errorMessage}`,
156
+ {
157
+ tokenIndex: i,
158
+ kid: kidFromTokenHeader,
159
+ x509ValidationStatus: x509ValidationResult.validationStatus,
160
+ x509ErrorMessage: x509ValidationResult.errorMessage,
161
+ }
162
+ );
163
+ }
164
+ return parsedToken;
165
+ });
166
+
167
+ return Promise.all(validationPromises);
92
168
  }
93
169
 
94
170
  /**
@@ -1,4 +1,5 @@
1
- import { IoWalletError, serializeAttrs } from "../utils/errors"; // Ensure this path is correct
1
+ import { IoWalletError, serializeAttrs } from "../utils/errors";
2
+ import type { CertificateValidationStatus } from "@pagopa/io-react-native-crypto"; // Ensure this path is correct
2
3
 
3
4
  /**
4
5
  * Base class for all federation-specific errors.
@@ -103,3 +104,33 @@ export class MissingFederationFetchEndpointError extends FederationError {
103
104
  super(message, details);
104
105
  }
105
106
  }
107
+
108
+ /**
109
+ * Error thrown when the X.509 certificate chain is missing in an entity's configuration.
110
+ */
111
+ export class MissingX509CertsError extends FederationError {
112
+ code = "ERR_FED_MISSING_X509_CERTS";
113
+ constructor(message: string) {
114
+ super(message, undefined);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Error thrown when an X.509 certificate validation fails.
120
+ * This is used to indicate issues with the certificate chain or signature verification.
121
+ */
122
+ export class X509ValidationError extends FederationError {
123
+ code = "ERR_FED_X509_VALIDATION_FAILED";
124
+ constructor(
125
+ message: string,
126
+ details?: {
127
+ tokenIndex?: number;
128
+ kid?: string;
129
+ x509ValidationStatus?: CertificateValidationStatus;
130
+ x509ErrorMessage?: string;
131
+ [key: string]: unknown;
132
+ }
133
+ ) {
134
+ super(message, details);
135
+ }
136
+ }
@@ -19,6 +19,7 @@ import {
19
19
  RelyingPartyNotAuthorizedError,
20
20
  TrustAnchorKidMissingError,
21
21
  } from "./errors";
22
+ import type { X509CertificateOptions } from "@pagopa/io-react-native-crypto";
22
23
 
23
24
  export type {
24
25
  WalletProviderEntityConfiguration,
@@ -35,25 +36,31 @@ export type {
35
36
  *
36
37
  * @param trustAnchorEntity The entity configuration of the known trust anchor
37
38
  * @param chain The chain of statements to be validated
38
- * @param renewOnFail Whether to renew the provided chain if the validation fails at first. Default: true
39
- * @param appFetch Fetch api implementation. Default: the built-in implementation
39
+ * @param x509Options Options for the verification process
40
+ * @param appFetch (optional) fetch api implementation
41
+ * @param renewOnFail Whether to attempt to renew the trust chain if the initial validation fails
40
42
  * @returns The result of the chain validation
41
43
  * @throws {FederationError} If the chain is not valid
42
44
  */
43
45
  export async function verifyTrustChain(
44
46
  trustAnchorEntity: TrustAnchorEntityConfiguration,
45
47
  chain: string[],
48
+ x509Options: X509CertificateOptions = {
49
+ connectTimeout: 10000,
50
+ readTimeout: 10000,
51
+ requireCrl: true,
52
+ },
46
53
  {
47
54
  appFetch = fetch,
48
55
  renewOnFail = true,
49
56
  }: { appFetch?: GlobalFetch["fetch"]; renewOnFail?: boolean } = {}
50
57
  ): Promise<ReturnType<typeof validateTrustChain>> {
51
58
  try {
52
- return validateTrustChain(trustAnchorEntity, chain);
59
+ return validateTrustChain(trustAnchorEntity, chain, x509Options);
53
60
  } catch (error) {
54
61
  if (renewOnFail) {
55
62
  const renewedChain = await renewTrustChain(chain, appFetch);
56
- return validateTrustChain(trustAnchorEntity, renewedChain);
63
+ return validateTrustChain(trustAnchorEntity, renewedChain, x509Options);
57
64
  } else {
58
65
  throw error;
59
66
  }
@@ -4,6 +4,8 @@ import {
4
4
  } from "@pagopa/io-react-native-jwt";
5
5
 
6
6
  import type { JWK, JWTDecodeResult } from "../utils/jwk";
7
+ import { FederationError } from "./errors";
8
+ import type { TrustAnchorEntityConfiguration } from "./types";
7
9
 
8
10
  export type ParsedToken = {
9
11
  header: JWTDecodeResult["protectedHeader"];
@@ -33,3 +35,36 @@ export const decode = (token: string): ParsedToken => {
33
35
  const { protectedHeader: header, payload } = decodeJwt(token);
34
36
  return { header, payload };
35
37
  };
38
+
39
+ /**
40
+ * Extracts the X.509 Trust Anchor certificate (Base64 encoded) from the
41
+ * Trust Anchor's Entity Configuration.
42
+ *
43
+ * @param trustAnchorEntity The entity configuration of the known trust anchor.
44
+ * @returns The Base64 encoded X.509 certificate string.
45
+ * @throws {FederationError} If the certificate cannot be derived.
46
+ */
47
+ export function getTrustAnchorX509Certificate(
48
+ trustAnchorEntity: TrustAnchorEntityConfiguration
49
+ ): string {
50
+ const taHeaderKid = trustAnchorEntity.header.kid;
51
+ const taSigningJwk = trustAnchorEntity.payload.jwks.keys.find(
52
+ (key) => key.kid === taHeaderKid
53
+ );
54
+
55
+ if (!taSigningJwk) {
56
+ throw new FederationError(
57
+ `Cannot derive X.509 Trust Anchor certificate: JWK with kid '${taHeaderKid}' not found in Trust Anchor's JWKS.`,
58
+ { trustAnchorKid: taHeaderKid, reason: "JWK not found for header kid" }
59
+ );
60
+ }
61
+
62
+ if (taSigningJwk.x5c && taSigningJwk.x5c.length > 0 && taSigningJwk.x5c[0]) {
63
+ return taSigningJwk.x5c[0];
64
+ }
65
+
66
+ throw new FederationError(
67
+ `Cannot derive X.509 Trust Anchor certificate: JWK with kid '${taHeaderKid}' does not contain a valid 'x5c' certificate array.`,
68
+ { trustAnchorKid: taHeaderKid, reason: "Missing or empty x5c in JWK" }
69
+ );
70
+ }