renames and tauri app

This commit is contained in:
2026-03-09 17:51:01 +07:00
parent 6ca47336a1
commit 6f4dd88a8e
62 changed files with 5215 additions and 70 deletions
+6
View File
@@ -0,0 +1,6 @@
use bytes::{Bytes, BytesMut};
pub trait AeadPacker {
fn encrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error>;
fn decrypt(&mut self, data: &mut BytesMut) -> Result<Bytes, chacha20poly1305::aead::Error>;
}
+136
View File
@@ -0,0 +1,136 @@
use bytes::{Bytes, BytesMut};
use chacha20poly1305::aead::generic_array::GenericArray;
use chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, Key, KeyInit, Nonce};
use crate::crypto::aead::AeadPacker;
pub struct NonceState {
counter: u64,
base_iv: [u8; 12],
}
impl NonceState {
pub fn new(base_iv: [u8; 12]) -> Self {
Self {
counter: 0,
base_iv,
}
}
// Возвращает 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();
for i in 0..8 {
iv[i + 4] ^= counter_bytes[i];
}
self.counter += 1;
*GenericArray::from_slice(&iv)
}
}
pub struct ChaChaCipher {
pub encrypt_cipher: ChaCha20Poly1305,
pub decrypt_cipher: ChaCha20Poly1305,
pub encrypt_state: NonceState,
pub decrypt_state: NonceState,
}
impl ChaChaCipher {
pub fn new() -> Self {
let start_key = Key::from([0u8; 32]);
let encrypt_cipher = ChaCha20Poly1305::new(&start_key);
let decrypt_cipher = ChaCha20Poly1305::new(&start_key);
Self {
encrypt_state: NonceState::new([0u8; 12]),
decrypt_state: NonceState::new([0u8; 12]),
encrypt_cipher,
decrypt_cipher,
}
}
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(&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 data_len = data.len();
// maybe ad should be stream id
match self.encrypt_cipher.encrypt_in_place(&nonce, &nonce, data) {
Ok(_) => {
tracing::trace!(
counter = current_counter,
nonce = %hex::encode(nonce),
len = data_len,
"Encryption successful"
);
Ok(data.split().freeze())
}
Err(e) => {
tracing::error!(
counter = current_counter,
nonce = %hex::encode(nonce),
len = data_len,
error = ?e,
"AEAD encryption failure"
);
Err(e)
}
}
}
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 data_len = data.len();
// maybe ad should be stream id
match self.decrypt_cipher.decrypt_in_place(&nonce, &nonce, data) {
Ok(_) => {
tracing::trace!(
counter = current_counter,
nonce = %hex::encode(nonce),
len = data_len,
"Decryption successful"
);
Ok(data.split().freeze())
}
Err(e) => {
let data_prefix = if data.len() >= 8 {
hex::encode(&data[..8])
} else {
hex::encode(data.as_ref())
};
tracing::error!(
counter = current_counter,
nonce = %hex::encode(nonce),
len = data_len,
prefix = %data_prefix,
error = ?e,
"AEAD decryption failure! Verification failed or data malformed"
);
Err(e)
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
use aead::OsRng;
use x25519_dalek::{EphemeralSecret, PublicKey};
pub struct ECDH {
pub public_key: PublicKey,
pub private_key: Option<EphemeralSecret>,
}
impl ECDH {
pub fn new() -> Self {
let secret = EphemeralSecret::random_from_rng(&mut OsRng);
let public = PublicKey::from(&secret);
Self {
private_key: Some(secret),
public_key: public,
}
}
pub fn get_shared(&mut self, public: &PublicKey) -> Option<[u8; 32]> {
let private_key = self.private_key.take()?;
let shared = private_key.diffie_hellman(&public);
Some(*shared.as_bytes())
}
}
+22
View File
@@ -0,0 +1,22 @@
use hkdf::Hkdf;
use sha2::Sha256;
pub struct HKDF;
impl HKDF {
pub fn extract_key(salt: &[u8], ikm: &[u8]) -> Hkdf<Sha256> {
let extracted_key = Hkdf::<Sha256>::new(Some(salt), ikm);
extracted_key
}
pub fn expand_key<const N: usize>(
extracted_key: &Hkdf<Sha256>,
mark: &[u8],
) -> Result<[u8; N], String> {
let mut expanded_key: [u8; N] = [0u8; N];
extracted_key
.expand(mark, &mut expanded_key)
.map_err(|e| e.to_string())?;
Ok(expanded_key)
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod aead;
pub mod chacha;
mod ecdh;
mod hkdf;
pub mod session;
+225
View File
@@ -0,0 +1,225 @@
use x25519_dalek::PublicKey;
use crate::{
crypto::{ecdh::ECDH, hkdf::HKDF},
tlseng::extension::ExtensionStack,
};
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
use aead::{rand_core::RngCore, OsRng};
pub struct SaltPair {
local_salt: [u8; 32],
remote_salt: [u8; 32],
is_initiator: bool,
}
impl SaltPair {
pub fn new(is_initiator: bool) -> Self {
let mut local_salt = [0u8; 32];
OsRng.fill_bytes(&mut local_salt);
Self {
local_salt,
remote_salt: [0; 32],
is_initiator,
}
}
pub fn get_local(&self) -> [u8; 32] {
self.local_salt
}
pub fn set_remote_salt(&mut self, salt: [u8; 32]) {
self.remote_salt = salt
}
pub fn get_total(&self) -> [u8; 64] {
let mut salt = [0u8; 64];
if self.is_initiator {
salt[..32].copy_from_slice(&self.local_salt);
salt[32..].copy_from_slice(&self.remote_salt);
salt
} else {
salt[..32].copy_from_slice(&self.remote_salt);
salt[32..].copy_from_slice(&self.local_salt);
salt
}
}
}
pub struct SessionKeys {
pub salt: SaltPair,
pub ecdh: ECDH,
pub auth_key: [u8; 32],
}
impl SessionKeys {
pub fn new(is_initiator: bool) -> Self {
Self {
salt: SaltPair::new(is_initiator),
ecdh: ECDH::new(),
auth_key: [0u8; 32],
}
}
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())?;
let auth_secret = HKDF::expand_key::<32>(&hkdf, b"auth_key").map_err(|e| e.to_string())?;
self.auth_key = auth_secret;
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)) // Клиент пишет своим, читает серверным
}
}
fn compute_tag(secret: &[u8], step: u64) -> [u8; 16] {
let mut mac = HmacSha256::new_from_slice(secret).expect("HMAC error");
mac.update(&step.to_be_bytes());
let result = mac.finalize().into_bytes();
let mut tag = [0u8; 16];
tag.copy_from_slice(&result[..16]);
tag
}
pub fn generate_auth_tag(&self) -> [u8; 16] {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
// Генерируем на основе текущей минуты
Self::compute_tag(&self.auth_key, now / 60)
}
pub fn verify_auth_tag(&self, received_tag: &[u8; 16]) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let current_step = now / 60;
// Вставляем цикл проверки расширенного окна [-2, +2]
// Это дает запас по времени в обе стороны
for step in (current_step.saturating_sub(2))..=(current_step.saturating_add(2)) {
if &Self::compute_tag(&self.auth_key, step) == received_tag {
// Если подошел не текущий, а другой шаг — логируем это для диагностики
if step != current_step {
tracing::debug!(expected = %current_step, matched = %step, "Auth tag valid with time offset");
}
return true;
}
}
// Если ни один не подошел — логируем для отладки
tracing::warn!(
current_step = %current_step,
"AUTH MISMATCH: All tags rejected for current window"
);
false
}
}
+8
View File
@@ -0,0 +1,8 @@
mod crypto;
mod logger;
pub mod protocol;
pub mod proxy;
mod tlseng;
mod utils;
pub use logger::logger_init;
+17
View File
@@ -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();
}
+174
View File
@@ -0,0 +1,174 @@
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};
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(),
))
}
}
pub fn pack_app_data(buffer: Bytes) -> Bytes {
TlsRecord::build_application_data(buffer)
}
}
+238
View File
@@ -0,0 +1,238 @@
use bytes::{Bytes, BytesMut};
use crate::crypto::aead::AeadPacker;
use crate::crypto::chacha::ChaChaCipher;
use crate::crypto::session::SessionKeys;
use crate::protocol::codec::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,
pub session_keys: SessionKeys,
staging: BytesMut,
}
impl Codec {
pub fn new(is_initiator: bool) -> Self {
Self {
crypto: ChaChaCipher::new(),
session_keys: SessionKeys::new(is_initiator),
staging: BytesMut::new(),
}
}
pub fn make_client_handshake(
&mut self,
profile: &BrowserProfile,
host: &str,
) -> Result<Bytes, TlsError> {
let pub_key = self.session_keys.ecdh.public_key.to_bytes();
// 2. Передаем его в мост
Ok(TlsBridge::wrap_client_hello(
profile,
host,
&pub_key,
self.session_keys.salt.get_local(),
))
}
pub fn make_server_handshake(&mut self, buffer: &mut BytesMut) -> Result<Bytes, TlsError> {
let client_msg = TlsBridge::unpack_handshake(buffer)?.ok_or_else(|| {
TlsError::new(
ErrorStage::Handshake("No CH"),
ErrorAction::Wait,
Bytes::new(),
)
})?;
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(),
)?;
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),
}
}
fn outbound(
&mut self,
stream_id: u32,
frame_type: FrameType,
payload: Bytes,
) -> Result<Bytes, TlsError> {
let padding = Padding::generate_padding();
let tag = self.session_keys.generate_auth_tag();
tracing::debug!(
step = %(std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() / 60),
auth_key_hash = %hex::encode(&self.session_keys.auth_key[..4]),
generated_tag = %hex::encode(&tag[..4]),
"OUTBOUND: Generated auth tag"
);
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(&tag);
// ВАЖНО: вызываем шифрование ОДИН РАЗ.
// Метод 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 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> {
// 1. Проверка старых данных
if !self.staging.is_empty() {
if let Some(frame) = self.try_parse_frame()? {
return Ok(Some(frame));
}
}
// 2. Цикл обработки новых рекордов
while let Some(app_data) = TlsBridge::unpack_app_data(buffer)? {
let mut data_to_decrypt = BytesMut::from(app_data.payload);
let decrypted = self.crypto.decrypt(&mut data_to_decrypt).map_err(|_| {
TlsError::new(
ErrorStage::Tls("Decr error"),
ErrorAction::Drop,
Bytes::new(),
)
})?;
// --- КРИТИЧЕСКАЯ ПРОВЕРКА ТЕГА ---
if decrypted.len() < 16 {
return Err(TlsError::new(
ErrorStage::Tls("Packet too short for auth"),
ErrorAction::Drop,
Bytes::new(),
));
}
let mut received_tag = [0u8; 16];
received_tag.copy_from_slice(&decrypted[..16]);
// Используем метод verify_auth_tag, который мы обсуждали ранее
if !self.session_keys.verify_auth_tag(&received_tag) {
tracing::error!(
expected_hash = %hex::encode(&self.session_keys.auth_key[..4]),
received = %hex::encode(&received_tag[..4]),
"AUTH MISMATCH: Potential replay or MITM attack. Dropping connection."
);
return Err(TlsError::new(
ErrorStage::Tls("Auth tag mismatch"),
ErrorAction::Drop, // Убиваем соединение
Bytes::new(),
));
}
// ---------------------------------
self.staging.extend_from_slice(&decrypted);
if let Some(frame) = self.try_parse_frame()? {
return Ok(Some(frame));
}
}
Ok(None)
}
// Выносим парсинг в отдельный метод, чтобы не дублировать код
fn try_parse_frame(&mut self) -> Result<Option<Frame>, TlsError> {
match Frame::parse(&mut self.staging) {
Ok(Some(frame)) => Ok(Some(frame)),
Ok(None) => Ok(None),
Err(_) => Err(TlsError::new(
ErrorStage::Tls("Parse error"),
ErrorAction::Drop,
Bytes::new(),
)),
}
}
}
+55
View File
@@ -0,0 +1,55 @@
use bytes::{BufMut, Bytes, BytesMut};
use crate::protocol::codec::padding::Padding;
#[derive(Copy, Clone, Debug)]
pub enum FrameType {
Connect = 0x00,
Data = 0x01,
Close = 0x02,
Heartbeat = 0x03,
}
#[derive(Copy, Clone)]
pub struct FrameHeader {
pub auth_tag: [u8; 16],
pub stream_id: u32,
pub frame_type: FrameType,
pub payload_len: u16,
pub padding_len: u16,
}
pub struct Frame {
pub header: FrameHeader,
pub payload: Bytes,
pub padding: Bytes,
}
const AUTH_TAG_SIZE: u16 = 16;
const STREAM_ID_SIZE: u16 = 4;
const FRAME_TYPE_SIZE: u16 = 1;
const PAYLOAD_LEN_SIZE: u16 = 2;
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 Frame {
pub fn into_bytes(self, auth_key: &[u8; 16]) -> 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);
buf.put_slice(auth_key);
buf.put_u32(self.header.stream_id);
buf.put_u8(self.header.frame_type as u8);
buf.put_u16(self.header.payload_len);
buf.put_u16(updated_padding.len);
buf.put(self.payload);
buf.put(updated_padding.data);
buf
}
}
+5
View File
@@ -0,0 +1,5 @@
mod bridge;
pub mod codec;
pub mod frame;
mod padding;
pub mod socks;
+21
View File
@@ -0,0 +1,21 @@
use bytes::Bytes;
use rand::Rng;
pub struct Padding {
pub len: u16,
pub data: Bytes,
}
impl Padding {
pub fn generate_padding() -> Padding {
let mut rng = rand::rng();
let random_u32: u32 = rng.next_u32();
let padding_len: u16 = (random_u32 % 255) as u16;
let mut padding = vec![0u8; padding_len as usize];
rng.fill_bytes(&mut padding);
Padding {
len: padding_len,
data: Bytes::from(padding),
}
}
}
+231
View File
@@ -0,0 +1,231 @@
use bytes::{BufMut, Bytes, BytesMut};
use crate::protocol::parser::parser::Parser;
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,
}
impl SocksRequest {
pub async fn handle_handshake<S>(
stream: &mut S,
buf: &mut BytesMut,
) -> Result<SocksTarget, String>
where
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
{
// 1. Handshake Phase
loop {
// Используем трейт Parser
if let Some(req) = Self::parse(buf)? {
if let SocksRequest::Handshake { .. } = req {
let mut reply = BytesMut::with_capacity(2);
SocksReply::HandshakeSelect { method: 0x00 }.write_to(&mut reply);
stream.write_all(&reply).await.map_err(|e| e.to_string())?;
break;
}
return Err("Expected Handshake, got something else".into());
}
if stream.read_buf(buf).await.map_err(|e| e.to_string())? == 0 {
return Err("Client closed during greeting".into());
}
}
// 2. Connect Request Phase
loop {
if let Some(req) = Self::parse(buf)? {
if let SocksRequest::Connect { command, target } = req {
// Проверяем, что это именно CONNECT (0x01)
if command != 0x01 {
return Err(format!("Unsupported SOCKS command: 0x{:02X}", command));
}
return Ok(target);
}
}
if stream.read_buf(buf).await.map_err(|e| e.to_string())? == 0 {
return Err("Client closed during connect request".into());
}
}
}
pub async fn perform_client_handshake<S>(
stream: &mut S,
target_addr: &TargetAddress,
) -> Result<(), String>
where
S: tokio::io::AsyncReadExt + tokio::io::AsyncWriteExt + Unpin,
{
// 1. Отправляем Greeting (SOCKS5, 1 метод: No Auth)
let greeting = [SOCKS5_VERSION, 0x01, 0x00];
stream
.write_all(&greeting)
.await
.map_err(|e| e.to_string())?;
// 2. Читаем выбор метода (должно быть 0x05 0x00)
let mut method_selection = [0u8; 2];
stream
.read_exact(&mut method_selection)
.await
.map_err(|e| e.to_string())?;
if method_selection[0] != SOCKS5_VERSION || method_selection[1] != 0x00 {
return Err(format!(
"Proxy rejected auth method or version: {:02X?}",
method_selection
));
}
// 3. Формируем CONNECT запрос
let mut connect_req = BytesMut::with_capacity(32);
connect_req.put_u8(SOCKS5_VERSION);
connect_req.put_u8(0x01); // CMD: Connect
connect_req.put_u8(0x00); // RSV
match target_addr {
TargetAddress::Ipv4(ip, port) => {
connect_req.put_u8(ATYP_IPV4);
connect_req.put_slice(&ip.octets());
connect_req.put_u16(*port);
}
TargetAddress::Ipv6(ip, port) => {
connect_req.put_u8(ATYP_IPV6);
connect_req.put_slice(&ip.octets());
connect_req.put_u16(*port);
}
TargetAddress::Domain(host, port) => {
connect_req.put_u8(ATYP_DOMAIN);
// SOCKS5 для домена требует: [1 байт длина] + [строка]
let host_bytes = host.as_bytes();
connect_req.put_u8(host_bytes.len() as u8);
connect_req.put_slice(host_bytes);
connect_req.put_u16(*port);
}
}
stream
.write_all(&connect_req)
.await
.map_err(|e| e.to_string())?;
// 4. Читаем ответ на Connect (REP)
// Нам нужно как минимум 4 байта, чтобы узнать статус (REPLY_SUCCESS)
let mut reply_header = [0u8; 4];
stream
.read_exact(&mut reply_header)
.await
.map_err(|e| e.to_string())?;
if reply_header[1] != REPLY_SUCCESS {
return Err(format!(
"Proxy failed to connect, code: {:02X}",
reply_header[1]
));
}
// Дочитываем оставшуюся часть адреса в ответе (BND.ADDR + BND.PORT),
// чтобы очистить поток перед передачей данных.
let atyp = reply_header[3];
let remain_len = match atyp {
ATYP_IPV4 => IPV4_SIZE + PORT_SIZE,
ATYP_IPV6 => IPV6_SIZE + PORT_SIZE,
ATYP_DOMAIN => {
let len = stream.read_u8().await.map_err(|e| e.to_string())?;
len as usize + PORT_SIZE
}
_ => return Err("Unknown ATYP in proxy response".into()),
};
let mut discard = vec![0u8; remain_len];
stream
.read_exact(&mut discard)
.await
.map_err(|e| e.to_string())?;
Ok(())
}
}
#[derive(Debug)]
pub enum SocksReply {
HandshakeSelect {
method: u8,
},
ConnectResult {
reply_code: u8,
atyp: u8,
addr: [u8; 4],
port: u16,
},
}
#[derive(Debug, Clone)]
pub enum TargetAddress {
Ipv4(std::net::Ipv4Addr, u16), // Теперь 2 поля
Domain(String, u16), // Теперь 2 поля
Ipv6(std::net::Ipv6Addr, u16), // Теперь 2 поля
}
#[derive(Debug)]
pub struct SocksTarget {
pub addr: TargetAddress,
}
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 {
match &self.addr {
// Теперь в каждом варианте TargetAddress уже есть порт (port)
TargetAddress::Ipv4(ip, port) => {
format!("{}:{}", ip, port)
}
TargetAddress::Ipv6(ip, port) => {
// IPv6 адреса принято заключать в квадратные скобки при наличии порта
format!("[{}]:{}", ip, port)
}
TargetAddress::Domain(domain, port) => {
// Вычищаем нулевые байты, если они случайно попали в строку
let clean_domain = domain.replace('\0', "");
format!("{}:{}", clean_domain, port)
}
}
}
}
+94
View File
@@ -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
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod codec;
pub mod errors;
pub mod parser;
+6
View File
@@ -0,0 +1,6 @@
mod netr;
pub mod parser;
mod socks;
mod tls;
use parser::Parser;
+100
View File
@@ -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,
}))
}
}
+9
View File
@@ -0,0 +1,9 @@
use bytes::BytesMut;
pub trait Parser {
type Error;
fn can_parse(bytes: &BytesMut) -> bool;
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error>
where
Self: Sized;
}
+126
View File
@@ -0,0 +1,126 @@
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 {
// Минимальная длина SOCKS5 CONNECT (VER, CMD, RSV, ATYP) = 4 байта
if bytes.len() < 4 {
return false;
}
let atyp = bytes[3];
match atyp {
// IPv4: 4 байта IP + 2 байта порт
ATYP_IPV4 => bytes.len() >= 4 + 4 + 2,
// IPv6: 16 байт IP + 2 байта порт
ATYP_IPV6 => bytes.len() >= 4 + 16 + 2,
// Domain: 1 байт длины + N байт домена + 2 байта порт
ATYP_DOMAIN => {
if bytes.len() < 5 {
return false;
}
let domain_len = bytes[4] as usize;
bytes.len() >= 4 + 1 + domain_len + 2
}
_ => false,
}
}
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
if !Self::can_parse(bytes) {
return Ok(None);
}
let atyp = bytes[3];
// Вычисляем длину (включая ATYP и порт)
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(4); // Пропускаем [VER, CMD, RSV, ATYP]
let addr = match atyp {
ATYP_IPV4 => {
let octets: [u8; 4] = packet.split_to(4)[..].try_into().unwrap();
let port = packet.get_u16(); // Достаем порт сразу
TargetAddress::Ipv4(octets.into(), port)
}
ATYP_DOMAIN => {
let len = packet.get_u8() as usize;
let domain_bytes = packet.split_to(len);
let domain = String::from_utf8(domain_bytes.to_vec())
.map_err(|_| "Invalid UTF-8 domain".to_string())?;
let port = packet.get_u16(); // Достаем порт сразу
TargetAddress::Domain(domain, port)
}
ATYP_IPV6 => {
let octets: [u8; 16] = packet.split_to(16)[..].try_into().unwrap();
let port = packet.get_u16(); // Достаем порт сразу
TargetAddress::Ipv6(octets.into(), port)
}
_ => unreachable!(),
};
// Теперь SocksTarget — это просто оболочка над новым TargetAddress
Ok(Some(SocksTarget { addr }))
}
}
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)
}
}
+368
View File
@@ -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 }))
}
}
+71
View File
@@ -0,0 +1,71 @@
use crate::protocol::codec::frame::FrameType;
use crate::proxy::connection::connection::BUF_SIZE;
use crate::proxy::connection::muxer::{MuxMessage, Muxer};
use bytes::{Bytes, BytesMut};
use tokio::sync::mpsc;
use tracing::{debug, error};
pub async fn run_proxy_bridge<R, W>(
stream_id: u32,
mut reader: R,
mut writer: W,
muxer: Muxer,
mut v_rx: mpsc::Receiver<Bytes>,
) where
R: tokio::io::AsyncReadExt + Unpin,
W: tokio::io::AsyncWriteExt + Unpin,
{
let mut buf = BytesMut::with_capacity(BUF_SIZE);
loop {
tokio::select! {
res = reader.read_buf(&mut buf) => {
match res {
Ok(0) => {
debug!(stream_id, "Socket closed (EOF)");
break;
}
Ok(_) => {
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, "Socket read error");
break;
}
}
}
// Читаем из туннеля (v_rx) -> шлем в сокет
maybe_data = v_rx.recv() => {
match maybe_data {
Some(data) => {
if data.is_empty() { break; } // EOF от другой стороны
if let Err(e) = writer.write_all(&data).await {
error!(stream_id, error = %e, "Socket write error");
break;
}
}
None => {
debug!(stream_id, "Virtual channel closed");
break;
}
}
}
}
}
// Финализация (общая для всех)
let _ = muxer
.to_network
.send(MuxMessage {
stream_id,
frame_type: FrameType::Close,
data: Bytes::new(),
})
.await;
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
muxer.remove_stream(stream_id).await;
}
+247
View File
@@ -0,0 +1,247 @@
use bytes::{Bytes, BytesMut};
use tracing::{instrument, info, debug, error, trace, warn};
use std::{net::SocketAddr};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpStream,
},
sync::mpsc::{self},
};
use crate::{
protocol::{
codec::{
codec::Codec,
frame::FrameType,
socks::{SocksReply, SocksRequest, SocksTarget},
},
errors::ErrorAction,
parser::parser::Parser,
},
proxy::connection::{
bridge::run_proxy_bridge, engine::TunnelEngine, handler::StreamHandler, muxer::{MuxMessage, Muxer}
},
};
pub const BUF_SIZE: usize = 16384;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ConnectionRole {
Client,
Server,
}
pub struct Connection {
addr: SocketAddr,
pub inbound: OwnedReadHalf,
pub outbound: OwnedWriteHalf,
pub read_buf: BytesMut,
pub codec: Codec,
}
impl Connection {
pub fn new(
stream: TcpStream,
addr: SocketAddr,
init: bool,
) -> Self {
let (inbound, outbound) = stream.into_split();
Self {
addr,
inbound,
outbound,
read_buf: BytesMut::with_capacity(BUF_SIZE),
codec: Codec::new(init),
}
}
/// Читает и парсит запрос SOCKS5 из входящего потока
async fn read_socks_request(&mut self) -> Result<SocksRequest, String> {
loop {
// Попытка парсинга из текущего буфера
match SocksRequest::parse(&mut self.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.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.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 = target.to_string();
// --- НОВАЯ ЛОГИКА ОЖИДАНИЯ ---
// Регистрируем временный канал, чтобы получить Connect-подтверждение от сервера
let (v_tx, mut v_rx) = mpsc::channel::<Bytes>(1024);
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;
let muxer_clone = muxer.clone();
tokio::spawn(async move {
run_proxy_bridge(stream_id, browser_in, browser_out, muxer_clone, v_rx).await;
});
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(BUF_SIZE);
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.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.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");
let handler = std::sync::Arc::new(StreamHandler::new(muxer.clone(), ConnectionRole::Server));
// 2. Передача управления в TunnelEngine
debug!("Handover to TunnelEngine");
let engine = TunnelEngine {
inbound: self.inbound,
outbound: self.outbound,
codec: self.codec,
read_buf: self.read_buf,
mux_rx,
handler
};
engine.run().await.map_err(|e| {
error!("TunnelEngine error: {}", e);
e
})
}
}
+111
View File
@@ -0,0 +1,111 @@
use std::sync::Arc;
use bytes::BytesMut;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::tcp::{OwnedReadHalf, OwnedWriteHalf},
sync::mpsc::Receiver,
};
use tracing::{debug, error};
use crate::{
protocol::{codec::codec::Codec, errors::ErrorAction},
proxy::connection::{handler::StreamHandler, muxer::MuxMessage},
};
pub struct TunnelEngine {
pub inbound: OwnedReadHalf,
pub outbound: OwnedWriteHalf,
pub codec: Codec,
pub read_buf: BytesMut,
pub mux_rx: Receiver<MuxMessage>,
pub handler: Arc<StreamHandler>, // Добавь это вместо прямого вызова логики
}
impl TunnelEngine {
pub async fn run(self) -> Result<(), String> {
let mut inbound = self.inbound;
let mut outbound = self.outbound;
let mut codec = self.codec;
let mut read_buf = self.read_buf;
let mut mux_rx = self.mux_rx;
let handler = self.handler;
loop {
tokio::select! {
res = Self::process_inbound(&mut inbound, &mut codec, &mut read_buf, &handler) => {
res?
}
// НУЖНО ОТПРАВИТЬ В СЕТЬ (В сторону удаленного прокси)
Some(msg) = mux_rx.recv() => {
Self::handle_outbound( &mut outbound, &mut codec, msg).await?;
}
}
}
}
async fn process_inbound(
inbound: &mut OwnedReadHalf,
codec: &mut Codec,
read_buf: &mut BytesMut,
handler: &Arc<StreamHandler>,
) -> Result<(), String> {
let n = inbound
.read_buf(read_buf)
.await
.map_err(|e| e.to_string())?;
if n == 0 && read_buf.is_empty() {
return Err("EOF".into());
}
loop {
match codec.inbound(read_buf) {
// 1. Успешно достали фрейм
Ok(Some(frame)) => {
handler.handle(frame).await;
}
// 2. Данных в буфере недостаточно (нужно подождать еще)
Ok(None) => break,
// 3. Ошибка кодека
Err(e) => {
// Если кодек говорит "подожди", выходим из цикла парсинга
if e.action == ErrorAction::Wait {
break;
}
// Иначе — это реальная проблема (кривой TLS и т.д.)
error!(error = ?e, "Codec inbound failed");
return Err(format!("Codec error: {:?}", e));
}
}
}
Ok(())
}
async fn handle_outbound(
outbound: &mut OwnedWriteHalf,
codec: &mut Codec,
msg: MuxMessage,
) -> Result<(), String> {
// 1. Шифруем данные, используя только кодек
match codec.encrypt_data(msg.stream_id, msg.frame_type, msg.data) {
Ok(pkt) => {
// 2. Пишем в сокет, используя только outbound
outbound
.write_all(&pkt)
.await
.map_err(|e| {
error!(stream_id = msg.stream_id, error = %e, "Failed to write encrypted data to network");
e.to_string()
})?;
debug!(stream_id = msg.stream_id, "Outbound packet sent");
Ok(())
}
Err(e) => {
error!(stream_id = msg.stream_id, error = ?e, "Encryption failed for outbound message");
Err(format!("Encryption error: {:?}", e))
}
}
}
}
+98
View File
@@ -0,0 +1,98 @@
use bytes::{Bytes, BytesMut};
use tracing::{debug, error, info};
use crate::{
protocol::codec::{
frame::{Frame, FrameType},
socks::SocksReply,
},
proxy::connection::{bridge::run_proxy_bridge, connection::ConnectionRole, muxer::Muxer},
};
// proxy/connection/stream_handler.rs
pub struct StreamHandler {
muxer: Muxer,
role: ConnectionRole,
}
impl StreamHandler {
pub fn new(muxer: Muxer, role: ConnectionRole) -> Self {
Self { muxer, role }
}
pub async fn handle(&self, frame: Frame) {
let stream_id = frame.header.stream_id;
match frame.header.frame_type {
FrameType::Connect => self.on_connect(stream_id, frame.payload).await,
FrameType::Data => self.on_data(stream_id, frame.payload).await,
FrameType::Close => self.on_close(stream_id).await,
_ => debug!(stream_id, "Unhandled frame type"),
}
}
async fn on_connect(&self, stream_id: u32, payload: Bytes) {
if self.role == ConnectionRole::Server {
let target_str = String::from_utf8_lossy(&payload).to_string();
let muxer = self.muxer.clone();
let (v_tx, v_rx) = tokio::sync::mpsc::channel(100);
muxer.register_stream(stream_id, v_tx).await;
tokio::spawn(async move {
info!(stream_id, target = %target_str, "Attempting remote connection");
match tokio::net::TcpStream::connect(&target_str).await {
Ok(stream) => {
// --- ШАГ 2: ШЛЕМ ПОДТВЕРЖДЕНИЕ ---
let mut reply_buf = BytesMut::with_capacity(10);
let reply = SocksReply::ConnectResult {
reply_code: 0x00,
atyp: 0x01,
addr: [0, 0, 0, 0],
port: 0,
};
reply.write_to(&mut reply_buf);
let _ = muxer
.send_control(stream_id, FrameType::Connect, reply_buf.freeze())
.await;
// --- ШАГ 3: ЗАПУСКАЕМ МОСТ ---
let (r, w) = stream.into_split();
run_proxy_bridge(stream_id, r, w, muxer, v_rx).await;
}
Err(e) => {
error!(stream_id, error = %e, "Connection failed");
// Если не подключились — удаляем стрим, чтобы не висел в мапе
muxer.remove_stream(stream_id).await;
let mut reply_buf = BytesMut::with_capacity(10);
let reply = SocksReply::ConnectResult {
reply_code: 0x01,
atyp: 0x01,
addr: [0, 0, 0, 0],
port: 0,
};
reply.write_to(&mut reply_buf);
let _ = muxer
.send_control(stream_id, FrameType::Connect, reply_buf.freeze())
.await;
}
}
});
} else {
// Логика для клиента (проброс ответа сервера браузеру)
self.muxer.dispatch_to_local(stream_id, payload).await;
}
}
async fn on_data(&self, stream_id: u32, payload: Bytes) {
self.muxer.dispatch_to_local(stream_id, payload).await;
}
async fn on_close(&self, stream_id: u32) {
self.muxer.dispatch_to_local(stream_id, Bytes::new()).await;
self.muxer.remove_stream(stream_id).await;
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod bridge;
pub mod connection;
pub mod engine;
pub mod handler;
pub mod muxer;
+115
View File
@@ -0,0 +1,115 @@
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::{debug, 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)),
}
}
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(),
"MUXER: [REGISTER] Stream added"
);
}
pub async fn remove_stream(&self, stream_id: u32) {
let mut lock = self.streams.write().await;
lock.remove(&stream_id); // Просто удаляем, если есть
}
pub async fn send_control(
&self,
stream_id: u32,
f_type: FrameType,
data: Bytes,
) -> Result<(), String> {
self.to_network
.send(MuxMessage {
stream_id,
frame_type: f_type,
data,
})
.await
.map_err(|e| e.to_string())
}
pub async fn dispatch_to_local(&self, stream_id: u32, data: Bytes) {
let tx = {
let lock = self.streams.read().await;
lock.get(&stream_id).cloned()
};
if let Some(tx) = tx {
if data.is_empty() {
tracing::debug!(stream_id, "MUXER: [EOF] Forwarding EOF to local handler");
} else {
tracing::trace!(
stream_id,
len = data.len(),
"MUXER: [DISPATCH] Sending data"
);
}
if let Err(_e) = tx.send(data).await {
tracing::debug!(
stream_id,
"MUXER: [WARN] Local channel closed, dropping packet"
);
self.remove_stream(stream_id).await;
}
} else {
tracing::trace!(
stream_id,
"MUXER: [IGNORE] Packet for already closed stream"
);
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod connection;
pub mod network;
+157
View File
@@ -0,0 +1,157 @@
use crate::{
protocol::errors::ErrorAction,
proxy::connection::{
connection::{Connection, ConnectionRole, BUF_SIZE},
engine::TunnelEngine,
muxer::Muxer,
},
tlseng::profile::BrowserProfile,
};
use bytes::BytesMut;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream},
};
use tracing::{error, info, instrument}; // Импортируем макросы
pub struct Network {
host: String,
port: u16,
role: ConnectionRole,
remote_proxy_addr: Option<String>,
}
impl Network {
pub fn new(
host: String,
port: u16,
role: ConnectionRole,
remote_proxy_addr: Option<String>,
) -> Self {
Self {
host,
port,
role,
remote_proxy_addr,
}
}
// Добавляем инструмент, чтобы видеть параметры запуска сети в логах
#[instrument(skip(self), fields(role = ?self.role, port = self.port))]
pub async fn run(&self) {
let addr = format!("{}:{}", self.host, self.port);
match self.role {
ConnectionRole::Client => {
info!("Starting Client mode: Initializing persistent tunnel to proxy...");
let muxer = match self.initialize_client_tunnel().await {
Ok(m) => m,
Err(e) => {
error!(error = %e, "Global tunnel failed. Exit.");
return;
}
};
let listener = TcpListener::bind(&addr).await.expect("SOCKS bind failed");
info!(socks_addr = %addr, "SOCKS5 ready");
loop {
if let Ok((stream, client_addr)) = listener.accept().await {
let current_muxer = muxer.clone();
tokio::spawn(async move {
// Здесь мы просто создаем Connection и сразу в SOCKS
let connection = Connection::new(stream, client_addr, false);
let _ = connection.handle_socks_client(current_muxer).await;
});
}
}
}
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
pub async fn initialize_client_tunnel(&self) -> Result<Muxer, String> {
let server_addr = self.remote_proxy_addr.as_ref().ok_or("No proxy addr")?;
// Вместо создания Connection (который нужен для обработки клиентов),
// работаем напрямую с TcpStream для первичного TLS-хендшейка.
let stream = TcpStream::connect(server_addr)
.await
.map_err(|e| e.to_string())?;
let (mut inbound, mut outbound) = stream.into_split();
// Кодек создаем «с чистого листа»
let mut codec = crate::protocol::codec::codec::Codec::new(false);
// --- TLS Handshake ---
let ch = codec
.make_client_handshake(&BrowserProfile::CHROME_131, "google.com")
.map_err(|e| format!("{:?}", e))?;
outbound.write_all(&ch).await.map_err(|e| e.to_string())?;
let mut sh_buf = BytesMut::with_capacity(2048);
loop {
// Пытаемся обработать то, что уже есть в буфере
match codec.process_handshake(&mut sh_buf) {
Ok(_) => break, // Готово!
Err(e) if e.action == ErrorAction::Wait => {
let n = inbound
.read_buf(&mut sh_buf)
.await
.map_err(|e| e.to_string())?;
if n == 0 {
return Err("EOF during handshake".into());
}
}
Err(e) => return Err(format!("TLS error: {:?}", e)),
}
}
// --- Запуск инфраструктуры ---
let (mux_tx, mux_rx) = tokio::sync::mpsc::channel(BUF_SIZE);
let muxer = Muxer::new(mux_tx, true);
let handler = std::sync::Arc::new(crate::proxy::connection::handler::StreamHandler::new(
muxer.clone(),
ConnectionRole::Client,
));
let engine = TunnelEngine {
inbound,
outbound,
codec,
read_buf: sh_buf, // Передаем остатки данных из буфера хендшейка в движок!
mux_rx,
handler,
};
tokio::spawn(async move { engine.run().await });
Ok(muxer)
}
pub fn get_self_local_address(&self) -> String {
format!("127.0.0.1:{}", self.port)
}
}
+23
View File
@@ -0,0 +1,23 @@
/// Handshake message types
pub const HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
pub const HANDSHAKE_TYPE_SERVER_HELLO: u8 = 0x02;
/// SNI (Server Name Indication) specific
pub const TYPE_HOST_NAME: u8 = 0x00;
/// 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)
/// Используется для предотвращения ошибок серверов при встрече с неизвестными ID.
pub const GREASE_IDENTIFIERS: [u16; 16] = [
0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA,
0xCACA, 0xDADA, 0xEAEA, 0xFAFA,
];
+276
View File
@@ -0,0 +1,276 @@
use bytes::{BufMut, Bytes, BytesMut};
use rand::RngExt;
// Using your provided constants and types
use crate::tlseng::{
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)]
pub struct Extension {
pub etype: u16,
pub elen: u16,
pub data: Bytes,
}
#[derive(Debug)]
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,
elen: data.len() as u16,
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);
ext.put_u16(data.len() as u16);
ext.put_slice(data);
ext.freeze()
}
}
pub struct ExtensionBuilder {
payload: BytesMut,
}
impl ExtensionBuilder {
pub fn new() -> Self {
Self {
payload: BytesMut::with_capacity(2048),
}
}
fn add_extension(&mut self, etype: u16, data: &[u8]) {
let ext = Extension::pack(etype, data);
self.payload.put_slice(&ext);
}
pub fn grease(&mut self) {
let mut rng = rand::rng();
let rnd = rng.random_range(0..GREASE_IDENTIFIERS.len());
let etype = GREASE_IDENTIFIERS[rnd];
self.add_extension(etype, &[]);
}
pub fn server_name(&mut self, host: &str) {
let host_bytes = host.as_bytes();
let host_len = host_bytes.len() as u16;
let list_inner_len = 1 + 2 + host_len;
let mut data = BytesMut::with_capacity(2 + list_inner_len as usize);
data.put_u16(list_inner_len);
data.put_u8(TYPE_HOST_NAME);
data.put_u16(host_len);
data.put_slice(host_bytes);
self.add_extension(TlsExtensions::SNI, &data);
}
pub fn extended_main_secret(&mut self) {
self.add_extension(TlsExtensions::EMS, &[]);
}
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(TlsExtensions::SUPPORTED_GROUPS, &data);
}
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(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);
}
pub fn application_settings(&mut self, protocols: &[&str]) {
let mut data = BytesMut::new();
for proto in protocols {
let p_bytes = proto.as_bytes();
data.put_u8(p_bytes.len() as u8);
data.put_slice(p_bytes);
data.put_u16(0); // Empty settings per-protocol
}
self.add_extension(TlsExtensions::ALPS, &data);
}
pub fn alpn(&mut self, protocols: &[&str]) {
let mut list_data = BytesMut::new();
for proto in protocols {
let bytes = proto.as_bytes();
list_data.put_u8(bytes.len() as u8);
list_data.put_slice(bytes);
}
let mut extension_data = BytesMut::new();
extension_data.put_u16(list_data.len() as u16);
extension_data.put_slice(&list_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);
}
pub fn session_ticket(&mut self) {
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();
if target_size > current_size + 4 {
let pad_len = target_size - current_size - 4;
let data = vec![0u8; pad_len];
self.add_extension(TlsExtensions::PADDING, &data);
}
}
pub fn apply_profile(&mut self, profile: &BrowserProfile, host: &str, pub_key: &[u8]) {
for &ext_id in &profile.extension_order {
match ext_id {
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 {
self.padding(512);
} else {
self.add_extension(TlsExtensions::PADDING, &[]);
}
}
// Обработка GREASE по маске
id if (id & 0x0f0f) == 0x0a0a => self.grease(),
_ => {}
}
}
}
pub fn build(&mut self) -> Bytes {
self.payload.split().freeze()
}
}
+238
View File
@@ -0,0 +1,238 @@
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 {
let tls_random = salt;
let mut ext_builder = ExtensionBuilder::new();
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]);
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,
};
let record = TlsRecord::new(
ContentType::Handshake,
ProtocolVersion::Tls10,
client_hello.serialize(),
);
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 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
}
}
+13
View File
@@ -0,0 +1,13 @@
use bytes::Bytes;
pub struct ApplicationData {
pub len: usize,
pub payload: Bytes,
}
mod consts;
pub mod extension;
pub mod handshake;
pub mod profile;
pub mod tls_record;
pub mod types;
+103
View File
@@ -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,
};
}
+63
View File
@@ -0,0 +1,63 @@
use bytes::{BufMut, Bytes, BytesMut};
use crate::tlseng::types::{ContentType, ProtocolVersion};
/// The TLS Record Layer structure.
/// This is the outer envelope that wraps all TLS messages sent over the wire.
#[derive(Debug)]
pub struct TlsRecord {
/// The type of data contained (Handshake, ApplicationData, etc.)
pub content_type: ContentType,
/// The record layer version (usually 0x0301 for legacy support)
pub version: ProtocolVersion,
pub len: u16,
/// The actual data being transported (e.g., a serialized ClientHello)
pub payload: Bytes,
}
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,
version,
len: payload.len() as u16,
payload,
}
}
/// Serializes the Record Layer header and payload.
/// Wire Format: [Type (1)] [Version (2)] [Length (2)] [Payload (N)]
pub fn serialize(&self) -> Bytes {
let mut buf = BytesMut::with_capacity(5 + self.payload.len());
buf.put_u8(self.content_type as u8);
buf.put_u16(self.version as u16);
buf.put_u16(self.payload.len() as u16);
buf.put_slice(&self.payload);
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()
}
}
+259
View File
@@ -0,0 +1,259 @@
/// TLS Content Types as defined in the TLS Record Protocol.
/// These identify what is contained within the TLS Record payload.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentType {
/// Handshake messages (e.g., ClientHello, ServerHello)
Handshake = 0x16,
/// Encrypted application data (the actual traffic)
ApplicationData = 0x17,
/// Notification messages (e.g., CloseNotify or error signals)
Alert = 0x15,
}
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),
0x17 => Ok(ContentType::ApplicationData),
0x15 => Ok(ContentType::Alert),
_ => Err("This is not ContentType"),
}
}
}
///
/// 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 {
Tls10 = 0x0301,
Tls12 = 0x0303,
Tls13 = 0x0304,
}
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),
0x02 => Ok(HelloType::Server),
_ => Err("This is not Hello header"),
}
}
}
/// 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
View File
@@ -0,0 +1 @@
pub mod u24;
+59
View File
@@ -0,0 +1,59 @@
use bytes::Buf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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;
let b3 = self.get_u8() as u32;
(b1 << 16) | (b2 << 8) | b3
}
}
impl<T: Buf> BufExt for T {}