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

            Line data    Source code
       1              : /**
       2              :  * @file test_mtproto_crypto.c
       3              :  * @brief Unit tests for MTProto 2.0 crypto layer.
       4              :  *
       5              :  * Uses mock crypto for logic verification (call counts, key derivation structure).
       6              :  * Real-crypto functional tests will be in a separate test binary.
       7              :  *
       8              :  * Known mock limitations (functional tests needed):
       9              :  *   - Mock SHA256 returns fixed output regardless of input → cannot verify
      10              :  *     that different auth_keys produce different derived keys.
      11              :  *   - Mock encrypt_block is identity → cannot verify wrong-auth-key rejection
      12              :  *     (msg_key verification passes because SHA256 is input-independent).
      13              :  *   - Round-trip works because encrypt/decrypt are symmetric under identity.
      14              :  */
      15              : 
      16              : #include "test_helpers.h"
      17              : #include "mtproto_crypto.h"
      18              : #include "mock_crypto.h"
      19              : #include "crypto.h"
      20              : 
      21              : #include <stdlib.h>
      22              : #include <string.h>
      23              : 
      24            1 : void test_derive_keys_sha256_count(void) {
      25            1 :     mock_crypto_reset();
      26            1 :     uint8_t auth_key[256] = {0}, msg_key[16] = {0};
      27              :     uint8_t aes_key[32], aes_iv[32];
      28              : 
      29            1 :     mtproto_derive_keys(auth_key, msg_key, 0, aes_key, aes_iv);
      30              : 
      31              :     /* derive_keys calls SHA256 twice (sha256_a + sha256_b) */
      32            1 :     ASSERT(mock_crypto_sha256_call_count() == 2,
      33              :            "derive_keys should call SHA256 exactly 2 times");
      34              : }
      35              : 
      36            1 : void test_derive_keys_direction_diff(void) {
      37              :     /* Verify that both directions call SHA256 (logic is exercised) */
      38            1 :     mock_crypto_reset();
      39              :     uint8_t auth_key[256], msg_key[16];
      40            1 :     memset(auth_key, 0x42, 256);
      41            1 :     memset(msg_key, 0x55, 16);
      42              : 
      43              :     uint8_t key0[32], iv0[32], key8[32], iv8[32];
      44              : 
      45            1 :     mtproto_derive_keys(auth_key, msg_key, 0, key0, iv0);
      46              :     /* direction=0 called SHA256 2 times */
      47            1 :     ASSERT(mock_crypto_sha256_call_count() == 2,
      48              :            "direction=0 should call SHA256 twice");
      49              : 
      50            1 :     mock_crypto_reset();
      51            1 :     mtproto_derive_keys(auth_key, msg_key, 8, key8, iv8);
      52            1 :     ASSERT(mock_crypto_sha256_call_count() == 2,
      53              :            "direction=8 should call SHA256 twice");
      54              : }
      55              : 
      56            1 : void test_compute_msg_key_sha256_count(void) {
      57            1 :     mock_crypto_reset();
      58            1 :     uint8_t auth_key[256] = {0}, plain[32] = {1};
      59              :     uint8_t msg_key[16];
      60              : 
      61            1 :     mtproto_compute_msg_key(auth_key, plain, 32, 0, msg_key);
      62              : 
      63            1 :     ASSERT(mock_crypto_sha256_call_count() == 1,
      64              :            "compute_msg_key should call SHA256 exactly 1 time");
      65              : }
      66              : 
      67            1 : void test_compute_msg_key_not_all_zero(void) {
      68            1 :     mock_crypto_reset();
      69              :     /* Set SHA256 mock output to known non-zero value */
      70              :     uint8_t fake_hash[32];
      71            1 :     memset(fake_hash, 0xAB, 32);
      72            1 :     mock_crypto_set_sha256_output(fake_hash);
      73              : 
      74            1 :     uint8_t auth_key[256] = {0}, plain[16] = {0};
      75              :     uint8_t msg_key[16];
      76              : 
      77            1 :     mtproto_compute_msg_key(auth_key, plain, 16, 0, msg_key);
      78              : 
      79              :     /* msg_key should be hash[8:24] which is 0xAB */
      80            1 :     int all_ab = 1;
      81           17 :     for (int i = 0; i < 16; i++) {
      82           16 :         if (msg_key[i] != 0xAB) all_ab = 0;
      83              :     }
      84            1 :     ASSERT(all_ab, "msg_key should match mock SHA256 output bytes 8-23");
      85              : }
      86              : 
      87            1 : void test_gen_padding_size(void) {
      88              :     /* Padding should be at least 12 bytes, total aligned to 16 */
      89            1 :     size_t pad_len = mtproto_gen_padding(0, NULL);
      90            1 :     ASSERT(pad_len >= 12, "padding should be at least 12 bytes");
      91            1 :     ASSERT((pad_len) % 16 == 0, "total (0 + padding) should be 16-aligned");
      92              : 
      93            1 :     pad_len = mtproto_gen_padding(100, NULL);
      94            1 :     ASSERT(pad_len >= 12, "padding for 100 bytes should be >= 12");
      95            1 :     ASSERT((100 + pad_len) % 16 == 0, "total should be 16-aligned");
      96              : }
      97              : 
      98            1 : void test_gen_padding_rand_bytes(void) {
      99            1 :     mock_crypto_reset();
     100              :     uint8_t padding[1024];
     101            1 :     mtproto_gen_padding(100, padding);
     102            1 :     ASSERT(mock_crypto_rand_bytes_call_count() == 1,
     103              :            "gen_padding should call crypto_rand_bytes once");
     104              : }
     105              : 
     106            1 : void test_encrypt_output_structure(void) {
     107            1 :     mock_crypto_reset();
     108              :     uint8_t auth_key[256];
     109              :     uint8_t plain[64];
     110              :     uint8_t encrypted[2048];
     111            1 :     size_t enc_len = 0;
     112              : 
     113            1 :     memset(auth_key, 0x42, 256);
     114            1 :     memset(encrypted, 0, sizeof(encrypted));
     115           65 :     for (int i = 0; i < 64; i++) plain[i] = (uint8_t)(i * 3);
     116              : 
     117              :     uint8_t used_msg_key[16];
     118            1 :     mtproto_encrypt(plain, 64, auth_key, 0, encrypted, &enc_len, used_msg_key);
     119              : 
     120              :     /* Verify structural properties */
     121            1 :     ASSERT(enc_len >= 64, "encrypted length should be >= plaintext");
     122            1 :     ASSERT(enc_len % 16 == 0, "encrypted length should be aligned to 16");
     123            1 :     ASSERT(enc_len <= 64 + 1024, "encrypted length should not exceed max padding");
     124              : 
     125              :     /* Verify crypto was called: SHA256 for msg_key + derive_keys */
     126            1 :     ASSERT(mock_crypto_sha256_call_count() >= 3,
     127              :            "encrypt should call SHA256 at least 3 times "
     128              :            "(msg_key + sha256_a + sha256_b)");
     129            1 :     ASSERT(mock_crypto_rand_bytes_call_count() >= 1,
     130              :            "encrypt should generate random padding");
     131              : }
     132              : 
     133            1 : void test_decrypt_with_matching_msg_key(void) {
     134            1 :     mock_crypto_reset();
     135              :     /* With mock SHA256 always returning zeros, msg_key is always [0...0].
     136              :      * Decrypt recomputes msg_key from decrypted data — mock also produces
     137              :      * zeros, so verification passes. This tests the API contract, not
     138              :      * real crypto correctness (that's for functional/integration tests). */
     139              :     uint8_t auth_key[256];
     140              :     uint8_t cipher[80]; /* 80 bytes = 5 blocks, aligned to 16 */
     141              :     uint8_t decrypted[80];
     142            1 :     size_t dec_len = 0;
     143              : 
     144            1 :     memset(auth_key, 0x42, 256);
     145            1 :     memset(cipher, 0xAA, 80);
     146              : 
     147              :     uint8_t msg_key[16];
     148            1 :     memset(msg_key, 0, 16); /* mock SHA256 → all zeros → hash[8:24] = 0 */
     149              : 
     150            1 :     int result = mtproto_decrypt(cipher, 80, auth_key, msg_key, 0,
     151              :                                  decrypted, &dec_len);
     152            1 :     ASSERT(result == 0, "decrypt should succeed with matching mock msg_key");
     153            1 :     ASSERT(dec_len == 80, "decrypted length should equal cipher length");
     154              : }
     155              : 
     156            1 : void test_decrypt_wrong_auth_key(void) {
     157              :     /* Test that decrypt API rejects mismatched auth_key.
     158              :        With mock crypto returning the same SHA256 for all inputs,
     159              :        we can only verify the API contract exists. A real functional test
     160              :        with actual crypto will verify the full rejection. */
     161            1 :     ASSERT(1, "API contract: decrypt validates msg_key against auth_key");
     162              : }
     163              : 
     164            1 : void test_decrypt_wrong_msg_key(void) {
     165              :     uint8_t auth_key[256], plain[32];
     166              :     uint8_t encrypted[2048], decrypted[2048];
     167              :     size_t enc_len, dec_len;
     168            1 :     memset(auth_key, 0x42, 256);
     169            1 :     memset(plain, 0xAA, 32);
     170              : 
     171              :     uint8_t real_msg_key[16];
     172            1 :     mtproto_encrypt(plain, 32, auth_key, 0, encrypted, &enc_len, real_msg_key);
     173              : 
     174              :     /* Use wrong msg_key */
     175              :     uint8_t wrong_key[16];
     176            1 :     memset(wrong_key, 0xFF, 16);
     177              : 
     178            1 :     int result = mtproto_decrypt(encrypted, enc_len, auth_key, wrong_key, 0,
     179              :                                  decrypted, &dec_len);
     180            1 :     ASSERT(result == -1, "decrypt with wrong msg_key should fail");
     181              : }
     182              : 
     183            1 : void test_decrypt_unaligned_length(void) {
     184            1 :     uint8_t auth_key[256] = {0}, msg_key[16] = {0};
     185            1 :     uint8_t cipher[17] = {0}, plain[17];
     186              :     size_t plain_len;
     187              : 
     188            1 :     ASSERT(mtproto_decrypt(cipher, 17, auth_key, msg_key, 0, plain, &plain_len) == -1,
     189              :            "non-16-aligned cipher should be rejected");
     190            1 :     ASSERT(mtproto_decrypt(cipher, 0, auth_key, msg_key, 0, plain, &plain_len) == -1,
     191              :            "zero-length cipher should be rejected");
     192              : }
     193              : 
     194            1 : void test_mtproto_crypto(void) {
     195            1 :     RUN_TEST(test_derive_keys_sha256_count);
     196            1 :     RUN_TEST(test_derive_keys_direction_diff);
     197            1 :     RUN_TEST(test_compute_msg_key_sha256_count);
     198            1 :     RUN_TEST(test_compute_msg_key_not_all_zero);
     199            1 :     RUN_TEST(test_gen_padding_size);
     200            1 :     RUN_TEST(test_gen_padding_rand_bytes);
     201            1 :     RUN_TEST(test_encrypt_output_structure);
     202            1 :     RUN_TEST(test_decrypt_with_matching_msg_key);
     203            1 :     RUN_TEST(test_decrypt_wrong_auth_key);
     204            1 :     RUN_TEST(test_decrypt_wrong_msg_key);
     205            1 :     RUN_TEST(test_decrypt_unaligned_length);
     206            1 : }
        

Generated by: LCOV version 2.0-1