Skip to main content

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