use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use thiserror::Error;
const SGX_QUOTE_MAX_SIZE: usize = 8192;
const SGX_REPORT_DATA_SIZE: usize = 64;
#[derive(Error, Debug)]
pub enum AttestationError {
#[error("Attestation not available (not running in SGX enclave)")]
NotAvailable,
#[error("Failed to write user report data: {0}")]
WriteReportData(std::io::Error),
#[error("Failed to read quote: {0}")]
ReadQuote(std::io::Error),
#[error("Failed to read attestation type: {0}")]
ReadAttestationType(std::io::Error),
#[error("Invalid user report data size: expected {expected}, got {actual}")]
InvalidReportDataSize { expected: usize, actual: usize },
#[error("Attestation type is 'none' - SGX remote attestation not enabled")]
AttestationDisabled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttestationReport {
pub quote: String,
pub attestation_type: String,
pub quote_size: usize,
pub user_report_data: String,
}
pub fn is_attestation_available() -> bool {
std::path::Path::new("/dev/attestation/attestation_type").exists()
}
pub fn get_attestation_type() -> Result<String, AttestationError> {
let mut file = File::open("/dev/attestation/attestation_type")
.map_err(AttestationError::ReadAttestationType)?;
let mut attestation_type = String::new();
file.read_to_string(&mut attestation_type)
.map_err(AttestationError::ReadAttestationType)?;
Ok(attestation_type.trim().to_string())
}
pub fn get_attestation_report(
user_report_data: Option<&[u8]>,
) -> Result<AttestationReport, AttestationError> {
if let Some(data) = user_report_data {
if data.len() > SGX_REPORT_DATA_SIZE {
return Err(AttestationError::InvalidReportDataSize {
expected: SGX_REPORT_DATA_SIZE,
actual: data.len(),
});
}
}
if !is_attestation_available() {
return Err(AttestationError::NotAvailable);
}
let attestation_type = get_attestation_type()?;
if attestation_type == "none" {
return Err(AttestationError::AttestationDisabled);
}
let mut report_data = [0u8; SGX_REPORT_DATA_SIZE];
if let Some(data) = user_report_data {
report_data[..data.len()].copy_from_slice(data);
}
{
let mut file = OpenOptions::new()
.write(true)
.open("/dev/attestation/user_report_data")
.map_err(AttestationError::WriteReportData)?;
file.write_all(&report_data)
.map_err(AttestationError::WriteReportData)?;
}
let mut quote_buffer = vec![0u8; SGX_QUOTE_MAX_SIZE];
let quote_size = {
let mut file = File::open("/dev/attestation/quote")
.map_err(AttestationError::ReadQuote)?;
file.read(&mut quote_buffer)
.map_err(AttestationError::ReadQuote)?
};
quote_buffer.truncate(quote_size);
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
Ok(AttestationReport {
quote: STANDARD.encode("e_buffer),
attestation_type,
quote_size,
user_report_data: STANDARD.encode(&report_data),
})
}
pub fn get_attestation_with_pubkey(public_key: &[u8]) -> Result<AttestationReport, AttestationError> {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(public_key);
let hash = hasher.finalize();
let mut report_data = [0u8; SGX_REPORT_DATA_SIZE];
report_data[..32].copy_from_slice(&hash);
get_attestation_report(Some(&report_data))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attestation_availability() {
let available = is_attestation_available();
println!("Attestation available: {}", available);
}
#[test]
fn test_report_data_validation() {
let too_large = vec![0u8; 65];
let result = get_attestation_report(Some(&too_large));
assert!(matches!(result, Err(AttestationError::InvalidReportDataSize { .. })));
}
}