Add randomized ETCP padding and isolate STCP CTR streams per connection
This commit is contained in:
1 parent
1f5b42845b
commit
a49cf5434e
31 files changed
+854
-103
No files matched your search
+5
-1
@@ -130,7 +130,7 @@ check_PROGRAMS = \
|
||||
# Linux-only: test_auto_socket_dynamic требует root + dummy-интерфейсы + iproute2/netlink.
|
||||
if OS_LINUX
|
||||
check_PROGRAMS += test_auto_socket_dynamic test_standby test_standby_transport test_group_connect_standby test_android_udp_log test_pool_corruption_log
|
||||
check_PROGRAMS += test_uasync_regressions test_ll_queue_regressions
|
||||
check_PROGRAMS += test_uasync_regressions test_ll_queue_regressions test_etcp_padding
|
||||
endif
|
||||
|
||||
# Silero VAD: только при включённом ONNX Runtime (--with-silero-vad)
|
||||
@@ -164,6 +164,7 @@ COMMON_LIBS = $(top_builddir)/lib/libuasync.a $(CRYPTO_LIBS) -lpthread $(WIN_LIB
|
||||
|
||||
# Вариант общего ядра с Android duty-cycle, без JNI и устройства.
|
||||
if OS_LINUX
|
||||
test_stcp_LDFLAGS = -Wl,--wrap=send -Wl,--wrap=random_bytes
|
||||
standby_test_sources = ../tools/chatgui-android/libutun_lite/standby.c ../src/utun_instance.c ../src/transport_layer/etcp_keepalive.c
|
||||
standby_test_cppflags = $(AM_CPPFLAGS) -I$(top_srcdir)/src/chat -I$(top_srcdir)/tools/chatgui-android/libutun_lite -DUTUN_HAVE_STANDBY
|
||||
test_standby_SOURCES = ../tools/chatgui-android/libutun_lite/tests/test_standby.c $(standby_test_sources)
|
||||
@@ -186,6 +187,9 @@ test_pool_corruption_log_LDADD = $(COMMON_LIBS)
|
||||
async_regression_sources = async_regression.c async_regression.h
|
||||
async_regression_ldflags = -Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl -Wl,--wrap=u_realloc_impl \
|
||||
-Wl,--wrap=gettimeofday -Wl,--wrap=epoll_wait -Wl,--wrap=epoll_ctl -Wl,--wrap=eventfd -Wl,--wrap=epoll_create1
|
||||
test_etcp_padding_SOURCES = test_etcp_padding.c
|
||||
test_etcp_padding_LDADD = $(LIBUTUN) $(COMMON_LIBS)
|
||||
test_etcp_padding_LDFLAGS = -Wl,--wrap=uasync_set_timeout -Wl,--wrap=socket_sendto
|
||||
test_uasync_regressions_SOURCES = test_uasync_regressions.c $(async_regression_sources)
|
||||
test_uasync_regressions_LDADD = $(COMMON_LIBS)
|
||||
test_uasync_regressions_LDFLAGS = $(async_regression_ldflags)
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
/* Padding: реальные таймеры/UDP, проверка расшифрованных пакетов и независимости владельцев. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "etcp.h"
|
||||
#include "etcp_keepalive.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL %s:%d: %s\n", __func__, __LINE__, #x); exit(1); } } while (0)
|
||||
|
||||
static struct { struct ETCP_PADDING* padding; timeout_callback_t callback; void* timer; int delay; } timers[128];
|
||||
static size_t timer_count;
|
||||
static int fail_timer;
|
||||
static struct UTUN_INSTANCE *sender, *receiver;
|
||||
static sc_context_t wire_crypto;
|
||||
static int capture_ping, ping_requests, ping_responses, ping_done, expected_req_padding, expected_resp_padding, wire_calls;
|
||||
static int keepalive_packets, expected_keepalive_padding;
|
||||
static const uint8_t payload[] = {0, 0xff, 0x12, 0, 0x34, 0x56};
|
||||
|
||||
void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*);
|
||||
void* __wrap_uasync_set_timeout(struct UASYNC* ua, int delay, void* arg, timeout_callback_t cb, const char* name) {
|
||||
int padding = name && strcmp(name, "etcp_padding") == 0;
|
||||
if (padding && fail_timer) return NULL;
|
||||
void* timer = __real_uasync_set_timeout(ua, delay, arg, cb, name);
|
||||
if (padding && timer) {
|
||||
CHECK(timer_count < sizeof(timers) / sizeof(timers[0]));
|
||||
CHECK(delay >= 500000 && delay <= 3000000);
|
||||
timers[timer_count].padding = arg; timers[timer_count].callback = cb;
|
||||
timers[timer_count].timer = timer; timers[timer_count++].delay = delay;
|
||||
}
|
||||
return timer;
|
||||
}
|
||||
|
||||
static void check_zeroes(const uint8_t* data, size_t size) {
|
||||
for (size_t i = 0; i < size; i++) CHECK(data[i] == 0);
|
||||
}
|
||||
|
||||
ssize_t __real_socket_sendto(socket_t, const void*, size_t, const struct sockaddr*, socklen_t);
|
||||
ssize_t __wrap_socket_sendto(socket_t sock, const void* buf, size_t len, const struct sockaddr* dest, socklen_t dest_len) {
|
||||
wire_calls++;
|
||||
if (capture_ping) {
|
||||
CHECK(len <= PACKET_DATA_MAX_MTU - IP_UDP_OVERHEAD_V4);
|
||||
CHECK(len >= 96);
|
||||
const uint8_t* bytes = buf;
|
||||
uint8_t plain[PACKET_DATA_SIZE]; size_t plain_len = 0;
|
||||
CHECK(sc_decrypt(&wire_crypto, bytes, len - SC_PUBKEY_ENC_SIZE, plain, &plain_len) == SC_OK);
|
||||
int code = plain[3]; CHECK(code == ETCP_PING || code == ETCP_PONG);
|
||||
size_t ulen = ((size_t)plain[21] << 8) | plain[22];
|
||||
CHECK(ulen == sizeof(payload) && memcmp(plain + 3 + 20, payload, ulen) == 0);
|
||||
size_t padding = plain_len - 3 - 20 - ulen;
|
||||
CHECK(padding == (size_t)(code == ETCP_PING ? expected_req_padding : expected_resp_padding));
|
||||
check_zeroes(plain + 3 + 20 + ulen, padding);
|
||||
if (code == ETCP_PING) ping_requests++; else ping_responses++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "wire PING/PONG code=%u payload=%zu padding=%zu bytes=%zu", code, ulen, padding, len);
|
||||
}
|
||||
return __real_socket_sendto(sock, buf, len, dest, dest_len);
|
||||
}
|
||||
|
||||
static ssize_t keepalive_hook(socket_t fd, const void* buf, size_t len, const struct sockaddr* addr, socklen_t alen,
|
||||
struct ETCP_LINK* link, void* arg) {
|
||||
(void)fd; (void)addr; (void)alen; (void)arg;
|
||||
uint8_t plain[PACKET_DATA_SIZE]; size_t plain_len = 0;
|
||||
CHECK(sc_decrypt(&wire_crypto, buf, len, plain, &plain_len) == SC_OK);
|
||||
CHECK(plain[3] == ETCP_KEEPALIVE && plain[4] == 0);
|
||||
CHECK(plain_len == 3 + 4 + (size_t)expected_keepalive_padding);
|
||||
check_zeroes(plain + 3 + 4, expected_keepalive_padding);
|
||||
struct ETCP_DGRAM* pkt = memory_pool_alloc(sender->pkt_pool); CHECK(pkt);
|
||||
memcpy(pkt->data, plain + 3, plain_len - 3); pkt->data_len = plain_len - 3;
|
||||
uint32_t before = link->keepalive_recv_count;
|
||||
CHECK(etcp_keepalive_on_recv(sender->etcp_sockets, pkt, link, plain_len) == 1);
|
||||
CHECK(link->keepalive_recv_count == before + 1);
|
||||
keepalive_packets++;
|
||||
return len;
|
||||
}
|
||||
|
||||
static void force_refresh(struct ETCP_PADDING* padding) {
|
||||
size_t i = timer_count;
|
||||
while (i && timers[i - 1].timer != padding->timer) i--;
|
||||
CHECK(i > 0 && timers[i - 1].padding == padding);
|
||||
CHECK(uasync_cancel_timeout(padding->ua, padding->timer) == 0);
|
||||
timers[i - 1].callback(padding);
|
||||
CHECK(padding->timer && padding->min <= 30);
|
||||
CHECK(padding->max >= padding->min + 32 && padding->max <= padding->min + 128);
|
||||
uint64_t now = get_time_tb();
|
||||
CHECK(padding->next_refresh_tb > now && padding->next_refresh_tb <= now + 3000000);
|
||||
}
|
||||
|
||||
static struct UTUN_INSTANCE* make_instance(struct UASYNC* ua, const char* private_key, const char* public_key) {
|
||||
char config[1024];
|
||||
snprintf(config, sizeof(config), "[global]\nmy_private_key=%s\nmy_public_key=%s\nauto_sockets=no\n"
|
||||
"[server: padding]\naddr=127.0.0.1:0\ntype=public\n", private_key, public_key);
|
||||
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); CHECK(inst);
|
||||
CHECK(inst->padding.timer && inst->padding.min <= 30 && inst->padding.max <= ETCP_PADDING_MAX);
|
||||
return inst;
|
||||
}
|
||||
|
||||
static struct ETCP_CONN* make_connection(struct UTUN_INSTANCE* inst, const struct SC_MYKEYS* peer) {
|
||||
struct ETCP_CONN* conn = etcp_connection_create(inst, "padding-test"); CHECK(conn);
|
||||
conn->peer_node_id = sc_derive_node_id_from_pubkey(peer->public_key);
|
||||
etcp_conn_queue_set_ready(conn);
|
||||
CHECK(sc_init_ctx(&conn->crypto_ctx, &inst->my_keys) == SC_OK);
|
||||
CHECK(sc_set_peer_public_key(&conn->crypto_ctx, peer->public_key, SC_PEER_PUBKEY_BIN) == SC_OK);
|
||||
CHECK(conn->padding.timer && conn->padding.timer != inst->padding.timer);
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce, const uint8_t* data, size_t size) {
|
||||
(void)rtt; (void)arg; (void)nonce;
|
||||
CHECK(success && size == sizeof(payload) && memcmp(data, payload, size) == 0);
|
||||
ping_done = 1;
|
||||
}
|
||||
|
||||
static void ping_case(int req_padding, int resp_padding) {
|
||||
expected_req_padding = req_padding; expected_resp_padding = resp_padding;
|
||||
ping_requests = ping_responses = ping_done = 0;
|
||||
struct sockaddr_storage addr; socklen_t size = sizeof(addr);
|
||||
CHECK(getsockname(receiver->etcp_sockets->fd, (struct sockaddr*)&addr, &size) == 0);
|
||||
capture_ping = 1;
|
||||
CHECK(etcp_send_ping(sender, receiver->my_keys.public_key, &addr, 500, ping_cb, NULL, payload, sizeof(payload)) == 0);
|
||||
for (int i = 0; !ping_done && i < 1000; i++) uasync_poll(sender->ua, 10);
|
||||
capture_ping = 0;
|
||||
CHECK(ping_done && ping_requests == 1 && ping_responses == 1 && !sender->pending_pings);
|
||||
}
|
||||
|
||||
/* Слишком длинный user_data и лишний padding отбрасываются до эхо/доставки callback. */
|
||||
static void malformed_ping_cases(void) {
|
||||
for (int code = ETCP_PING; code <= ETCP_PONG; code++) {
|
||||
for (int extra = 0; extra <= 1; extra++) {
|
||||
struct sockaddr_storage addr; socklen_t alen = sizeof(addr);
|
||||
CHECK(getsockname(receiver->etcp_sockets->fd, (struct sockaddr*)&addr, &alen) == 0);
|
||||
uint8_t plain[3 + 20 + ETCP_PADDING_MAX + 1] = {0}, packet[512];
|
||||
plain[3] = code;
|
||||
plain[22] = extra ? 0 : 1;
|
||||
struct UTUN_INSTANCE *source = sender, *target = receiver;
|
||||
if (code == ETCP_PONG) {
|
||||
/* Битый ответ имеет nonce настоящего ожидающего пинга и приходит раньше корректного ответа. */
|
||||
ping_done = 0;
|
||||
CHECK(etcp_send_ping(sender, receiver->my_keys.public_key, &addr, 500,
|
||||
ping_cb, NULL, payload, sizeof(payload)) == 0);
|
||||
CHECK(sender->pending_pings);
|
||||
uint64_t nonce = sender->pending_pings->nonce;
|
||||
for (int i = 0; i < 8; i++) plain[3 + 10 + i] = nonce >> (56 - i * 8);
|
||||
source = receiver; target = sender;
|
||||
alen = sizeof(addr);
|
||||
CHECK(getsockname(target->etcp_sockets->fd, (struct sockaddr*)&addr, &alen) == 0);
|
||||
}
|
||||
size_t enc_len = 0;
|
||||
size_t plain_len = extra ? sizeof(plain) : 23;
|
||||
CHECK(sc_encrypt(&wire_crypto, plain, plain_len, packet, &enc_len) == SC_OK);
|
||||
memset(packet + enc_len, 0, SC_PUBKEY_ENC_SALT_SIZE);
|
||||
CHECK(sc_obfuscate_pubkey(packet + enc_len, target->my_keys.public_key, source->my_keys.public_key,
|
||||
packet + enc_len + SC_PUBKEY_ENC_SALT_SIZE) == SC_OK);
|
||||
int before = wire_calls;
|
||||
size_t len = enc_len + SC_PUBKEY_ENC_SIZE;
|
||||
CHECK(__real_socket_sendto(source->etcp_sockets->fd, packet, len, (const struct sockaddr*)&addr, alen) == (ssize_t)len);
|
||||
if (code == ETCP_PING) {
|
||||
for (int i = 0; i < 10; i++) uasync_poll(sender->ua, 10);
|
||||
CHECK(wire_calls == before);
|
||||
} else {
|
||||
for (int i = 0; !ping_done && i < 1000; i++) uasync_poll(sender->ua, 10);
|
||||
CHECK(ping_done && !sender->pending_pings && wire_calls == before + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
puts("PASS malformed PING/PONG: invalid user length and oversized padding rejected");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
|
||||
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_INFO);
|
||||
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG);
|
||||
debug_set_category_level(DEBUG_CATEGORY_KEEPALIVE, DEBUG_LEVEL_DEBUG);
|
||||
utun_instance_set_tun_init_enabled(0); utun_instance_set_topo_group_enabled(0);
|
||||
struct UASYNC* ua = uasync_create(); CHECK(ua);
|
||||
sender = make_instance(ua, "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68",
|
||||
"ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a");
|
||||
receiver = make_instance(ua, "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f",
|
||||
"b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01");
|
||||
sc_init_ctx(&wire_crypto, &sender->my_keys);
|
||||
CHECK(sc_set_peer_public_key(&wire_crypto, receiver->my_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK);
|
||||
sender->padding.min = sender->padding.max = 0;
|
||||
receiver->padding.min = receiver->padding.max = ETCP_PADDING_MAX;
|
||||
ping_case(0, ETCP_PADDING_MAX);
|
||||
sender->padding.min = sender->padding.max = ETCP_PADDING_MAX;
|
||||
receiver->padding.min = receiver->padding.max = 0;
|
||||
ping_case(ETCP_PADDING_MAX, 0);
|
||||
puts("PASS stateless PING/PONG: encrypted padding boundaries and byte-exact echo");
|
||||
|
||||
struct ETCP_CONN* conn = make_connection(sender, &receiver->my_keys);
|
||||
struct ETCP_CONN* peer_conn = make_connection(receiver, &sender->my_keys);
|
||||
conn->padding.min = conn->padding.max = 7;
|
||||
peer_conn->padding.min = peer_conn->padding.max = 13;
|
||||
CHECK(etcp_padding_for_peer(sender, receiver->my_keys.public_key) == &conn->padding);
|
||||
ping_case(7, 13);
|
||||
puts("PASS PING/PONG: connection ranges override independent instance ranges");
|
||||
malformed_ping_cases();
|
||||
uint8_t large[1370] = {0}; struct sockaddr_storage addr; socklen_t alen = sizeof(addr);
|
||||
CHECK(getsockname(receiver->etcp_sockets->fd, (struct sockaddr*)&addr, &alen) == 0);
|
||||
int before = wire_calls;
|
||||
CHECK(etcp_send_ping(sender, receiver->my_keys.public_key, &addr, 500, ping_cb, NULL, large, sizeof(large)) < 0);
|
||||
CHECK(wire_calls == before && !sender->pending_pings);
|
||||
puts("PASS oversized PING: minimum padding must fit MTU, user_data is not truncated");
|
||||
|
||||
struct ETCP_LINK link = {0}; link.etcp = conn; link.conn = sender->etcp_sockets;
|
||||
link.mtu = 1280; link.remote_addr.ss_family = AF_INET; link.ka_period_ms = 200;
|
||||
link.send_hook = keepalive_hook;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
expected_keepalive_padding = i ? ETCP_PADDING_MAX : 0;
|
||||
conn->padding.min = conn->padding.max = expected_keepalive_padding;
|
||||
etcp_link_send_keepalive(&link);
|
||||
}
|
||||
CHECK(keepalive_packets == 2);
|
||||
struct ETCP_DGRAM* malformed = memory_pool_alloc(sender->pkt_pool); CHECK(malformed);
|
||||
memset(malformed->data, 0, 7); malformed->data[0] = ETCP_KEEPALIVE;
|
||||
malformed->data[1] = KA_FLAG_SLEEP; malformed->data_len = 7;
|
||||
uint32_t received = link.keepalive_recv_count;
|
||||
CHECK(etcp_keepalive_on_recv(sender->etcp_sockets, malformed, &link, 10) == 1);
|
||||
CHECK(link.keepalive_recv_count == received && !link.ka_peer_sleeping);
|
||||
puts("PASS keepalive: encrypted padding boundaries and receive parsing");
|
||||
|
||||
struct ETCP_CONN* sibling = etcp_connection_create(sender, "padding-sibling"); CHECK(sibling);
|
||||
struct ETCP_PADDING saved = sender->padding, saved_peer = peer_conn->padding, saved_sibling = sibling->padding;
|
||||
force_refresh(&conn->padding);
|
||||
CHECK(memcmp(&saved, &sender->padding, sizeof(saved)) == 0);
|
||||
CHECK(memcmp(&saved_peer, &peer_conn->padding, sizeof(saved_peer)) == 0);
|
||||
CHECK(memcmp(&saved_sibling, &sibling->padding, sizeof(saved_sibling)) == 0);
|
||||
saved = conn->padding;
|
||||
force_refresh(&sender->padding);
|
||||
CHECK(memcmp(&saved, &conn->padding, sizeof(saved)) == 0);
|
||||
int seen[ETCP_PADDING_MAX + 1] = {0}, different = 0;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
int size = etcp_padding_size(&conn->padding, ETCP_PADDING_MAX);
|
||||
CHECK(size >= conn->padding.min && size <= conn->padding.max);
|
||||
if (!seen[size]++) different++;
|
||||
}
|
||||
CHECK(different > 1);
|
||||
struct ETCP_PADDING boundary = {.min = 10, .max = 20};
|
||||
CHECK(etcp_padding_size(&boundary, 9) == -1 && etcp_padding_size(&boundary, 10) == 10);
|
||||
puts("PASS independent refresh timers, range invariants and MTU capacity");
|
||||
|
||||
CHECK(etcp_conn_ref_take(conn) == 0);
|
||||
etcp_connection_close(conn); CHECK(!conn->padding.timer);
|
||||
CHECK(etcp_padding_for_peer(sender, receiver->my_keys.public_key) == &sender->padding);
|
||||
etcp_conn_ref_free(conn); etcp_connection_close(peer_conn); etcp_connection_close(sibling); uasync_poll(ua, 0);
|
||||
fail_timer = 1;
|
||||
CHECK(etcp_connection_create(sender, "padding-allocation-failure") == NULL);
|
||||
size_t allocations = u_get_allocated_count();
|
||||
CHECK(utun_instance_create_from_config(ua, sender->config) == NULL);
|
||||
CHECK(u_get_allocated_count() == allocations);
|
||||
fail_timer = 0; uasync_poll(ua, 0);
|
||||
CHECK(!sender->connections->count);
|
||||
utun_instance_destroy(sender); utun_instance_destroy(receiver); uasync_poll(ua, 0);
|
||||
CHECK(ua->timer_alloc_count == ua->timer_free_count);
|
||||
uasync_destroy(ua, 0);
|
||||
puts("PASS timer cancellation and connection creation failure cleanup");
|
||||
return 0;
|
||||
}
|
||||
@@ -24,10 +24,13 @@ int __wrap_etcp_encrypt_send(struct ETCP_DGRAM* pkt) {
|
||||
int i;
|
||||
for (i = 0; i < 3 && pkt->link != &links[i]; i++);
|
||||
assert(i < 3);
|
||||
size_t body = (pkt->data[1] & KA_FLAG_SLEEP) ? 8 : 4;
|
||||
assert(pkt->data_len >= body + pkt->link->etcp->padding.min && pkt->data_len <= body + pkt->link->etcp->padding.max);
|
||||
for (size_t j = body; j < pkt->data_len; j++) assert(pkt->data[j] == 0);
|
||||
sent[i].count++;
|
||||
sent[i].flags = pkt->data[1];
|
||||
sent[i].active = pkt->data_len >= 6 ? pkt->data[4] | ((uint16_t)pkt->data[5] << 8) : 0;
|
||||
sent[i].sleep = pkt->data_len >= 8 ? pkt->data[6] | ((uint16_t)pkt->data[7] << 8) : 0;
|
||||
sent[i].active = (pkt->data[1] & KA_FLAG_SLEEP) ? pkt->data[4] | ((uint16_t)pkt->data[5] << 8) : 0;
|
||||
sent[i].sleep = (pkt->data[1] & KA_FLAG_SLEEP) ? pkt->data[6] | ((uint16_t)pkt->data[7] << 8) : 0;
|
||||
return pkt->data_len;
|
||||
}
|
||||
|
||||
@@ -71,6 +74,8 @@ static void setup(void) {
|
||||
activity_events = wait_calls = fail_phase_timer = 0;
|
||||
inst.ua = ua; inst.client_type = CLIENT_TYPE_MOBILE; inst.client_activity = CLIENT_ACTIVITY_STANDBY;
|
||||
udp.instance = tcp.instance = &inst;
|
||||
udp.padding.min = 7; udp.padding.max = 13;
|
||||
tcp.padding.min = 19; tcp.padding.max = 24;
|
||||
strcpy(udp.log_name, "standby-test-udp"); strcpy(tcp.log_name, "standby-test-tcp");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
links[i].etcp = i == 2 ? &tcp : &udp;
|
||||
|
||||
+204
-11
@@ -3,6 +3,7 @@
|
||||
#include "stcp_server.h"
|
||||
#include "stcp_client.h"
|
||||
#include "secure_channel.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
@@ -17,6 +18,60 @@ static struct SC_MYKEYS s_keys, c_keys;
|
||||
|
||||
#define BASE_PORT 23456
|
||||
|
||||
#ifdef __linux__
|
||||
#define WIRE_PADDING 32
|
||||
#define WIRE_HANDSHAKE_SIZE (STCP_HS_CLIENT_MIN + WIRE_PADDING)
|
||||
static int capture_wire, salt_number;
|
||||
static uint16_t capture_port;
|
||||
static size_t wire_count;
|
||||
static struct {
|
||||
uint16_t client_port;
|
||||
size_t size[2];
|
||||
uint8_t bytes[2][WIRE_HANDSHAKE_SIZE];
|
||||
} wire[2];
|
||||
|
||||
int __real_random_bytes(uint8_t*, size_t);
|
||||
int __wrap_random_bytes(uint8_t* out, size_t len) {
|
||||
if (capture_wire && len == SC_PUBKEY_ENC_SALT_SIZE) {
|
||||
memset(out, ++salt_number, len);
|
||||
return 0;
|
||||
}
|
||||
return __real_random_bytes(out, len);
|
||||
}
|
||||
|
||||
ssize_t __real_send(socket_t, const void*, size_t, int);
|
||||
ssize_t __wrap_send(socket_t sock, const void* buf, size_t len, int flags) {
|
||||
if (!capture_wire) return __real_send(sock, buf, len, flags);
|
||||
struct sockaddr_in local, peer; socklen_t alen = sizeof(local);
|
||||
if (getsockname(sock, (struct sockaddr*)&local, &alen) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "wire capture: getsockname failed fd=%d", (int)sock);
|
||||
return -1;
|
||||
}
|
||||
alen = sizeof(peer);
|
||||
if (getpeername(sock, (struct sockaddr*)&peer, &alen) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "wire capture: getpeername failed fd=%d", (int)sock);
|
||||
return -1;
|
||||
}
|
||||
int direction = ntohs(local.sin_port) == capture_port ? STCP_STREAM_SERVER_SEND : STCP_STREAM_CLIENT_SEND;
|
||||
uint16_t port = direction ? ntohs(peer.sin_port) : ntohs(local.sin_port);
|
||||
size_t i = 0;
|
||||
while (i < wire_count && wire[i].client_port != port) i++;
|
||||
if (i == wire_count) {
|
||||
if (wire_count >= 2) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "wire capture: unexpected connection port=%u", port); return -1; }
|
||||
wire[wire_count++].client_port = port;
|
||||
}
|
||||
/* Реальные send/recv получают короткие части: соль, тело и padding пересекают границы чтений. */
|
||||
ssize_t sent = __real_send(sock, buf, len > 7 ? 7 : len, flags);
|
||||
if (sent > 0) {
|
||||
size_t copied = WIRE_HANDSHAKE_SIZE - wire[i].size[direction];
|
||||
if (copied > (size_t)sent) copied = sent;
|
||||
memcpy(wire[i].bytes[direction] + wire[i].size[direction], buf, copied);
|
||||
wire[i].size[direction] += copied;
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define TASSERT(cond) do { \
|
||||
if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); return 1; } \
|
||||
} while(0)
|
||||
@@ -104,7 +159,7 @@ static int test1_sizes(void) {
|
||||
uint16_t port = BASE_PORT + 1;
|
||||
|
||||
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, NULL); TASSERT(sc);
|
||||
|
||||
size_t sizes[] = {0, 1, 16, 17, 255, 256, 1000, 65535};
|
||||
int n_sizes = 8;
|
||||
@@ -144,7 +199,7 @@ static int test2_many(void) {
|
||||
uint16_t port = BASE_PORT + 2;
|
||||
|
||||
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, NULL); TASSERT(sc);
|
||||
|
||||
int sent = 0, ticks = 0;
|
||||
while (srv.msg_count < 200 && ticks < 200) {
|
||||
@@ -185,7 +240,7 @@ static int test3_wrong_key(void) {
|
||||
|
||||
struct SC_MYKEYS rogue;
|
||||
TASSERT(sc_generate_keypair(&rogue) == SC_OK);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, rogue.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, rogue.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, NULL); TASSERT(sc);
|
||||
|
||||
int ticks = 0;
|
||||
while (ticks < 200) {
|
||||
@@ -210,7 +265,7 @@ static int test4_close(void) {
|
||||
uint16_t port = BASE_PORT + 4;
|
||||
|
||||
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, NULL); TASSERT(sc);
|
||||
|
||||
int closed = 0, ticks = 0;
|
||||
while (!srv.closed && ticks < 200) {
|
||||
@@ -260,7 +315,7 @@ static int test5_multi(void) {
|
||||
|
||||
struct stcp_client *clients[NCLI] = {0};
|
||||
for (int i = 0; i < NCLI; i++) {
|
||||
clients[i] = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &clip[i], NULL, NULL, peer_close_cb, &clip[i], NULL, 0, NULL, NULL);
|
||||
clients[i] = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &clip[i], NULL, NULL, peer_close_cb, &clip[i], NULL, 0, NULL, NULL, NULL);
|
||||
TASSERT(clients[i]);
|
||||
}
|
||||
|
||||
@@ -309,7 +364,7 @@ static int test6_interleaved(void) {
|
||||
uint16_t port = BASE_PORT + 6;
|
||||
|
||||
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, NULL); TASSERT(sc);
|
||||
|
||||
int round = 0, ticks = 0;
|
||||
while (srv.msg_count < 50 || cli.msg_count < 50) {
|
||||
@@ -343,7 +398,7 @@ static int test7_bulk_4mb(void) {
|
||||
uint16_t port = BASE_PORT + 7;
|
||||
|
||||
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, NULL); TASSERT(sc);
|
||||
|
||||
#define N_BULK 64
|
||||
#define SZ_BULK 65535
|
||||
@@ -382,7 +437,7 @@ static int test8_srv_recv_close(void) {
|
||||
uint16_t port = BASE_PORT + 8;
|
||||
|
||||
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, NULL, server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL); TASSERT(sc);
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli, NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, NULL); TASSERT(sc);
|
||||
|
||||
int ticks = 0;
|
||||
while ((!srv.ready || !cli.ready) && ticks < 200) { uasync_poll(ua, 10); ticks++; }
|
||||
@@ -450,9 +505,14 @@ static int build_malicious_handshake(uint16_t padding_size, uint8_t *out) {
|
||||
struct secure_channel sc;
|
||||
sc_init_ctx(&sc, &c_keys);
|
||||
if (sc_set_peer_public_key(&sc, s_keys.public_key, SC_PEER_PUBKEY_BIN) != SC_OK) return -1;
|
||||
struct sc_stream_state ss;
|
||||
if (sc_stream_init(&sc, &ss, STCP_STREAM_CLIENT_SEND) != SC_OK) return -1;
|
||||
struct sc_stream_state ss = {0};
|
||||
if (sc_stream_init(sc.session_key, &ss, STCP_STREAM_CLIENT_SEND, salt) != SC_OK) return -1;
|
||||
int rc = sc_stream_xor(&ss, enc, STCP_HS_ENC_CLIENT);
|
||||
if (rc == SC_OK && padding_size <= STCP_HS_PADDING_MAX) {
|
||||
uint8_t* pad = out + SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT;
|
||||
memset(pad, 0, padding_size);
|
||||
rc = sc_stream_xor(&ss, pad, padding_size);
|
||||
}
|
||||
sc_stream_cleanup(&ss);
|
||||
if (rc != SC_OK) return -1;
|
||||
|
||||
@@ -471,7 +531,6 @@ static int test9_raw_handshake_ok(void) {
|
||||
|
||||
uint8_t buf[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + 8];
|
||||
TASSERT(build_malicious_handshake(8, buf) == 0);
|
||||
memset(buf + SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT, 0, 8); // 8 байт паддинга (как легитимный клиент)
|
||||
socket_t s = raw_connect_send(port, buf, sizeof(buf));
|
||||
TASSERT(s != SOCKET_INVALID);
|
||||
|
||||
@@ -542,6 +601,135 @@ static int test11_garbage_handshake(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Границы padding в обе стороны; проверяем продолжение CTR после handshake реальными данными. */
|
||||
static int test12_padding_boundaries(void) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
struct UASYNC* ua = uasync_create(); TASSERT(ua);
|
||||
struct UTUN_INSTANCE si = {0}, ci = {0};
|
||||
si.ua = ci.ua = ua; si.my_keys = s_keys; ci.my_keys = c_keys;
|
||||
si.keepalive_interval = ci.keepalive_interval = 200;
|
||||
si.padding.min = si.padding.max = i ? ETCP_PADDING_MAX : 0;
|
||||
ci.padding.min = ci.padding.max = i ? 0 : ETCP_PADDING_MAX;
|
||||
struct test_peer srv = {0}, cli = {0};
|
||||
uint16_t port = BASE_PORT + 12 + i;
|
||||
struct stcp_server* ss = stcp_server_create(ua, port, &s_keys, NULL, &si,
|
||||
server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
struct stcp_client* sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key,
|
||||
NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli,
|
||||
NULL, NULL, peer_close_cb, &cli, NULL, 0, NULL, NULL, &ci); TASSERT(sc);
|
||||
for (int j = 0; (!srv.ready || !cli.ready) && j < 1000; j++) uasync_poll(ua, 10);
|
||||
TASSERT(srv.ready && cli.ready);
|
||||
const uint8_t data[] = {0, 0xff, 3, 4, 0};
|
||||
TASSERT(peer_send(&cli, data, sizeof(data)) == 0 && peer_send(&srv, data, sizeof(data)) == 0);
|
||||
for (int j = 0; (!srv.msg_count || !cli.msg_count) && j < 1000; j++) uasync_poll(ua, 10);
|
||||
TASSERT(srv.accum_len == sizeof(data) && cli.accum_len == sizeof(data));
|
||||
TASSERT(memcmp(srv.accum, data, sizeof(data)) == 0 && memcmp(cli.accum, data, sizeof(data)) == 0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "padding boundary: client=%u server=%u byte-exact DATA OK", ci.padding.max, si.padding.max);
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
if (srv.conn) stcp_conn_free(srv.conn);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss); uasync_destroy(ua, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
/* Два одновременных линка и повторное подключение: поля одинаковы, меняются только соли и шифротекст. */
|
||||
static int test13_salted_handshakes(void) {
|
||||
uint8_t ed_client[32], ed_server[32], history[4][2][WIRE_HANDSHAKE_SIZE];
|
||||
TASSERT(sc_derive_ed25519_pubkey(c_keys.private_key, ed_client) == SC_OK);
|
||||
TASSERT(sc_derive_ed25519_pubkey(s_keys.private_key, ed_server) == SC_OK);
|
||||
sc_context_t crypto;
|
||||
sc_init_ctx(&crypto, &c_keys);
|
||||
TASSERT(sc_set_peer_public_key(&crypto, s_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK);
|
||||
salt_number = 0;
|
||||
for (int round = 0; round < 2; round++) {
|
||||
struct UASYNC* ua = uasync_create(); TASSERT(ua);
|
||||
struct UTUN_INSTANCE si = {0}, ci = {0};
|
||||
si.ua = ci.ua = ua; si.my_keys = s_keys; ci.my_keys = c_keys;
|
||||
si.keepalive_interval = ci.keepalive_interval = 200;
|
||||
si.padding.min = si.padding.max = ci.padding.min = ci.padding.max = WIRE_PADDING;
|
||||
struct test_peer srv[2] = {{0}}, cli[2] = {{0}};
|
||||
struct stcp_client* clients[2] = {0};
|
||||
g_multi_peers = srv; g_multi_idx = 0; g_multi_max = 2;
|
||||
capture_port = BASE_PORT + 14 + round;
|
||||
struct stcp_server* ss = stcp_server_create(ua, capture_port, &s_keys, ed_server, &si,
|
||||
multi_connect_cb, NULL, NULL, NULL, AF_INET); TASSERT(ss);
|
||||
memset(wire, 0, sizeof(wire)); wire_count = 0; capture_wire = 1;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
clients[i] = stcp_client_connect(ua, "127.0.0.1", capture_port, &c_keys, s_keys.public_key,
|
||||
ed_client, 0, NULL, 0, 200, 0, client_ready_cb, &cli[i],
|
||||
NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, &ci);
|
||||
TASSERT(clients[i]);
|
||||
}
|
||||
for (int i = 0; (!srv[0].ready || !srv[1].ready || !cli[0].ready || !cli[1].ready) && i < 1000; i++) {
|
||||
uasync_poll(ua, 10);
|
||||
}
|
||||
TASSERT(srv[0].ready && srv[1].ready && cli[0].ready && cli[1].ready);
|
||||
TASSERT(wire_count == 2 && g_multi_idx == 2);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
TASSERT(memcmp(srv[i].conn->peer_ed25519_pubkey, ed_client, 32) == 0);
|
||||
TASSERT(memcmp(cli[i].conn->peer_ed25519_pubkey, ed_server, 32) == 0);
|
||||
for (int direction = 0; direction < 2; direction++) {
|
||||
TASSERT(wire[i].size[direction] == WIRE_HANDSHAKE_SIZE);
|
||||
struct sc_stream_state stream = {0};
|
||||
TASSERT(sc_stream_init(crypto.session_key, &stream, direction, wire[i].bytes[direction]) == SC_OK);
|
||||
uint8_t plain[STCP_HS_ENC_CLIENT + WIRE_PADDING]; size_t size = 0;
|
||||
memcpy(plain, wire[i].bytes[direction] + SC_PUBKEY_ENC_SIZE, sizeof(plain));
|
||||
TASSERT(stcp_frame_decrypt(plain, STCP_HS_ENC_CLIENT, &stream, &size) == 0);
|
||||
TASSERT(size == STCP_HS_PLAIN_SIZE);
|
||||
TASSERT(memcmp(plain, direction ? ed_server : ed_client, 32) == 0);
|
||||
TASSERT(sc_stream_xor(&stream, plain + STCP_HS_ENC_CLIENT, WIRE_PADDING) == SC_OK);
|
||||
sc_stream_cleanup(&stream);
|
||||
for (size_t p = STCP_HS_ENC_CLIENT; p < sizeof(plain); p++) TASSERT(plain[p] == 0);
|
||||
size_t current = round * 2 + i;
|
||||
for (size_t old = 0; old < current; old++) {
|
||||
TASSERT(memcmp(history[old][direction], wire[i].bytes[direction], SC_PUBKEY_ENC_SALT_SIZE) != 0);
|
||||
TASSERT(memcmp(history[old][direction] + 40, wire[i].bytes[direction] + 40, 32) != 0);
|
||||
TASSERT(memcmp(history[old][direction] + 91, wire[i].bytes[direction] + 91, WIRE_PADDING) != 0);
|
||||
}
|
||||
memcpy(history[current][direction], wire[i].bytes[direction], WIRE_HANDSHAKE_SIZE);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "wire handshake=%zu direction=%d: fresh identity/padding ciphertext", current, direction);
|
||||
}
|
||||
}
|
||||
const uint8_t data[] = {1, 0, 0xff, 8, 13};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
TASSERT(peer_send(&cli[i], data, sizeof(data)) == 0);
|
||||
TASSERT(peer_send(&srv[i], data, sizeof(data)) == 0);
|
||||
}
|
||||
for (int i = 0; (!srv[0].msg_count || !srv[1].msg_count || !cli[0].msg_count || !cli[1].msg_count) && i < 1000; i++) {
|
||||
uasync_poll(ua, 10);
|
||||
}
|
||||
capture_wire = 0;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
TASSERT(srv[i].accum_len == sizeof(data) && cli[i].accum_len == sizeof(data));
|
||||
TASSERT(memcmp(srv[i].accum, data, sizeof(data)) == 0 && memcmp(cli[i].accum, data, sizeof(data)) == 0);
|
||||
peer_cleanup(&srv[i]); peer_cleanup(&cli[i]);
|
||||
stcp_conn_free(srv[i].conn); stcp_client_destroy(clients[i]);
|
||||
}
|
||||
stcp_server_destroy(ss); uasync_destroy(ua, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Меняем соль и заново маскируем прежний X25519 ключ: ECDH тот же, но CTR обязан отвергнуть тело. */
|
||||
static int test14_modified_salt(void) {
|
||||
struct UASYNC* ua = uasync_create(); TASSERT(ua);
|
||||
struct test_peer srv = {0};
|
||||
uint16_t port = BASE_PORT + 16;
|
||||
struct stcp_server* ss = stcp_server_create(ua, port, &s_keys, NULL, NULL,
|
||||
server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
uint8_t packet[STCP_HS_CLIENT_MIN + 8];
|
||||
TASSERT(build_malicious_handshake(8, packet) == 0);
|
||||
packet[0] ^= 1;
|
||||
TASSERT(sc_obfuscate_pubkey(packet, s_keys.public_key, c_keys.public_key, packet + SC_PUBKEY_ENC_SALT_SIZE) == SC_OK);
|
||||
socket_t sock = raw_connect_send(port, packet, sizeof(packet)); TASSERT(sock != SOCKET_INVALID);
|
||||
for (int i = 0; !srv.closed && i < 1000; i++) uasync_poll(ua, 10);
|
||||
TASSERT(srv.closed && !srv.ready && srv.close_err == 2);
|
||||
socket_close_wrapper(sock); stcp_server_destroy(ss); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ======================= main =======================
|
||||
|
||||
int main(void) {
|
||||
@@ -566,6 +754,11 @@ int main(void) {
|
||||
TRUN(test9_raw_handshake_ok);
|
||||
TRUN(test10_oversized_padding);
|
||||
TRUN(test11_garbage_handshake);
|
||||
TRUN(test12_padding_boundaries);
|
||||
#ifdef __linux__
|
||||
TRUN(test13_salted_handshakes);
|
||||
#endif
|
||||
TRUN(test14_modified_salt);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "============================================");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", tests_passed, tests_total);
|
||||
|
||||
@@ -52,7 +52,7 @@ static int test1_basic(void) {
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", BASE_PORT + 1, &c_keys,
|
||||
s_keys.public_key, NULL,
|
||||
0, NULL,
|
||||
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL);
|
||||
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL);
|
||||
TASSERT(sc);
|
||||
|
||||
int ticks = 0;
|
||||
@@ -81,7 +81,7 @@ static int test2_wrong_key(void) {
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", BASE_PORT + 2, &c_keys,
|
||||
rogue.public_key, NULL,
|
||||
0, NULL,
|
||||
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL);
|
||||
0, 200, 0, on_cli_ready, &cli_ready, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL);
|
||||
TASSERT(sc);
|
||||
|
||||
int ticks = 0;
|
||||
|
||||
+24
-2
@@ -63,6 +63,8 @@ int main(void) {
|
||||
|
||||
uint8_t srv_pub[SC_PUBKEY_SIZE];
|
||||
memcpy(srv_pub, srv->my_keys.public_key, SC_PUBKEY_SIZE);
|
||||
cli->padding.min = cli->padding.max = 0;
|
||||
srv->padding.min = srv->padding.max = ETCP_PADDING_MAX;
|
||||
|
||||
/* success path: ping listening STCP server */
|
||||
struct sockaddr_storage addr; memset(&addr, 0, sizeof(addr));
|
||||
@@ -77,6 +79,17 @@ int main(void) {
|
||||
if (ping_ok != 1) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "=== FAILED (success path): success=%d rtt=%u ===", ping_ok, ping_rtt); return 1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== success path PASSED: rtt=%u ===", ping_rtt);
|
||||
|
||||
/* Обратная граница: нулевой ответ сервера должен завершиться без ожидания следующего пакета. */
|
||||
ping_ok = -1;
|
||||
cli->padding.min = cli->padding.max = ETCP_PADDING_MAX;
|
||||
srv->padding.min = srv->padding.max = 0;
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL);
|
||||
if (rc != 0) return 1;
|
||||
ticks = 0;
|
||||
while (ping_ok < 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
|
||||
if (ping_ok != 1) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "TCP ping zero server padding failed"); return 1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== reversed padding boundary PASSED ===");
|
||||
|
||||
/* failure path: port with no listener */
|
||||
ping_ok = -1; ping_rtt = 0;
|
||||
struct sockaddr_storage bad_addr; memset(&bad_addr, 0, sizeof(bad_addr));
|
||||
@@ -88,8 +101,17 @@ int main(void) {
|
||||
if (ping_ok != 0) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "=== FAILED (failure path): success=%d rtt=%u rc=%d ===", ping_ok, ping_rtt, rc); return 1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== failure path PASSED: success=%d ===", ping_ok);
|
||||
|
||||
srv->running = 0; cli->running = 0;
|
||||
utun_instance_destroy(srv); utun_instance_destroy(cli);
|
||||
/* Одноразовый TCP-пинг не удерживает instance: длина выбирается до асинхронного connect. */
|
||||
ping_ok = -1;
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL);
|
||||
if (rc != 0) return 1;
|
||||
cli->running = 0; utun_instance_destroy(cli); cli = NULL;
|
||||
ticks = 0;
|
||||
while (ping_ok < 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
|
||||
if (ping_ok != 1) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "TCP ping outliving instance failed"); return 1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== TCP ping without retained instance PASSED ===");
|
||||
srv->running = 0;
|
||||
utun_instance_destroy(srv);
|
||||
uasync_destroy(ua, 0);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== PASSED ===");
|
||||
|
||||
+30
-14
@@ -34,11 +34,13 @@ int main(void) {
|
||||
CHECK(sc_set_peer_public_key(&ctx_a, keys_b.public_key, SC_PEER_PUBKEY_BIN) == SC_OK, "set peer key A<-B");
|
||||
CHECK(sc_set_peer_public_key(&ctx_b, keys_a.public_key, SC_PEER_PUBKEY_BIN) == SC_OK, "set peer key B<-A");
|
||||
|
||||
struct sc_stream_state send_a, recv_a, send_b, recv_b;
|
||||
CHECK(sc_stream_init(&ctx_a, &send_a, 0) == SC_OK, "init send_A (id=0)");
|
||||
CHECK(sc_stream_init(&ctx_a, &recv_a, 1) == SC_OK, "init recv_A (id=1)");
|
||||
CHECK(sc_stream_init(&ctx_b, &recv_b, 0) == SC_OK, "init recv_B (id=0)");
|
||||
CHECK(sc_stream_init(&ctx_b, &send_b, 1) == SC_OK, "init send_B (id=1)");
|
||||
const uint8_t salt_a[SC_PUBKEY_ENC_SALT_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||
const uint8_t salt_b[SC_PUBKEY_ENC_SALT_SIZE] = {8, 7, 6, 5, 4, 3, 2, 1};
|
||||
struct sc_stream_state send_a = {0}, recv_a = {0}, send_b = {0}, recv_b = {0};
|
||||
CHECK(sc_stream_init(ctx_a.session_key, &send_a, 0, salt_a) == SC_OK, "init send_A (id=0, salt_A)");
|
||||
CHECK(sc_stream_init(ctx_a.session_key, &recv_a, 1, salt_b) == SC_OK, "init recv_A (id=1, salt_B)");
|
||||
CHECK(sc_stream_init(ctx_b.session_key, &recv_b, 0, salt_a) == SC_OK, "init recv_B (id=0, salt_A)");
|
||||
CHECK(sc_stream_init(ctx_b.session_key, &send_b, 1, salt_b) == SC_OK, "init send_B (id=1, salt_B)");
|
||||
|
||||
uint8_t original[2048], data[2048];
|
||||
for (int i = 0; i < (int)sizeof(original); i++) original[i] = (uint8_t)(i * 3 + 17);
|
||||
@@ -109,20 +111,34 @@ int main(void) {
|
||||
// Test 8: error handling
|
||||
CHECK(sc_stream_xor(&send_a, NULL, 0) == SC_OK, "xor len=0 OK");
|
||||
CHECK(sc_stream_xor(&send_a, data, 0) == SC_OK, "xor data_len=0 OK");
|
||||
CHECK(sc_stream_init(&ctx_a, NULL, 0) == SC_ERR_INVALID_ARG, "init NULL state");
|
||||
CHECK(sc_stream_init(NULL, &send_a, 0) == SC_ERR_INVALID_ARG, "init NULL ctx");
|
||||
CHECK(sc_stream_init(ctx_a.session_key, NULL, 0, salt_a) == SC_ERR_INVALID_ARG, "init NULL state");
|
||||
CHECK(sc_stream_init(NULL, &send_a, 0, salt_a) == SC_ERR_INVALID_ARG, "init NULL key");
|
||||
CHECK(sc_stream_init(ctx_a.session_key, &send_a, 0, NULL) == SC_ERR_INVALID_ARG, "init NULL salt");
|
||||
CHECK(sc_stream_init(ctx_a.session_key, &send_a, 0, salt_a) == SC_ERR_INVALID_ARG, "reject initialized stream reuse");
|
||||
|
||||
struct sc_stream_state uninit;
|
||||
memset(&uninit, 0, sizeof(uninit));
|
||||
CHECK(sc_stream_xor(&uninit, data, 1) == SC_ERR_NOT_INITIALIZED, "xor uninitialized");
|
||||
|
||||
// У ctx без session_ready
|
||||
sc_context_t no_session_ctx;
|
||||
struct SC_MYKEYS dummy_keys;
|
||||
memset(&dummy_keys, 0, sizeof(dummy_keys));
|
||||
sc_init_ctx(&no_session_ctx, &dummy_keys);
|
||||
struct sc_stream_state bad_stream;
|
||||
CHECK(sc_stream_init(&no_session_ctx, &bad_stream, 0) == SC_ERR_NOT_INITIALIZED, "init without session");
|
||||
/* Одинаковые ключ, позиция и текст: отдельно проверяем влияние соли и направления на поток. */
|
||||
for (uint32_t direction = 0; direction < 2; direction++) {
|
||||
struct sc_stream_state first = {0}, repeat = {0}, fresh = {0}, opposite = {0};
|
||||
uint8_t one[256] = {0}, same[256] = {0}, other[256] = {0}, reverse[256] = {0};
|
||||
CHECK(sc_stream_init(ctx_a.session_key, &first, direction, salt_a) == SC_OK, "init reference stream");
|
||||
CHECK(sc_stream_init(ctx_b.session_key, &repeat, direction, salt_a) == SC_OK, "init matching peer stream");
|
||||
CHECK(sc_stream_init(ctx_a.session_key, &fresh, direction, salt_b) == SC_OK, "init fresh salt stream");
|
||||
CHECK(sc_stream_init(ctx_a.session_key, &opposite, direction ^ 1, salt_a) == SC_OK, "init opposite direction");
|
||||
CHECK(sc_stream_xor(&first, one, sizeof(one)) == SC_OK, "reference zero plaintext");
|
||||
CHECK(sc_stream_xor(&repeat, same, sizeof(same)) == SC_OK, "matching zero plaintext");
|
||||
CHECK(sc_stream_xor(&fresh, other, sizeof(other)) == SC_OK, "fresh salt zero plaintext");
|
||||
CHECK(sc_stream_xor(&opposite, reverse, sizeof(reverse)) == SC_OK, "opposite direction zero plaintext");
|
||||
CHECK(memcmp(one, same, sizeof(one)) == 0, "peer uses identical key/direction/salt");
|
||||
CHECK(memcmp(one, other, 32) != 0, "fresh salt changes first 32 encrypted bytes");
|
||||
CHECK(memcmp(one + 51, other + 51, 158) != 0, "fresh salt changes zero padding keystream");
|
||||
CHECK(memcmp(one, reverse, sizeof(one)) != 0, "direction separates streams at equal position");
|
||||
sc_stream_cleanup(&first); sc_stream_cleanup(&repeat);
|
||||
sc_stream_cleanup(&fresh); sc_stream_cleanup(&opposite);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
sc_stream_cleanup(&send_a);
|
||||
|
||||
Reference in new issue
Block a user