Skip to main content

ferogram_mtproto/
bind_temp_key.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3//
4// ferogram: async Telegram MTProto client in Rust
5// https://github.com/ankit-chaubey/ferogram
6//
7//
8// If you use or modify this code, keep this notice at the top of your file
9// and include the LICENSE-MIT or LICENSE-APACHE file from this repository:
10// https://github.com/ankit-chaubey/ferogram
11
12//! Build the `encrypted_message` payload for `auth.bindTempAuthKey`.
13//!
14//! The inner message must be encrypted with the **permanent** auth key using
15//! the legacy **MTProto v1** scheme (SHA-1-based key derivation + AES-256-IGE),
16//! NOT the normal MTProto 2.0 (SHA-256) scheme.
17//!
18//! Wire layout of `encrypted_message`:
19//! ```text
20//!   perm_key_id (8 bytes)
21//!   msg_key     (16 bytes)  = SHA1(plaintext_no_pad)[4..20]
22//!   ciphertext  (N bytes)   = AES-IGE(plaintext, derived from perm_key + msg_key)
23//!
24//! plaintext =
25//!   server_salt   (8 bytes, random)
26//!   session_id    (8 bytes, random)
27//!   msg_id        (8 bytes)
28//!   seq_no        (4 bytes, = 0)
29//!   data_len      (4 bytes)
30//!   bind_auth_key_inner (40 bytes)
31//!   padding       (0-15 bytes to reach 16-byte alignment)
32//! ```
33
34use ferogram_crypto::{aes, derive_aes_key_iv_v1};
35use sha1::{Digest, Sha1};
36
37fn serialize_inner(
38    nonce: i64,
39    temp_auth_key_id: i64,
40    perm_auth_key_id: i64,
41    temp_session_id: i64,
42    expires_at: i32,
43) -> [u8; 40] {
44    let mut out = [0u8; 40];
45    out[0..4].copy_from_slice(&0x75a3f765_u32.to_le_bytes());
46    out[4..12].copy_from_slice(&nonce.to_le_bytes());
47    out[12..20].copy_from_slice(&temp_auth_key_id.to_le_bytes());
48    out[20..28].copy_from_slice(&perm_auth_key_id.to_le_bytes());
49    out[28..36].copy_from_slice(&temp_session_id.to_le_bytes());
50    out[36..40].copy_from_slice(&expires_at.to_le_bytes());
51    out
52}
53
54/// Build the `encrypted_message` bytes for `auth.bindTempAuthKey`.
55pub fn encrypt_bind_inner(
56    perm_auth_key: &[u8; 256],
57    msg_id: i64,
58    nonce: i64,
59    temp_auth_key_id: i64,
60    perm_auth_key_id: i64,
61    temp_session_id: i64,
62    expires_at: i32,
63) -> Vec<u8> {
64    let inner = serialize_inner(
65        nonce,
66        temp_auth_key_id,
67        perm_auth_key_id,
68        temp_session_id,
69        expires_at,
70    );
71
72    let header_len = 32usize;
73    let content_len = header_len + 40;
74    let pad_len = (16 - content_len % 16) % 16;
75    let total = content_len + pad_len;
76
77    let mut rnd = [0u8; 24];
78    getrandom::getrandom(&mut rnd).expect("getrandom");
79
80    let mut plaintext = Vec::with_capacity(total);
81    plaintext.extend_from_slice(&rnd[..8]);
82    plaintext.extend_from_slice(&rnd[8..16]);
83    plaintext.extend_from_slice(&msg_id.to_le_bytes());
84    plaintext.extend_from_slice(&0i32.to_le_bytes());
85    plaintext.extend_from_slice(&40u32.to_le_bytes());
86    plaintext.extend_from_slice(&inner);
87    plaintext.extend_from_slice(&rnd[16..16 + pad_len]);
88    assert_eq!(plaintext.len(), total);
89
90    let hash: [u8; 20] = {
91        let mut h = Sha1::new();
92        h.update(&plaintext[..content_len]);
93        h.finalize().into()
94    };
95    let mut msg_key = [0u8; 16];
96    msg_key.copy_from_slice(&hash[4..20]);
97
98    let (aes_key, aes_iv) = derive_aes_key_iv_v1(perm_auth_key, &msg_key);
99    aes::ige_encrypt(&mut plaintext, &aes_key, &aes_iv);
100
101    let key_sha: [u8; 20] = {
102        let mut h = Sha1::new();
103        h.update(perm_auth_key);
104        h.finalize().into()
105    };
106
107    let mut result = Vec::with_capacity(8 + 16 + plaintext.len());
108    result.extend_from_slice(&key_sha[12..20]);
109    result.extend_from_slice(&msg_key);
110    result.extend_from_slice(&plaintext);
111    result
112}
113
114/// Serialize `auth.bindTempAuthKey#cdd42a05` to raw TL bytes.
115pub fn serialize_bind_temp_auth_key(
116    perm_auth_key_id: i64,
117    nonce: i64,
118    expires_at: i32,
119    encrypted_message: &[u8],
120) -> Vec<u8> {
121    let mut out = Vec::new();
122    out.extend_from_slice(&0xcdd42a05_u32.to_le_bytes());
123    out.extend_from_slice(&perm_auth_key_id.to_le_bytes());
124    out.extend_from_slice(&nonce.to_le_bytes());
125    out.extend_from_slice(&expires_at.to_le_bytes());
126    tl_write_bytes(&mut out, encrypted_message);
127    out
128}
129
130/// Generate a monotonic MTProto message ID from the current system clock.
131pub fn gen_msg_id() -> i64 {
132    use std::time::{SystemTime, UNIX_EPOCH};
133    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
134    ((now.as_secs() << 32) | (now.subsec_nanos() as u64 & !3)) as i64
135}
136
137fn tl_write_bytes(out: &mut Vec<u8>, data: &[u8]) {
138    let len = data.len();
139    if len < 254 {
140        out.push(len as u8);
141        out.extend_from_slice(data);
142        let pad = (4 - (1 + len) % 4) % 4;
143        out.extend(std::iter::repeat_n(0u8, pad));
144    } else {
145        out.push(0xfe);
146        out.push((len & 0xff) as u8);
147        out.push(((len >> 8) & 0xff) as u8);
148        out.push(((len >> 16) & 0xff) as u8);
149        out.extend_from_slice(data);
150        let pad = (4 - (4 + len) % 4) % 4;
151        out.extend(std::iter::repeat_n(0u8, pad));
152    }
153}