hmac and ad
This commit is contained in:
@@ -76,8 +76,8 @@ impl AeadPacker for ChaChaCipher {
|
|||||||
let current_counter = self.encrypt_state.counter;
|
let current_counter = self.encrypt_state.counter;
|
||||||
let nonce = self.encrypt_state.next_nonce();
|
let nonce = self.encrypt_state.next_nonce();
|
||||||
let data_len = data.len();
|
let data_len = data.len();
|
||||||
|
// maybe ad should be stream id
|
||||||
match self.encrypt_cipher.encrypt_in_place(&nonce, &[], data) {
|
match self.encrypt_cipher.encrypt_in_place(&nonce, &nonce, data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
counter = current_counter,
|
counter = current_counter,
|
||||||
@@ -104,8 +104,8 @@ impl AeadPacker for ChaChaCipher {
|
|||||||
let current_counter = self.decrypt_state.counter;
|
let current_counter = self.decrypt_state.counter;
|
||||||
let nonce = self.decrypt_state.next_nonce();
|
let nonce = self.decrypt_state.next_nonce();
|
||||||
let data_len = data.len();
|
let data_len = data.len();
|
||||||
|
// maybe ad should be stream id
|
||||||
match self.decrypt_cipher.decrypt_in_place(&nonce, &[], data) {
|
match self.decrypt_cipher.decrypt_in_place(&nonce, &nonce, data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
counter = current_counter,
|
counter = current_counter,
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
use hmac::{Hmac, Mac};
|
|
||||||
use sha2::Sha256;
|
|
||||||
|
|
||||||
type HmacSha256 = Hmac<Sha256>;
|
|
||||||
|
|
||||||
pub fn generate_auth_tag(secret: &[u8]) -> [u8; 16] {
|
|
||||||
let now = std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs();
|
|
||||||
let minute_step = (now / 60).to_be_bytes();
|
|
||||||
|
|
||||||
// Создаем HMAC-Sha256 с твоим секретным ключом
|
|
||||||
let mut mac = HmacSha256::new_from_slice(secret).expect("HMAC can take key of any size");
|
|
||||||
|
|
||||||
// Подмешиваем туда текущую минуту
|
|
||||||
mac.update(&minute_step);
|
|
||||||
|
|
||||||
let result = mac.finalize().into_bytes();
|
|
||||||
|
|
||||||
// Берем первые 16 байт от 32-байтного хеша SHA256
|
|
||||||
let mut tag = [0u8; 16];
|
|
||||||
tag.copy_from_slice(&result[..16]);
|
|
||||||
tag
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,5 @@ pub mod aead;
|
|||||||
pub mod chacha;
|
pub mod chacha;
|
||||||
mod ecdh;
|
mod ecdh;
|
||||||
mod hkdf;
|
mod hkdf;
|
||||||
mod hmac;
|
|
||||||
mod salt_pair;
|
mod salt_pair;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
|||||||
@@ -5,9 +5,15 @@ use crate::{
|
|||||||
tlseng::extension::ExtensionStack,
|
tlseng::extension::ExtensionStack,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
|
use sha2::Sha256;
|
||||||
|
|
||||||
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
pub struct SessionKeys {
|
pub struct SessionKeys {
|
||||||
pub salt: SaltPair,
|
pub salt: SaltPair,
|
||||||
pub ecdh: ECDH,
|
pub ecdh: ECDH,
|
||||||
|
pub auth_key: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionKeys {
|
impl SessionKeys {
|
||||||
@@ -15,6 +21,7 @@ impl SessionKeys {
|
|||||||
Self {
|
Self {
|
||||||
salt: SaltPair::new(is_initiator),
|
salt: SaltPair::new(is_initiator),
|
||||||
ecdh: ECDH::new(),
|
ecdh: ECDH::new(),
|
||||||
|
auth_key: [0u8; 32],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +119,8 @@ impl SessionKeys {
|
|||||||
let s_key = HKDF::expand_key::<32>(&hkdf, b"server_aead").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 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!(
|
tracing::info!(
|
||||||
client_key_short = %hex::encode(&c_key[..4]),
|
client_key_short = %hex::encode(&c_key[..4]),
|
||||||
server_key_short = %hex::encode(&s_key[..4]),
|
server_key_short = %hex::encode(&s_key[..4]),
|
||||||
@@ -125,4 +134,48 @@ impl SessionKeys {
|
|||||||
Ok((c_key, c_iv, s_key, s_iv)) // Клиент пишет своим, читает серверным
|
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;
|
||||||
|
|
||||||
|
// 1. Проверяем текущую минуту (самый вероятный случай)
|
||||||
|
if &Self::compute_tag(&self.auth_key, current_step) == received_tag {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Проверяем предыдущую минуту (на случай стыка минут или задержки сети)
|
||||||
|
if &Self::compute_tag(&self.auth_key, current_step - 1) == received_tag {
|
||||||
|
tracing::debug!("Auth tag valid (matched previous minute window)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если ни один не подошел — тег невалиден
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательная функция для генерации конкретного тега
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub fn logger_init() {
|
|||||||
.with_line_number(true); // Показывать строку кода (очень полезно для дебага)
|
.with_line_number(true); // Показывать строку кода (очень полезно для дебага)
|
||||||
|
|
||||||
let filter_layer = EnvFilter::try_from_default_env()
|
let filter_layer = EnvFilter::try_from_default_env()
|
||||||
.or_else(|_| EnvFilter::try_new("info")) // По умолчанию уровень info
|
.or_else(|_| EnvFilter::try_new("trace")) // По умолчанию уровень info
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::tlseng::extension::ExtensionStack;
|
|||||||
use crate::tlseng::handshake::{ClientHello, HelloHeader, ServerHello};
|
use crate::tlseng::handshake::{ClientHello, HelloHeader, ServerHello};
|
||||||
use crate::tlseng::profile::BrowserProfile;
|
use crate::tlseng::profile::BrowserProfile;
|
||||||
use crate::tlseng::tls_record::TlsRecord;
|
use crate::tlseng::tls_record::TlsRecord;
|
||||||
use crate::tlseng::types::{ContentType, HelloType, ProtocolVersion};
|
use crate::tlseng::types::{ContentType, HelloType};
|
||||||
use crate::tlseng::ApplicationData;
|
use crate::tlseng::ApplicationData;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
|
|
||||||
|
|||||||
@@ -119,6 +119,14 @@ impl Codec {
|
|||||||
) -> Result<Bytes, TlsError> {
|
) -> Result<Bytes, TlsError> {
|
||||||
let padding = Padding::generate_padding();
|
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 {
|
let header = FrameHeader {
|
||||||
auth_tag: [0u8; 16],
|
auth_tag: [0u8; 16],
|
||||||
stream_id,
|
stream_id,
|
||||||
@@ -132,8 +140,7 @@ impl Codec {
|
|||||||
payload,
|
payload,
|
||||||
padding: padding.data,
|
padding: padding.data,
|
||||||
};
|
};
|
||||||
|
let mut frame_bytes = frame.into_bytes(&tag);
|
||||||
let mut frame_bytes = frame.into_bytes();
|
|
||||||
|
|
||||||
// ВАЖНО: вызываем шифрование ОДИН РАЗ.
|
// ВАЖНО: вызываем шифрование ОДИН РАЗ.
|
||||||
// Метод encrypt возвращает Result<Bytes, chacha20poly1305::Error>
|
// Метод encrypt возвращает Result<Bytes, chacha20poly1305::Error>
|
||||||
@@ -161,20 +168,17 @@ impl Codec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
|
pub fn inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
|
||||||
// 1. Сначала проверяем, нет ли уже готового фрейма в staging с прошлого раза
|
// 1. Проверка старых данных
|
||||||
if !self.staging.is_empty() {
|
if !self.staging.is_empty() {
|
||||||
if let Some(frame) = self.try_parse_frame()? {
|
if let Some(frame) = self.try_parse_frame()? {
|
||||||
return Ok(Some(frame));
|
return Ok(Some(frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Распаковываем ВСЕ доступные TLS-рекорды из сетевого буфера
|
// 2. Цикл обработки новых рекордов
|
||||||
while let Some(app_data) = TlsBridge::unpack_app_data(buffer)? {
|
while let Some(app_data) = TlsBridge::unpack_app_data(buffer)? {
|
||||||
// Берем Bytes напрямую (app_data.payload — это уже Bytes)
|
|
||||||
let mut data_to_decrypt = BytesMut::from(app_data.payload);
|
let mut data_to_decrypt = BytesMut::from(app_data.payload);
|
||||||
|
|
||||||
// Дешифруем "на месте" (In-place decryption)
|
|
||||||
// Твоя библиотека ChaCha скорее всего поддерживает дешифровку прямо в том же буфере
|
|
||||||
let decrypted = self.crypto.decrypt(&mut data_to_decrypt).map_err(|_| {
|
let decrypted = self.crypto.decrypt(&mut data_to_decrypt).map_err(|_| {
|
||||||
TlsError::new(
|
TlsError::new(
|
||||||
ErrorStage::Tls("Decr error"),
|
ErrorStage::Tls("Decr error"),
|
||||||
@@ -183,11 +187,35 @@ impl Codec {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// ВАЖНО: Вместо extend_from_slice (копирование), используем split_off/unsplit или просто Bytes
|
// --- КРИТИЧЕСКАЯ ПРОВЕРКА ТЕГА ---
|
||||||
// Если staging — это BytesMut, используй put или reserve
|
if decrypted.len() < 16 {
|
||||||
self.staging.extend_from_slice(&decrypted); // Увы, BytesMut требует копирования для конкатенации
|
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()? {
|
if let Some(frame) = self.try_parse_frame()? {
|
||||||
return Ok(Some(frame));
|
return Ok(Some(frame));
|
||||||
}
|
}
|
||||||
@@ -195,7 +223,6 @@ impl Codec {
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Выносим парсинг в отдельный метод, чтобы не дублировать код
|
// Выносим парсинг в отдельный метод, чтобы не дублировать код
|
||||||
fn try_parse_frame(&mut self) -> Result<Option<Frame>, TlsError> {
|
fn try_parse_frame(&mut self) -> Result<Option<Frame>, TlsError> {
|
||||||
match Frame::parse(&mut self.staging) {
|
match Frame::parse(&mut self.staging) {
|
||||||
|
|||||||
@@ -35,13 +35,12 @@ pub const FRAME_HEADER_SIZE: u16 =
|
|||||||
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE;
|
AUTH_TAG_SIZE + STREAM_ID_SIZE + FRAME_TYPE_SIZE + PAYLOAD_LEN_SIZE + PADDING_LEN_SIZE;
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
pub fn into_bytes(self) -> BytesMut {
|
pub fn into_bytes(self, auth_key: &[u8; 16]) -> BytesMut {
|
||||||
let updated_padding = Padding::generate_padding();
|
let updated_padding = Padding::generate_padding();
|
||||||
let total_size = FRAME_HEADER_SIZE as usize + self.payload.len() + self.padding.len();
|
let total_size = FRAME_HEADER_SIZE as usize + self.payload.len() + self.padding.len();
|
||||||
let mut buf = BytesMut::with_capacity(total_size);
|
let mut buf = BytesMut::with_capacity(total_size);
|
||||||
let hmac = [0; 16]; //generate_auth_tag(&[0; 16]);
|
|
||||||
|
|
||||||
buf.put_slice(&hmac);
|
buf.put_slice(auth_key);
|
||||||
buf.put_u32(self.header.stream_id);
|
buf.put_u32(self.header.stream_id);
|
||||||
buf.put_u8(self.header.frame_type as u8);
|
buf.put_u8(self.header.frame_type as u8);
|
||||||
buf.put_u16(self.header.payload_len);
|
buf.put_u16(self.header.payload_len);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::protocol::codec::frame::FrameType;
|
use crate::protocol::codec::frame::FrameType;
|
||||||
|
use crate::proxy::connection::connection::BUF_SIZE;
|
||||||
use crate::proxy::connection::muxer::{MuxMessage, Muxer};
|
use crate::proxy::connection::muxer::{MuxMessage, Muxer};
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
@@ -13,7 +14,7 @@ pub async fn run_proxy_bridge<R, W>(
|
|||||||
R: tokio::io::AsyncReadExt + Unpin,
|
R: tokio::io::AsyncReadExt + Unpin,
|
||||||
W: tokio::io::AsyncWriteExt + Unpin,
|
W: tokio::io::AsyncWriteExt + Unpin,
|
||||||
{
|
{
|
||||||
let mut buf = BytesMut::with_capacity(16384);
|
let mut buf = BytesMut::with_capacity(BUF_SIZE);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct BufPair {
|
|
||||||
pub write_buf: BytesMut,
|
|
||||||
pub read_buf: BytesMut,
|
|
||||||
}
|
|
||||||
pub const BUF_SIZE: usize = 16384;
|
pub const BUF_SIZE: usize = 16384;
|
||||||
|
|
||||||
|
|
||||||
@@ -208,7 +204,7 @@ impl Connection {
|
|||||||
info!("Acting as TLS Server, waiting for ClientHello");
|
info!("Acting as TLS Server, waiting for ClientHello");
|
||||||
|
|
||||||
// Создаем Muxer для сервера
|
// Создаем Muxer для сервера
|
||||||
let (mux_tx, mux_rx) = mpsc::channel(16384);
|
let (mux_tx, mux_rx) = mpsc::channel(BUF_SIZE);
|
||||||
let muxer = Muxer::new(mux_tx.clone(), false); // false, так как это Сервер
|
let muxer = Muxer::new(mux_tx.clone(), false); // false, так как это Сервер
|
||||||
|
|
||||||
// 1. TLS Handshake
|
// 1. TLS Handshake
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
protocol::errors::ErrorAction,
|
protocol::errors::ErrorAction,
|
||||||
proxy::connection::{
|
proxy::connection::{
|
||||||
connection::{Connection, ConnectionRole},
|
connection::{Connection, ConnectionRole, BUF_SIZE},
|
||||||
engine::TunnelEngine,
|
engine::TunnelEngine,
|
||||||
muxer::{MuxMessage, Muxer},
|
muxer::Muxer,
|
||||||
},
|
},
|
||||||
tlseng::profile::BrowserProfile,
|
tlseng::profile::BrowserProfile,
|
||||||
};
|
};
|
||||||
@@ -12,7 +12,7 @@ use tokio::{
|
|||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
};
|
};
|
||||||
use tracing::{debug, error, info, instrument}; // Импортируем макросы
|
use tracing::{error, info, instrument}; // Импортируем макросы
|
||||||
|
|
||||||
pub struct Network {
|
pub struct Network {
|
||||||
port: u16,
|
port: u16,
|
||||||
@@ -99,7 +99,7 @@ impl Network {
|
|||||||
|
|
||||||
// --- TLS Handshake ---
|
// --- TLS Handshake ---
|
||||||
let ch = codec
|
let ch = codec
|
||||||
.make_client_handshake(&BrowserProfile::CHROME_131, "proxy.server")
|
.make_client_handshake(&BrowserProfile::CHROME_131, "google.com")
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
outbound.write_all(&ch).await.map_err(|e| e.to_string())?;
|
outbound.write_all(&ch).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ impl Network {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Запуск инфраструктуры ---
|
// --- Запуск инфраструктуры ---
|
||||||
let (mux_tx, mux_rx) = tokio::sync::mpsc::channel(16384);
|
let (mux_tx, mux_rx) = tokio::sync::mpsc::channel(BUF_SIZE);
|
||||||
let muxer = Muxer::new(mux_tx, true);
|
let muxer = Muxer::new(mux_tx, true);
|
||||||
|
|
||||||
let handler = std::sync::Arc::new(crate::proxy::connection::handler::StreamHandler::new(
|
let handler = std::sync::Arc::new(crate::proxy::connection::handler::StreamHandler::new(
|
||||||
|
|||||||
Reference in New Issue
Block a user