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
@@ -32,6 +32,8 @@ utun_CORE_SOURCES = \
|
||||
tun_freebsd.c \
|
||||
tun_windows.c \
|
||||
transport_layer/etcp.c \
|
||||
transport_layer/etcp_padding.c \
|
||||
transport_layer/etcp_padding.h \
|
||||
transport_layer/etcp_session.c \
|
||||
transport_layer/etcp_session.h \
|
||||
transport_layer/etcp_connections.c \
|
||||
@@ -170,6 +172,8 @@ libutun_a_SOURCES = \
|
||||
tun_freebsd.c \
|
||||
tun_windows.c \
|
||||
transport_layer/etcp.c \
|
||||
transport_layer/etcp_padding.c \
|
||||
transport_layer/etcp_padding.h \
|
||||
transport_layer/etcp_session.c \
|
||||
transport_layer/etcp_session.h \
|
||||
transport_layer/etcp_connections.c \
|
||||
|
||||
@@ -276,6 +276,11 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
|
||||
queue_data_put_with_index(instance->connections, qe);
|
||||
}
|
||||
|
||||
if (etcp_padding_start(&etcp->padding, instance->ua, etcp->log_name) < 0) {
|
||||
etcp_connection_close(etcp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] connection initialized. ETCP=%p mtu=%d, next_tx_id=%u state=pending",
|
||||
etcp->log_name, etcp, etcp->mtu, etcp->next_tx_id);
|
||||
|
||||
@@ -423,6 +428,7 @@ void etcp_connection_close(struct ETCP_CONN* etcp) {
|
||||
callring_put(CR_CCLOSE_IN, (uintptr_t)etcp, 0);
|
||||
|
||||
etcp->close_requested = 1;
|
||||
etcp_padding_stop(&etcp->padding);
|
||||
if (etcp->callbacks_running) {
|
||||
if (etcp->close_token) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] close requested inside callback chain — deferring", etcp->log_name);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "etcp_connections.h"
|
||||
#include "secure_channel.h"
|
||||
#include "etcp_padding.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -121,6 +122,8 @@ struct ETCP_CONN {
|
||||
uint8_t tcp_link_count; // количество UP TCP линков
|
||||
uint8_t last_assigned_link_id; // последний выданный link_id (для циклического перебора)
|
||||
|
||||
struct ETCP_PADDING padding; // диапазон и независимый таймер всех линков этого соединения
|
||||
|
||||
// Crypto and state
|
||||
struct secure_channel crypto_ctx;
|
||||
struct PKTNORM* normalizer;
|
||||
|
||||
@@ -363,16 +363,16 @@ void etcp_link_enter_reinit(struct ETCP_LINK* link) {
|
||||
|
||||
|
||||
// Собирает 19-байтовый ключ из sockaddr (port+addr+family) для hash-индекса links_queue.
|
||||
static void sockaddr_to_key(struct sockaddr_storage* addr, uint8_t key[LINK_ADDR_KEY_SIZE], int is_tcp) {
|
||||
static void sockaddr_to_key(const struct sockaddr_storage* addr, uint8_t key[LINK_ADDR_KEY_SIZE], int is_tcp) {
|
||||
memset(key, 0, LINK_ADDR_KEY_SIZE);
|
||||
if (!addr) return;
|
||||
if (addr->ss_family == AF_INET) {
|
||||
struct sockaddr_in* sa = (struct sockaddr_in*)addr;
|
||||
const struct sockaddr_in* sa = (const struct sockaddr_in*)addr;
|
||||
memcpy(key, &sa->sin_port, 2);
|
||||
memcpy(key + 2, &sa->sin_addr.s_addr, 4);
|
||||
key[18] = 4 | (is_tcp ? 0x80 : 0);
|
||||
} else {
|
||||
struct sockaddr_in6* sa = (struct sockaddr_in6*)addr;
|
||||
const struct sockaddr_in6* sa = (const struct sockaddr_in6*)addr;
|
||||
memcpy(key, &sa->sin6_port, 2);
|
||||
memcpy(key + 2, &sa->sin6_addr, 16);
|
||||
key[18] = 6 | (is_tcp ? 0x80 : 0);
|
||||
@@ -480,7 +480,7 @@ static int sockaddr_equal(const struct sockaddr_storage* a, const struct sockadd
|
||||
}
|
||||
|
||||
// Поиск линка в сокете по remote-адресу (через hash-индекс links_queue).
|
||||
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr, int is_tcp) {
|
||||
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, const struct sockaddr_storage* addr, int is_tcp) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "");
|
||||
if (!e_sock || !addr || !e_sock->links_queue) return NULL;
|
||||
uint8_t key[LINK_ADDR_KEY_SIZE];
|
||||
@@ -1451,6 +1451,25 @@ static int etcp_send_ping_raw(struct ETCP_DGRAM* dgram, struct ETCP_SOCKET* e_so
|
||||
}
|
||||
int len = dgram->data_len - dgram->noencrypt_len;
|
||||
uint8_t enc_buf[1600];
|
||||
struct ETCP_LINK* link = etcp_link_find_by_addr(e_sock, addr, 0);
|
||||
int mtu = link ? link->mtu : PACKET_DATA_MAX_MTU;
|
||||
int udp_overhead = addr->ss_family == AF_INET6 ? IP_UDP_OVERHEAD_V6 : IP_UDP_OVERHEAD_V4;
|
||||
int available = mtu - udp_overhead - len - 3 - SC_NONCE_SIZE - SC_CRC32_SIZE - SC_TAG_SIZE - dgram->noencrypt_len;
|
||||
if (len < 0 || available < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "ping exceeds MTU mtu=%d body=%d tail=%u addr=%s",
|
||||
mtu, len, dgram->noencrypt_len, sockaddr_storage_to_str(addr).str);
|
||||
return -1;
|
||||
}
|
||||
const struct ETCP_PADDING* profile = etcp_padding_for_peer(e_sock->instance, sc->peer_public_key);
|
||||
int padding = etcp_padding_size(profile, available);
|
||||
if (padding < 0) return -1;
|
||||
memmove(dgram->data + len + padding, dgram->data + len, dgram->noencrypt_len);
|
||||
memset(dgram->data + len, 0, padding);
|
||||
dgram->data_len += padding;
|
||||
len += padding;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ping TX code=%u owner=%s range=%u..%u padding=%d body=%d mtu=%d",
|
||||
dgram->data[0], profile == &e_sock->instance->padding ? "instance" : "connection",
|
||||
profile->min, profile->max, padding, len, mtu);
|
||||
if (len < 0 || len + (int)dgram->noencrypt_len + SC_ENCRYPT_OVERHEAD > (int)sizeof(enc_buf)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "ping packet too large len=%d ne=%d", len, dgram->noencrypt_len);
|
||||
return -1;
|
||||
@@ -1699,7 +1718,7 @@ int etcp_send_tcp_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubke
|
||||
|
||||
struct stcp_client* cli = stcp_ping_send(instance->ua, addr_str, port, &instance->my_keys, peer_pubkey_bin,
|
||||
instance->my_ed25519_pubkey, instance->client_type,
|
||||
instance->keepalive_interval, timeout_ms, tcp_ping_cb_adapter, a, socks);
|
||||
instance->keepalive_interval, timeout_ms, tcp_ping_cb_adapter, a, socks, instance);
|
||||
if (!cli) { u_free(a); return -4; }
|
||||
if (out_cli) *out_cli = cli;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "tcp ping to %s:%u timeout=%d", addr_str, (unsigned)port, timeout_ms);
|
||||
@@ -1718,7 +1737,11 @@ static int handle_ping(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
|
||||
uint64_t peer_id = wire_read64(pkt->data + 2);
|
||||
uint64_t nonce = wire_read64(pkt->data + 10);
|
||||
uint16_t ulen = wire_read16(pkt->data + 18);
|
||||
if (ulen > pkt->data_len - 20) ulen = (uint16_t)(pkt->data_len - 20);
|
||||
if (ulen > pkt->data_len - 20 || pkt->data_len - 20 - ulen > ETCP_PADDING_MAX) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid PING user_len=%u body=%u from=%s",
|
||||
ulen, pkt->data_len, sockaddr_storage_to_str(addr).str);
|
||||
return 7;
|
||||
}
|
||||
const uint8_t* udata = (ulen > 0) ? (pkt->data + 20) : NULL;
|
||||
|
||||
if ((flags & ETCP_PING_FLAG_SEND_RTT) && ulen >= 2) {
|
||||
@@ -1784,6 +1807,11 @@ static int handle_pong(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt, const
|
||||
const uint8_t* udata = NULL;
|
||||
if (pkt->data_len >= 20) {
|
||||
ulen = wire_read16(pkt->data + 18);
|
||||
if (ulen > pkt->data_len - 20 || pkt->data_len - 20 - ulen > ETCP_PADDING_MAX) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid PONG user_len=%u body=%u from=%s",
|
||||
ulen, pkt->data_len, sockaddr_storage_to_str(addr).str);
|
||||
return 7;
|
||||
}
|
||||
if (ulen > 0 && pkt->data_len >= 20 + ulen) {
|
||||
udata = pkt->data + 20;
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ void etcp_link_close(struct ETCP_LINK* link);
|
||||
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
|
||||
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
|
||||
// find link by address
|
||||
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr, int is_tcp);
|
||||
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, const struct sockaddr_storage* addr, int is_tcp);
|
||||
|
||||
// Выбор исходящего линка для collision INIT по принимающему сокету и адресу отправителя.
|
||||
// Линк через другой локальный сокет не подходит. NULL — встречной попытки на этой паре нет.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include <string.h>
|
||||
#ifdef UTUN_HAVE_STANDBY
|
||||
#include "standby.h"
|
||||
#endif
|
||||
@@ -43,7 +44,9 @@ static void etcp_link_send_keepalive_ex(struct ETCP_LINK* link, uint8_t flags) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_KEEPALIVE, "");
|
||||
if (!link || !link->etcp || !link->etcp->instance) return;
|
||||
|
||||
struct ETCP_DGRAM* dgram = u_malloc(sizeof(struct ETCP_DGRAM) + 8);
|
||||
int padding = etcp_padding_size(&link->etcp->padding, ETCP_PADDING_MAX);
|
||||
if (padding < 0) return;
|
||||
struct ETCP_DGRAM* dgram = u_malloc(sizeof(struct ETCP_DGRAM) + 8 + padding);
|
||||
if (!dgram) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_KEEPALIVE, "malloc failed");
|
||||
return;
|
||||
@@ -63,11 +66,15 @@ static void etcp_link_send_keepalive_ex(struct ETCP_LINK* link, uint8_t flags) {
|
||||
} else {
|
||||
dgram->data_len = 4;
|
||||
}
|
||||
memset(dgram->data + dgram->data_len, 0, padding);
|
||||
dgram->data_len += padding;
|
||||
dgram->noencrypt_len = 0;
|
||||
dgram->timestamp = get_current_timestamp();
|
||||
dgram->flag_up = link->is_tcp ? 1 : link->recv_keepalive;
|
||||
|
||||
link->keepalive_sent_count++;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] keepalive TX link=%u flags=%02x padding=%d body=%u transport=%s",
|
||||
link->etcp->log_name, link->local_link_id, flags, padding, dgram->data_len, link->is_tcp ? "tcp" : "udp");
|
||||
|
||||
etcp_encrypt_send(dgram);
|
||||
u_free(dgram);
|
||||
@@ -485,14 +492,18 @@ int etcp_keepalive_on_recv(struct ETCP_SOCKET* e_sock, struct ETCP_DGRAM* pkt,
|
||||
uint8_t code = pkt->data[0];
|
||||
|
||||
if (code == ETCP_KEEPALIVE) {
|
||||
uint16_t peer_period = 0;
|
||||
if (pkt->data_len >= 4)
|
||||
peer_period = pkt->data[2] | ((uint16_t)pkt->data[3] << 8);
|
||||
else if (pkt->data_len >= 3) /* легаси-формат (без флагов) */
|
||||
peer_period = pkt->data[1] | ((uint16_t)pkt->data[2] << 8);
|
||||
if (peer_period) link->keepalive_timeout = (uint32_t)peer_period * KA_TIMEOUT_MULT;
|
||||
|
||||
uint8_t flags = (pkt->data_len >= 2) ? pkt->data[1] : 0;
|
||||
size_t body_len = (flags & KA_FLAG_SLEEP) ? 8 : 4;
|
||||
if (pkt->data_len < body_len || pkt->data_len - body_len > ETCP_PADDING_MAX) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_KEEPALIVE, "[%s] invalid keepalive size=%u flags=%02x body=%zu",
|
||||
link->etcp->log_name, pkt->data_len, flags, body_len);
|
||||
memory_pool_free(e_sock->instance->pkt_pool, pkt);
|
||||
return 1;
|
||||
}
|
||||
uint16_t peer_period = pkt->data[2] | ((uint16_t)pkt->data[3] << 8);
|
||||
if (peer_period) link->keepalive_timeout = (uint32_t)peer_period * KA_TIMEOUT_MULT;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_KEEPALIVE, "[%s] keepalive RX link=%u flags=%02x padding=%zu period=%ums",
|
||||
link->etcp->log_name, link->local_link_id, flags, pkt->data_len - body_len, peer_period);
|
||||
|
||||
if (flags & KA_FLAG_SLEEP_ACK) {
|
||||
/* Ответ на мой анонс сна — команда дошла, ретраи сняты, счётчик провалов обнулён. */
|
||||
|
||||
@@ -31,6 +31,7 @@ extern "C" {
|
||||
* data[2..3] = ka_period_ms (LE 16-bit) — база dead-link таймаута, мс
|
||||
* data[4..5] = active_remaining (LE 16-bit, x0.1с) — до SLEEP-фазы (0 = уже спим)
|
||||
* data[6..7] = sleep_duration (LE 16-bit, x0.1с) — длительность SLEEP-фазы
|
||||
* далее = зашифрованный нулевой padding; тело без SLEEP занимает 4 байта, с SLEEP — 8
|
||||
*
|
||||
* Протокол полностью симметричный (без привязки к STCP-роли is_server):
|
||||
* спящий шлёт анонс KA_FLAG_SLEEP, пир «затыкается» (гасит свой normal-keepalive)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "etcp_padding.h"
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static unsigned padding_random(unsigned min, unsigned max) {
|
||||
return min + (unsigned)rand() % (max - min + 1);
|
||||
}
|
||||
|
||||
static void padding_generate(struct ETCP_PADDING* padding) {
|
||||
padding->min = padding_random(0, 30);
|
||||
padding->max = padding->min + padding_random(32, 128);
|
||||
}
|
||||
|
||||
static void padding_timer_cb(void* arg);
|
||||
|
||||
static int padding_refresh(struct ETCP_PADDING* padding) {
|
||||
padding_generate(padding);
|
||||
unsigned seconds = padding_random(50, 300);
|
||||
padding->next_refresh_tb = get_time_tb() + (uint64_t)seconds * 10000;
|
||||
padding->timer = uasync_set_timeout(padding->ua, seconds * 10000, padding, padding_timer_cb, "etcp_padding");
|
||||
if (!padding->timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] padding=%p: refresh timer allocation failed", padding->name, (void*)padding);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] padding=%p: range=%u..%u refresh_in=%us",
|
||||
padding->name, (void*)padding, padding->min, padding->max, seconds);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void padding_timer_cb(void* arg) {
|
||||
struct ETCP_PADDING* padding = arg;
|
||||
padding->timer = NULL;
|
||||
padding_refresh(padding);
|
||||
}
|
||||
|
||||
int etcp_padding_start(struct ETCP_PADDING* padding, struct UASYNC* ua, const char* name) {
|
||||
if (!padding || !ua || padding->timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "padding: invalid start padding=%p ua=%p", (void*)padding, (void*)ua);
|
||||
return -1;
|
||||
}
|
||||
padding->ua = ua;
|
||||
padding->name = name && name[0] ? name : "instance";
|
||||
return padding_refresh(padding);
|
||||
}
|
||||
|
||||
void etcp_padding_stop(struct ETCP_PADDING* padding) {
|
||||
if (!padding) return;
|
||||
if (padding->timer) {
|
||||
uasync_cancel_timeout(padding->ua, padding->timer);
|
||||
padding->timer = NULL;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] padding=%p: refresh timer cancelled", padding->name, (void*)padding);
|
||||
}
|
||||
padding->ua = NULL;
|
||||
padding->next_refresh_tb = 0;
|
||||
}
|
||||
|
||||
int etcp_padding_size(const struct ETCP_PADDING* padding, size_t available) {
|
||||
struct ETCP_PADDING temporary = {0};
|
||||
if (!padding) { padding_generate(&temporary); padding = &temporary; }
|
||||
if (padding->min > padding->max || padding->max > ETCP_PADDING_MAX || available < padding->min) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "padding: invalid range/capacity min=%u max=%u available=%zu",
|
||||
padding->min, padding->max, available);
|
||||
return -1;
|
||||
}
|
||||
unsigned max = available < padding->max ? (unsigned)available : padding->max;
|
||||
return padding_random(padding->min, max);
|
||||
}
|
||||
|
||||
const struct ETCP_PADDING* etcp_padding_for_peer(struct UTUN_INSTANCE* inst, const uint8_t* public_key) {
|
||||
if (!inst || !public_key) return NULL;
|
||||
struct ETCP_CONN* conn = instance_find_conn(inst, sc_derive_node_id_from_pubkey(public_key));
|
||||
if (conn && !conn->close_requested) return &conn->padding;
|
||||
return &inst->padding;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* Независимый диапазон padding владельца; таймер и выбор длины работают в его uasync-потоке. */
|
||||
#ifndef ETCP_PADDING_H
|
||||
#define ETCP_PADDING_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct UASYNC;
|
||||
struct UTUN_INSTANCE;
|
||||
|
||||
#define ETCP_PADDING_MAX 158
|
||||
|
||||
struct ETCP_PADDING {
|
||||
uint16_t min, max;
|
||||
void* timer;
|
||||
struct UASYNC* ua;
|
||||
const char* name; // заимствованное имя владельца для диагностики
|
||||
uint64_t next_refresh_tb;
|
||||
};
|
||||
|
||||
int etcp_padding_start(struct ETCP_PADDING* padding, struct UASYNC* ua, const char* name);
|
||||
void etcp_padding_stop(struct ETCP_PADDING* padding);
|
||||
/* NULL допустим только для автономного STCP без instance: диапазон генерируется на операцию. */
|
||||
int etcp_padding_size(const struct ETCP_PADDING* padding, size_t available);
|
||||
/* Существующий незакрываемый ETCP_CONN владеет своим диапазоном; иначе используется instance. */
|
||||
const struct ETCP_PADDING* etcp_padding_for_peer(struct UTUN_INSTANCE* inst, const uint8_t* public_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -107,8 +107,8 @@ static void sc_derive_session_key(const uint8_t *shared_secret, uint8_t *session
|
||||
memcpy(session_key, hash, SC_SESSION_KEY_SIZE);
|
||||
}
|
||||
|
||||
// Derive 12-byte nonce for stream cipher from session key + stream_id
|
||||
static void sc_stream_derive_nonce(const uint8_t *session_key, uint32_t stream_id, uint8_t *nonce_out) {
|
||||
// Соль отправителя разделяет CTR-потоки повторных и параллельных подключений одной пары узлов.
|
||||
static void sc_stream_derive_nonce(const uint8_t *session_key, uint32_t stream_id, const uint8_t *salt, uint8_t *nonce_out) {
|
||||
SHA256_CTX sha_ctx;
|
||||
uint8_t hash[SC_HASH_SIZE];
|
||||
uint8_t stream_id_le[4];
|
||||
@@ -119,6 +119,7 @@ static void sc_stream_derive_nonce(const uint8_t *session_key, uint32_t stream_i
|
||||
SHA256_Init(&sha_ctx);
|
||||
SHA256_Update(&sha_ctx, session_key, SC_SESSION_KEY_SIZE);
|
||||
SHA256_Update(&sha_ctx, stream_id_le, sizeof(stream_id_le));
|
||||
SHA256_Update(&sha_ctx, salt, SC_PUBKEY_ENC_SALT_SIZE);
|
||||
SHA256_Update(&sha_ctx, (const uint8_t *)"uTun3-stream", 12);
|
||||
SHA256_Final(hash, &sha_ctx);
|
||||
memcpy(nonce_out, hash, SC_STREAM_NONCE_SIZE);
|
||||
@@ -354,24 +355,27 @@ sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8
|
||||
|
||||
// --- Streaming cipher (AES-128-CTR) ---
|
||||
|
||||
sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uint32_t stream_id) {
|
||||
if (!ctx || !state) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
|
||||
if (!ctx->session_ready) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "session not ready"); return SC_ERR_NOT_INITIALIZED; }
|
||||
sc_status_t sc_stream_init(const uint8_t session_key[SC_SESSION_KEY_SIZE], struct sc_stream_state *state,
|
||||
uint32_t stream_id, const uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE]) {
|
||||
if (!session_key || !state || !salt || state->initialized) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream init: invalid arguments or already initialized, direction=%u", stream_id);
|
||||
return SC_ERR_INVALID_ARG;
|
||||
}
|
||||
uint8_t nonce[SC_STREAM_NONCE_SIZE];
|
||||
sc_stream_derive_nonce(ctx->session_key, stream_id, nonce);
|
||||
sc_stream_derive_nonce(session_key, stream_id, salt, nonce);
|
||||
uint8_t iv[16];
|
||||
memcpy(iv, nonce, SC_STREAM_NONCE_SIZE);
|
||||
memset(iv + SC_STREAM_NONCE_SIZE, 0, 4);
|
||||
EVP_CIPHER_CTX *ectx = EVP_CIPHER_CTX_new();
|
||||
if (!ectx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_CIPHER_CTX_new failed"); return SC_ERR_CRYPTO; }
|
||||
if (EVP_EncryptInit_ex(ectx, EVP_aes_128_ctr(), NULL, ctx->session_key, iv) != 1) {
|
||||
if (EVP_EncryptInit_ex(ectx, EVP_aes_128_ctr(), NULL, session_key, iv) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_EncryptInit_ex failed");
|
||||
EVP_CIPHER_CTX_free(ectx); return SC_ERR_CRYPTO;
|
||||
}
|
||||
state->ectx = ectx;
|
||||
state->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "stream_id=%u nonce=%02x%02x%02x%02x...",
|
||||
stream_id, nonce[0], nonce[1], nonce[2], nonce[3]);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CRYPTO, "stream init: direction=%u salt=%02x%02x%02x%02x... nonce=%02x%02x%02x%02x...",
|
||||
stream_id, salt[0], salt[1], salt[2], salt[3], nonce[0], nonce[1], nonce[2], nonce[3]);
|
||||
return SC_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,9 @@ struct sc_stream_state {
|
||||
uint8_t initialized;
|
||||
};
|
||||
|
||||
sc_status_t sc_stream_init(sc_context_t *ctx, struct sc_stream_state *state, uint32_t stream_id);
|
||||
/* Производный ключ, соль отправителя и направление задают CTR IV. state — нулевой или после cleanup. */
|
||||
sc_status_t sc_stream_init(const uint8_t session_key[SC_SESSION_KEY_SIZE], struct sc_stream_state *state,
|
||||
uint32_t stream_id, const uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE]);
|
||||
sc_status_t sc_stream_xor(struct sc_stream_state *state, uint8_t *data, size_t data_len);
|
||||
void sc_stream_cleanup(struct sc_stream_state *state);
|
||||
|
||||
|
||||
@@ -87,8 +87,10 @@ sc_obfuscate_pubkey(salt, my_pubkey, received_obfuscated, decrypted_pubkey);
|
||||
Для больших объёмов данных, где не нужна посылочная аутентификация:
|
||||
|
||||
```c
|
||||
struct sc_stream_state stream;
|
||||
sc_stream_init(&ctx, &stream, stream_id); // stream_id — произвольный uint32_t
|
||||
struct sc_stream_state stream = {0};
|
||||
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
|
||||
if (random_bytes(salt, sizeof(salt)) != 0) return -1;
|
||||
if (sc_stream_init(ctx.session_key, &stream, stream_id, salt) != SC_OK) return -1;
|
||||
|
||||
sc_stream_xor(&stream, data_chunk1, len1); // XOR in-place (шифрует/дешифрует)
|
||||
sc_stream_xor(&stream, data_chunk2, len2);
|
||||
@@ -96,7 +98,9 @@ sc_stream_xor(&stream, data_chunk2, len2);
|
||||
sc_stream_cleanup(&stream);
|
||||
```
|
||||
|
||||
Nonce (12 байт) для CTR выводится из `SHA256(session_key || stream_id_LE || "uTun3-stream")`.
|
||||
Nonce — первые 12 байт `SHA256(session_key || stream_id_LE || salt(8) || "uTun3-stream")`; последние 4 байта IV нулевые.
|
||||
Получатель использует соль отправителя. Ключ должен быть заранее выведен через ECDH; state нулевой или после cleanup.
|
||||
Повторная инициализация активного state отклоняется. STCP передаёт соль в существующем открытом блоке handshake.
|
||||
|
||||
### 2.4. Ed25519 подписи
|
||||
|
||||
@@ -233,7 +237,7 @@ sc_sha_transcode(key, key_len, data, data_len);
|
||||
|
||||
| Функция | Описание |
|
||||
|---|---|
|
||||
| `sc_stream_init(ctx, state, stream_id)` | Инициализирует CTR-контекст. Nonce из `SHA256(session_key \|\| stream_id_LE \|\| "uTun3-stream")`. |
|
||||
| `sc_stream_init(session_key, state, stream_id, salt)` | Инициализирует CTR-контекст с выведенным ключом и солью отправителя. Nonce из `SHA256(session_key \|\| stream_id_LE \|\| salt \|\| "uTun3-stream")`. |
|
||||
| `sc_stream_xor(state, data, len)` | XOR данных с keystream in-place. Нулевая длина — no-op. |
|
||||
| `sc_stream_cleanup(state)` | Освобождает `EVP_CIPHER_CTX`. |
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ extern "C" {
|
||||
|
||||
|
||||
#include "secure_channel.h"
|
||||
#include "etcp_padding.h"
|
||||
#include "reality.h"
|
||||
#include "crc32.h"
|
||||
#include "../lib/u_async.h"
|
||||
@@ -46,7 +47,7 @@ typedef void (*stcp_ping_cb)(int success, uint16_t rtt, void *arg);
|
||||
// [44..45] keepalive_interval
|
||||
// [46] flags
|
||||
#define STCP_HS_PLAIN_SIZE 47 // без CRC32
|
||||
#define STCP_HS_PADDING_MAX 512 // верхняя граница padding_size в рукопожатии (легитимно 8)
|
||||
#define STCP_HS_PADDING_MAX ETCP_PADDING_MAX
|
||||
|
||||
#define STCP_HANDSHAKE_FLAG_PING 0x01
|
||||
|
||||
@@ -76,7 +77,7 @@ struct stcp_conn {
|
||||
struct UASYNC *ua;
|
||||
void *socket_id;
|
||||
|
||||
struct UTUN_INSTANCE *inst; // для lookup ETCP_CONN при хендшейке (сервер)
|
||||
struct UTUN_INSTANCE *inst; // сервер: диапазон padding и поиск ETCP_CONN по ключу пира
|
||||
struct ETCP_CONN *etcp_conn; // клиент: родительский conn для live-чтения gop/reset_id при handshake
|
||||
|
||||
enum stcp_state state;
|
||||
|
||||
@@ -32,6 +32,7 @@ struct stcp_client {
|
||||
uint8_t ping_done;
|
||||
uint8_t peer_pubkey[SC_PUBKEY_SIZE];
|
||||
uint8_t my_ed25519_pubkey[SC_PUBKEY_SIZE];
|
||||
uint16_t handshake_padding; // снимок выбранной длины для одноразового TCP-пинга без владения instance
|
||||
// reality-камуфляж (клиент): перед STCP-хендшейком шлём ClientHello и читаем ServerHello
|
||||
uint8_t reality_enabled;
|
||||
struct reality_client_config reality_cfg;
|
||||
@@ -54,16 +55,20 @@ static int client_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey
|
||||
if (sc_set_peer_public_key(&sc, peer_pubkey, SC_PEER_PUBKEY_BIN) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client ECDH failed"); return -1; }
|
||||
memcpy(c->session_key, sc.session_key, SC_SESSION_KEY_SIZE);
|
||||
memcpy(c->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE); c->peer_pubkey_set = 1;
|
||||
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_client session_key", c->session_key, 16);
|
||||
if (sc_stream_init(&sc, &c->stream_send, STCP_STREAM_CLIENT_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client stream_send init failed"); return -1; }
|
||||
if (sc_stream_init(&sc, &c->stream_recv, STCP_STREAM_SERVER_SEND) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client stream_recv init failed"); return -1; }
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: ECDH ready, streams await handshake salts");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pubkey, const uint8_t *my_ed25519) {
|
||||
uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE];
|
||||
if (random_bytes(salt, SC_PUBKEY_ENC_SALT_SIZE) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client random_bytes failed"); stcp_conn_do_close(c, 1); return; }
|
||||
uint16_t padding = 8;
|
||||
if (sc_stream_init(c->session_key, &c->stream_send, STCP_STREAM_CLIENT_SEND, salt) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: send stream initialization failed");
|
||||
stcp_conn_do_close(c, 2); return;
|
||||
}
|
||||
struct stcp_client* cli = (struct stcp_client*)c;
|
||||
int padding = c->etcp_conn ? etcp_padding_size(&c->etcp_conn->padding, STCP_HS_PADDING_MAX) : cli->handshake_padding;
|
||||
if (padding < 0) { stcp_conn_do_close(c, 1); return; }
|
||||
size_t total = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + padding;
|
||||
uint8_t *hs = u_malloc(total);
|
||||
if (!hs) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client hs malloc failed"); stcp_conn_do_close(c, 1); return; }
|
||||
@@ -84,7 +89,9 @@ static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pub
|
||||
memcpy(enc_dst, plain, STCP_HS_PLAIN_SIZE);
|
||||
enc_dst[47] = (uint8_t)(crc >> 0); enc_dst[48] = (uint8_t)(crc >> 8); enc_dst[49] = (uint8_t)(crc >> 16); enc_dst[50] = (uint8_t)(crc >> 24);
|
||||
if (sc_stream_xor(&c->stream_send, enc_dst, STCP_HS_ENC_CLIENT) != SC_OK) { u_free(hs); stcp_conn_do_close(c, 2); return; }
|
||||
for (int i = 0; i < padding; i++) hs[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT + i] = (uint8_t)(salt[0] ^ i);
|
||||
uint8_t* pad = hs + SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT;
|
||||
memset(pad, 0, padding);
|
||||
if (sc_stream_xor(&c->stream_send, pad, padding) != SC_OK) { u_free(hs); stcp_conn_do_close(c, 2); return; }
|
||||
|
||||
c->hs_send_time = get_time_tb();
|
||||
c->state = STCP_STATE_HS_CLIENT_SENT;
|
||||
@@ -105,6 +112,10 @@ static void client_hs_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: server pubkey mismatch — possible MITM");
|
||||
stcp_conn_do_close(c, 4); return;
|
||||
}
|
||||
if (sc_stream_init(c->session_key, &c->stream_recv, STCP_STREAM_SERVER_SEND, salt) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: receive stream initialization failed");
|
||||
stcp_conn_do_close(c, 3); return;
|
||||
}
|
||||
|
||||
uint8_t enc_hs[STCP_HS_ENC_SERVER];
|
||||
memcpy(enc_hs, data + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_SERVER);
|
||||
@@ -130,11 +141,12 @@ static void client_hs_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
|
||||
c->peer_flags = enc_hs[46];
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: server response OK gop=%d rid=%016llx padding=%u dev=%d ka=%u flags=%02x",
|
||||
c->peer_got_initial_pkt, (unsigned long long)c->peer_reset_id, padding_size, c->peer_device_type, c->peer_keepalive_interval, c->peer_flags);
|
||||
stcp_recv_set(c, padding_size, 0, client_hs_padding_cb);
|
||||
if (padding_size) stcp_recv_set(c, padding_size, 0, client_hs_padding_cb);
|
||||
else client_hs_padding_cb(c, NULL, 0);
|
||||
}
|
||||
|
||||
static void client_hs_padding_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
|
||||
(void)data; (void)len;
|
||||
if (sc_stream_xor(&c->stream_recv, data, len) != SC_OK) { stcp_conn_do_close(c, 3); return; }
|
||||
if (c->hs_timer) { uasync_cancel_timeout(c->ua, c->hs_timer); c->hs_timer = NULL; }
|
||||
if (c->hs_flags & STCP_HANDSHAKE_FLAG_PING) {
|
||||
struct stcp_client *cli = (struct stcp_client *)c;
|
||||
@@ -261,7 +273,8 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
|
||||
const struct sockaddr_storage *local_addr,
|
||||
int timeout_ms,
|
||||
const struct reality_client_config *reality,
|
||||
const struct socks_cfg *socks) {
|
||||
const struct socks_cfg *socks,
|
||||
struct UTUN_INSTANCE *inst) {
|
||||
if (!ua || !addr || !keys || !peer_pubkey || (!ready_cb && !ping_cb)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "invalid args"); return NULL; }
|
||||
struct stcp_client *cli = u_calloc(1, sizeof(struct stcp_client));
|
||||
if (!cli) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "calloc failed"); return NULL; }
|
||||
@@ -270,6 +283,11 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
|
||||
memcpy(cli->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE);
|
||||
if (my_ed25519_pubkey) memcpy(cli->my_ed25519_pubkey, my_ed25519_pubkey, SC_PUBKEY_SIZE);
|
||||
if (reality) { cli->reality_enabled = 1; cli->reality_cfg = *reality; }
|
||||
if (!etcp_conn) {
|
||||
int padding = etcp_padding_size(etcp_padding_for_peer(inst, peer_pubkey), STCP_HS_PADDING_MAX);
|
||||
if (padding < 0) { u_free(cli); return NULL; }
|
||||
cli->handshake_padding = padding;
|
||||
}
|
||||
struct stcp_conn *c = &cli->conn;
|
||||
c->ua = ua; c->state = STCP_STATE_INIT; c->is_server = 0; c->my_keys = *keys;
|
||||
c->on_ready = ready_cb; c->ready_arg = arg;
|
||||
@@ -359,11 +377,12 @@ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t
|
||||
const uint8_t *my_ed25519_pubkey,
|
||||
uint8_t device_type, uint16_t keepalive_interval,
|
||||
int timeout_ms, stcp_ping_cb cb, void *arg,
|
||||
const struct socks_cfg *socks) {
|
||||
const struct socks_cfg *socks,
|
||||
struct UTUN_INSTANCE *inst) {
|
||||
struct stcp_client *cli = stcp_client_connect(ua, addr, port, keys, peer_pubkey, my_ed25519_pubkey,
|
||||
0, NULL, device_type, keepalive_interval,
|
||||
STCP_HANDSHAKE_FLAG_PING,
|
||||
NULL, NULL, cb, arg, ping_close_cb, NULL, NULL, timeout_ms, NULL, socks);
|
||||
NULL, NULL, cb, arg, ping_close_cb, NULL, NULL, timeout_ms, NULL, socks, inst);
|
||||
if (cli) cli->conn.free_on_close = cli;
|
||||
return cli;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ extern "C" {
|
||||
typedef void (*stcp_ready_cb)(struct stcp_conn *conn, void *arg);
|
||||
typedef void (*stcp_close_cb)(struct stcp_conn *conn, int err, void *arg);
|
||||
|
||||
/* inst используется только при запуске для выбора padding без etcp_conn; клиент не хранит ссылку на inst. */
|
||||
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
|
||||
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
|
||||
const uint8_t *my_ed25519_pubkey,
|
||||
@@ -29,7 +30,8 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
|
||||
const struct sockaddr_storage *local_addr,
|
||||
int timeout_ms,
|
||||
const struct reality_client_config *reality,
|
||||
const struct socks_cfg *socks);
|
||||
const struct socks_cfg *socks,
|
||||
struct UTUN_INSTANCE *inst);
|
||||
/* TCP-ping: подключается и шлёт хендшейк с флагом STCP_HANDSHAKE_FLAG_PING.
|
||||
* Ответчик отвечает обычным хендшейк-ответом и мягко закрывает соединение.
|
||||
* По завершении хендшейка вызывается cb(success=1, rtt, arg); при ошибке/таймауте — cb(0, 0, arg). */
|
||||
@@ -38,7 +40,8 @@ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t
|
||||
const uint8_t *my_ed25519_pubkey,
|
||||
uint8_t device_type, uint16_t keepalive_interval,
|
||||
int timeout_ms, stcp_ping_cb cb, void *arg,
|
||||
const struct socks_cfg *socks);
|
||||
const struct socks_cfg *socks,
|
||||
struct UTUN_INSTANCE *inst);
|
||||
void stcp_client_destroy(struct stcp_client *cli);
|
||||
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli);
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ stcp_close_cb on_close = (stcp_close_cb)my_close_handler;
|
||||
|
||||
struct stcp_client *cli = stcp_client_connect(
|
||||
ua, addr, port, &my_keys, peer_pubkey_bin,
|
||||
on_ready, ready_ctx, on_close, close_ctx
|
||||
my_ed25519_pubkey, 0, NULL, instance->client_type, instance->keepalive_interval, 0,
|
||||
on_ready, ready_ctx, NULL, NULL, on_close, close_ctx, NULL, 0, NULL, NULL, instance
|
||||
);
|
||||
// После handshake — on_ready.
|
||||
// Соединение доступно через stcp_client_get_conn(cli).
|
||||
@@ -22,9 +23,9 @@ struct stcp_client *cli = stcp_client_connect(
|
||||
```
|
||||
|
||||
### Ключевые нюансы
|
||||
- **Handshake**: клиент посылает salt(8) + обфусцированный pubkey(XOR с SHA256(salt||server_pubkey), 32) + padding_size(2) + CRC32(4), зашифрованные stream_xor, плюс padding (8+ байт). Сервер присылает аналогичный ответ со статусом. После проверки CRC — переход в `STCP_STATE_DATA`.
|
||||
- **Стрим-шифр**: используется `sc_stream_xor` с начальным nonce = `SHA256(session_key)`. Два стрима: `STCP_STREAM_CLIENT_SEND` (клиент→сервер) и `STCP_STREAM_SERVER_SEND` (сервер→клиент) — инициализируются в `client_derive_session`.
|
||||
- **Фрейминг в DATA**: каждое сообщение — `len(2 LE)` + данные + `CRC32(4)`. CRC считается от данных, затем данные+CRC шифруются stream_xor.
|
||||
- **Handshake**: salt(8) + обфусцированный pubkey(32) открыты; следующие 51 байт содержат Ed25519 pubkey, состояние/эпоху ETCP, padding_size, тип устройства, keepalive, flags и CRC32. Они и следующий нулевой padding шифруются одним CTR-потоком. Длина padding выбирается независимо из диапазона ETCP_CONN, иначе instance; диапазон обновляется своим таймером каждые 50–300 секунд. Приём продвигает CTR через padding, при padding_size=0 сразу завершает handshake.
|
||||
- **Стрим-шифр**: nonce — первые 12 байт `SHA256(session_key || stream_id_LE || salt(8) || "uTun3-stream")`, последние 4 байта CTR IV нулевые. `STCP_STREAM_CLIENT_SEND` использует соль клиента, `STCP_STREAM_SERVER_SEND` — соль сервера. Отправка запускается с собственной солью, приём — после чтения ответа; CTR продолжается через padding и DATA.
|
||||
- **Фрейминг в DATA**: `len(2 LE)` + данные + `CRC32(4)`. CRC считается от данных, весь кадр вместе с длиной шифруется stream_xor.
|
||||
- **Очереди**: `tx_queue` через callback `client_tx_queue_cb` шифрует каждое сообщение и отправляет. `rx_queue` получает расшифрованные сообщения для вышележащего слоя.
|
||||
- **Асинхронность**: все операции через `UASYNC` — сокет в неблокирующем режиме. Поддержка частичной отправки (send_offset) и EAGAIN/WOULDBLOCK.
|
||||
- **Обработка ошибок**: CRC mismatch, ошибки шифрования, сетевые ошибки — все ведут к `client_do_close` с кодом ошибки.
|
||||
@@ -48,7 +49,7 @@ struct stcp_client *cli = stcp_client_connect(
|
||||
- **`stcp_close_cb`**: вызывается при закрытии или ошибке соединения, получает код ошибки.
|
||||
|
||||
### Внутренние функции
|
||||
- **`client_derive_session()`** — вычисляет session_key через ECDH, инициализирует stream_send/stream_recv.
|
||||
- **`client_derive_session()`** — вычисляет session_key через ECDH. Потоки инициализируются после получения соответствующих солей.
|
||||
- **`client_send_handshake()`** — формирует и отправляет handshake-пакет (salt + обфусцированный pubkey + зашифрованный padding+CRC + padding).
|
||||
- **`process_server_response()`** — деобфусцирует pubkey сервера, расшифровывает и проверяет CRC ответа сервера.
|
||||
- **`stcp_conn_process_recv()`** — конечный автомат разбора входящих данных: в HS-состоянии ждёт полный ответ, в DATA — разбирает фреймы (len + data + CRC).
|
||||
|
||||
@@ -254,7 +254,7 @@ struct stcp_link *stcp_link_connect(struct ETCP_LINK *etcp_link,
|
||||
etcp->got_initial_pkt, etcp,
|
||||
inst->client_type, inst->keepalive_interval,
|
||||
0,
|
||||
client_ready_cb, link, NULL, NULL, NULL, NULL, local_addr, 0, reality, socks);
|
||||
client_ready_cb, link, NULL, NULL, NULL, NULL, local_addr, 0, reality, socks, inst);
|
||||
if (!link->cli) { u_free(link); return NULL; }
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_link: connecting to %s:%u pubkey=%016llx bind=%s",
|
||||
|
||||
@@ -60,7 +60,7 @@ static void server_data_cb(struct stcp_conn *c, uint8_t *plain_data, size_t data
|
||||
static void reality_ch_hdr_cb(struct stcp_conn *c, uint8_t *data, size_t len);
|
||||
static void reality_ch_body_cb(struct stcp_conn *c, uint8_t *data, size_t len);
|
||||
|
||||
static int derive_session_and_streams(struct stcp_conn *c, const uint8_t *peer_pubkey) {
|
||||
static int server_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey, const uint8_t *salt) {
|
||||
struct secure_channel sc;
|
||||
sc_init_ctx(&sc, &c->my_keys);
|
||||
if (sc_set_peer_public_key(&sc, peer_pubkey, SC_PEER_PUBKEY_BIN) != SC_OK) {
|
||||
@@ -68,17 +68,13 @@ static int derive_session_and_streams(struct stcp_conn *c, const uint8_t *peer_p
|
||||
return -1;
|
||||
}
|
||||
memcpy(c->session_key, sc.session_key, SC_SESSION_KEY_SIZE);
|
||||
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_server session_key", c->session_key, 16);
|
||||
if (sc_stream_init(&sc, &c->stream_send, STCP_STREAM_SERVER_SEND) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stream_send init failed");
|
||||
return -1;
|
||||
}
|
||||
if (sc_stream_init(&sc, &c->stream_recv, STCP_STREAM_CLIENT_SEND) != SC_OK) {
|
||||
if (sc_stream_init(c->session_key, &c->stream_recv, STCP_STREAM_CLIENT_SEND, salt) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stream_recv init failed");
|
||||
return -1;
|
||||
}
|
||||
memcpy(c->peer_pubkey, peer_pubkey, SC_PUBKEY_SIZE);
|
||||
c->peer_pubkey_set = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_server: ECDH and receive stream ready, send stream awaits response salt");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -88,7 +84,7 @@ static void server_hs_phase1_cb(struct stcp_conn *c, uint8_t *data, size_t len)
|
||||
const uint8_t *enc_pubkey = salt + SC_PUBKEY_ENC_SALT_SIZE;
|
||||
uint8_t client_pubkey[SC_PUBKEY_SIZE];
|
||||
sc_obfuscate_pubkey(salt, c->my_keys.public_key, enc_pubkey, client_pubkey);
|
||||
if (derive_session_and_streams(c, client_pubkey)) { stcp_conn_do_close(c, 1); return; }
|
||||
if (server_derive_session(c, client_pubkey, salt)) { stcp_conn_do_close(c, 1); return; }
|
||||
uint8_t enc_hs[STCP_HS_ENC_CLIENT];
|
||||
memcpy(enc_hs, data + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_CLIENT);
|
||||
size_t hs_data_len;
|
||||
@@ -112,11 +108,12 @@ static void server_hs_phase1_cb(struct stcp_conn *c, uint8_t *data, size_t len)
|
||||
c->peer_flags = enc_hs[46];
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_server: client handshake OK gop=%d rid=%016llx padding=%u dev=%d ka=%u flags=%02x",
|
||||
c->peer_got_initial_pkt, (unsigned long long)c->peer_reset_id, padding_size, c->peer_device_type, c->peer_keepalive_interval, c->peer_flags);
|
||||
stcp_recv_set(c, padding_size, 0, server_hs_phase2_cb);
|
||||
if (padding_size) stcp_recv_set(c, padding_size, 0, server_hs_phase2_cb);
|
||||
else server_hs_phase2_cb(c, NULL, 0);
|
||||
}
|
||||
|
||||
static void server_hs_phase2_cb(struct stcp_conn *c, uint8_t *data, size_t len) {
|
||||
(void)data; (void)len;
|
||||
if (sc_stream_xor(&c->stream_recv, data, len) != SC_OK) { stcp_conn_do_close(c, 2); return; }
|
||||
|
||||
if (c->hs_timer) { uasync_cancel_timeout(c->ua, c->hs_timer); c->hs_timer = NULL; }
|
||||
|
||||
@@ -137,7 +134,13 @@ static void server_hs_phase2_cb(struct stcp_conn *c, uint8_t *data, size_t len)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "random_bytes failed");
|
||||
stcp_conn_do_close(c, 3); return;
|
||||
}
|
||||
uint16_t padding = 8;
|
||||
if (sc_stream_init(c->session_key, &c->stream_send, STCP_STREAM_SERVER_SEND, salt2) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server: send stream initialization failed");
|
||||
stcp_conn_do_close(c, 3); return;
|
||||
}
|
||||
const struct ETCP_PADDING* profile = etcp_padding_for_peer(c->inst, c->peer_pubkey);
|
||||
int padding = etcp_padding_size(profile, STCP_HS_PADDING_MAX);
|
||||
if (padding < 0) { stcp_conn_do_close(c, 3); return; }
|
||||
size_t total_resp = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + padding;
|
||||
uint8_t *resp = u_malloc(total_resp);
|
||||
if (!resp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "malloc failed"); stcp_conn_do_close(c, 3); return; }
|
||||
@@ -161,7 +164,12 @@ static void server_hs_phase2_cb(struct stcp_conn *c, uint8_t *data, size_t len)
|
||||
u_free(resp); stcp_conn_do_close(c, 3); return;
|
||||
}
|
||||
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_server hs_resp AFTER xor", enc_dst, STCP_HS_ENC_SERVER);
|
||||
for (int i = 0; i < padding; i++) resp[SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + i] = (uint8_t)(salt2[0] ^ i);
|
||||
uint8_t* pad = resp + SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER;
|
||||
memset(pad, 0, padding);
|
||||
if (sc_stream_xor(&c->stream_send, pad, padding) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_server: padding encryption failed size=%d", padding);
|
||||
u_free(resp); stcp_conn_do_close(c, 3); return;
|
||||
}
|
||||
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_ETCP, "stcp_server FULL RESP", resp, total_resp);
|
||||
|
||||
int r = stcp_try_send(c, resp, total_resp);
|
||||
|
||||
@@ -22,7 +22,7 @@ stcp_server_destroy(srv);
|
||||
|
||||
Ключевые нюансы:
|
||||
- Сервер принимает клиентский pubkey+salt (обфусцированный), извлекает pubkey через `sc_obfuscate_pubkey`, выполняет ECDH.
|
||||
- Отправляет ответ: статус OK + padding + CRC32, всё зашифровано AES-CTR.
|
||||
- Отправляет ответ: открытый блок salt/pubkey, зашифрованное тело handshake с CRC32 и зашифрованный padding.
|
||||
- После рукопожатая трафик шифруется потоковым AES-CTR (XOR), для каждого направления — свой stream.
|
||||
- Рукопожатие обрабатывается в два этапа: `process_client_handshake` (извлечение ключа, дешифровка) → `finish_client_handshake` (отправка ответа, переход в DATA).
|
||||
|
||||
@@ -36,9 +36,9 @@ stcp_server_destroy(srv);
|
||||
- `stcp_server_destroy(srv)` — закрыть слушающий сокет, освободить память.
|
||||
|
||||
### Протокол рукопожатия (серверная сторона)
|
||||
1. Сервер читает `SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT` байт (pubkey+salt + зашифрованные padding_size + CRC32).
|
||||
2. Деобфусцирует клиентский pubkey (`sc_obfuscate_pubkey`, salt XOR с SHA256(salt||server_pubkey)).
|
||||
3. Выполняет ECDH (`sc_init_ctx` + `sc_set_peer_public_key`) → получает `session_key`, инициализирует `stream_send`/`stream_recv`.
|
||||
4. Расшифровывает padding_size, проверяет CRC32, ожидает padding-байты.
|
||||
5. Формирует ответ: salt2(8) + обфусцированный серверный pubkey(32) + status=OK(1) + padding_size(2) + CRC32(4) + padding(8). Всё кроме salt2 зашифровано `sc_stream_xor`.
|
||||
1. Сервер читает `SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT` байт (открытый salt/pubkey и 51 байт зашифрованного тела).
|
||||
2. Деобфусцирует клиентский pubkey через XOR с `SHA256(salt || server_pubkey)`.
|
||||
3. Выполняет ECDH (`sc_init_ctx` + `sc_set_peer_public_key`) → получает `session_key`, запускает `stream_recv` с клиентской солью. Для ответа генерирует собственную соль и запускает `stream_send` с ней; соль входит в вывод CTR IV вместе с ключом и направлением.
|
||||
4. Расшифровывает тело, проверяет CRC32 и границы padding_size, затем принимает и расшифровывает padding.
|
||||
5. Формирует ответ: открытые salt2(8) + обфусцированный pubkey(32), затем зашифрованные 51 байт handshake и нулевой padding случайной длины. Диапазон берётся у существующего ETCP_CONN, иначе у instance. Приём padding продвигает CTR; при длине 0 handshake завершается сразу.
|
||||
6. Переходит в `STCP_STATE_DATA`, вызывает `on_ready` (== `connect_cb`).
|
||||
@@ -152,6 +152,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
|
||||
memcpy(instance->my_ed25519_privkey, ed_priv_hash, SC_PRIVKEY_SIZE);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "Ed25519 pubkey derived successfully");
|
||||
if (etcp_padding_start(&instance->padding, ua, instance->name) < 0) return -1;
|
||||
|
||||
// Initialize networks queue (indexed by 56-bit network id)
|
||||
instance->networks = queue_new(ua, 16, offsetof(struct NETWORK_ENTRY, id), 7, "networks");
|
||||
@@ -328,6 +329,7 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config
|
||||
// Initialize using common function
|
||||
if (instance_init_common(instance, ua, config) != 0) {
|
||||
// Cleanup on error
|
||||
etcp_padding_stop(&instance->padding);
|
||||
if (instance->config) free_config(instance->config);
|
||||
u_free(instance);
|
||||
return NULL;
|
||||
@@ -368,6 +370,7 @@ struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct
|
||||
// Initialize using common function (config ownership transferred to instance)
|
||||
if (instance_init_common(instance, ua, config) != 0) {
|
||||
// Cleanup on error - caller still owns config since we failed
|
||||
etcp_padding_stop(&instance->padding);
|
||||
u_free(instance);
|
||||
return NULL;
|
||||
}
|
||||
@@ -401,6 +404,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
|
||||
if (instance->ua) uasync_print_resources(instance->ua, "INSTANCE_DESTROY_BEFORE");
|
||||
|
||||
instance->running = 0;
|
||||
etcp_padding_stop(&instance->padding);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] A done — diagnose complete");
|
||||
|
||||
/* Phase B: NTP */
|
||||
|
||||
@@ -17,6 +17,7 @@ extern "C" {
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/sqlite3.h"
|
||||
#include "secure_channel.h"
|
||||
#include "etcp_padding.h"
|
||||
#include "etcp_api.h"
|
||||
#include "config_parser.h"
|
||||
#include "stcp_link.h"
|
||||
@@ -179,6 +180,7 @@ struct UTUN_INSTANCE {
|
||||
|
||||
// Pending one-shot pings (for callback on PONG or timeout)
|
||||
struct PING_CONTEXT* pending_pings;
|
||||
struct ETCP_PADDING padding; // диапазон и таймер PING/PONG без ETCP_CONN
|
||||
|
||||
// Routing statistics
|
||||
uint64_t routed_packets;
|
||||
|
||||
Reference in new issue
Block a user