Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file mtproto_auth.c
6 : * @brief MTProto DH auth key generation.
7 : *
8 : * Implements the 8-step DH key exchange to generate an auth_key.
9 : * Uses: PQ factorization (Pollard's rho), RSA_PAD, AES-IGE, SHA-1/SHA-256.
10 : */
11 :
12 : #include "mtproto_auth.h"
13 : #include "crypto.h"
14 : #include "ige_aes.h"
15 : #include "mtproto_rpc.h"
16 : #include "tl_serial.h"
17 : #include "telegram_server_key.h"
18 : #include "logger.h"
19 : #include "raii.h"
20 :
21 : #include <stdlib.h>
22 : #include <string.h>
23 :
24 : /* ---- TL Constructor IDs ---- */
25 : #define CRC_req_pq_multi 0xbe7e8ef1
26 : #define CRC_resPQ 0x05162463
27 : #define CRC_p_q_inner_data 0x83c95aec /* MTProto 1.0, no dc field */
28 : #define CRC_p_q_inner_data_dc 0xa9f55f95 /* MTProto 2.0, with dc field */
29 : #define CRC_req_DH_params 0xd712e4be
30 : #define CRC_server_DH_params_ok 0xd0e8075c
31 : #define CRC_server_DH_inner_data 0xb5890dba
32 : #define CRC_client_DH_inner_data 0x6643b654
33 : #define CRC_set_client_DH_params 0xf5045f1f
34 : #define CRC_dh_gen_ok 0x3bcbf734
35 : #define CRC_dh_gen_retry 0x46dc1fb9
36 : #define CRC_dh_gen_fail 0xa69dae02
37 :
38 : /* Maximum number of RSA fingerprints accepted in a ResPQ vector.
39 : * Telegram uses ≤8 in practice; cap at 64 to reject DoS from untrusted servers
40 : * during the unauthenticated DH phase. */
41 : #define MAX_FP_COUNT 64
42 :
43 : /* ---- Big-endian byte helpers ---- */
44 :
45 : /** Encode uint64 as big-endian bytes, stripping leading zeros. */
46 4 : static size_t uint64_to_be(uint64_t val, uint8_t *out) {
47 : uint8_t tmp[8];
48 36 : for (int i = 7; i >= 0; i--) {
49 32 : tmp[i] = (uint8_t)(val & 0xFF);
50 32 : val >>= 8;
51 : }
52 : /* Skip leading zeros */
53 4 : size_t start = 0;
54 32 : while (start < 7 && tmp[start] == 0) start++;
55 4 : size_t len = 8 - start;
56 4 : memcpy(out, tmp + start, len);
57 4 : return len;
58 : }
59 :
60 : /** Encode uint32 as big-endian bytes, stripping leading zeros. */
61 9 : static size_t uint32_to_be(uint32_t val, uint8_t *out) {
62 : uint8_t tmp[4];
63 9 : tmp[0] = (uint8_t)((val >> 24) & 0xFF);
64 9 : tmp[1] = (uint8_t)((val >> 16) & 0xFF);
65 9 : tmp[2] = (uint8_t)((val >> 8) & 0xFF);
66 9 : tmp[3] = (uint8_t)( val & 0xFF);
67 9 : size_t start = 0;
68 36 : while (start < 3 && tmp[start] == 0) start++;
69 9 : size_t len = 4 - start;
70 9 : memcpy(out, tmp + start, len);
71 9 : return len;
72 : }
73 :
74 : /** Decode big-endian bytes to uint64. */
75 7 : static uint64_t be_to_uint64(const uint8_t *data, size_t len) {
76 7 : uint64_t val = 0;
77 21 : for (size_t i = 0; i < len; i++) {
78 14 : val = (val << 8) | data[i];
79 : }
80 7 : return val;
81 : }
82 :
83 : /* ---- RSA_PAD (MTProto 2.0 spec, core.telegram.org/mtproto/auth_key) ---- */
84 :
85 : /*
86 : * MTProto 1.0 RSA encryption: SHA1(data) || data || random_padding → 255 bytes,
87 : * prepend zero byte → 256 bytes, then RSA_NO_PADDING encrypt.
88 : * Used with p_q_inner_data (no dc field) — accepted by all production DCs.
89 : */
90 4 : static int rsa_sha1_encrypt(CryptoRsaKey *rsa_key,
91 : const uint8_t *data, size_t data_len,
92 : uint8_t *out, size_t *out_len) {
93 4 : if (!rsa_key || !data || !out || !out_len) return -1;
94 4 : if (data_len > 235) return -1; /* SHA1(20) + data + padding = 255 bytes max */
95 :
96 : uint8_t to_encrypt[255];
97 4 : crypto_sha1(data, data_len, to_encrypt); /* 20 bytes SHA1 */
98 4 : memcpy(to_encrypt + 20, data, data_len); /* data */
99 4 : crypto_rand_bytes(to_encrypt + 20 + data_len,
100 : 235 - data_len); /* random padding */
101 :
102 : /* Prepend one zero byte so input is 256 bytes (numerically < modulus). */
103 : uint8_t rsa_input[256];
104 4 : rsa_input[0] = 0;
105 4 : memcpy(rsa_input + 1, to_encrypt, 255);
106 :
107 4 : return crypto_rsa_public_encrypt(rsa_key, rsa_input, 256, out, out_len);
108 : }
109 :
110 : /* ---- DH temp key derivation ---- */
111 :
112 3 : static void dh_derive_temp_aes(const uint8_t new_nonce[32],
113 : const uint8_t server_nonce[16],
114 : uint8_t *tmp_aes_key, /* 32 bytes */
115 : uint8_t *tmp_aes_iv) /* 32 bytes */
116 : {
117 : uint8_t buf[64];
118 :
119 : /* SHA1(new_nonce + server_nonce) */
120 3 : memcpy(buf, new_nonce, 32);
121 3 : memcpy(buf + 32, server_nonce, 16);
122 : uint8_t sha1_a[20];
123 3 : crypto_sha1(buf, 48, sha1_a);
124 :
125 : /* SHA1(server_nonce + new_nonce) */
126 3 : memcpy(buf, server_nonce, 16);
127 3 : memcpy(buf + 16, new_nonce, 32);
128 : uint8_t sha1_b[20];
129 3 : crypto_sha1(buf, 48, sha1_b);
130 :
131 : /* tmp_aes_key = sha1_a(20) + sha1_b[0:12] = 32 bytes */
132 3 : memcpy(tmp_aes_key, sha1_a, 20);
133 3 : memcpy(tmp_aes_key + 20, sha1_b, 12);
134 :
135 : /* tmp_aes_iv = sha1_b[12:8] + SHA1(new_nonce+new_nonce) + new_nonce[0:4] */
136 : uint8_t sha1_c[20];
137 3 : memcpy(buf, new_nonce, 32);
138 3 : memcpy(buf + 32, new_nonce, 32);
139 3 : crypto_sha1(buf, 64, sha1_c);
140 :
141 3 : memcpy(tmp_aes_iv, sha1_b + 12, 8);
142 3 : memcpy(tmp_aes_iv + 8, sha1_c, 20);
143 3 : memcpy(tmp_aes_iv + 28, new_nonce, 4);
144 3 : }
145 :
146 : /* ---- PQ Factorization (Pollard's rho) ---- */
147 :
148 5 : int pq_factorize(uint64_t pq, uint32_t *p_out, uint32_t *q_out) {
149 5 : if (pq < 2 || !p_out || !q_out) return -1;
150 :
151 : /* Try small primes first for quick factorization */
152 5 : if (pq % 2 == 0) {
153 0 : *p_out = 2;
154 0 : *q_out = (uint32_t)(pq / 2);
155 0 : if (*p_out > *q_out) {
156 0 : uint32_t tmp = *p_out; *p_out = *q_out; *q_out = tmp;
157 : }
158 0 : return 0;
159 : }
160 :
161 : /* Pollard's rho algorithm with multiple attempts.
162 : * Uses __uint128_t to avoid overflow in (x*x) for 64-bit pq. */
163 28 : for (uint64_t c = 1; c < 20; c++) {
164 27 : uint64_t x = 2, y = 2, d = 1;
165 27 : int steps = 0;
166 :
167 19000035 : while (d == 1 && steps < 1000000) {
168 19000012 : x = (uint64_t)(((__uint128_t)x * x + c) % pq);
169 19000012 : y = (uint64_t)(((__uint128_t)y * y + c) % pq);
170 19000012 : y = (uint64_t)(((__uint128_t)y * y + c) % pq);
171 19000012 : steps++;
172 :
173 : /* GCD(|x-y|, pq) */
174 19000012 : uint64_t a = x > y ? x - y : y - x;
175 19000012 : if (a == 0) break; /* x == y, try different c */
176 19000008 : uint64_t b = pq;
177 752455376 : while (b != 0) {
178 733455368 : uint64_t t = b;
179 733455368 : b = a % b;
180 733455368 : a = t;
181 : }
182 19000008 : d = a;
183 : }
184 :
185 27 : if (d != 1 && d != pq) {
186 4 : uint64_t p = d;
187 4 : uint64_t q = pq / d;
188 4 : if (p > q) { uint64_t tmp = p; p = q; q = tmp; }
189 4 : if (p > UINT32_MAX || q > UINT32_MAX) {
190 0 : logger_log(LOG_ERROR,
191 : "pq_factorize: factor exceeds 2^32 (p=%llu q=%llu) — "
192 : "server input is invalid",
193 : (unsigned long long)p, (unsigned long long)q);
194 0 : return -1;
195 : }
196 4 : *p_out = (uint32_t)p;
197 4 : *q_out = (uint32_t)q;
198 4 : return 0;
199 : }
200 : }
201 :
202 1 : return -1;
203 : }
204 :
205 : /* ---- Step 1: req_pq_multi → ResPQ ---- */
206 :
207 10 : int auth_step_req_pq(AuthKeyCtx *ctx) {
208 10 : if (!ctx || !ctx->transport || !ctx->session) return -1;
209 :
210 : /* Generate random nonce */
211 10 : crypto_rand_bytes(ctx->nonce, 16);
212 :
213 : /* Build req_pq_multi TL */
214 : TlWriter w;
215 10 : tl_writer_init(&w);
216 10 : tl_write_uint32(&w, CRC_req_pq_multi);
217 10 : tl_write_int128(&w, ctx->nonce);
218 :
219 10 : int rc = rpc_send_unencrypted(ctx->session, ctx->transport, w.data, w.len);
220 10 : tl_writer_free(&w);
221 10 : if (rc != 0) {
222 0 : logger_log(LOG_ERROR, "auth: failed to send req_pq_multi");
223 0 : return -1;
224 : }
225 :
226 : /* Receive ResPQ */
227 : uint8_t buf[4096];
228 10 : size_t buf_len = 0;
229 10 : rc = rpc_recv_unencrypted(ctx->session, ctx->transport, buf, sizeof(buf), &buf_len);
230 10 : if (rc != 0) {
231 1 : logger_log(LOG_ERROR, "auth: failed to receive ResPQ");
232 1 : return -1;
233 : }
234 :
235 : /* Parse ResPQ */
236 9 : TlReader r = tl_reader_init(buf, buf_len);
237 :
238 9 : uint32_t constructor = tl_read_uint32(&r);
239 9 : if (constructor != CRC_resPQ) {
240 1 : logger_log(LOG_ERROR, "auth: unexpected constructor 0x%08x", constructor);
241 1 : return -1;
242 : }
243 :
244 : /* Verify nonce */
245 : uint8_t recv_nonce[16];
246 8 : tl_read_int128(&r, recv_nonce);
247 8 : if (memcmp(recv_nonce, ctx->nonce, 16) != 0) {
248 1 : logger_log(LOG_ERROR, "auth: nonce mismatch in ResPQ");
249 1 : return -1;
250 : }
251 :
252 : /* Server nonce */
253 7 : tl_read_int128(&r, ctx->server_nonce);
254 :
255 : /* PQ as bytes (big-endian) */
256 7 : size_t pq_len = 0;
257 14 : RAII_STRING uint8_t *pq_bytes = tl_read_bytes(&r, &pq_len);
258 7 : if (!pq_bytes) {
259 0 : logger_log(LOG_ERROR, "auth: failed to read pq bytes");
260 0 : return -1;
261 : }
262 7 : ctx->pq = be_to_uint64(pq_bytes, pq_len);
263 : /* pq_bytes freed automatically by RAII_STRING */
264 :
265 : /* Vector of fingerprints */
266 7 : uint32_t vec_crc = tl_read_uint32(&r); /* vector constructor */
267 : (void)vec_crc;
268 7 : uint32_t fp_count = tl_read_uint32(&r);
269 :
270 7 : if (fp_count > MAX_FP_COUNT) {
271 0 : logger_log(LOG_ERROR, "auth: fp_count %u exceeds cap %u — rejecting",
272 : fp_count, MAX_FP_COUNT);
273 0 : return -1;
274 : }
275 :
276 7 : uint64_t our_fp = telegram_server_key_get_fingerprint();
277 7 : int found_fp = 0;
278 14 : for (uint32_t i = 0; i < fp_count; i++) {
279 7 : uint64_t fp = tl_read_uint64(&r);
280 7 : logger_log(LOG_INFO, "auth: server fingerprint[%u] = 0x%016llx%s",
281 : i, (unsigned long long)fp,
282 : (fp == our_fp) ? " ← MATCH" : "");
283 7 : if (fp == our_fp) {
284 6 : found_fp = 1;
285 : }
286 : }
287 :
288 7 : if (!found_fp) {
289 1 : logger_log(LOG_ERROR,
290 : "auth: no matching RSA fingerprint "
291 : "(our key fingerprint = 0x%016llx)",
292 : (unsigned long long)our_fp);
293 1 : return -1;
294 : }
295 :
296 6 : logger_log(LOG_INFO, "auth: ResPQ received, pq=%llu",
297 6 : (unsigned long long)ctx->pq);
298 6 : return 0;
299 : }
300 :
301 : /* ---- Step 2: req_DH_params ---- */
302 :
303 5 : int auth_step_req_dh(AuthKeyCtx *ctx) {
304 5 : if (!ctx || !ctx->transport || !ctx->session) return -1;
305 :
306 : /* Factorize PQ */
307 5 : if (pq_factorize(ctx->pq, &ctx->p, &ctx->q) != 0) {
308 1 : logger_log(LOG_ERROR, "auth: PQ factorization failed");
309 1 : return -1;
310 : }
311 : /* Generate new_nonce */
312 4 : crypto_rand_bytes(ctx->new_nonce, 32);
313 :
314 : /* Encode p, q, pq as big-endian bytes */
315 : uint8_t pq_be[8];
316 4 : size_t pq_be_len = uint64_to_be(ctx->pq, pq_be);
317 : uint8_t p_be[4];
318 4 : size_t p_be_len = uint32_to_be(ctx->p, p_be);
319 : uint8_t q_be[4];
320 4 : size_t q_be_len = uint32_to_be(ctx->q, q_be);
321 :
322 : /* Build p_q_inner_data (MTProto 1.0, no dc field).
323 : * Use the legacy SHA1 RSA scheme — accepted by all production DCs. */
324 : TlWriter inner;
325 4 : tl_writer_init(&inner);
326 4 : tl_write_uint32(&inner, CRC_p_q_inner_data);
327 4 : tl_write_bytes(&inner, pq_be, pq_be_len);
328 4 : tl_write_bytes(&inner, p_be, p_be_len);
329 4 : tl_write_bytes(&inner, q_be, q_be_len);
330 4 : tl_write_int128(&inner, ctx->nonce);
331 4 : tl_write_int128(&inner, ctx->server_nonce);
332 4 : tl_write_int256(&inner, ctx->new_nonce);
333 :
334 4 : logger_log(LOG_INFO,
335 : "auth: p_q_inner_data built (%zu bytes, "
336 : "fingerprint=0x%016llx)",
337 : inner.len,
338 4 : (unsigned long long)telegram_server_key_get_fingerprint());
339 :
340 : /* SHA1-based RSA encryption (MTProto 1.0 legacy scheme) */
341 4 : CryptoRsaKey *rsa_key = crypto_rsa_load_public(telegram_server_key_get_pem());
342 4 : if (!rsa_key) {
343 0 : tl_writer_free(&inner);
344 0 : logger_log(LOG_ERROR, "auth: failed to load RSA key");
345 0 : return -1;
346 : }
347 :
348 : uint8_t encrypted[256];
349 4 : size_t enc_len = 0;
350 4 : int rc = rsa_sha1_encrypt(rsa_key, inner.data, inner.len, encrypted, &enc_len);
351 4 : crypto_rsa_free(rsa_key);
352 4 : tl_writer_free(&inner);
353 :
354 4 : if (rc != 0) {
355 0 : logger_log(LOG_ERROR, "auth: RSA SHA1 encrypt failed");
356 0 : return -1;
357 : }
358 : /* Build req_DH_params */
359 4 : uint64_t fp_val = telegram_server_key_get_fingerprint();
360 :
361 : TlWriter w;
362 4 : tl_writer_init(&w);
363 4 : tl_write_uint32(&w, CRC_req_DH_params);
364 4 : tl_write_int128(&w, ctx->nonce);
365 4 : tl_write_int128(&w, ctx->server_nonce);
366 4 : tl_write_bytes(&w, p_be, p_be_len);
367 4 : tl_write_bytes(&w, q_be, q_be_len);
368 4 : tl_write_uint64(&w, fp_val);
369 4 : tl_write_bytes(&w, encrypted, enc_len);
370 :
371 4 : rc = rpc_send_unencrypted(ctx->session, ctx->transport, w.data, w.len);
372 4 : tl_writer_free(&w);
373 :
374 4 : if (rc != 0) {
375 0 : logger_log(LOG_ERROR, "auth: failed to send req_DH_params");
376 0 : return -1;
377 : }
378 :
379 4 : logger_log(LOG_INFO, "auth: req_DH_params sent, p=%u q=%u, enc_len=%zu",
380 : ctx->p, ctx->q, enc_len);
381 4 : return 0;
382 : }
383 :
384 : /* ---- Step 3: Parse server_DH_params_ok ---- */
385 :
386 3 : int auth_step_parse_dh(AuthKeyCtx *ctx) {
387 3 : if (!ctx || !ctx->transport || !ctx->session) return -1;
388 :
389 : /* Receive server_DH_params */
390 : uint8_t buf[4096];
391 3 : size_t buf_len = 0;
392 3 : int rc = rpc_recv_unencrypted(ctx->session, ctx->transport,
393 : buf, sizeof(buf), &buf_len);
394 3 : if (rc != 0) {
395 0 : logger_log(LOG_ERROR, "auth: failed to receive server_DH_params");
396 0 : return -1;
397 : }
398 :
399 3 : TlReader r = tl_reader_init(buf, buf_len);
400 :
401 3 : uint32_t constructor = tl_read_uint32(&r);
402 3 : if (constructor != CRC_server_DH_params_ok) {
403 0 : logger_log(LOG_ERROR, "auth: unexpected constructor 0x%08x", constructor);
404 0 : return -1;
405 : }
406 :
407 : /* Verify nonces */
408 : uint8_t recv_nonce[16];
409 3 : tl_read_int128(&r, recv_nonce);
410 3 : if (memcmp(recv_nonce, ctx->nonce, 16) != 0) {
411 0 : logger_log(LOG_ERROR, "auth: nonce mismatch in server_DH_params");
412 0 : return -1;
413 : }
414 :
415 : uint8_t recv_server_nonce[16];
416 3 : tl_read_int128(&r, recv_server_nonce);
417 3 : if (memcmp(recv_server_nonce, ctx->server_nonce, 16) != 0) {
418 0 : logger_log(LOG_ERROR, "auth: server_nonce mismatch");
419 0 : return -1;
420 : }
421 :
422 : /* Read encrypted_answer */
423 3 : size_t enc_answer_len = 0;
424 6 : RAII_STRING uint8_t *enc_answer = tl_read_bytes(&r, &enc_answer_len);
425 3 : if (!enc_answer || enc_answer_len == 0) {
426 0 : logger_log(LOG_ERROR, "auth: failed to read encrypted_answer");
427 0 : return -1;
428 : }
429 :
430 : /* Derive temp AES key/IV */
431 3 : dh_derive_temp_aes(ctx->new_nonce, ctx->server_nonce,
432 3 : ctx->tmp_aes_key, ctx->tmp_aes_iv);
433 :
434 : /* Decrypt answer */
435 3 : RAII_STRING uint8_t *decrypted = (uint8_t *)malloc(enc_answer_len);
436 3 : if (!decrypted) return -1;
437 3 : aes_ige_decrypt(enc_answer, enc_answer_len,
438 3 : ctx->tmp_aes_key, ctx->tmp_aes_iv, decrypted);
439 : /* enc_answer freed automatically by RAII_STRING */
440 :
441 : /* Parse decrypted: skip 20-byte SHA1 hash, then server_DH_inner_data */
442 3 : if (enc_answer_len < 20 + 4) {
443 0 : return -1;
444 : }
445 :
446 3 : TlReader inner = tl_reader_init(decrypted + 20, enc_answer_len - 20);
447 :
448 3 : uint32_t inner_crc = tl_read_uint32(&inner);
449 3 : if (inner_crc != CRC_server_DH_inner_data) {
450 2 : logger_log(LOG_ERROR, "auth: wrong inner constructor 0x%08x", inner_crc);
451 2 : return -1;
452 : }
453 :
454 : /* Verify inner nonces */
455 : uint8_t inner_nonce[16];
456 1 : tl_read_int128(&inner, inner_nonce);
457 1 : if (memcmp(inner_nonce, ctx->nonce, 16) != 0) {
458 0 : return -1;
459 : }
460 :
461 : uint8_t inner_sn[16];
462 1 : tl_read_int128(&inner, inner_sn);
463 1 : if (memcmp(inner_sn, ctx->server_nonce, 16) != 0) {
464 0 : return -1;
465 : }
466 :
467 1 : ctx->g = tl_read_int32(&inner);
468 :
469 : /* MTProto spec: g must be one of {2, 3, 4, 5, 6, 7}. */
470 1 : if (ctx->g < 2 || ctx->g > 7) {
471 0 : logger_log(LOG_ERROR, "auth: invalid DH g=%d (must be 2–7)", ctx->g);
472 0 : return -1;
473 : }
474 :
475 : /* dh_prime as bytes */
476 1 : size_t prime_len = 0;
477 2 : RAII_STRING uint8_t *prime_bytes = tl_read_bytes(&inner, &prime_len);
478 1 : if (!prime_bytes || prime_len > sizeof(ctx->dh_prime)) return -1;
479 1 : memcpy(ctx->dh_prime, prime_bytes, prime_len);
480 1 : ctx->dh_prime_len = prime_len;
481 : /* prime_bytes freed automatically by RAII_STRING */
482 :
483 : /* g_a as bytes */
484 1 : size_t ga_len = 0;
485 2 : RAII_STRING uint8_t *ga_bytes = tl_read_bytes(&inner, &ga_len);
486 1 : if (!ga_bytes || ga_len > sizeof(ctx->g_a)) return -1;
487 1 : memcpy(ctx->g_a, ga_bytes, ga_len);
488 1 : ctx->g_a_len = ga_len;
489 : /* ga_bytes freed automatically by RAII_STRING */
490 :
491 1 : ctx->server_time = tl_read_int32(&inner);
492 :
493 : /* decrypted is freed automatically by RAII_STRING */
494 1 : logger_log(LOG_INFO, "auth: DH params parsed, g=%d, prime_len=%zu",
495 : ctx->g, ctx->dh_prime_len);
496 1 : return 0;
497 : }
498 :
499 : /* ---- Step 4: set_client_DH_params → dh_gen_ok ---- */
500 :
501 1 : int auth_step_set_client_dh(AuthKeyCtx *ctx) {
502 1 : if (!ctx || !ctx->transport || !ctx->session) return -1;
503 :
504 : /* Generate random b (256 bytes) */
505 1 : crypto_rand_bytes(ctx->b, 256);
506 :
507 : /* Compute g_b = pow(g, b) mod dh_prime */
508 : uint8_t g_be[4];
509 1 : size_t g_be_len = uint32_to_be((uint32_t)ctx->g, g_be);
510 :
511 : uint8_t g_b[256];
512 1 : size_t g_b_len = sizeof(g_b);
513 1 : CryptoBnCtx *bn_ctx = crypto_bn_ctx_new();
514 1 : if (!bn_ctx) return -1;
515 :
516 1 : int rc = crypto_bn_mod_exp(g_b, &g_b_len, g_be, g_be_len,
517 1 : ctx->b, 256,
518 1 : ctx->dh_prime, ctx->dh_prime_len, bn_ctx);
519 1 : if (rc != 0) {
520 0 : crypto_bn_ctx_free(bn_ctx);
521 0 : logger_log(LOG_ERROR, "auth: g_b computation failed");
522 0 : return -1;
523 : }
524 :
525 : /* Build client_DH_inner_data */
526 : TlWriter inner;
527 1 : tl_writer_init(&inner);
528 1 : tl_write_uint32(&inner, CRC_client_DH_inner_data);
529 1 : tl_write_int128(&inner, ctx->nonce);
530 1 : tl_write_int128(&inner, ctx->server_nonce);
531 1 : tl_write_int64(&inner, 0); /* retry_id = 0 */
532 1 : tl_write_bytes(&inner, g_b, g_b_len);
533 :
534 : /* Prepend SHA1 hash + pad to multiple of 16 */
535 : uint8_t sha1_hash[20];
536 1 : crypto_sha1(inner.data, inner.len, sha1_hash);
537 :
538 1 : size_t data_with_hash_len = 20 + inner.len;
539 : /* Pad to 16-byte boundary */
540 1 : size_t padded_len = data_with_hash_len;
541 1 : if (padded_len % 16 != 0) {
542 1 : padded_len += 16 - (padded_len % 16);
543 : }
544 :
545 1 : RAII_STRING uint8_t *padded = (uint8_t *)calloc(1, padded_len);
546 1 : if (!padded) {
547 0 : tl_writer_free(&inner);
548 0 : crypto_bn_ctx_free(bn_ctx);
549 0 : return -1;
550 : }
551 1 : memcpy(padded, sha1_hash, 20);
552 1 : memcpy(padded + 20, inner.data, inner.len);
553 : /* Fill padding with random bytes */
554 1 : if (padded_len > data_with_hash_len) {
555 1 : crypto_rand_bytes(padded + data_with_hash_len,
556 : padded_len - data_with_hash_len);
557 : }
558 1 : tl_writer_free(&inner);
559 :
560 : /* Encrypt with temp AES key/IV */
561 1 : RAII_STRING uint8_t *encrypted = (uint8_t *)malloc(padded_len);
562 1 : if (!encrypted) {
563 0 : crypto_bn_ctx_free(bn_ctx);
564 0 : return -1;
565 : }
566 1 : aes_ige_encrypt(padded, padded_len, ctx->tmp_aes_key, ctx->tmp_aes_iv,
567 : encrypted);
568 :
569 : /* Build set_client_DH_params */
570 : TlWriter w;
571 1 : tl_writer_init(&w);
572 1 : tl_write_uint32(&w, CRC_set_client_DH_params);
573 1 : tl_write_int128(&w, ctx->nonce);
574 1 : tl_write_int128(&w, ctx->server_nonce);
575 1 : tl_write_bytes(&w, encrypted, padded_len);
576 : /* encrypted and padded freed automatically by RAII_STRING */
577 :
578 1 : rc = rpc_send_unencrypted(ctx->session, ctx->transport, w.data, w.len);
579 1 : tl_writer_free(&w);
580 1 : if (rc != 0) {
581 0 : crypto_bn_ctx_free(bn_ctx);
582 0 : logger_log(LOG_ERROR, "auth: failed to send set_client_DH_params");
583 0 : return -1;
584 : }
585 :
586 : /* Receive response */
587 : uint8_t buf[4096];
588 1 : size_t buf_len = 0;
589 1 : rc = rpc_recv_unencrypted(ctx->session, ctx->transport,
590 : buf, sizeof(buf), &buf_len);
591 1 : if (rc != 0) {
592 0 : crypto_bn_ctx_free(bn_ctx);
593 0 : logger_log(LOG_ERROR, "auth: failed to receive DH gen response");
594 0 : return -1;
595 : }
596 :
597 1 : TlReader r = tl_reader_init(buf, buf_len);
598 1 : uint32_t constructor = tl_read_uint32(&r);
599 :
600 1 : if (constructor == CRC_dh_gen_retry) {
601 0 : crypto_bn_ctx_free(bn_ctx);
602 0 : logger_log(LOG_WARN, "auth: dh_gen_retry");
603 0 : return -1;
604 : }
605 1 : if (constructor == CRC_dh_gen_fail) {
606 0 : crypto_bn_ctx_free(bn_ctx);
607 0 : logger_log(LOG_ERROR, "auth: dh_gen_fail");
608 0 : return -1;
609 : }
610 1 : if (constructor != CRC_dh_gen_ok) {
611 0 : crypto_bn_ctx_free(bn_ctx);
612 0 : logger_log(LOG_ERROR, "auth: unexpected constructor 0x%08x", constructor);
613 0 : return -1;
614 : }
615 :
616 : /* Verify nonce in dh_gen_ok */
617 : uint8_t recv_nonce[16];
618 1 : tl_read_int128(&r, recv_nonce);
619 1 : if (memcmp(recv_nonce, ctx->nonce, 16) != 0) {
620 0 : crypto_bn_ctx_free(bn_ctx);
621 0 : return -1;
622 : }
623 :
624 : /* Read server_nonce and new_nonce_hash1 (to be verified below). */
625 : uint8_t recv_sn[16];
626 1 : tl_read_int128(&r, recv_sn);
627 : uint8_t new_nonce_hash[16];
628 1 : tl_read_int128(&r, new_nonce_hash);
629 :
630 : /* Compute auth_key = pow(g_a, b) mod dh_prime */
631 : uint8_t auth_key[256];
632 1 : size_t ak_len = sizeof(auth_key);
633 1 : rc = crypto_bn_mod_exp(auth_key, &ak_len, ctx->g_a, ctx->g_a_len,
634 1 : ctx->b, 256,
635 1 : ctx->dh_prime, ctx->dh_prime_len, bn_ctx);
636 1 : crypto_bn_ctx_free(bn_ctx);
637 :
638 1 : if (rc != 0) {
639 0 : logger_log(LOG_ERROR, "auth: auth_key computation failed");
640 0 : return -1;
641 : }
642 :
643 : /* Set auth_key on session (pad to 256 bytes if needed) */
644 : uint8_t auth_key_padded[256];
645 1 : memset(auth_key_padded, 0, sizeof(auth_key_padded));
646 1 : if (ak_len <= 256) {
647 1 : memcpy(auth_key_padded + (256 - ak_len), auth_key, ak_len);
648 : }
649 :
650 : /* QA-12: verify new_nonce_hash1 =
651 : * last 128 bits of SHA1(new_nonce[32] || 0x01 || auth_key_aux_hash[8])
652 : * where auth_key_aux_hash = SHA1(auth_key)[0:8].
653 : * Without this check a MITM could substitute auth_key during DH
654 : * exchange without detection. */
655 : {
656 : uint8_t ak_full_hash[20];
657 1 : crypto_sha1(auth_key_padded, 256, ak_full_hash);
658 : /* auth_key_aux_hash = ak_full_hash[0:8] */
659 :
660 : uint8_t nonce_hash_input[32 + 1 + 8];
661 1 : memcpy(nonce_hash_input, ctx->new_nonce, 32);
662 1 : nonce_hash_input[32] = 0x01; /* dh_gen_ok marker */
663 1 : memcpy(nonce_hash_input + 33, ak_full_hash, 8);
664 :
665 : uint8_t expected_full[20];
666 1 : crypto_sha1(nonce_hash_input, sizeof(nonce_hash_input), expected_full);
667 : /* last 16 bytes of the SHA1 result */
668 1 : if (memcmp(expected_full + 4, new_nonce_hash, 16) != 0) {
669 0 : logger_log(LOG_ERROR,
670 : "auth: new_nonce_hash1 mismatch — possible MITM");
671 0 : return -1;
672 : }
673 : }
674 :
675 1 : mtproto_session_set_auth_key(ctx->session, auth_key_padded);
676 :
677 : /* Compute server_salt = new_nonce[0:8] XOR server_nonce[0:8] */
678 1 : uint64_t salt = 0;
679 9 : for (int i = 0; i < 8; i++) {
680 8 : ((uint8_t *)&salt)[i] = ctx->new_nonce[i] ^ ctx->server_nonce[i];
681 : }
682 1 : mtproto_session_set_salt(ctx->session, salt);
683 :
684 1 : logger_log(LOG_INFO, "auth: DH key exchange complete, auth_key set");
685 1 : return 0;
686 : }
687 :
688 : /* ---- Auth Key Generation (orchestrator) ---- */
689 :
690 5 : int mtproto_auth_key_gen(Transport *t, MtProtoSession *s) {
691 5 : if (!t || !s) return -1;
692 :
693 3 : logger_log(LOG_INFO, "Starting DH auth key generation...");
694 :
695 : AuthKeyCtx ctx;
696 3 : memset(&ctx, 0, sizeof(ctx));
697 3 : ctx.transport = t;
698 3 : ctx.session = s;
699 3 : ctx.dc_id = t->dc_id;
700 :
701 3 : if (auth_step_req_pq(&ctx) != 0) return -1;
702 2 : if (auth_step_req_dh(&ctx) != 0) return -1;
703 2 : if (auth_step_parse_dh(&ctx) != 0) return -1;
704 1 : if (auth_step_set_client_dh(&ctx) != 0) return -1;
705 :
706 1 : logger_log(LOG_INFO, "Auth key generation complete");
707 1 : return 0;
708 : }
|