Skip to main content

a3s_box_runtime/tee/
simulate.rs

1//! Simulated TEE attestation for development and testing.
2//!
3//! When `A3S_TEE_SIMULATE=1` is set, the guest attestation server generates
4//! fake SNP reports with correct field layout but no hardware signature.
5//! The host verifier can accept these with `allow_simulated: true`.
6
7/// Environment variable to enable TEE simulation mode.
8pub const TEE_SIMULATE_ENV: &str = "A3S_TEE_SIMULATE";
9
10/// Check if TEE simulation mode is enabled via environment variable.
11pub fn is_simulate_mode() -> bool {
12    std::env::var(TEE_SIMULATE_ENV)
13        .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
14        .unwrap_or(false)
15}
16
17/// Simulated SNP report version marker.
18/// Real SNP reports use version 2; simulated reports use 0xA3 to distinguish.
19pub const SIMULATED_REPORT_VERSION: u32 = 0xA3;
20
21/// Simulated chip ID (all 0xA3 bytes, clearly fake).
22pub const SIMULATED_CHIP_ID: [u8; 64] = [0xA3; 64];
23
24/// Build a simulated 1184-byte SNP report with the given report_data.
25///
26/// The report has correct field layout per AMD SEV-SNP ABI spec (Table 21)
27/// but uses a marker version (0xA3) and zero signature to indicate simulation.
28/// Nonce, measurement, TCB, and policy fields are populated normally so that
29/// policy checks still work.
30pub fn build_simulated_report(report_data: &[u8; 64]) -> Vec<u8> {
31    const SNP_REPORT_SIZE: usize = 1184;
32    let mut report = vec![0u8; SNP_REPORT_SIZE];
33
34    // version at 0x00 (4 bytes LE) — use simulated marker
35    report[0x00..0x04].copy_from_slice(&SIMULATED_REPORT_VERSION.to_le_bytes());
36
37    // guest_svn at 0x04 (4 bytes LE)
38    report[0x04..0x08].copy_from_slice(&1u32.to_le_bytes());
39
40    // policy at 0x08 (8 bytes LE) — no debug, no SMT
41    report[0x08..0x10].copy_from_slice(&0u64.to_le_bytes());
42
43    // current_tcb at 0x38 (8 bytes)
44    report[0x38] = 3; // boot_loader
45    report[0x39] = 0; // tee
46    report[0x3E] = 8; // snp
47    report[0x3F] = 115; // microcode
48
49    // report_data at 0x50 (64 bytes)
50    report[0x50..0x90].copy_from_slice(report_data);
51
52    // measurement at 0x90 (48 bytes) — deterministic fake measurement
53    for i in 0..48 {
54        report[0x90 + i] = (i as u8).wrapping_mul(0xA3);
55    }
56
57    // chip_id at 0x1A0 (64 bytes)
58    report[0x1A0..0x1E0].copy_from_slice(&SIMULATED_CHIP_ID);
59
60    // signature at 0x2A0 (512 bytes) — left as zeros (simulation marker)
61
62    report
63}
64
65/// Check if an SNP report is a simulated report (version == 0xA3).
66pub fn is_simulated_report(report: &[u8]) -> bool {
67    if report.len() < 4 {
68        return false;
69    }
70    let version = u32::from_le_bytes(report[0x00..0x04].try_into().unwrap_or([0; 4]));
71    version == SIMULATED_REPORT_VERSION
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_is_simulate_mode_default() {
80        // Should be false when env is not set (or set to something else)
81        // We can't reliably test env vars in unit tests without side effects,
82        // so just test the function exists and returns a bool
83        let _ = is_simulate_mode();
84    }
85
86    #[test]
87    fn test_build_simulated_report_size() {
88        let data = [0u8; 64];
89        let report = build_simulated_report(&data);
90        assert_eq!(report.len(), 1184);
91    }
92
93    #[test]
94    fn test_build_simulated_report_version() {
95        let data = [0u8; 64];
96        let report = build_simulated_report(&data);
97        let version = u32::from_le_bytes(report[0x00..0x04].try_into().unwrap());
98        assert_eq!(version, SIMULATED_REPORT_VERSION);
99    }
100
101    #[test]
102    fn test_build_simulated_report_contains_nonce() {
103        let mut data = [0u8; 64];
104        data[0] = 0xDE;
105        data[1] = 0xAD;
106        data[2] = 0xBE;
107        data[3] = 0xEF;
108        let report = build_simulated_report(&data);
109        assert_eq!(report[0x50], 0xDE);
110        assert_eq!(report[0x51], 0xAD);
111        assert_eq!(report[0x52], 0xBE);
112        assert_eq!(report[0x53], 0xEF);
113    }
114
115    #[test]
116    fn test_build_simulated_report_tcb() {
117        let data = [0u8; 64];
118        let report = build_simulated_report(&data);
119        assert_eq!(report[0x38], 3); // boot_loader
120        assert_eq!(report[0x3E], 8); // snp
121        assert_eq!(report[0x3F], 115); // microcode
122    }
123
124    #[test]
125    fn test_build_simulated_report_chip_id() {
126        let data = [0u8; 64];
127        let report = build_simulated_report(&data);
128        assert_eq!(&report[0x1A0..0x1E0], &SIMULATED_CHIP_ID);
129    }
130
131    #[test]
132    fn test_build_simulated_report_zero_signature() {
133        let data = [0u8; 64];
134        let report = build_simulated_report(&data);
135        // Signature at 0x2A0, 512 bytes — should all be zero
136        assert!(report[0x2A0..].iter().all(|&b| b == 0));
137    }
138
139    #[test]
140    fn test_is_simulated_report_true() {
141        let data = [0u8; 64];
142        let report = build_simulated_report(&data);
143        assert!(is_simulated_report(&report));
144    }
145
146    #[test]
147    fn test_is_simulated_report_false() {
148        let mut report = vec![0u8; 1184];
149        report[0x00..0x04].copy_from_slice(&2u32.to_le_bytes()); // real version
150        assert!(!is_simulated_report(&report));
151    }
152
153    #[test]
154    fn test_is_simulated_report_too_short() {
155        assert!(!is_simulated_report(&[0u8; 2]));
156    }
157
158    #[test]
159    fn test_simulated_report_version_constant() {
160        assert_eq!(SIMULATED_REPORT_VERSION, 0xA3);
161    }
162}