Skip to content

Commit 7753737

Browse files
committed
refactor(attestation): move TCB measurement extraction to build.rs and remove runtime parsing path
1 parent 57e3f58 commit 7753737

File tree

4 files changed

+207
-7
lines changed

4 files changed

+207
-7
lines changed

crates/mpc-attestation/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name = "mpc-attestation"
33
version = { workspace = true }
44
license = { workspace = true }
55
edition = { workspace = true }
6+
build = "build.rs"
67

78
[dependencies]
89
attestation = { workspace = true }
@@ -19,3 +20,8 @@ sha3 = { workspace = true }
1920
dcap-qvl = { workspace = true }
2021
rstest = { workspace = true }
2122
test_utils = { workspace = true }
23+
24+
[build-dependencies]
25+
serde = { workspace = true }
26+
serde_json = { workspace = true }
27+
hex = { workspace = true }

crates/mpc-attestation/build.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use std::env;
2+
use std::fs::{self, File};
3+
use std::io::Write;
4+
use std::path::PathBuf;
5+
6+
fn main() {
7+
// Location of assets/*.json
8+
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
9+
let assets_dir = PathBuf::from(manifest_dir).join("assets");
10+
11+
// Find all tcb_info*.json files (prod, dev, future ones)
12+
let mut measurement_files = Vec::new();
13+
for entry in fs::read_dir(&assets_dir).unwrap() {
14+
let entry = entry.unwrap();
15+
let path = entry.path();
16+
17+
if path.extension().and_then(|x| x.to_str()) == Some("json")
18+
&& path.file_name().unwrap().to_str().unwrap().starts_with("tcb_info")
19+
{
20+
measurement_files.push(path);
21+
}
22+
}
23+
24+
// Output file
25+
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
26+
let out_file = out_dir.join("measurements_generated.rs");
27+
let mut f = File::create(out_file).unwrap();
28+
29+
// Write prelude
30+
writeln!(
31+
f,
32+
"// AUTO-GENERATED FILE. DO NOT EDIT.\n\
33+
use attestation::measurements::*;
34+
pub const EXPECTED_MEASUREMENTS: &[ExpectedMeasurements] = &[\n"
35+
)
36+
.unwrap();
37+
38+
// Process each file
39+
for path in measurement_files {
40+
let json_str = fs::read_to_string(&path).unwrap();
41+
let tcb: serde_json::Value = serde_json::from_str(&json_str).unwrap();
42+
43+
// extract 4 RTMRs + MRTD
44+
let mrtd = decode_hex(&tcb["mrtd"].as_str().unwrap());
45+
let rtmr0 = decode_hex(&tcb["rtmr0"].as_str().unwrap());
46+
let rtmr1 = decode_hex(&tcb["rtmr1"].as_str().unwrap());
47+
let rtmr2 = decode_hex(&tcb["rtmr2"].as_str().unwrap());
48+
49+
// extract key-provider digest
50+
let mut key_provider_digest = None;
51+
if let Some(events) = tcb["event_log"].as_array() {
52+
for event in events {
53+
if event["event"].as_str().unwrap() == "key-provider" {
54+
key_provider_digest =
55+
Some(decode_hex(event["digest"].as_str().unwrap()));
56+
break;
57+
}
58+
}
59+
}
60+
61+
let key_provider_digest =
62+
key_provider_digest.expect("key-provider event not found");
63+
64+
// Emit Rust struct
65+
writeln!(
66+
f,
67+
" ExpectedMeasurements {{ \
68+
rtmrs: Measurements {{ \
69+
mrtd: {:?}, \
70+
rtmr0: {:?}, \
71+
rtmr1: {:?}, \
72+
rtmr2: {:?} \
73+
}}, \
74+
key_provider_event_digest: {:?}, \
75+
}},",
76+
mrtd, rtmr0, rtmr1, rtmr2, key_provider_digest
77+
)
78+
.unwrap();
79+
}
80+
81+
// Close the array
82+
writeln!(f, "];").unwrap();
83+
}
84+
85+
/// Decode a hex string into a fixed 48-byte array
86+
fn decode_hex(hex: &str) -> [u8; 48] {
87+
let bytes = hex::decode(hex).expect("invalid hex");
88+
assert!(
89+
bytes.len() == 48,
90+
"expected 48-byte measurement, got {} bytes",
91+
bytes.len()
92+
);
93+
let mut arr = [0u8; 48];
94+
arr.copy_from_slice(&bytes);
95+
arr
96+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# 📘 TCB Measurements Build Guide
2+
3+
## 📁 Location of JSON Files
4+
TCB measurement JSON files now live in:
5+
6+
```
7+
crates/mpc-attestation/assets/
8+
```
9+
10+
Typical files:
11+
12+
```
13+
tcb_info.json
14+
tcb_info_dev.json
15+
```
16+
17+
json format is taken directly from the node `/public_data` endpoint.
18+
19+
You can add more files in the future (e.g., staging, new image versions).
20+
Every file matching the prefix:
21+
22+
```
23+
tcb_info*
24+
```
25+
26+
will be automatically included at build time.
27+
28+
---
29+
30+
## 🔄 How to Update Measurements
31+
1. Replace or edit the JSON files under:
32+
33+
```
34+
crates/mpc-attestation/assets/
35+
```
36+
37+
2. Run a rebuild:
38+
39+
```bash
40+
cargo clean -p mpc-attestation
41+
cargo build -p mpc-attestation
42+
```
43+
44+
The build script will:
45+
46+
- parse the JSON files
47+
- decode the measurements
48+
- generate Rust static byte arrays
49+
- embed them into the final WASM contract
50+
51+
No runtime parsing and no JSON reading inside the contract.
52+
53+
---
54+
55+
56+
## 🧬 Location of the Generated Measurements File
57+
During build, Cargo produces:
58+
59+
```
60+
target/debug/build/mpc-attestation-*/out/measurements_generated.rs
61+
```
62+
63+
or in release mode:
64+
65+
```
66+
target/release/build/mpc-attestation-*/out/measurements_generated.rs
67+
```
68+
69+
This file contains:
70+
71+
```rust
72+
pub const EXPECTED_MEASUREMENTS: &[ExpectedMeasurements] = &[
73+
ExpectedMeasurements { … },
74+
ExpectedMeasurements { … },
75+
];
76+
```
77+
78+
And is included into the crate via:
79+
80+
```rust
81+
include!(concat!(env!("OUT_DIR"), "/measurements_generated.rs"));
82+
```
83+
84+
This file is **auto-generated** and should **not be committed** to the repo.

crates/mpc-attestation/src/attestation.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ use attestation::{
66
report_data::ReportData,
77
};
88

9+
include!(concat!(env!("OUT_DIR"), "/measurements_generated.rs"));
10+
11+
pub struct ExpectedMeasurementsSet;
12+
13+
impl ExpectedMeasurementsSet {
14+
pub fn all() -> &'static [ExpectedMeasurements] {
15+
EXPECTED_MEASUREMENTS
16+
}
17+
}
18+
919
pub use attestation::attestation::{DstackAttestation, VerificationError};
1020

1121
use mpc_primitives::hash::{LauncherDockerComposeHash, MpcDockerImageHash};
@@ -70,16 +80,20 @@ impl Attestation {
7080
}
7181
};
7282

83+
//todo remove this
7384
// Embedded JSON assets
74-
const TCB_INFO_STRING_PROD: &str = include_str!("../assets/tcb_info.json");
85+
// const TCB_INFO_STRING_PROD: &str = include_str!("../assets/tcb_info.json");
7586
// TODO Security #1433 - remove dev measurements from production builds after testing is complete.
76-
const TCB_INFO_STRING_DEV: &str = include_str!("../assets/tcb_info_dev.json");
87+
// TCB_INFO_STRING_DEV: &str = include_str!("../assets/tcb_info_dev.json");
88+
89+
//todo remove this
90+
//let accepted_measurements = ExpectedMeasurements::from_embedded_tcb_info(&[
91+
// TCB_INFO_STRING_PROD,
92+
// TCB_INFO_STRING_DEV,
93+
//])
94+
// .map_err(VerificationError::EmbeddedMeasurementsParsing)?;
7795

78-
let accepted_measurements = ExpectedMeasurements::from_embedded_tcb_info(&[
79-
TCB_INFO_STRING_PROD,
80-
TCB_INFO_STRING_DEV,
81-
])
82-
.map_err(VerificationError::EmbeddedMeasurementsParsing)?;
96+
let accepted_measurements = ExpectedMeasurementsSet::all();
8397

8498
attestation.verify(
8599
expected_report_data,

0 commit comments

Comments
 (0)