use hmac::{Hmac, Mac};
use rand::RngCore;
use sha2::Sha256;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, 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;
const TLS_MAX_HANDSHAKE_RECORDS: usize = 20;
const TLS_DIGEST_POS: usize = 11;
pub const TLS_DIGEST_LEN: usize = 32;
const TLS_HANDSHAKE_CLIENT_HELLO: u8 = 0x01;
const TLS_HANDSHAKE_SERVER_HELLO: u8 = 0x02;
const TLS_CLIENT_RANDOM_OFFSET_IN_HANDSHAKE: usize = 6;
const TLS_SERVER_RANDOM_OFFSET_IN_PACKET: usize = 11;
const TLS_CHANGE_CIPHER_SPEC_VALUE: u8 = 0x01;
type HmacSha256 = Hmac<Sha256>;
#[derive(Clone, Debug)]
pub struct FakeTlsClientHello {
pub random: [u8; TLS_DIGEST_LEN],
pub session_id: Vec<u8>,
pub cipher_suite: [u8; 2],
pub hostname: Option<String>,
}
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 fn parse_faketls_client_hello(record: &[u8], secret: &[u8]) -> Option<FakeTlsClientHello> {
if record.len() < 5 + 4 + TLS_CLIENT_RANDOM_OFFSET_IN_HANDSHAKE + TLS_DIGEST_LEN {
return None;
}
if record[0] != TLS_RECORD_HANDSHAKE {
return None;
}
let record_len = u16::from_be_bytes([record[3], record[4]]) as usize;
if record.len() != 5 + record_len {
return None;
}
let handshake = &record[5..];
if handshake.len() < 4 || handshake[0] != TLS_HANDSHAKE_CLIENT_HELLO {
return None;
}
let handshake_len =
((handshake[1] as usize) << 16) | ((handshake[2] as usize) << 8) | handshake[3] as usize;
if handshake.len() != 4 + handshake_len {
return None;
}
let random_offset = 5 + TLS_CLIENT_RANDOM_OFFSET_IN_HANDSHAKE;
let mut client_random = [0u8; TLS_DIGEST_LEN];
client_random.copy_from_slice(&record[random_offset..random_offset + TLS_DIGEST_LEN]);
let mut signed = record.to_vec();
signed[random_offset..random_offset + TLS_DIGEST_LEN].fill(0);
let mut mac = HmacSha256::new_from_slice(secret).ok()?;
mac.update(&signed);
let computed = mac.finalize().into_bytes();
let mut xored = [0u8; TLS_DIGEST_LEN];
for i in 0..TLS_DIGEST_LEN {
xored[i] = computed[i] ^ client_random[i];
}
if xored[..28].iter().any(|&b| b != 0) {
return None;
}
let body = &handshake[4..];
if body.len() < 2 + 32 + 1 {
return None;
}
let session_id_len = body[34] as usize;
let session_start = 35;
let session_end = session_start + session_id_len;
if body.len() < session_end + 2 {
return None;
}
let session_id = body[session_start..session_end].to_vec();
let cipher_suites_len = u16::from_be_bytes([body[session_end], body[session_end + 1]]) as usize;
if cipher_suites_len < 2 || body.len() < session_end + 2 + cipher_suites_len + 1 {
return None;
}
let cipher_suite = [body[session_end + 2], body[session_end + 3]];
let mut p = session_end + 2 + cipher_suites_len;
let compression_len = *body.get(p)? as usize;
p += 1 + compression_len;
if body.len() < p + 2 {
return None;
}
let extensions_len = u16::from_be_bytes([body[p], body[p + 1]]) as usize;
p += 2;
if body.len() < p + extensions_len {
return None;
}
let hostname = parse_sni_extension(&body[p..p + extensions_len]);
Some(FakeTlsClientHello {
random: client_random,
session_id,
cipher_suite,
hostname,
})
}
fn parse_sni_extension(mut extensions: &[u8]) -> Option<String> {
while extensions.len() >= 4 {
let ext_type = u16::from_be_bytes([extensions[0], extensions[1]]);
let ext_len = u16::from_be_bytes([extensions[2], extensions[3]]) as usize;
extensions = &extensions[4..];
if extensions.len() < ext_len {
return None;
}
let ext = &extensions[..ext_len];
extensions = &extensions[ext_len..];
if ext_type != 0x0000 || ext.len() < 5 {
continue;
}
let list_len = u16::from_be_bytes([ext[0], ext[1]]) as usize;
if ext.len() < 2 + list_len || list_len < 3 || ext[2] != 0 {
return None;
}
let host_len = u16::from_be_bytes([ext[3], ext[4]]) as usize;
if ext.len() < 5 + host_len {
return None;
}
return std::str::from_utf8(&ext[5..5 + host_len])
.ok()
.map(ToOwned::to_owned);
}
None
}
pub fn build_faketls_server_hello(secret: &[u8], hello: &FakeTlsClientHello) -> Vec<u8> {
let mut server_body = Vec::new();
server_body.extend_from_slice(&TLS_VERSION_12);
server_body.extend_from_slice(&[0u8; TLS_DIGEST_LEN]);
server_body.push(hello.session_id.len() as u8);
server_body.extend_from_slice(&hello.session_id);
server_body.extend_from_slice(&hello.cipher_suite);
server_body.extend_from_slice(&[
0x00, 0x00, 0x2e, 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d,
0x00, 0x20,
]);
let mut rng = rand::rng();
let mut key_share = [0u8; 32];
rng.fill_bytes(&mut key_share);
server_body.extend_from_slice(&key_share);
let mut handshake = Vec::with_capacity(4 + server_body.len());
handshake.push(TLS_HANDSHAKE_SERVER_HELLO);
handshake.push(((server_body.len() >> 16) & 0xff) as u8);
handshake.push(((server_body.len() >> 8) & 0xff) as u8);
handshake.push((server_body.len() & 0xff) as u8);
handshake.extend_from_slice(&server_body);
let mut packet = Vec::new();
push_tls_record(&mut packet, TLS_RECORD_HANDSHAKE, &handshake);
push_tls_record(
&mut packet,
TLS_RECORD_CHANGE_CIPHER_SPEC,
&[TLS_CHANGE_CIPHER_SPEC_VALUE],
);
let mut cert = vec![0u8; 1024 + (rng.next_u32() as usize % 3092)];
rng.fill_bytes(&mut cert);
push_tls_record(&mut packet, TLS_RECORD_APPLICATION_DATA, &cert);
let mut mac = HmacSha256::new_from_slice(secret).expect("HMAC-SHA256 accepts any key length");
mac.update(&hello.random);
mac.update(&packet);
let digest = mac.finalize().into_bytes();
packet[TLS_SERVER_RANDOM_OFFSET_IN_PACKET..TLS_SERVER_RANDOM_OFFSET_IN_PACKET + TLS_DIGEST_LEN]
.copy_from_slice(&digest);
packet
}
fn push_tls_record(out: &mut Vec<u8>, record_type: u8, payload: &[u8]) {
out.push(record_type);
out.extend_from_slice(&TLS_VERSION_12);
out.extend_from_slice(&(payload.len() as u16).to_be_bytes());
out.extend_from_slice(payload);
}
pub async fn read_tls_record<R: AsyncRead + Unpin>(
reader: &mut R,
max_payload_len: usize,
) -> std::io::Result<Option<(u8, [u8; 2], Vec<u8>)>> {
let mut header = [0u8; 5];
if reader.read_exact(&mut header).await.is_err() {
return Ok(None);
}
let payload_len = u16::from_be_bytes([header[3], header[4]]) as usize;
if payload_len > max_payload_len {
return Ok(None);
}
let mut payload = vec![0u8; payload_len];
reader.read_exact(&mut payload).await?;
Ok(Some((header[0], [header[1], header[2]], payload)))
}
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 (impl AsyncWrite + Unpin),
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 (impl AsyncRead + Unpin),
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);
}
}
}
}