initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
things.txt
|
||||||
Generated
+1358
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"common",
|
||||||
|
"client",
|
||||||
|
"server",
|
||||||
|
]
|
||||||
|
resolver = "2"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "netrunner-client"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "client_example"
|
||||||
|
path = "examples/client_example.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
netrunner-common = { path = "../common" } # Наша общая либа
|
||||||
|
smoltcp = { version = "0.12.0", default-features = false, features = ["medium-ip", "proto-ipv4", "socket-tcp"] }
|
||||||
|
tokio = { version = "1.36", features = ["full"] }
|
||||||
|
jni = "0.21"
|
||||||
|
android_logger = "0.13"
|
||||||
|
log = "0.4"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use netrunner_common::proxy::{
|
||||||
|
connection::handler::{socks2netr::Socks2Netr, tcp2netr::Tcp2Netr},
|
||||||
|
network::Network,
|
||||||
|
};
|
||||||
|
fn main() {
|
||||||
|
println!("Подготовка конфигов...");
|
||||||
|
let inbound_handler = Arc::new(Socks2Netr); //change here to Netr2tcp
|
||||||
|
let outbound_handler = Arc::new(Tcp2Netr);
|
||||||
|
let net = Network::new(inbound_handler, outbound_handler, 8080);
|
||||||
|
|
||||||
|
// Создаем движок (Runtime)
|
||||||
|
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
||||||
|
|
||||||
|
// "Блокируем" основной поток, пока работает асинхронный код
|
||||||
|
rt.block_on(async {
|
||||||
|
net.run().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "netrunner-common"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chacha20poly1305 = "0.10.1"
|
||||||
|
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
|
||||||
|
aead = { version = "0.5.2", features = ["bytes"] }
|
||||||
|
hkdf = "0.12"
|
||||||
|
hmac = "0.12"
|
||||||
|
sha2 = "0.10"
|
||||||
|
anyhow = "1.0"
|
||||||
|
bytes = "1.5"
|
||||||
|
log = "0.4"
|
||||||
|
tokio = "1.49.0"
|
||||||
|
rand = "0.10.0"
|
||||||
|
async-trait = "0.1.89"
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
use chacha20poly1305::aead::Buffer;
|
||||||
|
|
||||||
|
pub trait AeadPacker {
|
||||||
|
fn encrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error>;
|
||||||
|
fn decrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||||
|
use chacha20poly1305::aead::{Buffer, OsRng};
|
||||||
|
use chacha20poly1305::{
|
||||||
|
AeadCore, AeadInPlace, ChaCha20Poly1305, ChaChaPoly1305, Key, KeyInit, Nonce,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::crypto::aead::AeadPacker;
|
||||||
|
|
||||||
|
pub struct NonceState {
|
||||||
|
counter: u64,
|
||||||
|
nonce: Nonce,
|
||||||
|
handshake: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NonceState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||||
|
println!("Nonce is {:?}", nonce);
|
||||||
|
Self {
|
||||||
|
counter: 0,
|
||||||
|
nonce,
|
||||||
|
handshake: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_handshake(&self) -> bool {
|
||||||
|
return self.handshake;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_nonce(&mut self, nonce: Nonce) {
|
||||||
|
self.nonce = nonce;
|
||||||
|
self.handshake = true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increase_counter(&mut self) {
|
||||||
|
self.counter += 1;
|
||||||
|
let counter_bytes = self.counter.to_be_bytes();
|
||||||
|
self.nonce[4..12].copy_from_slice(&counter_bytes);
|
||||||
|
println!("Current Nonce is {:?}", self.nonce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChaChaCipher {
|
||||||
|
key: Key,
|
||||||
|
pub encrypt_state: NonceState,
|
||||||
|
pub decrypt_state: NonceState,
|
||||||
|
pub cipher: ChaCha20Poly1305,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChaChaCipher {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let key = GenericArray::clone_from_slice(&[0; 32]);
|
||||||
|
let cipher = ChaCha20Poly1305::new(&key);
|
||||||
|
Self {
|
||||||
|
key,
|
||||||
|
encrypt_state: NonceState::new(),
|
||||||
|
decrypt_state: NonceState::new(),
|
||||||
|
cipher,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_key(&mut self, key: Key) -> () {
|
||||||
|
self.key = key;
|
||||||
|
self.cipher = ChaChaPoly1305::new(&self.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AeadPacker for ChaChaCipher {
|
||||||
|
fn encrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error> {
|
||||||
|
self.cipher
|
||||||
|
.encrypt_in_place(&self.encrypt_state.nonce, &[], data)?;
|
||||||
|
self.encrypt_state.increase_counter();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrypt<B: Buffer>(&mut self, data: &mut B) -> Result<(), chacha20poly1305::aead::Error> {
|
||||||
|
println!("Buffer: {:?}", data.as_mut());
|
||||||
|
println!("nonce: {:?}", &self.decrypt_state.nonce);
|
||||||
|
self.cipher
|
||||||
|
.decrypt_in_place(&self.decrypt_state.nonce, &[], data)?;
|
||||||
|
self.decrypt_state.increase_counter();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use aead::OsRng;
|
||||||
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
pub struct ECDH {
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
secret_key: EphemeralSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ECDH {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let secret = EphemeralSecret::random_from_rng(&mut OsRng);
|
||||||
|
let public = PublicKey::from(&secret);
|
||||||
|
Self {
|
||||||
|
secret_key: secret,
|
||||||
|
public_key: public,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_shared(self, public: &PublicKey) -> [u8; 32] {
|
||||||
|
let shared = self.secret_key.diffie_hellman(&public);
|
||||||
|
*shared.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod aead;
|
||||||
|
pub mod chacha;
|
||||||
|
pub mod ecdh;
|
||||||
|
pub mod hkdf;
|
||||||
|
pub mod hmac;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod crypto;
|
||||||
|
pub mod protocol;
|
||||||
|
pub mod proxy;
|
||||||
|
pub mod tlseng;
|
||||||
|
pub mod utils;
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
use aead::rand_core::RngCore;
|
||||||
|
use aead::OsRng;
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::crypto::chacha::ChaChaCipher;
|
||||||
|
use crate::crypto::ecdh::ECDH;
|
||||||
|
use crate::crypto::hmac::generate_auth_tag;
|
||||||
|
use crate::protocol::interceptors::hello_interceptor::extension_interceptor::ExtensionStack;
|
||||||
|
use crate::protocol::interceptors::interceptor::Interceptor;
|
||||||
|
use crate::tlseng::etype::EXT_KEY_SHARE;
|
||||||
|
use crate::tlseng::extension::ExtensionBuilder;
|
||||||
|
use crate::tlseng::profile::profile::BrowserProfile;
|
||||||
|
use crate::tlseng::tls::{
|
||||||
|
ClientHello, ContentType, HelloHeader, HelloType, ProtocolVersion, ServerHello, TlsRecord,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::crypto::aead::AeadPacker;
|
||||||
|
pub struct Codec {
|
||||||
|
crypto: ChaChaCipher,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Codec {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
crypto: ChaChaCipher::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_handshake(&mut self, profile: &BrowserProfile, host: &str) -> Bytes {
|
||||||
|
// 1. Key Exchange: Generate ECDH pair and get public key
|
||||||
|
let ecdh = ECDH::new();
|
||||||
|
let pub_key = &ecdh.public_key;
|
||||||
|
|
||||||
|
// 2. Authentication: Generate 32 bytes for TLS Random
|
||||||
|
// [16 bytes entropy] + [16 bytes HMAC(timestamp)]
|
||||||
|
let mut tls_random = [0u8; 32];
|
||||||
|
OsRng.fill_bytes(&mut tls_random[..16]);
|
||||||
|
|
||||||
|
let auth_token = generate_auth_tag(&[]);
|
||||||
|
tls_random[16..32].copy_from_slice(&auth_token);
|
||||||
|
|
||||||
|
// 3. Extensions: Build the extensions block using the profile
|
||||||
|
let mut ext_builder = ExtensionBuilder::new();
|
||||||
|
// Pass the public key into the KeyShare extension via apply_profile
|
||||||
|
ext_builder.apply_profile(profile, host, pub_key.as_bytes());
|
||||||
|
let extensions_bytes = ext_builder.build();
|
||||||
|
|
||||||
|
let mut session_id = BytesMut::with_capacity(32);
|
||||||
|
session_id.put_slice(&[0u8; 32]);
|
||||||
|
|
||||||
|
// 4. Assemble ClientHello Handshake message
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5. Wrap ClientHello into a TLS Record
|
||||||
|
let record = TlsRecord {
|
||||||
|
content_type: ContentType::Handshake,
|
||||||
|
version: ProtocolVersion::Tls10, // Standard for ClientHello records
|
||||||
|
len: client_hello.serialize().len() as u16,
|
||||||
|
payload: client_hello.serialize(),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Client Hello: {:02x?}", record.payload.to_vec());
|
||||||
|
|
||||||
|
// Final result: Byte buffer ready for the wire
|
||||||
|
record.serialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Основная точка входа для входящих данных из сокета
|
||||||
|
pub fn process_incoming(&mut self, mut buffer: BytesMut) {
|
||||||
|
// 1. Пытаемся достать TLS Record (самый верхний слой)
|
||||||
|
match TlsRecord::intercept(&mut buffer) {
|
||||||
|
Ok(Some(record)) => {
|
||||||
|
println!(
|
||||||
|
"Получен Record: type={:?}, len={}",
|
||||||
|
record.content_type, record.len
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Диспетчеризация по типу контента
|
||||||
|
self.handle_record(record);
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
// Пакет неполный, ждем догрузки в буфер
|
||||||
|
}
|
||||||
|
Err(_e) => {
|
||||||
|
// Заглушка: ошибка парсинга рекорда
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_record(&mut self, record: TlsRecord) {
|
||||||
|
match record.content_type {
|
||||||
|
ContentType::Handshake => {
|
||||||
|
self.handle_handshake(record.payload);
|
||||||
|
}
|
||||||
|
ContentType::ApplicationData => {
|
||||||
|
self.handle_application_data(record.payload);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Остальные типы (Alert, ChangeCipherSpec) пока игнорируем
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_handshake(&mut self, mut payload: Bytes) {
|
||||||
|
// Превращаем Bytes в BytesMut для интерцепторов (без копирования данных)
|
||||||
|
let mut body = BytesMut::from(payload.as_ref());
|
||||||
|
|
||||||
|
// 1. Парсим заголовок Handshake (Type + Length)
|
||||||
|
match HelloHeader::intercept(&mut body) {
|
||||||
|
Ok(Some(header)) => {
|
||||||
|
match header.header_type {
|
||||||
|
HelloType::Server => {
|
||||||
|
let mut server_hello_body = body; // Тут уже откушенное тело
|
||||||
|
if let Ok(Some(server_hello)) =
|
||||||
|
ServerHello::intercept(&mut server_hello_body)
|
||||||
|
{
|
||||||
|
self.process_server_hello(server_hello);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HelloType::Client => {
|
||||||
|
let mut client_hello_body = body;
|
||||||
|
if let Ok(Some(client_hello)) =
|
||||||
|
ClientHello::intercept(&mut client_hello_body)
|
||||||
|
{
|
||||||
|
self.process_client_hello(client_hello);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* Другие типы Handshake (EncryptedExtensions, Certificate и т.д.) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* Заглушка на ошибку */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_server_hello(&mut self, hello: ServerHello) {
|
||||||
|
println!("Server Hello получен! Random: {:02x?}", hello.random);
|
||||||
|
|
||||||
|
// Парсим расширения сервера, если нужно
|
||||||
|
let mut ext_bytes = BytesMut::from(hello.extensions.as_ref());
|
||||||
|
if let Ok(Some(stack)) = ExtensionStack::intercept(&mut ext_bytes) {
|
||||||
|
// Например, ищем KeyShare сервера для завершения ECDH
|
||||||
|
if let Some(key_share) = stack.find_by_type(EXT_KEY_SHARE) {
|
||||||
|
println!("Найден KeyShare сервера, длина: {}", key_share.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_client_hello(&mut self, hello: ClientHello) {
|
||||||
|
println!("Client Hello получен! Random: {:02x?}", hello.random);
|
||||||
|
|
||||||
|
// Парсим расширения сервера, если нужно
|
||||||
|
let mut ext_bytes = BytesMut::from(hello.extensions.as_ref());
|
||||||
|
if let Ok(Some(stack)) = ExtensionStack::intercept(&mut ext_bytes) {
|
||||||
|
if let Some(key_share) = stack.find_by_type(EXT_KEY_SHARE) {
|
||||||
|
println!("Найден KeyShare сервера, длина: {}", key_share.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_application_data(&mut self, payload: Bytes) {
|
||||||
|
// Здесь будет логика расшифровки через твой ChaChaCipher
|
||||||
|
// Пока просто выводим длину зашифрованных данных
|
||||||
|
println!("Получены Application Data: {} байт", payload.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
use aead::OsRng;
|
||||||
|
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::{crypto::hmac::generate_auth_tag, protocol::codec::padding::Padding};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum FrameType {
|
||||||
|
Connect = 0x00,
|
||||||
|
Data = 0x01,
|
||||||
|
Close = 0x02,
|
||||||
|
Heartbeat = 0x03,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
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 {
|
||||||
|
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 FrameHeader {
|
||||||
|
fn get_header(bytes: &mut BytesMut) -> Option<Self> {
|
||||||
|
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||||
|
return 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();
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
auth_tag,
|
||||||
|
stream_id,
|
||||||
|
frame_type,
|
||||||
|
payload_len,
|
||||||
|
padding_len,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn unpack(bytes: &mut BytesMut) -> Result<Option<Self>, String> {
|
||||||
|
if bytes.len() < FRAME_HEADER_SIZE as usize {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if bytes.len() < (FRAME_HEADER_SIZE as usize + p_len + pad_len) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let header = FrameHeader::get_header(bytes).unwrap();
|
||||||
|
let payload = bytes.split_to(p_len).freeze();
|
||||||
|
let padding = bytes.split_to(pad_len).freeze();
|
||||||
|
|
||||||
|
Ok(Some(Self {
|
||||||
|
header,
|
||||||
|
payload,
|
||||||
|
padding,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_bytes(self) -> 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);
|
||||||
|
let hmac = generate_auth_tag(&[0; 16]);
|
||||||
|
|
||||||
|
buf.put_slice(&hmac);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod codec;
|
||||||
|
pub mod frame;
|
||||||
|
mod padding;
|
||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use crate::{
|
||||||
|
protocol::interceptors::{
|
||||||
|
error_interceptor::interceptor_error::InterceptorError, interceptor::Interceptor,
|
||||||
|
},
|
||||||
|
tlseng::tls::ApplicationData,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Interceptor for ApplicationData {
|
||||||
|
type Error = InterceptorError;
|
||||||
|
|
||||||
|
fn can_handle(bytes: &bytes::BytesMut) -> bool {
|
||||||
|
// Application Data не может быть пустым по спецификации (хотя бы 1 байт)
|
||||||
|
!bytes.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intercept(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let len = bytes.len();
|
||||||
|
|
||||||
|
if len == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = bytes.split_to(len).freeze();
|
||||||
|
|
||||||
|
Ok(Some(Self {
|
||||||
|
length: len,
|
||||||
|
payload,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum ErrorAction {
|
||||||
|
Wait,
|
||||||
|
Redirect,
|
||||||
|
Drop,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ErrorType {
|
||||||
|
Tls(&'static str),
|
||||||
|
Handshake(&'static str),
|
||||||
|
ApplicationData(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InterceptorError {
|
||||||
|
pub error_type: ErrorType,
|
||||||
|
pub action: ErrorAction,
|
||||||
|
pub data: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterceptorError {
|
||||||
|
pub fn new(error_type: ErrorType, action: ErrorAction, data: Bytes) -> Self {
|
||||||
|
Self {
|
||||||
|
error_type,
|
||||||
|
action,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn log_error(&self) {
|
||||||
|
let (category, message) = match &self.error_type {
|
||||||
|
ErrorType::Tls(m) => ("TLS", m),
|
||||||
|
ErrorType::Handshake(m) => ("Handshake", m),
|
||||||
|
ErrorType::ApplicationData(m) => ("AppData", m),
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"[{}] Error: {} (Byte: {:#02x})",
|
||||||
|
category, message, self.data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_strategy(&self) -> ErrorAction {
|
||||||
|
self.log_error();
|
||||||
|
self.action
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod interceptor_error;
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
use bytes::{Buf, Bytes};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::interceptors::{
|
||||||
|
error_interceptor::interceptor_error::{ErrorAction, ErrorType, InterceptorError},
|
||||||
|
interceptor::Interceptor,
|
||||||
|
},
|
||||||
|
tlseng::tls::{ClientHello, ProtocolVersion},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Interceptor for ClientHello {
|
||||||
|
type Error = InterceptorError;
|
||||||
|
|
||||||
|
fn can_handle(bytes: &bytes::BytesMut) -> bool {
|
||||||
|
// Мы предполагаем, что HelloHeader уже проверил тип.
|
||||||
|
// Здесь можно проверить минимально допустимый размер ClientHello
|
||||||
|
// (Version 2 + Random 32 + SessionID_len 1 = 35 байт)
|
||||||
|
bytes.len() >= 35
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intercept(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// 1. Проверяем минимальную длину для базовых полей (до Session ID включительно)
|
||||||
|
if bytes.len() < 35 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Version (2 bytes)
|
||||||
|
let raw_version = bytes.get_u16();
|
||||||
|
let version = ProtocolVersion::try_from(raw_version).map_err(|e| {
|
||||||
|
InterceptorError::new(
|
||||||
|
ErrorType::Tls(e),
|
||||||
|
ErrorAction::Drop,
|
||||||
|
Bytes::copy_from_slice(&raw_version.to_be_bytes()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// 3. Random (32 bytes)
|
||||||
|
let mut random = [0u8; 32];
|
||||||
|
bytes.copy_to_slice(&mut random);
|
||||||
|
|
||||||
|
// 4. Session ID (Variable length: 1 byte len + data)
|
||||||
|
let session_id_len = bytes.get_u8() as usize;
|
||||||
|
if bytes.len() < session_id_len {
|
||||||
|
return Ok(None); // Данные неполные
|
||||||
|
}
|
||||||
|
let session_id = bytes.split_to(session_id_len).freeze();
|
||||||
|
|
||||||
|
// 5. Cipher Suites (Variable length: 2 bytes len + data)
|
||||||
|
if bytes.len() < 2 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let ciphers_len = bytes.get_u16() as usize;
|
||||||
|
if bytes.len() < ciphers_len {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cipher_suites = Vec::with_capacity(ciphers_len / 2);
|
||||||
|
let mut ciphers_data = bytes.split_to(ciphers_len);
|
||||||
|
while ciphers_data.has_remaining() {
|
||||||
|
cipher_suites.push(ciphers_data.get_u16());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Compression Methods (1 byte len + data) - в TLS 1.3 обычно [0x01, 0x00]
|
||||||
|
if bytes.len() < 1 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let comp_len = bytes.get_u8() as usize;
|
||||||
|
if bytes.len() < comp_len {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
bytes.advance(comp_len); // Мы их просто пропускаем, они не нужны в структуре
|
||||||
|
|
||||||
|
// 7. Extensions (2 bytes len + data)
|
||||||
|
if bytes.is_empty() {
|
||||||
|
// Расширений может не быть (теоретически в старых TLS)
|
||||||
|
return Ok(Some(Self {
|
||||||
|
version,
|
||||||
|
random,
|
||||||
|
session_id,
|
||||||
|
cipher_suites,
|
||||||
|
extensions: Bytes::new(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.len() < 2 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let extensions_len = bytes.get_u16() as usize;
|
||||||
|
if bytes.len() < extensions_len {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let extensions = bytes.split_to(extensions_len).freeze();
|
||||||
|
|
||||||
|
Ok(Some(Self {
|
||||||
|
version,
|
||||||
|
random,
|
||||||
|
session_id,
|
||||||
|
cipher_suites,
|
||||||
|
extensions,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use bytes::{Buf, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::interceptors::{
|
||||||
|
error_interceptor::interceptor_error::{ErrorAction, ErrorType, InterceptorError},
|
||||||
|
interceptor::Interceptor,
|
||||||
|
},
|
||||||
|
tlseng::extension::Extension,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ExtensionStack {
|
||||||
|
pub extensions: Vec<Extension>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interceptor for ExtensionStack {
|
||||||
|
type Error = InterceptorError;
|
||||||
|
|
||||||
|
fn can_handle(bytes: &BytesMut) -> bool {
|
||||||
|
// Минимальное расширение: тип(2) + длина(2) = 4 байта
|
||||||
|
bytes.len() >= 4
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intercept(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error> {
|
||||||
|
let mut extensions = Vec::new();
|
||||||
|
|
||||||
|
while bytes.remaining() >= 4 {
|
||||||
|
let etype = bytes.get_u16();
|
||||||
|
let elen = bytes.get_u16() as usize;
|
||||||
|
|
||||||
|
if bytes.remaining() < elen {
|
||||||
|
// Если данных меньше, чем обещала длина расширения — это ошибка протокола
|
||||||
|
return Err(InterceptorError::new(
|
||||||
|
ErrorType::Tls("Invalid extension payload"),
|
||||||
|
ErrorAction::Drop,
|
||||||
|
Bytes::new(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = bytes.split_to(elen).freeze();
|
||||||
|
extensions.push(Extension::new(etype, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(Self { extensions }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) // Клонирование Bytes дешево (increment refcount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверяет наличие расширения
|
||||||
|
pub fn has_extension(&self, etype: u16) -> bool {
|
||||||
|
self.extensions.iter().any(|e| e.etype == etype)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use bytes::{Buf, Bytes};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::interceptors::{
|
||||||
|
error_interceptor::interceptor_error::{ErrorAction, ErrorType, InterceptorError},
|
||||||
|
interceptor::Interceptor,
|
||||||
|
},
|
||||||
|
tlseng::tls::{HelloHeader, HelloType},
|
||||||
|
utils::u24::{BufExt, U24},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Interceptor for HelloHeader {
|
||||||
|
type Error = InterceptorError;
|
||||||
|
fn can_handle(bytes: &bytes::BytesMut) -> bool {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let is_valid = match bytes[0] {
|
||||||
|
x if x == HelloType::Client as u8 => true,
|
||||||
|
x if x == HelloType::Server as u8 => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
is_valid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intercept(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
if bytes.len() < 4 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let raw_header_type = bytes.get_u8();
|
||||||
|
let header_type = HelloType::try_from(raw_header_type).map_err(|e| {
|
||||||
|
InterceptorError::new(
|
||||||
|
ErrorType::Handshake(e),
|
||||||
|
ErrorAction::Drop,
|
||||||
|
Bytes::copy_from_slice(&raw_header_type.to_be_bytes()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let len = bytes.get_u24();
|
||||||
|
Ok(Some(Self {
|
||||||
|
header_type,
|
||||||
|
len: U24::from_u32(len),
|
||||||
|
body: bytes.split_to(len as usize).freeze(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod client_hello_interceptor;
|
||||||
|
pub mod extension_interceptor;
|
||||||
|
pub mod hello_interceptor;
|
||||||
|
pub mod server_hello_interceptor;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
use bytes::{Buf, Bytes};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::interceptors::{
|
||||||
|
error_interceptor::interceptor_error::{ErrorAction, ErrorType, InterceptorError},
|
||||||
|
interceptor::Interceptor,
|
||||||
|
},
|
||||||
|
tlseng::tls::{ProtocolVersion, ServerHello},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Interceptor for ServerHello {
|
||||||
|
type Error = InterceptorError;
|
||||||
|
|
||||||
|
fn can_handle(bytes: &bytes::BytesMut) -> bool {
|
||||||
|
// Минимальный ServerHello:
|
||||||
|
// Version(2) + Random(32) + SessionID_len(1) + Cipher(2) + Compression(1) = 38 байт
|
||||||
|
bytes.len() >= 38
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intercept(bytes: &mut bytes::BytesMut) -> Result<Option<Self>, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// 1. Базовая проверка длины (базовые поля до компрессии включительно)
|
||||||
|
if bytes.len() < 38 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Version (2 bytes)
|
||||||
|
let raw_version = bytes.get_u16();
|
||||||
|
let version = ProtocolVersion::try_from(raw_version).map_err(|e| {
|
||||||
|
InterceptorError::new(
|
||||||
|
ErrorType::Tls(e),
|
||||||
|
ErrorAction::Drop,
|
||||||
|
Bytes::copy_from_slice(&raw_version.to_be_bytes()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// 3. Random (32 bytes)
|
||||||
|
let mut random = [0u8; 32];
|
||||||
|
bytes.copy_to_slice(&mut random);
|
||||||
|
|
||||||
|
// 4. Session ID (1 byte len + data)
|
||||||
|
let session_id_len = bytes.get_u8() as usize;
|
||||||
|
if bytes.len() < session_id_len {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let session_id = bytes.split_to(session_id_len).freeze();
|
||||||
|
|
||||||
|
// 5. Cipher Suite (2 bytes)
|
||||||
|
if bytes.len() < 2 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let cipher_suite = bytes.get_u16();
|
||||||
|
|
||||||
|
// 6. Compression Method (1 byte)
|
||||||
|
if bytes.len() < 1 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let _compression = bytes.get_u8();
|
||||||
|
|
||||||
|
// 7. Extensions (2 bytes len + data)
|
||||||
|
// Если после компрессии байтов нет — расширений просто нет.
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Ok(Some(Self {
|
||||||
|
version,
|
||||||
|
random,
|
||||||
|
session_id,
|
||||||
|
cipher_suite,
|
||||||
|
extensions: Bytes::new(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если байты есть, проверяем заголовок длины расширений
|
||||||
|
if bytes.len() < 2 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let extensions_len = bytes.get_u16() as usize;
|
||||||
|
|
||||||
|
if bytes.len() < extensions_len {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let extensions = bytes.split_to(extensions_len).freeze();
|
||||||
|
|
||||||
|
Ok(Some(Self {
|
||||||
|
version,
|
||||||
|
random,
|
||||||
|
session_id,
|
||||||
|
cipher_suite,
|
||||||
|
extensions,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use bytes::BytesMut;
|
||||||
|
|
||||||
|
pub trait Interceptor {
|
||||||
|
type Error;
|
||||||
|
fn can_handle(bytes: &BytesMut) -> bool;
|
||||||
|
fn intercept(bytes: &mut BytesMut) -> Result<Option<Self>, Self::Error>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod application_data_interceptor;
|
||||||
|
mod error_interceptor;
|
||||||
|
pub mod hello_interceptor;
|
||||||
|
pub mod interceptor;
|
||||||
|
pub mod tls_record_interceptor;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
use bytes::{Buf, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::interceptors::{
|
||||||
|
error_interceptor::interceptor_error::{ErrorAction, ErrorType, InterceptorError},
|
||||||
|
interceptor::Interceptor,
|
||||||
|
},
|
||||||
|
tlseng::tls::{ContentType, ProtocolVersion, TlsRecord},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Interceptor for TlsRecord {
|
||||||
|
type Error = InterceptorError;
|
||||||
|
fn can_handle(bytes: &BytesMut) -> bool {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let is_valid = match bytes[0] {
|
||||||
|
x if x == ContentType::Handshake as u8 => true,
|
||||||
|
x if x == ContentType::ApplicationData as u8 => true,
|
||||||
|
x if x == ContentType::Alert as u8 => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
is_valid
|
||||||
|
}
|
||||||
|
fn intercept(bytes: &mut BytesMut) -> Result<Option<TlsRecord>, Self::Error> {
|
||||||
|
if bytes.len() < 5 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let len = u16::from_be_bytes([bytes[3], bytes[4]]);
|
||||||
|
if bytes.len() < 5 + len as usize {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let raw_content_type = bytes.get_u8();
|
||||||
|
let raw_version = bytes.get_u16();
|
||||||
|
let content_type = ContentType::try_from(raw_content_type).map_err(|e| {
|
||||||
|
InterceptorError::new(
|
||||||
|
ErrorType::Tls(e),
|
||||||
|
ErrorAction::Drop,
|
||||||
|
Bytes::copy_from_slice(&raw_content_type.to_be_bytes()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let version = ProtocolVersion::try_from(raw_version).map_err(|e| {
|
||||||
|
InterceptorError::new(
|
||||||
|
ErrorType::Tls(e),
|
||||||
|
ErrorAction::Drop,
|
||||||
|
Bytes::copy_from_slice(&raw_version.to_be_bytes()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let _raw_len = bytes.get_u16();
|
||||||
|
let payload = bytes.split_to(len as usize).freeze();
|
||||||
|
Ok(Some(TlsRecord {
|
||||||
|
content_type,
|
||||||
|
version,
|
||||||
|
len,
|
||||||
|
payload,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use bytes::Buf;
|
||||||
|
|
||||||
|
pub trait PeekExt: Buf {
|
||||||
|
fn peek_u16(&self, offset: usize) -> Option<u16> {
|
||||||
|
let chunk = self.chunk();
|
||||||
|
if chunk.len() >= offset + 2 {
|
||||||
|
let b = &chunk[offset..offset + 2];
|
||||||
|
Some(u16::from_be_bytes([b[0], b[1]]))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_u24(&self, offset: usize) -> Option<u32> {
|
||||||
|
let chunk = self.chunk();
|
||||||
|
if chunk.len() >= offset + 3 {
|
||||||
|
let b = &chunk[offset..offset + 3];
|
||||||
|
Some(((b[0] as u32) << 16) | ((b[1] as u32) << 8) | (b[2] as u32))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Buf> PeekExt for T {}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod codec;
|
||||||
|
pub mod interceptors;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use bytes::BytesMut;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; // <--- ОБЯЗАТЕЛЬНО ТУТ
|
||||||
|
|
||||||
|
pub struct BufPair {
|
||||||
|
pub write_buf: BytesMut,
|
||||||
|
pub read_buf: BytesMut,
|
||||||
|
}
|
||||||
|
const BUF_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
impl BufPair {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let write_buf = BytesMut::with_capacity(BUF_SIZE);
|
||||||
|
let read_buf = BytesMut::with_capacity(BUF_SIZE);
|
||||||
|
Self {
|
||||||
|
write_buf,
|
||||||
|
read_buf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn read_from(&mut self, reader: &mut OwnedReadHalf) -> Result<usize, String> {
|
||||||
|
self.read_buf.clear(); // Вот она, автоматическая чистка
|
||||||
|
let n = reader
|
||||||
|
.read_buf(&mut self.read_buf)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
return Err("Connection closed by peer".to_string());
|
||||||
|
}
|
||||||
|
//println!("Reader {:?}", self.read_buf);
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_to(&mut self, writer: &mut OwnedWriteHalf) -> Result<(), String> {
|
||||||
|
println!("Writer Before write {:?}", self.write_buf);
|
||||||
|
writer
|
||||||
|
.write_all_buf(&mut self.write_buf)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.read_buf.clear();
|
||||||
|
self.write_buf.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use std::{net::SocketAddr, sync::Arc, vec};
|
||||||
|
use tokio::net::{
|
||||||
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
|
TcpStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::codec::codec::Codec,
|
||||||
|
proxy::connection::{
|
||||||
|
buf_pair::BufPair, handler::handler::ProxyHandler, state::ConnectionState,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Connection {
|
||||||
|
pub addr: SocketAddr,
|
||||||
|
inbound: OwnedReadHalf,
|
||||||
|
outbound: OwnedWriteHalf,
|
||||||
|
state: ConnectionState,
|
||||||
|
buffers: BufPair,
|
||||||
|
codec: Codec,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
pub fn new(stream: TcpStream, addr: SocketAddr) -> Self {
|
||||||
|
let (inbound, outbound) = stream.into_split();
|
||||||
|
Self {
|
||||||
|
addr,
|
||||||
|
inbound,
|
||||||
|
outbound,
|
||||||
|
state: ConnectionState::New,
|
||||||
|
buffers: BufPair::new(),
|
||||||
|
codec: Codec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn handle(
|
||||||
|
&mut self,
|
||||||
|
handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
loop {
|
||||||
|
match &mut self.state {
|
||||||
|
ConnectionState::New => {
|
||||||
|
self.state = handler
|
||||||
|
.do_new(&mut self.inbound, &mut self.outbound, &mut self.buffers)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
ConnectionState::Handshake => {
|
||||||
|
self.state = handler
|
||||||
|
.do_handshake(
|
||||||
|
&mut self.inbound,
|
||||||
|
&mut self.outbound,
|
||||||
|
&mut self.buffers,
|
||||||
|
&mut self.codec,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
ConnectionState::Tunnel(ref mut stream) => {
|
||||||
|
self.state = handler
|
||||||
|
.do_tunnel(
|
||||||
|
&mut self.inbound,
|
||||||
|
&mut self.outbound,
|
||||||
|
&mut self.buffers,
|
||||||
|
&mut self.codec,
|
||||||
|
stream,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionState::Close => {
|
||||||
|
self.state = handler
|
||||||
|
.do_close(&mut self.inbound, &mut self.outbound, &mut self.buffers)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
ConnectionState::Disconnected => {
|
||||||
|
println!("Disconnected");
|
||||||
|
return Ok(ConnectionState::Disconnected);
|
||||||
|
}
|
||||||
|
_ => return Err("Invalid transition".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use tokio::net::{
|
||||||
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
|
TcpStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::codec::codec::Codec,
|
||||||
|
proxy::connection::{buf_pair::BufPair, state::ConnectionState},
|
||||||
|
};
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ProxyHandler {
|
||||||
|
async fn do_new(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String>;
|
||||||
|
async fn do_handshake(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
) -> Result<ConnectionState, String>;
|
||||||
|
async fn do_tunnel(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
target: &mut TcpStream,
|
||||||
|
) -> Result<ConnectionState, String>;
|
||||||
|
async fn do_close(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod handler;
|
||||||
|
pub mod netr2tcp;
|
||||||
|
pub mod socks2netr;
|
||||||
|
pub mod tcp2netr;
|
||||||
|
mod utils;
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
use crate::{
|
||||||
|
protocol::codec::codec::Codec,
|
||||||
|
proxy::connection::{
|
||||||
|
buf_pair::BufPair,
|
||||||
|
handler::{handler::ProxyHandler, utils::relay_data},
|
||||||
|
state::ConnectionState,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
|
net::{
|
||||||
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
|
TcpStream,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
enum NetrMessages {}
|
||||||
|
|
||||||
|
pub struct Netr2Tcp;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ProxyHandler for Netr2Tcp {
|
||||||
|
//TODO MAKE ANSWER TO CONNECT
|
||||||
|
//CLOSE CONNECT HERE IF IT IS NOT MY PROXY
|
||||||
|
async fn do_new(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
//buffers.read_from(reader).await?;
|
||||||
|
let codec = Codec::new();
|
||||||
|
Ok(ConnectionState::Handshake)
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO MAKE HANDSHAKE WITH TARGET
|
||||||
|
//GET TARGET FROM CUSTOM FRAME
|
||||||
|
async fn do_handshake(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
buffers.read_from(reader).await?;
|
||||||
|
|
||||||
|
let mut target_stream = TcpStream::connect("google.com:443")
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
target_stream.write_buf(&mut buffers.read_buf).await;
|
||||||
|
target_stream.read_buf(&mut buffers.write_buf).await;
|
||||||
|
writer.write_buf(&mut buffers.write_buf);
|
||||||
|
buffers.write_to(writer).await?;
|
||||||
|
Ok(ConnectionState::Tunnel(target_stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO TUNNEL TO TARGET
|
||||||
|
async fn do_tunnel(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
mut writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
target: &mut TcpStream,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
let (mut target_reader, mut target_writer) = target.split();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
// 1. from client to target
|
||||||
|
res = reader.read_buf(&mut buffers.read_buf) => {
|
||||||
|
let should_break =
|
||||||
|
relay_data(res, &mut target_writer, &mut buffers.read_buf).await?;
|
||||||
|
if should_break { break;}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. From target to client
|
||||||
|
res = target_reader.read_buf(&mut buffers.write_buf) => {
|
||||||
|
let should_break =
|
||||||
|
relay_data(res, &mut writer, &mut buffers.write_buf).await?;
|
||||||
|
if should_break { break;}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||||
|
target_writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||||
|
Ok(ConnectionState::Close)
|
||||||
|
}
|
||||||
|
|
||||||
|
//CLOSE CONNECT WITH TARGET
|
||||||
|
async fn do_close(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
Ok(ConnectionState::Disconnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
protocol::codec::codec::Codec,
|
||||||
|
proxy::connection::{
|
||||||
|
buf_pair::BufPair,
|
||||||
|
handler::{handler::ProxyHandler, utils::relay_data},
|
||||||
|
state::ConnectionState,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use bytes::BufMut;
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
|
net::{
|
||||||
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
|
TcpStream,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
|
||||||
|
enum SocksMsg {
|
||||||
|
Hello, // Hello (0x05, 0x00)
|
||||||
|
AuthFailed, // Error (0x05, 0xFF)
|
||||||
|
ConnectOk, // Connection is OK
|
||||||
|
Custom(Vec<u8>), // If needed raw bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SocksMsg {
|
||||||
|
pub fn write_to(self, buf: &mut BytesMut) {
|
||||||
|
buf.clear(); // Чистим перед записью ВСЕГДА
|
||||||
|
match self {
|
||||||
|
SocksMsg::Hello => {
|
||||||
|
buf.put_slice(&[0x05, 0x00]);
|
||||||
|
}
|
||||||
|
SocksMsg::AuthFailed => {
|
||||||
|
buf.put_slice(&[0x05, 0xFF]);
|
||||||
|
}
|
||||||
|
SocksMsg::ConnectOk => {
|
||||||
|
// SOCKS5 требует 10 байт в ответ на CONNECT:
|
||||||
|
// VER, REP(0), RSV, ATYP(1), ADDR(0,0,0,0), PORT(0,0)
|
||||||
|
buf.put_slice(&[0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]);
|
||||||
|
}
|
||||||
|
SocksMsg::Custom(data) => {
|
||||||
|
buf.put_slice(&data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Socks2Netr;
|
||||||
|
|
||||||
|
impl Socks2Netr {
|
||||||
|
fn get_addr(data: &BytesMut) -> Result<String, String> {
|
||||||
|
let target_addr = match data[3] {
|
||||||
|
0x01 => {
|
||||||
|
// IPv4 (4 байта IP + 2 байта порт)
|
||||||
|
let ip = format!("{}.{}.{}.{}", data[4], data[5], data[6], data[7]);
|
||||||
|
let port = u16::from_be_bytes([data[8], data[9]]);
|
||||||
|
format!("{}:{}", ip, port)
|
||||||
|
}
|
||||||
|
0x03 => {
|
||||||
|
// Domain Name
|
||||||
|
let len = data[4] as usize;
|
||||||
|
let domain = String::from_utf8_lossy(&data[5..5 + len]);
|
||||||
|
let port = u16::from_be_bytes([data[5 + len], data[5 + len + 1]]);
|
||||||
|
format!("{}:{}", domain, port)
|
||||||
|
}
|
||||||
|
_ => return Err("Unsupported address type".to_string()),
|
||||||
|
};
|
||||||
|
Ok(target_addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ProxyHandler for Socks2Netr {
|
||||||
|
//get tcp from socks connection. give answer to it
|
||||||
|
async fn do_new(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
buffers.read_from(reader).await?;
|
||||||
|
SocksMsg::Hello.write_to(&mut buffers.write_buf);
|
||||||
|
buffers.write_to(writer).await?;
|
||||||
|
|
||||||
|
let codec = Codec::new();
|
||||||
|
|
||||||
|
Ok(ConnectionState::Handshake)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_handshake(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
buffers.read_from(reader).await?;
|
||||||
|
let data = &buffers.read_buf;
|
||||||
|
|
||||||
|
if data.len() < 7 {
|
||||||
|
return Err("SOCKS5 request too short".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
//CODEC HERE SHOULD CREATE CUSTOM FRAME
|
||||||
|
//AND MAKE MOCK TLS HANDSHAKE WITH MY SERVER
|
||||||
|
//AFTER THIS SEND THE REAL HANDSHAKE BUT IN APPLICATION DATA
|
||||||
|
|
||||||
|
let target_addr = Socks2Netr::get_addr(data)?;
|
||||||
|
println!("Connecting to target: {}", target_addr);
|
||||||
|
//todo dynamic address of proxy server
|
||||||
|
let endpoint_stream = TcpStream::connect("127.0.0.1:4443")
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Could not connect to {}: {}", target_addr, e))?;
|
||||||
|
|
||||||
|
SocksMsg::ConnectOk.write_to(&mut buffers.write_buf);
|
||||||
|
buffers.write_to(writer).await?;
|
||||||
|
Ok(ConnectionState::Tunnel(endpoint_stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
//their tunnel. there is encrypt tcp to netr
|
||||||
|
async fn do_tunnel(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
mut writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
target: &mut TcpStream,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
buffers.reset();
|
||||||
|
let (mut target_reader, mut target_writer) = target.split();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
// 1. from client to target
|
||||||
|
res = reader.read_buf(&mut buffers.read_buf) => {
|
||||||
|
let should_break =
|
||||||
|
relay_data(res, &mut target_writer, &mut buffers.read_buf).await?;
|
||||||
|
if should_break { break;}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. From target to client
|
||||||
|
res = target_reader.read_buf(&mut buffers.write_buf) => {
|
||||||
|
let should_break =
|
||||||
|
relay_data(res, &mut writer, &mut buffers.write_buf).await?;
|
||||||
|
if should_break { break;}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||||
|
target_writer.shutdown().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(ConnectionState::Close)
|
||||||
|
}
|
||||||
|
|
||||||
|
//close tunnel between mobile (tun2socks) and server
|
||||||
|
async fn do_close(
|
||||||
|
&self,
|
||||||
|
_reader: &mut OwnedReadHalf,
|
||||||
|
_writer: &mut OwnedWriteHalf,
|
||||||
|
_buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
println!("SOCKS5 CONNECTUON CLOSED");
|
||||||
|
Ok(ConnectionState::Disconnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use crate::{
|
||||||
|
protocol::codec::codec::Codec,
|
||||||
|
proxy::connection::{
|
||||||
|
buf_pair::BufPair, handler::handler::ProxyHandler, state::ConnectionState,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tokio::net::{
|
||||||
|
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||||
|
TcpStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Tcp2Netr;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ProxyHandler for Tcp2Netr {
|
||||||
|
//for new tunnel between site.ru and proxy
|
||||||
|
async fn do_new(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
todo!()
|
||||||
|
//Ok(ConnectionState::Handshake)
|
||||||
|
}
|
||||||
|
|
||||||
|
//their handshake. need to encrypt this in netr protocol
|
||||||
|
async fn do_handshake(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
Ok(ConnectionState::Close)
|
||||||
|
}
|
||||||
|
|
||||||
|
//data flow like application data in netr protocol
|
||||||
|
async fn do_tunnel(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
codec: &mut Codec,
|
||||||
|
target: &mut TcpStream,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
Ok(ConnectionState::Close)
|
||||||
|
}
|
||||||
|
|
||||||
|
//close of connection
|
||||||
|
async fn do_close(
|
||||||
|
&self,
|
||||||
|
reader: &mut OwnedReadHalf,
|
||||||
|
writer: &mut OwnedWriteHalf,
|
||||||
|
buffers: &mut BufPair,
|
||||||
|
) -> Result<ConnectionState, String> {
|
||||||
|
Ok(ConnectionState::Close)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
pub async fn relay_data<W>(
|
||||||
|
res: Result<usize, Error>,
|
||||||
|
writer: &mut W,
|
||||||
|
buffer: &mut BytesMut,
|
||||||
|
) -> Result<bool, String>
|
||||||
|
where
|
||||||
|
W: AsyncWriteExt + Unpin,
|
||||||
|
{
|
||||||
|
let n = res.map_err(|e| e.to_string())?;
|
||||||
|
if n == 0 {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(">>> Client sent {} bytes", n);
|
||||||
|
|
||||||
|
writer.write_all(&buffer).await.map_err(|e| e.to_string())?;
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod buf_pair;
|
||||||
|
pub mod connection;
|
||||||
|
pub mod handler;
|
||||||
|
pub mod state;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::protocol::codec::codec::Codec;
|
||||||
|
|
||||||
|
//todo split to codec that uses my frame and tls codec that remove camouflage
|
||||||
|
pub enum ConnectionState {
|
||||||
|
New,
|
||||||
|
Handshake,
|
||||||
|
Tunnel(TcpStream),
|
||||||
|
Close,
|
||||||
|
Disconnected,
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod connection;
|
||||||
|
pub mod network;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::proxy::connection::{
|
||||||
|
connection::Connection, handler::handler::ProxyHandler, state::ConnectionState,
|
||||||
|
};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
pub struct Network {
|
||||||
|
inbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||||
|
outbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Network {
|
||||||
|
pub fn new(
|
||||||
|
inbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||||
|
outbound_handler: Arc<dyn ProxyHandler + Send + Sync>,
|
||||||
|
port: u16,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
inbound_handler,
|
||||||
|
outbound_handler,
|
||||||
|
port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&self) {
|
||||||
|
let port = self.port;
|
||||||
|
let listener: TcpListener = TcpListener::bind(format!("127.0.0.1:{port}"))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
println!("Server Listening on Port {}...", port);
|
||||||
|
loop {
|
||||||
|
let (stream, addr) = listener.accept().await.expect("Error on connection");
|
||||||
|
let handler = self.inbound_handler.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut conection: Connection = Connection::new(stream, addr);
|
||||||
|
|
||||||
|
let res = conection.handle(handler).await;
|
||||||
|
match res {
|
||||||
|
Ok(state) => match state {
|
||||||
|
ConnectionState::New => {
|
||||||
|
println!("New connection {}", addr)
|
||||||
|
}
|
||||||
|
ConnectionState::Handshake => {
|
||||||
|
println!("Connection {} handshaked", addr)
|
||||||
|
}
|
||||||
|
ConnectionState::Tunnel(stream) => {
|
||||||
|
println!("Connection {} tunnel", addr)
|
||||||
|
}
|
||||||
|
ConnectionState::Close => {
|
||||||
|
println!("Connection {} closed", addr)
|
||||||
|
}
|
||||||
|
ConnectionState::Disconnected => {
|
||||||
|
println!("Connection {} Disconnected", addr)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Ошибка соединения с {}: {}", conection.addr, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// --- Core Handshake Identifiers ---
|
||||||
|
/// ClientHello handshake message type
|
||||||
|
pub const HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
|
||||||
|
pub const HANDSHAKE_TYPE_SERVER_HELLO: u8 = 0x02;
|
||||||
|
|
||||||
|
// --- TLS Extension Type Codes (IANA) ---
|
||||||
|
/// Server Name Indication (SNI) - maps a hostname to the IP
|
||||||
|
pub const EXT_TYPE_SNI: u16 = 0x0000;
|
||||||
|
/// Certificate Status Request (OCSP Stapling)
|
||||||
|
pub const EXT_STATUS_REQUEST: u16 = 0x0005;
|
||||||
|
/// Supported Elliptic Curves (Named Groups)
|
||||||
|
pub const EXT_SUPPORTED_GROUPS: u16 = 0x000a;
|
||||||
|
/// Supported Point Formats (Legacy, but required for compatibility)
|
||||||
|
pub const EXT_EC_POINT_FORMATS: u16 = 0x000b;
|
||||||
|
/// Signature Algorithms the client can verify
|
||||||
|
pub const EXT_SIGNATURE_ALGORITHMS: u16 = 0x000d;
|
||||||
|
/// Application-Layer Protocol Negotiation (h2, http/1.1)
|
||||||
|
pub const EXT_ALPN: u16 = 0x0010;
|
||||||
|
/// Signed Certificate Timestamp (SCT) - used for Certificate Transparency
|
||||||
|
pub const EXT_SIGNED_CERT_TIMESTAMP: u16 = 0x0012;
|
||||||
|
/// Padding extension to avoid MTU issues or fingerprinting
|
||||||
|
pub const EXT_PADDING: u16 = 0x0015;
|
||||||
|
/// Extended Master Secret - prevents MITM key synchronization attacks
|
||||||
|
pub const EXT_EXTENDED_MASTER_SECRET: u16 = 0x0017;
|
||||||
|
/// Certificate Compression (Used by modern browsers like Chrome)
|
||||||
|
pub const EXT_COMPRESS_CERTIFICATE: u16 = 0x001b;
|
||||||
|
/// Delegated Credentials (RFC 9345)
|
||||||
|
pub const EXT_DELEGATED_CREDENTIAL: u16 = 0x0022;
|
||||||
|
///SESSION TICKET
|
||||||
|
pub const EXT_SESSION_TICKET: u16 = 0x0023;
|
||||||
|
/// Negotiated TLS Versions (Crucial for TLS 1.3)
|
||||||
|
pub const EXT_SUPPORTED_VERSIONS: u16 = 0x002b;
|
||||||
|
/// Pre-Shared Key (PSK) Exchange Modes
|
||||||
|
pub const EXT_PSK_KEY_EXCHANGE_MODES: u16 = 0x002d;
|
||||||
|
/// Key Share - carries the Diffie-Hellman public keys
|
||||||
|
pub const EXT_KEY_SHARE: u16 = 0x0033;
|
||||||
|
/// Application Settings (ALPS) - Chrome specific protocol settings
|
||||||
|
pub const EXT_ALPS: u16 = 0x44cd;
|
||||||
|
|
||||||
|
// --- Compatibility & Anti-Detection (Fingerprinting) ---
|
||||||
|
/// Secure Renegotiation Indication (RFC 5746)
|
||||||
|
pub const EXT_RENEGOTIATION_INFO: u16 = 0xff01;
|
||||||
|
|
||||||
|
/// GREASE (Generate Random Extensions And Sustain Extensibility)
|
||||||
|
/// Used to prevent server bugs where unknown extensions cause failures.
|
||||||
|
pub const GREASE_IDENTIFIERS: [u16; 16] = [
|
||||||
|
0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA,
|
||||||
|
0xCACA, 0xDADA, 0xEAEA, 0xFAFA,
|
||||||
|
];
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
// Using your provided constants and types
|
||||||
|
use crate::tlseng::{
|
||||||
|
etype::*,
|
||||||
|
params::{TlsGroups, TlsSignatures, TlsVersions},
|
||||||
|
profile::profile::BrowserProfile,
|
||||||
|
values::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Extension {
|
||||||
|
pub etype: u16,
|
||||||
|
pub elen: u16,
|
||||||
|
pub data: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extension {
|
||||||
|
pub fn new(etype: u16, data: Bytes) -> Self {
|
||||||
|
Self {
|
||||||
|
etype,
|
||||||
|
elen: data.len() as u16,
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal helper to pack and append an extension.
|
||||||
|
fn add_extension(&mut self, etype: u16, data: &[u8]) {
|
||||||
|
let ext = Extension::pack(etype, data);
|
||||||
|
self.payload.put_slice(&ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x?a?a - Randomized GREASE
|
||||||
|
pub fn grease(&mut self) {
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
let rnd = Rng::next_u32(&mut rng) % 16;
|
||||||
|
let etype = GREASE_IDENTIFIERS[rnd as usize];
|
||||||
|
self.add_extension(etype, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used for exact hex-matching of GREASE values
|
||||||
|
pub fn grease_fixed(&mut self, etype: u16) {
|
||||||
|
self.add_extension(etype, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0000 - SNI
|
||||||
|
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(EXT_TYPE_SNI, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0017 - Extended Master Secret
|
||||||
|
pub fn extended_main_secret(&mut self) {
|
||||||
|
self.add_extension(EXT_EXTENDED_MASTER_SECRET, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x000a - Supported Groups
|
||||||
|
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(EXT_SUPPORTED_GROUPS, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x000d - Signature Algorithms
|
||||||
|
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(EXT_SIGNATURE_ALGORITHMS, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x44cd - ALPS (Application Settings)
|
||||||
|
/// Updated to support specific protocols
|
||||||
|
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(EXT_ALPS, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x002b - Supported Versions
|
||||||
|
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(EXT_SUPPORTED_VERSIONS, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x002d - PSK Key Exchange Modes
|
||||||
|
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(EXT_PSK_KEY_EXCHANGE_MODES, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x001b - Certificate Compression
|
||||||
|
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(EXT_COMPRESS_CERTIFICATE, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0005 - Status Request
|
||||||
|
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(EXT_STATUS_REQUEST, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0033 - Key Share
|
||||||
|
/// Corrected: ClientHello KeyShare has a list length AND a group/key length
|
||||||
|
pub fn key_share(&mut self, public_key: &[u8]) {
|
||||||
|
let mut data = BytesMut::with_capacity(38);
|
||||||
|
data.put_u16(34); // Total Key Share List Length
|
||||||
|
data.put_u16(GROUP_X25519);
|
||||||
|
data.put_u16(32); // Public Key length
|
||||||
|
data.put_slice(public_key);
|
||||||
|
self.add_extension(EXT_KEY_SHARE, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x000b - EC Point Formats
|
||||||
|
pub fn ec_point_formats(&mut self) {
|
||||||
|
let mut data = BytesMut::with_capacity(2);
|
||||||
|
data.put_u8(1);
|
||||||
|
data.put_u8(EC_POINT_FORMAT_UNCOMPRESSED);
|
||||||
|
self.add_extension(EXT_EC_POINT_FORMATS, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0012 - SCT
|
||||||
|
pub fn signed_certificate_timestamp(&mut self) {
|
||||||
|
self.add_extension(EXT_SIGNED_CERT_TIMESTAMP, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0022 - Delegated Credentials
|
||||||
|
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(EXT_DELEGATED_CREDENTIAL, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0010 - ALPN
|
||||||
|
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(EXT_ALPN, &extension_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0x0023 - Session Ticket
|
||||||
|
pub fn session_ticket(&mut self) {
|
||||||
|
self.add_extension(0x0023, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn padding(&mut self, target_size: usize) {
|
||||||
|
// Текущий размер накопленной нагрузки
|
||||||
|
let current_size = self.payload.len();
|
||||||
|
|
||||||
|
// 4 байта резервируем под заголовок самого расширения (Type + Length)
|
||||||
|
if target_size > current_size + 4 {
|
||||||
|
let pad_len = target_size - current_size - 4;
|
||||||
|
let data = vec![0u8; pad_len];
|
||||||
|
|
||||||
|
// Используем pack, как и в других методах
|
||||||
|
let ext = Extension::pack(EXT_PADDING, &data);
|
||||||
|
self.payload.put_slice(&ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 0xff01 - Renegotiation Info
|
||||||
|
pub fn renegotiation_info(&mut self) {
|
||||||
|
self.add_extension(EXT_RENEGOTIATION_INFO, &[0x00]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&mut self) -> Bytes {
|
||||||
|
self.payload.split().freeze()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_profile(&mut self, profile: &BrowserProfile, host: &str, pub_key: &[u8]) {
|
||||||
|
for &ext_id in profile.extension_order {
|
||||||
|
match ext_id {
|
||||||
|
0x0000 => self.server_name(host),
|
||||||
|
0x000a => self.supported_groups(profile.groups),
|
||||||
|
0x000d => self.signature_algorithms(profile.signatures),
|
||||||
|
0x0010 => self.alpn(&["h2", "http/1.1"]),
|
||||||
|
0x0012 => self.signed_certificate_timestamp(),
|
||||||
|
0x0017 => self.extended_main_secret(),
|
||||||
|
0x001b => self.compress_certificate(&[CERT_COMPRESSION_BROTLI]),
|
||||||
|
0x0022 => self.delegated_credential(profile.delegated_signatures),
|
||||||
|
0x0023 => self.session_ticket(),
|
||||||
|
0x002b => self.supported_versions(profile.versions),
|
||||||
|
0x002d => self.psk_key_exchange_modes(),
|
||||||
|
0x0033 => self.key_share(pub_key),
|
||||||
|
0x44cd => self.application_settings(&["h2"]),
|
||||||
|
0x0005 => self.status_request(),
|
||||||
|
0x000b => self.ec_point_formats(),
|
||||||
|
0xff01 => self.renegotiation_info(),
|
||||||
|
// Padding logic
|
||||||
|
0x0015 => {
|
||||||
|
if profile.is_chromium {
|
||||||
|
// Standard Chromium behavior: pad to 512 bytes
|
||||||
|
self.padding(512);
|
||||||
|
} else {
|
||||||
|
// Non-chromium might use different logic or no padding
|
||||||
|
self.add_extension(0x0015, &[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id if (id & 0x0f0f) == 0x0a0a => self.grease(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
pub mod etype;
|
||||||
|
pub mod extension;
|
||||||
|
mod params;
|
||||||
|
pub mod profile;
|
||||||
|
pub mod tls;
|
||||||
|
mod values;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct TlsGroups(pub &'static [u16]);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct TlsSignatures(pub &'static [u16]);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct TlsVersions(pub &'static [u16]);
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use crate::tlseng::etype::*;
|
||||||
|
use crate::tlseng::params::{TlsGroups, TlsSignatures};
|
||||||
|
use crate::tlseng::values::{
|
||||||
|
GROUP_SECP256R1, GROUP_SECP384R1, GROUP_X25519, SIG_ECDSA_SECP256R1_SHA256,
|
||||||
|
SIG_ECDSA_SECP384R1_SHA384, SIG_RSA_PKCS1_SHA256, SIG_RSA_PKCS1_SHA384, SIG_RSA_PKCS1_SHA512,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA256, SIG_RSA_PSS_RSAE_SHA384, SIG_RSA_PSS_RSAE_SHA512,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CHROME_GROUPS: TlsGroups =
|
||||||
|
TlsGroups(&[0xaaaa, GROUP_X25519, GROUP_SECP256R1, GROUP_SECP384R1]);
|
||||||
|
|
||||||
|
pub const CHROME_SIGNATURES: TlsSignatures = TlsSignatures(&[
|
||||||
|
SIG_ECDSA_SECP256R1_SHA256,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA256,
|
||||||
|
SIG_RSA_PKCS1_SHA256,
|
||||||
|
SIG_ECDSA_SECP384R1_SHA384,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA384,
|
||||||
|
SIG_RSA_PKCS1_SHA384,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA512,
|
||||||
|
SIG_RSA_PKCS1_SHA512,
|
||||||
|
]);
|
||||||
|
|
||||||
|
pub const CHROME_DELEGATED_ALGS: TlsSignatures = TlsSignatures(&[
|
||||||
|
SIG_ECDSA_SECP256R1_SHA256,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA256,
|
||||||
|
SIG_RSA_PKCS1_SHA256,
|
||||||
|
SIG_ECDSA_SECP384R1_SHA384,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA384,
|
||||||
|
SIG_RSA_PKCS1_SHA384,
|
||||||
|
]);
|
||||||
|
|
||||||
|
pub const CHROME_ALPN_PROTOCOLS: &[&str] = &["h2", "http/1.1"];
|
||||||
|
|
||||||
|
pub const CHROMIUM_EXT_ORDER: &[u16] = &[
|
||||||
|
0xaaaa, // GREASE
|
||||||
|
EXT_TYPE_SNI, // 0x0000
|
||||||
|
EXT_EXTENDED_MASTER_SECRET, // 0x0017
|
||||||
|
EXT_SESSION_TICKET, // SessionTicket
|
||||||
|
EXT_SUPPORTED_GROUPS, // 0x000a
|
||||||
|
EXT_EC_POINT_FORMATS, // 0x000b
|
||||||
|
EXT_SIGNATURE_ALGORITHMS, // 0x000d
|
||||||
|
EXT_ALPN, // 0x0010
|
||||||
|
EXT_ALPS, // 0x44cd
|
||||||
|
EXT_STATUS_REQUEST, // 0x0005
|
||||||
|
EXT_KEY_SHARE, // 0x0033
|
||||||
|
EXT_SUPPORTED_VERSIONS, // 0x002b
|
||||||
|
EXT_PSK_KEY_EXCHANGE_MODES, // 0x002d
|
||||||
|
EXT_COMPRESS_CERTIFICATE, // 0x001b
|
||||||
|
EXT_SIGNED_CERT_TIMESTAMP, // 0x0012
|
||||||
|
EXT_DELEGATED_CREDENTIAL, // 0x0022
|
||||||
|
];
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use crate::tlseng::etype::*;
|
||||||
|
use crate::tlseng::params::{TlsGroups, TlsSignatures};
|
||||||
|
use crate::tlseng::values::{
|
||||||
|
GROUP_SECP256R1, GROUP_SECP384R1, GROUP_X25519, SIG_ECDSA_SECP256R1_SHA256,
|
||||||
|
SIG_ECDSA_SECP384R1_SHA384, SIG_RSA_PKCS1_SHA256, SIG_RSA_PKCS1_SHA384, SIG_RSA_PKCS1_SHA512,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA256, SIG_RSA_PSS_RSAE_SHA384, SIG_RSA_PSS_RSAE_SHA512,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- MICROSOFT EDGE ---
|
||||||
|
// Edge often mirrors Chrome exactly but sometimes removes specific
|
||||||
|
// experimental GREASE values or adds Windows-specific signature prefs.
|
||||||
|
pub const EDGE_GROUPS: TlsGroups = TlsGroups(&[
|
||||||
|
0x0a0a, // GREASE
|
||||||
|
GROUP_X25519,
|
||||||
|
GROUP_SECP256R1,
|
||||||
|
GROUP_SECP384R1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
pub const EDGE_SIGNATURES: TlsSignatures = TlsSignatures(&[
|
||||||
|
SIG_ECDSA_SECP256R1_SHA256,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA256,
|
||||||
|
SIG_RSA_PKCS1_SHA256,
|
||||||
|
SIG_ECDSA_SECP384R1_SHA384,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA384,
|
||||||
|
SIG_RSA_PKCS1_SHA384,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA512,
|
||||||
|
SIG_RSA_PKCS1_SHA512,
|
||||||
|
]);
|
||||||
|
|
||||||
|
pub const EDGE_DELEGATED_ALGS: TlsSignatures = TlsSignatures(&[
|
||||||
|
SIG_ECDSA_SECP256R1_SHA256,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA256,
|
||||||
|
SIG_RSA_PKCS1_SHA256,
|
||||||
|
SIG_ECDSA_SECP384R1_SHA384,
|
||||||
|
SIG_RSA_PSS_RSAE_SHA384,
|
||||||
|
SIG_RSA_PKCS1_SHA384,
|
||||||
|
]);
|
||||||
|
|
||||||
|
pub const EDGE_ALPN_PROTOCOLS: &[&str] = &["h2", "http/1.1"];
|
||||||
|
|
||||||
|
// Microsoft Edge Extension Order (Chromium v130+)
|
||||||
|
pub const EDGE_EXT_ORDER: &[u16] = &[
|
||||||
|
0x1a1a, // GREASE
|
||||||
|
EXT_TYPE_SNI, // 0x0000
|
||||||
|
EXT_EXTENDED_MASTER_SECRET, // 0x0017
|
||||||
|
EXT_SESSION_TICKET, // 0x0023
|
||||||
|
EXT_SUPPORTED_GROUPS, // 0x000a
|
||||||
|
EXT_EC_POINT_FORMATS, // 0x000b
|
||||||
|
EXT_SIGNATURE_ALGORITHMS, // 0x000d
|
||||||
|
EXT_ALPN, // 0x0010
|
||||||
|
EXT_ALPS, // 0x44cd
|
||||||
|
EXT_STATUS_REQUEST, // 0x0005
|
||||||
|
EXT_KEY_SHARE, // 0x0033
|
||||||
|
EXT_SUPPORTED_VERSIONS, // 0x002b
|
||||||
|
EXT_PSK_KEY_EXCHANGE_MODES, // 0x002d
|
||||||
|
EXT_COMPRESS_CERTIFICATE, // 0x001b
|
||||||
|
EXT_SIGNED_CERT_TIMESTAMP, // 0x0012
|
||||||
|
EXT_DELEGATED_CREDENTIAL, // 0x0022
|
||||||
|
EXT_PADDING, // 0x0015
|
||||||
|
0x3a3a, // GREASE
|
||||||
|
];
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod chrome_groups;
|
||||||
|
pub mod edge_groups;
|
||||||
|
pub mod shared;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
use crate::tlseng::params::TlsVersions;
|
||||||
|
|
||||||
|
pub const MODERN_VERSIONS: TlsVersions = TlsVersions(&[0x0304, 0x0303]);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod groups;
|
||||||
|
pub mod profile;
|
||||||
|
pub mod versions;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::tlseng::params::{TlsGroups, TlsSignatures, TlsVersions};
|
||||||
|
|
||||||
|
/// Represents a complete TLS fingerprint profile for a specific browser.
|
||||||
|
pub struct BrowserProfile {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub groups: TlsGroups,
|
||||||
|
pub signatures: TlsSignatures,
|
||||||
|
pub delegated_signatures: TlsSignatures,
|
||||||
|
pub versions: TlsVersions,
|
||||||
|
pub alpn: &'static [&'static str],
|
||||||
|
/// The specific order of Extension IDs (e.g., [0x0000, 0x0017, ...])
|
||||||
|
pub extension_order: &'static [u16],
|
||||||
|
pub is_chromium: bool,
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use crate::tlseng::{
|
||||||
|
params::TlsVersions,
|
||||||
|
profile::{
|
||||||
|
groups::{
|
||||||
|
chrome_groups::{
|
||||||
|
CHROME_ALPN_PROTOCOLS, CHROME_DELEGATED_ALGS, CHROME_GROUPS, CHROME_SIGNATURES,
|
||||||
|
CHROMIUM_EXT_ORDER,
|
||||||
|
},
|
||||||
|
edge_groups::{
|
||||||
|
EDGE_ALPN_PROTOCOLS, EDGE_DELEGATED_ALGS, EDGE_EXT_ORDER, EDGE_GROUPS,
|
||||||
|
EDGE_SIGNATURES,
|
||||||
|
},
|
||||||
|
shared::MODERN_VERSIONS,
|
||||||
|
},
|
||||||
|
profile::BrowserProfile,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Versions ---
|
||||||
|
pub const TLS_13_ONLY: TlsVersions = TlsVersions(&[0x0304]);
|
||||||
|
|
||||||
|
// --- CHROME 131 PROFILE ---
|
||||||
|
pub const CHROME_131: BrowserProfile = BrowserProfile {
|
||||||
|
name: "Chrome 131 (Windows)",
|
||||||
|
groups: CHROME_GROUPS,
|
||||||
|
signatures: CHROME_SIGNATURES,
|
||||||
|
alpn: CHROME_ALPN_PROTOCOLS,
|
||||||
|
delegated_signatures: CHROME_DELEGATED_ALGS,
|
||||||
|
versions: TLS_13_ONLY,
|
||||||
|
extension_order: CHROMIUM_EXT_ORDER,
|
||||||
|
is_chromium: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- FIREFOX 133 PROFILE (Example) ---
|
||||||
|
// Note: Firefox uses different groups and no ALPS
|
||||||
|
pub const EDGE_PROFILE: BrowserProfile = BrowserProfile {
|
||||||
|
name: "Edge",
|
||||||
|
groups: EDGE_GROUPS,
|
||||||
|
signatures: EDGE_SIGNATURES, // Usually identical to Chrome
|
||||||
|
alpn: EDGE_ALPN_PROTOCOLS,
|
||||||
|
delegated_signatures: EDGE_DELEGATED_ALGS,
|
||||||
|
versions: MODERN_VERSIONS,
|
||||||
|
extension_order: EDGE_EXT_ORDER,
|
||||||
|
is_chromium: true,
|
||||||
|
};
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
tlseng::etype::{HANDSHAKE_TYPE_CLIENT_HELLO, HANDSHAKE_TYPE_SERVER_HELLO},
|
||||||
|
utils::u24::U24,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// TLS Content Types as defined in the TLS Record Protocol.
|
||||||
|
/// These identify what is contained within the TLS Record payload.
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
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;
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Known TLS protocol versions.
|
||||||
|
/// Note: TLS 1.3 often uses legacy versions in headers for compatibility.
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum ProtocolVersion {
|
||||||
|
Tls10 = 0x0301,
|
||||||
|
Tls12 = 0x0303,
|
||||||
|
Tls13 = 0x0304,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for ProtocolVersion {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0x0301 => Ok(ProtocolVersion::Tls10),
|
||||||
|
0x0303 => Ok(ProtocolVersion::Tls12),
|
||||||
|
0x0304 => Ok(ProtocolVersion::Tls13),
|
||||||
|
_ => Err("This is not Protocol Version"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ApplicationData {
|
||||||
|
pub length: usize,
|
||||||
|
pub payload: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HelloType {
|
||||||
|
Client = 0x00,
|
||||||
|
Server = 0x01,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for HelloType {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0x00 => Ok(HelloType::Client),
|
||||||
|
0x01 => Ok(HelloType::Server),
|
||||||
|
_ => Err("This is not Hello header"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HelloHeader {
|
||||||
|
pub header_type: HelloType,
|
||||||
|
pub len: U24,
|
||||||
|
pub body: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the ClientHello Handshake message.
|
||||||
|
/// This is the first message sent by a client to initiate a TLS connection.
|
||||||
|
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]);
|
||||||
|
|
||||||
|
buf.freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ServerHello {
|
||||||
|
pub version: ProtocolVersion,
|
||||||
|
pub random: [u8; 32],
|
||||||
|
pub session_id: Bytes,
|
||||||
|
pub cipher_suite: u16,
|
||||||
|
pub extensions: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerHello {
|
||||||
|
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(ProtocolVersion::Tls12 as u16); // Legacy 0x0303
|
||||||
|
buf.put_slice(&self.random);
|
||||||
|
|
||||||
|
// Session ID
|
||||||
|
buf.put_u8(self.session_id.len() as u8);
|
||||||
|
buf.put_slice(&self.session_id);
|
||||||
|
|
||||||
|
// Выбранный Cipher Suite (в отличие от клиента, тут только ОДИН)
|
||||||
|
buf.put_u16(self.cipher_suite);
|
||||||
|
|
||||||
|
// Compression: всегда 0x00
|
||||||
|
buf.put_u8(0);
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
buf.put_u16(self.extensions.len() as u16);
|
||||||
|
buf.put_slice(&self.extensions);
|
||||||
|
|
||||||
|
// 4. Патчим длину (переиспользуем твой метод)
|
||||||
|
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]);
|
||||||
|
|
||||||
|
buf.freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
pub const TYPE_HOST_NAME: u8 = 0x00;
|
||||||
|
|
||||||
|
pub const GROUP_X25519: u16 = 0x001d;
|
||||||
|
pub const GROUP_SECP256R1: u16 = 0x0017;
|
||||||
|
pub const GROUP_SECP384R1: u16 = 0x0018;
|
||||||
|
|
||||||
|
// Signature Algorithms (Signature Schemes)
|
||||||
|
pub const SIG_ECDSA_SECP256R1_SHA256: u16 = 0x0403;
|
||||||
|
pub const SIG_RSA_PSS_RSAE_SHA256: u16 = 0x0804;
|
||||||
|
pub const SIG_RSA_PKCS1_SHA256: u16 = 0x0401;
|
||||||
|
pub const SIG_ECDSA_SECP384R1_SHA384: u16 = 0x0503;
|
||||||
|
pub const SIG_RSA_PSS_RSAE_SHA384: u16 = 0x0805;
|
||||||
|
pub const SIG_RSA_PKCS1_SHA384: u16 = 0x0501;
|
||||||
|
pub const SIG_RSA_PSS_RSAE_SHA512: u16 = 0x0806;
|
||||||
|
pub const SIG_RSA_PKCS1_SHA512: u16 = 0x0601;
|
||||||
|
|
||||||
|
// Versions & Modes
|
||||||
|
pub const PSK_DHE_KE_MODE: u8 = 0x01;
|
||||||
|
pub const CERT_COMPRESSION_BROTLI: u16 = 0x0002;
|
||||||
|
pub const OCSP_STATUS_TYPE: u8 = 0x01;
|
||||||
|
pub const EC_POINT_FORMAT_UNCOMPRESSED: u8 = 0x00;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod u24;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
use bytes::Buf;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct U24([u8; 3]);
|
||||||
|
|
||||||
|
impl U24 {
|
||||||
|
pub fn from_u32(value: u32) -> Self {
|
||||||
|
let b = value.to_be_bytes();
|
||||||
|
U24([b[1], b[2], b[3]])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_u32(&self) -> u32 {
|
||||||
|
u32::from_be_bytes([0, self.0[0], self.0[1], self.0[2]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BufExt: Buf {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Реализуем этот трейт для всего, что поддерживает Buf (включая BytesMut)
|
||||||
|
impl<T: Buf> BufExt for T {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "netrunner-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
netrunner-common = { path = "../common" }
|
||||||
|
tokio = { version = "1.36", features = ["full"] }
|
||||||
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
|
anyhow = "1.0"
|
||||||
|
log = "0.4"
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use netrunner_common::proxy::{
|
||||||
|
connection::handler::{netr2tcp::Netr2Tcp, tcp2netr::Tcp2Netr},
|
||||||
|
network::Network,
|
||||||
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about = "Netrunner Proxy Server")]
|
||||||
|
struct Args {
|
||||||
|
/// Порт, который будет слушать сервер
|
||||||
|
#[arg(short, long, default_value_t = 8080)]
|
||||||
|
port: u16,
|
||||||
|
|
||||||
|
/// IP адрес для привязки
|
||||||
|
#[arg(long, default_value = "0.0.0.0")]
|
||||||
|
host: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let inbound_handler = Arc::new(Netr2Tcp); //change here to Netr2tcp
|
||||||
|
let outbound_handler = Arc::new(Tcp2Netr);
|
||||||
|
let net = Network::new(inbound_handler, outbound_handler, args.port);
|
||||||
|
|
||||||
|
// Создаем движок (Runtime)
|
||||||
|
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
||||||
|
|
||||||
|
// "Блокируем" основной поток, пока работает асинхронный код
|
||||||
|
rt.block_on(async {
|
||||||
|
net.run().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user