split codec and dynamic tcp sockers

This commit is contained in:
Трапезников Кирилл Иванович
2026-04-10 11:27:38 +10:00
parent 67be2d3056
commit 11d2b4e1d9
14 changed files with 368 additions and 667 deletions
+19 -64
View File
@@ -1,4 +1,4 @@
use crate::crypto::SessionKeys;
use crate::crypto::{SessionAuth, SessionKeys};
use crate::nrxp::errors::{ErrorAction, ErrorStage, TlsError};
use crate::parser::Parser;
use crate::tlseng::ExtensionStack;
@@ -69,16 +69,9 @@ impl TlsInterceptor for HandshakeMessage {
let ext =
ExtensionStack::parse(&mut BytesMut::from(hello.extensions.as_ref()))?
.ok_or_else(|| {
TlsError::new(
ErrorStage::Handshake("Ext Err"),
ErrorAction::Drop,
Bytes::new(),
)
TlsError::new(ErrorStage::Handshake("Ext Err"), ErrorAction::Drop, Bytes::new())
})?;
return Ok(Some(HandshakeMessage::Client {
base: hello,
extensions: ext,
}));
return Ok(Some(HandshakeMessage::Client { base: hello, extensions: ext }));
}
}
HelloType::Server => {
@@ -86,16 +79,9 @@ impl TlsInterceptor for HandshakeMessage {
let ext =
ExtensionStack::parse(&mut BytesMut::from(hello.extensions.as_ref()))?
.ok_or_else(|| {
TlsError::new(
ErrorStage::Handshake("Ext Err"),
ErrorAction::Drop,
Bytes::new(),
)
TlsError::new(ErrorStage::Handshake("Ext Err"), ErrorAction::Drop, Bytes::new())
})?;
return Ok(Some(HandshakeMessage::Server {
base: hello,
extensions: ext,
}));
return Ok(Some(HandshakeMessage::Server { base: hello, extensions: ext }));
}
}
}
@@ -144,64 +130,33 @@ impl TlsBridge {
) -> Result<Bytes, TlsError> {
if let HandshakeMessage::Client { base, extensions } = client_msg {
if base.session_id.len() != 32 {
netrunner_logger::warn!(
"❌ Auth failed: Expected 32 bytes SessionID, got {}. Client IP: ...",
base.session_id.len()
);
return Err(TlsError::new(
ErrorStage::Handshake("Invalid SessionID len"),
ErrorAction::Drop,
Bytes::new(),
));
return Err(TlsError::new(ErrorStage::Handshake("Invalid SessionID len"), ErrorAction::Drop, Bytes::new()));
}
let mut received_tag = [0u8; 16];
if base.session_id.len() >= 32 {
received_tag.copy_from_slice(&base.session_id[16..32]);
} else {
return Err(TlsError::new(
ErrorStage::Handshake("Short SessionID"),
ErrorAction::Drop,
Bytes::new(),
));
}
received_tag.copy_from_slice(&base.session_id[16..32]);
if !keys.verify_auth_tag(&received_tag) {
// ВАЖНО: Используем SessionAuth для проверки начального тега хендшейка
let auth = SessionAuth::new(keys.get_auth_key());
if !auth.verify_tag(&received_tag) {
netrunner_logger::warn!("Unauthorized ClientHello: Auth Tag mismatch");
return Err(TlsError::new(
ErrorStage::Handshake("Auth Failed"),
ErrorAction::Drop,
Bytes::new(),
));
return Err(TlsError::new(ErrorStage::Handshake("Auth Failed"), ErrorAction::Drop, Bytes::new()));
}
keys.update_keys(base.random, extensions, true)
.map_err(|e| {
netrunner_logger::error!(error = %e, "Server failed key update");
TlsError::new(
ErrorStage::Handshake("Key Exchange Failed"),
ErrorAction::Drop,
Bytes::new(),
)
})?;
keys.update_keys(base.random, extensions, true).map_err(|e| {
netrunner_logger::error!(error = %e, "Server failed key update");
TlsError::new(ErrorStage::Handshake("Key Exchange Failed"), ErrorAction::Drop, Bytes::new())
})?;
let server_pub_key = keys.public_key_bytes();
Ok(ServerHello::make_server_hello(
base,
&server_pub_key,
keys.local_salt(),
profile,
))
Ok(ServerHello::make_server_hello(base, &server_pub_key, keys.local_salt(), profile))
} else {
Err(TlsError::new(
ErrorStage::Handshake("Expected ClientHello for SH generation"),
ErrorAction::Drop,
Bytes::new(),
))
Err(TlsError::new(ErrorStage::Handshake("Expected ClientHello"), ErrorAction::Drop, Bytes::new()))
}
}
pub fn pack_app_data(buffer: Bytes) -> Bytes {
TlsRecord::build_application_data(buffer)
}
}
}
+54 -131
View File
@@ -1,130 +1,51 @@
use bytes::{Bytes, BytesMut};
use crate::crypto::AeadPacker;
use crate::crypto::ChaChaCipher;
use crate::crypto::SessionKeys;
use crate::crypto::{AeadPacker, ChaChaCipher, ChaChaStream, SessionAuth};
use crate::nrxp::bridge::TlsBridge;
use crate::nrxp::errors::{ErrorAction, ErrorStage, TlsError};
use crate::nrxp::frame::{Frame, FrameType};
use crate::parser::Parser;
use crate::tlseng::{BrowserProfile, ServerProfile};
pub struct Codec {
crypto: ChaChaCipher,
session_keys: SessionKeys,
staging: BytesMut,
pub struct TxCodec {
crypto: ChaChaStream,
auth: SessionAuth,
}
impl Codec {
pub(crate) fn new(is_initiator: bool) -> Self {
Self {
crypto: ChaChaCipher::new(),
session_keys: SessionKeys::new(is_initiator),
staging: BytesMut::new(),
}
impl TxCodec {
pub fn new(crypto: ChaChaStream, auth: SessionAuth) -> Self {
Self { crypto, auth }
}
pub(crate) fn make_client_handshake(
&mut self,
profile: &BrowserProfile,
host: &str,
) -> Result<Bytes, TlsError> {
Ok(TlsBridge::wrap_client_hello(
profile,
host,
&self.session_keys,
))
}
pub(crate) 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_hello_record = TlsBridge::wrap_server_hello(
&client_msg,
&mut self.session_keys,
&ServerProfile::MODERN,
)?;
let (w_key, w_iv, r_key, r_iv) = self.session_keys.get_aead_parameters();
self.crypto.set_keys(w_key, w_iv, r_key, r_iv);
Ok(server_hello_record)
}
pub(crate) 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(),
)
})?;
let (w_key, w_iv, r_key, r_iv) = self
.session_keys
.update_keys(mes.random(), mes.extensions(), false)
.map_err(|e| {
netrunner_logger::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(())
}
fn outbound(
pub fn encode_frame(
&mut self,
stream_id: u32,
frame_type: FrameType,
payload: Bytes,
) -> Result<Bytes, TlsError> {
let tag = self.session_keys.generate_auth_tag();
netrunner_logger::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_fingerprint()),
generated_tag = %hex::encode(&tag[..4]),
"OUTBOUND: Generated auth tag"
);
let tag = self.auth.generate_current_tag();
let frame = Frame::new(stream_id, frame_type, payload);
let mut frame_bytes = frame.into_bytes(&tag);
let encrypted_payload = self.crypto.encrypt(&mut frame_bytes).map_err(|e| {
netrunner_logger::error!("Encryption failed: {:?}", e);
TlsError::new(
ErrorStage::Tls("Encryption failed"),
ErrorAction::Drop,
Bytes::new(),
)
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 struct RxCodec {
crypto: ChaChaStream,
auth: SessionAuth,
staging: BytesMut,
}
impl RxCodec {
pub fn new(crypto: ChaChaStream, auth: SessionAuth, staging: BytesMut) -> Self {
Self { crypto, auth, staging }
}
pub fn inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
pub fn decode_inbound(&mut self, buffer: &mut BytesMut) -> Result<Option<Frame>, TlsError> {
if !self.staging.is_empty() {
if let Some(frame) = self.try_parse_frame()? {
return Ok(Some(frame));
@@ -136,43 +57,24 @@ impl Codec {
e
})? {
let mut data_to_decrypt = BytesMut::from(app_data.payload);
let decrypted = self.crypto.decrypt(&mut data_to_decrypt).map_err(|_| {
let decrypted = self.crypto.decrypt(&mut data_to_decrypt).map_err(|e| {
self.staging.clear();
let bad_data =
Bytes::copy_from_slice(&data_to_decrypt[..data_to_decrypt.len().min(32)]);
TlsError::new(
ErrorStage::Tls("AEAD Decrypt Failed (MITM?)"),
ErrorAction::Drop,
bad_data,
)
let bad_data = Bytes::copy_from_slice(&data_to_decrypt[..data_to_decrypt.len().min(32)]);
TlsError::new(ErrorStage::Tls("AEAD Decrypt Failed"), ErrorAction::Drop, bad_data)
})?;
if decrypted.len() < 16 {
self.staging.clear();
return Err(TlsError::new(
ErrorStage::Tls("Packet too short for auth"),
ErrorAction::Drop,
Bytes::new(),
));
return Err(TlsError::new(ErrorStage::Tls("Packet too short"), ErrorAction::Drop, Bytes::new()));
}
let mut received_tag = [0u8; 16];
received_tag.copy_from_slice(&decrypted[..16]);
if !self.session_keys.verify_auth_tag(&received_tag) {
netrunner_logger::error!(
expected_hash = %hex::encode(&self.session_keys.auth_key_fingerprint()),
received = %hex::encode(&received_tag[..4]),
"AUTH MISMATCH: Potential replay or MITM attack. Dropping connection."
);
if !self.auth.verify_tag(&received_tag) {
self.staging.clear();
return Err(TlsError::new(
ErrorStage::Tls("Auth tag mismatch"),
ErrorAction::Drop,
Bytes::new(),
));
return Err(TlsError::new(ErrorStage::Tls("Auth mismatch"), ErrorAction::Drop, Bytes::new()));
}
self.staging.extend_from_slice(&decrypted);
@@ -191,12 +93,33 @@ impl Codec {
Ok(None) => Ok(None),
Err(_) => {
self.staging.clear();
Err(TlsError::new(
ErrorStage::Tls("Parse error"),
ErrorAction::Drop,
Bytes::new(),
))
Err(TlsError::new(ErrorStage::Tls("Parse error"), ErrorAction::Drop, Bytes::new()))
}
}
}
}
pub struct Codec {
tx: Option<TxCodec>,
rx: Option<RxCodec>,
}
impl Codec {
// УБРАЛИ staging из аргументов
pub fn new(cipher: ChaChaCipher, auth_key: [u8; 32]) -> Self {
let (rx_stream, tx_stream) = cipher.split();
let auth = SessionAuth::new(auth_key);
Self {
tx: Some(TxCodec::new(tx_stream, auth)),
rx: Some(RxCodec::new(rx_stream, auth, BytesMut::with_capacity(4096))),
}
}
pub fn split(mut self) -> (RxCodec, TxCodec) {
(
self.rx.take().expect("RxCodec missing"),
self.tx.take().expect("TxCodec missing"),
)
}
}
+2 -1
View File
@@ -3,6 +3,7 @@ mod codec;
mod errors;
mod frame;
pub(crate) use codec::Codec;
pub(crate) use codec::{RxCodec, TxCodec, Codec};
pub(crate) use errors::{ErrorAction, ErrorStage, TlsError};
pub(crate) use frame::{Frame, FrameType};
pub(crate) use bridge::TlsBridge;