Files
telemt/src/proxy/tests/masking_timing_normalization_security_tests.rs
David Osipov bb355e916f Add comprehensive security tests for masking and shape hardening features
- Introduced red-team expected-fail tests for client masking shape hardening.
- Added integration tests for masking AB envelope blur to improve obfuscation.
- Implemented masking security tests to validate the behavior of masking under various conditions.
- Created tests for masking shape above-cap blur to ensure proper functionality.
- Developed adversarial tests for masking shape hardening to evaluate robustness against attacks.
- Added timing normalization security tests to assess the effectiveness of timing obfuscation.
- Implemented red-team expected-fail tests for timing side-channel vulnerabilities.
2026-03-21 00:30:51 +04:00

121 lines
4.0 KiB
Rust

use super::*;
use tokio::io::duplex;
use tokio::net::TcpListener;
use tokio::time::{Duration, Instant};
#[derive(Clone, Copy)]
enum MaskPath {
ConnectFail,
ConnectSuccess,
SlowBackend,
}
async fn measure_bad_client_duration_ms(path: MaskPath, floor_ms: u64, ceiling_ms: u64) -> u128 {
let mut config = ProxyConfig::default();
config.general.beobachten = false;
config.censorship.mask = true;
config.censorship.mask_unix_sock = None;
config.censorship.mask_timing_normalization_enabled = true;
config.censorship.mask_timing_normalization_floor_ms = floor_ms;
config.censorship.mask_timing_normalization_ceiling_ms = ceiling_ms;
let accept_task = match path {
MaskPath::ConnectFail => {
config.censorship.mask_host = Some("127.0.0.1".to_string());
config.censorship.mask_port = 1;
None
}
MaskPath::ConnectSuccess => {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let backend_addr = listener.local_addr().unwrap();
config.censorship.mask_host = Some("127.0.0.1".to_string());
config.censorship.mask_port = backend_addr.port();
Some(tokio::spawn(async move {
let (_stream, _) = listener.accept().await.unwrap();
}))
}
MaskPath::SlowBackend => {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let backend_addr = listener.local_addr().unwrap();
config.censorship.mask_host = Some("127.0.0.1".to_string());
config.censorship.mask_port = backend_addr.port();
Some(tokio::spawn(async move {
let (_stream, _) = listener.accept().await.unwrap();
tokio::time::sleep(Duration::from_millis(320)).await;
}))
}
};
let (client_reader, _client_writer) = duplex(1024);
let (_client_visible_reader, client_visible_writer) = duplex(1024);
let peer: SocketAddr = "198.51.100.221:57121".parse().unwrap();
let local: SocketAddr = "127.0.0.1:443".parse().unwrap();
let beobachten = BeobachtenStore::new();
let started = Instant::now();
handle_bad_client(
client_reader,
client_visible_writer,
b"GET /timing-normalize HTTP/1.1\r\nHost: x\r\n\r\n",
peer,
local,
&config,
&beobachten,
)
.await;
if let Some(task) = accept_task {
let _ = tokio::time::timeout(Duration::from_secs(2), task).await;
}
started.elapsed().as_millis()
}
#[tokio::test]
async fn timing_normalization_envelope_applies_to_connect_fail_and_success() {
let floor = 160u64;
let ceiling = 180u64;
let fail = measure_bad_client_duration_ms(MaskPath::ConnectFail, floor, ceiling).await;
let success = measure_bad_client_duration_ms(MaskPath::ConnectSuccess, floor, ceiling).await;
assert!(
fail >= floor as u128,
"connect-fail duration below floor: {fail}ms < {floor}ms"
);
assert!(
fail <= (ceiling + 60) as u128,
"connect-fail duration exceeded relaxed ceiling: {fail}ms > {}ms",
ceiling + 60
);
assert!(
success >= floor as u128,
"connect-success duration below floor: {success}ms < {floor}ms"
);
assert!(
success <= (ceiling + 60) as u128,
"connect-success duration exceeded relaxed ceiling: {success}ms > {}ms",
ceiling + 60
);
let delta = fail.abs_diff(success);
assert!(
delta <= 80,
"timing normalization should reduce path divergence (delta={}ms)",
delta
);
}
#[tokio::test]
async fn timing_normalization_does_not_sleep_if_path_already_exceeds_ceiling() {
let floor = 120u64;
let ceiling = 150u64;
let slow = measure_bad_client_duration_ms(MaskPath::SlowBackend, floor, ceiling).await;
assert!(slow >= 280, "slow backend path should remain slow (got {slow}ms)");
assert!(slow <= 520, "slow backend path should remain bounded in tests (got {slow}ms)");
}