use hmac::{Hmac, Mac};
use rand::RngCore;
use sha2::Sha256;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
pub const TLS_RECORD_HANDSHAKE: u8 = 0x16;
pub const TLS_RECORD_CHANGE_CIPHER_SPEC: u8 = 0x14;
pub const TLS_RECORD_APPLICATION_DATA: u8 = 0x17;
pub const TLS_RECORD_ALERT: u8 = 0x15;
pub const TLS_VERSION_12: [u8; 2] = [0x03, 0x03];
const TLS_RECORD_VERSION: [u8; 2] = [0x03, 0x01];
pub const TLS_MAX_RECORD_PAYLOAD: usize = 16_384;
const TLS_MAX_APPDATA_WRITE: usize = TLS_MAX_RECORD_PAYLOAD + 24;
const TLS_MAX_HANDSHAKE_RECORDS: usize = 20;
const TLS_DIGEST_POS: usize = 11;
const TLS_DIGEST_LEN: usize = 32;
type HmacSha256 = Hmac<Sha256>;
pub fn build_faketls_client_hello(hostname: &str) -> Vec<u8> {
let mut exts: Vec<u8> = Vec::new();
let host_b = hostname.as_bytes();
let host_len = host_b.len() as u16;
let sni_entry_len = 1u16 + 2 + host_len; let sni_list_len = sni_entry_len;
let sni_data_len = 2u16 + sni_list_len; exts.extend_from_slice(&0x0000u16.to_be_bytes()); exts.extend_from_slice(&sni_data_len.to_be_bytes());
exts.extend_from_slice(&sni_list_len.to_be_bytes());
exts.push(0x00); exts.extend_from_slice(&host_len.to_be_bytes());
exts.extend_from_slice(host_b);
exts.extend_from_slice(&[0x00, 0x17, 0x00, 0x00]);
exts.extend_from_slice(&[0xff, 0x01, 0x00, 0x01, 0x00]);
#[rustfmt::skip]
exts.extend_from_slice(&[
0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08,
0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19,
]);
exts.extend_from_slice(&[0x00, 0x0b, 0x00, 0x02, 0x01, 0x00]);
exts.extend_from_slice(&[0x00, 0x23, 0x00, 0x00]);
#[rustfmt::skip]
exts.extend_from_slice(&[
0x00, 0x0d, 0x00, 0x14, 0x00, 0x12,
0x04, 0x03, 0x08, 0x04, 0x04, 0x01,
0x05, 0x03, 0x08, 0x05, 0x05, 0x01,
0x08, 0x06, 0x06, 0x01, 0x02, 0x01,
]);
#[rustfmt::skip]
let cipher_suites: &[u8] = &[
0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, ];
let mut session_id = [0u8; 32];
rand::rng().fill_bytes(&mut session_id);
let mut hello: Vec<u8> = Vec::new();
hello.extend_from_slice(&TLS_VERSION_12); hello.extend_from_slice(&[0u8; 32]); hello.push(0x20); hello.extend_from_slice(&session_id);
hello.extend_from_slice(&(cipher_suites.len() as u16).to_be_bytes());
hello.extend_from_slice(cipher_suites);
hello.push(0x01); hello.push(0x00); hello.extend_from_slice(&(exts.len() as u16).to_be_bytes());
hello.extend_from_slice(&exts);
let hello_len = hello.len() as u32;
let mut handshake: Vec<u8> = Vec::with_capacity(4 + hello.len());
handshake.push(0x01); handshake.push((hello_len >> 16) as u8);
handshake.push((hello_len >> 8) as u8);
handshake.push(hello_len as u8);
handshake.extend_from_slice(&hello);
let mut record: Vec<u8> = Vec::with_capacity(5 + handshake.len());
record.push(TLS_RECORD_HANDSHAKE);
record.extend_from_slice(&TLS_RECORD_VERSION);
record.extend_from_slice(&(handshake.len() as u16).to_be_bytes());
record.extend_from_slice(&handshake);
record
}
pub fn sign_faketls_client_hello(record: &mut [u8], secret: &[u8]) {
record[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);
let mut mac =
HmacSha256::new_from_slice(secret).expect("HMAC-SHA256 accepts any key length");
mac.update(record);
let computed = mac.finalize().into_bytes();
record[TLS_DIGEST_POS..TLS_DIGEST_POS + 28].copy_from_slice(&computed[..28]);
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let ts_bytes = timestamp.to_le_bytes();
for i in 0..4 {
record[TLS_DIGEST_POS + 28 + i] = computed[28 + i] ^ ts_bytes[i];
}
}
pub async fn drain_faketls_server_hello(
reader: &mut tokio::io::ReadHalf<TcpStream>,
) -> bool {
let mut header = [0u8; 5];
for _ in 0..TLS_MAX_HANDSHAKE_RECORDS {
if reader.read_exact(&mut header).await.is_err() {
return false;
}
let record_type = header[0];
let version = [header[1], header[2]];
let payload_len = u16::from_be_bytes([header[3], header[4]]) as usize;
if version != TLS_VERSION_12 {
return false;
}
if payload_len > TLS_MAX_RECORD_PAYLOAD + 256 {
return false;
}
let mut payload = vec![0u8; payload_len];
if reader.read_exact(&mut payload).await.is_err() {
return false;
}
match record_type {
TLS_RECORD_HANDSHAKE | TLS_RECORD_CHANGE_CIPHER_SPEC => {
}
TLS_RECORD_APPLICATION_DATA => {
return true;
}
TLS_RECORD_ALERT => return false,
_ => return false,
}
}
false }
pub async fn write_tls_appdata(
writer: &mut tokio::io::WriteHalf<TcpStream>,
data: &[u8],
) -> std::io::Result<()> {
let mut offset = 0;
while offset < data.len() {
let end = std::cmp::min(offset + TLS_MAX_APPDATA_WRITE, data.len());
let chunk = &data[offset..end];
let len = chunk.len();
let hdr = [
TLS_RECORD_APPLICATION_DATA,
TLS_VERSION_12[0],
TLS_VERSION_12[1],
(len >> 8) as u8,
len as u8,
];
writer.write_all(&hdr).await?;
writer.write_all(chunk).await?;
offset = end;
}
Ok(())
}
pub async fn read_tls_appdata(
reader: &mut tokio::io::ReadHalf<TcpStream>,
buf: &mut [u8],
) -> std::io::Result<usize> {
let mut header = [0u8; 5];
loop {
if reader.read_exact(&mut header).await.is_err() {
return Ok(0);
}
let record_type = header[0];
let payload_len = u16::from_be_bytes([header[3], header[4]]) as usize;
if payload_len > buf.len() {
return Ok(0);
}
match record_type {
TLS_RECORD_APPLICATION_DATA => {
if reader.read_exact(&mut buf[..payload_len]).await.is_err() {
return Ok(0);
}
return Ok(payload_len);
}
TLS_RECORD_CHANGE_CIPHER_SPEC => {
let mut discard = vec![0u8; payload_len];
if reader.read_exact(&mut discard).await.is_err() {
return Ok(0);
}
continue;
}
_ => {
return Ok(0);
}
}
}
}