Line data Source code
1 : /**
2 : * @file test_auth_session.c
3 : * @brief Unit tests for auth_session.c (auth.sendCode + auth.signIn).
4 : *
5 : * Uses mock socket and mock crypto; verifies TL request structure and
6 : * response parsing.
7 : */
8 :
9 : #include "test_helpers.h"
10 : #include "auth_session.h"
11 : #include "mtproto_session.h"
12 : #include "transport.h"
13 : #include "tl_serial.h"
14 : #include "tl_registry.h"
15 : #include "mock_socket.h"
16 : #include "mock_crypto.h"
17 :
18 : #include <string.h>
19 : #include <stdlib.h>
20 : #include <stdint.h>
21 :
22 : /* ---- Helper: build a minimal fake encrypted response ----
23 : *
24 : * The RPC layer (rpc_recv_encrypted) expects:
25 : * auth_key_id(8) + msg_key(16) + encrypted_payload
26 : * where encrypted_payload (after mock decrypt = identity) is:
27 : * salt(8) + session_id(8) + msg_id(8) + seq_no(4) + data_len(4) + data
28 : * and data is the raw TL response we want to return.
29 : *
30 : * Since mock crypto uses passthrough (encrypt = copy), we build the
31 : * "encrypted" buffer as if it were already the decrypted payload.
32 : */
33 9 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
34 : uint8_t *out, size_t *out_len) {
35 : TlWriter w;
36 9 : tl_writer_init(&w);
37 :
38 : /* auth_key_id (8) + msg_key (16) = 24 bytes header */
39 9 : uint8_t zeros24[24] = {0};
40 9 : tl_write_raw(&w, zeros24, 24);
41 :
42 : /* plaintext frame: salt(8)+session_id(8)+msg_id(8)+seq_no(4)+len(4)+data */
43 9 : uint8_t header[32] = {0};
44 9 : uint32_t plen32 = (uint32_t)plen;
45 9 : memcpy(header + 28, &plen32, 4); /* data_len */
46 9 : tl_write_raw(&w, header, 32);
47 9 : tl_write_raw(&w, payload, plen);
48 :
49 : /* Pad to 16-byte boundary */
50 9 : size_t total = w.len;
51 9 : size_t payload_start = 24; /* after auth_key_id + msg_key */
52 9 : size_t enc_part = total - payload_start;
53 9 : if (enc_part % 16 != 0) {
54 8 : size_t pad = 16 - (enc_part % 16);
55 8 : uint8_t zeros[16] = {0};
56 8 : tl_write_raw(&w, zeros, pad);
57 : }
58 :
59 : /* Wrap in abridged transport: 1-byte length prefix (in 4-byte units) */
60 9 : size_t wire_bytes = w.len;
61 9 : size_t wire_units = wire_bytes / 4;
62 :
63 9 : uint8_t *result = (uint8_t *)malloc(1 + wire_bytes);
64 9 : result[0] = (uint8_t)wire_units;
65 9 : memcpy(result + 1, w.data, wire_bytes);
66 9 : *out_len = 1 + wire_bytes;
67 9 : memcpy(out, result, *out_len);
68 9 : free(result);
69 9 : tl_writer_free(&w);
70 9 : }
71 :
72 : /* ---- Helper: make session ready for encrypted calls ---- */
73 11 : static void session_setup(MtProtoSession *s) {
74 11 : mtproto_session_init(s);
75 11 : s->session_id = 0; /* match the zero session_id in fake encrypted frames */
76 11 : uint8_t fake_key[256] = {0};
77 11 : mtproto_session_set_auth_key(s, fake_key);
78 11 : mtproto_session_set_salt(s, 0x1122334455667788ULL);
79 11 : }
80 :
81 : /* ---- Helper: make fake sentCode TL payload ---- */
82 1 : static size_t make_sent_code_payload(uint8_t *buf, size_t max) {
83 : TlWriter w;
84 1 : tl_writer_init(&w);
85 :
86 1 : tl_write_uint32(&w, CRC_auth_sentCode);
87 1 : tl_write_uint32(&w, 0); /* flags = 0 */
88 : /* type: sentCodeTypeApp */
89 1 : tl_write_uint32(&w, CRC_auth_sentCodeTypeApp);
90 1 : tl_write_int32(&w, 5); /* length = 5 digits */
91 : /* phone_code_hash */
92 1 : tl_write_string(&w, "abc123hash456xyz");
93 :
94 1 : size_t len = w.len < max ? w.len : max;
95 1 : memcpy(buf, w.data, len);
96 1 : tl_writer_free(&w);
97 1 : return len;
98 : }
99 :
100 : /* ---- Helper: make fake auth.authorization TL payload ---- */
101 1 : static size_t make_authorization_payload(uint8_t *buf, size_t max,
102 : int64_t user_id) {
103 : TlWriter w;
104 1 : tl_writer_init(&w);
105 :
106 1 : tl_write_uint32(&w, CRC_auth_authorization);
107 1 : tl_write_uint32(&w, 0); /* flags = 0 */
108 : /* user: user#3ff6ecb0 */
109 1 : tl_write_uint32(&w, TL_user);
110 1 : tl_write_uint32(&w, 0); /* user flags */
111 1 : tl_write_int64(&w, user_id); /* id */
112 :
113 1 : size_t len = w.len < max ? w.len : max;
114 1 : memcpy(buf, w.data, len);
115 1 : tl_writer_free(&w);
116 1 : return len;
117 : }
118 :
119 : /* ---- Test: auth_send_code sends correct TL request ---- */
120 1 : static void test_send_code_request_structure(void) {
121 1 : mock_socket_reset();
122 1 : mock_crypto_reset();
123 :
124 : /* Prepare fake sentCode response */
125 : uint8_t payload[512];
126 1 : size_t plen = make_sent_code_payload(payload, sizeof(payload));
127 :
128 : uint8_t resp_buf[1024];
129 1 : size_t resp_len = 0;
130 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
131 1 : mock_socket_set_response(resp_buf, resp_len);
132 :
133 : MtProtoSession s;
134 1 : session_setup(&s);
135 : Transport t;
136 1 : transport_init(&t);
137 1 : t.fd = 42;
138 1 : t.connected = 1;
139 1 : t.dc_id = 1;
140 :
141 : ApiConfig cfg;
142 1 : api_config_init(&cfg);
143 1 : cfg.api_id = 12345;
144 1 : cfg.api_hash = "deadbeef";
145 :
146 : AuthSentCode result;
147 1 : memset(&result, 0, sizeof(result));
148 1 : int rc = auth_send_code(&cfg, &s, &t, "+15551234567", &result, NULL);
149 :
150 1 : ASSERT(rc == 0, "send_code: must succeed with valid sentCode response");
151 1 : ASSERT(strcmp(result.phone_code_hash, "abc123hash456xyz") == 0,
152 : "send_code: phone_code_hash must be parsed correctly");
153 :
154 : /* Verify the sent bytes contain the phone number and api_id */
155 1 : size_t sent_len = 0;
156 1 : const uint8_t *sent = mock_socket_get_sent(&sent_len);
157 1 : ASSERT(sent_len > 10, "send_code: must have sent data");
158 : (void)sent;
159 : }
160 :
161 : /* ---- Test: auth_send_code handles RPC error ---- */
162 1 : static void test_send_code_rpc_error(void) {
163 1 : mock_socket_reset();
164 1 : mock_crypto_reset();
165 :
166 : /* Build rpc_error payload */
167 : uint8_t payload[256];
168 : TlWriter w;
169 1 : tl_writer_init(&w);
170 1 : tl_write_uint32(&w, TL_rpc_error);
171 1 : tl_write_int32(&w, 400);
172 1 : tl_write_string(&w, "PHONE_NUMBER_INVALID");
173 1 : memcpy(payload, w.data, w.len);
174 1 : size_t plen = w.len;
175 1 : tl_writer_free(&w);
176 :
177 : uint8_t resp_buf[1024];
178 1 : size_t resp_len = 0;
179 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
180 1 : mock_socket_set_response(resp_buf, resp_len);
181 :
182 : MtProtoSession s;
183 1 : session_setup(&s);
184 : Transport t;
185 1 : transport_init(&t);
186 1 : t.fd = 42; t.connected = 1; t.dc_id = 1;
187 :
188 : ApiConfig cfg;
189 1 : api_config_init(&cfg);
190 1 : cfg.api_id = 12345; cfg.api_hash = "deadbeef";
191 :
192 : AuthSentCode result;
193 1 : memset(&result, 0, sizeof(result));
194 1 : int rc = auth_send_code(&cfg, &s, &t, "+15551234567", &result, NULL);
195 :
196 1 : ASSERT(rc != 0, "send_code: must fail on RPC error");
197 : }
198 :
199 : /* ---- Test: auth_sign_in success path ---- */
200 1 : static void test_sign_in_success(void) {
201 1 : mock_socket_reset();
202 1 : mock_crypto_reset();
203 :
204 : uint8_t payload[512];
205 1 : size_t plen = make_authorization_payload(payload, sizeof(payload), 987654321LL);
206 :
207 : uint8_t resp_buf[1024];
208 1 : size_t resp_len = 0;
209 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
210 1 : mock_socket_set_response(resp_buf, resp_len);
211 :
212 : MtProtoSession s;
213 1 : session_setup(&s);
214 : Transport t;
215 1 : transport_init(&t);
216 1 : t.fd = 42; t.connected = 1; t.dc_id = 1;
217 :
218 : ApiConfig cfg;
219 1 : api_config_init(&cfg);
220 1 : cfg.api_id = 12345; cfg.api_hash = "deadbeef";
221 :
222 1 : int64_t user_id = 0;
223 1 : int rc = auth_sign_in(&cfg, &s, &t,
224 : "+15551234567", "abc123hash456xyz", "12345",
225 : &user_id, NULL);
226 :
227 1 : ASSERT(rc == 0, "sign_in: must succeed");
228 1 : ASSERT(user_id == 987654321LL, "sign_in: user_id must be parsed correctly");
229 : }
230 :
231 : /* ---- Test: auth_sign_in RPC error ---- */
232 1 : static void test_sign_in_rpc_error(void) {
233 1 : mock_socket_reset();
234 1 : mock_crypto_reset();
235 :
236 : uint8_t payload[256];
237 : TlWriter w;
238 1 : tl_writer_init(&w);
239 1 : tl_write_uint32(&w, TL_rpc_error);
240 1 : tl_write_int32(&w, 400);
241 1 : tl_write_string(&w, "PHONE_CODE_INVALID");
242 1 : memcpy(payload, w.data, w.len);
243 1 : size_t plen = w.len;
244 1 : tl_writer_free(&w);
245 :
246 : uint8_t resp_buf[1024];
247 1 : size_t resp_len = 0;
248 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
249 1 : mock_socket_set_response(resp_buf, resp_len);
250 :
251 : MtProtoSession s;
252 1 : session_setup(&s);
253 : Transport t;
254 1 : transport_init(&t);
255 1 : t.fd = 42; t.connected = 1; t.dc_id = 1;
256 :
257 : ApiConfig cfg;
258 1 : api_config_init(&cfg);
259 1 : cfg.api_id = 12345; cfg.api_hash = "deadbeef";
260 :
261 1 : int64_t user_id = 0;
262 1 : int rc = auth_sign_in(&cfg, &s, &t,
263 : "+15551234567", "abc123hash456xyz", "00000",
264 : &user_id, NULL);
265 :
266 1 : ASSERT(rc != 0, "sign_in: must fail on RPC error");
267 : }
268 :
269 : /* ---- Test: null arguments are rejected ---- */
270 1 : static void test_null_args_rejected(void) {
271 : AuthSentCode out;
272 1 : ASSERT(auth_send_code(NULL, NULL, NULL, NULL, &out, NULL) == -1,
273 : "send_code: null args must return -1");
274 1 : ASSERT(auth_sign_in(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) == -1,
275 : "sign_in: null args must return -1");
276 : }
277 :
278 : /* ---- Helpers for additional error-path tests ---- */
279 7 : static void setup_session_and_transport(MtProtoSession *s, Transport *t,
280 : ApiConfig *cfg) {
281 7 : session_setup(s);
282 7 : transport_init(t);
283 7 : t->fd = 42; t->connected = 1; t->dc_id = 1;
284 7 : api_config_init(cfg);
285 7 : cfg->api_id = 12345; cfg->api_hash = "deadbeef";
286 7 : }
287 :
288 : /* ---- Test: send_code with unexpected top-level constructor ---- */
289 1 : static void test_send_code_unexpected_constructor(void) {
290 1 : mock_socket_reset();
291 1 : mock_crypto_reset();
292 :
293 : uint8_t payload[64];
294 : TlWriter w;
295 1 : tl_writer_init(&w);
296 1 : tl_write_uint32(&w, 0xDEADBEEF); /* not auth.sentCode */
297 1 : tl_write_uint32(&w, 0);
298 1 : memcpy(payload, w.data, w.len);
299 1 : size_t plen = w.len;
300 1 : tl_writer_free(&w);
301 :
302 1 : uint8_t resp_buf[256]; size_t resp_len = 0;
303 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
304 1 : mock_socket_set_response(resp_buf, resp_len);
305 :
306 : MtProtoSession s; Transport t; ApiConfig cfg;
307 1 : setup_session_and_transport(&s, &t, &cfg);
308 :
309 1 : AuthSentCode result = {0};
310 1 : int rc = auth_send_code(&cfg, &s, &t, "+15551234567", &result, NULL);
311 1 : ASSERT(rc != 0, "send_code: unexpected constructor must fail");
312 : }
313 :
314 : /* ---- Test: send_code hitting sentCodeTypeFlashCall ---- */
315 1 : static void test_send_code_flash_call_type(void) {
316 1 : mock_socket_reset();
317 1 : mock_crypto_reset();
318 :
319 : uint8_t payload[256];
320 : TlWriter w;
321 1 : tl_writer_init(&w);
322 1 : tl_write_uint32(&w, CRC_auth_sentCode);
323 1 : tl_write_uint32(&w, 1u << 2); /* flags: timeout present */
324 1 : tl_write_uint32(&w, CRC_auth_sentCodeTypeFlashCall);
325 1 : tl_write_string(&w, "\\d{6}"); /* pattern */
326 1 : tl_write_string(&w, "flash_hash");
327 1 : tl_write_int32(&w, 120); /* timeout */
328 1 : memcpy(payload, w.data, w.len);
329 1 : size_t plen = w.len;
330 1 : tl_writer_free(&w);
331 :
332 1 : uint8_t resp_buf[512]; size_t resp_len = 0;
333 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
334 1 : mock_socket_set_response(resp_buf, resp_len);
335 :
336 : MtProtoSession s; Transport t; ApiConfig cfg;
337 1 : setup_session_and_transport(&s, &t, &cfg);
338 :
339 1 : AuthSentCode result = {0};
340 1 : int rc = auth_send_code(&cfg, &s, &t, "+15551234567", &result, NULL);
341 1 : ASSERT(rc == 0, "send_code: FlashCall path must succeed");
342 1 : ASSERT(result.timeout == 120, "timeout must be parsed from flags.2");
343 1 : ASSERT(strcmp(result.phone_code_hash, "flash_hash") == 0,
344 : "phone_code_hash must match");
345 : }
346 :
347 : /* ---- Test: send_code with unknown sentCodeType constructor ---- */
348 1 : static void test_send_code_unknown_type(void) {
349 1 : mock_socket_reset();
350 1 : mock_crypto_reset();
351 :
352 : uint8_t payload[128];
353 : TlWriter w;
354 1 : tl_writer_init(&w);
355 1 : tl_write_uint32(&w, CRC_auth_sentCode);
356 1 : tl_write_uint32(&w, 0);
357 1 : tl_write_uint32(&w, 0xBADCAFEE); /* unknown sentCodeType */
358 1 : memcpy(payload, w.data, w.len);
359 1 : size_t plen = w.len;
360 1 : tl_writer_free(&w);
361 :
362 1 : uint8_t resp_buf[256]; size_t resp_len = 0;
363 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
364 1 : mock_socket_set_response(resp_buf, resp_len);
365 :
366 : MtProtoSession s; Transport t; ApiConfig cfg;
367 1 : setup_session_and_transport(&s, &t, &cfg);
368 :
369 1 : AuthSentCode result = {0};
370 1 : int rc = auth_send_code(&cfg, &s, &t, "+15551234567", &result, NULL);
371 1 : ASSERT(rc != 0, "send_code: unknown sentCodeType must fail");
372 : }
373 :
374 : /* ---- Test: send_code API-call failure (no response) ---- */
375 1 : static void test_send_code_api_call_fails(void) {
376 1 : mock_socket_reset();
377 1 : mock_crypto_reset();
378 : /* No response queued → recv returns 0 → api_call fails. */
379 :
380 : MtProtoSession s; Transport t; ApiConfig cfg;
381 1 : setup_session_and_transport(&s, &t, &cfg);
382 :
383 1 : AuthSentCode result = {0};
384 1 : int rc = auth_send_code(&cfg, &s, &t, "+15551234567", &result, NULL);
385 1 : ASSERT(rc != 0, "send_code: api_call failure must propagate");
386 : }
387 :
388 : /* ---- Test: sign_in with unexpected top-level constructor ---- */
389 1 : static void test_sign_in_unexpected_constructor(void) {
390 1 : mock_socket_reset();
391 1 : mock_crypto_reset();
392 :
393 : uint8_t payload[64];
394 : TlWriter w;
395 1 : tl_writer_init(&w);
396 1 : tl_write_uint32(&w, 0xBADBADBA);
397 1 : tl_write_uint32(&w, 0);
398 1 : memcpy(payload, w.data, w.len);
399 1 : size_t plen = w.len;
400 1 : tl_writer_free(&w);
401 :
402 1 : uint8_t resp_buf[256]; size_t resp_len = 0;
403 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
404 1 : mock_socket_set_response(resp_buf, resp_len);
405 :
406 : MtProtoSession s; Transport t; ApiConfig cfg;
407 1 : setup_session_and_transport(&s, &t, &cfg);
408 :
409 1 : int64_t uid = 0;
410 1 : int rc = auth_sign_in(&cfg, &s, &t, "+1", "h", "12345", &uid, NULL);
411 1 : ASSERT(rc != 0, "sign_in: unexpected constructor must fail");
412 : }
413 :
414 : /* ---- Test: sign_in with unexpected user constructor — still OK, uid=0 ---- */
415 1 : static void test_sign_in_unexpected_user_crc(void) {
416 1 : mock_socket_reset();
417 1 : mock_crypto_reset();
418 :
419 : uint8_t payload[128];
420 : TlWriter w;
421 1 : tl_writer_init(&w);
422 1 : tl_write_uint32(&w, CRC_auth_authorization);
423 1 : tl_write_uint32(&w, 0); /* flags */
424 1 : tl_write_uint32(&w, 0xCAFEBABE); /* unexpected user constructor */
425 1 : memcpy(payload, w.data, w.len);
426 1 : size_t plen = w.len;
427 1 : tl_writer_free(&w);
428 :
429 1 : uint8_t resp_buf[256]; size_t resp_len = 0;
430 1 : build_fake_encrypted_response(payload, plen, resp_buf, &resp_len);
431 1 : mock_socket_set_response(resp_buf, resp_len);
432 :
433 : MtProtoSession s; Transport t; ApiConfig cfg;
434 1 : setup_session_and_transport(&s, &t, &cfg);
435 :
436 1 : int64_t uid = 77;
437 1 : int rc = auth_sign_in(&cfg, &s, &t, "+1", "h", "12345", &uid, NULL);
438 1 : ASSERT(rc == 0, "sign_in: unexpected user constructor still OK");
439 1 : ASSERT(uid == 0, "sign_in: uid must be reset to 0");
440 : }
441 :
442 : /* ---- Test: sign_in api_call fails ---- */
443 1 : static void test_sign_in_api_call_fails(void) {
444 1 : mock_socket_reset();
445 1 : mock_crypto_reset();
446 : /* No response queued */
447 :
448 : MtProtoSession s; Transport t; ApiConfig cfg;
449 1 : setup_session_and_transport(&s, &t, &cfg);
450 :
451 1 : int64_t uid = 0;
452 1 : int rc = auth_sign_in(&cfg, &s, &t, "+1", "h", "12345", &uid, NULL);
453 1 : ASSERT(rc != 0, "sign_in: api_call failure must propagate");
454 : }
455 :
456 1 : void run_auth_session_tests(void) {
457 1 : RUN_TEST(test_send_code_request_structure);
458 1 : RUN_TEST(test_send_code_rpc_error);
459 1 : RUN_TEST(test_sign_in_success);
460 1 : RUN_TEST(test_sign_in_rpc_error);
461 1 : RUN_TEST(test_null_args_rejected);
462 1 : RUN_TEST(test_send_code_unexpected_constructor);
463 1 : RUN_TEST(test_send_code_flash_call_type);
464 1 : RUN_TEST(test_send_code_unknown_type);
465 1 : RUN_TEST(test_send_code_api_call_fails);
466 1 : RUN_TEST(test_sign_in_unexpected_constructor);
467 1 : RUN_TEST(test_sign_in_unexpected_user_crc);
468 1 : RUN_TEST(test_sign_in_api_call_fails);
469 1 : }
|