@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 +384 -0
- package/dist/index.d.mts +1229 -0
- package/dist/index.mjs +573 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
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
|