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