Skip to main content

ferogram_mtproto/
authentication.rs

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