Skip to main content

layer_client/
transport_obfuscated.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19//! Obfuscated MTProto transport (Obfuscated2).
20
21pub use layer_crypto::ObfuscatedCipher;
22
23use crate::InvocationError;
24use tokio::io::{AsyncReadExt, AsyncWriteExt};
25use tokio::net::TcpStream;
26
27pub struct ObfuscatedStream {
28    stream: TcpStream,
29    cipher: ObfuscatedCipher,
30}
31
32impl ObfuscatedStream {
33    pub async fn connect(
34        addr: &str,
35        proxy_secret: Option<&[u8; 16]>,
36        dc_id: i16,
37    ) -> Result<Self, InvocationError> {
38        let stream = TcpStream::connect(addr).await?;
39        Self::handshake(stream, proxy_secret, dc_id).await
40    }
41
42    async fn handshake(
43        mut stream: TcpStream,
44        proxy_secret: Option<&[u8; 16]>,
45        dc_id: i16,
46    ) -> Result<Self, InvocationError> {
47        use sha2::Digest;
48
49        let mut nonce = [0u8; 64];
50        loop {
51            getrandom::getrandom(&mut nonce)
52                .map_err(|_| InvocationError::Deserialize("getrandom failed".into()))?;
53            let first = u32::from_le_bytes(nonce[0..4].try_into().unwrap());
54            let second = u32::from_le_bytes(nonce[4..8].try_into().unwrap());
55            let bad = nonce[0] == 0xEF
56                || first == 0x44414548
57                || first == 0x54534F50
58                || first == 0x20544547
59                || first == 0xEEEEEEEE
60                || first == 0xDDDDDDDD
61                || first == 0x02010316
62                || second == 0x00000000;
63            if !bad {
64                break;
65            }
66        }
67
68        let tx_raw: [u8; 32] = nonce[8..40].try_into().unwrap();
69        let tx_iv: [u8; 16] = nonce[40..56].try_into().unwrap();
70        let mut rev48 = nonce[8..56].to_vec();
71        rev48.reverse();
72        let rx_raw: [u8; 32] = rev48[0..32].try_into().unwrap();
73        let rx_iv: [u8; 16] = rev48[32..48].try_into().unwrap();
74
75        let (tx_key, rx_key): ([u8; 32], [u8; 32]) = if let Some(s) = proxy_secret {
76            let mut h = sha2::Sha256::new();
77            h.update(tx_raw);
78            h.update(s.as_ref());
79            let tx: [u8; 32] = h.finalize().into();
80            let mut h = sha2::Sha256::new();
81            h.update(rx_raw);
82            h.update(s.as_ref());
83            let rx: [u8; 32] = h.finalize().into();
84            (tx, rx)
85        } else {
86            (tx_raw, rx_raw)
87        };
88
89        nonce[56] = 0xef;
90        nonce[57] = 0xef;
91        nonce[58] = 0xef;
92        nonce[59] = 0xef;
93        let dc_bytes = dc_id.to_le_bytes();
94        nonce[60] = dc_bytes[0];
95        nonce[61] = dc_bytes[1];
96
97        // Single continuous cipher: advance TX past plaintext nonce[0..56], then
98        // encrypt nonce[56..64].  The same instance is stored for all later TX so
99        // the AES-CTR stream continues from position 64 matching tDesktop.
100        let mut cipher = ObfuscatedCipher::from_keys(&tx_key, &tx_iv, &rx_key, &rx_iv);
101        let mut skip = [0u8; 56];
102        cipher.encrypt(&mut skip);
103        cipher.encrypt(&mut nonce[56..64]);
104
105        stream.write_all(&nonce).await?;
106        Ok(Self { stream, cipher })
107    }
108
109    pub async fn send(&mut self, data: &[u8]) -> Result<(), InvocationError> {
110        let words = data.len() / 4;
111        let mut frame = if words < 0x7f {
112            let mut v = Vec::with_capacity(1 + data.len());
113            v.push(words as u8);
114            v
115        } else {
116            let mut v = Vec::with_capacity(4 + data.len());
117            v.extend_from_slice(&[
118                0x7f,
119                (words & 0xff) as u8,
120                ((words >> 8) & 0xff) as u8,
121                ((words >> 16) & 0xff) as u8,
122            ]);
123            v
124        };
125        frame.extend_from_slice(data);
126        self.cipher.encrypt(&mut frame);
127        self.stream.write_all(&frame).await?;
128        Ok(())
129    }
130
131    pub async fn recv(&mut self) -> Result<Vec<u8>, InvocationError> {
132        let mut h = [0u8; 1];
133        self.stream.read_exact(&mut h).await?;
134        self.cipher.decrypt(&mut h);
135
136        let words = if h[0] < 0x7f {
137            h[0] as usize
138        } else {
139            let mut b = [0u8; 3];
140            self.stream.read_exact(&mut b).await?;
141            self.cipher.decrypt(&mut b);
142            b[0] as usize | (b[1] as usize) << 8 | (b[2] as usize) << 16
143        };
144
145        let mut buf = vec![0u8; words * 4];
146        self.stream.read_exact(&mut buf).await?;
147        self.cipher.decrypt(&mut buf);
148        Ok(buf)
149    }
150}