Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TOB] DEV-3731: ID-1 #18

Merged
merged 6 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/bases/DaoBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import "../interfaces/IDaoAttestationResolver.sol";
abstract contract DaoBase {
IDaoAttestationResolver public immutable resolver;

// 72bd8361
error Duplicate_Collateral();

constructor(address _resolver) {
resolver = IDaoAttestationResolver(_resolver);
}
Expand Down Expand Up @@ -58,6 +61,22 @@ abstract contract DaoBase {
return _fetchDataFromResolver(key, hash);
}

/**
* @notice check whether the hash for the provided collateral already exists in the PCCS
* @param key - the key to locate the collateral attestation
* @param hash - the hash of the collateral
*/
function _checkCollateralDuplicate(bytes32 key, bytes32 hash) internal view {
// if a matching hash is found, that means the caller is attempting to re-upsert duplicate collateral
bytes memory existingHashData = _fetchDataFromResolver(key, true);
if (existingHashData.length > 0) {
bytes32 existingHash = abi.decode(existingHashData, (bytes32));
if (existingHash == hash) {
revert Duplicate_Collateral();
}
}
}

/// @dev https://github.com/Vectorized/solady/blob/4964e3e2da1bc86b0394f63a90821f51d60a260b/src/utils/JSONParserLib.sol#L339-L364
/// @dev Parses an unsigned integer from a string (in hexadecimal, i.e. base 16).
/// Reverts if `s` is not a valid uint256 hex string matching the RegEx
Expand Down
20 changes: 12 additions & 8 deletions src/bases/EnclaveIdentityDao.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,13 @@ abstract contract EnclaveIdentityDao is DaoBase, SigVerifyBase {
external
returns (bytes32 attestationId)
{
_validateQeIdentity(enclaveIdentityObj);
(bytes32 key, bytes memory req) = _buildEnclaveIdentityAttestationRequest(id, version, enclaveIdentityObj);
bytes32 key = ENCLAVE_ID_KEY(id, version);
bytes32 hash = sha256(bytes(enclaveIdentityObj.identityStr));

_checkCollateralDuplicate(key, hash);

_validateQeIdentity(enclaveIdentityObj, hash);
bytes memory req = _buildEnclaveIdentityAttestationRequest(id, version, key, enclaveIdentityObj);
attestationId = _attestEnclaveIdentity(req, hash, key);

emit UpsertedEnclaveIdentity(id, version);
Expand Down Expand Up @@ -125,8 +129,9 @@ abstract contract EnclaveIdentityDao is DaoBase, SigVerifyBase {
function _buildEnclaveIdentityAttestationRequest(
uint256 id,
uint256 version,
bytes32 key,
EnclaveIdentityJsonObj calldata enclaveIdentityObj
) private view returns (bytes32 key, bytes memory reqData) {
) private view returns (bytes memory reqData) {
IdentityObj memory identity = EnclaveIdentityLib.parseIdentityString(enclaveIdentityObj.identityStr);
if (id != uint256(identity.id)) {
revert Enclave_Id_Mismatch();
Expand All @@ -141,13 +146,12 @@ abstract contract EnclaveIdentityDao is DaoBase, SigVerifyBase {
}

// make sure new collateral is "newer"
key = ENCLAVE_ID_KEY(id, version);
bytes memory existingData = _onFetchDataFromResolver(key, false);
bytes memory existingData = _fetchDataFromResolver(key, false);
if (existingData.length > 0) {
(IdentityObj memory existingIdentity, , ) =
abi.decode(existingData, (IdentityObj, string, bytes));
bool outOfDate = existingIdentity.tcbEvaluationDataNumber > identity.tcbEvaluationDataNumber ||
existingIdentity.issueDateTimestamp > identity.issueDateTimestamp;
existingIdentity.issueDateTimestamp >= identity.issueDateTimestamp;
if (outOfDate) {
revert Enclave_Id_Out_Of_Date();
}
Expand All @@ -159,12 +163,12 @@ abstract contract EnclaveIdentityDao is DaoBase, SigVerifyBase {
/**
* @notice validates IdentityString is signed by Intel TCB Signing Cert
*/
function _validateQeIdentity(EnclaveIdentityJsonObj calldata enclaveIdentityObj) private view {
function _validateQeIdentity(EnclaveIdentityJsonObj calldata enclaveIdentityObj, bytes32 hash) private view {
bytes memory signingDer = _fetchDataFromResolver(Pcs.PCS_KEY(CA.SIGNING, false), false);

// Validate signature
bool sigVerified =
verifySignature(sha256(bytes(enclaveIdentityObj.identityStr)), enclaveIdentityObj.signature, signingDer);
verifySignature(hash, enclaveIdentityObj.signature, signingDer);

if (!sigVerified) {
revert Invalid_TCB_Cert_Signature();
Expand Down
84 changes: 42 additions & 42 deletions src/bases/FmspcTcbDao.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,40 @@
* @param tcbInfoObj See {FmspcTcbHelper.sol} to learn more about the structure definition
*/
function upsertFmspcTcb(TcbInfoJsonObj calldata tcbInfoObj) external returns (bytes32 attestationId) {
_validateTcbInfo(tcbInfoObj);
(
bytes memory req,
bytes32 key,
uint8 tcbId,
bytes6 fmspc,
uint32 version,
uint64 issueDateTimestamp,
uint32 evaluationDataNumber
) = _buildTcbAttestationRequest(tcbInfoObj);
bytes32 hash = sha256(bytes(tcbInfoObj.tcbInfoStr));

// parse tcb info basic here so we can compute the key
(
TcbInfoBasic memory tcbInfo,
string memory tcbLevelsString,
string memory tdxModuleString,
string memory tdxModuleIdentitiesString
) = FmspcTcbLib.parseTcbString(tcbInfoObj.tcbInfoStr);
bytes32 key = FMSPC_TCB_KEY(uint8(tcbInfo.id), tcbInfo.fmspc, tcbInfo.version);

_checkCollateralDuplicate(key, hash);
_validateTcbInfo(tcbInfoObj);

bytes memory req = _buildTcbAttestationRequest(
key,
tcbInfoObj,
tcbInfo,
tcbLevelsString,
tdxModuleString,
tdxModuleIdentitiesString
);

attestationId = _attestTcb(req, hash, key);
_storeTcbInfoIssueEvaluation(key, issueDateTimestamp, evaluationDataNumber);
emit UpsertedFmpscTcb(tcbId, fmspc, version);
_storeTcbInfoIssueEvaluation(
key,
tcbInfo.issueDate,
tcbInfo.evaluationDataNumber
);
emit UpsertedFmpscTcb(
uint8(tcbInfo.id),
tcbInfo.fmspc,
tcbInfo.version
);
}

/**
Expand Down Expand Up @@ -142,70 +162,50 @@
/**
* @notice constructs the TcbInfo.json attestation data
*/
function _buildTcbAttestationRequest(TcbInfoJsonObj calldata tcbInfoObj)
function _buildTcbAttestationRequest(
bytes32 key,
TcbInfoJsonObj calldata tcbInfoObj,
TcbInfoBasic memory tcbInfo,
string memory tcbLevelsString,
string memory tdxModuleString,
string memory tdxModuleIdentitiesString
)
private
view
returns
(
bytes memory reqData,
bytes32 key,
uint8 id,
bytes6 fmspc,
uint32 version,
uint64 issueDateTimestamp,
uint32 evaluationDataNumber
)
returns (bytes memory reqData)
{
TcbInfoBasic memory tcbInfo;

string memory tcbLevelsString;
string memory tdxModuleString;
string memory tdxModuleIdentitiesString;
(
tcbInfo,
tcbLevelsString,
tdxModuleString,
tdxModuleIdentitiesString
) = FmspcTcbLib.parseTcbString(tcbInfoObj.tcbInfoStr);

// check expiration before continuing...
if (block.timestamp < tcbInfo.issueDate || block.timestamp > tcbInfo.nextUpdate) {
revert TCB_Expired();
}

// Make sure new collateral is "newer"
id = uint8(tcbInfo.id);
fmspc = tcbInfo.fmspc;
version = tcbInfo.version;
key = FMSPC_TCB_KEY(id, fmspc, version);
(uint64 existingIssueDate, uint32 existingEvaluationDataNumber) = _loadTcbInfoIssueEvaluation(key);
if (existingIssueDate > 0) {
/// I don't think there can be a scenario where an existing tcbinfo with a higher evaluation data number
/// to be issued BEFORE a new tcbinfo with a lower evaluation data number
bool outOfDate = tcbInfo.evaluationDataNumber < existingEvaluationDataNumber ||
tcbInfo.issueDate < existingIssueDate;
tcbInfo.issueDate <= existingIssueDate;
if (outOfDate) {
revert TCB_Out_Of_Date();
}
}

issueDateTimestamp = tcbInfo.issueDate;
evaluationDataNumber = tcbInfo.evaluationDataNumber;
TCBLevelsObj[] memory tcbLevels = FmspcTcbLib.parseTcbLevels(tcbInfo.version, tcbLevelsString);
bytes memory encodedTcbLevels = _encodeTcbLevels(tcbLevels);
if (tcbInfo.version < 3) {
reqData = abi.encode(tcbInfo, encodedTcbLevels, tcbInfoObj.tcbInfoStr, tcbInfoObj.signature);
} else {
TDXModule memory module;
TDXModuleIdentity[] memory moduleIdentities;
bytes memory encodedModuleIdentities;
if (tcbInfo.id == TcbId.TDX) {
(module, moduleIdentities) = FmspcTcbLib.parseTcbTdxModules(tdxModuleString, tdxModuleIdentitiesString);
encodedModuleIdentities = _encodeTdxModuleIdentities(moduleIdentities);
}
reqData = abi.encode(tcbInfo, module, encodedModuleIdentities, encodedTcbLevels, tcbInfoObj.tcbInfoStr, tcbInfoObj.signature);
}
}

Check notice

Code scanning / Slither

Block timestamp Low


function _validateTcbInfo(TcbInfoJsonObj calldata tcbInfoObj) private view {
// Get TCB Signing Cert
Expand Down
23 changes: 13 additions & 10 deletions src/bases/PckDao.sol
Original file line number Diff line number Diff line change
Expand Up @@ -277,27 +277,30 @@ abstract contract PckDao is DaoBase, SigVerifyBase {
function _validatePck(CA ca, bytes memory der, bytes16 qeid, bytes2 pceid, bytes18 tcbm) internal view returns (bytes32 hash, bytes32 key) {
X509CertObj memory pck = pckLib.parseX509DER(der);

// Step 0: Check whether the pck has expired
hash = keccak256(pck.tbs);
key = PCK_KEY(qeid, pceid, tcbm);

// Step 0: Check whether the certificate has been previously attested
_checkCollateralDuplicate(key, hash);

// Step 1: Check whether the pck has expired
bool notExpired = block.timestamp > pck.validityNotBefore && block.timestamp < pck.validityNotAfter;
if (!notExpired) {
revert Certificate_Expired();
}

hash = keccak256(pck.tbs);
key = PCK_KEY(qeid, pceid, tcbm);

// Step 1: Rollback prevention: new certificate should not have an issued date
// Step 2: Rollback prevention: new certificate should not have an issued date
// that is older than the existing certificate
bytes memory existingData = _fetchDataFromResolver(key, false);
if (existingData.length > 0) {
(uint256 existingCertNotValidBefore, ) = pckLib.getCertValidity(existingData);
bool outOfDate = existingCertNotValidBefore > pck.validityNotBefore;
bool outOfDate = existingCertNotValidBefore >= pck.validityNotBefore;
if (outOfDate) {
revert Pck_Out_Of_Date();
}
}

// Step 2: Check Issuer and Subject names
// Step 3: Check Issuer and Subject names
string memory expectedIssuer;
if (ca == CA.PLATFORM) {
expectedIssuer = PCK_PLATFORM_CA_COMMON_NAME;
Expand All @@ -311,10 +314,10 @@ abstract contract PckDao is DaoBase, SigVerifyBase {
revert Invalid_Subject_Name();
}

// Step 3: validate PCEID and TCBm
// Step 4: validate PCEID and TCBm
_validatePckTcb(pceid, tcbm, der, pck.extensionPtr);

// Step 4: Check whether the pck has been revoked
// Step 5: Check whether the pck has been revoked
bytes memory crlData = _fetchDataFromResolver(Pcs.PCS_KEY(ca, true), false);
if (crlData.length > 0) {
bool revocable = crlLib.serialNumberIsRevoked(pck.serialNumber, crlData);
Expand All @@ -323,7 +326,7 @@ abstract contract PckDao is DaoBase, SigVerifyBase {
}
}

// Step 5: Check signature
// Step 6: Check signature
bytes memory issuerCert = _fetchDataFromResolver(Pcs.PCS_KEY(ca, false), false);
if (issuerCert.length > 0) {
bytes32 digest = sha256(pck.tbs);
Expand Down
22 changes: 14 additions & 8 deletions src/bases/PcsDao.sol
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ abstract contract PcsDao is DaoBase, SigVerifyBase {

function _upsertPcsCrl(CA ca, bytes calldata crl) private returns (bytes32 attestationId) {
(bytes32 hash, bytes32 key) = _validatePcsCrl(ca, crl);

attestationId = _attestPcs(crl, hash, key);

emit UpsertedPCSCollateral(ca, true);
Expand All @@ -151,6 +152,12 @@ abstract contract PcsDao is DaoBase, SigVerifyBase {
X509Helper x509Lib = X509Helper(x509);
X509CertObj memory currentCert = x509Lib.parseX509DER(cert);

key = PCS_KEY(ca, false);
hash = keccak256(currentCert.tbs);

// Step 0: Check whether the provided certificate has been previously attested
_checkCollateralDuplicate(key, hash);

// Step 1: Check whether cert has expired
bool validTimestamp =
block.timestamp > currentCert.validityNotBefore &&
Expand All @@ -161,11 +168,10 @@ abstract contract PcsDao is DaoBase, SigVerifyBase {

// Step 2: Rollback prevention: new certificate should not have an issued date
// that is older than the existing certificate
key = PCS_KEY(ca, false);
bytes memory existingData = _fetchDataFromResolver(key, false);
if (existingData.length > 0) {
(uint256 existingCertNotValidBefore, ) = x509Lib.getCertValidity(existingData);
bool outOfDate = existingCertNotValidBefore > currentCert.validityNotBefore;
bool outOfDate = existingCertNotValidBefore >= currentCert.validityNotBefore;
if (outOfDate) {
revert Certificate_Out_Of_Date();
}
Expand Down Expand Up @@ -222,12 +228,15 @@ abstract contract PcsDao is DaoBase, SigVerifyBase {
if (!sigVerified) {
revert Invalid_Signature();
}

hash = keccak256(currentCert.tbs);
}

function _validatePcsCrl(CA ca, bytes calldata crl) private view returns (bytes32 hash, bytes32 key) {
X509CRLObj memory currentCrl = crlLib.parseCRLDER(crl);

key = PCS_KEY(ca, true);
hash = keccak256(currentCrl.tbs);

_checkCollateralDuplicate(key, hash);

// Step 1: Check whether CRL has expired
bool validTimestamp =
Expand All @@ -239,11 +248,10 @@ abstract contract PcsDao is DaoBase, SigVerifyBase {

// Step 2: Rollback prevention: new CRL should not have an issued date
// that is older than the existing CRL
key = PCS_KEY(ca, true);
bytes memory existingData = _fetchDataFromResolver(key, false);
if (existingData.length > 0) {
(uint256 existingCrlNotValidBefore, ) = crlLib.getCrlValidity(existingData);
bool outOfDate = existingCrlNotValidBefore > currentCrl.validityNotBefore;
bool outOfDate = existingCrlNotValidBefore >= currentCrl.validityNotBefore;
if (outOfDate) {
revert Certificate_Out_Of_Date();
}
Expand All @@ -266,8 +274,6 @@ abstract contract PcsDao is DaoBase, SigVerifyBase {
if (!sigVerified) {
revert Invalid_Signature();
}

hash = keccak256(currentCrl.tbs);
}

function _getIssuer(CA ca) private view returns (bytes memory issuerCert) {
Expand Down
7 changes: 7 additions & 0 deletions test/identity/AutomataEnclaveIdentityTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.0;
import "../pcs/PCSSetupBase.t.sol";
import "./IdentityConstants.t.sol";
import {AutomataEnclaveIdentityDao} from "../../src/automata_pccs/AutomataEnclaveIdentityDao.sol";
import {DaoBase} from "../../src/bases/DaoBase.sol";

contract AutomataEnclaveIdentityDaoTest is PCSSetupBase, IdentityConstants {
function setUp() public override {
Expand All @@ -24,6 +25,12 @@ contract AutomataEnclaveIdentityDaoTest is PCSSetupBase, IdentityConstants {
EnclaveIdentityJsonObj memory fetched = enclaveIdDao.getEnclaveIdentity(id, version);
assertEq(fetched.signature, enclaveIdentityObj.signature);
assertEq(keccak256(bytes(fetched.identityStr)), keccak256(bytes(enclaveIdentityObj.identityStr)));

// duplicate check
vm.expectRevert(abi.encodeWithSelector(
DaoBase.Duplicate_Collateral.selector
));
enclaveIdDao.upsertEnclaveIdentity(id, version, enclaveIdentityObj);
}

function testTcbIssuerChain() public readAsAuthorizedCaller {
Expand Down
8 changes: 8 additions & 0 deletions test/pcs/AutomataPckDaoTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.0;

import "../pcs/PCSSetupBase.t.sol";
import {AutomataPckDao} from "../../src/automata_pccs/AutomataPckDao.sol";
import {DaoBase} from "../../src/bases/DaoBase.sol";

contract AutomataPckDaoTest is PCSSetupBase {
// TEMP: placeholder only, circle back on this to verify the inputs
Expand Down Expand Up @@ -38,6 +39,13 @@ contract AutomataPckDaoTest is PCSSetupBase {
assertEq(keccak256(bytes(fetchedTcbm)), keccak256(bytes(tcbm)));
}

function testDuplicatePckUpsert() public {
vm.expectRevert(abi.encodeWithSelector(
DaoBase.Duplicate_Collateral.selector
));
pck.upsertPckCert(CA.PLATFORM, qeid, pceid, tcbm, pckDer);
}

function testPckIssuerChain() public readAsAuthorizedCaller {
(bytes memory intermediateCert, bytes memory rootCert) = pck.getPckCertChain(CA.PLATFORM);
assertEq(keccak256(platformDer), keccak256(intermediateCert));
Expand Down
Loading
Loading