routex-settlement 0.1.3 → 0.1.5
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 +9 -9
- package/index.js +378 -268
- package/package.json +7 -4
- package/util.d.ts +40 -0
package/index.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ export declare class KeySettlement {
|
|
|
3
3
|
private _onErrorResponse;
|
|
4
4
|
private _onRequestError;
|
|
5
5
|
private _secretKey;
|
|
6
|
-
private
|
|
7
|
-
private
|
|
6
|
+
private _publicKey;
|
|
7
|
+
private _requirements?;
|
|
8
|
+
private _yaxiSigningKeys?;
|
|
8
9
|
private _serverKey?;
|
|
9
10
|
private _settlementPromise?;
|
|
10
11
|
private _measurement?;
|
|
@@ -13,12 +14,11 @@ export declare class KeySettlement {
|
|
|
13
14
|
measurement(): Uint8Array | undefined;
|
|
14
15
|
private _getServerKey;
|
|
15
16
|
getBase64SessionId(settlementHeaders?: HeadersInit): Promise<string>;
|
|
16
|
-
seal(plaintext: Uint8Array, settlementHeaders?: HeadersInit): Promise<Uint8Array
|
|
17
|
-
unseal(ciphertext: Uint8Array): Uint8Array
|
|
18
|
-
private _verifyAttestation;
|
|
19
|
-
private _verifyTcbVersion;
|
|
20
|
-
private _verifyReport;
|
|
21
|
-
private _verifyVcekChain;
|
|
17
|
+
seal(plaintext: Uint8Array, settlementHeaders?: HeadersInit): Promise<Uint8Array<ArrayBuffer>>;
|
|
18
|
+
unseal(ciphertext: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
22
19
|
}
|
|
23
|
-
export declare function binaryStringToBytes(binaryString: string): Uint8Array
|
|
20
|
+
export declare function binaryStringToBytes(binaryString: string): Uint8Array;
|
|
24
21
|
export declare function bytesToBinaryString(bytes: Uint8Array): string;
|
|
22
|
+
export declare const _testing: {
|
|
23
|
+
[Key: string]: unknown;
|
|
24
|
+
};
|
package/index.js
CHANGED
|
@@ -1,37 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
|
|
2
|
+
import { blake2b } from '@noble/hashes/blake2.js';
|
|
3
|
+
import { hkdf } from '@noble/hashes/hkdf.js';
|
|
4
|
+
import { createHasher } from '@noble/hashes/utils.js';
|
|
5
|
+
import { equalBytes } from '@noble/curves/utils.js';
|
|
6
|
+
import { x25519, ed25519 } from '@noble/curves/ed25519.js';
|
|
7
|
+
import { p384 } from '@noble/curves/nist.js';
|
|
8
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
9
|
+
import { AsnConvert } from '@peculiar/asn1-schema';
|
|
10
|
+
import { SubjectPublicKeyInfo } from '@peculiar/asn1-x509';
|
|
11
|
+
import * as x509 from '@peculiar/x509';
|
|
12
|
+
|
|
13
|
+
/******************************************************************************
|
|
14
|
+
Copyright (c) Microsoft Corporation.
|
|
15
|
+
|
|
16
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
17
|
+
purpose with or without fee is hereby granted.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
20
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
21
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
22
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
23
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
24
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
25
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
26
|
+
***************************************************************************** */
|
|
27
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
31
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
32
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
33
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
34
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
35
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
36
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
41
|
+
var e = new Error(message);
|
|
42
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
9
43
|
};
|
|
10
|
-
|
|
11
|
-
import { bytesToUtf8 } from "@noble/ciphers/utils";
|
|
12
|
-
import { equalBytes } from "@noble/curves/utils";
|
|
13
|
-
import { ed25519, x25519 } from "@noble/curves/ed25519";
|
|
14
|
-
import { blake2b } from "@noble/hashes/blake2";
|
|
15
|
-
import { p384 } from "@noble/curves/nist";
|
|
16
|
-
import { hkdf } from "@noble/hashes/hkdf";
|
|
17
|
-
import { createHasher } from "@noble/hashes/utils";
|
|
18
|
-
import { sha256 } from "@noble/hashes/sha2";
|
|
19
|
-
import * as x509 from "@peculiar/x509";
|
|
20
|
-
import { AsnConvert } from "@peculiar/asn1-schema";
|
|
21
|
-
import { SubjectPublicKeyInfo } from "@peculiar/asn1-x509";
|
|
44
|
+
|
|
22
45
|
const REQUIREMENTS = {
|
|
23
46
|
Genoa: {
|
|
24
|
-
minCommittedVersion: [0x1, 0x37,
|
|
25
|
-
minCommittedTcbSnp:
|
|
47
|
+
minCommittedVersion: [0x1, 0x37, 0x31],
|
|
48
|
+
minCommittedTcbSnp: 0x1b,
|
|
26
49
|
},
|
|
27
50
|
Milan: {
|
|
28
|
-
minCommittedVersion: [0x1, 0x37,
|
|
29
|
-
minCommittedTcbSnp:
|
|
51
|
+
minCommittedVersion: [0x1, 0x37, 0x23],
|
|
52
|
+
minCommittedTcbSnp: 0x1b,
|
|
30
53
|
},
|
|
31
|
-
// for Turin: SB-3019 doesn't list a TCB[SNP] version in the mitigation, only firmware version
|
|
32
54
|
Turin: {
|
|
33
|
-
minCommittedVersion: [0x1, 0x37,
|
|
34
|
-
minCommittedTcbSnp:
|
|
55
|
+
minCommittedVersion: [0x1, 0x37, 0x41],
|
|
56
|
+
minCommittedTcbSnp: 0x04,
|
|
35
57
|
},
|
|
36
58
|
};
|
|
37
59
|
const YAXI_SIGNING_KEYS = {
|
|
@@ -288,17 +310,332 @@ zpacMwFusA==
|
|
|
288
310
|
`),
|
|
289
311
|
],
|
|
290
312
|
});
|
|
291
|
-
|
|
313
|
+
function generateKey() {
|
|
314
|
+
const secret = x25519.utils.randomSecretKey();
|
|
315
|
+
return { secret, public: x25519.getPublicKey(secret) };
|
|
316
|
+
}
|
|
317
|
+
function seal(serverPublicKey, plaintext) {
|
|
318
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
319
|
+
const tagLength = 16;
|
|
320
|
+
const ephemeralSecretKey = x25519.utils.randomSecretKey();
|
|
321
|
+
const ephemeralPublicKey = x25519.getPublicKey(ephemeralSecretKey);
|
|
322
|
+
const nonce = _getSealNonce(ephemeralPublicKey, serverPublicKey);
|
|
323
|
+
const info = _getInfo(ephemeralPublicKey, serverPublicKey);
|
|
324
|
+
const cipher = _createCipher(serverPublicKey, ephemeralSecretKey, info, nonce);
|
|
325
|
+
const ciphertext = new Uint8Array(ephemeralPublicKey.length + tagLength + plaintext.length);
|
|
326
|
+
ciphertext.set(ephemeralPublicKey);
|
|
327
|
+
ciphertext.set(cipher.encrypt(plaintext), ephemeralPublicKey.length);
|
|
328
|
+
return ciphertext;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
function unseal(clientKey, ciphertext) {
|
|
332
|
+
const ephemeralPublicKey = ciphertext.subarray(0, 32);
|
|
333
|
+
const publicKey = x25519.getPublicKey(clientKey.secret);
|
|
334
|
+
const nonce = _getSealNonce(ephemeralPublicKey, publicKey);
|
|
335
|
+
const info = _getInfo(ephemeralPublicKey, publicKey);
|
|
336
|
+
const cipher = _createCipher(ephemeralPublicKey, clientKey.secret, info, nonce);
|
|
337
|
+
return cipher.decrypt(ciphertext.subarray(32), new Uint8Array(ciphertext.length - 32 - chacha20poly1305.tagLength));
|
|
338
|
+
}
|
|
339
|
+
function verifyAttestation(response, requirements, signingKeys) {
|
|
340
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
341
|
+
const attestationReport = binaryStringToBytes$1(atob(response.attestationReport));
|
|
342
|
+
yield _verifyReport(attestationReport, response.vcek, requirements !== null && requirements !== void 0 ? requirements : REQUIREMENTS);
|
|
343
|
+
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.reportData, REPORT_OFFSETS.reportData + 32), sha256(binaryStringToBytes$1(atob(response.chachaBox))))) {
|
|
344
|
+
throw new Error("Data in attestation report doesn't match chacha box");
|
|
345
|
+
}
|
|
346
|
+
const encoder = new TextEncoder();
|
|
347
|
+
const launchMeasurementInSystemVersion = binaryStringToBytes$1(atob(response.systemVersion.launchMeasurement));
|
|
348
|
+
const input = new Uint8Array([
|
|
349
|
+
...encoder.encode(response.systemVersion.kind),
|
|
350
|
+
...encoder.encode(response.systemVersion.generation.toString()),
|
|
351
|
+
...encoder.encode(
|
|
352
|
+
// clerk-report used to use Z for serialization, but not for the signature input
|
|
353
|
+
response.systemVersion.createdAt.replace("Z", "+00:00")),
|
|
354
|
+
...encoder.encode(response.systemVersion.ref),
|
|
355
|
+
...launchMeasurementInSystemVersion,
|
|
356
|
+
]);
|
|
357
|
+
const key = (signingKeys !== null && signingKeys !== void 0 ? signingKeys : YAXI_SIGNING_KEYS)[response.systemVersion.signature.keyId];
|
|
358
|
+
if (key == null) {
|
|
359
|
+
throw new Error("The system version's signature key is unknown");
|
|
360
|
+
}
|
|
361
|
+
if (!ed25519.verify(binaryStringToBytes$1(atob(response.systemVersion.signature.value)), input, key)) {
|
|
362
|
+
throw new Error("Verification failed: Invalid system version signature");
|
|
363
|
+
}
|
|
364
|
+
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.measurement, REPORT_OFFSETS.measurement + 48), launchMeasurementInSystemVersion)) {
|
|
365
|
+
throw new Error("Reported measurement doesn't match expected measurement");
|
|
366
|
+
}
|
|
367
|
+
return launchMeasurementInSystemVersion;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function _verifyTcbVersion(attestationReport, vcekTcb) {
|
|
371
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
372
|
+
const versionBytes = attestationReport.slice(0, 4);
|
|
373
|
+
const version = new DataView(versionBytes.buffer).getInt32(0, true);
|
|
374
|
+
let turinLike;
|
|
375
|
+
switch (version) {
|
|
376
|
+
case 0:
|
|
377
|
+
case 1:
|
|
378
|
+
throw new Error("Unsupported Attestation Report Version");
|
|
379
|
+
case 2: {
|
|
380
|
+
const chipId = attestationReport.slice(REPORT_OFFSETS.chipId, 64);
|
|
381
|
+
if (equalBytes(chipId, new Uint8Array(64))) {
|
|
382
|
+
throw new Error("Could not derive CPU family: MASK_CHIP_ID enabled");
|
|
383
|
+
}
|
|
384
|
+
turinLike = equalBytes(chipId.slice(8), new Uint8Array(56));
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
default: // from version 3 onwards CPUID_FAM_ID field should exist
|
|
388
|
+
turinLike = attestationReport[REPORT_OFFSETS.cpuIdFamily] == 0x1a;
|
|
389
|
+
}
|
|
390
|
+
const reportedTcb = attestationReport.slice(REPORT_OFFSETS.reportedTcb, REPORT_OFFSETS.reportedTcb + 8);
|
|
391
|
+
let tcb;
|
|
392
|
+
if (turinLike) {
|
|
393
|
+
tcb = {
|
|
394
|
+
microcode: reportedTcb[7],
|
|
395
|
+
snp: reportedTcb[3],
|
|
396
|
+
tee: reportedTcb[2],
|
|
397
|
+
bootloader: reportedTcb[1],
|
|
398
|
+
fmc: reportedTcb[0],
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
tcb = {
|
|
403
|
+
microcode: reportedTcb[7],
|
|
404
|
+
snp: reportedTcb[6],
|
|
405
|
+
tee: reportedTcb[1],
|
|
406
|
+
bootloader: reportedTcb[0],
|
|
407
|
+
fmc: 0,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (!(tcb.microcode === vcekTcb.microcode &&
|
|
411
|
+
tcb.snp === vcekTcb.snp &&
|
|
412
|
+
tcb.tee === vcekTcb.tee &&
|
|
413
|
+
tcb.bootloader === vcekTcb.bootloader &&
|
|
414
|
+
tcb.fmc === vcekTcb.fmc)) {
|
|
415
|
+
throw new Error("Verification failed: REPORTED_TCB doesn't match with VCEK's TCB_VERSION");
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function _verifyReport(attestationReport, vcekChain, requirementsMap) {
|
|
420
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
421
|
+
if (attestationReport.length != 1184) {
|
|
422
|
+
throw new Error(`Attestation report has unexpected length: ${attestationReport.length}`);
|
|
423
|
+
}
|
|
424
|
+
const { product, publicKey, vcekTcb } = yield _verifyVcekChain(vcekChain, requirementsMap);
|
|
425
|
+
const requirements = requirementsMap[product];
|
|
426
|
+
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.signatureR + 48, REPORT_OFFSETS.signatureR + 72), new Uint8Array(24)) ||
|
|
427
|
+
!equalBytes(attestationReport.slice(REPORT_OFFSETS.signatureS + 48, REPORT_OFFSETS.signatureS + 72), new Uint8Array(24))) {
|
|
428
|
+
throw new Error("Unexpected signature bits");
|
|
429
|
+
}
|
|
430
|
+
const sig = new Uint8Array(96);
|
|
431
|
+
sig.set(attestationReport
|
|
432
|
+
.slice(REPORT_OFFSETS.signatureR, REPORT_OFFSETS.signatureR + 48)
|
|
433
|
+
// Change endianness
|
|
434
|
+
.reverse(), 0);
|
|
435
|
+
sig.set(attestationReport
|
|
436
|
+
.slice(REPORT_OFFSETS.signatureS, REPORT_OFFSETS.signatureS + 48)
|
|
437
|
+
// Change endianness
|
|
438
|
+
.reverse(), 48);
|
|
439
|
+
let valid;
|
|
440
|
+
try {
|
|
441
|
+
valid = p384.verify(sig, attestationReport.slice(0, 672), new Uint8Array(publicKey), { lowS: false });
|
|
442
|
+
}
|
|
443
|
+
catch (_a) {
|
|
444
|
+
valid = false;
|
|
445
|
+
}
|
|
446
|
+
if (!valid) {
|
|
447
|
+
throw new Error("Verification failed: Invalid attestation report signature");
|
|
448
|
+
}
|
|
449
|
+
if ((attestationReport[REPORT_OFFSETS.platInfo] &
|
|
450
|
+
(1 << PLATFORM_INFO_BITS.aliasCheckComplete)) ===
|
|
451
|
+
0) {
|
|
452
|
+
throw new Error("Verification failed: Alias check complete is false");
|
|
453
|
+
}
|
|
454
|
+
if (attestationReport[REPORT_OFFSETS.committedMajor] <
|
|
455
|
+
requirements.minCommittedVersion[0] ||
|
|
456
|
+
attestationReport[REPORT_OFFSETS.committedMinor] <
|
|
457
|
+
requirements.minCommittedVersion[1] ||
|
|
458
|
+
attestationReport[REPORT_OFFSETS.committedBuild] <
|
|
459
|
+
requirements.minCommittedVersion[2]) {
|
|
460
|
+
throw new Error("Verification failed: Firmware version too small");
|
|
461
|
+
}
|
|
462
|
+
if (attestationReport[REPORT_OFFSETS.committedTcbSnp] <
|
|
463
|
+
requirements.minCommittedTcbSnp) {
|
|
464
|
+
throw new Error("Verification failed: SNP patch level too small");
|
|
465
|
+
}
|
|
466
|
+
const cpuModel = attestationReport[REPORT_OFFSETS.cpuIdModel];
|
|
467
|
+
const cpuStep = attestationReport[REPORT_OFFSETS.cpuIdStep];
|
|
468
|
+
let requiredMicrocode;
|
|
469
|
+
switch (product) {
|
|
470
|
+
case "Milan": {
|
|
471
|
+
if (cpuModel === 1 && cpuStep === 1) {
|
|
472
|
+
// Milan
|
|
473
|
+
requiredMicrocode = 0xde;
|
|
474
|
+
}
|
|
475
|
+
else if (cpuModel === 1 && cpuStep === 2) {
|
|
476
|
+
// Milan-X
|
|
477
|
+
requiredMicrocode = 0x45;
|
|
478
|
+
}
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
case "Genoa": {
|
|
482
|
+
if (cpuModel === 0x11) {
|
|
483
|
+
if (cpuStep === 1) {
|
|
484
|
+
// Genoa
|
|
485
|
+
requiredMicrocode = 0x56;
|
|
486
|
+
}
|
|
487
|
+
else if (cpuStep === 2) {
|
|
488
|
+
// Genoa-X
|
|
489
|
+
requiredMicrocode = 0x51;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else if (cpuModel === 0xa0 && cpuStep === 2) {
|
|
493
|
+
// Bergamo/Siena
|
|
494
|
+
requiredMicrocode = 0x1b;
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case "Turin":
|
|
499
|
+
if (cpuModel === 2 && cpuStep === 1) {
|
|
500
|
+
// Turin Classic
|
|
501
|
+
requiredMicrocode = 0x50;
|
|
502
|
+
}
|
|
503
|
+
else if (cpuModel === 0x11 && cpuStep === 0) {
|
|
504
|
+
// Turin Dense
|
|
505
|
+
requiredMicrocode = 0x4d;
|
|
506
|
+
}
|
|
507
|
+
else if (cpuModel > 2 ||
|
|
508
|
+
(cpuModel === 2 && cpuStep > 1) ||
|
|
509
|
+
(cpuModel === 0x11 && cpuStep > 0)) {
|
|
510
|
+
// Accept any newer models/steppings for Turin
|
|
511
|
+
requiredMicrocode = 0;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (requiredMicrocode === undefined) {
|
|
515
|
+
throw new Error("Verification failed: Report doesn't match any known CPU family");
|
|
516
|
+
}
|
|
517
|
+
const reportedMicrocode = attestationReport.slice(REPORT_OFFSETS.reportedTcb, REPORT_OFFSETS.reportedTcb + 8)[7];
|
|
518
|
+
if (reportedMicrocode < requiredMicrocode) {
|
|
519
|
+
throw new Error("Verification failed: Reported microcode version too small");
|
|
520
|
+
}
|
|
521
|
+
yield _verifyTcbVersion(attestationReport, vcekTcb);
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function _verifyVcekChain(vcek, requirements) {
|
|
525
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
526
|
+
let cert;
|
|
527
|
+
try {
|
|
528
|
+
cert = new x509.X509Certificate(vcek);
|
|
529
|
+
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
throw new Error(`Invalid VCEK: ${e}\n\nVCEK: ${vcek}`);
|
|
532
|
+
}
|
|
533
|
+
const chain = yield ROOT_STORE.build(cert);
|
|
534
|
+
if (chain.length != 3) {
|
|
535
|
+
throw new Error(`Certificate chain verification failed\n\nChain: ${chain}`);
|
|
536
|
+
}
|
|
537
|
+
const find_extension = (oid, type) => {
|
|
538
|
+
const ext = chain[0].extensions.find((ext) => ext.type == oid);
|
|
539
|
+
if (!ext) {
|
|
540
|
+
throw new Error(`Could not find ${type}\n\nExtensions: ${chain[0].extensions}`);
|
|
541
|
+
}
|
|
542
|
+
return new Uint8Array(ext.value);
|
|
543
|
+
};
|
|
544
|
+
const productIA5String = find_extension("1.3.6.1.4.1.3704.1.2", "product name");
|
|
545
|
+
if (productIA5String[0] !== 0x16) {
|
|
546
|
+
throw new Error(`Unexpected product extension: ${productIA5String}`);
|
|
547
|
+
}
|
|
548
|
+
const product = String.fromCharCode(...productIA5String.slice(2)).split("-", 1)[0];
|
|
549
|
+
if (!Object.prototype.hasOwnProperty.call(requirements, product)) {
|
|
550
|
+
throw new Error(`Unexpected product: ${product}`);
|
|
551
|
+
}
|
|
552
|
+
const subjectPublicKeyInfo = AsnConvert.parse(chain[0].publicKey.rawData, SubjectPublicKeyInfo);
|
|
553
|
+
const bootloader = find_extension("1.3.6.1.4.1.3704.1.3.1", "bootloader version")[2];
|
|
554
|
+
const tee = find_extension("1.3.6.1.4.1.3704.1.3.2", "TEE")[2];
|
|
555
|
+
const snp = find_extension("1.3.6.1.4.1.3704.1.3.3", "SNP version")[2];
|
|
556
|
+
const microcode = find_extension("1.3.6.1.4.1.3704.1.3.8", "microcode")[2];
|
|
557
|
+
const tcb = {
|
|
558
|
+
bootloader: bootloader,
|
|
559
|
+
tee: tee,
|
|
560
|
+
snp: snp,
|
|
561
|
+
microcode: microcode,
|
|
562
|
+
fmc: 0,
|
|
563
|
+
};
|
|
564
|
+
const fmc = chain[0].extensions.find((ext) => ext.type == "1.3.6.1.4.1.3704.1.3.9");
|
|
565
|
+
if (fmc) {
|
|
566
|
+
tcb.fmc = new Uint8Array(fmc.value)[2];
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
product: product,
|
|
570
|
+
publicKey: new Uint8Array(subjectPublicKeyInfo.subjectPublicKey),
|
|
571
|
+
vcekTcb: tcb,
|
|
572
|
+
};
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
function binaryStringToBytes$1(binaryString) {
|
|
576
|
+
return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
|
|
577
|
+
}
|
|
578
|
+
function _createCipher(publicKey, secretKey, info, nonce) {
|
|
579
|
+
const sharedSecret = x25519.getSharedSecret(secretKey, publicKey);
|
|
580
|
+
const key = hkdf(createHasher(() => blake2b.create({ dkLen: 64 })), sharedSecret, Uint8Array.from([]), info, 32);
|
|
581
|
+
return chacha20poly1305(key, nonce);
|
|
582
|
+
}
|
|
583
|
+
function _getInfo(ephemeralPublicKey, recipientPublicKey) {
|
|
584
|
+
const info = new Uint8Array(ephemeralPublicKey.length + recipientPublicKey.length);
|
|
585
|
+
info.set(ephemeralPublicKey);
|
|
586
|
+
info.set(recipientPublicKey, ephemeralPublicKey.length);
|
|
587
|
+
return info;
|
|
588
|
+
}
|
|
589
|
+
function _getSealNonce(ephemeralPublicKey, recipientPublicKey) {
|
|
590
|
+
return blake2b
|
|
591
|
+
.create({ dkLen: 12 })
|
|
592
|
+
.update(ephemeralPublicKey)
|
|
593
|
+
.update(recipientPublicKey)
|
|
594
|
+
.digest();
|
|
595
|
+
}
|
|
596
|
+
const REPORT_OFFSETS = {
|
|
597
|
+
platInfo: 64,
|
|
598
|
+
reportData: 80,
|
|
599
|
+
measurement: 144,
|
|
600
|
+
reportedTcb: 384,
|
|
601
|
+
cpuIdFamily: 392,
|
|
602
|
+
cpuIdModel: 393,
|
|
603
|
+
cpuIdStep: 394,
|
|
604
|
+
chipId: 416,
|
|
605
|
+
committedTcbSnp: 486,
|
|
606
|
+
committedBuild: 492,
|
|
607
|
+
committedMinor: 493,
|
|
608
|
+
committedMajor: 494,
|
|
609
|
+
signatureR: 672,
|
|
610
|
+
signatureS: 744,
|
|
611
|
+
};
|
|
612
|
+
const PLATFORM_INFO_BITS = {
|
|
613
|
+
aliasCheckComplete: 5,
|
|
614
|
+
};
|
|
615
|
+
function bytesToUtf8(bytes) {
|
|
616
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
617
|
+
return new TextDecoder().decode(bytes);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
const _testing$1 = {};
|
|
621
|
+
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
|
|
622
|
+
_testing$1.REQUIREMENTS = REQUIREMENTS;
|
|
623
|
+
_testing$1.YAXI_SIGNING_KEYS = YAXI_SIGNING_KEYS;
|
|
624
|
+
_testing$1.verifyReport = _verifyReport;
|
|
625
|
+
_testing$1.verifyTcbVersion = _verifyTcbVersion;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
class KeySettlement {
|
|
292
629
|
constructor(url, onErrorResponse, onRequestError) {
|
|
293
|
-
this._requirements = REQUIREMENTS;
|
|
294
|
-
this._yaxiSigningKeys = YAXI_SIGNING_KEYS;
|
|
295
630
|
this._url = url;
|
|
296
631
|
this._onErrorResponse = onErrorResponse;
|
|
297
632
|
this._onRequestError =
|
|
298
633
|
onRequestError !== null && onRequestError !== void 0 ? onRequestError : ((e) => {
|
|
299
634
|
throw e;
|
|
300
635
|
});
|
|
301
|
-
|
|
636
|
+
const { secret, public: pub } = generateKey();
|
|
637
|
+
this._secretKey = secret;
|
|
638
|
+
this._publicKey = pub;
|
|
302
639
|
}
|
|
303
640
|
_settle(headers) {
|
|
304
641
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -306,7 +643,7 @@ export class KeySettlement {
|
|
|
306
643
|
method: "POST",
|
|
307
644
|
headers: headers,
|
|
308
645
|
body: JSON.stringify({
|
|
309
|
-
publicKey: btoa(bytesToBinaryString(
|
|
646
|
+
publicKey: btoa(bytesToBinaryString(this._publicKey)),
|
|
310
647
|
}),
|
|
311
648
|
})
|
|
312
649
|
.catch(this._onRequestError)
|
|
@@ -317,8 +654,8 @@ export class KeySettlement {
|
|
|
317
654
|
throw yield this._onErrorResponse(response);
|
|
318
655
|
}
|
|
319
656
|
const responseData = yield response.json();
|
|
320
|
-
const measurement = yield this.
|
|
321
|
-
const settlementBoxMessage = JSON.parse(bytesToUtf8(this.unseal(binaryStringToBytes(atob(responseData.chachaBox)))));
|
|
657
|
+
const measurement = yield verifyAttestation(responseData, this._requirements, this._yaxiSigningKeys);
|
|
658
|
+
const settlementBoxMessage = JSON.parse(yield bytesToUtf8(this.unseal(binaryStringToBytes(atob(responseData.chachaBox)))));
|
|
322
659
|
this._serverKey = {
|
|
323
660
|
publicKey: binaryStringToBytes(atob(settlementBoxMessage.publicKey)),
|
|
324
661
|
base64SessionId: settlementBoxMessage.sessionId,
|
|
@@ -349,246 +686,19 @@ export class KeySettlement {
|
|
|
349
686
|
return __awaiter(this, arguments, void 0, function* (plaintext, settlementHeaders = {}) {
|
|
350
687
|
const serverPublicKey = (yield this._getServerKey(settlementHeaders))
|
|
351
688
|
.publicKey;
|
|
352
|
-
|
|
353
|
-
const ephemeralSecretKey = x25519.utils.randomSecretKey();
|
|
354
|
-
const ephemeralPublicKey = x25519.getPublicKey(ephemeralSecretKey);
|
|
355
|
-
const nonce = _getSealNonce(ephemeralPublicKey, serverPublicKey);
|
|
356
|
-
const info = _getInfo(ephemeralPublicKey, serverPublicKey);
|
|
357
|
-
const cipher = _createCipher(serverPublicKey, ephemeralSecretKey, info, nonce);
|
|
358
|
-
const ciphertext = new Uint8Array(ephemeralPublicKey.length + tagLength + plaintext.length);
|
|
359
|
-
ciphertext.set(ephemeralPublicKey);
|
|
360
|
-
ciphertext.set(cipher.encrypt(plaintext), ephemeralPublicKey.length);
|
|
361
|
-
return ciphertext;
|
|
689
|
+
return yield seal(serverPublicKey, plaintext);
|
|
362
690
|
});
|
|
363
691
|
}
|
|
364
692
|
unseal(ciphertext) {
|
|
365
|
-
|
|
366
|
-
const publicKey = x25519.getPublicKey(this._secretKey);
|
|
367
|
-
const nonce = _getSealNonce(ephemeralPublicKey, publicKey);
|
|
368
|
-
const info = _getInfo(ephemeralPublicKey, publicKey);
|
|
369
|
-
const cipher = _createCipher(ephemeralPublicKey, this._secretKey, info, nonce);
|
|
370
|
-
return cipher.decrypt(ciphertext.subarray(32));
|
|
371
|
-
}
|
|
372
|
-
_verifyAttestation(response) {
|
|
373
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
374
|
-
const attestationReport = binaryStringToBytes(atob(response.attestationReport));
|
|
375
|
-
yield this._verifyReport(attestationReport, response.vcek);
|
|
376
|
-
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.reportData, REPORT_OFFSETS.reportData + 32), sha256(binaryStringToBytes(atob(response.chachaBox))))) {
|
|
377
|
-
throw new Error("Data in attestation report doesn't match chacha box");
|
|
378
|
-
}
|
|
379
|
-
const encoder = new TextEncoder();
|
|
380
|
-
const launchMeasurementInSystemVersion = binaryStringToBytes(atob(response.systemVersion.launchMeasurement));
|
|
381
|
-
const input = new Uint8Array([
|
|
382
|
-
...encoder.encode(response.systemVersion.kind),
|
|
383
|
-
...encoder.encode(response.systemVersion.generation.toString()),
|
|
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")),
|
|
387
|
-
...encoder.encode(response.systemVersion.ref),
|
|
388
|
-
...launchMeasurementInSystemVersion,
|
|
389
|
-
]);
|
|
390
|
-
const key = this._yaxiSigningKeys[response.systemVersion.signature.keyId];
|
|
391
|
-
if (key == null) {
|
|
392
|
-
throw new Error("The system version's signature key is unknown");
|
|
393
|
-
}
|
|
394
|
-
if (!ed25519.verify(binaryStringToBytes(atob(response.systemVersion.signature.value)), input, key)) {
|
|
395
|
-
throw new Error("Verification failed: Invalid system version signature");
|
|
396
|
-
}
|
|
397
|
-
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.measurement, REPORT_OFFSETS.measurement + 48), launchMeasurementInSystemVersion)) {
|
|
398
|
-
throw new Error("Reported measurement doesn't match expected measurement");
|
|
399
|
-
}
|
|
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
|
-
}
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
_verifyReport(attestationReport, vcekChain) {
|
|
453
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
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) {
|
|
479
|
-
throw new Error("Verification failed: Invalid attestation report signature");
|
|
480
|
-
}
|
|
481
|
-
if ((attestationReport[REPORT_OFFSETS.platInfo] &
|
|
482
|
-
(1 << PLATFORM_INFO_BITS.aliasCheckComplete)) ===
|
|
483
|
-
0) {
|
|
484
|
-
throw new Error("Verification failed: Alias check complete is false");
|
|
485
|
-
}
|
|
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]) {
|
|
492
|
-
throw new Error("Verification failed: Firmware version too small");
|
|
493
|
-
}
|
|
494
|
-
if (attestationReport[REPORT_OFFSETS.committedTcbSnp] <
|
|
495
|
-
requirements.minCommittedTcbSnp) {
|
|
496
|
-
throw new Error("Verification failed: SNP patch level too small");
|
|
497
|
-
}
|
|
498
|
-
yield this._verifyTcbVersion(attestationReport, vcekTcb);
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
_verifyVcekChain(vcek) {
|
|
502
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
503
|
-
let cert;
|
|
504
|
-
try {
|
|
505
|
-
cert = new x509.X509Certificate(vcek);
|
|
506
|
-
}
|
|
507
|
-
catch (e) {
|
|
508
|
-
throw new Error(`Invalid VCEK: ${e}\n\nVCEK: ${vcek}`);
|
|
509
|
-
}
|
|
510
|
-
const chain = yield ROOT_STORE.build(cert);
|
|
511
|
-
if (chain.length != 3) {
|
|
512
|
-
throw new Error(`Certificate chain verification failed\n\nChain: ${chain}`);
|
|
513
|
-
}
|
|
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}`);
|
|
524
|
-
}
|
|
525
|
-
const product = String.fromCharCode(...productIA5String.slice(2)).split("-", 1)[0];
|
|
526
|
-
if (!Object.prototype.hasOwnProperty.call(this._requirements, product)) {
|
|
527
|
-
throw new Error(`Unexpected product: ${product}`);
|
|
528
|
-
}
|
|
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
|
-
}
|
|
545
|
-
return {
|
|
546
|
-
requirements: this._requirements[product],
|
|
547
|
-
publicKey: new Uint8Array(subjectPublicKeyInfo.subjectPublicKey),
|
|
548
|
-
vcekTcb: tcb,
|
|
549
|
-
};
|
|
550
|
-
});
|
|
693
|
+
return unseal({ secret: this._secretKey, public: this._publicKey }, ciphertext);
|
|
551
694
|
}
|
|
552
695
|
}
|
|
553
|
-
|
|
696
|
+
function binaryStringToBytes(binaryString) {
|
|
554
697
|
return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
|
|
555
698
|
}
|
|
556
|
-
|
|
699
|
+
function bytesToBinaryString(bytes) {
|
|
557
700
|
return String.fromCharCode.apply(null, [...bytes]);
|
|
558
701
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
return chacha20poly1305(key, nonce);
|
|
563
|
-
}
|
|
564
|
-
function _getInfo(ephemeralPublicKey, recipientPublicKey) {
|
|
565
|
-
const info = new Uint8Array(ephemeralPublicKey.length + recipientPublicKey.length);
|
|
566
|
-
info.set(ephemeralPublicKey);
|
|
567
|
-
info.set(recipientPublicKey, ephemeralPublicKey.length);
|
|
568
|
-
return info;
|
|
569
|
-
}
|
|
570
|
-
function _getSealNonce(ephemeralPublicKey, recipientPublicKey) {
|
|
571
|
-
return blake2b
|
|
572
|
-
.create({ dkLen: 12 })
|
|
573
|
-
.update(ephemeralPublicKey)
|
|
574
|
-
.update(recipientPublicKey)
|
|
575
|
-
.digest();
|
|
576
|
-
}
|
|
577
|
-
const REPORT_OFFSETS = {
|
|
578
|
-
sigAlgo: 52,
|
|
579
|
-
platInfo: 64,
|
|
580
|
-
reportData: 80,
|
|
581
|
-
measurement: 144,
|
|
582
|
-
reportedTcb: 384,
|
|
583
|
-
cpuIdFamily: 392,
|
|
584
|
-
chipId: 416,
|
|
585
|
-
committedTcbSnp: 486,
|
|
586
|
-
committedBuild: 492,
|
|
587
|
-
committedMinor: 493,
|
|
588
|
-
committedMajor: 494,
|
|
589
|
-
signatureR: 672,
|
|
590
|
-
signatureS: 744,
|
|
591
|
-
};
|
|
592
|
-
const PLATFORM_INFO_BITS = {
|
|
593
|
-
aliasCheckComplete: 5,
|
|
594
|
-
};
|
|
702
|
+
const _testing = _testing$1;
|
|
703
|
+
|
|
704
|
+
export { KeySettlement, _testing, binaryStringToBytes, bytesToBinaryString };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routex-settlement",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Key settlement for the YAXI routex client",
|
|
5
5
|
"homepage": "https://yaxi.tech",
|
|
6
6
|
"author": "YAXI GmbH",
|
|
@@ -11,14 +11,15 @@
|
|
|
11
11
|
"types": "./index.d.ts",
|
|
12
12
|
"files": [
|
|
13
13
|
"index.d.ts",
|
|
14
|
-
"index.js"
|
|
14
|
+
"index.js",
|
|
15
|
+
"util.d.ts"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|
|
17
|
-
"build": "
|
|
18
|
+
"build": "rollup --input index.ts --dir . --plugin typescript --external @noble/ciphers/chacha.js,@noble/curves/ed25519.js,@noble/curves/nist.js,@noble/curves/utils.js,@noble/hashes/blake2.js,@noble/hashes/hkdf.js,@noble/hashes/sha2.js,@noble/hashes/utils.js,@peculiar/asn1-schema,@peculiar/asn1-x509,@peculiar/x509",
|
|
18
19
|
"clean": "tsc --build --clean",
|
|
19
20
|
"fmt": "prettier --write .",
|
|
20
21
|
"lint": "eslint",
|
|
21
|
-
"test": "
|
|
22
|
+
"test": "npm run build && NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
25
|
"@noble/ciphers": "^2.0.0",
|
|
@@ -30,10 +31,12 @@
|
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@eslint/js": "^9.21.0",
|
|
34
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
33
35
|
"eslint": "^9.21.0",
|
|
34
36
|
"globals": "^16.0.0",
|
|
35
37
|
"jest": "^30.1.3",
|
|
36
38
|
"prettier": "^3.6.2",
|
|
39
|
+
"rollup": "^4.52.2",
|
|
37
40
|
"typescript": "^5.7.3",
|
|
38
41
|
"typescript-eslint": "^8.24.1"
|
|
39
42
|
}
|
package/util.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
declare const YAXI_SIGNING_KEYS: {
|
|
2
|
+
[id: string]: Uint8Array | undefined;
|
|
3
|
+
};
|
|
4
|
+
export declare function generateKey(): {
|
|
5
|
+
secret: Uint8Array;
|
|
6
|
+
public: Uint8Array;
|
|
7
|
+
};
|
|
8
|
+
export declare function seal(serverPublicKey: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
9
|
+
export declare function unseal(clientKey: {
|
|
10
|
+
secret: Uint8Array;
|
|
11
|
+
public: Uint8Array;
|
|
12
|
+
}, ciphertext: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
13
|
+
type SettlementResponse = {
|
|
14
|
+
attestationReport: string;
|
|
15
|
+
vcek: string;
|
|
16
|
+
chachaBox: string;
|
|
17
|
+
systemVersion: {
|
|
18
|
+
kind: string;
|
|
19
|
+
generation: number;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
ref: string;
|
|
22
|
+
launchMeasurement: string;
|
|
23
|
+
signature: {
|
|
24
|
+
keyId: string;
|
|
25
|
+
value: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
type Requirements = {
|
|
30
|
+
[id: string]: {
|
|
31
|
+
minCommittedVersion: [number, number, number];
|
|
32
|
+
minCommittedTcbSnp: number;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export declare function verifyAttestation(response: SettlementResponse, requirements?: Requirements, signingKeys?: typeof YAXI_SIGNING_KEYS): Promise<Uint8Array>;
|
|
36
|
+
export declare function bytesToUtf8(bytes: Uint8Array): Promise<string>;
|
|
37
|
+
export declare const _testing: {
|
|
38
|
+
[Key: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
export {};
|