1use serde::{Deserialize, Serialize};
16
17pub const ATTEST_VSOCK_PORT: u32 = a3s_transport::ports::TEE_CHANNEL;
19
20pub const RUNTIME_ATTESTATION_BINDING_ENV: &str = "A3S_RUNTIME_ATTESTATION_BINDING";
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum TeeType {
34 SevSnp,
36 Tdx,
38 Simulated,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct TeeCapability {
45 pub available: bool,
47 pub tee_type: Option<TeeType>,
49 pub sev_guest_device: bool,
51 pub sev_device: bool,
53 pub simulated: bool,
55}
56
57pub 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
100pub fn is_tee_available() -> bool {
104 detect_tee().available
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct AttestRequest {
110 pub route: AttestRoute,
112 #[serde(default)]
114 pub payload: serde_json::Value,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119#[serde(rename_all = "snake_case")]
120pub enum AttestRoute {
121 Status,
123 Secrets,
125 Seal,
127 Unseal,
129 Process,
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
140 fn test_detect_tee_returns_capability() {
141 let cap = detect_tee();
142 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 #[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}