Skip to main content

ferogram_mtproto/
bind_temp_key.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
13use ferogram_crypto::{aes, derive_aes_key_iv_v1};
14use sha1::{Digest, Sha1};
15
16fn serialize_inner(
17    nonce: i64,
18    temp_auth_key_id: i64,
19    perm_auth_key_id: i64,
20    temp_session_id: i64,
21    expires_at: i32,
22) -> [u8; 40] {
23    let mut out = [0u8; 40];
24    out[0..4].copy_from_slice(&0x75a3f765_u32.to_le_bytes());
25    out[4..12].copy_from_slice(&nonce.to_le_bytes());
26    out[12..20].copy_from_slice(&temp_auth_key_id.to_le_bytes());
27    out[20..28].copy_from_slice(&perm_auth_key_id.to_le_bytes());
28    out[28..36].copy_from_slice(&temp_session_id.to_le_bytes());
29    out[36..40].copy_from_slice(&expires_at.to_le_bytes());
30    out
31}
32
33/// Build the `encrypted_message` bytes for `auth.bindTempAuthKey`.
34pub fn encrypt_bind_inner(
35    perm_auth_key: &[u8; 256],
36    msg_id: i64,
37    nonce: i64,
38    temp_auth_key_id: i64,
39    perm_auth_key_id: i64,
40    temp_session_id: i64,
41    expires_at: i32,
42) -> Vec<u8> {
43    let inner = serialize_inner(
44        nonce,
45        temp_auth_key_id,
46        perm_auth_key_id,
47        temp_session_id,
48        expires_at,
49    );
50
51    let header_len = 32usize;
52    let content_len = header_len + 40;
53    let pad_len = (16 - content_len % 16) % 16;
54    let total = content_len + pad_len;
55
56    let mut rnd = [0u8; 24];
57    getrandom::getrandom(&mut rnd).expect("getrandom");
58
59    let mut plaintext = Vec::with_capacity(total);
60    plaintext.extend_from_slice(&rnd[..8]);
61    plaintext.extend_from_slice(&rnd[8..16]);
62    plaintext.extend_from_slice(&msg_id.to_le_bytes());
63    plaintext.extend_from_slice(&0i32.to_le_bytes());
64    plaintext.extend_from_slice(&40u32.to_le_bytes());
65    plaintext.extend_from_slice(&inner);
66    plaintext.extend_from_slice(&rnd[16..16 + pad_len]);
67    assert_eq!(plaintext.len(), total);
68
69    let hash: [u8; 20] = {
70        let mut h = Sha1::new();
71        h.update(&plaintext[..content_len]);
72        h.finalize().into()
73    };
74    let mut msg_key = [0u8; 16];
75    msg_key.copy_from_slice(&hash[4..20]);
76
77    let (aes_key, aes_iv) = derive_aes_key_iv_v1(perm_auth_key, &msg_key);
78    aes::ige_encrypt(&mut plaintext, &aes_key, &aes_iv);
79
80    let key_sha: [u8; 20] = {
81        let mut h = Sha1::new();
82        h.update(perm_auth_key);
83        h.finalize().into()
84    };
85
86    let mut result = Vec::with_capacity(8 + 16 + plaintext.len());
87    result.extend_from_slice(&key_sha[12..20]);
88    result.extend_from_slice(&msg_key);
89    result.extend_from_slice(&plaintext);
90    result
91}
92
93/// Serialize `auth.bindTempAuthKey#cdd42a05` to raw TL bytes.
94pub fn serialize_bind_temp_auth_key(
95    perm_auth_key_id: i64,
96    nonce: i64,
97    expires_at: i32,
98    encrypted_message: &[u8],
99) -> Vec<u8> {
100    let mut out = Vec::new();
101    out.extend_from_slice(&0xcdd42a05_u32.to_le_bytes());
102    out.extend_from_slice(&perm_auth_key_id.to_le_bytes());
103    out.extend_from_slice(&nonce.to_le_bytes());
104    out.extend_from_slice(&expires_at.to_le_bytes());
105    tl_write_bytes(&mut out, encrypted_message);
106    out
107}
108
109/// Returns the auth-key ID: SHA-1(key)[12..20] as little-endian i64.
110pub fn auth_key_id_from_key(key: &[u8; 256]) -> i64 {
111    use sha1::{Digest, Sha1};
112    let hash: [u8; 20] = Sha1::new().chain_update(key).finalize().into();
113    i64::from_le_bytes(hash[12..20].try_into().unwrap())
114}
115
116/// Generate a monotonic MTProto message ID from the current system clock.
117///
118/// The previous implementation used `nanos & !3` (clears bottom 2 bits), which
119/// produces values in range 0..999_999_996. `EncryptedSession::next_msg_id` uses
120/// `nanos << 2` (multiply by 4), range 0..3_999_999_996. At the same wall-clock
121/// instant the old `gen_msg_id` output was 4x smaller in the lower 32 bits than
122/// any msg_id already assigned by the session, triggering `bad_msg_notification`
123/// code 16 (msg_id too low) from the server on the bind request.
124/// Uses `nanos << 2` to match the session's scaling exactly.
125pub fn gen_msg_id() -> i64 {
126    use std::time::{SystemTime, UNIX_EPOCH};
127    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
128    let secs = now.as_secs();
129    let nanos = now.subsec_nanos() as u64;
130    // Use same formula as EncryptedSession::next_msg_id: nanos << 2.
131    // Bottom 2 bits are 0 (plaintext DH handshake message; not content-related).
132    ((secs << 32) | (nanos << 2)) as i64
133}
134
135fn tl_write_bytes(out: &mut Vec<u8>, data: &[u8]) {
136    let len = data.len();
137    if len < 254 {
138        out.push(len as u8);
139        out.extend_from_slice(data);
140        let pad = (4 - (1 + len) % 4) % 4;
141        out.extend(std::iter::repeat_n(0u8, pad));
142    } else {
143        out.push(0xfe);
144        out.push((len & 0xff) as u8);
145        out.push(((len >> 8) & 0xff) as u8);
146        out.push(((len >> 16) & 0xff) as u8);
147        out.extend_from_slice(data);
148        let pad = (4 - (4 + len) % 4) % 4;
149        out.extend(std::iter::repeat_n(0u8, pad));
150    }
151}