Skip to main content

phantom_protocol/transport/legs/
faketls.rs

1//! FakeTLS Transport Leg
2//!
3//! Obfuscated transport that mimics TLS 1.3 traffic for DPI bypass.
4//! Wraps actual payload in fake TLS records.
5
6use crate::transport::legs::TransportLeg;
7
8use async_trait::async_trait;
9use bytes::{Bytes, BytesMut};
10use rand::rngs::OsRng;
11use ring::aead::{self, LessSafeKey, Nonce, UnboundKey};
12use std::io;
13use std::net::SocketAddr;
14use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
15use tokio::io::{AsyncReadExt, AsyncWriteExt};
16use tokio::net::TcpStream;
17use tokio::sync::Mutex;
18use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
19
20/// TLS record types
21#[repr(u8)]
22#[derive(Debug, Clone, Copy)]
23enum TlsContentType {
24    #[allow(dead_code)]
25    ChangeCipherSpec = 20,
26    #[allow(dead_code)]
27    Alert = 21,
28    Handshake = 22,
29    ApplicationData = 23,
30}
31
32/// FakeTLS configuration
33#[derive(Debug, Clone)]
34pub struct FakeTlsConfig {
35    /// Server Name Indication (SNI) to use in ClientHello
36    pub sni: String,
37    /// TLS version to advertise
38    pub version: u16,
39}
40
41impl Default for FakeTlsConfig {
42    fn default() -> Self {
43        Self {
44            sni: "www.google.com".to_string(),
45            version: 0x0303, // TLS 1.2 (for compatibility)
46        }
47    }
48}
49
50/// State machine for FakeTLS Handshake
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[repr(u8)]
53pub enum FakeTlsState {
54    /// Initial state (0)
55    Init = 0,
56    /// Client: Sent ClientHello, waiting for ServerHello (1)
57    /// Server: Received ClientHello, waiting to send ServerHello (1)
58    ClientHelloDone = 1,
59    /// Handshake completed, ready for Application Data (2)
60    ServerHelloDone = 2,
61    /// Ready for Phantom packets wrapped in ApplicationData (3)
62    ApplicationData = 3,
63}
64
65/// FakeTLS transport leg
66///
67/// AEAD note: This is an *outer* DPI-obfuscation layer. Real confidentiality
68/// and authentication come from the inner Phantom session (see
69/// `crate::transport::session::Session`). The outer AEAD key is derived
70/// deterministically from a public seed (SNI + version) so both peers reach
71/// the same key without a Diffie-Hellman exchange; nonce uniqueness per record
72/// is provided by an atomic counter (mirrors `AesSession::make_nonce`).
73pub struct FakeTlsLeg {
74    /// Configuration
75    config: FakeTlsConfig,
76    /// Underlying TCP stream
77    stream: Mutex<Option<TcpStream>>,
78    /// Remote address
79    remote_addr: Option<SocketAddr>,
80    /// Current RTT estimate (ms)
81    rtt_ms: AtomicU32,
82    /// Whether leg is available
83    available: AtomicBool,
84    /// Current connection state
85    state: parking_lot::RwLock<FakeTlsState>,
86    /// Read buffer
87    read_buf: Mutex<BytesMut>,
88    /// AES-256-GCM key used for sealing outbound records.
89    send_key: LessSafeKey,
90    /// AES-256-GCM key used for opening inbound records.
91    recv_key: LessSafeKey,
92    /// 4-byte nonce prefix; remaining 8 bytes are a per-direction counter.
93    nonce_prefix: [u8; 4],
94    /// Monotonically increasing counter for outbound records.
95    send_counter: AtomicU64,
96    /// Monotonically increasing counter for inbound records (TCP is in-order).
97    recv_counter: AtomicU64,
98    /// Is this leg acting as a server?
99    #[allow(dead_code)]
100    is_server: bool,
101}
102
103/// Derive the outer AEAD key material for both peers from a publicly-known seed.
104///
105/// Because the seed is public this layer provides no real confidentiality — it
106/// exists solely to produce pseudo-random ciphertext that defeats DPI
107/// fingerprinting. The inner Phantom session provides actual auth/conf.
108fn derive_outer_keys(
109    sni: &str,
110    version: u16,
111    is_server: bool,
112) -> io::Result<(LessSafeKey, LessSafeKey, [u8; 4])> {
113    let mut seed = Vec::with_capacity(64);
114    seed.extend_from_slice(b"phantom-faketls-outer-v1");
115    seed.extend_from_slice(&version.to_be_bytes());
116    seed.extend_from_slice(sni.as_bytes());
117
118    // `crypto::kdf::derive_key_32` dispatches to `blake3::derive_key`
119    // by default and HKDF-SHA256 under `--features fips`. FakeTLS is
120    // anti-DPI obfuscation only (the inner Phantom session provides the
121    // real confidentiality + auth), but consistency with the rest of
122    // the codebase under fips removes a non-approved primitive from the
123    // FIPS surface.
124    let key_c2s = crate::crypto::kdf::derive_key_32("phantom-faketls-c2s-v1", &seed);
125    let key_s2c = crate::crypto::kdf::derive_key_32("phantom-faketls-s2c-v1", &seed);
126    let pfx = crate::crypto::kdf::derive_key_32("phantom-faketls-pfx-v1", &seed);
127
128    let mut nonce_prefix = [0u8; 4];
129    nonce_prefix.copy_from_slice(&pfx[..4]);
130
131    let (send_bytes, recv_bytes) = if is_server {
132        (key_s2c, key_c2s)
133    } else {
134        (key_c2s, key_s2c)
135    };
136    // `UnboundKey::new(&AES_256_GCM, key)` only fails if the slice length
137    // is wrong — `derive_key_32` always emits exactly 32 bytes and
138    // `AES_256_GCM.key_len()` is 32, so this is structurally infallible.
139    // Surface it as a typed error anyway so the function stays total.
140    let send_unbound = UnboundKey::new(&aead::AES_256_GCM, &send_bytes)
141        .map_err(|e| io::Error::other(format!("AES key init (send): {}", e)))?;
142    let recv_unbound = UnboundKey::new(&aead::AES_256_GCM, &recv_bytes)
143        .map_err(|e| io::Error::other(format!("AES key init (recv): {}", e)))?;
144    Ok((
145        LessSafeKey::new(send_unbound),
146        LessSafeKey::new(recv_unbound),
147        nonce_prefix,
148    ))
149}
150
151impl FakeTlsLeg {
152    /// Create a new FakeTLS leg with default config. Returns an error only
153    /// in the structurally-impossible case that AES-256-GCM key initialization
154    /// fails (preserved as a `Result` to keep the API panic-free).
155    pub fn new() -> io::Result<Self> {
156        Self::with_config(FakeTlsConfig::default())
157    }
158
159    /// Create with custom config (client role; not yet connected). See
160    /// [`FakeTlsLeg::new`] for the error contract.
161    pub fn with_config(config: FakeTlsConfig) -> io::Result<Self> {
162        let (send_key, recv_key, nonce_prefix) =
163            derive_outer_keys(&config.sni, config.version, false)?;
164
165        Ok(Self {
166            config,
167            stream: Mutex::new(None),
168            remote_addr: None,
169            rtt_ms: AtomicU32::new(150),
170            available: AtomicBool::new(false),
171            state: parking_lot::RwLock::new(FakeTlsState::Init),
172            read_buf: Mutex::new(BytesMut::with_capacity(16384)),
173            send_key,
174            recv_key,
175            nonce_prefix,
176            send_counter: AtomicU64::new(0),
177            recv_counter: AtomicU64::new(0),
178            is_server: false,
179        })
180    }
181
182    /// Connect as Client and perform fake TLS handshake
183    pub async fn connect(addr: SocketAddr, config: FakeTlsConfig) -> io::Result<Self> {
184        let start = std::time::Instant::now();
185        let stream = TcpStream::connect(addr).await?;
186        let rtt = start.elapsed().as_millis() as u32;
187
188        stream.set_nodelay(true)?;
189
190        let (send_key, recv_key, nonce_prefix) =
191            derive_outer_keys(&config.sni, config.version, false)?;
192
193        let leg = Self {
194            config,
195            stream: Mutex::new(Some(stream)),
196            remote_addr: Some(addr),
197            rtt_ms: AtomicU32::new(rtt),
198            available: AtomicBool::new(true),
199            state: parking_lot::RwLock::new(FakeTlsState::Init),
200            read_buf: Mutex::new(BytesMut::with_capacity(16384)),
201            send_key,
202            recv_key,
203            nonce_prefix,
204            send_counter: AtomicU64::new(0),
205            recv_counter: AtomicU64::new(0),
206            is_server: false,
207        };
208
209        leg.do_client_handshake().await?;
210
211        Ok(leg)
212    }
213
214    /// Accept connection as Server and perform fake TLS handshake
215    pub async fn accept(stream: TcpStream, config: FakeTlsConfig) -> io::Result<Self> {
216        stream.set_nodelay(true)?;
217        let remote_addr = stream.peer_addr().ok();
218
219        let (send_key, recv_key, nonce_prefix) =
220            derive_outer_keys(&config.sni, config.version, true)?;
221
222        let leg = Self {
223            config,
224            stream: Mutex::new(Some(stream)),
225            remote_addr,
226            rtt_ms: AtomicU32::new(150),
227            available: AtomicBool::new(true),
228            state: parking_lot::RwLock::new(FakeTlsState::Init),
229            read_buf: Mutex::new(BytesMut::with_capacity(16384)),
230            send_key,
231            recv_key,
232            nonce_prefix,
233            send_counter: AtomicU64::new(0),
234            recv_counter: AtomicU64::new(0),
235            is_server: true,
236        };
237
238        leg.do_server_handshake().await?;
239
240        Ok(leg)
241    }
242
243    /// Construct nonce: 4-byte prefix || 8-byte big-endian counter.
244    #[inline]
245    fn make_nonce(&self, counter: u64) -> Nonce {
246        let mut n = [0u8; 12];
247        n[..4].copy_from_slice(&self.nonce_prefix);
248        n[4..12].copy_from_slice(&counter.to_be_bytes());
249        Nonce::assume_unique_for_key(n)
250    }
251
252    /// Perform client-side fake handshake
253    async fn do_client_handshake(&self) -> io::Result<()> {
254        let mut stream_guard = self.stream.lock().await;
255        let stream = stream_guard
256            .as_mut()
257            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
258
259        // State 0 -> 1: Send ClientHello
260        let client_hello = self.build_fake_client_hello();
261        stream.write_all(&client_hello).await?;
262        *self.state.write() = FakeTlsState::ClientHelloDone;
263
264        // State 1 -> 2: Read ServerHello
265        let mut buf = [0u8; 4096];
266        let n = tokio::time::timeout(std::time::Duration::from_secs(5), stream.read(&mut buf))
267            .await
268            .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "ServerHello timeout"))??;
269
270        if n == 0 {
271            return Err(io::Error::new(
272                io::ErrorKind::ConnectionAborted,
273                "Connection closed by server",
274            ));
275        }
276
277        // We received dummy ServerHello
278        *self.state.write() = FakeTlsState::ServerHelloDone;
279
280        // State 2 -> 3: Application Data (Ready)
281        *self.state.write() = FakeTlsState::ApplicationData;
282        Ok(())
283    }
284
285    /// Perform server-side fake handshake
286    async fn do_server_handshake(&self) -> io::Result<()> {
287        let mut stream_guard = self.stream.lock().await;
288        let stream = stream_guard
289            .as_mut()
290            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
291
292        // State 0 -> 1: Read ClientHello
293        let mut buf = [0u8; 4096];
294        let n = tokio::time::timeout(std::time::Duration::from_secs(5), stream.read(&mut buf))
295            .await
296            .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "ClientHello timeout"))??;
297
298        if n == 0 {
299            return Err(io::Error::new(
300                io::ErrorKind::ConnectionAborted,
301                "Connection closed by client",
302            ));
303        }
304
305        *self.state.write() = FakeTlsState::ClientHelloDone;
306
307        // State 1 -> 2: Send ServerHello
308        let server_hello = self.build_fake_server_hello();
309        stream.write_all(&server_hello).await?;
310        *self.state.write() = FakeTlsState::ServerHelloDone;
311
312        // State 2 -> 3: Application Data (Ready)
313        *self.state.write() = FakeTlsState::ApplicationData;
314        Ok(())
315    }
316
317    /// Build a fake TLS ServerHello
318    fn build_fake_server_hello(&self) -> Vec<u8> {
319        let mut record = Vec::with_capacity(128);
320        record.push(TlsContentType::Handshake as u8);
321        record.extend_from_slice(&self.config.version.to_be_bytes()); // e.g. TLS 1.2
322
323        let mut hs = Vec::new();
324        hs.push(0x02); // ServerHello
325                       // length placeholder
326        hs.extend_from_slice(&[0, 0, 0]);
327        // Server version
328        hs.extend_from_slice(&0x0303u16.to_be_bytes());
329
330        // Server Random
331        let mut random = [0u8; 32];
332        if getrandom::getrandom(&mut random).is_err() {
333            use rand::RngCore;
334            rand::thread_rng().fill_bytes(&mut random);
335        }
336        hs.extend_from_slice(&random);
337
338        // Session ID length (0)
339        hs.push(0);
340
341        // Cipher suite TLS_AES_256_GCM_SHA384
342        hs.extend_from_slice(&0x1302u16.to_be_bytes());
343        // Compression method null
344        hs.push(0);
345
346        // Extensions
347        hs.extend_from_slice(&[0, 8]); // Extensions len: 8
348                                       // Key share (Server)
349        hs.extend_from_slice(&51u16.to_be_bytes());
350        hs.extend_from_slice(&4u16.to_be_bytes()); // len 4
351        hs.extend_from_slice(&0x001du16.to_be_bytes()); // X25519
352        hs.extend_from_slice(&0u16.to_be_bytes()); // dummy payload
353
354        let hs_len = (hs.len() - 4) as u32;
355        hs[1] = ((hs_len >> 16) & 0xFF) as u8;
356        hs[2] = ((hs_len >> 8) & 0xFF) as u8;
357        hs[3] = (hs_len & 0xFF) as u8;
358
359        record.extend_from_slice(&(hs.len() as u16).to_be_bytes());
360        record.extend_from_slice(&hs);
361
362        record
363    }
364
365    /// Build a fake TLS ClientHello that looks legitimate and randomized (JA3)
366    fn build_fake_client_hello(&self) -> Vec<u8> {
367        let mut record = Vec::with_capacity(512);
368        let mut rng = OsRng;
369
370        // TLS record header
371        record.push(TlsContentType::Handshake as u8);
372        record.extend_from_slice(&self.config.version.to_be_bytes());
373
374        // Placeholder for length (will fill in later)
375        let length_pos = record.len();
376        record.extend_from_slice(&[0u8; 2]);
377
378        // Handshake header
379        record.push(0x01); // ClientHello
380                           // Placeholder for handshake length
381        let hs_length_pos = record.len();
382        record.extend_from_slice(&[0u8; 3]);
383
384        // Client version (Legacy TLS 1.2)
385        record.extend_from_slice(&0x0303u16.to_be_bytes());
386
387        // Random (32 bytes)
388        let mut random = [0u8; 32];
389        if getrandom::getrandom(&mut random).is_err() {
390            rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut random);
391        }
392        record.extend_from_slice(&random);
393
394        // Session ID (32 bytes)
395        record.push(32);
396        let mut session_id = [0u8; 32];
397        if getrandom::getrandom(&mut session_id).is_err() {
398            rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut session_id);
399        }
400        record.extend_from_slice(&session_id);
401
402        // Cipher suites (Randomized order and selection)
403        // Grease ciphers: 0x0a0a, 0x1a1a, etc.
404        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
405
406        let mut suites = vec![
407            0x1301, // TLS_AES_128_GCM_SHA256
408            0x1302, // TLS_AES_256_GCM_SHA384
409            0x1303, // TLS_CHACHA20_POLY1305_SHA256
410            0xc02b, // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
411            0xc02c, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
412            0xc02f, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
413            0xc030, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
414            0xcca9, // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
415            0xcca8, // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
416        ];
417
418        // Shuffle and take random subset (at least 3)
419        use rand::seq::SliceRandom;
420        use rand::Rng; // Add this import if not present, but better to use fully qualified or assume scope
421
422        suites.shuffle(&mut rng);
423        let num_suites = rng.gen_range(3..=suites.len());
424        suites.truncate(num_suites);
425
426        // Insert GREASE at random position
427        suites.insert(rng.gen_range(0..=suites.len()), grease);
428
429        record.extend_from_slice(&((suites.len() * 2) as u16).to_be_bytes());
430        for s in suites {
431            record.extend_from_slice(&s.to_be_bytes());
432        }
433
434        // Compression methods (Unused in TLS 1.3 but required for legacy)
435        record.push(1);
436        record.push(0); // null compression
437
438        // Extensions
439        let extensions_start = record.len();
440        record.extend_from_slice(&[0u8; 2]); // Extensions length placeholder
441
442        // Generate extensions. The `make_*` helpers that take `&mut rng` must
443        // be called in sequence, so we build the vec eagerly then push the
444        // rng-dependent entries afterwards.
445        let supported_groups = self.make_supported_groups_body(&mut rng);
446        let key_share = self.make_key_share_body(&mut rng);
447        let supported_versions = self.make_supported_versions_body(&mut rng);
448        let mut exts: Vec<(u16, Vec<u8>)> = vec![
449            // SNI (0)
450            (0u16, self.make_sni_extension_body()),
451            // Supported Groups (10) - X25519, P-256, P-384 + GREASE
452            (10u16, supported_groups),
453            // EC Point Formats (11)
454            (11u16, vec![1, 0]),
455            // Signature Algorithms (13)
456            (13u16, self.make_signature_algorithms_body()),
457            // Key Share (51)
458            (51u16, key_share),
459            // Supported Versions (43) - TLS 1.3, TLS 1.2 + GREASE
460            (43u16, supported_versions),
461        ];
462
463        // Shuffle extensions (except SNI which usually comes first)
464        if !exts.is_empty() {
465            let sni = exts.remove(0); // Assuming we pushed SNI first
466            exts.shuffle(&mut rng);
467            exts.insert(0, sni);
468        }
469
470        // Padding (21) - MUST be at the end for JA3 fingerprinting evasion
471        // Add random size padding to randomize the ClientHello length
472        exts.push((21u16, vec![0u8; rng.gen_range(50..200)]));
473
474        for (etype, body) in exts {
475            record.extend_from_slice(&etype.to_be_bytes());
476            record.extend_from_slice(&(body.len() as u16).to_be_bytes());
477            record.extend_from_slice(&body);
478        }
479
480        // Fill in extensions length
481        let extensions_len = (record.len() - extensions_start - 2) as u16;
482        record[extensions_start..extensions_start + 2]
483            .copy_from_slice(&extensions_len.to_be_bytes());
484
485        // Fill in handshake length
486        let hs_len = (record.len() - hs_length_pos - 3) as u32;
487        record[hs_length_pos] = ((hs_len >> 16) & 0xFF) as u8;
488        record[hs_length_pos + 1] = ((hs_len >> 8) & 0xFF) as u8;
489        record[hs_length_pos + 2] = (hs_len & 0xFF) as u8;
490
491        // Fill in record length
492        let record_len = (record.len() - 5) as u16;
493        record[length_pos..length_pos + 2].copy_from_slice(&record_len.to_be_bytes());
494
495        record
496    }
497
498    fn make_sni_extension_body(&self) -> Vec<u8> {
499        let mut body = Vec::new();
500        let sni_bytes = self.config.sni.as_bytes();
501        body.extend_from_slice(&((3 + sni_bytes.len()) as u16).to_be_bytes()); // Server name list length
502        body.push(0); // Name type: host_name
503        body.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
504        body.extend_from_slice(sni_bytes);
505        body
506    }
507
508    fn make_supported_groups_body(&self, rng: &mut impl rand::Rng) -> Vec<u8> {
509        let mut body = Vec::new();
510        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
511        let groups = [
512            grease, 0x001d, // X25519
513            0x0017, // secp256r1
514        ];
515        let len = (groups.len() * 2) as u16;
516        body.extend_from_slice(&len.to_be_bytes());
517        for g in groups {
518            body.extend_from_slice(&g.to_be_bytes());
519        }
520        body
521    }
522
523    fn make_signature_algorithms_body(&self) -> Vec<u8> {
524        let mut body = Vec::new();
525        let algos: [u16; 8] = [
526            0x0403, // ecdsa_secp256r1_sha256
527            0x0804, // rsa_pss_rsae_sha256
528            0x0401, // rsa_pkcs1_sha256
529            0x0503, // ecdsa_secp384r1_sha384
530            0x0805, // rsa_pss_rsae_sha384
531            0x0501, // rsa_pkcs1_sha384
532            0x0806, // rsa_pss_rsae_sha512
533            0x0601, // rsa_pkcs1_sha512
534        ];
535        let len = (algos.len() * 2) as u16;
536        body.extend_from_slice(&len.to_be_bytes());
537        for a in algos {
538            body.extend_from_slice(&a.to_be_bytes());
539        }
540        body
541    }
542
543    fn make_key_share_body(&self, rng: &mut (impl rand::Rng + rand::CryptoRng)) -> Vec<u8> {
544        let mut body = Vec::new();
545        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
546
547        // Real X25519 key share
548        let secret = StaticSecret::random_from_rng(rng);
549        let public = X25519PublicKey::from(&secret);
550        let x25519_share = public.as_bytes();
551
552        let client_shares_len = 4 + (2 + 32); // Grease (4) + X25519 (2+32)
553        body.extend_from_slice(&(client_shares_len as u16).to_be_bytes());
554
555        // GREASE share
556        body.extend_from_slice(&grease.to_be_bytes());
557        body.extend_from_slice(&1u16.to_be_bytes()); // Len 1
558        body.push(0);
559
560        // X25519 share
561        body.extend_from_slice(&0x001du16.to_be_bytes());
562        body.extend_from_slice(&(x25519_share.len() as u16).to_be_bytes());
563        body.extend_from_slice(x25519_share);
564
565        body
566    }
567
568    fn make_supported_versions_body(&self, rng: &mut impl rand::Rng) -> Vec<u8> {
569        let mut body = Vec::new();
570        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
571        let versions = [
572            grease, 0x0304, // TLS 1.3
573            0x0303, // TLS 1.2
574        ];
575        let len = (versions.len() * 2) as u16;
576        body.push((len & 0xFF) as u8); // Supported versions length is 1 byte in this extension body
577        for v in versions {
578            body.extend_from_slice(&v.to_be_bytes());
579        }
580        body
581    }
582
583    /// Wrap data as TLS Application Data record using ring AEAD.
584    ///
585    /// Each call increments `send_counter` so the (key, nonce) pair is unique
586    /// per record — the previous implementation reused `[0u8; 12]` and was
587    /// vulnerable to the Forbidden Attack on AES-GCM.
588    fn wrap_as_tls_record(&self, data: &[u8]) -> io::Result<Vec<u8>> {
589        // faketls-2: the outer TLS record carries its body length in a u16
590        // field. The sealed body is `data + 1` (inner content-type byte) plus
591        // the AEAD tag. Reject anything whose sealed length would exceed
592        // `u16::MAX` BEFORE sealing, so an oversized payload can never silently
593        // truncate the length field into a corrupt record (and we skip a wasted
594        // seal). Invariant 3 is untouched — the per-record `send_counter` nonce
595        // and direction-keyed `send_key` below are exactly as before.
596        let tag_len = self.send_key.algorithm().tag_len();
597        let framed_len = data.len().saturating_add(1).saturating_add(tag_len);
598        if framed_len > u16::MAX as usize {
599            return Err(io::Error::new(
600                io::ErrorKind::InvalidData,
601                format!(
602                    "faketls record body would be {framed_len} bytes, exceeding the {}-byte u16 length field",
603                    u16::MAX
604                ),
605            ));
606        }
607
608        // TLS 1.3 framing: inner plaintext + TLS1.3 AppData type (0x17)
609        let mut inner_plaintext = Vec::with_capacity(data.len() + 1);
610        inner_plaintext.extend_from_slice(data);
611        inner_plaintext.push(TlsContentType::ApplicationData as u8); // inner content type
612
613        // Encrypt with ring using a counter-based nonce.
614        let mut in_out = inner_plaintext;
615        let counter = self.send_counter.fetch_add(1, Ordering::Relaxed);
616        let nonce = self.make_nonce(counter);
617
618        let aad = aead::Aad::empty();
619        // A seal failure here means ring rejected the input (e.g. the AEAD
620        // invocation ceiling); surface it rather than panicking.
621        self.send_key
622            .seal_in_place_append_tag(nonce, aad, &mut in_out)
623            .map_err(|_| io::Error::other("faketls AEAD seal failed"))?;
624
625        let mut record = Vec::with_capacity(5 + in_out.len());
626
627        // Outer TLS record header (Legacy Application Data). `in_out.len()` is
628        // ≤ u16::MAX by the guard above, so the cast cannot truncate.
629        record.push(TlsContentType::ApplicationData as u8);
630        record.extend_from_slice(&self.config.version.to_be_bytes());
631        record.extend_from_slice(&(in_out.len() as u16).to_be_bytes());
632        record.extend_from_slice(&in_out);
633
634        Ok(record)
635    }
636
637    /// Unwrap TLS Application Data record.
638    ///
639    /// Each call increments `recv_counter` in lockstep with the peer's
640    /// `send_counter` (TCP guarantees in-order delivery). If a record is
641    /// corrupted or the counters drift, decryption fails and the leg should
642    /// be closed.
643    fn unwrap_tls_record<'a>(&self, record: &'a mut [u8]) -> io::Result<&'a [u8]> {
644        if record.len() < 5 {
645            return Err(io::Error::new(
646                io::ErrorKind::InvalidData,
647                "Record too short",
648            ));
649        }
650
651        let payload = &mut record[5..];
652
653        let counter = self.recv_counter.fetch_add(1, Ordering::Relaxed);
654        let nonce = self.make_nonce(counter);
655        let aad = aead::Aad::empty();
656
657        let decrypted = self
658            .recv_key
659            .open_in_place(nonce, aad, payload)
660            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "AEAD decryption failed"))?;
661
662        // Remove inner content type (last byte)
663        if decrypted.is_empty() {
664            return Err(io::Error::new(
665                io::ErrorKind::InvalidData,
666                "Empty inner plaintext",
667            ));
668        }
669
670        let inner_len = decrypted.len() - 1;
671        Ok(&decrypted[..inner_len])
672    }
673}
674
675impl Default for FakeTlsLeg {
676    /// `Default` panics on key-init failure — see [`FakeTlsLeg::new`] for the
677    /// structurally-impossible error contract. Prefer the explicit
678    /// `FakeTlsLeg::new()` in code that cares about the error.
679    fn default() -> Self {
680        #[allow(clippy::expect_used)]
681        Self::new().expect("FakeTlsLeg::default: AES key init invariant violated")
682    }
683}
684
685#[async_trait]
686impl TransportLeg for FakeTlsLeg {
687    async fn send(&self, data: Bytes) -> io::Result<()> {
688        if !self.is_available() {
689            return Err(io::Error::new(
690                io::ErrorKind::NotConnected,
691                "FakeTLS not connected",
692            ));
693        }
694
695        if *self.state.read() != FakeTlsState::ApplicationData {
696            return Err(io::Error::other("Handshake not finished"));
697        }
698
699        let record = self.wrap_as_tls_record(&data)?;
700
701        let mut stream_guard = self.stream.lock().await;
702        let stream = stream_guard
703            .as_mut()
704            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
705
706        stream.write_all(&record).await?;
707        stream.flush().await
708    }
709
710    async fn recv(&self) -> io::Result<Bytes> {
711        if !self.is_available() {
712            return Err(io::Error::new(
713                io::ErrorKind::NotConnected,
714                "FakeTLS not connected",
715            ));
716        }
717
718        if *self.state.read() != FakeTlsState::ApplicationData {
719            return Err(io::Error::other("Handshake not finished"));
720        }
721
722        let mut stream_guard = self.stream.lock().await;
723        let stream = stream_guard
724            .as_mut()
725            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
726
727        let mut read_buf = self.read_buf.lock().await;
728
729        // Read TLS record header (5 bytes)
730        while read_buf.len() < 5 {
731            let mut temp = [0u8; 4096];
732            let n = stream.read(&mut temp).await?;
733            if n == 0 {
734                return Err(io::Error::new(
735                    io::ErrorKind::UnexpectedEof,
736                    "Connection closed",
737                ));
738            }
739            read_buf.extend_from_slice(&temp[..n]);
740        }
741
742        // Parse record length
743        let record_len = u16::from_be_bytes([read_buf[3], read_buf[4]]) as usize;
744
745        // Read full record
746        while read_buf.len() < 5 + record_len {
747            let mut temp = [0u8; 4096];
748            let n = stream.read(&mut temp).await?;
749            if n == 0 {
750                return Err(io::Error::new(
751                    io::ErrorKind::UnexpectedEof,
752                    "Connection closed",
753                ));
754            }
755            read_buf.extend_from_slice(&temp[..n]);
756        }
757
758        // Extract record
759        let mut record = read_buf.split_to(5 + record_len);
760
761        // Unwrap and decrypt payload
762        // We have to copy it to a new Bytes because unwrap_tls_record mutates in place
763        // and returns a reference. Or we can just use `unwrap_tls_record(&mut record)`.
764        let payload_len = self.unwrap_tls_record(&mut record)?.len();
765
766        // The decrypted payload is at `&record[5..5+payload_len]`
767        let mut out = BytesMut::with_capacity(payload_len);
768        out.extend_from_slice(&record[5..5 + payload_len]);
769
770        Ok(out.freeze())
771    }
772
773    fn is_available(&self) -> bool {
774        self.available.load(Ordering::Relaxed)
775            && *self.state.read() == FakeTlsState::ApplicationData
776    }
777
778    fn rtt_ms(&self) -> u32 {
779        self.rtt_ms.load(Ordering::Relaxed)
780    }
781
782    fn loss_percent(&self) -> u8 {
783        0 // Based on TCP
784    }
785
786    fn remote_addr(&self) -> Option<SocketAddr> {
787        self.remote_addr
788    }
789
790    async fn close(&self) -> io::Result<()> {
791        self.available.store(false, Ordering::Relaxed);
792
793        if let Some(stream) = self.stream.lock().await.take() {
794            drop(stream);
795        }
796
797        log::info!("FakeTLS closed");
798        Ok(())
799    }
800}
801
802impl std::fmt::Debug for FakeTlsLeg {
803    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
804        f.debug_struct("FakeTlsLeg")
805            .field("sni", &self.config.sni)
806            .field("remote", &self.remote_addr)
807            .field("rtt_ms", &self.rtt_ms.load(Ordering::Relaxed))
808            .field("available", &self.is_available())
809            .finish()
810    }
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816
817    #[test]
818    fn test_faketls_leg_creation() {
819        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
820        assert!(!leg.is_available());
821        assert_eq!(leg.config.sni, "www.google.com");
822    }
823
824    #[test]
825    fn test_fake_client_hello() {
826        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
827        let hello = leg.build_fake_client_hello();
828
829        // Should start with TLS handshake record
830        assert_eq!(hello[0], TlsContentType::Handshake as u8);
831        assert!(hello.len() > 100); // Should be substantial
832    }
833
834    #[test]
835    fn test_wrap_tls_record() {
836        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
837        let data = b"test payload";
838        let record = leg.wrap_as_tls_record(data).expect("wrap_as_tls_record");
839
840        assert_eq!(record[0], TlsContentType::ApplicationData as u8);
841        // data len + 1 for inner content type + 16 for auth tag
842        assert_eq!(record.len(), 5 + data.len() + 1 + 16);
843    }
844
845    /// faketls-2: a payload whose sealed length would overflow the u16 TLS
846    /// record-length field must be rejected with `InvalidData`, never silently
847    /// truncated into a corrupt record.
848    #[test]
849    fn oversized_record_payload_is_rejected_not_truncated() {
850        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
851        // sealed = data + 1 (inner content-type) + 16 (AES-GCM tag). The largest
852        // payload that still fits u16::MAX (65_535) is 65_518; one more overflows.
853        let ok = leg
854            .wrap_as_tls_record(&vec![0u8; 65_518])
855            .expect("boundary payload fits");
856        assert_eq!(ok.len(), 5 + u16::MAX as usize);
857        let err = leg
858            .wrap_as_tls_record(&vec![0u8; 65_519])
859            .expect_err("oversized payload must be rejected, not truncated");
860        assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
861    }
862
863    #[test]
864    fn test_ja3_randomization() {
865        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
866        let hello1_ = leg.build_fake_client_hello();
867        let hello2 = leg.build_fake_client_hello();
868
869        // They should be different due to random session ID, random, and shuffling
870        assert_ne!(hello1_, hello2);
871
872        // Check standard parts presence
873        // TLS 1.2 Version (0x0303) at offset 9
874        assert_eq!(hello1_[9], 0x03);
875        assert_eq!(hello1_[10], 0x03);
876    }
877
878    /// Round-trip test: a client-side leg encrypts, a server-side leg decrypts.
879    /// Proves that derived-key + counter-nonce works end-to-end.
880    #[test]
881    fn test_wrap_unwrap_roundtrip_across_peers() {
882        // Build a "client" leg.
883        let client =
884            FakeTlsLeg::with_config(FakeTlsConfig::default()).expect("FakeTlsLeg::with_config");
885
886        // Build a "server" leg from scratch: same config but is_server = true.
887        // We construct it directly since `accept` requires a real TcpStream.
888        let cfg = FakeTlsConfig::default();
889        let (send_key, recv_key, nonce_prefix) =
890            derive_outer_keys(&cfg.sni, cfg.version, true).expect("derive_outer_keys");
891        let server = FakeTlsLeg {
892            config: cfg,
893            stream: Mutex::new(None),
894            remote_addr: None,
895            rtt_ms: AtomicU32::new(0),
896            available: AtomicBool::new(false),
897            state: parking_lot::RwLock::new(FakeTlsState::ApplicationData),
898            read_buf: Mutex::new(BytesMut::new()),
899            send_key,
900            recv_key,
901            nonce_prefix,
902            send_counter: AtomicU64::new(0),
903            recv_counter: AtomicU64::new(0),
904            is_server: true,
905        };
906
907        let plaintexts: &[&[u8]] = &[
908            b"first record",
909            b"second record",
910            b"third record (a bit longer to vary length)",
911        ];
912        for pt in plaintexts {
913            let mut record = client.wrap_as_tls_record(pt).expect("wrap_as_tls_record");
914            let recovered_len = server.unwrap_tls_record(&mut record).unwrap().len();
915            assert_eq!(&record[5..5 + recovered_len], *pt);
916        }
917    }
918
919    /// Encrypting identical plaintexts twice must produce different ciphertexts
920    /// because the per-record counter advances.
921    #[test]
922    fn test_nonce_advances_per_record() {
923        let leg =
924            FakeTlsLeg::with_config(FakeTlsConfig::default()).expect("FakeTlsLeg::with_config");
925        let r1 = leg.wrap_as_tls_record(b"identical").expect("wrap r1");
926        let r2 = leg.wrap_as_tls_record(b"identical").expect("wrap r2");
927        assert_ne!(
928            r1, r2,
929            "counter must advance — identical plaintext must not produce identical ciphertext"
930        );
931    }
932
933    #[test]
934    fn test_client_hello_structure() {
935        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
936        let hello = leg.build_fake_client_hello();
937
938        // Verify TLS record header
939        assert_eq!(
940            hello[0],
941            TlsContentType::Handshake as u8,
942            "First byte should be Handshake (22)"
943        );
944
945        // TLS record version (bytes 1-2)
946        let record_version = u16::from_be_bytes([hello[1], hello[2]]);
947        assert_eq!(
948            record_version, 0x0303,
949            "Record version should be TLS 1.2 (0x0303)"
950        );
951
952        // Record length (bytes 3-4)
953        let record_len = u16::from_be_bytes([hello[3], hello[4]]) as usize;
954        assert_eq!(
955            hello.len(),
956            5 + record_len,
957            "Record length should match payload"
958        );
959
960        // Handshake type (byte 5) should be ClientHello (0x01)
961        assert_eq!(hello[5], 0x01, "Handshake type should be ClientHello");
962
963        // Client version at bytes 9-10 (legacy TLS 1.2)
964        assert_eq!(hello[9], 0x03, "Legacy version major");
965        assert_eq!(hello[10], 0x03, "Legacy version minor");
966
967        // Random (32 bytes starting at byte 11) should not be all zeros
968        let random = &hello[11..43];
969        assert!(
970            !random.iter().all(|&b| b == 0),
971            "Random should not be all zeros"
972        );
973
974        // Session ID length at byte 43 should be 32
975        assert_eq!(hello[43], 32, "Session ID length should be 32");
976    }
977}