#[cfg(all(feature = "policy-l5", not(feature = "providers-pqclean")))]
compile_error!("policy-l5 requires providers-pqclean (ML-KEM-1024 / ML-DSA-87)");
#[derive(Debug, thiserror::Error)]
pub enum PolicyError {
#[error("L5 policy: peer negotiated non-L5 KEM ({0})")] NonL5Kem(String),
#[error("L5 policy: peer negotiated non-L5 signature ({0})")] NonL5Sig(String),
#[error("L5 policy: sec_cat too low ({0})")] SecCatTooLow(u8),
#[error("L5 policy: wrong KDF ({0})")] WrongKdf(String),
#[error("L5 policy: wrong AEAD ({0})")] WrongAead(String),
}
#[cfg(feature = "policy-l5")]
pub fn assert_level5(kem_name: &str, sig_name: Option<&str>) -> Result<(), PolicyError> {
if kem_name != "ML-KEM-1024" { return Err(PolicyError::NonL5Kem(kem_name.to_string())); }
if let Some(s) = sig_name { if s != "ML-DSA-87" { return Err(PolicyError::NonL5Sig(s.to_string())); } }
Ok(())
}
#[cfg(not(feature = "policy-l5"))]
pub fn assert_level5(_k: &str, _s: Option<&str>) -> Result<(), ()> { Ok(()) }
#[cfg(feature = "policy-l5")]
pub fn assert_level5_caps(caps: &serde_json::Value) -> Result<(), PolicyError> {
let sec = caps.get("sec_cat").and_then(|v| v.as_u64()).unwrap_or(0) as u8;
if sec < 5 { return Err(PolicyError::SecCatTooLow(sec)); }
let kem = caps.get("kem").and_then(|v| v.as_str()).unwrap_or("");
if kem != "ML-KEM-1024" { return Err(PolicyError::NonL5Kem(kem.to_string())); }
if let Some(sig) = caps.get("sig").and_then(|v| v.as_str()) {
if sig != "ML-DSA-87" { return Err(PolicyError::NonL5Sig(sig.to_string())); }
}
let kdf = caps.get("kdf").and_then(|v| v.as_str()).unwrap_or("");
if kdf != "HKDF-SHA512" { return Err(PolicyError::WrongKdf(kdf.to_string())); }
let aead = caps.get("aead").and_then(|v| v.as_str()).unwrap_or("");
if aead != "AES-256-GCM-SIV" { return Err(PolicyError::WrongAead(aead.to_string())); }
Ok(())
}
#[cfg(not(feature = "policy-l5"))]
pub fn assert_level5_caps(_caps: &serde_json::Value) -> Result<(), ()> { Ok(()) }
#[cfg(feature = "policy-l5")]
pub fn l5_enforcement_enabled() -> bool {
match std::env::var("ANUBIS_RELAX_L5") {
Ok(v) => !matches!(v.as_str(), "1"|"true"|"on"),
Err(_) => true,
}
}
#[cfg(not(feature = "policy-l5"))]
pub fn l5_enforcement_enabled() -> bool { false }