Skip to content

Commit bfeaa5b

Browse files
committed
feat(kms): add optional TCB up-to-date check for apps
1 parent 9cb24e8 commit bfeaa5b

File tree

13 files changed

+302
-26
lines changed

13 files changed

+302
-26
lines changed

kms/auth-eth/contracts/DstackApp.sol

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ 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() {
@@ -46,13 +50,15 @@ contract DstackApp is
4650
function initialize(
4751
address initialOwner,
4852
bool _disableUpgrades,
53+
bool _requireTcbUpToDate,
4954
bool _allowAnyDevice,
5055
bytes32 initialDeviceId,
5156
bytes32 initialComposeHash
5257
) public initializer {
5358
require(initialOwner != address(0), "invalid owner address");
5459

5560
_upgradesDisabled = _disableUpgrades;
61+
requireTcbUpToDate = _requireTcbUpToDate;
5662
allowAnyDevice = _allowAnyDevice;
5763

5864
// Add initial device if provided
@@ -114,6 +120,12 @@ contract DstackApp is
114120
emit AllowAnyDeviceSet(_allowAnyDevice);
115121
}
116122

123+
// Set whether TCB status must be UpToDate to boot this app
124+
function setRequireTcbUpToDate(bool _requireUpToDate) external onlyOwner {
125+
requireTcbUpToDate = _requireUpToDate;
126+
emit RequireTcbUpToDateSet(_requireUpToDate);
127+
}
128+
117129
// Add a device ID to allowed list
118130
function addDevice(bytes32 deviceId) external onlyOwner {
119131
allowedDeviceIds[deviceId] = true;
@@ -130,6 +142,15 @@ contract DstackApp is
130142
function isAppAllowed(
131143
IAppAuth.AppBootInfo calldata bootInfo
132144
) external view override returns (bool isAllowed, string memory reason) {
145+
// Optionally require TCB status to be up to date
146+
if (
147+
requireTcbUpToDate &&
148+
keccak256(abi.encodePacked(bootInfo.tcbStatus)) !=
149+
keccak256(abi.encodePacked("UpToDate"))
150+
) {
151+
return (false, "TCB status is not up to date");
152+
}
153+
133154
// Check if compose hash is allowed
134155
if (!allowedComposeHashes[bootInfo.composeHash]) {
135156
return (false, "Compose hash not allowed");
@@ -150,5 +171,5 @@ contract DstackApp is
150171
}
151172

152173
// Add storage gap for upgradeable contracts
153-
uint256[50] private __gap;
174+
uint256[49] private __gap;
154175
}

kms/auth-eth/contracts/DstackKms.sol

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,38 @@ contract DstackKms is
144144
function deployAndRegisterApp(
145145
address initialOwner,
146146
bool disableUpgrades,
147+
bool requireTcbUpToDate,
147148
bool allowAnyDevice,
148149
bytes32 initialDeviceId,
149150
bytes32 initialComposeHash
150151
) external returns (address appId) {
152+
return _deployAndRegisterApp(
153+
initialOwner,
154+
disableUpgrades,
155+
requireTcbUpToDate,
156+
allowAnyDevice,
157+
initialDeviceId,
158+
initialComposeHash
159+
);
160+
}
161+
162+
function _deployAndRegisterApp(
163+
address initialOwner,
164+
bool disableUpgrades,
165+
bool requireTcbUpToDate,
166+
bool allowAnyDevice,
167+
bytes32 initialDeviceId,
168+
bytes32 initialComposeHash
169+
) internal returns (address appId) {
151170
require(appImplementation != address(0), "DstackApp implementation not set");
152171
require(initialOwner != address(0), "Invalid owner address");
153172

154173
// Prepare initialization data
155174
bytes memory initData = abi.encodeWithSelector(
156-
bytes4(keccak256("initialize(address,bool,bool,bytes32,bytes32)")),
175+
bytes4(keccak256("initialize(address,bool,bool,bool,bytes32,bytes32)")),
157176
initialOwner,
158177
disableUpgrades,
178+
requireTcbUpToDate,
159179
allowAnyDevice,
160180
initialDeviceId,
161181
initialComposeHash
@@ -168,6 +188,24 @@ contract DstackKms is
168188
emit AppDeployedViaFactory(appId, msg.sender);
169189
}
170190

191+
// Backward compatible factory method (pre TCB requirement flag)
192+
function deployAndRegisterApp(
193+
address initialOwner,
194+
bool disableUpgrades,
195+
bool allowAnyDevice,
196+
bytes32 initialDeviceId,
197+
bytes32 initialComposeHash
198+
) external returns (address appId) {
199+
return _deployAndRegisterApp(
200+
initialOwner,
201+
disableUpgrades,
202+
false, // requireTcbUpToDate (default)
203+
allowAnyDevice,
204+
initialDeviceId,
205+
initialComposeHash
206+
);
207+
}
208+
171209
// Function to register an aggregated MR measurement
172210
function addKmsAggregatedMr(bytes32 mrAggregated) external onlyOwner {
173211
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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ task("app:deploy", "Deploy DstackApp with a UUPS proxy")
333333

334334
task("kms:create-app", "Create DstackApp via KMS factory method (single transaction)")
335335
.addFlag("allowAnyDevice", "Allow any device to boot this app")
336+
.addFlag("requireTcbUpToDate", "Require TCB status to be UpToDate")
336337
.addOptionalParam("device", "Initial device ID", "", types.string)
337338
.addOptionalParam("hash", "Initial compose hash", "", types.string)
338339
.setAction(async (taskArgs, hre) => {
@@ -355,6 +356,7 @@ task("kms:create-app", "Create DstackApp via KMS factory method (single transact
355356
const tx = await kmsContract.deployAndRegisterApp(
356357
deployerAddress, // deployer owns the contract
357358
false, // disableUpgrades
359+
taskArgs.requireTcbUpToDate,
358360
taskArgs.allowAnyDevice,
359361
deviceId,
360362
composeHash

kms/auth-eth/test/DstackApp.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe("DstackApp", function () {
2020
appAuth = await deployContract(hre, "DstackApp", [
2121
owner.address,
2222
false, // _disableUpgrades
23+
false, // _requireTcbUpToDate
2324
true, // _allowAnyDevice
2425
ethers.ZeroHash, // initialDeviceId (empty)
2526
ethers.ZeroHash // initialComposeHash (empty)
@@ -91,6 +92,26 @@ describe("DstackApp", function () {
9192
expect(isAllowed).to.be.true;
9293
});
9394

95+
it("Should reject outdated TCB when required", async function () {
96+
await appAuth.setRequireTcbUpToDate(true);
97+
98+
const bootInfo = {
99+
appId: appId,
100+
composeHash,
101+
instanceId,
102+
deviceId,
103+
mrAggregated,
104+
mrSystem,
105+
osImageHash,
106+
tcbStatus: "OutOfDate",
107+
advisoryIds: []
108+
};
109+
110+
const [isAllowed, reason] = await appAuth.isAppAllowed(bootInfo);
111+
expect(isAllowed).to.be.false;
112+
expect(reason).to.equal("TCB status is not up to date");
113+
});
114+
94115
it("Should reject unallowed compose hash", async function () {
95116
const bootInfo = {
96117
tcbStatus: "UpToDate",
@@ -138,7 +159,7 @@ describe("DstackApp", function () {
138159
const contractFactory = await ethers.getContractFactory("DstackApp");
139160
appAuthWithData = await hre.upgrades.deployProxy(
140161
contractFactory,
141-
[owner.address, false, false, testDevice, testHash],
162+
[owner.address, false, false, false, testDevice, testHash],
142163
{
143164
kind: 'uups'
144165
}
@@ -237,7 +258,7 @@ describe("DstackApp", function () {
237258
const contractFactory = await ethers.getContractFactory("DstackApp");
238259
const appAuthEmpty = await hre.upgrades.deployProxy(
239260
contractFactory,
240-
[owner.address, false, false, ethers.ZeroHash, ethers.ZeroHash],
261+
[owner.address, false, false, false, ethers.ZeroHash, ethers.ZeroHash],
241262
{
242263
kind: 'uups'
243264
}

kms/auth-eth/test/setup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ beforeAll(async () => {
3333
const appAuth = await deployContract(hre, "DstackApp", [
3434
owner.address,
3535
false, // _disableUpgrades
36+
false, // _requireTcbUpToDate
3637
true, // _allowAnyDevice
3738
ethers.ZeroHash, // initialDeviceId (empty)
3839
ethers.ZeroHash // initialComposeHash (empty)

0 commit comments

Comments
 (0)