LCOV - code coverage report
Current view: top level - tests/functional - test_mtproto_crypto_functional.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 79 79
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 10 10

            Line data    Source code
       1              : /**
       2              :  * @file test_mtproto_crypto_functional.c
       3              :  * @brief Functional tests for MTProto 2.0 encryption layer (real OpenSSL).
       4              :  *
       5              :  * Unlike unit tests, these link against the real crypto.c and ige_aes.c,
       6              :  * so they verify the full crypto pipeline including actual OpenSSL calls.
       7              :  *
       8              :  * Test strategy:
       9              :  *   1. mtproto_derive_keys: deterministic, direction 0 vs 8 differ.
      10              :  *   2. mtproto_compute_msg_key: output changes with different inputs.
      11              :  *   3. encrypt/decrypt round-trip via the mathematically correct path:
      12              :  *      build padded plaintext manually → compute msg_key → encrypt with
      13              :  *      mtproto (direct IGE call) → decrypt with mtproto_decrypt.
      14              :  *   4. msg_key corruption: mtproto_decrypt returns -1 when msg_key is wrong.
      15              :  */
      16              : 
      17              : #include "test_helpers.h"
      18              : #include "mtproto_crypto.h"
      19              : #include "ige_aes.h"
      20              : #include "crypto.h"
      21              : 
      22              : #include <string.h>
      23              : #include <stdint.h>
      24              : #include <stddef.h>
      25              : 
      26              : #define AUTH_KEY_SIZE 256
      27              : #define BLOCK 16
      28              : 
      29           28 : static void fill_pattern(uint8_t *buf, size_t len, uint8_t base) {
      30         4188 :     for (size_t i = 0; i < len; i++) buf[i] = (uint8_t)(base + (uint8_t)i);
      31           28 : }
      32              : 
      33              : /* ---- Test: key derivation is deterministic ---- */
      34            2 : static void test_derive_keys_deterministic(void) {
      35              :     uint8_t auth_key[AUTH_KEY_SIZE], msg_key[16];
      36              :     uint8_t aes_key1[32], aes_iv1[32];
      37              :     uint8_t aes_key2[32], aes_iv2[32];
      38              : 
      39            2 :     fill_pattern(auth_key, AUTH_KEY_SIZE, 0x01);
      40            2 :     fill_pattern(msg_key, 16, 0xAA);
      41              : 
      42            2 :     mtproto_derive_keys(auth_key, msg_key, 0, aes_key1, aes_iv1);
      43            2 :     mtproto_derive_keys(auth_key, msg_key, 0, aes_key2, aes_iv2);
      44              : 
      45            2 :     ASSERT(memcmp(aes_key1, aes_key2, 32) == 0,
      46              :            "derive_keys: same inputs must produce same key");
      47            2 :     ASSERT(memcmp(aes_iv1,  aes_iv2,  32) == 0,
      48              :            "derive_keys: same inputs must produce same IV");
      49              :     /* Key must be non-trivial */
      50            2 :     uint8_t zero32[32] = {0};
      51            2 :     ASSERT(memcmp(aes_key1, zero32, 32) != 0,
      52              :            "derive_keys: key must not be all-zero");
      53              : }
      54              : 
      55              : /* ---- Test: direction 0 and 8 produce different keys ---- */
      56            2 : static void test_derive_keys_direction_differs(void) {
      57              :     uint8_t auth_key[AUTH_KEY_SIZE], msg_key[16];
      58              :     uint8_t key_c2s[32], iv_c2s[32];
      59              :     uint8_t key_s2c[32], iv_s2c[32];
      60              : 
      61            2 :     fill_pattern(auth_key, AUTH_KEY_SIZE, 0x55);
      62            2 :     fill_pattern(msg_key,  16,            0x33);
      63              : 
      64            2 :     mtproto_derive_keys(auth_key, msg_key, 0, key_c2s, iv_c2s);
      65            2 :     mtproto_derive_keys(auth_key, msg_key, 8, key_s2c, iv_s2c);
      66              : 
      67            2 :     ASSERT(memcmp(key_c2s, key_s2c, 32) != 0,
      68              :            "derive_keys: c2s and s2c keys must differ");
      69              : }
      70              : 
      71              : /* ---- Test: msg_key changes with different auth_key ---- */
      72            2 : static void test_msg_key_auth_key_sensitivity(void) {
      73              :     uint8_t auth_key1[AUTH_KEY_SIZE], auth_key2[AUTH_KEY_SIZE];
      74              :     uint8_t plain[48];
      75              :     uint8_t mk1[16], mk2[16];
      76              : 
      77            2 :     fill_pattern(auth_key1, AUTH_KEY_SIZE, 0x11);
      78            2 :     memcpy(auth_key2, auth_key1, AUTH_KEY_SIZE);
      79            2 :     auth_key2[88] ^= 0x01;  /* offset 88 is within the slice used by compute_msg_key */
      80            2 :     fill_pattern(plain, 48, 0xBB);
      81              : 
      82            2 :     mtproto_compute_msg_key(auth_key1, plain, 48, 0, mk1);
      83            2 :     mtproto_compute_msg_key(auth_key2, plain, 48, 0, mk2);
      84              : 
      85            2 :     ASSERT(memcmp(mk1, mk2, 16) != 0,
      86              :            "msg_key: different auth_key must produce different msg_key");
      87              : }
      88              : 
      89              : /* ---- Test: msg_key changes with different plaintext ---- */
      90            2 : static void test_msg_key_plain_sensitivity(void) {
      91              :     uint8_t auth_key[AUTH_KEY_SIZE];
      92              :     uint8_t plain1[48], plain2[48];
      93              :     uint8_t mk1[16], mk2[16];
      94              : 
      95            2 :     fill_pattern(auth_key, AUTH_KEY_SIZE, 0x44);
      96            2 :     fill_pattern(plain1, 48, 0xCC);
      97            2 :     memcpy(plain2, plain1, 48);
      98            2 :     plain2[0] ^= 0x01;
      99              : 
     100            2 :     mtproto_compute_msg_key(auth_key, plain1, 48, 0, mk1);
     101            2 :     mtproto_compute_msg_key(auth_key, plain2, 48, 0, mk2);
     102              : 
     103            2 :     ASSERT(memcmp(mk1, mk2, 16) != 0,
     104              :            "msg_key: different plaintext must produce different msg_key");
     105              : }
     106              : 
     107              : /* ---- Test: encrypt/decrypt round-trip at the mathematical level ----
     108              :  *
     109              :  * We manually build a padded plaintext (already aligned), compute msg_key,
     110              :  * derive AES keys, encrypt with IGE directly, then pass the result and the
     111              :  * correct msg_key to mtproto_decrypt.  This tests the full decrypt pipeline
     112              :  * without depending on the internal padding of mtproto_encrypt.
     113              :  */
     114            2 : static void test_mtproto_decrypt_roundtrip(void) {
     115              :     uint8_t auth_key[AUTH_KEY_SIZE];
     116              :     /* Padded plaintext: 64 bytes (multiple of 16, satisfies >=12 pad requirement) */
     117              :     uint8_t padded[64];
     118            2 :     fill_pattern(auth_key, AUTH_KEY_SIZE, 0x77);
     119            2 :     fill_pattern(padded,   64,            0x42);
     120              : 
     121              :     /* Compute msg_key from padded plaintext (direction 0 = client→server) */
     122              :     uint8_t msg_key[16];
     123            2 :     mtproto_compute_msg_key(auth_key, padded, 64, 0, msg_key);
     124              : 
     125              :     /* Derive AES key + IV */
     126              :     uint8_t aes_key[32], aes_iv[32];
     127            2 :     mtproto_derive_keys(auth_key, msg_key, 0, aes_key, aes_iv);
     128              : 
     129              :     /* Encrypt with AES-256-IGE */
     130              :     uint8_t cipher[64];
     131            2 :     aes_ige_encrypt(padded, 64, aes_key, aes_iv, cipher);
     132              : 
     133              :     /* Now decrypt using mtproto_decrypt — it must verify msg_key and succeed */
     134              :     uint8_t recovered[64];
     135            2 :     size_t rec_len = 0;
     136            2 :     int rc = mtproto_decrypt(cipher, 64, auth_key, msg_key, 0, recovered, &rec_len);
     137              : 
     138            2 :     ASSERT(rc == 0,    "mtproto_decrypt: must succeed with correct msg_key");
     139            2 :     ASSERT(rec_len == 64, "mtproto_decrypt: recovered length must match");
     140            2 :     ASSERT(memcmp(padded, recovered, 64) == 0,
     141              :            "mtproto_decrypt: recovered plaintext must match original");
     142              : }
     143              : 
     144              : /* ---- Test: msg_key corruption causes decrypt failure ---- */
     145            2 : static void test_mtproto_decrypt_bad_msg_key(void) {
     146              :     uint8_t auth_key[AUTH_KEY_SIZE];
     147              :     uint8_t padded[48];
     148            2 :     fill_pattern(auth_key, AUTH_KEY_SIZE, 0x99);
     149            2 :     fill_pattern(padded,   48,            0x12);
     150              : 
     151              :     uint8_t msg_key[16];
     152            2 :     mtproto_compute_msg_key(auth_key, padded, 48, 0, msg_key);
     153              : 
     154              :     uint8_t aes_key[32], aes_iv[32];
     155            2 :     mtproto_derive_keys(auth_key, msg_key, 0, aes_key, aes_iv);
     156              : 
     157              :     uint8_t cipher[48];
     158            2 :     aes_ige_encrypt(padded, 48, aes_key, aes_iv, cipher);
     159              : 
     160              :     /* Corrupt msg_key */
     161              :     uint8_t bad_key[16];
     162            2 :     memcpy(bad_key, msg_key, 16);
     163            2 :     bad_key[0] ^= 0xFF;
     164              : 
     165              :     uint8_t recovered[48];
     166            2 :     size_t rec_len = 0;
     167            2 :     int rc = mtproto_decrypt(cipher, 48, auth_key, bad_key, 0, recovered, &rec_len);
     168              : 
     169            2 :     ASSERT(rc != 0, "mtproto_decrypt: must fail when msg_key is corrupted");
     170              : }
     171              : 
     172              : /* ---- Test: mtproto_gen_padding produces valid length ---- */
     173            2 : static void test_gen_padding_length(void) {
     174              :     uint8_t padding[1024];
     175           36 :     for (size_t plain_len = 0; plain_len <= 256; plain_len += 16) {
     176           34 :         size_t pad_len = mtproto_gen_padding(plain_len, padding);
     177           34 :         ASSERT(pad_len >= 12,                  "gen_padding: must be >= 12");
     178           34 :         ASSERT((plain_len + pad_len) % 16 == 0, "gen_padding: total must be 16-aligned");
     179           34 :         ASSERT(pad_len <= 1024,                "gen_padding: must be <= 1024");
     180              :     }
     181              : }
     182              : 
     183              : /* ---- Test: encrypted output differs from plaintext ---- */
     184            2 : static void test_mtproto_encrypt_non_trivial(void) {
     185              :     uint8_t auth_key[AUTH_KEY_SIZE], padded[48];
     186            2 :     fill_pattern(auth_key, AUTH_KEY_SIZE, 0x55);
     187            2 :     fill_pattern(padded,   48,            0xCC);
     188              : 
     189              :     uint8_t msg_key[16];
     190            2 :     mtproto_compute_msg_key(auth_key, padded, 48, 0, msg_key);
     191              :     uint8_t aes_key[32], aes_iv[32];
     192            2 :     mtproto_derive_keys(auth_key, msg_key, 0, aes_key, aes_iv);
     193              : 
     194              :     uint8_t cipher[48];
     195            2 :     aes_ige_encrypt(padded, 48, aes_key, aes_iv, cipher);
     196              : 
     197            2 :     ASSERT(memcmp(padded, cipher, 48) != 0,
     198              :            "encrypt: output must differ from plaintext");
     199              : }
     200              : 
     201            2 : void run_mtproto_crypto_functional_tests(void) {
     202            2 :     RUN_TEST(test_derive_keys_deterministic);
     203            2 :     RUN_TEST(test_derive_keys_direction_differs);
     204            2 :     RUN_TEST(test_msg_key_auth_key_sensitivity);
     205            2 :     RUN_TEST(test_msg_key_plain_sensitivity);
     206            2 :     RUN_TEST(test_mtproto_decrypt_roundtrip);
     207            2 :     RUN_TEST(test_mtproto_decrypt_bad_msg_key);
     208            2 :     RUN_TEST(test_gen_padding_length);
     209            2 :     RUN_TEST(test_mtproto_encrypt_non_trivial);
     210            2 : }
        

Generated by: LCOV version 2.0-1