LCOV - code coverage report
Current view: top level - src/infrastructure - mtproto_auth.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 82.1 % 358 294
Test Date: 2026-05-06 13:17:08 Functions: 100.0 % 11 11

            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              : }
        

Generated by: LCOV version 2.0-1