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 : }
|