From c36eb8180845ab971c6f25b17e03414438e23873 Mon Sep 17 00:00:00 2001 From: Alexey <247128645+axkurcom@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:17:06 +0300 Subject: [PATCH] =?UTF-8?q?Fix=20for=20TLS-F,=20ALPN=20=D0=B8=20SNI/ALPN?= =?UTF-8?q?=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com> --- src/proxy/tests/client_security_tests.rs | 16 +++ .../tests/handshake_advanced_clever_tests.rs | 79 +++++++++++--- .../handshake_baseline_invariant_tests.rs | 48 ++++++++- .../tests/handshake_more_clever_tests.rs | 63 ++++++++++- .../tests/handshake_real_bug_stress_tests.rs | 16 +++ src/proxy/tests/handshake_security_tests.rs | 100 ++++++++++++++---- .../handshake_timing_manual_bench_tests.rs | 63 ++++++++++- 7 files changed, 339 insertions(+), 46 deletions(-) diff --git a/src/proxy/tests/client_security_tests.rs b/src/proxy/tests/client_security_tests.rs index 50f8de2..e00e5fc 100644 --- a/src/proxy/tests/client_security_tests.rs +++ b/src/proxy/tests/client_security_tests.rs @@ -1719,6 +1719,9 @@ fn make_valid_tls_client_hello_with_alpn( timestamp: u32, alpn_protocols: &[&[u8]], ) -> Vec { + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; + let mut body = Vec::new(); body.extend_from_slice(&TLS_VERSION); body.extend_from_slice(&[0u8; 32]); @@ -1730,6 +1733,19 @@ fn make_valid_tls_client_hello_with_alpn( body.push(0); let mut ext_blob = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + ext_blob.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + ext_blob.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + ext_blob.extend_from_slice(&key_share_extension); + if !alpn_protocols.is_empty() { let mut alpn_list = Vec::new(); for proto in alpn_protocols { diff --git a/src/proxy/tests/handshake_advanced_clever_tests.rs b/src/proxy/tests/handshake_advanced_clever_tests.rs index 4a521d8..7d7cc80 100644 --- a/src/proxy/tests/handshake_advanced_clever_tests.rs +++ b/src/proxy/tests/handshake_advanced_clever_tests.rs @@ -21,11 +21,52 @@ fn test_config_with_secret_hex(secret_hex: &str) -> ProxyConfig { } fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec { + const TLS_AES_128_GCM_SHA256: [u8; 2] = [0x13, 0x01]; + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; let session_id_len: usize = 32; - let len = tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN + 1 + session_id_len; - let mut handshake = vec![0x42u8; len]; + let fill = 0x42u8; - handshake[tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] = session_id_len as u8; + let mut extensions = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + extensions.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + extensions.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + extensions.extend_from_slice(&key_share_extension); + + let body_len = + 2 + 32 + 1 + session_id_len + 2 + TLS_AES_128_GCM_SHA256.len() + 1 + 1 + 2 + + extensions.len(); + let mut body = Vec::with_capacity(body_len); + body.extend_from_slice(&TLS_VERSION); + body.extend_from_slice(&[fill; 32]); + body.push(session_id_len as u8); + body.extend_from_slice(&[fill; 32]); + body.extend_from_slice(&(TLS_AES_128_GCM_SHA256.len() as u16).to_be_bytes()); + body.extend_from_slice(&TLS_AES_128_GCM_SHA256); + body.push(1); + body.push(0); + body.extend_from_slice(&(extensions.len() as u16).to_be_bytes()); + body.extend_from_slice(&extensions); + assert_eq!(body.len(), body_len); + + let mut handshake = Vec::with_capacity(5 + 4 + body_len); + handshake.push(TLS_RECORD_HANDSHAKE); + handshake.extend_from_slice(&[0x03, 0x01]); + handshake.extend_from_slice(&((4 + body_len) as u16).to_be_bytes()); + handshake.push(0x01); + let body_len_bytes = (body_len as u32).to_be_bytes(); + handshake.extend_from_slice(&body_len_bytes[1..4]); + handshake.extend_from_slice(&body); + + // The proxy authenticates TLS-fronted clients through the random field. handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN].fill(0); let computed = sha256_hmac(secret, &handshake); @@ -85,6 +126,9 @@ fn make_valid_tls_client_hello_with_alpn( timestamp: u32, alpn_protocols: &[&[u8]], ) -> Vec { + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; + let mut body = Vec::new(); body.extend_from_slice(&TLS_VERSION); body.extend_from_slice(&[0u8; 32]); @@ -96,6 +140,19 @@ fn make_valid_tls_client_hello_with_alpn( body.push(0); let mut ext_blob = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + ext_blob.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + ext_blob.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + ext_blob.extend_from_slice(&key_share_extension); + if !alpn_protocols.is_empty() { let mut alpn_list = Vec::new(); for proto in alpn_protocols { @@ -150,13 +207,7 @@ async fn tls_minimum_viable_length_boundary() { let rng = SecureRandom::new(); let peer: SocketAddr = "192.0.2.1:12345".parse().unwrap(); - let min_len = tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN + 1; - let mut exact_min_handshake = vec![0x42u8; min_len]; - exact_min_handshake[min_len - 1] = 0; - exact_min_handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN].fill(0); - let digest = sha256_hmac(&secret, &exact_min_handshake); - exact_min_handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] - .copy_from_slice(&digest); + let exact_min_handshake = make_valid_tls_handshake(&secret, 0); let res = handle_tls_handshake( &exact_min_handshake, @@ -171,12 +222,12 @@ async fn tls_minimum_viable_length_boundary() { .await; assert!( matches!(res, HandshakeResult::Success(_)), - "Exact minimum length TLS handshake must succeed" + "Minimum valid TLS ClientHello must succeed" ); - let short_handshake = vec![0x42u8; min_len - 1]; + let short_handshake = &exact_min_handshake[..exact_min_handshake.len() - 1]; let res_short = handle_tls_handshake( - &short_handshake, + short_handshake, tokio::io::empty(), tokio::io::sink(), peer, @@ -188,7 +239,7 @@ async fn tls_minimum_viable_length_boundary() { .await; assert!( matches!(res_short, HandshakeResult::BadClient { .. }), - "Handshake 1 byte shorter than minimum must fail closed" + "Handshake 1 byte shorter than minimum valid ClientHello must fail closed" ); } diff --git a/src/proxy/tests/handshake_baseline_invariant_tests.rs b/src/proxy/tests/handshake_baseline_invariant_tests.rs index 5f938d8..8273303 100644 --- a/src/proxy/tests/handshake_baseline_invariant_tests.rs +++ b/src/proxy/tests/handshake_baseline_invariant_tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::crypto::sha256_hmac; +use crate::protocol::constants::{TLS_RECORD_HANDSHAKE, TLS_VERSION}; use crate::stats::ReplayChecker; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::{Duration, Instant}; @@ -17,11 +18,52 @@ fn test_config_with_secret_hex(secret_hex: &str) -> ProxyConfig { } fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec { + const TLS_AES_128_GCM_SHA256: [u8; 2] = [0x13, 0x01]; + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; let session_id_len: usize = 32; - let len = tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN + 1 + session_id_len; - let mut handshake = vec![0x42u8; len]; + let fill = 0x42u8; - handshake[tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] = session_id_len as u8; + let mut extensions = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + extensions.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + extensions.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + extensions.extend_from_slice(&key_share_extension); + + let body_len = + 2 + 32 + 1 + session_id_len + 2 + TLS_AES_128_GCM_SHA256.len() + 1 + 1 + 2 + + extensions.len(); + let mut body = Vec::with_capacity(body_len); + body.extend_from_slice(&TLS_VERSION); + body.extend_from_slice(&[fill; 32]); + body.push(session_id_len as u8); + body.extend_from_slice(&[fill; 32]); + body.extend_from_slice(&(TLS_AES_128_GCM_SHA256.len() as u16).to_be_bytes()); + body.extend_from_slice(&TLS_AES_128_GCM_SHA256); + body.push(1); + body.push(0); + body.extend_from_slice(&(extensions.len() as u16).to_be_bytes()); + body.extend_from_slice(&extensions); + assert_eq!(body.len(), body_len); + + let mut handshake = Vec::with_capacity(5 + 4 + body_len); + handshake.push(TLS_RECORD_HANDSHAKE); + handshake.extend_from_slice(&[0x03, 0x01]); + handshake.extend_from_slice(&((4 + body_len) as u16).to_be_bytes()); + handshake.push(0x01); + let body_len_bytes = (body_len as u32).to_be_bytes(); + handshake.extend_from_slice(&body_len_bytes[1..4]); + handshake.extend_from_slice(&body); + + // The proxy authenticates TLS-fronted clients through the random field. handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN].fill(0); let computed = sha256_hmac(secret, &handshake); diff --git a/src/proxy/tests/handshake_more_clever_tests.rs b/src/proxy/tests/handshake_more_clever_tests.rs index f570424..8c56d4e 100644 --- a/src/proxy/tests/handshake_more_clever_tests.rs +++ b/src/proxy/tests/handshake_more_clever_tests.rs @@ -25,11 +25,52 @@ fn test_config_with_secret_hex(secret_hex: &str) -> ProxyConfig { } fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec { + const TLS_AES_128_GCM_SHA256: [u8; 2] = [0x13, 0x01]; + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; let session_id_len: usize = 32; - let len = tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN + 1 + session_id_len; - let mut handshake = vec![0x42u8; len]; + let fill = 0x42u8; - handshake[tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] = session_id_len as u8; + let mut extensions = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + extensions.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + extensions.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + extensions.extend_from_slice(&key_share_extension); + + let body_len = + 2 + 32 + 1 + session_id_len + 2 + TLS_AES_128_GCM_SHA256.len() + 1 + 1 + 2 + + extensions.len(); + let mut body = Vec::with_capacity(body_len); + body.extend_from_slice(&TLS_VERSION); + body.extend_from_slice(&[fill; 32]); + body.push(session_id_len as u8); + body.extend_from_slice(&[fill; 32]); + body.extend_from_slice(&(TLS_AES_128_GCM_SHA256.len() as u16).to_be_bytes()); + body.extend_from_slice(&TLS_AES_128_GCM_SHA256); + body.push(1); + body.push(0); + body.extend_from_slice(&(extensions.len() as u16).to_be_bytes()); + body.extend_from_slice(&extensions); + assert_eq!(body.len(), body_len); + + let mut handshake = Vec::with_capacity(5 + 4 + body_len); + handshake.push(TLS_RECORD_HANDSHAKE); + handshake.extend_from_slice(&[0x03, 0x01]); + handshake.extend_from_slice(&((4 + body_len) as u16).to_be_bytes()); + handshake.push(0x01); + let body_len_bytes = (body_len as u32).to_be_bytes(); + handshake.extend_from_slice(&body_len_bytes[1..4]); + handshake.extend_from_slice(&body); + + // The proxy authenticates TLS-fronted clients through the random field. handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN].fill(0); let computed = sha256_hmac(secret, &handshake); @@ -90,6 +131,9 @@ fn make_valid_tls_client_hello_with_sni_and_alpn( sni_host: &str, alpn_protocols: &[&[u8]], ) -> Vec { + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; + let mut body = Vec::new(); body.extend_from_slice(&TLS_VERSION); body.extend_from_slice(&[0u8; 32]); @@ -112,6 +156,19 @@ fn make_valid_tls_client_hello_with_sni_and_alpn( ext_blob.extend_from_slice(&(sni_payload.len() as u16).to_be_bytes()); ext_blob.extend_from_slice(&sni_payload); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + ext_blob.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + ext_blob.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + ext_blob.extend_from_slice(&key_share_extension); + if !alpn_protocols.is_empty() { let mut alpn_list = Vec::new(); for proto in alpn_protocols { diff --git a/src/proxy/tests/handshake_real_bug_stress_tests.rs b/src/proxy/tests/handshake_real_bug_stress_tests.rs index 9705853..97b88d6 100644 --- a/src/proxy/tests/handshake_real_bug_stress_tests.rs +++ b/src/proxy/tests/handshake_real_bug_stress_tests.rs @@ -24,6 +24,9 @@ fn make_valid_tls_client_hello_with_alpn( timestamp: u32, alpn_protocols: &[&[u8]], ) -> Vec { + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; + let mut body = Vec::new(); body.extend_from_slice(&TLS_VERSION); body.extend_from_slice(&[0u8; 32]); @@ -35,6 +38,19 @@ fn make_valid_tls_client_hello_with_alpn( body.push(0); let mut ext_blob = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + ext_blob.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + ext_blob.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + ext_blob.extend_from_slice(&key_share_extension); + if !alpn_protocols.is_empty() { let mut alpn_list = Vec::new(); for proto in alpn_protocols { diff --git a/src/proxy/tests/handshake_security_tests.rs b/src/proxy/tests/handshake_security_tests.rs index dd7ad08..980075c 100644 --- a/src/proxy/tests/handshake_security_tests.rs +++ b/src/proxy/tests/handshake_security_tests.rs @@ -10,11 +10,52 @@ use std::time::{Duration, Instant}; use tokio::sync::Barrier; fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec { + const TLS_AES_128_GCM_SHA256: [u8; 2] = [0x13, 0x01]; + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; let session_id_len: usize = 32; - let len = tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN + 1 + session_id_len; - let mut handshake = vec![0x42u8; len]; + let fill = 0x42u8; - handshake[tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] = session_id_len as u8; + let mut extensions = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + extensions.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + extensions.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + extensions.extend_from_slice(&key_share_extension); + + let body_len = + 2 + 32 + 1 + session_id_len + 2 + TLS_AES_128_GCM_SHA256.len() + 1 + 1 + 2 + + extensions.len(); + let mut body = Vec::with_capacity(body_len); + body.extend_from_slice(&TLS_VERSION); + body.extend_from_slice(&[fill; 32]); + body.push(session_id_len as u8); + body.extend_from_slice(&[fill; 32]); + body.extend_from_slice(&(TLS_AES_128_GCM_SHA256.len() as u16).to_be_bytes()); + body.extend_from_slice(&TLS_AES_128_GCM_SHA256); + body.push(1); + body.push(0); + body.extend_from_slice(&(extensions.len() as u16).to_be_bytes()); + body.extend_from_slice(&extensions); + assert_eq!(body.len(), body_len); + + let mut handshake = Vec::with_capacity(5 + 4 + body_len); + handshake.push(TLS_RECORD_HANDSHAKE); + handshake.extend_from_slice(&[0x03, 0x01]); + handshake.extend_from_slice(&((4 + body_len) as u16).to_be_bytes()); + handshake.push(0x01); + let body_len_bytes = (body_len as u32).to_be_bytes(); + handshake.extend_from_slice(&body_len_bytes[1..4]); + handshake.extend_from_slice(&body); + + // The proxy authenticates TLS-fronted clients through the random field. handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN].fill(0); let computed = sha256_hmac(secret, &handshake); @@ -34,6 +75,9 @@ fn make_valid_tls_client_hello_with_alpn( timestamp: u32, alpn_protocols: &[&[u8]], ) -> Vec { + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; + let mut body = Vec::new(); body.extend_from_slice(&TLS_VERSION); body.extend_from_slice(&[0u8; 32]); @@ -45,6 +89,19 @@ fn make_valid_tls_client_hello_with_alpn( body.push(0); let mut ext_blob = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + ext_blob.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + ext_blob.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + ext_blob.extend_from_slice(&key_share_extension); + if !alpn_protocols.is_empty() { let mut alpn_list = Vec::new(); for proto in alpn_protocols { @@ -92,6 +149,9 @@ fn make_valid_tls_client_hello_with_sni_and_alpn( sni_host: &str, alpn_protocols: &[&[u8]], ) -> Vec { + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; + let mut body = Vec::new(); body.extend_from_slice(&TLS_VERSION); body.extend_from_slice(&[0u8; 32]); @@ -114,6 +174,19 @@ fn make_valid_tls_client_hello_with_sni_and_alpn( ext_blob.extend_from_slice(&(sni_payload.len() as u16).to_be_bytes()); ext_blob.extend_from_slice(&sni_payload); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + ext_blob.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + ext_blob.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + ext_blob.extend_from_slice(&key_share_extension); + if !alpn_protocols.is_empty() { let mut alpn_list = Vec::new(); for proto in alpn_protocols { @@ -549,25 +622,6 @@ async fn adversarial_tls_replay_churn_allows_only_unique_digests() { let replay_checker = Arc::new(ReplayChecker::new(8192, Duration::from_secs(60))); let rng = Arc::new(SecureRandom::new()); - let make_tagged_handshake = |timestamp: u32, tag: u8| { - let session_id_len: usize = 32; - let len = tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN + 1 + session_id_len; - let mut handshake = vec![tag; len]; - - handshake[tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] = session_id_len as u8; - handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN].fill(0); - let computed = sha256_hmac(&secret, &handshake); - let mut digest = computed; - let ts = timestamp.to_le_bytes(); - for i in 0..4 { - digest[28 + i] ^= ts[i]; - } - - handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] - .copy_from_slice(&digest); - handshake - }; - let mut tasks = Vec::new(); // 128 exact duplicates: only one should pass. @@ -601,7 +655,7 @@ async fn adversarial_tls_replay_churn_allows_only_unique_digests() { let config = Arc::clone(&config); let replay_checker = Arc::clone(&replay_checker); let rng = Arc::clone(&rng); - let handshake = make_tagged_handshake(10_000 + i as u32, (i as u8).wrapping_add(0x80)); + let handshake = make_valid_tls_handshake(&secret, 10_000 + i as u32); tasks.push(tokio::spawn(async move { let peer = SocketAddr::new( IpAddr::V4(Ipv4Addr::new(198, 18, 0, ((i % 250) + 1) as u8)), diff --git a/src/proxy/tests/handshake_timing_manual_bench_tests.rs b/src/proxy/tests/handshake_timing_manual_bench_tests.rs index 458cb4f..446b9f3 100644 --- a/src/proxy/tests/handshake_timing_manual_bench_tests.rs +++ b/src/proxy/tests/handshake_timing_manual_bench_tests.rs @@ -47,11 +47,52 @@ fn make_valid_mtproto_handshake( } fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec { + const TLS_AES_128_GCM_SHA256: [u8; 2] = [0x13, 0x01]; + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; let session_id_len: usize = 32; - let len = tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN + 1 + session_id_len; - let mut handshake = vec![0x42u8; len]; + let fill = 0x42u8; - handshake[tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN] = session_id_len as u8; + let mut extensions = Vec::new(); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + extensions.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + extensions.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + extensions.extend_from_slice(&key_share_extension); + + let body_len = + 2 + 32 + 1 + session_id_len + 2 + TLS_AES_128_GCM_SHA256.len() + 1 + 1 + 2 + + extensions.len(); + let mut body = Vec::with_capacity(body_len); + body.extend_from_slice(&TLS_VERSION); + body.extend_from_slice(&[fill; 32]); + body.push(session_id_len as u8); + body.extend_from_slice(&[fill; 32]); + body.extend_from_slice(&(TLS_AES_128_GCM_SHA256.len() as u16).to_be_bytes()); + body.extend_from_slice(&TLS_AES_128_GCM_SHA256); + body.push(1); + body.push(0); + body.extend_from_slice(&(extensions.len() as u16).to_be_bytes()); + body.extend_from_slice(&extensions); + assert_eq!(body.len(), body_len); + + let mut handshake = Vec::with_capacity(5 + 4 + body_len); + handshake.push(TLS_RECORD_HANDSHAKE); + handshake.extend_from_slice(&[0x03, 0x01]); + handshake.extend_from_slice(&((4 + body_len) as u16).to_be_bytes()); + handshake.push(0x01); + let body_len_bytes = (body_len as u32).to_be_bytes(); + handshake.extend_from_slice(&body_len_bytes[1..4]); + handshake.extend_from_slice(&body); + + // The proxy authenticates TLS-fronted clients through the random field. handshake[tls::TLS_DIGEST_POS..tls::TLS_DIGEST_POS + tls::TLS_DIGEST_LEN].fill(0); let computed = sha256_hmac(secret, &handshake); @@ -72,6 +113,9 @@ fn make_valid_tls_client_hello_with_sni_and_alpn( sni_host: &str, alpn_protocols: &[&[u8]], ) -> Vec { + const TLS_EXTENSION_KEY_SHARE: u16 = 0x0033; + const X25519_KEY_SHARE_LEN: usize = 32; + let mut body = Vec::new(); body.extend_from_slice(&TLS_VERSION); body.extend_from_slice(&[0u8; 32]); @@ -93,6 +137,19 @@ fn make_valid_tls_client_hello_with_sni_and_alpn( ext_blob.extend_from_slice(&(sni_payload.len() as u16).to_be_bytes()); ext_blob.extend_from_slice(&sni_payload); + let mut key_share = Vec::new(); + key_share.extend_from_slice(&tls::TLS_NAMED_GROUP_X25519.to_be_bytes()); + key_share.extend_from_slice(&(X25519_KEY_SHARE_LEN as u16).to_be_bytes()); + key_share.push(9); + key_share.resize(key_share.len() + X25519_KEY_SHARE_LEN - 1, 0); + + let mut key_share_extension = Vec::new(); + key_share_extension.extend_from_slice(&(key_share.len() as u16).to_be_bytes()); + key_share_extension.extend_from_slice(&key_share); + ext_blob.extend_from_slice(&TLS_EXTENSION_KEY_SHARE.to_be_bytes()); + ext_blob.extend_from_slice(&(key_share_extension.len() as u16).to_be_bytes()); + ext_blob.extend_from_slice(&key_share_extension); + if !alpn_protocols.is_empty() { let mut alpn_list = Vec::new(); for proto in alpn_protocols {