|
|
|
|
@ -28,9 +28,9 @@ struct stcp_client {
|
|
|
|
|
|
|
|
|
|
static void client_connect_write_cb(socket_t sock, void *arg); |
|
|
|
|
static void client_conn_read_cb(socket_t sock, void *arg); |
|
|
|
|
static void client_tx_queue_cb(struct ll_queue *q, void *arg); |
|
|
|
|
static void stcp_conn_process_recv(struct stcp_conn *c); |
|
|
|
|
static void client_do_close(struct stcp_conn *c, int err); |
|
|
|
|
static void client_hs_cb(struct stcp_conn *c, uint8_t *data, size_t len); |
|
|
|
|
static void client_hs_padding_cb(struct stcp_conn *c, uint8_t *data, size_t len); |
|
|
|
|
static void client_data_cb(struct stcp_conn *c, uint8_t *plain_data, size_t data_len); |
|
|
|
|
|
|
|
|
|
static int client_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey) { |
|
|
|
|
struct secure_channel sc; |
|
|
|
|
@ -46,11 +46,11 @@ static int client_derive_session(struct stcp_conn *c, const uint8_t *peer_pubkey
|
|
|
|
|
|
|
|
|
|
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"); client_do_close(c, 1); return; } |
|
|
|
|
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; |
|
|
|
|
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"); client_do_close(c, 1); return; } |
|
|
|
|
if (!hs) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client hs malloc failed"); stcp_conn_do_close(c, 1); return; } |
|
|
|
|
memcpy(hs, salt, SC_PUBKEY_ENC_SALT_SIZE); |
|
|
|
|
sc_obfuscate_pubkey(salt, server_pubkey, c->my_keys.public_key, hs + SC_PUBKEY_ENC_SALT_SIZE); |
|
|
|
|
|
|
|
|
|
@ -59,7 +59,7 @@ static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pub
|
|
|
|
|
uint8_t *enc_dst = hs + SC_PUBKEY_ENC_SIZE; |
|
|
|
|
memcpy(enc_dst, plain, 34); |
|
|
|
|
enc_dst[34] = (uint8_t)(crc >> 0); enc_dst[35] = (uint8_t)(crc >> 8); enc_dst[36] = (uint8_t)(crc >> 16); enc_dst[37] = (uint8_t)(crc >> 24); |
|
|
|
|
if (sc_stream_xor(&c->stream_send, enc_dst, STCP_HS_ENC_CLIENT) != SC_OK) { u_free(hs); client_do_close(c, 2); return; } |
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
c->state = STCP_STATE_HS_CLIENT_SENT; |
|
|
|
|
@ -68,107 +68,53 @@ static void client_send_handshake(struct stcp_conn *c, const uint8_t *server_pub
|
|
|
|
|
uasync_set_socket_write(c->ua, c->socket_id, 1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void process_server_response(struct stcp_conn *c) { |
|
|
|
|
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_ETCP, "stcp_client process_srv_resp recv_buf", c->recv_buf, c->recv_buf_len); |
|
|
|
|
if (!c->hs_key_processed) { |
|
|
|
|
const uint8_t *salt = c->recv_buf; |
|
|
|
|
static void client_hs_cb(struct stcp_conn *c, uint8_t *data, size_t len) { |
|
|
|
|
(void)len; |
|
|
|
|
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_ETCP, "stcp_client process_srv_resp recv_buf", data, len); |
|
|
|
|
|
|
|
|
|
const uint8_t *salt = data; |
|
|
|
|
const uint8_t *enc_pubkey = salt + SC_PUBKEY_ENC_SALT_SIZE; |
|
|
|
|
uint8_t server_pubkey[SC_PUBKEY_SIZE]; |
|
|
|
|
sc_obfuscate_pubkey(salt, c->my_keys.public_key, enc_pubkey, server_pubkey); |
|
|
|
|
if (c->peer_pubkey_set && memcmp(server_pubkey, c->peer_pubkey, SC_PUBKEY_SIZE) != 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: server pubkey mismatch — possible MITM"); |
|
|
|
|
client_do_close(c, 4); return; |
|
|
|
|
} |
|
|
|
|
c->hs_key_processed = 1; |
|
|
|
|
stcp_conn_do_close(c, 4); return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint8_t enc_hs[STCP_HS_ENC_SERVER]; |
|
|
|
|
memcpy(enc_hs, c->recv_buf + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_SERVER); |
|
|
|
|
memcpy(enc_hs, data + SC_PUBKEY_ENC_SIZE, STCP_HS_ENC_SERVER); |
|
|
|
|
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_client enc_hs BEFORE xor", enc_hs, STCP_HS_ENC_SERVER); |
|
|
|
|
size_t hs_data_len; |
|
|
|
|
if (stcp_frame_decrypt(enc_hs, STCP_HS_ENC_SERVER, &c->stream_recv, &hs_data_len)) { client_do_close(c, 3); return; } |
|
|
|
|
if (stcp_frame_decrypt(enc_hs, STCP_HS_ENC_SERVER, &c->stream_recv, &hs_data_len)) { stcp_conn_do_close(c, 3); return; } |
|
|
|
|
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CRYPTO, "stcp_client enc_hs AFTER xor", enc_hs, STCP_HS_ENC_SERVER); |
|
|
|
|
memcpy(c->peer_ed25519_pubkey, enc_hs, SC_PUBKEY_SIZE); c->peer_ed25519_set = 1; |
|
|
|
|
uint8_t status = enc_hs[32]; |
|
|
|
|
uint16_t padding_size = (uint16_t)enc_hs[33] | ((uint16_t)enc_hs[34] << 8); |
|
|
|
|
c->hs_expected_len = SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER + padding_size; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: server response OK status=%d padding=%u", status, padding_size); |
|
|
|
|
if (status != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "server handshake status=%d", status); client_do_close(c, 4); return; } |
|
|
|
|
if (status != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "server handshake status=%d", status); stcp_conn_do_close(c, 4); return; } |
|
|
|
|
stcp_recv_set(c, padding_size, 0, client_hs_padding_cb); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void finish_server_response(struct stcp_conn *c) { |
|
|
|
|
memmove(c->recv_buf, c->recv_buf + c->hs_expected_len, c->recv_buf_len - c->hs_expected_len); |
|
|
|
|
c->recv_buf_len -= c->hs_expected_len; |
|
|
|
|
c->hs_expected_len = 0; |
|
|
|
|
c->hs_key_processed = 0; |
|
|
|
|
static void client_hs_padding_cb(struct stcp_conn *c, uint8_t *data, size_t len) { |
|
|
|
|
(void)data; (void)len; |
|
|
|
|
if (c->hs_timer) { uasync_cancel_timeout(c->ua, c->hs_timer); c->hs_timer = NULL; } |
|
|
|
|
c->state = STCP_STATE_DATA; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: handshake OK, entering DATA state"); |
|
|
|
|
if (c->on_ready) { void (*cb)(struct stcp_conn*, void*) = c->on_ready; c->on_ready = NULL; cb(c, c->ready_arg); } |
|
|
|
|
stcp_recv_set(c, 0, 1, client_data_cb); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void stcp_conn_process_recv(struct stcp_conn *c) { |
|
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "stcp_client: process_recv entry state=%d sock=%d recv_buf=%p len=%zu cap=%zu", |
|
|
|
|
c->state, (int)c->sock, (void*)c->recv_buf, c->recv_buf_len, c->recv_buf_cap); |
|
|
|
|
if (!c->recv_buf && c->recv_buf_len > 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: process_recv NULL recv_buf with len=%zu — closing", c->recv_buf_len); |
|
|
|
|
client_do_close(c, EFAULT); return; |
|
|
|
|
} |
|
|
|
|
while (c->recv_buf_len > 0) { |
|
|
|
|
switch (c->state) { |
|
|
|
|
case STCP_STATE_HS_CLIENT_SENT: |
|
|
|
|
if (!c->hs_key_processed) { |
|
|
|
|
if (c->recv_buf_len < SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER) return; |
|
|
|
|
process_server_response(c); |
|
|
|
|
if (c->state != STCP_STATE_HS_CLIENT_SENT) return; |
|
|
|
|
} |
|
|
|
|
if (c->hs_key_processed && c->recv_buf_len >= c->hs_expected_len) { finish_server_response(c); return; } |
|
|
|
|
return; |
|
|
|
|
case STCP_STATE_DATA: { |
|
|
|
|
if (c->recv_buf_len < 2) return; |
|
|
|
|
uint16_t msg_size = (uint16_t)c->recv_buf[0] | ((uint16_t)c->recv_buf[1] << 8); |
|
|
|
|
if (msg_size > STCP_MAX_MSG_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "client msg too large: %u", msg_size); client_do_close(c, 5); return; } |
|
|
|
|
size_t total = 2 + msg_size + SC_CRC32_SIZE; |
|
|
|
|
if (c->recv_buf_len < total) return; |
|
|
|
|
uint8_t *enc_data = c->recv_buf + 2; |
|
|
|
|
size_t data_len; |
|
|
|
|
if (stcp_frame_decrypt(enc_data, msg_size + SC_CRC32_SIZE, &c->stream_recv, &data_len)) { client_do_close(c, 6); return; } |
|
|
|
|
if (c->rx_queue) { |
|
|
|
|
struct ll_entry *e = queue_entry_new(0); |
|
|
|
|
if (e) { e->dgram = u_malloc(data_len); if (e->dgram) { if (data_len) memcpy(e->dgram, enc_data, data_len); e->len = (uint16_t)data_len; queue_data_put(c->rx_queue, e); } else { queue_entry_free(e); } } |
|
|
|
|
if (c->state != STCP_STATE_DATA) return; |
|
|
|
|
} |
|
|
|
|
if ((uintptr_t)c->recv_buf < 4096) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client: suspicious recv_buf=%p (addr < 4K) — closing", (void*)c->recv_buf); |
|
|
|
|
client_do_close(c, EFAULT); return; |
|
|
|
|
} |
|
|
|
|
memmove(c->recv_buf, c->recv_buf + total, c->recv_buf_len - total); |
|
|
|
|
c->recv_buf_len -= total; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
default: return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
static void client_data_cb(struct stcp_conn *c, uint8_t *plain_data, size_t data_len) { |
|
|
|
|
stcp_rx_push(c, plain_data, data_len); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void client_conn_read_cb(socket_t sock, void *arg) { |
|
|
|
|
struct stcp_conn *c = (struct stcp_conn *)arg; |
|
|
|
|
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: read_cb SKIP state=%d sock=%d c=%p recv_buf=%p", c->state, (int)sock, (void*)c, (void*)c->recv_buf); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "stcp_client: read_cb state=%d sock=%d c=%p recv_buf=%p len=%zu cap=%zu", |
|
|
|
|
c->state, (int)sock, (void*)c, (void*)c->recv_buf, c->recv_buf_len, c->recv_buf_cap); |
|
|
|
|
if (!c->recv_buf) { c->recv_buf_cap = STCP_RECV_BUF_INIT; c->recv_buf = u_malloc(c->recv_buf_cap); if (!c->recv_buf) { client_do_close(c, ENOMEM); return; } } |
|
|
|
|
if (c->recv_buf_len + 4096 > c->recv_buf_cap) { |
|
|
|
|
size_t nc = c->recv_buf_cap * 2; if (nc > STCP_RECV_BUF_MAX) nc = STCP_RECV_BUF_MAX; |
|
|
|
|
if (nc <= c->recv_buf_cap) { client_do_close(c, ENOBUFS); return; } |
|
|
|
|
uint8_t *nb = u_realloc(c->recv_buf, nc); if (!nb) { client_do_close(c, ENOMEM); return; } |
|
|
|
|
c->recv_buf = nb; c->recv_buf_cap = nc; |
|
|
|
|
} |
|
|
|
|
ssize_t n = recv(sock, c->recv_buf + c->recv_buf_len, c->recv_buf_cap - c->recv_buf_len, 0); |
|
|
|
|
if (n < 0) { int e = socket_get_error(); if (e == ERR_AGAIN || e == ERR_WOULDBLOCK) return; client_do_close(c, e); return; } |
|
|
|
|
if (n == 0) { client_do_close(c, 0); return; } |
|
|
|
|
c->recv_buf_len += (size_t)n; |
|
|
|
|
stcp_conn_process_recv(c); |
|
|
|
|
(void)sock; |
|
|
|
|
int r = stcp_conn_read(c); |
|
|
|
|
if (r == -1) return; |
|
|
|
|
if (r == 0) return; |
|
|
|
|
stcp_recv_try(c); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void client_connect_write_cb(socket_t sock, void *arg) { |
|
|
|
|
@ -178,42 +124,17 @@ static void client_connect_write_cb(socket_t sock, void *arg) {
|
|
|
|
|
socklen_t len = sizeof(err); |
|
|
|
|
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &len) < 0 || err != 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "stcp_client connect failed err=%d", err); |
|
|
|
|
client_do_close(c, err); return; |
|
|
|
|
stcp_conn_do_close(c, err); return; |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: TCP connected, starting handshake"); |
|
|
|
|
uasync_remove_socket_t(cli->ua, sock); |
|
|
|
|
c->socket_id = uasync_add_socket_t(cli->ua, sock, client_conn_read_cb, stcp_write_cb, NULL, c); |
|
|
|
|
if (!c->socket_id) { client_do_close(c, ENOMEM); return; } |
|
|
|
|
if (!c->socket_id) { stcp_conn_do_close(c, ENOMEM); return; } |
|
|
|
|
int opt = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt)); |
|
|
|
|
if (client_derive_session(c, cli->peer_pubkey)) { client_do_close(c, 1); return; } |
|
|
|
|
if (client_derive_session(c, cli->peer_pubkey)) { stcp_conn_do_close(c, 1); return; } |
|
|
|
|
client_send_handshake(c, cli->peer_pubkey, cli->my_ed25519_pubkey); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void client_tx_queue_cb(struct ll_queue *q, void *arg) { |
|
|
|
|
struct stcp_conn *c = (struct stcp_conn *)arg; |
|
|
|
|
struct ll_entry *e = queue_data_get(q); |
|
|
|
|
if (!e) { queue_resume_callback(q); return; } |
|
|
|
|
if (c->state == STCP_STATE_DATA && e->dgram) |
|
|
|
|
stcp_pending_queue(c, e->dgram, e->len); |
|
|
|
|
queue_dgram_free(e); |
|
|
|
|
queue_entry_free(e); |
|
|
|
|
queue_resume_callback(q); |
|
|
|
|
stcp_flush_pending(c); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void client_do_close(struct stcp_conn *c, int err) { |
|
|
|
|
if (c->state == STCP_STATE_CLOSED || c->state == STCP_STATE_ERROR) return; |
|
|
|
|
int prev = c->state; |
|
|
|
|
c->state = STCP_STATE_CLOSED; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "stcp_client: closed is_server=%d err=%d prev_state=%d", c->is_server, err, prev); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: do_close sock=%d sock_id=%p recv_buf=%p len=%zu cap=%zu send_buf=%p on_close=%p", |
|
|
|
|
(int)c->sock, (void*)c->socket_id, (void*)c->recv_buf, c->recv_buf_len, c->recv_buf_cap, (void*)c->send_buf, (void*)c->on_close); |
|
|
|
|
if (c->socket_id) { uasync_remove_socket_t(c->ua, c->sock); c->socket_id = NULL; } |
|
|
|
|
if (c->sock != SOCKET_INVALID) { socket_close_wrapper(c->sock); c->sock = SOCKET_INVALID; } |
|
|
|
|
if (c->recv_buf) { void *p = c->recv_buf; u_free(c->recv_buf); c->recv_buf = NULL; c->recv_buf_len = 0; c->recv_buf_cap = 0; DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: do_close u_free recv_buf=%p", p); } |
|
|
|
|
if (c->send_buf) { void *p = c->send_buf; u_free(c->send_buf); c->send_buf = NULL; c->send_len = 0; DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: do_close u_free send_buf=%p", p); } |
|
|
|
|
stcp_pending_clear(c); |
|
|
|
|
if (c->on_close) { void (*cb)(struct stcp_conn*, int, void*) = c->on_close; c->on_close = NULL; DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: do_close calling on_close=%p", (void*)cb); cb(c, err, c->close_arg); DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "stcp_client: do_close on_close returned"); } |
|
|
|
|
// after handshake sent, wait for server response
|
|
|
|
|
stcp_recv_set(c, SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER, 0, client_hs_cb); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port, |
|
|
|
|
@ -231,8 +152,8 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
|
|
|
|
|
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; |
|
|
|
|
c->on_close = close_cb; c->close_arg = close_arg; |
|
|
|
|
c->on_write_error = client_do_close; |
|
|
|
|
c->tx_cb = client_tx_queue_cb; |
|
|
|
|
c->on_write_error = stcp_conn_do_close; |
|
|
|
|
c->tx_cb = stcp_tx_queue_cb; |
|
|
|
|
|
|
|
|
|
struct addrinfo hints = {0}; |
|
|
|
|
hints.ai_family = AF_UNSPEC; |
|
|
|
|
@ -256,19 +177,22 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
|
|
|
|
|
} |
|
|
|
|
c->socket_id = uasync_add_socket_t(ua, c->sock, NULL, client_connect_write_cb, NULL, cli); |
|
|
|
|
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; } |
|
|
|
|
c->hs_timer = uasync_set_timeout(ua, STCP_CONNECT_TIMEOUT, c, hs_timeout_cb, "stcp_hs"); |
|
|
|
|
} else { |
|
|
|
|
int opt = 1; setsockopt(c->sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&opt, sizeof(opt)); |
|
|
|
|
c->socket_id = uasync_add_socket_t(ua, c->sock, client_conn_read_cb, stcp_write_cb, NULL, c); |
|
|
|
|
if (!c->socket_id) { socket_close_wrapper(c->sock); u_free(cli); return NULL; } |
|
|
|
|
if (client_derive_session(c, cli->peer_pubkey)) { client_do_close(c, 1); return cli; } |
|
|
|
|
c->hs_timer = uasync_set_timeout(ua, STCP_CONNECT_TIMEOUT, c, hs_timeout_cb, "stcp_hs"); |
|
|
|
|
if (client_derive_session(c, cli->peer_pubkey)) { stcp_conn_do_close(c, 1); return cli; } |
|
|
|
|
client_send_handshake(c, cli->peer_pubkey, cli->my_ed25519_pubkey); |
|
|
|
|
stcp_recv_set(c, SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_SERVER, 0, client_hs_cb); |
|
|
|
|
} |
|
|
|
|
return cli; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void stcp_client_destroy(struct stcp_client *cli) { |
|
|
|
|
if (!cli) return; |
|
|
|
|
client_do_close(&cli->conn, 0); |
|
|
|
|
stcp_conn_do_close(&cli->conn, 0); |
|
|
|
|
stcp_conn_free(&cli->conn); |
|
|
|
|
u_free(cli); |
|
|
|
|
} |
|
|
|
|
|