Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/EspressoSGXTEEVerifier.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {V3QuoteVerifier} from
"@automata-network/dcap-attestation/contracts/verifiers/V3QuoteVerifier.sol";
import {BELE} from "@automata-network/dcap-attestation/contracts/utils/BELE.sol";
import {Header} from "@automata-network/dcap-attestation/contracts/types/CommonStruct.sol";
import {
HEADER_LENGTH,
ENCLAVE_REPORT_LENGTH
} from "@automata-network/dcap-attestation/contracts/types/Constants.sol";
import {EnclaveReport} from "@automata-network/dcap-attestation/contracts/types/V3Structs.sol";
import {BytesUtils} from "@automata-network/dcap-attestation/contracts/utils/BytesUtils.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IEspressoSGXTEEVerifier} from "./interface/IEspressoSGXTEEVerifier.sol";
/**
*
* @title Verifies quotes from the TEE and attests on-chain
* @notice Contains the logic to verify a quote from the TEE and attest on-chain. It uses the V3QuoteVerifier contract
* from automata to verify the quote. Along with some additional verification logic.
*/
contract EspressoSGXTEEVerifier is IEspressoSGXTEEVerifier, Ownable2Step {
using BytesUtils for bytes;
// V3QuoteVerififer contract from automata to verify the quote
V3QuoteVerifier public quoteVerifier;
mapping(bytes32 => bool) public registeredEnclaveHash;
mapping(address => bool) public registeredSigners;
constructor(bytes32 enclaveHash, address _quoteVerifier) Ownable() {
if (_quoteVerifier == address(0) || _quoteVerifier.code.length <= 0) {
revert InvalidQuoteVerifierAddress();
}
quoteVerifier = V3QuoteVerifier(_quoteVerifier);
registeredEnclaveHash[enclaveHash] = true;
_transferOwnership(msg.sender);
}
/*
@notice Verify a quote from the TEE and attest on-chain
The verification is considered successful if the function does not revert.
@param rawQuote The quote from the TEE
@param reportDataHash The hash of the report data
*/
function verify(bytes calldata rawQuote, bytes32 reportDataHash)
public
view
returns (EnclaveReport memory)
{
// Parse the header
Header memory header = parseQuoteHeader(rawQuote);
// Currently only version 3 is supported
if (header.version != 3) {
revert InvalidHeaderVersion();
}
// Verify the quote
(bool success,) = quoteVerifier.verifyQuote(header, rawQuote);
if (!success) {
revert InvalidQuote();
}
// Parse enclave quote
uint256 lastIndex = HEADER_LENGTH + ENCLAVE_REPORT_LENGTH;
EnclaveReport memory localReport;
(success, localReport) = parseEnclaveReport(rawQuote[HEADER_LENGTH:lastIndex]);
if (!success) {
revert FailedToParseEnclaveReport();
}
// Check that mrEnclave match
if (!registeredEnclaveHash[localReport.mrEnclave]) {
revert InvalidEnclaveHash();
}
// Verify that the reportDataHash if the hash signed by the TEE
// We do not check the signature because `quoteVerifier.verifyQuote` already does that
if (reportDataHash != bytes32(localReport.reportData.substring(0, 32))) {
revert InvalidReportDataHash();
}
return localReport;
}
/*
@notice Register a new signer by verifying a quote from the TEE
@param attestation The attestation from the TEE
@param data which the TEE has attested to
*/
function registerSigner(bytes calldata attestation, bytes calldata data) external {
// Check that the data length is 20 bytes because an address is 20 bytes
if (data.length != 20) {
revert InvalidDataLength();
}
bytes32 signerAddressHash = keccak256(data);
EnclaveReport memory localReport = verify(attestation, signerAddressHash);
if (localReport.reportData.length < 20) {
revert ReportDataTooShort();
}
address signer = address(uint160(bytes20(data[:20])));
// Check if the extracted address is valid
if (signer == address(0)) {
revert InvalidSignerAddress(); // Custom revert if the address is invalid
}
// Mark the signer as registered
if (!registeredSigners[signer]) {
registeredSigners[signer] = true;
emit SignerRegistered(signer, localReport.mrEnclave);
}
}
/*
@notice Parses the header from the quote
@param rawQuote The raw quote in bytes
@return header The parsed header
*/
function parseQuoteHeader(bytes calldata rawQuote) public pure returns (Header memory header) {
header = Header({
version: uint16(BELE.leBytesToBeUint(rawQuote[0:2])),
attestationKeyType: bytes2(rawQuote[2:4]),
teeType: bytes4(uint32(BELE.leBytesToBeUint(rawQuote[4:8]))),
qeSvn: bytes2(rawQuote[8:10]),
pceSvn: bytes2(rawQuote[10:12]),
qeVendorId: bytes16(rawQuote[12:28]),
userData: bytes20(rawQuote[28:48])
});
}
/*
@notice Parses the enclave report from the quote
@param rawEnclaveReport The raw enclave report from the quote in bytes
@return success True if the enclave report was parsed successfully
@return enclaveReport The parsed enclave report
*/
function parseEnclaveReport(bytes memory rawEnclaveReport)
public
pure
returns (bool success, EnclaveReport memory enclaveReport)
{
if (rawEnclaveReport.length != ENCLAVE_REPORT_LENGTH) {
return (false, enclaveReport);
}
enclaveReport.cpuSvn = bytes16(rawEnclaveReport.substring(0, 16));
enclaveReport.miscSelect = bytes4(rawEnclaveReport.substring(16, 4));
enclaveReport.reserved1 = bytes28(rawEnclaveReport.substring(20, 28));
enclaveReport.attributes = bytes16(rawEnclaveReport.substring(48, 16));
enclaveReport.mrEnclave = bytes32(rawEnclaveReport.substring(64, 32));
enclaveReport.reserved2 = bytes32(rawEnclaveReport.substring(96, 32));
enclaveReport.mrSigner = bytes32(rawEnclaveReport.substring(128, 32));
enclaveReport.reserved3 = rawEnclaveReport.substring(160, 96);
enclaveReport.isvProdId = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(256, 2)));
enclaveReport.isvSvn = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(258, 2)));
enclaveReport.reserved4 = rawEnclaveReport.substring(260, 60);
enclaveReport.reportData = rawEnclaveReport.substring(320, 64);
success = true;
}
function setEnclaveHash(bytes32 enclaveHash, bool valid) external onlyOwner {
registeredEnclaveHash[enclaveHash] = valid;
emit EnclaveHashSet(enclaveHash, valid);
}
function deleteRegisteredSigners(address[] memory signers) external onlyOwner {
for (uint256 i = 0; i < signers.length; i++) {
delete registeredSigners[signers[i]];
emit DeletedRegisteredSigner(signers[i]);
}
}
}
"
},
"lib/automata-dcap-attestation/contracts/verifiers/V3QuoteVerifier.sol": {
"content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../bases/QuoteVerifierBase.sol";
import "../types/V3Structs.sol";
import "../bases/tcb/TCBInfoV2Base.sol";
/**
* @title Automata DCAP QuoteV3 Verifier
*/
contract V3QuoteVerifier is QuoteVerifierBase, TCBInfoV2Base {
constructor(address _ecdsaVerifier, address _router) QuoteVerifierBase(_router, 3) P256Verifier(_ecdsaVerifier) {}
function verifyZkOutput(bytes calldata outputBytes)
external
view
override
returns (bool success, bytes memory output)
{
uint256 offset = 2 + uint16(bytes2(outputBytes[0:2]));
success = checkCollateralHashes(offset + 72, outputBytes);
if (success) {
output = outputBytes[2:offset];
} else {
output = bytes("Found one or more collaterals mismatch");
}
}
function verifyQuote(Header calldata header, bytes calldata rawQuote)
external
view
override
returns (bool success, bytes memory output)
{
string memory reason;
V3Quote memory quote;
bytes memory rawQeReport;
(success, reason, quote, rawQeReport) = _parseV3Quote(header, rawQuote);
if (!success) {
return (false, bytes(reason));
}
(success, output) = _verifyQuote(
quote, rawQuote[0:HEADER_LENGTH], rawQuote[HEADER_LENGTH:HEADER_LENGTH + ENCLAVE_REPORT_LENGTH], rawQeReport
);
}
function _parseV3Quote(Header calldata header, bytes calldata quote)
private
view
returns (bool success, string memory reason, V3Quote memory parsed, bytes memory rawQeReport)
{
(success, reason) = validateHeader(header, quote.length, header.teeType == SGX_TEE);
if (!success) {
return (success, reason, parsed, rawQeReport);
}
// now that we are able to confirm that the provided quote is a valid V3 SGX quote
// based on information found in the header
// we continue parsing the remainder of the quote
// parse the local isv report
EnclaveReport memory localReport;
uint256 offset = HEADER_LENGTH + ENCLAVE_REPORT_LENGTH;
(success, localReport) = parseEnclaveReport(quote[HEADER_LENGTH:offset]);
if (!success) {
return (false, "failed to parse local isv report", parsed, rawQeReport);
}
// check authData length
uint256 localAuthDataSize = BELE.leBytesToBeUint(quote[offset:offset + 4]);
offset += 4;
if (quote.length - offset < localAuthDataSize) {
return (false, "quote length is incorrect", parsed, rawQeReport);
}
// at this point, we have verified the length of the entire quote to be correct
// parse authData
ECDSAQuoteV3AuthData memory authData;
(success, authData, rawQeReport) = _parseAuthData(quote[offset:offset + localAuthDataSize]);
if (!success) {
return (false, "failed to parse authdata", parsed, rawQeReport);
}
success = true;
parsed = V3Quote({header: header, localEnclaveReport: localReport, authData: authData});
}
function _verifyQuote(V3Quote memory quote, bytes memory rawHeader, bytes memory rawBody, bytes memory rawQeReport)
private
view
returns (bool success, bytes memory serialized)
{
// Step 0: Check QE Report Data
success = verifyQeReportData(
quote.authData.qeReport.reportData, quote.authData.ecdsaAttestationKey, quote.authData.qeAuthData.data
);
if (!success) {
return (success, bytes("Invalid QEReport data"));
}
// Step 1: Fetch QEIdentity to validate TCB of the QE
EnclaveIdTcbStatus qeTcbStatus;
EnclaveReport memory qeReport = quote.authData.qeReport;
(success, qeTcbStatus) = fetchQeIdentityAndCheckQeReport(EnclaveId.QE, qeReport);
if (!success || qeTcbStatus == EnclaveIdTcbStatus.SGX_ENCLAVE_REPORT_ISVSVN_REVOKED) {
return (success, bytes("Verification failed by QEIdentity check"));
}
// Step 2: Fetch FMSPC TCB then get TCBStatus
X509CertObj[] memory parsedCerts = quote.authData.certification.pck.pckChain;
PCKCertTCB memory pckTcb = quote.authData.certification.pck.pckExtension;
(bool tcbValid, TCBLevelsObj[] memory tcbLevels) = pccsRouter.getFmspcTcbV2(bytes6(pckTcb.fmspcBytes));
if (!tcbValid) {
return (false, bytes("TCB not found or expired"));
}
TCBStatus tcbStatus;
bool statusFound;
for (uint256 i = 0; i < tcbLevels.length; i++) {
(statusFound, tcbStatus) = getSGXTcbStatus(pckTcb, tcbLevels[i]);
if (statusFound) {
break;
}
}
if (!statusFound || tcbStatus == TCBStatus.TCB_REVOKED) {
return (statusFound, bytes("Verificaton failed by TCBInfo check"));
}
// Step 3: Converge QEIdentity and FMSPC TCB Status
tcbStatus = convergeTcbStatusWithQeTcbStatus(qeTcbStatus, tcbStatus);
// Step 4: verify cert chain
success = verifyCertChain(pccsRouter, pccsRouter.crlHelperAddr(), parsedCerts);
if (!success) {
return (success, bytes("Failed to verify X509 Chain"));
}
// Step 5: Signature Verification on local isv report and qereport by PCK
bytes memory localAttestationData = abi.encodePacked(rawHeader, rawBody);
V3Quote memory quoteCopy = quote;
bytes memory rawBodyCopy = rawBody;
success = attestationVerification(
rawQeReport,
quoteCopy.authData.qeReportSignature,
parsedCerts[0].subjectPublicKey,
localAttestationData,
quoteCopy.authData.ecdsa256BitSignature,
quoteCopy.authData.ecdsaAttestationKey
);
if (!success) {
return (success, bytes("Failed to verify attestation and/or qe report signatures"));
}
Output memory output = Output({
quoteVersion: quoteVersion,
tee: SGX_TEE,
tcbStatus: tcbStatus,
fmspcBytes: bytes6(pckTcb.fmspcBytes),
quoteBody: rawBodyCopy,
advisoryIDs: new string[](0)
});
serialized = serializeOutput(output);
}
/**
* [0:64] bytes: ecdsa256BitSignature
* [64:128] bytes: ecdsaAttestationKey
* [128:512] bytes: qeReport
* [512:576] bytes: qeReportSignature
* [576:578] bytes: qeAuthDataSize (Y)
* [578:578+Y] bytes: qeAuthData
* [578+Y:580+Y] bytes: pckCertType
* NOTE: the calculations below assume pckCertType == 5
* [580+Y:584+Y] bytes: certSize (Z)
* [584+Y:584+Y+Z] bytes: certData
*/
function _parseAuthData(bytes calldata rawAuthData)
private
view
returns (bool success, ECDSAQuoteV3AuthData memory authDataV3, bytes memory rawQeReport)
{
authDataV3.ecdsa256BitSignature = rawAuthData[0:64];
authDataV3.ecdsaAttestationKey = rawAuthData[64:128];
rawQeReport = rawAuthData[128:512];
authDataV3.qeReportSignature = rawAuthData[512:576];
uint16 qeAuthDataSize = uint16(BELE.leBytesToBeUint(rawAuthData[576:578]));
authDataV3.qeAuthData.parsedDataSize = qeAuthDataSize;
uint256 offset = 578;
authDataV3.qeAuthData.data = rawAuthData[offset:offset + qeAuthDataSize];
offset += qeAuthDataSize;
uint16 certType = uint16(BELE.leBytesToBeUint(rawAuthData[offset:offset + 2]));
authDataV3.certification.certType = certType;
offset += 2;
uint32 certDataSize = uint32(BELE.leBytesToBeUint(rawAuthData[offset:offset + 4]));
offset += 4;
authDataV3.certification.certDataSize = certDataSize;
bytes memory rawCertData = rawAuthData[offset:offset + certDataSize];
// parsing complete, now we need to decode some raw data
(success, authDataV3.qeReport) = parseEnclaveReport(rawQeReport);
if (!success) {
return (false, authDataV3, rawQeReport);
}
(success, authDataV3.certification.pck) = getPckCollateral(pccsRouter.pckHelperAddr(), certType, rawCertData);
if (!success) {
return (false, authDataV3, rawQeReport);
}
}
}
"
},
"lib/automata-dcap-attestation/contracts/utils/BELE.sol": {
"content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @notice Converts a little-endian encoded bytes to a big-endian uint256 integer
*/
library BELE {
function leBytesToBeUint(bytes memory encoded) internal pure returns (uint256 decoded) {
for (uint256 i = 0; i < encoded.length; i++) {
uint256 digits = uint256(uint8(bytes1(encoded[i])));
uint256 upperDigit = digits / 16;
uint256 lowerDigit = digits % 16;
uint256 acc = lowerDigit * (16 ** (2 * i));
acc += upperDigit * (16 ** ((2 * i) + 1));
decoded += acc;
}
}
}
"
},
"lib/automata-dcap-attestation/contracts/types/CommonStruct.sol": {
"content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {TCBStatus} from "@automata-network/on-chain-pccs/helpers/FmspcTcbHelper.sol";
import {X509CertObj} from "@automata-network/on-chain-pccs/helpers/X509Helper.sol";
/// @dev https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.h#L42-L53
struct Header {
uint16 version;
bytes2 attestationKeyType;
bytes4 teeType;
bytes2 qeSvn;
bytes2 pceSvn;
bytes16 qeVendorId;
bytes20 userData;
}
/// @dev https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.h#L63-L80
struct EnclaveReport {
bytes16 cpuSvn;
bytes4 miscSelect;
bytes28 reserved1;
bytes16 attributes;
bytes32 mrEnclave;
bytes32 reserved2;
bytes32 mrSigner;
bytes reserved3; // 96 bytes
uint16 isvProdId;
uint16 isvSvn;
bytes reserved4; // 60 bytes
bytes reportData; // 64 bytes - For QEReports, this contains the hash of the concatenation of attestation key and QEAuthData
}
/// @dev https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.h#L128-L133
struct QEAuthData {
uint16 parsedDataSize;
bytes data;
}
/// @dev Modified from https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.h#L135-L141
struct CertificationData {
uint16 certType;
uint32 certDataSize;
PCKCollateral pck;
}
/// ========== CUSTOM TYPES ==========
struct PCKCollateral {
X509CertObj[] pckChain; // base64 decoded array containing the PCK chain
PCKCertTCB pckExtension;
}
struct PCKCertTCB {
uint16 pcesvn;
uint8[] cpusvns;
bytes fmspcBytes;
bytes pceidBytes;
}
struct Output {
uint16 quoteVersion; // BE
bytes4 tee; // BE
TCBStatus tcbStatus;
bytes6 fmspcBytes;
bytes quoteBody;
string[] advisoryIDs;
}
"
},
"lib/automata-dcap-attestation/contracts/types/Constants.sol": {
"content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @dev https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/QuoteVerification/QuoteConstants.h
uint16 constant HEADER_LENGTH = 48;
bytes2 constant SUPPORTED_ATTESTATION_KEY_TYPE = 0x0200; // ECDSA_256_WITH_P256_CURVE (LE)
bytes4 constant SGX_TEE = 0x00000000;
bytes4 constant TDX_TEE = 0x00000081;
bytes16 constant VALID_QE_VENDOR_ID = 0x939a7233f79c4ca9940a0db3957f0607;
uint16 constant ENCLAVE_REPORT_LENGTH = 384;
uint16 constant TD_REPORT10_LENGTH = 584;
uint16 constant TD_REPORT15_LENGTH = 648;
// Header (48 bytes) + Body (minimum 384 bytes) + AuthDataSize (4 bytes) + AuthData:
// ECDSA_SIGNATURE (64 bytes) + ECDSA_KEY (64 bytes) + QE_REPORT_BYTES (384 bytes)
// + QE_REPORT_SIGNATURE (64 bytes) + QE_AUTH_DATA_SIZE (2 bytes) + QE_CERT_DATA_TYPE (2 bytes)
// + QE_CERT_DATA_SIZE (4 bytes)
uint16 constant MINIMUM_QUOTE_LENGTH = 1020;
// QUOTE_VERSION (2 bytes) + TEE_TYPE (4 bytes) + TCB_STATUS (1 byte) + FMSPC (6 bytes)
uint16 constant MINIMUM_OUTPUT_LENGTH = 13;
"
},
"lib/automata-dcap-attestation/contracts/types/V3Structs.sol": {
"content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./CommonStruct.sol";
/// @dev https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.h#L153-L164
struct ECDSAQuoteV3AuthData {
bytes ecdsa256BitSignature; // 64 bytes
bytes ecdsaAttestationKey; // 64 bytes
EnclaveReport qeReport; // 384 bytes
bytes qeReportSignature; // 64 bytes
QEAuthData qeAuthData;
CertificationData certification;
}
struct V3Quote {
Header header;
EnclaveReport localEnclaveReport;
ECDSAQuoteV3AuthData authData;
}
"
},
"lib/automata-dcap-attestation/contracts/utils/BytesUtils.sol": {
"content": "// SPDX-License-Identifier: BSD 2-Clause License
pragma solidity ^0.8.0;
// Inspired by ensdomains/dnssec-oracle - BSD-2-Clause license
// https://github.com/ensdomains/dnssec-oracle/blob/master/contracts/BytesUtils.sol
library BytesUtils {
/*
* @dev Returns the keccak-256 hash of a byte range.
* @param self The byte string to hash.
* @param offset The position to start hashing at.
* @param len The number of bytes to hash.
* @return The hash of the byte range.
*/
function keccak(bytes memory self, uint256 offset, uint256 len) internal pure returns (bytes32 ret) {
require(offset + len <= self.length);
assembly {
ret := keccak256(add(add(self, 32), offset), len)
}
}
/*
* @dev Returns a positive number if `other` comes lexicographically after
* `self`, a negative number if it comes before, or zero if the
* contents of the two bytes are equal.
* @param self The first bytes to compare.
* @param other The second bytes to compare.
* @return The result of the comparison.
*/
function compare(bytes memory self, bytes memory other) internal pure returns (int256) {
return compare(self, 0, self.length, other, 0, other.length);
}
/*
* @dev Returns a positive number if `other` comes lexicographically after
* `self`, a negative number if it comes before, or zero if the
* contents of the two bytes are equal. Comparison is done per-rune,
* on unicode codepoints.
* @param self The first bytes to compare.
* @param offset The offset of self.
* @param len The length of self.
* @param other The second bytes to compare.
* @param otheroffset The offset of the other string.
* @param otherlen The length of the other string.
* @return The result of the comparison.
*/
function compare(
bytes memory self,
uint256 offset,
uint256 len,
bytes memory other,
uint256 otheroffset,
uint256 otherlen
) internal pure returns (int256) {
uint256 shortest = len;
if (otherlen < len) {
shortest = otherlen;
}
uint256 selfptr;
uint256 otherptr;
assembly {
selfptr := add(self, add(offset, 32))
otherptr := add(other, add(otheroffset, 32))
}
for (uint256 idx = 0; idx < shortest; idx += 32) {
uint256 a;
uint256 b;
assembly {
a := mload(selfptr)
b := mload(otherptr)
}
if (a != b) {
// Mask out irrelevant bytes and check again
uint256 mask;
if (shortest > 32) {
mask = type(uint256).max; // aka 0xffffff....
} else {
mask = ~(2 ** (8 * (32 - shortest + idx)) - 1);
}
uint256 diff = (a & mask) - (b & mask);
if (diff != 0) {
return int256(diff);
}
}
selfptr += 32;
otherptr += 32;
}
return int256(len) - int256(otherlen);
}
/*
* @dev Returns true if the two byte ranges are equal.
* @param self The first byte range to compare.
* @param offset The offset into the first byte range.
* @param other The second byte range to compare.
* @param otherOffset The offset into the second byte range.
* @param len The number of bytes to compare
* @return True if the byte ranges are equal, false otherwise.
*/
function equals(bytes memory self, uint256 offset, bytes memory other, uint256 otherOffset, uint256 len)
internal
pure
returns (bool)
{
return keccak(self, offset, len) == keccak(other, otherOffset, len);
}
/*
* @dev Returns true if the two byte ranges are equal with offsets.
* @param self The first byte range to compare.
* @param offset The offset into the first byte range.
* @param other The second byte range to compare.
* @param otherOffset The offset into the second byte range.
* @return True if the byte ranges are equal, false otherwise.
*/
function equals(bytes memory self, uint256 offset, bytes memory other, uint256 otherOffset)
internal
pure
returns (bool)
{
return keccak(self, offset, self.length - offset) == keccak(other, otherOffset, other.length - otherOffset);
}
/*
* @dev Compares a range of 'self' to all of 'other' and returns True iff
* they are equal.
* @param self The first byte range to compare.
* @param offset The offset into the first byte range.
* @param other The second byte range to compare.
* @return True if the byte ranges are equal, false otherwise.
*/
function equals(bytes memory self, uint256 offset, bytes memory other) internal pure returns (bool) {
return self.length >= offset + other.length && equals(self, offset, other, 0, other.length);
}
/*
* @dev Returns true if the two byte ranges are equal.
* @param self The first byte range to compare.
* @param other The second byte range to compare.
* @return True if the byte ranges are equal, false otherwise.
*/
function equals(bytes memory self, bytes memory other) internal pure returns (bool) {
return self.length == other.length && equals(self, 0, other, 0, self.length);
}
/*
* @dev Returns the 8-bit number at the specified index of self.
* @param self The byte string.
* @param idx The index into the bytes
* @return The specified 8 bits of the string, interpreted as an integer.
*/
function readUint8(bytes memory self, uint256 idx) internal pure returns (uint8 ret) {
return uint8(self[idx]);
}
/*
* @dev Returns the 16-bit number at the specified index of self.
* @param self The byte string.
* @param idx The index into the bytes
* @return The specified 16 bits of the string, interpreted as an integer.
*/
function readUint16(bytes memory self, uint256 idx) internal pure returns (uint16 ret) {
require(idx + 2 <= self.length);
assembly {
ret := and(mload(add(add(self, 2), idx)), 0xFFFF)
}
}
/*
* @dev Returns the 32-bit number at the specified index of self.
* @param self The byte string.
* @param idx The index into the bytes
* @return The specified 32 bits of the string, interpreted as an integer.
*/
function readUint32(bytes memory self, uint256 idx) internal pure returns (uint32 ret) {
require(idx + 4 <= self.length);
assembly {
ret := and(mload(add(add(self, 4), idx)), 0xFFFFFFFF)
}
}
/*
* @dev Returns the 32 byte value at the specified index of self.
* @param self The byte string.
* @param idx The index into the bytes
* @return The specified 32 bytes of the string.
*/
function readBytes32(bytes memory self, uint256 idx) internal pure returns (bytes32 ret) {
require(idx + 32 <= self.length);
assembly {
ret := mload(add(add(self, 32), idx))
}
}
/*
* @dev Returns the 32 byte value at the specified index of self.
* @param self The byte string.
* @param idx The index into the bytes
* @return The specified 32 bytes of the string.
*/
function readBytes20(bytes memory self, uint256 idx) internal pure returns (bytes20 ret) {
require(idx + 20 <= self.length);
assembly {
ret :=
and(mload(add(add(self, 32), idx)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000)
}
}
/*
* @dev Returns the n byte value at the specified index of self.
* @param self The byte string.
* @param idx The index into the bytes.
* @param len The number of bytes.
* @return The specified 32 bytes of the string.
*/
function readBytesN(bytes memory self, uint256 idx, uint256 len) internal pure returns (bytes32 ret) {
require(len <= 32);
require(idx + len <= self.length);
assembly {
let mask := not(sub(exp(256, sub(32, len)), 1))
ret := and(mload(add(add(self, 32), idx)), mask)
}
}
function memcpy(uint256 dest, uint256 src, uint256 len) private pure {
// Copy word-length chunks while possible
for (; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
// Copy remaining bytes
uint256 mask;
if (len == 0) {
mask = type(uint256).max; // Set to maximum value of uint256
} else {
mask = 256 ** (32 - len) - 1;
}
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
/*
* @dev Copies a substring into a new byte string.
* @param self The byte string to copy from.
* @param offset The offset to start copying at.
* @param len The number of bytes to copy.
*/
function substring(bytes memory self, uint256 offset, uint256 len) internal pure returns (bytes memory) {
require(offset + len <= self.length);
bytes memory ret = new bytes(len);
uint256 dest;
uint256 src;
assembly {
dest := add(ret, 32)
src := add(add(self, 32), offset)
}
memcpy(dest, src, len);
return ret;
}
// Maps characters from 0x30 to 0x7A to their base32 values.
// 0xFF represents invalid characters in that range.
bytes constant base32HexTable =
hex"00010203040506070809FFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1FFFFFFFFFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1F";
/**
* @dev Decodes unpadded base32 data of up to one word in length.
* @param self The data to decode.
* @param off Offset into the string to start at.
* @param len Number of characters to decode.
* @return The decoded data, left aligned.
*/
function base32HexDecodeWord(bytes memory self, uint256 off, uint256 len) internal pure returns (bytes32) {
require(len <= 52);
uint256 ret = 0;
uint8 decoded;
for (uint256 i = 0; i < len; i++) {
bytes1 char = self[off + i];
require(char >= 0x30 && char <= 0x7A);
decoded = uint8(base32HexTable[uint256(uint8(char)) - 0x30]);
require(decoded <= 0x20);
if (i == len - 1) {
break;
}
ret = (ret << 5) | decoded;
}
uint256 bitlen = len * 5;
if (len % 8 == 0) {
// Multiple of 8 characters, no padding
ret = (ret << 5) | decoded;
} else if (len % 8 == 2) {
// Two extra characters - 1 byte
ret = (ret << 3) | (decoded >> 2);
bitlen -= 2;
} else if (len % 8 == 4) {
// Four extra characters - 2 bytes
ret = (ret << 1) | (decoded >> 4);
bitlen -= 4;
} else if (len % 8 == 5) {
// Five extra characters - 3 bytes
ret = (ret << 4) | (decoded >> 1);
bitlen -= 1;
} else if (len % 8 == 7) {
// Seven extra characters - 4 bytes
ret = (ret << 2) | (decoded >> 3);
bitlen -= 3;
} else {
revert();
}
return bytes32(ret << (256 - bitlen));
}
function compareBytes(bytes memory a, bytes memory b) internal pure returns (bool) {
if (a.length != b.length) {
return false;
}
for (uint256 i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() external {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
"
},
"src/interface/IEspressoSGXTEEVerifier.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Header} from "@automata-network/dcap-attestation/contracts/types/CommonStruct.sol";
import {EnclaveReport} from "@automata-network/dcap-attestation/contracts/types/V3Structs.sol";
interface IEspressoSGXTEEVerifier {
// We only support version 3 for now
error InvalidHeaderVersion();
// This error is thrown when the automata verification fails
error InvalidQuote();
// This error is thrown when the enclave report fails to parse
error FailedToParseEnclaveReport();
// This error is thrown when the mrEnclave don't match
error InvalidEnclaveHash();
// This error is thrown when the reportDataHash doesn't match the hash signed by the TEE
error InvalidReportDataHash();
// This error is thrown when the reportData is too short
error ReportDataTooShort();
// This error is thrown when the data length is invalid
error InvalidDataLength();
// This error is thrown when the signer address is invalid
error InvalidSignerAddress();
// This error is thrown when the quote verifier address is invalid
error InvalidQuoteVerifierAddress();
event EnclaveHashSet(bytes32 enclaveHash, bool valid);
event SignerRegistered(address signer, bytes32 enclaveHash);
event DeletedRegisteredSigner(address signer);
function registeredSigners(address signer) external view returns (bool);
function registeredEnclaveHash(bytes32 enclaveHash) external view returns (bool);
function registerSigner(bytes calldata attestation, bytes calldata data) external;
function verify(bytes calldata rawQuote, bytes32 reportDataHash)
external
view
returns (EnclaveReport memory);
function parseQuoteHeader(bytes calldata rawQuote)
external
pure
returns (Header memory header);
function parseEnclaveReport(bytes memory rawEnclaveReport)
external
pure
returns (bool success, EnclaveReport memory enclaveReport);
function setEnclaveHash(bytes32 enclaveHash, bool valid) external;
function deleteRegisteredSigners(address[] memory signers) external;
}
"
},
"lib/automata-dcap-attestation/contracts/bases/QuoteVerifierBase.sol": {
"content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {TCBStatus} from "@automata-network/on-chain-pccs/helpers/FmspcTcbHelper.sol";
import {IQuoteVerifier, IPCCSRouter} from "../interfaces/IQuoteVerifier.sol";
import {BytesUtils} from "../utils/BytesUtils.sol";
import {BELE} from "../utils/BELE.sol";
import {P256Verifier} from "../utils/P256Verifier.sol";
import {Header, EnclaveReport, Output} from "../types/CommonStruct.sol";
import "../types/Constants.sol";
import "./EnclaveIdBase.sol";
import "./X509ChainBase.sol";
abstract contract QuoteVerifierBase is IQuoteVerifier, EnclaveIdBase, X509ChainBase {
using BytesUtils for bytes;
IPCCSRouter public immutable override pccsRouter;
uint16 public immutable override quoteVersion;
constructor(address _router, uint16 _version) {
pccsRouter = IPCCSRouter(_router);
quoteVersion = _version;
}
function validateHeader(Header calldata header, uint256 quoteLength, bool teeIsValid)
internal
view
returns (bool valid, string memory reason)
{
if (quoteLength < MINIMUM_QUOTE_LENGTH) {
return (false, "Quote length is less than minimum");
}
if (header.version != quoteVersion) {
return (false, "Version mismatch");
}
if (header.attestationKeyType != SUPPORTED_ATTESTATION_KEY_TYPE) {
return (false, "Unsupported attestation key type");
}
if (!teeIsValid) {
return (false, "Unknown TEE type");
}
if (header.qeVendorId != VALID_QE_VENDOR_ID) {
return (false, "Not a valid Intel SGX QE Vendor ID");
}
valid = true;
}
function parseEnclaveReport(bytes memory rawEnclaveReport)
internal
pure
returns (bool success, EnclaveReport memory enclaveReport)
{
if (rawEnclaveReport.length != ENCLAVE_REPORT_LENGTH) {
return (false, enclaveReport);
}
enclaveReport.cpuSvn = bytes16(rawEnclaveReport.substring(0, 16));
enclaveReport.miscSelect = bytes4(rawEnclaveReport.substring(16, 4));
enclaveReport.reserved1 = bytes28(rawEnclaveReport.substring(20, 28));
enclaveReport.attributes = bytes16(rawEnclaveReport.substring(48, 16));
enclaveReport.mrEnclave = bytes32(rawEnclaveReport.substring(64, 32));
enclaveReport.reserved2 = bytes32(rawEnclaveReport.substring(96, 32));
enclaveReport.mrSigner = bytes32(rawEnclaveReport.substring(128, 32));
enclaveReport.reserved3 = rawEnclaveReport.substring(160, 96);
enclaveReport.isvProdId = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(256, 2)));
enclaveReport.isvSvn = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(258, 2)));
enclaveReport.reserved4 = rawEnclaveReport.substring(260, 60);
enclaveReport.reportData = rawEnclaveReport.substring(320, 64);
success = true;
}
function fetchQeIdentityAndCheckQeReport(EnclaveId id, EnclaveReport memory qeReport)
internal
view
returns (bool success, EnclaveIdTcbStatus qeTcbStatus)
{
IdentityObj memory qeIdentity;
(success, qeIdentity) = pccsRouter.getQeIdentity(id, quoteVersion);
if (!success) {
return (success, EnclaveIdTcbStatus.SGX_ENCLAVE_REPORT_ISVSVN_NOT_SUPPORTED);
}
(success, qeTcbStatus) = verifyQEReportWithIdentity(
qeIdentity, qeReport.miscSelect, qeReport.attributes, qeReport.mrSigner, qeReport.isvProdId, qeReport.isvSvn
);
}
function verifyQeReportData(bytes memory qeReportData, bytes memory attestationKey, bytes memory qeAuthData)
internal
pure
returns (bool)
{
bytes32 expectedHash = bytes32(qeReportData);
bytes memory preimage = abi.encodePacked(attestationKey, qeAuthData);
bytes32 computedHash = sha256(preimage);
return expectedHash == computedHash;
}
function attestationVerification(
bytes memory rawQeReport,
bytes memory qeSignature,
bytes memory pckPubkey,
bytes memory signedAttestationData,
bytes memory attestationSignature,
bytes memory attestationKey
) internal view returns (bool) {
bool qeReportVerified = P256Verifier.ecdsaVerify(sha256(rawQeReport), qeSignature, pckPubkey);
if (!qeReportVerified) {
return false;
}
bool attestationVerified =
P256Verifier.ecdsaVerify(sha256(signedAttestationData), attestationSignature, attestationKey);
return attestationVerified;
}
function convergeTcbStatusWithQeTcbStatus(EnclaveIdTcbStatus qeTcbStatus, TCBStatus tcbStatus)
internal
pure
returns (TCBStatus convergedStatus)
{
// https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/16b7291a7a86e486fdfcf1dfb4be885c0cc00b4e/Src/AttestationLibrary/src/Verifiers/QuoteVerifier.cpp#L271-L312
if (qeTcbStatus == EnclaveIdTcbStatus.SGX_ENCLAVE_REPORT_ISVSVN_OUT_OF_DATE) {
if (tcbStatus == TCBStatus.OK || tcbStatus == TCBStatus.TCB_SW_HARDENING_NEEDED) {
convergedStatus = TCBStatus.TCB_OUT_OF_DATE;
}
if (
tcbStatus == TCBStatus.TCB_CONFIGURATION_NEEDED
|| tcbStatus == TCBStatus.TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED
) {
convergedStatus = TCBStatus.TCB_OUT_OF_DATE_CONFIGURATION_NEEDED;
}
} else {
convergedStatus = tcbStatus;
}
}
function serializeOutput(Output memory output) internal pure returns (bytes memory) {
return abi.encodePacked(output.quoteVersion, output.tee, output.tcbStatus, output.fmspcBytes, output.quoteBody, abi.encode(output.advisoryIDs));
}
function checkCollateralHashes(uint256 offset, bytes calldata journal) internal view returns (bool) {
bytes32 rootCaHash = bytes32(journal[offset:offset + 32]);
bytes32 tcbSigningHash = bytes32(journal[offset + 32:offset + 64]);
bytes32 rootCaCrlHash = bytes32(journal[offset + 64:offset + 96]);
bytes32 pckCrlHash = bytes32(journal[offset + 96:offset + 128]);
return _checkCollateralHashes(rootCaHash, tcbSigningHash, rootCaCrlHash, pckCrlHash);
}
function _checkCollateralHashes(bytes32 rootCaHash, bytes32 tcbSigningHash, bytes32 rootCaCrlHash, bytes32 pckCrlHash) internal view returns (bool) {
(bool tcbSigningFound, bytes32 expectedTcbSigningHash) = pccsRouter.getCertHash(CA.SIGNING);
if (!tcbSigningFound || tcbSigningHash != expectedTcbSigningHash) {
return false;
}
(bool rootCaFound, bytes32 expectedRootCaHash) = pccsRouter.getCertHash(CA.ROOT);
if (!rootCaFound || rootCaHash != expectedRootCaHash) {
return false;
}
(bool rootCrlFound, bytes32 expectedRootCrlHash) = pccsRouter.getCrlHash(CA.ROOT);
if (!rootCrlFound || rootCaCrlHash != expectedRootCrlHash) {
return false;
}
// use low level calls for PCK CRLs, because we don't know which one of the CAs is used
// to verify the quote
// we can catch reverts here, and consider it a valid quote as long as:
// - one of the PCK CAs has a CRL stored on-chain
// - the hash of the on-chain CRL matches with the CRL hash in the journal
(bool platformSuccess, bytes memory platformRet) =
address(pccsRouter).staticcall(abi.encodeWithSelector(IPCCSRouter.getCrlHash.selector, CA.PLATFORM));
(bool processorSuccess, bytes memory processorRet) =
address(pccsRouter).staticcall(abi.encodeWithSelector(IPCCSRouter.getCrlHash.selector, CA.PROCESSOR));
bytes32 expectedPlatformCrlHash;
bytes32 expectedProcessorCrlHash;
if (platformSuccess) {
(, expectedPlatformCrlHash) = abi.decode(platformRet, (bool, bytes32));
} else if (processorSuccess) {
(, expectedProcessorCrlHash) = abi.decode(processorRet, (bool, bytes32));
} else {
// Both Processor and Platform PCKs not found
return false;
}
return pckCrlHash == expectedPlatformCrlHash || pckCrlHash == expectedProcessorCrlHash;
}
}
"
},
"lib/automata-dcap-attestation/contracts/bases/tcb/TCBInfoV2Base.sol": {
"content": "//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {TCBLevelsObj, TCBStatus} from "@automata-network/on-chain-pccs/helpers/FmspcTcbHelper.sol";
import {EnclaveIdTcbStatus} from "@automata-network/on-chain-pccs/helpers/EnclaveIdentityHelper.sol";
import {LibString} from "solady/utils/LibString.sol";
import {PCKCertTCB} from "../../types/CommonStruct.sol";
abstract contract TCBInfoV2Base {
using LibString for string;
// https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/e7604e02331b3377f3766ed3653250e03af72d45/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/X509Constants.h#L64
uint256 internal constant CPUSVN_LENGTH = 16;
function getSGXTcbStatus(PCKCertTCB memory pckTcb, TCBLevelsObj memory current)
internal
pure
returns (bool, TCBStatus status)
{
bool pceSvnIsHigherOrGreater;
bool cpuSvnsAreHigherOrGreater;
(pceSvnIsHigherOrGreater, cpuSvnsAreHigherOrGreater) = _checkSgxCpuSvns(pckTcb, current);
status = current.status;
bool statusFound = pceSvnIsHigherOrGreater && cpuSvnsAreHigherOrGreater;
return (statusFound, statusFound ? status : TCBStatus.TCB_UNRECOGNIZED);
}
function _checkSgxCpuSvns(PCKCertTCB memory pckTcb, TCBLevelsObj memory tcbLevel)
internal
pure
returns (bool, bool)
{
bool pceSvnIsHigherOrGreater = pckTcb.pcesvn >= tcbLevel.pcesvn;
bool cpuSvnsAreHigherOrGreater = _isCpuSvnHigherOrGreater(pckTcb.cpusvns, tcbLevel.sgxComponentCpuSvns);
return (pceSvnIsHigherOrGreater, cpuSvnsAreHigherOrGreater);
}
function _isCpuSvnHigherOrGreater(uint8[] memory pckCpuSvns, uint8[] memory tcbCpuSvns)
internal
pure
returns (bool)
{
if (pckCpuSvns.length != CPUSVN_LENGTH || tcbCpuSvns.length != CPUSVN_LENGTH) {
return false;
}
for (uint256 i = 0; i < CPUSVN_LENGTH; i++) {
if (uint256(pckCpuSvns[i]) < tcbCpuSvns[i]) {
return false;
}
}
return true;
}
}
"
},
"lib/automata-dcap-attestation/lib/automata-on-chain-pccs/src/helpers/FmspcTcbHelper.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {JSONParserLib} from "solady/utils/JSONParserLib.sol";
import {LibString} from "solady/utils/LibString.sol";
import {DateTimeUtils} from "../utils/DateTimeUtils.sol";
// https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/e7604e02331b3377f3766ed3653250e03af72d45/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/X509Constants.h#L64
uint256 constant TCB_CPUSVN_SIZE = 16;
enum TcbId {
/// the "id" field is absent from TCBInfo V2
/// which defaults TcbId to SGX
/// since TDX TCBInfos are only included in V3 or above
SGX,
TDX
}
/**
* @dev This is a simple representation of the TCBInfo.json in string as a Solidity object.
* @param tcbInfo: tcbInfoJson.tcbInfo string object body
* @param signature The signature to be passed as bytes array
*/
struct TcbInfoJsonObj {
string tcbInfoStr;
bytes signature;
}
/// @dev Solidity object representing TCBInfo.json excluding TCBLevels
struct TcbInfoBasic {
/// the name "tcbType" can be confusing/misleading
/// as the tcbType referred here in this struct is the type
/// of TCB level composition that determines TCB level comparison logic
/// It is not the same as the "type" parameter passed as an argument to the
/// getTcbInfo() API method described in Section 4.2.3 of the Intel PCCS Design Document
/// Instead, getTcbInfo() "type" argument should be checked against the "id" value of this struct
/// which represents the TEE type for the given TCBInfo
uint8 tcbType;
TcbId id;
uint32 version;
uint64 issueDate;
uint64 nextUpdate;
uint32 evaluationDataNumber;
bytes6 fmspc;
bytes2 pceid;
}
struct TCBLevelsObj {
uint16 pcesvn;
uint8[] sgxComponentCpuSvns;
uint8[] tdxSvns;
uint64 tcbDateTimestamp;
TCBStatus status;
string[] advisoryIDs;
}
struct TDXModule {
bytes mrsigner; // 48 bytes
bytes8 attributes;
bytes8 attributesMask;
}
struct TDXModuleIdentity {
string id;
bytes8 attributes;
bytes8 attributesMask;
bytes mrsigner; // 48 bytes
TDXModuleTCBLevelsObj[] tcbLevels;
}
struct TDXModuleTCBLevelsObj {
uint8 isvsvn;
uint64 tcbDateTimestamp;
TCBStatus status;
}
enum TCBStatus {
OK,
TCB_SW_HARDENING_NEEDED,
TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED,
TCB_CONFIGURATION_NEEDED,
TCB_OUT_OF_DATE,
TCB_OUT_OF_DATE_CONFIGURATION_NEEDED,
TCB_REVOKED,
TCB_UNRECOGNIZED
}
/**
* @title FMSPC TCB Helper Contract
* @notice This is a standalone contract that can be used by off-chain applications and smart contracts
* to parse TCBInfo data
*/
contract FmspcTcbHelper {
using JSONParserLib for JSONParserLib.Item;
using LibString for string;
error TCBInfo_Invalid();
error TCB_TDX_Version_Invalid();
error TCB_TDX_ID_Invalid();
function parseTcbString(string calldata tcbInfoStr) external pure returns (TcbInfoBasic memory tcbInfo) {
JSONParserLib.Item memory root = JSONParserLib.parse(tcbInfoStr);
JSONParserLib.Item[] memory tcbInfoObj = root.children();
bool tcbTypeFound;
bool fmspcFound;
bool versionFound;
bool issueDateFound;
bool nextUpdateFound;
bool pceidFound;
bool evaluationFound;
bool idFound;
bool allFound;
TcbInfoBasic memory tcbInfoCopy;
for (uint256 y = 0; y < root.size(); y++) {
JSONParserLib.Item memory current = tcbInfoObj[y];
string memory decodedKey = JSONParserLib.decodeString(current.key());
string memory val = current.value();
if (decodedKey.eq("tcbType")) {
tcbInfoCopy.tcbType = uint8(JSONParserLib.parseUint(val));
tcbTypeFound = true;
} else if (decodedKey.eq("id")) {
string memory idStr = JSONParserLib.decodeString(val);
if (idStr.eq("SGX")) {
tcbInfoCopy.id = TcbId.SGX;
} else if (idStr.eq("TDX")) {
tcbInfoCopy.id = TcbId.TDX;
} else {
revert TCBInfo_Invalid();
}
idFound = true;
} else if (decodedKey.eq("fmspc")) {
tcbInfoCopy.fmspc = bytes6(uint48(JSONParserLib.parseUintFromHex(JSONParserLib.decodeString(val))));
fmspcFound = true;
} else if (decodedKey.eq("version")) {
tcbInfoCopy.version = uint32(JSONParserLib.parseUint(val));
versionFound = true;
} else if (decodedKey.eq("issueDate")) {
tcbInfoCopy.issueDate = uint64(DateTimeUtils.fromISOToTimestamp(JSONParserLib.decodeString(val)));
issueDateFound = true;
} else if (decodedKey.eq("nextUpdate")) {
tcbInfoCopy.nextUpdate = uint64(DateTimeUtils.fromISOToTimestamp(JSONParserLib.decodeString(val)));
nextUpdateFound = true;
} else if (decodedKey.eq("pceId")) {
tcbInfoCopy.pceid = bytes2(uint16(JSONParserLib.parseUintFromHex(JSONParserLib.decodeString(val))));
pceidFound = true;
} else if (decodedKey.eq("tcbEvaluationDataNumber")) {
tcbInfoCopy.evaluationDataNumber = uint32(JSONParserLib.parseUint(val));
evaluationFound = true;
}
if (versionFound) {
allFound =
(tcbTypeFound && fmspcFound && issueDateFound && nextUpdateFound && pceidFound && evaluationFound);
if (tcbInfoCopy.version >= 3) {
allFound = allFound && idFound;
}
if (allFound) {
break;
}
}
}
if (!allFound) {
revert TCBInfo_Invalid();
}
tcbInfo = tcbInfoCopy;
}
function parseTcbLevels(string calldata tcbInfoStr)
external
pure
returns (uint256 version, TCBLevelsObj[] memory tcbLevels)
{
JSONParserLib.Item memory root = JSONParserLib.parse(tcbInfoStr);
JSONParserLib.Item[] memory tcbInfoObj = root.children();
bool versionFound;
bool tcbLevelsFound;
JSONParserLib.Item[] memory tcbLevelsObj;
for (uint256 i = 0; i < root.size(); i++) {
JSONParserLib.Item memory current = tcbInfoObj[i];
string memory decodedKey = JSONParserLib.decodeString(current.key());
if (decodedKey.eq("version")) {
version = JSONParserLib.parseUint(current.value());
versionFound = true;
}
if (decodedKey.eq("tcbLevels")) {
tcbLevelsObj = current.children();
tcbLevelsFound = true;
}
if (versionFound && tcbLevelsFound) {
break;
}
}
if (versionFound && tcbLevelsFound) {
tcbLevels = _parseTCBLevels(version, tcbLevelsObj);
} else {
revert TCBInfo_Invalid();
}
}
function parseTcbTdxModules(string calldata tcbInfoStr)
external
pure
returns (TDXModule memory module, TDXModuleIdentity[] memory moduleIdentities)
{
JSONParserLib.Item memory root = JSONParserLib.parse(tcbInfoStr);
JSONParserLib.Item[] memory tcbInfoObj = root.children();
bool versionFound;
bool idFound;
bool tdxModuleFound;
bool tdxModuleIdentitiesFound;
bool allFound;
for (uint256 i = 0; i < root.size(); i++) {
JSONParserLib.Item memory current = tcbInfoObj[i];
string memory decodedKey = JSONParserLib.decodeString(current.key());
if (decodedKey.eq("version")) {
uint256 version = JSONParserLib.parseUint(current.value());
if (version < 3) {
revert TCB_TDX_Version_Invalid();
}
versionFound = true;
}
if (decodedKey.eq("id")) {
string memory id = JSONParserLib.decodeString(current.value());
if (!id.eq("TDX")) {
revert TCB_TDX_ID_Invalid();
}
idFound = true;
}
if (decodedKey.eq("tdxModule")) {
module = _parseTdxModule(current.children());
tdxModuleFound = true;
}
if (decodedKey.eq("tdxModuleIdentities")) {
moduleIdentities = _parseTdxModuleIdentities(current.children());
tdxModuleIdentitiesFound = true;
}
allFound = versionFound && idFound && tdxModuleFound && tdxModuleIdentitiesFound;
if (allFound) {
break;
}
}
if (!allFound) {
revert TCBInfo_Invalid();
}
}
/// ====== INTERNAL METHODS BELOW ======
function _parseTCBLevels(uint256 version, JSONParserLib.Item[] memory tcbLevelsObj)
private
pure
returns (TCBLevelsObj[] memory tcbLevels)
{
uint256 tcbLevelsSize = tcbLevelsObj.length;
tcbLevels = new TCBLevelsObj[](tcbLevelsSize);
// iterating through the array
for (uint256 i = 0; i < tcbLevelsSize; i++) {
JSONParserLib.Item[] memory tcbObj = tcbLevelsObj[i].children();
// iterating through individual tcb objects
for (uint256 j = 0; j < tcbLevelsObj[i].size(); j++) {
string memory tcbKey = JSONParserLib.decodeString(tcbObj[j].key());
if (tcbKey.eq("tcb")) {
string memory tcbStr = tcbObj[j].value();
JSONParserLib.Item memory tcbParent = JSONParserLib.parse(tcbStr);
JSONParserLib.Item[] memory tcbComponents = tcbParent.children();
if (version == 2) {
(tcbLevels[i].sgxComponentCpuSvns, tcbLevels[i].pcesvn) = _parseV2Tcb(tcbComponents);
} else if (version == 3) {
(tcbLevels[i].sgxComponentCpuSvns, tcbLevels[i].tdxSvns, tcbLevels[i].pcesvn) =
_parseV3Tcb(tcbComponents);
} else {
revert TCBInfo_Invalid();
}
} else if (tcbKey.eq("tcbDate")) {
tcbLevels[i].tcbDateTimestamp =
uint64(DateTimeUtils.fromISOToTimestamp(JSONParserLib.decodeString(tcbObj[j].value())));
} else if (tcbKey.eq("tcbStatus")) {
tcbLevels[i].status = _getTcbStatus(JSONParserLib.decodeString(tcbObj[j].value()));
} else if (tcbKey.eq("advisoryIDs")) {
JSONParserLib.Item[] memory advisoryArr = tcbObj[j].children();
uint256 n = tcbObj[j].size();
tcbLevels[i].advisoryIDs = new string[](n);
for (uint256 k = 0; k < n; k++) {
tcbLevels[i].advisoryIDs[k] = JSONParserLib.decodeString(advisoryArr[k].value());
}
}
}
}
}
function _getTcbStatus(string memory statusStr) private pure returns (TCBStatus status) {
if (statusStr.eq("UpToDate")) {
status = TCBStatus.OK;
} else if (statusStr.eq("OutOfDate")) {
status = TCBStatus.TCB_OUT_OF_DATE;
} else if (statusStr.eq("OutOfDateConfigurationNeeded")) {
status = TCBStatus.TCB_OUT_OF_DATE_CONFIGURATION_NEEDED;
} else if (statusStr.eq("ConfigurationNeeded")) {
status = TCBStatus.TCB_CONFIGURATION_NEEDED;
} else if (statusStr.eq("ConfigurationAndSWHardeningNeeded")) {
status = TCBStatus.TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED;
} else if (statusStr.eq("SWHardeningNeeded")) {
status = TCBStatus.TCB_SW_HARDENING_NEEDED;
} else if (statusStr.eq("Revoked")) {
status = TCBStatus.TCB_REVOKED;
} else {
status = TCBStatus.TCB_UNRECOGNIZED;
}
}
function _parseV2Tcb(JSONParserLib.Item[] memory tcbComponents)
private
pure
returns (uint8[] memory sgxComponentCpuSvns, uint16 pcesvn)
{
sgxComponentCpuSvns = new uint8[](TCB_CPUSVN_SIZE);
uint256 cpusvnCounter = 0;
for (uint256 i = 0; i < tcbComponents.length; i++) {
string memory key = JSONParserLib.decodeString(tcbComponents[i].key());
uint256 value = JSONParserLib.parseUint(tcbComponents[i].value());
if (key.eq("pcesvn")) {
pcesvn = uint16(value);
} else {
sgxComponentCpuSvns[cpusvnCounter++] = uint8(value);
}
}
if (cpusvnCounter != TCB_CPUSVN_SIZE) {
revert TCBInfo_Invalid();
}
}
function _parseV3Tcb(JSONParserLib.Item[] memory tcbComponents)
private
pure
returns (uint8[] memory sgxComponentCpuSvns, uint8[] memory tdxSvns, uint16 pcesvn)
{
sgxComponentCpuSvns = new uint8[](TCB_CPUSVN_SIZE);
tdxSvns = new uint8[](TCB_CPUSVN_SIZE);
for (uint256 i = 0; i < tcbComponents.length; i++) {
string memory key = JSONParserLib.decodeString(tcbComponents[i].key());
if (key.eq("pcesvn")) {
pcesvn = uint16(JSONParserLib.parseUint(tcbComponents[i].value()));
} else {
string memory componentKey = key;
JSONParserLib.Item[] memory componentArr = tcbComponents[i].children();
uint256 cpusvnCounter = 0;
for (uint256 j = 0; j < tcbComponents[i].size(); j++) {
JSONParserLib.Item[] memory component = componentArr[j].children();
for (uint256 k = 0; k < componentArr[j].size(); k++) {
key = JSONParserLib.decodeString(component[k].key());
if (key.eq("svn")) {
if (componentKey.eq("tdxtcbcomponents")) {
tdxSvns[cpusvnCounter++] = uint8(JSONParserLib.parseUint(component[k].value()));
} else {
sgxComponentCpuSvns[cpusvnCounter++] =
uint8(JSONParserLib.parseUint(component[k].value()));
}
}
}
}
if (cpusvnCounter != TCB_CPUSVN_SIZE) {
revert TCBInfo_Invalid();
}
}
}
}
function _parseTdxModule(JSONParserLib.Item[] memory tdxModuleObj) private pure returns (TDXModule memory module) {
for (uint256 i = 0; i < tdxModuleObj.length; i++) {
string memory key = JSONParserLib.decodeString(tdxModuleObj[i].key());
string memory val = JSONParserLib.decodeString(tdxModuleObj[i].value());
if (key.eq("attributes")) {
module.attributes = bytes8(uint64(JSONParserLib.parseUintFromHex(val)));
}
if (key.eq("attributesMask")) {
module.attributesMask = bytes8(uint64(JSONParserLib.parseUintFromHex(val)));
}
if (key.eq("mrsigner")) {
module.mrsigner = _getMrSignerHex(val);
}
}
}
function _parseTdxModuleIdentities(JSONParserLib.Item[] memory tdxModuleIdentities
Submitted on: 2025-09-30 09:45:59
Comments
Log in to comment.
No comments yet.