edockit 0.2.4 → 0.4.0-dev.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +81 -175
  3. package/TRUSTED-LIST.md +308 -0
  4. package/dist/certificate-c46e14a0.js +560 -0
  5. package/dist/certificate-c46e14a0.js.map +1 -0
  6. package/dist/certificate-fc0e06f7.js +571 -0
  7. package/dist/certificate-fc0e06f7.js.map +1 -0
  8. package/dist/core/canonicalization/XMLCanonicalizer.d.ts +9 -3
  9. package/dist/core/rsa-digestinfo-workaround.d.ts +29 -0
  10. package/dist/core/trustedlist/build.d.ts +41 -0
  11. package/dist/core/trustedlist/bundled-provider.d.ts +2 -0
  12. package/dist/core/trustedlist/contract.d.ts +19 -0
  13. package/dist/core/trustedlist/dom.d.ts +12 -0
  14. package/dist/core/trustedlist/extract.d.ts +6 -0
  15. package/dist/core/trustedlist/http.d.ts +8 -0
  16. package/dist/core/trustedlist/identity.d.ts +7 -0
  17. package/dist/core/trustedlist/index.d.ts +18 -0
  18. package/dist/core/trustedlist/loader.d.ts +5 -0
  19. package/dist/core/trustedlist/matcher.d.ts +11 -0
  20. package/dist/core/trustedlist/normalize.d.ts +14 -0
  21. package/dist/core/trustedlist/reference-provider.d.ts +12 -0
  22. package/dist/core/trustedlist/types.d.ts +114 -0
  23. package/dist/core/unzip.d.ts +0 -0
  24. package/dist/core/verification.d.ts +50 -0
  25. package/dist/data/trusted-list.d.ts +3 -0
  26. package/dist/identity-1a3dddc3.js +902 -0
  27. package/dist/identity-1a3dddc3.js.map +1 -0
  28. package/dist/identity-b3a70fc1.js +897 -0
  29. package/dist/identity-b3a70fc1.js.map +1 -0
  30. package/dist/index.cjs.js +1275 -7892
  31. package/dist/index.cjs.js.map +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.esm.js +783 -7399
  34. package/dist/index.esm.js.map +1 -1
  35. package/dist/index.umd.js +12 -15
  36. package/dist/index.umd.js.map +1 -1
  37. package/dist/loader-1ac52e12.js +217 -0
  38. package/dist/loader-1ac52e12.js.map +1 -0
  39. package/dist/loader-43d8e17a.js +222 -0
  40. package/dist/loader-43d8e17a.js.map +1 -0
  41. package/dist/normalize-60f2d7e6.js +6270 -0
  42. package/dist/normalize-60f2d7e6.js.map +1 -0
  43. package/dist/normalize-70da6516.js +6214 -0
  44. package/dist/normalize-70da6516.js.map +1 -0
  45. package/dist/reference-provider-1cd85b7b.js +217 -0
  46. package/dist/reference-provider-1cd85b7b.js.map +1 -0
  47. package/dist/reference-provider-53240217.js +211 -0
  48. package/dist/reference-provider-53240217.js.map +1 -0
  49. package/dist/trusted-list-build.cjs.js +575 -0
  50. package/dist/trusted-list-build.cjs.js.map +1 -0
  51. package/dist/trusted-list-build.d.ts +4 -0
  52. package/dist/trusted-list-build.esm.js +564 -0
  53. package/dist/trusted-list-build.esm.js.map +1 -0
  54. package/dist/trusted-list-bundled.cjs.js +30436 -0
  55. package/dist/trusted-list-bundled.cjs.js.map +1 -0
  56. package/dist/trusted-list-bundled.d.ts +1 -0
  57. package/dist/trusted-list-bundled.esm.js +30432 -0
  58. package/dist/trusted-list-bundled.esm.js.map +1 -0
  59. package/dist/trusted-list-http.cjs.js +85 -0
  60. package/dist/trusted-list-http.cjs.js.map +1 -0
  61. package/dist/trusted-list-http.d.ts +1 -0
  62. package/dist/trusted-list-http.esm.js +81 -0
  63. package/dist/trusted-list-http.esm.js.map +1 -0
  64. package/dist/trusted-list.cjs.js +35 -0
  65. package/dist/trusted-list.cjs.js.map +1 -0
  66. package/dist/trusted-list.d.ts +9 -0
  67. package/dist/trusted-list.esm.js +10 -0
  68. package/dist/trusted-list.esm.js.map +1 -0
  69. package/package.json +34 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - **Verification checklist output** - `verifySignature()` can now return a structured `checklist` with per-check status details when `includeChecklist: true`
13
+ - **Pluggable trusted-list provider support** - `verifySignature()` now uses `trustListProvider` for trust-list checks instead of built-in root-level trusted-list behavior
14
+ - **Purpose-aware trusted-list matching** - Trusted-list checks now distinguish signer issuer lookup from timestamp authority lookup and expose both `trustListMatch` and `timestampTrustListMatch`
15
+ - **Trusted-list package split** - Added opt-in subpath exports for `edockit/trusted-list`, `edockit/trusted-list/build`, `edockit/trusted-list/http`, and `edockit/trusted-list/bundled`
16
+ - **Compact trusted-list bundle format** - Added local matching against compact JSON bundles with a dedicated provider contract
17
+ - **Public Node-only trusted-list builder** - Added `edockit/trusted-list/build` for generating app-hosted trusted-list JSON, along with the repository `npm run update-trusted-list` script
18
+
19
+ ### Fixed
20
+
21
+ - **SignatureTimeStamp canonicalization** - Respect the timestamp's declared canonicalization method when hashing `ds:SignatureValue`, fixing false `coversSignature: false` results for some real samples
22
+ - **Skip LDAP CRL distribution points** - Filter out non-HTTP(S) URLs from CRL distribution points to avoid failed fetch attempts on unsupported protocols like LDAP
23
+ - **Timestamp trust-list evaluation** - Trusted-list verification now checks timestamp authorities at the timestamp signing time instead of only checking the signer issuer side
24
+
25
+ ## [0.3.0] - 2026-01-04
26
+
27
+ ### Added
28
+
29
+ - **Multi-state validation results** - `VerificationResult` now includes granular status beyond boolean `isValid`:
30
+ - `status`: `"VALID"` | `"INVALID"` | `"INDETERMINATE"` | `"UNSUPPORTED"`
31
+ - `statusMessage`: Human-readable explanation
32
+ - `limitations`: Array describing platform/environment constraints
33
+ - **Platform limitation detection** - Detect unsupported RSA key sizes (>4096 bits) in Safari/WebKit and return `UNSUPPORTED` status instead of failing as `INVALID`
34
+ - **Cross-browser testing** - Added Safari/WebKit and Firefox to browser test suite locally
35
+
36
+ ### Fixed
37
+
38
+ - **C14N 1.1 canonicalization** - Fixed bug where C14N 1.1 incorrectly added newlines between XML elements when the original had none. This caused signature verification to fail for compact XML.
39
+ - **INDETERMINATE for expired timestamps** - Return `INDETERMINATE` status (instead of `INVALID`) when timestamp or certificate has expired but signature is otherwise valid
40
+ - **Legacy RSA DigestInfo verification** - Fix signature verification for old documents signed with pre-Java 8 tools that produced non-standard DigestInfo format (missing NULL in AlgorithmIdentifier)
41
+
8
42
  ## [0.2.4] - 2025-12-31
9
43
 
10
44
  ### Fixed
@@ -70,6 +104,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
70
104
  - File checksum verification (SHA-256/384/512)
71
105
  - Browser and Node.js support
72
106
 
107
+ [Unreleased]: https://github.com/edgarsj/edockit/compare/v0.3.0...HEAD
108
+ [0.3.0]: https://github.com/edgarsj/edockit/compare/v0.2.4...v0.3.0
73
109
  [0.2.4]: https://github.com/edgarsj/edockit/compare/v0.2.3...v0.2.4
74
110
  [0.2.3]: https://github.com/edgarsj/edockit/compare/v0.2.2...v0.2.3
75
111
  [0.2.2]: https://github.com/edgarsj/edockit/compare/v0.2.1...v0.2.2
package/README.md CHANGED
@@ -1,221 +1,127 @@
1
1
  # edockit
2
2
 
3
- A JavaScript/TypeScript library for viewing and verifying EU standard ASiC-E containers (including Latvian eDoc files, which use the same format with a different extension). Works in both browser and Node.js environments.
3
+ A JavaScript/TypeScript library for viewing and verifying EU standard ASiC-E containers, including Latvian eDoc files, which use the same format with a different extension. It works in both browser and Node.js environments.
4
4
 
5
- > **Note: Work in Progress** - This library is under active development and requires more real-world testing with various ASiC-E implementations from different European countries. If you have sample files or encounter issues, please contribute!
6
-
7
- ## About
8
-
9
- This library supports standard European ASiC-E (.asice, .sce) containers as defined by the ETSI standards. Latvian eDoc (.edoc) files are effectively ASiC-E containers with a different file extension, so they are also supported. While the core functionality exists, extensive testing with real-world documents from various EU countries is still needed to ensure complete compatibility across different implementations.
5
+ > Note: Work in progress. This library still needs broader real-world testing with ASiC-E implementations from more EU countries.
10
6
 
11
7
  ## Installation
12
8
 
13
9
  ```bash
14
- # Install the core library
15
10
  npm install edockit
16
-
17
- # If using in Node.js environment, also install xmldom
18
- npm install @xmldom/xmldom
19
11
  ```
20
12
 
21
- ## Usage
13
+ If you implement trusted-list checking, [TRUSTED-LIST.md](TRUSTED-LIST.md) is required reading. The README only covers the quick-start path.
22
14
 
23
- ### Basic Usage
15
+ ## Quick Start
24
16
 
25
17
  ```typescript
26
- import { parseEdoc, verifySignature } from 'edockit';
18
+ import { parseEdoc, verifySignature } from "edockit";
19
+ import { createTrustListProvider } from "edockit/trusted-list";
27
20
 
28
- // Parse an ASiC-E/eDoc file
29
- const fileBuffer = /* your file buffer */;
30
21
  const container = parseEdoc(fileBuffer);
31
- // container = {
32
- // files: Map<string, Uint8Array>, // All files in the container
33
- // documentFileList: string[], // Document files (.pdf, .docx, etc.)
34
- // metadataFileList: string[], // Metadata files
35
- // signedFileList: string[], // Files covered by signatures
36
- // signatures: SignatureInfo[] // Signature objects
37
- // }
38
-
39
- // List files in container
40
- console.log(Array.from(container.files.keys()));
41
-
42
- // Verify a signature (with revocation and timestamp checking)
22
+
23
+ const trustListProvider = createTrustListProvider({
24
+ url: "/assets/trusted-list.json",
25
+ });
26
+
43
27
  const result = await verifySignature(container.signatures[0], container.files, {
44
- checkRevocation: true, // OCSP/CRL checking (default: true)
45
- revocationOptions: { // Optional: configure revocation check behavior
46
- ocspTimeout: 5000, // OCSP request timeout in ms (default: 5000)
47
- crlTimeout: 10000, // CRL fetch timeout in ms (default: 10000)
48
- proxyUrl: 'https://cors-proxy.example.com/?url=', // CORS proxy for browser (optional)
28
+ includeChecklist: true,
29
+ trustListProvider,
30
+ revocationOptions: {
31
+ proxyUrl: "https://cors-proxy.example.com/?url=",
32
+ },
33
+ trustedListFetchOptions: {
34
+ proxyUrl: "https://cors-proxy.example.com/?url=",
49
35
  },
50
- verifyTimestamps: true, // RFC 3161 timestamp verification (default: true)
51
- verifyTime: new Date() // Verify certificate at specific time (default: timestamp time if present, otherwise now)
52
36
  });
53
- // result = {
54
- // isValid: boolean, // Overall validity
55
- // certificate: {
56
- // isValid: boolean, // Certificate validity (time-based)
57
- // revocation: { // Revocation check result
58
- // status: 'good' | 'revoked' | 'unknown' | 'error',
59
- // method: 'ocsp' | 'crl' | 'none',
60
- // checkedAt: Date,
61
- // isValid: boolean
62
- // }
63
- // },
64
- // checksums: {isValid: boolean}, // File checksums validation result
65
- // signature: {isValid: boolean}, // XML signature validation result
66
- // timestamp: { // Timestamp verification (if present)
67
- // isValid: boolean,
68
- // info: { genTime: Date, policy: string, ... },
69
- // coversSignature: boolean,
70
- // tsaRevocation: { status, method, ... }
71
- // },
72
- // errors: string[] // Any validation errors (if present)
73
- // }
74
- console.log(`Signature valid: ${result.isValid}`);
37
+
38
+ console.log(result.status, result.statusMessage);
39
+ console.log(
40
+ result.checklist?.find((item) => item.check === "issuer_trusted_at_signing_time"),
41
+ );
75
42
  ```
76
43
 
77
- ### Node.js Example
44
+ Use `revocationOptions.proxyUrl` in browsers because OCSP and CRL endpoints usually do not support CORS. `trustedListFetchOptions.proxyUrl` is only needed if the verifier must fetch issuer certificates to strengthen trusted-list matching.
78
45
 
79
- ```typescript
80
- import { readFileSync } from "fs";
81
- import { parseEdoc, verifySignature } from "edockit";
46
+ ## Verification Results
82
47
 
83
- // Read file
84
- const fileBuffer = readFileSync("document.asice");
85
- const container = parseEdoc(fileBuffer);
48
+ `verifySignature()` returns:
86
49
 
87
- // Check signatures with revocation and timestamp checking
88
- for (const signature of container.signatures) {
89
- const result = await verifySignature(signature, container.files, {
90
- checkRevocation: true,
91
- verifyTimestamps: true
92
- });
93
- console.log(`Signature valid: ${result.isValid}`);
94
-
95
- if (result.timestamp?.info) {
96
- console.log(`Signed at (TSA): ${result.timestamp.info.genTime}`);
97
- }
98
-
99
- if (result.certificate.revocation) {
100
- console.log(`Revocation status: ${result.certificate.revocation.status}`);
101
- }
102
-
103
- if (!result.isValid && result.errors) {
104
- console.log(`Errors: ${result.errors.join(', ')}`);
105
- }
106
- }
107
- ```
50
+ - `status`: `"VALID" | "INVALID" | "INDETERMINATE" | "UNSUPPORTED"`
51
+ - `statusMessage`: a human-readable explanation
52
+ - `checklist`: optional structured verification steps when `includeChecklist: true`
53
+ - `trustListMatch`: signer-issuer trust-list result when `trustListProvider` is configured
54
+ - `timestampTrustListMatch`: timestamp-authority trust-list result when `trustListProvider` is configured
108
55
 
109
- ### Browser Example
110
-
111
- ```javascript
112
- // Fetch and verify a document
113
- async function verifyDocument(url) {
114
- const response = await fetch(url);
115
- const fileBuffer = await response.arrayBuffer();
116
-
117
- const container = parseEdoc(new Uint8Array(fileBuffer));
118
-
119
- // List document files
120
- console.log("Documents:", container.documentFileList);
121
-
122
- for (const signature of container.signatures) {
123
- const result = await verifySignature(signature, container.files, {
124
- checkRevocation: true,
125
- revocationOptions: {
126
- // Use a CORS proxy for OCSP/CRL requests in browser environments
127
- proxyUrl: 'https://your-cors-proxy.example.com/?url=',
128
- },
129
- });
130
-
131
- console.log(`Valid: ${result.isValid}`);
132
-
133
- if (result.timestamp?.info) {
134
- console.log(`Timestamp: ${result.timestamp.info.genTime}`);
135
- }
136
- if (result.certificate.revocation) {
137
- console.log(`Revocation: ${result.certificate.revocation.status}`);
138
- }
139
- }
140
- }
141
- ```
56
+ `allowWeakDnOnlyTrustMatch` is off by default, so DN-only trusted-list matches remain `indeterminate`.
57
+
58
+ ## Trusted List Setup
142
59
 
143
- > **Note:** OCSP and CRL endpoints typically don't support CORS, so browser environments need a proxy to perform revocation checks. The `proxyUrl` option routes all revocation requests through the specified proxy, which should accept the original URL as a query parameter.
60
+ Recommended production path:
144
61
 
145
- ### Timestamp Utilities
62
+ 1. Generate your own compact trusted-list bundle in CI or a build step.
63
+ 2. Host that JSON from your own app or CDN.
64
+ 3. Load it with `createTrustListProvider({ url })`.
146
65
 
147
- For advanced timestamp handling, you can use the timestamp utilities directly:
66
+ Build-time generation uses the Node-only helper:
148
67
 
149
68
  ```typescript
150
- import { parseTimestamp, verifyTimestamp, getTimestampTime } from 'edockit';
151
-
152
- // Get timestamp time from a signature (quick utility)
153
- const timestampTime = getTimestampTime(signature.signatureTimestamp);
154
- console.log(`Signed at: ${timestampTime}`);
155
-
156
- // Parse timestamp for detailed info
157
- const info = parseTimestamp(signature.signatureTimestamp);
158
- // info = {
159
- // genTime: Date, // When TSA signed
160
- // policy: string, // TSA policy OID
161
- // hashAlgorithm: string, // e.g., 'SHA-256'
162
- // messageImprint: string, // Hash of timestamped data
163
- // tsaName?: string, // TSA name
164
- // tsaCertificate?: string, // TSA cert in PEM format
165
- // }
166
-
167
- // Verify timestamp with options
168
- const result = await verifyTimestamp(signature.signatureTimestamp, {
169
- signatureValue: signature.signatureValue, // Verify timestamp covers this signature
170
- verifyTsaCertificate: true, // Check TSA cert validity
171
- checkTsaRevocation: true, // Check TSA cert revocation
69
+ import { generateTrustedListBundle } from "edockit/trusted-list/build";
70
+
71
+ await generateTrustedListBundle({
72
+ outputPath: "public/assets/trusted-list.json",
172
73
  });
173
74
  ```
174
75
 
175
- ## Features
176
-
177
- - Support for EU standard ASiC-E containers and Latvian eDoc files/containers (same format, different extension)
178
- - List files contained in ASiC-E/eDoc container
179
- - Extract and display signature information
180
- - Verify XML signatures against file checksums
181
- - Validate certificate validity (time-based)
182
- - RFC 3161 timestamp verification (when present, certificate is validated at the trusted TSA timestamp time)
183
- - OCSP/CRL revocation checking for both signer and TSA certificates (soft-fail behavior - network errors don't invalidate signatures)
76
+ Runtime local matching uses:
184
77
 
185
- ## Testing Status
78
+ ```typescript
79
+ import { createTrustListProvider } from "edockit/trusted-list";
80
+ ```
186
81
 
187
- The library has been tested with a limited set of real Latvian eDoc files (which are ASiC-E containers with a .edoc extension). More testing is needed with:
82
+ Other opt-in trusted-list subpaths:
188
83
 
189
- - ASiC-E containers from different EU countries
190
- - Files created with different software implementations
191
- - Various signature algorithms and certificate types
192
- - Edge cases and non-standard implementations
84
+ - `edockit/trusted-list/build`: Node-only bundle generation helpers
85
+ - `edockit/trusted-list/http`: tiny remote API wrapper
86
+ - `edockit/trusted-list/bundled`: explicit bundled fallback snapshot
193
87
 
194
- ## Browser Usage with UMD Build
88
+ For proper trusted-list integration, remote API usage, hybrid local+remote setups, the provider contract, and bundle/manifest details, read [TRUSTED-LIST.md](TRUSTED-LIST.md).
195
89
 
196
- If you're not using a module bundler, you can use the UMD build:
90
+ ## Timestamp Utilities
197
91
 
198
- ```html
199
- <script src="path/to/edockit/dist/index.umd.js"></script>
200
- <script>
201
- // Access the library from the global 'edockit' object
202
- const { parseEdoc, verifySignature } = edockit;
92
+ The root package also exposes timestamp helpers:
203
93
 
204
- // Your code here
205
- </script>
94
+ ```typescript
95
+ import { getTimestampTime, parseTimestamp, verifyTimestamp } from "edockit";
206
96
  ```
207
97
 
208
- ## Contributing
98
+ Use these if you need direct RFC 3161 parsing or verification outside `verifySignature()`.
99
+
100
+ ## Features
209
101
 
210
- Contributions are highly encouraged! The library needs more real-world testing to improve compatibility and robustness. In particular:
102
+ - Parse ASiC-E containers, including Latvian `.edoc`
103
+ - Verify XML signatures and signed file checksums
104
+ - Validate signer certificates at the relevant signing time
105
+ - Verify RFC 3161 timestamps
106
+ - Check revocation for signer and TSA certificates
107
+ - Return granular validation statuses instead of only boolean success/failure
108
+ - Return a structured verification checklist for consumer applications
109
+ - Match both signer issuers and timestamp authorities against a trusted list through an explicit provider contract
211
110
 
212
- 1. Testing with ASiC-E containers from different European countries
213
- 2. Bug reports with sample files (when possible)
214
- 3. Feature requests for specific EU country implementations
215
- 4. Documentation improvements
111
+ ## Testing Status
216
112
 
217
- If you encounter any issues, please open an issue on GitHub. Including sample files with your issue report (if possible) will help tremendously with debugging and improving compatibility.
113
+ The library has been tested with a limited set of real Latvian eDoc files. More testing is still needed with:
114
+
115
+ - ASiC-E containers from more EU countries
116
+ - files produced by different vendor implementations
117
+ - more signature algorithm and certificate variations
118
+ - more edge cases and malformed samples
119
+
120
+ ## Contributing
218
121
 
219
- ## License
122
+ Contributions are welcome, especially:
220
123
 
221
- MIT - See LICENSE file for details.
124
+ - real-world ASiC-E samples from different countries
125
+ - bug reports with reproducible files when possible
126
+ - interoperability fixes
127
+ - documentation improvements
@@ -0,0 +1,308 @@
1
+ # Trusted List Usage
2
+
3
+ This document describes the intended trusted-list integration model for `edockit`.
4
+
5
+ If you are implementing trusted-list verification in a real app, this document is required reading. The root `README.md` only shows the shortest happy-path examples.
6
+
7
+ ## Package Surfaces
8
+
9
+ `edockit` exposes the verification API and the trusted-list contract types:
10
+
11
+ - `verifySignature`
12
+ - `TrustListProvider`
13
+ - `TrustListQuery`
14
+ - `TrustListMatch`
15
+
16
+ Trusted-list runtime is opt-in and split into subpaths:
17
+
18
+ - `edockit/trusted-list`
19
+ - local compact-JSON provider
20
+ - use this for app-hosted JSON data
21
+ - `edockit/trusted-list/build`
22
+ - Node.js-only trusted-list bundle generation helpers
23
+ - use this in CI or app build scripts to generate your own JSON
24
+ - `edockit/trusted-list/http`
25
+ - tiny remote API wrapper
26
+ - use this if trust matching happens on your server
27
+ - `edockit/trusted-list/bundled`
28
+ - convenience bundled fallback snapshot
29
+ - use only if you explicitly want the library-shipped snapshot
30
+
31
+ ## Recommended Production Setup
32
+
33
+ For a first production version, use:
34
+
35
+ - `edockit`
36
+ - `edockit/trusted-list`
37
+ - your own hosted compact trusted-list JSON
38
+
39
+ Do not use the bundled fallback as the main production path.
40
+
41
+ This avoids:
42
+
43
+ - duplicating trusted-list data in your app and in the package
44
+ - tying freshness to npm package releases
45
+ - shipping extra trusted-list bytes you do not control
46
+
47
+ Important:
48
+
49
+ - `createTrustListProvider({ url })` expects the compact bundle JSON itself
50
+ - it does not read `manifest.json`
51
+ - if you generate a manifest, use it for your own rollout logic, then pass the actual bundle URL to the provider
52
+
53
+ ## Browser: Local JSON
54
+
55
+ ```typescript
56
+ import { parseEdoc, verifySignature } from "edockit";
57
+ import { createTrustListProvider } from "edockit/trusted-list";
58
+
59
+ const trustListProvider = createTrustListProvider({
60
+ url: "/assets/trusted-list.json",
61
+ });
62
+
63
+ const container = parseEdoc(fileBuffer);
64
+
65
+ const result = await verifySignature(container.signatures[0], container.files, {
66
+ includeChecklist: true,
67
+ trustListProvider,
68
+ });
69
+ ```
70
+
71
+ This is the preferred default for normal web apps.
72
+
73
+ ## Node.js: Local JSON
74
+
75
+ ```typescript
76
+ import { readFileSync } from "node:fs";
77
+ import { parseEdoc, verifySignature } from "edockit";
78
+ import { createTrustListProvider } from "edockit/trusted-list";
79
+
80
+ const trustedListBundle = JSON.parse(readFileSync("./trusted-list.json", "utf8"));
81
+ const trustListProvider = createTrustListProvider({
82
+ data: trustedListBundle,
83
+ });
84
+
85
+ const container = parseEdoc(fileBuffer);
86
+
87
+ const result = await verifySignature(container.signatures[0], container.files, {
88
+ trustListProvider,
89
+ });
90
+ ```
91
+
92
+ ## Provider Contract
93
+
94
+ The provider answers one narrow question:
95
+
96
+ > Was this service trusted at time `T` for this purpose?
97
+
98
+ The verifier combines that with timestamp, certificate, and revocation checks into the final signature result.
99
+
100
+ ```typescript
101
+ type TrustListQueryPurpose = "signature_issuer" | "timestamp_tsa";
102
+
103
+ interface TrustListQuery {
104
+ purpose: TrustListQueryPurpose;
105
+ time: Date;
106
+ spkiSha256Hex?: string | null;
107
+ skiHex?: string | null;
108
+ subjectDn?: string | null;
109
+ }
110
+
111
+ interface TrustListMatch {
112
+ found: boolean;
113
+ trustedAtTime?: boolean;
114
+ confidence?: "exact" | "ski_dn" | "dn_only";
115
+ country?: string;
116
+ detail?: string;
117
+ }
118
+
119
+ interface TrustListProvider {
120
+ match(query: TrustListQuery): Promise<TrustListMatch>;
121
+ }
122
+ ```
123
+
124
+ Notes:
125
+
126
+ - `purpose` distinguishes signer-issuer matching from timestamp-authority matching.
127
+ - `time` is required because trust-list answers are historical.
128
+ - `subjectDn` should be the normalized X.500 DN used by the matcher.
129
+ - `dn_only` matches are weak evidence. `verifySignature()` keeps them `indeterminate` unless `allowWeakDnOnlyTrustMatch` is enabled.
130
+
131
+ ## Remote API Example
132
+
133
+ If you want a server-side trust-match API, you can either use the tiny helper or write the provider inline.
134
+
135
+ ```typescript
136
+ import { verifySignature, type TrustListProvider } from "edockit";
137
+
138
+ const trustListProvider: TrustListProvider = {
139
+ async match(query) {
140
+ const response = await fetch("/api/trust-list/match", {
141
+ method: "POST",
142
+ headers: {
143
+ "content-type": "application/json",
144
+ },
145
+ body: JSON.stringify(query),
146
+ });
147
+
148
+ if (!response.ok) {
149
+ throw new Error(`Trust-list lookup failed: HTTP ${response.status}`);
150
+ }
151
+
152
+ return response.json();
153
+ },
154
+ };
155
+
156
+ const result = await verifySignature(signature, files, {
157
+ trustListProvider,
158
+ });
159
+ ```
160
+
161
+ The packaged helper is equivalent in spirit:
162
+
163
+ ```typescript
164
+ import { createRemoteTrustListProvider } from "edockit/trusted-list/http";
165
+ ```
166
+
167
+ ## Hybrid Model
168
+
169
+ You can combine local hot data with remote fallback:
170
+
171
+ ```typescript
172
+ import { createTrustListProvider } from "edockit/trusted-list";
173
+ import { createRemoteTrustListProvider } from "edockit/trusted-list/http";
174
+
175
+ const local = createTrustListProvider({
176
+ url: "/assets/trusted-list-baltics.json",
177
+ });
178
+
179
+ const remote = createRemoteTrustListProvider({
180
+ url: "/api/trust-list/match",
181
+ });
182
+
183
+ const trustListProvider = {
184
+ async match(query) {
185
+ const localMatch = await local.match(query);
186
+ if (localMatch.found) {
187
+ return localMatch;
188
+ }
189
+
190
+ return remote.match(query);
191
+ },
192
+ };
193
+ ```
194
+
195
+ That is a later optimization, not the recommended first step.
196
+
197
+ ## Bundled Fallback
198
+
199
+ If you explicitly want the library-shipped fallback snapshot:
200
+
201
+ ```typescript
202
+ import { createBundledTrustListProvider } from "edockit/trusted-list/bundled";
203
+
204
+ const trustListProvider = createBundledTrustListProvider();
205
+ ```
206
+
207
+ Behavior:
208
+
209
+ - this is opt-in only
210
+ - the bundled snapshot carries `generatedAt`
211
+ - if it is older than 14 days, the provider emits a one-time warning
212
+
213
+ Use this for demos, tests, or temporary convenience. Prefer app-hosted JSON in production.
214
+
215
+ ## Generating and Hosting JSON
216
+
217
+ Apps can generate compact trusted-list JSON themselves with the public Node-only build helper:
218
+
219
+ ```typescript
220
+ import { generateTrustedListBundle } from "edockit/trusted-list/build";
221
+
222
+ await generateTrustedListBundle({
223
+ outputPath: "public/assets/trusted-list.json",
224
+ });
225
+ ```
226
+
227
+ That is the recommended production setup for a web app that wants local matching from app-hosted JSON.
228
+
229
+ If you also want a manifest for later sharding or rollout logic:
230
+
231
+ ```typescript
232
+ await generateTrustedListBundle({
233
+ outputPath: "public/assets/trusted-list.json",
234
+ manifestOutputPath: "public/assets/trusted-list-manifest.json",
235
+ baseUrl: "/assets",
236
+ });
237
+ ```
238
+
239
+ The runtime provider still loads the bundle JSON:
240
+
241
+ ```typescript
242
+ const trustListProvider = createTrustListProvider({
243
+ url: "/assets/trusted-list.json",
244
+ });
245
+ ```
246
+
247
+ Do not point `createTrustListProvider({ url })` at `trusted-list-manifest.json`.
248
+
249
+ If you want to do the same thing in this repository itself, the repo script still exists:
250
+
251
+ ```bash
252
+ npm run update-trusted-list
253
+ ```
254
+
255
+ That repo script writes:
256
+
257
+ - `trusted-list/manifest.json`
258
+ - `trusted-list/bundles/<bundleId>.json`
259
+
260
+ In production, the usual pattern is:
261
+
262
+ 1. generate the JSON in CI
263
+ 2. deploy it as a static asset from your own origin
264
+ 3. load it with `createTrustListProvider({ url })`
265
+
266
+ If your app later moves issuer/TSA matching to a server API, keep generating the same JSON and load it on the server:
267
+
268
+ ```typescript
269
+ import { readFileSync } from "node:fs";
270
+ import { buildTrustedListData, matchTrustListQuery } from "edockit/trusted-list";
271
+
272
+ const bundle = JSON.parse(readFileSync("./public/assets/trusted-list.json", "utf8"));
273
+ const trustedListData = buildTrustedListData(bundle);
274
+
275
+ export async function postTrustMatch(request: Request): Promise<Response> {
276
+ const body = await request.json();
277
+ const match = matchTrustListQuery(
278
+ {
279
+ ...body,
280
+ time: new Date(body.time),
281
+ },
282
+ trustedListData,
283
+ );
284
+
285
+ return Response.json(match);
286
+ }
287
+ ```
288
+
289
+ Browser code can then use:
290
+
291
+ ```typescript
292
+ import { createRemoteTrustListProvider } from "edockit/trusted-list/http";
293
+
294
+ const trustListProvider = createRemoteTrustListProvider({
295
+ url: "/api/trust-list/match",
296
+ });
297
+ ```
298
+
299
+ ## Practical Rollout
300
+
301
+ Recommended order:
302
+
303
+ 1. Start with one compact JSON file.
304
+ 2. Verify locally with `edockit/trusted-list`.
305
+ 3. If needed later, split hot shards such as `lv`, `lt`, `ee`, or `baltics`.
306
+ 4. Only after that, add remote fallback for the long tail.
307
+
308
+ That keeps the first production version simple and avoids unnecessary moving parts.