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
+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 }))
}
}