use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::crypto::constant_time_eq;
use crate::dpop::normalize_htu;
use crate::pkce::{derive_s256_challenge, validate_verifier};
use crate::ssrf::{is_restricted_ip, is_restricted_ipv4, is_restricted_ipv6, SsrfFilter};
use crate::verification::verus_contracts::{
ConstantTimeEqSpec, DPoPHtuFormalSpec, OAuthStateTransitionModel, PkceFormalSpec,
SsrfFormalSpec,
};
#[derive(Debug, Default)]
pub struct AntiVacuityCoverage {
hit_points: std::sync::RwLock<HashSet<String>>,
total_assertions: AtomicUsize,
}
impl AntiVacuityCoverage {
#[must_use]
pub fn new() -> Self {
Self {
hit_points: std::sync::RwLock::new(HashSet::new()),
total_assertions: AtomicUsize::new(0),
}
}
pub fn cover(&self, tag: &str, condition: bool) {
if condition {
if let Ok(mut guard) = self.hit_points.write() {
guard.insert(tag.to_string());
}
}
self.total_assertions.fetch_add(1, Ordering::Relaxed);
}
#[allow(clippy::panic)]
pub fn assert_all_covered(&self, required_tags: &[&str]) {
let guard = match self.hit_points.read() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
for tag in required_tags {
assert!(
guard.contains(*tag),
"ANTI-VACUITY VIOLATION: Required cover point '{tag}' was never reached! Proof is vacuous."
);
}
}
#[must_use]
pub fn covered_count(&self) -> usize {
let guard = match self.hit_points.read() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
guard.len()
}
}
static GLOBAL_COVERAGE: std::sync::OnceLock<AntiVacuityCoverage> = std::sync::OnceLock::new();
#[must_use]
pub fn global_coverage() -> &'static AntiVacuityCoverage {
GLOBAL_COVERAGE.get_or_init(AntiVacuityCoverage::new)
}
#[macro_export]
macro_rules! anti_vacuity_cover {
($tag:expr, $cond:expr) => {
let cond_val = $cond;
$crate::verification::kani_harnesses::global_coverage().cover($tag, cond_val);
#[cfg(kani)]
kani::cover!(cond_val, $tag);
};
}
#[cfg_attr(kani, kani::proof)]
#[cfg_attr(kani, kani::unwind(10))]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_single_use_state_consumption() {
let mut model = OAuthStateTransitionModel::new();
let state_token = "symbolic_state_token_123";
let client_id = "https://app.example.com/client-metadata.json";
let ttl_ticks = 100u64;
let initial_take = model.take_state(state_token, 0);
assert!(initial_take.is_none());
anti_vacuity_cover!("uninitialized_state_rejected", initial_take.is_none());
let inserted = model.insert(state_token, client_id, ttl_ticks, 10);
assert!(inserted);
anti_vacuity_cover!("state_inserted", inserted);
assert!(model.verify_global_store_invariants());
let first_take = model.take_state(state_token, 20);
assert!(first_take.is_some());
if let Some(entry) = &first_take {
assert_eq!(entry.state_id, state_token);
assert_eq!(entry.client_id, client_id);
}
anti_vacuity_cover!("first_take_success", first_take.is_some());
assert!(model.verify_single_use_invariant(state_token));
let second_take = model.take_state(state_token, 25);
assert!(second_take.is_none());
anti_vacuity_cover!("second_take_rejected", second_take.is_none());
assert!(model.verify_single_use_invariant(state_token));
let third_take = model.take_state(state_token, 30);
assert!(third_take.is_none());
assert!(model.verify_single_use_invariant(state_token));
let expired_token = "symbolic_expired_state";
let exp_inserted = model.insert(expired_token, client_id, 50, 0);
assert!(exp_inserted);
let exp_take = model.take_state(expired_token, 60);
assert!(exp_take.is_none());
anti_vacuity_cover!("expired_state_rejected", exp_take.is_none());
let race_token = "symbolic_race_state";
assert!(model.insert(race_token, client_id, 100, 0));
let (winners, losers) = model.simulate_concurrent_consumption_race(race_token, 50, 10);
assert_eq!(winners, 1);
assert_eq!(losers, 49);
anti_vacuity_cover!(
"concurrent_race_single_winner",
winners == 1 && losers == 49
);
assert!(model.verify_single_use_invariant(race_token));
assert!(model.verify_global_store_invariants());
}
#[cfg_attr(kani, kani::proof)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_ssrf_restricted_ip_rejection() {
let filter = SsrfFilter::new(false);
let ip_10 = Ipv4Addr::new(10, 254, 1, 2);
assert!(is_restricted_ip(IpAddr::V4(ip_10)));
assert!(is_restricted_ipv4(&ip_10));
assert!(SsrfFormalSpec::spec_is_restricted_ipv4(&ip_10));
assert!(filter.validate_ip(IpAddr::V4(ip_10)).is_err());
anti_vacuity_cover!("rfc1918_10_blocked", is_restricted_ipv4(&ip_10));
let ip_172 = Ipv4Addr::new(172, 31, 255, 254);
assert!(is_restricted_ip(IpAddr::V4(ip_172)));
assert!(is_restricted_ipv4(&ip_172));
assert!(SsrfFormalSpec::spec_is_restricted_ipv4(&ip_172));
assert!(filter.validate_ip(IpAddr::V4(ip_172)).is_err());
anti_vacuity_cover!("rfc1918_172_blocked", is_restricted_ipv4(&ip_172));
let ip_192 = Ipv4Addr::new(192, 168, 100, 1);
assert!(is_restricted_ip(IpAddr::V4(ip_192)));
assert!(is_restricted_ipv4(&ip_192));
assert!(SsrfFormalSpec::spec_is_restricted_ipv4(&ip_192));
assert!(filter.validate_ip(IpAddr::V4(ip_192)).is_err());
anti_vacuity_cover!("rfc1918_192_blocked", is_restricted_ipv4(&ip_192));
let ip_meta = Ipv4Addr::new(169, 254, 169, 254);
assert!(is_restricted_ip(IpAddr::V4(ip_meta)));
assert!(is_restricted_ipv4(&ip_meta));
assert!(SsrfFormalSpec::spec_is_restricted_ipv4(&ip_meta));
assert!(filter.validate_ip(IpAddr::V4(ip_meta)).is_err());
anti_vacuity_cover!(
"cloud_metadata_169_254_blocked",
is_restricted_ipv4(&ip_meta)
);
let ip_loop = Ipv4Addr::new(127, 0, 0, 1);
assert!(is_restricted_ip(IpAddr::V4(ip_loop)));
assert!(is_restricted_ipv4(&ip_loop));
assert!(SsrfFormalSpec::spec_is_restricted_ipv4(&ip_loop));
assert!(filter.validate_ip(IpAddr::V4(ip_loop)).is_err());
anti_vacuity_cover!("loopback_127_blocked", is_restricted_ipv4(&ip_loop));
let ip_cgnat = Ipv4Addr::new(100, 64, 0, 1);
assert!(is_restricted_ip(IpAddr::V4(ip_cgnat)));
assert!(is_restricted_ipv4(&ip_cgnat));
assert!(SsrfFormalSpec::spec_is_restricted_ipv4(&ip_cgnat));
assert!(filter.validate_ip(IpAddr::V4(ip_cgnat)).is_err());
anti_vacuity_cover!("cgnat_100_64_blocked", is_restricted_ipv4(&ip_cgnat));
let ip_ula = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1);
assert!(is_restricted_ip(IpAddr::V6(ip_ula)));
assert!(is_restricted_ipv6(&ip_ula));
assert!(SsrfFormalSpec::spec_is_restricted_ipv6(&ip_ula));
assert!(filter.validate_ip(IpAddr::V6(ip_ula)).is_err());
anti_vacuity_cover!("ipv6_ula_fc00_blocked", is_restricted_ipv6(&ip_ula));
let ip_fe80 = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
assert!(is_restricted_ip(IpAddr::V6(ip_fe80)));
assert!(is_restricted_ipv6(&ip_fe80));
assert!(SsrfFormalSpec::spec_is_restricted_ipv6(&ip_fe80));
assert!(filter.validate_ip(IpAddr::V6(ip_fe80)).is_err());
anti_vacuity_cover!("ipv6_link_local_fe80_blocked", is_restricted_ipv6(&ip_fe80));
let mapped_priv = Ipv4Addr::new(10, 0, 0, 1).to_ipv6_mapped();
assert!(is_restricted_ip(IpAddr::V6(mapped_priv)));
assert!(is_restricted_ipv6(&mapped_priv));
assert!(SsrfFormalSpec::spec_is_restricted_ipv6(&mapped_priv));
assert!(filter.validate_ip(IpAddr::V6(mapped_priv)).is_err());
anti_vacuity_cover!("ipv4_mapped_ipv6_blocked", is_restricted_ipv6(&mapped_priv));
let ip_pub = Ipv4Addr::new(8, 8, 8, 8);
assert!(!is_restricted_ip(IpAddr::V4(ip_pub)));
assert!(!is_restricted_ipv4(&ip_pub));
assert!(!SsrfFormalSpec::spec_is_restricted_ipv4(&ip_pub));
assert!(filter.validate_ip(IpAddr::V4(ip_pub)).is_ok());
anti_vacuity_cover!("public_ip_allowed", !is_restricted_ipv4(&ip_pub));
}
#[cfg_attr(kani, kani::proof)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_pkce_s256_verifier_bounds() {
let min_verifier = "a".repeat(43);
assert!(validate_verifier(&min_verifier).is_ok());
assert!(PkceFormalSpec::spec_validate_verifier(
min_verifier.as_bytes()
));
let ch_min = derive_s256_challenge(&min_verifier);
assert_eq!(ch_min.len(), 43);
anti_vacuity_cover!(
"valid_min_length_43_verifier",
validate_verifier(&min_verifier).is_ok()
);
let max_verifier = "z".repeat(128);
assert!(validate_verifier(&max_verifier).is_ok());
assert!(PkceFormalSpec::spec_validate_verifier(
max_verifier.as_bytes()
));
let ch_max = derive_s256_challenge(&max_verifier);
assert_eq!(ch_max.len(), 43);
anti_vacuity_cover!(
"valid_max_length_128_verifier",
validate_verifier(&max_verifier).is_ok()
);
let mid_verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk.test~example-pkce-1234567";
let mid_verifier = &mid_verifier[..64];
assert!(validate_verifier(mid_verifier).is_ok());
assert!(PkceFormalSpec::spec_validate_verifier(
mid_verifier.as_bytes()
));
anti_vacuity_cover!(
"valid_mid_length_verifier",
validate_verifier(mid_verifier).is_ok()
);
let short_verifier = "a".repeat(42);
assert!(validate_verifier(&short_verifier).is_err());
assert!(!PkceFormalSpec::spec_validate_verifier(
short_verifier.as_bytes()
));
anti_vacuity_cover!(
"invalid_short_length_rejected",
validate_verifier(&short_verifier).is_err()
);
let long_verifier = "a".repeat(129);
assert!(validate_verifier(&long_verifier).is_err());
assert!(!PkceFormalSpec::spec_validate_verifier(
long_verifier.as_bytes()
));
anti_vacuity_cover!(
"invalid_long_length_rejected",
validate_verifier(&long_verifier).is_err()
);
let illegal_space = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEj k";
assert!(validate_verifier(illegal_space).is_err());
assert!(!PkceFormalSpec::spec_validate_verifier(
illegal_space.as_bytes()
));
let illegal_plus = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEj+k";
assert!(validate_verifier(illegal_plus).is_err());
anti_vacuity_cover!(
"invalid_character_rejected",
validate_verifier(illegal_space).is_err()
);
anti_vacuity_cover!(
"challenge_length_is_43",
ch_min.len() == 43 && ch_max.len() == 43
);
}
#[cfg_attr(kani, kani::proof)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_constant_time_eq_soundness() {
let s1 = b"cryptographic_secret_token_1234";
let s2 = b"cryptographic_secret_token_1234";
assert!(constant_time_eq(s1, s2));
assert!(ConstantTimeEqSpec::verify_soundness(s1, s2));
anti_vacuity_cover!("equal_non_empty_slices_true", constant_time_eq(s1, s2));
let mut diff_first = *s1;
diff_first[0] ^= 0x01;
assert!(!constant_time_eq(s1, &diff_first));
assert!(ConstantTimeEqSpec::verify_soundness(s1, &diff_first));
anti_vacuity_cover!(
"differing_first_byte_false",
!constant_time_eq(s1, &diff_first)
);
let mut diff_last = *s1;
diff_last[s1.len() - 1] ^= 0x01;
assert!(!constant_time_eq(s1, &diff_last));
assert!(ConstantTimeEqSpec::verify_soundness(s1, &diff_last));
anti_vacuity_cover!(
"differing_last_byte_false",
!constant_time_eq(s1, &diff_last)
);
let mut diff_mid = *s1;
diff_mid[s1.len() / 2] ^= 0x01;
assert!(!constant_time_eq(s1, &diff_mid));
assert!(ConstantTimeEqSpec::verify_soundness(s1, &diff_mid));
anti_vacuity_cover!(
"differing_middle_byte_false",
!constant_time_eq(s1, &diff_mid)
);
let short = &s1[..16];
assert!(!constant_time_eq(s1, short));
assert!(ConstantTimeEqSpec::verify_soundness(s1, short));
anti_vacuity_cover!("mismatched_length_false", !constant_time_eq(s1, short));
assert!(constant_time_eq(b"", b""));
assert!(ConstantTimeEqSpec::verify_soundness(b"", b""));
anti_vacuity_cover!("empty_slices_true", constant_time_eq(b"", b""));
}
#[cfg_attr(kani, kani::proof)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_dpop_htu_normalization_invariants() {
if let Ok(res_query) = normalize_htu("https://example.com/oauth/token?grant_type=code") {
assert_eq!(res_query, "https://example.com/oauth/token");
assert!(DPoPHtuFormalSpec::spec_has_no_query(&res_query));
anti_vacuity_cover!(
"query_stripped_success",
DPoPHtuFormalSpec::spec_has_no_query(&res_query)
);
}
if let Ok(res_frag) = normalize_htu("https://example.com/oauth/token#frag") {
assert_eq!(res_frag, "https://example.com/oauth/token");
assert!(DPoPHtuFormalSpec::spec_has_no_fragment(&res_frag));
anti_vacuity_cover!(
"fragment_stripped_success",
DPoPHtuFormalSpec::spec_has_no_fragment(&res_frag)
);
}
if let Ok(res_443) = normalize_htu("https://example.com:443/oauth/token") {
assert_eq!(res_443, "https://example.com/oauth/token");
assert!(DPoPHtuFormalSpec::spec_no_default_ports(&res_443));
anti_vacuity_cover!(
"port_443_stripped_success",
DPoPHtuFormalSpec::spec_no_default_ports(&res_443)
);
}
if let Ok(res_80) = normalize_htu("http://example.com:80/oauth/token") {
assert_eq!(res_80, "http://example.com/oauth/token");
assert!(DPoPHtuFormalSpec::spec_no_default_ports(&res_80));
anti_vacuity_cover!(
"port_80_stripped_success",
DPoPHtuFormalSpec::spec_no_default_ports(&res_80)
);
}
if let Ok(res_custom) = normalize_htu("https://example.com:8443/oauth/token") {
assert_eq!(res_custom, "https://example.com:8443/oauth/token");
anti_vacuity_cover!(
"custom_port_preserved_success",
res_custom.contains(":8443")
);
}
if let Ok(res_case) = normalize_htu("https://AUTH.EXAMPLE.COM/Token/Path") {
assert_eq!(res_case, "https://auth.example.com/Token/Path");
assert!(DPoPHtuFormalSpec::spec_valid_scheme(&res_case));
anti_vacuity_cover!(
"uppercase_host_lowercased_success",
res_case.starts_with("https://auth.example.com")
);
}
let res_ftp = normalize_htu("ftp://example.com/token");
assert!(res_ftp.is_err());
anti_vacuity_cover!("invalid_scheme_rejected", res_ftp.is_err());
}