LCOV - code coverage report
Current view: top level - src/infrastructure - mtproto_auth.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 88.3 % 358 316
Test Date: 2026-05-06 13:17:06 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           11 : static size_t uint64_to_be(uint64_t val, uint8_t *out) {
      47              :     uint8_t tmp[8];
      48           99 :     for (int i = 7; i >= 0; i--) {
      49           88 :         tmp[i] = (uint8_t)(val & 0xFF);
      50           88 :         val >>= 8;
      51              :     }
      52              :     /* Skip leading zeros */
      53           11 :     size_t start = 0;
      54           87 :     while (start < 7 && tmp[start] == 0) start++;
      55           11 :     size_t len = 8 - start;
      56           11 :     memcpy(out, tmp + start, len);
      57           11 :     return len;
      58              : }
      59              : 
      60              : /** Encode uint32 as big-endian bytes, stripping leading zeros. */
      61           31 : static size_t uint32_to_be(uint32_t val, uint8_t *out) {
      62              :     uint8_t tmp[4];
      63           31 :     tmp[0] = (uint8_t)((val >> 24) & 0xFF);
      64           31 :     tmp[1] = (uint8_t)((val >> 16) & 0xFF);
      65           31 :     tmp[2] = (uint8_t)((val >>  8) & 0xFF);
      66           31 :     tmp[3] = (uint8_t)( val        & 0xFF);
      67           31 :     size_t start = 0;
      68          124 :     while (start < 3 && tmp[start] == 0) start++;
      69           31 :     size_t len = 4 - start;
      70           31 :     memcpy(out, tmp + start, len);
      71           31 :     return len;
      72              : }
      73              : 
      74              : /** Decode big-endian bytes to uint64. */
      75           20 : static uint64_t be_to_uint64(const uint8_t *data, size_t len) {
      76           20 :     uint64_t val = 0;
      77           54 :     for (size_t i = 0; i < len; i++) {
      78           34 :         val = (val << 8) | data[i];
      79              :     }
      80           20 :     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           11 : 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           11 :     if (!rsa_key || !data || !out || !out_len) return -1;
      94           11 :     if (data_len > 235) return -1;  /* SHA1(20) + data + padding = 255 bytes max */
      95              : 
      96              :     uint8_t to_encrypt[255];
      97           11 :     crypto_sha1(data, data_len, to_encrypt);               /* 20 bytes SHA1 */
      98           11 :     memcpy(to_encrypt + 20, data, data_len);                /* data */
      99           11 :     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           11 :     rsa_input[0] = 0;
     105           11 :     memcpy(rsa_input + 1, to_encrypt, 255);
     106              : 
     107           11 :     return crypto_rsa_public_encrypt(rsa_key, rsa_input, 256, out, out_len);
     108              : }
     109              : 
     110              : /* ---- DH temp key derivation ---- */
     111              : 
     112           12 : 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           12 :     memcpy(buf, new_nonce, 32);
     121           12 :     memcpy(buf + 32, server_nonce, 16);
     122              :     uint8_t sha1_a[20];
     123           12 :     crypto_sha1(buf, 48, sha1_a);
     124              : 
     125              :     /* SHA1(server_nonce + new_nonce) */
     126           12 :     memcpy(buf, server_nonce, 16);
     127           12 :     memcpy(buf + 16, new_nonce, 32);
     128              :     uint8_t sha1_b[20];
     129           12 :     crypto_sha1(buf, 48, sha1_b);
     130              : 
     131              :     /* tmp_aes_key = sha1_a(20) + sha1_b[0:12] = 32 bytes */
     132           12 :     memcpy(tmp_aes_key, sha1_a, 20);
     133           12 :     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           12 :     memcpy(buf, new_nonce, 32);
     138           12 :     memcpy(buf + 32, new_nonce, 32);
     139           12 :     crypto_sha1(buf, 64, sha1_c);
     140              : 
     141           12 :     memcpy(tmp_aes_iv, sha1_b + 12, 8);
     142           12 :     memcpy(tmp_aes_iv + 8, sha1_c, 20);
     143           12 :     memcpy(tmp_aes_iv + 28, new_nonce, 4);
     144           12 : }
     145              : 
     146              : /* ---- PQ Factorization (Pollard's rho) ---- */
     147              : 
     148           26 : int pq_factorize(uint64_t pq, uint32_t *p_out, uint32_t *q_out) {
     149           26 :     if (pq < 2 || !p_out || !q_out) return -1;
     150              : 
     151              :     /* Try small primes first for quick factorization */
     152           22 :     if (pq % 2 == 0) {
     153            1 :         *p_out = 2;
     154            1 :         *q_out = (uint32_t)(pq / 2);
     155            1 :         if (*p_out > *q_out) {
     156            0 :             uint32_t tmp = *p_out; *p_out = *q_out; *q_out = tmp;
     157              :         }
     158            1 :         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           89 :     for (uint64_t c = 1; c < 20; c++) {
     164           86 :         uint64_t x = 2, y = 2, d = 1;
     165           86 :         int steps = 0;
     166              : 
     167     38027356 :         while (d == 1 && steps < 1000000) {
     168     38027300 :             x = (uint64_t)(((__uint128_t)x * x + c) % pq);
     169     38027300 :             y = (uint64_t)(((__uint128_t)y * y + c) % pq);
     170     38027300 :             y = (uint64_t)(((__uint128_t)y * y + c) % pq);
     171     38027300 :             steps++;
     172              : 
     173              :             /* GCD(|x-y|, pq) */
     174     38027300 :             uint64_t a = x > y ? x - y : y - x;
     175     38027300 :             if (a == 0) break; /* x == y, try different c */
     176     38027270 :             uint64_t b = pq;
     177   1505921841 :             while (b != 0) {
     178   1467894571 :                 uint64_t t = b;
     179   1467894571 :                 b = a % b;
     180   1467894571 :                 a = t;
     181              :             }
     182     38027270 :             d = a;
     183              :         }
     184              : 
     185           86 :         if (d != 1 && d != pq) {
     186           18 :             uint64_t p = d;
     187           18 :             uint64_t q = pq / d;
     188           18 :             if (p > q) { uint64_t tmp = p; p = q; q = tmp; }
     189           18 :             if (p > UINT32_MAX || q > UINT32_MAX) {
     190            1 :                 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            1 :                 return -1;
     195              :             }
     196           17 :             *p_out = (uint32_t)p;
     197           17 :             *q_out = (uint32_t)q;
     198           17 :             return 0;
     199              :         }
     200              :     }
     201              : 
     202            3 :     return -1;
     203              : }
     204              : 
     205              : /* ---- Step 1: req_pq_multi → ResPQ ---- */
     206              : 
     207           29 : int auth_step_req_pq(AuthKeyCtx *ctx) {
     208           29 :     if (!ctx || !ctx->transport || !ctx->session) return -1;
     209              : 
     210              :     /* Generate random nonce */
     211           29 :     crypto_rand_bytes(ctx->nonce, 16);
     212              : 
     213              :     /* Build req_pq_multi TL */
     214              :     TlWriter w;
     215           29 :     tl_writer_init(&w);
     216           29 :     tl_write_uint32(&w, CRC_req_pq_multi);
     217           29 :     tl_write_int128(&w, ctx->nonce);
     218              : 
     219           29 :     int rc = rpc_send_unencrypted(ctx->session, ctx->transport, w.data, w.len);
     220           29 :     tl_writer_free(&w);
     221           29 :     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           29 :     size_t buf_len = 0;
     229           29 :     rc = rpc_recv_unencrypted(ctx->session, ctx->transport, buf, sizeof(buf), &buf_len);
     230           29 :     if (rc != 0) {
     231            3 :         logger_log(LOG_ERROR, "auth: failed to receive ResPQ");
     232            3 :         return -1;
     233              :     }
     234              : 
     235              :     /* Parse ResPQ */
     236           26 :     TlReader r = tl_reader_init(buf, buf_len);
     237              : 
     238           26 :     uint32_t constructor = tl_read_uint32(&r);
     239           26 :     if (constructor != CRC_resPQ) {
     240            3 :         logger_log(LOG_ERROR, "auth: unexpected constructor 0x%08x", constructor);
     241            3 :         return -1;
     242              :     }
     243              : 
     244              :     /* Verify nonce */
     245              :     uint8_t recv_nonce[16];
     246           23 :     tl_read_int128(&r, recv_nonce);
     247           23 :     if (memcmp(recv_nonce, ctx->nonce, 16) != 0) {
     248            3 :         logger_log(LOG_ERROR, "auth: nonce mismatch in ResPQ");
     249            3 :         return -1;
     250              :     }
     251              : 
     252              :     /* Server nonce */
     253           20 :     tl_read_int128(&r, ctx->server_nonce);
     254              : 
     255              :     /* PQ as bytes (big-endian) */
     256           20 :     size_t pq_len = 0;
     257           40 :     RAII_STRING uint8_t *pq_bytes = tl_read_bytes(&r, &pq_len);
     258           20 :     if (!pq_bytes) {
     259            0 :         logger_log(LOG_ERROR, "auth: failed to read pq bytes");
     260            0 :         return -1;
     261              :     }
     262           20 :     ctx->pq = be_to_uint64(pq_bytes, pq_len);
     263              :     /* pq_bytes freed automatically by RAII_STRING */
     264              : 
     265              :     /* Vector of fingerprints */
     266           20 :     uint32_t vec_crc = tl_read_uint32(&r); /* vector constructor */
     267              :     (void)vec_crc;
     268           20 :     uint32_t fp_count = tl_read_uint32(&r);
     269              : 
     270           20 :     if (fp_count > MAX_FP_COUNT) {
     271            1 :         logger_log(LOG_ERROR, "auth: fp_count %u exceeds cap %u — rejecting",
     272              :                    fp_count, MAX_FP_COUNT);
     273            1 :         return -1;
     274              :     }
     275              : 
     276           19 :     uint64_t our_fp = telegram_server_key_get_fingerprint();
     277           19 :     int found_fp = 0;
     278           40 :     for (uint32_t i = 0; i < fp_count; i++) {
     279           21 :         uint64_t fp = tl_read_uint64(&r);
     280           21 :         logger_log(LOG_INFO, "auth: server fingerprint[%u] = 0x%016llx%s",
     281              :                    i, (unsigned long long)fp,
     282              :                    (fp == our_fp) ? " ← MATCH" : "");
     283           21 :         if (fp == our_fp) {
     284           16 :             found_fp = 1;
     285              :         }
     286              :     }
     287              : 
     288           19 :     if (!found_fp) {
     289            3 :         logger_log(LOG_ERROR,
     290              :                    "auth: no matching RSA fingerprint "
     291              :                    "(our key fingerprint = 0x%016llx)",
     292              :                    (unsigned long long)our_fp);
     293            3 :         return -1;
     294              :     }
     295              : 
     296           16 :     logger_log(LOG_INFO, "auth: ResPQ received, pq=%llu",
     297           16 :                (unsigned long long)ctx->pq);
     298           16 :     return 0;
     299              : }
     300              : 
     301              : /* ---- Step 2: req_DH_params ---- */
     302              : 
     303           13 : int auth_step_req_dh(AuthKeyCtx *ctx) {
     304           13 :     if (!ctx || !ctx->transport || !ctx->session) return -1;
     305              : 
     306              :     /* Factorize PQ */
     307           13 :     if (pq_factorize(ctx->pq, &ctx->p, &ctx->q) != 0) {
     308            2 :         logger_log(LOG_ERROR, "auth: PQ factorization failed");
     309            2 :         return -1;
     310              :     }
     311              :     /* Generate new_nonce */
     312           11 :     crypto_rand_bytes(ctx->new_nonce, 32);
     313              : 
     314              :     /* Encode p, q, pq as big-endian bytes */
     315              :     uint8_t pq_be[8];
     316           11 :     size_t pq_be_len = uint64_to_be(ctx->pq, pq_be);
     317              :     uint8_t p_be[4];
     318           11 :     size_t p_be_len = uint32_to_be(ctx->p, p_be);
     319              :     uint8_t q_be[4];
     320           11 :     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           11 :     tl_writer_init(&inner);
     326           11 :     tl_write_uint32(&inner, CRC_p_q_inner_data);
     327           11 :     tl_write_bytes(&inner, pq_be, pq_be_len);
     328           11 :     tl_write_bytes(&inner, p_be, p_be_len);
     329           11 :     tl_write_bytes(&inner, q_be, q_be_len);
     330           11 :     tl_write_int128(&inner, ctx->nonce);
     331           11 :     tl_write_int128(&inner, ctx->server_nonce);
     332           11 :     tl_write_int256(&inner, ctx->new_nonce);
     333              : 
     334           11 :     logger_log(LOG_INFO,
     335              :                "auth: p_q_inner_data built (%zu bytes, "
     336              :                "fingerprint=0x%016llx)",
     337              :                inner.len,
     338           11 :                (unsigned long long)telegram_server_key_get_fingerprint());
     339              : 
     340              :     /* SHA1-based RSA encryption (MTProto 1.0 legacy scheme) */
     341           11 :     CryptoRsaKey *rsa_key = crypto_rsa_load_public(telegram_server_key_get_pem());
     342           11 :     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           11 :     size_t enc_len = 0;
     350           11 :     int rc = rsa_sha1_encrypt(rsa_key, inner.data, inner.len, encrypted, &enc_len);
     351           11 :     crypto_rsa_free(rsa_key);
     352           11 :     tl_writer_free(&inner);
     353              : 
     354           11 :     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           11 :     uint64_t fp_val = telegram_server_key_get_fingerprint();
     360              : 
     361              :     TlWriter w;
     362           11 :     tl_writer_init(&w);
     363           11 :     tl_write_uint32(&w, CRC_req_DH_params);
     364           11 :     tl_write_int128(&w, ctx->nonce);
     365           11 :     tl_write_int128(&w, ctx->server_nonce);
     366           11 :     tl_write_bytes(&w, p_be, p_be_len);
     367           11 :     tl_write_bytes(&w, q_be, q_be_len);
     368           11 :     tl_write_uint64(&w, fp_val);
     369           11 :     tl_write_bytes(&w, encrypted, enc_len);
     370              : 
     371           11 :     rc = rpc_send_unencrypted(ctx->session, ctx->transport, w.data, w.len);
     372           11 :     tl_writer_free(&w);
     373              : 
     374           11 :     if (rc != 0) {
     375            0 :         logger_log(LOG_ERROR, "auth: failed to send req_DH_params");
     376            0 :         return -1;
     377              :     }
     378              : 
     379           11 :     logger_log(LOG_INFO, "auth: req_DH_params sent, p=%u q=%u, enc_len=%zu",
     380              :                ctx->p, ctx->q, enc_len);
     381           11 :     return 0;
     382              : }
     383              : 
     384              : /* ---- Step 3: Parse server_DH_params_ok ---- */
     385              : 
     386           14 : int auth_step_parse_dh(AuthKeyCtx *ctx) {
     387           14 :     if (!ctx || !ctx->transport || !ctx->session) return -1;
     388              : 
     389              :     /* Receive server_DH_params */
     390              :     uint8_t buf[4096];
     391           14 :     size_t buf_len = 0;
     392           14 :     int rc = rpc_recv_unencrypted(ctx->session, ctx->transport,
     393              :                                   buf, sizeof(buf), &buf_len);
     394           14 :     if (rc != 0) {
     395            0 :         logger_log(LOG_ERROR, "auth: failed to receive server_DH_params");
     396            0 :         return -1;
     397              :     }
     398              : 
     399           14 :     TlReader r = tl_reader_init(buf, buf_len);
     400              : 
     401           14 :     uint32_t constructor = tl_read_uint32(&r);
     402           14 :     if (constructor != CRC_server_DH_params_ok) {
     403            1 :         logger_log(LOG_ERROR, "auth: unexpected constructor 0x%08x", constructor);
     404            1 :         return -1;
     405              :     }
     406              : 
     407              :     /* Verify nonces */
     408              :     uint8_t recv_nonce[16];
     409           13 :     tl_read_int128(&r, recv_nonce);
     410           13 :     if (memcmp(recv_nonce, ctx->nonce, 16) != 0) {
     411            1 :         logger_log(LOG_ERROR, "auth: nonce mismatch in server_DH_params");
     412            1 :         return -1;
     413              :     }
     414              : 
     415              :     uint8_t recv_server_nonce[16];
     416           12 :     tl_read_int128(&r, recv_server_nonce);
     417           12 :     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           12 :     size_t enc_answer_len = 0;
     424           24 :     RAII_STRING uint8_t *enc_answer = tl_read_bytes(&r, &enc_answer_len);
     425           12 :     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           12 :     dh_derive_temp_aes(ctx->new_nonce, ctx->server_nonce,
     432           12 :                        ctx->tmp_aes_key, ctx->tmp_aes_iv);
     433              : 
     434              :     /* Decrypt answer */
     435           12 :     RAII_STRING uint8_t *decrypted = (uint8_t *)malloc(enc_answer_len);
     436           12 :     if (!decrypted) return -1;
     437           12 :     aes_ige_decrypt(enc_answer, enc_answer_len,
     438           12 :                     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           12 :     if (enc_answer_len < 20 + 4) {
     443            0 :         return -1;
     444              :     }
     445              : 
     446           12 :     TlReader inner = tl_reader_init(decrypted + 20, enc_answer_len - 20);
     447              : 
     448           12 :     uint32_t inner_crc = tl_read_uint32(&inner);
     449           12 :     if (inner_crc != CRC_server_DH_inner_data) {
     450            4 :         logger_log(LOG_ERROR, "auth: wrong inner constructor 0x%08x", inner_crc);
     451            4 :         return -1;
     452              :     }
     453              : 
     454              :     /* Verify inner nonces */
     455              :     uint8_t inner_nonce[16];
     456            8 :     tl_read_int128(&inner, inner_nonce);
     457            8 :     if (memcmp(inner_nonce, ctx->nonce, 16) != 0) {
     458            0 :         return -1;
     459              :     }
     460              : 
     461              :     uint8_t inner_sn[16];
     462            8 :     tl_read_int128(&inner, inner_sn);
     463            8 :     if (memcmp(inner_sn, ctx->server_nonce, 16) != 0) {
     464            0 :         return -1;
     465              :     }
     466              : 
     467            8 :     ctx->g = tl_read_int32(&inner);
     468              : 
     469              :     /* MTProto spec: g must be one of {2, 3, 4, 5, 6, 7}. */
     470            8 :     if (ctx->g < 2 || ctx->g > 7) {
     471            3 :         logger_log(LOG_ERROR, "auth: invalid DH g=%d (must be 2–7)", ctx->g);
     472            3 :         return -1;
     473              :     }
     474              : 
     475              :     /* dh_prime as bytes */
     476            5 :     size_t prime_len = 0;
     477           10 :     RAII_STRING uint8_t *prime_bytes = tl_read_bytes(&inner, &prime_len);
     478            5 :     if (!prime_bytes || prime_len > sizeof(ctx->dh_prime)) return -1;
     479            5 :     memcpy(ctx->dh_prime, prime_bytes, prime_len);
     480            5 :     ctx->dh_prime_len = prime_len;
     481              :     /* prime_bytes freed automatically by RAII_STRING */
     482              : 
     483              :     /* g_a as bytes */
     484            5 :     size_t ga_len = 0;
     485           10 :     RAII_STRING uint8_t *ga_bytes = tl_read_bytes(&inner, &ga_len);
     486            5 :     if (!ga_bytes || ga_len > sizeof(ctx->g_a)) return -1;
     487            5 :     memcpy(ctx->g_a, ga_bytes, ga_len);
     488            5 :     ctx->g_a_len = ga_len;
     489              :     /* ga_bytes freed automatically by RAII_STRING */
     490              : 
     491            5 :     ctx->server_time = tl_read_int32(&inner);
     492              : 
     493              :     /* decrypted is freed automatically by RAII_STRING */
     494            5 :     logger_log(LOG_INFO, "auth: DH params parsed, g=%d, prime_len=%zu",
     495              :                ctx->g, ctx->dh_prime_len);
     496            5 :     return 0;
     497              : }
     498              : 
     499              : /* ---- Step 4: set_client_DH_params → dh_gen_ok ---- */
     500              : 
     501            9 : int auth_step_set_client_dh(AuthKeyCtx *ctx) {
     502            9 :     if (!ctx || !ctx->transport || !ctx->session) return -1;
     503              : 
     504              :     /* Generate random b (256 bytes) */
     505            9 :     crypto_rand_bytes(ctx->b, 256);
     506              : 
     507              :     /* Compute g_b = pow(g, b) mod dh_prime */
     508              :     uint8_t g_be[4];
     509            9 :     size_t g_be_len = uint32_to_be((uint32_t)ctx->g, g_be);
     510              : 
     511              :     uint8_t g_b[256];
     512            9 :     size_t g_b_len = sizeof(g_b);
     513            9 :     CryptoBnCtx *bn_ctx = crypto_bn_ctx_new();
     514            9 :     if (!bn_ctx) return -1;
     515              : 
     516            9 :     int rc = crypto_bn_mod_exp(g_b, &g_b_len, g_be, g_be_len,
     517            9 :                                 ctx->b, 256,
     518            9 :                                 ctx->dh_prime, ctx->dh_prime_len, bn_ctx);
     519            9 :     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            9 :     tl_writer_init(&inner);
     528            9 :     tl_write_uint32(&inner, CRC_client_DH_inner_data);
     529            9 :     tl_write_int128(&inner, ctx->nonce);
     530            9 :     tl_write_int128(&inner, ctx->server_nonce);
     531            9 :     tl_write_int64(&inner, 0); /* retry_id = 0 */
     532            9 :     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            9 :     crypto_sha1(inner.data, inner.len, sha1_hash);
     537              : 
     538            9 :     size_t data_with_hash_len = 20 + inner.len;
     539              :     /* Pad to 16-byte boundary */
     540            9 :     size_t padded_len = data_with_hash_len;
     541            9 :     if (padded_len % 16 != 0) {
     542            9 :         padded_len += 16 - (padded_len % 16);
     543              :     }
     544              : 
     545            9 :     RAII_STRING uint8_t *padded = (uint8_t *)calloc(1, padded_len);
     546            9 :     if (!padded) {
     547            0 :         tl_writer_free(&inner);
     548            0 :         crypto_bn_ctx_free(bn_ctx);
     549            0 :         return -1;
     550              :     }
     551            9 :     memcpy(padded, sha1_hash, 20);
     552            9 :     memcpy(padded + 20, inner.data, inner.len);
     553              :     /* Fill padding with random bytes */
     554            9 :     if (padded_len > data_with_hash_len) {
     555            9 :         crypto_rand_bytes(padded + data_with_hash_len,
     556              :                           padded_len - data_with_hash_len);
     557              :     }
     558            9 :     tl_writer_free(&inner);
     559              : 
     560              :     /* Encrypt with temp AES key/IV */
     561            9 :     RAII_STRING uint8_t *encrypted = (uint8_t *)malloc(padded_len);
     562            9 :     if (!encrypted) {
     563            0 :         crypto_bn_ctx_free(bn_ctx);
     564            0 :         return -1;
     565              :     }
     566            9 :     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            9 :     tl_writer_init(&w);
     572            9 :     tl_write_uint32(&w, CRC_set_client_DH_params);
     573            9 :     tl_write_int128(&w, ctx->nonce);
     574            9 :     tl_write_int128(&w, ctx->server_nonce);
     575            9 :     tl_write_bytes(&w, encrypted, padded_len);
     576              :     /* encrypted and padded freed automatically by RAII_STRING */
     577              : 
     578            9 :     rc = rpc_send_unencrypted(ctx->session, ctx->transport, w.data, w.len);
     579            9 :     tl_writer_free(&w);
     580            9 :     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            9 :     size_t buf_len = 0;
     589            9 :     rc = rpc_recv_unencrypted(ctx->session, ctx->transport,
     590              :                               buf, sizeof(buf), &buf_len);
     591            9 :     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            9 :     TlReader r = tl_reader_init(buf, buf_len);
     598            9 :     uint32_t constructor = tl_read_uint32(&r);
     599              : 
     600            9 :     if (constructor == CRC_dh_gen_retry) {
     601            1 :         crypto_bn_ctx_free(bn_ctx);
     602            1 :         logger_log(LOG_WARN, "auth: dh_gen_retry");
     603            1 :         return -1;
     604              :     }
     605            8 :     if (constructor == CRC_dh_gen_fail) {
     606            1 :         crypto_bn_ctx_free(bn_ctx);
     607            1 :         logger_log(LOG_ERROR, "auth: dh_gen_fail");
     608            1 :         return -1;
     609              :     }
     610            7 :     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            7 :     tl_read_int128(&r, recv_nonce);
     619            7 :     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            7 :     tl_read_int128(&r, recv_sn);
     627              :     uint8_t new_nonce_hash[16];
     628            7 :     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            7 :     size_t ak_len = sizeof(auth_key);
     633            7 :     rc = crypto_bn_mod_exp(auth_key, &ak_len, ctx->g_a, ctx->g_a_len,
     634            7 :                             ctx->b, 256,
     635            7 :                             ctx->dh_prime, ctx->dh_prime_len, bn_ctx);
     636            7 :     crypto_bn_ctx_free(bn_ctx);
     637              : 
     638            7 :     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            7 :     memset(auth_key_padded, 0, sizeof(auth_key_padded));
     646            7 :     if (ak_len <= 256) {
     647            7 :         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            7 :         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            7 :         memcpy(nonce_hash_input,          ctx->new_nonce, 32);
     662            7 :         nonce_hash_input[32] = 0x01; /* dh_gen_ok marker */
     663            7 :         memcpy(nonce_hash_input + 33,     ak_full_hash, 8);
     664              : 
     665              :         uint8_t expected_full[20];
     666            7 :         crypto_sha1(nonce_hash_input, sizeof(nonce_hash_input), expected_full);
     667              :         /* last 16 bytes of the SHA1 result */
     668            7 :         if (memcmp(expected_full + 4, new_nonce_hash, 16) != 0) {
     669            1 :             logger_log(LOG_ERROR,
     670              :                        "auth: new_nonce_hash1 mismatch — possible MITM");
     671            1 :             return -1;
     672              :         }
     673              :     }
     674              : 
     675            6 :     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            6 :     uint64_t salt = 0;
     679           54 :     for (int i = 0; i < 8; i++) {
     680           48 :         ((uint8_t *)&salt)[i] = ctx->new_nonce[i] ^ ctx->server_nonce[i];
     681              :     }
     682            6 :     mtproto_session_set_salt(ctx->session, salt);
     683              : 
     684            6 :     logger_log(LOG_INFO, "auth: DH key exchange complete, auth_key set");
     685            6 :     return 0;
     686              : }
     687              : 
     688              : /* ---- Auth Key Generation (orchestrator) ---- */
     689              : 
     690           14 : int mtproto_auth_key_gen(Transport *t, MtProtoSession *s) {
     691           14 :     if (!t || !s) return -1;
     692              : 
     693            8 :     logger_log(LOG_INFO, "Starting DH auth key generation...");
     694              : 
     695              :     AuthKeyCtx ctx;
     696            8 :     memset(&ctx, 0, sizeof(ctx));
     697            8 :     ctx.transport = t;
     698            8 :     ctx.session = s;
     699            8 :     ctx.dc_id = t->dc_id;
     700              : 
     701            8 :     if (auth_step_req_pq(&ctx) != 0) return -1;
     702            5 :     if (auth_step_req_dh(&ctx) != 0) return -1;
     703            5 :     if (auth_step_parse_dh(&ctx) != 0) return -1;
     704            3 :     if (auth_step_set_client_dh(&ctx) != 0) return -1;
     705              : 
     706            3 :     logger_log(LOG_INFO, "Auth key generation complete");
     707            3 :     return 0;
     708              : }
        

Generated by: LCOV version 2.0-1