use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttestationPolicy {
#[serde(default)]
pub expected_measurement: Option<String>,
#[serde(default)]
pub min_tcb: Option<MinTcbPolicy>,
#[serde(default = "default_true")]
pub require_no_debug: bool,
#[serde(default)]
pub require_no_smt: bool,
#[serde(default)]
pub allowed_policy_mask: Option<u64>,
#[serde(default)]
pub max_report_age_secs: Option<u64>,
}
impl Default for AttestationPolicy {
fn default() -> Self {
Self {
expected_measurement: None,
min_tcb: None,
require_no_debug: true,
require_no_smt: false,
allowed_policy_mask: None,
max_report_age_secs: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MinTcbPolicy {
#[serde(default)]
pub boot_loader: Option<u8>,
#[serde(default)]
pub tee: Option<u8>,
#[serde(default)]
pub snp: Option<u8>,
#[serde(default)]
pub microcode: Option<u8>,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone)]
pub struct PolicyResult {
pub passed: bool,
pub violations: Vec<PolicyViolation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyViolation {
pub check: String,
pub reason: String,
}
impl std::fmt::Display for PolicyViolation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.check, self.reason)
}
}
impl PolicyResult {
pub fn pass() -> Self {
Self {
passed: true,
violations: Vec::new(),
}
}
pub fn from_violations(violations: Vec<PolicyViolation>) -> Self {
Self {
passed: violations.is_empty(),
violations,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_policy() {
let policy = AttestationPolicy::default();
assert!(policy.expected_measurement.is_none());
assert!(policy.min_tcb.is_none());
assert!(policy.require_no_debug); assert!(!policy.require_no_smt);
assert!(policy.allowed_policy_mask.is_none());
assert!(policy.max_report_age_secs.is_none());
}
#[test]
fn test_policy_serialization() {
let policy = AttestationPolicy {
expected_measurement: Some("ab".repeat(48)),
require_no_debug: true,
require_no_smt: true,
min_tcb: Some(MinTcbPolicy {
snp: Some(8),
microcode: Some(115),
..Default::default()
}),
..Default::default()
};
let json = serde_json::to_string(&policy).unwrap();
let parsed: AttestationPolicy = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.expected_measurement, policy.expected_measurement);
assert!(parsed.require_no_smt);
assert_eq!(parsed.min_tcb.unwrap().snp, Some(8));
}
#[test]
fn test_policy_result_pass() {
let result = PolicyResult::pass();
assert!(result.passed);
assert!(result.violations.is_empty());
}
#[test]
fn test_policy_result_with_violations() {
let violations = vec![
PolicyViolation {
check: "measurement".to_string(),
reason: "Mismatch".to_string(),
},
PolicyViolation {
check: "debug".to_string(),
reason: "Debug mode enabled".to_string(),
},
];
let result = PolicyResult::from_violations(violations);
assert!(!result.passed);
assert_eq!(result.violations.len(), 2);
}
#[test]
fn test_policy_violation_display() {
let v = PolicyViolation {
check: "tcb".to_string(),
reason: "SNP version too low".to_string(),
};
assert_eq!(v.to_string(), "tcb: SNP version too low");
}
#[test]
fn test_min_tcb_policy_default() {
let tcb = MinTcbPolicy::default();
assert!(tcb.boot_loader.is_none());
assert!(tcb.tee.is_none());
assert!(tcb.snp.is_none());
assert!(tcb.microcode.is_none());
}
}