Skip to main content

a3s_box_core/
tee.rs

1//! TEE (Trusted Execution Environment) types and detection.
2//!
3//! Provides:
4//! - [`TeeCapability`] — detected TEE hardware/simulation status
5//! - [`detect_tee()`] — probe the current environment for TEE support
6//! - [`AttestRequest`] / [`AttestRoute`] — RA-TLS attestation protocol types
7//!
8//! The attest server runs inside the guest TEE and communicates with
9//! host-side clients over TLS (RA-TLS). Inside the TLS tunnel, messages
10//! use the `a3s-transport` Frame wire format:
11//!
12//! - Client sends a [`Data`] frame with JSON [`AttestRequest`]
13//! - Server responds with a [`Data`] frame (JSON response) or [`Error`] frame
14
15use serde::{Deserialize, Serialize};
16
17/// Vsock port for the attestation server.
18pub const ATTEST_VSOCK_PORT: u32 = a3s_transport::ports::TEE_CHANNEL;
19
20/// Guest environment key carrying the SHA-256 Runtime specification binding.
21///
22/// The RA-TLS server places these 32 bytes in the second half of SNP
23/// `report_data`; the first half remains bound to the ephemeral TLS key.
24pub const RUNTIME_ATTESTATION_BINDING_ENV: &str = "A3S_RUNTIME_ATTESTATION_BINDING";
25
26// ---------------------------------------------------------------------------
27// TEE self-detection API
28// ---------------------------------------------------------------------------
29
30/// The type of TEE environment detected.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum TeeType {
34    /// AMD SEV-SNP (real hardware).
35    SevSnp,
36    /// Intel TDX (Trust Domain Extensions).
37    Tdx,
38    /// Simulation mode (`A3S_TEE_SIMULATE` env var).
39    Simulated,
40}
41
42/// Result of probing the current environment for TEE support.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct TeeCapability {
45    /// Whether a TEE environment is available.
46    pub available: bool,
47    /// The type of TEE detected (if any).
48    pub tee_type: Option<TeeType>,
49    /// Whether `/dev/sev-guest` exists (ioctl interface for attestation reports).
50    pub sev_guest_device: bool,
51    /// Whether `/dev/sev` exists (SEV driver loaded).
52    pub sev_device: bool,
53    /// Whether simulation mode is active.
54    pub simulated: bool,
55}
56
57/// Detect TEE capability in the current environment.
58///
59/// Checks (in order):
60/// 1. `A3S_TEE_SIMULATE` env var → simulation mode
61/// 2. `/dev/sev-guest` → AMD SEV-SNP with guest attestation support
62/// 3. `/dev/sev` → AMD SEV driver loaded
63///
64/// # Example
65///
66/// ```rust
67/// use a3s_box_core::tee::detect_tee;
68///
69/// let cap = detect_tee();
70/// if cap.available {
71///     println!("TEE type: {:?}", cap.tee_type);
72/// }
73/// ```
74pub fn detect_tee() -> TeeCapability {
75    let simulated = std::env::var("A3S_TEE_SIMULATE").is_ok();
76    let sev_guest_device = std::path::Path::new("/dev/sev-guest").exists();
77    let sev_device = std::path::Path::new("/dev/sev").exists();
78    let tdx_guest_device = std::path::Path::new("/dev/tdx_guest").exists()
79        || std::path::Path::new("/dev/tdx-guest").exists();
80
81    let (available, tee_type) = if simulated {
82        (true, Some(TeeType::Simulated))
83    } else if sev_guest_device || sev_device {
84        (true, Some(TeeType::SevSnp))
85    } else if tdx_guest_device {
86        (true, Some(TeeType::Tdx))
87    } else {
88        (false, None)
89    };
90
91    TeeCapability {
92        available,
93        tee_type,
94        sev_guest_device,
95        sev_device,
96        simulated,
97    }
98}
99
100/// Check if this environment has TEE support (hardware or simulated).
101///
102/// Convenience wrapper around [`detect_tee()`].
103pub fn is_tee_available() -> bool {
104    detect_tee().available
105}
106
107/// Request sent inside the TLS tunnel (JSON payload of a Data frame).
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct AttestRequest {
110    /// Route determines which handler processes the request.
111    pub route: AttestRoute,
112    /// JSON-encoded payload specific to the route.
113    #[serde(default)]
114    pub payload: serde_json::Value,
115}
116
117/// Routes available on the attest server (replaces HTTP path routing).
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119#[serde(rename_all = "snake_case")]
120pub enum AttestRoute {
121    /// Get TEE status.
122    Status,
123    /// Inject secrets into the guest.
124    Secrets,
125    /// Seal data bound to TEE identity.
126    Seal,
127    /// Unseal previously sealed data.
128    Unseal,
129    /// Forward a message to the local agent for processing.
130    Process,
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    // -- TEE detection tests --
138
139    #[test]
140    fn test_detect_tee_returns_capability() {
141        let cap = detect_tee();
142        // On dev machines without SEV hardware and without A3S_TEE_SIMULATE,
143        // TEE should not be available (unless the test runner sets the env var).
144        assert_eq!(cap.available, cap.tee_type.is_some());
145    }
146
147    #[test]
148    fn test_is_tee_available_matches_detect() {
149        let cap = detect_tee();
150        assert_eq!(is_tee_available(), cap.available);
151    }
152
153    #[test]
154    fn test_tee_capability_serde_roundtrip() {
155        let cap = TeeCapability {
156            available: true,
157            tee_type: Some(TeeType::SevSnp),
158            sev_guest_device: true,
159            sev_device: true,
160            simulated: false,
161        };
162        let json = serde_json::to_string(&cap).unwrap();
163        let parsed: TeeCapability = serde_json::from_str(&json).unwrap();
164        assert_eq!(parsed, cap);
165    }
166
167    #[test]
168    fn test_tee_capability_simulated() {
169        let cap = TeeCapability {
170            available: true,
171            tee_type: Some(TeeType::Simulated),
172            sev_guest_device: false,
173            sev_device: false,
174            simulated: true,
175        };
176        let json = serde_json::to_string(&cap).unwrap();
177        assert!(json.contains("\"simulated\""));
178        let parsed: TeeCapability = serde_json::from_str(&json).unwrap();
179        assert_eq!(parsed.tee_type, Some(TeeType::Simulated));
180    }
181
182    #[test]
183    fn test_tee_capability_none() {
184        let cap = TeeCapability {
185            available: false,
186            tee_type: None,
187            sev_guest_device: false,
188            sev_device: false,
189            simulated: false,
190        };
191        assert!(!cap.available);
192        assert!(cap.tee_type.is_none());
193    }
194
195    #[test]
196    fn test_tee_type_serde() {
197        assert_eq!(
198            serde_json::to_string(&TeeType::SevSnp).unwrap(),
199            "\"sev_snp\""
200        );
201        assert_eq!(serde_json::to_string(&TeeType::Tdx).unwrap(), "\"tdx\"");
202        assert_eq!(
203            serde_json::to_string(&TeeType::Simulated).unwrap(),
204            "\"simulated\""
205        );
206    }
207
208    #[test]
209    fn test_tee_type_tdx_roundtrip() {
210        let cap = TeeCapability {
211            available: true,
212            tee_type: Some(TeeType::Tdx),
213            sev_guest_device: false,
214            sev_device: false,
215            simulated: false,
216        };
217        let json = serde_json::to_string(&cap).unwrap();
218        let parsed: TeeCapability = serde_json::from_str(&json).unwrap();
219        assert_eq!(parsed.tee_type, Some(TeeType::Tdx));
220        assert!(parsed.available);
221    }
222
223    // -- Attest protocol tests --
224
225    #[test]
226    fn test_attest_vsock_port() {
227        assert_eq!(ATTEST_VSOCK_PORT, 4091);
228    }
229
230    #[test]
231    fn test_attest_request_serde_roundtrip() {
232        let req = AttestRequest {
233            route: AttestRoute::Status,
234            payload: serde_json::Value::Null,
235        };
236        let json = serde_json::to_string(&req).unwrap();
237        let parsed: AttestRequest = serde_json::from_str(&json).unwrap();
238        assert_eq!(parsed.route, AttestRoute::Status);
239    }
240
241    #[test]
242    fn test_attest_route_variants() {
243        let routes = [
244            (AttestRoute::Status, "\"status\""),
245            (AttestRoute::Secrets, "\"secrets\""),
246            (AttestRoute::Seal, "\"seal\""),
247            (AttestRoute::Unseal, "\"unseal\""),
248            (AttestRoute::Process, "\"process\""),
249        ];
250        for (route, expected) in routes {
251            let json = serde_json::to_string(&route).unwrap();
252            assert_eq!(json, expected);
253        }
254    }
255
256    #[test]
257    fn test_attest_request_with_payload() {
258        let req = AttestRequest {
259            route: AttestRoute::Seal,
260            payload: serde_json::json!({"data": "base64data", "context": "test"}),
261        };
262        let json = serde_json::to_string(&req).unwrap();
263        let parsed: AttestRequest = serde_json::from_str(&json).unwrap();
264        assert_eq!(parsed.route, AttestRoute::Seal);
265        assert_eq!(parsed.payload["context"], "test");
266    }
267
268    #[test]
269    fn test_attest_request_default_payload() {
270        let json = r#"{"route":"status"}"#;
271        let req: AttestRequest = serde_json::from_str(json).unwrap();
272        assert_eq!(req.route, AttestRoute::Status);
273        assert!(req.payload.is_null());
274    }
275}