Skip to main content

ferogram_mtproto/
authentication.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// If you use or modify this code, keep this notice at the top of your file
8// and include the LICENSE-MIT or LICENSE-APACHE file from this repository:
9// https://github.com/ankit-chaubey/ferogram
10
11use std::fmt;
12use std::time::{SystemTime, UNIX_EPOCH};
13
14use ferogram_crypto::{AuthKey, aes, check_p_and_g, factorize, generate_key_data_from_nonce, rsa};
15use ferogram_tl_types::{Cursor, Deserializable, Serializable};
16use num_bigint::BigUint;
17use sha1::{Digest, Sha1};
18
19// Manual TL serialization helper for PQInnerDataDc
20//
21// Constructor: p_q_inner_data_dc#a9f55f95
22//   pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int
23//
24// TL "string" (bytes) encoding: if len < 254 -> [len_byte, data..., 0-pad to 4-align],
25// else [0xfe, len_lo, len_mid, len_hi, data..., 0-pad to 4-align].
26fn tl_serialize_bytes(v: &[u8]) -> Vec<u8> {
27    let len = v.len();
28    let mut out = Vec::new();
29    if len < 254 {
30        out.push(len as u8);
31        out.extend_from_slice(v);
32        let total = 1 + len;
33        let pad = (4 - total % 4) % 4;
34        out.extend(std::iter::repeat_n(0u8, pad));
35    } else {
36        out.push(0xfe);
37        out.push((len & 0xff) as u8);
38        out.push(((len >> 8) & 0xff) as u8);
39        out.push(((len >> 16) & 0xff) as u8);
40        out.extend_from_slice(v);
41        let total = 4 + len;
42        let pad = (4 - total % 4) % 4;
43        out.extend(std::iter::repeat_n(0u8, pad));
44    }
45    out
46}
47
48/// Serialize a `p_q_inner_data_dc` (constructor 0xa9f55f95) from raw fields.
49/// Needed because the generated TL bindings only expose `PQInnerData`
50/// (legacy, no DC id) which Telegram rejects for non-DC2 connections.
51fn serialize_pq_inner_data_dc(
52    pq: &[u8],
53    p: &[u8],
54    q: &[u8],
55    nonce: &[u8; 16],
56    server_nonce: &[u8; 16],
57    new_nonce: &[u8; 32],
58    dc_id: i32,
59) -> Vec<u8> {
60    let mut out = Vec::new();
61    // Constructor id (little-endian)
62    out.extend_from_slice(&0xa9f55f95_u32.to_le_bytes());
63    out.extend(tl_serialize_bytes(pq));
64    out.extend(tl_serialize_bytes(p));
65    out.extend(tl_serialize_bytes(q));
66    out.extend_from_slice(nonce);
67    out.extend_from_slice(server_nonce);
68    out.extend_from_slice(new_nonce);
69    out.extend_from_slice(&dc_id.to_le_bytes());
70    out
71}
72
73/// Serialize p_q_inner_data_temp_dc#56fddf88 (temp key variant with expires_in).
74#[allow(clippy::too_many_arguments)]
75fn serialize_pq_inner_data_temp_dc(
76    pq: &[u8],
77    p: &[u8],
78    q: &[u8],
79    nonce: &[u8; 16],
80    server_nonce: &[u8; 16],
81    new_nonce: &[u8; 32],
82    dc_id: i32,
83    expires_in: i32,
84) -> Vec<u8> {
85    let mut out = Vec::new();
86    out.extend_from_slice(&0x56fddf88_u32.to_le_bytes()); // p_q_inner_data_temp_dc
87    out.extend(tl_serialize_bytes(pq));
88    out.extend(tl_serialize_bytes(p));
89    out.extend(tl_serialize_bytes(q));
90    out.extend_from_slice(nonce);
91    out.extend_from_slice(server_nonce);
92    out.extend_from_slice(new_nonce);
93    out.extend_from_slice(&dc_id.to_le_bytes());
94    out.extend_from_slice(&expires_in.to_le_bytes()); // extra field vs permanent variant
95    out
96}
97
98/// Errors that can occur during auth key generation.
99#[allow(missing_docs)]
100#[derive(Clone, Debug, PartialEq)]
101pub enum Error {
102    InvalidNonce {
103        got: [u8; 16],
104        expected: [u8; 16],
105    },
106    InvalidPqSize {
107        size: usize,
108    },
109    UnknownFingerprints {
110        fingerprints: Vec<i64>,
111    },
112    DhParamsFail,
113    InvalidServerNonce {
114        got: [u8; 16],
115        expected: [u8; 16],
116    },
117    EncryptedResponseNotPadded {
118        len: usize,
119    },
120    InvalidDhInnerData {
121        error: ferogram_tl_types::deserialize::Error,
122    },
123    InvalidDhPrime {
124        source: ferogram_crypto::DhError,
125    },
126    GParameterOutOfRange {
127        value: BigUint,
128        low: BigUint,
129        high: BigUint,
130    },
131    DhGenRetry,
132    DhGenFail,
133    InvalidAnswerHash {
134        got: [u8; 20],
135        expected: [u8; 20],
136    },
137    InvalidNewNonceHash {
138        got: [u8; 16],
139        expected: [u8; 16],
140    },
141}
142
143impl std::error::Error for Error {}
144
145impl fmt::Display for Error {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        match self {
148            Self::InvalidNonce { got, expected } => {
149                write!(f, "nonce mismatch: got {got:?}, expected {expected:?}")
150            }
151            Self::InvalidPqSize { size } => write!(f, "pq size {size} invalid (expected 8)"),
152            Self::UnknownFingerprints { fingerprints } => {
153                write!(f, "no known fingerprint in {fingerprints:?}")
154            }
155            Self::DhParamsFail => write!(f, "server returned DH params failure"),
156            Self::InvalidServerNonce { got, expected } => write!(
157                f,
158                "server_nonce mismatch: got {got:?}, expected {expected:?}"
159            ),
160            Self::EncryptedResponseNotPadded { len } => {
161                write!(f, "encrypted answer len {len} is not 16-byte aligned")
162            }
163            Self::InvalidDhInnerData { error } => {
164                write!(f, "DH inner data deserialization error: {error}")
165            }
166            Self::InvalidDhPrime { source } => {
167                write!(f, "DH prime/generator validation failed: {source}")
168            }
169            Self::GParameterOutOfRange { value, low, high } => {
170                write!(f, "g={value} not in range ({low}, {high})")
171            }
172            Self::DhGenRetry => write!(f, "DH gen retry requested"),
173            Self::DhGenFail => write!(f, "DH gen failed"),
174            Self::InvalidAnswerHash { got, expected } => write!(
175                f,
176                "answer hash mismatch: got {got:?}, expected {expected:?}"
177            ),
178            Self::InvalidNewNonceHash { got, expected } => write!(
179                f,
180                "new nonce hash mismatch: got {got:?}, expected {expected:?}"
181            ),
182        }
183    }
184}
185
186/// State after step 1.
187pub struct Step1 {
188    nonce: [u8; 16],
189}
190
191/// State after step 2.
192#[derive(Clone)]
193pub struct Step2 {
194    nonce: [u8; 16],
195    server_nonce: [u8; 16],
196    new_nonce: [u8; 32],
197}
198
199/// Pre-processed server DH parameters retained so that step 3 can be
200/// repeated on `dh_gen_retry` without having to re-decrypt the server response.
201#[derive(Clone)]
202pub struct DhParamsForRetry {
203    /// Server-supplied DH prime (big-endian bytes).
204    pub dh_prime: Vec<u8>,
205    /// DH generator `g`.
206    pub g: u32,
207    /// Server's public DH value `g_a` (big-endian bytes).
208    pub g_a: Vec<u8>,
209    /// Server's reported Unix timestamp (used to compute `time_offset`).
210    pub server_time: i32,
211    /// AES key derived from nonces for this session's IGE encryption.
212    pub aes_key: [u8; 32],
213    /// AES IV derived from nonces for this session's IGE encryption.
214    pub aes_iv: [u8; 32],
215}
216
217/// State after step 3.
218pub struct Step3 {
219    nonce: [u8; 16],
220    server_nonce: [u8; 16],
221    new_nonce: [u8; 32],
222    time_offset: i32,
223    /// Auth key candidate bytes (needed to derive `auth_key_aux_hash` on retry).
224    auth_key: [u8; 256],
225    /// The processed DH parameters stored so `retry_step3` can re-derive g_b
226    /// without re-parsing the encrypted server response.
227    pub dh_params: DhParamsForRetry,
228}
229
230/// Result of [`finish`] either the handshake is done, or the server wants us
231/// to retry step 3 with the `auth_key_aux_hash` as `retry_id`.
232pub enum FinishResult {
233    /// Handshake complete.
234    Done(Finished),
235    /// Server sent `dh_gen_retry`.  Call [`retry_step3`] with the returned
236    /// `retry_id` and the stored [`DhParamsForRetry`] from the previous Step3.
237    Retry {
238        /// The `auth_key_aux_hash` to embed as `retry_id` in the next attempt.
239        retry_id: i64,
240        /// DH parameters to feed back into [`retry_step3`].
241        dh_params: DhParamsForRetry,
242        /// Client nonce from the original step 1.
243        nonce: [u8; 16],
244        /// Server nonce from the ResPQ response.
245        server_nonce: [u8; 16],
246        /// Fresh nonce generated in step 2.
247        new_nonce: [u8; 32],
248    },
249}
250
251/// The final output of a successful auth key handshake.
252#[derive(Clone, Debug, PartialEq)]
253pub struct Finished {
254    /// The 256-byte Telegram authorization key.
255    pub auth_key: [u8; 256],
256    /// Clock skew in seconds relative to the server.
257    pub time_offset: i32,
258    /// Initial server salt.
259    pub first_salt: i64,
260}
261
262/// Generate a `req_pq_multi` request. Returns the request + opaque state.
263pub fn step1() -> Result<(ferogram_tl_types::functions::ReqPqMulti, Step1), Error> {
264    let mut buf = [0u8; 16];
265    getrandom::getrandom(&mut buf).expect("getrandom");
266    do_step1(&buf)
267}
268
269fn do_step1(random: &[u8; 16]) -> Result<(ferogram_tl_types::functions::ReqPqMulti, Step1), Error> {
270    let nonce = *random;
271    Ok((
272        ferogram_tl_types::functions::ReqPqMulti { nonce },
273        Step1 { nonce },
274    ))
275}
276
277/// Process `ResPQ` and generate `req_DH_params`.
278///
279/// `dc_id` must be the numerical DC id of the server we are connecting to
280/// (e.g. 1 ... 5).  It is embedded in the `PQInnerDataDc` payload so that
281/// Telegram can reject misrouted handshakes on non-DC2 endpoints.
282pub fn step2(
283    data: Step1,
284    response: ferogram_tl_types::enums::ResPq,
285    dc_id: i32,
286) -> Result<(ferogram_tl_types::functions::ReqDhParams, Step2), Error> {
287    let mut rnd = [0u8; 256];
288    getrandom::getrandom(&mut rnd).expect("getrandom");
289    do_step2(data, response, &rnd, dc_id)
290}
291
292/// Like `step2` but requests a temporary auth key (PFS).
293/// Uses p_q_inner_data_temp_dc instead of p_q_inner_data_dc.
294/// Caller must bind the resulting key via auth.bindTempAuthKey before any RPCs.
295pub fn step2_temp(
296    data: Step1,
297    response: ferogram_tl_types::enums::ResPq,
298    dc_id: i32,
299    expires_in: i32,
300) -> Result<(ferogram_tl_types::functions::ReqDhParams, Step2), Error> {
301    let mut rnd = [0u8; 256];
302    getrandom::getrandom(&mut rnd).expect("getrandom");
303    do_step2_temp(data, response, &rnd, dc_id, expires_in)
304}
305
306fn do_step2_temp(
307    data: Step1,
308    response: ferogram_tl_types::enums::ResPq,
309    random: &[u8; 256],
310    dc_id: i32,
311    expires_in: i32,
312) -> Result<(ferogram_tl_types::functions::ReqDhParams, Step2), Error> {
313    let Step1 { nonce } = data;
314    let ferogram_tl_types::enums::ResPq::ResPq(res_pq) = response;
315    check_nonce(&res_pq.nonce, &nonce)?;
316    if res_pq.pq.len() != 8 {
317        return Err(Error::InvalidPqSize {
318            size: res_pq.pq.len(),
319        });
320    }
321    let pq = u64::from_be_bytes(res_pq.pq.as_slice().try_into().unwrap());
322    let (p, q) = factorize(pq);
323    let mut new_nonce = [0u8; 32];
324    new_nonce.copy_from_slice(&random[..32]);
325    let rnd224: &[u8; 224] = random[32..].try_into().unwrap();
326    fn trim_be(v: u64) -> Vec<u8> {
327        let b = v.to_be_bytes();
328        let skip = b.iter().position(|&x| x != 0).unwrap_or(7);
329        b[skip..].to_vec()
330    }
331    let p_bytes = trim_be(p);
332    let q_bytes = trim_be(q);
333    let pq_inner = serialize_pq_inner_data_temp_dc(
334        &pq.to_be_bytes(),
335        &p_bytes,
336        &q_bytes,
337        &nonce,
338        &res_pq.server_nonce,
339        &new_nonce,
340        dc_id,
341        expires_in,
342    );
343    let fingerprint = res_pq
344        .server_public_key_fingerprints
345        .iter()
346        .copied()
347        .find(|&fp| key_for_fingerprint(fp).is_some())
348        .ok_or_else(|| Error::UnknownFingerprints {
349            fingerprints: res_pq.server_public_key_fingerprints.clone(),
350        })?;
351    let key = key_for_fingerprint(fingerprint).unwrap();
352    let ciphertext = rsa::encrypt_hashed(&pq_inner, &key, rnd224);
353    Ok((
354        ferogram_tl_types::functions::ReqDhParams {
355            nonce,
356            server_nonce: res_pq.server_nonce,
357            p: p_bytes,
358            q: q_bytes,
359            public_key_fingerprint: fingerprint,
360            encrypted_data: ciphertext,
361        },
362        Step2 {
363            nonce,
364            server_nonce: res_pq.server_nonce,
365            new_nonce,
366        },
367    ))
368}
369
370fn do_step2(
371    data: Step1,
372    response: ferogram_tl_types::enums::ResPq,
373    random: &[u8; 256],
374    dc_id: i32,
375) -> Result<(ferogram_tl_types::functions::ReqDhParams, Step2), Error> {
376    let Step1 { nonce } = data;
377
378    // ResPq has a single constructor: resPQ -> variant ResPq
379    let ferogram_tl_types::enums::ResPq::ResPq(res_pq) = response;
380
381    check_nonce(&res_pq.nonce, &nonce)?;
382
383    if res_pq.pq.len() != 8 {
384        return Err(Error::InvalidPqSize {
385            size: res_pq.pq.len(),
386        });
387    }
388
389    let pq = u64::from_be_bytes(res_pq.pq.as_slice().try_into().unwrap());
390    let (p, q) = factorize(pq);
391
392    let mut new_nonce = [0u8; 32];
393    new_nonce.copy_from_slice(&random[..32]);
394
395    // random[32..256] is 224 bytes for RSA padding
396    let rnd224: &[u8; 224] = random[32..].try_into().unwrap();
397
398    fn trim_be(v: u64) -> Vec<u8> {
399        let b = v.to_be_bytes();
400        let skip = b.iter().position(|&x| x != 0).unwrap_or(7);
401        b[skip..].to_vec()
402    }
403
404    let p_bytes = trim_be(p);
405    let q_bytes = trim_be(q);
406
407    // Serialize PQInnerDataDc (constructor 0xa9f55f95) manually.
408    // The legacy PQInnerData constructor (#83c95aec, no dc field) is rejected
409    // by Telegram servers for connections to non-DC2 endpoints.
410    let pq_inner = serialize_pq_inner_data_dc(
411        &pq.to_be_bytes(),
412        &p_bytes,
413        &q_bytes,
414        &nonce,
415        &res_pq.server_nonce,
416        &new_nonce,
417        dc_id,
418    );
419
420    let fingerprint = res_pq
421        .server_public_key_fingerprints
422        .iter()
423        .copied()
424        .find(|&fp| key_for_fingerprint(fp).is_some())
425        .ok_or_else(|| Error::UnknownFingerprints {
426            fingerprints: res_pq.server_public_key_fingerprints.clone(),
427        })?;
428
429    let key = key_for_fingerprint(fingerprint).unwrap();
430    let ciphertext = rsa::encrypt_hashed(&pq_inner, &key, rnd224);
431
432    Ok((
433        ferogram_tl_types::functions::ReqDhParams {
434            nonce,
435            server_nonce: res_pq.server_nonce,
436            p: p_bytes,
437            q: q_bytes,
438            public_key_fingerprint: fingerprint,
439            encrypted_data: ciphertext,
440        },
441        Step2 {
442            nonce,
443            server_nonce: res_pq.server_nonce,
444            new_nonce,
445        },
446    ))
447}
448
449/// Process `ServerDhParams` into a reusable [`DhParamsForRetry`] + send the
450/// first `set_client_DH_params` request.
451///
452/// `retry_id` should be 0 on the first call, or `auth_key_aux_hash` (returned
453/// by [`finish`] as [`FinishResult::Retry`]) on subsequent attempts.
454pub fn step3(
455    data: Step2,
456    response: ferogram_tl_types::enums::ServerDhParams,
457) -> Result<(ferogram_tl_types::functions::SetClientDhParams, Step3), Error> {
458    let mut rnd = [0u8; 272];
459    getrandom::getrandom(&mut rnd).expect("getrandom");
460    let now = SystemTime::now()
461        .duration_since(UNIX_EPOCH)
462        .unwrap()
463        .as_secs() as i32;
464    do_step3(data, response, &rnd, now, 0)
465}
466
467/// Re-run the client DH params generation after a `dh_gen_retry` response.
468/// Feed the `dh_params`, `nonce`, `server_nonce`, `new_nonce` from
469/// [`FinishResult::Retry`] and the `retry_id` (= `auth_key_aux_hash`).
470pub fn retry_step3(
471    dh_params: &DhParamsForRetry,
472    nonce: [u8; 16],
473    server_nonce: [u8; 16],
474    new_nonce: [u8; 32],
475    retry_id: i64,
476) -> Result<(ferogram_tl_types::functions::SetClientDhParams, Step3), Error> {
477    let mut rnd = [0u8; 272];
478    getrandom::getrandom(&mut rnd).expect("getrandom");
479    let now = SystemTime::now()
480        .duration_since(UNIX_EPOCH)
481        .unwrap()
482        .as_secs() as i32;
483    generate_client_dh_params(
484        dh_params,
485        nonce,
486        server_nonce,
487        new_nonce,
488        retry_id,
489        &rnd,
490        now,
491    )
492}
493
494fn generate_client_dh_params(
495    dh: &DhParamsForRetry,
496    nonce: [u8; 16],
497    server_nonce: [u8; 16],
498    new_nonce: [u8; 32],
499    retry_id: i64,
500    random: &[u8; 272],
501    now: i32,
502) -> Result<(ferogram_tl_types::functions::SetClientDhParams, Step3), Error> {
503    let dh_prime = BigUint::from_bytes_be(&dh.dh_prime);
504    let g = BigUint::from(dh.g);
505    let g_a = BigUint::from_bytes_be(&dh.g_a);
506    let time_offset = dh.server_time - now;
507
508    let b = BigUint::from_bytes_be(&random[..256]);
509    let g_b = g.modpow(&b, &dh_prime);
510
511    let one = BigUint::from(1u32);
512    let safety = one.clone() << (2048 - 64);
513    check_g_in_range(&g_b, &one, &(&dh_prime - &one))?;
514    check_g_in_range(&g_b, &safety, &(&dh_prime - &safety))?;
515
516    let client_dh_inner = ferogram_tl_types::enums::ClientDhInnerData::ClientDhInnerData(
517        ferogram_tl_types::types::ClientDhInnerData {
518            nonce,
519            server_nonce,
520            retry_id,
521            g_b: g_b.to_bytes_be(),
522        },
523    )
524    .to_bytes();
525
526    let digest: [u8; 20] = {
527        let mut sha = Sha1::new();
528        sha.update(&client_dh_inner);
529        sha.finalize().into()
530    };
531
532    let pad_len = (16 - ((20 + client_dh_inner.len()) % 16)) % 16;
533    let rnd16 = &random[256..256 + pad_len.min(16)];
534
535    let mut hashed = Vec::with_capacity(20 + client_dh_inner.len() + pad_len);
536    hashed.extend_from_slice(&digest);
537    hashed.extend_from_slice(&client_dh_inner);
538    hashed.extend_from_slice(&rnd16[..pad_len]);
539
540    let key: [u8; 32] = dh.aes_key;
541    let iv: [u8; 32] = dh.aes_iv;
542    aes::ige_encrypt(&mut hashed, &key, &iv);
543
544    // Compute auth_key = g_a^b mod dh_prime for this attempt.
545    let mut auth_key_bytes = [0u8; 256];
546    let gab_bytes = g_a.modpow(&b, &dh_prime).to_bytes_be();
547    let skip = 256 - gab_bytes.len();
548    auth_key_bytes[skip..].copy_from_slice(&gab_bytes);
549
550    Ok((
551        ferogram_tl_types::functions::SetClientDhParams {
552            nonce,
553            server_nonce,
554            encrypted_data: hashed,
555        },
556        Step3 {
557            nonce,
558            server_nonce,
559            new_nonce,
560            time_offset,
561            auth_key: auth_key_bytes,
562            dh_params: dh.clone(),
563        },
564    ))
565}
566
567fn do_step3(
568    data: Step2,
569    response: ferogram_tl_types::enums::ServerDhParams,
570    random: &[u8; 272],
571    now: i32,
572    retry_id: i64,
573) -> Result<(ferogram_tl_types::functions::SetClientDhParams, Step3), Error> {
574    let Step2 {
575        nonce,
576        server_nonce,
577        new_nonce,
578    } = data;
579
580    let mut server_dh_ok = match response {
581        ferogram_tl_types::enums::ServerDhParams::Fail(f) => {
582            check_nonce(&f.nonce, &nonce)?;
583            check_server_nonce(&f.server_nonce, &server_nonce)?;
584            return Err(Error::DhParamsFail);
585        }
586        ferogram_tl_types::enums::ServerDhParams::Ok(x) => x,
587    };
588
589    check_nonce(&server_dh_ok.nonce, &nonce)?;
590    check_server_nonce(&server_dh_ok.server_nonce, &server_nonce)?;
591
592    if server_dh_ok.encrypted_answer.len() % 16 != 0 {
593        return Err(Error::EncryptedResponseNotPadded {
594            len: server_dh_ok.encrypted_answer.len(),
595        });
596    }
597
598    let (key_arr, iv_arr) = generate_key_data_from_nonce(&server_nonce, &new_nonce);
599    aes::ige_decrypt(&mut server_dh_ok.encrypted_answer, &key_arr, &iv_arr);
600    let plain = server_dh_ok.encrypted_answer;
601
602    let got_hash: [u8; 20] = plain[..20].try_into().unwrap();
603    let mut cursor = Cursor::from_slice(&plain[20..]);
604
605    let inner = match ferogram_tl_types::enums::ServerDhInnerData::deserialize(&mut cursor) {
606        Ok(ferogram_tl_types::enums::ServerDhInnerData::ServerDhInnerData(x)) => x,
607        Err(e) => return Err(Error::InvalidDhInnerData { error: e }),
608    };
609
610    let expected_hash: [u8; 20] = {
611        let mut sha = Sha1::new();
612        sha.update(&plain[20..20 + cursor.pos()]);
613        sha.finalize().into()
614    };
615    if got_hash != expected_hash {
616        return Err(Error::InvalidAnswerHash {
617            got: got_hash,
618            expected: expected_hash,
619        });
620    }
621
622    check_nonce(&inner.nonce, &nonce)?;
623    check_server_nonce(&inner.server_nonce, &server_nonce)?;
624
625    check_p_and_g(&inner.dh_prime, inner.g as u32)
626        .map_err(|source| Error::InvalidDhPrime { source })?;
627
628    // Validate g_a range.
629    let dh_prime_bn = BigUint::from_bytes_be(&inner.dh_prime);
630    let one = BigUint::from(1u32);
631    let g_a_bn = BigUint::from_bytes_be(&inner.g_a);
632    let safety = one.clone() << (2048 - 64);
633    check_g_in_range(&g_a_bn, &safety, &(&dh_prime_bn - &safety))?;
634
635    let dh = DhParamsForRetry {
636        dh_prime: inner.dh_prime,
637        g: inner.g as u32,
638        g_a: inner.g_a,
639        server_time: inner.server_time,
640        aes_key: key_arr,
641        aes_iv: iv_arr,
642    };
643
644    generate_client_dh_params(&dh, nonce, server_nonce, new_nonce, retry_id, random, now)
645}
646
647/// Finalise the handshake.
648///
649/// Returns [`FinishResult::Done`] on success or [`FinishResult::Retry`] when
650/// the server sends `dh_gen_retry` (up to 5 attempts are typical). On retry,
651/// call [`retry_step3`] with the returned fields, send the new request, receive
652/// the answer, then call `finish` again.
653pub fn finish(
654    data: Step3,
655    response: ferogram_tl_types::enums::SetClientDhParamsAnswer,
656) -> Result<FinishResult, Error> {
657    let Step3 {
658        nonce,
659        server_nonce,
660        new_nonce,
661        time_offset,
662        auth_key: auth_key_bytes,
663        dh_params,
664    } = data;
665
666    struct DhData {
667        nonce: [u8; 16],
668        server_nonce: [u8; 16],
669        hash: [u8; 16],
670        num: u8,
671    }
672
673    let dh = match response {
674        // Variant names come from the constructor names: dh_gen_ok -> DhGenOk, etc.
675        ferogram_tl_types::enums::SetClientDhParamsAnswer::DhGenOk(x) => DhData {
676            nonce: x.nonce,
677            server_nonce: x.server_nonce,
678            hash: x.new_nonce_hash1,
679            num: 1,
680        },
681        ferogram_tl_types::enums::SetClientDhParamsAnswer::DhGenRetry(x) => DhData {
682            nonce: x.nonce,
683            server_nonce: x.server_nonce,
684            hash: x.new_nonce_hash2,
685            num: 2,
686        },
687        ferogram_tl_types::enums::SetClientDhParamsAnswer::DhGenFail(x) => DhData {
688            nonce: x.nonce,
689            server_nonce: x.server_nonce,
690            hash: x.new_nonce_hash3,
691            num: 3,
692        },
693    };
694
695    check_nonce(&dh.nonce, &nonce)?;
696    check_server_nonce(&dh.server_nonce, &server_nonce)?;
697
698    let auth_key = AuthKey::from_bytes(auth_key_bytes);
699    let expected_hash = auth_key.calc_new_nonce_hash(&new_nonce, dh.num);
700    check_new_nonce_hash(&dh.hash, &expected_hash)?;
701
702    let first_salt = {
703        let mut buf = [0u8; 8];
704        for ((dst, a), b) in buf.iter_mut().zip(&new_nonce[..8]).zip(&server_nonce[..8]) {
705            *dst = a ^ b;
706        }
707        i64::from_le_bytes(buf)
708    };
709
710    match dh.num {
711        1 => Ok(FinishResult::Done(Finished {
712            auth_key: auth_key.to_bytes(),
713            time_offset,
714            first_salt,
715        })),
716        2 => {
717            // dh_gen_retry: compute auth_key_aux_hash = SHA1(auth_key)[0..8] as i64 LE.
718            let aux_hash: [u8; 20] = {
719                let mut sha = Sha1::new();
720                sha.update(auth_key.to_bytes());
721                sha.finalize().into()
722            };
723            let retry_id = i64::from_le_bytes(aux_hash[..8].try_into().unwrap());
724            Ok(FinishResult::Retry {
725                retry_id,
726                dh_params,
727                nonce,
728                server_nonce,
729                new_nonce,
730            })
731        }
732        _ => Err(Error::DhGenFail),
733    }
734}
735
736fn check_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
737    if got == expected {
738        Ok(())
739    } else {
740        Err(Error::InvalidNonce {
741            got: *got,
742            expected: *expected,
743        })
744    }
745}
746fn check_server_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
747    if got == expected {
748        Ok(())
749    } else {
750        Err(Error::InvalidServerNonce {
751            got: *got,
752            expected: *expected,
753        })
754    }
755}
756fn check_new_nonce_hash(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
757    if got == expected {
758        Ok(())
759    } else {
760        Err(Error::InvalidNewNonceHash {
761            got: *got,
762            expected: *expected,
763        })
764    }
765}
766fn check_g_in_range(val: &BigUint, lo: &BigUint, hi: &BigUint) -> Result<(), Error> {
767    if lo < val && val < hi {
768        Ok(())
769    } else {
770        Err(Error::GParameterOutOfRange {
771            value: val.clone(),
772            low: lo.clone(),
773            high: hi.clone(),
774        })
775    }
776}
777
778/// RSA key by server fingerprint. Includes both production and test DC keys.
779#[allow(clippy::unreadable_literal)]
780pub fn key_for_fingerprint(fp: i64) -> Option<rsa::Key> {
781    Some(match fp {
782        // Production DC key (fingerprint -3414540481677951611)
783        -3414540481677951611 => rsa::Key::new(
784            "29379598170669337022986177149456128565388431120058863768162556424047512191330847455146576344487764408661701890505066208632169112269581063774293102577308490531282748465986139880977280302242772832972539403531316010870401287642763009136156734339538042419388722777357134487746169093539093850251243897188928735903389451772730245253062963384108812842079887538976360465290946139638691491496062099570836476454855996319192747663615955633778034897140982517446405334423701359108810182097749467210509584293428076654573384828809574217079944388301239431309115013843331317877374435868468779972014486325557807783825502498215169806323",
785            "65537",
786        )?,
787        // Test DC key (fingerprint -5595554452916591101)
788        -5595554452916591101 => rsa::Key::new(
789            "25342889448840415564971689590713473206898847759084779052582026594546022463853940585885215951168491965708222649399180603818074200620463776135424884632162512403163793083921641631564740959529419359595852941166848940585952337613333022396096584117954892216031229237302943701877588456738335398602461675225081791820393153757504952636234951323237820036543581047826906120927972487366805292115792231423684261262330394324750785450942589751755390156647751460719351439969059949569615302809050721500330239005077889855323917509948255722081644689442127297605422579707142646660768825302832201908302295573257427896031830742328565032949",
790            "65537",
791        )?,
792        _ => return None,
793    })
794}