Skip to content

Commit 05d77bd

Browse files
authored
Merge pull request #498 from Dstack-TEE/feature/app-tcb-toggle
feat(kms): optional TCB UpToDate requirement for apps
2 parents c019113 + 7bf5bfb commit 05d77bd

13 files changed

Lines changed: 797 additions & 51 deletions

File tree

kms/auth-eth/contracts/DstackApp.sol

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,50 @@ contract DstackApp is
3333
// Mapping of allowed device IDs for this app
3434
mapping(bytes32 => bool) public allowedDeviceIds;
3535

36+
// Whether to require TCB status to be UpToDate
37+
bool public requireTcbUpToDate;
38+
3639
// Additional events specific to DstackApp
3740
event UpgradesDisabled();
3841
event AllowAnyDeviceSet(bool allowAny);
42+
event RequireTcbUpToDateSet(bool requireUpToDate);
3943

4044
/// @custom:oz-upgrades-unsafe-allow constructor
4145
constructor() {
4246
_disableInitializers();
4347
}
4448

45-
// Initialize the contract
49+
// Old initialize — preserved for upgrade compatibility
50+
function initialize(
51+
address initialOwner,
52+
bool _disableUpgrades,
53+
bool _allowAnyDevice,
54+
bytes32 initialDeviceId,
55+
bytes32 initialComposeHash
56+
) public initializer {
57+
_initializeCommon(initialOwner, _disableUpgrades, _allowAnyDevice, initialDeviceId, initialComposeHash);
58+
}
59+
60+
// New initialize — includes requireTcbUpToDate
4661
function initialize(
4762
address initialOwner,
4863
bool _disableUpgrades,
64+
bool _requireTcbUpToDate,
4965
bool _allowAnyDevice,
5066
bytes32 initialDeviceId,
5167
bytes32 initialComposeHash
5268
) public initializer {
69+
requireTcbUpToDate = _requireTcbUpToDate;
70+
_initializeCommon(initialOwner, _disableUpgrades, _allowAnyDevice, initialDeviceId, initialComposeHash);
71+
}
72+
73+
function _initializeCommon(
74+
address initialOwner,
75+
bool _disableUpgrades,
76+
bool _allowAnyDevice,
77+
bytes32 initialDeviceId,
78+
bytes32 initialComposeHash
79+
) internal {
5380
require(initialOwner != address(0), "invalid owner address");
5481

5582
_upgradesDisabled = _disableUpgrades;
@@ -72,6 +99,10 @@ contract DstackApp is
7299
__ERC165_init();
73100
}
74101

102+
function version() public pure returns (uint256) {
103+
return 2;
104+
}
105+
75106
/**
76107
* @dev See {IERC165-supportsInterface}.
77108
* @notice Returns true if this contract implements the interface defined by interfaceId
@@ -114,6 +145,12 @@ contract DstackApp is
114145
emit AllowAnyDeviceSet(_allowAnyDevice);
115146
}
116147

148+
// Set whether TCB status must be UpToDate to boot this app
149+
function setRequireTcbUpToDate(bool _requireUpToDate) external onlyOwner {
150+
requireTcbUpToDate = _requireUpToDate;
151+
emit RequireTcbUpToDateSet(_requireUpToDate);
152+
}
153+
117154
// Add a device ID to allowed list
118155
function addDevice(bytes32 deviceId) external onlyOwner {
119156
allowedDeviceIds[deviceId] = true;
@@ -130,6 +167,15 @@ contract DstackApp is
130167
function isAppAllowed(
131168
IAppAuth.AppBootInfo calldata bootInfo
132169
) external view override returns (bool isAllowed, string memory reason) {
170+
// Optionally require TCB status to be up to date
171+
if (
172+
requireTcbUpToDate &&
173+
keccak256(abi.encodePacked(bootInfo.tcbStatus)) !=
174+
keccak256(abi.encodePacked("UpToDate"))
175+
) {
176+
return (false, "TCB status is not up to date");
177+
}
178+
133179
// Check if compose hash is allowed
134180
if (!allowedComposeHashes[bootInfo.composeHash]) {
135181
return (false, "Compose hash not allowed");
@@ -150,5 +196,5 @@ contract DstackApp is
150196
}
151197

152198
// Add storage gap for upgradeable contracts
153-
uint256[50] private __gap;
199+
uint256[49] private __gap;
154200
}

kms/auth-eth/contracts/DstackKms.sol

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,30 +144,47 @@ contract DstackKms is
144144
function deployAndRegisterApp(
145145
address initialOwner,
146146
bool disableUpgrades,
147+
bool requireTcbUpToDate,
147148
bool allowAnyDevice,
148149
bytes32 initialDeviceId,
149150
bytes32 initialComposeHash
150-
) external returns (address appId) {
151+
) public returns (address appId) {
151152
require(appImplementation != address(0), "DstackApp implementation not set");
152153
require(initialOwner != address(0), "Invalid owner address");
153154

154-
// Prepare initialization data
155155
bytes memory initData = abi.encodeWithSelector(
156-
bytes4(keccak256("initialize(address,bool,bool,bytes32,bytes32)")),
156+
bytes4(keccak256("initialize(address,bool,bool,bool,bytes32,bytes32)")),
157157
initialOwner,
158158
disableUpgrades,
159+
requireTcbUpToDate,
159160
allowAnyDevice,
160161
initialDeviceId,
161162
initialComposeHash
162163
);
163164

164-
// Deploy proxy contract
165165
appId = address(new ERC1967Proxy(appImplementation, initData));
166-
// Register to KMS
167166
registerApp(appId);
168167
emit AppDeployedViaFactory(appId, msg.sender);
169168
}
170169

170+
// Backward compatible factory method for old SDK callers (pre TCB flag)
171+
function deployAndRegisterApp(
172+
address initialOwner,
173+
bool disableUpgrades,
174+
bool allowAnyDevice,
175+
bytes32 initialDeviceId,
176+
bytes32 initialComposeHash
177+
) external returns (address appId) {
178+
return deployAndRegisterApp(
179+
initialOwner,
180+
disableUpgrades,
181+
false,
182+
allowAnyDevice,
183+
initialDeviceId,
184+
initialComposeHash
185+
);
186+
}
187+
171188
// Function to register an aggregated MR measurement
172189
function addKmsAggregatedMr(bytes32 mrAggregated) external onlyOwner {
173190
kmsAllowedAggregatedMrs[mrAggregated] = true;

kms/auth-eth/foundry-cast-cheatsheet.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ npx hardhat kms:deploy-impl --network test
8484
cast send $KMS_CONTRACT_ADDRESS "upgradeTo(address)" "NEW_IMPL_ADDRESS" \
8585
--private-key $PRIVATE_KEY --rpc-url $RPC_URL --gas-limit 500000
8686

87+
# Note: Existing KMS proxy deployments can be upgraded in-place using the steps above.
88+
# This release only adds optional app boot TCB checks in DstackApp and keeps the KMS
89+
# storage layout unchanged, so no initializer is required for the KMS upgrade.
90+
8791
# Verify upgrade success
8892
cast storage $KMS_CONTRACT_ADDRESS 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url $RPC_URL
8993
# Should show the new implementation address
@@ -181,18 +185,18 @@ cast send $KMS_CONTRACT_ADDRESS "removeKmsDevice(bytes32)" \
181185

182186
```bash
183187
# kms:create-app - Deploy and register DstackApp in single transaction
184-
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bytes32,bytes32)" \
185-
"$DEPLOYER_ADDRESS" false true \
188+
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)" \
189+
"$DEPLOYER_ADDRESS" false false true \
186190
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" \
187191
"0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321" \
188192
--private-key $PRIVATE_KEY --rpc-url $RPC_URL
189-
# Parameters: (owner, disableUpgrades, allowAnyDevice, initialDeviceId, initialComposeHash)
193+
# Parameters: (owner, disableUpgrades, requireTcbUpToDate, allowAnyDevice, initialDeviceId, initialComposeHash)
190194
# Use 0x0000...0000 for empty device/hash values
191-
# To decode return: cast abi-decode "deployAndRegisterApp(address,bool,bool,bytes32,bytes32)(address,address)" RETURN_DATA
195+
# To decode return: cast abi-decode "deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)(address,address)" RETURN_DATA
192196

193197
# Example with no initial data:
194-
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bytes32,bytes32)" \
195-
"$DEPLOYER_ADDRESS" false true \
198+
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)" \
199+
"$DEPLOYER_ADDRESS" false false true \
196200
"0x0000000000000000000000000000000000000000000000000000000000000000" \
197201
"0x0000000000000000000000000000000000000000000000000000000000000000" \
198202
--private-key $PRIVATE_KEY --rpc-url $RPC_URL
@@ -379,7 +383,7 @@ cast abi-decode "kmsAllowedAggregatedMrs(bytes32)(bool)" RETURN_DATA
379383
cast abi-decode "isAppAllowed((address,bytes32,address,bytes32,bytes32,bytes32,bytes32,string,string[]))(bool,string)" RETURN_DATA
380384

381385
# Decode factory deployment response
382-
cast abi-decode "deployAndRegisterApp(address,bool,bool,bytes32,bytes32)(address,address)" RETURN_DATA
386+
cast abi-decode "deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)(address,address)" RETURN_DATA
383387
```
384388

385389
### Get Contract Information
@@ -459,7 +463,7 @@ cast send $KMS_CONTRACT_ADDRESS "addKmsAggregatedMr(bytes32)" "0x..." \
459463
--private-key $PRIVATE_KEY --rpc-url $RPC_URL
460464

461465
# 3. Users can now deploy apps via factory immediately!
462-
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bytes32,bytes32)" \
466+
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)" \
463467
"$USER_ADDRESS" false true "0x..." "0x..." \
464468
--private-key $USER_PRIVATE_KEY --rpc-url $RPC_URL
465469
```
@@ -485,7 +489,7 @@ cast send $KMS_CONTRACT_ADDRESS "addKmsAggregatedMr(bytes32)" "0x..." \
485489
--private-key $PRIVATE_KEY --rpc-url $RPC_URL
486490

487491
# 5. Users can now deploy apps via factory
488-
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bytes32,bytes32)" \
492+
cast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)" \
489493
"$USER_ADDRESS" false true "0x..." "0x..." \
490494
--private-key $USER_PRIVATE_KEY --rpc-url $RPC_URL
491495
```
@@ -547,7 +551,7 @@ mycast send $APP_AUTH_ADDRESS "addDevice(bytes32)" \
547551
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
548552

549553
# Example: Factory deployment
550-
mycast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bytes32,bytes32)" \
554+
mycast send $KMS_CONTRACT_ADDRESS "deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)" \
551555
"$DEPLOYER_ADDRESS" false true \
552556
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" \
553557
"0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"
@@ -557,4 +561,4 @@ mycast storage $KMS_CONTRACT_ADDRESS 0x360894a13ba1a3210667c828492db98dca3e2076c
557561

558562
# Example: Upgrade contract
559563
mycast send $KMS_CONTRACT_ADDRESS "upgradeTo(address)" "NEW_IMPL_ADDRESS" --gas-limit 500000
560-
```
564+
```

kms/auth-eth/hardhat.config.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ task("kms:get-app-implementation", "Get current DstackApp implementation address
255255

256256
task("app:deploy", "Deploy DstackApp with a UUPS proxy")
257257
.addFlag("allowAnyDevice", "Allow any device to boot this app")
258+
.addFlag("requireTcbUpToDate", "Require TCB status to be UpToDate")
258259
.addOptionalParam("device", "Initial device ID", "", types.string)
259260
.addOptionalParam("hash", "Initial compose hash", "", types.string)
260261
.setAction(async (taskArgs, hre) => {
@@ -278,14 +279,14 @@ task("app:deploy", "Deploy DstackApp with a UUPS proxy")
278279
console.log("Initial compose hash:", composeHash === "0x0000000000000000000000000000000000000000000000000000000000000000" ? "none" : composeHash);
279280
}
280281

281-
// Use standard deployment - all cases use the same 6-parameter initializer
282282
const appContract = await deployContract(hre, "DstackApp", [
283-
deployerAddress,
284-
false,
285-
taskArgs.allowAnyDevice,
283+
deployerAddress,
284+
false, // _disableUpgrades
285+
taskArgs.requireTcbUpToDate, // _requireTcbUpToDate
286+
taskArgs.allowAnyDevice, // _allowAnyDevice
286287
deviceId,
287288
composeHash
288-
]);
289+
], false, "initialize(address,bool,bool,bool,bytes32,bytes32)");
289290

290291
if (!appContract) {
291292
return;
@@ -344,6 +345,7 @@ task("app:deploy", "Deploy DstackApp with a UUPS proxy")
344345

345346
task("kms:create-app", "Create DstackApp via KMS factory method (single transaction)")
346347
.addFlag("allowAnyDevice", "Allow any device to boot this app")
348+
.addFlag("requireTcbUpToDate", "Require TCB status to be UpToDate")
347349
.addOptionalParam("device", "Initial device ID", "", types.string)
348350
.addOptionalParam("hash", "Initial compose hash", "", types.string)
349351
.setAction(async (taskArgs, hre) => {
@@ -362,10 +364,11 @@ task("kms:create-app", "Create DstackApp via KMS factory method (single transact
362364
console.log("Initial compose hash:", composeHash === "0x0000000000000000000000000000000000000000000000000000000000000000" ? "none" : composeHash);
363365
console.log("Using factory method for single-transaction deployment...");
364366

365-
// Single transaction deployment via factory
366-
const tx = await kmsContract.deployAndRegisterApp(
367+
// Single transaction deployment via factory (explicit signature to disambiguate overloads)
368+
const tx = await kmsContract["deployAndRegisterApp(address,bool,bool,bool,bytes32,bytes32)"](
367369
deployerAddress, // deployer owns the contract
368370
false, // disableUpgrades
371+
taskArgs.requireTcbUpToDate,
369372
taskArgs.allowAnyDevice,
370373
deviceId,
371374
composeHash

kms/auth-eth/lib/deployment-helpers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,15 @@ export async function accountBalance(ethers: any, address: string) {
6262
export async function estimateDeploymentCost(
6363
hre: HardhatRuntimeEnvironment,
6464
contractName: string,
65-
initializerArgs: any[] = []
65+
initializerArgs: any[] = [],
66+
initializer?: string
6667
) {
6768
console.log("Estimating deployment costs...");
6869
const factory = await hre.ethers.getContractFactory(contractName);
6970

7071
// Get the data for initialize function
7172
const initData = factory.interface.encodeFunctionData(
72-
"initialize",
73+
initializer || "initialize",
7374
initializerArgs
7475
);
7576

kms/auth-eth/scripts/deploy.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types";
66
import * as helpers from "../lib/deployment-helpers";
77

88
// This function should be called directly by Hardhat tasks
9-
export async function deployContract(hre: HardhatRuntimeEnvironment, contractName: string, initializerArgs: any[] = [], quiet: boolean = false) {
9+
export async function deployContract(hre: HardhatRuntimeEnvironment, contractName: string, initializerArgs: any[] = [], quiet: boolean = false, initializer?: string) {
1010
try {
1111
function log(...msgs: any[]) {
1212
if (!quiet) {
@@ -29,7 +29,8 @@ export async function deployContract(hre: HardhatRuntimeEnvironment, contractNam
2929
await helpers.estimateDeploymentCost(
3030
hre,
3131
contractName,
32-
initializerArgs
32+
initializerArgs,
33+
initializer
3334
);
3435

3536
// Prompt for confirmation
@@ -43,7 +44,7 @@ export async function deployContract(hre: HardhatRuntimeEnvironment, contractNam
4344
log("Deploying proxy...");
4445
const contract = await hre.upgrades.deployProxy(contractFactory,
4546
initializerArgs,
46-
{ kind: 'uups' }
47+
{ kind: 'uups', ...(initializer ? { initializer } : {}) }
4748
);
4849
log("Waiting for deployment...");
4950
await contract.waitForDeployment();

0 commit comments

Comments
 (0)