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