edockit 0.3.0 → 0.4.0-dev.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.
- package/CHANGELOG.md +17 -0
- package/README.md +81 -198
- package/TRUSTED-LIST.md +308 -0
- package/dist/certificate-3c9dcdac.js +549 -0
- package/dist/certificate-3c9dcdac.js.map +1 -0
- package/dist/certificate-c7123a37.js +581 -0
- package/dist/certificate-c7123a37.js.map +1 -0
- package/dist/core/canonicalization/XMLCanonicalizer.d.ts +9 -3
- package/dist/core/trustedlist/build.d.ts +41 -0
- package/dist/core/trustedlist/bundled-provider.d.ts +2 -0
- package/dist/core/trustedlist/contract.d.ts +19 -0
- package/dist/core/trustedlist/dom.d.ts +12 -0
- package/dist/core/trustedlist/extract.d.ts +6 -0
- package/dist/core/trustedlist/http.d.ts +8 -0
- package/dist/core/trustedlist/identity.d.ts +7 -0
- package/dist/core/trustedlist/index.d.ts +18 -0
- package/dist/core/trustedlist/loader.d.ts +5 -0
- package/dist/core/trustedlist/matcher.d.ts +11 -0
- package/dist/core/trustedlist/normalize.d.ts +14 -0
- package/dist/core/trustedlist/reference-provider.d.ts +12 -0
- package/dist/core/trustedlist/types.d.ts +114 -0
- package/dist/core/unzip.d.ts +0 -0
- package/dist/core/verification.d.ts +22 -0
- package/dist/data/trusted-list.d.ts +3 -0
- package/dist/identity-c9e5052e.js +410 -0
- package/dist/identity-c9e5052e.js.map +1 -0
- package/dist/identity-fca881b1.js +406 -0
- package/dist/identity-fca881b1.js.map +1 -0
- package/dist/index.cjs.js +540 -8838
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.esm.js +498 -8795
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +23 -17
- package/dist/index.umd.js.map +1 -1
- package/dist/loader-7a0f771f.js +222 -0
- package/dist/loader-7a0f771f.js.map +1 -0
- package/dist/loader-ad1a5051.js +217 -0
- package/dist/loader-ad1a5051.js.map +1 -0
- package/dist/normalize-50862581.js +456 -0
- package/dist/normalize-50862581.js.map +1 -0
- package/dist/normalize-9626be7c.js +479 -0
- package/dist/normalize-9626be7c.js.map +1 -0
- package/dist/reference-provider-3838ebfb.js +217 -0
- package/dist/reference-provider-3838ebfb.js.map +1 -0
- package/dist/reference-provider-9bbbaab8.js +211 -0
- package/dist/reference-provider-9bbbaab8.js.map +1 -0
- package/dist/trusted-list-build.cjs.js +580 -0
- package/dist/trusted-list-build.cjs.js.map +1 -0
- package/dist/trusted-list-build.d.ts +4 -0
- package/dist/trusted-list-build.esm.js +569 -0
- package/dist/trusted-list-build.esm.js.map +1 -0
- package/dist/trusted-list-bundled.cjs.js +30439 -0
- package/dist/trusted-list-bundled.cjs.js.map +1 -0
- package/dist/trusted-list-bundled.d.ts +1 -0
- package/dist/trusted-list-bundled.esm.js +30435 -0
- package/dist/trusted-list-bundled.esm.js.map +1 -0
- package/dist/trusted-list-http.cjs.js +85 -0
- package/dist/trusted-list-http.cjs.js.map +1 -0
- package/dist/trusted-list-http.d.ts +1 -0
- package/dist/trusted-list-http.esm.js +81 -0
- package/dist/trusted-list-http.esm.js.map +1 -0
- package/dist/trusted-list.cjs.js +38 -0
- package/dist/trusted-list.cjs.js.map +1 -0
- package/dist/trusted-list.d.ts +9 -0
- package/dist/trusted-list.esm.js +13 -0
- package/dist/trusted-list.esm.js.map +1 -0
- package/package.json +32 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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
|
+
|
|
8
25
|
## [0.3.0] - 2026-01-04
|
|
9
26
|
|
|
10
27
|
### Added
|
package/README.md
CHANGED
|
@@ -1,244 +1,127 @@
|
|
|
1
1
|
# edockit
|
|
2
2
|
|
|
3
|
-
A JavaScript/TypeScript library for viewing and verifying EU standard ASiC-E containers
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
15
|
+
## Quick Start
|
|
24
16
|
|
|
25
17
|
```typescript
|
|
26
|
-
import { parseEdoc, verifySignature } from
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// code: string, // e.g., 'RSA_KEY_SIZE_UNSUPPORTED'
|
|
59
|
-
// description: string,
|
|
60
|
-
// platform?: string // e.g., 'Safari/WebKit'
|
|
61
|
-
// }],
|
|
62
|
-
// certificate: {
|
|
63
|
-
// isValid: boolean, // Certificate validity (time-based)
|
|
64
|
-
// revocation: { // Revocation check result
|
|
65
|
-
// status: 'good' | 'revoked' | 'unknown' | 'error',
|
|
66
|
-
// method: 'ocsp' | 'crl' | 'none',
|
|
67
|
-
// checkedAt: Date,
|
|
68
|
-
// isValid: boolean
|
|
69
|
-
// }
|
|
70
|
-
// },
|
|
71
|
-
// checksums: {isValid: boolean}, // File checksums validation result
|
|
72
|
-
// signature: {isValid: boolean}, // XML signature validation result
|
|
73
|
-
// timestamp: { // Timestamp verification (if present)
|
|
74
|
-
// isValid: boolean,
|
|
75
|
-
// info: { genTime: Date, policy: string, ... },
|
|
76
|
-
// coversSignature: boolean,
|
|
77
|
-
// tsaRevocation: { status, method, ... }
|
|
78
|
-
// },
|
|
79
|
-
// errors: string[] // Any validation errors (if present)
|
|
80
|
-
// }
|
|
81
|
-
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
|
+
);
|
|
82
42
|
```
|
|
83
43
|
|
|
84
|
-
|
|
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.
|
|
85
45
|
|
|
86
|
-
|
|
87
|
-
import { readFileSync } from "fs";
|
|
88
|
-
import { parseEdoc, verifySignature } from "edockit";
|
|
46
|
+
## Verification Results
|
|
89
47
|
|
|
90
|
-
|
|
91
|
-
const fileBuffer = readFileSync("document.asice");
|
|
92
|
-
const container = parseEdoc(fileBuffer);
|
|
48
|
+
`verifySignature()` returns:
|
|
93
49
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Use granular status for detailed handling
|
|
102
|
-
console.log(`Status: ${result.status}`); // VALID, INVALID, INDETERMINATE, or UNSUPPORTED
|
|
103
|
-
|
|
104
|
-
if (result.status === 'VALID') {
|
|
105
|
-
console.log('Signature is valid');
|
|
106
|
-
} else if (result.status === 'UNSUPPORTED') {
|
|
107
|
-
// Platform limitation (e.g., RSA >4096 bits in Safari)
|
|
108
|
-
console.log(`Cannot verify: ${result.statusMessage}`);
|
|
109
|
-
} else if (result.status === 'INDETERMINATE') {
|
|
110
|
-
// Can't conclude (e.g., revocation status unknown)
|
|
111
|
-
console.log(`Inconclusive: ${result.statusMessage}`);
|
|
112
|
-
} else {
|
|
113
|
-
// INVALID - definitely wrong
|
|
114
|
-
console.log(`Invalid: ${result.statusMessage}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (result.timestamp?.info) {
|
|
118
|
-
console.log(`Signed at (TSA): ${result.timestamp.info.genTime}`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (result.certificate.revocation) {
|
|
122
|
-
console.log(`Revocation status: ${result.certificate.revocation.status}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
```
|
|
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
|
|
126
55
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Fetch and verify a document
|
|
131
|
-
async function verifyDocument(url) {
|
|
132
|
-
const response = await fetch(url);
|
|
133
|
-
const fileBuffer = await response.arrayBuffer();
|
|
134
|
-
|
|
135
|
-
const container = parseEdoc(new Uint8Array(fileBuffer));
|
|
136
|
-
|
|
137
|
-
// List document files
|
|
138
|
-
console.log("Documents:", container.documentFileList);
|
|
139
|
-
|
|
140
|
-
for (const signature of container.signatures) {
|
|
141
|
-
const result = await verifySignature(signature, container.files, {
|
|
142
|
-
checkRevocation: true,
|
|
143
|
-
revocationOptions: {
|
|
144
|
-
// Use a CORS proxy for OCSP/CRL requests in browser environments
|
|
145
|
-
proxyUrl: 'https://your-cors-proxy.example.com/?url=',
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Handle different validation states
|
|
150
|
-
if (result.status === 'VALID') {
|
|
151
|
-
console.log('Signature verified successfully');
|
|
152
|
-
} else if (result.status === 'UNSUPPORTED') {
|
|
153
|
-
// Some signatures can't be verified in certain browsers (e.g., RSA >4096 in Safari)
|
|
154
|
-
console.log(`Browser limitation: ${result.statusMessage}`);
|
|
155
|
-
} else {
|
|
156
|
-
console.log(`${result.status}: ${result.statusMessage}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (result.timestamp?.info) {
|
|
160
|
-
console.log(`Timestamp: ${result.timestamp.info.genTime}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
```
|
|
56
|
+
`allowWeakDnOnlyTrustMatch` is off by default, so DN-only trusted-list matches remain `indeterminate`.
|
|
57
|
+
|
|
58
|
+
## Trusted List Setup
|
|
165
59
|
|
|
166
|
-
|
|
60
|
+
Recommended production path:
|
|
167
61
|
|
|
168
|
-
|
|
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 })`.
|
|
169
65
|
|
|
170
|
-
|
|
66
|
+
Build-time generation uses the Node-only helper:
|
|
171
67
|
|
|
172
68
|
```typescript
|
|
173
|
-
import {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
console.log(`Signed at: ${timestampTime}`);
|
|
178
|
-
|
|
179
|
-
// Parse timestamp for detailed info
|
|
180
|
-
const info = parseTimestamp(signature.signatureTimestamp);
|
|
181
|
-
// info = {
|
|
182
|
-
// genTime: Date, // When TSA signed
|
|
183
|
-
// policy: string, // TSA policy OID
|
|
184
|
-
// hashAlgorithm: string, // e.g., 'SHA-256'
|
|
185
|
-
// messageImprint: string, // Hash of timestamped data
|
|
186
|
-
// tsaName?: string, // TSA name
|
|
187
|
-
// tsaCertificate?: string, // TSA cert in PEM format
|
|
188
|
-
// }
|
|
189
|
-
|
|
190
|
-
// Verify timestamp with options
|
|
191
|
-
const result = await verifyTimestamp(signature.signatureTimestamp, {
|
|
192
|
-
signatureValue: signature.signatureValue, // Verify timestamp covers this signature
|
|
193
|
-
verifyTsaCertificate: true, // Check TSA cert validity
|
|
194
|
-
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",
|
|
195
73
|
});
|
|
196
74
|
```
|
|
197
75
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
- Support for EU standard ASiC-E containers and Latvian eDoc files/containers (same format, different extension)
|
|
201
|
-
- List files contained in ASiC-E/eDoc container
|
|
202
|
-
- Extract and display signature information
|
|
203
|
-
- Verify XML signatures against file checksums
|
|
204
|
-
- Validate certificate validity (time-based)
|
|
205
|
-
- RFC 3161 timestamp verification (when present, certificate is validated at the trusted TSA timestamp time)
|
|
206
|
-
- OCSP/CRL revocation checking for both signer and TSA certificates (soft-fail behavior - network errors don't invalidate signatures)
|
|
76
|
+
Runtime local matching uses:
|
|
207
77
|
|
|
208
|
-
|
|
78
|
+
```typescript
|
|
79
|
+
import { createTrustListProvider } from "edockit/trusted-list";
|
|
80
|
+
```
|
|
209
81
|
|
|
210
|
-
|
|
82
|
+
Other opt-in trusted-list subpaths:
|
|
211
83
|
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
- 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
|
|
216
87
|
|
|
217
|
-
|
|
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).
|
|
218
89
|
|
|
219
|
-
|
|
90
|
+
## Timestamp Utilities
|
|
220
91
|
|
|
221
|
-
|
|
222
|
-
<script src="path/to/edockit/dist/index.umd.js"></script>
|
|
223
|
-
<script>
|
|
224
|
-
// Access the library from the global 'edockit' object
|
|
225
|
-
const { parseEdoc, verifySignature } = edockit;
|
|
92
|
+
The root package also exposes timestamp helpers:
|
|
226
93
|
|
|
227
|
-
|
|
228
|
-
|
|
94
|
+
```typescript
|
|
95
|
+
import { getTimestampTime, parseTimestamp, verifyTimestamp } from "edockit";
|
|
229
96
|
```
|
|
230
97
|
|
|
231
|
-
|
|
98
|
+
Use these if you need direct RFC 3161 parsing or verification outside `verifySignature()`.
|
|
99
|
+
|
|
100
|
+
## Features
|
|
232
101
|
|
|
233
|
-
|
|
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
|
|
234
110
|
|
|
235
|
-
|
|
236
|
-
2. Bug reports with sample files (when possible)
|
|
237
|
-
3. Feature requests for specific EU country implementations
|
|
238
|
-
4. Documentation improvements
|
|
111
|
+
## Testing Status
|
|
239
112
|
|
|
240
|
-
|
|
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
|
|
241
121
|
|
|
242
|
-
|
|
122
|
+
Contributions are welcome, especially:
|
|
243
123
|
|
|
244
|
-
|
|
124
|
+
- real-world ASiC-E samples from different countries
|
|
125
|
+
- bug reports with reproducible files when possible
|
|
126
|
+
- interoperability fixes
|
|
127
|
+
- documentation improvements
|
package/TRUSTED-LIST.md
ADDED
|
@@ -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.
|