Skip to main content

ferogram_crypto/
obfuscated.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2//
3// ferogram: async Telegram MTProto client in Rust
4// https://github.com/ankit-chaubey/ferogram
5//
6// Licensed under either the MIT License or the Apache License 2.0.
7// See the LICENSE-MIT or LICENSE-APACHE file in this repository:
8// https://github.com/ankit-chaubey/ferogram
9//
10// Feel free to use, modify, and share this code.
11// Please keep this notice when redistributing.
12
13#[allow(deprecated)]
14use aes::cipher::{KeyIvInit, StreamCipher, generic_array::GenericArray};
15
16/// AES-256-CTR stream cipher pair for MTProto obfuscated transport.
17pub struct ObfuscatedCipher {
18    #[allow(deprecated)]
19    rx: ctr::Ctr128BE<aes::Aes256>,
20    #[allow(deprecated)]
21    tx: ctr::Ctr128BE<aes::Aes256>,
22}
23
24impl ObfuscatedCipher {
25    /// Build cipher state from the 64-byte random init buffer.
26    #[allow(deprecated)]
27    pub fn new(init: &[u8; 64]) -> Self {
28        let rev: Vec<u8> = init.iter().copied().rev().collect();
29        Self {
30            rx: ctr::Ctr128BE::<aes::Aes256>::new(
31                GenericArray::from_slice(&rev[8..40]),
32                GenericArray::from_slice(&rev[40..56]),
33            ),
34            tx: ctr::Ctr128BE::<aes::Aes256>::new(
35                GenericArray::from_slice(&init[8..40]),
36                GenericArray::from_slice(&init[40..56]),
37            ),
38        }
39    }
40
41    /// Build cipher from explicit key/IV pairs (used when MTProxy secret
42    /// mixing has already been applied externally via SHA-256).
43    #[allow(deprecated)]
44    pub fn from_keys(
45        tx_key: &[u8; 32],
46        tx_iv: &[u8; 16],
47        rx_key: &[u8; 32],
48        rx_iv: &[u8; 16],
49    ) -> Self {
50        Self {
51            tx: ctr::Ctr128BE::<aes::Aes256>::new(
52                GenericArray::from_slice(tx_key),
53                GenericArray::from_slice(tx_iv),
54            ),
55            rx: ctr::Ctr128BE::<aes::Aes256>::new(
56                GenericArray::from_slice(rx_key),
57                GenericArray::from_slice(rx_iv),
58            ),
59        }
60    }
61
62    /// Encrypt outgoing bytes in-place (TX direction).
63    pub fn encrypt(&mut self, buf: &mut [u8]) {
64        self.tx.apply_keystream(buf);
65    }
66
67    /// Decrypt incoming bytes in-place (RX direction).
68    pub fn decrypt(&mut self, buf: &mut [u8]) {
69        self.rx.apply_keystream(buf);
70    }
71}
72
73/// Generate the 64-byte obfuscated init buffer and build the cipher for it.
74///
75/// `framing_byte`: 0xef = Abridged, 0xdd = PaddedIntermediate.
76/// `proxy_secret`: if present, SHA-256 mixes the key with the secret (MTProxy).
77///
78/// Returns `(nonce, cipher)`. The caller writes `nonce` to the stream; the
79/// cipher is used for all subsequent I/O on that connection.
80pub fn build_obfuscated_init(
81    framing_byte: u8,
82    dc_id: i16,
83    proxy_secret: Option<&[u8]>,
84) -> ([u8; 64], ObfuscatedCipher) {
85    use sha2::Digest;
86
87    let mut nonce = [0u8; 64];
88    loop {
89        crate::fill_random(&mut nonce);
90        let first = u32::from_le_bytes(nonce[0..4].try_into().expect("4-byte slice"));
91        let second = u32::from_le_bytes(nonce[4..8].try_into().expect("4-byte slice"));
92        let bad = nonce[0] == 0xEF
93            || first == 0x44414548 // HEAD
94            || first == 0x54534F50 // POST
95            || first == 0x20544547 // GET
96            || first == 0x4954504f // OPTIONS
97            || first == 0xEEEEEEEE
98            || first == 0xDDDDDDDD
99            || first == 0x02010316
100            || second == 0x00000000;
101        if !bad {
102            break;
103        }
104    }
105
106    let tx_raw: [u8; 32] = nonce[8..40].try_into().expect("32-byte slice");
107    let tx_iv: [u8; 16] = nonce[40..56].try_into().expect("16-byte slice");
108    let mut rev48 = nonce[8..56].to_vec();
109    rev48.reverse();
110    let rx_raw: [u8; 32] = rev48[0..32].try_into().expect("32-byte slice");
111    let rx_iv: [u8; 16] = rev48[32..48].try_into().expect("16-byte slice");
112
113    let (tx_key, rx_key): ([u8; 32], [u8; 32]) = if let Some(s) = proxy_secret {
114        let mut h = sha2::Sha256::new();
115        h.update(tx_raw);
116        h.update(s);
117        let tx: [u8; 32] = h.finalize().into();
118
119        let mut h = sha2::Sha256::new();
120        h.update(rx_raw);
121        h.update(s);
122        let rx: [u8; 32] = h.finalize().into();
123        (tx, rx)
124    } else {
125        (tx_raw, rx_raw)
126    };
127
128    nonce[56] = framing_byte;
129    nonce[57] = framing_byte;
130    nonce[58] = framing_byte;
131    nonce[59] = framing_byte;
132    let dc_bytes = dc_id.to_le_bytes();
133    nonce[60] = dc_bytes[0];
134    nonce[61] = dc_bytes[1];
135
136    let mut cipher = ObfuscatedCipher::from_keys(&tx_key, &tx_iv, &rx_key, &rx_iv);
137    let mut skip = [0u8; 56];
138    cipher.encrypt(&mut skip);
139    cipher.encrypt(&mut nonce[56..64]);
140
141    (nonce, cipher)
142}
143
144/// Derive the MTProxy FakeTls obfuscation cipher and the HMAC to embed in the
145/// ClientHello random field.
146///
147/// `secret`: the 16-byte MTProxy secret.
148/// `tls_record`: the fully assembled TLS ClientHello record bytes (before the
149///   random field is filled in; the caller fills it after this returns).
150///
151/// Returns `(hmac_result, cipher)`:
152/// - `hmac_result` is the 32-byte `HMAC-SHA256(secret, tls_record)` value that
153///   the caller writes into the ClientHello random field at offset 11.
154/// - `cipher` is the `ObfuscatedCipher` keyed from `SHA256(secret || hmac_result)`,
155///   used for all subsequent encrypted I/O on the connection.
156pub fn build_fake_tls_keys(secret: &[u8; 16], tls_record: &[u8]) -> ([u8; 32], ObfuscatedCipher) {
157    use hmac::{Hmac, Mac};
158    use sha2::Digest;
159    type HmacSha256 = Hmac<sha2::Sha256>;
160
161    // HMAC-SHA256(secret, tls_record) → random field value
162    let mut mac =
163        HmacSha256::new_from_slice(secret).expect("HMAC key error: secret must be non-empty");
164    mac.update(tls_record);
165    let hmac_result: [u8; 32] = mac.finalize().into_bytes().into();
166
167    // SHA256(secret || hmac_result) → obfuscation key
168    let mut h = sha2::Sha256::new();
169    h.update(secret.as_ref());
170    h.update(hmac_result);
171    let derived: [u8; 32] = h.finalize().into();
172
173    let iv = [0u8; 16];
174    let cipher = ObfuscatedCipher::from_keys(&derived, &iv, &derived, &iv);
175    (hmac_result, cipher)
176}