base proxy ready
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use chacha20poly1305::aead::Buffer;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
pub trait AeadPacker {
|
||||
fn encrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error>;
|
||||
fn decrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error>;
|
||||
fn encrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error>;
|
||||
fn decrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error>;
|
||||
}
|
||||
|
||||
+103
-44
@@ -1,80 +1,139 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||
use chacha20poly1305::aead::{Buffer, OsRng};
|
||||
use chacha20poly1305::{
|
||||
AeadCore, AeadInPlace, ChaCha20Poly1305, ChaChaPoly1305, Key, KeyInit, Nonce,
|
||||
};
|
||||
use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, Key, KeyInit, Nonce};
|
||||
|
||||
use crate::crypto::aead::AeadPacker;
|
||||
|
||||
pub struct NonceState {
|
||||
counter: u64,
|
||||
nonce: Nonce,
|
||||
handshake: bool,
|
||||
base_iv: [u8; 12],
|
||||
}
|
||||
|
||||
impl NonceState {
|
||||
pub fn new() -> Self {
|
||||
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||
pub fn new(base_iv: [u8; 12]) -> Self {
|
||||
Self {
|
||||
counter: 0,
|
||||
nonce,
|
||||
handshake: false,
|
||||
base_iv,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_handshake(&self) -> bool {
|
||||
return self.handshake;
|
||||
}
|
||||
|
||||
pub fn set_nonce(&mut self, nonce: Nonce) {
|
||||
self.nonce = nonce;
|
||||
self.handshake = true
|
||||
}
|
||||
|
||||
pub fn increase_counter(&mut self) {
|
||||
self.counter += 1;
|
||||
// Возвращает nonce для текущего пакета и ПЕРЕХОДИТ к следующему
|
||||
pub fn next_nonce(&mut self) -> Nonce {
|
||||
let mut iv = self.base_iv;
|
||||
// В TLS 1.3 используется Little Endian для счетчика при XOR
|
||||
// Но если ты сам пишешь протокол, BE тоже пойдет.
|
||||
// Главное — единообразие.
|
||||
let counter_bytes = self.counter.to_be_bytes();
|
||||
self.nonce[4..12].copy_from_slice(&counter_bytes);
|
||||
|
||||
for i in 0..8 {
|
||||
iv[i + 4] ^= counter_bytes[i];
|
||||
}
|
||||
|
||||
self.counter += 1;
|
||||
*GenericArray::from_slice(&iv)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChaChaCipher {
|
||||
key: Key,
|
||||
pub encrypt_cipher: ChaCha20Poly1305,
|
||||
pub decrypt_cipher: ChaCha20Poly1305,
|
||||
pub encrypt_state: NonceState,
|
||||
pub decrypt_state: NonceState,
|
||||
pub cipher: ChaCha20Poly1305,
|
||||
}
|
||||
|
||||
impl ChaChaCipher {
|
||||
pub fn new() -> Self {
|
||||
let key = GenericArray::clone_from_slice(&[0; 32]);
|
||||
let cipher = ChaCha20Poly1305::new(&key);
|
||||
let start_key = Key::from([0u8; 32]);
|
||||
let encrypt_cipher = ChaCha20Poly1305::new(&start_key);
|
||||
let decrypt_cipher = ChaCha20Poly1305::new(&start_key);
|
||||
Self {
|
||||
key,
|
||||
encrypt_state: NonceState::new(),
|
||||
decrypt_state: NonceState::new(),
|
||||
cipher,
|
||||
encrypt_state: NonceState::new([0u8; 12]),
|
||||
decrypt_state: NonceState::new([0u8; 12]),
|
||||
encrypt_cipher,
|
||||
decrypt_cipher,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_key(&mut self, key: Key) -> () {
|
||||
self.key = key;
|
||||
self.cipher = ChaChaPoly1305::new(&self.key);
|
||||
pub fn set_keys(
|
||||
&mut self,
|
||||
w_key: [u8; 32],
|
||||
w_iv: [u8; 12], // Write (исходящие)
|
||||
r_key: [u8; 32],
|
||||
r_iv: [u8; 12], // Read (входящие)
|
||||
) {
|
||||
self.encrypt_cipher = ChaCha20Poly1305::new(Key::from_slice(&w_key));
|
||||
self.decrypt_cipher = ChaCha20Poly1305::new(Key::from_slice(&r_key));
|
||||
|
||||
self.encrypt_state = NonceState::new(w_iv);
|
||||
self.decrypt_state = NonceState::new(r_iv);
|
||||
|
||||
tracing::debug!("Cipher keys and IVs updated for both directions");
|
||||
}
|
||||
}
|
||||
|
||||
impl AeadPacker for ChaChaCipher {
|
||||
fn encrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error> {
|
||||
self.cipher
|
||||
.encrypt_in_place(&self.encrypt_state.nonce, &[], data)?;
|
||||
self.encrypt_state.increase_counter();
|
||||
Ok(())
|
||||
fn encrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error> {
|
||||
// Сначала получаем текущий counter для лога (до того, как next_nonce его инкрементирует)
|
||||
let current_counter = self.encrypt_state.counter;
|
||||
let nonce = self.encrypt_state.next_nonce();
|
||||
let nonce_hex = hex::encode(nonce);
|
||||
let data_len = data.len();
|
||||
|
||||
match self.encrypt_cipher.encrypt_in_place(&nonce, &[], data) {
|
||||
Ok(_) => {
|
||||
tracing::trace!(
|
||||
counter = current_counter,
|
||||
nonce = %nonce_hex,
|
||||
len = data_len,
|
||||
"Encryption successful"
|
||||
);
|
||||
Ok(data.split().freeze())
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
counter = current_counter,
|
||||
nonce = %nonce_hex,
|
||||
len = data_len,
|
||||
error = ?e,
|
||||
"AEAD encryption failure"
|
||||
);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error> {
|
||||
self.cipher
|
||||
.decrypt_in_place(&self.decrypt_state.nonce, &[], data)?;
|
||||
self.decrypt_state.increase_counter();
|
||||
Ok(())
|
||||
fn decrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error> {
|
||||
let current_counter = self.decrypt_state.counter;
|
||||
let nonce = self.decrypt_state.next_nonce();
|
||||
let nonce_hex = hex::encode(nonce);
|
||||
let data_len = data.len();
|
||||
|
||||
let data_prefix = if data.len() >= 8 {
|
||||
hex::encode(&data[..8])
|
||||
} else {
|
||||
hex::encode(data.as_ref())
|
||||
};
|
||||
|
||||
match self.decrypt_cipher.decrypt_in_place(&nonce, &[], data) {
|
||||
Ok(_) => {
|
||||
tracing::trace!(
|
||||
counter = current_counter,
|
||||
nonce = %nonce_hex,
|
||||
len = data_len,
|
||||
"Decryption successful"
|
||||
);
|
||||
Ok(data.split().freeze())
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
counter = current_counter,
|
||||
nonce = %nonce_hex,
|
||||
len = data_len,
|
||||
prefix = %data_prefix,
|
||||
error = ?e,
|
||||
"AEAD decryption failure! Verification failed or data malformed"
|
||||
);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod aead;
|
||||
pub mod chacha;
|
||||
pub mod ecdh;
|
||||
pub mod hkdf;
|
||||
pub mod hmac;
|
||||
pub mod salt_pair;
|
||||
mod ecdh;
|
||||
mod hkdf;
|
||||
mod hmac;
|
||||
mod salt_pair;
|
||||
pub mod session;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use crate::{
|
||||
crypto::{ecdh::ECDH, hkdf::HKDF, salt_pair::SaltPair},
|
||||
tlseng::extension::ExtensionStack,
|
||||
};
|
||||
|
||||
pub struct SessionKeys {
|
||||
pub salt: SaltPair,
|
||||
pub ecdh: ECDH,
|
||||
}
|
||||
|
||||
impl SessionKeys {
|
||||
pub fn new(is_initiator: bool) -> Self {
|
||||
Self {
|
||||
salt: SaltPair::new(is_initiator),
|
||||
ecdh: ECDH::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_keys(
|
||||
&mut self,
|
||||
salt: [u8; 32],
|
||||
extensions: &ExtensionStack,
|
||||
is_server: bool, // true если мы Сервер (парсим ClientHello), false если Клиент
|
||||
) -> Result<([u8; 32], [u8; 12], [u8; 32], [u8; 12]), String> {
|
||||
// Сохраняем соль от удаленной стороны
|
||||
self.salt.set_remote_salt(salt);
|
||||
|
||||
tracing::debug!(
|
||||
remote_salt = %hex::encode(&salt[..8]),
|
||||
local_salt = %hex::encode(&self.salt.get_local()[..8]),
|
||||
total_salt = %hex::encode(&self.salt.get_total()[28..36]),
|
||||
"Updating keys with new salt"
|
||||
);
|
||||
|
||||
const EXT_KEY_SHARE: u16 = 0x0033;
|
||||
|
||||
if let Some(dh_data) = extensions.find_by_type(EXT_KEY_SHARE) {
|
||||
let mut key_bytes = [0u8; 32];
|
||||
|
||||
if is_server {
|
||||
// МЫ СЕРВЕР: Парсим ClientHello KeyShare (там список)
|
||||
// Минимум: 2 (длина списка) + 2 (группа) + 2 (длина ключа) + 32 (ключ) = 38
|
||||
if dh_data.len() < 38 {
|
||||
return Err(format!(
|
||||
"Client KeyShare too short: {} bytes",
|
||||
dh_data.len()
|
||||
));
|
||||
}
|
||||
|
||||
let mut found = false;
|
||||
// Ищем маркер x25519 (00 1d) и длину 32 (00 20)
|
||||
for i in 2..=(dh_data.len() - 34) {
|
||||
if dh_data[i..i + 4] == [0x00, 0x1d, 0x00, 0x20] {
|
||||
key_bytes.copy_from_slice(&dh_data[i + 4..i + 36]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return Err("Could not find x25519 key in ClientHello".into());
|
||||
}
|
||||
} else {
|
||||
// МЫ КЛИЕНТ: Парсим ServerHello KeyShare (там сразу группа и ключ)
|
||||
// [Group:2] [KeyLen:2] [Key:32] = 36 байт
|
||||
if dh_data.len() < 36 {
|
||||
return Err("Server KeyShare too short".into());
|
||||
}
|
||||
key_bytes.copy_from_slice(&dh_data[4..36]);
|
||||
}
|
||||
|
||||
// Проверка на "кривой" ключ
|
||||
if key_bytes.iter().all(|&x| x == 0) {
|
||||
return Err("Extracted remote public key is all ZEROS!".into());
|
||||
}
|
||||
|
||||
let public_key = PublicKey::from(key_bytes);
|
||||
|
||||
tracing::debug!(
|
||||
remote_pub = %hex::encode(&key_bytes[..4]),
|
||||
role = if is_server { "Server" } else { "Client" },
|
||||
"Key exchange successful, deriving material..."
|
||||
);
|
||||
|
||||
// Вызываем генерацию, которая вернет (w_key, w_iv, r_key, r_iv)
|
||||
self.generate_keys(&public_key, is_server)
|
||||
} else {
|
||||
Err("No KeyShare extension found in handshake".into())
|
||||
}
|
||||
}
|
||||
fn generate_keys(
|
||||
&mut self,
|
||||
public_key: &PublicKey,
|
||||
is_server: bool,
|
||||
) -> Result<([u8; 32], [u8; 12], [u8; 32], [u8; 12]), String> {
|
||||
let shared_key = self
|
||||
.ecdh
|
||||
.get_shared(public_key)
|
||||
.ok_or_else(|| "No shared secret".to_string())?;
|
||||
|
||||
tracing::debug!(
|
||||
shared_prefix = %hex::encode(&shared_key[..8]),
|
||||
"DH Shared secret derived"
|
||||
);
|
||||
|
||||
let hkdf = HKDF::extract_key(&self.salt.get_total(), &shared_key);
|
||||
|
||||
let c_key = HKDF::expand_key::<32>(&hkdf, b"client_aead").map_err(|e| e.to_string())?;
|
||||
let c_iv = HKDF::expand_key::<12>(&hkdf, b"client_iv").map_err(|e| e.to_string())?;
|
||||
let s_key = HKDF::expand_key::<32>(&hkdf, b"server_aead").map_err(|e| e.to_string())?;
|
||||
let s_iv = HKDF::expand_key::<12>(&hkdf, b"server_iv").map_err(|e| e.to_string())?;
|
||||
|
||||
tracing::info!(
|
||||
client_key_short = %hex::encode(&c_key[..4]),
|
||||
server_key_short = %hex::encode(&s_key[..4]),
|
||||
"HKDF expansion complete"
|
||||
);
|
||||
|
||||
// Распределяем: (write_key, write_iv, read_key, read_iv)
|
||||
if is_server {
|
||||
Ok((s_key, s_iv, c_key, c_iv)) // Сервер пишет своим, читает клиентским
|
||||
} else {
|
||||
Ok((c_key, c_iv, s_key, s_iv)) // Клиент пишет своим, читает серверным
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
pub mod crypto;
|
||||
mod logger;
|
||||
pub mod protocol;
|
||||
pub mod proxy;
|
||||
pub mod tlseng;
|
||||
pub mod utils;
|
||||
|
||||
pub use logger::logger_init;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
pub fn logger_init() {
|
||||
let fmt_layer = fmt::layer()
|
||||
.with_target(true) // Показывать, из какого модуля пришел лог
|
||||
.with_thread_ids(false)
|
||||
.with_line_number(true); // Показывать строку кода (очень полезно для дебага)
|
||||
|
||||
let filter_layer = EnvFilter::try_from_default_env()
|
||||
.or_else(|_| EnvFilter::try_new("trace")) // По умолчанию уровень info
|
||||
.unwrap();
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter_layer)
|
||||
.with(fmt_layer)
|
||||
.init();
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod tls;
|
||||
pub mod netr_bridge;
|
||||
pub mod tls_bridge;
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
codec::bridges::tls::tls_interceptor::TlsInterceptor,
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
},
|
||||
tlseng::{application_data::ApplicationData, tls_record::TlsRecord, types::ContentType},
|
||||
};
|
||||
|
||||
impl TlsInterceptor for ApplicationData {
|
||||
type Output = ApplicationData;
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, InterceptorError> {
|
||||
let mut payload = BytesMut::from(record.payload.as_ref());
|
||||
match record.content_type {
|
||||
ContentType::ApplicationData => Self::handle_application_data(&mut payload),
|
||||
_ => {
|
||||
println!("content type byte is: {:?}", record.content_type);
|
||||
Err(InterceptorError::new(
|
||||
ErrorType::ApplicationData("Not Application Data"),
|
||||
ErrorAction::Drop,
|
||||
record.serialize(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationData {
|
||||
fn handle_application_data(payload: &mut BytesMut) -> Result<Option<Self>, InterceptorError> {
|
||||
println!("Bytes here?: {:?}", &payload);
|
||||
let data_option = ApplicationData::start_process(payload)?;
|
||||
let data = data_option.ok_or_else(|| {
|
||||
InterceptorError::new(
|
||||
ErrorType::ApplicationData("AppData Err TODO"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&[]),
|
||||
)
|
||||
})?;
|
||||
println!("Получены Application Data: {} байт", payload.len());
|
||||
Ok(Some(data))
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use crate::{
|
||||
protocol::codec::{
|
||||
bridges::tls::{handshake::HandshakeMessage, tls_interceptor::TlsInterceptor},
|
||||
codec::Codec,
|
||||
},
|
||||
tlseng::{application_data::ApplicationData, consts::EXT_KEY_SHARE, extension::ExtensionStack},
|
||||
};
|
||||
|
||||
pub struct TlsBridge;
|
||||
|
||||
impl TlsBridge {
|
||||
fn process_key_share(extensions: &ExtensionStack, codec: &mut Codec) -> Result<(), String> {
|
||||
if let Some(dh_key) = extensions.find_by_type(EXT_KEY_SHARE) {
|
||||
let mut key = [0u8; 32];
|
||||
if dh_key.len() < 32 {
|
||||
return Err("Key too short".into());
|
||||
}
|
||||
key.copy_from_slice(&dh_key[..32]);
|
||||
|
||||
let public_key = PublicKey::from(key);
|
||||
codec
|
||||
.session_keys
|
||||
.generate_keys(&public_key)
|
||||
.map_err(|e| format!("Key gen error: {:?}", e))?; // Обработка Result
|
||||
Ok(())
|
||||
} else {
|
||||
Err("No key share extension found".into())
|
||||
}
|
||||
}
|
||||
//unpack handshake
|
||||
pub fn make_handshake(buffer: &mut BytesMut, codec: &mut Codec) -> Result<(), String> {
|
||||
let incoming_process = HandshakeMessage::start_process(buffer);
|
||||
println!("buffer {:02x?}", &buffer);
|
||||
let message_option = match incoming_process {
|
||||
Ok(mes) => mes,
|
||||
Err(e) => {
|
||||
println!("Error: {:?}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(message) = message_option {
|
||||
match message {
|
||||
HandshakeMessage::Client { base, extensions } => {
|
||||
codec.session_keys.salt.set_remote_salt(base.random);
|
||||
Self::process_key_share(&extensions, codec)?;
|
||||
}
|
||||
HandshakeMessage::Server { base, extensions } => {
|
||||
codec.session_keys.salt.set_remote_salt(base.random);
|
||||
Self::process_key_share(&extensions, codec)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unpack_app_data(buffer: &mut BytesMut) -> Result<BytesMut, &str> {
|
||||
println!("What is here?");
|
||||
println!("Data {:?}", &buffer);
|
||||
let incoming_process = ApplicationData::start_process(buffer);
|
||||
let option_bytes = match incoming_process {
|
||||
Ok(mes) => mes,
|
||||
Err(e) => {
|
||||
println!("Error: {:?}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(b) = option_bytes {
|
||||
return Ok(BytesMut::from(b.payload.as_ref()));
|
||||
} else {
|
||||
Err("no data")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pack_in_app_data(buffer: &mut BytesMut) -> Bytes {
|
||||
ApplicationData::make_application_data(buffer)
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
codec::bridges::tls::tls_interceptor::TlsInterceptor,
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
parser::parser::FrameParser,
|
||||
},
|
||||
tlseng::{
|
||||
extension::ExtensionStack,
|
||||
handshake::{
|
||||
client_hello::ClientHello, hello_header::HelloHeader, server_hello::ServerHello,
|
||||
},
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, HelloType},
|
||||
},
|
||||
};
|
||||
|
||||
pub enum HandshakeMessage {
|
||||
Client {
|
||||
base: ClientHello,
|
||||
extensions: ExtensionStack,
|
||||
},
|
||||
Server {
|
||||
base: ServerHello,
|
||||
extensions: ExtensionStack,
|
||||
},
|
||||
}
|
||||
|
||||
impl TlsInterceptor for HandshakeMessage {
|
||||
type Output = HandshakeMessage;
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, InterceptorError> {
|
||||
let mut payload = BytesMut::from(record.payload.as_ref());
|
||||
match record.content_type {
|
||||
ContentType::Handshake => Self::handle_handshake(&mut payload),
|
||||
_ => {
|
||||
// It is strange if in this prcoess not a Handshake
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HandshakeMessage {
|
||||
fn handle_handshake(payload: &mut BytesMut) -> Result<Option<Self>, InterceptorError> {
|
||||
if let Some(header) = HelloHeader::parse(payload)? {
|
||||
match header.header_type {
|
||||
HelloType::Server => {
|
||||
let mut server_hello_body = payload;
|
||||
if let Some(server_hello) = ServerHello::parse(&mut server_hello_body)? {
|
||||
return Self::process_server_hello(server_hello);
|
||||
}
|
||||
}
|
||||
HelloType::Client => {
|
||||
let mut client_hello_body = payload;
|
||||
if let Some(client_hello) = ClientHello::parse(&mut client_hello_body)? {
|
||||
return Self::process_client_hello(client_hello);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn process_server_hello(hello: ServerHello) -> Result<Option<Self>, InterceptorError> {
|
||||
println!("Server Hello получен! Random: {:02x?}", hello.random);
|
||||
|
||||
// Парсим расширения сервера, если нужно
|
||||
let mut ext_bytes = BytesMut::from(hello.extensions.as_ref());
|
||||
let ext_stack_option = ExtensionStack::parse(&mut ext_bytes)?;
|
||||
let ext_stack = ext_stack_option.ok_or_else(|| {
|
||||
InterceptorError::new(
|
||||
ErrorType::Handshake("Extension Err TODO"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&[]),
|
||||
)
|
||||
})?;
|
||||
Ok(Some(HandshakeMessage::Server {
|
||||
base: hello,
|
||||
extensions: ext_stack,
|
||||
}))
|
||||
}
|
||||
|
||||
fn process_client_hello(hello: ClientHello) -> Result<Option<Self>, InterceptorError> {
|
||||
println!("Client Hello получен! Random: {:02x?}", hello.random);
|
||||
|
||||
// Парсим расширения клиента, если нужно
|
||||
let mut ext_bytes = BytesMut::from(hello.extensions.as_ref());
|
||||
let ext_stack_option = ExtensionStack::parse(&mut ext_bytes)?;
|
||||
let ext_stack = ext_stack_option.ok_or_else(|| {
|
||||
InterceptorError::new(
|
||||
ErrorType::Handshake("Extension Err TODO"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&[]),
|
||||
)
|
||||
})?;
|
||||
Ok(Some(HandshakeMessage::Client {
|
||||
base: hello,
|
||||
extensions: ext_stack,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
mod appdata;
|
||||
pub mod bridge;
|
||||
mod handshake;
|
||||
mod tls_interceptor;
|
||||
@@ -1,25 +0,0 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
parser::parser::FrameParser,
|
||||
},
|
||||
tlseng::tls_record::TlsRecord,
|
||||
};
|
||||
|
||||
pub trait TlsInterceptor {
|
||||
type Output;
|
||||
fn start_process(buffer: &mut BytesMut) -> Result<Option<Self::Output>, InterceptorError> {
|
||||
match TlsRecord::parse(buffer) {
|
||||
Ok(Some(record)) => Self::handle_record(record),
|
||||
Ok(None) => Err(InterceptorError::new(
|
||||
ErrorType::Tls("Not full Data"),
|
||||
ErrorAction::Wait,
|
||||
Bytes::new(),
|
||||
)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, InterceptorError>;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use crate::protocol::errors::{ErrorAction, ErrorStage, TlsError};
|
||||
use crate::protocol::parser::parser::Parser;
|
||||
use crate::tlseng::extension::ExtensionStack;
|
||||
use crate::tlseng::handshake::{ClientHello, HelloHeader, ServerHello};
|
||||
use crate::tlseng::profile::BrowserProfile;
|
||||
use crate::tlseng::tls_record::TlsRecord;
|
||||
use crate::tlseng::types::{ContentType, HelloType, ProtocolVersion};
|
||||
use crate::tlseng::ApplicationData;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
// --- 1. Общий интерфейс перехвата ---
|
||||
pub trait TlsInterceptor {
|
||||
type Output;
|
||||
|
||||
fn start_process(buffer: &mut BytesMut) -> Result<Option<Self::Output>, TlsError> {
|
||||
match TlsRecord::parse(buffer) {
|
||||
Ok(Some(record)) => Self::handle_record(record),
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError>;
|
||||
}
|
||||
|
||||
// --- 2. Обработка Handshake ---
|
||||
pub enum HandshakeMessage {
|
||||
Client {
|
||||
base: ClientHello,
|
||||
extensions: ExtensionStack,
|
||||
},
|
||||
Server {
|
||||
base: ServerHello,
|
||||
extensions: ExtensionStack,
|
||||
},
|
||||
}
|
||||
|
||||
impl HandshakeMessage {
|
||||
pub fn random(&self) -> [u8; 32] {
|
||||
match self {
|
||||
Self::Client { base, .. } => base.random,
|
||||
Self::Server { base, .. } => base.random,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extensions(&self) -> &ExtensionStack {
|
||||
match self {
|
||||
Self::Client { extensions, .. } => extensions,
|
||||
Self::Server { extensions, .. } => extensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsInterceptor for HandshakeMessage {
|
||||
type Output = HandshakeMessage;
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError> {
|
||||
if record.content_type != ContentType::Handshake {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Handshake("Expected Handshake record"),
|
||||
ErrorAction::Drop,
|
||||
record.serialize(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut payload = BytesMut::from(record.payload.as_ref());
|
||||
if let Some(header) = HelloHeader::parse(&mut payload)? {
|
||||
match header.header_type {
|
||||
HelloType::Client => {
|
||||
if let Some(hello) = ClientHello::parse(&mut payload)? {
|
||||
let ext =
|
||||
ExtensionStack::parse(&mut BytesMut::from(hello.extensions.as_ref()))?
|
||||
.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Ext Err"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
return Ok(Some(HandshakeMessage::Client {
|
||||
base: hello,
|
||||
extensions: ext,
|
||||
}));
|
||||
}
|
||||
}
|
||||
HelloType::Server => {
|
||||
if let Some(hello) = ServerHello::parse(&mut payload)? {
|
||||
let ext =
|
||||
ExtensionStack::parse(&mut BytesMut::from(hello.extensions.as_ref()))?
|
||||
.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Ext Err"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
return Ok(Some(HandshakeMessage::Server {
|
||||
base: hello,
|
||||
extensions: ext,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// --- 3. Обработка Application Data ---
|
||||
impl TlsInterceptor for ApplicationData {
|
||||
type Output = ApplicationData;
|
||||
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError> {
|
||||
if record.content_type != ContentType::ApplicationData {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::ApplicationData("Expected AppData record"),
|
||||
ErrorAction::Drop,
|
||||
record.serialize(),
|
||||
));
|
||||
}
|
||||
Ok(Some(ApplicationData {
|
||||
len: record.payload.len(),
|
||||
payload: record.payload,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. Высокоуровневый Bridge API ---
|
||||
pub struct TlsBridge;
|
||||
|
||||
impl TlsBridge {
|
||||
// --- Распаковка (уже была) ---
|
||||
|
||||
pub fn unpack_handshake(buffer: &mut BytesMut) -> Result<Option<HandshakeMessage>, TlsError> {
|
||||
HandshakeMessage::start_process(buffer)
|
||||
}
|
||||
|
||||
pub fn unpack_app_data(buffer: &mut BytesMut) -> Result<Option<ApplicationData>, TlsError> {
|
||||
ApplicationData::start_process(buffer)
|
||||
}
|
||||
|
||||
// --- Запаковка (новое) ---
|
||||
|
||||
/// Создает полный TLS Record с ClientHello внутри
|
||||
pub fn wrap_client_hello(
|
||||
profile: &BrowserProfile,
|
||||
host: &str,
|
||||
public_key: &[u8; 32],
|
||||
salt: [u8; 32],
|
||||
) -> Bytes {
|
||||
ClientHello::make_client_hello(profile, host, public_key, salt) // Передаем ключ дальше
|
||||
}
|
||||
|
||||
/// Создает полный TLS Record с ServerHello, базируясь на данных из HandshakeMessage::Client
|
||||
pub fn wrap_server_hello(
|
||||
client_msg: &HandshakeMessage,
|
||||
server_pub_key: &[u8],
|
||||
salt: [u8; 32],
|
||||
) -> Result<Bytes, TlsError> {
|
||||
if let HandshakeMessage::Client { base, .. } = client_msg {
|
||||
Ok(ServerHello::make_server_hello(base, server_pub_key, salt))
|
||||
} else {
|
||||
Err(TlsError::new(
|
||||
ErrorStage::Handshake("Wrong message type for ServerHello generation"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Вспомогательный метод для упаковки уже готового Handshake-сообщения в TlsRecord
|
||||
pub fn pack_handshake(payload: Bytes) -> Bytes {
|
||||
let record = TlsRecord::new(ContentType::Handshake, ProtocolVersion::Tls12, payload);
|
||||
record.serialize()
|
||||
}
|
||||
|
||||
pub fn pack_app_data(buffer: Bytes) -> Bytes {
|
||||
TlsRecord::build_application_data(buffer)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::crypto::chacha::ChaChaCipher;
|
||||
use crate::protocol::codec::bridges::tls::bridge::TlsBridge;
|
||||
use crate::protocol::codec::session_keys::SessionKeys;
|
||||
|
||||
use crate::crypto::aead::AeadPacker;
|
||||
use crate::crypto::chacha::ChaChaCipher;
|
||||
use crate::crypto::session::SessionKeys;
|
||||
use crate::protocol::codec::bridges::tls_bridge::TlsBridge;
|
||||
use crate::protocol::codec::frame::{Frame, FrameHeader, FrameType};
|
||||
use crate::protocol::codec::padding::Padding;
|
||||
use crate::protocol::errors::{ErrorAction, ErrorStage, TlsError};
|
||||
use crate::protocol::parser::parser::Parser;
|
||||
use crate::tlseng::profile::BrowserProfile;
|
||||
|
||||
pub struct Codec {
|
||||
crypto: ChaChaCipher, //rename chacha
|
||||
crypto: ChaChaCipher,
|
||||
pub session_keys: SessionKeys,
|
||||
staging: BytesMut,
|
||||
}
|
||||
|
||||
impl Codec {
|
||||
@@ -16,34 +21,217 @@ impl Codec {
|
||||
Self {
|
||||
crypto: ChaChaCipher::new(),
|
||||
session_keys: SessionKeys::new(is_initiator),
|
||||
staging: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
//maybe generator?
|
||||
//should anwer socks5 and open connection to proxy server
|
||||
/// Логика для Клиента: Генерирует байты ClientHello для инициализации соединения.
|
||||
/// Клиент: генерирует TLS Record [ ClientHello ]
|
||||
pub fn make_client_handshake(
|
||||
&mut self,
|
||||
profile: &BrowserProfile,
|
||||
host: &str,
|
||||
) -> Result<Bytes, TlsError> {
|
||||
// 1. Извлекаем публичный ключ нашей текущей сессии
|
||||
let my_pub_key = self.session_keys.ecdh.public_key.to_bytes();
|
||||
// (Убедись, что метод возвращает [u8; 32])
|
||||
|
||||
// 2. Передаем его в мост
|
||||
Ok(TlsBridge::wrap_client_hello(
|
||||
profile,
|
||||
host,
|
||||
&my_pub_key,
|
||||
self.session_keys.salt.get_local(),
|
||||
))
|
||||
}
|
||||
/// Сервер: берет буфер, достает ClientHello и генерирует в ответ TLS Record [ ServerHello ]
|
||||
pub fn make_server_handshake(&mut self, buffer: &mut BytesMut) -> Result<Bytes, TlsError> {
|
||||
// 1. Распаковываем сообщение клиента
|
||||
let client_msg = TlsBridge::unpack_handshake(buffer)?.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("No CH"),
|
||||
ErrorAction::Wait,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// 2. Генерируем ответный ServerHello рекорд
|
||||
let server_pub_key = self.session_keys.ecdh.public_key.to_bytes();
|
||||
let server_hello_record = TlsBridge::wrap_server_hello(
|
||||
&client_msg,
|
||||
&server_pub_key,
|
||||
self.session_keys.salt.get_local(),
|
||||
)?;
|
||||
|
||||
// 3. ОБНОВЛЕНИЕ КЛЮЧЕЙ НА СЕРВЕРЕ
|
||||
// Передаем true, так как сервер ПАРСИТ ClientHello (смещение 6 байт)
|
||||
let (w_key, w_iv, r_key, r_iv) = self
|
||||
.session_keys
|
||||
.update_keys(client_msg.random(), client_msg.extensions(), true)
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "Server failed to update keys from ClientHello");
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Key Err"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Инициализируем шифратор сервера
|
||||
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
|
||||
|
||||
Ok(server_hello_record)
|
||||
}
|
||||
|
||||
pub fn process_handshake(&mut self, buffer: &mut BytesMut) -> Result<(), TlsError> {
|
||||
let mes_opt = TlsBridge::unpack_handshake(buffer)?;
|
||||
let mes = mes_opt.ok_or_else(|| {
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Incomplete record"),
|
||||
ErrorAction::Wait,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// ОБНОВЛЕНИЕ КЛЮЧЕЙ НА КЛИЕНТЕ
|
||||
// Передаем false, так как клиент ПАРСИТ ServerHello (смещение 4 байта)
|
||||
let (w_key, w_iv, r_key, r_iv) = self
|
||||
.session_keys
|
||||
.update_keys(mes.random(), mes.extensions(), false)
|
||||
.map_err(|e| {
|
||||
tracing::error!(error = %e, "Client failed to update keys from ServerHello");
|
||||
TlsError::new(
|
||||
ErrorStage::Handshake("Keys update error"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn try_handshake(&mut self, buffer: &mut BytesMut) -> Result<bool, TlsError> {
|
||||
match self.process_handshake(buffer) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) if e.action == ErrorAction::Wait => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_handshake(&mut self, buffer: &mut BytesMut) {
|
||||
println!("Handshake len in codec: {:?}", &buffer.len());
|
||||
TlsBridge::make_handshake(buffer, self);
|
||||
fn outbound(
|
||||
&mut self,
|
||||
stream_id: u32,
|
||||
frame_type: FrameType,
|
||||
payload: Bytes,
|
||||
) -> Result<Bytes, TlsError> {
|
||||
let padding = Padding::generate_padding();
|
||||
|
||||
let header = FrameHeader {
|
||||
auth_tag: [0u8; 16],
|
||||
stream_id,
|
||||
frame_type,
|
||||
payload_len: payload.len() as u16,
|
||||
padding_len: padding.len as u16,
|
||||
};
|
||||
|
||||
let frame = Frame {
|
||||
header,
|
||||
payload,
|
||||
padding: padding.data,
|
||||
};
|
||||
|
||||
let mut frame_bytes = frame.into_bytes();
|
||||
|
||||
// ВАЖНО: вызываем шифрование ОДИН РАЗ.
|
||||
// Метод encrypt возвращает Result<Bytes, chacha20poly1305::Error>
|
||||
// Мы вручную превращаем его ошибку в твой TlsError.
|
||||
let encrypted_payload = self.crypto.encrypt(&mut frame_bytes).map_err(|e| {
|
||||
tracing::error!("Encryption failed: {:?}", e);
|
||||
TlsError::new(
|
||||
ErrorStage::Tls("Encryption failed"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Теперь передаем зашифрованные байты в новый метод упаковки
|
||||
Ok(TlsBridge::pack_app_data(encrypted_payload))
|
||||
}
|
||||
|
||||
pub fn unpack(&mut self, buffer: &mut BytesMut) -> Result<Bytes, String> {
|
||||
println!("App data unpack len in codec?: {:?}", &buffer.len());
|
||||
let mut data = TlsBridge::unpack_app_data(buffer);
|
||||
//self.decrypt(&mut data);
|
||||
match data {
|
||||
Ok(bytes) => Ok(bytes.freeze()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
pub fn encrypt_data(
|
||||
&mut self,
|
||||
stream_id: u32,
|
||||
frame_type: FrameType,
|
||||
data: Bytes,
|
||||
) -> Result<Bytes, TlsError> {
|
||||
self.outbound(stream_id, frame_type, data)
|
||||
}
|
||||
|
||||
pub fn inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
|
||||
// Логгируем входящее состояние сетевого буфера (TLS слой)
|
||||
if !buffer.is_empty() {
|
||||
let header = &buffer[..std::cmp::min(buffer.len(), 5)];
|
||||
tracing::debug!(
|
||||
buf_len = buffer.len(),
|
||||
header_hex = %hex::encode(header),
|
||||
"RAW TLS buffer state"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pack(&mut self, buffer: &mut BytesMut) -> Bytes {
|
||||
println!("App data len in codec?: {:?}", &buffer.len());
|
||||
TlsBridge::pack_in_app_data(buffer)
|
||||
}
|
||||
// --- ШАГ 1: Извлекаем ВСЕ доступные TLS-рекорды и расшифровываем в staging ---
|
||||
// Мы крутим цикл, пока TlsBridge может "откусить" целый TLS-рекорд из buffer
|
||||
while let Some(app_data) = TlsBridge::unpack_app_data(buffer)? {
|
||||
let mut encrypted_chunk = BytesMut::from(app_data.payload.as_ref());
|
||||
let raw_len = encrypted_chunk.len();
|
||||
|
||||
pub fn encrypt(&mut self, data: &mut BytesMut) {
|
||||
self.crypto.encrypt(data);
|
||||
}
|
||||
// Расшифровываем кусок
|
||||
let decrypted_chunk = self.crypto.decrypt(&mut encrypted_chunk).map_err(|e| {
|
||||
tracing::error!(len = raw_len, "Decryption failed: {:?}", e);
|
||||
TlsError::new(
|
||||
ErrorStage::Tls("Decryption error"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
pub fn decrypt(&mut self, data: &mut BytesMut) {
|
||||
self.crypto.decrypt(data);
|
||||
// КЛАДЕМ В ЧИСТУЮ ЗОНУ: расшифрованный поток байтов нашего протокола
|
||||
self.staging.extend_from_slice(&decrypted_chunk);
|
||||
|
||||
tracing::debug!(
|
||||
added = decrypted_chunk.len(),
|
||||
total_staging = self.staging.len(),
|
||||
"Decrypted data moved to staging"
|
||||
);
|
||||
}
|
||||
|
||||
// --- ШАГ 2: Парсим Frame из "чистых" данных в staging ---
|
||||
if !self.staging.is_empty() {
|
||||
// Важно: Frame::parse должен вызывать advance() или split_to() у staging
|
||||
match Frame::parse(&mut self.staging) {
|
||||
Ok(Some(frame)) => {
|
||||
tracing::info!(
|
||||
stream_id = frame.header.stream_id,
|
||||
"Frame successfully parsed from staging"
|
||||
);
|
||||
return Ok(Some(frame));
|
||||
}
|
||||
Ok(None) => {
|
||||
tracing::debug!("Frame is incomplete in staging, waiting for more TLS records");
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Frame parse error: {:?}", e);
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Tls("Frame parse error"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::{crypto::hmac::generate_auth_tag, protocol::codec::padding::Padding};
|
||||
use crate::protocol::codec::padding::Padding;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum FrameType {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FrameType {
|
||||
Connect = 0x00,
|
||||
Data = 0x01,
|
||||
Close = 0x02,
|
||||
@@ -11,7 +11,7 @@ enum FrameType {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct FrameHeader {
|
||||
pub struct FrameHeader {
|
||||
pub auth_tag: [u8; 16],
|
||||
pub stream_id: u32,
|
||||
pub frame_type: FrameType,
|
||||
@@ -20,7 +20,7 @@ struct FrameHeader {
|
||||
}
|
||||
|
||||
pub struct Frame {
|
||||
header: FrameHeader,
|
||||
pub header: FrameHeader,
|
||||
pub payload: Bytes,
|
||||
pub padding: Bytes,
|
||||
}
|
||||
@@ -34,69 +34,12 @@ const PADDING_LEN_SIZE: u16 = 2;
|
||||
pub const FRAME_HEADER_SIZE: u16 =
|
||||
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE;
|
||||
|
||||
impl FrameHeader {
|
||||
fn get_header(bytes: &mut BytesMut) -> Option<Self> {
|
||||
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut header_chunk = bytes.split_to(FRAME_HEADER_SIZE as usize);
|
||||
|
||||
let mut auth_tag = [0u8; 16];
|
||||
header_chunk.copy_to_slice(&mut auth_tag);
|
||||
|
||||
let stream_id = header_chunk.get_u32();
|
||||
|
||||
let frame_type_byte = header_chunk.get_u8();
|
||||
let frame_type = match frame_type_byte {
|
||||
0x00 => FrameType::Connect,
|
||||
0x01 => FrameType::Data,
|
||||
0x02 => FrameType::Close,
|
||||
0x03 => FrameType::Heartbeat,
|
||||
_ => FrameType::Close,
|
||||
};
|
||||
|
||||
let payload_len = header_chunk.get_u16();
|
||||
let padding_len = header_chunk.get_u16();
|
||||
|
||||
Some(Self {
|
||||
auth_tag,
|
||||
stream_id,
|
||||
frame_type,
|
||||
payload_len,
|
||||
padding_len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn unpack(bytes: &mut BytesMut) -> Result<Option<Self>, String> {
|
||||
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let p_len = u16::from_be_bytes([bytes[21], bytes[22]]) as usize;
|
||||
let pad_len = u16::from_be_bytes([bytes[23], bytes[24]]) as usize;
|
||||
|
||||
if bytes.len() < (FRAME_HEADER_SIZE as usize + p_len + pad_len) {
|
||||
return Ok(None);
|
||||
}
|
||||
let header = FrameHeader::get_header(bytes).unwrap();
|
||||
let payload = bytes.split_to(p_len).freeze();
|
||||
let padding = bytes.split_to(pad_len).freeze();
|
||||
|
||||
Ok(Some(Self {
|
||||
header,
|
||||
payload,
|
||||
padding,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> BytesMut {
|
||||
let updated_padding = Padding::generate_padding();
|
||||
let total_size = FRAME_HEADER_SIZE as usize + self.payload.len() + self.padding.len();
|
||||
let mut buf = BytesMut::with_capacity(total_size);
|
||||
let hmac = generate_auth_tag(&[0; 16]);
|
||||
let hmac = [0; 16]; //generate_auth_tag(&[0; 16]);
|
||||
|
||||
buf.put_slice(&hmac);
|
||||
buf.put_u32(self.header.stream_id);
|
||||
|
||||
@@ -2,4 +2,4 @@ mod bridges;
|
||||
pub mod codec;
|
||||
pub mod frame;
|
||||
mod padding;
|
||||
mod session_keys;
|
||||
pub mod socks;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use crate::crypto::{ecdh::ECDH, hkdf::HKDF, salt_pair::SaltPair};
|
||||
|
||||
pub struct KeyPair {
|
||||
pub aead_key: [u8; 32],
|
||||
pub hmac_key: [u8; 32],
|
||||
}
|
||||
|
||||
pub struct SessionKeys {
|
||||
pub salt: SaltPair,
|
||||
pub ecdh: ECDH,
|
||||
key_pair: KeyPair,
|
||||
}
|
||||
|
||||
impl SessionKeys {
|
||||
pub fn new(is_initiator: bool) -> Self {
|
||||
Self {
|
||||
salt: SaltPair::new(is_initiator),
|
||||
ecdh: ECDH::new(),
|
||||
key_pair: KeyPair {
|
||||
aead_key: [0; 32],
|
||||
hmac_key: [0; 32],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_keys(&mut self, public_key: &PublicKey) -> Result<(), ()> {
|
||||
let shared_key = self.ecdh.get_shared(public_key);
|
||||
match shared_key {
|
||||
Some(key) => {
|
||||
let hkdf = HKDF::extract_key(&self.salt.get_total(), &key);
|
||||
let aead = HKDF::expand_key::<32>(&hkdf, b"aead").map_err(|e| {
|
||||
println!("Aead Key expand Error: {}", e);
|
||||
})?;
|
||||
self.key_pair.aead_key = aead;
|
||||
let hmac: [u8; 32] = HKDF::expand_key::<32>(&hkdf, b"hmac").map_err(|e| {
|
||||
println!("HMAC Key expand Error: {}", e);
|
||||
})?;
|
||||
self.key_pair.hmac_key = hmac;
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
println!("Error while generate keys. No Shared key");
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
pub const SOCKS5_VERSION: u8 = 0x05;
|
||||
pub const REPLY_SUCCESS: u8 = 0x00;
|
||||
pub const REPLY_AUTH_FAILURE: u8 = 0xFF;
|
||||
pub const SOCKS5_MIN_HEADER: usize = 4;
|
||||
pub const ATYP_IPV4: u8 = 0x01;
|
||||
pub const ATYP_DOMAIN: u8 = 0x03;
|
||||
pub const ATYP_IPV6: u8 = 0x04;
|
||||
pub const IPV4_SIZE: usize = 4;
|
||||
pub const IPV6_SIZE: usize = 16;
|
||||
pub const PORT_SIZE: usize = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SocksRequest {
|
||||
Handshake { methods: Vec<u8> },
|
||||
Connect { command: u8, target: SocksTarget },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SocksReply {
|
||||
HandshakeSelect {
|
||||
method: u8,
|
||||
},
|
||||
ConnectResult {
|
||||
reply_code: u8,
|
||||
atyp: u8,
|
||||
addr: [u8; 4],
|
||||
port: u16,
|
||||
},
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct SocksTarget {
|
||||
pub host: Bytes,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl SocksReply {
|
||||
pub fn write_to(self, buf: &mut BytesMut) {
|
||||
match self {
|
||||
SocksReply::HandshakeSelect { method } => {
|
||||
buf.put_u8(SOCKS5_VERSION);
|
||||
buf.put_u8(method);
|
||||
}
|
||||
SocksReply::ConnectResult {
|
||||
reply_code,
|
||||
atyp,
|
||||
addr,
|
||||
port,
|
||||
} => {
|
||||
buf.put_u8(SOCKS5_VERSION);
|
||||
buf.put_u8(reply_code);
|
||||
buf.put_u8(0x00); // Reserved
|
||||
buf.put_u8(atyp);
|
||||
buf.put_slice(&addr);
|
||||
buf.put_u16(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SocksTarget {
|
||||
pub fn to_string(&self) -> String {
|
||||
let host_str = String::from_utf8_lossy(&self.host);
|
||||
format!("{}:{}", host_str, self.port)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
use bytes::Bytes;
|
||||
use tracing::{error, trace, warn};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ErrorAction {
|
||||
Wait,
|
||||
Redirect,
|
||||
Drop,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorStage {
|
||||
Tls(&'static str),
|
||||
Handshake(&'static str),
|
||||
ApplicationData(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TlsError {
|
||||
pub stage: ErrorStage,
|
||||
pub action: ErrorAction,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
impl TlsError {
|
||||
pub fn new(stage: ErrorStage, action: ErrorAction, data: Bytes) -> Self {
|
||||
Self {
|
||||
stage,
|
||||
action,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
fn log_error(&self) {
|
||||
// Определяем уровень логирования в зависимости от действия
|
||||
// Если мы просто ждем данные (Wait) — это не ошибка, а рабочий процесс (debug/trace)
|
||||
// Если дропаем соединение (Drop) — это серьезно (error)
|
||||
|
||||
let stage_name = match &self.stage {
|
||||
ErrorStage::Tls(_) => "TLS",
|
||||
ErrorStage::Handshake(_) => "Handshake",
|
||||
ErrorStage::ApplicationData(_) => "AppData",
|
||||
};
|
||||
|
||||
let message = match &self.stage {
|
||||
ErrorStage::Tls(m) | ErrorStage::Handshake(m) | ErrorStage::ApplicationData(m) => m,
|
||||
};
|
||||
|
||||
// Подготавливаем превью данных (первые 8 байт в хексе)
|
||||
let data_preview = if !self.data.is_empty() {
|
||||
let limit = self.data.len().min(8);
|
||||
format!(
|
||||
"Hex: {:02x?}{}",
|
||||
&self.data[..limit],
|
||||
if self.data.len() > 8 { "..." } else { "" }
|
||||
)
|
||||
} else {
|
||||
"No data".to_string()
|
||||
};
|
||||
|
||||
match self.action {
|
||||
ErrorAction::Wait => {
|
||||
// Wait — это нормальное состояние асинхронного чтения
|
||||
trace!(
|
||||
stage = stage_name,
|
||||
action = ?self.action,
|
||||
data = %data_preview,
|
||||
"{}", message
|
||||
);
|
||||
}
|
||||
ErrorAction::Redirect => {
|
||||
warn!(
|
||||
stage = stage_name,
|
||||
action = ?self.action,
|
||||
data = %data_preview,
|
||||
"⚠️ {}", message
|
||||
);
|
||||
}
|
||||
ErrorAction::Drop => {
|
||||
error!(
|
||||
stage = stage_name,
|
||||
action = ?self.action,
|
||||
data = %data_preview,
|
||||
"🚨 {}", message
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_strategy(&self) -> ErrorAction {
|
||||
self.log_error();
|
||||
self.action
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use bytes::Bytes;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ErrorAction {
|
||||
Wait,
|
||||
Redirect,
|
||||
Drop,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorType {
|
||||
Tls(&'static str),
|
||||
Handshake(&'static str),
|
||||
ApplicationData(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InterceptorError {
|
||||
pub error_type: ErrorType,
|
||||
pub action: ErrorAction,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
impl InterceptorError {
|
||||
pub fn new(error_type: ErrorType, action: ErrorAction, data: Bytes) -> Self {
|
||||
Self {
|
||||
error_type,
|
||||
action,
|
||||
data,
|
||||
}
|
||||
}
|
||||
fn log_error(&self) {
|
||||
let (category, message) = match &self.error_type {
|
||||
ErrorType::Tls(m) => ("TLS", m),
|
||||
ErrorType::Handshake(m) => ("Handshake", m),
|
||||
ErrorType::ApplicationData(m) => ("AppData", m),
|
||||
};
|
||||
println!(
|
||||
"[{}] Error: {} (Byte: {:#02x})",
|
||||
category, message, self.data
|
||||
);
|
||||
}
|
||||
|
||||
pub fn execute_strategy(&self) -> ErrorAction {
|
||||
self.log_error();
|
||||
self.action
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod error_interceptor;
|
||||
@@ -1,25 +0,0 @@
|
||||
use bytes::Buf;
|
||||
|
||||
pub trait PeekExt: Buf {
|
||||
fn peek_u16(&self, offset: usize) -> Option<u16> {
|
||||
let chunk = self.chunk();
|
||||
if chunk.len() >= offset + 2 {
|
||||
let b = &chunk[offset..offset + 2];
|
||||
Some(u16::from_be_bytes([b[0], b[1]]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_u24(&self, offset: usize) -> Option<u32> {
|
||||
let chunk = self.chunk();
|
||||
if chunk.len() >= offset + 3 {
|
||||
let b = &chunk[offset..offset + 3];
|
||||
Some(((b[0] as u32) << 16) | ((b[1] as u32) << 8) | (b[2] as u32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Buf> PeekExt for T {}
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod codec;
|
||||
pub mod interceptors;
|
||||
mod parser;
|
||||
pub mod errors;
|
||||
pub mod parser;
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
mod netr;
|
||||
pub mod parser;
|
||||
mod socks;
|
||||
mod tls;
|
||||
|
||||
use parser::Parser;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use bytes::{Buf, BytesMut};
|
||||
|
||||
use crate::protocol::{
|
||||
codec::frame::{Frame, FrameHeader, FrameType, FRAME_HEADER_SIZE},
|
||||
parser::parser::Parser,
|
||||
};
|
||||
|
||||
impl Parser for FrameHeader {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
bytes.len() >= FRAME_HEADER_SIZE as usize
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut header_chunk = bytes.split_to(FRAME_HEADER_SIZE as usize);
|
||||
|
||||
let mut auth_tag = [0u8; 16];
|
||||
header_chunk.copy_to_slice(&mut auth_tag);
|
||||
|
||||
let stream_id = header_chunk.get_u32();
|
||||
|
||||
let frame_type_byte = header_chunk.get_u8();
|
||||
let frame_type = match frame_type_byte {
|
||||
0x00 => FrameType::Connect,
|
||||
0x01 => FrameType::Data,
|
||||
0x02 => FrameType::Close,
|
||||
0x03 => FrameType::Heartbeat,
|
||||
_ => FrameType::Close,
|
||||
};
|
||||
|
||||
let payload_len = header_chunk.get_u16();
|
||||
let padding_len = header_chunk.get_u16();
|
||||
|
||||
Ok(Some(Self {
|
||||
auth_tag,
|
||||
stream_id,
|
||||
frame_type,
|
||||
payload_len,
|
||||
padding_len,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for Frame {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
// 1. Сначала проверяем, есть ли хотя бы заголовок
|
||||
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Извлекаем длины из заголовка (БЕЗ удаления байтов из буфера)
|
||||
// По твоей структуре: Auth(16) + Stream(4) + Type(1) = 21 байт смещения
|
||||
let p_len = u16::from_be_bytes([bytes[21], bytes[22]]) as usize;
|
||||
let pad_len = u16::from_be_bytes([bytes[23], bytes[24]]) as usize;
|
||||
|
||||
tracing::debug!(
|
||||
"CAN_PARSE: p_len={}, pad_len={}, total_needed={}, have={}",
|
||||
p_len,
|
||||
pad_len,
|
||||
25 + p_len + pad_len,
|
||||
bytes.len()
|
||||
);
|
||||
|
||||
// 3. Проверяем, есть ли в буфере весь фрейм целиком
|
||||
bytes.len() >= (FRAME_HEADER_SIZE as usize + p_len + pad_len)
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Извлекаем заголовок (теперь split_to удалит эти байты из начала bytes)
|
||||
let header = FrameHeader::parse(bytes)?.ok_or("Failed to parse header")?;
|
||||
|
||||
let p_len = header.payload_len as usize;
|
||||
let pad_len = header.padding_len as usize;
|
||||
|
||||
// Теперь байты заголовка уже удалены, и в начале 'bytes' лежит Payload
|
||||
if bytes.len() < p_len + pad_len {
|
||||
return Err("Buffer corrupted: length mismatch after header parse".into());
|
||||
}
|
||||
|
||||
let payload = bytes.split_to(p_len).freeze();
|
||||
let padding = bytes.split_to(pad_len).freeze();
|
||||
|
||||
Ok(Some(Self {
|
||||
header,
|
||||
payload,
|
||||
padding,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use bytes::BytesMut;
|
||||
|
||||
pub trait FrameParser {
|
||||
pub trait Parser {
|
||||
type Error;
|
||||
fn can_parse(bytes: &BytesMut) -> bool;
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
use bytes::{Buf, BytesMut};
|
||||
|
||||
use crate::protocol::{codec::socks::*, parser::parser::Parser};
|
||||
|
||||
impl Parser for SocksTarget {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < SOCKS5_MIN_HEADER {
|
||||
return false;
|
||||
}
|
||||
|
||||
let atyp = bytes[3];
|
||||
match atyp {
|
||||
ATYP_IPV4 => bytes.len() >= SOCKS5_MIN_HEADER + IPV4_SIZE + PORT_SIZE,
|
||||
ATYP_DOMAIN => {
|
||||
if bytes.len() < SOCKS5_MIN_HEADER + 1 {
|
||||
return false;
|
||||
}
|
||||
let domain_len = bytes[4] as usize;
|
||||
bytes.len() >= SOCKS5_MIN_HEADER + 1 + domain_len + PORT_SIZE
|
||||
}
|
||||
ATYP_IPV6 => bytes.len() >= SOCKS5_MIN_HEADER + IPV6_SIZE + PORT_SIZE,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let atyp = bytes[3];
|
||||
|
||||
// Вычисляем длину еще раз для split_to (либо можно вынести в хелпер)
|
||||
let total_len = match atyp {
|
||||
ATYP_IPV4 => SOCKS5_MIN_HEADER + IPV4_SIZE + PORT_SIZE,
|
||||
ATYP_DOMAIN => SOCKS5_MIN_HEADER + 1 + (bytes[4] as usize) + PORT_SIZE,
|
||||
ATYP_IPV6 => SOCKS5_MIN_HEADER + IPV6_SIZE + PORT_SIZE,
|
||||
_ => return Err("Unsupported address type".to_string()),
|
||||
};
|
||||
|
||||
let mut packet = bytes.split_to(total_len);
|
||||
packet.advance(SOCKS5_MIN_HEADER);
|
||||
|
||||
let host = if atyp == ATYP_DOMAIN {
|
||||
let len = packet.get_u8() as usize;
|
||||
packet.split_to(len).freeze()
|
||||
} else if atyp == ATYP_IPV4 {
|
||||
packet.split_to(IPV4_SIZE).freeze()
|
||||
} else {
|
||||
packet.split_to(IPV6_SIZE).freeze()
|
||||
};
|
||||
|
||||
let port = packet.get_u16();
|
||||
|
||||
Ok(Some(SocksTarget { host, port }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for SocksRequest {
|
||||
type Error = String;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < 2 || bytes[0] != SOCKS5_VERSION {
|
||||
return false;
|
||||
}
|
||||
|
||||
let nmethods = bytes[1] as usize;
|
||||
if bytes.len() >= 2 + nmethods {
|
||||
// Это может быть Handshake. Проверяем, не Connect ли это (мин. 6-10 байт)
|
||||
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
||||
return true;
|
||||
}
|
||||
// Если для Connect данных мало или структура не совпадает,
|
||||
// но для Handshake достаточно — ок.
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if bytes.len() < 2 || bytes[0] != SOCKS5_VERSION {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 1. Пытаемся распарсить как Connect (у него строгая структура)
|
||||
if bytes.len() >= SOCKS5_MIN_HEADER && SocksTarget::can_parse(bytes) {
|
||||
let command = bytes[1];
|
||||
if let Some(target) = SocksTarget::parse(bytes)? {
|
||||
return Ok(Some(SocksRequest::Connect { command, target }));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Если не Connect, пробуем Handshake
|
||||
let nmethods = bytes[1] as usize;
|
||||
let total_handshake = 2 + nmethods;
|
||||
|
||||
if bytes.len() >= total_handshake {
|
||||
let mut packet = bytes.split_to(total_handshake);
|
||||
packet.advance(2);
|
||||
let mut methods = vec![0u8; nmethods];
|
||||
packet.copy_to_slice(&mut methods);
|
||||
|
||||
return Ok(Some(SocksRequest::Handshake { methods }));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
use crate::{
|
||||
protocol::{
|
||||
errors::{ErrorAction, ErrorStage, TlsError},
|
||||
parser::Parser,
|
||||
},
|
||||
tlseng::{
|
||||
extension::{Extension, ExtensionStack},
|
||||
handshake::*,
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, HelloType, ProtocolVersion},
|
||||
ApplicationData,
|
||||
},
|
||||
utils::u24::{BufExt, U24},
|
||||
};
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
|
||||
// =================================================================
|
||||
// 1. RECORD LAYER
|
||||
// =================================================================
|
||||
|
||||
impl Parser for TlsRecord {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
// 1. Минимум 5 байт для заголовка
|
||||
if bytes.len() < 5 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Проверяем ContentType
|
||||
let content_type = bytes[0];
|
||||
let is_valid_type = content_type == ContentType::Handshake as u8
|
||||
|| content_type == ContentType::ApplicationData as u8
|
||||
|| content_type == ContentType::Alert as u8;
|
||||
|
||||
if !is_valid_type {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Извлекаем заявленную длину тела рекорда
|
||||
let record_len = u16::from_be_bytes([bytes[3], bytes[4]]) as usize;
|
||||
|
||||
// 4. Специфика TLS 1.3:
|
||||
// Если это зашифрованные данные (0x17), они ДОЛЖНЫ содержать тег (16 байт)
|
||||
// + как минимум 1 байт зашифрованного типа контента.
|
||||
if content_type == ContentType::ApplicationData as u8 {
|
||||
if record_len < 17 {
|
||||
// Если длина < 17, это либо неполный пакет, либо ошибка протокола.
|
||||
// Возвращаем false, чтобы подождать еще данных из сокета.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Ждем, пока в буфере будет заголовок + всё тело
|
||||
bytes.len() >= 5 + record_len
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<TlsRecord>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// --- ТОЛЬКО ТЕПЕРЬ МЫ ИЗМЕНЯЕМ БУФЕР ---
|
||||
let raw_content_type = bytes.get_u8();
|
||||
let raw_version = bytes.get_u16();
|
||||
let record_len = bytes.get_u16() as usize;
|
||||
|
||||
let content_type = ContentType::try_from(raw_content_type)
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
let version = ProtocolVersion::try_from(raw_version)
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
// Забираем ровно столько, сколько указано в заголовке
|
||||
let payload = bytes.split_to(record_len).freeze();
|
||||
|
||||
Ok(Some(TlsRecord::new(content_type, version, payload)))
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 2. PAYLOAD TYPES
|
||||
// =================================================================
|
||||
|
||||
impl Parser for ApplicationData {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
!bytes.is_empty()
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
let len = bytes.len();
|
||||
if len == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let payload = bytes.split_to(len).freeze();
|
||||
Ok(Some(Self { len, payload }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for HelloHeader {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.len() < 4 {
|
||||
return false;
|
||||
}
|
||||
bytes[0] == HelloType::Client as u8 || bytes[0] == HelloType::Server as u8
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
if !Self::can_parse(bytes) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let raw_type = bytes.get_u8();
|
||||
let header_type = HelloType::try_from(raw_type).map_err(|e| {
|
||||
TlsError::new(ErrorStage::Handshake(e), ErrorAction::Drop, Bytes::new())
|
||||
})?;
|
||||
|
||||
let len = bytes.get_u24();
|
||||
|
||||
Ok(Some(Self {
|
||||
header_type,
|
||||
len: U24::from_u32(len),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 3. HELLO MESSAGES
|
||||
// =================================================================
|
||||
|
||||
impl Parser for ClientHello {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut offset = 34; // ProtocolVersion (2) + Random (32)
|
||||
if bytes.len() < offset + 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let session_id_len = bytes[offset] as usize;
|
||||
offset += 1 + session_id_len;
|
||||
if bytes.len() < offset + 2 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let ciphers_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ciphers_len;
|
||||
if bytes.len() < offset + 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let comp_len = bytes[offset] as usize;
|
||||
offset += 1 + comp_len;
|
||||
|
||||
if bytes.len() >= offset + 2 {
|
||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ext_len;
|
||||
}
|
||||
|
||||
bytes.len() >= offset
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
// --- ШАГ 1: Атомарная проверка всего пакета ---
|
||||
let mut offset = 34; // Version + Random
|
||||
if bytes.len() < offset + 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
let session_id_len = bytes[offset] as usize;
|
||||
offset += 1 + session_id_len;
|
||||
|
||||
if bytes.len() < offset + 2 {
|
||||
return Ok(None);
|
||||
}
|
||||
let ciphers_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ciphers_len;
|
||||
|
||||
if bytes.len() < offset + 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
let comp_len = bytes[offset] as usize;
|
||||
offset += 1 + comp_len;
|
||||
|
||||
if bytes.len() >= offset + 2 {
|
||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ext_len;
|
||||
}
|
||||
|
||||
// Если нам не хватает данных для полного ClientHello, выходим, не трогая буфер
|
||||
if bytes.len() < offset {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// --- ШАГ 2: Безопасное чтение ---
|
||||
// Изолируем ровно тот кусок, который проверили.
|
||||
let mut msg = bytes.split_to(offset);
|
||||
|
||||
let version = ProtocolVersion::try_from(msg.get_u16())
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
let mut random = [0u8; 32];
|
||||
msg.copy_to_slice(&mut random);
|
||||
|
||||
let sid_len = msg.get_u8() as usize;
|
||||
let session_id = msg.split_to(sid_len).freeze();
|
||||
|
||||
let c_len = msg.get_u16() as usize;
|
||||
let mut cipher_suites = Vec::with_capacity(c_len / 2);
|
||||
let mut ciphers_data = msg.split_to(c_len);
|
||||
while ciphers_data.has_remaining() {
|
||||
cipher_suites.push(ciphers_data.get_u16());
|
||||
}
|
||||
|
||||
let cmp_len = msg.get_u8() as usize;
|
||||
msg.advance(cmp_len); // пропускаем методы сжатия
|
||||
|
||||
let extensions = if msg.remaining() >= 2 {
|
||||
let ext_len = msg.get_u16() as usize;
|
||||
msg.split_to(ext_len).freeze()
|
||||
} else {
|
||||
Bytes::new()
|
||||
};
|
||||
|
||||
Ok(Some(Self {
|
||||
version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suites,
|
||||
extensions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser for ServerHello {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut offset = 34; // ProtocolVersion (2) + Random (32)
|
||||
if bytes.len() < offset + 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let session_id_len = bytes[offset] as usize;
|
||||
offset += 1 + session_id_len;
|
||||
|
||||
// Cipher suite (2) + Compression (1)
|
||||
offset += 3;
|
||||
|
||||
if bytes.len() >= offset + 2 {
|
||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ext_len;
|
||||
}
|
||||
|
||||
bytes.len() >= offset
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
// --- ШАГ 1: Атомарная проверка всего пакета ---
|
||||
let mut offset = 34; // Version + Random
|
||||
if bytes.len() < offset + 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
let session_id_len = bytes[offset] as usize;
|
||||
offset += 1 + session_id_len;
|
||||
|
||||
offset += 3; // Cipher Suite (2) + Compression (1)
|
||||
|
||||
if bytes.len() >= offset + 2 {
|
||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
offset += 2 + ext_len;
|
||||
}
|
||||
|
||||
if bytes.len() < offset {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// --- ШАГ 2: Безопасное чтение ---
|
||||
let mut msg = bytes.split_to(offset);
|
||||
|
||||
let version = ProtocolVersion::try_from(msg.get_u16())
|
||||
.map_err(|e| TlsError::new(ErrorStage::Tls(e), ErrorAction::Drop, Bytes::new()))?;
|
||||
|
||||
let mut random = [0u8; 32];
|
||||
msg.copy_to_slice(&mut random);
|
||||
|
||||
let sid_len = msg.get_u8() as usize;
|
||||
let session_id = msg.split_to(sid_len).freeze();
|
||||
|
||||
let cipher_suite = msg.get_u16();
|
||||
msg.advance(1); // compression
|
||||
|
||||
let extensions = if msg.remaining() >= 2 {
|
||||
let ext_len = msg.get_u16() as usize;
|
||||
msg.split_to(ext_len)
|
||||
} else {
|
||||
BytesMut::new()
|
||||
};
|
||||
|
||||
Ok(Some(Self {
|
||||
version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suite,
|
||||
extensions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. EXTENSIONS
|
||||
// =================================================================
|
||||
|
||||
impl Parser for ExtensionStack {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut offset = 0;
|
||||
let data_len = bytes.len();
|
||||
|
||||
while offset + 4 <= data_len {
|
||||
let elen = u16::from_be_bytes([bytes[offset + 2], bytes[offset + 3]]) as usize;
|
||||
offset += 4 + elen;
|
||||
}
|
||||
|
||||
offset <= data_len
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
// Проверяем на целостность всех расширений
|
||||
let mut offset = 0;
|
||||
let data_len = bytes.len();
|
||||
|
||||
while offset + 4 <= data_len {
|
||||
let elen = u16::from_be_bytes([bytes[offset + 2], bytes[offset + 3]]) as usize;
|
||||
offset += 4 + elen;
|
||||
}
|
||||
|
||||
// Если offset > data_len, значит кусок данных расширения отсечен, ждем еще данных
|
||||
if offset > data_len {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Если offset < data_len, есть лишние байты (Trailing garbage). Но так как мы
|
||||
// обычно передаем сюда точный срез `extensions`, это может быть ошибкой формата.
|
||||
if offset != data_len {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Tls("Malformed extension stack: trailing data"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
|
||||
// Теперь гарантированно безопасно парсить всё до конца
|
||||
let mut extensions = Vec::new();
|
||||
while bytes.remaining() >= 4 {
|
||||
let etype = bytes.get_u16();
|
||||
let elen = bytes.get_u16() as usize;
|
||||
let data = bytes.split_to(elen).freeze();
|
||||
extensions.push(Extension::new(etype, data));
|
||||
}
|
||||
|
||||
Ok(Some(Self { extensions }))
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use crate::{
|
||||
protocol::{interceptors::error_interceptor::InterceptorError, parser::parser::FrameParser},
|
||||
tlseng::application_data::ApplicationData,
|
||||
};
|
||||
|
||||
impl FrameParser for ApplicationData {
|
||||
type Error = InterceptorError;
|
||||
|
||||
fn can_parse(bytes: &bytes::BytesMut) -> bool {
|
||||
// Application Data не может быть пустым по спецификации (хотя бы 1 байт)
|
||||
!bytes.is_empty()
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let len = bytes.len();
|
||||
|
||||
if len == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let payload = bytes.split_to(len).freeze();
|
||||
|
||||
Ok(Some(Self { len, payload }))
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
use bytes::{Buf, Bytes};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
parser::parser::FrameParser,
|
||||
},
|
||||
tlseng::{handshake::client_hello::ClientHello, types::ProtocolVersion},
|
||||
};
|
||||
|
||||
impl FrameParser for ClientHello {
|
||||
type Error = InterceptorError;
|
||||
|
||||
fn can_parse(bytes: &bytes::BytesMut) -> bool {
|
||||
// Мы предполагаем, что HelloHeader уже проверил тип.
|
||||
// Здесь можно проверить минимально допустимый размер ClientHello
|
||||
// (Version 2 + Random 32 + SessionID_len 1 = 35 байт)
|
||||
bytes.len() >= 35
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if bytes.len() < 35 {
|
||||
return Ok(None);
|
||||
}
|
||||
// 2. Version (2 bytes)
|
||||
let raw_version = bytes.get_u16();
|
||||
let version = ProtocolVersion::try_from(raw_version).map_err(|e| {
|
||||
InterceptorError::new(
|
||||
ErrorType::Tls(e),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&raw_version.to_be_bytes()),
|
||||
)
|
||||
})?;
|
||||
|
||||
// 3. Random (32 bytes)
|
||||
let mut random = [0u8; 32];
|
||||
bytes.copy_to_slice(&mut random);
|
||||
|
||||
// 4. Session ID (Variable length: 1 byte len + data)
|
||||
let session_id_len = bytes.get_u8() as usize;
|
||||
if bytes.len() < session_id_len {
|
||||
return Ok(None); // Данные неполные
|
||||
}
|
||||
let session_id = bytes.split_to(session_id_len).freeze();
|
||||
|
||||
// 5. Cipher Suites (Variable length: 2 bytes len + data)
|
||||
if bytes.len() < 2 {
|
||||
return Ok(None);
|
||||
}
|
||||
let ciphers_len = bytes.get_u16() as usize;
|
||||
if bytes.len() < ciphers_len {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut cipher_suites = Vec::with_capacity(ciphers_len / 2);
|
||||
let mut ciphers_data = bytes.split_to(ciphers_len);
|
||||
while ciphers_data.has_remaining() {
|
||||
cipher_suites.push(ciphers_data.get_u16());
|
||||
}
|
||||
|
||||
// 6. Compression Methods (1 byte len + data) - в TLS 1.3 обычно [0x01, 0x00]
|
||||
if bytes.len() < 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
let comp_len = bytes.get_u8() as usize;
|
||||
if bytes.len() < comp_len {
|
||||
return Ok(None);
|
||||
}
|
||||
bytes.advance(comp_len); // Мы их просто пропускаем, они не нужны в структуре
|
||||
|
||||
// 7. Extensions (2 bytes len + data)
|
||||
if bytes.is_empty() {
|
||||
// Расширений может не быть (теоретически в старых TLS)
|
||||
return Ok(Some(Self {
|
||||
version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suites,
|
||||
extensions: Bytes::new(),
|
||||
}));
|
||||
}
|
||||
|
||||
if bytes.len() < 2 {
|
||||
return Ok(None);
|
||||
}
|
||||
let extensions_len = bytes.get_u16() as usize;
|
||||
if bytes.len() < extensions_len {
|
||||
return Ok(None);
|
||||
}
|
||||
let extensions = bytes.split_to(extensions_len).freeze();
|
||||
|
||||
Ok(Some(Self {
|
||||
version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suites,
|
||||
extensions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
parser::parser::FrameParser,
|
||||
},
|
||||
tlseng::extension::{Extension, ExtensionStack},
|
||||
};
|
||||
|
||||
impl FrameParser for ExtensionStack {
|
||||
type Error = InterceptorError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
// Минимальное расширение: тип(2) + длина(2) = 4 байта
|
||||
bytes.len() >= 4
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
let mut extensions = Vec::new();
|
||||
|
||||
while bytes.remaining() >= 4 {
|
||||
let etype = bytes.get_u16();
|
||||
let elen = bytes.get_u16() as usize;
|
||||
|
||||
if bytes.remaining() < elen {
|
||||
// Если данных меньше, чем обещала длина расширения — это ошибка протокола
|
||||
return Err(InterceptorError::new(
|
||||
ErrorType::Tls("Invalid extension payload"),
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
|
||||
let data = bytes.split_to(elen).freeze();
|
||||
extensions.push(Extension::new(etype, data));
|
||||
}
|
||||
Ok(Some(Self { extensions }))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionStack {
|
||||
/// Возвращает данные расширения по его типу
|
||||
pub fn find_by_type(&self, etype: u16) -> Option<Bytes> {
|
||||
self.extensions
|
||||
.iter()
|
||||
.find(|e| e.etype == etype)
|
||||
.map(|e| e.data.clone()) // Клонирование Bytes дешево (increment refcount)
|
||||
}
|
||||
|
||||
/// Проверяет наличие расширения
|
||||
pub fn has_extension(&self, etype: u16) -> bool {
|
||||
self.extensions.iter().any(|e| e.etype == etype)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use bytes::{Buf, Bytes};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
parser::parser::FrameParser,
|
||||
},
|
||||
tlseng::{handshake::hello_header::HelloHeader, types::HelloType},
|
||||
utils::u24::{BufExt, U24},
|
||||
};
|
||||
|
||||
impl FrameParser for HelloHeader {
|
||||
type Error = InterceptorError;
|
||||
fn can_parse(bytes: &bytes::BytesMut) -> bool {
|
||||
if bytes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let is_valid = match bytes[0] {
|
||||
x if x == HelloType::Client as u8 => true,
|
||||
x if x == HelloType::Server as u8 => true,
|
||||
_ => false,
|
||||
};
|
||||
is_valid
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if bytes.len() < 4 {
|
||||
return Ok(None);
|
||||
}
|
||||
let raw_header_type = bytes.get_u8();
|
||||
let header_type = HelloType::try_from(raw_header_type).map_err(|e| {
|
||||
InterceptorError::new(
|
||||
ErrorType::Handshake(e),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&raw_header_type.to_be_bytes()),
|
||||
)
|
||||
})?;
|
||||
let len = bytes.get_u24();
|
||||
Ok(Some(Self {
|
||||
header_type,
|
||||
len: U24::from_u32(len),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
mod app_data_parser;
|
||||
mod client_hello_parser;
|
||||
mod extension_parser;
|
||||
mod h_header_parser;
|
||||
mod server_hello_parser;
|
||||
mod tls_header_parser;
|
||||
@@ -1,94 +0,0 @@
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
parser::parser::FrameParser,
|
||||
},
|
||||
tlseng::{handshake::server_hello::ServerHello, types::ProtocolVersion},
|
||||
};
|
||||
|
||||
impl FrameParser for ServerHello {
|
||||
type Error = InterceptorError;
|
||||
|
||||
fn can_parse(bytes: &bytes::BytesMut) -> bool {
|
||||
// Минимальный ServerHello:
|
||||
// Version(2) + Random(32) + SessionID_len(1) + Cipher(2) + Compression(1) = 38 байт
|
||||
bytes.len() >= 38
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// 1. Базовая проверка длины (базовые поля до компрессии включительно)
|
||||
if bytes.len() < 38 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 2. Version (2 bytes)
|
||||
let raw_version = bytes.get_u16();
|
||||
let version = ProtocolVersion::try_from(raw_version).map_err(|e| {
|
||||
InterceptorError::new(
|
||||
ErrorType::Tls(e),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&raw_version.to_be_bytes()),
|
||||
)
|
||||
})?;
|
||||
|
||||
// 3. Random (32 bytes)
|
||||
let mut random = [0u8; 32];
|
||||
bytes.copy_to_slice(&mut random);
|
||||
|
||||
// 4. Session ID (1 byte len + data)
|
||||
let session_id_len = bytes.get_u8() as usize;
|
||||
if bytes.len() < session_id_len {
|
||||
return Ok(None);
|
||||
}
|
||||
let session_id = bytes.split_to(session_id_len).freeze();
|
||||
|
||||
// 5. Cipher Suite (2 bytes)
|
||||
if bytes.len() < 2 {
|
||||
return Ok(None);
|
||||
}
|
||||
let cipher_suite = bytes.get_u16();
|
||||
|
||||
// 6. Compression Method (1 byte)
|
||||
if bytes.len() < 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
let _compression = bytes.get_u8();
|
||||
|
||||
// 7. Extensions (2 bytes len + data)
|
||||
// Если после компрессии байтов нет — расширений просто нет.
|
||||
if bytes.is_empty() {
|
||||
return Ok(Some(Self {
|
||||
version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suite,
|
||||
extensions: BytesMut::new(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Если байты есть, проверяем заголовок длины расширений
|
||||
if bytes.len() < 2 {
|
||||
return Ok(None);
|
||||
}
|
||||
let extensions_len = bytes.get_u16() as usize;
|
||||
|
||||
if bytes.len() < extensions_len {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let extensions = bytes.split_to(extensions_len);
|
||||
|
||||
Ok(Some(Self {
|
||||
version,
|
||||
random,
|
||||
session_id,
|
||||
cipher_suite,
|
||||
extensions,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
protocol::{
|
||||
interceptors::error_interceptor::{ErrorAction, ErrorType, InterceptorError},
|
||||
parser::parser::FrameParser,
|
||||
},
|
||||
tlseng::{
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, ProtocolVersion},
|
||||
},
|
||||
};
|
||||
|
||||
impl FrameParser for TlsRecord {
|
||||
type Error = InterceptorError;
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
if bytes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let is_valid = match bytes[0] {
|
||||
x if x == ContentType::Handshake as u8 => true,
|
||||
x if x == ContentType::ApplicationData as u8 => true,
|
||||
x if x == ContentType::Alert as u8 => true,
|
||||
_ => false,
|
||||
};
|
||||
is_valid
|
||||
}
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<TlsRecord>, Self::Error> {
|
||||
if bytes.len() < 5 {
|
||||
return Ok(None);
|
||||
}
|
||||
let len = u16::from_be_bytes([bytes[3], bytes[4]]);
|
||||
if bytes.len() < 5 + len as usize {
|
||||
return Ok(None);
|
||||
}
|
||||
let raw_content_type = bytes.get_u8();
|
||||
let raw_version = bytes.get_u16();
|
||||
let content_type = ContentType::try_from(raw_content_type).map_err(|e| {
|
||||
InterceptorError::new(
|
||||
ErrorType::Tls(e),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&raw_content_type.to_be_bytes()),
|
||||
)
|
||||
})?;
|
||||
let version = ProtocolVersion::try_from(raw_version).map_err(|e| {
|
||||
InterceptorError::new(
|
||||
ErrorType::Tls(e),
|
||||
ErrorAction::Drop,
|
||||
Bytes::copy_from_slice(&raw_version.to_be_bytes()),
|
||||
)
|
||||
})?;
|
||||
let _raw_len = bytes.get_u16();
|
||||
let payload = bytes.split_to(len as usize).freeze();
|
||||
Ok(Some(TlsRecord::new(content_type, version, payload)))
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ impl BufPair {
|
||||
}
|
||||
}
|
||||
pub async fn read_from(&mut self, reader: &mut OwnedReadHalf) -> Result<usize, String> {
|
||||
self.read_buf.clear(); // Вот она, автоматическая чистка
|
||||
let n = reader
|
||||
.read_buf(&mut self.read_buf)
|
||||
.await
|
||||
@@ -27,11 +26,10 @@ impl BufPair {
|
||||
if n == 0 {
|
||||
return Err("Connection closed by peer".to_string());
|
||||
}
|
||||
//println!("Reader {:?}", self.read_buf);
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
pub async fn write_to(&mut self, writer: &mut OwnedWriteHalf) -> Result<(), String> {
|
||||
pub async fn write_from(&mut self, writer: &mut OwnedWriteHalf) -> Result<(), String> {
|
||||
writer
|
||||
.write_all_buf(&mut self.write_buf)
|
||||
.await
|
||||
|
||||
@@ -1,80 +1,256 @@
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tokio::net::{
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
TcpStream,
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use tracing::{instrument, info, debug, error, trace, warn};
|
||||
use std::{collections::HashMap, net::SocketAddr};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
TcpStream,
|
||||
},
|
||||
sync::mpsc::{self},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
protocol::codec::codec::Codec,
|
||||
protocol::{
|
||||
codec::{
|
||||
codec::Codec,
|
||||
frame::FrameType,
|
||||
socks::{SocksReply, SocksRequest, SocksTarget},
|
||||
},
|
||||
errors::ErrorAction,
|
||||
parser::parser::Parser,
|
||||
},
|
||||
proxy::connection::{
|
||||
buf_pair::BufPair, handler::handler::ProxyHandler, state::ConnectionState,
|
||||
buf_pair::BufPair,
|
||||
engine::TunnelEngine,
|
||||
handler::spawn_client_local_handler_with_rx,
|
||||
muxer::{MuxMessage, Muxer},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct VirtualStreams {
|
||||
pub channels: HashMap<u32, tokio::sync::mpsc::Sender<Bytes>>,
|
||||
}
|
||||
|
||||
pub enum ConnectionState {
|
||||
Authorize,
|
||||
Tunnel(VirtualStreams),
|
||||
Close,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ConnectionRole {
|
||||
Client,
|
||||
Server,
|
||||
}
|
||||
|
||||
pub struct Connection {
|
||||
pub addr: SocketAddr,
|
||||
inbound: OwnedReadHalf,
|
||||
outbound: OwnedWriteHalf,
|
||||
state: ConnectionState,
|
||||
buffers: BufPair,
|
||||
codec: Codec,
|
||||
addr: SocketAddr,
|
||||
pub inbound: OwnedReadHalf,
|
||||
pub outbound: OwnedWriteHalf,
|
||||
pub buffers: BufPair,
|
||||
pub codec: Codec,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn new(stream: TcpStream, addr: SocketAddr, init: bool) -> Self {
|
||||
pub fn new(
|
||||
stream: TcpStream,
|
||||
addr: SocketAddr,
|
||||
init: bool,
|
||||
) -> Self {
|
||||
let (inbound, outbound) = stream.into_split();
|
||||
Self {
|
||||
addr,
|
||||
inbound,
|
||||
outbound,
|
||||
state: ConnectionState::New,
|
||||
buffers: BufPair::new(),
|
||||
codec: Codec::new(init),
|
||||
}
|
||||
}
|
||||
pub async fn handle(
|
||||
&mut self,
|
||||
handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||
) -> Result<ConnectionState, String> {
|
||||
loop {
|
||||
match &mut self.state {
|
||||
ConnectionState::New => {
|
||||
self.state = handler
|
||||
.init_session(&mut self.inbound, &mut self.outbound, &mut self.buffers)
|
||||
.await?
|
||||
}
|
||||
ConnectionState::Handshake => {
|
||||
self.state = handler
|
||||
.authorize_request(
|
||||
&mut self.inbound,
|
||||
&mut self.outbound,
|
||||
&mut self.buffers,
|
||||
&mut self.codec,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ConnectionState::Tunnel(ref mut stream) => {
|
||||
self.state = handler
|
||||
.exchange_data(
|
||||
&mut self.inbound,
|
||||
&mut self.outbound,
|
||||
&mut self.buffers,
|
||||
&mut self.codec,
|
||||
stream,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
ConnectionState::Close => {
|
||||
self.state = handler
|
||||
.finalize_session(&mut self.inbound, &mut self.outbound, &mut self.buffers)
|
||||
.await?;
|
||||
}
|
||||
ConnectionState::Disconnected => {
|
||||
println!("Disconnected");
|
||||
return Ok(ConnectionState::Disconnected);
|
||||
}
|
||||
/// Читает и парсит запрос SOCKS5 из входящего потока
|
||||
async fn read_socks_request(&mut self) -> Result<SocksRequest, String> {
|
||||
loop {
|
||||
// Попытка парсинга из текущего буфера
|
||||
match SocksRequest::parse(&mut self.buffers.read_buf) {
|
||||
Ok(Some(req)) => {
|
||||
// Используем Debug-вывод (?req), так как SocksRequest обычно Enum
|
||||
info!(client = %self.addr, request = ?req, "SOCKS request successfully parsed");
|
||||
return Ok(req);
|
||||
}
|
||||
Ok(None) => {
|
||||
// Это не ошибка, просто данных в сокете пока меньше, чем размер структуры SOCKS
|
||||
trace!(client = %self.addr, buffer_len = self.buffers.read_buf.len(), "SOCKS parse: need more data");
|
||||
}
|
||||
Err(e) => {
|
||||
error!(client = %self.addr, error = %e, "SOCKS protocol violation");
|
||||
return Err(format!("Socks parse error: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
// Чтение новых данных из сокета
|
||||
let n = self
|
||||
.inbound
|
||||
.read_buf(&mut self.buffers.read_buf)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(client = %self.addr, error = %e, "Failed to read from socket during SOCKS handshake");
|
||||
e.to_string()
|
||||
})?;
|
||||
|
||||
if n == 0 {
|
||||
warn!(client = %self.addr, "Client closed connection prematurely during SOCKS handshake");
|
||||
return Err("Client closed connection during SOCKS handshake".into());
|
||||
}
|
||||
|
||||
trace!(client = %self.addr, read_bytes = n, "Read data from client for SOCKS handshake");
|
||||
}
|
||||
}
|
||||
|
||||
/// Отправляет SOCKS ответ
|
||||
async fn send_socks_reply(&mut self, reply: SocksReply) -> Result<(), String> {
|
||||
let mut buf = BytesMut::with_capacity(24);
|
||||
debug!(client = %self.addr, reply = ?reply, "Sending SOCKS reply to client");
|
||||
reply.write_to(&mut buf);
|
||||
|
||||
|
||||
|
||||
self.outbound
|
||||
.write_all(&buf)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(client = %self.addr, error = %e, "Failed to send SOCKS reply");
|
||||
e.to_string()
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
name = "socks_handler",
|
||||
skip(self, muxer),
|
||||
fields(addr = %self.addr)
|
||||
)]
|
||||
pub async fn handle_socks_client(mut self, muxer: Muxer) -> Result<(), String> {
|
||||
info!("Starting SOCKS multiplexed handling");
|
||||
|
||||
// 1. SOCKS Handshake
|
||||
debug!("Reading SOCKS handshake request");
|
||||
let _ = self.read_socks_request().await.map_err(|e| {
|
||||
error!("SOCKS handshake failed: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
self.send_socks_reply(SocksReply::HandshakeSelect { method: 0x00 }).await?;
|
||||
|
||||
// 2. SOCKS Connect
|
||||
// 2. SOCKS Connect - читаем, КУДА хочет браузер
|
||||
let req = self.read_socks_request().await?;
|
||||
let target = if let SocksRequest::Connect { target, .. } = req {
|
||||
target
|
||||
} else {
|
||||
return Err("Expected Connect".into());
|
||||
};
|
||||
|
||||
let stream_id = muxer.next_id();
|
||||
let target_str = match &target {
|
||||
SocksTarget { host, port } => format!("{}:{}", String::from_utf8_lossy(host), port),
|
||||
};
|
||||
|
||||
// --- НОВАЯ ЛОГИКА ОЖИДАНИЯ ---
|
||||
// Регистрируем временный канал, чтобы получить Connect-подтверждение от сервера
|
||||
let (v_tx, mut v_rx) = mpsc::channel::<Bytes>(100);
|
||||
muxer.register_stream(stream_id, v_tx).await;
|
||||
|
||||
// Отправляем Connect-кадр на сервер
|
||||
muxer.to_network.send(MuxMessage {
|
||||
stream_id,
|
||||
frame_type: FrameType::Connect,
|
||||
data: Bytes::from(target_str),
|
||||
}).await.map_err(|e| e.to_string())?;
|
||||
|
||||
let first_payload = match tokio::time::timeout(std::time::Duration::from_secs(10), v_rx.recv()).await {
|
||||
Ok(Some(data)) => data,
|
||||
_ => {
|
||||
error!(stream_id, "Server timeout or failed to send Connect confirmation");
|
||||
// Шлем браузеру ошибку, если сервер промолчал
|
||||
self.send_socks_reply(SocksReply::ConnectResult {
|
||||
reply_code: 0x01, atyp: 0x01, addr: [0, 0, 0, 0], port: 0,
|
||||
}).await.ok();
|
||||
return Err("Target connection failed".into());
|
||||
}
|
||||
};
|
||||
|
||||
// Проверяем код ответа (второй байт в SOCKS5)
|
||||
if first_payload.len() >= 2 && first_payload[1] == 0x00 {
|
||||
debug!(stream_id, "Server confirmed connection, forwarding SOCKS reply to browser");
|
||||
|
||||
// ВАЖНО: Отправляем браузеру ТО, что прислал сервер (те самые 10 байт)
|
||||
// Не создаем новый SocksReply вручную, а пробрасываем байты сервера
|
||||
self.outbound.write_all(&first_payload).await.map_err(|e| e.to_string())?;
|
||||
} else {
|
||||
// Если сервер прислал ошибку (reply_code != 0), тоже пробрасываем её браузеру и выходим
|
||||
self.outbound.write_all(&first_payload).await.ok();
|
||||
return Err("Server rejected connection".into());
|
||||
}
|
||||
|
||||
// 4. Разбираем self и запускаем хендлер
|
||||
let Self { inbound: browser_in, outbound: browser_out, .. } = self;
|
||||
|
||||
// Передаем v_rx (в котором уже могут быть данные от сервера после "OK") в хендлер
|
||||
spawn_client_local_handler_with_rx(stream_id, browser_in, browser_out, muxer.clone(), v_rx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
name = "server_tunnel",
|
||||
skip(self),
|
||||
fields(addr = %self.addr)
|
||||
)]
|
||||
pub async fn handle_server_tunnel(mut self) -> Result<(), String> {
|
||||
info!("Acting as TLS Server, waiting for ClientHello");
|
||||
|
||||
// Создаем Muxer для сервера
|
||||
let (mux_tx, mux_rx) = mpsc::channel(1024);
|
||||
let muxer = Muxer::new(mux_tx.clone(), false); // false, так как это Сервер
|
||||
|
||||
// 1. TLS Handshake
|
||||
let server_hello_bytes = loop {
|
||||
match self.codec.make_server_handshake(&mut self.buffers.read_buf) {
|
||||
Ok(bytes) => {
|
||||
info!("ClientHello received, sending ServerHello");
|
||||
break bytes;
|
||||
},
|
||||
Err(e) if e.action == ErrorAction::Wait => {
|
||||
let n = self.inbound.read_buf(&mut self.buffers.read_buf).await
|
||||
.map_err(|err| format!("Read error: {}", err))?;
|
||||
|
||||
if n == 0 { return Err("Client closed connection".into()); }
|
||||
}
|
||||
Err(e) => return Err(format!("TLS error: {:?}", e)),
|
||||
}
|
||||
};
|
||||
|
||||
self.outbound.write_all(&server_hello_bytes).await.map_err(|e| e.to_string())?;
|
||||
info!("TLS Tunnel established as server");
|
||||
|
||||
// 2. Передача управления в TunnelEngine
|
||||
debug!("Handover to TunnelEngine");
|
||||
let engine = TunnelEngine {
|
||||
inbound: self.inbound,
|
||||
outbound: self.outbound,
|
||||
codec: self.codec,
|
||||
buffers: self.buffers,
|
||||
mux_rx,
|
||||
muxer,
|
||||
role: ConnectionRole::Server,
|
||||
};
|
||||
|
||||
engine.run().await.map_err(|e| {
|
||||
error!("TunnelEngine error: {}", e);
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
use crate::{
|
||||
protocol::{
|
||||
codec::{
|
||||
codec::Codec,
|
||||
frame::{Frame, FrameType},
|
||||
},
|
||||
errors::ErrorAction,
|
||||
},
|
||||
proxy::connection::{
|
||||
buf_pair::BufPair,
|
||||
connection::ConnectionRole,
|
||||
handler::spawn_server_target_handler,
|
||||
muxer::{MuxMessage, Muxer},
|
||||
},
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
pub struct TunnelEngine {
|
||||
pub inbound: OwnedReadHalf,
|
||||
pub outbound: OwnedWriteHalf,
|
||||
pub codec: Codec,
|
||||
pub buffers: BufPair,
|
||||
pub mux_rx: Receiver<MuxMessage>,
|
||||
pub muxer: Muxer,
|
||||
pub role: ConnectionRole,
|
||||
}
|
||||
|
||||
impl TunnelEngine {
|
||||
pub async fn run(mut self) -> Result<(), String> {
|
||||
info!(role = ?self.role, "TunnelEngine spinning up");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 1. Физический Inbound (Сеть -> Muxer)
|
||||
// Теперь возвращает Vec<Frame>, чтобы обработать всё накопленное
|
||||
res = Self::process_inbound(&mut self.inbound, &mut self.codec, &mut self.buffers) => {
|
||||
match res {
|
||||
Ok(frames) => {
|
||||
for frame in frames {
|
||||
self.handle_incoming_frame(frame).await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e == "EOF" {
|
||||
info!("Physical connection closed by remote (EOF)");
|
||||
} else {
|
||||
error!(error = %e, "Critical error in process_inbound");
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Физический Outbound (Muxer -> Сеть)
|
||||
Some(msg) = self.mux_rx.recv() => {
|
||||
if let Err(e) = self.handle_outbound_msg(msg).await {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Логика обработки конкретного фрейма (разгружаем основной loop)
|
||||
async fn handle_incoming_frame(&mut self, frame: Frame) {
|
||||
let stream_id = frame.header.stream_id;
|
||||
let frame_type = frame.header.frame_type;
|
||||
|
||||
trace!(stream_id = frame.header.stream_id, f_type = ?frame.header.frame_type, len = frame.payload.len(), "Engine received frame from network");
|
||||
match frame_type {
|
||||
FrameType::Connect => {
|
||||
match self.role {
|
||||
ConnectionRole::Server => {
|
||||
let target = String::from_utf8_lossy(&frame.payload);
|
||||
info!(stream_id, target = %target, "New Connect request received");
|
||||
spawn_server_target_handler(stream_id, frame.payload, self.muxer.clone())
|
||||
.await;
|
||||
}
|
||||
ConnectionRole::Client => {
|
||||
// Тот самый фикс: клиент получает Connect как подтверждение (ACK)
|
||||
debug!(stream_id, "Connection confirmed by server");
|
||||
self.muxer.dispatch_to_local(stream_id, frame.payload).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
FrameType::Data => {
|
||||
self.muxer.dispatch_to_local(stream_id, frame.payload).await;
|
||||
}
|
||||
FrameType::Close => {
|
||||
info!(stream_id, "Received Close frame, tearing down stream");
|
||||
// Важно: muxer должен не просто удалить, а послать EOF в локальный канал
|
||||
self.muxer.dispatch_to_local(stream_id, Bytes::new()).await;
|
||||
self.muxer.remove_stream(stream_id).await;
|
||||
}
|
||||
_ => debug!(stream_id, ?frame_type, "Received unhandled frame type"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Вспомогательная функция для чтения из сети
|
||||
async fn process_inbound(
|
||||
inbound: &mut OwnedReadHalf,
|
||||
codec: &mut Codec,
|
||||
buffers: &mut BufPair,
|
||||
) -> Result<Vec<Frame>, String> {
|
||||
let mut frames = Vec::new();
|
||||
|
||||
// Сначала читаем данные из сокета в буфер
|
||||
let n = inbound
|
||||
.read_buf(&mut buffers.read_buf)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if n == 0 && buffers.read_buf.is_empty() {
|
||||
return Err("EOF".into());
|
||||
}
|
||||
|
||||
// Теперь пытаемся достать столько фреймов, сколько получится
|
||||
loop {
|
||||
match codec.inbound(&mut buffers.read_buf) {
|
||||
Ok(Some(frame)) => frames.push(frame),
|
||||
Ok(None) => break, // Больше полных фреймов нет
|
||||
Err(e) if e.action == ErrorAction::Wait => break,
|
||||
Err(e) => return Err(format!("Codec error: {:?}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(frames)
|
||||
}
|
||||
|
||||
async fn handle_outbound_msg(&mut self, msg: MuxMessage) -> Result<(), String> {
|
||||
match self
|
||||
.codec
|
||||
.encrypt_data(msg.stream_id, msg.frame_type, msg.data)
|
||||
{
|
||||
Ok(pkt) => {
|
||||
self.outbound
|
||||
.write_all(&pkt)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(format!("Encryption error: {:?}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
use crate::protocol::codec::frame::FrameType;
|
||||
use crate::protocol::codec::socks::{SocksReply, ATYP_IPV4, REPLY_SUCCESS};
|
||||
use crate::proxy::connection::muxer::{MuxMessage, Muxer};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
pub fn spawn_client_local_handler_with_rx(
|
||||
stream_id: u32,
|
||||
mut r: OwnedReadHalf,
|
||||
mut w: OwnedWriteHalf,
|
||||
muxer: Muxer,
|
||||
mut v_rx: mpsc::Receiver<Bytes>, // Используем эту читалку, она уже зарегистрирована!
|
||||
) {
|
||||
// ВНИМАНИЕ: Здесь больше не создаем канал и не вызываем register_stream,
|
||||
// так как это уже сделал handle_socks_client до вызова этой функции.
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = BytesMut::with_capacity(8192);
|
||||
debug!(
|
||||
stream_id,
|
||||
"Spawned client local handler with existing receiver"
|
||||
);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 1. Читаем из браузера -> Шлем в общий туннель
|
||||
res = r.read_buf(&mut buf) => {
|
||||
match res {
|
||||
Ok(0) => {
|
||||
debug!(stream_id, "Browser closed connection (EOF)");
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
error!(stream_id, error = %e, "Read error from browser");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
let msg = MuxMessage {
|
||||
stream_id,
|
||||
frame_type: FrameType::Data,
|
||||
data: buf.split().freeze(),
|
||||
};
|
||||
if muxer.to_network.send(msg).await.is_err() { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Читаем из виртуального канала (данные из туннеля) -> Шлем браузеру
|
||||
maybe_data = v_rx.recv() => {
|
||||
match maybe_data {
|
||||
Some(data) => {
|
||||
if let Err(e) = w.write_all(&data).await {
|
||||
error!(stream_id, error = %e, "Write error to browser");
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!(stream_id, "Virtual channel closed by Muxer");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// КРИТИЧНО: Сообщаем серверу, что мы закрываем этот конкретный стрим
|
||||
let _ = muxer
|
||||
.to_network
|
||||
.send(MuxMessage {
|
||||
stream_id,
|
||||
frame_type: FrameType::Close,
|
||||
data: Bytes::new(),
|
||||
})
|
||||
.await;
|
||||
|
||||
// Чистим за собой в таблице стримов
|
||||
muxer.remove_stream(stream_id).await;
|
||||
info!(stream_id, "Client handler terminated");
|
||||
});
|
||||
}
|
||||
pub async fn spawn_server_target_handler(stream_id: u32, target_raw: Bytes, muxer: Muxer) {
|
||||
let addr = String::from_utf8_lossy(&target_raw).to_string();
|
||||
let (v_tx, mut v_rx) = mpsc::channel::<Bytes>(100);
|
||||
muxer.register_stream(stream_id, v_tx).await;
|
||||
|
||||
tokio::spawn(async move {
|
||||
info!(stream_id, target = %addr, "Attempting to connect to target");
|
||||
|
||||
match TcpStream::connect(&addr).await {
|
||||
Ok(stream) => {
|
||||
// Формируем SOCKS5 Success Reply используя структуру
|
||||
let mut reply_buf = BytesMut::with_capacity(10);
|
||||
let reply = SocksReply::ConnectResult {
|
||||
reply_code: REPLY_SUCCESS,
|
||||
atyp: ATYP_IPV4,
|
||||
addr: [0, 0, 0, 0],
|
||||
port: 0,
|
||||
};
|
||||
reply.write_to(&mut reply_buf);
|
||||
|
||||
// Отправляем подтверждение в сеть
|
||||
let _ = muxer
|
||||
.to_network
|
||||
.send(MuxMessage {
|
||||
stream_id,
|
||||
frame_type: FrameType::Connect,
|
||||
data: reply_buf.freeze(),
|
||||
})
|
||||
.await;
|
||||
|
||||
info!(stream_id, target = %addr, "Connected to target host, SOCKS reply sent");
|
||||
|
||||
let (mut r, mut w) = stream.into_split();
|
||||
let mut buf = BytesMut::with_capacity(8192);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Сеть -> Прокси -> Интернет (Запись в целевой хост)
|
||||
Some(data) = v_rx.recv() => {
|
||||
if data.is_empty() { break; } // EOF от локального клиента
|
||||
if let Err(e) = w.write_all(&data).await {
|
||||
warn!(stream_id, error = ?e, "Target write failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Интернет -> Прокси -> Сеть (Чтение из целевого хоста)
|
||||
res = r.read_buf(&mut buf) => {
|
||||
match res {
|
||||
Ok(0) => {
|
||||
debug!(stream_id, "Target host closed connection (EOF)");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
let msg = MuxMessage {
|
||||
stream_id,
|
||||
frame_type: FrameType::Data,
|
||||
data: buf.split().freeze(),
|
||||
};
|
||||
if muxer.to_network.send(msg).await.is_err() { break; }
|
||||
}
|
||||
Err(e) => {
|
||||
error!(stream_id, error = %e, "Target read error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(stream_id, target = %addr, error = %e, "Connection to target failed");
|
||||
|
||||
// Формируем SOCKS5 Failure Reply (0x01 - General failure)
|
||||
let mut reply_buf = BytesMut::with_capacity(10);
|
||||
let reply = SocksReply::ConnectResult {
|
||||
reply_code: 0x01,
|
||||
atyp: ATYP_IPV4,
|
||||
addr: [0, 0, 0, 0],
|
||||
port: 0,
|
||||
};
|
||||
reply.write_to(&mut reply_buf);
|
||||
|
||||
let _ = muxer
|
||||
.to_network
|
||||
.send(MuxMessage {
|
||||
stream_id,
|
||||
frame_type: FrameType::Connect,
|
||||
data: reply_buf.freeze(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
// Финализация: уведомляем сеть о закрытии и чистим муксер
|
||||
let _ = muxer
|
||||
.to_network
|
||||
.send(MuxMessage {
|
||||
stream_id,
|
||||
frame_type: FrameType::Close,
|
||||
data: Bytes::new(),
|
||||
})
|
||||
.await;
|
||||
|
||||
muxer.remove_stream(stream_id).await;
|
||||
info!(stream_id, "Server target handler closed");
|
||||
});
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use tokio::net::{
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
TcpStream,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
protocol::codec::codec::Codec,
|
||||
proxy::connection::{buf_pair::BufPair, state::ConnectionState},
|
||||
};
|
||||
#[async_trait]
|
||||
pub trait ProxyHandler {
|
||||
async fn init_session(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
) -> Result<ConnectionState, String>;
|
||||
async fn authorize_request(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
codec: &mut Codec,
|
||||
) -> Result<ConnectionState, String>;
|
||||
async fn exchange_data(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
codec: &mut Codec,
|
||||
target: &mut TcpStream,
|
||||
) -> Result<ConnectionState, String>;
|
||||
async fn finalize_session(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
) -> Result<ConnectionState, String>;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod handler;
|
||||
pub mod netr2tcp;
|
||||
pub mod tcp2netr;
|
||||
mod utils;
|
||||
@@ -1,130 +0,0 @@
|
||||
use crate::{
|
||||
protocol::codec::codec::Codec,
|
||||
proxy::connection::{
|
||||
buf_pair::BufPair,
|
||||
handler::{handler::ProxyHandler, utils::relay_data},
|
||||
state::ConnectionState,
|
||||
},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use bytes::BytesMut;
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
TcpStream,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Netr2Tcp;
|
||||
|
||||
impl Netr2Tcp {
|
||||
pub fn raw_addr_to_string(raw: &mut BytesMut) -> Result<String, String> {
|
||||
println!("len is {:?}", raw.len());
|
||||
if raw.is_empty() {
|
||||
return Err("Buffer is empty".into());
|
||||
}
|
||||
|
||||
let len = raw[0] as usize;
|
||||
|
||||
if raw.len() < 1 + len + 2 {
|
||||
return Err(format!(
|
||||
"Buffer too short: expected {}, got {}",
|
||||
1 + len + 2,
|
||||
raw.len()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let address = String::from_utf8_lossy(&raw[1..1 + len]);
|
||||
let port_start = 1 + len;
|
||||
let port = u16::from_be_bytes([raw[port_start], raw[port_start + 1]]);
|
||||
|
||||
Ok(format!("{}:{}", address, port))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProxyHandler for Netr2Tcp {
|
||||
async fn init_session(
|
||||
&self,
|
||||
_client_reader: &mut OwnedReadHalf,
|
||||
_client_writer: &mut OwnedWriteHalf,
|
||||
_buffers: &mut BufPair,
|
||||
) -> Result<ConnectionState, String> {
|
||||
Ok(ConnectionState::Handshake)
|
||||
}
|
||||
|
||||
async fn authorize_request(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
_client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
_codec: &mut Codec,
|
||||
) -> Result<ConnectionState, String> {
|
||||
buffers.read_from(client_reader).await?;
|
||||
|
||||
let header_len = if !buffers.read_buf.is_empty() {
|
||||
1 + (buffers.read_buf[0] as usize) + 2
|
||||
} else {
|
||||
return Err("Buffer is empty".into());
|
||||
};
|
||||
|
||||
let address_to_connect = Netr2Tcp::raw_addr_to_string(&mut buffers.read_buf)?;
|
||||
println!("Address is: {:?}", address_to_connect);
|
||||
|
||||
// Отрезаем заголовок, оставляя только данные приложения (TLS и т.д.)
|
||||
let _header = buffers.read_buf.split_to(header_len);
|
||||
|
||||
let target_stream = TcpStream::connect(address_to_connect)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(ConnectionState::Tunnel(target_stream))
|
||||
}
|
||||
|
||||
async fn exchange_data(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
_codec: &mut Codec,
|
||||
target: &mut TcpStream,
|
||||
) -> Result<ConnectionState, String> {
|
||||
let (mut target_reader, mut target_writer) = target.split();
|
||||
println!("Netr2Tcp Стартанул в тонель");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 1. От клиента к целевому серверу
|
||||
res = client_reader.read_buf(&mut buffers.read_buf) => {
|
||||
let should_break =
|
||||
relay_data(res, &mut target_writer, &mut buffers.read_buf).await?;
|
||||
if should_break { break; }
|
||||
}
|
||||
|
||||
// 2. От целевого сервера к клиенту
|
||||
res = target_reader.read_buf(&mut buffers.write_buf) => {
|
||||
let should_break =
|
||||
relay_data(res, client_writer, &mut buffers.write_buf).await?;
|
||||
if should_break { break;}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Cycles breaked");
|
||||
client_writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||
target_writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(ConnectionState::Close)
|
||||
}
|
||||
|
||||
async fn finalize_session(
|
||||
&self,
|
||||
_client_reader: &mut OwnedReadHalf,
|
||||
_client_writer: &mut OwnedWriteHalf,
|
||||
_buffers: &mut BufPair,
|
||||
) -> Result<ConnectionState, String> {
|
||||
Ok(ConnectionState::Disconnected)
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
use crate::{
|
||||
protocol::codec::codec::Codec,
|
||||
proxy::connection::{
|
||||
buf_pair::BufPair,
|
||||
handler::{handler::ProxyHandler, utils::relay_data},
|
||||
state::ConnectionState,
|
||||
},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use bytes::{BufMut, Bytes};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
TcpStream,
|
||||
},
|
||||
};
|
||||
|
||||
use bytes::BytesMut;
|
||||
|
||||
enum SocksMsg {
|
||||
Hello, // Hello (0x05, 0x00)
|
||||
AuthFailed, // Error (0x05, 0xFF)
|
||||
ConnectOk, // Connection is OK
|
||||
Custom(Vec<u8>), // If needed raw bytes
|
||||
}
|
||||
|
||||
impl SocksMsg {
|
||||
pub fn write_to(self, buf: &mut BytesMut) {
|
||||
buf.clear(); // Чистим перед записью ВСЕГДА
|
||||
match self {
|
||||
SocksMsg::Hello => {
|
||||
buf.put_slice(&[0x05, 0x00]);
|
||||
}
|
||||
SocksMsg::AuthFailed => {
|
||||
buf.put_slice(&[0x05, 0xFF]);
|
||||
}
|
||||
SocksMsg::ConnectOk => {
|
||||
// SOCKS5 требует 10 байт в ответ на CONNECT:
|
||||
// VER, REP(0), RSV, ATYP(1), ADDR(0,0,0,0), PORT(0,0)
|
||||
buf.put_slice(&[0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
SocksMsg::Custom(data) => {
|
||||
buf.put_slice(&data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tcp2Netr {
|
||||
socks5: bool,
|
||||
proxy_address: String,
|
||||
}
|
||||
|
||||
impl Tcp2Netr {
|
||||
pub fn new(socks5: bool, address: String) -> Self {
|
||||
Self {
|
||||
socks5,
|
||||
proxy_address: address,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_addr_raw(data: &mut BytesMut) -> Result<Bytes, String> {
|
||||
if data.len() < 4 {
|
||||
return Err("Too short".into());
|
||||
}
|
||||
|
||||
let atyp = data[3];
|
||||
let total_len = match atyp {
|
||||
0x01 => 10, // 4 (header) + 4 (ip) + 2 (port)
|
||||
0x03 => {
|
||||
let domain_len = data[4] as usize;
|
||||
4 + 1 + domain_len + 2 // 4 (header) + 1 (len) + N (domain) + 2 (port)
|
||||
}
|
||||
_ => return Err("Unsupported address type".to_string()),
|
||||
};
|
||||
|
||||
if data.len() < total_len {
|
||||
return Err("Incomplete SOCKS packet".into());
|
||||
}
|
||||
|
||||
// ВАЖНО: split_to удаляет эти байты из data и возвращает их нам
|
||||
// Теперь в data останется только TLS ClientHello!
|
||||
let socks_packet = data.split_to(total_len);
|
||||
|
||||
// Формируем твой кастомный адрес (длина + данные + порт)
|
||||
let mut result = BytesMut::new();
|
||||
if atyp == 0x01 {
|
||||
result.put_u8(4);
|
||||
result.put_slice(&socks_packet[4..10]);
|
||||
} else {
|
||||
let len = socks_packet[4];
|
||||
result.put_u8(len);
|
||||
result.put_slice(&socks_packet[5..total_len]);
|
||||
}
|
||||
|
||||
Ok(result.freeze())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ProxyHandler for Tcp2Netr {
|
||||
// 1. Инициализация: отвечаем SOCKS5 Hello (0x05, 0x00)
|
||||
async fn init_session(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
) -> Result<ConnectionState, String> {
|
||||
buffers.read_from(client_reader).await?;
|
||||
|
||||
SocksMsg::Hello.write_to(&mut buffers.write_buf);
|
||||
buffers.write_to(client_writer).await?;
|
||||
|
||||
Ok(ConnectionState::Handshake)
|
||||
}
|
||||
|
||||
// 2. Авторизация/Парсинг: достаем адрес из SOCKS5 Connect и подключаемся к Netr
|
||||
async fn authorize_request(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
_codec: &mut Codec,
|
||||
) -> Result<ConnectionState, String> {
|
||||
buffers.read_from(client_reader).await?;
|
||||
|
||||
// Извлекаем адрес и удаляем SOCKS-заголовок из буфера
|
||||
let address_to_connect = Tcp2Netr::get_addr_raw(&mut buffers.read_buf)?;
|
||||
|
||||
// Коннектимся к твоему Netr серверу (proxy_address)
|
||||
let mut target_stream = TcpStream::connect(&self.proxy_address)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Шлем кастомный заголовок адреса в сторону Netr
|
||||
target_stream
|
||||
.write_all(&address_to_connect)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Если в буфере остался TLS ClientHello (после split_to в get_addr_raw),
|
||||
// проталкиваем его немедленно, чтобы сервер не ждал
|
||||
if !buffers.read_buf.is_empty() {
|
||||
target_stream
|
||||
.write_all(&buffers.read_buf)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
buffers.read_buf.clear();
|
||||
}
|
||||
|
||||
// Отвечаем клиенту (браузеру), что SOCKS-соединение установлено
|
||||
SocksMsg::ConnectOk.write_to(&mut buffers.write_buf);
|
||||
buffers.write_to(client_writer).await?;
|
||||
|
||||
// Переходим в режим туннеля, передавая сокет до Netr-сервера
|
||||
Ok(ConnectionState::Tunnel(target_stream))
|
||||
}
|
||||
|
||||
// 3. Обмен данными: гоняем байты между клиентом и Netr-сервером
|
||||
async fn exchange_data(
|
||||
&self,
|
||||
client_reader: &mut OwnedReadHalf,
|
||||
client_writer: &mut OwnedWriteHalf,
|
||||
buffers: &mut BufPair,
|
||||
_codec: &mut Codec,
|
||||
target: &mut TcpStream,
|
||||
) -> Result<ConnectionState, String> {
|
||||
buffers.reset();
|
||||
let (mut target_reader, mut target_writer) = target.split();
|
||||
|
||||
println!("Socks2Netr Стартанул в тонель");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Из браузера -> в сторону Netr
|
||||
res = client_reader.read_buf(&mut buffers.read_buf) => {
|
||||
let should_break =
|
||||
relay_data(res, &mut target_writer, &mut buffers.read_buf).await?;
|
||||
if should_break { break;}
|
||||
}
|
||||
|
||||
// Из Netr -> обратно в браузер
|
||||
res = target_reader.read_buf(&mut buffers.write_buf) => {
|
||||
let should_break =
|
||||
relay_data(res, client_writer, &mut buffers.write_buf).await?;
|
||||
if should_break { break;}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client_writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||
target_writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(ConnectionState::Close)
|
||||
}
|
||||
|
||||
// 4. Финализация: логируем закрытие
|
||||
async fn finalize_session(
|
||||
&self,
|
||||
_client_reader: &mut OwnedReadHalf,
|
||||
_client_writer: &mut OwnedWriteHalf,
|
||||
_buffers: &mut BufPair,
|
||||
) -> Result<ConnectionState, String> {
|
||||
println!("SOCKS5 CONNECTION CLOSED");
|
||||
Ok(ConnectionState::Disconnected)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use std::io::Error;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
pub async fn relay_data<W>(
|
||||
res: Result<usize, Error>,
|
||||
writer: &mut W,
|
||||
buffer: &mut BytesMut,
|
||||
) -> Result<bool, String>
|
||||
where
|
||||
W: AsyncWriteExt + Unpin,
|
||||
{
|
||||
match res {
|
||||
Ok(0) => {
|
||||
println!("Read 0 bytes - closing half of connection");
|
||||
return Ok(true); // Это закроет туннель
|
||||
}
|
||||
Ok(n) => println!("Relayed {} bytes", n),
|
||||
Err(e) => println!("Relay error: {}", e),
|
||||
}
|
||||
|
||||
println!("What is here {:?}", &buffer);
|
||||
writer.write_buf(buffer).await.map_err(|e| e.to_string())?;
|
||||
Ok(false)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod buf_pair;
|
||||
pub mod connection;
|
||||
pub mod engine;
|
||||
pub mod handler;
|
||||
pub mod state;
|
||||
pub mod muxer;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
use crate::protocol::codec::frame::FrameType;
|
||||
use bytes::Bytes;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::error;
|
||||
|
||||
pub struct IdGenerator {
|
||||
counter: AtomicU32,
|
||||
}
|
||||
|
||||
impl IdGenerator {
|
||||
pub fn new(is_client: bool) -> Self {
|
||||
let start = if is_client { 1 } else { 2 };
|
||||
Self {
|
||||
counter: AtomicU32::new(start),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> u32 {
|
||||
self.counter.fetch_add(2, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MuxMessage {
|
||||
pub stream_id: u32,
|
||||
pub frame_type: FrameType,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Muxer {
|
||||
pub to_network: Sender<MuxMessage>,
|
||||
streams: Arc<RwLock<HashMap<u32, Sender<Bytes>>>>,
|
||||
id_gen: Arc<IdGenerator>,
|
||||
}
|
||||
|
||||
impl Muxer {
|
||||
pub fn new(to_network: Sender<MuxMessage>, is_client: bool) -> Self {
|
||||
Self {
|
||||
to_network,
|
||||
streams: Arc::new(RwLock::new(HashMap::new())),
|
||||
id_gen: Arc::new(IdGenerator::new(is_client)),
|
||||
}
|
||||
}
|
||||
|
||||
// Прокси-метод для получения ID
|
||||
pub fn next_id(&self) -> u32 {
|
||||
self.id_gen.next()
|
||||
}
|
||||
|
||||
pub async fn register_stream(&self, stream_id: u32, tx: Sender<Bytes>) {
|
||||
let mut lock = self.streams.write().await;
|
||||
lock.insert(stream_id, tx);
|
||||
// ДОБАВЬ ЭТО:
|
||||
tracing::debug!(
|
||||
stream_id,
|
||||
total_active = lock.len(),
|
||||
"STREAMS_MAP_UPDATE: Registered new stream"
|
||||
);
|
||||
}
|
||||
pub async fn remove_stream(&self, stream_id: u32) {
|
||||
self.streams.write().await.remove(&stream_id);
|
||||
}
|
||||
|
||||
/// Отправляет входящие данные конкретному локальному обработчику
|
||||
pub async fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
|
||||
// Асинхронный лок сам умеет корректно работать с .await
|
||||
let tx = self.streams.read().await.get(&stream_id).cloned();
|
||||
|
||||
if let Some(tx) = tx {
|
||||
if tx.send(data).await.is_err() {
|
||||
self.remove_stream(stream_id).await;
|
||||
}
|
||||
} else {
|
||||
error!(stream_id, "MUXER: Received data for UNKNOWN stream_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
//todo split to codec that uses my frame and tls codec that remove camouflage
|
||||
pub enum ConnectionState {
|
||||
New,
|
||||
Handshake,
|
||||
Tunnel(TcpStream),
|
||||
Close,
|
||||
Disconnected,
|
||||
}
|
||||
+144
-45
@@ -1,65 +1,164 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::proxy::connection::{
|
||||
connection::Connection, handler::handler::ProxyHandler, state::ConnectionState,
|
||||
use crate::{
|
||||
protocol::errors::ErrorAction,
|
||||
proxy::connection::{
|
||||
connection::{Connection, ConnectionRole},
|
||||
engine::TunnelEngine,
|
||||
muxer::{MuxMessage, Muxer},
|
||||
},
|
||||
tlseng::profile::BrowserProfile,
|
||||
};
|
||||
use tokio::net::TcpListener;
|
||||
use bytes::BytesMut;
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
use tracing::{debug, error, info, instrument}; // Импортируем макросы
|
||||
|
||||
pub struct Network {
|
||||
inbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||
outbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||
port: u16,
|
||||
role: ConnectionRole,
|
||||
remote_proxy_addr: Option<String>,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn new(
|
||||
inbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||
outbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||
port: u16,
|
||||
) -> Self {
|
||||
pub fn new(port: u16, role: ConnectionRole, remote_proxy_addr: Option<String>) -> Self {
|
||||
Self {
|
||||
inbound_handler,
|
||||
outbound_handler,
|
||||
port,
|
||||
role,
|
||||
remote_proxy_addr,
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем инструмент, чтобы видеть параметры запуска сети в логах
|
||||
#[instrument(skip(self), fields(role = ?self.role, port = self.port))]
|
||||
pub async fn run(&self) {
|
||||
let port = self.port;
|
||||
let listener: TcpListener = TcpListener::bind(format!("127.0.0.1:{port}"))
|
||||
.await
|
||||
.unwrap();
|
||||
println!("Server Listening on Port {}...", port);
|
||||
loop {
|
||||
let (stream, addr) = listener.accept().await.expect("Error on connection");
|
||||
let handler = self.inbound_handler.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut conection: Connection = Connection::new(stream, addr, false);
|
||||
let addr = format!("127.0.0.1:{}", self.port);
|
||||
|
||||
let res = conection.handle(handler).await;
|
||||
match res {
|
||||
Ok(state) => match state {
|
||||
ConnectionState::New => {
|
||||
println!("New connection {}", addr)
|
||||
}
|
||||
ConnectionState::Handshake => {
|
||||
println!("Connection {} handshaked", addr)
|
||||
}
|
||||
ConnectionState::Tunnel(_stream) => {
|
||||
println!("Connection {} tunnel", addr)
|
||||
}
|
||||
ConnectionState::Close => {
|
||||
println!("Connection {} closed", addr)
|
||||
}
|
||||
ConnectionState::Disconnected => {
|
||||
println!("Connection {} Disconnected", addr)
|
||||
}
|
||||
},
|
||||
match self.role {
|
||||
ConnectionRole::Client => {
|
||||
// --- ЛОГИКА КЛИЕНТА ---
|
||||
// 1. Создаем ОДИН туннель до прокси-сервера при старте
|
||||
info!("Starting Client mode: Initializing persistent tunnel to proxy...");
|
||||
|
||||
let muxer = match self.initialize_client_tunnel().await {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("Ошибка соединения с {}: {}", conection.addr, e);
|
||||
error!(error = %e, "Failed to initialize global tunnel. Exiting.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// 2. Теперь слушаем SOCKS-запросы от браузера
|
||||
let listener = TcpListener::bind(&addr)
|
||||
.await
|
||||
.expect("Failed to bind SOCKS port");
|
||||
info!(socks_addr = %addr, "SOCKS5 server ready for browser connections");
|
||||
|
||||
loop {
|
||||
if let Ok((stream, client_addr)) = listener.accept().await {
|
||||
let current_muxer = muxer.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Используем новый метод handle_socks_client
|
||||
let connection = Connection::new(stream, client_addr, false);
|
||||
if let Err(e) = connection.handle_socks_client(current_muxer).await {
|
||||
error!(client = %client_addr, error = %e, "SOCKS stream error");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionRole::Server => {
|
||||
// --- ЛОГИКА СЕРВЕРА ---
|
||||
let listener = TcpListener::bind(&addr)
|
||||
.await
|
||||
.expect("Failed to bind Server port");
|
||||
info!(listen_addr = %addr, "Proxy Server listening for incoming tunnels");
|
||||
|
||||
loop {
|
||||
if let Ok((stream, client_addr)) = listener.accept().await {
|
||||
tokio::spawn(async move {
|
||||
// Сервер использует handle_server_tunnel
|
||||
let connection = Connection::new(stream, client_addr, true);
|
||||
if let Err(e) = connection.handle_server_tunnel().await {
|
||||
error!(client = %client_addr, error = %e, "Tunnel error");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Вспомогательный метод для Клиента: создает TLS туннель и запускает TunnelEngine
|
||||
async fn initialize_client_tunnel(&self) -> Result<Muxer, String> {
|
||||
let server_addr = self
|
||||
.remote_proxy_addr
|
||||
.as_ref()
|
||||
.ok_or("Remote proxy address not configured")?;
|
||||
|
||||
// 1. Устанавливаем TCP соединение с сервером
|
||||
let stream = TcpStream::connect(server_addr)
|
||||
.await
|
||||
.map_err(|e| format!("Connect to proxy failed: {}", e))?;
|
||||
|
||||
// 2. Создаем временный Connection
|
||||
let dummy_addr = server_addr.parse().unwrap_or("0.0.0.0:0".parse().unwrap());
|
||||
// Передаем stream (Connection сам сделает into_split внутри, если у тебя так написано в new)
|
||||
let mut conn = Connection::new(stream, dummy_addr, false);
|
||||
|
||||
// 3. TLS Handshake (Клиентская часть)
|
||||
debug!("Starting persistent TLS handshake with proxy");
|
||||
let ch = conn
|
||||
.codec
|
||||
.make_client_handshake(&BrowserProfile::CHROME_131, "proxy.server")
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
|
||||
conn.outbound
|
||||
.write_all(&ch)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut sh_buf = BytesMut::with_capacity(2048);
|
||||
while let Err(e) = conn.codec.process_handshake(&mut sh_buf) {
|
||||
if e.action != ErrorAction::Wait {
|
||||
return Err(format!("Fatal handshake error: {:?}", e));
|
||||
}
|
||||
// Теперь read_buf найдется, так как мы импортировали AsyncReadExt
|
||||
let n = conn
|
||||
.inbound
|
||||
.read_buf(&mut sh_buf)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
if n == 0 {
|
||||
return Err("Server closed connection during handshake".into());
|
||||
}
|
||||
}
|
||||
info!("Persistent TLS Tunnel established successfully!");
|
||||
|
||||
// 4. Инициализируем Muxer и TunnelEngine
|
||||
// Явно указываем тип сообщения для канала, чтобы убрать "cannot infer type"
|
||||
let (mux_tx, mux_rx) = tokio::sync::mpsc::channel::<MuxMessage>(1024);
|
||||
let muxer = Muxer::new(mux_tx, true);
|
||||
|
||||
// 5. Запускаем TunnelEngine в фоне.
|
||||
let engine = TunnelEngine {
|
||||
inbound: conn.inbound,
|
||||
outbound: conn.outbound,
|
||||
codec: conn.codec,
|
||||
buffers: conn.buffers,
|
||||
mux_rx,
|
||||
muxer: muxer.clone(),
|
||||
role: ConnectionRole::Client,
|
||||
};
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = engine.run().await {
|
||||
error!("Main TunnelEngine died: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(muxer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::tlseng::{
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, ProtocolVersion},
|
||||
};
|
||||
|
||||
pub struct ApplicationData {
|
||||
pub len: usize,
|
||||
pub payload: Bytes,
|
||||
}
|
||||
|
||||
impl ApplicationData {
|
||||
pub fn make_application_data(bytes: &mut BytesMut) -> Bytes {
|
||||
let record = TlsRecord::new(
|
||||
ContentType::ApplicationData,
|
||||
ProtocolVersion::Tls12,
|
||||
bytes.split_to(bytes.len()).freeze(),
|
||||
);
|
||||
record.serialize()
|
||||
}
|
||||
}
|
||||
+13
-39
@@ -1,48 +1,22 @@
|
||||
// --- Core Handshake Identifiers ---
|
||||
/// ClientHello handshake message type
|
||||
/// Handshake message types
|
||||
pub const HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
|
||||
pub const HANDSHAKE_TYPE_SERVER_HELLO: u8 = 0x02;
|
||||
|
||||
// --- TLS Extension Type Codes (IANA) ---
|
||||
/// Server Name Indication (SNI) - maps a hostname to the IP
|
||||
pub const EXT_TYPE_SNI: u16 = 0x0000;
|
||||
/// Certificate Status Request (OCSP Stapling)
|
||||
pub const EXT_STATUS_REQUEST: u16 = 0x0005;
|
||||
/// Supported Elliptic Curves (Named Groups)
|
||||
pub const EXT_SUPPORTED_GROUPS: u16 = 0x000a;
|
||||
/// Supported Point Formats (Legacy, but required for compatibility)
|
||||
pub const EXT_EC_POINT_FORMATS: u16 = 0x000b;
|
||||
/// Signature Algorithms the client can verify
|
||||
pub const EXT_SIGNATURE_ALGORITHMS: u16 = 0x000d;
|
||||
/// Application-Layer Protocol Negotiation (h2, http/1.1)
|
||||
pub const EXT_ALPN: u16 = 0x0010;
|
||||
/// Signed Certificate Timestamp (SCT) - used for Certificate Transparency
|
||||
pub const EXT_SIGNED_CERT_TIMESTAMP: u16 = 0x0012;
|
||||
/// Padding extension to avoid MTU issues or fingerprinting
|
||||
pub const EXT_PADDING: u16 = 0x0015;
|
||||
/// Extended Master Secret - prevents MITM key synchronization attacks
|
||||
pub const EXT_EXTENDED_MASTER_SECRET: u16 = 0x0017;
|
||||
/// Certificate Compression (Used by modern browsers like Chrome)
|
||||
pub const EXT_COMPRESS_CERTIFICATE: u16 = 0x001b;
|
||||
/// Delegated Credentials (RFC 9345)
|
||||
pub const EXT_DELEGATED_CREDENTIAL: u16 = 0x0022;
|
||||
///SESSION TICKET
|
||||
pub const EXT_SESSION_TICKET: u16 = 0x0023;
|
||||
/// Negotiated TLS Versions (Crucial for TLS 1.3)
|
||||
pub const EXT_SUPPORTED_VERSIONS: u16 = 0x002b;
|
||||
/// Pre-Shared Key (PSK) Exchange Modes
|
||||
pub const EXT_PSK_KEY_EXCHANGE_MODES: u16 = 0x002d;
|
||||
/// Key Share - carries the Diffie-Hellman public keys
|
||||
pub const EXT_KEY_SHARE: u16 = 0x0033;
|
||||
/// Application Settings (ALPS) - Chrome specific protocol settings
|
||||
pub const EXT_ALPS: u16 = 0x44cd;
|
||||
/// SNI (Server Name Indication) specific
|
||||
pub const TYPE_HOST_NAME: u8 = 0x00;
|
||||
|
||||
// --- Compatibility & Anti-Detection (Fingerprinting) ---
|
||||
/// Secure Renegotiation Indication (RFC 5746)
|
||||
pub const EXT_RENEGOTIATION_INFO: u16 = 0xff01;
|
||||
/// PSK (Pre-Shared Key) modes
|
||||
pub const PSK_DHE_KE_MODE: u8 = 0x01;
|
||||
|
||||
/// Certificate compression algorithms
|
||||
pub const CERT_COMPRESSION_BROTLI: u16 = 0x0002;
|
||||
|
||||
/// Extension internal status types
|
||||
pub const OCSP_STATUS_TYPE: u8 = 0x01;
|
||||
//pub const EC_POINT_FORMAT_UNCOMPRESSED: u8 = 0x00;
|
||||
|
||||
/// GREASE (Generate Random Extensions And Sustain Extensibility)
|
||||
/// Used to prevent server bugs where unknown extensions cause failures.
|
||||
/// Используется для предотвращения ошибок серверов при встрече с неизвестными ID.
|
||||
pub const GREASE_IDENTIFIERS: [u16; 16] = [
|
||||
0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA,
|
||||
0xCACA, 0xDADA, 0xEAEA, 0xFAFA,
|
||||
|
||||
+142
-138
@@ -1,12 +1,14 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use rand::Rng;
|
||||
use rand::RngExt;
|
||||
|
||||
// Using your provided constants and types
|
||||
use crate::tlseng::{
|
||||
consts::*,
|
||||
params::{TlsGroups, TlsSignatures, TlsVersions},
|
||||
profile::profile::BrowserProfile,
|
||||
values::*,
|
||||
consts::{
|
||||
CERT_COMPRESSION_BROTLI, GREASE_IDENTIFIERS, OCSP_STATUS_TYPE, PSK_DHE_KE_MODE,
|
||||
TYPE_HOST_NAME,
|
||||
},
|
||||
profile::BrowserProfile,
|
||||
types::{TlsExtensions, TlsGroups, TlsSignatures, TlsVersions},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -21,7 +23,26 @@ pub struct ExtensionStack {
|
||||
pub extensions: Vec<Extension>,
|
||||
}
|
||||
|
||||
impl ExtensionStack {
|
||||
pub fn find_by_type(&self, etype: u16) -> Option<Bytes> {
|
||||
self.extensions
|
||||
.iter()
|
||||
.find(|e| e.etype == etype)
|
||||
.map(|e| e.data.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
/// Creates a new Extension from the given parameters.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `etype`: The type of extension (e.g., EXT_SUPPORTED_VERSIONS).
|
||||
/// * `data`: The actual data being transported (e.g., a serialized list of supported versions).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new Extension structure with the given parameters.
|
||||
pub fn new(etype: u16, data: Bytes) -> Self {
|
||||
Self {
|
||||
etype,
|
||||
@@ -29,6 +50,16 @@ impl Extension {
|
||||
data,
|
||||
}
|
||||
}
|
||||
/// Packs an extension into a single byte array.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `etype`: The type of extension (e.g., EXT_SUPPORTED_VERSIONS).
|
||||
/// * `data`: The actual data being transported (e.g., a serialized list of supported versions).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A single byte array containing the type and length of the extension, followed by the actual extension data.
|
||||
pub fn pack(etype: u16, data: &[u8]) -> Bytes {
|
||||
let mut ext = BytesMut::with_capacity(4 + data.len());
|
||||
ext.put_u16(etype);
|
||||
@@ -49,26 +80,18 @@ impl ExtensionBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal helper to pack and append an extension.
|
||||
fn add_extension(&mut self, etype: u16, data: &[u8]) {
|
||||
let ext = Extension::pack(etype, data);
|
||||
self.payload.put_slice(&ext);
|
||||
}
|
||||
|
||||
/// 0x?a?a - Randomized GREASE
|
||||
pub fn grease(&mut self) {
|
||||
let mut rng = rand::rng();
|
||||
let rnd = Rng::next_u32(&mut rng) % 16;
|
||||
let etype = GREASE_IDENTIFIERS[rnd as usize];
|
||||
let rnd = rng.random_range(0..GREASE_IDENTIFIERS.len());
|
||||
let etype = GREASE_IDENTIFIERS[rnd];
|
||||
self.add_extension(etype, &[]);
|
||||
}
|
||||
|
||||
/// Used for exact hex-matching of GREASE values
|
||||
pub fn grease_fixed(&mut self, etype: u16) {
|
||||
self.add_extension(etype, &[]);
|
||||
}
|
||||
|
||||
/// 0x0000 - SNI
|
||||
pub fn server_name(&mut self, host: &str) {
|
||||
let host_bytes = host.as_bytes();
|
||||
let host_len = host_bytes.len() as u16;
|
||||
@@ -80,36 +103,49 @@ impl ExtensionBuilder {
|
||||
data.put_u16(host_len);
|
||||
data.put_slice(host_bytes);
|
||||
|
||||
self.add_extension(EXT_TYPE_SNI, &data);
|
||||
self.add_extension(TlsExtensions::SNI, &data);
|
||||
}
|
||||
|
||||
/// 0x0017 - Extended Master Secret
|
||||
pub fn extended_main_secret(&mut self) {
|
||||
self.add_extension(EXT_EXTENDED_MASTER_SECRET, &[]);
|
||||
self.add_extension(TlsExtensions::EMS, &[]);
|
||||
}
|
||||
|
||||
/// 0x000a - Supported Groups
|
||||
pub fn supported_groups(&mut self, groups: TlsGroups) {
|
||||
let mut data = BytesMut::with_capacity(2 + groups.0.len() * 2);
|
||||
data.put_u16((groups.0.len() * 2) as u16);
|
||||
for &g in groups.0 {
|
||||
data.put_u16(g);
|
||||
}
|
||||
self.add_extension(EXT_SUPPORTED_GROUPS, &data);
|
||||
self.add_extension(TlsExtensions::SUPPORTED_GROUPS, &data);
|
||||
}
|
||||
|
||||
/// 0x000d - Signature Algorithms
|
||||
pub fn signature_algorithms(&mut self, algs: TlsSignatures) {
|
||||
let mut data = BytesMut::with_capacity(2 + algs.0.len() * 2);
|
||||
data.put_u16((algs.0.len() * 2) as u16);
|
||||
for &a in algs.0 {
|
||||
data.put_u16(a);
|
||||
}
|
||||
self.add_extension(EXT_SIGNATURE_ALGORITHMS, &data);
|
||||
self.add_extension(TlsExtensions::SIGNATURE_ALGORITHMS, &data);
|
||||
}
|
||||
|
||||
pub fn supported_versions(&mut self, versions: TlsVersions) {
|
||||
let mut data = BytesMut::with_capacity(1 + versions.0.len() * 2);
|
||||
data.put_u8((versions.0.len() * 2) as u8);
|
||||
for &v in versions.0 {
|
||||
data.put_u16(v);
|
||||
}
|
||||
self.add_extension(TlsExtensions::SUPPORTED_VERSIONS, &data);
|
||||
}
|
||||
|
||||
pub fn key_share(&mut self, pub_key: &[u8]) {
|
||||
let mut data = BytesMut::with_capacity(38);
|
||||
data.put_u16(34); // Total Key Share List Length
|
||||
data.put_u16(TlsGroups::X25519);
|
||||
data.put_u16(32); // Public Key length
|
||||
data.put_slice(pub_key);
|
||||
self.add_extension(TlsExtensions::KEY_SHARE, &data);
|
||||
}
|
||||
|
||||
/// 0x44cd - ALPS (Application Settings)
|
||||
/// Updated to support specific protocols
|
||||
pub fn application_settings(&mut self, protocols: &[&str]) {
|
||||
let mut data = BytesMut::new();
|
||||
for proto in protocols {
|
||||
@@ -118,81 +154,9 @@ impl ExtensionBuilder {
|
||||
data.put_slice(p_bytes);
|
||||
data.put_u16(0); // Empty settings per-protocol
|
||||
}
|
||||
self.add_extension(EXT_ALPS, &data);
|
||||
self.add_extension(TlsExtensions::ALPS, &data);
|
||||
}
|
||||
|
||||
/// 0x002b - Supported Versions
|
||||
pub fn supported_versions(&mut self, versions: TlsVersions) {
|
||||
let mut data = BytesMut::with_capacity(1 + versions.0.len() * 2);
|
||||
data.put_u8((versions.0.len() * 2) as u8);
|
||||
for &v in versions.0 {
|
||||
data.put_u16(v);
|
||||
}
|
||||
self.add_extension(EXT_SUPPORTED_VERSIONS, &data);
|
||||
}
|
||||
|
||||
/// 0x002d - PSK Key Exchange Modes
|
||||
pub fn psk_key_exchange_modes(&mut self) {
|
||||
let mut data = BytesMut::with_capacity(2);
|
||||
data.put_u8(1);
|
||||
data.put_u8(PSK_DHE_KE_MODE);
|
||||
self.add_extension(EXT_PSK_KEY_EXCHANGE_MODES, &data);
|
||||
}
|
||||
|
||||
/// 0x001b - Certificate Compression
|
||||
pub fn compress_certificate(&mut self, algorithms: &[u16]) {
|
||||
let mut data = BytesMut::with_capacity(1 + algorithms.len() * 2);
|
||||
data.put_u8((algorithms.len() * 2) as u8);
|
||||
for &alg in algorithms {
|
||||
data.put_u16(alg);
|
||||
}
|
||||
self.add_extension(EXT_COMPRESS_CERTIFICATE, &data);
|
||||
}
|
||||
|
||||
/// 0x0005 - Status Request
|
||||
pub fn status_request(&mut self) {
|
||||
let mut data = BytesMut::with_capacity(5);
|
||||
data.put_u8(OCSP_STATUS_TYPE);
|
||||
data.put_u16(0); // responder_id_list
|
||||
data.put_u16(0); // request_extensions
|
||||
self.add_extension(EXT_STATUS_REQUEST, &data);
|
||||
}
|
||||
|
||||
/// 0x0033 - Key Share
|
||||
/// Corrected: ClientHello KeyShare has a list length AND a group/key length
|
||||
pub fn key_share(&mut self, public_key: &[u8]) {
|
||||
let mut data = BytesMut::with_capacity(38);
|
||||
data.put_u16(34); // Total Key Share List Length
|
||||
data.put_u16(GROUP_X25519);
|
||||
data.put_u16(32); // Public Key length
|
||||
data.put_slice(public_key);
|
||||
self.add_extension(EXT_KEY_SHARE, &data);
|
||||
}
|
||||
|
||||
/// 0x000b - EC Point Formats
|
||||
pub fn ec_point_formats(&mut self) {
|
||||
let mut data = BytesMut::with_capacity(2);
|
||||
data.put_u8(1);
|
||||
data.put_u8(EC_POINT_FORMAT_UNCOMPRESSED);
|
||||
self.add_extension(EXT_EC_POINT_FORMATS, &data);
|
||||
}
|
||||
|
||||
/// 0x0012 - SCT
|
||||
pub fn signed_certificate_timestamp(&mut self) {
|
||||
self.add_extension(EXT_SIGNED_CERT_TIMESTAMP, &[]);
|
||||
}
|
||||
|
||||
/// 0x0022 - Delegated Credentials
|
||||
pub fn delegated_credential(&mut self, algs: TlsSignatures) {
|
||||
let mut data = BytesMut::with_capacity(2 + algs.0.len() * 2);
|
||||
data.put_u16((algs.0.len() * 2) as u16);
|
||||
for &a in algs.0 {
|
||||
data.put_u16(a);
|
||||
}
|
||||
self.add_extension(EXT_DELEGATED_CREDENTIAL, &data);
|
||||
}
|
||||
|
||||
/// 0x0010 - ALPN
|
||||
pub fn alpn(&mut self, protocols: &[&str]) {
|
||||
let mut list_data = BytesMut::new();
|
||||
for proto in protocols {
|
||||
@@ -203,70 +167,110 @@ impl ExtensionBuilder {
|
||||
let mut extension_data = BytesMut::new();
|
||||
extension_data.put_u16(list_data.len() as u16);
|
||||
extension_data.put_slice(&list_data);
|
||||
self.add_extension(EXT_ALPN, &extension_data);
|
||||
self.add_extension(TlsExtensions::ALPN, &extension_data);
|
||||
}
|
||||
|
||||
pub fn psk_key_exchange_modes(&mut self) {
|
||||
let mut data = BytesMut::with_capacity(2);
|
||||
data.put_u8(1);
|
||||
data.put_u8(PSK_DHE_KE_MODE);
|
||||
self.add_extension(TlsExtensions::PSK_MODES, &data);
|
||||
}
|
||||
|
||||
pub fn compress_certificate(&mut self, algorithms: &[u16]) {
|
||||
let mut data = BytesMut::with_capacity(1 + algorithms.len() * 2);
|
||||
data.put_u8((algorithms.len() * 2) as u8);
|
||||
for &alg in algorithms {
|
||||
data.put_u16(alg);
|
||||
}
|
||||
self.add_extension(TlsExtensions::COMPRESS_CERT, &data);
|
||||
}
|
||||
|
||||
pub fn status_request(&mut self) {
|
||||
let mut data = BytesMut::with_capacity(5);
|
||||
data.put_u8(OCSP_STATUS_TYPE);
|
||||
data.put_u16(0); // responder_id_list
|
||||
data.put_u16(0); // request_extensions
|
||||
self.add_extension(TlsExtensions::STATUS_REQUEST, &data);
|
||||
}
|
||||
|
||||
pub fn ec_point_formats(&mut self) {
|
||||
let mut data = BytesMut::with_capacity(2);
|
||||
data.put_u8(1);
|
||||
data.put_u8(0x00); // Uncompressed
|
||||
self.add_extension(TlsExtensions::EC_POINT_FORMATS, &data);
|
||||
}
|
||||
|
||||
pub fn signed_certificate_timestamp(&mut self) {
|
||||
self.add_extension(TlsExtensions::SCT, &[]);
|
||||
}
|
||||
|
||||
pub fn delegated_credential(&mut self, algs: TlsSignatures) {
|
||||
let mut data = BytesMut::with_capacity(2 + algs.0.len() * 2);
|
||||
data.put_u16((algs.0.len() * 2) as u16);
|
||||
for &a in algs.0 {
|
||||
data.put_u16(a);
|
||||
}
|
||||
self.add_extension(TlsExtensions::DELEGATED_CREDENTIAL, &data);
|
||||
}
|
||||
|
||||
/// 0x0023 - Session Ticket
|
||||
pub fn session_ticket(&mut self) {
|
||||
self.add_extension(0x0023, &[]);
|
||||
self.add_extension(TlsExtensions::SESSION_TICKET, &[]);
|
||||
}
|
||||
|
||||
pub fn renegotiation_info(&mut self) {
|
||||
self.add_extension(TlsExtensions::RENEGOTIATION_INFO, &[0x00]);
|
||||
}
|
||||
|
||||
pub fn padding(&mut self, target_size: usize) {
|
||||
// Текущий размер накопленной нагрузки
|
||||
let current_size = self.payload.len();
|
||||
|
||||
// 4 байта резервируем под заголовок самого расширения (Type + Length)
|
||||
if target_size > current_size + 4 {
|
||||
let pad_len = target_size - current_size - 4;
|
||||
let data = vec![0u8; pad_len];
|
||||
|
||||
// Используем pack, как и в других методах
|
||||
let ext = Extension::pack(EXT_PADDING, &data);
|
||||
self.payload.put_slice(&ext);
|
||||
self.add_extension(TlsExtensions::PADDING, &data);
|
||||
}
|
||||
}
|
||||
|
||||
/// 0xff01 - Renegotiation Info
|
||||
pub fn renegotiation_info(&mut self) {
|
||||
self.add_extension(EXT_RENEGOTIATION_INFO, &[0x00]);
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Bytes {
|
||||
self.payload.split().freeze()
|
||||
}
|
||||
|
||||
pub fn apply_profile(&mut self, profile: &BrowserProfile, host: &str, pub_key: &[u8]) {
|
||||
for &ext_id in profile.extension_order {
|
||||
for &ext_id in &profile.extension_order {
|
||||
match ext_id {
|
||||
0x0000 => self.server_name(host),
|
||||
0x000a => self.supported_groups(profile.groups),
|
||||
0x000d => self.signature_algorithms(profile.signatures),
|
||||
0x0010 => self.alpn(&["h2", "http/1.1"]),
|
||||
0x0012 => self.signed_certificate_timestamp(),
|
||||
0x0017 => self.extended_main_secret(),
|
||||
0x001b => self.compress_certificate(&[CERT_COMPRESSION_BROTLI]),
|
||||
0x0022 => self.delegated_credential(profile.delegated_signatures),
|
||||
0x0023 => self.session_ticket(),
|
||||
0x002b => self.supported_versions(profile.versions),
|
||||
0x002d => self.psk_key_exchange_modes(),
|
||||
0x0033 => self.key_share(pub_key),
|
||||
0x44cd => self.application_settings(&["h2"]),
|
||||
0x0005 => self.status_request(),
|
||||
0x000b => self.ec_point_formats(),
|
||||
0xff01 => self.renegotiation_info(),
|
||||
// Padding logic
|
||||
0x0015 => {
|
||||
TlsExtensions::SNI => self.server_name(host),
|
||||
TlsExtensions::SUPPORTED_GROUPS => self.supported_groups(profile.groups),
|
||||
TlsExtensions::SIGNATURE_ALGORITHMS => {
|
||||
self.signature_algorithms(profile.signatures)
|
||||
}
|
||||
TlsExtensions::ALPN => self.alpn(profile.alpn),
|
||||
TlsExtensions::SCT => self.signed_certificate_timestamp(),
|
||||
TlsExtensions::EMS => self.extended_main_secret(),
|
||||
TlsExtensions::COMPRESS_CERT => {
|
||||
self.compress_certificate(&[CERT_COMPRESSION_BROTLI])
|
||||
}
|
||||
TlsExtensions::DELEGATED_CREDENTIAL => {
|
||||
self.delegated_credential(profile.delegated_signatures)
|
||||
}
|
||||
TlsExtensions::SESSION_TICKET => self.session_ticket(),
|
||||
TlsExtensions::SUPPORTED_VERSIONS => self.supported_versions(profile.versions),
|
||||
TlsExtensions::PSK_MODES => self.psk_key_exchange_modes(),
|
||||
TlsExtensions::KEY_SHARE => self.key_share(pub_key),
|
||||
TlsExtensions::ALPS => self.application_settings(&["h2"]),
|
||||
TlsExtensions::STATUS_REQUEST => self.status_request(),
|
||||
TlsExtensions::EC_POINT_FORMATS => self.ec_point_formats(),
|
||||
TlsExtensions::RENEGOTIATION_INFO => self.renegotiation_info(),
|
||||
TlsExtensions::PADDING => {
|
||||
if profile.is_chromium {
|
||||
// Standard Chromium behavior: pad to 512 bytes
|
||||
self.padding(512);
|
||||
} else {
|
||||
// Non-chromium might use different logic or no padding
|
||||
self.add_extension(0x0015, &[]);
|
||||
self.add_extension(TlsExtensions::PADDING, &[]);
|
||||
}
|
||||
}
|
||||
// Обработка GREASE по маске
|
||||
id if (id & 0x0f0f) == 0x0a0a => self.grease(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Bytes {
|
||||
self.payload.split().freeze()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
use aead::rand_core::RngCore;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
tlseng::{
|
||||
consts::{HANDSHAKE_TYPE_CLIENT_HELLO, HANDSHAKE_TYPE_SERVER_HELLO},
|
||||
extension::ExtensionBuilder,
|
||||
profile::BrowserProfile,
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, HelloType, ProtocolVersion},
|
||||
},
|
||||
utils::u24::U24,
|
||||
};
|
||||
|
||||
pub struct HelloHeader {
|
||||
pub header_type: HelloType,
|
||||
pub len: U24,
|
||||
}
|
||||
|
||||
pub struct ClientHello {
|
||||
/// The maximum version supported (legacy field in TLS 1.3)
|
||||
pub version: ProtocolVersion,
|
||||
/// 32 bytes of client-generated entropy
|
||||
pub random: [u8; 32],
|
||||
/// Legacy session ID (used in TLS 1.3 for middlebox compatibility)
|
||||
pub session_id: Bytes,
|
||||
/// List of cryptographic ciphers the client supports
|
||||
pub cipher_suites: Vec<u16>,
|
||||
/// Opaque block of extensions generated by ExtensionBuilder
|
||||
pub extensions: Bytes,
|
||||
}
|
||||
|
||||
impl ClientHello {
|
||||
/// Serializes the ClientHello message into its wire format.
|
||||
/// Includes the 4-byte Handshake header (Type + Length).
|
||||
pub fn serialize(&self) -> Bytes {
|
||||
let mut buf = BytesMut::with_capacity(512 + self.extensions.len());
|
||||
|
||||
// Handshake Type: 0x01 (ClientHello)
|
||||
buf.put_u8(HANDSHAKE_TYPE_CLIENT_HELLO);
|
||||
|
||||
// Handshake Length Placeholder:
|
||||
// Handshake messages use a 24-bit (3 byte) length field.
|
||||
let length_pos = buf.len();
|
||||
buf.put_bytes(0, 3);
|
||||
|
||||
// Protocol Version:
|
||||
// For TLS 1.3, this is traditionally pinned to 0x0303 (TLS 1.2)
|
||||
// to prevent middleboxes from dropping the packet.
|
||||
buf.put_u16(0x0303);
|
||||
buf.put_slice(&self.random);
|
||||
|
||||
// Legacy Session ID:
|
||||
// Formatted as Length (1 byte) + ID bytes.
|
||||
buf.put_u8(self.session_id.len() as u8);
|
||||
buf.put_slice(&self.session_id);
|
||||
|
||||
// Cipher Suites:
|
||||
// Formatted as Total Length (2 bytes) + Suite IDs (2 bytes each).
|
||||
buf.put_u16((self.cipher_suites.len() * 2) as u16);
|
||||
for &suite in &self.cipher_suites {
|
||||
buf.put_u16(suite);
|
||||
}
|
||||
|
||||
// Legacy Compression Methods:
|
||||
// Always 1 byte of length (1) followed by the 'Null' method (0x00).
|
||||
buf.put_u8(1);
|
||||
buf.put_u8(0x00);
|
||||
|
||||
// Extensions Block:
|
||||
// Formatted as Total Length (2 bytes) + Extension Data.
|
||||
buf.put_u16(self.extensions.len() as u16);
|
||||
buf.put_slice(&self.extensions);
|
||||
|
||||
// Patch the Handshake Length:
|
||||
// We calculate the length of everything after the 3-byte placeholder.
|
||||
let total_len = (buf.len() - length_pos - 3) as u32;
|
||||
let len_bytes = total_len.to_be_bytes();
|
||||
// Copy the last 3 bytes of the big-endian u32 into the placeholder.
|
||||
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
||||
|
||||
let ext_len = self.extensions.len();
|
||||
let total_handshake_len = (buf.len() - length_pos - 3) as u32;
|
||||
|
||||
tracing::debug!(
|
||||
handshake_type = "ClientHello",
|
||||
body_len = total_handshake_len,
|
||||
extensions_len = ext_len,
|
||||
total_bytes = buf.len(),
|
||||
"Serialized Handshake message"
|
||||
);
|
||||
buf.freeze()
|
||||
}
|
||||
|
||||
pub fn make_client_hello(
|
||||
profile: &BrowserProfile,
|
||||
host: &str,
|
||||
public_key: &[u8; 32],
|
||||
salt: [u8; 32],
|
||||
) -> Bytes {
|
||||
// 1. Key Exchange: Generate ECDH pair and get public key
|
||||
|
||||
// 2. Authentication: Generate 32 bytes for TLS Random
|
||||
// [16 bytes entropy] + [16 bytes HMAC(timestamp)]
|
||||
let mut tls_random = salt;
|
||||
//todo
|
||||
let auth_token = [0; 16]; //generate_auth_tag(&[]);
|
||||
// tls_random[16..32].copy_from_slice(&auth_token);
|
||||
|
||||
// 3. Extensions: Build the extensions block using the profile
|
||||
let mut ext_builder = ExtensionBuilder::new();
|
||||
// Pass the public key into the KeyShare extension via apply_profile
|
||||
ext_builder.apply_profile(profile, host, public_key);
|
||||
let extensions_bytes = ext_builder.build();
|
||||
|
||||
let mut session_id = BytesMut::with_capacity(32);
|
||||
session_id.put_slice(&[0u8; 32]);
|
||||
|
||||
// 4. Assemble ClientHello Handshake message
|
||||
let client_hello = ClientHello {
|
||||
version: ProtocolVersion::Tls12, // Legacy version for compatibility
|
||||
random: tls_random,
|
||||
session_id: session_id.freeze(), // Standard 32-byte session ID
|
||||
cipher_suites: vec![0x1301, 0x1302, 0x1303], // TLS 1.3 suites
|
||||
extensions: extensions_bytes,
|
||||
};
|
||||
|
||||
// 5. Wrap ClientHello into a TLS Record
|
||||
let record = TlsRecord::new(
|
||||
ContentType::Handshake,
|
||||
ProtocolVersion::Tls10,
|
||||
client_hello.serialize(),
|
||||
);
|
||||
|
||||
// Final result: Byte buffer ready for the wire
|
||||
record.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerHello {
|
||||
pub version: ProtocolVersion,
|
||||
pub random: [u8; 32],
|
||||
pub session_id: Bytes,
|
||||
pub cipher_suite: u16,
|
||||
pub extensions: BytesMut,
|
||||
}
|
||||
impl ServerHello {
|
||||
/// Динамически создает ServerHello на основе данных из ClientHello
|
||||
pub fn from_client_hello(
|
||||
client_hello: &ClientHello,
|
||||
server_public_key: &[u8],
|
||||
salt: [u8; 32],
|
||||
) -> Self {
|
||||
let mut server_random = salt;
|
||||
|
||||
let selected_suite = client_hello
|
||||
.cipher_suites
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or(0x1301);
|
||||
|
||||
let mut extensions = BytesMut::new();
|
||||
|
||||
// --- Extension: Supported Versions (0x002b) ---
|
||||
extensions.put_u16(0x002b);
|
||||
extensions.put_u16(2);
|
||||
extensions.put_u16(0x0304); // TLS 1.3
|
||||
|
||||
// --- Extension: Key Share (0x0033) ---
|
||||
// Структура: Type(2) + Length(2) + Group(2) + KeyLength(2) + Key(N)
|
||||
extensions.put_u16(0x0033);
|
||||
extensions.put_u16(36); // Общая длина данных расширения (2+2+32)
|
||||
extensions.put_u16(0x001d); // Named Group: x25519
|
||||
extensions.put_u16(32); // Key Length
|
||||
extensions.put_slice(server_public_key);
|
||||
|
||||
Self {
|
||||
version: ProtocolVersion::Tls12,
|
||||
random: server_random,
|
||||
session_id: client_hello.session_id.clone(),
|
||||
cipher_suite: selected_suite,
|
||||
extensions,
|
||||
}
|
||||
}
|
||||
|
||||
/// Оборачивает в TLS Record, принимая ключ
|
||||
pub fn make_server_hello(
|
||||
client_hello: &ClientHello,
|
||||
server_public_key: &[u8],
|
||||
salt: [u8; 32],
|
||||
) -> Bytes {
|
||||
let server_hello = Self::from_client_hello(client_hello, server_public_key, salt);
|
||||
let handshake_payload = server_hello.serialize();
|
||||
|
||||
let record = TlsRecord::new(
|
||||
ContentType::Handshake,
|
||||
ProtocolVersion::Tls12,
|
||||
handshake_payload,
|
||||
);
|
||||
|
||||
record.serialize()
|
||||
}
|
||||
/// Сериализация самого сообщения Handshake (Type + Len gth + Body)
|
||||
pub fn serialize(&self) -> Bytes {
|
||||
let mut buf = BytesMut::with_capacity(256 + self.extensions.len());
|
||||
|
||||
// 1. Handshake Type: 0x02 (ServerHello)
|
||||
buf.put_u8(HANDSHAKE_TYPE_SERVER_HELLO);
|
||||
|
||||
// 2. Placeholder для длины (u24)
|
||||
let length_pos = buf.len();
|
||||
buf.put_bytes(0, 3);
|
||||
|
||||
// 3. Тело ServerHello
|
||||
buf.put_u16(0x0303); // Legacy Version
|
||||
buf.put_slice(&self.random);
|
||||
|
||||
// Session ID: Length (1 byte) + Data
|
||||
buf.put_u8(self.session_id.len() as u8);
|
||||
buf.put_slice(&self.session_id);
|
||||
|
||||
// Selected Cipher Suite
|
||||
buf.put_u16(self.cipher_suite);
|
||||
|
||||
// Compression: всегда 0x00
|
||||
buf.put_u8(0x00);
|
||||
|
||||
// Extensions: Length (2 bytes) + Data
|
||||
buf.put_u16(self.extensions.len() as u16);
|
||||
buf.put_slice(&self.extensions);
|
||||
|
||||
// 4. Патчим длину Handshake сообщения (u24)
|
||||
let total_len = (buf.len() - length_pos - 3) as u32;
|
||||
let len_bytes = total_len.to_be_bytes();
|
||||
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
||||
|
||||
let total_handshake_len = (buf.len() - length_pos - 3) as u32;
|
||||
let ext_len = self.extensions.len();
|
||||
// ИНФОРМАТИВНЫЙ ЛОГ
|
||||
tracing::debug!(
|
||||
handshake_type = "ServerHello",
|
||||
body_len = total_handshake_len,
|
||||
extensions_len = ext_len,
|
||||
total_bytes = buf.len(),
|
||||
"Serialized Handshake message"
|
||||
);
|
||||
|
||||
buf.freeze() // Превращаем BytesMut в Bytes
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
crypto::hmac::generate_auth_tag,
|
||||
tlseng::{
|
||||
consts::HANDSHAKE_TYPE_CLIENT_HELLO,
|
||||
extension::ExtensionBuilder,
|
||||
profile::profile::BrowserProfile,
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, ProtocolVersion},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ClientHello {
|
||||
/// The maximum version supported (legacy field in TLS 1.3)
|
||||
pub version: ProtocolVersion,
|
||||
/// 32 bytes of client-generated entropy
|
||||
pub random: [u8; 32],
|
||||
/// Legacy session ID (used in TLS 1.3 for middlebox compatibility)
|
||||
pub session_id: Bytes,
|
||||
/// List of cryptographic ciphers the client supports
|
||||
pub cipher_suites: Vec<u16>,
|
||||
/// Opaque block of extensions generated by ExtensionBuilder
|
||||
pub extensions: Bytes,
|
||||
}
|
||||
|
||||
impl ClientHello {
|
||||
/// Serializes the ClientHello message into its wire format.
|
||||
/// Includes the 4-byte Handshake header (Type + Length).
|
||||
pub fn serialize(&self) -> Bytes {
|
||||
let mut buf = BytesMut::with_capacity(512 + self.extensions.len());
|
||||
|
||||
// Handshake Type: 0x01 (ClientHello)
|
||||
buf.put_u8(HANDSHAKE_TYPE_CLIENT_HELLO);
|
||||
|
||||
// Handshake Length Placeholder:
|
||||
// Handshake messages use a 24-bit (3 byte) length field.
|
||||
let length_pos = buf.len();
|
||||
buf.put_bytes(0, 3);
|
||||
|
||||
// Protocol Version:
|
||||
// For TLS 1.3, this is traditionally pinned to 0x0303 (TLS 1.2)
|
||||
// to prevent middleboxes from dropping the packet.
|
||||
buf.put_u16(0x0303);
|
||||
buf.put_slice(&self.random);
|
||||
|
||||
// Legacy Session ID:
|
||||
// Formatted as Length (1 byte) + ID bytes.
|
||||
buf.put_u8(self.session_id.len() as u8);
|
||||
buf.put_slice(&self.session_id);
|
||||
|
||||
// Cipher Suites:
|
||||
// Formatted as Total Length (2 bytes) + Suite IDs (2 bytes each).
|
||||
buf.put_u16((self.cipher_suites.len() * 2) as u16);
|
||||
for &suite in &self.cipher_suites {
|
||||
buf.put_u16(suite);
|
||||
}
|
||||
|
||||
// Legacy Compression Methods:
|
||||
// Always 1 byte of length (1) followed by the 'Null' method (0x00).
|
||||
buf.put_u8(1);
|
||||
buf.put_u8(0x00);
|
||||
|
||||
// Extensions Block:
|
||||
// Formatted as Total Length (2 bytes) + Extension Data.
|
||||
buf.put_u16(self.extensions.len() as u16);
|
||||
buf.put_slice(&self.extensions);
|
||||
|
||||
// Patch the Handshake Length:
|
||||
// We calculate the length of everything after the 3-byte placeholder.
|
||||
let total_len = (buf.len() - length_pos - 3) as u32;
|
||||
let len_bytes = total_len.to_be_bytes();
|
||||
// Copy the last 3 bytes of the big-endian u32 into the placeholder.
|
||||
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
||||
|
||||
buf.freeze()
|
||||
}
|
||||
|
||||
pub fn make_client_hello(profile: &BrowserProfile, host: &str) -> Bytes {
|
||||
// 1. Key Exchange: Generate ECDH pair and get public key
|
||||
|
||||
// 2. Authentication: Generate 32 bytes for TLS Random
|
||||
// [16 bytes entropy] + [16 bytes HMAC(timestamp)]
|
||||
let mut tls_random = [0; 32];
|
||||
|
||||
let auth_token = generate_auth_tag(&[]);
|
||||
tls_random[16..32].copy_from_slice(&auth_token);
|
||||
|
||||
// 3. Extensions: Build the extensions block using the profile
|
||||
let mut ext_builder = ExtensionBuilder::new();
|
||||
// Pass the public key into the KeyShare extension via apply_profile
|
||||
ext_builder.apply_profile(profile, host, &[0; 32]);
|
||||
let extensions_bytes = ext_builder.build();
|
||||
|
||||
let mut session_id = BytesMut::with_capacity(32);
|
||||
session_id.put_slice(&[0u8; 32]);
|
||||
|
||||
// 4. Assemble ClientHello Handshake message
|
||||
let client_hello = ClientHello {
|
||||
version: ProtocolVersion::Tls12, // Legacy version for compatibility
|
||||
random: tls_random,
|
||||
session_id: session_id.freeze(), // Standard 32-byte session ID
|
||||
cipher_suites: vec![0x1301, 0x1302, 0x1303], // TLS 1.3 suites
|
||||
extensions: extensions_bytes,
|
||||
};
|
||||
|
||||
// 5. Wrap ClientHello into a TLS Record
|
||||
let record = TlsRecord::new(
|
||||
ContentType::Handshake,
|
||||
ProtocolVersion::Tls10,
|
||||
client_hello.serialize(),
|
||||
);
|
||||
|
||||
// Final result: Byte buffer ready for the wire
|
||||
record.serialize()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
use crate::{tlseng::types::HelloType, utils::u24::U24};
|
||||
|
||||
pub struct HelloHeader {
|
||||
pub header_type: HelloType,
|
||||
pub len: U24,
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod client_hello;
|
||||
pub mod hello_header;
|
||||
pub mod server_hello;
|
||||
@@ -1,95 +0,0 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::tlseng::{
|
||||
consts::HANDSHAKE_TYPE_SERVER_HELLO,
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, ProtocolVersion},
|
||||
};
|
||||
|
||||
pub struct ServerHello {
|
||||
pub version: ProtocolVersion,
|
||||
pub random: [u8; 32],
|
||||
pub session_id: Bytes,
|
||||
pub cipher_suite: u16,
|
||||
pub extensions: BytesMut,
|
||||
}
|
||||
|
||||
impl ServerHello {
|
||||
pub fn make_mock_server_hello() -> Bytes {
|
||||
// 1. Генерируем "рандом" (в реальном Nginx здесь случайные байты)
|
||||
let mut mock_random = [0u8; 32];
|
||||
mock_random[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); // Просто метка
|
||||
|
||||
// 2. Имитируем Session ID (в TLS 1.3 сервер часто эхоит ID клиента)
|
||||
let mut session_id = BytesMut::with_capacity(32);
|
||||
session_id.put_slice(&[0u8; 32]);
|
||||
|
||||
// 3. Подготавливаем минимальные расширения (пустые или базовые)
|
||||
// Для TLS 1.3 тут обязательно должны быть Supported Versions (0x002b)
|
||||
let mut mock_extensions = BytesMut::new();
|
||||
|
||||
// Extension: Supported Versions (TLS 1.3)
|
||||
mock_extensions.put_u16(0x002b); // Type
|
||||
mock_extensions.put_u16(2); // Length
|
||||
mock_extensions.put_u16(0x0304); // Value: TLS 1.3
|
||||
|
||||
let server_hello = ServerHello {
|
||||
version: ProtocolVersion::Tls12, // Legacy 0x0303
|
||||
random: mock_random,
|
||||
session_id: session_id.freeze(),
|
||||
cipher_suite: 0x1301, // TLS_AES_128_GCM_SHA256
|
||||
extensions: mock_extensions,
|
||||
};
|
||||
|
||||
// 4. Сериализуем Handshake сообщение
|
||||
let handshake_payload = server_hello.serialize();
|
||||
|
||||
// 5. Оборачиваем в TLS Record
|
||||
// ContentType: Handshake (22)
|
||||
// Version: TLS 1.0 (0x0301) для совместимости
|
||||
let record = TlsRecord::new(
|
||||
ContentType::Handshake,
|
||||
ProtocolVersion::Tls10,
|
||||
handshake_payload.freeze(), // Теперь payload — это Bytes
|
||||
);
|
||||
|
||||
// Финальный результат: [Header(5 bytes)][Handshake(N bytes)]
|
||||
record.serialize()
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> BytesMut {
|
||||
let mut buf = BytesMut::with_capacity(256 + self.extensions.len());
|
||||
|
||||
// 1. Handshake Type: 0x02 (ServerHello)
|
||||
buf.put_u8(HANDSHAKE_TYPE_SERVER_HELLO);
|
||||
|
||||
// 2. Placeholder for u24 length
|
||||
let length_pos = buf.len();
|
||||
buf.put_bytes(0, 3);
|
||||
|
||||
// 3. body of ServerHello
|
||||
buf.put_u16(ProtocolVersion::Tls12 as u16); // Legacy 0x0303
|
||||
buf.put_slice(&self.random);
|
||||
|
||||
// Session ID
|
||||
buf.put_u8(self.session_id.len() as u8);
|
||||
buf.put_slice(&self.session_id);
|
||||
|
||||
// Selected Cipher Suite (only one)
|
||||
buf.put_u16(self.cipher_suite);
|
||||
|
||||
// Compression: always 0x00
|
||||
buf.put_u8(0x00);
|
||||
|
||||
// Extensions
|
||||
buf.put_u16(self.extensions.len() as u16);
|
||||
buf.put_slice(&self.extensions);
|
||||
|
||||
// 4. Patch length
|
||||
let total_len = (buf.len() - length_pos - 3) as u32;
|
||||
let len_bytes = total_len.to_be_bytes();
|
||||
buf[length_pos..length_pos + 3].copy_from_slice(&len_bytes[1..4]);
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
pub mod application_data;
|
||||
pub mod consts;
|
||||
use bytes::Bytes;
|
||||
|
||||
pub struct ApplicationData {
|
||||
pub len: usize,
|
||||
pub payload: Bytes,
|
||||
}
|
||||
|
||||
mod consts;
|
||||
pub mod extension;
|
||||
pub mod handshake;
|
||||
mod params;
|
||||
pub mod profile;
|
||||
pub mod tls_record;
|
||||
pub mod types;
|
||||
mod values;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsGroups(pub &'static [u16]);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsSignatures(pub &'static [u16]);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsVersions(pub &'static [u16]);
|
||||
@@ -0,0 +1,103 @@
|
||||
use crate::tlseng::types::{ExtensionOrder, TlsGroups, TlsSignatures, TlsVersions};
|
||||
|
||||
/// Represents a complete TLS fingerprint profile for a specific browser.
|
||||
///
|
||||
/// This struct contains all the necessary information to generate a TLS
|
||||
/// fingerprint for a specific browser, including the groups, signatures,
|
||||
/// delegated signatures, versions, ALPN, and extension order.
|
||||
pub struct BrowserProfile {
|
||||
/// The name of the browser profile.
|
||||
pub name: &'static str,
|
||||
|
||||
/// The groups supported by the browser.
|
||||
pub groups: TlsGroups,
|
||||
|
||||
/// The signatures supported by the browser.
|
||||
pub signatures: TlsSignatures,
|
||||
|
||||
/// The delegated signatures supported by the browser.
|
||||
pub delegated_signatures: TlsSignatures,
|
||||
|
||||
/// The versions of TLS supported by the browser.
|
||||
pub versions: TlsVersions,
|
||||
|
||||
/// The ALPN protocols supported by the browser.
|
||||
pub alpn: &'static [&'static str],
|
||||
|
||||
/// The specific order of Extension IDs (e.g., [0x0000, 0x0017, ...])
|
||||
pub extension_order: ExtensionOrder,
|
||||
|
||||
/// Whether the browser is based on Chromium.
|
||||
pub is_chromium: bool,
|
||||
}
|
||||
|
||||
impl BrowserProfile {
|
||||
pub const CHROME_131: Self = Self {
|
||||
name: "Chrome 131 (Windows)",
|
||||
groups: TlsGroups::CHROMIUM,
|
||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
delegated_signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
versions: TlsVersions::TLS_13_ONLY,
|
||||
alpn: &["h2", "http/1.1"],
|
||||
extension_order: ExtensionOrder::CHROMIUM_131,
|
||||
is_chromium: true,
|
||||
};
|
||||
|
||||
pub const EDGE: Self = Self {
|
||||
name: "Edge",
|
||||
groups: TlsGroups::CHROMIUM, // Edge использует тот же набор, что и Chrome
|
||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
delegated_signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
versions: TlsVersions::MODERN,
|
||||
alpn: &["h2", "http/1.1"],
|
||||
extension_order: ExtensionOrder::EDGE_130,
|
||||
is_chromium: true,
|
||||
};
|
||||
|
||||
pub const DEFAULT: Self = Self::CHROME_131;
|
||||
}
|
||||
/// Represents a TLS configuration profile for the server side.
|
||||
pub struct ServerProfile {
|
||||
/// Имя профиля (например, "Modern-TLS-1.3-Only" или "Compatible-Nginx-Style")
|
||||
pub name: &'static str,
|
||||
|
||||
/// Поддерживаемые версии TLS. Сервер выберет высшую общую с клиентом.
|
||||
pub versions: TlsVersions,
|
||||
|
||||
/// Приоритетный список шифров (Cipher Suites).
|
||||
/// В TLS 1.3 это обычно [0x1301, 0x1302, 0x1303].
|
||||
pub cipher_suites: &'static [u16],
|
||||
|
||||
/// Группы для обмена ключами (Key Exchange Groups).
|
||||
pub groups: TlsGroups,
|
||||
|
||||
/// Поддерживаемые алгоритмы подписи для аутентификации сервера.
|
||||
pub signatures: TlsSignatures,
|
||||
|
||||
/// Протоколы ALPN, которые сервер готов подтвердить (h2, http/1.1).
|
||||
pub alpn: &'static [&'static str],
|
||||
|
||||
/// Настройки сессий
|
||||
pub session_tickets: bool,
|
||||
|
||||
/// Нужно ли форсировать порядок шифров сервера (Server Preference),
|
||||
/// игнорируя порядок предпочтений клиента.
|
||||
pub honor_cipher_order: bool,
|
||||
}
|
||||
|
||||
impl ServerProfile {
|
||||
pub const MODERN: Self = Self {
|
||||
name: "Modern-Secure",
|
||||
versions: TlsVersions::MODERN, // Допустим, у тебя есть такой хелпер
|
||||
cipher_suites: &[
|
||||
0x1301, // TLS_AES_128_GCM_SHA256
|
||||
0x1302, // TLS_AES_256_GCM_SHA384
|
||||
0x1303, // TLS_CHACHA20_POLY1305_SHA256
|
||||
],
|
||||
groups: TlsGroups::MODERN, // X25519, P-256
|
||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
alpn: &["h2", "http/1.1"],
|
||||
session_tickets: true,
|
||||
honor_cipher_order: true,
|
||||
};
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
use crate::tlseng::consts::*;
|
||||
use crate::tlseng::params::{TlsGroups, TlsSignatures};
|
||||
use crate::tlseng::values::{
|
||||
GROUP_SECP256R1, GROUP_SECP384R1, GROUP_X25519, SIG_ECDSA_SECP256R1_SHA256,
|
||||
SIG_ECDSA_SECP384R1_SHA384, SIG_RSA_PKCS1_SHA256, SIG_RSA_PKCS1_SHA384, SIG_RSA_PKCS1_SHA512,
|
||||
SIG_RSA_PSS_RSAE_SHA256, SIG_RSA_PSS_RSAE_SHA384, SIG_RSA_PSS_RSAE_SHA512,
|
||||
};
|
||||
|
||||
pub const CHROME_GROUPS: TlsGroups =
|
||||
TlsGroups(&[0xaaaa, GROUP_X25519, GROUP_SECP256R1, GROUP_SECP384R1]);
|
||||
|
||||
pub const CHROME_SIGNATURES: TlsSignatures = TlsSignatures(&[
|
||||
SIG_ECDSA_SECP256R1_SHA256,
|
||||
SIG_RSA_PSS_RSAE_SHA256,
|
||||
SIG_RSA_PKCS1_SHA256,
|
||||
SIG_ECDSA_SECP384R1_SHA384,
|
||||
SIG_RSA_PSS_RSAE_SHA384,
|
||||
SIG_RSA_PKCS1_SHA384,
|
||||
SIG_RSA_PSS_RSAE_SHA512,
|
||||
SIG_RSA_PKCS1_SHA512,
|
||||
]);
|
||||
|
||||
pub const CHROME_DELEGATED_ALGS: TlsSignatures = TlsSignatures(&[
|
||||
SIG_ECDSA_SECP256R1_SHA256,
|
||||
SIG_RSA_PSS_RSAE_SHA256,
|
||||
SIG_RSA_PKCS1_SHA256,
|
||||
SIG_ECDSA_SECP384R1_SHA384,
|
||||
SIG_RSA_PSS_RSAE_SHA384,
|
||||
SIG_RSA_PKCS1_SHA384,
|
||||
]);
|
||||
|
||||
pub const CHROME_ALPN_PROTOCOLS: &[&str] = &["h2", "http/1.1"];
|
||||
|
||||
pub const CHROMIUM_EXT_ORDER: &[u16] = &[
|
||||
0xaaaa, // GREASE
|
||||
EXT_TYPE_SNI, // 0x0000
|
||||
EXT_EXTENDED_MASTER_SECRET, // 0x0017
|
||||
EXT_SESSION_TICKET, // SessionTicket
|
||||
EXT_SUPPORTED_GROUPS, // 0x000a
|
||||
EXT_EC_POINT_FORMATS, // 0x000b
|
||||
EXT_SIGNATURE_ALGORITHMS, // 0x000d
|
||||
EXT_ALPN, // 0x0010
|
||||
EXT_ALPS, // 0x44cd
|
||||
EXT_STATUS_REQUEST, // 0x0005
|
||||
EXT_KEY_SHARE, // 0x0033
|
||||
EXT_SUPPORTED_VERSIONS, // 0x002b
|
||||
EXT_PSK_KEY_EXCHANGE_MODES, // 0x002d
|
||||
EXT_COMPRESS_CERTIFICATE, // 0x001b
|
||||
EXT_SIGNED_CERT_TIMESTAMP, // 0x0012
|
||||
EXT_DELEGATED_CREDENTIAL, // 0x0022
|
||||
];
|
||||
@@ -1,61 +0,0 @@
|
||||
use crate::tlseng::consts::*;
|
||||
use crate::tlseng::params::{TlsGroups, TlsSignatures};
|
||||
use crate::tlseng::values::{
|
||||
GROUP_SECP256R1, GROUP_SECP384R1, GROUP_X25519, SIG_ECDSA_SECP256R1_SHA256,
|
||||
SIG_ECDSA_SECP384R1_SHA384, SIG_RSA_PKCS1_SHA256, SIG_RSA_PKCS1_SHA384, SIG_RSA_PKCS1_SHA512,
|
||||
SIG_RSA_PSS_RSAE_SHA256, SIG_RSA_PSS_RSAE_SHA384, SIG_RSA_PSS_RSAE_SHA512,
|
||||
};
|
||||
|
||||
// --- MICROSOFT EDGE ---
|
||||
// Edge often mirrors Chrome exactly but sometimes removes specific
|
||||
// experimental GREASE values or adds Windows-specific signature prefs.
|
||||
pub const EDGE_GROUPS: TlsGroups = TlsGroups(&[
|
||||
0x0a0a, // GREASE
|
||||
GROUP_X25519,
|
||||
GROUP_SECP256R1,
|
||||
GROUP_SECP384R1,
|
||||
]);
|
||||
|
||||
pub const EDGE_SIGNATURES: TlsSignatures = TlsSignatures(&[
|
||||
SIG_ECDSA_SECP256R1_SHA256,
|
||||
SIG_RSA_PSS_RSAE_SHA256,
|
||||
SIG_RSA_PKCS1_SHA256,
|
||||
SIG_ECDSA_SECP384R1_SHA384,
|
||||
SIG_RSA_PSS_RSAE_SHA384,
|
||||
SIG_RSA_PKCS1_SHA384,
|
||||
SIG_RSA_PSS_RSAE_SHA512,
|
||||
SIG_RSA_PKCS1_SHA512,
|
||||
]);
|
||||
|
||||
pub const EDGE_DELEGATED_ALGS: TlsSignatures = TlsSignatures(&[
|
||||
SIG_ECDSA_SECP256R1_SHA256,
|
||||
SIG_RSA_PSS_RSAE_SHA256,
|
||||
SIG_RSA_PKCS1_SHA256,
|
||||
SIG_ECDSA_SECP384R1_SHA384,
|
||||
SIG_RSA_PSS_RSAE_SHA384,
|
||||
SIG_RSA_PKCS1_SHA384,
|
||||
]);
|
||||
|
||||
pub const EDGE_ALPN_PROTOCOLS: &[&str] = &["h2", "http/1.1"];
|
||||
|
||||
// Microsoft Edge Extension Order (Chromium v130+)
|
||||
pub const EDGE_EXT_ORDER: &[u16] = &[
|
||||
0x1a1a, // GREASE
|
||||
EXT_TYPE_SNI, // 0x0000
|
||||
EXT_EXTENDED_MASTER_SECRET, // 0x0017
|
||||
EXT_SESSION_TICKET, // 0x0023
|
||||
EXT_SUPPORTED_GROUPS, // 0x000a
|
||||
EXT_EC_POINT_FORMATS, // 0x000b
|
||||
EXT_SIGNATURE_ALGORITHMS, // 0x000d
|
||||
EXT_ALPN, // 0x0010
|
||||
EXT_ALPS, // 0x44cd
|
||||
EXT_STATUS_REQUEST, // 0x0005
|
||||
EXT_KEY_SHARE, // 0x0033
|
||||
EXT_SUPPORTED_VERSIONS, // 0x002b
|
||||
EXT_PSK_KEY_EXCHANGE_MODES, // 0x002d
|
||||
EXT_COMPRESS_CERTIFICATE, // 0x001b
|
||||
EXT_SIGNED_CERT_TIMESTAMP, // 0x0012
|
||||
EXT_DELEGATED_CREDENTIAL, // 0x0022
|
||||
EXT_PADDING, // 0x0015
|
||||
0x3a3a, // GREASE
|
||||
];
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod chrome_groups;
|
||||
pub mod edge_groups;
|
||||
pub mod shared;
|
||||
@@ -1,3 +0,0 @@
|
||||
use crate::tlseng::params::TlsVersions;
|
||||
|
||||
pub const MODERN_VERSIONS: TlsVersions = TlsVersions(&[0x0304, 0x0303]);
|
||||
@@ -1,3 +0,0 @@
|
||||
mod groups;
|
||||
pub mod profile;
|
||||
pub mod versions;
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::tlseng::params::{TlsGroups, TlsSignatures, TlsVersions};
|
||||
|
||||
/// Represents a complete TLS fingerprint profile for a specific browser.
|
||||
pub struct BrowserProfile {
|
||||
pub name: &'static str,
|
||||
pub groups: TlsGroups,
|
||||
pub signatures: TlsSignatures,
|
||||
pub delegated_signatures: TlsSignatures,
|
||||
pub versions: TlsVersions,
|
||||
pub alpn: &'static [&'static str],
|
||||
/// The specific order of Extension IDs (e.g., [0x0000, 0x0017, ...])
|
||||
pub extension_order: &'static [u16],
|
||||
pub is_chromium: bool,
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use crate::tlseng::{
|
||||
params::TlsVersions,
|
||||
profile::{
|
||||
groups::{
|
||||
chrome_groups::{
|
||||
CHROME_ALPN_PROTOCOLS, CHROME_DELEGATED_ALGS, CHROME_GROUPS, CHROME_SIGNATURES,
|
||||
CHROMIUM_EXT_ORDER,
|
||||
},
|
||||
edge_groups::{
|
||||
EDGE_ALPN_PROTOCOLS, EDGE_DELEGATED_ALGS, EDGE_EXT_ORDER, EDGE_GROUPS,
|
||||
EDGE_SIGNATURES,
|
||||
},
|
||||
shared::MODERN_VERSIONS,
|
||||
},
|
||||
profile::BrowserProfile,
|
||||
},
|
||||
};
|
||||
|
||||
// --- Versions ---
|
||||
pub const TLS_13_ONLY: TlsVersions = TlsVersions(&[0x0304]);
|
||||
|
||||
// --- CHROME 131 PROFILE ---
|
||||
pub const CHROME_131: BrowserProfile = BrowserProfile {
|
||||
name: "Chrome 131 (Windows)",
|
||||
groups: CHROME_GROUPS,
|
||||
signatures: CHROME_SIGNATURES,
|
||||
alpn: CHROME_ALPN_PROTOCOLS,
|
||||
delegated_signatures: CHROME_DELEGATED_ALGS,
|
||||
versions: TLS_13_ONLY,
|
||||
extension_order: CHROMIUM_EXT_ORDER,
|
||||
is_chromium: true,
|
||||
};
|
||||
|
||||
// --- FIREFOX 133 PROFILE (Example) ---
|
||||
// Note: Firefox uses different groups and no ALPS
|
||||
pub const EDGE_PROFILE: BrowserProfile = BrowserProfile {
|
||||
name: "Edge",
|
||||
groups: EDGE_GROUPS,
|
||||
signatures: EDGE_SIGNATURES, // Usually identical to Chrome
|
||||
alpn: EDGE_ALPN_PROTOCOLS,
|
||||
delegated_signatures: EDGE_DELEGATED_ALGS,
|
||||
versions: MODERN_VERSIONS,
|
||||
extension_order: EDGE_EXT_ORDER,
|
||||
is_chromium: true,
|
||||
};
|
||||
@@ -17,6 +17,17 @@ pub struct TlsRecord {
|
||||
}
|
||||
|
||||
impl TlsRecord {
|
||||
/// Creates a new TLS Record Layer from the given parameters.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `content_type`: The type of data contained (Handshake, ApplicationData, etc.).
|
||||
/// * `version`: The record layer version (usually 0x0301 for legacy support).
|
||||
/// * `payload`: The actual data being transported (e.g., a serialized ClientHello).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new TLS Record Layer structure with the given parameters.
|
||||
pub fn new(content_type: ContentType, version: ProtocolVersion, payload: Bytes) -> Self {
|
||||
Self {
|
||||
content_type,
|
||||
@@ -38,4 +49,15 @@ impl TlsRecord {
|
||||
|
||||
buf.freeze()
|
||||
}
|
||||
|
||||
pub fn build_application_data(payload: Bytes) -> Bytes {
|
||||
tracing::trace!(payload_len = payload.len(), "Building TlsRecord from Bytes");
|
||||
|
||||
let record = Self::new(
|
||||
ContentType::ApplicationData,
|
||||
ProtocolVersion::Tls12,
|
||||
payload,
|
||||
);
|
||||
record.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
+200
-3
@@ -1,7 +1,7 @@
|
||||
/// TLS Content Types as defined in the TLS Record Protocol.
|
||||
/// These identify what is contained within the TLS Record payload.
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ContentType {
|
||||
/// Handshake messages (e.g., ClientHello, ServerHello)
|
||||
Handshake = 0x16,
|
||||
@@ -13,6 +13,14 @@ pub enum ContentType {
|
||||
|
||||
impl TryFrom<u8> for ContentType {
|
||||
type Error = &'static str;
|
||||
/// Attempts to convert a given `u8` value into a `ContentType`.
|
||||
///
|
||||
/// Returns `Ok(ContentType)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x16 => Ok(ContentType::Handshake),
|
||||
@@ -23,8 +31,14 @@ impl TryFrom<u8> for ContentType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Known TLS protocol versions.
|
||||
/// Note: TLS 1.3 often uses legacy versions in headers for compatibility.
|
||||
///
|
||||
/// Represents known TLS protocol versions.
|
||||
///
|
||||
/// Note that TLS 1.3 often uses legacy versions in headers for compatibility.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ProtocolVersion {
|
||||
@@ -35,23 +49,67 @@ pub enum ProtocolVersion {
|
||||
|
||||
impl TryFrom<u16> for ProtocolVersion {
|
||||
type Error = &'static str;
|
||||
|
||||
/// Attempts to convert a given `u16` value into a `ProtocolVersion`.
|
||||
///
|
||||
/// Returns `Ok(ProtocolVersion)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// TLS 1.0 (RFC 2246)
|
||||
0x0301 => Ok(ProtocolVersion::Tls10),
|
||||
// TLS 1.2 (RFC 4346)
|
||||
0x0303 => Ok(ProtocolVersion::Tls12),
|
||||
// TLS 1.3 (draft-ietf-tls-tls13-28)
|
||||
0x0304 => Ok(ProtocolVersion::Tls13),
|
||||
_ => Err("This is not Protocol Version"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hello types as defined in the TLS Handshake Protocol.
|
||||
///
|
||||
/// These identify the type of the message in the TLS Handshake protocol.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The values of these enum variants are used as the first byte of the TLS
|
||||
/// Handshake protocol message.
|
||||
///
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum HelloType {
|
||||
/// Client hello message type
|
||||
Client = 0x01,
|
||||
/// Server hello message type
|
||||
Server = 0x02,
|
||||
}
|
||||
|
||||
/// Attempts to convert a given `u8` value into a `HelloType`.
|
||||
///
|
||||
/// Returns `Ok(HelloType)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This function is used to convert raw bytes into a `HelloType`.
|
||||
/// It is used in the `HelloHeader` parsing process.
|
||||
impl TryFrom<u8> for HelloType {
|
||||
type Error = &'static str;
|
||||
/// Attempts to convert a given `u8` value into a `HelloType`.
|
||||
///
|
||||
/// Returns `Ok(HelloType)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x01 => Ok(HelloType::Client),
|
||||
@@ -60,3 +118,142 @@ impl TryFrom<u8> for HelloType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of supported TLS groups.
|
||||
///
|
||||
/// This is a list of 16-bit group identifiers that the client supports.
|
||||
/// The server will select one of these groups to use for the key exchange.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsGroups(pub &'static [u16]);
|
||||
|
||||
impl TlsGroups {
|
||||
pub const X25519: u16 = 0x001d;
|
||||
pub const SECP256R1: u16 = 0x0017;
|
||||
pub const SECP384R1: u16 = 0x0018;
|
||||
|
||||
/// Стандартный набор для Chrome/Edge (X25519 + P-256)
|
||||
pub const CHROMIUM: Self = Self(&[Self::X25519, Self::SECP256R1, Self::SECP384R1]);
|
||||
|
||||
/// Набор "только современные кривые"
|
||||
pub const MODERN: Self = Self(&[Self::X25519, Self::SECP256R1]);
|
||||
}
|
||||
|
||||
/// A collection of supported TLS signature algorithms.
|
||||
///
|
||||
/// This is a list of 16-bit signature algorithm identifiers that the client supports.
|
||||
/// The server will select one of these algorithms to use for the digital signature.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsSignatures(pub &'static [u16]);
|
||||
|
||||
impl TlsSignatures {
|
||||
pub const ECDSA_SECP256R1_SHA256: u16 = 0x0403;
|
||||
pub const RSA_PSS_RSAE_SHA256: u16 = 0x0804;
|
||||
pub const RSA_PKCS1_SHA256: u16 = 0x0401;
|
||||
pub const ECDSA_SECP384R1_SHA384: u16 = 0x0503;
|
||||
pub const RSA_PSS_RSAE_SHA384: u16 = 0x0805;
|
||||
pub const RSA_PKCS1_SHA384: u16 = 0x0501;
|
||||
pub const RSA_PSS_RSAE_SHA512: u16 = 0x0806;
|
||||
pub const RSA_PKCS1_SHA512: u16 = 0x0601;
|
||||
|
||||
/// Типичный набор для современных браузеров
|
||||
pub const BROWSER_STANDARD: Self = Self(&[
|
||||
Self::ECDSA_SECP256R1_SHA256,
|
||||
Self::RSA_PSS_RSAE_SHA256,
|
||||
Self::RSA_PKCS1_SHA256,
|
||||
Self::ECDSA_SECP384R1_SHA384,
|
||||
Self::RSA_PSS_RSAE_SHA384,
|
||||
Self::RSA_PKCS1_SHA384,
|
||||
Self::RSA_PSS_RSAE_SHA512,
|
||||
]);
|
||||
}
|
||||
|
||||
/// A collection of supported TLS protocol versions.
|
||||
///
|
||||
/// This is a list of 16-bit protocol version identifiers that the client supports.
|
||||
/// The server will select one of these versions to use for the TLS connection.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsVersions(pub &'static [u16]);
|
||||
|
||||
impl TlsVersions {
|
||||
pub const TLS_1_3: u16 = 0x0304;
|
||||
pub const TLS_1_2: u16 = 0x0303;
|
||||
|
||||
pub const TLS_13_ONLY: Self = Self(&[Self::TLS_1_3]);
|
||||
pub const MODERN: Self = Self(&[Self::TLS_1_3, Self::TLS_1_2]);
|
||||
}
|
||||
|
||||
pub struct TlsExtensions;
|
||||
|
||||
impl TlsExtensions {
|
||||
pub const SNI: u16 = 0x0000;
|
||||
pub const STATUS_REQUEST: u16 = 0x0005;
|
||||
pub const SUPPORTED_GROUPS: u16 = 0x000a;
|
||||
pub const EC_POINT_FORMATS: u16 = 0x000b;
|
||||
pub const SIGNATURE_ALGORITHMS: u16 = 0x000d;
|
||||
pub const ALPN: u16 = 0x0010;
|
||||
pub const SCT: u16 = 0x0012;
|
||||
pub const PADDING: u16 = 0x0015;
|
||||
pub const EMS: u16 = 0x0017;
|
||||
pub const COMPRESS_CERT: u16 = 0x001b;
|
||||
pub const DELEGATED_CREDENTIAL: u16 = 0x0022;
|
||||
pub const SESSION_TICKET: u16 = 0x0023;
|
||||
pub const SUPPORTED_VERSIONS: u16 = 0x002b;
|
||||
pub const PSK_MODES: u16 = 0x002d;
|
||||
pub const KEY_SHARE: u16 = 0x0033;
|
||||
pub const ALPS: u16 = 0x44cd;
|
||||
pub const RENEGOTIATION_INFO: u16 = 0xff01;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ExtensionOrder(pub &'static [u16]);
|
||||
|
||||
impl<'a> IntoIterator for &'a ExtensionOrder {
|
||||
type Item = &'a u16;
|
||||
type IntoIter = std::slice::Iter<'a, u16>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionOrder {
|
||||
pub const CHROMIUM_131: Self = Self(&[
|
||||
0xaaaa, // GREASE
|
||||
TlsExtensions::SNI,
|
||||
TlsExtensions::EMS,
|
||||
TlsExtensions::SESSION_TICKET,
|
||||
TlsExtensions::SUPPORTED_GROUPS,
|
||||
TlsExtensions::EC_POINT_FORMATS,
|
||||
TlsExtensions::SIGNATURE_ALGORITHMS,
|
||||
TlsExtensions::ALPN,
|
||||
TlsExtensions::ALPS,
|
||||
TlsExtensions::STATUS_REQUEST,
|
||||
TlsExtensions::KEY_SHARE,
|
||||
TlsExtensions::SUPPORTED_VERSIONS,
|
||||
TlsExtensions::PSK_MODES,
|
||||
TlsExtensions::COMPRESS_CERT,
|
||||
TlsExtensions::SCT,
|
||||
TlsExtensions::DELEGATED_CREDENTIAL,
|
||||
]);
|
||||
|
||||
pub const EDGE_130: Self = Self(&[
|
||||
0x1a1a, // GREASE
|
||||
TlsExtensions::SNI,
|
||||
TlsExtensions::EMS,
|
||||
TlsExtensions::SESSION_TICKET,
|
||||
TlsExtensions::SUPPORTED_GROUPS,
|
||||
TlsExtensions::EC_POINT_FORMATS,
|
||||
TlsExtensions::SIGNATURE_ALGORITHMS,
|
||||
TlsExtensions::ALPN,
|
||||
TlsExtensions::ALPS,
|
||||
TlsExtensions::STATUS_REQUEST,
|
||||
TlsExtensions::KEY_SHARE,
|
||||
TlsExtensions::SUPPORTED_VERSIONS,
|
||||
TlsExtensions::PSK_MODES,
|
||||
TlsExtensions::COMPRESS_CERT,
|
||||
TlsExtensions::SCT,
|
||||
TlsExtensions::DELEGATED_CREDENTIAL,
|
||||
TlsExtensions::PADDING,
|
||||
0x3a3a, // GREASE
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
pub const TYPE_HOST_NAME: u8 = 0x00;
|
||||
|
||||
pub const GROUP_X25519: u16 = 0x001d;
|
||||
pub const GROUP_SECP256R1: u16 = 0x0017;
|
||||
pub const GROUP_SECP384R1: u16 = 0x0018;
|
||||
|
||||
// Signature Algorithms (Signature Schemes)
|
||||
pub const SIG_ECDSA_SECP256R1_SHA256: u16 = 0x0403;
|
||||
pub const SIG_RSA_PSS_RSAE_SHA256: u16 = 0x0804;
|
||||
pub const SIG_RSA_PKCS1_SHA256: u16 = 0x0401;
|
||||
pub const SIG_ECDSA_SECP384R1_SHA384: u16 = 0x0503;
|
||||
pub const SIG_RSA_PSS_RSAE_SHA384: u16 = 0x0805;
|
||||
pub const SIG_RSA_PKCS1_SHA384: u16 = 0x0501;
|
||||
pub const SIG_RSA_PSS_RSAE_SHA512: u16 = 0x0806;
|
||||
pub const SIG_RSA_PKCS1_SHA512: u16 = 0x0601;
|
||||
|
||||
// Versions & Modes
|
||||
pub const PSK_DHE_KE_MODE: u8 = 0x01;
|
||||
pub const CERT_COMPRESSION_BROTLI: u16 = 0x0002;
|
||||
pub const OCSP_STATUS_TYPE: u8 = 0x01;
|
||||
pub const EC_POINT_FORMAT_UNCOMPRESSED: u8 = 0x00;
|
||||
+33
-1
@@ -4,17 +4,50 @@ use bytes::Buf;
|
||||
pub struct U24([u8; 3]);
|
||||
|
||||
impl U24 {
|
||||
/// Creates a new `U24` from a given `u32` value.
|
||||
///
|
||||
/// The `u32` value is converted to big-endian byte order and then split into
|
||||
/// three bytes, which are used to initialize the `U24`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
pub fn from_u32(value: u32) -> Self {
|
||||
let b = value.to_be_bytes();
|
||||
U24([b[1], b[2], b[3]])
|
||||
}
|
||||
|
||||
/// Converts the `U24` into a `u32` value.
|
||||
///
|
||||
/// The `U24` is converted from big-endian byte order to a `u32` value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
u32::from_be_bytes([0, self.0[0], self.0[1], self.0[2]])
|
||||
}
|
||||
|
||||
/// Converts a slice of three bytes into a `u32` value.
|
||||
///
|
||||
/// The slice is interpreted as a big-endian byte order, and the resulting `u32` value is
|
||||
/// computed by combining the three bytes into a single 32-bit value.
|
||||
pub fn from_slice(slice: &[u8]) -> u32 {
|
||||
u32::from_be_bytes([0, slice[0], slice[1], slice[2]])
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BufExt: Buf {
|
||||
/// Reads three bytes from the buffer and returns a `u32` value.
|
||||
///
|
||||
/// The bytes are read in big-endian byte order and then combined into a single `u32` value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// let mut buf = BytesMut::from(&[0x12, 0x34, 0x56][..]);
|
||||
/// let val = buf.get_u24();
|
||||
/// assert_eq!(val, 0x123456);
|
||||
fn get_u24(&mut self) -> u32 {
|
||||
let b1 = self.get_u8() as u32;
|
||||
let b2 = self.get_u8() as u32;
|
||||
@@ -23,5 +56,4 @@ pub trait BufExt: Buf {
|
||||
}
|
||||
}
|
||||
|
||||
// Реализуем этот трейт для всего, что поддерживает Buf (включая BytesMut)
|
||||
impl<T: Buf> BufExt for T {}
|
||||
|
||||
Reference in New Issue
Block a user