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