mirror of
https://github.com/telemt/telemt.git
synced 2026-04-19 03:24:10 +03:00
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.
This commit is contained in:
120
src/proxy/tests/masking_timing_normalization_security_tests.rs
Normal file
120
src/proxy/tests/masking_timing_normalization_security_tests.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
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)");
|
||||
}
|
||||
Reference in New Issue
Block a user