renames and tauri app
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
/// Handshake message types
|
||||
pub const HANDSHAKE_TYPE_CLIENT_HELLO: u8 = 0x01;
|
||||
pub const HANDSHAKE_TYPE_SERVER_HELLO: u8 = 0x02;
|
||||
|
||||
/// SNI (Server Name Indication) specific
|
||||
pub const TYPE_HOST_NAME: u8 = 0x00;
|
||||
|
||||
/// PSK (Pre-Shared Key) modes
|
||||
pub const PSK_DHE_KE_MODE: u8 = 0x01;
|
||||
|
||||
/// Certificate compression algorithms
|
||||
pub const CERT_COMPRESSION_BROTLI: u16 = 0x0002;
|
||||
|
||||
/// Extension internal status types
|
||||
pub const OCSP_STATUS_TYPE: u8 = 0x01;
|
||||
//pub const EC_POINT_FORMAT_UNCOMPRESSED: u8 = 0x00;
|
||||
|
||||
/// GREASE (Generate Random Extensions And Sustain Extensibility)
|
||||
/// Используется для предотвращения ошибок серверов при встрече с неизвестными ID.
|
||||
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,276 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use rand::RngExt;
|
||||
|
||||
// Using your provided constants and types
|
||||
use crate::tlseng::{
|
||||
consts::{
|
||||
CERT_COMPRESSION_BROTLI, GREASE_IDENTIFIERS, OCSP_STATUS_TYPE, PSK_DHE_KE_MODE,
|
||||
TYPE_HOST_NAME,
|
||||
},
|
||||
profile::BrowserProfile,
|
||||
types::{TlsExtensions, TlsGroups, TlsSignatures, TlsVersions},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Extension {
|
||||
pub etype: u16,
|
||||
pub elen: u16,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtensionStack {
|
||||
pub extensions: Vec<Extension>,
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
/// Creates a new Extension from the given parameters.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `etype`: The type of extension (e.g., EXT_SUPPORTED_VERSIONS).
|
||||
/// * `data`: The actual data being transported (e.g., a serialized list of supported versions).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new Extension structure with the given parameters.
|
||||
pub fn new(etype: u16, data: Bytes) -> Self {
|
||||
Self {
|
||||
etype,
|
||||
elen: data.len() as u16,
|
||||
data,
|
||||
}
|
||||
}
|
||||
/// Packs an extension into a single byte array.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `etype`: The type of extension (e.g., EXT_SUPPORTED_VERSIONS).
|
||||
/// * `data`: The actual data being transported (e.g., a serialized list of supported versions).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A single byte array containing the type and length of the extension, followed by the actual extension 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),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_extension(&mut self, etype: u16, data: &[u8]) {
|
||||
let ext = Extension::pack(etype, data);
|
||||
self.payload.put_slice(&ext);
|
||||
}
|
||||
|
||||
pub fn grease(&mut self) {
|
||||
let mut rng = rand::rng();
|
||||
let rnd = rng.random_range(0..GREASE_IDENTIFIERS.len());
|
||||
let etype = GREASE_IDENTIFIERS[rnd];
|
||||
self.add_extension(etype, &[]);
|
||||
}
|
||||
|
||||
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(TlsExtensions::SNI, &data);
|
||||
}
|
||||
|
||||
pub fn extended_main_secret(&mut self) {
|
||||
self.add_extension(TlsExtensions::EMS, &[]);
|
||||
}
|
||||
|
||||
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(TlsExtensions::SUPPORTED_GROUPS, &data);
|
||||
}
|
||||
|
||||
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(TlsExtensions::SIGNATURE_ALGORITHMS, &data);
|
||||
}
|
||||
|
||||
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(TlsExtensions::SUPPORTED_VERSIONS, &data);
|
||||
}
|
||||
|
||||
pub fn key_share(&mut self, pub_key: &[u8]) {
|
||||
let mut data = BytesMut::with_capacity(38);
|
||||
data.put_u16(34); // Total Key Share List Length
|
||||
data.put_u16(TlsGroups::X25519);
|
||||
data.put_u16(32); // Public Key length
|
||||
data.put_slice(pub_key);
|
||||
self.add_extension(TlsExtensions::KEY_SHARE, &data);
|
||||
}
|
||||
|
||||
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(TlsExtensions::ALPS, &data);
|
||||
}
|
||||
|
||||
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(TlsExtensions::ALPN, &extension_data);
|
||||
}
|
||||
|
||||
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(TlsExtensions::PSK_MODES, &data);
|
||||
}
|
||||
|
||||
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(TlsExtensions::COMPRESS_CERT, &data);
|
||||
}
|
||||
|
||||
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(TlsExtensions::STATUS_REQUEST, &data);
|
||||
}
|
||||
|
||||
pub fn ec_point_formats(&mut self) {
|
||||
let mut data = BytesMut::with_capacity(2);
|
||||
data.put_u8(1);
|
||||
data.put_u8(0x00); // Uncompressed
|
||||
self.add_extension(TlsExtensions::EC_POINT_FORMATS, &data);
|
||||
}
|
||||
|
||||
pub fn signed_certificate_timestamp(&mut self) {
|
||||
self.add_extension(TlsExtensions::SCT, &[]);
|
||||
}
|
||||
|
||||
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(TlsExtensions::DELEGATED_CREDENTIAL, &data);
|
||||
}
|
||||
|
||||
pub fn session_ticket(&mut self) {
|
||||
self.add_extension(TlsExtensions::SESSION_TICKET, &[]);
|
||||
}
|
||||
|
||||
pub fn renegotiation_info(&mut self) {
|
||||
self.add_extension(TlsExtensions::RENEGOTIATION_INFO, &[0x00]);
|
||||
}
|
||||
|
||||
pub fn padding(&mut self, target_size: usize) {
|
||||
let current_size = self.payload.len();
|
||||
if target_size > current_size + 4 {
|
||||
let pad_len = target_size - current_size - 4;
|
||||
let data = vec![0u8; pad_len];
|
||||
self.add_extension(TlsExtensions::PADDING, &data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_profile(&mut self, profile: &BrowserProfile, host: &str, pub_key: &[u8]) {
|
||||
for &ext_id in &profile.extension_order {
|
||||
match ext_id {
|
||||
TlsExtensions::SNI => self.server_name(host),
|
||||
TlsExtensions::SUPPORTED_GROUPS => self.supported_groups(profile.groups),
|
||||
TlsExtensions::SIGNATURE_ALGORITHMS => {
|
||||
self.signature_algorithms(profile.signatures)
|
||||
}
|
||||
TlsExtensions::ALPN => self.alpn(profile.alpn),
|
||||
TlsExtensions::SCT => self.signed_certificate_timestamp(),
|
||||
TlsExtensions::EMS => self.extended_main_secret(),
|
||||
TlsExtensions::COMPRESS_CERT => {
|
||||
self.compress_certificate(&[CERT_COMPRESSION_BROTLI])
|
||||
}
|
||||
TlsExtensions::DELEGATED_CREDENTIAL => {
|
||||
self.delegated_credential(profile.delegated_signatures)
|
||||
}
|
||||
TlsExtensions::SESSION_TICKET => self.session_ticket(),
|
||||
TlsExtensions::SUPPORTED_VERSIONS => self.supported_versions(profile.versions),
|
||||
TlsExtensions::PSK_MODES => self.psk_key_exchange_modes(),
|
||||
TlsExtensions::KEY_SHARE => self.key_share(pub_key),
|
||||
TlsExtensions::ALPS => self.application_settings(&["h2"]),
|
||||
TlsExtensions::STATUS_REQUEST => self.status_request(),
|
||||
TlsExtensions::EC_POINT_FORMATS => self.ec_point_formats(),
|
||||
TlsExtensions::RENEGOTIATION_INFO => self.renegotiation_info(),
|
||||
TlsExtensions::PADDING => {
|
||||
if profile.is_chromium {
|
||||
self.padding(512);
|
||||
} else {
|
||||
self.add_extension(TlsExtensions::PADDING, &[]);
|
||||
}
|
||||
}
|
||||
// Обработка GREASE по маске
|
||||
id if (id & 0x0f0f) == 0x0a0a => self.grease(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Bytes {
|
||||
self.payload.split().freeze()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::{
|
||||
tlseng::{
|
||||
consts::{HANDSHAKE_TYPE_CLIENT_HELLO, HANDSHAKE_TYPE_SERVER_HELLO},
|
||||
extension::ExtensionBuilder,
|
||||
profile::BrowserProfile,
|
||||
tls_record::TlsRecord,
|
||||
types::{ContentType, HelloType, ProtocolVersion},
|
||||
},
|
||||
utils::u24::U24,
|
||||
};
|
||||
|
||||
pub struct HelloHeader {
|
||||
pub header_type: HelloType,
|
||||
pub len: U24,
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
let ext_len = self.extensions.len();
|
||||
let total_handshake_len = (buf.len() - length_pos - 3) as u32;
|
||||
|
||||
tracing::debug!(
|
||||
handshake_type = "ClientHello",
|
||||
body_len = total_handshake_len,
|
||||
extensions_len = ext_len,
|
||||
total_bytes = buf.len(),
|
||||
"Serialized Handshake message"
|
||||
);
|
||||
buf.freeze()
|
||||
}
|
||||
|
||||
pub fn make_client_hello(
|
||||
profile: &BrowserProfile,
|
||||
host: &str,
|
||||
public_key: &[u8; 32],
|
||||
salt: [u8; 32],
|
||||
) -> Bytes {
|
||||
let tls_random = salt;
|
||||
|
||||
let mut ext_builder = ExtensionBuilder::new();
|
||||
|
||||
ext_builder.apply_profile(profile, host, public_key);
|
||||
let extensions_bytes = ext_builder.build();
|
||||
|
||||
let mut session_id = BytesMut::with_capacity(32);
|
||||
session_id.put_slice(&[0u8; 32]);
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let record = TlsRecord::new(
|
||||
ContentType::Handshake,
|
||||
ProtocolVersion::Tls10,
|
||||
client_hello.serialize(),
|
||||
);
|
||||
|
||||
record.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerHello {
|
||||
pub version: ProtocolVersion,
|
||||
pub random: [u8; 32],
|
||||
pub session_id: Bytes,
|
||||
pub cipher_suite: u16,
|
||||
pub extensions: BytesMut,
|
||||
}
|
||||
impl ServerHello {
|
||||
/// Динамически создает ServerHello на основе данных из ClientHello
|
||||
pub fn from_client_hello(
|
||||
client_hello: &ClientHello,
|
||||
server_public_key: &[u8],
|
||||
salt: [u8; 32],
|
||||
) -> Self {
|
||||
let server_random = salt;
|
||||
|
||||
let selected_suite = client_hello
|
||||
.cipher_suites
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or(0x1301);
|
||||
|
||||
let mut extensions = BytesMut::new();
|
||||
|
||||
// --- Extension: Supported Versions (0x002b) ---
|
||||
extensions.put_u16(0x002b);
|
||||
extensions.put_u16(2);
|
||||
extensions.put_u16(0x0304); // TLS 1.3
|
||||
|
||||
// --- Extension: Key Share (0x0033) ---
|
||||
// Структура: Type(2) + Length(2) + Group(2) + KeyLength(2) + Key(N)
|
||||
extensions.put_u16(0x0033);
|
||||
extensions.put_u16(36); // Общая длина данных расширения (2+2+32)
|
||||
extensions.put_u16(0x001d); // Named Group: x25519
|
||||
extensions.put_u16(32); // Key Length
|
||||
extensions.put_slice(server_public_key);
|
||||
|
||||
Self {
|
||||
version: ProtocolVersion::Tls12,
|
||||
random: server_random,
|
||||
session_id: client_hello.session_id.clone(),
|
||||
cipher_suite: selected_suite,
|
||||
extensions,
|
||||
}
|
||||
}
|
||||
|
||||
/// Оборачивает в TLS Record, принимая ключ
|
||||
pub fn make_server_hello(
|
||||
client_hello: &ClientHello,
|
||||
server_public_key: &[u8],
|
||||
salt: [u8; 32],
|
||||
) -> Bytes {
|
||||
let server_hello = Self::from_client_hello(client_hello, server_public_key, salt);
|
||||
let handshake_payload = server_hello.serialize();
|
||||
|
||||
let record = TlsRecord::new(
|
||||
ContentType::Handshake,
|
||||
ProtocolVersion::Tls12,
|
||||
handshake_payload,
|
||||
);
|
||||
|
||||
record.serialize()
|
||||
}
|
||||
/// Сериализация самого сообщения Handshake (Type + Len gth + Body)
|
||||
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(0x0303); // Legacy Version
|
||||
buf.put_slice(&self.random);
|
||||
|
||||
// Session ID: Length (1 byte) + Data
|
||||
buf.put_u8(self.session_id.len() as u8);
|
||||
buf.put_slice(&self.session_id);
|
||||
|
||||
// Selected Cipher Suite
|
||||
buf.put_u16(self.cipher_suite);
|
||||
|
||||
// Compression: всегда 0x00
|
||||
buf.put_u8(0x00);
|
||||
|
||||
// Extensions: Length (2 bytes) + Data
|
||||
buf.put_u16(self.extensions.len() as u16);
|
||||
buf.put_slice(&self.extensions);
|
||||
|
||||
// 4. Патчим длину Handshake сообщения (u24)
|
||||
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]);
|
||||
|
||||
let total_handshake_len = (buf.len() - length_pos - 3) as u32;
|
||||
let ext_len = self.extensions.len();
|
||||
// ИНФОРМАТИВНЫЙ ЛОГ
|
||||
tracing::debug!(
|
||||
handshake_type = "ServerHello",
|
||||
body_len = total_handshake_len,
|
||||
extensions_len = ext_len,
|
||||
total_bytes = buf.len(),
|
||||
"Serialized Handshake message"
|
||||
);
|
||||
|
||||
buf.freeze() // Превращаем BytesMut в Bytes
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use bytes::Bytes;
|
||||
|
||||
pub struct ApplicationData {
|
||||
pub len: usize,
|
||||
pub payload: Bytes,
|
||||
}
|
||||
|
||||
mod consts;
|
||||
pub mod extension;
|
||||
pub mod handshake;
|
||||
pub mod profile;
|
||||
pub mod tls_record;
|
||||
pub mod types;
|
||||
@@ -0,0 +1,103 @@
|
||||
use crate::tlseng::types::{ExtensionOrder, TlsGroups, TlsSignatures, TlsVersions};
|
||||
|
||||
/// Represents a complete TLS fingerprint profile for a specific browser.
|
||||
///
|
||||
/// This struct contains all the necessary information to generate a TLS
|
||||
/// fingerprint for a specific browser, including the groups, signatures,
|
||||
/// delegated signatures, versions, ALPN, and extension order.
|
||||
pub struct BrowserProfile {
|
||||
/// The name of the browser profile.
|
||||
pub name: &'static str,
|
||||
|
||||
/// The groups supported by the browser.
|
||||
pub groups: TlsGroups,
|
||||
|
||||
/// The signatures supported by the browser.
|
||||
pub signatures: TlsSignatures,
|
||||
|
||||
/// The delegated signatures supported by the browser.
|
||||
pub delegated_signatures: TlsSignatures,
|
||||
|
||||
/// The versions of TLS supported by the browser.
|
||||
pub versions: TlsVersions,
|
||||
|
||||
/// The ALPN protocols supported by the browser.
|
||||
pub alpn: &'static [&'static str],
|
||||
|
||||
/// The specific order of Extension IDs (e.g., [0x0000, 0x0017, ...])
|
||||
pub extension_order: ExtensionOrder,
|
||||
|
||||
/// Whether the browser is based on Chromium.
|
||||
pub is_chromium: bool,
|
||||
}
|
||||
|
||||
impl BrowserProfile {
|
||||
pub const CHROME_131: Self = Self {
|
||||
name: "Chrome 131 (Windows)",
|
||||
groups: TlsGroups::CHROMIUM,
|
||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
delegated_signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
versions: TlsVersions::TLS_13_ONLY,
|
||||
alpn: &["h2", "http/1.1"],
|
||||
extension_order: ExtensionOrder::CHROMIUM_131,
|
||||
is_chromium: true,
|
||||
};
|
||||
|
||||
pub const EDGE: Self = Self {
|
||||
name: "Edge",
|
||||
groups: TlsGroups::CHROMIUM, // Edge использует тот же набор, что и Chrome
|
||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
delegated_signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
versions: TlsVersions::MODERN,
|
||||
alpn: &["h2", "http/1.1"],
|
||||
extension_order: ExtensionOrder::EDGE_130,
|
||||
is_chromium: true,
|
||||
};
|
||||
|
||||
pub const DEFAULT: Self = Self::CHROME_131;
|
||||
}
|
||||
/// Represents a TLS configuration profile for the server side.
|
||||
pub struct ServerProfile {
|
||||
/// Имя профиля (например, "Modern-TLS-1.3-Only" или "Compatible-Nginx-Style")
|
||||
pub name: &'static str,
|
||||
|
||||
/// Поддерживаемые версии TLS. Сервер выберет высшую общую с клиентом.
|
||||
pub versions: TlsVersions,
|
||||
|
||||
/// Приоритетный список шифров (Cipher Suites).
|
||||
/// В TLS 1.3 это обычно [0x1301, 0x1302, 0x1303].
|
||||
pub cipher_suites: &'static [u16],
|
||||
|
||||
/// Группы для обмена ключами (Key Exchange Groups).
|
||||
pub groups: TlsGroups,
|
||||
|
||||
/// Поддерживаемые алгоритмы подписи для аутентификации сервера.
|
||||
pub signatures: TlsSignatures,
|
||||
|
||||
/// Протоколы ALPN, которые сервер готов подтвердить (h2, http/1.1).
|
||||
pub alpn: &'static [&'static str],
|
||||
|
||||
/// Настройки сессий
|
||||
pub session_tickets: bool,
|
||||
|
||||
/// Нужно ли форсировать порядок шифров сервера (Server Preference),
|
||||
/// игнорируя порядок предпочтений клиента.
|
||||
pub honor_cipher_order: bool,
|
||||
}
|
||||
|
||||
impl ServerProfile {
|
||||
pub const MODERN: Self = Self {
|
||||
name: "Modern-Secure",
|
||||
versions: TlsVersions::MODERN, // Допустим, у тебя есть такой хелпер
|
||||
cipher_suites: &[
|
||||
0x1301, // TLS_AES_128_GCM_SHA256
|
||||
0x1302, // TLS_AES_256_GCM_SHA384
|
||||
0x1303, // TLS_CHACHA20_POLY1305_SHA256
|
||||
],
|
||||
groups: TlsGroups::MODERN, // X25519, P-256
|
||||
signatures: TlsSignatures::BROWSER_STANDARD,
|
||||
alpn: &["h2", "http/1.1"],
|
||||
session_tickets: true,
|
||||
honor_cipher_order: true,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use crate::tlseng::types::{ContentType, ProtocolVersion};
|
||||
|
||||
/// 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 {
|
||||
/// Creates a new TLS Record Layer from the given parameters.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `content_type`: The type of data contained (Handshake, ApplicationData, etc.).
|
||||
/// * `version`: The record layer version (usually 0x0301 for legacy support).
|
||||
/// * `payload`: The actual data being transported (e.g., a serialized ClientHello).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new TLS Record Layer structure with the given parameters.
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn build_application_data(payload: Bytes) -> Bytes {
|
||||
tracing::trace!(payload_len = payload.len(), "Building TlsRecord from Bytes");
|
||||
|
||||
let record = Self::new(
|
||||
ContentType::ApplicationData,
|
||||
ProtocolVersion::Tls12,
|
||||
payload,
|
||||
);
|
||||
record.serialize()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/// TLS Content Types as defined in the TLS Record Protocol.
|
||||
/// These identify what is contained within the TLS Record payload.
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
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;
|
||||
/// Attempts to convert a given `u8` value into a `ContentType`.
|
||||
///
|
||||
/// Returns `Ok(ContentType)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Represents known TLS protocol versions.
|
||||
///
|
||||
/// Note that TLS 1.3 often uses legacy versions in headers for compatibility.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ProtocolVersion {
|
||||
Tls10 = 0x0301,
|
||||
Tls12 = 0x0303,
|
||||
Tls13 = 0x0304,
|
||||
}
|
||||
|
||||
impl TryFrom<u16> for ProtocolVersion {
|
||||
type Error = &'static str;
|
||||
|
||||
/// Attempts to convert a given `u16` value into a `ProtocolVersion`.
|
||||
///
|
||||
/// Returns `Ok(ProtocolVersion)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
// TLS 1.0 (RFC 2246)
|
||||
0x0301 => Ok(ProtocolVersion::Tls10),
|
||||
// TLS 1.2 (RFC 4346)
|
||||
0x0303 => Ok(ProtocolVersion::Tls12),
|
||||
// TLS 1.3 (draft-ietf-tls-tls13-28)
|
||||
0x0304 => Ok(ProtocolVersion::Tls13),
|
||||
_ => Err("This is not Protocol Version"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hello types as defined in the TLS Handshake Protocol.
|
||||
///
|
||||
/// These identify the type of the message in the TLS Handshake protocol.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// The values of these enum variants are used as the first byte of the TLS
|
||||
/// Handshake protocol message.
|
||||
///
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum HelloType {
|
||||
/// Client hello message type
|
||||
Client = 0x01,
|
||||
/// Server hello message type
|
||||
Server = 0x02,
|
||||
}
|
||||
|
||||
/// Attempts to convert a given `u8` value into a `HelloType`.
|
||||
///
|
||||
/// Returns `Ok(HelloType)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// This function is used to convert raw bytes into a `HelloType`.
|
||||
/// It is used in the `HelloHeader` parsing process.
|
||||
impl TryFrom<u8> for HelloType {
|
||||
type Error = &'static str;
|
||||
/// Attempts to convert a given `u8` value into a `HelloType`.
|
||||
///
|
||||
/// Returns `Ok(HelloType)` if the conversion is successful, and `Err(&str)` if not.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
///
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x01 => Ok(HelloType::Client),
|
||||
0x02 => Ok(HelloType::Server),
|
||||
_ => Err("This is not Hello header"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of supported TLS groups.
|
||||
///
|
||||
/// This is a list of 16-bit group identifiers that the client supports.
|
||||
/// The server will select one of these groups to use for the key exchange.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsGroups(pub &'static [u16]);
|
||||
|
||||
impl TlsGroups {
|
||||
pub const X25519: u16 = 0x001d;
|
||||
pub const SECP256R1: u16 = 0x0017;
|
||||
pub const SECP384R1: u16 = 0x0018;
|
||||
|
||||
/// Стандартный набор для Chrome/Edge (X25519 + P-256)
|
||||
pub const CHROMIUM: Self = Self(&[Self::X25519, Self::SECP256R1, Self::SECP384R1]);
|
||||
|
||||
/// Набор "только современные кривые"
|
||||
pub const MODERN: Self = Self(&[Self::X25519, Self::SECP256R1]);
|
||||
}
|
||||
|
||||
/// A collection of supported TLS signature algorithms.
|
||||
///
|
||||
/// This is a list of 16-bit signature algorithm identifiers that the client supports.
|
||||
/// The server will select one of these algorithms to use for the digital signature.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsSignatures(pub &'static [u16]);
|
||||
|
||||
impl TlsSignatures {
|
||||
pub const ECDSA_SECP256R1_SHA256: u16 = 0x0403;
|
||||
pub const RSA_PSS_RSAE_SHA256: u16 = 0x0804;
|
||||
pub const RSA_PKCS1_SHA256: u16 = 0x0401;
|
||||
pub const ECDSA_SECP384R1_SHA384: u16 = 0x0503;
|
||||
pub const RSA_PSS_RSAE_SHA384: u16 = 0x0805;
|
||||
pub const RSA_PKCS1_SHA384: u16 = 0x0501;
|
||||
pub const RSA_PSS_RSAE_SHA512: u16 = 0x0806;
|
||||
pub const RSA_PKCS1_SHA512: u16 = 0x0601;
|
||||
|
||||
/// Типичный набор для современных браузеров
|
||||
pub const BROWSER_STANDARD: Self = Self(&[
|
||||
Self::ECDSA_SECP256R1_SHA256,
|
||||
Self::RSA_PSS_RSAE_SHA256,
|
||||
Self::RSA_PKCS1_SHA256,
|
||||
Self::ECDSA_SECP384R1_SHA384,
|
||||
Self::RSA_PSS_RSAE_SHA384,
|
||||
Self::RSA_PKCS1_SHA384,
|
||||
Self::RSA_PSS_RSAE_SHA512,
|
||||
]);
|
||||
}
|
||||
|
||||
/// A collection of supported TLS protocol versions.
|
||||
///
|
||||
/// This is a list of 16-bit protocol version identifiers that the client supports.
|
||||
/// The server will select one of these versions to use for the TLS connection.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TlsVersions(pub &'static [u16]);
|
||||
|
||||
impl TlsVersions {
|
||||
pub const TLS_1_3: u16 = 0x0304;
|
||||
pub const TLS_1_2: u16 = 0x0303;
|
||||
|
||||
pub const TLS_13_ONLY: Self = Self(&[Self::TLS_1_3]);
|
||||
pub const MODERN: Self = Self(&[Self::TLS_1_3, Self::TLS_1_2]);
|
||||
}
|
||||
|
||||
pub struct TlsExtensions;
|
||||
|
||||
impl TlsExtensions {
|
||||
pub const SNI: u16 = 0x0000;
|
||||
pub const STATUS_REQUEST: u16 = 0x0005;
|
||||
pub const SUPPORTED_GROUPS: u16 = 0x000a;
|
||||
pub const EC_POINT_FORMATS: u16 = 0x000b;
|
||||
pub const SIGNATURE_ALGORITHMS: u16 = 0x000d;
|
||||
pub const ALPN: u16 = 0x0010;
|
||||
pub const SCT: u16 = 0x0012;
|
||||
pub const PADDING: u16 = 0x0015;
|
||||
pub const EMS: u16 = 0x0017;
|
||||
pub const COMPRESS_CERT: u16 = 0x001b;
|
||||
pub const DELEGATED_CREDENTIAL: u16 = 0x0022;
|
||||
pub const SESSION_TICKET: u16 = 0x0023;
|
||||
pub const SUPPORTED_VERSIONS: u16 = 0x002b;
|
||||
pub const PSK_MODES: u16 = 0x002d;
|
||||
pub const KEY_SHARE: u16 = 0x0033;
|
||||
pub const ALPS: u16 = 0x44cd;
|
||||
pub const RENEGOTIATION_INFO: u16 = 0xff01;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ExtensionOrder(pub &'static [u16]);
|
||||
|
||||
impl<'a> IntoIterator for &'a ExtensionOrder {
|
||||
type Item = &'a u16;
|
||||
type IntoIter = std::slice::Iter<'a, u16>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtensionOrder {
|
||||
pub const CHROMIUM_131: Self = Self(&[
|
||||
0xaaaa, // GREASE
|
||||
TlsExtensions::SNI,
|
||||
TlsExtensions::EMS,
|
||||
TlsExtensions::SESSION_TICKET,
|
||||
TlsExtensions::SUPPORTED_GROUPS,
|
||||
TlsExtensions::EC_POINT_FORMATS,
|
||||
TlsExtensions::SIGNATURE_ALGORITHMS,
|
||||
TlsExtensions::ALPN,
|
||||
TlsExtensions::ALPS,
|
||||
TlsExtensions::STATUS_REQUEST,
|
||||
TlsExtensions::KEY_SHARE,
|
||||
TlsExtensions::SUPPORTED_VERSIONS,
|
||||
TlsExtensions::PSK_MODES,
|
||||
TlsExtensions::COMPRESS_CERT,
|
||||
TlsExtensions::SCT,
|
||||
TlsExtensions::DELEGATED_CREDENTIAL,
|
||||
]);
|
||||
|
||||
pub const EDGE_130: Self = Self(&[
|
||||
0x1a1a, // GREASE
|
||||
TlsExtensions::SNI,
|
||||
TlsExtensions::EMS,
|
||||
TlsExtensions::SESSION_TICKET,
|
||||
TlsExtensions::SUPPORTED_GROUPS,
|
||||
TlsExtensions::EC_POINT_FORMATS,
|
||||
TlsExtensions::SIGNATURE_ALGORITHMS,
|
||||
TlsExtensions::ALPN,
|
||||
TlsExtensions::ALPS,
|
||||
TlsExtensions::STATUS_REQUEST,
|
||||
TlsExtensions::KEY_SHARE,
|
||||
TlsExtensions::SUPPORTED_VERSIONS,
|
||||
TlsExtensions::PSK_MODES,
|
||||
TlsExtensions::COMPRESS_CERT,
|
||||
TlsExtensions::SCT,
|
||||
TlsExtensions::DELEGATED_CREDENTIAL,
|
||||
TlsExtensions::PADDING,
|
||||
0x3a3a, // GREASE
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user