Skip to main content

layer_mtproto/
authentication.rs

1//! Sans-IO MTProto authorization key generation.
2//!
3//! # Flow
4//!
5//! ```text
6//! let (req, s1) = authentication::step1()?;
7//! // send req, receive resp
8//! let (req, s2) = authentication::step2(s1, resp)?;
9//! // send req, receive resp
10//! let (req, s3) = authentication::step3(s2, resp)?;
11//! // send req, receive resp
12//! let done = authentication::finish(s3, resp)?;
13//! // done.auth_key is ready
14//! ```
15
16use std::fmt;
17use std::time::{SystemTime, UNIX_EPOCH};
18
19use layer_crypto::{AuthKey, aes, check_p_and_g, factorize, generate_key_data_from_nonce, rsa};
20use layer_tl_types::{Cursor, Deserializable, Serializable};
21use num_bigint::{BigUint, ToBigUint};
22use sha1::{Digest, Sha1};
23
24// ─── Error ────────────────────────────────────────────────────────────────────
25
26/// Errors that can occur during auth key generation.
27#[allow(missing_docs)]
28#[derive(Clone, Debug, PartialEq)]
29pub enum Error {
30    InvalidNonce         { got: [u8; 16], expected: [u8; 16] },
31    InvalidPqSize        { size: usize },
32    UnknownFingerprints  { fingerprints: Vec<i64> },
33    DhParamsFail,
34    InvalidServerNonce   { got: [u8; 16], expected: [u8; 16] },
35    EncryptedResponseNotPadded { len: usize },
36    InvalidDhInnerData   { error: layer_tl_types::deserialize::Error },
37    InvalidDhPrime       { source: layer_crypto::DhError },
38    GParameterOutOfRange { value: BigUint, low: BigUint, high: BigUint },
39    DhGenRetry,
40    DhGenFail,
41    InvalidAnswerHash    { got: [u8; 20], expected: [u8; 20] },
42    InvalidNewNonceHash  { got: [u8; 16], expected: [u8; 16] },
43}
44
45impl std::error::Error for Error {}
46
47impl fmt::Display for Error {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::InvalidNonce { got, expected }
51                => write!(f, "nonce mismatch: got {got:?}, expected {expected:?}"),
52            Self::InvalidPqSize { size }
53                => write!(f, "pq size {size} invalid (expected 8)"),
54            Self::UnknownFingerprints { fingerprints }
55                => write!(f, "no known fingerprint in {fingerprints:?}"),
56            Self::DhParamsFail
57                => write!(f, "server returned DH params failure"),
58            Self::InvalidServerNonce { got, expected }
59                => write!(f, "server_nonce mismatch: got {got:?}, expected {expected:?}"),
60            Self::EncryptedResponseNotPadded { len }
61                => write!(f, "encrypted answer len {len} is not 16-byte aligned"),
62            Self::InvalidDhInnerData { error }
63                => write!(f, "DH inner data deserialization error: {error}"),
64            Self::InvalidDhPrime { source }
65                => write!(f, "DH prime/generator validation failed: {source}"),
66            Self::GParameterOutOfRange { value, low, high }
67                => write!(f, "g={value} not in range ({low}, {high})"),
68            Self::DhGenRetry  => write!(f, "DH gen retry requested"),
69            Self::DhGenFail   => write!(f, "DH gen failed"),
70            Self::InvalidAnswerHash { got, expected }
71                => write!(f, "answer hash mismatch: got {got:?}, expected {expected:?}"),
72            Self::InvalidNewNonceHash { got, expected }
73                => write!(f, "new nonce hash mismatch: got {got:?}, expected {expected:?}"),
74        }
75    }
76}
77
78// ─── Step state ──────────────────────────────────────────────────────────────
79
80/// State after step 1.
81pub struct Step1 { nonce: [u8; 16] }
82
83/// State after step 2.
84pub struct Step2 {
85    nonce:        [u8; 16],
86    server_nonce: [u8; 16],
87    new_nonce:    [u8; 32],
88}
89
90/// State after step 3.
91pub struct Step3 {
92    nonce:        [u8; 16],
93    server_nonce: [u8; 16],
94    new_nonce:    [u8; 32],
95    gab:          BigUint,
96    time_offset:  i32,
97}
98
99/// The final output of a successful auth key handshake.
100#[derive(Clone, Debug, PartialEq)]
101pub struct Finished {
102    /// The 256-byte Telegram authorization key.
103    pub auth_key:    [u8; 256],
104    /// Clock skew in seconds relative to the server.
105    pub time_offset: i32,
106    /// Initial server salt.
107    pub first_salt:  i64,
108}
109
110// ─── Step 1: req_pq_multi ────────────────────────────────────────────────────
111
112/// Generate a `req_pq_multi` request. Returns the request + opaque state.
113pub fn step1() -> Result<(layer_tl_types::functions::ReqPqMulti, Step1), Error> {
114    let mut buf = [0u8; 16];
115    getrandom::getrandom(&mut buf).expect("getrandom");
116    do_step1(&buf)
117}
118
119fn do_step1(random: &[u8; 16]) -> Result<(layer_tl_types::functions::ReqPqMulti, Step1), Error> {
120    let nonce = *random;
121    Ok((layer_tl_types::functions::ReqPqMulti { nonce }, Step1 { nonce }))
122}
123
124// ─── Step 2: req_DH_params ───────────────────────────────────────────────────
125
126/// Process `ResPQ` and generate `req_DH_params`.
127pub fn step2(
128    data:     Step1,
129    response: layer_tl_types::enums::ResPq,
130) -> Result<(layer_tl_types::functions::ReqDhParams, Step2), Error> {
131    let mut rnd = [0u8; 256];
132    getrandom::getrandom(&mut rnd).expect("getrandom");
133    do_step2(data, response, &rnd)
134}
135
136fn do_step2(
137    data:     Step1,
138    response: layer_tl_types::enums::ResPq,
139    random:   &[u8; 256],
140) -> Result<(layer_tl_types::functions::ReqDhParams, Step2), Error> {
141    let Step1 { nonce } = data;
142
143    // ResPq has a single constructor: resPQ → variant ResPq
144    let layer_tl_types::enums::ResPq::ResPq(res_pq) = response;
145
146    check_nonce(&res_pq.nonce, &nonce)?;
147
148    if res_pq.pq.len() != 8 {
149        return Err(Error::InvalidPqSize { size: res_pq.pq.len() });
150    }
151
152    let pq = u64::from_be_bytes(res_pq.pq.as_slice().try_into().unwrap());
153    let (p, q) = factorize(pq);
154
155    let mut new_nonce = [0u8; 32];
156    new_nonce.copy_from_slice(&random[..32]);
157
158    // random[32..256] is 224 bytes for RSA padding
159    let rnd224: &[u8; 224] = random[32..].try_into().unwrap();
160
161    fn trim_be(v: u64) -> Vec<u8> {
162        let b = v.to_be_bytes();
163        let skip = b.iter().position(|&x| x != 0).unwrap_or(7);
164        b[skip..].to_vec()
165    }
166
167    let p_bytes = trim_be(p);
168    let q_bytes = trim_be(q);
169
170    // Build PQInnerData using the first (non-DC, non-temp) constructor
171    // variant name: PQInnerData (same as type name since constructor name == type name)
172    let pq_inner = layer_tl_types::enums::PQInnerData::PQInnerData(
173        layer_tl_types::types::PQInnerData {
174            pq: pq.to_be_bytes().to_vec(),
175            p: p_bytes.clone(),
176            q: q_bytes.clone(),
177            nonce,
178            server_nonce: res_pq.server_nonce,
179            new_nonce,
180        }
181    ).to_bytes();
182
183    let fingerprint = res_pq.server_public_key_fingerprints
184        .iter()
185        .copied()
186        .find(|&fp| key_for_fingerprint(fp).is_some())
187        .ok_or_else(|| Error::UnknownFingerprints {
188            fingerprints: res_pq.server_public_key_fingerprints.clone()
189        })?;
190
191    let key = key_for_fingerprint(fingerprint).unwrap();
192    let ciphertext = rsa::encrypt_hashed(&pq_inner, &key, rnd224);
193
194    Ok((
195        layer_tl_types::functions::ReqDhParams {
196            nonce,
197            server_nonce: res_pq.server_nonce,
198            p: p_bytes,
199            q: q_bytes,
200            public_key_fingerprint: fingerprint,
201            encrypted_data: ciphertext,
202        },
203        Step2 { nonce, server_nonce: res_pq.server_nonce, new_nonce },
204    ))
205}
206
207// ─── Step 3: set_client_DH_params ────────────────────────────────────────────
208
209/// Process `ServerDhParams` and generate `set_client_DH_params`.
210pub fn step3(
211    data:     Step2,
212    response: layer_tl_types::enums::ServerDhParams,
213) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
214    let mut rnd = [0u8; 272]; // 256 for DH b, 16 for padding
215    getrandom::getrandom(&mut rnd).expect("getrandom");
216    let now = SystemTime::now()
217        .duration_since(UNIX_EPOCH).unwrap().as_secs() as i32;
218    do_step3(data, response, &rnd, now)
219}
220
221fn do_step3(
222    data:     Step2,
223    response: layer_tl_types::enums::ServerDhParams,
224    random:   &[u8; 272],
225    now:      i32,
226) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
227    let Step2 { nonce, server_nonce, new_nonce } = data;
228
229    let mut server_dh_ok = match response {
230        layer_tl_types::enums::ServerDhParams::Fail(f) => {
231            check_nonce(&f.nonce, &nonce)?;
232            check_server_nonce(&f.server_nonce, &server_nonce)?;
233            // Verify new_nonce_hash
234            let digest: [u8; 20] = {
235                let mut sha = Sha1::new();
236                sha.update(new_nonce);
237                sha.finalize().into()
238            };
239            let mut expected_hash = [0u8; 16];
240            expected_hash.copy_from_slice(&digest[4..]);
241            check_new_nonce_hash(&f.new_nonce_hash, &expected_hash)?;
242            return Err(Error::DhParamsFail);
243        }
244        layer_tl_types::enums::ServerDhParams::Ok(x) => x,
245    };
246
247    check_nonce(&server_dh_ok.nonce, &nonce)?;
248    check_server_nonce(&server_dh_ok.server_nonce, &server_nonce)?;
249
250    if server_dh_ok.encrypted_answer.len() % 16 != 0 {
251        return Err(Error::EncryptedResponseNotPadded { len: server_dh_ok.encrypted_answer.len() });
252    }
253
254    let (key, iv) = generate_key_data_from_nonce(&server_nonce, &new_nonce);
255    aes::ige_decrypt(&mut server_dh_ok.encrypted_answer, &key, &iv);
256    let plain = server_dh_ok.encrypted_answer;
257
258    let got_hash: [u8; 20] = plain[..20].try_into().unwrap();
259    let mut cursor = Cursor::from_slice(&plain[20..]);
260
261    // ServerDhInnerData has single constructor server_DH_inner_data
262    // variant name = ServerDhInnerData (full name, since it equals type name)
263    let inner = match layer_tl_types::enums::ServerDhInnerData::deserialize(&mut cursor) {
264        Ok(layer_tl_types::enums::ServerDhInnerData::ServerDhInnerData(x)) => x,
265        Err(e) => return Err(Error::InvalidDhInnerData { error: e }),
266    };
267
268    let expected_hash: [u8; 20] = {
269        let mut sha = Sha1::new();
270        sha.update(&plain[20..20 + cursor.pos()]);
271        sha.finalize().into()
272    };
273    if got_hash != expected_hash {
274        return Err(Error::InvalidAnswerHash { got: got_hash, expected: expected_hash });
275    }
276
277    check_nonce(&inner.nonce, &nonce)?;
278    check_server_nonce(&inner.server_nonce, &server_nonce)?;
279
280    // G-53: validate server-supplied DH prime and generator against MTProto spec.
281    check_p_and_g(&inner.dh_prime, inner.g as u32)
282        .map_err(|source| Error::InvalidDhPrime { source })?;
283
284    let dh_prime = BigUint::from_bytes_be(&inner.dh_prime);
285    let g = inner.g.to_biguint().unwrap();
286    let g_a = BigUint::from_bytes_be(&inner.g_a);
287    let time_offset = inner.server_time - now;
288
289    let b = BigUint::from_bytes_be(&random[..256]);
290    let g_b = g.modpow(&b, &dh_prime);
291    let gab = g_a.modpow(&b, &dh_prime);
292
293    // Validate DH parameters
294    let one = BigUint::from(1u32);
295    check_g_in_range(&g,   &one, &(&dh_prime - &one))?;
296    check_g_in_range(&g_a, &one, &(&dh_prime - &one))?;
297    check_g_in_range(&g_b, &one, &(&dh_prime - &one))?;
298    let safety = one.clone() << (2048 - 64);
299    check_g_in_range(&g_a, &safety, &(&dh_prime - &safety))?;
300    check_g_in_range(&g_b, &safety, &(&dh_prime - &safety))?;
301
302    // ClientDhInnerData has single constructor client_DH_inner_data
303    // variant name = ClientDhInnerData
304    let client_dh_inner = layer_tl_types::enums::ClientDhInnerData::ClientDhInnerData(
305        layer_tl_types::types::ClientDhInnerData {
306            nonce,
307            server_nonce,
308            retry_id: 0,
309            g_b: g_b.to_bytes_be(),
310        }
311    ).to_bytes();
312
313    let digest: [u8; 20] = {
314        let mut sha = Sha1::new();
315        sha.update(&client_dh_inner);
316        sha.finalize().into()
317    };
318
319    let pad_len = (16 - ((20 + client_dh_inner.len()) % 16)) % 16;
320    let rnd16 = &random[256..256 + pad_len.min(16)];
321
322    let mut hashed = Vec::with_capacity(20 + client_dh_inner.len() + pad_len);
323    hashed.extend_from_slice(&digest);
324    hashed.extend_from_slice(&client_dh_inner);
325    hashed.extend_from_slice(&rnd16[..pad_len]);
326
327    aes::ige_encrypt(&mut hashed, &key, &iv);
328
329    Ok((
330        layer_tl_types::functions::SetClientDhParams {
331            nonce,
332            server_nonce,
333            encrypted_data: hashed,
334        },
335        Step3 { nonce, server_nonce, new_nonce, gab, time_offset },
336    ))
337}
338
339// ─── finish: create_key ──────────────────────────────────────────────────────
340
341/// Finalise the handshake. Returns the ready [`Finished`] on success.
342pub fn finish(
343    data:     Step3,
344    response: layer_tl_types::enums::SetClientDhParamsAnswer,
345) -> Result<Finished, Error> {
346    let Step3 { nonce, server_nonce, new_nonce, gab, time_offset } = data;
347
348    struct DhData { nonce: [u8; 16], server_nonce: [u8; 16], hash: [u8; 16], num: u8 }
349
350    let dh = match response {
351        // Variant names come from the constructor names: dh_gen_ok → DhGenOk, etc.
352        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenOk(x)    =>
353            DhData { nonce: x.nonce, server_nonce: x.server_nonce, hash: x.new_nonce_hash1, num: 1 },
354        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenRetry(x) =>
355            DhData { nonce: x.nonce, server_nonce: x.server_nonce, hash: x.new_nonce_hash2, num: 2 },
356        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenFail(x)  =>
357            DhData { nonce: x.nonce, server_nonce: x.server_nonce, hash: x.new_nonce_hash3, num: 3 },
358    };
359
360    check_nonce(&dh.nonce, &nonce)?;
361    check_server_nonce(&dh.server_nonce, &server_nonce)?;
362
363    let mut key_bytes = [0u8; 256];
364    let gab_bytes = gab.to_bytes_be();
365    let skip = 256 - gab_bytes.len();
366    key_bytes[skip..].copy_from_slice(&gab_bytes);
367
368    let auth_key = AuthKey::from_bytes(key_bytes);
369    let expected_hash = auth_key.calc_new_nonce_hash(&new_nonce, dh.num);
370    check_new_nonce_hash(&dh.hash, &expected_hash)?;
371
372    let first_salt = {
373        let mut buf = [0u8; 8];
374        for ((dst, a), b) in buf.iter_mut().zip(&new_nonce[..8]).zip(&server_nonce[..8]) {
375            *dst = a ^ b;
376        }
377        i64::from_le_bytes(buf)
378    };
379
380    match dh.num {
381        1 => Ok(Finished { auth_key: auth_key.to_bytes(), time_offset, first_salt }),
382        2 => Err(Error::DhGenRetry),
383        _ => Err(Error::DhGenFail),
384    }
385}
386
387// ─── Helpers ─────────────────────────────────────────────────────────────────
388
389fn check_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
390    if got == expected { Ok(()) } else {
391        Err(Error::InvalidNonce { got: *got, expected: *expected })
392    }
393}
394fn check_server_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
395    if got == expected { Ok(()) } else {
396        Err(Error::InvalidServerNonce { got: *got, expected: *expected })
397    }
398}
399fn check_new_nonce_hash(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
400    if got == expected { Ok(()) } else {
401        Err(Error::InvalidNewNonceHash { got: *got, expected: *expected })
402    }
403}
404fn check_g_in_range(val: &BigUint, lo: &BigUint, hi: &BigUint) -> Result<(), Error> {
405    if lo < val && val < hi { Ok(()) } else {
406        Err(Error::GParameterOutOfRange { value: val.clone(), low: lo.clone(), high: hi.clone() })
407    }
408}
409
410/// RSA key by server fingerprint. Includes both production and test DC keys.
411#[allow(clippy::unreadable_literal)]
412pub fn key_for_fingerprint(fp: i64) -> Option<rsa::Key> {
413    Some(match fp {
414        // Production DC key (fingerprint -3414540481677951611)
415        -3414540481677951611 => rsa::Key::new(
416            "29379598170669337022986177149456128565388431120058863768162556424047512191330847455146576344487764408661701890505066208632169112269581063774293102577308490531282748465986139880977280302242772832972539403531316010870401287642763009136156734339538042419388722777357134487746169093539093850251243897188928735903389451772730245253062963384108812842079887538976360465290946139638691491496062099570836476454855996319192747663615955633778034897140982517446405334423701359108810182097749467210509584293428076654573384828809574217079944388301239431309115013843331317877374435868468779972014486325557807783825502498215169806323",
417            "65537"
418        )?,
419        // Test DC key (fingerprint -5595554452916591101)
420        -5595554452916591101 => rsa::Key::new(
421            "25342889448840415564971689590713473206898847759084779052582026594546022463853940585885215951168491965708222649399180603818074200620463776135424884632162512403163793083921641631564740959529419359595852941166848940585952337613333022396096584117954892216031229237302943701877588456738335398602461675225081791820393153757504952636234951323237820036543581047826906120927972487366805292115792231423684261262330394324750785450942589751755390156647751460719351439969059949569615302809050721500330239005077889855323917509948255722081644689442127297605422579707142646660768825302832201908302295573257427896031830742328565032949",
422            "65537"
423        )?,
424        _ => return None,
425    })
426}