use serde::{Deserialize, Serialize};
pub const SNP_REPORT_SIZE: usize = 1184;
pub const SNP_USER_DATA_SIZE: usize = 64;
pub const SNP_MEASUREMENT_SIZE: usize = 48;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttestationRequest {
pub nonce: Vec<u8>,
#[serde(default)]
pub user_data: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttestationReport {
pub report: Vec<u8>,
pub cert_chain: CertificateChain,
pub platform: PlatformInfo,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CertificateChain {
#[serde(default)]
pub vcek: Vec<u8>,
#[serde(default)]
pub ask: Vec<u8>,
#[serde(default)]
pub ark: Vec<u8>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PlatformInfo {
pub version: u32,
pub guest_svn: u32,
pub policy: u64,
pub measurement: String,
pub tcb_version: TcbVersion,
pub chip_id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TcbVersion {
pub boot_loader: u8,
pub tee: u8,
pub snp: u8,
pub microcode: u8,
}
pub fn parse_platform_info(report: &[u8]) -> Option<PlatformInfo> {
if report.len() < SNP_REPORT_SIZE {
return None;
}
let version = u32::from_le_bytes(report[0x00..0x04].try_into().ok()?);
let guest_svn = u32::from_le_bytes(report[0x04..0x08].try_into().ok()?);
let policy = u64::from_le_bytes(report[0x08..0x10].try_into().ok()?);
let measurement = hex::encode(&report[0x90..0xC0]);
let tcb = TcbVersion {
boot_loader: report[0x38],
tee: report[0x39],
snp: report[0x3E],
microcode: report[0x3F],
};
let chip_id = hex::encode(&report[0x1A0..0x1E0]);
Some(PlatformInfo {
version,
guest_svn,
policy,
measurement,
tcb_version: tcb,
chip_id,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_attestation_request_serialization() {
let req = AttestationRequest {
nonce: vec![1, 2, 3, 4],
user_data: Some(vec![5, 6, 7]),
};
let json = serde_json::to_string(&req).unwrap();
let parsed: AttestationRequest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.nonce, vec![1, 2, 3, 4]);
assert_eq!(parsed.user_data, Some(vec![5, 6, 7]));
}
#[test]
fn test_attestation_request_without_user_data() {
let json = r#"{"nonce":[1,2,3]}"#;
let req: AttestationRequest = serde_json::from_str(json).unwrap();
assert_eq!(req.nonce, vec![1, 2, 3]);
assert!(req.user_data.is_none());
}
#[test]
fn test_attestation_report_serialization() {
let report = AttestationReport {
report: vec![0u8; SNP_REPORT_SIZE],
cert_chain: CertificateChain::default(),
platform: PlatformInfo::default(),
};
let json = serde_json::to_string(&report).unwrap();
let parsed: AttestationReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.report.len(), SNP_REPORT_SIZE);
}
#[test]
fn test_certificate_chain_default() {
let chain = CertificateChain::default();
assert!(chain.vcek.is_empty());
assert!(chain.ask.is_empty());
assert!(chain.ark.is_empty());
}
#[test]
fn test_tcb_version_default() {
let tcb = TcbVersion::default();
assert_eq!(tcb.boot_loader, 0);
assert_eq!(tcb.tee, 0);
assert_eq!(tcb.snp, 0);
assert_eq!(tcb.microcode, 0);
}
#[test]
fn test_parse_platform_info_valid_report() {
let mut report = vec![0u8; SNP_REPORT_SIZE];
report[0x00] = 2;
report[0x04] = 1;
report[0x08] = 0x30; report[0x38] = 3; report[0x39] = 0; report[0x3E] = 8; report[0x3F] = 115; report[0x90] = 0xAB;
report[0x91] = 0xCD;
let info = parse_platform_info(&report).unwrap();
assert_eq!(info.version, 2);
assert_eq!(info.guest_svn, 1);
assert_eq!(info.policy, 0x30);
assert_eq!(info.tcb_version.boot_loader, 3);
assert_eq!(info.tcb_version.snp, 8);
assert_eq!(info.tcb_version.microcode, 115);
assert!(info.measurement.starts_with("abcd"));
}
#[test]
fn test_parse_platform_info_too_short() {
let report = vec![0u8; 100]; assert!(parse_platform_info(&report).is_none());
}
#[test]
fn test_snp_constants() {
assert_eq!(SNP_REPORT_SIZE, 1184);
assert_eq!(SNP_USER_DATA_SIZE, 64);
assert_eq!(SNP_MEASUREMENT_SIZE, 48);
}
}