@vess-id/status-list 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1576 @@
1
+ 'use strict';
2
+
3
+ var pako = require('pako');
4
+ var jose = require('jose');
5
+ var cbor = require('cbor');
6
+ var crypto = require('crypto');
7
+
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var pako__namespace = /*#__PURE__*/_interopNamespace(pako);
27
+ var cbor__namespace = /*#__PURE__*/_interopNamespace(cbor);
28
+
29
+ // lib/types/errors.ts
30
+ var StatusListError = class extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = "StatusListError";
34
+ if (Error.captureStackTrace) {
35
+ Error.captureStackTrace(this, this.constructor);
36
+ }
37
+ }
38
+ };
39
+ var IndexOutOfBoundsError = class extends StatusListError {
40
+ constructor(index, maxIndex) {
41
+ super(`Index ${index} is out of bounds. Valid range: 0-${maxIndex}`);
42
+ this.name = "IndexOutOfBoundsError";
43
+ }
44
+ };
45
+ var InvalidBitSizeError = class extends StatusListError {
46
+ constructor(bits) {
47
+ super(`Invalid bit size: ${bits}. Must be one of: 1, 2, 4, or 8`);
48
+ this.name = "InvalidBitSizeError";
49
+ }
50
+ };
51
+ var InvalidStatusValueError = class extends StatusListError {
52
+ constructor(value, maxValue, bitsPerStatus) {
53
+ super(`Invalid status value: ${value}. For ${bitsPerStatus}-bit status, valid range is 0-${maxValue}`);
54
+ this.name = "InvalidStatusValueError";
55
+ }
56
+ };
57
+ var InvalidTokenFormatError = class extends StatusListError {
58
+ constructor(message) {
59
+ super(`Invalid token format: ${message}`);
60
+ this.name = "InvalidTokenFormatError";
61
+ }
62
+ };
63
+ var StatusListExpiredError = class extends StatusListError {
64
+ constructor(exp, currentTime) {
65
+ super(`Status list expired at ${exp} (current time: ${currentTime})`);
66
+ this.name = "StatusListExpiredError";
67
+ }
68
+ };
69
+ var FetchError = class extends StatusListError {
70
+ constructor(message) {
71
+ super(message);
72
+ this.name = "FetchError";
73
+ }
74
+ };
75
+ var MissingStatusListUriError = class extends StatusListError {
76
+ constructor(message = "Status list URI is missing from token") {
77
+ super(message);
78
+ this.name = "MissingStatusListUriError";
79
+ }
80
+ };
81
+ var CompressionError = class extends StatusListError {
82
+ constructor(message, cause) {
83
+ const fullMessage = cause ? `${message}: ${cause.message}` : message;
84
+ super(fullMessage);
85
+ this.name = "CompressionError";
86
+ this.cause = cause;
87
+ }
88
+ };
89
+ var ValidationError = class extends StatusListError {
90
+ errors;
91
+ constructor(errors) {
92
+ super(`Validation failed: ${errors.join(", ")}`);
93
+ this.name = "ValidationError";
94
+ this.errors = errors;
95
+ }
96
+ };
97
+
98
+ // lib/core/bitpack.ts
99
+ function validateBitSize(bits) {
100
+ if (bits !== 1 && bits !== 2 && bits !== 4 && bits !== 8) {
101
+ throw new InvalidBitSizeError(bits);
102
+ }
103
+ }
104
+ function validateStatusValue(value, bitsPerStatus) {
105
+ const maxValue = (1 << bitsPerStatus) - 1;
106
+ if (value < 0 || value > maxValue) {
107
+ throw new InvalidStatusValueError(value, maxValue, bitsPerStatus);
108
+ }
109
+ }
110
+ function packBits(statuses, bitsPerStatus) {
111
+ validateBitSize(bitsPerStatus);
112
+ const totalBits = statuses.length * bitsPerStatus;
113
+ const byteLength = Math.ceil(totalBits / 8);
114
+ const bytes = new Uint8Array(byteLength);
115
+ for (let i = 0; i < statuses.length; i++) {
116
+ const value = statuses[i];
117
+ validateStatusValue(value, bitsPerStatus);
118
+ const bitOffset = i * bitsPerStatus;
119
+ const byteIndex = Math.floor(bitOffset / 8);
120
+ const bitPosition = bitOffset % 8;
121
+ bytes[byteIndex] |= value << bitPosition;
122
+ if (bitPosition + bitsPerStatus > 8) {
123
+ const bitsInNextByte = bitPosition + bitsPerStatus - 8;
124
+ bytes[byteIndex + 1] |= value >> bitsPerStatus - bitsInNextByte;
125
+ }
126
+ }
127
+ return bytes;
128
+ }
129
+ function unpackBits(bytes, bitsPerStatus) {
130
+ validateBitSize(bitsPerStatus);
131
+ const totalBits = bytes.length * 8;
132
+ const statusCount = Math.floor(totalBits / bitsPerStatus);
133
+ const statuses = new Array(statusCount);
134
+ const mask = (1 << bitsPerStatus) - 1;
135
+ for (let i = 0; i < statusCount; i++) {
136
+ statuses[i] = getBitValue(bytes, i, bitsPerStatus, mask);
137
+ }
138
+ return statuses;
139
+ }
140
+ function getBitValue(bytes, index, bitsPerStatus, mask) {
141
+ validateBitSize(bitsPerStatus);
142
+ const totalBits = bytes.length * 8;
143
+ const maxIndex = Math.floor(totalBits / bitsPerStatus) - 1;
144
+ if (index < 0 || index > maxIndex) {
145
+ throw new IndexOutOfBoundsError(index, maxIndex);
146
+ }
147
+ const jump = bitsPerStatus * index;
148
+ const byteIndex = Math.floor(jump / 8);
149
+ const bitOffset = jump % 8;
150
+ const calculatedMask = mask ?? (1 << bitsPerStatus) - 1;
151
+ let value = bytes[byteIndex] >> bitOffset & calculatedMask;
152
+ if (bitOffset + bitsPerStatus > 8) {
153
+ const bitsInNextByte = bitOffset + bitsPerStatus - 8;
154
+ const nextByteBits = bytes[byteIndex + 1] & (1 << bitsInNextByte) - 1;
155
+ value |= nextByteBits << bitsPerStatus - bitsInNextByte;
156
+ }
157
+ return value;
158
+ }
159
+ function setBitValue(bytes, index, value, bitsPerStatus) {
160
+ validateBitSize(bitsPerStatus);
161
+ validateStatusValue(value, bitsPerStatus);
162
+ const totalBits = bytes.length * 8;
163
+ const maxIndex = Math.floor(totalBits / bitsPerStatus) - 1;
164
+ if (index < 0 || index > maxIndex) {
165
+ throw new IndexOutOfBoundsError(index, maxIndex);
166
+ }
167
+ const jump = bitsPerStatus * index;
168
+ const byteIndex = Math.floor(jump / 8);
169
+ const bitOffset = jump % 8;
170
+ const mask = (1 << bitsPerStatus) - 1;
171
+ bytes[byteIndex] &= ~(mask << bitOffset);
172
+ bytes[byteIndex] |= (value & mask) << bitOffset;
173
+ if (bitOffset + bitsPerStatus > 8) {
174
+ const bitsInNextByte = bitOffset + bitsPerStatus - 8;
175
+ const nextByteMask = (1 << bitsInNextByte) - 1;
176
+ bytes[byteIndex + 1] &= ~nextByteMask;
177
+ bytes[byteIndex + 1] |= value >> bitsPerStatus - bitsInNextByte & nextByteMask;
178
+ }
179
+ }
180
+ function calculateCapacity(byteLength, bitsPerStatus) {
181
+ validateBitSize(bitsPerStatus);
182
+ return Math.floor(byteLength * 8 / bitsPerStatus);
183
+ }
184
+ function compress(data) {
185
+ try {
186
+ return pako__namespace.deflate(data, { level: 9 });
187
+ } catch (error) {
188
+ throw new CompressionError("Failed to compress data", error);
189
+ }
190
+ }
191
+ function decompress(compressed) {
192
+ try {
193
+ return pako__namespace.inflate(compressed);
194
+ } catch (error) {
195
+ throw new CompressionError("Failed to decompress data", error);
196
+ }
197
+ }
198
+ function compressToBase64URL(data) {
199
+ const compressed = compress(data);
200
+ return base64UrlEncode(compressed);
201
+ }
202
+ function decompressFromBase64URL(base64url) {
203
+ try {
204
+ const compressed = base64UrlDecode(base64url);
205
+ return decompress(compressed);
206
+ } catch (error) {
207
+ if (error instanceof CompressionError) {
208
+ throw error;
209
+ }
210
+ throw new CompressionError("Failed to decode or decompress base64url data", error);
211
+ }
212
+ }
213
+ function base64UrlEncode(bytes) {
214
+ let base64 = "";
215
+ if (typeof Buffer !== "undefined") {
216
+ base64 = Buffer.from(bytes).toString("base64");
217
+ } else {
218
+ const binary = String.fromCharCode(...bytes);
219
+ base64 = btoa(binary);
220
+ }
221
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
222
+ }
223
+ function base64UrlDecode(base64url) {
224
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
225
+ const padding = base64.length % 4;
226
+ if (padding > 0) {
227
+ base64 += "=".repeat(4 - padding);
228
+ }
229
+ if (typeof Buffer !== "undefined") {
230
+ return new Uint8Array(Buffer.from(base64, "base64"));
231
+ } else {
232
+ const binary = atob(base64);
233
+ const bytes = new Uint8Array(binary.length);
234
+ for (let i = 0; i < binary.length; i++) {
235
+ bytes[i] = binary.charCodeAt(i);
236
+ }
237
+ return bytes;
238
+ }
239
+ }
240
+
241
+ // lib/core/StatusList.ts
242
+ var StatusList = class _StatusList {
243
+ statusArray;
244
+ bitsPerStatus;
245
+ /**
246
+ * Creates a StatusList from a byte array.
247
+ *
248
+ * @param statusArray - Byte array containing packed status values
249
+ * @param bitsPerStatus - Number of bits per status entry
250
+ *
251
+ * @private Use static factory methods instead: fromArray(), decompressFromBase64URL(), decompressFromBytes()
252
+ */
253
+ constructor(statusArray, bitsPerStatus) {
254
+ this.statusArray = statusArray;
255
+ this.bitsPerStatus = bitsPerStatus;
256
+ }
257
+ /**
258
+ * Gets the status value at a specific index.
259
+ *
260
+ * @param index - Index of the status entry (0-based)
261
+ * @returns Status value at the given index
262
+ * @throws {IndexOutOfBoundsError} If index is out of bounds
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * const status = list.getStatus(42);
267
+ * if (status === StandardStatusValues.INVALID) {
268
+ * console.log('Credential is revoked');
269
+ * }
270
+ * ```
271
+ */
272
+ getStatus(index) {
273
+ return getBitValue(this.statusArray, index, this.bitsPerStatus);
274
+ }
275
+ /**
276
+ * Sets the status value at a specific index.
277
+ *
278
+ * Note: This modifies the status list in place. After modifying statuses,
279
+ * you'll need to recompress and re-sign the status list token.
280
+ *
281
+ * @param index - Index of the status entry (0-based)
282
+ * @param value - New status value
283
+ * @throws {IndexOutOfBoundsError} If index is out of bounds
284
+ * @throws {InvalidStatusValueError} If value exceeds the maximum for the bit size
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * // Revoke credential at index 42
289
+ * list.setStatus(42, StandardStatusValues.INVALID);
290
+ *
291
+ * // Suspend credential at index 100
292
+ * list.setStatus(100, StandardStatusValues.SUSPENDED);
293
+ * ```
294
+ */
295
+ setStatus(index, value) {
296
+ setBitValue(this.statusArray, index, value, this.bitsPerStatus);
297
+ }
298
+ /**
299
+ * Gets the number of bits used per status entry.
300
+ *
301
+ * @returns Number of bits per status (1, 2, 4, or 8)
302
+ */
303
+ getBitsPerStatus() {
304
+ return this.bitsPerStatus;
305
+ }
306
+ /**
307
+ * Gets the total number of status entries in this list.
308
+ *
309
+ * @returns Total capacity of the status list
310
+ */
311
+ getSize() {
312
+ return calculateCapacity(this.statusArray.length, this.bitsPerStatus);
313
+ }
314
+ /**
315
+ * Gets the raw byte array (for internal use).
316
+ *
317
+ * @returns The underlying byte array
318
+ */
319
+ getBytes() {
320
+ return this.statusArray;
321
+ }
322
+ /**
323
+ * Compresses the status list to base64url format (for JWT).
324
+ *
325
+ * @returns Base64URL-encoded compressed status list
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * const lst = list.compressToBase64URL();
330
+ * // Use in JWT payload: { status_list: { bits: 1, lst } }
331
+ * ```
332
+ */
333
+ compressToBase64URL() {
334
+ return compressToBase64URL(this.statusArray);
335
+ }
336
+ /**
337
+ * Compresses the status list to raw bytes (for CWT).
338
+ *
339
+ * @returns Compressed status list as bytes
340
+ *
341
+ * @example
342
+ * ```typescript
343
+ * const lst = list.compressToBytes();
344
+ * // Use in CWT payload: { 65533: { bits: 1, lst } }
345
+ * ```
346
+ */
347
+ compressToBytes() {
348
+ return compress(this.statusArray);
349
+ }
350
+ // ===== Static Factory Methods =====
351
+ /**
352
+ * Creates a StatusList from an array of status values.
353
+ *
354
+ * @param statuses - Array of status values
355
+ * @param bitsPerStatus - Number of bits per status entry (1, 2, 4, or 8)
356
+ * @returns New StatusList instance
357
+ * @throws {InvalidBitSizeError} If bitsPerStatus is not 1, 2, 4, or 8
358
+ * @throws {InvalidStatusValueError} If any status value exceeds the maximum for the bit size
359
+ *
360
+ * @example
361
+ * ```typescript
362
+ * // Create list with 1-bit statuses (valid/invalid)
363
+ * const statuses = new Array(10000).fill(0); // All valid
364
+ * statuses[42] = 1; // Revoke one credential
365
+ * const list = StatusList.fromArray(statuses, 1);
366
+ *
367
+ * // Create list with 2-bit statuses (valid/invalid/suspended/custom)
368
+ * const statuses2 = [0, 1, 2, 0, 1, 3];
369
+ * const list2 = StatusList.fromArray(statuses2, 2);
370
+ * ```
371
+ */
372
+ static fromArray(statuses, bitsPerStatus) {
373
+ const bytes = packBits(statuses, bitsPerStatus);
374
+ return new _StatusList(bytes, bitsPerStatus);
375
+ }
376
+ /**
377
+ * Creates a StatusList by decompressing a base64url-encoded string (from JWT).
378
+ *
379
+ * @param compressed - Base64URL-encoded compressed status list
380
+ * @param bitsPerStatus - Number of bits per status entry
381
+ * @returns New StatusList instance
382
+ * @throws {CompressionError} If decompression fails
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * // From JWT payload
387
+ * const payload = parseJWT(token);
388
+ * const list = StatusList.decompressFromBase64URL(
389
+ * payload.status_list.lst,
390
+ * payload.status_list.bits
391
+ * );
392
+ * ```
393
+ */
394
+ static decompressFromBase64URL(compressed, bitsPerStatus) {
395
+ const bytes = decompressFromBase64URL(compressed);
396
+ return new _StatusList(bytes, bitsPerStatus);
397
+ }
398
+ /**
399
+ * Creates a StatusList by decompressing raw bytes (from CWT).
400
+ *
401
+ * @param compressed - Compressed status list as bytes
402
+ * @param bitsPerStatus - Number of bits per status entry
403
+ * @returns New StatusList instance
404
+ * @throws {CompressionError} If decompression fails
405
+ *
406
+ * @example
407
+ * ```typescript
408
+ * // From CWT payload
409
+ * const payload = parseCWT(token);
410
+ * const statusListData = payload[65533]; // status_list claim
411
+ * const list = StatusList.decompressFromBytes(
412
+ * statusListData.lst,
413
+ * statusListData.bits
414
+ * );
415
+ * ```
416
+ */
417
+ static decompressFromBytes(compressed, bitsPerStatus) {
418
+ const bytes = decompress(compressed);
419
+ return new _StatusList(bytes, bitsPerStatus);
420
+ }
421
+ /**
422
+ * Creates an empty StatusList with a specified capacity.
423
+ *
424
+ * All status entries are initialized to 0 (VALID).
425
+ *
426
+ * @param capacity - Number of status entries
427
+ * @param bitsPerStatus - Number of bits per status entry (1, 2, 4, or 8)
428
+ * @returns New StatusList instance
429
+ *
430
+ * @example
431
+ * ```typescript
432
+ * // Create empty list for 100,000 credentials
433
+ * const list = StatusList.create(100000, 1);
434
+ * ```
435
+ */
436
+ static create(capacity, bitsPerStatus) {
437
+ const totalBits = capacity * bitsPerStatus;
438
+ const byteLength = Math.ceil(totalBits / 8);
439
+ const bytes = new Uint8Array(byteLength);
440
+ return new _StatusList(bytes, bitsPerStatus);
441
+ }
442
+ /**
443
+ * Converts the status list to an array of status values (for debugging/testing).
444
+ *
445
+ * Warning: For large status lists, this can be memory-intensive.
446
+ *
447
+ * @returns Array of all status values
448
+ */
449
+ toArray() {
450
+ return unpackBits(this.statusArray, this.bitsPerStatus);
451
+ }
452
+ };
453
+
454
+ // lib/types/common.ts
455
+ var StandardStatusValues = /* @__PURE__ */ ((StandardStatusValues2) => {
456
+ StandardStatusValues2[StandardStatusValues2["VALID"] = 0] = "VALID";
457
+ StandardStatusValues2[StandardStatusValues2["INVALID"] = 1] = "INVALID";
458
+ StandardStatusValues2[StandardStatusValues2["SUSPENDED"] = 2] = "SUSPENDED";
459
+ StandardStatusValues2[StandardStatusValues2["APPLICATION_SPECIFIC"] = 3] = "APPLICATION_SPECIFIC";
460
+ return StandardStatusValues2;
461
+ })(StandardStatusValues || {});
462
+
463
+ // lib/formats/jwt/StatusListJWT.ts
464
+ function createJWTStatusListPayload(options) {
465
+ const {
466
+ iss,
467
+ sub,
468
+ lst,
469
+ bits,
470
+ iat = Math.floor(Date.now() / 1e3),
471
+ exp,
472
+ ttl,
473
+ aggregation_uri,
474
+ additionalClaims = {}
475
+ } = options;
476
+ const header = {
477
+ typ: "statuslist+jwt",
478
+ alg: "ES256"
479
+ // Default algorithm, can be overridden when signing
480
+ };
481
+ const payload = {
482
+ iss,
483
+ sub,
484
+ iat,
485
+ status_list: {
486
+ bits,
487
+ lst,
488
+ ...aggregation_uri && { aggregation_uri }
489
+ },
490
+ ...additionalClaims
491
+ };
492
+ if (exp !== void 0) {
493
+ payload.exp = exp;
494
+ }
495
+ if (ttl !== void 0) {
496
+ payload.ttl = ttl;
497
+ }
498
+ return { header, payload };
499
+ }
500
+ function parseJWTStatusList(jwt) {
501
+ const parts = jwt.split(".");
502
+ if (parts.length !== 3) {
503
+ throw new InvalidTokenFormatError("JWT must have 3 parts separated by dots");
504
+ }
505
+ try {
506
+ const headerJson = base64UrlDecode2(parts[0]);
507
+ const header = JSON.parse(headerJson);
508
+ if (header.typ !== "statuslist+jwt") {
509
+ throw new InvalidTokenFormatError(`Invalid typ header: expected "statuslist+jwt", got "${header.typ}"`);
510
+ }
511
+ const payloadJson = base64UrlDecode2(parts[1]);
512
+ const payload = JSON.parse(payloadJson);
513
+ validateJWTPayload(payload);
514
+ const statusList = StatusList.decompressFromBase64URL(payload.status_list.lst, payload.status_list.bits);
515
+ return {
516
+ header,
517
+ payload,
518
+ jwt,
519
+ statusList
520
+ };
521
+ } catch (error) {
522
+ if (error instanceof InvalidTokenFormatError) {
523
+ throw error;
524
+ }
525
+ throw new InvalidTokenFormatError(`Failed to parse JWT: ${error.message}`);
526
+ }
527
+ }
528
+ function extractStatusListReference(credentialJWT) {
529
+ const parts = credentialJWT.split(".");
530
+ if (parts.length !== 3) {
531
+ throw new InvalidTokenFormatError("JWT must have 3 parts");
532
+ }
533
+ try {
534
+ const payloadJson = base64UrlDecode2(parts[1]);
535
+ const payload = JSON.parse(payloadJson);
536
+ if (!payload.status || typeof payload.status !== "object") {
537
+ throw new InvalidTokenFormatError("Missing status claim in credential");
538
+ }
539
+ const status = payload.status;
540
+ if (!status.status_list || typeof status.status_list !== "object") {
541
+ throw new InvalidTokenFormatError("Missing status_list in status claim");
542
+ }
543
+ const statusList = status.status_list;
544
+ if (typeof statusList.idx !== "number" || statusList.idx < 0) {
545
+ throw new InvalidTokenFormatError("Invalid or missing idx in status_list");
546
+ }
547
+ if (typeof statusList.uri !== "string" || !statusList.uri) {
548
+ throw new InvalidTokenFormatError("Invalid or missing uri in status_list");
549
+ }
550
+ return {
551
+ idx: statusList.idx,
552
+ uri: statusList.uri
553
+ };
554
+ } catch (error) {
555
+ if (error instanceof InvalidTokenFormatError) {
556
+ throw error;
557
+ }
558
+ throw new InvalidTokenFormatError(`Failed to extract status reference: ${error.message}`);
559
+ }
560
+ }
561
+ function validateJWTPayload(payload) {
562
+ if (!payload.iss || typeof payload.iss !== "string") {
563
+ throw new InvalidTokenFormatError('Missing or invalid "iss" claim');
564
+ }
565
+ if (!payload.sub || typeof payload.sub !== "string") {
566
+ throw new InvalidTokenFormatError('Missing or invalid "sub" claim');
567
+ }
568
+ if (typeof payload.iat !== "number") {
569
+ throw new InvalidTokenFormatError('Missing or invalid "iat" claim');
570
+ }
571
+ if (!payload.status_list || typeof payload.status_list !== "object") {
572
+ throw new InvalidTokenFormatError('Missing or invalid "status_list" claim');
573
+ }
574
+ const { bits, lst } = payload.status_list;
575
+ if (![1, 2, 4, 8].includes(bits)) {
576
+ throw new InvalidTokenFormatError(`Invalid "bits" value: must be 1, 2, 4, or 8, got ${bits}`);
577
+ }
578
+ if (!lst || typeof lst !== "string") {
579
+ throw new InvalidTokenFormatError('Missing or invalid "lst" field in status_list');
580
+ }
581
+ if (payload.exp !== void 0 && typeof payload.exp !== "number") {
582
+ throw new InvalidTokenFormatError('Invalid "exp" claim: must be a number');
583
+ }
584
+ if (payload.ttl !== void 0 && typeof payload.ttl !== "number") {
585
+ throw new InvalidTokenFormatError('Invalid "ttl" claim: must be a number');
586
+ }
587
+ }
588
+ function base64UrlDecode2(base64url) {
589
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
590
+ const padding = base64.length % 4;
591
+ if (padding > 0) {
592
+ base64 += "=".repeat(4 - padding);
593
+ }
594
+ if (typeof Buffer !== "undefined") {
595
+ return Buffer.from(base64, "base64").toString("utf-8");
596
+ } else {
597
+ return decodeURIComponent(
598
+ atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
599
+ );
600
+ }
601
+ }
602
+ async function signStatusListJWT(payload, privateKey, options) {
603
+ const { alg = "ES256", kid, additionalHeaders = {} } = options || {};
604
+ const jwt = new jose.SignJWT(payload).setProtectedHeader({
605
+ typ: "statuslist+jwt",
606
+ alg,
607
+ ...kid && { kid },
608
+ ...additionalHeaders
609
+ }).setIssuer(payload.iss).setSubject(payload.sub).setIssuedAt(payload.iat);
610
+ if (payload.exp !== void 0) {
611
+ jwt.setExpirationTime(payload.exp);
612
+ }
613
+ return await jwt.sign(privateKey);
614
+ }
615
+ async function verifyStatusListJWT(jwt, publicKey, options) {
616
+ const { issuer, subject, currentTime, clockTolerance = 0 } = options || {};
617
+ try {
618
+ const result = await jose.jwtVerify(jwt, publicKey, {
619
+ typ: "statuslist+jwt",
620
+ ...issuer && { issuer },
621
+ ...subject && { subject },
622
+ ...currentTime && { currentDate: new Date(currentTime * 1e3) },
623
+ clockTolerance
624
+ });
625
+ const header = result.protectedHeader;
626
+ const payload = result.payload;
627
+ if (header.typ !== "statuslist+jwt") {
628
+ throw new InvalidTokenFormatError(`Invalid typ header: expected "statuslist+jwt", got "${header.typ}"`);
629
+ }
630
+ if (!payload.iss || typeof payload.iss !== "string") {
631
+ throw new InvalidTokenFormatError('Missing or invalid "iss" claim');
632
+ }
633
+ if (!payload.sub || typeof payload.sub !== "string") {
634
+ throw new InvalidTokenFormatError('Missing or invalid "sub" claim');
635
+ }
636
+ if (typeof payload.iat !== "number") {
637
+ throw new InvalidTokenFormatError('Missing or invalid "iat" claim');
638
+ }
639
+ if (!payload.status_list || typeof payload.status_list !== "object") {
640
+ throw new InvalidTokenFormatError('Missing or invalid "status_list" claim');
641
+ }
642
+ const { bits, lst } = payload.status_list;
643
+ if (![1, 2, 4, 8].includes(bits)) {
644
+ throw new InvalidTokenFormatError(`Invalid "bits" value: must be 1, 2, 4, or 8, got ${bits}`);
645
+ }
646
+ if (!lst || typeof lst !== "string") {
647
+ throw new InvalidTokenFormatError('Missing or invalid "lst" field in status_list');
648
+ }
649
+ return {
650
+ header,
651
+ payload
652
+ };
653
+ } catch (error) {
654
+ if (error instanceof InvalidTokenFormatError) {
655
+ throw error;
656
+ }
657
+ const message = error.message;
658
+ throw new InvalidTokenFormatError(`JWT verification failed: ${message}`);
659
+ }
660
+ }
661
+ async function verifySignatureOnly(jwt, publicKey) {
662
+ try {
663
+ const result = await jose.jwtVerify(jwt, publicKey, {
664
+ typ: "statuslist+jwt"
665
+ });
666
+ return {
667
+ header: result.protectedHeader,
668
+ payload: result.payload
669
+ };
670
+ } catch (error) {
671
+ throw new InvalidTokenFormatError(`Signature verification failed: ${error.message}`);
672
+ }
673
+ }
674
+
675
+ // lib/formats/cwt/types.ts
676
+ var CWT_CLAIMS = {
677
+ /** Issuer (iss) - Claim key 1 */
678
+ ISS: 1,
679
+ /** Subject (sub) - Claim key 2 */
680
+ SUB: 2,
681
+ /** Expiration Time (exp) - Claim key 4 */
682
+ EXP: 4,
683
+ /** Issued At (iat) - Claim key 6 */
684
+ IAT: 6,
685
+ /** Time to Live (ttl) - Claim key 65534 (custom) */
686
+ TTL: 65534,
687
+ /** Status List - Claim key 65533 (custom) */
688
+ STATUS_LIST: 65533
689
+ };
690
+ var COSE_HEADERS = {
691
+ /** Algorithm - Header parameter 1 */
692
+ ALG: 1,
693
+ /** Content Type - Header parameter 16 */
694
+ CONTENT_TYPE: 16,
695
+ /** Key ID - Header parameter 4 */
696
+ KID: 4
697
+ };
698
+ var COSE_ALGORITHMS = {
699
+ /** ES256 - ECDSA with SHA-256 */
700
+ ES256: -7,
701
+ /** ES384 - ECDSA with SHA-384 */
702
+ ES384: -35,
703
+ /** ES512 - ECDSA with SHA-512 */
704
+ ES512: -36,
705
+ /** EdDSA */
706
+ EdDSA: -8
707
+ };
708
+ var COSE_SIGN1_TAG = 18;
709
+ var ALGORITHM_MAP = {
710
+ "-7": { hash: "sha256", curve: "prime256v1" },
711
+ // ES256
712
+ "-35": { hash: "sha384", curve: "secp384r1" },
713
+ // ES384
714
+ "-36": { hash: "sha512", curve: "secp521r1" },
715
+ // ES512
716
+ "-8": { hash: "sha512" }
717
+ // EdDSA (Ed25519)
718
+ };
719
+ function signCOSE(payload, protectedHeader, privateKey) {
720
+ const protectedHeaderEncoded = cbor__namespace.encode(protectedHeader);
721
+ const sigStructure = cbor__namespace.encode([
722
+ "Signature1",
723
+ // Context
724
+ protectedHeaderEncoded,
725
+ new Uint8Array(0),
726
+ // External AAD (empty)
727
+ payload
728
+ ]);
729
+ const alg = protectedHeader.get(1);
730
+ if (!alg) {
731
+ throw new InvalidTokenFormatError("Algorithm (alg) must be specified in protected header");
732
+ }
733
+ const signature = signData(sigStructure, alg, privateKey);
734
+ const coseSign1 = [
735
+ protectedHeaderEncoded,
736
+ // Protected header (encoded)
737
+ {},
738
+ // Unprotected header (empty map)
739
+ payload,
740
+ // Payload
741
+ signature
742
+ // Signature
743
+ ];
744
+ const tagged = new cbor__namespace.Tagged(COSE_SIGN1_TAG, coseSign1);
745
+ return cbor__namespace.encode(tagged);
746
+ }
747
+ function verifyCOSE(coseSign1, publicKey) {
748
+ try {
749
+ const decoded = cbor__namespace.decode(coseSign1);
750
+ if (!(decoded instanceof cbor__namespace.Tagged) || decoded.tag !== COSE_SIGN1_TAG) {
751
+ throw new InvalidTokenFormatError("Invalid COSE Sign1 structure: missing tag 18");
752
+ }
753
+ const [protectedHeaderEncoded, , payload, signature] = decoded.value;
754
+ const protectedHeader = cbor__namespace.decode(protectedHeaderEncoded);
755
+ const alg = protectedHeader.get(1);
756
+ if (!alg) {
757
+ throw new InvalidTokenFormatError("Missing algorithm in protected header");
758
+ }
759
+ const sigStructure = cbor__namespace.encode([
760
+ "Signature1",
761
+ protectedHeaderEncoded,
762
+ new Uint8Array(0),
763
+ // External AAD
764
+ payload
765
+ ]);
766
+ const valid = verifySignature(sigStructure, signature, alg, publicKey);
767
+ if (!valid) {
768
+ throw new InvalidTokenFormatError("Invalid COSE Sign1 signature");
769
+ }
770
+ return payload;
771
+ } catch (error) {
772
+ if (error instanceof InvalidTokenFormatError) {
773
+ throw error;
774
+ }
775
+ throw new InvalidTokenFormatError(`Failed to verify COSE Sign1: ${error.message}`);
776
+ }
777
+ }
778
+ function signData(data, alg, privateKey) {
779
+ const algInfo = ALGORITHM_MAP[alg.toString()];
780
+ if (!algInfo) {
781
+ throw new InvalidTokenFormatError(`Unsupported algorithm: ${alg}`);
782
+ }
783
+ if (privateKey.kty === "EC2") {
784
+ if (!privateKey.d) {
785
+ throw new InvalidTokenFormatError("Private key (d) is required for signing");
786
+ }
787
+ const pemKey = ec2KeyToPEM();
788
+ const sign = crypto.createSign(`RSA-SHA${algInfo.hash.replace("sha", "")}`);
789
+ sign.update(data);
790
+ const signature = sign.sign(pemKey);
791
+ return derToRaw(signature, getSignatureLength(alg));
792
+ } else if (privateKey.kty === "OKP") {
793
+ if (!privateKey.d) {
794
+ throw new InvalidTokenFormatError("Private key (d) is required for signing");
795
+ }
796
+ const pemKey = okpKeyToPEM();
797
+ const sign = crypto.createSign("SHA512");
798
+ sign.update(data);
799
+ return sign.sign(pemKey);
800
+ }
801
+ const _exhaustiveCheck = privateKey;
802
+ throw new InvalidTokenFormatError(`Unsupported key type: ${_exhaustiveCheck.kty}`);
803
+ }
804
+ function verifySignature(data, signature, alg, publicKey) {
805
+ const algInfo = ALGORITHM_MAP[alg.toString()];
806
+ if (!algInfo) {
807
+ throw new InvalidTokenFormatError(`Unsupported algorithm: ${alg}`);
808
+ }
809
+ if (publicKey.kty === "EC2") {
810
+ const pemKey = ec2KeyToPEM();
811
+ const derSignature = rawToDer(signature);
812
+ const verify = crypto.createVerify(`RSA-SHA${algInfo.hash.replace("sha", "")}`);
813
+ verify.update(data);
814
+ return verify.verify(pemKey, derSignature);
815
+ } else if (publicKey.kty === "OKP") {
816
+ const pemKey = okpKeyToPEM();
817
+ const verify = crypto.createVerify("SHA512");
818
+ verify.update(data);
819
+ return verify.verify(pemKey, signature);
820
+ }
821
+ const _exhaustiveCheck = publicKey;
822
+ throw new InvalidTokenFormatError(`Unsupported key type: ${_exhaustiveCheck.kty}`);
823
+ }
824
+ function ec2KeyToPEM(_key, _isPrivate) {
825
+ throw new Error("EC2 to PEM conversion not fully implemented. Use a COSE library like @transmute/cose instead.");
826
+ }
827
+ function okpKeyToPEM(_key, _isPrivate) {
828
+ throw new Error("OKP to PEM conversion not fully implemented. Use a COSE library like @transmute/cose instead.");
829
+ }
830
+ function derToRaw(derSignature, length) {
831
+ let offset = 2;
832
+ const rLength = derSignature[offset + 1];
833
+ offset += 2;
834
+ const r = derSignature.slice(offset, offset + rLength);
835
+ offset += rLength;
836
+ const sLength = derSignature[offset + 1];
837
+ offset += 2;
838
+ const s = derSignature.slice(offset, offset + sLength);
839
+ const halfLength = length / 2;
840
+ const raw = new Uint8Array(length);
841
+ raw.set(r.slice(-halfLength), 0);
842
+ raw.set(s.slice(-halfLength), halfLength);
843
+ return raw;
844
+ }
845
+ function rawToDer(rawSignature) {
846
+ const halfLength = rawSignature.length / 2;
847
+ const r = rawSignature.slice(0, halfLength);
848
+ const s = rawSignature.slice(halfLength);
849
+ const derR = encodeDerInteger(r);
850
+ const derS = encodeDerInteger(s);
851
+ const sequence = new Uint8Array(2 + derR.length + derS.length);
852
+ sequence[0] = 48;
853
+ sequence[1] = derR.length + derS.length;
854
+ sequence.set(derR, 2);
855
+ sequence.set(derS, 2 + derR.length);
856
+ return sequence;
857
+ }
858
+ function encodeDerInteger(value) {
859
+ let i = 0;
860
+ while (i < value.length && value[i] === 0) {
861
+ i++;
862
+ }
863
+ value = value.slice(i);
864
+ const needsLeadingZero = value[0] >= 128;
865
+ const length = value.length + (needsLeadingZero ? 1 : 0);
866
+ const der = new Uint8Array(2 + length);
867
+ der[0] = 2;
868
+ der[1] = length;
869
+ if (needsLeadingZero) {
870
+ der[2] = 0;
871
+ der.set(value, 3);
872
+ } else {
873
+ der.set(value, 2);
874
+ }
875
+ return der;
876
+ }
877
+ function getSignatureLength(alg) {
878
+ switch (alg) {
879
+ case -7:
880
+ return 64;
881
+ case -35:
882
+ return 96;
883
+ case -36:
884
+ return 132;
885
+ case -8:
886
+ return 64;
887
+ default:
888
+ throw new InvalidTokenFormatError(`Unknown signature length for algorithm: ${alg}`);
889
+ }
890
+ }
891
+
892
+ // lib/formats/cwt/StatusListCWT.ts
893
+ function createCWTStatusListPayload(options) {
894
+ const {
895
+ iss,
896
+ sub,
897
+ lst,
898
+ bits,
899
+ iat = Math.floor(Date.now() / 1e3),
900
+ exp,
901
+ ttl,
902
+ aggregation_uri,
903
+ additionalClaims = {}
904
+ } = options;
905
+ const payload = {
906
+ ...additionalClaims,
907
+ [CWT_CLAIMS.STATUS_LIST]: {
908
+ bits,
909
+ lst,
910
+ ...aggregation_uri && { aggregation_uri }
911
+ }
912
+ };
913
+ if (iss !== void 0) {
914
+ payload[CWT_CLAIMS.ISS] = iss;
915
+ }
916
+ if (sub !== void 0) {
917
+ payload[CWT_CLAIMS.SUB] = sub;
918
+ }
919
+ if (iat !== void 0) {
920
+ payload[CWT_CLAIMS.IAT] = iat;
921
+ }
922
+ if (exp !== void 0) {
923
+ payload[CWT_CLAIMS.EXP] = exp;
924
+ }
925
+ if (ttl !== void 0) {
926
+ payload[CWT_CLAIMS.TTL] = ttl;
927
+ }
928
+ return payload;
929
+ }
930
+ function encodeCWTPayload(payload) {
931
+ return cbor__namespace.encode(payload);
932
+ }
933
+ function parseCWTStatusList(cwtBytes) {
934
+ try {
935
+ const payload = cbor__namespace.decode(cwtBytes);
936
+ validateCWTPayload(payload);
937
+ const typedPayload = payload;
938
+ const statusListClaim = typedPayload[CWT_CLAIMS.STATUS_LIST];
939
+ const statusList = StatusList.decompressFromBytes(statusListClaim.lst, statusListClaim.bits);
940
+ return {
941
+ protectedHeader: /* @__PURE__ */ new Map(),
942
+ unprotectedHeader: /* @__PURE__ */ new Map(),
943
+ payload: typedPayload,
944
+ cwt: cwtBytes,
945
+ statusList
946
+ };
947
+ } catch (error) {
948
+ if (error instanceof InvalidTokenFormatError) {
949
+ throw error;
950
+ }
951
+ throw new InvalidTokenFormatError(`Failed to parse CWT: ${error.message}`);
952
+ }
953
+ }
954
+ function parseCWTStatusListSigned(cwtBytes, publicKey) {
955
+ try {
956
+ const payloadBytes = verifyCOSE(cwtBytes, publicKey);
957
+ const payload = cbor__namespace.decode(payloadBytes);
958
+ validateCWTPayload(payload);
959
+ const typedPayload = payload;
960
+ const statusListClaim = typedPayload[CWT_CLAIMS.STATUS_LIST];
961
+ const statusList = StatusList.decompressFromBytes(statusListClaim.lst, statusListClaim.bits);
962
+ const decoded = cbor__namespace.decode(cwtBytes);
963
+ const [protectedHeaderEncoded] = decoded.value;
964
+ const protectedHeader = cbor__namespace.decode(protectedHeaderEncoded);
965
+ return {
966
+ protectedHeader,
967
+ unprotectedHeader: /* @__PURE__ */ new Map(),
968
+ payload: typedPayload,
969
+ cwt: cwtBytes,
970
+ statusList
971
+ };
972
+ } catch (error) {
973
+ if (error instanceof InvalidTokenFormatError) {
974
+ throw error;
975
+ }
976
+ throw new InvalidTokenFormatError(`Failed to parse signed CWT: ${error.message}`);
977
+ }
978
+ }
979
+ function signCWTStatusList(payload, privateKey, options) {
980
+ const { alg = COSE_ALGORITHMS.ES256, kid, additionalHeaders = /* @__PURE__ */ new Map() } = options || {};
981
+ const protectedHeader = new Map([
982
+ [COSE_HEADERS.CONTENT_TYPE, "application/statuslist+cwt"],
983
+ [COSE_HEADERS.ALG, alg],
984
+ ...additionalHeaders
985
+ ]);
986
+ if (kid) {
987
+ protectedHeader.set(COSE_HEADERS.KID, kid);
988
+ }
989
+ const payloadBytes = cbor__namespace.encode(payload);
990
+ return signCOSE(payloadBytes, protectedHeader, privateKey);
991
+ }
992
+ function extractStatusListReferenceCBOR(credentialCBOR) {
993
+ try {
994
+ const credential = cbor__namespace.decode(credentialCBOR);
995
+ const status = credential.status || credential["status"];
996
+ if (!status || typeof status !== "object") {
997
+ throw new InvalidTokenFormatError("Missing status claim in credential");
998
+ }
999
+ const statusObj = status;
1000
+ const statusList = statusObj.status_list || statusObj["status_list"];
1001
+ if (!statusList || typeof statusList !== "object") {
1002
+ throw new InvalidTokenFormatError("Missing status_list in status claim");
1003
+ }
1004
+ const statusListObj = statusList;
1005
+ if (typeof statusListObj.idx !== "number" || statusListObj.idx < 0) {
1006
+ throw new InvalidTokenFormatError("Invalid or missing idx in status_list");
1007
+ }
1008
+ if (typeof statusListObj.uri !== "string" || !statusListObj.uri) {
1009
+ throw new InvalidTokenFormatError("Invalid or missing uri in status_list");
1010
+ }
1011
+ return {
1012
+ idx: statusListObj.idx,
1013
+ uri: statusListObj.uri
1014
+ };
1015
+ } catch (error) {
1016
+ if (error instanceof InvalidTokenFormatError) {
1017
+ throw error;
1018
+ }
1019
+ throw new InvalidTokenFormatError(`Failed to extract status reference: ${error.message}`);
1020
+ }
1021
+ }
1022
+ function validateCWTPayload(payload) {
1023
+ const statusList = payload[CWT_CLAIMS.STATUS_LIST];
1024
+ if (!statusList || typeof statusList !== "object") {
1025
+ throw new InvalidTokenFormatError(`Missing or invalid status_list claim (key ${CWT_CLAIMS.STATUS_LIST})`);
1026
+ }
1027
+ const statusListObj = statusList;
1028
+ if (!statusListObj.bits || ![1, 2, 4, 8].includes(statusListObj.bits)) {
1029
+ throw new InvalidTokenFormatError(`Invalid "bits" value: must be 1, 2, 4, or 8`);
1030
+ }
1031
+ if (!statusListObj.lst || !(statusListObj.lst instanceof Uint8Array)) {
1032
+ throw new InvalidTokenFormatError('Missing or invalid "lst" field in status_list');
1033
+ }
1034
+ if (payload[CWT_CLAIMS.ISS] !== void 0 && typeof payload[CWT_CLAIMS.ISS] !== "string") {
1035
+ throw new InvalidTokenFormatError(`Invalid "iss" claim (key ${CWT_CLAIMS.ISS}): must be a string`);
1036
+ }
1037
+ if (payload[CWT_CLAIMS.SUB] !== void 0 && typeof payload[CWT_CLAIMS.SUB] !== "string") {
1038
+ throw new InvalidTokenFormatError(`Invalid "sub" claim (key ${CWT_CLAIMS.SUB}): must be a string`);
1039
+ }
1040
+ if (payload[CWT_CLAIMS.IAT] !== void 0 && typeof payload[CWT_CLAIMS.IAT] !== "number") {
1041
+ throw new InvalidTokenFormatError(`Invalid "iat" claim (key ${CWT_CLAIMS.IAT}): must be a number`);
1042
+ }
1043
+ if (payload[CWT_CLAIMS.EXP] !== void 0 && typeof payload[CWT_CLAIMS.EXP] !== "number") {
1044
+ throw new InvalidTokenFormatError(`Invalid "exp" claim (key ${CWT_CLAIMS.EXP}): must be a number`);
1045
+ }
1046
+ if (payload[CWT_CLAIMS.TTL] !== void 0 && typeof payload[CWT_CLAIMS.TTL] !== "number") {
1047
+ throw new InvalidTokenFormatError(`Invalid "ttl" claim (key ${CWT_CLAIMS.TTL}): must be a number`);
1048
+ }
1049
+ }
1050
+
1051
+ // lib/helper/fetcher.ts
1052
+ var DEFAULT_TIMEOUT = 1e4;
1053
+ var DEFAULT_MAX_REDIRECTS = 3;
1054
+ var CONTENT_TYPES = {
1055
+ JWT: "application/statuslist+jwt",
1056
+ CWT: "application/statuslist+cwt"
1057
+ };
1058
+ async function fetchStatusListToken(uri, options = {}) {
1059
+ if (!uri || typeof uri !== "string") {
1060
+ throw new MissingStatusListUriError("Status list URI is required");
1061
+ }
1062
+ let parsedUri;
1063
+ try {
1064
+ parsedUri = new URL(uri);
1065
+ } catch (error) {
1066
+ throw new MissingStatusListUriError(`Invalid status list URI: ${uri}`);
1067
+ }
1068
+ if (!["http:", "https:"].includes(parsedUri.protocol)) {
1069
+ throw new FetchError(`Unsupported protocol: ${parsedUri.protocol}. Only HTTP(S) is allowed.`);
1070
+ }
1071
+ const {
1072
+ timeout = DEFAULT_TIMEOUT,
1073
+ headers = {},
1074
+ maxRedirects = DEFAULT_MAX_REDIRECTS,
1075
+ fetchImpl = fetch
1076
+ } = options;
1077
+ const controller = new AbortController();
1078
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1079
+ try {
1080
+ const response = await fetchImpl(uri, {
1081
+ method: "GET",
1082
+ headers: {
1083
+ Accept: `${CONTENT_TYPES.JWT}, ${CONTENT_TYPES.CWT}`,
1084
+ ...headers
1085
+ },
1086
+ signal: controller.signal,
1087
+ redirect: "follow",
1088
+ // @ts-expect-error - maxRedirects is not in standard fetch but supported by undici
1089
+ maxRedirects
1090
+ });
1091
+ clearTimeout(timeoutId);
1092
+ if (!response.ok) {
1093
+ throw new FetchError(
1094
+ `HTTP ${response.status} ${response.statusText} while fetching status list from ${uri}`
1095
+ );
1096
+ }
1097
+ const contentType = response.headers.get("content-type");
1098
+ if (contentType?.includes(CONTENT_TYPES.JWT)) {
1099
+ return await response.text();
1100
+ }
1101
+ if (contentType?.includes(CONTENT_TYPES.CWT)) {
1102
+ const arrayBuffer2 = await response.arrayBuffer();
1103
+ return new Uint8Array(arrayBuffer2);
1104
+ }
1105
+ const arrayBuffer = await response.arrayBuffer();
1106
+ const bytes = new Uint8Array(arrayBuffer);
1107
+ const text = new TextDecoder().decode(bytes.slice(0, 3));
1108
+ if (text === "eyJ") {
1109
+ return new TextDecoder().decode(bytes);
1110
+ }
1111
+ return bytes;
1112
+ } catch (error) {
1113
+ clearTimeout(timeoutId);
1114
+ if (error instanceof FetchError || error instanceof MissingStatusListUriError) {
1115
+ throw error;
1116
+ }
1117
+ if (error.name === "AbortError") {
1118
+ throw new FetchError(`Request timeout after ${timeout}ms while fetching ${uri}`);
1119
+ }
1120
+ throw new FetchError(
1121
+ `Failed to fetch status list from ${uri}: ${error.message}`
1122
+ );
1123
+ }
1124
+ }
1125
+ function isValidStatusListUri(uri) {
1126
+ if (!uri || typeof uri !== "string") {
1127
+ return false;
1128
+ }
1129
+ try {
1130
+ const parsed = new URL(uri);
1131
+ return ["http:", "https:"].includes(parsed.protocol);
1132
+ } catch {
1133
+ return false;
1134
+ }
1135
+ }
1136
+
1137
+ // lib/helper/validator.ts
1138
+ var MAX_TTL_SECONDS = 31536e3;
1139
+ var MIN_TTL_SECONDS = 60;
1140
+ function validateJWTPayload2(payload) {
1141
+ const errors = [];
1142
+ if (!payload.iss || typeof payload.iss !== "string") {
1143
+ errors.push('Missing or invalid "iss" claim (must be a non-empty string)');
1144
+ }
1145
+ if (!payload.sub || typeof payload.sub !== "string") {
1146
+ errors.push('Missing or invalid "sub" claim (must be a non-empty string)');
1147
+ }
1148
+ if (typeof payload.iat !== "number") {
1149
+ errors.push('Missing or invalid "iat" claim (must be a number)');
1150
+ } else if (payload.iat < 0) {
1151
+ errors.push('"iat" claim must be a positive number');
1152
+ }
1153
+ if (!payload.status_list || typeof payload.status_list !== "object") {
1154
+ errors.push('Missing or invalid "status_list" claim (must be an object)');
1155
+ } else {
1156
+ const { bits, lst } = payload.status_list;
1157
+ if (![1, 2, 4, 8].includes(bits)) {
1158
+ errors.push('"status_list.bits" must be 1, 2, 4, or 8');
1159
+ }
1160
+ if (!lst || typeof lst !== "string") {
1161
+ errors.push('"status_list.lst" must be a non-empty base64url-encoded string');
1162
+ }
1163
+ if (payload.status_list.aggregation_uri !== void 0) {
1164
+ if (typeof payload.status_list.aggregation_uri !== "string") {
1165
+ errors.push('"status_list.aggregation_uri" must be a string');
1166
+ }
1167
+ }
1168
+ }
1169
+ if (payload.exp !== void 0) {
1170
+ if (typeof payload.exp !== "number") {
1171
+ errors.push('"exp" claim must be a number');
1172
+ } else if (payload.exp < 0) {
1173
+ errors.push('"exp" claim must be a positive number');
1174
+ }
1175
+ }
1176
+ if (payload.ttl !== void 0) {
1177
+ if (typeof payload.ttl !== "number") {
1178
+ errors.push('"ttl" claim must be a number');
1179
+ } else if (payload.ttl < 0) {
1180
+ errors.push('"ttl" claim must be a positive number');
1181
+ }
1182
+ }
1183
+ return {
1184
+ valid: errors.length === 0,
1185
+ errors
1186
+ };
1187
+ }
1188
+ function validateCWTPayload2(payload) {
1189
+ const errors = [];
1190
+ const statusList = payload[CWT_CLAIMS.STATUS_LIST];
1191
+ if (!statusList || typeof statusList !== "object") {
1192
+ errors.push(`Missing or invalid status_list claim (key ${CWT_CLAIMS.STATUS_LIST})`);
1193
+ } else {
1194
+ const { bits, lst } = statusList;
1195
+ if (![1, 2, 4, 8].includes(bits)) {
1196
+ errors.push('"status_list.bits" must be 1, 2, 4, or 8');
1197
+ }
1198
+ if (!(lst instanceof Uint8Array)) {
1199
+ errors.push('"status_list.lst" must be a Uint8Array');
1200
+ } else if (lst.length === 0) {
1201
+ errors.push('"status_list.lst" must not be empty');
1202
+ }
1203
+ if (statusList.aggregation_uri !== void 0) {
1204
+ if (typeof statusList.aggregation_uri !== "string") {
1205
+ errors.push('"status_list.aggregation_uri" must be a string');
1206
+ }
1207
+ }
1208
+ }
1209
+ if (payload[CWT_CLAIMS.ISS] !== void 0) {
1210
+ if (typeof payload[CWT_CLAIMS.ISS] !== "string") {
1211
+ errors.push(`"iss" claim (key ${CWT_CLAIMS.ISS}) must be a string`);
1212
+ }
1213
+ }
1214
+ if (payload[CWT_CLAIMS.SUB] !== void 0) {
1215
+ if (typeof payload[CWT_CLAIMS.SUB] !== "string") {
1216
+ errors.push(`"sub" claim (key ${CWT_CLAIMS.SUB}) must be a string`);
1217
+ }
1218
+ }
1219
+ if (payload[CWT_CLAIMS.EXP] !== void 0) {
1220
+ const exp = payload[CWT_CLAIMS.EXP];
1221
+ if (typeof exp !== "number") {
1222
+ errors.push(`"exp" claim (key ${CWT_CLAIMS.EXP}) must be a number`);
1223
+ } else if (exp < 0) {
1224
+ errors.push(`"exp" claim must be a positive number`);
1225
+ }
1226
+ }
1227
+ if (payload[CWT_CLAIMS.IAT] !== void 0) {
1228
+ const iat = payload[CWT_CLAIMS.IAT];
1229
+ if (typeof iat !== "number") {
1230
+ errors.push(`"iat" claim (key ${CWT_CLAIMS.IAT}) must be a number`);
1231
+ } else if (iat < 0) {
1232
+ errors.push(`"iat" claim must be a positive number`);
1233
+ }
1234
+ }
1235
+ if (payload[CWT_CLAIMS.TTL] !== void 0) {
1236
+ const ttl = payload[CWT_CLAIMS.TTL];
1237
+ if (typeof ttl !== "number") {
1238
+ errors.push(`"ttl" claim (key ${CWT_CLAIMS.TTL}) must be a number`);
1239
+ } else if (ttl < 0) {
1240
+ errors.push(`"ttl" claim must be a positive number`);
1241
+ }
1242
+ }
1243
+ return {
1244
+ valid: errors.length === 0,
1245
+ errors
1246
+ };
1247
+ }
1248
+ function validateExpiry(exp, iat, ttl, currentTime = Math.floor(Date.now() / 1e3)) {
1249
+ const errors = [];
1250
+ if (iat !== void 0 && iat > currentTime) {
1251
+ errors.push(`Token issued in the future (iat: ${iat}, current: ${currentTime})`);
1252
+ }
1253
+ if (exp !== void 0) {
1254
+ if (exp < currentTime) {
1255
+ errors.push(`Token expired (exp: ${exp}, current: ${currentTime})`);
1256
+ }
1257
+ }
1258
+ if (ttl !== void 0 && iat !== void 0) {
1259
+ const expirationTime = iat + ttl;
1260
+ if (expirationTime < currentTime) {
1261
+ errors.push(`Token expired by ttl (iat: ${iat}, ttl: ${ttl}, current: ${currentTime})`);
1262
+ }
1263
+ }
1264
+ if (exp !== void 0 && ttl !== void 0 && iat !== void 0) {
1265
+ const calculatedExp = iat + ttl;
1266
+ if (Math.abs(exp - calculatedExp) > 1) {
1267
+ errors.push(
1268
+ `Inconsistent exp and ttl values (exp: ${exp}, iat + ttl: ${calculatedExp})`
1269
+ );
1270
+ }
1271
+ }
1272
+ return {
1273
+ valid: errors.length === 0,
1274
+ errors
1275
+ };
1276
+ }
1277
+ function validateTTLBounds(ttl) {
1278
+ const errors = [];
1279
+ if (ttl === void 0) {
1280
+ return { valid: true, errors: [] };
1281
+ }
1282
+ if (ttl < MIN_TTL_SECONDS) {
1283
+ errors.push(
1284
+ `TTL too short (${ttl} seconds). Recommended minimum: ${MIN_TTL_SECONDS} seconds (1 minute)`
1285
+ );
1286
+ }
1287
+ if (ttl > MAX_TTL_SECONDS) {
1288
+ errors.push(
1289
+ `TTL too large (${ttl} seconds). Recommended maximum: ${MAX_TTL_SECONDS} seconds (1 year). Excessively large TTL values may be used for DoS attacks.`
1290
+ );
1291
+ }
1292
+ return {
1293
+ valid: errors.length === 0,
1294
+ errors
1295
+ };
1296
+ }
1297
+ function isExpired(exp, iat, ttl, currentTime = Math.floor(Date.now() / 1e3)) {
1298
+ if (exp !== void 0 && exp < currentTime) {
1299
+ return true;
1300
+ }
1301
+ if (ttl !== void 0 && iat !== void 0) {
1302
+ const expirationTime = iat + ttl;
1303
+ if (expirationTime < currentTime) {
1304
+ return true;
1305
+ }
1306
+ }
1307
+ return false;
1308
+ }
1309
+
1310
+ // lib/helper/StatusListTokenHelper.ts
1311
+ var StatusListTokenHelper = class _StatusListTokenHelper {
1312
+ constructor(header, payload, statusList, format) {
1313
+ this.header = header;
1314
+ this.payload = payload;
1315
+ this.statusList = statusList;
1316
+ this.format = format;
1317
+ }
1318
+ /**
1319
+ * Creates a helper from a Status List Token (JWT or CWT).
1320
+ *
1321
+ * Automatically detects the format:
1322
+ * - JWT: starts with "eyJ" (base64url-encoded header)
1323
+ * - CWT: CBOR binary format
1324
+ *
1325
+ * @param token - JWT string or CWT Uint8Array
1326
+ * @returns StatusListTokenHelper instance
1327
+ * @throws {InvalidTokenFormatError} If token format is invalid
1328
+ *
1329
+ * @example
1330
+ * ```typescript
1331
+ * // From JWT
1332
+ * const helper = StatusListTokenHelper.fromToken(jwtString);
1333
+ *
1334
+ * // From CWT
1335
+ * const helper = StatusListTokenHelper.fromToken(cwtBytes);
1336
+ * ```
1337
+ */
1338
+ static fromToken(token) {
1339
+ if (typeof token === "string") {
1340
+ const { header, payload: payload2, statusList: statusList2 } = parseJWTStatusList(token);
1341
+ return new _StatusListTokenHelper(header, payload2, statusList2, "jwt");
1342
+ }
1343
+ const { protectedHeader, payload, statusList } = parseCWTStatusList(token);
1344
+ return new _StatusListTokenHelper(protectedHeader, payload, statusList, "cwt");
1345
+ }
1346
+ /**
1347
+ * Creates a helper by fetching a Status List Token from a URI.
1348
+ *
1349
+ * Fetches the token via HTTP GET and automatically detects the format.
1350
+ *
1351
+ * @param reference - Status reference containing idx and uri
1352
+ * @param options - Fetch options
1353
+ * @returns StatusListTokenHelper instance
1354
+ * @throws {FetchError} If HTTP request fails
1355
+ * @throws {InvalidTokenFormatError} If token format is invalid
1356
+ *
1357
+ * @example
1358
+ * ```typescript
1359
+ * const reference = {
1360
+ * idx: 42,
1361
+ * uri: 'https://issuer.example.com/status/1'
1362
+ * };
1363
+ *
1364
+ * const helper = await StatusListTokenHelper.fromStatusReference(reference);
1365
+ * const status = helper.getStatus(reference.idx);
1366
+ * ```
1367
+ */
1368
+ static async fromStatusReference(reference, options) {
1369
+ const token = await fetchStatusListToken(reference.uri, options);
1370
+ return _StatusListTokenHelper.fromToken(token);
1371
+ }
1372
+ /**
1373
+ * Gets the status value at a specific index.
1374
+ *
1375
+ * @param index - Index in the status list
1376
+ * @returns Status value (0-255 depending on bits per status)
1377
+ * @throws {IndexOutOfBoundsError} If index is out of bounds
1378
+ *
1379
+ * @example
1380
+ * ```typescript
1381
+ * const status = helper.getStatus(42);
1382
+ * if (status === StandardStatusValues.INVALID) {
1383
+ * console.log('Credential 42 is revoked');
1384
+ * }
1385
+ * ```
1386
+ */
1387
+ getStatus(index) {
1388
+ return this.statusList.getStatus(index);
1389
+ }
1390
+ /**
1391
+ * Checks if the status list token is expired.
1392
+ *
1393
+ * A token is expired if:
1394
+ * - exp is set and is in the past, OR
1395
+ * - ttl is set and iat + ttl is in the past
1396
+ *
1397
+ * @param currentTime - Current time (seconds since epoch). Defaults to now.
1398
+ * @returns True if expired
1399
+ *
1400
+ * @example
1401
+ * ```typescript
1402
+ * if (helper.isExpired()) {
1403
+ * throw new Error('Status list expired - fetch a new one');
1404
+ * }
1405
+ * ```
1406
+ */
1407
+ isExpired(currentTime) {
1408
+ return isExpired(this.exp, this.iat, this.ttl, currentTime);
1409
+ }
1410
+ /**
1411
+ * Gets the issuer identifier (iss claim).
1412
+ *
1413
+ * @returns Issuer string or undefined if not set
1414
+ */
1415
+ get iss() {
1416
+ if (this.format === "jwt") {
1417
+ return this.payload.iss;
1418
+ }
1419
+ return this.payload[CWT_CLAIMS.ISS];
1420
+ }
1421
+ /**
1422
+ * Gets the subject identifier (sub claim).
1423
+ *
1424
+ * @returns Subject string or undefined if not set
1425
+ */
1426
+ get sub() {
1427
+ if (this.format === "jwt") {
1428
+ return this.payload.sub;
1429
+ }
1430
+ return this.payload[CWT_CLAIMS.SUB];
1431
+ }
1432
+ /**
1433
+ * Gets the issued at time (iat claim).
1434
+ *
1435
+ * @returns Timestamp in seconds since epoch, or undefined if not set
1436
+ */
1437
+ get iat() {
1438
+ if (this.format === "jwt") {
1439
+ return this.payload.iat;
1440
+ }
1441
+ return this.payload[CWT_CLAIMS.IAT];
1442
+ }
1443
+ /**
1444
+ * Gets the expiration time (exp claim).
1445
+ *
1446
+ * @returns Timestamp in seconds since epoch, or undefined if not set
1447
+ */
1448
+ get exp() {
1449
+ if (this.format === "jwt") {
1450
+ return this.payload.exp;
1451
+ }
1452
+ return this.payload[CWT_CLAIMS.EXP];
1453
+ }
1454
+ /**
1455
+ * Gets the time to live (ttl claim).
1456
+ *
1457
+ * @returns TTL in seconds, or undefined if not set
1458
+ */
1459
+ get ttl() {
1460
+ if (this.format === "jwt") {
1461
+ return this.payload.ttl;
1462
+ }
1463
+ return this.payload[CWT_CLAIMS.TTL];
1464
+ }
1465
+ /**
1466
+ * Gets the number of bits per status entry.
1467
+ *
1468
+ * @returns Bits per status (1, 2, 4, or 8)
1469
+ */
1470
+ get bits() {
1471
+ if (this.format === "jwt") {
1472
+ return this.payload.status_list.bits;
1473
+ }
1474
+ return this.payload[CWT_CLAIMS.STATUS_LIST].bits;
1475
+ }
1476
+ /**
1477
+ * Gets the aggregation URI if set.
1478
+ *
1479
+ * @returns Aggregation URI or undefined if not set
1480
+ */
1481
+ get aggregationUri() {
1482
+ if (this.format === "jwt") {
1483
+ return this.payload.status_list.aggregation_uri;
1484
+ }
1485
+ return this.payload[CWT_CLAIMS.STATUS_LIST].aggregation_uri;
1486
+ }
1487
+ /**
1488
+ * Gets the format of the token.
1489
+ *
1490
+ * @returns 'jwt' or 'cwt'
1491
+ */
1492
+ get tokenFormat() {
1493
+ return this.format;
1494
+ }
1495
+ /**
1496
+ * Gets the size of the status list.
1497
+ *
1498
+ * @returns Number of status entries
1499
+ */
1500
+ get size() {
1501
+ return this.statusList.getSize();
1502
+ }
1503
+ /**
1504
+ * Gets the raw header.
1505
+ *
1506
+ * @returns Header as object (JWT) or Map (CWT)
1507
+ */
1508
+ getHeader() {
1509
+ return this.header;
1510
+ }
1511
+ /**
1512
+ * Gets the raw payload.
1513
+ *
1514
+ * @returns Payload object
1515
+ */
1516
+ getPayload() {
1517
+ return this.payload;
1518
+ }
1519
+ /**
1520
+ * Gets the underlying StatusList instance.
1521
+ *
1522
+ * @returns StatusList instance
1523
+ */
1524
+ getStatusList() {
1525
+ return this.statusList;
1526
+ }
1527
+ };
1528
+
1529
+ exports.COSE_ALGORITHMS = COSE_ALGORITHMS;
1530
+ exports.COSE_HEADERS = COSE_HEADERS;
1531
+ exports.CWT_CLAIMS = CWT_CLAIMS;
1532
+ exports.CompressionError = CompressionError;
1533
+ exports.FetchError = FetchError;
1534
+ exports.IndexOutOfBoundsError = IndexOutOfBoundsError;
1535
+ exports.InvalidBitSizeError = InvalidBitSizeError;
1536
+ exports.InvalidStatusValueError = InvalidStatusValueError;
1537
+ exports.InvalidTokenFormatError = InvalidTokenFormatError;
1538
+ exports.MissingStatusListUriError = MissingStatusListUriError;
1539
+ exports.StandardStatusValues = StandardStatusValues;
1540
+ exports.StatusList = StatusList;
1541
+ exports.StatusListError = StatusListError;
1542
+ exports.StatusListExpiredError = StatusListExpiredError;
1543
+ exports.StatusListTokenHelper = StatusListTokenHelper;
1544
+ exports.ValidationError = ValidationError;
1545
+ exports.calculateCapacity = calculateCapacity;
1546
+ exports.compress = compress;
1547
+ exports.compressToBase64URL = compressToBase64URL;
1548
+ exports.createCWTStatusListPayload = createCWTStatusListPayload;
1549
+ exports.createJWTStatusListPayload = createJWTStatusListPayload;
1550
+ exports.decompress = decompress;
1551
+ exports.decompressFromBase64URL = decompressFromBase64URL;
1552
+ exports.encodeCWTPayload = encodeCWTPayload;
1553
+ exports.extractStatusListReference = extractStatusListReference;
1554
+ exports.extractStatusListReferenceCBOR = extractStatusListReferenceCBOR;
1555
+ exports.fetchStatusListToken = fetchStatusListToken;
1556
+ exports.getBitValue = getBitValue;
1557
+ exports.isExpired = isExpired;
1558
+ exports.isValidStatusListUri = isValidStatusListUri;
1559
+ exports.packBits = packBits;
1560
+ exports.parseCWTStatusList = parseCWTStatusList;
1561
+ exports.parseCWTStatusListSigned = parseCWTStatusListSigned;
1562
+ exports.parseJWTStatusList = parseJWTStatusList;
1563
+ exports.setBitValue = setBitValue;
1564
+ exports.signCOSE = signCOSE;
1565
+ exports.signCWTStatusList = signCWTStatusList;
1566
+ exports.signStatusListJWT = signStatusListJWT;
1567
+ exports.unpackBits = unpackBits;
1568
+ exports.validateCWTPayload = validateCWTPayload2;
1569
+ exports.validateExpiry = validateExpiry;
1570
+ exports.validateJWTPayload = validateJWTPayload2;
1571
+ exports.validateTTLBounds = validateTTLBounds;
1572
+ exports.verifyCOSE = verifyCOSE;
1573
+ exports.verifySignatureOnly = verifySignatureOnly;
1574
+ exports.verifyStatusListJWT = verifyStatusListJWT;
1575
+ //# sourceMappingURL=index.cjs.map
1576
+ //# sourceMappingURL=index.cjs.map