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