LCOV - code coverage report
Current view: top level - tests/unit - test_auth.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 99.2 % 602 597
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 42 42

            Line data    Source code
       1              : /**
       2              :  * @file test_auth.c
       3              :  * @brief Unit tests for MTProto auth key generation.
       4              :  *
       5              :  * Tests PQ factorization, individual DH exchange steps (with mocks),
       6              :  * and full auth_key_gen integration flow.
       7              :  */
       8              : 
       9              : #include "test_helpers.h"
      10              : #include "mtproto_auth.h"
      11              : #include "mtproto_session.h"
      12              : #include "transport.h"
      13              : #include "tl_serial.h"
      14              : #include "ige_aes.h"
      15              : #include "crypto.h"
      16              : #include "mock_crypto.h"
      17              : #include "mock_socket.h"
      18              : 
      19              : #include <stdint.h>
      20              : #include <stdio.h>
      21              : #include <stdlib.h>
      22              : #include <string.h>
      23              : 
      24              : /* ---- Telegram RSA fingerprint (from telegram_server_key.h) ---- */
      25              : #define TEST_RSA_FINGERPRINT 0xc3b42b026ce86b21ULL
      26              : 
      27              : /* ---- Helper: wrap TL data in unencrypted MTProto + abridged encoding ---- */
      28              : 
      29           23 : static size_t build_unenc_response(const uint8_t *tl_data, size_t tl_len,
      30              :                                     uint8_t *out) {
      31              :     /* Unencrypted wire: auth_key_id(8) + msg_id(8) + len(4) + data */
      32              :     TlWriter w;
      33           23 :     tl_writer_init(&w);
      34           23 :     tl_write_uint64(&w, 0);             /* auth_key_id = 0 */
      35           23 :     tl_write_uint64(&w, 0x12345678ULL); /* fake msg_id */
      36           23 :     tl_write_uint32(&w, (uint32_t)tl_len);
      37           23 :     tl_write_raw(&w, tl_data, tl_len);
      38              : 
      39              :     /* Abridged encoding: length in 4-byte units */
      40           23 :     size_t wire_units = w.len / 4;
      41           23 :     size_t pos = 0;
      42              : 
      43           23 :     if (wire_units < 0x7F) {
      44           23 :         out[0] = (uint8_t)wire_units;
      45           23 :         pos = 1;
      46              :     } else {
      47            0 :         out[0] = 0x7F;
      48            0 :         out[1] = (uint8_t)(wire_units & 0xFF);
      49            0 :         out[2] = (uint8_t)((wire_units >> 8) & 0xFF);
      50            0 :         pos = 3;
      51              :     }
      52           23 :     memcpy(out + pos, w.data, w.len);
      53           23 :     pos += w.len;
      54           23 :     tl_writer_free(&w);
      55           23 :     return pos;
      56              : }
      57              : 
      58              : /* ---- Helper: build ResPQ response ---- */
      59              : 
      60            5 : static size_t build_res_pq(const uint8_t nonce[16],
      61              :                             const uint8_t server_nonce[16],
      62              :                             const uint8_t *pq_be, size_t pq_be_len,
      63              :                             uint64_t fingerprint,
      64              :                             uint8_t *out) {
      65              :     TlWriter tl;
      66            5 :     tl_writer_init(&tl);
      67            5 :     tl_write_uint32(&tl, 0x05162463); /* CRC_resPQ */
      68            5 :     tl_write_int128(&tl, nonce);
      69            5 :     tl_write_int128(&tl, server_nonce);
      70            5 :     tl_write_bytes(&tl, pq_be, pq_be_len);
      71              :     /* Vector of fingerprints */
      72            5 :     tl_write_uint32(&tl, 0x1cb5c415); /* vector constructor */
      73            5 :     tl_write_uint32(&tl, 1);          /* count = 1 */
      74            5 :     tl_write_uint64(&tl, fingerprint);
      75              : 
      76            5 :     size_t len = build_unenc_response(tl.data, tl.len, out);
      77            5 :     tl_writer_free(&tl);
      78            5 :     return len;
      79              : }
      80              : 
      81              : /* ---- Helper: build ResPQ with arbitrary fp_count (for cap tests) ---- */
      82              : 
      83            1 : static size_t build_res_pq_fp_count(const uint8_t nonce[16],
      84              :                                      const uint8_t server_nonce[16],
      85              :                                      const uint8_t *pq_be, size_t pq_be_len,
      86              :                                      uint32_t fp_count,
      87              :                                      uint64_t fingerprint,
      88              :                                      uint8_t *out) {
      89              :     TlWriter tl;
      90            1 :     tl_writer_init(&tl);
      91            1 :     tl_write_uint32(&tl, 0x05162463); /* CRC_resPQ */
      92            1 :     tl_write_int128(&tl, nonce);
      93            1 :     tl_write_int128(&tl, server_nonce);
      94            1 :     tl_write_bytes(&tl, pq_be, pq_be_len);
      95            1 :     tl_write_uint32(&tl, 0x1cb5c415); /* vector constructor */
      96            1 :     tl_write_uint32(&tl, fp_count);
      97              :     /* Write only the real fingerprint; remaining entries will be zeros/padding
      98              :      * but TlReader saturates safely so this is sufficient for the cap test. */
      99            1 :     tl_write_uint64(&tl, fingerprint);
     100              : 
     101            1 :     size_t len = build_unenc_response(tl.data, tl.len, out);
     102            1 :     tl_writer_free(&tl);
     103            1 :     return len;
     104              : }
     105              : 
     106              : /* ---- Helper: build server_DH_params_ok response ---- */
     107              : 
     108            6 : static size_t build_server_dh_params_ok(const uint8_t nonce[16],
     109              :                                          const uint8_t server_nonce[16],
     110              :                                          const uint8_t new_nonce[32],
     111              :                                          int32_t g,
     112              :                                          const uint8_t *dh_prime, size_t prime_len,
     113              :                                          const uint8_t *g_a, size_t g_a_len,
     114              :                                          int32_t server_time,
     115              :                                          uint8_t *out) {
     116              :     /* Build server_DH_inner_data TL */
     117              :     TlWriter inner;
     118            6 :     tl_writer_init(&inner);
     119            6 :     tl_write_uint32(&inner, 0xb5890dba); /* CRC_server_DH_inner_data */
     120            6 :     tl_write_int128(&inner, nonce);
     121            6 :     tl_write_int128(&inner, server_nonce);
     122            6 :     tl_write_int32(&inner, g);
     123            6 :     tl_write_bytes(&inner, dh_prime, prime_len);
     124            6 :     tl_write_bytes(&inner, g_a, g_a_len);
     125            6 :     tl_write_int32(&inner, server_time);
     126              : 
     127              :     /* Prepend 20 bytes SHA1 hash (zeros for mock) + pad to 16 */
     128            6 :     size_t data_with_hash_len = 20 + inner.len;
     129            6 :     size_t padded_len = data_with_hash_len;
     130            6 :     if (padded_len % 16 != 0)
     131            6 :         padded_len += 16 - (padded_len % 16);
     132              : 
     133            6 :     uint8_t *plaintext = (uint8_t *)calloc(1, padded_len);
     134              :     /* SHA1 hash at front (zeros) */
     135            6 :     memcpy(plaintext + 20, inner.data, inner.len);
     136            6 :     tl_writer_free(&inner);
     137              : 
     138              :     /* "Encrypt" with IGE using derived temp key.
     139              :      * Since test uses mock crypto (identity block cipher), we need to run
     140              :      * aes_ige_encrypt so that the production aes_ige_decrypt roundtrips. */
     141              :     uint8_t tmp_key[32], tmp_iv[32];
     142              : 
     143              :     /* Derive temp AES from new_nonce + server_nonce (same as production) */
     144              :     /* We call the same helpers — crypto_sha1 is mocked (returns zeros),
     145              :      * so tmp_key and tmp_iv will be deterministic zeros. */
     146              :     /* Build the same temp key/IV that auth_step_parse_dh will derive */
     147              :     uint8_t sha1_buf[64];
     148              :     uint8_t sha1_out[20];
     149              : 
     150              :     /* SHA1(new_nonce + server_nonce) → sha1_a */
     151            6 :     memcpy(sha1_buf, new_nonce, 32);
     152            6 :     memcpy(sha1_buf + 32, server_nonce, 16);
     153            6 :     crypto_sha1(sha1_buf, 48, sha1_out);
     154              :     uint8_t sha1_a[20];
     155            6 :     memcpy(sha1_a, sha1_out, 20);
     156              : 
     157              :     /* SHA1(server_nonce + new_nonce) → sha1_b */
     158            6 :     memcpy(sha1_buf, server_nonce, 16);
     159            6 :     memcpy(sha1_buf + 16, new_nonce, 32);
     160            6 :     crypto_sha1(sha1_buf, 48, sha1_out);
     161              :     uint8_t sha1_b[20];
     162            6 :     memcpy(sha1_b, sha1_out, 20);
     163              : 
     164            6 :     memcpy(tmp_key, sha1_a, 20);
     165            6 :     memcpy(tmp_key + 20, sha1_b, 12);
     166              : 
     167              :     /* SHA1(new_nonce + new_nonce) → sha1_c */
     168            6 :     memcpy(sha1_buf, new_nonce, 32);
     169            6 :     memcpy(sha1_buf + 32, new_nonce, 32);
     170            6 :     crypto_sha1(sha1_buf, 64, sha1_out);
     171              : 
     172            6 :     memcpy(tmp_iv, sha1_b + 12, 8);
     173            6 :     memcpy(tmp_iv + 8, sha1_out, 20);
     174            6 :     memcpy(tmp_iv + 28, new_nonce, 4);
     175              : 
     176            6 :     uint8_t *encrypted = (uint8_t *)malloc(padded_len);
     177            6 :     aes_ige_encrypt(plaintext, padded_len, tmp_key, tmp_iv, encrypted);
     178            6 :     free(plaintext);
     179              : 
     180              :     /* Build outer TL */
     181              :     TlWriter outer;
     182            6 :     tl_writer_init(&outer);
     183            6 :     tl_write_uint32(&outer, 0xd0e8075c); /* CRC_server_DH_params_ok */
     184            6 :     tl_write_int128(&outer, nonce);
     185            6 :     tl_write_int128(&outer, server_nonce);
     186            6 :     tl_write_bytes(&outer, encrypted, padded_len);
     187            6 :     free(encrypted);
     188              : 
     189            6 :     size_t len = build_unenc_response(outer.data, outer.len, out);
     190            6 :     tl_writer_free(&outer);
     191            6 :     return len;
     192              : }
     193              : 
     194              : /* ---- Helper: build dh_gen_ok response ---- */
     195              : 
     196            4 : static size_t build_dh_gen_ok(const uint8_t nonce[16],
     197              :                                const uint8_t server_nonce[16],
     198              :                                uint8_t *out) {
     199              :     TlWriter tl;
     200            4 :     tl_writer_init(&tl);
     201            4 :     tl_write_uint32(&tl, 0x3bcbf734); /* CRC_dh_gen_ok */
     202            4 :     tl_write_int128(&tl, nonce);
     203            4 :     tl_write_int128(&tl, server_nonce);
     204              :     /* new_nonce_hash1: after QA-12 the auth step verifies this field
     205              :      * equals last 16 bytes of SHA1(new_nonce || 0x01 || auth_key_aux_hash).
     206              :      * Under mock crypto (crypto_sha1 returns zeros after mock_crypto_reset),
     207              :      * the last 16 bytes are zeros — match that to satisfy the check. */
     208            4 :     uint8_t zero_hash[16] = {0};
     209            4 :     tl_write_int128(&tl, zero_hash);
     210              : 
     211            4 :     size_t len = build_unenc_response(tl.data, tl.len, out);
     212            4 :     tl_writer_free(&tl);
     213            4 :     return len;
     214              : }
     215              : 
     216              : /* ---- Helper: build dh_gen response with custom constructor ---- */
     217              : 
     218            2 : static size_t build_dh_gen_response(uint32_t constructor,
     219              :                                      const uint8_t nonce[16],
     220              :                                      const uint8_t server_nonce[16],
     221              :                                      uint8_t *out) {
     222              :     TlWriter tl;
     223            2 :     tl_writer_init(&tl);
     224            2 :     tl_write_uint32(&tl, constructor);
     225            2 :     tl_write_int128(&tl, nonce);
     226            2 :     tl_write_int128(&tl, server_nonce);
     227              :     uint8_t fake_hash[16];
     228            2 :     memset(fake_hash, 0xDD, 16);
     229            2 :     tl_write_int128(&tl, fake_hash);
     230              : 
     231            2 :     size_t len = build_unenc_response(tl.data, tl.len, out);
     232            2 :     tl_writer_free(&tl);
     233            2 :     return len;
     234              : }
     235              : 
     236              : /* ---- Helper: init transport + session for testing ---- */
     237              : 
     238           19 : static void test_init(Transport *t, MtProtoSession *s) {
     239           19 :     mock_socket_reset();
     240           19 :     mock_crypto_reset();
     241           19 :     transport_init(t);
     242           19 :     transport_connect(t, "localhost", 443);
     243           19 :     mock_socket_clear_sent(); /* clear the 0xEF marker */
     244           19 :     mtproto_session_init(s);
     245           19 : }
     246              : 
     247              : /* ======================================================================
     248              :  * PQ Factorization Tests (existing)
     249              :  * ====================================================================== */
     250              : 
     251            1 : void test_pq_factorize_simple(void) {
     252              :     uint32_t p, q;
     253            1 :     int rc = pq_factorize(21, &p, &q);
     254            1 :     ASSERT(rc == 0, "factorize 21 should succeed");
     255            1 :     ASSERT(p == 3, "p should be 3");
     256            1 :     ASSERT(q == 7, "q should be 7");
     257              : }
     258              : 
     259            1 : void test_pq_factorize_larger(void) {
     260              :     uint32_t p, q;
     261            1 :     int rc = pq_factorize(10403, &p, &q);
     262            1 :     ASSERT(rc == 0, "factorize 10403 should succeed");
     263            1 :     ASSERT(p == 101, "p should be 101");
     264            1 :     ASSERT(q == 103, "q should be 103");
     265              : }
     266              : 
     267            1 : void test_pq_factorize_product_of_large_primes(void) {
     268              :     uint32_t p, q;
     269            1 :     uint64_t pq = (uint64_t)65537 * 65521;
     270            1 :     int rc = pq_factorize(pq, &p, &q);
     271            1 :     ASSERT(rc == 0, "factorize 4295098177 should succeed");
     272            1 :     ASSERT(p == 65521, "p should be 65521");
     273            1 :     ASSERT(q == 65537, "q should be 65537");
     274              : }
     275              : 
     276            1 : void test_pq_factorize_small_primes(void) {
     277              :     uint32_t p, q;
     278            1 :     int rc = pq_factorize(6, &p, &q);
     279            1 :     ASSERT(rc == 0, "factorize 6 should succeed");
     280            1 :     ASSERT(p == 2, "p should be 2");
     281            1 :     ASSERT(q == 3, "q should be 3");
     282              : }
     283              : 
     284            1 : void test_pq_factorize_unequal_primes(void) {
     285              :     uint32_t p, q;
     286            1 :     int rc = pq_factorize(371, &p, &q);
     287            1 :     ASSERT(rc == 0, "factorize 371 should succeed");
     288            1 :     ASSERT(p == 7, "p should be 7");
     289            1 :     ASSERT(q == 53, "q should be 53");
     290              : }
     291              : 
     292            1 : void test_pq_factorize_invalid(void) {
     293              :     uint32_t p, q;
     294            1 :     ASSERT(pq_factorize(0, &p, &q) == -1, "factorize 0 should fail");
     295            1 :     ASSERT(pq_factorize(1, &p, &q) == -1, "factorize 1 should fail");
     296            1 :     ASSERT(pq_factorize(7, &p, &q) == -1, "factorize prime 7 should fail");
     297              :     /* 49 = 7*7: valid factorization (p=7, q=7) */
     298            1 :     ASSERT(pq_factorize(49, &p, &q) == 0, "factorize 49 should succeed");
     299            1 :     ASSERT(p == 7 && q == 7, "49 = 7*7");
     300              : }
     301              : 
     302            1 : void test_pq_factorize_null(void) {
     303              :     uint32_t p;
     304            1 :     ASSERT(pq_factorize(21, NULL, &p) == -1, "NULL p should fail");
     305            1 :     ASSERT(pq_factorize(21, &p, NULL) == -1, "NULL q should fail");
     306              : }
     307              : 
     308            1 : void test_pq_factorize_mtproto_sized(void) {
     309              :     uint32_t p, q;
     310            1 :     uint64_t pq = (uint64_t)1073741789ULL * 1073741827ULL;
     311            1 :     int rc = pq_factorize(pq, &p, &q);
     312            1 :     ASSERT(rc == 0, "factorize large product should succeed");
     313            1 :     ASSERT((uint64_t)p * q == pq, "p * q should equal original pq");
     314            1 :     ASSERT(p <= q, "p should be <= q");
     315              : }
     316              : 
     317              : /* QA-22: reject PQ whose factors would be truncated to 32 bits. We can't
     318              :  * easily compute a pq with factors > UINT32_MAX that Pollard's rho will
     319              :  * crack quickly, but we can at least assert that pq_factorize rejects
     320              :  * a clearly-too-large product by never returning truncated values. Use a
     321              :  * product of 33-bit primes and verify p*q equality still holds OR the
     322              :  * function returns -1. */
     323            1 : void test_pq_factorize_rejects_wide_factors(void) {
     324              :     uint32_t p, q;
     325              :     /* 4294967311 is the smallest prime > 2^32. */
     326            1 :     uint64_t p33 = 4294967311ULL;
     327              :     /* We need the product to be <= 2^63-1 and factor-able by our rho
     328              :      * implementation. Multiply by a small prime instead so the test
     329              :      * is tractable. Our implementation will return either a valid
     330              :      * (p, q) within uint32 OR refuse with -1 — never a truncated one. */
     331            1 :     uint64_t pq = p33 * 3ULL;
     332            1 :     int rc = pq_factorize(pq, &p, &q);
     333            1 :     if (rc == 0) {
     334            0 :         ASSERT((uint64_t)p * q == pq,
     335              :                "returned factors must satisfy p*q == pq without truncation");
     336              :     } /* rc == -1 is also acceptable — it's the explicit rejection path. */
     337              : }
     338              : 
     339              : /* ======================================================================
     340              :  * Step 1: auth_step_req_pq tests
     341              :  * ====================================================================== */
     342              : 
     343            1 : void test_req_pq_parses_respq(void) {
     344              :     Transport t;
     345              :     MtProtoSession s;
     346            1 :     test_init(&t, &s);
     347              : 
     348              :     /* The mock crypto_rand_bytes fills nonce with 0xAA */
     349              :     uint8_t expected_nonce[16];
     350            1 :     memset(expected_nonce, 0xAA, 16);
     351              : 
     352              :     uint8_t server_nonce[16];
     353            1 :     memset(server_nonce, 0xBB, 16);
     354              : 
     355              :     /* pq = 21 (3 * 7) as big-endian bytes */
     356            1 :     uint8_t pq_be[] = { 0x15 };
     357              : 
     358              :     /* Build and program ResPQ response */
     359              :     uint8_t resp[4096];
     360            1 :     size_t resp_len = build_res_pq(expected_nonce, server_nonce,
     361              :                                     pq_be, sizeof(pq_be),
     362              :                                     TEST_RSA_FINGERPRINT, resp);
     363            1 :     mock_socket_set_response(resp, resp_len);
     364              : 
     365              :     AuthKeyCtx ctx;
     366            1 :     memset(&ctx, 0, sizeof(ctx));
     367            1 :     ctx.transport = &t;
     368            1 :     ctx.session = &s;
     369              : 
     370            1 :     int rc = auth_step_req_pq(&ctx);
     371            1 :     ASSERT(rc == 0, "req_pq should succeed");
     372            1 :     ASSERT(ctx.pq == 21, "pq should be 21");
     373            1 :     ASSERT(memcmp(ctx.server_nonce, server_nonce, 16) == 0,
     374              :            "server_nonce should match");
     375            1 :     ASSERT(memcmp(ctx.nonce, expected_nonce, 16) == 0,
     376              :            "nonce should be 0xAA (from mock rand_bytes)");
     377              : 
     378            1 :     transport_close(&t);
     379              : }
     380              : 
     381            1 : void test_req_pq_sends_correct_tl(void) {
     382              :     Transport t;
     383              :     MtProtoSession s;
     384            1 :     test_init(&t, &s);
     385              : 
     386              :     uint8_t nonce[16];
     387            1 :     memset(nonce, 0xAA, 16);
     388              :     uint8_t server_nonce[16];
     389            1 :     memset(server_nonce, 0xBB, 16);
     390            1 :     uint8_t pq_be[] = { 0x15 };
     391              : 
     392              :     uint8_t resp[4096];
     393            1 :     size_t resp_len = build_res_pq(nonce, server_nonce,
     394              :                                     pq_be, sizeof(pq_be),
     395              :                                     TEST_RSA_FINGERPRINT, resp);
     396            1 :     mock_socket_set_response(resp, resp_len);
     397              : 
     398              :     AuthKeyCtx ctx;
     399            1 :     memset(&ctx, 0, sizeof(ctx));
     400            1 :     ctx.transport = &t;
     401            1 :     ctx.session = &s;
     402              : 
     403            1 :     auth_step_req_pq(&ctx);
     404              : 
     405              :     /* Verify sent data contains req_pq_multi */
     406            1 :     size_t sent_len = 0;
     407            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     408            1 :     ASSERT(sent_len > 0, "should have sent data");
     409              : 
     410              :     /* Sent data: abridged prefix + unenc header (20 bytes) + TL payload.
     411              :      * TL payload starts with CRC_req_pq_multi = 0xbe7e8ef1 (LE). */
     412              :     /* Skip abridged prefix (1 byte) + auth_key_id(8) + msg_id(8) + len(4) = 21 */
     413            1 :     ASSERT(sent_len >= 21 + 20, "sent data should be at least 41 bytes");
     414              :     uint32_t constructor;
     415            1 :     memcpy(&constructor, sent + 21, 4);
     416            1 :     ASSERT(constructor == 0xbe7e8ef1, "should send CRC_req_pq_multi");
     417              : 
     418            1 :     transport_close(&t);
     419              : }
     420              : 
     421            1 : void test_req_pq_wrong_nonce(void) {
     422              :     Transport t;
     423              :     MtProtoSession s;
     424            1 :     test_init(&t, &s);
     425              : 
     426              :     /* Build ResPQ with wrong nonce */
     427              :     uint8_t wrong_nonce[16];
     428            1 :     memset(wrong_nonce, 0x99, 16); /* 0x99 != 0xAA (mock rand_bytes output) */
     429              :     uint8_t server_nonce[16];
     430            1 :     memset(server_nonce, 0xBB, 16);
     431            1 :     uint8_t pq_be[] = { 0x15 };
     432              : 
     433              :     uint8_t resp[4096];
     434            1 :     size_t resp_len = build_res_pq(wrong_nonce, server_nonce,
     435              :                                     pq_be, sizeof(pq_be),
     436              :                                     TEST_RSA_FINGERPRINT, resp);
     437            1 :     mock_socket_set_response(resp, resp_len);
     438              : 
     439              :     AuthKeyCtx ctx;
     440            1 :     memset(&ctx, 0, sizeof(ctx));
     441            1 :     ctx.transport = &t;
     442            1 :     ctx.session = &s;
     443              : 
     444            1 :     int rc = auth_step_req_pq(&ctx);
     445            1 :     ASSERT(rc == -1, "req_pq should fail with wrong nonce");
     446              : 
     447            1 :     transport_close(&t);
     448              : }
     449              : 
     450            1 : void test_req_pq_no_fingerprint(void) {
     451              :     Transport t;
     452              :     MtProtoSession s;
     453            1 :     test_init(&t, &s);
     454              : 
     455              :     uint8_t nonce[16];
     456            1 :     memset(nonce, 0xAA, 16);
     457              :     uint8_t server_nonce[16];
     458            1 :     memset(server_nonce, 0xBB, 16);
     459            1 :     uint8_t pq_be[] = { 0x15 };
     460              : 
     461              :     /* Wrong fingerprint */
     462              :     uint8_t resp[4096];
     463            1 :     size_t resp_len = build_res_pq(nonce, server_nonce,
     464              :                                     pq_be, sizeof(pq_be),
     465              :                                     0xDEADBEEFULL, resp);
     466            1 :     mock_socket_set_response(resp, resp_len);
     467              : 
     468              :     AuthKeyCtx ctx;
     469            1 :     memset(&ctx, 0, sizeof(ctx));
     470            1 :     ctx.transport = &t;
     471            1 :     ctx.session = &s;
     472              : 
     473            1 :     int rc = auth_step_req_pq(&ctx);
     474            1 :     ASSERT(rc == -1, "req_pq should fail with wrong fingerprint");
     475              : 
     476            1 :     transport_close(&t);
     477              : }
     478              : 
     479            1 : void test_req_pq_wrong_constructor(void) {
     480              :     Transport t;
     481              :     MtProtoSession s;
     482            1 :     test_init(&t, &s);
     483              : 
     484              :     /* Build response with wrong constructor */
     485              :     TlWriter tl;
     486            1 :     tl_writer_init(&tl);
     487            1 :     tl_write_uint32(&tl, 0xDEADBEEF); /* wrong constructor */
     488            1 :     tl_write_int128(&tl, (uint8_t[16]){0});
     489            1 :     tl_write_int128(&tl, (uint8_t[16]){0});
     490              : 
     491              :     uint8_t resp[4096];
     492            1 :     size_t resp_len = build_unenc_response(tl.data, tl.len, resp);
     493            1 :     tl_writer_free(&tl);
     494            1 :     mock_socket_set_response(resp, resp_len);
     495              : 
     496              :     AuthKeyCtx ctx;
     497            1 :     memset(&ctx, 0, sizeof(ctx));
     498            1 :     ctx.transport = &t;
     499            1 :     ctx.session = &s;
     500              : 
     501            1 :     int rc = auth_step_req_pq(&ctx);
     502            1 :     ASSERT(rc == -1, "req_pq should fail with wrong constructor");
     503              : 
     504            1 :     transport_close(&t);
     505              : }
     506              : 
     507              : /* ======================================================================
     508              :  * Step 2: auth_step_req_dh tests
     509              :  * ====================================================================== */
     510              : 
     511            1 : void test_req_dh_sends_correct_tl(void) {
     512              :     Transport t;
     513              :     MtProtoSession s;
     514            1 :     test_init(&t, &s);
     515              : 
     516              :     AuthKeyCtx ctx;
     517            1 :     memset(&ctx, 0, sizeof(ctx));
     518            1 :     ctx.transport = &t;
     519            1 :     ctx.session = &s;
     520            1 :     ctx.pq = 21; /* 3 * 7 */
     521            1 :     memset(ctx.nonce, 0xAA, 16);
     522            1 :     memset(ctx.server_nonce, 0xBB, 16);
     523              : 
     524            1 :     int rc = auth_step_req_dh(&ctx);
     525            1 :     ASSERT(rc == 0, "req_dh should succeed");
     526              : 
     527              :     /* Verify p, q were factorized */
     528            1 :     ASSERT(ctx.p == 3, "p should be 3");
     529            1 :     ASSERT(ctx.q == 7, "q should be 7");
     530              : 
     531              :     /* Verify RSA encrypt was called */
     532            1 :     ASSERT(mock_crypto_rsa_encrypt_call_count() == 1,
     533              :            "RSA encrypt should be called once");
     534              : 
     535              :     /* Verify sent data contains CRC_req_DH_params */
     536            1 :     size_t sent_len = 0;
     537            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     538            1 :     ASSERT(sent_len > 21, "should have sent data");
     539              :     uint32_t constructor;
     540            1 :     memcpy(&constructor, sent + 21, 4);
     541            1 :     ASSERT(constructor == 0xd712e4be, "should send CRC_req_DH_params");
     542              : 
     543            1 :     transport_close(&t);
     544              : }
     545              : 
     546            1 : void test_req_dh_factorizes_pq(void) {
     547              :     Transport t;
     548              :     MtProtoSession s;
     549            1 :     test_init(&t, &s);
     550              : 
     551              :     AuthKeyCtx ctx;
     552            1 :     memset(&ctx, 0, sizeof(ctx));
     553            1 :     ctx.transport = &t;
     554            1 :     ctx.session = &s;
     555            1 :     ctx.pq = 10403; /* 101 * 103 */
     556            1 :     memset(ctx.nonce, 0xAA, 16);
     557            1 :     memset(ctx.server_nonce, 0xBB, 16);
     558              : 
     559            1 :     int rc = auth_step_req_dh(&ctx);
     560            1 :     ASSERT(rc == 0, "req_dh should succeed");
     561            1 :     ASSERT(ctx.p == 101, "p should be 101");
     562            1 :     ASSERT(ctx.q == 103, "q should be 103");
     563              : 
     564            1 :     transport_close(&t);
     565              : }
     566              : 
     567              : /* ======================================================================
     568              :  * Step 3: auth_step_parse_dh tests
     569              :  * ====================================================================== */
     570              : 
     571            1 : void test_parse_dh_success(void) {
     572              :     Transport t;
     573              :     MtProtoSession s;
     574            1 :     test_init(&t, &s);
     575              : 
     576              :     uint8_t nonce[16], server_nonce[16], new_nonce[32];
     577            1 :     memset(nonce, 0xAA, 16);
     578            1 :     memset(server_nonce, 0xBB, 16);
     579            1 :     memset(new_nonce, 0xCC, 32);
     580              : 
     581              :     /* Fake DH params */
     582              :     uint8_t dh_prime[32];
     583            1 :     memset(dh_prime, 0x11, 32);
     584              :     uint8_t g_a[32];
     585            1 :     memset(g_a, 0x22, 32);
     586            1 :     int32_t g = 3;
     587            1 :     int32_t server_time = 1700000000;
     588              : 
     589              :     uint8_t resp[8192];
     590            1 :     size_t resp_len = build_server_dh_params_ok(nonce, server_nonce, new_nonce,
     591              :                                                  g, dh_prime, 32,
     592              :                                                  g_a, 32, server_time, resp);
     593            1 :     mock_socket_set_response(resp, resp_len);
     594              : 
     595              :     AuthKeyCtx ctx;
     596            1 :     memset(&ctx, 0, sizeof(ctx));
     597            1 :     ctx.transport = &t;
     598            1 :     ctx.session = &s;
     599            1 :     memcpy(ctx.nonce, nonce, 16);
     600            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     601            1 :     memcpy(ctx.new_nonce, new_nonce, 32);
     602              : 
     603            1 :     int rc = auth_step_parse_dh(&ctx);
     604            1 :     ASSERT(rc == 0, "parse_dh should succeed");
     605            1 :     ASSERT(ctx.g == 3, "g should be 3");
     606            1 :     ASSERT(ctx.dh_prime_len == 32, "dh_prime_len should be 32");
     607            1 :     ASSERT(memcmp(ctx.dh_prime, dh_prime, 32) == 0, "dh_prime should match");
     608            1 :     ASSERT(ctx.g_a_len == 32, "g_a_len should be 32");
     609            1 :     ASSERT(memcmp(ctx.g_a, g_a, 32) == 0, "g_a should match");
     610            1 :     ASSERT(ctx.server_time == 1700000000, "server_time should match");
     611              : 
     612            1 :     transport_close(&t);
     613              : }
     614              : 
     615              : /* TEST-38: helper that calls auth_step_parse_dh with a specific g value */
     616            4 : static int parse_dh_with_g(int32_t g_val) {
     617              :     Transport t;
     618              :     MtProtoSession s;
     619            4 :     mock_socket_reset();
     620            4 :     mock_crypto_reset();
     621            4 :     transport_init(&t);
     622            4 :     transport_connect(&t, "localhost", 443);
     623            4 :     mock_socket_clear_sent();
     624            4 :     mtproto_session_init(&s);
     625              : 
     626              :     uint8_t nonce[16], server_nonce[16], new_nonce[32];
     627            4 :     memset(nonce, 0xAA, 16);
     628            4 :     memset(server_nonce, 0xBB, 16);
     629            4 :     memset(new_nonce, 0xCC, 32);
     630              : 
     631              :     uint8_t dh_prime[32];
     632            4 :     memset(dh_prime, 0x11, 32);
     633              :     uint8_t g_a[32];
     634            4 :     memset(g_a, 0x22, 32);
     635              : 
     636              :     uint8_t resp[8192];
     637            4 :     size_t resp_len = build_server_dh_params_ok(nonce, server_nonce, new_nonce,
     638              :                                                  g_val, dh_prime, 32,
     639              :                                                  g_a, 32, 1700000000, resp);
     640            4 :     mock_socket_set_response(resp, resp_len);
     641              : 
     642              :     AuthKeyCtx ctx;
     643            4 :     memset(&ctx, 0, sizeof(ctx));
     644            4 :     ctx.transport = &t;
     645            4 :     ctx.session = &s;
     646            4 :     memcpy(ctx.nonce, nonce, 16);
     647            4 :     memcpy(ctx.server_nonce, server_nonce, 16);
     648            4 :     memcpy(ctx.new_nonce, new_nonce, 32);
     649              : 
     650            4 :     int rc = auth_step_parse_dh(&ctx);
     651            4 :     transport_close(&t);
     652            4 :     return rc;
     653              : }
     654              : 
     655              : /* TEST-38: g=1 is below the allowed range {2..7} — must be rejected */
     656            1 : void test_parse_dh_rejects_g_1(void) {
     657            1 :     ASSERT(parse_dh_with_g(1) == -1,
     658              :            "TEST-38: g=1 must be rejected");
     659              : }
     660              : 
     661              : /* TEST-38: g=8 is above the allowed range {2..7} — must be rejected */
     662            1 : void test_parse_dh_rejects_g_8(void) {
     663            1 :     ASSERT(parse_dh_with_g(8) == -1,
     664              :            "TEST-38: g=8 must be rejected");
     665              : }
     666              : 
     667              : /* TEST-38: g=42 is far outside the allowed range — must be rejected */
     668            1 : void test_parse_dh_rejects_g_42(void) {
     669            1 :     ASSERT(parse_dh_with_g(42) == -1,
     670              :            "TEST-38: g=42 must be rejected");
     671              : }
     672              : 
     673              : /* TEST-38: g=3 is within the allowed range {2..7} — must be accepted */
     674            1 : void test_parse_dh_accepts_g_3(void) {
     675            1 :     ASSERT(parse_dh_with_g(3) == 0,
     676              :            "TEST-38: g=3 must be accepted");
     677              : }
     678              : 
     679            1 : void test_parse_dh_wrong_constructor(void) {
     680              :     Transport t;
     681              :     MtProtoSession s;
     682            1 :     test_init(&t, &s);
     683              : 
     684              :     TlWriter tl;
     685            1 :     tl_writer_init(&tl);
     686            1 :     tl_write_uint32(&tl, 0xDEADBEEF); /* wrong constructor */
     687            1 :     tl_write_int128(&tl, (uint8_t[16]){0});
     688            1 :     tl_write_int128(&tl, (uint8_t[16]){0});
     689              : 
     690              :     uint8_t resp[4096];
     691            1 :     size_t resp_len = build_unenc_response(tl.data, tl.len, resp);
     692            1 :     tl_writer_free(&tl);
     693            1 :     mock_socket_set_response(resp, resp_len);
     694              : 
     695              :     AuthKeyCtx ctx;
     696            1 :     memset(&ctx, 0, sizeof(ctx));
     697            1 :     ctx.transport = &t;
     698            1 :     ctx.session = &s;
     699              : 
     700            1 :     int rc = auth_step_parse_dh(&ctx);
     701            1 :     ASSERT(rc == -1, "parse_dh should fail with wrong constructor");
     702              : 
     703            1 :     transport_close(&t);
     704              : }
     705              : 
     706            1 : void test_parse_dh_nonce_mismatch(void) {
     707              :     Transport t;
     708              :     MtProtoSession s;
     709            1 :     test_init(&t, &s);
     710              : 
     711              :     /* Build response with nonce = 0x11..., but ctx expects 0xAA... */
     712              :     uint8_t wrong_nonce[16];
     713            1 :     memset(wrong_nonce, 0x11, 16);
     714              :     uint8_t server_nonce[16];
     715            1 :     memset(server_nonce, 0xBB, 16);
     716              : 
     717              :     TlWriter tl;
     718            1 :     tl_writer_init(&tl);
     719            1 :     tl_write_uint32(&tl, 0xd0e8075c); /* CRC_server_DH_params_ok */
     720            1 :     tl_write_int128(&tl, wrong_nonce);
     721            1 :     tl_write_int128(&tl, server_nonce);
     722              :     /* encrypted_answer (empty bytes trigger) */
     723              :     uint8_t fake_enc[16];
     724            1 :     memset(fake_enc, 0, 16);
     725            1 :     tl_write_bytes(&tl, fake_enc, 16);
     726              : 
     727              :     uint8_t resp[4096];
     728            1 :     size_t resp_len = build_unenc_response(tl.data, tl.len, resp);
     729            1 :     tl_writer_free(&tl);
     730            1 :     mock_socket_set_response(resp, resp_len);
     731              : 
     732              :     AuthKeyCtx ctx;
     733            1 :     memset(&ctx, 0, sizeof(ctx));
     734            1 :     ctx.transport = &t;
     735            1 :     ctx.session = &s;
     736            1 :     memset(ctx.nonce, 0xAA, 16); /* different from 0x11 */
     737            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     738              : 
     739            1 :     int rc = auth_step_parse_dh(&ctx);
     740            1 :     ASSERT(rc == -1, "parse_dh should fail with nonce mismatch");
     741              : 
     742            1 :     transport_close(&t);
     743              : }
     744              : 
     745              : /* ======================================================================
     746              :  * Step 4: auth_step_set_client_dh tests
     747              :  * ====================================================================== */
     748              : 
     749            1 : void test_set_client_dh_gen_ok(void) {
     750              :     Transport t;
     751              :     MtProtoSession s;
     752            1 :     test_init(&t, &s);
     753              : 
     754              :     uint8_t nonce[16], server_nonce[16], new_nonce[32];
     755            1 :     memset(nonce, 0xAA, 16);
     756            1 :     memset(server_nonce, 0xBB, 16);
     757            1 :     memset(new_nonce, 0xCC, 32);
     758              : 
     759              :     uint8_t resp[4096];
     760            1 :     size_t resp_len = build_dh_gen_ok(nonce, server_nonce, resp);
     761            1 :     mock_socket_set_response(resp, resp_len);
     762              : 
     763              :     AuthKeyCtx ctx;
     764            1 :     memset(&ctx, 0, sizeof(ctx));
     765            1 :     ctx.transport = &t;
     766            1 :     ctx.session = &s;
     767            1 :     memcpy(ctx.nonce, nonce, 16);
     768            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     769            1 :     memcpy(ctx.new_nonce, new_nonce, 32);
     770            1 :     ctx.g = 2;
     771              :     /* Fake DH prime and g_a */
     772            1 :     memset(ctx.dh_prime, 0x11, 32);
     773            1 :     ctx.dh_prime_len = 32;
     774            1 :     memset(ctx.g_a, 0x22, 32);
     775            1 :     ctx.g_a_len = 32;
     776              : 
     777            1 :     int rc = auth_step_set_client_dh(&ctx);
     778            1 :     ASSERT(rc == 0, "set_client_dh should succeed");
     779            1 :     ASSERT(s.has_auth_key == 1, "session should have auth_key set");
     780            1 :     ASSERT(s.server_salt != 0, "server_salt should be non-zero");
     781              : 
     782              :     /* Verify BN mod exp was called at least twice (g_b + auth_key) */
     783            1 :     ASSERT(mock_crypto_bn_mod_exp_call_count() == 2,
     784              :            "bn_mod_exp should be called twice");
     785              : 
     786            1 :     transport_close(&t);
     787              : }
     788              : 
     789              : /* QA-12: supply a dh_gen_ok response whose new_nonce_hash1 does NOT match
     790              :  * the value our client computes. Must be rejected with -1, auth key must
     791              :  * remain unset on the session. */
     792            1 : void test_set_client_dh_rejects_bad_new_nonce_hash(void) {
     793              :     Transport t;
     794              :     MtProtoSession s;
     795            1 :     test_init(&t, &s);
     796              : 
     797              :     uint8_t nonce[16], server_nonce[16], new_nonce[32];
     798            1 :     memset(nonce, 0xAA, 16);
     799            1 :     memset(server_nonce, 0xBB, 16);
     800            1 :     memset(new_nonce, 0xCC, 32);
     801              : 
     802              :     /* Build dh_gen_ok manually with an INVALID new_nonce_hash. */
     803            1 :     TlWriter tl; tl_writer_init(&tl);
     804            1 :     tl_write_uint32(&tl, 0x3bcbf734); /* CRC_dh_gen_ok */
     805            1 :     tl_write_int128(&tl, nonce);
     806            1 :     tl_write_int128(&tl, server_nonce);
     807              :     uint8_t bad_hash[16];
     808            1 :     memset(bad_hash, 0xDE, 16);     /* does not match expected zeros */
     809            1 :     tl_write_int128(&tl, bad_hash);
     810              :     uint8_t resp[4096];
     811            1 :     size_t resp_len = build_unenc_response(tl.data, tl.len, resp);
     812            1 :     tl_writer_free(&tl);
     813            1 :     mock_socket_set_response(resp, resp_len);
     814              : 
     815              :     AuthKeyCtx ctx;
     816            1 :     memset(&ctx, 0, sizeof(ctx));
     817            1 :     ctx.transport = &t;
     818            1 :     ctx.session = &s;
     819            1 :     memcpy(ctx.nonce, nonce, 16);
     820            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     821            1 :     memcpy(ctx.new_nonce, new_nonce, 32);
     822            1 :     ctx.g = 2;
     823            1 :     memset(ctx.dh_prime, 0x11, 32);
     824            1 :     ctx.dh_prime_len = 32;
     825            1 :     memset(ctx.g_a, 0x22, 32);
     826            1 :     ctx.g_a_len = 32;
     827              : 
     828            1 :     int rc = auth_step_set_client_dh(&ctx);
     829            1 :     ASSERT(rc == -1, "QA-12: bad new_nonce_hash1 must be rejected");
     830            1 :     ASSERT(s.has_auth_key == 0, "auth_key must NOT be set on MITM failure");
     831              : 
     832            1 :     transport_close(&t);
     833              : }
     834              : 
     835            1 : void test_set_client_dh_sends_tl(void) {
     836              :     Transport t;
     837              :     MtProtoSession s;
     838            1 :     test_init(&t, &s);
     839              : 
     840              :     uint8_t nonce[16], server_nonce[16], new_nonce[32];
     841            1 :     memset(nonce, 0xAA, 16);
     842            1 :     memset(server_nonce, 0xBB, 16);
     843            1 :     memset(new_nonce, 0xCC, 32);
     844              : 
     845              :     uint8_t resp[4096];
     846            1 :     size_t resp_len = build_dh_gen_ok(nonce, server_nonce, resp);
     847            1 :     mock_socket_set_response(resp, resp_len);
     848              : 
     849              :     AuthKeyCtx ctx;
     850            1 :     memset(&ctx, 0, sizeof(ctx));
     851            1 :     ctx.transport = &t;
     852            1 :     ctx.session = &s;
     853            1 :     memcpy(ctx.nonce, nonce, 16);
     854            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     855            1 :     memcpy(ctx.new_nonce, new_nonce, 32);
     856            1 :     ctx.g = 2;
     857            1 :     memset(ctx.dh_prime, 0x11, 32);
     858            1 :     ctx.dh_prime_len = 32;
     859            1 :     memset(ctx.g_a, 0x22, 32);
     860            1 :     ctx.g_a_len = 32;
     861              : 
     862            1 :     auth_step_set_client_dh(&ctx);
     863              : 
     864              :     /* Verify sent data contains CRC_set_client_DH_params */
     865            1 :     size_t sent_len = 0;
     866            1 :     const uint8_t *sent = mock_socket_get_sent(&sent_len);
     867            1 :     ASSERT(sent_len > 21, "should have sent data");
     868              :     uint32_t constructor;
     869            1 :     memcpy(&constructor, sent + 21, 4);
     870            1 :     ASSERT(constructor == 0xf5045f1f, "should send CRC_set_client_DH_params");
     871              : 
     872            1 :     transport_close(&t);
     873              : }
     874              : 
     875            1 : void test_set_client_dh_gen_retry(void) {
     876              :     Transport t;
     877              :     MtProtoSession s;
     878            1 :     test_init(&t, &s);
     879              : 
     880              :     uint8_t nonce[16], server_nonce[16];
     881            1 :     memset(nonce, 0xAA, 16);
     882            1 :     memset(server_nonce, 0xBB, 16);
     883              : 
     884              :     uint8_t resp[4096];
     885            1 :     size_t resp_len = build_dh_gen_response(0x46dc1fb9, /* dh_gen_retry */
     886              :                                              nonce, server_nonce, resp);
     887            1 :     mock_socket_set_response(resp, resp_len);
     888              : 
     889              :     AuthKeyCtx ctx;
     890            1 :     memset(&ctx, 0, sizeof(ctx));
     891            1 :     ctx.transport = &t;
     892            1 :     ctx.session = &s;
     893            1 :     memcpy(ctx.nonce, nonce, 16);
     894            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     895            1 :     ctx.g = 2;
     896            1 :     memset(ctx.dh_prime, 0x11, 32);
     897            1 :     ctx.dh_prime_len = 32;
     898            1 :     memset(ctx.g_a, 0x22, 32);
     899            1 :     ctx.g_a_len = 32;
     900              : 
     901            1 :     int rc = auth_step_set_client_dh(&ctx);
     902            1 :     ASSERT(rc == -1, "set_client_dh should fail on dh_gen_retry");
     903              : 
     904            1 :     transport_close(&t);
     905              : }
     906              : 
     907            1 : void test_set_client_dh_gen_fail(void) {
     908              :     Transport t;
     909              :     MtProtoSession s;
     910            1 :     test_init(&t, &s);
     911              : 
     912              :     uint8_t nonce[16], server_nonce[16];
     913            1 :     memset(nonce, 0xAA, 16);
     914            1 :     memset(server_nonce, 0xBB, 16);
     915              : 
     916              :     uint8_t resp[4096];
     917            1 :     size_t resp_len = build_dh_gen_response(0xa69dae02, /* dh_gen_fail */
     918              :                                              nonce, server_nonce, resp);
     919            1 :     mock_socket_set_response(resp, resp_len);
     920              : 
     921              :     AuthKeyCtx ctx;
     922            1 :     memset(&ctx, 0, sizeof(ctx));
     923            1 :     ctx.transport = &t;
     924            1 :     ctx.session = &s;
     925            1 :     memcpy(ctx.nonce, nonce, 16);
     926            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     927            1 :     ctx.g = 2;
     928            1 :     memset(ctx.dh_prime, 0x11, 32);
     929            1 :     ctx.dh_prime_len = 32;
     930            1 :     memset(ctx.g_a, 0x22, 32);
     931            1 :     ctx.g_a_len = 32;
     932              : 
     933            1 :     int rc = auth_step_set_client_dh(&ctx);
     934            1 :     ASSERT(rc == -1, "set_client_dh should fail on dh_gen_fail");
     935              : 
     936            1 :     transport_close(&t);
     937              : }
     938              : 
     939            1 : void test_set_client_dh_salt_computation(void) {
     940              :     Transport t;
     941              :     MtProtoSession s;
     942            1 :     test_init(&t, &s);
     943              : 
     944              :     /* Use specific nonces to verify XOR */
     945              :     uint8_t nonce[16];
     946            1 :     memset(nonce, 0xAA, 16);
     947              :     uint8_t server_nonce[16];
     948            1 :     memset(server_nonce, 0xBB, 16);
     949              :     uint8_t new_nonce[32];
     950            1 :     memset(new_nonce, 0x11, 32);
     951              : 
     952              :     uint8_t resp[4096];
     953            1 :     size_t resp_len = build_dh_gen_ok(nonce, server_nonce, resp);
     954            1 :     mock_socket_set_response(resp, resp_len);
     955              : 
     956              :     AuthKeyCtx ctx;
     957            1 :     memset(&ctx, 0, sizeof(ctx));
     958            1 :     ctx.transport = &t;
     959            1 :     ctx.session = &s;
     960            1 :     memcpy(ctx.nonce, nonce, 16);
     961            1 :     memcpy(ctx.server_nonce, server_nonce, 16);
     962            1 :     memcpy(ctx.new_nonce, new_nonce, 32);
     963            1 :     ctx.g = 2;
     964            1 :     memset(ctx.dh_prime, 0x11, 32);
     965            1 :     ctx.dh_prime_len = 32;
     966            1 :     memset(ctx.g_a, 0x22, 32);
     967            1 :     ctx.g_a_len = 32;
     968              : 
     969            1 :     auth_step_set_client_dh(&ctx);
     970              : 
     971              :     /* Expected salt: new_nonce[0:8] XOR server_nonce[0:8]
     972              :      * = 0x11 XOR 0xBB = 0xAA for each byte */
     973              :     uint8_t expected_salt[8];
     974            1 :     memset(expected_salt, 0xAA, 8);
     975              :     uint64_t expected;
     976            1 :     memcpy(&expected, expected_salt, 8);
     977            1 :     ASSERT(s.server_salt == expected,
     978              :            "server_salt should be new_nonce XOR server_nonce");
     979              : 
     980            1 :     transport_close(&t);
     981              : }
     982              : 
     983              : /* ======================================================================
     984              :  * Integration test: full auth_key_gen flow
     985              :  * ====================================================================== */
     986              : 
     987            1 : void test_auth_key_gen_full_flow(void) {
     988              :     Transport t;
     989              :     MtProtoSession s;
     990            1 :     test_init(&t, &s);
     991              : 
     992              :     /* The nonce will be 0xAA... (from mock rand_bytes) */
     993              :     uint8_t nonce[16];
     994            1 :     memset(nonce, 0xAA, 16);
     995              :     uint8_t server_nonce[16];
     996            1 :     memset(server_nonce, 0xBB, 16);
     997              :     /* new_nonce will also be 0xAA (mock rand_bytes always returns 0xAA) */
     998              :     uint8_t new_nonce[32];
     999            1 :     memset(new_nonce, 0xAA, 32);
    1000              : 
    1001              :     /* pq = 21 (3 * 7) */
    1002            1 :     uint8_t pq_be[] = { 0x15 };
    1003              : 
    1004              :     /* Fake DH params */
    1005              :     uint8_t dh_prime[32];
    1006            1 :     memset(dh_prime, 0x11, 32);
    1007              :     uint8_t g_a[32];
    1008            1 :     memset(g_a, 0x22, 32);
    1009              : 
    1010              :     /* Response 1: ResPQ */
    1011              :     uint8_t resp1[4096];
    1012            1 :     size_t resp1_len = build_res_pq(nonce, server_nonce,
    1013              :                                      pq_be, sizeof(pq_be),
    1014              :                                      TEST_RSA_FINGERPRINT, resp1);
    1015              : 
    1016              :     /* Response 2: server_DH_params_ok
    1017              :      * Note: we need to reset sha1 count because build_server_dh_params_ok
    1018              :      * calls crypto_sha1. But that's OK — we just need the responses queued. */
    1019              :     uint8_t resp2[8192];
    1020            1 :     size_t resp2_len = build_server_dh_params_ok(nonce, server_nonce, new_nonce,
    1021              :                                                   3, dh_prime, 32,
    1022              :                                                   g_a, 32, 1700000000, resp2);
    1023              : 
    1024              :     /* Response 3: dh_gen_ok */
    1025              :     uint8_t resp3[4096];
    1026            1 :     size_t resp3_len = build_dh_gen_ok(nonce, server_nonce, resp3);
    1027              : 
    1028              :     /* Queue all responses */
    1029            1 :     mock_socket_set_response(resp1, resp1_len);
    1030            1 :     mock_socket_append_response(resp2, resp2_len);
    1031            1 :     mock_socket_append_response(resp3, resp3_len);
    1032              : 
    1033              :     /* Reset crypto counters after building responses */
    1034            1 :     mock_crypto_reset();
    1035              : 
    1036            1 :     int rc = mtproto_auth_key_gen(&t, &s);
    1037            1 :     ASSERT(rc == 0, "auth_key_gen should succeed");
    1038            1 :     ASSERT(s.has_auth_key == 1, "session should have auth_key");
    1039            1 :     ASSERT(s.server_salt != 0, "server_salt should be non-zero");
    1040              : 
    1041              :     /* Verify crypto usage */
    1042            1 :     ASSERT(mock_crypto_rand_bytes_call_count() >= 3,
    1043              :            "rand_bytes should be called at least 3 times (nonce, new_nonce, b)");
    1044            1 :     ASSERT(mock_crypto_rsa_encrypt_call_count() == 1,
    1045              :            "RSA encrypt should be called once");
    1046            1 :     ASSERT(mock_crypto_bn_mod_exp_call_count() == 2,
    1047              :            "bn_mod_exp should be called twice (g_b and auth_key)");
    1048              : 
    1049            1 :     transport_close(&t);
    1050              : }
    1051              : 
    1052              : /* FEAT-19: fp_count exceeding MAX_FP_COUNT (64) must be rejected */
    1053            1 : void test_req_pq_fp_count_exceeds_cap(void) {
    1054              :     Transport t;
    1055              :     MtProtoSession s;
    1056            1 :     test_init(&t, &s);
    1057              : 
    1058              :     uint8_t nonce[16];
    1059            1 :     memset(nonce, 0xAA, 16);
    1060              :     uint8_t server_nonce[16];
    1061            1 :     memset(server_nonce, 0xBB, 16);
    1062            1 :     uint8_t pq_be[] = { 0x15 };
    1063              : 
    1064              :     /* fp_count = 1000000 — far above cap of 64 */
    1065              :     uint8_t resp[4096];
    1066            1 :     size_t resp_len = build_res_pq_fp_count(nonce, server_nonce,
    1067              :                                              pq_be, sizeof(pq_be),
    1068              :                                              1000000,
    1069              :                                              TEST_RSA_FINGERPRINT, resp);
    1070            1 :     mock_socket_set_response(resp, resp_len);
    1071              : 
    1072              :     AuthKeyCtx ctx;
    1073            1 :     memset(&ctx, 0, sizeof(ctx));
    1074            1 :     ctx.transport = &t;
    1075            1 :     ctx.session = &s;
    1076              : 
    1077            1 :     int rc = auth_step_req_pq(&ctx);
    1078            1 :     ASSERT(rc == -1, "FEAT-19: fp_count 1000000 must be rejected");
    1079              : 
    1080            1 :     transport_close(&t);
    1081              : }
    1082              : 
    1083              : /* FEAT-19: fp_count within cap (3) must be accepted when fingerprint matches */
    1084            1 : void test_req_pq_fp_count_within_cap(void) {
    1085              :     Transport t;
    1086              :     MtProtoSession s;
    1087            1 :     test_init(&t, &s);
    1088              : 
    1089              :     uint8_t nonce[16];
    1090            1 :     memset(nonce, 0xAA, 16);
    1091              :     uint8_t server_nonce[16];
    1092            1 :     memset(server_nonce, 0xBB, 16);
    1093            1 :     uint8_t pq_be[] = { 0x15 };
    1094              : 
    1095              :     /* Build ResPQ with 3 fingerprints: two dummies + our real one */
    1096              :     TlWriter tl;
    1097            1 :     tl_writer_init(&tl);
    1098            1 :     tl_write_uint32(&tl, 0x05162463); /* CRC_resPQ */
    1099            1 :     tl_write_int128(&tl, nonce);
    1100            1 :     tl_write_int128(&tl, server_nonce);
    1101            1 :     tl_write_bytes(&tl, pq_be, sizeof(pq_be));
    1102            1 :     tl_write_uint32(&tl, 0x1cb5c415); /* vector constructor */
    1103            1 :     tl_write_uint32(&tl, 3);          /* count = 3 */
    1104            1 :     tl_write_uint64(&tl, 0xDEAD000000000001ULL); /* dummy */
    1105            1 :     tl_write_uint64(&tl, 0xDEAD000000000002ULL); /* dummy */
    1106            1 :     tl_write_uint64(&tl, TEST_RSA_FINGERPRINT);  /* real */
    1107              : 
    1108              :     uint8_t resp[4096];
    1109            1 :     size_t resp_len = build_unenc_response(tl.data, tl.len, resp);
    1110            1 :     tl_writer_free(&tl);
    1111            1 :     mock_socket_set_response(resp, resp_len);
    1112              : 
    1113              :     AuthKeyCtx ctx;
    1114            1 :     memset(&ctx, 0, sizeof(ctx));
    1115            1 :     ctx.transport = &t;
    1116            1 :     ctx.session = &s;
    1117              : 
    1118            1 :     int rc = auth_step_req_pq(&ctx);
    1119            1 :     ASSERT(rc == 0, "FEAT-19: fp_count 3 with matching fingerprint must succeed");
    1120            1 :     ASSERT(ctx.pq == 21, "pq should be 21");
    1121              : 
    1122            1 :     transport_close(&t);
    1123              : }
    1124              : 
    1125            1 : void test_auth_key_gen_null_args(void) {
    1126              :     Transport t;
    1127              :     MtProtoSession s;
    1128            1 :     transport_init(&t);
    1129            1 :     mtproto_session_init(&s);
    1130              : 
    1131            1 :     ASSERT(mtproto_auth_key_gen(NULL, &s) == -1, "NULL transport should fail");
    1132            1 :     ASSERT(mtproto_auth_key_gen(&t, NULL) == -1, "NULL session should fail");
    1133              : }
    1134              : 
    1135              : /* ======================================================================
    1136              :  * Test suite entry point
    1137              :  * ====================================================================== */
    1138              : 
    1139            1 : void test_auth(void) {
    1140              :     /* PQ factorization */
    1141            1 :     RUN_TEST(test_pq_factorize_simple);
    1142            1 :     RUN_TEST(test_pq_factorize_larger);
    1143            1 :     RUN_TEST(test_pq_factorize_product_of_large_primes);
    1144            1 :     RUN_TEST(test_pq_factorize_small_primes);
    1145            1 :     RUN_TEST(test_pq_factorize_unequal_primes);
    1146            1 :     RUN_TEST(test_pq_factorize_invalid);
    1147            1 :     RUN_TEST(test_pq_factorize_null);
    1148            1 :     RUN_TEST(test_pq_factorize_mtproto_sized);
    1149            1 :     RUN_TEST(test_pq_factorize_rejects_wide_factors);
    1150              : 
    1151              :     /* Step 1: req_pq */
    1152            1 :     RUN_TEST(test_req_pq_parses_respq);
    1153            1 :     RUN_TEST(test_req_pq_sends_correct_tl);
    1154            1 :     RUN_TEST(test_req_pq_wrong_nonce);
    1155            1 :     RUN_TEST(test_req_pq_no_fingerprint);
    1156            1 :     RUN_TEST(test_req_pq_wrong_constructor);
    1157            1 :     RUN_TEST(test_req_pq_fp_count_exceeds_cap);
    1158            1 :     RUN_TEST(test_req_pq_fp_count_within_cap);
    1159              : 
    1160              :     /* Step 2: req_dh */
    1161            1 :     RUN_TEST(test_req_dh_sends_correct_tl);
    1162            1 :     RUN_TEST(test_req_dh_factorizes_pq);
    1163              : 
    1164              :     /* Step 3: parse_dh */
    1165            1 :     RUN_TEST(test_parse_dh_success);
    1166            1 :     RUN_TEST(test_parse_dh_wrong_constructor);
    1167            1 :     RUN_TEST(test_parse_dh_nonce_mismatch);
    1168            1 :     RUN_TEST(test_parse_dh_rejects_g_1);
    1169            1 :     RUN_TEST(test_parse_dh_rejects_g_8);
    1170            1 :     RUN_TEST(test_parse_dh_rejects_g_42);
    1171            1 :     RUN_TEST(test_parse_dh_accepts_g_3);
    1172              : 
    1173              :     /* Step 4: set_client_dh */
    1174            1 :     RUN_TEST(test_set_client_dh_gen_ok);
    1175            1 :     RUN_TEST(test_set_client_dh_rejects_bad_new_nonce_hash);
    1176            1 :     RUN_TEST(test_set_client_dh_sends_tl);
    1177            1 :     RUN_TEST(test_set_client_dh_gen_retry);
    1178            1 :     RUN_TEST(test_set_client_dh_gen_fail);
    1179            1 :     RUN_TEST(test_set_client_dh_salt_computation);
    1180              : 
    1181              :     /* Integration */
    1182            1 :     RUN_TEST(test_auth_key_gen_full_flow);
    1183            1 :     RUN_TEST(test_auth_key_gen_null_args);
    1184            1 : }
        

Generated by: LCOV version 2.0-1