#[cfg(not(kani))]
mod ctx_not_kani {
pub(crate) use crate::dpop::normalize_htu;
pub(crate) use crate::pkce::{derive_s256_challenge, validate_verifier};
pub(crate) use crate::ssrf::{is_restricted_ip, is_restricted_ipv6};
pub(crate) use crate::verification::formal_models::{
DPoPHtuFormalSpec, OAuthStateTransitionModel,
};
pub(crate) use std::net::Ipv6Addr;
}
#[cfg(not(kani))]
use ctx_not_kani::*;
use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::crypto::constant_time_eq;
use crate::ssrf::{is_restricted_ipv4, SsrfFilter};
use crate::verification::formal_models::ConstantTimeEqSpec;
use crate::verification::formal_models::PkceFormalSpec;
use crate::verification::formal_models::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() {
#[cfg(kani)]
{
let mut model = crate::verification::formal_models::SingleStateModel::new();
let ttl_ticks: u64 = kani::any();
kani::assume(ttl_ticks > 0 && ttl_ticks <= 1000);
let insert_tick: u64 = kani::any();
kani::assume(insert_tick <= 100);
let initial_take = model.take_state(0);
assert!(initial_take.is_none());
let inserted = model.insert(ttl_ticks, insert_tick);
assert!(inserted);
let take1_tick: u64 = kani::any();
kani::assume(take1_tick >= insert_tick && take1_tick <= insert_tick + 2000);
let take1 = model.take_state(take1_tick);
if take1_tick < insert_tick.saturating_add(ttl_ticks) {
assert!(take1.is_some());
let take2_tick: u64 = kani::any();
kani::assume(take2_tick >= take1_tick && take2_tick <= take1_tick + 2000);
let take2 = model.take_state(take2_tick);
assert!(take2.is_none(), "Consumed state cannot be consumed again");
kani::cover!(take1.is_some(), "first_take_success");
} else {
assert!(take1.is_none(), "Expired state returns None");
kani::cover!(take1.is_none(), "expired_state_rejected");
}
assert!(model.verify_single_use_invariant());
}
#[cfg(not(kani))]
{
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() {
#[cfg(kani)]
{
let octets: [u8; 4] = kani::any();
let ip = Ipv4Addr::from(octets);
let filter = SsrfFilter::new(false);
let is_10 = octets[0] == 10;
let is_172 = octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31);
let is_192 = octets[0] == 192 && octets[1] == 168;
let is_loop = octets[0] == 127;
let is_meta = octets[0] == 169 && octets[1] == 254;
let is_cgnat = octets[0] == 100 && (octets[1] >= 64 && octets[1] <= 127);
let is_multi = octets[0] >= 224 && octets[0] <= 239;
let is_res = octets[0] >= 240;
let should_block =
is_10 || is_172 || is_192 || is_loop || is_meta || is_cgnat || is_multi || is_res;
assert_eq!(
is_restricted_ipv4(&ip),
SsrfFormalSpec::spec_is_restricted_ipv4(&ip),
"production must agree with the formal spec for every IPv4 address"
);
if should_block {
assert!(is_restricted_ipv4(&ip));
assert!(filter.validate_ip(IpAddr::V4(ip)).is_err());
}
if !SsrfFormalSpec::spec_is_restricted_ipv4(&ip) {
assert!(
filter.validate_ip(IpAddr::V4(ip)).is_ok(),
"permitted symbolic IPv4 must be accepted by SsrfFilter::validate_ip"
);
}
kani::cover!(is_10, "rfc1918_10_blocked");
kani::cover!(is_172, "rfc1918_172_blocked");
kani::cover!(is_192, "rfc1918_192_blocked");
kani::cover!(is_meta, "cloud_metadata_169_254_blocked");
kani::cover!(is_loop, "loopback_127_blocked");
kani::cover!(!should_block, "public_ip_allowed");
}
#[cfg(not(kani))]
{
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)]
#[cfg_attr(kani, kani::unwind(129))]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_pkce_s256_verifier_bounds() {
#[cfg(kani)]
{
let min_bytes: [u8; 43] = kani::any();
let max_bytes: [u8; 128] = kani::any();
let min_len_ok = PkceFormalSpec::is_valid_verifier_len(43);
let min_charset_ok = min_bytes
.iter()
.all(|&b| PkceFormalSpec::is_unreserved_char(b));
let min_prod_ok = min_len_ok
&& min_bytes.iter().all(|&b| {
b.is_ascii_alphanumeric() || b == b'-' || b == b'.' || b == b'_' || b == b'~'
});
assert_eq!(
min_prod_ok, min_charset_ok,
"production byte logic must equal the formal spec at length 43"
);
let max_len_ok = PkceFormalSpec::is_valid_verifier_len(128);
let max_charset_ok = max_bytes
.iter()
.all(|&b| PkceFormalSpec::is_unreserved_char(b));
let max_prod_ok = max_len_ok
&& max_bytes.iter().all(|&b| {
b.is_ascii_alphanumeric() || b == b'-' || b == b'.' || b == b'_' || b == b'~'
});
assert_eq!(
max_prod_ok, max_charset_ok,
"production byte logic must equal the formal spec at length 128"
);
assert_eq!(PkceFormalSpec::spec_s256_challenge_len(), 43);
let byte: u8 = kani::any();
assert_eq!(
PkceFormalSpec::is_unreserved_char(byte),
byte.is_ascii_alphanumeric()
|| byte == b'-'
|| byte == b'.'
|| byte == b'_'
|| byte == b'~'
);
kani::cover!(min_charset_ok, "valid_min_length_43_verifier");
kani::cover!(max_charset_ok, "valid_max_length_128_verifier");
}
#[cfg(not(kani))]
{
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)]
#[cfg_attr(kani, kani::unwind(17))]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_constant_time_eq_soundness() {
#[cfg(kani)]
{
let a_full: [u8; 8] = kani::any();
let b_full: [u8; 8] = kani::any();
assert_eq!(
constant_time_eq(&a_full, &b_full),
ConstantTimeEqSpec::spec_constant_time_eq_model(&a_full, &b_full),
"constant_time_eq must agree with the element-wise spec model"
);
assert_eq!(constant_time_eq(&a_full, &b_full), a_full == b_full);
let a_len: usize = kani::any();
kani::assume((1..=8).contains(&a_len));
let b_len: usize = kani::any();
kani::assume((1..=8).contains(&b_len));
let a: [u8; 8] = kani::any();
let b: [u8; 8] = kani::any();
let res = constant_time_eq(&a[..a_len], &b[..b_len]);
assert_eq!(
res,
ConstantTimeEqSpec::spec_constant_time_eq_model(&a[..a_len], &b[..b_len]),
"constant_time_eq must agree with the element-wise spec model"
);
assert_eq!(res, a[..a_len] == b[..b_len]);
kani::cover!(a_full == b_full, "equal_non_empty_slices_true");
kani::cover!(a_full != b_full, "differing_first_byte_false");
kani::cover!(a_len != b_len, "mismatched_length_false");
}
#[cfg(not(kani))]
{
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)]
#[cfg_attr(kani, kani::unwind(64))]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_dpop_htu_normalization_invariants() {
#[cfg(kani)]
{
}
#[cfg(not(kani))]
{
use crate::kernels::htu_components::{build_normalized_htu, invariants_hold, HtuScheme};
let host = "auth.example.com";
for is_https in [false, true] {
let htu_scheme = if is_https {
HtuScheme::Https
} else {
HtuScheme::Http
};
for class in 0usize..4 {
let port: Option<u16> = match class {
0 => None,
1 => Some(htu_scheme.default_port()),
2 => Some(8443),
_ => Some(8080),
};
for path in ["/", "/a", "/oauth/token", "/Token/Path"] {
let out = build_normalized_htu(htu_scheme, host, port, path);
assert!(invariants_hold(htu_scheme, host, port, path, &out));
anti_vacuity_cover!("port_443_stripped_success", class == 1 && is_https);
anti_vacuity_cover!("port_80_stripped_success", class == 1 && !is_https);
anti_vacuity_cover!("custom_port_preserved_success", class == 2);
anti_vacuity_cover!("absent_port_omitted", class == 0);
anti_vacuity_cover!("other_custom_port_preserved", class == 3);
anti_vacuity_cover!("uppercase_host_lowercased_success", true);
}
}
}
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));
}
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));
}
if let Ok(res_custom) = normalize_htu("https://example.com:8443/oauth/token") {
assert_eq!(res_custom, "https://example.com:8443/oauth/token");
}
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());
}
}
#[cfg_attr(kani, kani::proof)]
#[cfg_attr(kani, kani::unwind(140))]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_pkce_validator_refinement() {
#[cfg(kani)]
{
use crate::kernels::pkce_bytes::{validate_verifier_bytes, VerifierByteError};
use crate::verification::formal_models::PkceFormalSpec as Spec;
let len: usize = kani::any();
kani::assume(len <= 132);
let filler: u8 = kani::any();
kani::assume(crate::kernels::pkce_bytes::is_unreserved_byte(filler));
let bytes = [filler; 132];
let slice = &bytes[..len];
let accepted = validate_verifier_bytes(slice).is_ok();
let spec_ok = Spec::spec_validate_verifier(slice);
assert_eq!(
accepted, spec_ok,
"shipped validator must equal the formal spec over the bounded symbolic domain"
);
let pos: usize = kani::any();
kani::assume(pos < 44);
let bad: u8 = kani::any();
let mut probe = [b'a'; 44];
probe[pos] = bad;
let probe_res = validate_verifier_bytes(&probe);
let prod_rejects = probe_res.is_err();
let spec_rejects = !Spec::spec_validate_verifier(&probe);
assert_eq!(prod_rejects, spec_rejects, "one-byte-violation parity");
if let Err(VerifierByteError::InvalidCharacter { byte, position }) = probe_res {
assert_eq!(position, pos);
assert_eq!(byte, bad);
kani::cover!(
!crate::kernels::pkce_bytes::is_unreserved_byte(bad),
"invalid_byte_rejected"
);
}
kani::cover!(
crate::kernels::pkce_bytes::is_unreserved_byte(bad),
"valid_byte_admitted_at_pos"
);
let concrete_43 = [b'a'; 43];
assert!(validate_verifier_bytes(&concrete_43).is_ok());
kani::cover!(
validate_verifier_bytes(&concrete_43).is_ok(),
"valid_min_length_43_refined"
);
let concrete_128 = [b'z'; 128];
assert!(validate_verifier_bytes(&concrete_128).is_ok());
kani::cover!(
validate_verifier_bytes(&concrete_128).is_ok(),
"valid_max_length_128_refined"
);
let short_42 = [b'a'; 42];
assert!(validate_verifier_bytes(&short_42).is_err());
kani::cover!(
validate_verifier_bytes(&short_42).is_err(),
"short_below_min_rejected"
);
let long_129 = [b'a'; 129];
assert!(validate_verifier_bytes(&long_129).is_err());
kani::cover!(
validate_verifier_bytes(&long_129).is_err(),
"long_above_max_rejected"
);
let mut illegal = [b'a'; 44];
illegal[20] = b'+';
assert!(validate_verifier_bytes(&illegal).is_err());
}
#[cfg(not(kani))]
{
use crate::kernels::pkce_bytes::{validate_verifier_bytes, VerifierByteError};
for len in [0usize, 1, 42, 43, 64, 128, 129, 132] {
let v = vec![b'a'; len];
assert_eq!(
validate_verifier_bytes(&v).is_ok(),
PkceFormalSpec::spec_validate_verifier(&v),
"validator/spec divergence at length {len}"
);
}
let short_42 = vec![b'a'; 42];
assert!(validate_verifier_bytes(&short_42).is_err());
anti_vacuity_cover!(
"short_below_min_rejected",
validate_verifier_bytes(&short_42).is_err()
);
let long_129 = vec![b'a'; 129];
assert!(validate_verifier_bytes(&long_129).is_err());
anti_vacuity_cover!(
"long_above_max_rejected",
validate_verifier_bytes(&long_129).is_err()
);
let mut illegal = vec![b'a'; 43];
illegal[20] = b'+';
let err = validate_verifier_bytes(&illegal);
assert!(matches!(
err,
Err(VerifierByteError::InvalidCharacter {
byte: b'+',
position: 20
})
));
anti_vacuity_cover!("invalid_byte_rejected", err.is_err());
let mut ok_probe = vec![b'a'; 44];
ok_probe[20] = b'~';
assert!(validate_verifier_bytes(&ok_probe).is_ok());
anti_vacuity_cover!(
"valid_byte_admitted_at_pos",
validate_verifier_bytes(&ok_probe).is_ok()
);
let concrete_43 = vec![b'a'; 43];
assert!(validate_verifier_bytes(&concrete_43).is_ok());
anti_vacuity_cover!(
"valid_min_length_43_refined",
validate_verifier_bytes(&concrete_43).is_ok()
);
let concrete_128 = vec![b'z'; 128];
assert!(validate_verifier_bytes(&concrete_128).is_ok());
anti_vacuity_cover!(
"valid_max_length_128_refined",
validate_verifier_bytes(&concrete_128).is_ok()
);
}
}
#[cfg_attr(kani, kani::proof)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_ipv6_adapter_refinement() {
#[cfg(kani)]
{
use crate::kernels::ip_filter::{is_restricted_ipv4_octets, is_restricted_ipv6_segments};
use crate::verification::formal_models::SsrfFormalSpec as IPFormalSpec;
use std::net::Ipv6Addr;
let octets: [u8; 4] = kani::any();
let ip = Ipv4Addr::from(octets);
assert_eq!(is_restricted_ipv4(&ip), is_restricted_ipv4_octets(octets));
assert_eq!(
is_restricted_ipv4_octets(octets),
IPFormalSpec::spec_is_restricted_ipv4(&ip),
"production IPv4 adapter must equal the formal spec for every IPv4 address"
);
let segments: [u16; 8] = kani::any();
let v6 = Ipv6Addr::from(segments);
let core = is_restricted_ipv6_segments(segments);
assert_eq!(
crate::ssrf::is_restricted_ip(std::net::IpAddr::V6(v6)),
core,
"IPv6 adapter ≡ segment core"
);
if segments[0] == 0
&& segments[1] == 0
&& segments[2] == 0
&& segments[3] == 0
&& segments[4] == 0
&& segments[5] == 0xffff
{
let embedded = [
((segments[6] >> 8) & 0xff) as u8,
(segments[6] & 0xff) as u8,
((segments[7] >> 8) & 0xff) as u8,
(segments[7] & 0xff) as u8,
];
assert_eq!(core, is_restricted_ipv4_octets(embedded));
kani::cover!(
is_restricted_ipv4_octets(embedded),
"ipv6_mapped_private_embedded"
);
kani::cover!(
!is_restricted_ipv4_octets(embedded),
"ipv6_mapped_public_embedded"
);
}
let s0 = segments[0];
kani::cover!((s0 & 0xfe00) == 0xfc00, "ipv6_ula_hit");
kani::cover!((s0 & 0xffc0) == 0xfe80, "ipv6_link_local_hit");
kani::cover!((s0 & 0xff00) == 0xff00, "ipv6_multicast_hit");
kani::cover!(s0 == 0x2001 && segments[1] == 0, "ipv6_teredo_hit");
kani::cover!(s0 == 0x2002, "ipv6_6to4_hit");
kani::cover!(!core && s0 != 0x2001 && s0 != 0x2002, "ipv6_public_allowed");
}
#[cfg(not(kani))]
{
use crate::kernels::ip_filter::is_restricted_ipv6_segments;
use crate::verification::formal_models::SsrfFormalSpec as IPFormalSpec;
use std::net::Ipv6Addr;
let mapped_private = Ipv4Addr::new(10, 1, 2, 3).to_ipv6_mapped();
let mapped_public = Ipv4Addr::new(8, 8, 8, 8).to_ipv6_mapped();
assert!(is_restricted_ipv6(&mapped_private));
anti_vacuity_cover!(
"ipv6_mapped_private_embedded",
is_restricted_ipv6(&mapped_private)
);
assert!(!is_restricted_ipv6(&mapped_public));
anti_vacuity_cover!(
"ipv6_mapped_public_embedded",
!is_restricted_ipv6(&mapped_public)
);
let ula = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1);
let link_local = Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1);
let multicast = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1);
let teredo = Ipv6Addr::new(0x2001, 0, 0xdead, 0xbeef, 0, 0, 0, 1);
let sixto4 = Ipv6Addr::new(0x2002, 0x0a00, 0x0001, 0, 0, 0, 0, 1);
let documentation = Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1);
let nat64 = Ipv6Addr::new(0x64, 0xff9b, 0, 0, 0, 0, 0, 1);
let public = Ipv6Addr::new(0x2606, 0x4700, 0, 0, 0, 0, 0, 0x1111);
for ip in [
&ula,
&link_local,
&multicast,
&teredo,
&sixto4,
&documentation,
&nat64,
] {
let restricted = is_restricted_ipv6(ip);
let core = is_restricted_ipv6_segments(ip.segments());
assert_eq!(restricted, core, "adapter/core divergence at {ip}");
assert_eq!(
restricted,
IPFormalSpec::spec_is_restricted_ipv6(ip),
"production/spec divergence at {ip}"
);
}
assert!(is_restricted_ipv6(&ula));
anti_vacuity_cover!("ipv6_ula_hit", is_restricted_ipv6(&ula));
assert!(is_restricted_ipv6(&link_local));
anti_vacuity_cover!("ipv6_link_local_hit", is_restricted_ipv6(&link_local));
assert!(is_restricted_ipv6(&multicast));
anti_vacuity_cover!("ipv6_multicast_hit", is_restricted_ipv6(&multicast));
assert!(is_restricted_ipv6(&teredo));
anti_vacuity_cover!("ipv6_teredo_hit", is_restricted_ipv6(&teredo));
assert!(is_restricted_ipv6(&sixto4));
anti_vacuity_cover!("ipv6_6to4_hit", is_restricted_ipv6(&sixto4));
assert!(is_restricted_ipv6(&documentation));
anti_vacuity_cover!("ipv6_documentation_hit", is_restricted_ipv6(&documentation));
assert!(is_restricted_ipv6(&nat64));
anti_vacuity_cover!("ipv6_nat64_hit", is_restricted_ipv6(&nat64));
assert!(!is_restricted_ipv6(&public));
anti_vacuity_cover!("ipv6_public_allowed", !is_restricted_ipv6(&public));
}
}
#[cfg_attr(kani, kani::proof)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
pub fn proof_jti_admission_bound() {
#[cfg(kani)]
{
let max = crate::dpop::MAX_JTI_LENGTH;
let len: usize = kani::any();
kani::assume(len < 300);
let admissible = len > 0 && len <= max;
if len == 0 {
assert!(!admissible, "empty jti must be rejected");
kani::cover!(len == 0, "jti_empty_rejected");
} else if len > max {
assert!(!admissible, "over-cap jti must be rejected");
} else {
assert!(admissible, "in-bounds jti must be admitted");
}
kani::cover!(len == max, "jti_at_cap_admitted");
kani::cover!(len == max + 1, "jti_over_cap_rejected");
kani::cover!(len == 0, "jti_empty_rejected");
assert!(
max == 256 || max > 36,
"bound must accommodate UUID jti (36 bytes)"
);
}
#[cfg(not(kani))]
{
let max = crate::dpop::MAX_JTI_LENGTH;
let admissible = |len: usize| len > 0 && len <= max;
assert!(admissible(max), "at-cap jti must be admitted");
anti_vacuity_cover!("jti_at_cap_admitted", admissible(max));
assert!(!admissible(max + 1), "over-cap jti must be rejected");
anti_vacuity_cover!("jti_over_cap_rejected", !admissible(max + 1));
assert!(!admissible(0), "empty jti must be rejected");
anti_vacuity_cover!("jti_empty_rejected", !admissible(0));
}
}