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

Generated by: LCOV version 2.0-1