Skip to main content

layer_mtproto/
authentication.rs

1//! Sans-IO MTProto authorization key generation.
2//!
3//! # Flow
4//!
5//! ```text
6//! let (req, s1) = authentication::step1()?;
7//! // send req, receive resp
8//! let (req, s2) = authentication::step2(s1, resp)?;
9//! // send req, receive resp
10//! let (req, s3) = authentication::step3(s2, resp)?;
11//! // send req, receive resp
12//! let done = authentication::finish(s3, resp)?;
13//! // done.auth_key is ready
14//! ```
15
16use std::fmt;
17use std::time::{SystemTime, UNIX_EPOCH};
18
19use layer_crypto::{AuthKey, aes, factorize, generate_key_data_from_nonce, rsa};
20use layer_tl_types::{Cursor, Deserializable, Serializable};
21use num_bigint::{BigUint, ToBigUint};
22use sha1::{Digest, Sha1};
23
24// ─── Error ────────────────────────────────────────────────────────────────────
25
26/// Errors that can occur during auth key generation.
27#[allow(missing_docs)]
28#[derive(Clone, Debug, PartialEq)]
29pub enum Error {
30    InvalidNonce         { got: [u8; 16], expected: [u8; 16] },
31    InvalidPqSize        { size: usize },
32    UnknownFingerprints  { fingerprints: Vec<i64> },
33    DhParamsFail,
34    InvalidServerNonce   { got: [u8; 16], expected: [u8; 16] },
35    EncryptedResponseNotPadded { len: usize },
36    InvalidDhInnerData   { error: layer_tl_types::deserialize::Error },
37    GParameterOutOfRange { value: BigUint, low: BigUint, high: BigUint },
38    DhGenRetry,
39    DhGenFail,
40    InvalidAnswerHash    { got: [u8; 20], expected: [u8; 20] },
41    InvalidNewNonceHash  { got: [u8; 16], expected: [u8; 16] },
42}
43
44impl std::error::Error for Error {}
45
46impl fmt::Display for Error {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            Self::InvalidNonce { got, expected }
50                => write!(f, "nonce mismatch: got {got:?}, expected {expected:?}"),
51            Self::InvalidPqSize { size }
52                => write!(f, "pq size {size} invalid (expected 8)"),
53            Self::UnknownFingerprints { fingerprints }
54                => write!(f, "no known fingerprint in {fingerprints:?}"),
55            Self::DhParamsFail
56                => write!(f, "server returned DH params failure"),
57            Self::InvalidServerNonce { got, expected }
58                => write!(f, "server_nonce mismatch: got {got:?}, expected {expected:?}"),
59            Self::EncryptedResponseNotPadded { len }
60                => write!(f, "encrypted answer len {len} is not 16-byte aligned"),
61            Self::InvalidDhInnerData { error }
62                => write!(f, "DH inner data deserialization error: {error}"),
63            Self::GParameterOutOfRange { value, low, high }
64                => write!(f, "g={value} not in range ({low}, {high})"),
65            Self::DhGenRetry  => write!(f, "DH gen retry requested"),
66            Self::DhGenFail   => write!(f, "DH gen failed"),
67            Self::InvalidAnswerHash { got, expected }
68                => write!(f, "answer hash mismatch: got {got:?}, expected {expected:?}"),
69            Self::InvalidNewNonceHash { got, expected }
70                => write!(f, "new nonce hash mismatch: got {got:?}, expected {expected:?}"),
71        }
72    }
73}
74
75// ─── Step state ──────────────────────────────────────────────────────────────
76
77/// State after step 1.
78pub struct Step1 { nonce: [u8; 16] }
79
80/// State after step 2.
81pub struct Step2 {
82    nonce:        [u8; 16],
83    server_nonce: [u8; 16],
84    new_nonce:    [u8; 32],
85}
86
87/// State after step 3.
88pub struct Step3 {
89    nonce:        [u8; 16],
90    server_nonce: [u8; 16],
91    new_nonce:    [u8; 32],
92    gab:          BigUint,
93    time_offset:  i32,
94}
95
96/// The final output of a successful auth key handshake.
97#[derive(Clone, Debug, PartialEq)]
98pub struct Finished {
99    /// The 256-byte Telegram authorization key.
100    pub auth_key:    [u8; 256],
101    /// Clock skew in seconds relative to the server.
102    pub time_offset: i32,
103    /// Initial server salt.
104    pub first_salt:  i64,
105}
106
107// ─── Step 1: req_pq_multi ────────────────────────────────────────────────────
108
109/// Generate a `req_pq_multi` request. Returns the request + opaque state.
110pub fn step1() -> Result<(layer_tl_types::functions::ReqPqMulti, Step1), Error> {
111    let mut buf = [0u8; 16];
112    getrandom::getrandom(&mut buf).expect("getrandom");
113    do_step1(&buf)
114}
115
116fn do_step1(random: &[u8; 16]) -> Result<(layer_tl_types::functions::ReqPqMulti, Step1), Error> {
117    let nonce = *random;
118    Ok((layer_tl_types::functions::ReqPqMulti { nonce }, Step1 { nonce }))
119}
120
121// ─── Step 2: req_DH_params ───────────────────────────────────────────────────
122
123/// Process `ResPQ` and generate `req_DH_params`.
124pub fn step2(
125    data:     Step1,
126    response: layer_tl_types::enums::ResPq,
127) -> Result<(layer_tl_types::functions::ReqDhParams, Step2), Error> {
128    let mut rnd = [0u8; 256];
129    getrandom::getrandom(&mut rnd).expect("getrandom");
130    do_step2(data, response, &rnd)
131}
132
133fn do_step2(
134    data:     Step1,
135    response: layer_tl_types::enums::ResPq,
136    random:   &[u8; 256],
137) -> Result<(layer_tl_types::functions::ReqDhParams, Step2), Error> {
138    let Step1 { nonce } = data;
139
140    // ResPq has a single constructor: resPQ → variant ResPq
141    let res_pq = match response {
142        layer_tl_types::enums::ResPq::ResPq(x) => x,
143    };
144
145    check_nonce(&res_pq.nonce, &nonce)?;
146
147    if res_pq.pq.len() != 8 {
148        return Err(Error::InvalidPqSize { size: res_pq.pq.len() });
149    }
150
151    let pq = u64::from_be_bytes(res_pq.pq.as_slice().try_into().unwrap());
152    let (p, q) = factorize(pq);
153
154    let mut new_nonce = [0u8; 32];
155    new_nonce.copy_from_slice(&random[..32]);
156
157    // random[32..256] is 224 bytes for RSA padding
158    let rnd224: &[u8; 224] = random[32..].try_into().unwrap();
159
160    fn trim_be(v: u64) -> Vec<u8> {
161        let b = v.to_be_bytes();
162        let skip = b.iter().position(|&x| x != 0).unwrap_or(7);
163        b[skip..].to_vec()
164    }
165
166    let p_bytes = trim_be(p);
167    let q_bytes = trim_be(q);
168
169    // Build PQInnerData using the first (non-DC, non-temp) constructor
170    // variant name: PQInnerData (same as type name since constructor name == type name)
171    let pq_inner = layer_tl_types::enums::PQInnerData::PQInnerData(
172        layer_tl_types::types::PQInnerData {
173            pq: pq.to_be_bytes().to_vec(),
174            p: p_bytes.clone(),
175            q: q_bytes.clone(),
176            nonce,
177            server_nonce: res_pq.server_nonce,
178            new_nonce,
179        }
180    ).to_bytes();
181
182    let fingerprint = res_pq.server_public_key_fingerprints
183        .iter()
184        .copied()
185        .find(|&fp| key_for_fingerprint(fp).is_some())
186        .ok_or_else(|| Error::UnknownFingerprints {
187            fingerprints: res_pq.server_public_key_fingerprints.clone()
188        })?;
189
190    let key = key_for_fingerprint(fingerprint).unwrap();
191    let ciphertext = rsa::encrypt_hashed(&pq_inner, &key, rnd224);
192
193    Ok((
194        layer_tl_types::functions::ReqDhParams {
195            nonce,
196            server_nonce: res_pq.server_nonce,
197            p: p_bytes,
198            q: q_bytes,
199            public_key_fingerprint: fingerprint,
200            encrypted_data: ciphertext,
201        },
202        Step2 { nonce, server_nonce: res_pq.server_nonce, new_nonce },
203    ))
204}
205
206// ─── Step 3: set_client_DH_params ────────────────────────────────────────────
207
208/// Process `ServerDhParams` and generate `set_client_DH_params`.
209pub fn step3(
210    data:     Step2,
211    response: layer_tl_types::enums::ServerDhParams,
212) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
213    let mut rnd = [0u8; 272]; // 256 for DH b, 16 for padding
214    getrandom::getrandom(&mut rnd).expect("getrandom");
215    let now = SystemTime::now()
216        .duration_since(UNIX_EPOCH).unwrap().as_secs() as i32;
217    do_step3(data, response, &rnd, now)
218}
219
220fn do_step3(
221    data:     Step2,
222    response: layer_tl_types::enums::ServerDhParams,
223    random:   &[u8; 272],
224    now:      i32,
225) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
226    let Step2 { nonce, server_nonce, new_nonce } = data;
227
228    let mut server_dh_ok = match response {
229        layer_tl_types::enums::ServerDhParams::Fail(f) => {
230            check_nonce(&f.nonce, &nonce)?;
231            check_server_nonce(&f.server_nonce, &server_nonce)?;
232            // Verify new_nonce_hash
233            let digest: [u8; 20] = {
234                let mut sha = Sha1::new();
235                sha.update(new_nonce);
236                sha.finalize().into()
237            };
238            let mut expected_hash = [0u8; 16];
239            expected_hash.copy_from_slice(&digest[4..]);
240            check_new_nonce_hash(&f.new_nonce_hash, &expected_hash)?;
241            return Err(Error::DhParamsFail);
242        }
243        layer_tl_types::enums::ServerDhParams::Ok(x) => x,
244    };
245
246    check_nonce(&server_dh_ok.nonce, &nonce)?;
247    check_server_nonce(&server_dh_ok.server_nonce, &server_nonce)?;
248
249    if server_dh_ok.encrypted_answer.len() % 16 != 0 {
250        return Err(Error::EncryptedResponseNotPadded { len: server_dh_ok.encrypted_answer.len() });
251    }
252
253    let (key, iv) = generate_key_data_from_nonce(&server_nonce, &new_nonce);
254    aes::ige_decrypt(&mut server_dh_ok.encrypted_answer, &key, &iv);
255    let plain = server_dh_ok.encrypted_answer;
256
257    let got_hash: [u8; 20] = plain[..20].try_into().unwrap();
258    let mut cursor = Cursor::from_slice(&plain[20..]);
259
260    // ServerDhInnerData has single constructor server_DH_inner_data
261    // variant name = ServerDhInnerData (full name, since it equals type name)
262    let inner = match layer_tl_types::enums::ServerDhInnerData::deserialize(&mut cursor) {
263        Ok(layer_tl_types::enums::ServerDhInnerData::ServerDhInnerData(x)) => x,
264        Err(e) => return Err(Error::InvalidDhInnerData { error: e }),
265    };
266
267    let expected_hash: [u8; 20] = {
268        let mut sha = Sha1::new();
269        sha.update(&plain[20..20 + cursor.pos()]);
270        sha.finalize().into()
271    };
272    if got_hash != expected_hash {
273        return Err(Error::InvalidAnswerHash { got: got_hash, expected: expected_hash });
274    }
275
276    check_nonce(&inner.nonce, &nonce)?;
277    check_server_nonce(&inner.server_nonce, &server_nonce)?;
278
279    let dh_prime = BigUint::from_bytes_be(&inner.dh_prime);
280    let g = inner.g.to_biguint().unwrap();
281    let g_a = BigUint::from_bytes_be(&inner.g_a);
282    let time_offset = inner.server_time - now;
283
284    let b = BigUint::from_bytes_be(&random[..256]);
285    let g_b = g.modpow(&b, &dh_prime);
286    let gab = g_a.modpow(&b, &dh_prime);
287
288    // Validate DH parameters
289    let one = BigUint::from(1u32);
290    check_g_in_range(&g,   &one, &(&dh_prime - &one))?;
291    check_g_in_range(&g_a, &one, &(&dh_prime - &one))?;
292    check_g_in_range(&g_b, &one, &(&dh_prime - &one))?;
293    let safety = one.clone() << (2048 - 64);
294    check_g_in_range(&g_a, &safety, &(&dh_prime - &safety))?;
295    check_g_in_range(&g_b, &safety, &(&dh_prime - &safety))?;
296
297    // ClientDhInnerData has single constructor client_DH_inner_data
298    // variant name = ClientDhInnerData
299    let client_dh_inner = layer_tl_types::enums::ClientDhInnerData::ClientDhInnerData(
300        layer_tl_types::types::ClientDhInnerData {
301            nonce,
302            server_nonce,
303            retry_id: 0,
304            g_b: g_b.to_bytes_be(),
305        }
306    ).to_bytes();
307
308    let digest: [u8; 20] = {
309        let mut sha = Sha1::new();
310        sha.update(&client_dh_inner);
311        sha.finalize().into()
312    };
313
314    let pad_len = (16 - ((20 + client_dh_inner.len()) % 16)) % 16;
315    let rnd16 = &random[256..256 + pad_len.min(16)];
316
317    let mut hashed = Vec::with_capacity(20 + client_dh_inner.len() + pad_len);
318    hashed.extend_from_slice(&digest);
319    hashed.extend_from_slice(&client_dh_inner);
320    hashed.extend_from_slice(&rnd16[..pad_len]);
321
322    aes::ige_encrypt(&mut hashed, &key, &iv);
323
324    Ok((
325        layer_tl_types::functions::SetClientDhParams {
326            nonce,
327            server_nonce,
328            encrypted_data: hashed,
329        },
330        Step3 { nonce, server_nonce, new_nonce, gab, time_offset },
331    ))
332}
333
334// ─── finish: create_key ──────────────────────────────────────────────────────
335
336/// Finalise the handshake. Returns the ready [`Finished`] on success.
337pub fn finish(
338    data:     Step3,
339    response: layer_tl_types::enums::SetClientDhParamsAnswer,
340) -> Result<Finished, Error> {
341    let Step3 { nonce, server_nonce, new_nonce, gab, time_offset } = data;
342
343    struct DhData { nonce: [u8; 16], server_nonce: [u8; 16], hash: [u8; 16], num: u8 }
344
345    let dh = match response {
346        // Variant names come from the constructor names: dh_gen_ok → DhGenOk, etc.
347        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenOk(x)    =>
348            DhData { nonce: x.nonce, server_nonce: x.server_nonce, hash: x.new_nonce_hash1, num: 1 },
349        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenRetry(x) =>
350            DhData { nonce: x.nonce, server_nonce: x.server_nonce, hash: x.new_nonce_hash2, num: 2 },
351        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenFail(x)  =>
352            DhData { nonce: x.nonce, server_nonce: x.server_nonce, hash: x.new_nonce_hash3, num: 3 },
353    };
354
355    check_nonce(&dh.nonce, &nonce)?;
356    check_server_nonce(&dh.server_nonce, &server_nonce)?;
357
358    let mut key_bytes = [0u8; 256];
359    let gab_bytes = gab.to_bytes_be();
360    let skip = 256 - gab_bytes.len();
361    key_bytes[skip..].copy_from_slice(&gab_bytes);
362
363    let auth_key = AuthKey::from_bytes(key_bytes);
364    let expected_hash = auth_key.calc_new_nonce_hash(&new_nonce, dh.num);
365    check_new_nonce_hash(&dh.hash, &expected_hash)?;
366
367    let first_salt = {
368        let mut buf = [0u8; 8];
369        for ((dst, a), b) in buf.iter_mut().zip(&new_nonce[..8]).zip(&server_nonce[..8]) {
370            *dst = a ^ b;
371        }
372        i64::from_le_bytes(buf)
373    };
374
375    match dh.num {
376        1 => Ok(Finished { auth_key: auth_key.to_bytes(), time_offset, first_salt }),
377        2 => Err(Error::DhGenRetry),
378        _ => Err(Error::DhGenFail),
379    }
380}
381
382// ─── Helpers ─────────────────────────────────────────────────────────────────
383
384fn check_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
385    if got == expected { Ok(()) } else {
386        Err(Error::InvalidNonce { got: *got, expected: *expected })
387    }
388}
389fn check_server_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
390    if got == expected { Ok(()) } else {
391        Err(Error::InvalidServerNonce { got: *got, expected: *expected })
392    }
393}
394fn check_new_nonce_hash(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
395    if got == expected { Ok(()) } else {
396        Err(Error::InvalidNewNonceHash { got: *got, expected: *expected })
397    }
398}
399fn check_g_in_range(val: &BigUint, lo: &BigUint, hi: &BigUint) -> Result<(), Error> {
400    if lo < val && val < hi { Ok(()) } else {
401        Err(Error::GParameterOutOfRange { value: val.clone(), low: lo.clone(), high: hi.clone() })
402    }
403}
404
405/// RSA key by server fingerprint. Includes both production and test DC keys.
406#[allow(clippy::unreadable_literal)]
407pub fn key_for_fingerprint(fp: i64) -> Option<rsa::Key> {
408    Some(match fp {
409        // Production DC key (fingerprint -3414540481677951611)
410        -3414540481677951611 => rsa::Key::new(
411            "29379598170669337022986177149456128565388431120058863768162556424047512191330847455146576344487764408661701890505066208632169112269581063774293102577308490531282748465986139880977280302242772832972539403531316010870401287642763009136156734339538042419388722777357134487746169093539093850251243897188928735903389451772730245253062963384108812842079887538976360465290946139638691491496062099570836476454855996319192747663615955633778034897140982517446405334423701359108810182097749467210509584293428076654573384828809574217079944388301239431309115013843331317877374435868468779972014486325557807783825502498215169806323",
412            "65537"
413        )?,
414        // Test DC key (fingerprint -5595554452916591101)
415        -5595554452916591101 => rsa::Key::new(
416            "25342889448840415564971689590713473206898847759084779052582026594546022463853940585885215951168491965708222649399180603818074200620463776135424884632162512403163793083921641631564740959529419359595852941166848940585952337613333022396096584117954892216031229237302943701877588456738335398602461675225081791820393153757504952636234951323237820036543581047826906120927972487366805292115792231423684261262330394324750785450942589751755390156647751460719351439969059949569615302809050721500330239005077889855323917509948255722081644689442127297605422579707142646660768825302832201908302295573257427896031830742328565032949",
417            "65537"
418        )?,
419        _ => return None,
420    })
421}