Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
43 changes: 35 additions & 8 deletions contracts/solidity/TEERegistry.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
"name": "NotTEEOwner",
"type": "error"
},
{
"inputs": [],
"name": "PCRAlreadyExists",
"type": "error"
},
{
"inputs": [],
"name": "PCRExpired",
Expand All @@ -71,6 +76,11 @@
"name": "PCRNotApproved",
"type": "error"
},
{
"inputs": [],
"name": "PCRTypeMismatch",
"type": "error"
},
{
"inputs": [],
"name": "TEEAlreadyExists",
Expand Down Expand Up @@ -118,6 +128,12 @@
"name": "pcrHash",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "uint8",
"name": "teeType",
"type": "uint8"
},
{
"indexed": false,
"internalType": "string",
Expand All @@ -136,6 +152,12 @@
"internalType": "bytes32",
"name": "pcrHash",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "gracePeriod",
"type": "uint256"
}
],
"name": "PCRRevoked",
Expand Down Expand Up @@ -399,14 +421,9 @@
"type": "string"
},
{
"internalType": "bytes32",
"name": "previousPcrHash",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "gracePeriod",
"type": "uint256"
"internalType": "uint8",
"name": "teeType",
"type": "uint8"
}
],
"name": "approvePCR",
Expand All @@ -429,6 +446,11 @@
"name": "active",
"type": "bool"
},
{
"internalType": "uint8",
"name": "teeType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "approvedAt",
Expand Down Expand Up @@ -984,6 +1006,11 @@
"internalType": "bytes32",
"name": "pcrHash",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "gracePeriod",
"type": "uint256"
}
],
"name": "revokePCR",
Expand Down
84 changes: 63 additions & 21 deletions contracts/solidity/TEERegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract TEERegistry is AccessControl {

struct ApprovedPCR {
bool active;
uint8 teeType;
uint256 approvedAt;
uint256 expiresAt;
string version;
Expand Down Expand Up @@ -75,8 +76,8 @@ contract TEERegistry is AccessControl {

event TEETypeAdded(uint8 indexed typeId, string name);
event TEETypeDeactivated(uint8 indexed typeId);
event PCRApproved(bytes32 indexed pcrHash, string version);
event PCRRevoked(bytes32 indexed pcrHash);
event PCRApproved(bytes32 indexed pcrHash, uint8 indexed teeType, string version);
event PCRRevoked(bytes32 indexed pcrHash, uint256 gracePeriod);
event TEERegistered(bytes32 indexed teeId, address indexed owner, uint8 teeType);
event TEEDeactivated(bytes32 indexed teeId);
event TEEActivated(bytes32 indexed teeId);
Expand All @@ -90,6 +91,8 @@ contract TEERegistry is AccessControl {
error InvalidTEEType();
error PCRNotApproved();
error PCRExpired();
error PCRAlreadyExists();
error PCRTypeMismatch();
error TEEAlreadyExists();
error TEENotFound();
error TEENotActive();
Expand Down Expand Up @@ -141,50 +144,84 @@ contract TEERegistry is AccessControl {

// ============ PCR Management ============

/// @notice Approve a new PCR measurement for a specific TEE type
/// @param pcrs The PCR measurements (pcr0, pcr1, pcr2)
/// @param version Human-readable version string (e.g., "v1.2.0")
/// @param teeType The TEE type this PCR is valid for
function approvePCR(
PCRMeasurements calldata pcrs,
string calldata version,
bytes32 previousPcrHash,
uint256 gracePeriod
uint8 teeType
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (!isValidTEEType(teeType)) revert InvalidTEEType();

bytes32 pcrHash = computePCRHash(pcrs);
// Set expiry on previous PCR if provided
if (previousPcrHash != bytes32(0) && approvedPCRs[previousPcrHash].active) {
approvedPCRs[previousPcrHash].expiresAt = block.timestamp + gracePeriod;
}

// Allow re-approval of revoked/expired PCRs, but not currently active ones
if (isPCRApproved(pcrHash)) revert PCRAlreadyExists();

bool isNew = approvedPCRs[pcrHash].approvedAt == 0;

approvedPCRs[pcrHash] = ApprovedPCR({
active: true,
teeType: teeType,
approvedAt: block.timestamp,
expiresAt: 0,
version: version
});
_pcrList.push(pcrHash);
emit PCRApproved(pcrHash, version);

if (isNew) {
_pcrList.push(pcrHash);
}

emit PCRApproved(pcrHash, teeType, version);
}

function revokePCR(bytes32 pcrHash) external onlyRole(DEFAULT_ADMIN_ROLE) {
approvedPCRs[pcrHash].active = false;
emit PCRRevoked(pcrHash);
/// @notice Revoke a PCR, either immediately or with a grace period
/// @dev TEEs using this PCR are caught lazily at activateTEE() and heartbeat()
/// @param pcrHash The PCR hash to revoke
/// @param gracePeriod Seconds until revocation takes effect (0 = immediate)
function revokePCR(bytes32 pcrHash, uint256 gracePeriod) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (gracePeriod == 0) {
approvedPCRs[pcrHash].active = false;
} else {
approvedPCRs[pcrHash].expiresAt = block.timestamp + gracePeriod;
}
emit PCRRevoked(pcrHash, gracePeriod);
}

/// @notice Check if a PCR is currently approved and not expired
/// @param pcrHash The PCR hash to check
/// @return bool True if approved and not expired
function isPCRApproved(bytes32 pcrHash) public view returns (bool) {
ApprovedPCR storage pcr = approvedPCRs[pcrHash];
if (!pcr.active) return false;
if (pcr.expiresAt != 0 && block.timestamp >= pcr.expiresAt) return false;
return true;
}

/// @dev Reverts with specific error for expired vs revoked/unknown PCRs
function _requirePCRApproved(bytes32 pcrHash) private view {
ApprovedPCR storage pcr = approvedPCRs[pcrHash];
if (!pcr.active) revert PCRNotApproved();
if (pcr.expiresAt != 0 && block.timestamp >= pcr.expiresAt) revert PCRExpired();
}

/// @notice Compute PCR hash from measurements
/// @param pcrs The PCR measurements
/// @return bytes32 Hash of the concatenated PCRs
function computePCRHash(PCRMeasurements calldata pcrs) public pure returns (bytes32) {
return keccak256(abi.encodePacked(pcrs.pcr0, pcrs.pcr1, pcrs.pcr2));
}

/// @notice Get all currently active (approved and not expired) PCRs
/// @return bytes32[] Array of active PCR hashes
function getActivePCRs() external view returns (bytes32[] memory) {
uint256 count = 0;
for (uint256 i = 0; i < _pcrList.length; i++) {
if (isPCRApproved(_pcrList[i])) count++;
}

bytes32[] memory result = new bytes32[](count);
uint256 j = 0;
for (uint256 i = 0; i < _pcrList.length; i++) {
Expand Down Expand Up @@ -228,8 +265,9 @@ contract TEERegistry is AccessControl {
);
if (!valid) revert AttestationInvalid("Attestation verification failed");

// Verify PCR is approved
// Verify PCR is approved and matches the TEE type
if (!isPCRApproved(pcrHash)) revert PCRNotApproved();
if (approvedPCRs[pcrHash].teeType != teeType) revert PCRTypeMismatch();

// Store TEE
tees[teeId] = TEEInfo({
Expand Down Expand Up @@ -260,7 +298,7 @@ contract TEERegistry is AccessControl {
TEEInfo storage tee = tees[teeId];
if (tee.registeredAt == 0) revert TEENotFound();
if (tee.owner != msg.sender && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert NotTEEOwner();
if (!tee.active) return; // Already deactivated, nothing to do
if (!tee.active) return;

tee.active = false;
tee.lastUpdatedAt = block.timestamp;
Expand All @@ -272,7 +310,9 @@ contract TEERegistry is AccessControl {
TEEInfo storage tee = tees[teeId];
if (tee.registeredAt == 0) revert TEENotFound();
if (tee.owner != msg.sender && !hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) revert NotTEEOwner();
if (tee.active) return; // Already active, nothing to do
if (tee.active) return;

_requirePCRApproved(tee.pcrHash);

tee.active = true;
tee.lastUpdatedAt = block.timestamp;
Expand Down Expand Up @@ -317,7 +357,10 @@ contract TEERegistry is AccessControl {
if (tee.registeredAt == 0) revert TEENotFound();
if (!tee.active) revert TEENotActive();

// Reject stale or future signed timestamps.
// Lazy PCR enforcement
_requirePCRApproved(tee.pcrHash);

// Reject stale or future signed timestamps
if (timestamp > block.timestamp) revert HeartbeatTimestampInFuture();
if (block.timestamp - timestamp > heartbeatMaxAge) revert HeartbeatTimestampTooOld();

Expand All @@ -327,11 +370,10 @@ contract TEERegistry is AccessControl {
if (!valid) revert HeartbeatSignatureInvalid();

tee.lastUpdatedAt = block.timestamp;

emit HeartbeatReceived(teeId, timestamp);
}

/// @notice Update the max allowed age for heartbeat timestamps.
/// @notice Update the max allowed age for heartbeat timestamps
function setHeartbeatMaxAge(uint256 maxAge) external onlyRole(DEFAULT_ADMIN_ROLE) {
heartbeatMaxAge = maxAge;
}
Expand Down Expand Up @@ -387,4 +429,4 @@ contract TEERegistry is AccessControl {
function computeTEEId(bytes calldata publicKey) external pure returns (bytes32) {
return keccak256(publicKey);
}
}
}
2 changes: 1 addition & 1 deletion precompiles/tee/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TEERegistry registry = TEERegistry(registryAddress);

// 1. Admin: Setup TEE type and PCRs
registry.addTEEType(0, "LLMProxy");
registry.approvePCR(pcrs, "v1.0.0", bytes32(0), 0);
registry.approvePCR(pcrs, "v1.0.0", 0);

// 2. Operator: Register TEE
bytes32 teeId = registry.registerTEEWithAttestation(
Expand Down
181 changes: 168 additions & 13 deletions scripts/integration/local_tee_workflow.go

Large diffs are not rendered by default.

37 changes: 15 additions & 22 deletions scripts/tee-mgmt-cli/cmd/pcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,38 +34,26 @@ var pcrListCmd = &cobra.Command{

var pcrApproveCmd = &cobra.Command{
Use: "approve",
Short: "Approve a set of PCR measurements (PCR0, PCR1, PCR2) for TEE attestation",
Short: "Approve a set of PCR measurements (PCR0, PCR1, PCR2) for a specific TEE type",
RunE: func(cmd *cobra.Command, args []string) error {
measurementsFile, _ := cmd.Flags().GetString("measurements-file")
pcr0Hex, _ := cmd.Flags().GetString("pcr0")
pcr1Hex, _ := cmd.Flags().GetString("pcr1")
pcr2Hex, _ := cmd.Flags().GetString("pcr2")
version, _ := cmd.Flags().GetString("version")
gracePeriodStr, _ := cmd.Flags().GetString("grace-period")
prevPCRStr, _ := cmd.Flags().GetString("previous-pcr")
teeType, _ := cmd.Flags().GetUint8("tee-type")

pcr0, pcr1, pcr2 := registry.LoadPCRsFromArgs(measurementsFile, pcr0Hex, pcr1Hex, pcr2Hex)

gracePeriod := new(big.Int)
gracePeriod.SetString(gracePeriodStr, 10)

var prevPCR [32]byte
if prevPCRStr != "" {
var err error
prevPCR, err = registry.ParseBytes32(prevPCRStr)
if err != nil {
return fmt.Errorf("invalid --previous-pcr: %w", err)
}
}

pcrHash, _ := client.ComputePCRHash(pcr0, pcr1, pcr2)

fmt.Println("=== Approving PCR ===")
fmt.Printf(" PCR Hash: 0x%s\n", hex.EncodeToString(pcrHash[:]))
fmt.Printf(" Version: %s\n\n", version)
fmt.Printf(" Version: %s\n", version)
fmt.Printf(" TEE Type: %d\n\n", teeType)

account, _ := client.GetAccountAddress()
txHash, err := client.ApprovePCR(account, pcr0, pcr1, pcr2, version, prevPCR, gracePeriod)
txHash, err := client.ApprovePCR(account, pcr0, pcr1, pcr2, version, teeType)
if err != nil {
return fmt.Errorf("failed: %w", err)
}
Expand All @@ -77,17 +65,21 @@ var pcrApproveCmd = &cobra.Command{

var pcrRevokeCmd = &cobra.Command{
Use: "revoke <pcr_hash>",
Short: "Revoke a previously approved PCR hash",
Short: "Revoke a previously approved PCR hash (immediately or with grace period)",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
pcrHash, err := registry.ParseBytes32(args[0])
if err != nil {
return fmt.Errorf("invalid pcrHash: %w", err)
}
gracePeriodStr, _ := cmd.Flags().GetString("grace-period")
gracePeriod := new(big.Int)
gracePeriod.SetString(gracePeriodStr, 10)

account, _ := client.GetAccountAddress()

registry.Log("Revoking PCR: 0x%s", hex.EncodeToString(pcrHash[:]))
txHash, err := client.RevokePCR(account, pcrHash)
registry.Log("Revoking PCR: 0x%s (grace period: %s seconds)", hex.EncodeToString(pcrHash[:]), gracePeriod.String())
txHash, err := client.RevokePCR(account, pcrHash, gracePeriod)
if err != nil {
return fmt.Errorf("failed: %w", err)
}
Expand Down Expand Up @@ -146,8 +138,9 @@ func addPCRFlags(cmd *cobra.Command) {
func init() {
addPCRFlags(pcrApproveCmd)
pcrApproveCmd.Flags().StringP("version", "v", "v1.0.0", "Version label for this PCR set")
pcrApproveCmd.Flags().String("grace-period", "0", "Grace period in seconds before the previous PCR is revoked")
pcrApproveCmd.Flags().String("previous-pcr", "", "PCR hash being rotated out (bytes32 hex)")
pcrApproveCmd.Flags().Uint8("tee-type", 0, "TEE type ID this PCR is valid for")

pcrRevokeCmd.Flags().String("grace-period", "0", "Grace period in seconds before revocation takes effect (0 = immediate)")

addPCRFlags(pcrComputeCmd)

Expand Down
Loading
Loading