@owf/eudi-lote 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,384 @@
1
+ # @owf/eudi-lote
2
+
3
+ SDK for creating, signing, and managing Lists of Trusted Entities (LoTE) per **ETSI TS 119 602**.
4
+
5
+ ## Overview
6
+
7
+ This SDK implements the ETSI TS 119 602 standard for LoTE documents, enabling:
8
+
9
+ - **Create and manage LoTE documents** with trusted entities and their services
10
+ - **Sign LoTE documents** with private keys or custom signers (HSM/KMS)
11
+ - **Validate LoTE documents** against ETSI TS 119 602 schema
12
+
13
+ > **Note**: This package currently supports **JWT encoding only**. CBOR and XML encodings are not yet supported.
14
+
15
+ ## Specification Reference
16
+
17
+ - [ETSI TS 119 602](https://www.etsi.org/deliver/etsi_ts/119600_119699/119602/01.01.01_60/ts_119602v010101p.pdf) - Trusted Lists
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @owf/eudi-lote
23
+ # or
24
+ pnpm add @owf/eudi-lote
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Creating a LoTE Document
30
+
31
+ ```typescript
32
+ import {
33
+ createLoTE,
34
+ trustedEntity,
35
+ service,
36
+ addTrustedEntity,
37
+ } from '@owf/eudi-lote';
38
+
39
+ // Create the base LoTE document (only SchemeOperatorName is required)
40
+ let lote = createLoTE({
41
+ SchemeOperatorName: [{ lang: 'en', value: 'Example Trust Operator' }],
42
+ SchemeOperatorAddress: {
43
+ SchemeOperatorPostalAddress: [
44
+ {
45
+ lang: 'en',
46
+ StreetAddress: 'Main Street 1',
47
+ Locality: 'Berlin',
48
+ PostalCode: '10115',
49
+ Country: 'DE',
50
+ },
51
+ ],
52
+ SchemeOperatorElectronicAddress: [
53
+ { lang: 'en', uriValue: 'mailto:trust@example.org' },
54
+ ],
55
+ },
56
+ SchemeName: [{ lang: 'en', value: 'Gym Membership Issuers' }],
57
+ SchemeTerritory: 'DE',
58
+ });
59
+
60
+ // Build a trusted entity using the fluent builder
61
+ const gymEntity = trustedEntity()
62
+ .name('Mighty Fitness Center GmbH')
63
+ .tradeName('Mighty Fitness')
64
+ .postalAddress({
65
+ StreetAddress: 'Gym Street 5',
66
+ Locality: 'Fitville',
67
+ PostalCode: '54321',
68
+ Country: 'DE',
69
+ })
70
+ .email('contact@gym.example.com')
71
+ .website('https://gym.example.com')
72
+ .infoUri('https://gym.example.com/about')
73
+ .addService(
74
+ service()
75
+ .name('Gym Membership Attestation Issuance')
76
+ .type('https://example.org/service-type/gym-attestation-issuer')
77
+ .status('https://example.org/status/granted')
78
+ .addPublicKey({
79
+ kty: 'EC',
80
+ crv: 'P-256',
81
+ x: '...',
82
+ y: '...',
83
+ kid: 'gym-issuer-key-1',
84
+ use: 'sig',
85
+ alg: 'ES256',
86
+ })
87
+ .addEndpoint(
88
+ 'https://example.org/service-type/gym-attestation-issuer',
89
+ 'https://issuer.gym.example.com',
90
+ )
91
+ .build(),
92
+ )
93
+ .build();
94
+
95
+ // Add the entity to the LoTE document
96
+ lote = addTrustedEntity(lote, gymEntity);
97
+ ```
98
+
99
+ ### Service with Multiple Identity Types
100
+
101
+ The SDK supports all digital identity types from ETSI TS 119 602:
102
+
103
+ ```typescript
104
+ const svc = service()
105
+ .name('Multi-Identity Service')
106
+ .type('https://example.org/service-type/issuer')
107
+ .status('https://example.org/status/granted')
108
+ // X.509 certificates
109
+ .addCertificate('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...')
110
+ // X.509 subject names
111
+ .addX509SubjectName('CN=Test CA, O=Test Org, C=DE')
112
+ // X.509 Subject Key Identifiers
113
+ .addX509SKI('base64encodedSKI==')
114
+ // JWK public keys
115
+ .addPublicKey({ kty: 'EC', crv: 'P-256', x: '...', y: '...' })
116
+ // Other identifiers
117
+ .addOtherId('urn:example:custom-id:12345')
118
+ .build();
119
+ ```
120
+
121
+ ### Service History
122
+
123
+ Track historical changes to services:
124
+
125
+ ```typescript
126
+ const svc = service()
127
+ .name('Current Service Name')
128
+ .addPublicKey({ kty: 'EC', crv: 'P-256', x: '...', y: '...' })
129
+ .addHistoryEntry({
130
+ ServiceName: [{ lang: 'en', value: 'Previous Service Name' }],
131
+ ServiceDigitalIdentity: { PublicKeyValues: [{ kty: 'EC', crv: 'P-256' }] },
132
+ ServiceStatus: 'https://example.org/status/revoked',
133
+ StatusStartingTime: '2023-01-01T00:00:00.000Z',
134
+ })
135
+ .build();
136
+ ```
137
+
138
+ ### Pointers to Other LoTE Lists
139
+
140
+ Reference other LoTE documents:
141
+
142
+ ```typescript
143
+ const lote = createLoTE({
144
+ SchemeOperatorName: [{ lang: 'en', value: 'Master Registry' }],
145
+ PointersToOtherLoTE: [
146
+ {
147
+ LoTELocation: 'https://other-registry.example.com/lote',
148
+ ServiceDigitalIdentities: [
149
+ { PublicKeyValues: [{ kty: 'EC', crv: 'P-256', x: '...', y: '...' }] },
150
+ ],
151
+ LoTEQualifiers: [
152
+ {
153
+ LoTEType: 'https://example.org/lote-type/regional',
154
+ SchemeOperatorName: [{ lang: 'en', value: 'Regional Operator' }],
155
+ MimeType: 'application/jwt',
156
+ },
157
+ ],
158
+ },
159
+ ],
160
+ });
161
+ ```
162
+
163
+ ### Signing a LoTE Document
164
+
165
+ ```typescript
166
+ import { signLoTE } from '@owf/eudi-lote';
167
+ import { ES256 } from '@owf/crypto';
168
+
169
+ // With a local key
170
+ const signer = await ES256.getSigner(privateKeyPem);
171
+ const signed = await signLoTE({
172
+ lote,
173
+ keyId: 'lote-signer-2025',
174
+ signer,
175
+ });
176
+
177
+ console.log(signed.jws); // The signed JWS
178
+ ```
179
+
180
+ ### Using a KMS/HSM Signer
181
+
182
+ ```typescript
183
+ const signed = await signLoTE({
184
+ lote,
185
+ keyId: 'lote-signer-2025',
186
+ signer: async (data) => {
187
+ return await myKmsService.sign('ES256', data);
188
+ },
189
+ });
190
+ ```
191
+
192
+ ### Validating a LoTE Document
193
+
194
+ ```typescript
195
+ import {
196
+ validateLoTE,
197
+ assertValidLoTE,
198
+ } from '@owf/eudi-lote';
199
+
200
+ // Get validation result with error details
201
+ const result = validateLoTE(lote);
202
+ if (!result.valid) {
203
+ console.log('Validation errors:', result.errors);
204
+ }
205
+
206
+ // Or throw on invalid
207
+ assertValidLoTE(lote); // throws ValidationError if invalid
208
+ ```
209
+
210
+ ### Updating a LoTE Document
211
+
212
+ ```typescript
213
+ import {
214
+ updateLoTEVersion,
215
+ addTrustedEntity,
216
+ removeTrustedEntity,
217
+ } from '@owf/eudi-lote';
218
+
219
+ // Increment version and update timestamps
220
+ let updatedList = updateLoTEVersion(lote);
221
+
222
+ // Add new entity
223
+ updatedList = addTrustedEntity(updatedList, newEntity);
224
+
225
+ // Remove an entity by name
226
+ updatedList = removeTrustedEntity(updatedList, 'Old Gym Name');
227
+ ```
228
+
229
+ ## API Reference
230
+
231
+ ### Signing
232
+
233
+ #### `signLoTE(options): Promise<SignedLoTE>`
234
+
235
+ Signs a LoTE document using the provided signer function.
236
+
237
+ #### `createLoTE(schemeInfo, entities?): LoTEDocument`
238
+
239
+ Creates a new LoTE document with the given scheme information. Only `SchemeOperatorName` is required per ETSI TS 119 602.
240
+
241
+ #### `updateLoTEVersion(lote): LoTEDocument`
242
+
243
+ Increments the sequence number and updates timestamps.
244
+
245
+ #### `addTrustedEntity(lote, entity): LoTEDocument`
246
+
247
+ Adds a trusted entity to the list.
248
+
249
+ #### `removeTrustedEntity(lote, entityName): LoTEDocument`
250
+
251
+ Removes an entity by name.
252
+
253
+ ### Validation
254
+
255
+ #### `validateLoTE(lote): ValidationResult`
256
+
257
+ Validates a LoTE document against ETSI TS 119 602 schema. Returns validation result with error details.
258
+
259
+ #### `assertValidLoTE(lote): asserts lote is LoTEDocument`
260
+
261
+ Type assertion that throws `ValidationError` if the LoTE document is invalid.
262
+
263
+ ### Builders
264
+
265
+ #### `trustedEntity(): TrustedEntityBuilder`
266
+
267
+ Fluent builder for creating trusted entities.
268
+
269
+ Methods:
270
+
271
+ - `.name(name, lang?)` - Entity name
272
+ - `.tradeName(name, lang?)` - Trade name
273
+ - `.registrationId(id, lang?)` - Registration identifier
274
+ - `.postalAddress(address, lang?)` - Postal address
275
+ - `.email(email, lang?)` - Email address
276
+ - `.website(url, lang?)` - Website URL
277
+ - `.infoUri(uri, lang?)` - Information URI
278
+ - `.addExtension(extension)` - Add an extension
279
+ - `.addService(service)` - Add a service
280
+ - `.build()` - Build the entity
281
+
282
+ #### `service(): ServiceBuilder`
283
+
284
+ Fluent builder for creating services.
285
+
286
+ Methods:
287
+
288
+ - `.name(name, lang?)` - Service name
289
+ - `.type(typeUri)` - Service type identifier
290
+ - `.status(statusUri)` - Service status
291
+ - `.statusStartingTime(isoTimestamp)` - When status became effective
292
+ - `.addCertificate(base64Cert)` - Add X.509 certificate
293
+ - `.addX509SubjectName(subjectName)` - Add X.509 subject name
294
+ - `.addX509SKI(base64SKI)` - Add X.509 Subject Key Identifier
295
+ - `.addPublicKey(jwk)` - Add JWK public key
296
+ - `.addOtherId(identifier)` - Add custom identifier
297
+ - `.addEndpoint(type, uri)` - Add service endpoint
298
+ - `.addExtension(extension)` - Add service extension
299
+ - `.addHistoryEntry(entry)` - Add service history entry
300
+ - `.build()` - Build the service
301
+
302
+ ## Types
303
+
304
+ See [src/types.ts](./src/types.ts) for complete type definitions including:
305
+
306
+ - `LoTEDocument` - Root LoTE document structure
307
+ - `TrustedEntity` - Entity in the LoTE document
308
+ - `Service` - Service provided by an entity
309
+ - `ServiceDigitalIdentity` - Digital identity for a service
310
+ - `ServiceHistory` / `ServiceHistoryInstance` - Historical service states
311
+ - `PointersToOtherLoTE` / `OtherLoTEPointer` - Cross-list references
312
+ - `ValidationResult` / `ValidationError` - Validation types
313
+
314
+ All document types are derived from Zod schemas. You can import and use the schemas
315
+ directly for custom validation or to build upon:
316
+
317
+ ```typescript
318
+ import { LoTEDocumentSchema, TrustedEntitySchema } from '@owf/eudi-lote';
319
+
320
+ // Use schemas for custom validation
321
+ const result = LoTEDocumentSchema.safeParse(data);
322
+
323
+ // Compose with your own schemas
324
+ const mySchema = TrustedEntitySchema.extend({ customField: z.string() });
325
+ ```
326
+
327
+ ### SignerFunction
328
+
329
+ ```typescript
330
+ type SignerFunction = (data: Uint8Array) => Promise<Uint8Array | string>;
331
+ ```
332
+
333
+ ## External Key Management
334
+
335
+ The SDK supports delegating signing to external services:
336
+
337
+ ### Azure Key Vault
338
+
339
+ ```typescript
340
+ import { CryptographyClient } from '@azure/keyvault-keys';
341
+
342
+ const signed = await signLoTE({
343
+ lote,
344
+ keyId: 'lote-signer',
345
+ signer: async (data) => {
346
+ const result = await cryptoClient.sign('ES256', data);
347
+ return result.result;
348
+ },
349
+ });
350
+ ```
351
+
352
+ ### AWS KMS
353
+
354
+ ```typescript
355
+ import { KMSClient, SignCommand } from '@aws-sdk/client-kms';
356
+
357
+ const signed = await signLoTE({
358
+ lote,
359
+ keyId: 'lote-signer',
360
+ signer: async (data) => {
361
+ const response = await kmsClient.send(
362
+ new SignCommand({
363
+ KeyId: 'alias/lote-signer',
364
+ Message: data,
365
+ MessageType: 'RAW',
366
+ SigningAlgorithm: 'ECDSA_SHA_256',
367
+ }),
368
+ );
369
+ return new Uint8Array(response.Signature!);
370
+ },
371
+ });
372
+ ```
373
+
374
+ ## Security Considerations
375
+
376
+ - **Protect signing keys** - Use HSM or KMS for production
377
+ - **Validate trust anchors** - Verify the signer's certificate chain
378
+ - **Check sequence numbers** - Ensure you have the latest version
379
+ - **Use HTTPS** - Always use HTTPS for distribution points
380
+ - **Validate before signing** - Use `assertValidLoTE` before signing
381
+
382
+ ## License
383
+
384
+ Apache-2.0