amadeus_node/
attestation.rs1use serde::{Deserialize, Serialize};
20use std::fs::{File, OpenOptions};
21use std::io::{Read, Write};
22use thiserror::Error;
23
24const SGX_QUOTE_MAX_SIZE: usize = 8192;
26
27const SGX_REPORT_DATA_SIZE: usize = 64;
29
30#[derive(Error, Debug)]
31pub enum AttestationError {
32 #[error("Attestation not available (not running in SGX enclave)")]
33 NotAvailable,
34
35 #[error("Failed to write user report data: {0}")]
36 WriteReportData(std::io::Error),
37
38 #[error("Failed to read quote: {0}")]
39 ReadQuote(std::io::Error),
40
41 #[error("Failed to read attestation type: {0}")]
42 ReadAttestationType(std::io::Error),
43
44 #[error("Invalid user report data size: expected {expected}, got {actual}")]
45 InvalidReportDataSize { expected: usize, actual: usize },
46
47 #[error("Attestation type is 'none' - SGX remote attestation not enabled")]
48 AttestationDisabled,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct AttestationReport {
54 pub quote: String,
56
57 pub attestation_type: String,
59
60 pub quote_size: usize,
62
63 pub user_report_data: String,
65}
66
67pub fn is_attestation_available() -> bool {
71 std::path::Path::new("/dev/attestation/attestation_type").exists()
72}
73
74pub fn get_attestation_type() -> Result<String, AttestationError> {
76 let mut file = File::open("/dev/attestation/attestation_type")
77 .map_err(AttestationError::ReadAttestationType)?;
78
79 let mut attestation_type = String::new();
80 file.read_to_string(&mut attestation_type)
81 .map_err(AttestationError::ReadAttestationType)?;
82
83 Ok(attestation_type.trim().to_string())
84}
85
86pub fn get_attestation_report(
119 user_report_data: Option<&[u8]>,
120) -> Result<AttestationReport, AttestationError> {
121 if let Some(data) = user_report_data {
123 if data.len() > SGX_REPORT_DATA_SIZE {
124 return Err(AttestationError::InvalidReportDataSize {
125 expected: SGX_REPORT_DATA_SIZE,
126 actual: data.len(),
127 });
128 }
129 }
130
131 if !is_attestation_available() {
133 return Err(AttestationError::NotAvailable);
134 }
135
136 let attestation_type = get_attestation_type()?;
138 if attestation_type == "none" {
139 return Err(AttestationError::AttestationDisabled);
140 }
141
142 let mut report_data = [0u8; SGX_REPORT_DATA_SIZE];
144 if let Some(data) = user_report_data {
145 report_data[..data.len()].copy_from_slice(data);
146 }
147
148 {
150 let mut file = OpenOptions::new()
151 .write(true)
152 .open("/dev/attestation/user_report_data")
153 .map_err(AttestationError::WriteReportData)?;
154
155 file.write_all(&report_data)
156 .map_err(AttestationError::WriteReportData)?;
157 }
158
159 let mut quote_buffer = vec![0u8; SGX_QUOTE_MAX_SIZE];
161 let quote_size = {
162 let mut file = File::open("/dev/attestation/quote")
163 .map_err(AttestationError::ReadQuote)?;
164
165 file.read(&mut quote_buffer)
166 .map_err(AttestationError::ReadQuote)?
167 };
168
169 quote_buffer.truncate(quote_size);
171
172 use base64::engine::general_purpose::STANDARD;
173 use base64::Engine;
174
175 Ok(AttestationReport {
176 quote: STANDARD.encode("e_buffer),
177 attestation_type,
178 quote_size,
179 user_report_data: STANDARD.encode(&report_data),
180 })
181}
182
183pub fn get_attestation_with_pubkey(public_key: &[u8]) -> Result<AttestationReport, AttestationError> {
196 use sha2::{Digest, Sha256};
197
198 let mut hasher = Sha256::new();
200 hasher.update(public_key);
201 let hash = hasher.finalize();
202
203 let mut report_data = [0u8; SGX_REPORT_DATA_SIZE];
205 report_data[..32].copy_from_slice(&hash);
206
207 get_attestation_report(Some(&report_data))
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_attestation_availability() {
216 let available = is_attestation_available();
218 println!("Attestation available: {}", available);
219 }
220
221 #[test]
222 fn test_report_data_validation() {
223 let too_large = vec![0u8; 65];
224 let result = get_attestation_report(Some(&too_large));
225 assert!(matches!(result, Err(AttestationError::InvalidReportDataSize { .. })));
226 }
227}