clean project from comments
This commit is contained in:
@@ -8,7 +8,6 @@ use crate::tlseng::types::{ContentType, HelloType};
|
||||
use crate::tlseng::ApplicationData;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
// --- 1. Общий интерфейс перехвата ---
|
||||
pub trait TlsInterceptor {
|
||||
type Output;
|
||||
|
||||
@@ -23,7 +22,6 @@ pub trait TlsInterceptor {
|
||||
fn handle_record(record: TlsRecord) -> Result<Option<Self::Output>, TlsError>;
|
||||
}
|
||||
|
||||
// --- 2. Обработка Handshake ---
|
||||
pub enum HandshakeMessage {
|
||||
Client {
|
||||
base: ClientHello,
|
||||
@@ -106,7 +104,6 @@ impl TlsInterceptor for HandshakeMessage {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 3. Обработка Application Data ---
|
||||
impl TlsInterceptor for ApplicationData {
|
||||
type Output = ApplicationData;
|
||||
|
||||
@@ -125,12 +122,9 @@ impl TlsInterceptor for ApplicationData {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. Высокоуровневый Bridge API ---
|
||||
pub struct TlsBridge;
|
||||
|
||||
impl TlsBridge {
|
||||
// --- Распаковка (уже была) ---
|
||||
|
||||
pub fn unpack_handshake(buffer: &mut BytesMut) -> Result<Option<HandshakeMessage>, TlsError> {
|
||||
HandshakeMessage::start_process(buffer)
|
||||
}
|
||||
@@ -139,19 +133,15 @@ impl TlsBridge {
|
||||
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) // Передаем ключ дальше
|
||||
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],
|
||||
|
||||
@@ -32,7 +32,6 @@ impl Codec {
|
||||
) -> Result<Bytes, TlsError> {
|
||||
let pub_key = self.session_keys.ecdh.public_key.to_bytes();
|
||||
|
||||
// 2. Передаем его в мост
|
||||
Ok(TlsBridge::wrap_client_hello(
|
||||
profile,
|
||||
host,
|
||||
@@ -69,7 +68,6 @@ impl Codec {
|
||||
)
|
||||
})?;
|
||||
|
||||
// Инициализируем шифратор сервера
|
||||
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
|
||||
|
||||
Ok(server_hello_record)
|
||||
@@ -85,8 +83,6 @@ impl Codec {
|
||||
)
|
||||
})?;
|
||||
|
||||
// ОБНОВЛЕНИЕ КЛЮЧЕЙ НА КЛИЕНТЕ
|
||||
// Передаем false, так как клиент ПАРСИТ ServerHello (смещение 4 байта)
|
||||
let (w_key, w_iv, r_key, r_iv) = self
|
||||
.session_keys
|
||||
.update_keys(mes.random(), mes.extensions(), false)
|
||||
@@ -142,9 +138,6 @@ impl Codec {
|
||||
};
|
||||
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(
|
||||
@@ -154,7 +147,6 @@ impl Codec {
|
||||
)
|
||||
})?;
|
||||
|
||||
// Теперь передаем зашифрованные байты в новый метод упаковки
|
||||
Ok(TlsBridge::pack_app_data(encrypted_payload))
|
||||
}
|
||||
|
||||
@@ -168,14 +160,12 @@ impl Codec {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -187,7 +177,6 @@ impl Codec {
|
||||
)
|
||||
})?;
|
||||
|
||||
// --- КРИТИЧЕСКАЯ ПРОВЕРКА ТЕГА ---
|
||||
if decrypted.len() < 16 {
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Tls("Packet too short for auth"),
|
||||
@@ -199,7 +188,6 @@ impl Codec {
|
||||
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]),
|
||||
@@ -208,11 +196,10 @@ impl Codec {
|
||||
);
|
||||
return Err(TlsError::new(
|
||||
ErrorStage::Tls("Auth tag mismatch"),
|
||||
ErrorAction::Drop, // Убиваем соединение
|
||||
ErrorAction::Drop,
|
||||
Bytes::new(),
|
||||
));
|
||||
}
|
||||
// ---------------------------------
|
||||
|
||||
self.staging.extend_from_slice(&decrypted);
|
||||
|
||||
@@ -223,7 +210,7 @@ impl Codec {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
// Выносим парсинг в отдельный метод, чтобы не дублировать код
|
||||
|
||||
fn try_parse_frame(&mut self) -> Result<Option<Frame>, TlsError> {
|
||||
match Frame::parse(&mut self.staging) {
|
||||
Ok(Some(frame)) => Ok(Some(frame)),
|
||||
|
||||
@@ -27,9 +27,7 @@ impl SocksRequest {
|
||||
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);
|
||||
@@ -44,11 +42,9 @@ impl SocksRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
@@ -68,14 +64,12 @@ impl SocksRequest {
|
||||
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)
|
||||
@@ -89,11 +83,10 @@ impl SocksRequest {
|
||||
));
|
||||
}
|
||||
|
||||
// 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
|
||||
connect_req.put_u8(0x01);
|
||||
connect_req.put_u8(0x00);
|
||||
|
||||
match target_addr {
|
||||
TargetAddress::Ipv4(ip, port) => {
|
||||
@@ -108,7 +101,7 @@ impl SocksRequest {
|
||||
}
|
||||
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);
|
||||
@@ -121,8 +114,6 @@ impl SocksRequest {
|
||||
.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)
|
||||
@@ -136,8 +127,6 @@ impl SocksRequest {
|
||||
));
|
||||
}
|
||||
|
||||
// Дочитываем оставшуюся часть адреса в ответе (BND.ADDR + BND.PORT),
|
||||
// чтобы очистить поток перед передачей данных.
|
||||
let atyp = reply_header[3];
|
||||
let remain_len = match atyp {
|
||||
ATYP_IPV4 => IPV4_SIZE + PORT_SIZE,
|
||||
@@ -174,9 +163,9 @@ pub enum SocksReply {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TargetAddress {
|
||||
Ipv4(std::net::Ipv4Addr, u16), // Теперь 2 поля
|
||||
Domain(String, u16), // Теперь 2 поля
|
||||
Ipv6(std::net::Ipv6Addr, u16), // Теперь 2 поля
|
||||
Ipv4(std::net::Ipv4Addr, u16),
|
||||
Domain(String, u16),
|
||||
Ipv6(std::net::Ipv6Addr, u16),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -199,7 +188,7 @@ impl SocksReply {
|
||||
} => {
|
||||
buf.put_u8(SOCKS5_VERSION);
|
||||
buf.put_u8(reply_code);
|
||||
buf.put_u8(0x00); // Reserved
|
||||
buf.put_u8(0x00);
|
||||
buf.put_u8(atyp);
|
||||
buf.put_slice(&addr);
|
||||
buf.put_u16(port);
|
||||
@@ -211,18 +200,15 @@ impl SocksReply {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -32,10 +32,6 @@ impl TlsError {
|
||||
}
|
||||
|
||||
fn log_error(&self) {
|
||||
// Определяем уровень логирования в зависимости от действия
|
||||
// Если мы просто ждем данные (Wait) — это не ошибка, а рабочий процесс (debug/trace)
|
||||
// Если дропаем соединение (Drop) — это серьезно (error)
|
||||
|
||||
let stage_name = match &self.stage {
|
||||
ErrorStage::Tls(_) => "TLS",
|
||||
ErrorStage::Handshake(_) => "Handshake",
|
||||
@@ -46,7 +42,6 @@ impl TlsError {
|
||||
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!(
|
||||
@@ -60,7 +55,6 @@ impl TlsError {
|
||||
|
||||
match self.action {
|
||||
ErrorAction::Wait => {
|
||||
// Wait — это нормальное состояние асинхронного чтения
|
||||
trace!(
|
||||
stage = stage_name,
|
||||
action = ?self.action,
|
||||
|
||||
@@ -50,13 +50,10 @@ 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;
|
||||
|
||||
@@ -68,7 +65,6 @@ impl Parser for Frame {
|
||||
bytes.len()
|
||||
);
|
||||
|
||||
// 3. Проверяем, есть ли в буфере весь фрейм целиком
|
||||
bytes.len() >= (FRAME_HEADER_SIZE as usize + p_len + pad_len)
|
||||
}
|
||||
|
||||
@@ -77,13 +73,11 @@ impl Parser for Frame {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -6,18 +6,16 @@ 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;
|
||||
@@ -35,7 +33,7 @@ impl Parser for SocksTarget {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -44,12 +42,12 @@ impl Parser for SocksTarget {
|
||||
};
|
||||
|
||||
let mut packet = bytes.split_to(total_len);
|
||||
packet.advance(4); // Пропускаем [VER, CMD, RSV, ATYP]
|
||||
packet.advance(4);
|
||||
|
||||
let addr = match atyp {
|
||||
ATYP_IPV4 => {
|
||||
let octets: [u8; 4] = packet.split_to(4)[..].try_into().unwrap();
|
||||
let port = packet.get_u16(); // Достаем порт сразу
|
||||
let port = packet.get_u16();
|
||||
TargetAddress::Ipv4(octets.into(), port)
|
||||
}
|
||||
ATYP_DOMAIN => {
|
||||
@@ -57,18 +55,17 @@ impl Parser for SocksTarget {
|
||||
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(); // Достаем порт сразу
|
||||
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(); // Достаем порт сразу
|
||||
let port = packet.get_u16();
|
||||
TargetAddress::Ipv6(octets.into(), port)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// Теперь SocksTarget — это просто оболочка над новым TargetAddress
|
||||
Ok(Some(SocksTarget { addr }))
|
||||
}
|
||||
}
|
||||
@@ -83,12 +80,10 @@ impl Parser for SocksRequest {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -100,7 +95,6 @@ impl Parser for SocksRequest {
|
||||
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)? {
|
||||
@@ -108,7 +102,6 @@ impl Parser for SocksRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Если не Connect, пробуем Handshake
|
||||
let nmethods = bytes[1] as usize;
|
||||
let total_handshake = 2 + nmethods;
|
||||
|
||||
|
||||
@@ -14,20 +14,14 @@ use crate::{
|
||||
};
|
||||
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
|
||||
@@ -37,21 +31,14 @@ impl Parser for TlsRecord {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -60,7 +47,6 @@ impl Parser for TlsRecord {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// --- ТОЛЬКО ТЕПЕРЬ МЫ ИЗМЕНЯЕМ БУФЕР ---
|
||||
let raw_content_type = bytes.get_u8();
|
||||
let raw_version = bytes.get_u16();
|
||||
let record_len = bytes.get_u16() as usize;
|
||||
@@ -71,17 +57,12 @@ impl Parser for TlsRecord {
|
||||
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;
|
||||
|
||||
@@ -128,15 +109,11 @@ impl Parser for HelloHeader {
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 3. HELLO MESSAGES
|
||||
// =================================================================
|
||||
|
||||
impl Parser for ClientHello {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut offset = 34; // ProtocolVersion (2) + Random (32)
|
||||
let mut offset = 34;
|
||||
if bytes.len() < offset + 1 {
|
||||
return false;
|
||||
}
|
||||
@@ -165,8 +142,7 @@ impl Parser for ClientHello {
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
// --- ШАГ 1: Атомарная проверка всего пакета ---
|
||||
let mut offset = 34; // Version + Random
|
||||
let mut offset = 34;
|
||||
if bytes.len() < offset + 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -190,13 +166,10 @@ impl Parser for ClientHello {
|
||||
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())
|
||||
@@ -216,7 +189,7 @@ impl Parser for ClientHello {
|
||||
}
|
||||
|
||||
let cmp_len = msg.get_u8() as usize;
|
||||
msg.advance(cmp_len); // пропускаем методы сжатия
|
||||
msg.advance(cmp_len);
|
||||
|
||||
let extensions = if msg.remaining() >= 2 {
|
||||
let ext_len = msg.get_u16() as usize;
|
||||
@@ -239,7 +212,7 @@ impl Parser for ServerHello {
|
||||
type Error = TlsError;
|
||||
|
||||
fn can_parse(bytes: &BytesMut) -> bool {
|
||||
let mut offset = 34; // ProtocolVersion (2) + Random (32)
|
||||
let mut offset = 34;
|
||||
if bytes.len() < offset + 1 {
|
||||
return false;
|
||||
}
|
||||
@@ -247,7 +220,6 @@ impl Parser for ServerHello {
|
||||
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 {
|
||||
@@ -259,15 +231,14 @@ impl Parser for ServerHello {
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
// --- ШАГ 1: Атомарная проверка всего пакета ---
|
||||
let mut offset = 34; // Version + Random
|
||||
let mut offset = 34;
|
||||
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)
|
||||
offset += 3;
|
||||
|
||||
if bytes.len() >= offset + 2 {
|
||||
let ext_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize;
|
||||
@@ -278,7 +249,6 @@ impl Parser for ServerHello {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// --- ШАГ 2: Безопасное чтение ---
|
||||
let mut msg = bytes.split_to(offset);
|
||||
|
||||
let version = ProtocolVersion::try_from(msg.get_u16())
|
||||
@@ -291,7 +261,7 @@ impl Parser for ServerHello {
|
||||
let session_id = msg.split_to(sid_len).freeze();
|
||||
|
||||
let cipher_suite = msg.get_u16();
|
||||
msg.advance(1); // compression
|
||||
msg.advance(1);
|
||||
|
||||
let extensions = if msg.remaining() >= 2 {
|
||||
let ext_len = msg.get_u16() as usize;
|
||||
@@ -310,10 +280,6 @@ impl Parser for ServerHello {
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. EXTENSIONS
|
||||
// =================================================================
|
||||
|
||||
impl Parser for ExtensionStack {
|
||||
type Error = TlsError;
|
||||
|
||||
@@ -330,7 +296,6 @@ impl Parser for ExtensionStack {
|
||||
}
|
||||
|
||||
fn parse(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||
// Проверяем на целостность всех расширений
|
||||
let mut offset = 0;
|
||||
let data_len = bytes.len();
|
||||
|
||||
@@ -339,13 +304,10 @@ impl Parser for ExtensionStack {
|
||||
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"),
|
||||
@@ -354,7 +316,6 @@ impl Parser for ExtensionStack {
|
||||
));
|
||||
}
|
||||
|
||||
// Теперь гарантированно безопасно парсить всё до конца
|
||||
let mut extensions = Vec::new();
|
||||
while bytes.remaining() >= 4 {
|
||||
let etype = bytes.get_u16();
|
||||
|
||||
Reference in New Issue
Block a user