routex-settlement 0.1.1 → 0.1.3
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/index.d.ts +4 -1
- package/index.js +242 -50
- package/package.json +7 -5
package/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export declare class KeySettlement {
|
|
2
2
|
private _url;
|
|
3
3
|
private _onErrorResponse;
|
|
4
|
+
private _onRequestError;
|
|
4
5
|
private _secretKey;
|
|
5
6
|
private _requirements;
|
|
6
7
|
private _yaxiSigningKeys;
|
|
7
8
|
private _serverKey?;
|
|
9
|
+
private _settlementPromise?;
|
|
8
10
|
private _measurement?;
|
|
9
|
-
constructor(url: URL, onErrorResponse: (response: Response) => void);
|
|
11
|
+
constructor(url: URL, onErrorResponse: (response: Response) => void, onRequestError?: (e: Error) => never);
|
|
10
12
|
private _settle;
|
|
11
13
|
measurement(): Uint8Array | undefined;
|
|
12
14
|
private _getServerKey;
|
|
@@ -14,6 +16,7 @@ export declare class KeySettlement {
|
|
|
14
16
|
seal(plaintext: Uint8Array, settlementHeaders?: HeadersInit): Promise<Uint8Array>;
|
|
15
17
|
unseal(ciphertext: Uint8Array): Uint8Array;
|
|
16
18
|
private _verifyAttestation;
|
|
19
|
+
private _verifyTcbVersion;
|
|
17
20
|
private _verifyReport;
|
|
18
21
|
private _verifyVcekChain;
|
|
19
22
|
}
|
package/index.js
CHANGED
|
@@ -9,12 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { chacha20poly1305 } from "@noble/ciphers/chacha";
|
|
11
11
|
import { bytesToUtf8 } from "@noble/ciphers/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { equalBytes } from "@noble/curves/utils";
|
|
13
13
|
import { ed25519, x25519 } from "@noble/curves/ed25519";
|
|
14
|
-
import { blake2b } from "@noble/hashes/
|
|
15
|
-
import { p384 } from "@noble/curves/
|
|
14
|
+
import { blake2b } from "@noble/hashes/blake2";
|
|
15
|
+
import { p384 } from "@noble/curves/nist";
|
|
16
16
|
import { hkdf } from "@noble/hashes/hkdf";
|
|
17
|
-
import {
|
|
17
|
+
import { createHasher } from "@noble/hashes/utils";
|
|
18
18
|
import { sha256 } from "@noble/hashes/sha2";
|
|
19
19
|
import * as x509 from "@peculiar/x509";
|
|
20
20
|
import { AsnConvert } from "@peculiar/asn1-schema";
|
|
@@ -22,11 +22,16 @@ import { SubjectPublicKeyInfo } from "@peculiar/asn1-x509";
|
|
|
22
22
|
const REQUIREMENTS = {
|
|
23
23
|
Genoa: {
|
|
24
24
|
minCommittedVersion: [0x1, 0x37, 0x26],
|
|
25
|
-
|
|
25
|
+
minCommittedTcbSnp: 0x16,
|
|
26
26
|
},
|
|
27
27
|
Milan: {
|
|
28
28
|
minCommittedVersion: [0x1, 0x37, 0x16],
|
|
29
|
-
|
|
29
|
+
minCommittedTcbSnp: 0x17,
|
|
30
|
+
},
|
|
31
|
+
// for Turin: SB-3019 doesn't list a TCB[SNP] version in the mitigation, only firmware version
|
|
32
|
+
Turin: {
|
|
33
|
+
minCommittedVersion: [0x1, 0x37, 0x3b],
|
|
34
|
+
minCommittedTcbSnp: 0,
|
|
30
35
|
},
|
|
31
36
|
};
|
|
32
37
|
const YAXI_SIGNING_KEYS = {
|
|
@@ -200,16 +205,100 @@ JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH
|
|
|
200
205
|
CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4
|
|
201
206
|
AFZEAwoKCQ==
|
|
202
207
|
-----END CERTIFICATE-----
|
|
208
|
+
`),
|
|
209
|
+
// ARK-Turin
|
|
210
|
+
new x509.X509Certificate(`
|
|
211
|
+
-----BEGIN CERTIFICATE-----
|
|
212
|
+
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
|
|
247
|
+
-----END CERTIFICATE-----
|
|
248
|
+
`),
|
|
249
|
+
// SEV-Turin
|
|
250
|
+
new x509.X509Certificate(`
|
|
251
|
+
-----BEGIN CERTIFICATE-----
|
|
252
|
+
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
|
|
286
|
+
zpacMwFusA==
|
|
287
|
+
-----END CERTIFICATE-----
|
|
203
288
|
`),
|
|
204
289
|
],
|
|
205
290
|
});
|
|
206
291
|
export class KeySettlement {
|
|
207
|
-
constructor(url, onErrorResponse) {
|
|
292
|
+
constructor(url, onErrorResponse, onRequestError) {
|
|
208
293
|
this._requirements = REQUIREMENTS;
|
|
209
294
|
this._yaxiSigningKeys = YAXI_SIGNING_KEYS;
|
|
210
295
|
this._url = url;
|
|
211
296
|
this._onErrorResponse = onErrorResponse;
|
|
212
|
-
this.
|
|
297
|
+
this._onRequestError =
|
|
298
|
+
onRequestError !== null && onRequestError !== void 0 ? onRequestError : ((e) => {
|
|
299
|
+
throw e;
|
|
300
|
+
});
|
|
301
|
+
this._secretKey = x25519.utils.randomSecretKey();
|
|
213
302
|
}
|
|
214
303
|
_settle(headers) {
|
|
215
304
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -219,6 +308,10 @@ export class KeySettlement {
|
|
|
219
308
|
body: JSON.stringify({
|
|
220
309
|
publicKey: btoa(bytesToBinaryString(x25519.getPublicKey(this._secretKey))),
|
|
221
310
|
}),
|
|
311
|
+
})
|
|
312
|
+
.catch(this._onRequestError)
|
|
313
|
+
.finally(() => {
|
|
314
|
+
this._settlementPromise = undefined;
|
|
222
315
|
});
|
|
223
316
|
if (response.status >= 400) {
|
|
224
317
|
throw yield this._onErrorResponse(response);
|
|
@@ -226,10 +319,10 @@ export class KeySettlement {
|
|
|
226
319
|
const responseData = yield response.json();
|
|
227
320
|
const measurement = yield this._verifyAttestation(responseData);
|
|
228
321
|
const settlementBoxMessage = JSON.parse(bytesToUtf8(this.unseal(binaryStringToBytes(atob(responseData.chachaBox)))));
|
|
229
|
-
this._serverKey =
|
|
230
|
-
binaryStringToBytes(atob(settlementBoxMessage.publicKey)),
|
|
231
|
-
settlementBoxMessage.sessionId,
|
|
232
|
-
|
|
322
|
+
this._serverKey = {
|
|
323
|
+
publicKey: binaryStringToBytes(atob(settlementBoxMessage.publicKey)),
|
|
324
|
+
base64SessionId: settlementBoxMessage.sessionId,
|
|
325
|
+
};
|
|
233
326
|
this._measurement = measurement;
|
|
234
327
|
});
|
|
235
328
|
}
|
|
@@ -239,21 +332,25 @@ export class KeySettlement {
|
|
|
239
332
|
_getServerKey(settlementHeaders) {
|
|
240
333
|
return __awaiter(this, void 0, void 0, function* () {
|
|
241
334
|
if (this._serverKey == null) {
|
|
242
|
-
|
|
335
|
+
if (this._settlementPromise == null) {
|
|
336
|
+
this._settlementPromise = this._settle(settlementHeaders);
|
|
337
|
+
}
|
|
338
|
+
yield this._settlementPromise;
|
|
243
339
|
}
|
|
244
340
|
return this._serverKey;
|
|
245
341
|
});
|
|
246
342
|
}
|
|
247
343
|
getBase64SessionId() {
|
|
248
344
|
return __awaiter(this, arguments, void 0, function* (settlementHeaders = {}) {
|
|
249
|
-
return (yield this._getServerKey(settlementHeaders))
|
|
345
|
+
return (yield this._getServerKey(settlementHeaders)).base64SessionId;
|
|
250
346
|
});
|
|
251
347
|
}
|
|
252
348
|
seal(plaintext_1) {
|
|
253
349
|
return __awaiter(this, arguments, void 0, function* (plaintext, settlementHeaders = {}) {
|
|
254
|
-
const serverPublicKey = (yield this._getServerKey(settlementHeaders))
|
|
350
|
+
const serverPublicKey = (yield this._getServerKey(settlementHeaders))
|
|
351
|
+
.publicKey;
|
|
255
352
|
const tagLength = 16;
|
|
256
|
-
const ephemeralSecretKey = x25519.utils.
|
|
353
|
+
const ephemeralSecretKey = x25519.utils.randomSecretKey();
|
|
257
354
|
const ephemeralPublicKey = x25519.getPublicKey(ephemeralSecretKey);
|
|
258
355
|
const nonce = _getSealNonce(ephemeralPublicKey, serverPublicKey);
|
|
259
356
|
const info = _getInfo(ephemeralPublicKey, serverPublicKey);
|
|
@@ -280,13 +377,15 @@ export class KeySettlement {
|
|
|
280
377
|
throw new Error("Data in attestation report doesn't match chacha box");
|
|
281
378
|
}
|
|
282
379
|
const encoder = new TextEncoder();
|
|
283
|
-
const
|
|
380
|
+
const launchMeasurementInSystemVersion = binaryStringToBytes(atob(response.systemVersion.launchMeasurement));
|
|
284
381
|
const input = new Uint8Array([
|
|
285
382
|
...encoder.encode(response.systemVersion.kind),
|
|
286
383
|
...encoder.encode(response.systemVersion.generation.toString()),
|
|
287
|
-
...encoder.encode(
|
|
384
|
+
...encoder.encode(
|
|
385
|
+
// clerk-report used to use Z for serialization, but not for the signature input
|
|
386
|
+
response.systemVersion.createdAt.replace("Z", "+00:00")),
|
|
288
387
|
...encoder.encode(response.systemVersion.ref),
|
|
289
|
-
...
|
|
388
|
+
...launchMeasurementInSystemVersion,
|
|
290
389
|
]);
|
|
291
390
|
const key = this._yaxiSigningKeys[response.systemVersion.signature.keyId];
|
|
292
391
|
if (key == null) {
|
|
@@ -295,36 +394,108 @@ export class KeySettlement {
|
|
|
295
394
|
if (!ed25519.verify(binaryStringToBytes(atob(response.systemVersion.signature.value)), input, key)) {
|
|
296
395
|
throw new Error("Verification failed: Invalid system version signature");
|
|
297
396
|
}
|
|
298
|
-
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.measurement, REPORT_OFFSETS.measurement + 48),
|
|
397
|
+
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.measurement, REPORT_OFFSETS.measurement + 48), launchMeasurementInSystemVersion)) {
|
|
299
398
|
throw new Error("Reported measurement doesn't match expected measurement");
|
|
300
399
|
}
|
|
301
|
-
return
|
|
400
|
+
return launchMeasurementInSystemVersion;
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
_verifyTcbVersion(attestationReport, vcekTcb) {
|
|
404
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
405
|
+
const versionBytes = attestationReport.slice(0, 4);
|
|
406
|
+
const version = new DataView(versionBytes.buffer).getInt32(0, true);
|
|
407
|
+
let turinLike;
|
|
408
|
+
switch (version) {
|
|
409
|
+
case 0:
|
|
410
|
+
case 1:
|
|
411
|
+
throw new Error("Unsupported Attestation Report Version");
|
|
412
|
+
case 2: {
|
|
413
|
+
const chipId = attestationReport.slice(REPORT_OFFSETS.chipId, 64);
|
|
414
|
+
if (equalBytes(chipId, new Uint8Array(64))) {
|
|
415
|
+
throw new Error("Could not derive CPU family: MASK_CHIP_ID enabled");
|
|
416
|
+
}
|
|
417
|
+
turinLike = equalBytes(chipId.slice(8), new Uint8Array(56));
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
default: // from version 3 onwards CPUID_FAM_ID field should exist
|
|
421
|
+
turinLike = attestationReport[REPORT_OFFSETS.cpuIdFamily] == 0x1a;
|
|
422
|
+
}
|
|
423
|
+
const reportedTcb = attestationReport.slice(REPORT_OFFSETS.reportedTcb, REPORT_OFFSETS.reportedTcb + 8);
|
|
424
|
+
let tcb;
|
|
425
|
+
if (turinLike) {
|
|
426
|
+
tcb = {
|
|
427
|
+
microcode: reportedTcb[7],
|
|
428
|
+
snp: reportedTcb[3],
|
|
429
|
+
tee: reportedTcb[2],
|
|
430
|
+
bootloader: reportedTcb[1],
|
|
431
|
+
fmc: reportedTcb[0],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
tcb = {
|
|
436
|
+
microcode: reportedTcb[7],
|
|
437
|
+
snp: reportedTcb[6],
|
|
438
|
+
tee: reportedTcb[1],
|
|
439
|
+
bootloader: reportedTcb[0],
|
|
440
|
+
fmc: 0,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if (!(tcb.microcode === vcekTcb.microcode &&
|
|
444
|
+
tcb.snp === vcekTcb.snp &&
|
|
445
|
+
tcb.tee === vcekTcb.tee &&
|
|
446
|
+
tcb.bootloader === vcekTcb.bootloader &&
|
|
447
|
+
tcb.fmc === vcekTcb.fmc)) {
|
|
448
|
+
throw new Error("Verification failed: REPORTED_TCB doesn't match with VCEK's TCB_VERSION");
|
|
449
|
+
}
|
|
302
450
|
});
|
|
303
451
|
}
|
|
304
452
|
_verifyReport(attestationReport, vcekChain) {
|
|
305
453
|
return __awaiter(this, void 0, void 0, function* () {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
454
|
+
if (attestationReport.length != 1184) {
|
|
455
|
+
throw new Error(`Attestation report has unexpected length: ${attestationReport.length}`);
|
|
456
|
+
}
|
|
457
|
+
const { requirements, publicKey, vcekTcb } = yield this._verifyVcekChain(vcekChain);
|
|
458
|
+
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.signatureR + 48, REPORT_OFFSETS.signatureR + 72), new Uint8Array(24)) ||
|
|
459
|
+
!equalBytes(attestationReport.slice(REPORT_OFFSETS.signatureS + 48, REPORT_OFFSETS.signatureS + 72), new Uint8Array(24))) {
|
|
460
|
+
throw new Error("Unexpected signature bits");
|
|
461
|
+
}
|
|
462
|
+
const sig = new Uint8Array(96);
|
|
463
|
+
sig.set(attestationReport
|
|
464
|
+
.slice(REPORT_OFFSETS.signatureR, REPORT_OFFSETS.signatureR + 48)
|
|
465
|
+
// Change endianness
|
|
466
|
+
.reverse(), 0);
|
|
467
|
+
sig.set(attestationReport
|
|
468
|
+
.slice(REPORT_OFFSETS.signatureS, REPORT_OFFSETS.signatureS + 48)
|
|
469
|
+
// Change endianness
|
|
470
|
+
.reverse(), 48);
|
|
471
|
+
let valid;
|
|
472
|
+
try {
|
|
473
|
+
valid = p384.verify(sig, attestationReport.slice(0, 672), new Uint8Array(publicKey), { lowS: false });
|
|
474
|
+
}
|
|
475
|
+
catch (_a) {
|
|
476
|
+
valid = false;
|
|
477
|
+
}
|
|
478
|
+
if (!valid) {
|
|
312
479
|
throw new Error("Verification failed: Invalid attestation report signature");
|
|
313
480
|
}
|
|
314
|
-
if ((attestationReport[REPORT_OFFSETS.platInfo] &
|
|
481
|
+
if ((attestationReport[REPORT_OFFSETS.platInfo] &
|
|
482
|
+
(1 << PLATFORM_INFO_BITS.aliasCheckComplete)) ===
|
|
483
|
+
0) {
|
|
315
484
|
throw new Error("Verification failed: Alias check complete is false");
|
|
316
485
|
}
|
|
317
|
-
if ([
|
|
318
|
-
|
|
319
|
-
attestationReport[REPORT_OFFSETS.committedMinor]
|
|
320
|
-
|
|
321
|
-
|
|
486
|
+
if (attestationReport[REPORT_OFFSETS.committedMajor] <
|
|
487
|
+
requirements.minCommittedVersion[0] ||
|
|
488
|
+
attestationReport[REPORT_OFFSETS.committedMinor] <
|
|
489
|
+
requirements.minCommittedVersion[1] ||
|
|
490
|
+
attestationReport[REPORT_OFFSETS.committedBuild] <
|
|
491
|
+
requirements.minCommittedVersion[2]) {
|
|
322
492
|
throw new Error("Verification failed: Firmware version too small");
|
|
323
493
|
}
|
|
324
494
|
if (attestationReport[REPORT_OFFSETS.committedTcbSnp] <
|
|
325
|
-
requirements.
|
|
495
|
+
requirements.minCommittedTcbSnp) {
|
|
326
496
|
throw new Error("Verification failed: SNP patch level too small");
|
|
327
497
|
}
|
|
498
|
+
yield this._verifyTcbVersion(attestationReport, vcekTcb);
|
|
328
499
|
});
|
|
329
500
|
}
|
|
330
501
|
_verifyVcekChain(vcek) {
|
|
@@ -340,39 +511,54 @@ export class KeySettlement {
|
|
|
340
511
|
if (chain.length != 3) {
|
|
341
512
|
throw new Error(`Certificate chain verification failed\n\nChain: ${chain}`);
|
|
342
513
|
}
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
514
|
+
const find_extension = (oid, type) => {
|
|
515
|
+
const ext = chain[0].extensions.find((ext) => ext.type == oid);
|
|
516
|
+
if (!ext) {
|
|
517
|
+
throw new Error(`Could not find ${type}\n\nExtensions: ${chain[0].extensions}`);
|
|
518
|
+
}
|
|
519
|
+
return new Uint8Array(ext.value);
|
|
520
|
+
};
|
|
521
|
+
const productIA5String = find_extension("1.3.6.1.4.1.3704.1.2", "product name");
|
|
522
|
+
if (productIA5String[0] !== 0x16) {
|
|
523
|
+
throw new Error(`Unexpected product extension: ${productIA5String}`);
|
|
346
524
|
}
|
|
347
|
-
const product = String.fromCharCode(...
|
|
348
|
-
if (!(
|
|
525
|
+
const product = String.fromCharCode(...productIA5String.slice(2)).split("-", 1)[0];
|
|
526
|
+
if (!Object.prototype.hasOwnProperty.call(this._requirements, product)) {
|
|
349
527
|
throw new Error(`Unexpected product: ${product}`);
|
|
350
528
|
}
|
|
351
529
|
const subjectPublicKeyInfo = AsnConvert.parse(chain[0].publicKey.rawData, SubjectPublicKeyInfo);
|
|
530
|
+
const bootloader = find_extension("1.3.6.1.4.1.3704.1.3.1", "bootloader version")[2];
|
|
531
|
+
const tee = find_extension("1.3.6.1.4.1.3704.1.3.2", "TEE")[2];
|
|
532
|
+
const snp = find_extension("1.3.6.1.4.1.3704.1.3.3", "SNP version")[2];
|
|
533
|
+
const microcode = find_extension("1.3.6.1.4.1.3704.1.3.8", "microcode")[2];
|
|
534
|
+
const tcb = {
|
|
535
|
+
bootloader: bootloader,
|
|
536
|
+
tee: tee,
|
|
537
|
+
snp: snp,
|
|
538
|
+
microcode: microcode,
|
|
539
|
+
fmc: 0,
|
|
540
|
+
};
|
|
541
|
+
const fmc = chain[0].extensions.find((ext) => ext.type == "1.3.6.1.4.1.3704.1.3.9");
|
|
542
|
+
if (fmc) {
|
|
543
|
+
tcb.fmc = new Uint8Array(fmc.value)[2];
|
|
544
|
+
}
|
|
352
545
|
return {
|
|
353
546
|
requirements: this._requirements[product],
|
|
354
547
|
publicKey: new Uint8Array(subjectPublicKeyInfo.subjectPublicKey),
|
|
548
|
+
vcekTcb: tcb,
|
|
355
549
|
};
|
|
356
550
|
});
|
|
357
551
|
}
|
|
358
552
|
}
|
|
359
553
|
export function binaryStringToBytes(binaryString) {
|
|
360
|
-
|
|
361
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
362
|
-
byteArray[i] = binaryString.charCodeAt(i);
|
|
363
|
-
}
|
|
364
|
-
return byteArray;
|
|
554
|
+
return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
|
|
365
555
|
}
|
|
366
556
|
export function bytesToBinaryString(bytes) {
|
|
367
|
-
|
|
368
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
369
|
-
binaryString += String.fromCharCode(bytes[i]);
|
|
370
|
-
}
|
|
371
|
-
return binaryString;
|
|
557
|
+
return String.fromCharCode.apply(null, [...bytes]);
|
|
372
558
|
}
|
|
373
559
|
function _createCipher(publicKey, secretKey, info, nonce) {
|
|
374
560
|
const sharedSecret = x25519.getSharedSecret(secretKey, publicKey);
|
|
375
|
-
const key = hkdf(
|
|
561
|
+
const key = hkdf(createHasher(() => blake2b.create({ dkLen: 64 })), sharedSecret, Uint8Array.from([]), info, 32);
|
|
376
562
|
return chacha20poly1305(key, nonce);
|
|
377
563
|
}
|
|
378
564
|
function _getInfo(ephemeralPublicKey, recipientPublicKey) {
|
|
@@ -393,6 +579,9 @@ const REPORT_OFFSETS = {
|
|
|
393
579
|
platInfo: 64,
|
|
394
580
|
reportData: 80,
|
|
395
581
|
measurement: 144,
|
|
582
|
+
reportedTcb: 384,
|
|
583
|
+
cpuIdFamily: 392,
|
|
584
|
+
chipId: 416,
|
|
396
585
|
committedTcbSnp: 486,
|
|
397
586
|
committedBuild: 492,
|
|
398
587
|
committedMinor: 493,
|
|
@@ -400,3 +589,6 @@ const REPORT_OFFSETS = {
|
|
|
400
589
|
signatureR: 672,
|
|
401
590
|
signatureS: 744,
|
|
402
591
|
};
|
|
592
|
+
const PLATFORM_INFO_BITS = {
|
|
593
|
+
aliasCheckComplete: 5,
|
|
594
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routex-settlement",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Key settlement for the YAXI routex client",
|
|
5
5
|
"homepage": "https://yaxi.tech",
|
|
6
6
|
"author": "YAXI GmbH",
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "tsc --build",
|
|
18
18
|
"clean": "tsc --build --clean",
|
|
19
|
+
"fmt": "prettier --write .",
|
|
19
20
|
"lint": "eslint",
|
|
20
21
|
"test": "tsc --build && NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@noble/ciphers": "^
|
|
24
|
-
"@noble/curves": "^
|
|
25
|
-
"@noble/hashes": "^
|
|
24
|
+
"@noble/ciphers": "^2.0.0",
|
|
25
|
+
"@noble/curves": "^2.0.0",
|
|
26
|
+
"@noble/hashes": "^2.0.0",
|
|
26
27
|
"@peculiar/asn1-schema": "^2.3.15",
|
|
27
28
|
"@peculiar/asn1-x509": "^2.3.15",
|
|
28
29
|
"@peculiar/x509": "^1.12.3"
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
"@eslint/js": "^9.21.0",
|
|
32
33
|
"eslint": "^9.21.0",
|
|
33
34
|
"globals": "^16.0.0",
|
|
34
|
-
"jest": "^
|
|
35
|
+
"jest": "^30.1.3",
|
|
36
|
+
"prettier": "^3.6.2",
|
|
35
37
|
"typescript": "^5.7.3",
|
|
36
38
|
"typescript-eslint": "^8.24.1"
|
|
37
39
|
}
|