1.0.0
Tschuss Status Quo - Hallo, Zukunft!
This commit is contained in:
261
src/protocol/constants.rs
Normal file
261
src/protocol/constants.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
//! Protocol constants and datacenter addresses
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
// ============= Telegram Datacenters =============
|
||||
|
||||
pub const TG_DATACENTER_PORT: u16 = 443;
|
||||
|
||||
pub static TG_DATACENTERS_V4: Lazy<Vec<IpAddr>> = Lazy::new(|| {
|
||||
vec![
|
||||
IpAddr::V4(Ipv4Addr::new(149, 154, 175, 50)),
|
||||
IpAddr::V4(Ipv4Addr::new(149, 154, 167, 51)),
|
||||
IpAddr::V4(Ipv4Addr::new(149, 154, 175, 100)),
|
||||
IpAddr::V4(Ipv4Addr::new(149, 154, 167, 91)),
|
||||
IpAddr::V4(Ipv4Addr::new(149, 154, 171, 5)),
|
||||
]
|
||||
});
|
||||
|
||||
pub static TG_DATACENTERS_V6: Lazy<Vec<IpAddr>> = Lazy::new(|| {
|
||||
vec![
|
||||
IpAddr::V6("2001:b28:f23d:f001::a".parse().unwrap()),
|
||||
IpAddr::V6("2001:67c:04e8:f002::a".parse().unwrap()),
|
||||
IpAddr::V6("2001:b28:f23d:f003::a".parse().unwrap()),
|
||||
IpAddr::V6("2001:67c:04e8:f004::a".parse().unwrap()),
|
||||
IpAddr::V6("2001:b28:f23f:f005::a".parse().unwrap()),
|
||||
]
|
||||
});
|
||||
|
||||
// ============= Middle Proxies (for advertising) =============
|
||||
|
||||
pub static TG_MIDDLE_PROXIES_V4: Lazy<std::collections::HashMap<i32, Vec<(IpAddr, u16)>>> =
|
||||
Lazy::new(|| {
|
||||
let mut m = std::collections::HashMap::new();
|
||||
m.insert(1, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 50)), 8888)]);
|
||||
m.insert(-1, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 50)), 8888)]);
|
||||
m.insert(2, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 161, 144)), 8888)]);
|
||||
m.insert(-2, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 161, 144)), 8888)]);
|
||||
m.insert(3, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 100)), 8888)]);
|
||||
m.insert(-3, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 100)), 8888)]);
|
||||
m.insert(4, vec![(IpAddr::V4(Ipv4Addr::new(91, 108, 4, 136)), 8888)]);
|
||||
m.insert(-4, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 165, 109)), 8888)]);
|
||||
m.insert(5, vec![(IpAddr::V4(Ipv4Addr::new(91, 108, 56, 183)), 8888)]);
|
||||
m.insert(-5, vec![(IpAddr::V4(Ipv4Addr::new(91, 108, 56, 183)), 8888)]);
|
||||
m
|
||||
});
|
||||
|
||||
pub static TG_MIDDLE_PROXIES_V6: Lazy<std::collections::HashMap<i32, Vec<(IpAddr, u16)>>> =
|
||||
Lazy::new(|| {
|
||||
let mut m = std::collections::HashMap::new();
|
||||
m.insert(1, vec![(IpAddr::V6("2001:b28:f23d:f001::d".parse().unwrap()), 8888)]);
|
||||
m.insert(-1, vec![(IpAddr::V6("2001:b28:f23d:f001::d".parse().unwrap()), 8888)]);
|
||||
m.insert(2, vec![(IpAddr::V6("2001:67c:04e8:f002::d".parse().unwrap()), 80)]);
|
||||
m.insert(-2, vec![(IpAddr::V6("2001:67c:04e8:f002::d".parse().unwrap()), 80)]);
|
||||
m.insert(3, vec![(IpAddr::V6("2001:b28:f23d:f003::d".parse().unwrap()), 8888)]);
|
||||
m.insert(-3, vec![(IpAddr::V6("2001:b28:f23d:f003::d".parse().unwrap()), 8888)]);
|
||||
m.insert(4, vec![(IpAddr::V6("2001:67c:04e8:f004::d".parse().unwrap()), 8888)]);
|
||||
m.insert(-4, vec![(IpAddr::V6("2001:67c:04e8:f004::d".parse().unwrap()), 8888)]);
|
||||
m.insert(5, vec![(IpAddr::V6("2001:b28:f23f:f005::d".parse().unwrap()), 8888)]);
|
||||
m.insert(-5, vec![(IpAddr::V6("2001:b28:f23f:f005::d".parse().unwrap()), 8888)]);
|
||||
m
|
||||
});
|
||||
|
||||
// ============= Protocol Tags =============
|
||||
|
||||
/// MTProto transport protocol variants
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[repr(u32)]
|
||||
pub enum ProtoTag {
|
||||
/// Abridged protocol - compact framing
|
||||
Abridged = 0xefefefef,
|
||||
/// Intermediate protocol - simple 4-byte length prefix
|
||||
Intermediate = 0xeeeeeeee,
|
||||
/// Secure intermediate - with random padding
|
||||
Secure = 0xdddddddd,
|
||||
}
|
||||
|
||||
impl ProtoTag {
|
||||
/// Parse protocol tag from 4 bytes
|
||||
pub fn from_bytes(bytes: [u8; 4]) -> Option<Self> {
|
||||
match u32::from_le_bytes(bytes) {
|
||||
0xefefefef => Some(ProtoTag::Abridged),
|
||||
0xeeeeeeee => Some(ProtoTag::Intermediate),
|
||||
0xdddddddd => Some(ProtoTag::Secure),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to 4 bytes (little-endian)
|
||||
pub fn to_bytes(self) -> [u8; 4] {
|
||||
(self as u32).to_le_bytes()
|
||||
}
|
||||
|
||||
/// Get protocol tag as bytes slice
|
||||
pub fn as_bytes(&self) -> &'static [u8; 4] {
|
||||
match self {
|
||||
ProtoTag::Abridged => &PROTO_TAG_ABRIDGED,
|
||||
ProtoTag::Intermediate => &PROTO_TAG_INTERMEDIATE,
|
||||
ProtoTag::Secure => &PROTO_TAG_SECURE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Protocol tag bytes
|
||||
pub const PROTO_TAG_ABRIDGED: [u8; 4] = [0xef, 0xef, 0xef, 0xef];
|
||||
pub const PROTO_TAG_INTERMEDIATE: [u8; 4] = [0xee, 0xee, 0xee, 0xee];
|
||||
pub const PROTO_TAG_SECURE: [u8; 4] = [0xdd, 0xdd, 0xdd, 0xdd];
|
||||
|
||||
// ============= Handshake Layout =============
|
||||
|
||||
/// Bytes to skip at the start of handshake
|
||||
pub const SKIP_LEN: usize = 8;
|
||||
/// Pre-key length (before hashing with secret)
|
||||
pub const PREKEY_LEN: usize = 32;
|
||||
/// AES key length
|
||||
pub const KEY_LEN: usize = 32;
|
||||
/// AES IV length
|
||||
pub const IV_LEN: usize = 16;
|
||||
/// Total handshake length
|
||||
pub const HANDSHAKE_LEN: usize = 64;
|
||||
/// Position of protocol tag in decrypted handshake
|
||||
pub const PROTO_TAG_POS: usize = 56;
|
||||
/// Position of datacenter index
|
||||
pub const DC_IDX_POS: usize = 60;
|
||||
|
||||
// ============= Message Limits =============
|
||||
|
||||
/// Minimum message length
|
||||
pub const MIN_MSG_LEN: usize = 12;
|
||||
/// Maximum message length (16 MB)
|
||||
pub const MAX_MSG_LEN: usize = 1 << 24;
|
||||
/// CBC block padding size
|
||||
pub const CBC_PADDING: usize = 16;
|
||||
/// Padding filler bytes
|
||||
pub const PADDING_FILLER: [u8; 4] = [0x04, 0x00, 0x00, 0x00];
|
||||
|
||||
// ============= TLS Constants =============
|
||||
|
||||
/// Minimum certificate length for detection
|
||||
pub const MIN_CERT_LEN: usize = 1024;
|
||||
/// TLS 1.3 version bytes
|
||||
pub const TLS_VERSION: [u8; 2] = [0x03, 0x03];
|
||||
/// TLS record type: Handshake
|
||||
pub const TLS_RECORD_HANDSHAKE: u8 = 0x16;
|
||||
/// TLS record type: Change Cipher Spec
|
||||
pub const TLS_RECORD_CHANGE_CIPHER: u8 = 0x14;
|
||||
/// TLS record type: Application Data
|
||||
pub const TLS_RECORD_APPLICATION: u8 = 0x17;
|
||||
/// TLS record type: Alert
|
||||
pub const TLS_RECORD_ALERT: u8 = 0x15;
|
||||
/// Maximum TLS record size
|
||||
pub const MAX_TLS_RECORD_SIZE: usize = 16384;
|
||||
/// Maximum TLS chunk size (with overhead)
|
||||
pub const MAX_TLS_CHUNK_SIZE: usize = 16384 + 24;
|
||||
|
||||
// ============= Timeouts =============
|
||||
|
||||
/// Default handshake timeout in seconds
|
||||
pub const DEFAULT_HANDSHAKE_TIMEOUT_SECS: u64 = 10;
|
||||
/// Default connect timeout in seconds
|
||||
pub const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 10;
|
||||
/// Default keepalive interval in seconds
|
||||
pub const DEFAULT_KEEPALIVE_SECS: u64 = 600;
|
||||
/// Default ACK timeout in seconds
|
||||
pub const DEFAULT_ACK_TIMEOUT_SECS: u64 = 300;
|
||||
|
||||
// ============= Buffer Sizes =============
|
||||
|
||||
/// Default buffer size
|
||||
pub const DEFAULT_BUFFER_SIZE: usize = 65536;
|
||||
/// Small buffer size for bad client handling
|
||||
pub const SMALL_BUFFER_SIZE: usize = 8192;
|
||||
|
||||
// ============= Statistics =============
|
||||
|
||||
/// Duration buckets for histogram metrics
|
||||
pub static DURATION_BUCKETS: &[f64] = &[
|
||||
0.1, 0.5, 1.0, 2.0, 5.0, 15.0, 60.0, 300.0, 600.0, 1800.0,
|
||||
];
|
||||
|
||||
// ============= Reserved Nonce Patterns =============
|
||||
|
||||
/// Reserved first bytes of nonce (must avoid)
|
||||
pub static RESERVED_NONCE_FIRST_BYTES: &[u8] = &[0xef];
|
||||
|
||||
/// Reserved 4-byte beginnings of nonce
|
||||
pub static RESERVED_NONCE_BEGINNINGS: &[[u8; 4]] = &[
|
||||
[0x48, 0x45, 0x41, 0x44], // HEAD
|
||||
[0x50, 0x4F, 0x53, 0x54], // POST
|
||||
[0x47, 0x45, 0x54, 0x20], // GET
|
||||
[0xee, 0xee, 0xee, 0xee], // Intermediate
|
||||
[0xdd, 0xdd, 0xdd, 0xdd], // Secure
|
||||
[0x16, 0x03, 0x01, 0x02], // TLS
|
||||
];
|
||||
|
||||
/// Reserved continuation bytes (bytes 4-7)
|
||||
pub static RESERVED_NONCE_CONTINUES: &[[u8; 4]] = &[
|
||||
[0x00, 0x00, 0x00, 0x00],
|
||||
];
|
||||
|
||||
// ============= RPC Constants (for Middle Proxy) =============
|
||||
|
||||
/// RPC Proxy Request
|
||||
pub const RPC_PROXY_REQ: [u8; 4] = [0xee, 0xf1, 0xce, 0x36];
|
||||
/// RPC Proxy Answer
|
||||
pub const RPC_PROXY_ANS: [u8; 4] = [0x0d, 0xda, 0x03, 0x44];
|
||||
/// RPC Close Extended
|
||||
pub const RPC_CLOSE_EXT: [u8; 4] = [0xa2, 0x34, 0xb6, 0x5e];
|
||||
/// RPC Simple ACK
|
||||
pub const RPC_SIMPLE_ACK: [u8; 4] = [0x9b, 0x40, 0xac, 0x3b];
|
||||
/// RPC Unknown
|
||||
pub const RPC_UNKNOWN: [u8; 4] = [0xdf, 0xa2, 0x30, 0x57];
|
||||
/// RPC Handshake
|
||||
pub const RPC_HANDSHAKE: [u8; 4] = [0xf5, 0xee, 0x82, 0x76];
|
||||
/// RPC Nonce
|
||||
pub const RPC_NONCE: [u8; 4] = [0xaa, 0x87, 0xcb, 0x7a];
|
||||
|
||||
/// RPC Flags
|
||||
pub mod rpc_flags {
|
||||
pub const FLAG_NOT_ENCRYPTED: u32 = 0x2;
|
||||
pub const FLAG_HAS_AD_TAG: u32 = 0x8;
|
||||
pub const FLAG_MAGIC: u32 = 0x1000;
|
||||
pub const FLAG_EXTMODE2: u32 = 0x20000;
|
||||
pub const FLAG_PAD: u32 = 0x8000000;
|
||||
pub const FLAG_INTERMEDIATE: u32 = 0x20000000;
|
||||
pub const FLAG_ABRIDGED: u32 = 0x40000000;
|
||||
pub const FLAG_QUICKACK: u32 = 0x80000000;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_proto_tag_roundtrip() {
|
||||
for tag in [ProtoTag::Abridged, ProtoTag::Intermediate, ProtoTag::Secure] {
|
||||
let bytes = tag.to_bytes();
|
||||
let parsed = ProtoTag::from_bytes(bytes).unwrap();
|
||||
assert_eq!(tag, parsed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proto_tag_values() {
|
||||
assert_eq!(ProtoTag::Abridged.to_bytes(), PROTO_TAG_ABRIDGED);
|
||||
assert_eq!(ProtoTag::Intermediate.to_bytes(), PROTO_TAG_INTERMEDIATE);
|
||||
assert_eq!(ProtoTag::Secure.to_bytes(), PROTO_TAG_SECURE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_proto_tag() {
|
||||
assert!(ProtoTag::from_bytes([0, 0, 0, 0]).is_none());
|
||||
assert!(ProtoTag::from_bytes([0xff, 0xff, 0xff, 0xff]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_datacenters_count() {
|
||||
assert_eq!(TG_DATACENTERS_V4.len(), 5);
|
||||
assert_eq!(TG_DATACENTERS_V6.len(), 5);
|
||||
}
|
||||
}
|
||||
120
src/protocol/frame.rs
Normal file
120
src/protocol/frame.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
//! MTProto frame types and metadata
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Extra metadata associated with a frame
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FrameExtra {
|
||||
/// Quick ACK flag - request immediate acknowledgment
|
||||
pub quickack: bool,
|
||||
/// Simple ACK - this is an acknowledgment message
|
||||
pub simple_ack: bool,
|
||||
/// Skip sending - internal flag to skip forwarding
|
||||
pub skip_send: bool,
|
||||
/// Custom key-value metadata
|
||||
pub custom: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl FrameExtra {
|
||||
/// Create new empty frame extra
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Create with quickack flag set
|
||||
pub fn with_quickack() -> Self {
|
||||
Self {
|
||||
quickack: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with simple_ack flag set
|
||||
pub fn with_simple_ack() -> Self {
|
||||
Self {
|
||||
simple_ack: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if any flags are set
|
||||
pub fn has_flags(&self) -> bool {
|
||||
self.quickack || self.simple_ack || self.skip_send
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of reading a frame
|
||||
#[derive(Debug)]
|
||||
pub enum FrameReadResult {
|
||||
/// Successfully read a frame with data and metadata
|
||||
Data(Vec<u8>, FrameExtra),
|
||||
/// Connection closed normally
|
||||
Closed,
|
||||
/// Need more data (for non-blocking reads)
|
||||
WouldBlock,
|
||||
}
|
||||
|
||||
/// Frame encoding/decoding mode
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FrameMode {
|
||||
/// Abridged - 1 or 4 byte length prefix
|
||||
Abridged,
|
||||
/// Intermediate - 4 byte length prefix
|
||||
Intermediate,
|
||||
/// Secure Intermediate - 4 byte length with padding
|
||||
SecureIntermediate,
|
||||
/// Full MTProto - with seq_no and CRC32
|
||||
Full,
|
||||
}
|
||||
|
||||
impl FrameMode {
|
||||
/// Get maximum overhead for this frame mode
|
||||
pub fn max_overhead(&self) -> usize {
|
||||
match self {
|
||||
FrameMode::Abridged => 4,
|
||||
FrameMode::Intermediate => 4,
|
||||
FrameMode::SecureIntermediate => 4 + 3, // length + padding
|
||||
FrameMode::Full => 12 + 16, // header + max CBC padding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate message length for MTProto
|
||||
pub fn validate_message_length(len: usize) -> bool {
|
||||
use super::constants::{MIN_MSG_LEN, MAX_MSG_LEN, PADDING_FILLER};
|
||||
|
||||
len >= MIN_MSG_LEN && len <= MAX_MSG_LEN && len % PADDING_FILLER.len() == 0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_frame_extra_default() {
|
||||
let extra = FrameExtra::default();
|
||||
assert!(!extra.quickack);
|
||||
assert!(!extra.simple_ack);
|
||||
assert!(!extra.skip_send);
|
||||
assert!(!extra.has_flags());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_frame_extra_flags() {
|
||||
let extra = FrameExtra::with_quickack();
|
||||
assert!(extra.quickack);
|
||||
assert!(extra.has_flags());
|
||||
|
||||
let extra = FrameExtra::with_simple_ack();
|
||||
assert!(extra.simple_ack);
|
||||
assert!(extra.has_flags());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_message_length() {
|
||||
assert!(validate_message_length(12)); // MIN_MSG_LEN
|
||||
assert!(validate_message_length(16));
|
||||
assert!(!validate_message_length(8)); // Too small
|
||||
assert!(!validate_message_length(13)); // Not aligned to 4
|
||||
}
|
||||
}
|
||||
11
src/protocol/mod.rs
Normal file
11
src/protocol/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! MTProto Defs + Cons
|
||||
|
||||
pub mod constants;
|
||||
pub mod frame;
|
||||
pub mod obfuscation;
|
||||
pub mod tls;
|
||||
|
||||
pub use constants::*;
|
||||
pub use frame::*;
|
||||
pub use obfuscation::*;
|
||||
pub use tls::*;
|
||||
217
src/protocol/obfuscation.rs
Normal file
217
src/protocol/obfuscation.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
//! MTProto Obfuscation
|
||||
|
||||
use crate::crypto::{sha256, AesCtr};
|
||||
use crate::error::Result;
|
||||
use super::constants::*;
|
||||
|
||||
/// Obfuscation parameters from handshake
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObfuscationParams {
|
||||
/// Key for decrypting client -> proxy traffic
|
||||
pub decrypt_key: [u8; 32],
|
||||
/// IV for decrypting client -> proxy traffic
|
||||
pub decrypt_iv: u128,
|
||||
/// Key for encrypting proxy -> client traffic
|
||||
pub encrypt_key: [u8; 32],
|
||||
/// IV for encrypting proxy -> client traffic
|
||||
pub encrypt_iv: u128,
|
||||
/// Protocol tag (abridged/intermediate/secure)
|
||||
pub proto_tag: ProtoTag,
|
||||
/// Datacenter index
|
||||
pub dc_idx: i16,
|
||||
}
|
||||
|
||||
impl ObfuscationParams {
|
||||
/// Parse obfuscation parameters from handshake bytes
|
||||
/// Returns None if handshake doesn't match any user secret
|
||||
pub fn from_handshake(
|
||||
handshake: &[u8; HANDSHAKE_LEN],
|
||||
secrets: &[(String, Vec<u8>)], // (username, secret_bytes)
|
||||
) -> Option<(Self, String)> {
|
||||
// Extract prekey and IV for decryption
|
||||
let dec_prekey_iv = &handshake[SKIP_LEN..SKIP_LEN + PREKEY_LEN + IV_LEN];
|
||||
let dec_prekey = &dec_prekey_iv[..PREKEY_LEN];
|
||||
let dec_iv_bytes = &dec_prekey_iv[PREKEY_LEN..];
|
||||
|
||||
// Reversed for encryption direction
|
||||
let enc_prekey_iv: Vec<u8> = dec_prekey_iv.iter().rev().copied().collect();
|
||||
let enc_prekey = &enc_prekey_iv[..PREKEY_LEN];
|
||||
let enc_iv_bytes = &enc_prekey_iv[PREKEY_LEN..];
|
||||
|
||||
for (username, secret) in secrets {
|
||||
// Derive decryption key
|
||||
let mut dec_key_input = Vec::with_capacity(PREKEY_LEN + secret.len());
|
||||
dec_key_input.extend_from_slice(dec_prekey);
|
||||
dec_key_input.extend_from_slice(secret);
|
||||
let decrypt_key = sha256(&dec_key_input);
|
||||
|
||||
let decrypt_iv = u128::from_be_bytes(dec_iv_bytes.try_into().unwrap());
|
||||
|
||||
// Create decryptor and decrypt handshake
|
||||
let mut decryptor = AesCtr::new(&decrypt_key, decrypt_iv);
|
||||
let decrypted = decryptor.decrypt(handshake);
|
||||
|
||||
// Check protocol tag
|
||||
let tag_bytes: [u8; 4] = decrypted[PROTO_TAG_POS..PROTO_TAG_POS + 4]
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let proto_tag = match ProtoTag::from_bytes(tag_bytes) {
|
||||
Some(tag) => tag,
|
||||
None => continue, // Try next secret
|
||||
};
|
||||
|
||||
// Extract DC index
|
||||
let dc_idx = i16::from_le_bytes(
|
||||
decrypted[DC_IDX_POS..DC_IDX_POS + 2].try_into().unwrap()
|
||||
);
|
||||
|
||||
// Derive encryption key
|
||||
let mut enc_key_input = Vec::with_capacity(PREKEY_LEN + secret.len());
|
||||
enc_key_input.extend_from_slice(enc_prekey);
|
||||
enc_key_input.extend_from_slice(secret);
|
||||
let encrypt_key = sha256(&enc_key_input);
|
||||
let encrypt_iv = u128::from_be_bytes(enc_iv_bytes.try_into().unwrap());
|
||||
|
||||
return Some((
|
||||
ObfuscationParams {
|
||||
decrypt_key,
|
||||
decrypt_iv,
|
||||
encrypt_key,
|
||||
encrypt_iv,
|
||||
proto_tag,
|
||||
dc_idx,
|
||||
},
|
||||
username.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Create AES-CTR decryptor for client -> proxy direction
|
||||
pub fn create_decryptor(&self) -> AesCtr {
|
||||
AesCtr::new(&self.decrypt_key, self.decrypt_iv)
|
||||
}
|
||||
|
||||
/// Create AES-CTR encryptor for proxy -> client direction
|
||||
pub fn create_encryptor(&self) -> AesCtr {
|
||||
AesCtr::new(&self.encrypt_key, self.encrypt_iv)
|
||||
}
|
||||
|
||||
/// Get the combined encrypt key and IV for fast mode
|
||||
pub fn enc_key_iv(&self) -> Vec<u8> {
|
||||
let mut result = Vec::with_capacity(KEY_LEN + IV_LEN);
|
||||
result.extend_from_slice(&self.encrypt_key);
|
||||
result.extend_from_slice(&self.encrypt_iv.to_be_bytes());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a valid random nonce for Telegram handshake
|
||||
pub fn generate_nonce<R: FnMut(usize) -> Vec<u8>>(mut random_bytes: R) -> [u8; HANDSHAKE_LEN] {
|
||||
loop {
|
||||
let nonce_vec = random_bytes(HANDSHAKE_LEN);
|
||||
let mut nonce = [0u8; HANDSHAKE_LEN];
|
||||
nonce.copy_from_slice(&nonce_vec);
|
||||
|
||||
if is_valid_nonce(&nonce) {
|
||||
return nonce;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if nonce is valid (not matching reserved patterns)
|
||||
pub fn is_valid_nonce(nonce: &[u8; HANDSHAKE_LEN]) -> bool {
|
||||
// Check first byte
|
||||
if RESERVED_NONCE_FIRST_BYTES.contains(&nonce[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check first 4 bytes
|
||||
let first_four: [u8; 4] = nonce[..4].try_into().unwrap();
|
||||
if RESERVED_NONCE_BEGINNINGS.contains(&first_four) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check bytes 4-7
|
||||
let continue_four: [u8; 4] = nonce[4..8].try_into().unwrap();
|
||||
if RESERVED_NONCE_CONTINUES.contains(&continue_four) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Prepare nonce for sending to Telegram
|
||||
pub fn prepare_tg_nonce(
|
||||
nonce: &mut [u8; HANDSHAKE_LEN],
|
||||
proto_tag: ProtoTag,
|
||||
enc_key_iv: Option<&[u8]>, // For fast mode
|
||||
) {
|
||||
// Set protocol tag
|
||||
nonce[PROTO_TAG_POS..PROTO_TAG_POS + 4].copy_from_slice(&proto_tag.to_bytes());
|
||||
|
||||
// For fast mode, copy the reversed enc_key_iv
|
||||
if let Some(key_iv) = enc_key_iv {
|
||||
let reversed: Vec<u8> = key_iv.iter().rev().copied().collect();
|
||||
nonce[SKIP_LEN..SKIP_LEN + KEY_LEN + IV_LEN].copy_from_slice(&reversed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt the outgoing nonce for Telegram
|
||||
pub fn encrypt_nonce(nonce: &[u8; HANDSHAKE_LEN]) -> Vec<u8> {
|
||||
// Derive encryption key from the nonce itself
|
||||
let key_iv = &nonce[SKIP_LEN..SKIP_LEN + KEY_LEN + IV_LEN];
|
||||
let enc_key = sha256(key_iv);
|
||||
let enc_iv = u128::from_be_bytes(key_iv[..IV_LEN].try_into().unwrap());
|
||||
|
||||
let mut encryptor = AesCtr::new(&enc_key, enc_iv);
|
||||
|
||||
// Only encrypt from PROTO_TAG_POS onwards
|
||||
let mut result = nonce.to_vec();
|
||||
let encrypted_part = encryptor.encrypt(&nonce[PROTO_TAG_POS..]);
|
||||
result[PROTO_TAG_POS..].copy_from_slice(&encrypted_part);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_valid_nonce() {
|
||||
// Valid nonce
|
||||
let mut valid = [0x42u8; HANDSHAKE_LEN];
|
||||
valid[4..8].copy_from_slice(&[1, 2, 3, 4]);
|
||||
assert!(is_valid_nonce(&valid));
|
||||
|
||||
// Invalid: starts with 0xef
|
||||
let mut invalid = [0x00u8; HANDSHAKE_LEN];
|
||||
invalid[0] = 0xef;
|
||||
assert!(!is_valid_nonce(&invalid));
|
||||
|
||||
// Invalid: starts with HEAD
|
||||
let mut invalid = [0x00u8; HANDSHAKE_LEN];
|
||||
invalid[..4].copy_from_slice(b"HEAD");
|
||||
assert!(!is_valid_nonce(&invalid));
|
||||
|
||||
// Invalid: bytes 4-7 are zeros
|
||||
let mut invalid = [0x42u8; HANDSHAKE_LEN];
|
||||
invalid[4..8].copy_from_slice(&[0, 0, 0, 0]);
|
||||
assert!(!is_valid_nonce(&invalid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_nonce() {
|
||||
let mut counter = 0u8;
|
||||
let nonce = generate_nonce(|n| {
|
||||
counter = counter.wrapping_add(1);
|
||||
vec![counter; n]
|
||||
});
|
||||
|
||||
assert!(is_valid_nonce(&nonce));
|
||||
assert_eq!(nonce.len(), HANDSHAKE_LEN);
|
||||
}
|
||||
}
|
||||
244
src/protocol/tls.rs
Normal file
244
src/protocol/tls.rs
Normal file
@@ -0,0 +1,244 @@
|
||||
//! Fake TLS 1.3 Handshake
|
||||
|
||||
use crate::crypto::{sha256_hmac, random::SECURE_RANDOM};
|
||||
use crate::error::{ProxyError, Result};
|
||||
use super::constants::*;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// TLS handshake digest length
|
||||
pub const TLS_DIGEST_LEN: usize = 32;
|
||||
/// Position of digest in TLS ClientHello
|
||||
pub const TLS_DIGEST_POS: usize = 11;
|
||||
/// Length to store for replay protection (first 16 bytes of digest)
|
||||
pub const TLS_DIGEST_HALF_LEN: usize = 16;
|
||||
|
||||
/// Time skew limits for anti-replay (in seconds)
|
||||
pub const TIME_SKEW_MIN: i64 = -20 * 60; // 20 minutes before
|
||||
pub const TIME_SKEW_MAX: i64 = 10 * 60; // 10 minutes after
|
||||
|
||||
/// Result of validating TLS handshake
|
||||
#[derive(Debug)]
|
||||
pub struct TlsValidation {
|
||||
/// Username that validated
|
||||
pub user: String,
|
||||
/// Session ID from ClientHello
|
||||
pub session_id: Vec<u8>,
|
||||
/// Client digest for response generation
|
||||
pub digest: [u8; TLS_DIGEST_LEN],
|
||||
/// Timestamp extracted from digest
|
||||
pub timestamp: u32,
|
||||
}
|
||||
|
||||
/// Validate TLS ClientHello against user secrets
|
||||
pub fn validate_tls_handshake(
|
||||
handshake: &[u8],
|
||||
secrets: &[(String, Vec<u8>)],
|
||||
ignore_time_skew: bool,
|
||||
) -> Option<TlsValidation> {
|
||||
if handshake.len() < TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Extract digest
|
||||
let digest: [u8; TLS_DIGEST_LEN] = handshake[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN]
|
||||
.try_into()
|
||||
.ok()?;
|
||||
|
||||
// Extract session ID
|
||||
let session_id_len_pos = TLS_DIGEST_POS + TLS_DIGEST_LEN;
|
||||
let session_id_len = handshake.get(session_id_len_pos).copied()? as usize;
|
||||
let session_id_start = session_id_len_pos + 1;
|
||||
|
||||
if handshake.len() < session_id_start + session_id_len {
|
||||
return None;
|
||||
}
|
||||
|
||||
let session_id = handshake[session_id_start..session_id_start + session_id_len].to_vec();
|
||||
|
||||
// Build message for HMAC (with zeroed digest)
|
||||
let mut msg = handshake.to_vec();
|
||||
msg[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);
|
||||
|
||||
// Get current time
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
|
||||
for (user, secret) in secrets {
|
||||
let computed = sha256_hmac(secret, &msg);
|
||||
|
||||
// XOR digests
|
||||
let xored: Vec<u8> = digest.iter()
|
||||
.zip(computed.iter())
|
||||
.map(|(a, b)| a ^ b)
|
||||
.collect();
|
||||
|
||||
// Check that first 28 bytes are zeros (timestamp in last 4)
|
||||
if !xored[..28].iter().all(|&b| b == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract timestamp
|
||||
let timestamp = u32::from_le_bytes(xored[28..32].try_into().unwrap());
|
||||
let time_diff = now - timestamp as i64;
|
||||
|
||||
// Check time skew
|
||||
if !ignore_time_skew {
|
||||
// Allow very small timestamps (boot time instead of unix time)
|
||||
let is_boot_time = timestamp < 60 * 60 * 24 * 1000;
|
||||
|
||||
if !is_boot_time && (time_diff < TIME_SKEW_MIN || time_diff > TIME_SKEW_MAX) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(TlsValidation {
|
||||
user: user.clone(),
|
||||
session_id,
|
||||
digest,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Generate a fake X25519 public key for TLS
|
||||
/// This generates a value that looks like a valid X25519 key
|
||||
pub fn gen_fake_x25519_key() -> [u8; 32] {
|
||||
// For simplicity, just generate random 32 bytes
|
||||
// In real X25519, this would be a point on the curve
|
||||
let bytes = SECURE_RANDOM.bytes(32);
|
||||
bytes.try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Build TLS ServerHello response
|
||||
pub fn build_server_hello(
|
||||
secret: &[u8],
|
||||
client_digest: &[u8; TLS_DIGEST_LEN],
|
||||
session_id: &[u8],
|
||||
fake_cert_len: usize,
|
||||
) -> Vec<u8> {
|
||||
let x25519_key = gen_fake_x25519_key();
|
||||
|
||||
// TLS extensions
|
||||
let mut extensions = Vec::new();
|
||||
extensions.extend_from_slice(&[0x00, 0x2e]); // Extension length placeholder
|
||||
extensions.extend_from_slice(&[0x00, 0x33, 0x00, 0x24]); // Key share extension
|
||||
extensions.extend_from_slice(&[0x00, 0x1d, 0x00, 0x20]); // X25519 curve
|
||||
extensions.extend_from_slice(&x25519_key);
|
||||
extensions.extend_from_slice(&[0x00, 0x2b, 0x00, 0x02, 0x03, 0x04]); // Supported versions
|
||||
|
||||
// ServerHello body
|
||||
let mut srv_hello = Vec::new();
|
||||
srv_hello.extend_from_slice(&TLS_VERSION);
|
||||
srv_hello.extend_from_slice(&[0u8; TLS_DIGEST_LEN]); // Placeholder for digest
|
||||
srv_hello.push(session_id.len() as u8);
|
||||
srv_hello.extend_from_slice(session_id);
|
||||
srv_hello.extend_from_slice(&[0x13, 0x01]); // TLS_AES_128_GCM_SHA256
|
||||
srv_hello.push(0x00); // No compression
|
||||
srv_hello.extend_from_slice(&extensions);
|
||||
|
||||
// Build complete packet
|
||||
let mut hello_pkt = Vec::new();
|
||||
|
||||
// ServerHello record
|
||||
hello_pkt.push(TLS_RECORD_HANDSHAKE);
|
||||
hello_pkt.extend_from_slice(&TLS_VERSION);
|
||||
hello_pkt.extend_from_slice(&((srv_hello.len() + 4) as u16).to_be_bytes());
|
||||
hello_pkt.push(0x02); // ServerHello message type
|
||||
let len_bytes = (srv_hello.len() as u32).to_be_bytes();
|
||||
hello_pkt.extend_from_slice(&len_bytes[1..4]); // 3-byte length
|
||||
hello_pkt.extend_from_slice(&srv_hello);
|
||||
|
||||
// Change Cipher Spec record
|
||||
hello_pkt.extend_from_slice(&[
|
||||
TLS_RECORD_CHANGE_CIPHER,
|
||||
TLS_VERSION[0], TLS_VERSION[1],
|
||||
0x00, 0x01, 0x01
|
||||
]);
|
||||
|
||||
// Application Data record (fake certificate)
|
||||
let fake_cert = SECURE_RANDOM.bytes(fake_cert_len);
|
||||
hello_pkt.push(TLS_RECORD_APPLICATION);
|
||||
hello_pkt.extend_from_slice(&TLS_VERSION);
|
||||
hello_pkt.extend_from_slice(&(fake_cert.len() as u16).to_be_bytes());
|
||||
hello_pkt.extend_from_slice(&fake_cert);
|
||||
|
||||
// Compute HMAC for the response
|
||||
let mut hmac_input = Vec::with_capacity(TLS_DIGEST_LEN + hello_pkt.len());
|
||||
hmac_input.extend_from_slice(client_digest);
|
||||
hmac_input.extend_from_slice(&hello_pkt);
|
||||
let response_digest = sha256_hmac(secret, &hmac_input);
|
||||
|
||||
// Insert computed digest
|
||||
// Position: after record header (5) + message type/length (4) + version (2) = 11
|
||||
hello_pkt[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN]
|
||||
.copy_from_slice(&response_digest);
|
||||
|
||||
hello_pkt
|
||||
}
|
||||
|
||||
/// Check if bytes look like a TLS ClientHello
|
||||
pub fn is_tls_handshake(first_bytes: &[u8]) -> bool {
|
||||
if first_bytes.len() < 3 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TLS record header: 0x16 0x03 0x01
|
||||
first_bytes[0] == TLS_RECORD_HANDSHAKE
|
||||
&& first_bytes[1] == 0x03
|
||||
&& first_bytes[2] == 0x01
|
||||
}
|
||||
|
||||
/// Parse TLS record header, returns (record_type, length)
|
||||
pub fn parse_tls_record_header(header: &[u8; 5]) -> Option<(u8, u16)> {
|
||||
let record_type = header[0];
|
||||
let version = [header[1], header[2]];
|
||||
|
||||
// We accept both TLS 1.0 header (for ClientHello) and TLS 1.2/1.3
|
||||
if version != [0x03, 0x01] && version != TLS_VERSION {
|
||||
return None;
|
||||
}
|
||||
|
||||
let length = u16::from_be_bytes([header[3], header[4]]);
|
||||
Some((record_type, length))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_tls_handshake() {
|
||||
assert!(is_tls_handshake(&[0x16, 0x03, 0x01]));
|
||||
assert!(is_tls_handshake(&[0x16, 0x03, 0x01, 0x02, 0x00]));
|
||||
assert!(!is_tls_handshake(&[0x17, 0x03, 0x01])); // Application data
|
||||
assert!(!is_tls_handshake(&[0x16, 0x03, 0x02])); // Wrong version
|
||||
assert!(!is_tls_handshake(&[0x16, 0x03])); // Too short
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tls_record_header() {
|
||||
let header = [0x16, 0x03, 0x01, 0x02, 0x00];
|
||||
let result = parse_tls_record_header(&header).unwrap();
|
||||
assert_eq!(result.0, TLS_RECORD_HANDSHAKE);
|
||||
assert_eq!(result.1, 512);
|
||||
|
||||
let header = [0x17, 0x03, 0x03, 0x40, 0x00];
|
||||
let result = parse_tls_record_header(&header).unwrap();
|
||||
assert_eq!(result.0, TLS_RECORD_APPLICATION);
|
||||
assert_eq!(result.1, 16384);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gen_fake_x25519_key() {
|
||||
let key1 = gen_fake_x25519_key();
|
||||
let key2 = gen_fake_x25519_key();
|
||||
|
||||
assert_eq!(key1.len(), 32);
|
||||
assert_eq!(key2.len(), 32);
|
||||
assert_ne!(key1, key2); // Should be random
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user