Line data Source code
1 : /**
2 : * @file test_auth_transfer.c
3 : * @brief Unit tests for P10-04 cross-DC authorization transfer.
4 : */
5 :
6 : #include "test_helpers.h"
7 : #include "infrastructure/auth_transfer.h"
8 : #include "tl_serial.h"
9 : #include "tl_registry.h"
10 : #include "mock_socket.h"
11 : #include "mock_crypto.h"
12 : #include "mtproto_session.h"
13 : #include "transport.h"
14 : #include "api_call.h"
15 :
16 : #include <stdlib.h>
17 : #include <string.h>
18 :
19 : #define CRC_auth_exportedAuthorization_t 0xb434e2b8u
20 : #define CRC_auth_authorization_t 0x2ea2c0d4u
21 :
22 3 : static void build_fake_encrypted_response(const uint8_t *payload, size_t plen,
23 : uint8_t *out, size_t *out_len) {
24 3 : TlWriter w; tl_writer_init(&w);
25 3 : uint8_t zeros24[24] = {0}; tl_write_raw(&w, zeros24, 24);
26 3 : uint8_t header[32] = {0};
27 3 : uint32_t plen32 = (uint32_t)plen;
28 3 : memcpy(header + 28, &plen32, 4);
29 3 : tl_write_raw(&w, header, 32);
30 3 : tl_write_raw(&w, payload, plen);
31 3 : size_t enc = w.len - 24;
32 3 : if (enc % 16 != 0) {
33 2 : uint8_t pad[16] = {0}; tl_write_raw(&w, pad, 16 - (enc % 16));
34 : }
35 3 : size_t dwords = w.len / 4;
36 3 : size_t off = 0;
37 3 : if (dwords < 0x7F) { out[0] = (uint8_t)dwords; off = 1; }
38 : else {
39 0 : out[0] = 0x7F;
40 0 : out[1] = (uint8_t)(dwords);
41 0 : out[2] = (uint8_t)(dwords >> 8);
42 0 : out[3] = (uint8_t)(dwords >> 16);
43 0 : off = 4;
44 : }
45 3 : memcpy(out + off, w.data, w.len);
46 3 : *out_len = off + w.len;
47 3 : tl_writer_free(&w);
48 3 : }
49 :
50 4 : static void fix_session(MtProtoSession *s) {
51 4 : mtproto_session_init(s);
52 4 : s->session_id = 0; /* match the zero session_id in fake encrypted frames */
53 4 : uint8_t k[256] = {0}; mtproto_session_set_auth_key(s, k);
54 4 : mtproto_session_set_salt(s, 0xBADCAFEDEADBEEFULL);
55 4 : }
56 4 : static void fix_transport(Transport *t) {
57 4 : transport_init(t); t->fd = 42; t->connected = 1; t->dc_id = 1;
58 4 : }
59 4 : static void fix_cfg(ApiConfig *cfg) {
60 4 : api_config_init(cfg); cfg->api_id = 12345; cfg->api_hash = "deadbeef";
61 4 : }
62 :
63 1 : static size_t make_exported_auth(uint8_t *buf, size_t max,
64 : int64_t id,
65 : const uint8_t *bytes, size_t blen) {
66 1 : TlWriter w; tl_writer_init(&w);
67 1 : tl_write_uint32(&w, CRC_auth_exportedAuthorization_t);
68 1 : tl_write_int64 (&w, id);
69 1 : tl_write_bytes (&w, bytes, blen);
70 1 : size_t n = w.len < max ? w.len : max;
71 1 : memcpy(buf, w.data, n);
72 1 : tl_writer_free(&w);
73 1 : return n;
74 : }
75 :
76 1 : static size_t make_authorization(uint8_t *buf, size_t max) {
77 1 : TlWriter w; tl_writer_init(&w);
78 1 : tl_write_uint32(&w, CRC_auth_authorization_t);
79 : /* Build a minimal auth.authorization — flags=0, user:userEmpty. */
80 1 : tl_write_uint32(&w, 0); /* flags */
81 1 : tl_write_uint32(&w, 0xd3bc4b7au); /* userEmpty crc */
82 1 : tl_write_int64 (&w, 0); /* id */
83 1 : size_t n = w.len < max ? w.len : max;
84 1 : memcpy(buf, w.data, n);
85 1 : tl_writer_free(&w);
86 1 : return n;
87 : }
88 :
89 1 : static void test_export_parses_id_and_bytes(void) {
90 1 : mock_socket_reset(); mock_crypto_reset();
91 :
92 : uint8_t token[32];
93 33 : for (size_t i = 0; i < sizeof(token); i++) token[i] = (uint8_t)(i * 7 + 1);
94 :
95 : uint8_t payload[512];
96 1 : size_t plen = make_exported_auth(payload, sizeof(payload),
97 : 0xFEEDBEEFCAFEDEADLL,
98 : token, sizeof(token));
99 1 : uint8_t resp[1024]; size_t rlen = 0;
100 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
101 1 : mock_socket_set_response(resp, rlen);
102 :
103 : MtProtoSession s; Transport t; ApiConfig cfg;
104 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
105 :
106 1 : AuthExported out = {0};
107 1 : int rc = auth_transfer_export(&cfg, &s, &t, 4, &out, NULL);
108 1 : ASSERT(rc == 0, "export succeeds");
109 1 : ASSERT(out.id == (int64_t)0xFEEDBEEFCAFEDEADLL, "id parsed");
110 1 : ASSERT(out.bytes_len == sizeof(token), "bytes_len parsed");
111 1 : ASSERT(memcmp(out.bytes, token, sizeof(token)) == 0, "bytes round-trip");
112 : }
113 :
114 1 : static void test_export_rpc_error_surfaces(void) {
115 1 : mock_socket_reset(); mock_crypto_reset();
116 :
117 : uint8_t payload[128];
118 1 : TlWriter w; tl_writer_init(&w);
119 1 : tl_write_uint32(&w, TL_rpc_error);
120 1 : tl_write_int32 (&w, 400);
121 1 : tl_write_string(&w, "DC_ID_INVALID");
122 1 : memcpy(payload, w.data, w.len);
123 1 : size_t plen = w.len;
124 1 : tl_writer_free(&w);
125 :
126 1 : uint8_t resp[512]; size_t rlen = 0;
127 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
128 1 : mock_socket_set_response(resp, rlen);
129 :
130 : MtProtoSession s; Transport t; ApiConfig cfg;
131 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
132 :
133 1 : AuthExported out = {0};
134 1 : RpcError err = {0};
135 1 : int rc = auth_transfer_export(&cfg, &s, &t, 99, &out, &err);
136 1 : ASSERT(rc == -1, "rpc error propagates");
137 1 : ASSERT(err.error_code == 400, "error_code preserved");
138 1 : ASSERT(strcmp(err.error_msg, "DC_ID_INVALID") == 0, "error_msg preserved");
139 : }
140 :
141 1 : static void test_import_accepts_authorization(void) {
142 1 : mock_socket_reset(); mock_crypto_reset();
143 :
144 : uint8_t payload[512];
145 1 : size_t plen = make_authorization(payload, sizeof(payload));
146 1 : uint8_t resp[1024]; size_t rlen = 0;
147 1 : build_fake_encrypted_response(payload, plen, resp, &rlen);
148 1 : mock_socket_set_response(resp, rlen);
149 :
150 : MtProtoSession s; Transport t; ApiConfig cfg;
151 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
152 :
153 1 : AuthExported tok = {0};
154 1 : tok.id = 42;
155 1 : tok.bytes_len = 4;
156 1 : tok.bytes[0] = 1; tok.bytes[1] = 2; tok.bytes[2] = 3; tok.bytes[3] = 4;
157 :
158 1 : int rc = auth_transfer_import(&cfg, &s, &t, &tok, NULL);
159 1 : ASSERT(rc == 0, "import succeeds on auth.authorization");
160 : }
161 :
162 1 : static void test_import_rejects_empty_token(void) {
163 : ApiConfig cfg; MtProtoSession s; Transport t;
164 1 : fix_session(&s); fix_transport(&t); fix_cfg(&cfg);
165 :
166 1 : AuthExported tok = {0}; /* bytes_len = 0 */
167 1 : ASSERT(auth_transfer_import(&cfg, &s, &t, &tok, NULL) == -1,
168 : "zero-length token rejected");
169 : }
170 :
171 1 : static void test_null_args(void) {
172 1 : ASSERT(auth_transfer_export(NULL, NULL, NULL, 2, NULL, NULL) == -1,
173 : "export rejects nulls");
174 1 : ASSERT(auth_transfer_import(NULL, NULL, NULL, NULL, NULL) == -1,
175 : "import rejects nulls");
176 : }
177 :
178 1 : void run_auth_transfer_tests(void) {
179 1 : RUN_TEST(test_export_parses_id_and_bytes);
180 1 : RUN_TEST(test_export_rpc_error_surfaces);
181 1 : RUN_TEST(test_import_accepts_authorization);
182 1 : RUN_TEST(test_import_rejects_empty_token);
183 1 : RUN_TEST(test_null_args);
184 1 : }
|