1.1.1.0
This commit is contained in:
@@ -17,12 +17,13 @@ use crate::transport::{configure_client_socket, UpstreamManager};
|
|||||||
use crate::stream::{CryptoReader, CryptoWriter, FakeTlsReader, FakeTlsWriter, BufferPool};
|
use crate::stream::{CryptoReader, CryptoWriter, FakeTlsReader, FakeTlsWriter, BufferPool};
|
||||||
use crate::crypto::AesCtr;
|
use crate::crypto::AesCtr;
|
||||||
|
|
||||||
use super::handshake::{
|
// Use absolute paths to avoid confusion
|
||||||
|
use crate::proxy::handshake::{
|
||||||
handle_tls_handshake, handle_mtproto_handshake,
|
handle_tls_handshake, handle_mtproto_handshake,
|
||||||
HandshakeSuccess, generate_tg_nonce, encrypt_tg_nonce,
|
HandshakeSuccess, generate_tg_nonce, encrypt_tg_nonce,
|
||||||
};
|
};
|
||||||
use super::relay::relay_bidirectional;
|
use crate::proxy::relay::relay_bidirectional;
|
||||||
use super::masking::handle_bad_client;
|
use crate::proxy::masking::handle_bad_client;
|
||||||
|
|
||||||
/// Client connection handler (builder struct)
|
/// Client connection handler (builder struct)
|
||||||
pub struct ClientHandler;
|
pub struct ClientHandler;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ where
|
|||||||
|
|
||||||
// Check for replay
|
// Check for replay
|
||||||
if replay_checker.check_tls_digest(digest_half) {
|
if replay_checker.check_tls_digest(digest_half) {
|
||||||
warn!(peer = %peer, "TLS replay attack detected");
|
warn!(peer = %peer, "TLS replay attack detected (duplicate digest)");
|
||||||
return HandshakeResult::BadClient { reader, writer };
|
return HandshakeResult::BadClient { reader, writer };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +72,6 @@ where
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
debug!(peer = %peer, num_users = secrets.len(), "Validating TLS handshake against users");
|
|
||||||
|
|
||||||
// Validate handshake
|
// Validate handshake
|
||||||
let validation = match tls::validate_tls_handshake(
|
let validation = match tls::validate_tls_handshake(
|
||||||
handshake,
|
handshake,
|
||||||
@@ -82,7 +80,11 @@ where
|
|||||||
) {
|
) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => {
|
||||||
debug!(peer = %peer, "TLS handshake validation failed - no matching user");
|
debug!(
|
||||||
|
peer = %peer,
|
||||||
|
ignore_time_skew = config.access.ignore_time_skew,
|
||||||
|
"TLS handshake validation failed - no matching user or time skew"
|
||||||
|
);
|
||||||
return HandshakeResult::BadClient { reader, writer };
|
return HandshakeResult::BadClient { reader, writer };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -104,14 +106,16 @@ where
|
|||||||
debug!(peer = %peer, response_len = response.len(), "Sending TLS ServerHello");
|
debug!(peer = %peer, response_len = response.len(), "Sending TLS ServerHello");
|
||||||
|
|
||||||
if let Err(e) = writer.write_all(&response).await {
|
if let Err(e) = writer.write_all(&response).await {
|
||||||
|
warn!(peer = %peer, error = %e, "Failed to write TLS ServerHello");
|
||||||
return HandshakeResult::Error(ProxyError::Io(e));
|
return HandshakeResult::Error(ProxyError::Io(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = writer.flush().await {
|
if let Err(e) = writer.flush().await {
|
||||||
|
warn!(peer = %peer, error = %e, "Failed to flush TLS ServerHello");
|
||||||
return HandshakeResult::Error(ProxyError::Io(e));
|
return HandshakeResult::Error(ProxyError::Io(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record for replay protection
|
// Record for replay protection only after successful handshake
|
||||||
replay_checker.add_tls_digest(digest_half);
|
replay_checker.add_tls_digest(digest_half);
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
@@ -146,12 +150,6 @@ where
|
|||||||
// Extract prekey and IV
|
// Extract prekey and IV
|
||||||
let dec_prekey_iv = &handshake[SKIP_LEN..SKIP_LEN + PREKEY_LEN + IV_LEN];
|
let dec_prekey_iv = &handshake[SKIP_LEN..SKIP_LEN + PREKEY_LEN + IV_LEN];
|
||||||
|
|
||||||
debug!(
|
|
||||||
peer = %peer,
|
|
||||||
dec_prekey_iv = %hex::encode(dec_prekey_iv),
|
|
||||||
"Extracted prekey+IV from handshake"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for replay
|
// Check for replay
|
||||||
if replay_checker.check_handshake(dec_prekey_iv) {
|
if replay_checker.check_handshake(dec_prekey_iv) {
|
||||||
warn!(peer = %peer, "MTProto replay attack detected");
|
warn!(peer = %peer, "MTProto replay attack detected");
|
||||||
@@ -183,13 +181,6 @@ where
|
|||||||
let mut decryptor = AesCtr::new(&dec_key, dec_iv);
|
let mut decryptor = AesCtr::new(&dec_key, dec_iv);
|
||||||
let decrypted = decryptor.decrypt(handshake);
|
let decrypted = decryptor.decrypt(handshake);
|
||||||
|
|
||||||
trace!(
|
|
||||||
peer = %peer,
|
|
||||||
user = %user,
|
|
||||||
decrypted_tail = %hex::encode(&decrypted[PROTO_TAG_POS..]),
|
|
||||||
"Decrypted handshake tail"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check protocol tag
|
// Check protocol tag
|
||||||
let tag_bytes: [u8; 4] = decrypted[PROTO_TAG_POS..PROTO_TAG_POS + 4]
|
let tag_bytes: [u8; 4] = decrypted[PROTO_TAG_POS..PROTO_TAG_POS + 4]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -197,14 +188,9 @@ where
|
|||||||
|
|
||||||
let proto_tag = match ProtoTag::from_bytes(tag_bytes) {
|
let proto_tag = match ProtoTag::from_bytes(tag_bytes) {
|
||||||
Some(tag) => tag,
|
Some(tag) => tag,
|
||||||
None => {
|
None => continue,
|
||||||
trace!(peer = %peer, user = %user, tag = %hex::encode(tag_bytes), "Invalid proto tag");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(peer = %peer, user = %user, proto = ?proto_tag, "Found valid proto tag");
|
|
||||||
|
|
||||||
// Check if mode is enabled
|
// Check if mode is enabled
|
||||||
let mode_ok = match proto_tag {
|
let mode_ok = match proto_tag {
|
||||||
ProtoTag::Secure => {
|
ProtoTag::Secure => {
|
||||||
@@ -274,9 +260,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generate nonce for Telegram connection
|
/// Generate nonce for Telegram connection
|
||||||
///
|
|
||||||
/// In FAST MODE: we use the same keys for TG as for client, but reversed.
|
|
||||||
/// This means: client's enc_key becomes TG's dec_key and vice versa.
|
|
||||||
pub fn generate_tg_nonce(
|
pub fn generate_tg_nonce(
|
||||||
proto_tag: ProtoTag,
|
proto_tag: ProtoTag,
|
||||||
client_dec_key: &[u8; 32],
|
client_dec_key: &[u8; 32],
|
||||||
@@ -287,39 +270,22 @@ pub fn generate_tg_nonce(
|
|||||||
let bytes = SECURE_RANDOM.bytes(HANDSHAKE_LEN);
|
let bytes = SECURE_RANDOM.bytes(HANDSHAKE_LEN);
|
||||||
let mut nonce: [u8; HANDSHAKE_LEN] = bytes.try_into().unwrap();
|
let mut nonce: [u8; HANDSHAKE_LEN] = bytes.try_into().unwrap();
|
||||||
|
|
||||||
// Check reserved patterns
|
if RESERVED_NONCE_FIRST_BYTES.contains(&nonce[0]) { continue; }
|
||||||
if RESERVED_NONCE_FIRST_BYTES.contains(&nonce[0]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let first_four: [u8; 4] = nonce[..4].try_into().unwrap();
|
let first_four: [u8; 4] = nonce[..4].try_into().unwrap();
|
||||||
if RESERVED_NONCE_BEGINNINGS.contains(&first_four) {
|
if RESERVED_NONCE_BEGINNINGS.contains(&first_four) { continue; }
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let continue_four: [u8; 4] = nonce[4..8].try_into().unwrap();
|
let continue_four: [u8; 4] = nonce[4..8].try_into().unwrap();
|
||||||
if RESERVED_NONCE_CONTINUES.contains(&continue_four) {
|
if RESERVED_NONCE_CONTINUES.contains(&continue_four) { continue; }
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set protocol tag
|
|
||||||
nonce[PROTO_TAG_POS..PROTO_TAG_POS + 4].copy_from_slice(&proto_tag.to_bytes());
|
nonce[PROTO_TAG_POS..PROTO_TAG_POS + 4].copy_from_slice(&proto_tag.to_bytes());
|
||||||
|
|
||||||
// Fast mode: copy client's dec_key+iv (this becomes TG's enc direction)
|
|
||||||
// In fast mode, we make TG use the same keys as client but swapped:
|
|
||||||
// - What we decrypt FROM TG = what we encrypt TO client (so no re-encryption needed)
|
|
||||||
// - What we encrypt TO TG = what we decrypt FROM client
|
|
||||||
if fast_mode {
|
if fast_mode {
|
||||||
// Put client's dec_key + dec_iv into nonce[8:56]
|
|
||||||
// This will be used by TG for encryption TO us
|
|
||||||
nonce[SKIP_LEN..SKIP_LEN + KEY_LEN].copy_from_slice(client_dec_key);
|
nonce[SKIP_LEN..SKIP_LEN + KEY_LEN].copy_from_slice(client_dec_key);
|
||||||
nonce[SKIP_LEN + KEY_LEN..SKIP_LEN + KEY_LEN + IV_LEN]
|
nonce[SKIP_LEN + KEY_LEN..SKIP_LEN + KEY_LEN + IV_LEN]
|
||||||
.copy_from_slice(&client_dec_iv.to_be_bytes());
|
.copy_from_slice(&client_dec_iv.to_be_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now compute what keys WE will use for TG connection
|
|
||||||
// enc_key_iv = nonce[8:56] (for encrypting TO TG)
|
|
||||||
// dec_key_iv = nonce[8:56] reversed (for decrypting FROM TG)
|
|
||||||
let enc_key_iv = &nonce[SKIP_LEN..SKIP_LEN + KEY_LEN + IV_LEN];
|
let enc_key_iv = &nonce[SKIP_LEN..SKIP_LEN + KEY_LEN + IV_LEN];
|
||||||
let dec_key_iv: Vec<u8> = enc_key_iv.iter().rev().copied().collect();
|
let dec_key_iv: Vec<u8> = enc_key_iv.iter().rev().copied().collect();
|
||||||
|
|
||||||
@@ -329,44 +295,22 @@ pub fn generate_tg_nonce(
|
|||||||
let tg_dec_key: [u8; 32] = dec_key_iv[..KEY_LEN].try_into().unwrap();
|
let tg_dec_key: [u8; 32] = dec_key_iv[..KEY_LEN].try_into().unwrap();
|
||||||
let tg_dec_iv = u128::from_be_bytes(dec_key_iv[KEY_LEN..].try_into().unwrap());
|
let tg_dec_iv = u128::from_be_bytes(dec_key_iv[KEY_LEN..].try_into().unwrap());
|
||||||
|
|
||||||
debug!(
|
|
||||||
fast_mode = fast_mode,
|
|
||||||
tg_enc_key = %hex::encode(&tg_enc_key[..8]),
|
|
||||||
tg_dec_key = %hex::encode(&tg_dec_key[..8]),
|
|
||||||
"Generated TG nonce"
|
|
||||||
);
|
|
||||||
|
|
||||||
return (nonce, tg_enc_key, tg_enc_iv, tg_dec_key, tg_dec_iv);
|
return (nonce, tg_enc_key, tg_enc_iv, tg_dec_key, tg_dec_iv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encrypt nonce for sending to Telegram
|
/// Encrypt nonce for sending to Telegram
|
||||||
///
|
|
||||||
/// Only the part from PROTO_TAG_POS onwards is encrypted.
|
|
||||||
/// The encryption key is derived from enc_key_iv in the nonce itself.
|
|
||||||
pub fn encrypt_tg_nonce(nonce: &[u8; HANDSHAKE_LEN]) -> Vec<u8> {
|
pub fn encrypt_tg_nonce(nonce: &[u8; HANDSHAKE_LEN]) -> Vec<u8> {
|
||||||
// enc_key_iv is at nonce[8:56]
|
|
||||||
let enc_key_iv = &nonce[SKIP_LEN..SKIP_LEN + KEY_LEN + IV_LEN];
|
let enc_key_iv = &nonce[SKIP_LEN..SKIP_LEN + KEY_LEN + IV_LEN];
|
||||||
|
|
||||||
// Key for encrypting is just the first 32 bytes of enc_key_iv
|
|
||||||
let key: [u8; 32] = enc_key_iv[..KEY_LEN].try_into().unwrap();
|
let key: [u8; 32] = enc_key_iv[..KEY_LEN].try_into().unwrap();
|
||||||
let iv = u128::from_be_bytes(enc_key_iv[KEY_LEN..].try_into().unwrap());
|
let iv = u128::from_be_bytes(enc_key_iv[KEY_LEN..].try_into().unwrap());
|
||||||
|
|
||||||
let mut encryptor = AesCtr::new(&key, iv);
|
let mut encryptor = AesCtr::new(&key, iv);
|
||||||
|
|
||||||
// Encrypt the entire nonce first, then take only the encrypted tail
|
|
||||||
let encrypted_full = encryptor.encrypt(nonce);
|
let encrypted_full = encryptor.encrypt(nonce);
|
||||||
|
|
||||||
// Result: unencrypted head + encrypted tail
|
|
||||||
let mut result = nonce[..PROTO_TAG_POS].to_vec();
|
let mut result = nonce[..PROTO_TAG_POS].to_vec();
|
||||||
result.extend_from_slice(&encrypted_full[PROTO_TAG_POS..]);
|
result.extend_from_slice(&encrypted_full[PROTO_TAG_POS..]);
|
||||||
|
|
||||||
trace!(
|
|
||||||
original = %hex::encode(&nonce[PROTO_TAG_POS..]),
|
|
||||||
encrypted = %hex::encode(&result[PROTO_TAG_POS..]),
|
|
||||||
"Encrypted nonce tail"
|
|
||||||
);
|
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ where
|
|||||||
// Client -> Server task
|
// Client -> Server task
|
||||||
let c2s = tokio::spawn(async move {
|
let c2s = tokio::spawn(async move {
|
||||||
// Get buffer from pool
|
// Get buffer from pool
|
||||||
let mut buf = pool_c2s.get();
|
let mut pooled_buf = pool_c2s.get();
|
||||||
|
// CRITICAL FIX: BytesMut from pool has len 0. We must resize it to be usable as &mut [u8].
|
||||||
|
// We use the full capacity.
|
||||||
|
let cap = pooled_buf.capacity();
|
||||||
|
pooled_buf.resize(cap, 0);
|
||||||
|
|
||||||
let mut total_bytes = 0u64;
|
let mut total_bytes = 0u64;
|
||||||
let mut prev_total_bytes = 0u64;
|
let mut prev_total_bytes = 0u64;
|
||||||
let mut msg_count = 0u64;
|
let mut msg_count = 0u64;
|
||||||
@@ -59,7 +64,7 @@ where
|
|||||||
// Read with timeout
|
// Read with timeout
|
||||||
let read_result = tokio::time::timeout(
|
let read_result = tokio::time::timeout(
|
||||||
activity_timeout,
|
activity_timeout,
|
||||||
client_reader.read(&mut buf)
|
client_reader.read(&mut pooled_buf)
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
match read_result {
|
match read_result {
|
||||||
@@ -108,7 +113,6 @@ where
|
|||||||
let delta = total_bytes - prev_total_bytes;
|
let delta = total_bytes - prev_total_bytes;
|
||||||
let rate = delta as f64 / elapsed.as_secs_f64();
|
let rate = delta as f64 / elapsed.as_secs_f64();
|
||||||
|
|
||||||
// Changed to DEBUG to reduce log spam
|
|
||||||
debug!(
|
debug!(
|
||||||
user = %user_c2s,
|
user = %user_c2s,
|
||||||
total_bytes = total_bytes,
|
total_bytes = total_bytes,
|
||||||
@@ -121,7 +125,7 @@ where
|
|||||||
prev_total_bytes = total_bytes;
|
prev_total_bytes = total_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = server_writer.write_all(&buf[..n]).await {
|
if let Err(e) = server_writer.write_all(&pooled_buf[..n]).await {
|
||||||
debug!(user = %user_c2s, error = %e, "Failed to write to server");
|
debug!(user = %user_c2s, error = %e, "Failed to write to server");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -142,7 +146,11 @@ where
|
|||||||
// Server -> Client task
|
// Server -> Client task
|
||||||
let s2c = tokio::spawn(async move {
|
let s2c = tokio::spawn(async move {
|
||||||
// Get buffer from pool
|
// Get buffer from pool
|
||||||
let mut buf = pool_s2c.get();
|
let mut pooled_buf = pool_s2c.get();
|
||||||
|
// CRITICAL FIX: Resize buffer
|
||||||
|
let cap = pooled_buf.capacity();
|
||||||
|
pooled_buf.resize(cap, 0);
|
||||||
|
|
||||||
let mut total_bytes = 0u64;
|
let mut total_bytes = 0u64;
|
||||||
let mut prev_total_bytes = 0u64;
|
let mut prev_total_bytes = 0u64;
|
||||||
let mut msg_count = 0u64;
|
let mut msg_count = 0u64;
|
||||||
@@ -152,7 +160,7 @@ where
|
|||||||
loop {
|
loop {
|
||||||
let read_result = tokio::time::timeout(
|
let read_result = tokio::time::timeout(
|
||||||
activity_timeout,
|
activity_timeout,
|
||||||
server_reader.read(&mut buf)
|
server_reader.read(&mut pooled_buf)
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
match read_result {
|
match read_result {
|
||||||
@@ -200,7 +208,6 @@ where
|
|||||||
let delta = total_bytes - prev_total_bytes;
|
let delta = total_bytes - prev_total_bytes;
|
||||||
let rate = delta as f64 / elapsed.as_secs_f64();
|
let rate = delta as f64 / elapsed.as_secs_f64();
|
||||||
|
|
||||||
// Changed to DEBUG to reduce log spam
|
|
||||||
debug!(
|
debug!(
|
||||||
user = %user_s2c,
|
user = %user_s2c,
|
||||||
total_bytes = total_bytes,
|
total_bytes = total_bytes,
|
||||||
@@ -213,7 +220,7 @@ where
|
|||||||
prev_total_bytes = total_bytes;
|
prev_total_bytes = total_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = client_writer.write_all(&buf[..n]).await {
|
if let Err(e) = client_writer.write_all(&pooled_buf[..n]).await {
|
||||||
debug!(user = %user_s2c, error = %e, "Failed to write to client");
|
debug!(user = %user_s2c, error = %e, "Failed to write to client");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user