diff --git a/.gitmodules b/.gitmodules index d075cf60403..05b317fb278 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,10 @@ path = packages/protocol/lib/forge-std url = https://github.com/foundry-rs/forge-std branch = chore/v1.5.1 +[submodule "packages/protocol/lib/solady"] + path = packages/protocol/lib/solady + url = https://github.com/Vectorized/solady +[submodule "packages/protocol/lib/p256-verifier"] + path = packages/protocol/lib/p256-verifier + url = https://github.com/taikoxyz/p256-verifier + branch = use_at_taiko diff --git a/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol b/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol index 4b37005c9a7..ab44c3638c4 100644 --- a/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol @@ -17,6 +17,8 @@ pragma solidity 0.8.24; import "lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; import "../../common/EssentialContract.sol"; import "../../thirdparty/optimism/Bytes.sol"; +import "../../thirdparty/automata-attestation/interfaces/IAttestation.sol"; +import "../../thirdparty/automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; import "../ITaikoL1.sol"; import "./IVerifier.sol"; @@ -32,10 +34,12 @@ contract SgxVerifier is EssentialContract, IVerifier { /// bootstrapping the network with trustworthy instances. struct Instance { address addr; - uint64 addedAt; // We can calculate if expired + uint64 validSince; } - uint256 public constant INSTANCE_EXPIRY = 180 days; + uint64 public constant INSTANCE_EXPIRY = 180 days; + // A security feature, a delay until an instanace is enabled when using onchain RA verification + uint64 public constant INSTANCE_VALIDITY_DELAY = 1 days; /// @dev For gas savings, we shall assign each SGX instance with an id /// so that when we need to set a new pub key, just write storage once. @@ -52,13 +56,17 @@ contract SgxVerifier is EssentialContract, IVerifier { uint256[48] private __gap; event InstanceAdded( - uint256 indexed id, address indexed instance, address replaced, uint256 timstamp + uint256 indexed id, address indexed instance, address replaced, uint256 validSince ); event InstanceDeleted(uint256 indexed id, address indexed instance); + error SGX_DELETE_NOT_AUTHORIZED(); + error SGX_INVALID_ATTESTATION(); error SGX_INVALID_INSTANCE(); error SGX_INVALID_INSTANCES(); error SGX_INVALID_PROOF(); + error SGX_MISSING_ATTESTATION(); + error SGX_RA_NOT_SUPPORTED(); /// @notice Initializes the contract with the provided address manager. /// @param _addressManager The address of the address manager contract. @@ -75,7 +83,7 @@ contract SgxVerifier is EssentialContract, IVerifier { returns (uint256[] memory ids) { if (_instances.length == 0) revert SGX_INVALID_INSTANCES(); - ids = _addInstances(_instances); + ids = _addInstances(_instances, true); } /// @notice Deletes SGX instances from the registry. @@ -94,37 +102,27 @@ contract SgxVerifier is EssentialContract, IVerifier { } } - /// @notice Adds SGX instances to the registry by another SGX instance. - /// @param id The id of the SGX instance who is adding new members. - /// @param newInstance The new address of this instance. - /// @param extraInstances The address array of SGX instances. - /// @param signature The signature proving authenticity. - /// @return ids The respective instanceId array per addresses. - function addInstances( - uint256 id, - address newInstance, - address[] calldata extraInstances, - bytes calldata signature - ) + /// @notice Adds an SGX instance after the attestation is verified + /// @param attestation The parsed attestation quote. + /// @return id The respective instanceId + function registerInstance(V3Struct.ParsedV3QuoteStruct calldata attestation) external - returns (uint256[] memory ids) + returns (uint256) { - address taikoL1 = resolve("taiko", false); - bytes32 signedHash = keccak256( - abi.encode( - "ADD_INSTANCES", - ITaikoL1(taikoL1).getConfig().chainId, - address(this), - newInstance, - extraInstances - ) - ); - address oldInstance = ECDSA.recover(signedHash, signature); - if (!_isInstanceValid(id, oldInstance)) revert SGX_INVALID_INSTANCE(); + address automataDcapAttestation = (resolve("automata_dcap_attestation", true)); - _replaceInstance(id, oldInstance, newInstance); + if (automataDcapAttestation == address(0)) { + revert SGX_RA_NOT_SUPPORTED(); + } + + (bool verified,) = IAttestation(automataDcapAttestation).verifyParsedQuote(attestation); - ids = _addInstances(extraInstances); + if (!verified) revert SGX_INVALID_ATTESTATION(); + + address[] memory _address = new address[](1); + _address[0] = address(bytes20(attestation.localEnclaveReport.reportData)); + + return _addInstances(_address, false)[0]; } /// @inheritdoc IVerifier @@ -178,22 +176,36 @@ contract SgxVerifier is EssentialContract, IVerifier { ); } - function _addInstances(address[] calldata _instances) private returns (uint256[] memory ids) { + function _addInstances( + address[] memory _instances, + bool instantValid + ) + private + returns (uint256[] memory ids) + { ids = new uint256[](_instances.length); + uint64 validSince = uint64(block.timestamp); + + if (!instantValid) { + validSince += INSTANCE_VALIDITY_DELAY; + } + for (uint256 i; i < _instances.length; ++i) { if (_instances[i] == address(0)) revert SGX_INVALID_INSTANCE(); - instances[nextInstanceId] = Instance(_instances[i], uint64(block.timestamp)); + instances[nextInstanceId] = Instance(_instances[i], validSince); ids[i] = nextInstanceId; - emit InstanceAdded(nextInstanceId, _instances[i], address(0), block.timestamp); + emit InstanceAdded(nextInstanceId, _instances[i], address(0), validSince); nextInstanceId++; } } function _replaceInstance(uint256 id, address oldInstance, address newInstance) private { + // Replacing an instance means, it went through a cooldown (if added by on-chain RA) so no + // need to have a cooldown instances[id] = Instance(newInstance, uint64(block.timestamp)); emit InstanceAdded(id, newInstance, oldInstance, block.timestamp); } @@ -201,6 +213,6 @@ contract SgxVerifier is EssentialContract, IVerifier { function _isInstanceValid(uint256 id, address instance) private view returns (bool) { if (instance == address(0)) return false; if (instance != instances[id].addr) return false; - return instances[id].addedAt + INSTANCE_EXPIRY > block.timestamp; + return instances[id].validSince <= block.timestamp && block.timestamp <= instances[id].validSince + INSTANCE_EXPIRY; } } diff --git a/packages/protocol/contracts/thirdparty/README.md b/packages/protocol/contracts/thirdparty/README.md index bb94bcd3f5e..f6630671da4 100644 --- a/packages/protocol/contracts/thirdparty/README.md +++ b/packages/protocol/contracts/thirdparty/README.md @@ -1 +1,6 @@ +# ABOUT THIRDPARTY CODE + +- /automata-attestation: original code (main branch) forked from https://github.com/automata-network/automata-dcap-v3-attestation and applied some gas optimizations here: https://github.com/smtmfft/automata-dcap-v3-attestation/tree/optimize-gas, which then got merged into taiko-mono. + - /optimism: code copied from https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.4.3 as-is with only solidity pragma changed. + diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/AutomataDcapV3Attestation.sol b/packages/protocol/contracts/thirdparty/automata-attestation/AutomataDcapV3Attestation.sol new file mode 100644 index 00000000000..37be5772190 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/AutomataDcapV3Attestation.sol @@ -0,0 +1,650 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { V3Struct } from "./lib/QuoteV3Auth/V3Struct.sol"; +import { V3Parser } from "./lib/QuoteV3Auth/V3Parser.sol"; +import { IPEMCertChainLib } from "./lib/interfaces/IPEMCertChainLib.sol"; +import { PEMCertChainLib } from "./lib/PEMCertChainLib.sol"; +import { TCBInfoStruct } from "./lib/TCBInfoStruct.sol"; +import { EnclaveIdStruct } from "./lib/EnclaveIdStruct.sol"; +import { IAttestation } from "./interfaces/IAttestation.sol"; + +// Internal Libraries +import { Base64 } from "../../../lib/solady/src/utils/Base64.sol"; +import { LibString } from "../../../lib/solady/src/utils/LibString.sol"; +import { BytesUtils } from "./utils/BytesUtils.sol"; + +// External Libraries +import { ISigVerifyLib } from "./interfaces/ISigVerifyLib.sol"; + +// import "hardhat/console.sol"; +// import "forge-std/console.sol"; + +contract AutomataDcapV3Attestation is IAttestation { + using BytesUtils for bytes; + + ISigVerifyLib public immutable sigVerifyLib; + IPEMCertChainLib public immutable pemCertLib; + + // https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/e7604e02331b3377f3766ed3653250e03af72d45/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/X509Constants.h#L64 + uint256 constant CPUSVN_LENGTH = 16; + + // keccak256(hex"0ba9c4c0c0c86193a3fe23d6b02cda10a8bbd4e88e48b4458561a36e705525f567918e2edc88e40d860bd0cc4ee26aacc988e505a953558c453f6b0904ae7394") + // the uncompressed (0x04) prefix is not included in the pubkey pre-image + bytes32 constant ROOTCA_PUBKEY_HASH = + 0x89f72d7c488e5b53a77c23ebcb36970ef7eb5bcf6658e9b8292cfbe4703a8473; + + uint8 constant INVALID_EXIT_CODE = 255; + + bool private checkLocalEnclaveReport; + mapping(bytes32 => bool) private trustedUserMrEnclave; + mapping(bytes32 => bool) private trustedUserMrSigner; + + // Quote Collateral Configuration + + // Index definition: + // 0 = Quote PCKCrl + // 1 = RootCrl + mapping(uint256 => mapping(bytes => bool)) private serialNumIsRevoked; + // fmspc => tcbInfo + mapping(string => TCBInfoStruct.TCBInfo) public tcbInfo; + EnclaveIdStruct.EnclaveId public qeIdentity; + + address public owner; + + constructor(address sigVerifyLibAddr, address pemCertLibAddr) { + sigVerifyLib = ISigVerifyLib(sigVerifyLibAddr); + pemCertLib = PEMCertChainLib(pemCertLibAddr); + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner, "onlyOwner"); + _; + } + + function setMrSigner(bytes32 _mrSigner, bool _trusted) external onlyOwner { + trustedUserMrSigner[_mrSigner] = _trusted; + } + + function setMrEnclave(bytes32 _mrEnclave, bool _trusted) external onlyOwner { + trustedUserMrEnclave[_mrEnclave] = _trusted; + } + + function addRevokedCertSerialNum( + uint256 index, + bytes[] calldata serialNumBatch + ) + external + onlyOwner + { + for (uint256 i = 0; i < serialNumBatch.length; i++) { + if (serialNumIsRevoked[index][serialNumBatch[i]]) { + continue; + } + serialNumIsRevoked[index][serialNumBatch[i]] = true; + } + } + + function removeRevokedCertSerialNum( + uint256 index, + bytes[] calldata serialNumBatch + ) + external + onlyOwner + { + for (uint256 i = 0; i < serialNumBatch.length; i++) { + if (!serialNumIsRevoked[index][serialNumBatch[i]]) { + continue; + } + delete serialNumIsRevoked[index][serialNumBatch[i]]; + } + } + + function configureTcbInfoJson( + string calldata fmspc, + TCBInfoStruct.TCBInfo calldata tcbInfoInput + ) + public + onlyOwner + { + // 2.2M gas + tcbInfo[fmspc] = tcbInfoInput; + } + + function configureQeIdentityJson(EnclaveIdStruct.EnclaveId calldata qeIdentityInput) + external + onlyOwner + { + // 250k gas + qeIdentity = qeIdentityInput; + } + + function toggleLocalReportCheck() external onlyOwner { + checkLocalEnclaveReport = !checkLocalEnclaveReport; + } + + function _attestationTcbIsValid(TCBInfoStruct.TCBStatus status) + internal + pure + virtual + returns (bool valid) + { + return status == TCBInfoStruct.TCBStatus.OK + || status == TCBInfoStruct.TCBStatus.TCB_SW_HARDENING_NEEDED + || status == TCBInfoStruct.TCBStatus.TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED + || status == TCBInfoStruct.TCBStatus.TCB_OUT_OF_DATE_CONFIGURATION_NEEDED; + } + + function verifyAttestation(bytes calldata data) external view returns (bool) { + (bool success,) = _verify(data); + return success; + } + + /// @dev Provide the raw quote binary as input + /// @dev The attestation data (or the returned data of this method) + /// is constructed depending on the validity of the quote verification. + /// @dev After confirming that a quote has been verified, the attestation's validity then + /// depends on the + /// status of the associated TCB. + /// @dev Example scenarios as below: + /// -------------------------------- + /// @dev Invalid quote verification: returns (false, INVALID_EXIT_CODE) + /// + /// @dev For all valid quote verification, the validity of the attestation depends on the status + /// of a + /// matching TCBInfo and this is defined in the _attestationTcbIsValid() method, which can be + /// overwritten + /// in derived contracts. (Except for "Revoked" status, which also returns (false, + /// INVALID_EXIT_CODE) value) + /// @dev For all valid quote verification, returns the following data: + /// (_attestationTcbIsValid(), abi.encodePacked(sha256(quote), uint8 exitCode)) + /// @dev exitCode is defined in the {{ TCBInfoStruct.TCBStatus }} enum + function _verify(bytes calldata quote) private view returns (bool, bytes memory) { + bytes memory retData = abi.encodePacked(INVALID_EXIT_CODE); + + // Step 1: Parse the quote input = 152k gas + ( + bool successful, + , + V3Struct.EnclaveReport memory localEnclaveReport, + bytes memory signedQuoteData, + V3Struct.ECDSAQuoteV3AuthData memory authDataV3 + ) = V3Parser.parseInput(quote); + if (!successful) { + return (false, retData); + } + + // Step 2: Verify application enclave report MRENCLAVE and MRSIGNER + { + if (checkLocalEnclaveReport) { + // 4k gas + bool mrEnclaveIsTrusted = trustedUserMrEnclave[localEnclaveReport.mrEnclave]; + bool mrSignerIsTrusted = trustedUserMrSigner[localEnclaveReport.mrSigner]; + + if (!mrEnclaveIsTrusted || !mrSignerIsTrusted) { + return (false, retData); + } + } + } + + // Step 3: Verify enclave identity = 43k gas + V3Struct.EnclaveReport memory qeEnclaveReport; + EnclaveIdStruct.EnclaveIdStatus qeTcbStatus; + { + qeEnclaveReport = V3Parser.parseEnclaveReport(authDataV3.rawQeReport); + bool verifiedEnclaveIdSuccessfully; + (verifiedEnclaveIdSuccessfully, qeTcbStatus) = + _verifyQEReportWithIdentity(qeEnclaveReport); + if (!verifiedEnclaveIdSuccessfully) { + return (false, retData); + } + if ( + !verifiedEnclaveIdSuccessfully + || qeTcbStatus == EnclaveIdStruct.EnclaveIdStatus.SGX_ENCLAVE_REPORT_ISVSVN_REVOKED + ) { + return (false, retData); + } + } + + // Step 4: Parse Quote CertChain + IPEMCertChainLib.ECSha256Certificate[] memory parsedQuoteCerts; + TCBInfoStruct.TCBInfo memory fetchedTcbInfo; + { + // 660k gas + (bool certParsedSuccessfully, bytes[] memory quoteCerts) = + pemCertLib.splitCertificateChain(authDataV3.certification.certData, 3); + if (!certParsedSuccessfully) { + return (false, retData); + } + + // 536k gas + parsedQuoteCerts = new IPEMCertChainLib.ECSha256Certificate[](3); + for (uint256 i = 0; i < 3; i++) { + quoteCerts[i] = Base64.decode(string(quoteCerts[i])); + //console.log("Step 4.%s: Parse Quote parsedQuoteCerts", i); + //console.logBytes(quoteCerts[i]); + bool isPckCert = i == 0; // additional parsing for PCKCert + bool certDecodedSuccessfully; + (certDecodedSuccessfully, parsedQuoteCerts[i]) = + pemCertLib.decodeCert(quoteCerts[i], isPckCert); + if (!certDecodedSuccessfully) { + return (false, retData); + } + } + } + + // Step 5: basic PCK and TCB check = 381k gas + { + string memory parsedFmspc = parsedQuoteCerts[0].pck.sgxExtension.fmspc; + fetchedTcbInfo = tcbInfo[parsedFmspc]; + bool tcbConfigured = LibString.eq(parsedFmspc, fetchedTcbInfo.fmspc); + if (!tcbConfigured) { + return (false, retData); + } + + IPEMCertChainLib.ECSha256Certificate memory pckCert = parsedQuoteCerts[0]; + bool pceidMatched = LibString.eq(pckCert.pck.sgxExtension.pceid, fetchedTcbInfo.pceid); + if (!pceidMatched) { + return (false, retData); + } + } + + // Step 6: Verify TCB Level + TCBInfoStruct.TCBStatus tcbStatus; + { + // 4k gas + bool tcbVerified; + (tcbVerified, tcbStatus) = _checkTcbLevels(parsedQuoteCerts[0].pck, fetchedTcbInfo); + if (!tcbVerified) { + return (false, retData); + } + } + + // Step 7: Verify cert chain for PCK + { + // 660k gas (rootCA pubkey is trusted) + bool pckCertChainVerified = _verifyCertChain(parsedQuoteCerts); + if (!pckCertChainVerified) { + return (false, retData); + } + } + + // Step 8: Verify the local attestation sig and qe report sig = 670k gas + { + bool enclaveReportSigsVerified = _enclaveReportSigVerification( + parsedQuoteCerts[0].pubKey, signedQuoteData, authDataV3, qeEnclaveReport + ); + if (!enclaveReportSigsVerified) { + return (false, retData); + } + } + + retData = abi.encodePacked(sha256(quote), tcbStatus); + + return (_attestationTcbIsValid(tcbStatus), retData); + } + + function _verifyQEReportWithIdentity(V3Struct.EnclaveReport memory quoteEnclaveReport) + private + view + returns (bool, EnclaveIdStruct.EnclaveIdStatus status) + { + EnclaveIdStruct.EnclaveId memory enclaveId = qeIdentity; + bool miscselectMatched = + quoteEnclaveReport.miscSelect & enclaveId.miscselectMask == enclaveId.miscselect; + + bool attributesMatched = + quoteEnclaveReport.attributes & enclaveId.attributesMask == enclaveId.attributes; + bool mrsignerMatched = quoteEnclaveReport.mrSigner == enclaveId.mrsigner; + + bool isvprodidMatched = quoteEnclaveReport.isvProdId == enclaveId.isvprodid; + + bool tcbFound; + for (uint256 i = 0; i < enclaveId.tcbLevels.length; i++) { + EnclaveIdStruct.TcbLevel memory tcb = enclaveId.tcbLevels[i]; + if (tcb.tcb.isvsvn <= quoteEnclaveReport.isvSvn) { + tcbFound = true; + status = tcb.tcbStatus; + break; + } + } + return ( + miscselectMatched && attributesMatched && mrsignerMatched && isvprodidMatched + && tcbFound, + status + ); + } + + function _checkTcbLevels( + IPEMCertChainLib.PCKCertificateField memory pck, + TCBInfoStruct.TCBInfo memory tcb + ) + private + pure + returns (bool, TCBInfoStruct.TCBStatus status) + { + for (uint256 i = 0; i < tcb.tcbLevels.length; i++) { + TCBInfoStruct.TCBLevelObj memory current = tcb.tcbLevels[i]; + bool pceSvnIsHigherOrGreater = pck.sgxExtension.pcesvn >= current.pcesvn; + bool cpuSvnsAreHigherOrGreater = _isCpuSvnHigherOrGreater( + pck.sgxExtension.sgxTcbCompSvnArr, current.sgxTcbCompSvnArr + ); + if (pceSvnIsHigherOrGreater && cpuSvnsAreHigherOrGreater) { + status = current.status; + bool tcbIsRevoked = status == TCBInfoStruct.TCBStatus.TCB_REVOKED; + return (!tcbIsRevoked, status); + } + } + return (true, TCBInfoStruct.TCBStatus.TCB_UNRECOGNIZED); + } + + function _isCpuSvnHigherOrGreater( + uint256[] memory pckCpuSvns, + uint8[] memory tcbCpuSvns + ) + private + pure + returns (bool) + { + if (pckCpuSvns.length != CPUSVN_LENGTH || tcbCpuSvns.length != CPUSVN_LENGTH) { + return false; + } + for (uint256 i = 0; i < CPUSVN_LENGTH; i++) { + if (pckCpuSvns[i] < tcbCpuSvns[i]) { + return false; + } + } + return true; + } + + function _verifyCertChain(IPEMCertChainLib.ECSha256Certificate[] memory certs) + private + view + returns (bool) + { + uint256 n = certs.length; + bool certRevoked; + bool certNotExpired; + bool verified; + bool certChainCanBeTrusted; + + for (uint256 i = 0; i < n; i++) { + IPEMCertChainLib.ECSha256Certificate memory issuer; + if (i == n - 1) { + // rootCA + issuer = certs[i]; + } else { + issuer = certs[i + 1]; + if (i == n - 2) { + // this cert is expected to be signed by the root + certRevoked = serialNumIsRevoked[uint256(IPEMCertChainLib.CRL.ROOT)][certs[i] + .serialNumber]; + } else if (certs[i].isPck) { + certRevoked = + serialNumIsRevoked[uint256(IPEMCertChainLib.CRL.PCK)][certs[i].serialNumber]; + } + if (certRevoked) { + break; + } + } + + certNotExpired = + block.timestamp > certs[i].notBefore && block.timestamp < certs[i].notAfter; + if (!certNotExpired) { + break; + } + + verified = sigVerifyLib.verifyES256Signature( + certs[i].tbsCertificate, certs[i].signature, issuer.pubKey + ); + if (!verified) { + break; + } + + bytes32 issuerPubKeyHash = keccak256(issuer.pubKey); + + if (issuerPubKeyHash == ROOTCA_PUBKEY_HASH) { + certChainCanBeTrusted = true; + break; + } + } + + return !certRevoked && certNotExpired && verified && certChainCanBeTrusted; + } + + function _enclaveReportSigVerification( + bytes memory pckCertPubKey, + bytes memory signedQuoteData, + V3Struct.ECDSAQuoteV3AuthData memory authDataV3, + V3Struct.EnclaveReport memory qeEnclaveReport + ) + private + view + returns (bool) + { + bytes32 expectedAuthDataHash = bytes32(qeEnclaveReport.reportData.substring(0, 32)); + bytes memory concatOfAttestKeyAndQeAuthData = + abi.encodePacked(authDataV3.ecdsaAttestationKey, authDataV3.qeAuthData.data); + bytes32 computedAuthDataHash = sha256(concatOfAttestKeyAndQeAuthData); + + bool qeReportDataIsValid = expectedAuthDataHash == computedAuthDataHash; + if (qeReportDataIsValid) { + bool qeSigVerified = sigVerifyLib.verifyES256Signature( + authDataV3.rawQeReport, authDataV3.qeReportSignature, pckCertPubKey + ); + bool quoteSigVerified = sigVerifyLib.verifyES256Signature( + signedQuoteData, authDataV3.ecdsa256BitSignature, authDataV3.ecdsaAttestationKey + ); + return qeSigVerified && quoteSigVerified; + } else { + return false; + } + } + + function _enclaveParsedReportSigVerification( + bytes memory pckCertPubKey, + bytes memory signedQuoteData, + V3Struct.ParsedECDSAQuoteV3AuthData memory authDataV3, + V3Struct.EnclaveReport memory qeEnclaveReport + ) + private + view + returns (bool) + { + bytes32 expectedAuthDataHash = bytes32(qeEnclaveReport.reportData.substring(0, 32)); + bytes memory concatOfAttestKeyAndQeAuthData = + abi.encodePacked(authDataV3.ecdsaAttestationKey, authDataV3.qeAuthData.data); + bytes32 computedAuthDataHash = sha256(concatOfAttestKeyAndQeAuthData); + + bool qeReportDataIsValid = expectedAuthDataHash == computedAuthDataHash; + if (qeReportDataIsValid) { + bytes memory pckSignedQeReportBytes = + V3Parser.packQEReport(authDataV3.pckSignedQeReport); + bool qeSigVerified = sigVerifyLib.verifyES256Signature( + pckSignedQeReportBytes, authDataV3.qeReportSignature, pckCertPubKey + ); + // console.log("qeSigVerified = %s", qeSigVerified); + bool quoteSigVerified = sigVerifyLib.verifyES256Signature( + signedQuoteData, authDataV3.ecdsa256BitSignature, authDataV3.ecdsaAttestationKey + ); + // console.log("quoteSigVerified = %s", quoteSigVerified); + // console.logBytes(signedQuoteData); + // console.logBytes(authDataV3.ecdsa256BitSignature); + // console.logBytes(authDataV3.ecdsaAttestationKey); + return qeSigVerified && quoteSigVerified; + } else { + return false; + } + } + + /// --------------- validate parsed quote --------------- + + /// @dev Provide the parsed quote binary as input + /// @dev The attestation data (or the returned data of this method) + /// is constructed depending on the validity of the quote verification. + /// @dev After confirming that a quote has been verified, the attestation's validity then + /// depends on the + /// status of the associated TCB. + /// @dev Example scenarios as below: + /// -------------------------------- + /// @dev Invalid quote verification: returns (false, INVALID_EXIT_CODE) + /// + /// @dev For all valid quote verification, the validity of the attestation depends on the status + /// of a + /// matching TCBInfo and this is defined in the _attestationTcbIsValid() method, which can be + /// overwritten + /// in derived contracts. (Except for "Revoked" status, which also returns (false, + /// INVALID_EXIT_CODE) value) + /// @dev For all valid quote verification, returns the following data: + /// (_attestationTcbIsValid()) + /// @dev exitCode is defined in the {{ TCBInfoStruct.TCBStatus }} enum + function verifyParsedQuote(V3Struct.ParsedV3QuoteStruct calldata v3quote) + external + view + returns (bool success, uint8 exitStep) + { + success = false; + exitStep = 1; + + // Step 1: Parse the quote input = 152k gas + // console.log("Step 1: Parse the quote input = 152k gas"); + // todo: validate(v3quote) + ( + bool successful, + , + , + bytes memory signedQuoteData, + V3Struct.ParsedECDSAQuoteV3AuthData memory authDataV3 + ) = V3Parser.validateParsedInput(v3quote); + if (!successful) { + return (false, exitStep); + } + + exitStep += 1; + // Step 2: Verify application enclave report MRENCLAVE and MRSIGNER + // console.log( + // "Step 2: Verify application enclave report MRENCLAVE and MRSIGNER" + // ); + { + if (checkLocalEnclaveReport) { + // 4k gas + bool mrEnclaveIsTrusted = trustedUserMrEnclave[v3quote.localEnclaveReport.mrEnclave]; + bool mrSignerIsTrusted = trustedUserMrSigner[v3quote.localEnclaveReport.mrSigner]; + + if (!mrEnclaveIsTrusted || !mrSignerIsTrusted) { + return (false, exitStep); + } + } + } + + exitStep += 1; + // console.log("Step 3: Verify enclave identity = 43k gas"); + // Step 3: Verify enclave identity = 43k gas + EnclaveIdStruct.EnclaveIdStatus qeTcbStatus; + { + bool verifiedEnclaveIdSuccessfully; + (verifiedEnclaveIdSuccessfully, qeTcbStatus) = + _verifyQEReportWithIdentity(v3quote.v3AuthData.pckSignedQeReport); + if (!verifiedEnclaveIdSuccessfully) { + return (false, exitStep); + } + if ( + !verifiedEnclaveIdSuccessfully + || qeTcbStatus == EnclaveIdStruct.EnclaveIdStatus.SGX_ENCLAVE_REPORT_ISVSVN_REVOKED + ) { + return (false, exitStep); + } + } + + exitStep += 1; + // console.log("Step 4: Parse Quote CertChain"); + // Step 4: Parse Quote CertChain + IPEMCertChainLib.ECSha256Certificate[] memory parsedQuoteCerts; + TCBInfoStruct.TCBInfo memory fetchedTcbInfo; + { + // 536k gas + parsedQuoteCerts = new IPEMCertChainLib.ECSha256Certificate[](3); + for (uint256 i = 0; i < 3; i++) { + //console.log("Step 4.%s: Parse Quote parsedQuoteCerts", i); + // quoteCerts[i] = Base64.decode(string(authDataV3.certification.certArray[i])); + bool isPckCert = i == 0; // additional parsing for PCKCert + bool certDecodedSuccessfully; + // todo! move decodeCert offchain + (certDecodedSuccessfully, parsedQuoteCerts[i]) = pemCertLib.decodeCert( + authDataV3.certification.decodedCertDataArray[i], isPckCert + ); + if (!certDecodedSuccessfully) { + return (false, exitStep); + } + } + } + + exitStep += 1; + // console.log("Step 5: basic PCK and TCB check = 381k gas"); + // Step 5: basic PCK and TCB check = 381k gas + { + string memory parsedFmspc = parsedQuoteCerts[0].pck.sgxExtension.fmspc; + fetchedTcbInfo = tcbInfo[parsedFmspc]; + bool tcbConfigured = LibString.eq(parsedFmspc, fetchedTcbInfo.fmspc); + if (!tcbConfigured) { + return (false, exitStep); + } + + IPEMCertChainLib.ECSha256Certificate memory pckCert = parsedQuoteCerts[0]; + bool pceidMatched = LibString.eq(pckCert.pck.sgxExtension.pceid, fetchedTcbInfo.pceid); + if (!pceidMatched) { + return (false, exitStep); + } + } + + exitStep += 1; + // console.log("Step 6: Verify TCB Level"); + // Step 6: Verify TCB Level + TCBInfoStruct.TCBStatus tcbStatus; + { + // 4k gas + bool tcbVerified; + (tcbVerified, tcbStatus) = _checkTcbLevels(parsedQuoteCerts[0].pck, fetchedTcbInfo); + if (!tcbVerified) { + return (false, exitStep); + } + } + + exitStep += 1; + // console.log("Step 7: Verify cert chain for PCK"); + // Step 7: Verify cert chain for PCK + { + // 660k gas (rootCA pubkey is trusted) + bool pckCertChainVerified = _verifyCertChain(parsedQuoteCerts); + if (!pckCertChainVerified) { + return (false, exitStep); + } + } + + exitStep += 1; + // console.log( + // "Step 8: Verify the local attestation sig and qe report sig = 670k gas" + // ); + // Step 8: Verify the local attestation sig and qe report sig + { + bool enclaveReportSigsVerified = _enclaveParsedReportSigVerification( + parsedQuoteCerts[0].pubKey, + signedQuoteData, + authDataV3, + v3quote.v3AuthData.pckSignedQeReport + ); + if (!enclaveReportSigsVerified) { + return (false, exitStep); + } + } + + // retData = abi.encodePacked( + // sha256(abi.encodePacked(v3quote)), + // tcbStatus + // ); + exitStep += 1; + success = _attestationTcbIsValid(tcbStatus); + // console.log("Step 9: return success = %s, tcbStatus = %s", success, uint256(tcbStatus)); + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/interfaces/IAttestation.sol b/packages/protocol/contracts/thirdparty/automata-attestation/interfaces/IAttestation.sol new file mode 100644 index 00000000000..95494455d18 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/interfaces/IAttestation.sol @@ -0,0 +1,11 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import { V3Struct } from "../lib/QuoteV3Auth/V3Struct.sol"; + +interface IAttestation { + function verifyAttestation(bytes calldata data) external returns (bool); + function verifyParsedQuote(V3Struct.ParsedV3QuoteStruct calldata v3quote) + external + returns (bool success, uint8 exitStep); +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/interfaces/ISigVerifyLib.sol b/packages/protocol/contracts/thirdparty/automata-attestation/interfaces/ISigVerifyLib.sol new file mode 100644 index 00000000000..0ab36429783 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/interfaces/ISigVerifyLib.sol @@ -0,0 +1,82 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface ISigVerifyLib { + enum KeyType { + RSA, + ECDSA + } + + struct PublicKey { + KeyType keyType; + // If RSA, pubKey = abi.encodePacked(exponent, modulus) + // If ECDSA, pubKey = abi.encodePacked(gx, gy) + bytes pubKey; + } + + enum CertSigAlgorithm { + Sha256WithRSAEncryption, + Sha1WithRSAEncryption + } + + struct Certificate { + // Asn.1 DER encoding of the to-be-signed certificate + bytes tbsCertificate; + PublicKey publicKey; + bytes signature; + CertSigAlgorithm sigAlg; + } + + enum Algorithm { + RS256, + ES256, + RS1 + } + + function verifyAttStmtSignature( + bytes memory tbs, + bytes memory signature, + PublicKey memory publicKey, + Algorithm alg + ) + external + view + returns (bool); + + function verifyCertificateSignature( + bytes memory tbs, + bytes memory signature, + PublicKey memory publicKey, + CertSigAlgorithm alg + ) + external + view + returns (bool); + + function verifyRS256Signature( + bytes memory tbs, + bytes memory signature, + bytes memory publicKey + ) + external + view + returns (bool sigValid); + + function verifyRS1Signature( + bytes memory tbs, + bytes memory signature, + bytes memory publicKey + ) + external + view + returns (bool sigValid); + + function verifyES256Signature( + bytes memory tbs, + bytes memory signature, + bytes memory publicKey + ) + external + view + returns (bool sigValid); +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/lib/EnclaveIdStruct.sol b/packages/protocol/contracts/thirdparty/automata-attestation/lib/EnclaveIdStruct.sol new file mode 100644 index 00000000000..fe20f2f1054 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/lib/EnclaveIdStruct.sol @@ -0,0 +1,28 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +library EnclaveIdStruct { + struct EnclaveId { + bytes4 miscselect; + bytes4 miscselectMask; + uint16 isvprodid; + bytes16 attributes; + bytes16 attributesMask; + bytes32 mrsigner; + TcbLevel[] tcbLevels; + } + + struct TcbLevel { + TcbObj tcb; + EnclaveIdStatus tcbStatus; + } + + struct TcbObj { + uint16 isvsvn; + } + + enum EnclaveIdStatus { + OK, + SGX_ENCLAVE_REPORT_ISVSVN_REVOKED + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/lib/PEMCertChainLib.sol b/packages/protocol/contracts/thirdparty/automata-attestation/lib/PEMCertChainLib.sol new file mode 100644 index 00000000000..e35ee5f46fe --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/lib/PEMCertChainLib.sol @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { LibString } from "../../../../lib/solady/src/utils/LibString.sol"; +import { Asn1Decode, NodePtr } from "../utils/Asn1Decode.sol"; +import { BytesUtils } from "../utils/BytesUtils.sol"; +import { X509DateUtils } from "../utils/X509DateUtils.sol"; +import { IPEMCertChainLib } from "./interfaces/IPEMCertChainLib.sol"; + +contract PEMCertChainLib is IPEMCertChainLib { + using Asn1Decode for bytes; + using NodePtr for uint256; + using BytesUtils for bytes; + + string constant HEADER = "-----BEGIN CERTIFICATE-----"; + string constant FOOTER = "-----END CERTIFICATE-----"; + uint256 internal constant HEADER_LENGTH = 27; + uint256 internal constant FOOTER_LENGTH = 25; + + string constant PCK_COMMON_NAME = "Intel SGX PCK Certificate"; + string constant PLATFORM_ISSUER_NAME = "Intel SGX PCK Platform CA"; + string constant PROCESSOR_ISSUER_NAME = "Intel SGX PCK Processor CA"; + bytes constant SGX_EXTENSION_OID = hex"2A864886F84D010D01"; + bytes constant TCB_OID = hex"2A864886F84D010D0102"; + bytes constant PCESVN_OID = hex"2A864886F84D010D010211"; + bytes constant PCEID_OID = hex"2A864886F84D010D0103"; + bytes constant FMSPC_OID = hex"2A864886F84D010D0104"; + + // https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/e7604e02331b3377f3766ed3653250e03af72d45/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/X509Constants.h#L64 + uint256 constant SGX_TCB_CPUSVN_SIZE = 16; + + struct PCKTCBFlags { + bool fmspcFound; + bool pceidFound; + bool tcbFound; + } + + function splitCertificateChain( + bytes memory pemChain, + uint256 size + ) + external + pure + returns (bool success, bytes[] memory certs) + { + certs = new bytes[](size); + string memory pemChainStr = string(pemChain); + + uint256 index = 0; + uint256 len = pemChain.length; + + for (uint256 i = 0; i < size; i++) { + string memory input; + if (i > 0) { + input = LibString.slice(pemChainStr, index, index + len); + } else { + input = pemChainStr; + } + uint256 increment; + (success, certs[i], increment) = _removeHeadersAndFooters(input); + + if (!success) { + return (false, certs); + } + + index += increment; + } + + success = true; + } + + function decodeCert( + bytes memory der, + bool isPckCert + ) + external + pure + returns (bool success, ECSha256Certificate memory cert) + { + uint256 root = der.root(); + + // Entering tbsCertificate sequence + uint256 tbsParentPtr = der.firstChildOf(root); + + // Begin iterating through the descendants of tbsCertificate + uint256 tbsPtr = der.firstChildOf(tbsParentPtr); + + // The Serial Number is located one element below Version + + // The issuer commonName value is contained in the Issuer sequence + // which is 3 elements below the first element of the tbsCertificate sequence + + // The Validity sequence is located 4 elements below the first element of the tbsCertificate + // sequence + + // The subject commanName value is contained in the Subject sequence + // which is 5 elements below the first element of the tbsCertificate sequence + + // The PublicKey is located in the second element of subjectPublicKeyInfo sequence + // which is 6 elements below the first element of the tbsCertificate sequence + + tbsPtr = der.nextSiblingOf(tbsPtr); + + { + bytes memory serialNumBytes = der.bytesAt(tbsPtr); + cert.serialNumber = serialNumBytes; + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + tbsPtr = der.nextSiblingOf(tbsPtr); + + if (isPckCert) { + uint256 issuerPtr = der.firstChildOf(tbsPtr); + issuerPtr = der.firstChildOf(issuerPtr); + issuerPtr = der.firstChildOf(issuerPtr); + issuerPtr = der.nextSiblingOf(issuerPtr); + cert.pck.issuerName = string(der.bytesAt(issuerPtr)); + bool issuerNameIsValid = LibString.eq(cert.pck.issuerName, PLATFORM_ISSUER_NAME) + || LibString.eq(cert.pck.issuerName, PROCESSOR_ISSUER_NAME); + if (!issuerNameIsValid) { + return (false, cert); + } + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + + { + uint256 notBeforePtr = der.firstChildOf(tbsPtr); + uint256 notAfterPtr = der.nextSiblingOf(notBeforePtr); + bytes1 notBeforeTag = der[notBeforePtr.ixs()]; + bytes1 notAfterTag = der[notAfterPtr.ixs()]; + if ( + (notBeforeTag != 0x17 && notBeforeTag == 0x18) + || (notAfterTag != 0x17 && notAfterTag != 0x18) + ) { + return (false, cert); + } + cert.notBefore = X509DateUtils.toTimestamp(der.bytesAt(notBeforePtr)); + cert.notAfter = X509DateUtils.toTimestamp(der.bytesAt(notAfterPtr)); + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + + if (isPckCert) { + uint256 subjectPtr = der.firstChildOf(tbsPtr); + subjectPtr = der.firstChildOf(subjectPtr); + subjectPtr = der.firstChildOf(subjectPtr); + subjectPtr = der.nextSiblingOf(subjectPtr); + cert.pck.commonName = string(der.bytesAt(subjectPtr)); + if (!LibString.eq(cert.pck.commonName, PCK_COMMON_NAME)) { + return (false, cert); + } + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + + { + // Entering subjectPublicKeyInfo sequence + uint256 subjectPublicKeyInfoPtr = der.firstChildOf(tbsPtr); + subjectPublicKeyInfoPtr = der.nextSiblingOf(subjectPublicKeyInfoPtr); + + // The Signature sequence is located two sibling elements below the tbsCertificate + // element + uint256 sigPtr = der.nextSiblingOf(tbsParentPtr); + sigPtr = der.nextSiblingOf(sigPtr); + + // Skip three bytes to the right, TODO: why is it tagged with 0x03? + // the three bytes in question: 0x034700 or 0x034800 or 0x034900 + sigPtr = NodePtr.getPtr(sigPtr.ixs() + 3, sigPtr.ixf() + 3, sigPtr.ixl()); + + sigPtr = der.firstChildOf(sigPtr); + bytes memory sigX = _trimBytes(der.bytesAt(sigPtr), 32); + + sigPtr = der.nextSiblingOf(sigPtr); + bytes memory sigY = _trimBytes(der.bytesAt(sigPtr), 32); + + cert.tbsCertificate = der.allBytesAt(tbsParentPtr); + cert.pubKey = _trimBytes(der.bytesAt(subjectPublicKeyInfoPtr), 64); + cert.signature = abi.encodePacked(sigX, sigY); + } + + if (isPckCert) { + // entering Extension sequence + tbsPtr = der.nextSiblingOf(tbsPtr); + + // check for the extension tag + if (der[tbsPtr.ixs()] != 0xA3) { + return (false, cert); + } + + tbsPtr = der.firstChildOf(tbsPtr); + tbsPtr = der.firstChildOf(tbsPtr); + + bool sgxExtnTraversedSuccessfully; + uint256 pcesvn; + uint256[] memory cpuSvns; + bytes memory fmspcBytes; + bytes memory pceidBytes; + (sgxExtnTraversedSuccessfully, pcesvn, cpuSvns, fmspcBytes, pceidBytes) = + _findPckTcbInfo(der, tbsPtr, tbsParentPtr); + if (!sgxExtnTraversedSuccessfully) { + return (false, cert); + } + cert.pck.sgxExtension.pcesvn = pcesvn; + cert.pck.sgxExtension.sgxTcbCompSvnArr = cpuSvns; + cert.pck.sgxExtension.pceid = LibString.toHexStringNoPrefix(pceidBytes); + cert.pck.sgxExtension.fmspc = LibString.toHexStringNoPrefix(fmspcBytes); + cert.isPck = true; + } + + success = true; + } + + function _removeHeadersAndFooters(string memory pemData) + private + pure + returns (bool success, bytes memory extracted, uint256 endIndex) + { + // Check if the input contains the "BEGIN" and "END" headers + uint256 beginPos = LibString.indexOf(pemData, HEADER); + uint256 endPos = LibString.indexOf(pemData, FOOTER); + + bool headerFound = beginPos != LibString.NOT_FOUND; + bool footerFound = endPos != LibString.NOT_FOUND; + + if (!headerFound || !footerFound) { + return (false, extracted, endIndex); + } + + // Extract the content between the headers + uint256 contentStart = beginPos + HEADER_LENGTH; + + // Extract and return the content + bytes memory contentBytes; + + // do not include newline + bytes memory delimiter = hex"0a"; + string memory contentSlice = LibString.slice(pemData, contentStart, endPos); + string[] memory split = LibString.split(contentSlice, string(delimiter)); + string memory contentStr; + + for (uint256 i = 0; i < split.length; i++) { + contentStr = LibString.concat(contentStr, split[i]); + } + + contentBytes = bytes(contentStr); + return (true, contentBytes, endPos + FOOTER_LENGTH); + } + + function _trimBytes( + bytes memory input, + uint256 expectedLength + ) + private + pure + returns (bytes memory output) + { + uint256 n = input.length; + + if (n <= expectedLength) { + return input; + } + uint256 lengthDiff = n - expectedLength; + output = input.substring(lengthDiff, expectedLength); + } + + function _findPckTcbInfo( + bytes memory der, + uint256 tbsPtr, + uint256 tbsParentPtr + ) + private + pure + returns ( + bool success, + uint256 pcesvn, + uint256[] memory cpusvns, + bytes memory fmspcBytes, + bytes memory pceidBytes + ) + { + // iterate through the elements in the Extension sequence + // until we locate the SGX Extension OID + while (tbsPtr != 0) { + uint256 internalPtr = der.firstChildOf(tbsPtr); + if (der[internalPtr.ixs()] != 0x06) { + return (false, pcesvn, cpusvns, fmspcBytes, pceidBytes); + } + + if (BytesUtils.compareBytes(der.bytesAt(internalPtr), SGX_EXTENSION_OID)) { + // 1.2.840.113741.1.13.1 + internalPtr = der.nextSiblingOf(internalPtr); + uint256 extnValueParentPtr = der.rootOfOctetStringAt(internalPtr); + uint256 extnValuePtr = der.firstChildOf(extnValueParentPtr); + + // Copy flags to memory to avoid stack too deep + PCKTCBFlags memory flags; + + while (!(flags.fmspcFound && flags.pceidFound && flags.tcbFound)) { + uint256 extnValueOidPtr = der.firstChildOf(extnValuePtr); + if (der[extnValueOidPtr.ixs()] != 0x06) { + return (false, pcesvn, cpusvns, fmspcBytes, pceidBytes); + } + if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), TCB_OID)) { + // 1.2.840.113741.1.13.1.2 + (flags.tcbFound, pcesvn, cpusvns) = _findTcb(der, extnValueOidPtr); + } + if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), PCEID_OID)) { + // 1.2.840.113741.1.13.1.3 + uint256 pceidPtr = der.nextSiblingOf(extnValueOidPtr); + pceidBytes = der.bytesAt(pceidPtr); + flags.pceidFound = true; + } + if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), FMSPC_OID)) { + // 1.2.840.113741.1.13.1.4 + uint256 fmspcPtr = der.nextSiblingOf(extnValueOidPtr); + fmspcBytes = der.bytesAt(fmspcPtr); + flags.fmspcFound = true; + } + + if (extnValuePtr.ixl() < extnValueParentPtr.ixl()) { + extnValuePtr = der.nextSiblingOf(extnValuePtr); + } else { + break; + } + } + success = flags.fmspcFound && flags.pceidFound && flags.tcbFound; + break; + } + + if (tbsPtr.ixl() < tbsParentPtr.ixl()) { + tbsPtr = der.nextSiblingOf(tbsPtr); + } else { + tbsPtr = 0; // exit + } + } + } + + function _findTcb( + bytes memory der, + uint256 oidPtr + ) + private + pure + returns (bool success, uint256 pcesvn, uint256[] memory cpusvns) + { + // sibiling of tcbOid + uint256 tcbPtr = der.nextSiblingOf(oidPtr); + // get the first svn object in the sequence + uint256 svnParentPtr = der.firstChildOf(tcbPtr); + cpusvns = new uint256[](SGX_TCB_CPUSVN_SIZE); + for (uint256 i = 0; i < SGX_TCB_CPUSVN_SIZE + 1; i++) { + uint256 svnPtr = der.firstChildOf(svnParentPtr); // OID + uint256 svnValuePtr = der.nextSiblingOf(svnPtr); // value + bytes memory svnValueBytes = der.bytesAt(svnValuePtr); + uint16 svnValue = svnValueBytes.length < 2 + ? uint16(bytes2(svnValueBytes)) / 256 + : uint16(bytes2(svnValueBytes)); + if (BytesUtils.compareBytes(der.bytesAt(svnPtr), PCESVN_OID)) { + // pcesvn is 4 bytes in size + pcesvn = uint256(svnValue); + } else { + // each cpusvn is at maximum two bytes in size + uint256 cpusvn = uint256(svnValue); + cpusvns[i] = cpusvn; + } + + // iterate to the next svn object in the sequence + svnParentPtr = der.nextSiblingOf(svnParentPtr); + } + success = true; + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Parser.sol b/packages/protocol/contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Parser.sol new file mode 100644 index 00000000000..c594f843817 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Parser.sol @@ -0,0 +1,287 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { BytesUtils } from "../../utils/BytesUtils.sol"; +import { V3Struct } from "./V3Struct.sol"; +// import {PEMCertChainLib} from "../PEMCertChainLib.sol"; + +// import "hardhat/console.sol"; + +library V3Parser { + using BytesUtils for bytes; + + uint256 constant MINIMUM_QUOTE_LENGTH = 1020; + bytes2 constant SUPPORTED_QUOTE_VERSION = 0x0300; + bytes2 constant SUPPORTED_ATTESTATION_KEY_TYPE = 0x0200; + // SGX only + bytes4 constant SUPPORTED_TEE_TYPE = 0; + bytes16 constant VALID_QE_VENDOR_ID = 0x939a7233f79c4ca9940a0db3957f0607; + + // todo! import HEADER & FOOTER from PEMCertChainLib + string constant HEADER = "-----BEGIN CERTIFICATE-----"; + string constant FOOTER = "-----END CERTIFICATE-----"; + uint256 constant HEADER_LENGTH = 27; + uint256 constant FOOTER_LENGTH = 25; + + function parseInput(bytes memory quote) + internal + pure + returns ( + bool success, + V3Struct.Header memory header, + V3Struct.EnclaveReport memory localEnclaveReport, + bytes memory signedQuoteData, // concatenation of header and local enclave report bytes + V3Struct.ECDSAQuoteV3AuthData memory authDataV3 + ) + { + if (quote.length <= MINIMUM_QUOTE_LENGTH) { + return (false, header, localEnclaveReport, signedQuoteData, authDataV3); + } + + uint256 localAuthDataSize = littleEndianDecode(quote.substring(432, 4)); + if (quote.length - 436 != localAuthDataSize) { + return (false, header, localEnclaveReport, signedQuoteData, authDataV3); + } + + bytes memory rawHeader = quote.substring(0, 48); + bool headerVerifiedSuccessfully; + (headerVerifiedSuccessfully, header) = parseAndVerifyHeader(rawHeader); + if (!headerVerifiedSuccessfully) { + return (false, header, localEnclaveReport, signedQuoteData, authDataV3); + } + + bool authDataVerifiedSuccessfully; + (authDataVerifiedSuccessfully, authDataV3) = + parseAuthDataAndVerifyCertType(quote.substring(436, localAuthDataSize)); + if (!authDataVerifiedSuccessfully) { + return (false, header, localEnclaveReport, signedQuoteData, authDataV3); + } + + bytes memory rawLocalEnclaveReport = quote.substring(48, 384); + localEnclaveReport = parseEnclaveReport(rawLocalEnclaveReport); + signedQuoteData = abi.encodePacked(rawHeader, rawLocalEnclaveReport); + + success = true; + } + + function validateParsedInput(V3Struct.ParsedV3QuoteStruct calldata v3Quote) + internal + pure + returns ( + bool success, + V3Struct.Header memory header, + V3Struct.EnclaveReport memory localEnclaveReport, + bytes memory signedQuoteData, // concatenation of header and local enclave report bytes + V3Struct.ParsedECDSAQuoteV3AuthData memory authDataV3 + ) + { + success = true; + localEnclaveReport = v3Quote.localEnclaveReport; + V3Struct.EnclaveReport memory pckSignedQeReport = v3Quote.v3AuthData.pckSignedQeReport; + + require( + localEnclaveReport.reserved3.length == 96 && localEnclaveReport.reserved4.length == 60 + && localEnclaveReport.reportData.length == 64, + "local QE report has wrong length" + ); + require( + pckSignedQeReport.reserved3.length == 96 && pckSignedQeReport.reserved4.length == 60 + && pckSignedQeReport.reportData.length == 64, + "QE report has wrong length" + ); + require( + v3Quote.v3AuthData.certification.certType == 5, + "certType must be 5: Concatenated PCK Cert Chain (PEM formatted)" + ); + require( + v3Quote.v3AuthData.certification.decodedCertDataArray.length == 3, "3 certs in chain" + ); + require( + v3Quote.v3AuthData.ecdsa256BitSignature.length == 64 + && v3Quote.v3AuthData.ecdsaAttestationKey.length == 64 + && v3Quote.v3AuthData.qeReportSignature.length == 64, + "Invalid ECDSA signature format" + ); + require( + v3Quote.v3AuthData.qeAuthData.parsedDataSize + == v3Quote.v3AuthData.qeAuthData.data.length, + "Invalid QEAuthData size" + ); + + // todo! + // v3Quote.v3AuthData.certification.certDataSize == + // len(join((BEGIN_CERT, base64.encode(certArray[i]), END_CERT) for i in 0..3)) + // This check need b64 encoding, skip it now. + // require( + // base64.encode(v3Quote.v3AuthData.certification.decodedCertDataArray[0]).length + + // base64.encode(v3Quote + // .v3AuthData + // .certification + // .decodedCertDataArray[1]) + // .length + + // base64.encode(v3Quote + // .v3AuthData + // .certification + // .decodedCertDataArray[2]) + // .length + + // 3 * + // (HEADER_LENGTH + FOOTER_LENGTH) == + // v3Quote.v3AuthData.certification.certDataSize, + // "Invalid certData size" + // ); + + uint32 totalQuoteSize = 48 // header + + 384 // local QE report + + 64 // ecdsa256BitSignature + + 64 // ecdsaAttestationKey + + 384 // QE report + + 64 // qeReportSignature + + v3Quote.v3AuthData.qeAuthData.parsedDataSize + + v3Quote.v3AuthData.certification.certDataSize; + require(totalQuoteSize >= MINIMUM_QUOTE_LENGTH, "Invalid quote size"); + + header = v3Quote.header; + bytes memory headerBytes = abi.encodePacked( + header.version, + header.attestationKeyType, + header.teeType, + header.qeSvn, + header.pceSvn, + header.qeVendorId, + header.userData + ); + + signedQuoteData = abi.encodePacked(headerBytes, V3Parser.packQEReport(localEnclaveReport)); + authDataV3 = v3Quote.v3AuthData; + } + + function parseEnclaveReport(bytes memory rawEnclaveReport) + internal + pure + returns (V3Struct.EnclaveReport memory 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(littleEndianDecode(rawEnclaveReport.substring(256, 2))); + enclaveReport.isvSvn = uint16(littleEndianDecode(rawEnclaveReport.substring(258, 2))); + enclaveReport.reserved4 = rawEnclaveReport.substring(260, 60); + enclaveReport.reportData = rawEnclaveReport.substring(320, 64); + } + + function littleEndianDecode(bytes memory encoded) private 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; + } + } + + function parseAndVerifyHeader(bytes memory rawHeader) + private + pure + returns (bool success, V3Struct.Header memory header) + { + bytes2 version = bytes2(rawHeader.substring(0, 2)); + if (version != SUPPORTED_QUOTE_VERSION) { + return (false, header); + } + + bytes2 attestationKeyType = bytes2(rawHeader.substring(2, 2)); + if (attestationKeyType != SUPPORTED_ATTESTATION_KEY_TYPE) { + return (false, header); + } + + bytes4 teeType = bytes4(rawHeader.substring(4, 4)); + if (teeType != SUPPORTED_TEE_TYPE) { + return (false, header); + } + + bytes16 qeVendorId = bytes16(rawHeader.substring(12, 16)); + if (qeVendorId != VALID_QE_VENDOR_ID) { + return (false, header); + } + + header = V3Struct.Header({ + version: version, + attestationKeyType: attestationKeyType, + teeType: teeType, + qeSvn: bytes2(rawHeader.substring(8, 2)), + pceSvn: bytes2(rawHeader.substring(10, 2)), + qeVendorId: qeVendorId, + userData: bytes20(rawHeader.substring(28, 20)) + }); + + success = true; + } + + function parseAuthDataAndVerifyCertType(bytes memory rawAuthData) + private + pure + returns (bool success, V3Struct.ECDSAQuoteV3AuthData memory authDataV3) + { + V3Struct.QEAuthData memory qeAuthData; + qeAuthData.parsedDataSize = littleEndianDecode(rawAuthData.substring(576, 2)); + qeAuthData.data = rawAuthData.substring(578, qeAuthData.parsedDataSize); + + uint256 offset = 578 + qeAuthData.parsedDataSize; + V3Struct.CertificationData memory cert; + cert.certType = littleEndianDecode(rawAuthData.substring(offset, 2)); + if (cert.certType < 1 || cert.certType > 5) { + return (false, authDataV3); + } + offset += 2; + cert.certDataSize = littleEndianDecode(rawAuthData.substring(offset, 4)); + offset += 4; + cert.certData = rawAuthData.substring(offset, cert.certDataSize); + + authDataV3.ecdsa256BitSignature = rawAuthData.substring(0, 64); + authDataV3.ecdsaAttestationKey = rawAuthData.substring(64, 64); + authDataV3.rawQeReport = rawAuthData.substring(128, 384); + // console.logBytes(authDataV3.rawQeReport); + authDataV3.qeReportSignature = rawAuthData.substring(512, 64); + authDataV3.qeAuthData = qeAuthData; + authDataV3.certification = cert; + + success = true; + } + + /// enclaveReport to bytes for hash calculation. + /// the only difference between enclaveReport and packedQEReport is the + /// order of isvProdId and isvSvn. enclaveReport is in little endian, while + /// in bytes should be in big endian according to Intel spec. + /// @param enclaveReport enclave report + /// @return packedQEReport enclave report in bytes + function packQEReport(V3Struct.EnclaveReport memory enclaveReport) + internal + pure + returns (bytes memory packedQEReport) + { + uint16 isvProdIdPackBE = (enclaveReport.isvProdId >> 8) | (enclaveReport.isvProdId << 8); + uint16 isvSvnPackBE = (enclaveReport.isvSvn >> 8) | (enclaveReport.isvSvn << 8); + packedQEReport = abi.encodePacked( + enclaveReport.cpuSvn, + enclaveReport.miscSelect, + enclaveReport.reserved1, + enclaveReport.attributes, + enclaveReport.mrEnclave, + enclaveReport.reserved2, + enclaveReport.mrSigner, + enclaveReport.reserved3, + isvProdIdPackBE, + isvSvnPackBE, + enclaveReport.reserved4, + enclaveReport.reportData + ); + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Struct.sol b/packages/protocol/contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Struct.sol new file mode 100644 index 00000000000..25b2ea503d4 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Struct.sol @@ -0,0 +1,81 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +library V3Struct { + struct Header { + bytes2 version; + bytes2 attestationKeyType; + bytes4 teeType; + bytes2 qeSvn; + bytes2 pceSvn; + bytes16 qeVendorId; + bytes20 userData; + } + + 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 + } + + struct QEAuthData { + // avoid uint256 -> uint16 conversion to save gas + uint256 parsedDataSize; + bytes data; + } + + struct ParsedQEAuthData { + uint16 parsedDataSize; + bytes data; // todo! data.length = parsedDataSize + } + + struct CertificationData { + // avoid uint256 -> uint16 conversion to save gas + uint256 certType; + // avoid uint256 -> uint32 conversion to save gas + uint256 certDataSize; + bytes certData; + } + + struct ParsedCertificationData { + uint16 certType; + // avoid uint256 -> uint32 conversion to save gas + uint32 certDataSize; // todo! certDataSize = len(join((BEGIN_CERT, certArray[i], END_CERT) + // for i in 0..3)) + bytes[3] decodedCertDataArray; // base64 decoded cert bytes array + } + + struct ECDSAQuoteV3AuthData { + bytes ecdsa256BitSignature; // 64 bytes + bytes ecdsaAttestationKey; // 64 bytes + bytes rawQeReport; // 384 bytes + bytes qeReportSignature; // 64 bytes + QEAuthData qeAuthData; + CertificationData certification; + } + + struct ParsedECDSAQuoteV3AuthData { + bytes ecdsa256BitSignature; // 64 bytes + bytes ecdsaAttestationKey; // 64 bytes + EnclaveReport pckSignedQeReport; // 384 bytes + bytes qeReportSignature; // 64 bytes + ParsedQEAuthData qeAuthData; + ParsedCertificationData certification; + } + + struct ParsedV3QuoteStruct { + Header header; + EnclaveReport localEnclaveReport; + ParsedECDSAQuoteV3AuthData v3AuthData; + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/lib/TCBInfoStruct.sol b/packages/protocol/contracts/thirdparty/automata-attestation/lib/TCBInfoStruct.sol new file mode 100644 index 00000000000..b0a463909d6 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/lib/TCBInfoStruct.sol @@ -0,0 +1,27 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +library TCBInfoStruct { + struct TCBInfo { + string pceid; + string fmspc; + TCBLevelObj[] tcbLevels; + } + + struct TCBLevelObj { + uint256 pcesvn; + uint8[] sgxTcbCompSvnArr; + 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 + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/lib/interfaces/IPEMCertChainLib.sol b/packages/protocol/contracts/thirdparty/automata-attestation/lib/interfaces/IPEMCertChainLib.sol new file mode 100644 index 00000000000..c6a0de81f23 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/lib/interfaces/IPEMCertChainLib.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface IPEMCertChainLib { + struct ECSha256Certificate { + uint256 notBefore; + uint256 notAfter; + bytes serialNumber; + bytes tbsCertificate; + bytes pubKey; + bytes signature; + bool isPck; + PCKCertificateField pck; + } + + struct PCKCertificateField { + string commonName; + string issuerName; + PCKTCBInfo sgxExtension; + } + + struct PCKTCBInfo { + string pceid; + string fmspc; + uint256 pcesvn; + uint256[] sgxTcbCompSvnArr; + } + + enum CRL { + PCK, + ROOT + } + + function splitCertificateChain( + bytes memory pemChain, + uint256 size + ) + external + pure + returns (bool success, bytes[] memory certs); + + function decodeCert( + bytes memory der, + bool isPckCert + ) + external + pure + returns (bool success, ECSha256Certificate memory cert); +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/utils/Asn1Decode.sol b/packages/protocol/contracts/thirdparty/automata-attestation/utils/Asn1Decode.sol new file mode 100644 index 00000000000..844a0545231 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/utils/Asn1Decode.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +// Original source: https://github.com/JonahGroendal/asn1-decode +pragma solidity 0.8.24; + +// Inspired by PufferFinance/rave - Apache-2.0 license +// https://github.com/JonahGroendal/asn1-decode/blob/5c2d1469fc678513753786acb441e597969192ec/contracts/Asn1Decode.sol + +import "./BytesUtils.sol"; + +library NodePtr { + // Unpack first byte index + function ixs(uint256 self) internal pure returns (uint256) { + return uint80(self); + } + // Unpack first content byte index + + function ixf(uint256 self) internal pure returns (uint256) { + return uint80(self >> 80); + } + // Unpack last content byte index + + function ixl(uint256 self) internal pure returns (uint256) { + return uint80(self >> 160); + } + // Pack 3 uint80s into a uint256 + + function getPtr(uint256 _ixs, uint256 _ixf, uint256 _ixl) internal pure returns (uint256) { + _ixs |= _ixf << 80; + _ixs |= _ixl << 160; + return _ixs; + } +} + +library Asn1Decode { + using NodePtr for uint256; + using BytesUtils for bytes; + + /* + * @dev Get the root node. First step in traversing an ASN1 structure + * @param der The DER-encoded ASN1 structure + * @return A pointer to the outermost node + */ + function root(bytes memory der) internal pure returns (uint256) { + return readNodeLength(der, 0); + } + + /* + * @dev Get the root node of an ASN1 structure that's within a bit string value + * @param der The DER-encoded ASN1 structure + * @return A pointer to the outermost node + */ + function rootOfBitStringAt(bytes memory der, uint256 ptr) internal pure returns (uint256) { + require(der[ptr.ixs()] == 0x03, "Not type BIT STRING"); + return readNodeLength(der, ptr.ixf() + 1); + } + + /* + * @dev Get the root node of an ASN1 structure that's within an octet string value + * @param der The DER-encoded ASN1 structure + * @return A pointer to the outermost node + */ + function rootOfOctetStringAt(bytes memory der, uint256 ptr) internal pure returns (uint256) { + require(der[ptr.ixs()] == 0x04, "Not type OCTET STRING"); + return readNodeLength(der, ptr.ixf()); + } + + /* + * @dev Get the next sibling node + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return A pointer to the next sibling node + */ + function nextSiblingOf(bytes memory der, uint256 ptr) internal pure returns (uint256) { + return readNodeLength(der, ptr.ixl() + 1); + } + + /* + * @dev Get the first child node of the current node + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return A pointer to the first child node + */ + function firstChildOf(bytes memory der, uint256 ptr) internal pure returns (uint256) { + require(der[ptr.ixs()] & 0x20 == 0x20, "Not a constructed type"); + return readNodeLength(der, ptr.ixf()); + } + + /* + * @dev Use for looping through children of a node (either i or j). + * @param i Pointer to an ASN1 node + * @param j Pointer to another ASN1 node of the same ASN1 structure + * @return True iff j is child of i or i is child of j. + */ + function isChildOf(uint256 i, uint256 j) internal pure returns (bool) { + return ( + ((i.ixf() <= j.ixs()) && (j.ixl() <= i.ixl())) + || ((j.ixf() <= i.ixs()) && (i.ixl() <= j.ixl())) + ); + } + + /* + * @dev Extract value of node from DER-encoded structure + * @param der The der-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Value bytes of node + */ + function bytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes memory) { + return der.substring(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); + } + + /* + * @dev Extract entire node from DER-encoded structure + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return All bytes of node + */ + function allBytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes memory) { + return der.substring(ptr.ixs(), ptr.ixl() + 1 - ptr.ixs()); + } + + /* + * @dev Extract value of node from DER-encoded structure + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Value bytes of node as bytes32 + */ + function bytes32At(bytes memory der, uint256 ptr) internal pure returns (bytes32) { + return der.readBytesN(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); + } + + /* + * @dev Extract value of node from DER-encoded structure + * @param der The der-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Uint value of node + */ + function uintAt(bytes memory der, uint256 ptr) internal pure returns (uint256) { + require(der[ptr.ixs()] == 0x02, "Not type INTEGER"); + require(der[ptr.ixf()] & 0x80 == 0, "Not positive"); + uint256 len = ptr.ixl() + 1 - ptr.ixf(); + return uint256(der.readBytesN(ptr.ixf(), len) >> (32 - len) * 8); + } + + /* + * @dev Extract value of a positive integer node from DER-encoded structure + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Value bytes of a positive integer node + */ + function uintBytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes memory) { + require(der[ptr.ixs()] == 0x02, "Not type INTEGER"); + require(der[ptr.ixf()] & 0x80 == 0, "Not positive"); + uint256 valueLength = ptr.ixl() + 1 - ptr.ixf(); + if (der[ptr.ixf()] == 0) { + return der.substring(ptr.ixf() + 1, valueLength - 1); + } else { + return der.substring(ptr.ixf(), valueLength); + } + } + + function keccakOfBytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes32) { + return der.keccak(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); + } + + function keccakOfAllBytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes32) { + return der.keccak(ptr.ixs(), ptr.ixl() + 1 - ptr.ixs()); + } + + /* + * @dev Extract value of bitstring node from DER-encoded structure + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Value of bitstring converted to bytes + */ + function bitstringAt(bytes memory der, uint256 ptr) internal pure returns (bytes memory) { + require(der[ptr.ixs()] == 0x03, "Not type BIT STRING"); + // Only 00 padded bitstr can be converted to bytestr! + require(der[ptr.ixf()] == 0x00); + uint256 valueLength = ptr.ixl() + 1 - ptr.ixf(); + return der.substring(ptr.ixf() + 1, valueLength - 1); + } + + function readNodeLength(bytes memory der, uint256 ix) private pure returns (uint256) { + uint256 length; + uint80 ixFirstContentByte; + uint80 ixLastContentByte; + if ((der[ix + 1] & 0x80) == 0) { + length = uint8(der[ix + 1]); + ixFirstContentByte = uint80(ix + 2); + ixLastContentByte = uint80(ixFirstContentByte + length - 1); + } else { + uint8 lengthbytesLength = uint8(der[ix + 1] & 0x7F); + if (lengthbytesLength == 1) { + length = der.readUint8(ix + 2); + } else if (lengthbytesLength == 2) { + length = der.readUint16(ix + 2); + } else { + length = uint256( + der.readBytesN(ix + 2, lengthbytesLength) >> (32 - lengthbytesLength) * 8 + ); + } + ixFirstContentByte = uint80(ix + 2 + lengthbytesLength); + ixLastContentByte = uint80(ixFirstContentByte + length - 1); + } + return NodePtr.getPtr(ix, ixFirstContentByte, ixLastContentByte); + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/utils/BytesUtils.sol b/packages/protocol/contracts/thirdparty/automata-attestation/utils/BytesUtils.sol new file mode 100644 index 00000000000..22f3f3f77dc --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/utils/BytesUtils.sol @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: BSD 2-Clause License +pragma solidity 0.8.24; + +// 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 { + assembly { + mcopy(dest, src, len) + } + } + + /* + * @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) { + return keccak256(a) == keccak256(b); + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/utils/RsaVerify.sol b/packages/protocol/contracts/thirdparty/automata-attestation/utils/RsaVerify.sol new file mode 100644 index 00000000000..65db8ef7f86 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/utils/RsaVerify.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.24; + +import "./SHA1.sol"; + +// Inspired by adria0/SolRsaVerify - GPL-3.0 license +// https://github.com/adria0/SolRsaVerify/blob/master/src/RsaVerify.sol + +/* + Copyright 2016, Adrià Massanet + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Checked results with FIPS test vectors +https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss/186-2rsatestvectors.zip + file SigVer15_186-3.rsp + + */ + +library RsaVerify { + /** + * @dev Verifies a PKCSv1.5 SHA256 signature + * @param _sha256 is the sha256 of the data + * @param _s is the signature + * @param _e is the exponent + * @param _m is the modulus + * @return true if success, false otherwise + */ + function pkcs1Sha256( + bytes32 _sha256, + bytes memory _s, + bytes memory _e, + bytes memory _m + ) + internal + view + returns (bool) + { + uint8[17] memory sha256ExplicitNullParam = [ + 0x30, + 0x31, + 0x30, + 0x0d, + 0x06, + 0x09, + 0x60, + 0x86, + 0x48, + 0x01, + 0x65, + 0x03, + 0x04, + 0x02, + 0x01, + 0x05, + 0x00 + ]; + + uint8[15] memory sha256ImplicitNullParam = [ + 0x30, + 0x2f, + 0x30, + 0x0b, + 0x06, + 0x09, + 0x60, + 0x86, + 0x48, + 0x01, + 0x65, + 0x03, + 0x04, + 0x02, + 0x01 + ]; + + // decipher + + bytes memory input = + bytes.concat(bytes32(_s.length), bytes32(_e.length), bytes32(_m.length), _s, _e, _m); + uint256 inputlen = input.length; + + uint256 decipherlen = _m.length; + bytes memory decipher = new bytes(decipherlen); + assembly { + pop( + staticcall( + sub(gas(), 2000), + 5, + add(input, 0x20), + inputlen, + add(decipher, 0x20), + decipherlen + ) + ) + } + + // Check that is well encoded: + // + // 0x00 || 0x01 || PS || 0x00 || DigestInfo + // PS is padding filled with 0xff + // DigestInfo ::= SEQUENCE { + // digestAlgorithm AlgorithmIdentifier, + // [optional algorithm parameters] + // digest OCTET STRING + // } + + bool hasNullParam; + uint256 digestAlgoWithParamLen; + + if (uint8(decipher[decipherlen - 50]) == 0x31) { + hasNullParam = true; + digestAlgoWithParamLen = sha256ExplicitNullParam.length; + } else if (uint8(decipher[decipherlen - 48]) == 0x2f) { + hasNullParam = false; + digestAlgoWithParamLen = sha256ImplicitNullParam.length; + } else { + return false; + } + + uint256 paddingLen = decipherlen - 5 - digestAlgoWithParamLen - 32; + + if (decipher[0] != 0 || decipher[1] != 0x01) { + return false; + } + for (uint256 i = 2; i < 2 + paddingLen; i++) { + if (decipher[i] != 0xff) { + return false; + } + } + if (decipher[2 + paddingLen] != 0) { + return false; + } + + // check digest algorithm + + if (digestAlgoWithParamLen == sha256ExplicitNullParam.length) { + for (uint256 i = 0; i < digestAlgoWithParamLen; i++) { + if (decipher[3 + paddingLen + i] != bytes1(sha256ExplicitNullParam[i])) { + return false; + } + } + } else { + for (uint256 i = 0; i < digestAlgoWithParamLen; i++) { + if (decipher[3 + paddingLen + i] != bytes1(sha256ImplicitNullParam[i])) { + return false; + } + } + } + + // check digest + + if ( + decipher[3 + paddingLen + digestAlgoWithParamLen] != 0x04 + || decipher[4 + paddingLen + digestAlgoWithParamLen] != 0x20 + ) { + return false; + } + + for (uint256 i = 0; i < _sha256.length; i++) { + if (decipher[5 + paddingLen + digestAlgoWithParamLen + i] != _sha256[i]) { + return false; + } + } + + return true; + } + + /** + * @dev Verifies a PKCSv1.5 SHA256 signature + * @param _data to verify + * @param _s is the signature + * @param _e is the exponent + * @param _m is the modulus + * @return 0 if success, >0 otherwise + */ + function pkcs1Sha256Raw( + bytes memory _data, + bytes memory _s, + bytes memory _e, + bytes memory _m + ) + internal + view + returns (bool) + { + return pkcs1Sha256(sha256(_data), _s, _e, _m); + } + + /** + * @dev Verifies a PKCSv1.5 SHA1 signature + * @param _sha1 is the sha1 of the data + * @param _s is the signature + * @param _e is the exponent + * @param _m is the modulus + * @return true if success, false otherwise + */ + function pkcs1Sha1( + bytes20 _sha1, + bytes memory _s, + bytes memory _e, + bytes memory _m + ) + internal + view + returns (bool) + { + uint8[15] memory sha1Prefix = [ + 0x30, + 0x21, + 0x30, + 0x09, + 0x06, + 0x05, + 0x2b, + 0x0e, + 0x03, + 0x02, + 0x1a, + 0x05, + 0x00, + 0x04, + 0x14 + ]; + + // decipher + bytes memory input = + bytes.concat(bytes32(_s.length), bytes32(_e.length), bytes32(_m.length), _s, _e, _m); + uint256 inputlen = input.length; + + uint256 decipherlen = _m.length; + bytes memory decipher = new bytes(decipherlen); + assembly { + pop( + staticcall( + sub(gas(), 2000), + 5, + add(input, 0x20), + inputlen, + add(decipher, 0x20), + decipherlen + ) + ) + } + + // Check that is well encoded: + // 0x00 || 0x01 || PS || 0x00 || DigestInfo + // PS is padding filled with 0xff + // DigestInfo ::= SEQUENCE { + // digestAlgorithm AlgorithmIdentifier, + // digest OCTET STRING + // } + + uint256 paddingLen = decipherlen - 3 - sha1Prefix.length - 20; + + if (decipher[0] != 0 || decipher[1] != 0x01) { + return false; + } + for (uint256 i = 2; i < 2 + paddingLen; i++) { + if (decipher[i] != 0xff) { + return false; + } + } + if (decipher[2 + paddingLen] != 0) { + return false; + } + + // check digest algorithm + for (uint256 i = 0; i < sha1Prefix.length; i++) { + if (decipher[3 + paddingLen + i] != bytes1(sha1Prefix[i])) { + return false; + } + } + + // check digest + for (uint256 i = 0; i < _sha1.length; i++) { + if (decipher[3 + paddingLen + sha1Prefix.length + i] != _sha1[i]) { + return false; + } + } + + return true; + } + + /** + * @dev Verifies a PKCSv1.5 SHA1 signature + * @param _data to verify + * @param _s is the signature + * @param _e is the exponent + * @param _m is the modulus + * @return 0 if success, >0 otherwise + */ + function pkcs1Sha1Raw( + bytes memory _data, + bytes memory _s, + bytes memory _e, + bytes memory _m + ) + internal + view + returns (bool) + { + return pkcs1Sha1(SHA1.sha1(_data), _s, _e, _m); + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/utils/SHA1.sol b/packages/protocol/contracts/thirdparty/automata-attestation/utils/SHA1.sol new file mode 100644 index 00000000000..c0ae73dd035 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/utils/SHA1.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: BSD 2-Clause License + +pragma solidity 0.8.24; + +// Inspired by ensdomains/solsha1 - BSD 2-Clause License +// https://github.com/ensdomains/solsha1/blob/master/contracts/SHA1.sol + +library SHA1 { + event Debug(bytes32 x); + + function sha1(bytes memory data) internal pure returns (bytes20 ret) { + assembly { + // Get a safe scratch location + let scratch := mload(0x40) + + // Get the data length, and point data at the first byte + let len := mload(data) + data := add(data, 32) + + // Find the length after padding + let totallen := add(and(add(len, 1), 0xFFFFFFFFFFFFFFC0), 64) + switch lt(sub(totallen, len), 9) + case 1 { totallen := add(totallen, 64) } + + let h := 0x6745230100EFCDAB890098BADCFE001032547600C3D2E1F0 + + function readword(ptr, off, count) -> result { + result := 0 + if lt(off, count) { + result := mload(add(ptr, off)) + count := sub(count, off) + if lt(count, 32) { + let mask := not(sub(exp(256, sub(32, count)), 1)) + result := and(result, mask) + } + } + } + + for { let i := 0 } lt(i, totallen) { i := add(i, 64) } { + mstore(scratch, readword(data, i, len)) + mstore(add(scratch, 32), readword(data, add(i, 32), len)) + + // If we loaded the last byte, store the terminator byte + switch lt(sub(len, i), 64) + case 1 { mstore8(add(scratch, sub(len, i)), 0x80) } + + // If this is the last block, store the length + switch eq(i, sub(totallen, 64)) + case 1 { mstore(add(scratch, 32), or(mload(add(scratch, 32)), mul(len, 8))) } + + // Expand the 16 32-bit words into 80 + for { let j := 64 } lt(j, 128) { j := add(j, 12) } { + let temp := + xor( + xor(mload(add(scratch, sub(j, 12))), mload(add(scratch, sub(j, 32)))), + xor(mload(add(scratch, sub(j, 56))), mload(add(scratch, sub(j, 64)))) + ) + temp := + or( + and( + mul(temp, 2), + 0xFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFE + ), + and( + div(temp, 0x80000000), + 0x0000000100000001000000010000000100000001000000010000000100000001 + ) + ) + mstore(add(scratch, j), temp) + } + for { let j := 128 } lt(j, 320) { j := add(j, 24) } { + let temp := + xor( + xor(mload(add(scratch, sub(j, 24))), mload(add(scratch, sub(j, 64)))), + xor(mload(add(scratch, sub(j, 112))), mload(add(scratch, sub(j, 128)))) + ) + temp := + or( + and( + mul(temp, 4), + 0xFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFC + ), + and( + div(temp, 0x40000000), + 0x0000000300000003000000030000000300000003000000030000000300000003 + ) + ) + mstore(add(scratch, j), temp) + } + + let x := h + let f := 0 + let k := 0 + for { let j := 0 } lt(j, 80) { j := add(j, 1) } { + switch div(j, 20) + case 0 { + // f = d xor (b and (c xor d)) + f := xor(div(x, 0x100000000000000000000), div(x, 0x10000000000)) + f := and(div(x, 0x1000000000000000000000000000000), f) + f := xor(div(x, 0x10000000000), f) + k := 0x5A827999 + } + case 1 { + // f = b xor c xor d + f := + xor( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ) + f := xor(div(x, 0x10000000000), f) + k := 0x6ED9EBA1 + } + case 2 { + // f = (b and c) or (d and (b or c)) + f := + or( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ) + f := and(div(x, 0x10000000000), f) + f := + or( + and( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ), + f + ) + k := 0x8F1BBCDC + } + case 3 { + // f = b xor c xor d + f := + xor( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ) + f := xor(div(x, 0x10000000000), f) + k := 0xCA62C1D6 + } + // temp = (a leftrotate 5) + f + e + k + w[i] + let temp := and(div(x, 0x80000000000000000000000000000000000000000000000), 0x1F) + temp := + or(and(div(x, 0x800000000000000000000000000000000000000), 0xFFFFFFE0), temp) + temp := add(f, temp) + temp := add(and(x, 0xFFFFFFFF), temp) + temp := add(k, temp) + temp := + add( + div( + mload(add(scratch, mul(j, 4))), + 0x100000000000000000000000000000000000000000000000000000000 + ), + temp + ) + x := + or( + div(x, 0x10000000000), + mul(temp, 0x10000000000000000000000000000000000000000) + ) + x := + or( + and(x, 0xFFFFFFFF00FFFFFFFF000000000000FFFFFFFF00FFFFFFFF), + mul( + or( + and(div(x, 0x4000000000000), 0xC0000000), + and(div(x, 0x400000000000000000000), 0x3FFFFFFF) + ), + 0x100000000000000000000 + ) + ) + } + + h := and(add(h, x), 0xFFFFFFFF00FFFFFFFF00FFFFFFFF00FFFFFFFF00FFFFFFFF) + } + ret := + mul( + or( + or( + or( + or( + and(div(h, 0x100000000), 0xFFFFFFFF00000000000000000000000000000000), + and(div(h, 0x1000000), 0xFFFFFFFF000000000000000000000000) + ), + and(div(h, 0x10000), 0xFFFFFFFF0000000000000000) + ), + and(div(h, 0x100), 0xFFFFFFFF00000000) + ), + and(h, 0xFFFFFFFF) + ), + 0x1000000000000000000000000 + ) + } + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/utils/SigVerifyLib.sol b/packages/protocol/contracts/thirdparty/automata-attestation/utils/SigVerifyLib.sol new file mode 100644 index 00000000000..6e5486879a5 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/utils/SigVerifyLib.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.24; + +import "../interfaces/ISigVerifyLib.sol"; +import "./RsaVerify.sol"; +import "./BytesUtils.sol"; + +// Library for verifying signatures +// Supports verifying signatures with the following algorithms: +// - RS256 +// - ES256 +// - RS1 + +contract SigVerifyLib is ISigVerifyLib { + using BytesUtils for bytes; + + address private ES256VERIFIER; + + constructor(address es256Verifier) { + ES256VERIFIER = es256Verifier; + } + + function verifyAttStmtSignature( + bytes memory tbs, + bytes memory signature, + PublicKey memory publicKey, + Algorithm alg + ) + public + view + returns (bool) + { + if (alg == Algorithm.RS256) { + if (publicKey.keyType != KeyType.RSA) { + return false; + } + return verifyRS256Signature(tbs, signature, publicKey.pubKey); + } else if (alg == Algorithm.ES256) { + if (publicKey.keyType != KeyType.ECDSA) { + return false; + } + return verifyES256Signature(tbs, signature, publicKey.pubKey); + } else if (alg == Algorithm.RS1) { + if (publicKey.keyType != KeyType.RSA) { + return false; + } + return verifyRS1Signature(tbs, signature, publicKey.pubKey); + } else { + revert("Unsupported algorithm"); + } + } + + function verifyCertificateSignature( + bytes memory tbs, + bytes memory signature, + PublicKey memory publicKey, + CertSigAlgorithm alg + ) + public + view + returns (bool) + { + if (alg == CertSigAlgorithm.Sha256WithRSAEncryption) { + if (publicKey.keyType != KeyType.RSA) { + return false; + } + return verifyRS256Signature(tbs, signature, publicKey.pubKey); + } else if (alg == CertSigAlgorithm.Sha1WithRSAEncryption) { + if (publicKey.keyType != KeyType.RSA) { + return false; + } + return verifyRS1Signature(tbs, signature, publicKey.pubKey); + } else { + revert("Unsupported algorithm"); + } + } + + function verifyRS256Signature( + bytes memory tbs, + bytes memory signature, + bytes memory publicKey + ) + public + view + returns (bool sigValid) + { + // Parse public key + bytes memory exponent = publicKey.substring(0, 3); + bytes memory modulus = publicKey.substring(3, publicKey.length - 3); + + // Verify signature + sigValid = RsaVerify.pkcs1Sha256Raw(tbs, signature, exponent, modulus); + } + + function verifyRS1Signature( + bytes memory tbs, + bytes memory signature, + bytes memory publicKey + ) + public + view + returns (bool sigValid) + { + // Parse public key + bytes memory exponent = publicKey.substring(0, 3); + bytes memory modulus = publicKey.substring(3, publicKey.length - 3); + + // Verify signature + sigValid = RsaVerify.pkcs1Sha1Raw(tbs, signature, exponent, modulus); + } + + function verifyES256Signature( + bytes memory tbs, + bytes memory signature, + bytes memory publicKey + ) + public + view + returns (bool sigValid) + { + // Parse signature + if (signature.length != 64) { + return false; + } + uint256 r = uint256(bytes32(signature.substring(0, 32))); + uint256 s = uint256(bytes32(signature.substring(32, 32))); + // Parse public key + if (publicKey.length != 64) { + return false; + } + uint256 gx = uint256(bytes32(publicKey.substring(0, 32))); + uint256 gy = uint256(bytes32(publicKey.substring(32, 32))); + + // Verify signature + bytes memory args = abi.encode(sha256(tbs), r, s, gx, gy); + (bool success, bytes memory ret) = ES256VERIFIER.staticcall(args); + assert(success); // never reverts, always returns 0 or 1 + + return abi.decode(ret, (uint256)) == 1; + } +} diff --git a/packages/protocol/contracts/thirdparty/automata-attestation/utils/X509DateUtils.sol b/packages/protocol/contracts/thirdparty/automata-attestation/utils/X509DateUtils.sol new file mode 100644 index 00000000000..b9b27ac8435 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/automata-attestation/utils/X509DateUtils.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +library X509DateUtils { + function toTimestamp(bytes memory x509Time) internal pure returns (uint256) { + uint16 yrs; + uint8 mnths; + uint8 dys; + uint8 hrs; + uint8 mins; + uint8 secs; + uint8 offset; + + if (x509Time.length == 13) { + if (uint8(x509Time[0]) - 48 < 5) yrs += 2000; + else yrs += 1900; + } else { + yrs += (uint8(x509Time[0]) - 48) * 1000 + (uint8(x509Time[1]) - 48) * 100; + offset = 2; + } + yrs += (uint8(x509Time[offset + 0]) - 48) * 10 + uint8(x509Time[offset + 1]) - 48; + mnths = (uint8(x509Time[offset + 2]) - 48) * 10 + uint8(x509Time[offset + 3]) - 48; + dys += (uint8(x509Time[offset + 4]) - 48) * 10 + uint8(x509Time[offset + 5]) - 48; + hrs += (uint8(x509Time[offset + 6]) - 48) * 10 + uint8(x509Time[offset + 7]) - 48; + mins += (uint8(x509Time[offset + 8]) - 48) * 10 + uint8(x509Time[offset + 9]) - 48; + secs += (uint8(x509Time[offset + 10]) - 48) * 10 + uint8(x509Time[offset + 11]) - 48; + + return toUnixTimestamp(yrs, mnths, dys, hrs, mins, secs); + } + + function toUnixTimestamp( + uint16 year, + uint8 month, + uint8 day, + uint8 hour, + uint8 minute, + uint8 second + ) + internal + pure + returns (uint256) + { + uint256 timestamp = 0; + + for (uint16 i = 1970; i < year; i++) { + if (isLeapYear(i)) { + timestamp += 31_622_400; // Leap year in seconds + } else { + timestamp += 31_536_000; // Normal year in seconds + } + } + + uint8[12] memory monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if (isLeapYear(year)) monthDays[1] = 29; + + for (uint8 i = 1; i < month; i++) { + timestamp += uint256(monthDays[i - 1]) * 86_400; // Days in seconds + } + + timestamp += uint256(day - 1) * 86_400; // Days in seconds + timestamp += uint256(hour) * 3600; // Hours in seconds + timestamp += uint256(minute) * 60; // Minutes in seconds + timestamp += second; + + return timestamp; + } + + function isLeapYear(uint16 year) internal pure returns (bool) { + if (year % 4 != 0) return false; + if (year % 100 != 0) return true; + if (year % 400 != 0) return false; + return true; + } +} diff --git a/packages/protocol/lib/p256-verifier b/packages/protocol/lib/p256-verifier new file mode 160000 index 00000000000..6ef45b11764 --- /dev/null +++ b/packages/protocol/lib/p256-verifier @@ -0,0 +1 @@ +Subproject commit 6ef45b117642786b08a37b4c37c6a6ce151166da diff --git a/packages/protocol/lib/solady b/packages/protocol/lib/solady new file mode 160000 index 00000000000..fb11b3e9ea6 --- /dev/null +++ b/packages/protocol/lib/solady @@ -0,0 +1 @@ +Subproject commit fb11b3e9ea6c1aafdbd0a1515ff440509d60bff9 diff --git a/packages/protocol/script/DeployOnL1.s.sol b/packages/protocol/script/DeployOnL1.s.sol index 9a97c22d675..1e0a04c3c75 100644 --- a/packages/protocol/script/DeployOnL1.s.sol +++ b/packages/protocol/script/DeployOnL1.s.sol @@ -37,6 +37,15 @@ import "../contracts/signal/SignalService.sol"; import "../test/common/erc20/FreeMintERC20.sol"; import "../test/common/erc20/MayFailFreeMintERC20.sol"; import "../test/DeployCapability.sol"; +import "../contracts/thirdparty/automata-attestation/AutomataDcapV3Attestation.sol"; +import "../contracts/thirdparty/automata-attestation/utils/SigVerifyLib.sol"; +import "../contracts/thirdparty/automata-attestation/lib/PEMCertChainLib.sol"; + +// Actually this one is deployed already on mainnets, but we are now deploying our own (non vi-ir) +// version. For mainnet, it is easier to go with either this: +// https://github.com/daimo-eth/p256-verifier or this: +// https://github.com/rdubois-crypto/FreshCryptoLib +import { P256Verifier } from "../lib/p256-verifier/src/P256Verifier.sol"; /// @title DeployOnL1 /// @notice This script deploys the core Taiko protocol smart contract on L1, @@ -382,6 +391,18 @@ contract DeployOnL1 is DeployCapability { uint8 minGuardians = uint8(vm.envUint("MIN_GUARDIANS")); GuardianProver(guardianProver).setGuardians(guardians, minGuardians); GuardianProver(guardianProver).transferOwnership(timelock); + + // No need to proxy these, because they are 3rd party. If we want to modify, we simply + // change the registerAddress("automata_dcap_attestation", address(attestation)); + P256Verifier p256Verifier = new P256Verifier(); + SigVerifyLib sigVerifyLib = new SigVerifyLib(address(p256Verifier)); + PEMCertChainLib pemCertChainLib = new PEMCertChainLib(); + AutomataDcapV3Attestation automateDcapV3Attestation = + new AutomataDcapV3Attestation(address(sigVerifyLib), address(pemCertChainLib)); + + register( + rollupAddressManager, "automata_dcap_attestation", address(automateDcapV3Attestation) + ); } function deployAuxContracts() private { diff --git a/packages/protocol/script/SetDcapParams.s.sol b/packages/protocol/script/SetDcapParams.s.sol new file mode 100644 index 00000000000..59100c2e1f6 --- /dev/null +++ b/packages/protocol/script/SetDcapParams.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +// +// Email: security@taiko.xyz +// Website: https://taiko.xyz +// GitHub: https://github.com/taikoxyz +// Discord: https://discord.gg/taikoxyz +// Twitter: https://twitter.com/taikoxyz +// Blog: https://mirror.xyz/labs.taiko.eth +// Youtube: https://www.youtube.com/@taikoxyz + +pragma solidity 0.8.24; + +import "forge-std/Script.sol"; +import "forge-std/console2.sol"; + +import "../test/automata-attestation/common/AttestationBase.t.sol"; + +contract SetDcapParams is Script, AttestationBase { + uint256 public ownerPrivateKey = vm.envUint("PRIVATE_KEY"); // Owner of the attestation contract + address public dcapAttestationAddress = vm.envAddress("ATTESTATION_ADDRESS"); + address public sgxVerifier = vm.envAddress("SGX_VERIFIER_ADDRESS"); + + function run() external { + tcbInfoPath = vm.envString("TCB_INFO_PATH"); + idPath = vm.envString("QEID_PATH"); + v3QuotePath = vm.envString("V3_QUOTE_PATH"); + mrEnclave = vm.envBytes32("MR_ENCLAVE"); + mrSigner = vm.envBytes32("MR_SIGNER"); + + require(ownerPrivateKey != 0, "PRIVATE_KEY not set"); + require(dcapAttestationAddress != address(0), "ATTESTATION_ADDRESS not set"); + + vm.startBroadcast(ownerPrivateKey); + + setMrEnclave(dcapAttestationAddress, mrEnclave); + setMrSigner(dcapAttestationAddress, mrSigner); + + string memory enclaveIdJson = vm.readFile(string.concat(vm.projectRoot(), idPath)); + configureQeIdentityJson(dcapAttestationAddress, enclaveIdJson); + + string memory tcbInfoJson = vm.readFile(string.concat(vm.projectRoot(), tcbInfoPath)); + configureTcbInfoJson(dcapAttestationAddress, tcbInfoJson); + + string memory v3QuoteJsonStr = vm.readFile(string.concat(vm.projectRoot(), v3QuotePath)); + registerSgxInstanceWithQuote(sgxVerifier, v3QuoteJsonStr); + + vm.stopBroadcast(); + } +} diff --git a/packages/protocol/script/config_dcap_sgx_verifier.sh b/packages/protocol/script/config_dcap_sgx_verifier.sh new file mode 100755 index 00000000000..7f4ba758aeb --- /dev/null +++ b/packages/protocol/script/config_dcap_sgx_verifier.sh @@ -0,0 +1,20 @@ +# for foundry test only! +PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ +ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 \ +LOG_LEVEL=DEBUG \ +REPORT_GAS=true \ +SGX_VERIFIER_ADDRESS=0x1fA02b2d6A771842690194Cf62D91bdd92BfE28d \ +TIMELOCK_ADDRESS=0xB2b580ce436E6F77A5713D80887e14788Ef49c9A \ +ATTESTATION_ADDRESS=0xC9a43158891282A2B1475592D5719c001986Aaec \ +TCB_INFO_PATH=/test/automata-attestation/assets/0923/tcbInfo.json \ +QEID_PATH=/test/automata-attestation/assets/0923/identity.json \ +V3_QUOTE_PATH=/test/automata-attestation/assets/0923/v3quote.json \ +MR_ENCLAVE=0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef \ +MR_SIGNER=0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef \ +forge script script/SetDcapParams.s.sol:SetDcapParams \ + --fork-url http://localhost:8545 \ + --broadcast \ + --ffi \ + -vvvv \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ #foundry test key + --block-gas-limit 100000000 \ No newline at end of file diff --git a/packages/protocol/test/L1/SgxVerifier.t.sol b/packages/protocol/test/L1/SgxVerifier.t.sol index f5e713dc706..e84192c20fa 100644 --- a/packages/protocol/test/L1/SgxVerifier.t.sol +++ b/packages/protocol/test/L1/SgxVerifier.t.sol @@ -2,13 +2,28 @@ pragma solidity 0.8.24; import "./TaikoL1TestBase.sol"; +import "../automata-attestation/common/AttestationBase.t.sol"; + +contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { + address internal SGX_Y = + vm.addr(0x9b1bb8cb3bdb539d0d1f03951d27f167f2d5443e7ef0d7ce745cd4ec619d3dd7); + address internal SGX_Z = randAddress(); -contract TestSgxVerifier is TaikoL1TestBase { function deployTaikoL1() internal override returns (TaikoL1) { return TaikoL1(payable(deployProxy({ name: "taiko", impl: address(new TaikoL1()), data: "" }))); } + function setUp() public override { + // Call the TaikoL1TestBase setUp() + super.setUp(); + + // Call the AttestationBase init setup + super.intialSetup(); + + registerAddress("automata_dcap_attestation", address(attestation)); + } + function test_addInstancesByOwner() external { address[] memory _instances = new address[](3); _instances[0] = SGX_X_1; @@ -42,15 +57,14 @@ contract TestSgxVerifier is TaikoL1TestBase { assertEq(instance, address(0)); } - function test_addInstancesBySgxInstance() external { - address[] memory _instances = new address[](2); - _instances[0] = SGX_Y; - _instances[1] = SGX_Z; + function test_registerInstanceWithAttestation() external { + string memory v3QuoteJsonStr = vm.readFile(string.concat(vm.projectRoot(), v3QuotePath)); + bytes memory v3QuotePacked = vm.parseJson(v3QuoteJsonStr); - bytes memory signature = _getSignature(SGX_X_1, _instances, 0x4); + (, V3Struct.ParsedV3QuoteStruct memory v3quote) = parseV3QuoteJson(v3QuotePacked); vm.prank(Bob, Bob); - sv.addInstances(0, SGX_X_1, _instances, signature); + sv.registerInstance(v3quote); } function _getSignature( diff --git a/packages/protocol/test/L1/TaikoL1TestBase.sol b/packages/protocol/test/L1/TaikoL1TestBase.sol index 219afbbcbc9..7dd97a7b81c 100644 --- a/packages/protocol/test/L1/TaikoL1TestBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestBase.sol @@ -29,6 +29,9 @@ abstract contract TaikoL1TestBase is TaikoTest { address public L2SS = randAddress(); address public L2 = randAddress(); + // Bootstrapped SGX instances (by owner) + address internal SGX_X_0 = vm.addr(0x4); + address internal SGX_X_1 = vm.addr(0x5); function deployTaikoL1() internal virtual returns (TaikoL1 taikoL1); @@ -269,6 +272,7 @@ abstract contract TaikoL1TestBase is TaikoTest { } address newInstance; + // Keep changing the pub key associated with an instance to avoid // attacks, // obviously just a mock due to 2 addresses changing all the time. diff --git a/packages/protocol/test/TaikoTest.sol b/packages/protocol/test/TaikoTest.sol index f9d05bb8754..7dae33e02a4 100644 --- a/packages/protocol/test/TaikoTest.sol +++ b/packages/protocol/test/TaikoTest.sol @@ -71,10 +71,6 @@ abstract contract TaikoTest is Test, DeployCapability { address internal Xavier = randAddress(); address internal Yasmine = randAddress(); address internal Zachary = randAddress(); - address internal SGX_X_0 = vm.addr(0x4); - address internal SGX_X_1 = vm.addr(0x5); - address internal SGX_Y = randAddress(); - address internal SGX_Z = randAddress(); function randAddress() internal returns (address) { bytes32 randomHash = keccak256(abi.encodePacked("address", _seed++)); diff --git a/packages/protocol/test/automata-attestation/AutomataDcapV3AttestationTest.t.sol b/packages/protocol/test/automata-attestation/AutomataDcapV3AttestationTest.t.sol new file mode 100644 index 00000000000..9150e0cdcbe --- /dev/null +++ b/packages/protocol/test/automata-attestation/AutomataDcapV3AttestationTest.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.24; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "forge-std/StdJson.sol"; +import "./common/AttestationBase.t.sol"; + +contract AutomataDcapV3AttestationTest is Test, AttestationBase { + using BytesUtils for bytes; + using stdJson for string; + + function setUp() public { + // Call the AttestationBase init setup + super.intialSetup(); + } + + function testAttestation() public { + vm.prank(user); + bool verified = attestation.verifyAttestation(sampleQuote); + assertTrue(verified); + } + + function testParsedQuoteAttestation() public { + vm.prank(user); + string memory v3QuoteJsonStr = vm.readFile(string.concat(vm.projectRoot(), v3QuotePath)); + console.log("[LOG] v3QuoteJsonStr: %s", v3QuoteJsonStr); + bytes memory v3QuotePacked = vm.parseJson(v3QuoteJsonStr); + console.logBytes(v3QuotePacked); + + (, V3Struct.ParsedV3QuoteStruct memory v3quote) = parseV3QuoteJson(v3QuotePacked); + console.log("v3quote.header.userData = %s", address(v3quote.header.userData)); + console.logBytes(v3quote.localEnclaveReport.reportData); + (bool verified,) = attestation.verifyParsedQuote(v3quote); + + assertTrue(verified); + } + + function testParsedQuoteAbiEncoding() public { + vm.prank(user); + string memory v3QuoteJsonStr = vm.readFile(string.concat(vm.projectRoot(), v3QuotePath)); + bytes memory v3QuotePacked = vm.parseJson(v3QuoteJsonStr); + + (, V3Struct.ParsedV3QuoteStruct memory v3quote) = parseV3QuoteJson(v3QuotePacked); + bytes32 hash = keccak256(abi.encode(v3quote)); + //console.logBytes32(hash); + assertEq(hash, 0xa27c4167ab139dffb020230b2ec856080d0e1af437b3a2c2beea1c9af17469bc); + } +} diff --git a/packages/protocol/test/automata-attestation/assets/0923/identity.json b/packages/protocol/test/automata-attestation/assets/0923/identity.json new file mode 100644 index 00000000000..621a2eab13f --- /dev/null +++ b/packages/protocol/test/automata-attestation/assets/0923/identity.json @@ -0,0 +1,78 @@ +{ + "enclaveIdentity": { + "id": "QE", + "version": 2, + "issueDate": "2024-01-03T01:56:45Z", + "nextUpdate": "2024-02-02T01:56:45Z", + "tcbEvaluationDataNumber": 16, + "miscselect": "00000000", + "miscselectMask": "FFFFFFFF", + "attributes": "11000000000000000000000000000000", + "attributesMask": "FBFFFFFFFFFFFFFF0000000000000000", + "mrsigner": "8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF", + "isvprodid": 1, + "tcbLevels": [ + { + "tcb": { + "isvsvn": 8 + }, + "tcbDate": "2023-08-09T00:00:00Z", + "tcbStatus": "UpToDate" + }, + { + "tcb": { + "isvsvn": 6 + }, + "tcbDate": "2021-11-10T00:00:00Z", + "tcbStatus": "OutOfDate", + "advisoryIDs": ["INTEL-SA-00615"] + }, + { + "tcb": { + "isvsvn": 5 + }, + "tcbDate": "2020-11-11T00:00:00Z", + "tcbStatus": "OutOfDate", + "advisoryIDs": ["INTEL-SA-00477", "INTEL-SA-00615"] + }, + { + "tcb": { + "isvsvn": 4 + }, + "tcbDate": "2019-11-13T00:00:00Z", + "tcbStatus": "OutOfDate", + "advisoryIDs": ["INTEL-SA-00334", "INTEL-SA-00477", "INTEL-SA-00615"] + }, + { + "tcb": { + "isvsvn": 2 + }, + "tcbDate": "2019-05-15T00:00:00Z", + "tcbStatus": "OutOfDate", + "advisoryIDs": [ + "INTEL-SA-00219", + "INTEL-SA-00293", + "INTEL-SA-00334", + "INTEL-SA-00477", + "INTEL-SA-00615" + ] + }, + { + "tcb": { + "isvsvn": 1 + }, + "tcbDate": "2018-08-15T00: 00: 00Z", + "tcbStatus": "OutOfDate", + "advisoryIDs": [ + "INTEL-SA-00202", + "INTEL-SA-00219", + "INTEL-SA-00293", + "INTEL-SA-00334", + "INTEL-SA-00477", + "INTEL-SA-00615" + ] + } + ] + }, + "signature": "510deee07e2ca622264c87e8324351a3c3faf7679ee13efa2883d4cc30373c6f51e45f1fc95c983410bf66604f1ee218a3ee6c8e279367a1991f40cba53aff9b" +} diff --git a/packages/protocol/test/automata-attestation/assets/0923/tcbInfo.json b/packages/protocol/test/automata-attestation/assets/0923/tcbInfo.json new file mode 100644 index 00000000000..0be7ef91eaa --- /dev/null +++ b/packages/protocol/test/automata-attestation/assets/0923/tcbInfo.json @@ -0,0 +1,221 @@ +{ + "tcbInfo": { + "version": 2, + "issueDate": "2024-01-10T02:32:48Z", + "nextUpdate": "2024-02-09T02:32:48Z", + "fmspc": "00606a000000", + "pceId": "0000", + "tcbType": 0, + "tcbEvaluationDataNumber": 16, + "tcbLevels": [ + { + "tcb": { + "sgxtcbcomp01svn": 12, + "sgxtcbcomp02svn": 12, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 1, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-08-09T00:00:00Z", + "tcbStatus": "SWHardeningNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 12, + "sgxtcbcomp02svn": 12, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-08-09T00:00:00Z", + "tcbStatus": "ConfigurationAndSWHardeningNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 11, + "sgxtcbcomp02svn": 11, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 1, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-02-15T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 11, + "sgxtcbcomp02svn": 11, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2023-02-15T00:00:00Z", + "tcbStatus": "OutOfDateConfigurationNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 7, + "sgxtcbcomp02svn": 9, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 1, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2022-08-10T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 7, + "sgxtcbcomp02svn": 9, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 13 + }, + "tcbDate": "2022-08-10T00:00:00Z", + "tcbStatus": "OutOfDateConfigurationNeeded" + }, + { + "tcb": { + "sgxtcbcomp01svn": 4, + "sgxtcbcomp02svn": 4, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 11 + }, + "tcbDate": "2021-11-10T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 4, + "sgxtcbcomp02svn": 4, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 10 + }, + "tcbDate": "2020-11-11T00:00:00Z", + "tcbStatus": "OutOfDate" + }, + { + "tcb": { + "sgxtcbcomp01svn": 4, + "sgxtcbcomp02svn": 4, + "sgxtcbcomp03svn": 3, + "sgxtcbcomp04svn": 3, + "sgxtcbcomp05svn": 255, + "sgxtcbcomp06svn": 255, + "sgxtcbcomp07svn": 0, + "sgxtcbcomp08svn": 0, + "sgxtcbcomp09svn": 0, + "sgxtcbcomp10svn": 0, + "sgxtcbcomp11svn": 0, + "sgxtcbcomp12svn": 0, + "sgxtcbcomp13svn": 0, + "sgxtcbcomp14svn": 0, + "sgxtcbcomp15svn": 0, + "sgxtcbcomp16svn": 0, + "pcesvn": 5 + }, + "tcbDate": "2018-01-04T00:00:00Z", + "tcbStatus": "OutOfDate" + } + ] + }, + "signature": "82d69c3a986618c91ac973fc2c4ae1baf7ec0de1c1e2366a412dabc3198c507caafb6bce84c8ee3ca13091f442ae387688431ff2779257328b59bfd7cfd2772e" +} diff --git a/packages/protocol/test/automata-attestation/assets/0923/v3quote.json b/packages/protocol/test/automata-attestation/assets/0923/v3quote.json new file mode 100644 index 00000000000..352bc55e0cb --- /dev/null +++ b/packages/protocol/test/automata-attestation/assets/0923/v3quote.json @@ -0,0 +1,57 @@ +{ + "header": { + "version": "0x0300", + "attestationKeyType": "0x0200", + "teeType": "0x00000000", + "qeSvn": "0x0a00", + "pceSvn": "0x0f00", + "qeVendorId": "0x939a7233f79c4ca9940a0db3957f0607", + "userData": "0x12ce6af1e4a81e0ecdac427b99bb029500000000" + }, + "localEnclaveReport": { + "cpuSvn": "0x0b0b100fffff00000000000000000000", + "miscSelect": "0x00000000", + "reserved1": "0x00000000000000000000000000000000000000000000000000000000", + "attributes": "0x0700000000000000e700000000000000", + "mrEnclave": "0xe56a8f1fac6b812c5c77e270467be1d5a8aebed2017e16b258710834ef9de957", + "reserved2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "mrSigner": "0x1d3d2b8e78a9081c4d7865026f984b265197696dfe4a0598a2d0ef0764f700f5", + "reserved3": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "isvProdId": 0, + "isvSvn": 0, + "reserved4": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "reportData": "0xb28644a88665e31dd0af25aa85344426a2f7dce30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "v3AuthData": { + "ecdsa256BitSignature": "0xc5f4e73d19cf88ec676981adbe9ffa48ffcf71892842a797a5522ac776add292cc93eeac910f9c8c817721a986d487cd09076e13097c9064e626cae78efa7ff3", + "ecdsaAttestationKey": "0xc7277e139f5f2982256989fb65198701d836f8d6f15256ff05d4891bcadae813757a7c09fd1ce02297783baf66b9d97662b5fc38053c34970280bea0eb6e1a7e", + "pckSignedQeReport": { + "cpuSvn": "0x0b0b100fffff00000000000000000000", + "miscSelect": "0x00000000", + "reserved1": "0x00000000000000000000000000000000000000000000000000000000", + "attributes": "0x1500000000000000e700000000000000", + "mrEnclave": "0x96b347a64e5a045e27369c26e6dcda51fd7c850e9b3a3a79e718f43261dee1e4", + "reserved2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "mrSigner": "0x8c4f5775d796503e96137f77c68a829a0056ac8ded70140b081b094490c57bff", + "reserved3": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "isvProdId": 1, + "isvSvn": 10, + "reserved4": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "reportData": "0x35b9ea12f4cf90ec68e8f4b0cbeb15ab6c70e858f1ed8b00c6f3b8471bf114660000000000000000000000000000000000000000000000000000000000000000" + }, + "qeReportSignature": "0x5ec0f952f3ef6572b1dfa26bbd07e36d47bff6cfa731c726bd977f93beda6edf836944a8ddd88f3809ab8746a1875cf13089e481d8c36275d1fb71f5837c58f1", + "qeAuthData": { + "parsedDataSize": 32, + "data": "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + }, + "certification": { + "certType": 5, + "certDataSize": 3682, + "decodedCertDataArray": [ + "0x308204f330820499a003020102021500bcfe8d88f1717f9f26898051b089aa089f22897a300a06082a8648ce3d04030230703122302006035504030c19496e74656c205347582050434b20506c6174666f726d204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553301e170d3233303832383131313330355a170d3330303832383131313330355a30703122302006035504030c19496e74656c205347582050434b204365727469666963617465311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d0301070342000432b004ab4baae47a3d781dfd494a9b8f3b5343515cb35c10afa75878d85d74514d001545a0d58e3ca1e060eedbcde57d884c6101e731c18f38eff64b96c948d4a382030e3082030a301f0603551d23041830168014956f5dcdbd1be1e94049c9d4f433ce01570bde54306b0603551d1f046430623060a05ea05c865a68747470733a2f2f6170692e7472757374656473657276696365732e696e74656c2e636f6d2f7367782f63657274696669636174696f6e2f76342f70636b63726c3f63613d706c6174666f726d26656e636f64696e673d646572301d0603551d0e041604145357a665cf5bc991849f9098fc3627f3aa06b058300e0603551d0f0101ff0404030206c0300c0603551d130101ff040230003082023b06092a864886f84d010d010482022c30820228301e060a2a864886f84d010d01010410fe46ae011cce8a4d6e8334b08d1bb40130820165060a2a864886f84d010d0102308201553010060b2a864886f84d010d01020102010b3010060b2a864886f84d010d01020202010b3010060b2a864886f84d010d0102030201033010060b2a864886f84d010d0102040201033011060b2a864886f84d010d010205020200ff3011060b2a864886f84d010d010206020200ff3010060b2a864886f84d010d0102070201003010060b2a864886f84d010d0102080201003010060b2a864886f84d010d0102090201003010060b2a864886f84d010d01020a0201003010060b2a864886f84d010d01020b0201003010060b2a864886f84d010d01020c0201003010060b2a864886f84d010d01020d0201003010060b2a864886f84d010d01020e0201003010060b2a864886f84d010d01020f0201003010060b2a864886f84d010d0102100201003010060b2a864886f84d010d01021102010d301f060b2a864886f84d010d01021204100b0b0303ffff000000000000000000003010060a2a864886f84d010d0103040200003014060a2a864886f84d010d0104040600606a000000300f060a2a864886f84d010d01050a0101301e060a2a864886f84d010d010604104589ccebf2644f0ade48ff1e15c46bfb3044060a2a864886f84d010d010730363010060b2a864886f84d010d0107010101ff3010060b2a864886f84d010d0107020101ff3010060b2a864886f84d010d0107030101ff300a06082a8648ce3d040302034800304502201ab7bf1d8335a99004599474c7be98f6040aef385e0a050a0230489578709693022100e37a7bb6bc01f34b3eda2c1a8660dd02708cc0954f2e2484bb00d017c544812c", + "0x308202963082023da003020102021500956f5dcdbd1be1e94049c9d4f433ce01570bde54300a06082a8648ce3d0403023068311a301806035504030c11496e74656c2053475820526f6f74204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553301e170d3138303532313130353031305a170d3333303532313130353031305a30703122302006035504030c19496e74656c205347582050434b20506c6174666f726d204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d0301070342000435207feeddb595748ed82bb3a71c3be1e241ef61320c6816e6b5c2b71dad5532eaea12a4eb3f948916429ea47ba6c3af82a15e4b19664e52657939a2d96633dea381bb3081b8301f0603551d2304183016801422650cd65a9d3489f383b49552bf501b392706ac30520603551d1f044b30493047a045a043864168747470733a2f2f6365727469666963617465732e7472757374656473657276696365732e696e74656c2e636f6d2f496e74656c534758526f6f7443412e646572301d0603551d0e04160414956f5dcdbd1be1e94049c9d4f433ce01570bde54300e0603551d0f0101ff04040302010630120603551d130101ff040830060101ff020100300a06082a8648ce3d040302034700304402205ec5648b4c3e8ba558196dd417fdb6b9a5ded182438f551e9c0f938c3d5a8b970220261bd520260f9c647d3569be8e14a32892631ac358b994478088f4d2b27cf37e", + "0x3082028f30820234a003020102021422650cd65a9d3489f383b49552bf501b392706ac300a06082a8648ce3d0403023068311a301806035504030c11496e74656c2053475820526f6f74204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553301e170d3138303532313130343531305a170d3439313233313233353935395a3068311a301806035504030c11496e74656c2053475820526f6f74204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b30090603550406130255533059301306072a8648ce3d020106082a8648ce3d030107034200040ba9c4c0c0c86193a3fe23d6b02cda10a8bbd4e88e48b4458561a36e705525f567918e2edc88e40d860bd0cc4ee26aacc988e505a953558c453f6b0904ae7394a381bb3081b8301f0603551d2304183016801422650cd65a9d3489f383b49552bf501b392706ac30520603551d1f044b30493047a045a043864168747470733a2f2f6365727469666963617465732e7472757374656473657276696365732e696e74656c2e636f6d2f496e74656c534758526f6f7443412e646572301d0603551d0e0416041422650cd65a9d3489f383b49552bf501b392706ac300e0603551d0f0101ff04040302010630120603551d130101ff040830060101ff020101300a06082a8648ce3d0403020349003046022100e5bfe50911f92f428920dc368a302ee3d12ec5867ff622ec6497f78060c13c20022100e09d25ac7a0cb3e5e8e68fec5fa3bd416c47440bd950639d450edcbea4576aa2" + ] + } + } +} \ No newline at end of file diff --git a/packages/protocol/test/automata-attestation/assets/complex.json b/packages/protocol/test/automata-attestation/assets/complex.json new file mode 100644 index 00000000000..4f0a0be7e13 --- /dev/null +++ b/packages/protocol/test/automata-attestation/assets/complex.json @@ -0,0 +1,25 @@ +{ + "a_header": { + "version": "0xCDCD", + "attestationKeyType": "0x0200", + "teeType": "0x00000000", + "qeSvn": "0x0900", + "pceSvn": "0x0e00", + "qeVendorId": "0x939a7233f79c4ca9940a0db3957f0607", + "userData": "0x12ce6af1e4a81e0ecdac427b99bb029500000000" + }, + "b_miscSelect": { + "cpuSvn": "0x0b0b100fffff00000000000000000000", + "miscSelect": "0x00000000", + "reserved1": "0x00000000000000000000000000000000000000000000000000000000", + "attributes": "0x0700000000000000e700000000000000", + "mrEnclave": "0x21c3fac1fee25b4f8a4f5764da7dc52ff9c3cf2aa5b6b68c5a19c4ec69c51421", + "reserved2": "0x0000000000000000000000000000000000000000000000000000000000000000", + "mrSigner": "0x1d3d2b8e78a9081c4d7865026f984b265197696dfe4a0598a2d0ef0764f700f5", + "reserved3": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "isvProdId": 0, + "isvSvn": 0, + "reserved4": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "reportData": "0x01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" + } +} diff --git a/packages/protocol/test/automata-attestation/common/AttestationBase.t.sol b/packages/protocol/test/automata-attestation/common/AttestationBase.t.sol new file mode 100644 index 00000000000..0fae1abbd8f --- /dev/null +++ b/packages/protocol/test/automata-attestation/common/AttestationBase.t.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.24; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "forge-std/StdJson.sol"; +import "../../../contracts/L1/verifiers/SgxVerifier.sol"; +import { AutomataDcapV3Attestation } from + "../../../contracts/thirdparty/automata-attestation/AutomataDcapV3Attestation.sol"; +import { P256Verifier } from "../../../lib/p256-verifier/src/P256Verifier.sol"; +import { SigVerifyLib } from + "../../../contracts/thirdparty/automata-attestation/utils/SigVerifyLib.sol"; +import { PEMCertChainLib } from + "../../../contracts/thirdparty/automata-attestation/lib/PEMCertChainLib.sol"; +import { V3Struct } from + "../../../contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; +import { BytesUtils } from "../../../contracts/thirdparty/automata-attestation/utils/BytesUtils.sol"; +import { Base64 } from "../../../lib/solady/src/utils/Base64.sol"; +import "../utils/DcapTestUtils.t.sol"; +import "../utils/V3JsonUtils.t.sol"; + +contract AttestationBase is Test, DcapTestUtils, V3JsonUtils { + using BytesUtils for bytes; + using stdJson for string; + + AutomataDcapV3Attestation attestation; + SigVerifyLib sigVerifyLib; + P256Verifier p256Verifier; + PEMCertChainLib pemCertChainLib; + // use a network that where the P256Verifier contract exists + // ref: https://github.com/daimo-eth/p256-verifier + //string internal rpcUrl = vm.envString("RPC_URL"); + string internal tcbInfoPath = "/test/automata-attestation/assets/0923/tcbInfo.json"; + string internal idPath = "/test/automata-attestation/assets/0923/identity.json"; + string internal v3QuotePath = "/test/automata-attestation/assets/0923/v3quote.json"; + address constant admin = address(1); + address constant user = 0x0926b716f6aEF52F9F3C3474A2846e1Bf1ACedf6; + bytes32 mrEnclave = 0x46049af725ec3986eeb788693df7bc5f14d3f2705106a19cd09b9d89237db1a0; + bytes32 mrSigner = 0xef69011f29043f084e99ce420bfebdfa410aee1e132014e7ceff29efa9659bd9; + + bytes sampleQuote = + hex"030002000000000009000e00939a7233f79c4ca9940a0db3957f0607ccb12a326354d33986ff47365f17ad4c000000000c0c100fffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000e70000000000000046049af725ec3986eeb788693df7bc5f14d3f2705106a19cd09b9d89237db1a00000000000000000000000000000000000000000000000000000000000000000ef69011f29043f084e99ce420bfebdfa410aee1e132014e7ceff29efa9659bd90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ca10000084af1f392be216944059f3fa05bf91e1b4e9b513c67493521eb4488af35f49c8f300d57955afc1df97d423c8718ed5b0af82f71047a229df221faa6817ad5daa44131b5c2ed877295959f7333543ba3f17994d767da194a27ba7a4e8a71940118a138dce8499572433c2cc4e4312f92e7144b26f84c59022bfc9aea59967f00d0c0c100fffff0100000000000000000000000000000000000000000000000000000000000000000000000000000000001500000000000000e700000000000000192aa50ce1c0cef03ccf89e7b5b16b0d7978f5c2b1edcf774d87702e8154d8bf00000000000000000000000000000000000000000000000000000000000000008c4f5775d796503e96137f77c68a829a0056ac8ded70140b081b094490c57bff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a654bcd78ffaa5cfc888fc90cbc24fb7f6e19bc8661671f1e3b2cc947db3b6340000000000000000000000000000000000000000000000000000000000000000839adce904d2aec1fc021ad0ec370c7176942d4b64939b95a2e1e1d3e09bf2e57093231f4308b64e8f53b81cd6ae36fc52f202e66ac77b93b13307ee577be36b2000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f0500620e00002d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494945386a4343424a696741774942416749554b6e314f2b2b58517264456161433535634a4c307470464867336b77436759494b6f5a497a6a3045417749770a634445694d434147413155454177775a535735305a577767553064594946424453794251624746305a6d397962534244515445614d42674741315545436777520a535735305a577767513239796347397959585270623234784644415342674e564241634d43314e68626e526849454e7359584a684d51737743515944565151490a44414a445154454c4d416b474131554542684d4356564d774868634e4d6a4d774f4449304d6a45304d444d775768634e4d7a41774f4449304d6a45304d444d770a576a42774d534977494159445651514444426c4a626e526c624342545231676755454e4c49454e6c636e52705a6d6c6a5958526c4d526f77474159445651514b0a4442464a626e526c6243424462334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e560a4241674d416b4e424d517377435159445651514745774a56557a425a4d424d4742797147534d34394167454743437147534d34394177454841304941424e47520a727a716c416d4a66617756324b67656a39576e774a736666457868445631756847396e6d57377430505a646e6276732f6c677872584255625657436d5043456f0a4f49587768563673736d6e6b6b48462b576d536a67674d4f4d494944436a416642674e5648534d4547444157674253566231334e765276683655424a796454300a4d383442567776655644427242674e56485238455a4442694d47436758714263686c706f64485277637a6f764c32467761533530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c334e6e6543396a5a584a3061575a7059324630615739754c33597a4c33426a61324e796244396a595431770a624746305a6d397962535a6c626d4e765a476c755a7a316b5a584977485159445652304f424259454641337234524b62476e54316e584c775a5a7272515559410a4a6b776c4d41344741315564447745422f775145417749477744414d42674e5648524d4241663845416a41414d4949434f77594a4b6f5a496876684e415130420a424949434c444343416967774867594b4b6f5a496876684e415130424151515179753373424e6d7632566643337932772f445344627a434341575547436971470a534962345451454e41514977676746564d42414743797147534962345451454e415149424167454d4d42414743797147534962345451454e415149434167454d0a4d42414743797147534962345451454e41514944416745444d42414743797147534962345451454e41514945416745444d42454743797147534962345451454e0a41514946416749412f7a415242677371686b69472b4530424451454342674943415038774541594c4b6f5a496876684e4151304241676343415145774541594c0a4b6f5a496876684e4151304241676743415141774541594c4b6f5a496876684e4151304241676b43415141774541594c4b6f5a496876684e4151304241676f430a415141774541594c4b6f5a496876684e4151304241677343415141774541594c4b6f5a496876684e4151304241677743415141774541594c4b6f5a496876684e0a4151304241673043415141774541594c4b6f5a496876684e4151304241673443415141774541594c4b6f5a496876684e4151304241673843415141774541594c0a4b6f5a496876684e4151304241684143415141774541594c4b6f5a496876684e4151304241684543415130774877594c4b6f5a496876684e41513042416849450a4541774d4177502f2f7745414141414141414141414141774541594b4b6f5a496876684e4151304241775143414141774641594b4b6f5a496876684e415130420a4241514741474271414141414d41384743697147534962345451454e4151554b415145774867594b4b6f5a496876684e415130424267515136657645326f42790a6f684e362f30727741346d642b6a424542676f71686b69472b453042445145484d4459774541594c4b6f5a496876684e4151304242774542416638774541594c0a4b6f5a496876684e4151304242774942415141774541594c4b6f5a496876684e4151304242774d4241514177436759494b6f5a497a6a304541774944534141770a52514967522b344377346437476a73684848436c7a394c6269785a4a45632f31666c7a734449504d5451437a2b43304349514430516e6d514c2b4e6b4e374a7a0a655a666c5078644734687a374b652b3443595366744b416a48545a7539413d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436c6a4343416a32674177494241674956414a567658633239472b487051456e4a3150517a7a674658433935554d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c756447567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a62334a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a414a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a45784d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484178496a41670a42674e5642414d4d47556c756447567349464e4857434251513073675547786864475a76636d306751304578476a415942674e5642416f4d45556c75644756730a49454e76636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b474131554543417743513045780a437a414a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741454e53422f377432316c58534f0a3243757a7078773734654a423732457944476757357258437478327456544c7136684b6b367a2b5569525a436e71523770734f766771466553786c6d546c4a6c0a65546d693257597a33714f42757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f536347724442530a42674e5648523845537a424a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b633256790a646d6c6a5a584d75615735305a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e5648513445466751556c5739640a7a62306234656c4153636e553944504f4156634c336c517744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159420a4166384341514177436759494b6f5a497a6a30454177494452774177524149675873566b6930772b6936565947573355462f32327561586530594a446a3155650a6e412b546a44316169356343494359623153416d4435786b66545670766f34556f79695359787244574c6d5552344349394e4b7966504e2b0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a6737535655723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d4267474131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e760a636e4276636d4630615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c756447567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d4547444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e567a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d397664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a55723951477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b533943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c72486f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a00"; + + function intialSetup() public { + // pinned September 23rd, 2023, 0221 UTC + // comment this line out if you are replacing sampleQuote with your own + // this line is needed to bypass expiry reverts for stale quotes + vm.warp(1_695_435_682); + + vm.deal(admin, 100 ether); + + vm.startPrank(admin); + p256Verifier = new P256Verifier(); + sigVerifyLib = new SigVerifyLib(address(p256Verifier)); + pemCertChainLib = new PEMCertChainLib(); + attestation = new AutomataDcapV3Attestation(address(sigVerifyLib), address(pemCertChainLib)); + + setMrEnclave(address(attestation), mrEnclave); + setMrSigner(address(attestation), mrSigner); + + string memory tcbInfoJson = vm.readFile(string.concat(vm.projectRoot(), tcbInfoPath)); + string memory enclaveIdJson = vm.readFile(string.concat(vm.projectRoot(), idPath)); + + string memory fmspc = "00606a000000"; + (bool tcbParsedSuccess, TCBInfoStruct.TCBInfo memory parsedTcbInfo) = + parseTcbInfoJson(tcbInfoJson); + require(tcbParsedSuccess, "tcb parsed failed"); + attestation.configureTcbInfoJson(fmspc, parsedTcbInfo); + + configureQeIdentityJson(address(attestation), enclaveIdJson); + vm.stopPrank(); + } + + function setMrEnclave(address _attestationAddress, bytes32 _mrEnclave) internal { + AutomataDcapV3Attestation(_attestationAddress).setMrEnclave(_mrEnclave, true); + } + + function setMrSigner(address _attestationAddress, bytes32 _mrSigner) internal { + AutomataDcapV3Attestation(_attestationAddress).setMrSigner(_mrSigner, true); + } + + function configureQeIdentityJson( + address _attestationAddress, + string memory _enclaveIdJson + ) + internal + { + (bool qeIdParsedSuccess, EnclaveIdStruct.EnclaveId memory parsedEnclaveId) = + parseEnclaveIdentityJson(_enclaveIdJson); + AutomataDcapV3Attestation(_attestationAddress).configureQeIdentityJson(parsedEnclaveId); + console.log("qeIdParsedSuccess: %s", qeIdParsedSuccess); + } + + function configureTcbInfoJson( + address _attestationAddress, + string memory _tcbInfoJson + ) + internal + { + (bool tcbParsedSuccess, TCBInfoStruct.TCBInfo memory parsedTcbInfo) = + parseTcbInfoJson(_tcbInfoJson); + string memory fmspc = parsedTcbInfo.fmspc; + AutomataDcapV3Attestation(_attestationAddress).configureTcbInfoJson(fmspc, parsedTcbInfo); + console.log("tcbParsedSuccess: %s", tcbParsedSuccess); + } + + function registerSgxInstanceWithQuote( + address _sgxVerifier, + string memory _v3QuoteJsonStr + ) + internal + { + console.log("[LOG] v3QuoteJsonStr: %s", _v3QuoteJsonStr); + bytes memory v3QuotePacked = vm.parseJson(_v3QuoteJsonStr); + console.logBytes(v3QuotePacked); + + (, V3Struct.ParsedV3QuoteStruct memory v3quote) = parseV3QuoteJson(v3QuotePacked); + console.log("v3quote.header.userData = %s", address(v3quote.header.userData)); + console.logBytes(v3quote.localEnclaveReport.reportData); + uint256 ret = SgxVerifier(_sgxVerifier).registerInstance(v3quote); + console.log("ret: %s", ret); + } +} diff --git a/packages/protocol/test/automata-attestation/utils/DcapTestUtils.t.sol b/packages/protocol/test/automata-attestation/utils/DcapTestUtils.t.sol new file mode 100644 index 00000000000..ed32e29dcc5 --- /dev/null +++ b/packages/protocol/test/automata-attestation/utils/DcapTestUtils.t.sol @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { TCBInfoStruct } from + "../../../contracts/thirdparty/automata-attestation/lib/TCBInfoStruct.sol"; +import { EnclaveIdStruct } from + "../../../contracts/thirdparty/automata-attestation/lib/EnclaveIdStruct.sol"; +import { V3Struct } from + "../../../contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; +import { JSONParserLib } from "../../../lib/solady/src/utils/JSONParserLib.sol"; +import { LibString } from "../../../lib/solady/src/utils/LibString.sol"; + +contract DcapTestUtils { + using JSONParserLib for JSONParserLib.Item; + using LibString for string; + + uint256 constant INDEX_ERROR = type(uint256).max; + + function parseTcbInfoJson(string memory tcbInfoJsonStr) + internal + pure + returns (bool success, TCBInfoStruct.TCBInfo memory tcbInfo) + { + JSONParserLib.Item memory root = JSONParserLib.parse(tcbInfoJsonStr); + JSONParserLib.Item[] memory children = root.children(); + JSONParserLib.Item[] memory tcbInfoObj; + + uint256 tcbInfoIndex = INDEX_ERROR; + + for (uint256 i; i < root.size(); ++i) { + string memory decodedKey = JSONParserLib.decodeString(children[i].key()); + if (decodedKey.eq("tcbInfo")) { + tcbInfoObj = children[i].children(); + tcbInfoIndex = i; + } + } + + if (tcbInfoIndex == INDEX_ERROR) { + return (false, tcbInfo); + } + + JSONParserLib.Item[] memory tcbLevels; + + bool pceIdFound; + bool fmspcFound; + bool tcbLevelsFound; + + for (uint256 i = 0; i < children[tcbInfoIndex].size(); i++) { + JSONParserLib.Item memory current = tcbInfoObj[i]; + string memory decodedKey = JSONParserLib.decodeString(current.key()); + + if (decodedKey.eq("pceId")) { + tcbInfo.pceid = JSONParserLib.decodeString(current.value()); + pceIdFound = true; + } + + if (decodedKey.eq("fmspc")) { + tcbInfo.fmspc = JSONParserLib.decodeString(current.value()); + fmspcFound = true; + } + + if (decodedKey.eq("tcbLevels")) { + tcbLevels = current.children(); + uint256 tcbLevelsSize = current.size(); + tcbInfo.tcbLevels = new TCBInfoStruct.TCBLevelObj[](tcbLevelsSize); + _parsev2TcbLevels(tcbInfo, tcbLevels, tcbLevelsSize); + tcbLevelsFound = true; + } + } + + success = pceIdFound && fmspcFound && tcbLevelsFound; + } + + struct EnclaveIdFlag { + bool miscselectFound; + bool miscselectMaskFound; + bool attributesFound; + bool attributesMaskFound; + bool mrsignerFound; + bool isvprodidFound; + bool tcbLevelsFound; + } + + function parseEnclaveIdentityJson(string memory enclaveIdJsonStr) + internal + pure + returns (bool success, EnclaveIdStruct.EnclaveId memory enclaveId) + { + JSONParserLib.Item memory root = JSONParserLib.parse(enclaveIdJsonStr); + JSONParserLib.Item[] memory children = root.children(); + JSONParserLib.Item[] memory qeIdObj; + + EnclaveIdFlag memory flag; + + for (uint256 i = 0; i < root.size(); i++) { + string memory decodedKey = JSONParserLib.decodeString(children[i].key()); + if (decodedKey.eq("enclaveIdentity")) { + qeIdObj = children[i].children(); + for (uint256 j = 0; j < children[i].size(); j++) { + decodedKey = JSONParserLib.decodeString(qeIdObj[j].key()); + string memory decodedValue; + if (qeIdObj[j].isString()) { + decodedValue = JSONParserLib.decodeString(qeIdObj[j].value()); + } + if (decodedKey.eq("miscselect")) { + bytes memory hexString = _fromHex(decodedValue); + enclaveId.miscselect = bytes4(hexString); + flag.miscselectFound = true; + } + if (decodedKey.eq("miscselectMask")) { + bytes memory hexString = _fromHex(decodedValue); + enclaveId.miscselectMask = bytes4(hexString); + flag.miscselectMaskFound = true; + } + if (decodedKey.eq("attributes")) { + bytes memory hexString = _fromHex(decodedValue); + enclaveId.attributes = bytes16(hexString); + flag.attributesFound = true; + } + if (decodedKey.eq("attributesMask")) { + bytes memory hexString = _fromHex(decodedValue); + enclaveId.attributesMask = bytes16(hexString); + flag.attributesMaskFound = true; + } + if (decodedKey.eq("mrsigner")) { + bytes memory hexString = _fromHex(decodedValue); + enclaveId.mrsigner = bytes32(hexString); + flag.mrsignerFound = true; + } + if (decodedKey.eq("isvprodid")) { + enclaveId.isvprodid = uint16(JSONParserLib.parseUint(qeIdObj[j].value())); + flag.isvprodidFound = true; + } + if (decodedKey.eq("tcbLevels")) { + JSONParserLib.Item memory current = qeIdObj[j]; + JSONParserLib.Item[] memory tcbLevels = current.children(); + uint256 tcbLevelsSize = current.size(); + enclaveId.tcbLevels = new EnclaveIdStruct.TcbLevel[](tcbLevelsSize); + bool parsedSuccessfully = + _parseQuoteIdentityTcbLevels(enclaveId, tcbLevels, tcbLevelsSize); + flag.tcbLevelsFound = parsedSuccessfully; + } + } + } + } + + success = flag.miscselectFound && flag.miscselectMaskFound && flag.attributesFound + && flag.attributesMaskFound && flag.mrsignerFound && flag.isvprodidFound + && flag.tcbLevelsFound; + } + + function _parseQuoteIdentityTcbLevels( + EnclaveIdStruct.EnclaveId memory enclaveId, + JSONParserLib.Item[] memory tcbLevels, + uint256 tcbLevelsSize + ) + private + pure + returns (bool) + { + for (uint256 j = 0; j < tcbLevelsSize; j++) { + JSONParserLib.Item[] memory tcbObjValue = tcbLevels[j].children(); + for (uint256 k = 0; k < tcbLevels[j].size(); k++) { + string memory decodedKey = JSONParserLib.decodeString(tcbObjValue[k].key()); + if (decodedKey.eq("tcb")) { + JSONParserLib.Item memory isvsvn = (tcbObjValue[k].children())[0]; + decodedKey = JSONParserLib.decodeString(isvsvn.key()); + if (decodedKey.eq("isvsvn")) { + enclaveId.tcbLevels[j].tcb.isvsvn = + uint16(JSONParserLib.parseUint(isvsvn.value())); + } else { + return false; + } + } else if (decodedKey.eq("tcbStatus")) { + string memory decodedValue = JSONParserLib.decodeString(tcbObjValue[k].value()); + if (decodedValue.eq("UpToDate")) { + enclaveId.tcbLevels[j].tcbStatus = EnclaveIdStruct.EnclaveIdStatus.OK; + } else if (decodedValue.eq("Revoked")) { + enclaveId.tcbLevels[j].tcbStatus = + EnclaveIdStruct.EnclaveIdStatus.SGX_ENCLAVE_REPORT_ISVSVN_REVOKED; + } + } + } + } + return true; + } + + function _parsev2TcbLevels( + TCBInfoStruct.TCBInfo memory tcbInfo, + JSONParserLib.Item[] memory tcbLevels, + uint256 tcbLevelsSize + ) + private + pure + { + for (uint256 j = 0; j < tcbLevelsSize; j++) { + JSONParserLib.Item[] memory tcbObjValue = tcbLevels[j].children(); + for (uint256 k = 0; k < tcbLevels[j].size(); k++) { + string memory decodedKey = JSONParserLib.decodeString(tcbObjValue[k].key()); + if (decodedKey.eq("tcb")) { + JSONParserLib.Item[] memory tcb = tcbObjValue[k].children(); + tcbInfo.tcbLevels[j].sgxTcbCompSvnArr = new uint8[](tcbObjValue[k].size() - 1); + for (uint256 l = 0; l < tcbObjValue[k].size(); l++) { + decodedKey = JSONParserLib.decodeString(tcb[l].key()); + if (decodedKey.eq("pcesvn")) { + tcbInfo.tcbLevels[j].pcesvn = JSONParserLib.parseUint(tcb[l].value()); + } else { + tcbInfo.tcbLevels[j].sgxTcbCompSvnArr[l] = + uint8(JSONParserLib.parseUint(tcb[l].value())); + } + } + } else if (decodedKey.eq("tcbStatus")) { + string memory decodedValue = JSONParserLib.decodeString(tcbObjValue[k].value()); + if (decodedValue.eq("UpToDate")) { + tcbInfo.tcbLevels[j].status = TCBInfoStruct.TCBStatus.OK; + } else if (decodedValue.eq("OutOfDate")) { + tcbInfo.tcbLevels[j].status = TCBInfoStruct.TCBStatus.TCB_OUT_OF_DATE; + } else if (decodedValue.eq("OutOfDateConfigurationNeeded")) { + tcbInfo.tcbLevels[j].status = + TCBInfoStruct.TCBStatus.TCB_OUT_OF_DATE_CONFIGURATION_NEEDED; + } else if (decodedValue.eq("ConfigurationNeeded")) { + tcbInfo.tcbLevels[j].status = + TCBInfoStruct.TCBStatus.TCB_CONFIGURATION_NEEDED; + } else if (decodedValue.eq("ConfigurationAndSWHardeningNeeded")) { + tcbInfo.tcbLevels[j].status = + TCBInfoStruct.TCBStatus.TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED; + } else if (decodedValue.eq("SWHardeningNeeded")) { + tcbInfo.tcbLevels[j].status = + TCBInfoStruct.TCBStatus.TCB_SW_HARDENING_NEEDED; + } else if (decodedValue.eq("Revoked")) { + tcbInfo.tcbLevels[j].status = TCBInfoStruct.TCBStatus.TCB_REVOKED; + } + } + } + } + } + + // Converts a string to a hexstring (of bytes type) + // https://ethereum.stackexchange.com/questions/39989/solidity-convert-hex-string-to-bytes + + // Convert an hexadecimal character to their value + function _fromHexChar(uint8 c) private pure returns (uint8) { + if (bytes1(c) >= bytes1("0") && bytes1(c) <= bytes1("9")) { + return c - uint8(bytes1("0")); + } + if (bytes1(c) >= bytes1("a") && bytes1(c) <= bytes1("f")) { + return 10 + c - uint8(bytes1("a")); + } + if (bytes1(c) >= bytes1("A") && bytes1(c) <= bytes1("F")) { + return 10 + c - uint8(bytes1("A")); + } + revert("failed to convert hex value"); + } + + // Convert an hexadecimal string to raw bytes + function _fromHex(string memory s) private pure returns (bytes memory) { + bytes memory ss = bytes(s); + require(ss.length % 2 == 0); // length must be even + bytes memory r = new bytes(ss.length / 2); + for (uint256 i = 0; i < ss.length / 2; ++i) { + r[i] = bytes1(_fromHexChar(uint8(ss[2 * i])) * 16 + _fromHexChar(uint8(ss[2 * i + 1]))); + } + return r; + } +} diff --git a/packages/protocol/test/automata-attestation/utils/V3JsonUtils.t.sol b/packages/protocol/test/automata-attestation/utils/V3JsonUtils.t.sol new file mode 100644 index 00000000000..ff0e337f143 --- /dev/null +++ b/packages/protocol/test/automata-attestation/utils/V3JsonUtils.t.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import { V3Struct } from + "../../../contracts/thirdparty/automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; +import { JSONParserLib } from "../../../lib/solady/src/utils/JSONParserLib.sol"; +import { LibString } from "../../../lib/solady/src/utils/LibString.sol"; + +contract V3JsonUtils { + using JSONParserLib for JSONParserLib.Item; + using LibString for string; + + // all helper structure are ordered by alphabetical order of their field names + // because the foundry Json decoder will decode the json string in alphabetical order + struct HeaderHelper { + bytes attestationKeyType; + bytes pceSvn; + bytes qeSvn; + bytes qeVendorId; + bytes teeType; + address userData; + bytes version; + } + + struct EnclaveReportHelper { + bytes attributes; + bytes cpuSvn; + uint256 isvProdId; + uint256 isvSvn; + bytes miscSelect; + bytes32 mrEnclave; + bytes32 mrSigner; + bytes reportData; // 64 bytes - For QEReports, this contains the hash of the concatenation + // of attestation key and QEAuthData + bytes reserved1; + bytes32 reserved2; + bytes reserved3; // 96 bytes + bytes reserved4; // 60 bytes + } + + struct QEAuthDataHelper { + bytes32 data; + uint256 parsedDataSize; + } + + // in case data length is 20 + struct QEAuthDataHelperAddress { + address data; + uint256 parsedDataSize; + } + + // in case data length is neither 20 nor 32 + struct QEAuthDataHelperBytes { + bytes data; + uint256 parsedDataSize; + } + + struct CertificationDataHelper { + uint256 certDataSize; + uint256 certType; + bytes[] decodedCertDataArray; + } + + struct ECDSAQuoteV3AuthDataHelper { + CertificationDataHelper certification; + bytes ecdsa256BitSignature; + bytes ecdsaAttestationKey; + EnclaveReportHelper pckSignedQeReport; + QEAuthDataHelper qeAuthData; + bytes qeReportSignature; + } + + struct ParsedV3QuoteStructHelper { + HeaderHelper header; + EnclaveReportHelper localEnclaveReport; + ECDSAQuoteV3AuthDataHelper v3AuthData; + } + + function parseV3QuoteJson(bytes memory v3QuotePacked) + internal + pure + returns (bool success, V3Struct.ParsedV3QuoteStruct memory v3quote) + { + success = true; + ParsedV3QuoteStructHelper memory v3quoteHelper = + abi.decode(v3QuotePacked, (ParsedV3QuoteStructHelper)); + + // setup header + v3quote.header.version = bytes2(v3quoteHelper.header.version); + v3quote.header.attestationKeyType = bytes2(v3quoteHelper.header.attestationKeyType); + v3quote.header.teeType = bytes4(v3quoteHelper.header.teeType); + v3quote.header.qeSvn = bytes2(v3quoteHelper.header.qeSvn); + v3quote.header.pceSvn = bytes2(v3quoteHelper.header.pceSvn); + v3quote.header.qeVendorId = bytes16(v3quoteHelper.header.qeVendorId); + v3quote.header.userData = bytes20(v3quoteHelper.header.userData); + + // setup localEnclaveReport + v3quote.localEnclaveReport.cpuSvn = bytes16(v3quoteHelper.localEnclaveReport.cpuSvn); + v3quote.localEnclaveReport.miscSelect = bytes4(v3quoteHelper.localEnclaveReport.miscSelect); + v3quote.localEnclaveReport.reserved1 = bytes28(v3quoteHelper.localEnclaveReport.reserved1); + v3quote.localEnclaveReport.attributes = bytes16(v3quoteHelper.localEnclaveReport.attributes); + v3quote.localEnclaveReport.mrEnclave = v3quoteHelper.localEnclaveReport.mrEnclave; + v3quote.localEnclaveReport.reserved2 = v3quoteHelper.localEnclaveReport.reserved2; + v3quote.localEnclaveReport.mrSigner = v3quoteHelper.localEnclaveReport.mrSigner; + v3quote.localEnclaveReport.reserved3 = bytes(v3quoteHelper.localEnclaveReport.reserved3); + v3quote.localEnclaveReport.isvProdId = uint16(v3quoteHelper.localEnclaveReport.isvProdId); + v3quote.localEnclaveReport.isvSvn = uint16(v3quoteHelper.localEnclaveReport.isvSvn); + v3quote.localEnclaveReport.reserved4 = bytes(v3quoteHelper.localEnclaveReport.reserved4); + v3quote.localEnclaveReport.reportData = bytes(v3quoteHelper.localEnclaveReport.reportData); + + // setup v3AuthData + v3quote.v3AuthData.ecdsa256BitSignature = + bytes(v3quoteHelper.v3AuthData.ecdsa256BitSignature); + v3quote.v3AuthData.ecdsaAttestationKey = bytes(v3quoteHelper.v3AuthData.ecdsaAttestationKey); + v3quote.v3AuthData.pckSignedQeReport = V3Struct.EnclaveReport({ + cpuSvn: bytes16(v3quoteHelper.v3AuthData.pckSignedQeReport.cpuSvn), + miscSelect: bytes4(v3quoteHelper.v3AuthData.pckSignedQeReport.miscSelect), + reserved1: bytes28(v3quoteHelper.v3AuthData.pckSignedQeReport.reserved1), + attributes: bytes16(v3quoteHelper.v3AuthData.pckSignedQeReport.attributes), + mrEnclave: v3quoteHelper.v3AuthData.pckSignedQeReport.mrEnclave, + reserved2: v3quoteHelper.v3AuthData.pckSignedQeReport.reserved2, + mrSigner: v3quoteHelper.v3AuthData.pckSignedQeReport.mrSigner, + reserved3: bytes(v3quoteHelper.v3AuthData.pckSignedQeReport.reserved3), + isvProdId: uint16(v3quoteHelper.v3AuthData.pckSignedQeReport.isvProdId), + isvSvn: uint16(v3quoteHelper.v3AuthData.pckSignedQeReport.isvSvn), + reserved4: bytes(v3quoteHelper.v3AuthData.pckSignedQeReport.reserved4), + reportData: bytes(v3quoteHelper.v3AuthData.pckSignedQeReport.reportData) + }); + v3quote.v3AuthData.qeReportSignature = bytes(v3quoteHelper.v3AuthData.qeReportSignature); + v3quote.v3AuthData.qeAuthData = V3Struct.ParsedQEAuthData({ + parsedDataSize: uint16(v3quoteHelper.v3AuthData.qeAuthData.parsedDataSize), + data: bytes.concat(v3quoteHelper.v3AuthData.qeAuthData.data) + }); + v3quote.v3AuthData.certification = V3Struct.ParsedCertificationData({ + certType: uint16(v3quoteHelper.v3AuthData.certification.certType), + certDataSize: uint32(v3quoteHelper.v3AuthData.certification.certDataSize), + decodedCertDataArray: [ + v3quoteHelper.v3AuthData.certification.decodedCertDataArray[0], + v3quoteHelper.v3AuthData.certification.decodedCertDataArray[1], + v3quoteHelper.v3AuthData.certification.decodedCertDataArray[2] + ] + }); + } +}