Skip to main content

a3s_box_runtime/tee/
attestation.rs

1//! TEE attestation report types and guest-side report generation.
2//!
3//! This module defines the data structures for requesting and receiving
4//! SNP attestation reports from a guest VM running in a TEE environment.
5//! The report is generated by the CPU hardware and cannot be forged.
6
7use serde::{Deserialize, Serialize};
8
9/// Size of the SNP attestation report in bytes (AMD SEV-SNP spec v1.52).
10pub const SNP_REPORT_SIZE: usize = 1184;
11
12/// Size of the report_data / user_data field in the SNP report.
13pub const SNP_USER_DATA_SIZE: usize = 64;
14
15/// Size of the measurement field in the SNP report.
16pub const SNP_MEASUREMENT_SIZE: usize = 48;
17
18/// Request for an attestation report from the guest VM.
19///
20/// The host sends this to the guest agent, which calls the
21/// `SNP_GET_REPORT` ioctl on `/dev/sev-guest` to obtain a
22/// hardware-signed attestation report.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct AttestationRequest {
25    /// Nonce provided by the verifier (included in report_data to prevent replay).
26    /// Will be hashed together with any additional user_data before being placed
27    /// into the report's `report_data` field.
28    pub nonce: Vec<u8>,
29
30    /// Optional additional data to bind into the report (e.g., a DH public key).
31    /// Combined with the nonce via SHA-512 to produce the 64-byte report_data.
32    #[serde(default)]
33    pub user_data: Option<Vec<u8>>,
34}
35
36/// Attestation report returned from the guest VM.
37///
38/// Contains the raw hardware-signed SNP report and the certificate
39/// chain needed to verify the report's signature.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct AttestationReport {
42    /// Raw SNP attestation report bytes (1184 bytes, signed by VCEK).
43    pub report: Vec<u8>,
44
45    /// Certificate chain for verifying the report signature.
46    pub cert_chain: CertificateChain,
47
48    /// TEE platform information.
49    pub platform: PlatformInfo,
50}
51
52/// Certificate chain for SNP report verification.
53///
54/// Verification order: report signature → VCEK → ASK → ARK (AMD root).
55#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56pub struct CertificateChain {
57    /// VCEK (Versioned Chip Endorsement Key) certificate, DER-encoded.
58    /// Signs the attestation report. Unique per chip + TCB version.
59    #[serde(default)]
60    pub vcek: Vec<u8>,
61
62    /// ASK (AMD SEV Signing Key) certificate, DER-encoded.
63    /// Signs the VCEK. Intermediate CA.
64    #[serde(default)]
65    pub ask: Vec<u8>,
66
67    /// ARK (AMD Root Key) certificate, DER-encoded.
68    /// Signs the ASK. AMD's root of trust.
69    #[serde(default)]
70    pub ark: Vec<u8>,
71}
72
73/// Platform information extracted from the SNP report.
74#[derive(Debug, Clone, Default, Serialize, Deserialize)]
75pub struct PlatformInfo {
76    /// SNP report version.
77    pub version: u32,
78
79    /// Guest Security Version Number.
80    pub guest_svn: u32,
81
82    /// Guest policy (debug, migration, etc.).
83    pub policy: u64,
84
85    /// Launch measurement (SHA-384 of initial guest memory).
86    /// 48 bytes, hex-encoded for readability.
87    pub measurement: String,
88
89    /// Current TCB (Trusted Computing Base) version.
90    pub tcb_version: TcbVersion,
91
92    /// CPU chip ID (unique per physical processor), hex-encoded.
93    pub chip_id: String,
94}
95
96/// TCB (Trusted Computing Base) version components.
97#[derive(Debug, Clone, Default, Serialize, Deserialize)]
98pub struct TcbVersion {
99    /// Boot loader SVN.
100    pub boot_loader: u8,
101    /// TEE (PSP) SVN.
102    pub tee: u8,
103    /// SNP firmware SVN.
104    pub snp: u8,
105    /// CPU microcode SVN.
106    pub microcode: u8,
107}
108
109/// Parse platform info from raw SNP report bytes.
110///
111/// Field offsets are based on the AMD SEV-SNP ABI specification (Table 21).
112pub fn parse_platform_info(report: &[u8]) -> Option<PlatformInfo> {
113    if report.len() < SNP_REPORT_SIZE {
114        return None;
115    }
116
117    let version = u32::from_le_bytes(report[0x00..0x04].try_into().ok()?);
118    let guest_svn = u32::from_le_bytes(report[0x04..0x08].try_into().ok()?);
119    let policy = u64::from_le_bytes(report[0x08..0x10].try_into().ok()?);
120
121    // measurement is at offset 0x90, 48 bytes
122    let measurement = hex::encode(&report[0x90..0xC0]);
123
124    // current_tcb is at offset 0x38, 8 bytes
125    let tcb = TcbVersion {
126        boot_loader: report[0x38],
127        tee: report[0x39],
128        snp: report[0x3E],
129        microcode: report[0x3F],
130    };
131
132    // chip_id is at offset 0x1A0, 64 bytes
133    let chip_id = hex::encode(&report[0x1A0..0x1E0]);
134
135    Some(PlatformInfo {
136        version,
137        guest_svn,
138        policy,
139        measurement,
140        tcb_version: tcb,
141        chip_id,
142    })
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_attestation_request_serialization() {
151        let req = AttestationRequest {
152            nonce: vec![1, 2, 3, 4],
153            user_data: Some(vec![5, 6, 7]),
154        };
155        let json = serde_json::to_string(&req).unwrap();
156        let parsed: AttestationRequest = serde_json::from_str(&json).unwrap();
157        assert_eq!(parsed.nonce, vec![1, 2, 3, 4]);
158        assert_eq!(parsed.user_data, Some(vec![5, 6, 7]));
159    }
160
161    #[test]
162    fn test_attestation_request_without_user_data() {
163        let json = r#"{"nonce":[1,2,3]}"#;
164        let req: AttestationRequest = serde_json::from_str(json).unwrap();
165        assert_eq!(req.nonce, vec![1, 2, 3]);
166        assert!(req.user_data.is_none());
167    }
168
169    #[test]
170    fn test_attestation_report_serialization() {
171        let report = AttestationReport {
172            report: vec![0u8; SNP_REPORT_SIZE],
173            cert_chain: CertificateChain::default(),
174            platform: PlatformInfo::default(),
175        };
176        let json = serde_json::to_string(&report).unwrap();
177        let parsed: AttestationReport = serde_json::from_str(&json).unwrap();
178        assert_eq!(parsed.report.len(), SNP_REPORT_SIZE);
179    }
180
181    #[test]
182    fn test_certificate_chain_default() {
183        let chain = CertificateChain::default();
184        assert!(chain.vcek.is_empty());
185        assert!(chain.ask.is_empty());
186        assert!(chain.ark.is_empty());
187    }
188
189    #[test]
190    fn test_tcb_version_default() {
191        let tcb = TcbVersion::default();
192        assert_eq!(tcb.boot_loader, 0);
193        assert_eq!(tcb.tee, 0);
194        assert_eq!(tcb.snp, 0);
195        assert_eq!(tcb.microcode, 0);
196    }
197
198    #[test]
199    fn test_parse_platform_info_valid_report() {
200        let mut report = vec![0u8; SNP_REPORT_SIZE];
201
202        // version = 2 at offset 0x00
203        report[0x00] = 2;
204        // guest_svn = 1 at offset 0x04
205        report[0x04] = 1;
206        // policy at offset 0x08
207        report[0x08] = 0x30; // SMT + migration allowed
208                             // tcb at offset 0x38
209        report[0x38] = 3; // boot_loader
210        report[0x39] = 0; // tee
211        report[0x3E] = 8; // snp
212        report[0x3F] = 115; // microcode
213                            // measurement at offset 0x90 (48 bytes)
214        report[0x90] = 0xAB;
215        report[0x91] = 0xCD;
216
217        let info = parse_platform_info(&report).unwrap();
218        assert_eq!(info.version, 2);
219        assert_eq!(info.guest_svn, 1);
220        assert_eq!(info.policy, 0x30);
221        assert_eq!(info.tcb_version.boot_loader, 3);
222        assert_eq!(info.tcb_version.snp, 8);
223        assert_eq!(info.tcb_version.microcode, 115);
224        assert!(info.measurement.starts_with("abcd"));
225    }
226
227    #[test]
228    fn test_parse_platform_info_too_short() {
229        let report = vec![0u8; 100]; // Too short
230        assert!(parse_platform_info(&report).is_none());
231    }
232
233    #[test]
234    fn test_snp_constants() {
235        assert_eq!(SNP_REPORT_SIZE, 1184);
236        assert_eq!(SNP_USER_DATA_SIZE, 64);
237        assert_eq!(SNP_MEASUREMENT_SIZE, 48);
238    }
239}