routex-settlement 0.2.0 → 0.2.1
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.js +105 -71
- package/package.json +4 -3
- package/util.d.ts +1 -0
package/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { sha256 } from '@noble/hashes/sha2.js';
|
|
|
9
9
|
import { AsnConvert } from '@peculiar/asn1-schema';
|
|
10
10
|
import { SubjectPublicKeyInfo } from '@peculiar/asn1-x509';
|
|
11
11
|
import * as x509 from '@peculiar/x509';
|
|
12
|
+
import { fromBER, Integer, IA5String } from 'asn1js';
|
|
12
13
|
|
|
13
14
|
/******************************************************************************
|
|
14
15
|
Copyright (c) Microsoft Corporation.
|
|
@@ -46,14 +47,17 @@ const REQUIREMENTS = {
|
|
|
46
47
|
Genoa: {
|
|
47
48
|
minCommittedVersion: [0x1, 0x37, 0x31],
|
|
48
49
|
minCommittedTcbSnp: 0x1b,
|
|
50
|
+
minMitVector: (1 << 0) | (1 << 1),
|
|
49
51
|
},
|
|
50
52
|
Milan: {
|
|
51
53
|
minCommittedVersion: [0x1, 0x37, 0x23],
|
|
52
54
|
minCommittedTcbSnp: 0x1b,
|
|
55
|
+
minMitVector: 1 << 1,
|
|
53
56
|
},
|
|
54
57
|
Turin: {
|
|
55
58
|
minCommittedVersion: [0x1, 0x37, 0x41],
|
|
56
59
|
minCommittedTcbSnp: 0x04,
|
|
60
|
+
minMitVector: (1 << 0) | (1 << 1) | (1 << 2) | (1 << 4) | (1 << 5),
|
|
57
61
|
},
|
|
58
62
|
};
|
|
59
63
|
const YAXI_SIGNING_KEYS = {
|
|
@@ -366,46 +370,28 @@ function verifyAttestation(response, requirements, signingKeys) {
|
|
|
366
370
|
}
|
|
367
371
|
});
|
|
368
372
|
}
|
|
369
|
-
function
|
|
373
|
+
function parseTcbVersion(tcbBytes, product) {
|
|
374
|
+
if (product === "Turin") {
|
|
375
|
+
return {
|
|
376
|
+
fmc: tcbBytes[0],
|
|
377
|
+
bootloader: tcbBytes[1],
|
|
378
|
+
tee: tcbBytes[2],
|
|
379
|
+
snp: tcbBytes[3],
|
|
380
|
+
microcode: tcbBytes[7],
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
bootloader: tcbBytes[0],
|
|
385
|
+
tee: tcbBytes[1],
|
|
386
|
+
snp: tcbBytes[6],
|
|
387
|
+
microcode: tcbBytes[7],
|
|
388
|
+
fmc: 0,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function _verifyTcbVersion(attestationReport, product, vcekTcb) {
|
|
370
392
|
return __awaiter(this, void 0, void 0, function* () {
|
|
371
|
-
const versionBytes = attestationReport.slice(0, 4);
|
|
372
|
-
const version = new DataView(versionBytes.buffer).getInt32(0, true);
|
|
373
|
-
let turinLike;
|
|
374
|
-
switch (version) {
|
|
375
|
-
case 0:
|
|
376
|
-
case 1:
|
|
377
|
-
throw new Error("Unsupported Attestation Report Version");
|
|
378
|
-
case 2: {
|
|
379
|
-
const chipId = attestationReport.slice(REPORT_OFFSETS.chipId, REPORT_OFFSETS.chipId + 64);
|
|
380
|
-
if (equalBytes(chipId, new Uint8Array(64))) {
|
|
381
|
-
throw new Error("Could not derive CPU family: MASK_CHIP_ID enabled");
|
|
382
|
-
}
|
|
383
|
-
turinLike = equalBytes(chipId.slice(8), new Uint8Array(56));
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
default: // from version 3 onwards CPUID_FAM_ID field should exist
|
|
387
|
-
turinLike = attestationReport[REPORT_OFFSETS.cpuIdFamily] == 0x1a;
|
|
388
|
-
}
|
|
389
393
|
const reportedTcb = attestationReport.slice(REPORT_OFFSETS.reportedTcb, REPORT_OFFSETS.reportedTcb + 8);
|
|
390
|
-
|
|
391
|
-
if (turinLike) {
|
|
392
|
-
tcb = {
|
|
393
|
-
microcode: reportedTcb[7],
|
|
394
|
-
snp: reportedTcb[3],
|
|
395
|
-
tee: reportedTcb[2],
|
|
396
|
-
bootloader: reportedTcb[1],
|
|
397
|
-
fmc: reportedTcb[0],
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
tcb = {
|
|
402
|
-
microcode: reportedTcb[7],
|
|
403
|
-
snp: reportedTcb[6],
|
|
404
|
-
tee: reportedTcb[1],
|
|
405
|
-
bootloader: reportedTcb[0],
|
|
406
|
-
fmc: 0,
|
|
407
|
-
};
|
|
408
|
-
}
|
|
394
|
+
const tcb = parseTcbVersion(reportedTcb, product);
|
|
409
395
|
if (!(tcb.microcode === vcekTcb.microcode &&
|
|
410
396
|
tcb.snp === vcekTcb.snp &&
|
|
411
397
|
tcb.tee === vcekTcb.tee &&
|
|
@@ -422,6 +408,10 @@ function _verifyReport(attestationReport, vcekChain, requirementsMap) {
|
|
|
422
408
|
}
|
|
423
409
|
const { product, publicKey, vcekTcb } = yield _verifyVcekChain(vcekChain, requirementsMap);
|
|
424
410
|
const requirements = requirementsMap[product];
|
|
411
|
+
const sigAlgo = attestationReport[REPORT_OFFSETS.sigAlgo];
|
|
412
|
+
if (sigAlgo !== 0x1) {
|
|
413
|
+
throw new Error(`Verification failed: Unsupported signature algorithm ${sigAlgo}, only ECDSA P-384 with SHA-384 (0x1) is supported`);
|
|
414
|
+
}
|
|
425
415
|
if (!equalBytes(attestationReport.slice(REPORT_OFFSETS.signatureR + 48, REPORT_OFFSETS.signatureR + 72), new Uint8Array(24)) ||
|
|
426
416
|
!equalBytes(attestationReport.slice(REPORT_OFFSETS.signatureS + 48, REPORT_OFFSETS.signatureS + 72), new Uint8Array(24))) {
|
|
427
417
|
throw new Error("Unexpected signature bits");
|
|
@@ -450,18 +440,49 @@ function _verifyReport(attestationReport, vcekChain, requirementsMap) {
|
|
|
450
440
|
0) {
|
|
451
441
|
throw new Error("Verification failed: Alias check complete is false");
|
|
452
442
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
443
|
+
const debugAllowed = attestationReport[REPORT_OFFSETS.policy + Math.floor(GUEST_POLICY_BITS.debugAllowed / 8)] &
|
|
444
|
+
(1 << GUEST_POLICY_BITS.debugAllowed % 8);
|
|
445
|
+
if (debugAllowed) {
|
|
446
|
+
throw new Error("Verification failed: Debug mode is enabled in guest policy");
|
|
447
|
+
}
|
|
448
|
+
const vmpl = new DataView(attestationReport.buffer, attestationReport.byteOffset + REPORT_OFFSETS.vmpl, 4).getUint32(0, true);
|
|
449
|
+
if (vmpl > 3) {
|
|
450
|
+
throw new Error(`Verification failed: Unexpected VMPL ${vmpl}`);
|
|
451
|
+
}
|
|
452
|
+
const signingKey = (attestationReport[REPORT_OFFSETS.keyInfo] >> KEY_INFO_BITS.signingKey) &
|
|
453
|
+
((1 << 3) - 1);
|
|
454
|
+
if (signingKey !== 0) {
|
|
455
|
+
throw new Error("Verification failed: Report is not signed by a VCEK");
|
|
456
|
+
}
|
|
457
|
+
const reportedVersion = (attestationReport[REPORT_OFFSETS.committedMajor] << 16) |
|
|
458
|
+
(attestationReport[REPORT_OFFSETS.committedMinor] << 8) |
|
|
459
|
+
attestationReport[REPORT_OFFSETS.committedBuild];
|
|
460
|
+
const requiredVersion = (requirements.minCommittedVersion[0] << 16) |
|
|
461
|
+
(requirements.minCommittedVersion[1] << 8) |
|
|
462
|
+
requirements.minCommittedVersion[2];
|
|
463
|
+
if (reportedVersion < requiredVersion) {
|
|
459
464
|
throw new Error("Verification failed: Firmware version too small");
|
|
460
465
|
}
|
|
461
|
-
|
|
462
|
-
|
|
466
|
+
const committedTcb = parseTcbVersion(attestationReport.slice(REPORT_OFFSETS.committedTcb, REPORT_OFFSETS.committedTcb + 8), product);
|
|
467
|
+
if (committedTcb.snp < requirements.minCommittedTcbSnp) {
|
|
463
468
|
throw new Error("Verification failed: SNP patch level too small");
|
|
464
469
|
}
|
|
470
|
+
if (requirements.minMitVector !== undefined) {
|
|
471
|
+
const reportView = new DataView(attestationReport.buffer, attestationReport.byteOffset, attestationReport.byteLength);
|
|
472
|
+
const reportVersion = reportView.getUint32(REPORT_OFFSETS.version, true);
|
|
473
|
+
const min = BigInt(requirements.minMitVector);
|
|
474
|
+
const checkMitVector = (offset, label) => {
|
|
475
|
+
if (reportVersion < 5) {
|
|
476
|
+
throw new Error(`Verification failed: ${label} mitigation vector not present in report`);
|
|
477
|
+
}
|
|
478
|
+
const value = reportView.getBigUint64(offset, true);
|
|
479
|
+
if ((value & min) !== min) {
|
|
480
|
+
throw new Error(`Verification failed: ${label} mitigation vector required bits not set`);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
checkMitVector(REPORT_OFFSETS.currentMitVector, "Current");
|
|
484
|
+
checkMitVector(REPORT_OFFSETS.launchMitVector, "Launch");
|
|
485
|
+
}
|
|
465
486
|
const cpuModel = attestationReport[REPORT_OFFSETS.cpuIdModel];
|
|
466
487
|
const cpuStep = attestationReport[REPORT_OFFSETS.cpuIdStep];
|
|
467
488
|
let requiredMicrocode;
|
|
@@ -473,7 +494,7 @@ function _verifyReport(attestationReport, vcekChain, requirementsMap) {
|
|
|
473
494
|
}
|
|
474
495
|
else if (cpuModel === 1 && cpuStep === 2) {
|
|
475
496
|
// Milan-X
|
|
476
|
-
requiredMicrocode =
|
|
497
|
+
requiredMicrocode = 0x47;
|
|
477
498
|
}
|
|
478
499
|
break;
|
|
479
500
|
}
|
|
@@ -497,29 +518,29 @@ function _verifyReport(attestationReport, vcekChain, requirementsMap) {
|
|
|
497
518
|
case "Turin":
|
|
498
519
|
if (cpuModel === 2 && cpuStep === 1) {
|
|
499
520
|
// Turin Classic
|
|
500
|
-
requiredMicrocode =
|
|
521
|
+
requiredMicrocode = 0x51;
|
|
501
522
|
}
|
|
502
523
|
else if (cpuModel === 0x11 && cpuStep === 0) {
|
|
503
524
|
// Turin Dense
|
|
504
|
-
requiredMicrocode =
|
|
505
|
-
}
|
|
506
|
-
else if (cpuModel > 2 ||
|
|
507
|
-
(cpuModel === 2 && cpuStep > 1) ||
|
|
508
|
-
(cpuModel === 0x11 && cpuStep > 0)) {
|
|
509
|
-
// Accept any newer models/steppings for Turin
|
|
510
|
-
requiredMicrocode = 0;
|
|
525
|
+
requiredMicrocode = 0x4e;
|
|
511
526
|
}
|
|
512
527
|
}
|
|
513
528
|
if (requiredMicrocode === undefined) {
|
|
514
529
|
throw new Error("Verification failed: Report doesn't match any known CPU family");
|
|
515
530
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
throw new Error("Verification failed: Reported microcode version too small");
|
|
531
|
+
if (committedTcb.microcode < requiredMicrocode) {
|
|
532
|
+
throw new Error("Verification failed: Committed microcode version too small");
|
|
519
533
|
}
|
|
520
|
-
yield _verifyTcbVersion(attestationReport, vcekTcb);
|
|
534
|
+
yield _verifyTcbVersion(attestationReport, product, vcekTcb);
|
|
521
535
|
});
|
|
522
536
|
}
|
|
537
|
+
function readExtensionInt(ext) {
|
|
538
|
+
const { result } = fromBER(ext);
|
|
539
|
+
if (!(result instanceof Integer)) {
|
|
540
|
+
throw new Error("Expected ASN.1 INTEGER");
|
|
541
|
+
}
|
|
542
|
+
return result.valueBlock.valueDec;
|
|
543
|
+
}
|
|
523
544
|
function _verifyVcekChain(vcek, requirements) {
|
|
524
545
|
return __awaiter(this, void 0, void 0, function* () {
|
|
525
546
|
let cert;
|
|
@@ -540,19 +561,20 @@ function _verifyVcekChain(vcek, requirements) {
|
|
|
540
561
|
}
|
|
541
562
|
return new Uint8Array(ext.value);
|
|
542
563
|
};
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
564
|
+
const productExtension = find_extension("1.3.6.1.4.1.3704.1.2", "product name");
|
|
565
|
+
const { result: productIA5String } = fromBER(productExtension);
|
|
566
|
+
if (!(productIA5String instanceof IA5String)) {
|
|
567
|
+
throw new Error("Expected ASN.1 IA5String for product extension");
|
|
546
568
|
}
|
|
547
|
-
const product =
|
|
569
|
+
const product = productIA5String.valueBlock.value.split("-", 1)[0];
|
|
548
570
|
if (!Object.prototype.hasOwnProperty.call(requirements, product)) {
|
|
549
571
|
throw new Error(`Unexpected product: ${product}`);
|
|
550
572
|
}
|
|
551
573
|
const subjectPublicKeyInfo = AsnConvert.parse(chain[0].publicKey.rawData, SubjectPublicKeyInfo);
|
|
552
|
-
const bootloader = find_extension("1.3.6.1.4.1.3704.1.3.1", "bootloader version")
|
|
553
|
-
const tee = find_extension("1.3.6.1.4.1.3704.1.3.2", "TEE")
|
|
554
|
-
const snp = find_extension("1.3.6.1.4.1.3704.1.3.3", "SNP version")
|
|
555
|
-
const microcode = find_extension("1.3.6.1.4.1.3704.1.3.8", "microcode")
|
|
574
|
+
const bootloader = readExtensionInt(find_extension("1.3.6.1.4.1.3704.1.3.1", "bootloader version"));
|
|
575
|
+
const tee = readExtensionInt(find_extension("1.3.6.1.4.1.3704.1.3.2", "TEE"));
|
|
576
|
+
const snp = readExtensionInt(find_extension("1.3.6.1.4.1.3704.1.3.3", "SNP version"));
|
|
577
|
+
const microcode = readExtensionInt(find_extension("1.3.6.1.4.1.3704.1.3.8", "microcode"));
|
|
556
578
|
const tcb = {
|
|
557
579
|
bootloader: bootloader,
|
|
558
580
|
tee: tee,
|
|
@@ -562,7 +584,7 @@ function _verifyVcekChain(vcek, requirements) {
|
|
|
562
584
|
};
|
|
563
585
|
const fmc = chain[0].extensions.find((ext) => ext.type == "1.3.6.1.4.1.3704.1.3.9");
|
|
564
586
|
if (fmc) {
|
|
565
|
-
tcb.fmc = new Uint8Array(fmc.value)
|
|
587
|
+
tcb.fmc = readExtensionInt(new Uint8Array(fmc.value));
|
|
566
588
|
}
|
|
567
589
|
return {
|
|
568
590
|
product: product,
|
|
@@ -593,24 +615,35 @@ function _getSealNonce(ephemeralPublicKey, recipientPublicKey) {
|
|
|
593
615
|
.digest();
|
|
594
616
|
}
|
|
595
617
|
const REPORT_OFFSETS = {
|
|
618
|
+
version: 0,
|
|
619
|
+
policy: 8,
|
|
620
|
+
vmpl: 48,
|
|
621
|
+
sigAlgo: 52,
|
|
596
622
|
platInfo: 64,
|
|
623
|
+
keyInfo: 72,
|
|
597
624
|
reportData: 80,
|
|
598
625
|
measurement: 144,
|
|
599
626
|
reportedTcb: 384,
|
|
600
|
-
cpuIdFamily: 392,
|
|
601
627
|
cpuIdModel: 393,
|
|
602
628
|
cpuIdStep: 394,
|
|
603
|
-
|
|
604
|
-
committedTcbSnp: 486,
|
|
629
|
+
committedTcb: 480,
|
|
605
630
|
committedBuild: 492,
|
|
606
631
|
committedMinor: 493,
|
|
607
632
|
committedMajor: 494,
|
|
633
|
+
launchMitVector: 504,
|
|
634
|
+
currentMitVector: 512,
|
|
608
635
|
signatureR: 672,
|
|
609
636
|
signatureS: 744,
|
|
610
637
|
};
|
|
611
638
|
const PLATFORM_INFO_BITS = {
|
|
612
639
|
aliasCheckComplete: 5,
|
|
613
640
|
};
|
|
641
|
+
const GUEST_POLICY_BITS = {
|
|
642
|
+
debugAllowed: 19,
|
|
643
|
+
};
|
|
644
|
+
const KEY_INFO_BITS = {
|
|
645
|
+
signingKey: 2,
|
|
646
|
+
};
|
|
614
647
|
function bytesToUtf8(bytes) {
|
|
615
648
|
return __awaiter(this, void 0, void 0, function* () {
|
|
616
649
|
return new TextDecoder().decode(bytes);
|
|
@@ -622,6 +655,7 @@ if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
|
|
|
622
655
|
_testing$1.YAXI_SIGNING_KEYS = YAXI_SIGNING_KEYS;
|
|
623
656
|
_testing$1.verifyReport = _verifyReport;
|
|
624
657
|
_testing$1.verifyTcbVersion = _verifyTcbVersion;
|
|
658
|
+
_testing$1.readExtensionInt = readExtensionInt;
|
|
625
659
|
}
|
|
626
660
|
|
|
627
661
|
class KeySettlement {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routex-settlement",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Key settlement for the YAXI routex client",
|
|
5
5
|
"homepage": "https://yaxi.tech",
|
|
6
6
|
"author": "YAXI GmbH",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"util.d.ts"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
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
|
+
"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,asn1js",
|
|
19
19
|
"clean": "tsc --build --clean",
|
|
20
20
|
"fmt": "prettier --write .",
|
|
21
21
|
"lint": "eslint",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"@noble/hashes": "^2.0.0",
|
|
28
28
|
"@peculiar/asn1-schema": "^2.3.15",
|
|
29
29
|
"@peculiar/asn1-x509": "^2.3.15",
|
|
30
|
-
"@peculiar/x509": "^1.12.3"
|
|
30
|
+
"@peculiar/x509": "^1.12.3",
|
|
31
|
+
"asn1js": "^3.0.6"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@eslint/js": "^9.21.0",
|
package/util.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ type Requirements = {
|
|
|
30
30
|
[id: string]: {
|
|
31
31
|
minCommittedVersion: [number, number, number];
|
|
32
32
|
minCommittedTcbSnp: number;
|
|
33
|
+
minMitVector?: number;
|
|
33
34
|
};
|
|
34
35
|
};
|
|
35
36
|
export declare function verifyAttestation(response: SettlementResponse, requirements?: Requirements, signingKeys?: typeof YAXI_SIGNING_KEYS): Promise<void>;
|