mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-21 06:23:52 +00:00
The programs now use only the C++ codecs. The C stream/AEAD/AEAD-2022
implementations stay as the reference the differential tests and the C
unit tests compare against. Key derivation and the cipher tables used by
the doc snippets move to src/crypto/keys.{hpp,cpp}.
ss-manager keeps its servers in an ss_list (at most MAX_PORT_NUM,
looked up only on control messages) instead of a uthash table, and
tests/test_manager.py covers add/ping/list/remove end to end.
599 lines
19 KiB
C++
599 lines
19 KiB
C++
/*
|
|
* aead2022_udp.c - Shadowsocks 2022 Edition (SIP022) session-based UDP
|
|
*
|
|
* Copyright (C) 2013 - 2026, Max Lv <max.c.lv@gmail.com>
|
|
*
|
|
* This file is part of the shadowsocks-libev.
|
|
*
|
|
* shadowsocks-libev is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* shadowsocks-libev is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with shadowsocks-libev; see the file COPYING. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* A UDP packet is:
|
|
*
|
|
* AES methods: [AES-ECB(psk, session ID || packet ID)] [AEAD body]
|
|
* ChaCha: [24B random nonce] [XChaCha20-Poly1305(psk) body]
|
|
*
|
|
* The body carries a main header (type, timestamp, optional client session
|
|
* ID, padding, SOCKS address) followed by the payload.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <time.h>
|
|
|
|
#include <sodium.h>
|
|
#include <mbedtls/aes.h>
|
|
|
|
#include "aead.h"
|
|
#include "aead_internal.h"
|
|
#include "cache.h"
|
|
#include "utils.h"
|
|
#include "ss_ref.h"
|
|
|
|
#define SS2022_UDP_TYPE_CLIENT 0
|
|
#define SS2022_UDP_TYPE_SERVER 1
|
|
#define SS2022_UDP_SEP_HDR_LEN 16
|
|
#define SS2022_UDP_TAG_LEN 16
|
|
#define SS2022_UDP_XNONCE_LEN 24
|
|
/* type(1) + timestamp(8) + padding length(2) */
|
|
#define SS2022_UDP_MIN_HDR 11
|
|
/* sliding window width for replay protection */
|
|
#define SS2022_WINDOW_BITS 256
|
|
|
|
/*
|
|
* Per-conversation UDP session state. The client keeps one of these per
|
|
* source address; the server keeps one per client session ID. It holds our
|
|
* own outgoing session (ID, packet counter) and the replay window of the
|
|
* peer session we are receiving from.
|
|
*/
|
|
typedef struct ss2022_udp_session {
|
|
ss_ref_t ref; /* Table and relay slots independently own this session. */
|
|
cipher_t *cipher;
|
|
|
|
/* outgoing */
|
|
uint8_t tx_session_id[SS2022_SESSION_ID_LEN];
|
|
uint64_t tx_packet_id;
|
|
uint8_t tx_subkey[MAX_KEY_LENGTH];
|
|
int tx_ready;
|
|
|
|
/* incoming: last accepted peer session and its sliding window */
|
|
uint8_t rx_session_id[SS2022_SESSION_ID_LEN];
|
|
uint8_t rx_subkey[MAX_KEY_LENGTH];
|
|
int rx_ready;
|
|
uint64_t rx_highest;
|
|
uint8_t rx_window[SS2022_WINDOW_BITS / 8];
|
|
|
|
/* peer's client session ID, echoed by the server in its replies */
|
|
uint8_t peer_session_id[SS2022_SESSION_ID_LEN];
|
|
int peer_session_known;
|
|
|
|
} ss2022_udp_session_t;
|
|
|
|
/*
|
|
* Server-side session table, keyed by the 8-byte client session ID. SIP022
|
|
* requires servers to route by session ID rather than source address, so
|
|
* that sessions survive client network changes.
|
|
*/
|
|
#define SS2022_SESSION_TABLE_CAP AEAD_2022_UDP_SESSION_TABLE_CAP
|
|
#define SS2022_SESSION_TTL 600
|
|
|
|
static struct cache *ss2022_sessions = NULL;
|
|
|
|
static void
|
|
session_destroy(void *object)
|
|
{
|
|
sodium_memzero(object, sizeof(ss2022_udp_session_t));
|
|
ss_free(object);
|
|
}
|
|
|
|
/* Cache callback: drop the table's reference when an entry is evicted. */
|
|
static void
|
|
session_free_cb(void *key, void *element)
|
|
{
|
|
(void)key;
|
|
ss_ref_release(element);
|
|
}
|
|
|
|
static void
|
|
store16_be(uint8_t *p, uint16_t v)
|
|
{
|
|
p[0] = (uint8_t)(v >> 8);
|
|
p[1] = (uint8_t)v;
|
|
}
|
|
|
|
static void
|
|
store64_be(uint8_t *p, uint64_t v)
|
|
{
|
|
for (int i = 7; i >= 0; i--) {
|
|
p[i] = (uint8_t)(v & 0xff);
|
|
v >>= 8;
|
|
}
|
|
}
|
|
|
|
static uint64_t
|
|
load64_be(const uint8_t *p)
|
|
{
|
|
uint64_t v = 0;
|
|
for (int i = 0; i < 8; i++)
|
|
v = (v << 8) | p[i];
|
|
return v;
|
|
}
|
|
|
|
static int
|
|
ss2022_check_timestamp(uint64_t ts)
|
|
{
|
|
uint64_t now = (uint64_t)time(NULL);
|
|
uint64_t diff = now > ts ? now - ts : ts - now;
|
|
return diff <= SS2022_TIME_WINDOW ? CRYPTO_OK : CRYPTO_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Sliding window replay filter (WireGuard style). Only called after the
|
|
* packet has been authenticated and its header validated.
|
|
*/
|
|
static int
|
|
window_check(const ss2022_udp_session_t *s, uint64_t id)
|
|
{
|
|
if (id > s->rx_highest)
|
|
return CRYPTO_OK;
|
|
if (s->rx_highest - id >= SS2022_WINDOW_BITS)
|
|
return CRYPTO_ERROR;
|
|
|
|
uint64_t bit = id % SS2022_WINDOW_BITS;
|
|
return (s->rx_window[bit / 8] & (1u << (bit % 8)))
|
|
? CRYPTO_ERROR : CRYPTO_OK;
|
|
}
|
|
|
|
static void
|
|
window_update(ss2022_udp_session_t *s, uint64_t id)
|
|
{
|
|
if (id > s->rx_highest) {
|
|
uint64_t shift = id - s->rx_highest;
|
|
if (shift >= SS2022_WINDOW_BITS) {
|
|
memset(s->rx_window, 0, sizeof(s->rx_window));
|
|
} else {
|
|
for (uint64_t i = s->rx_highest + 1; i <= id; i++) {
|
|
uint64_t b = i % SS2022_WINDOW_BITS;
|
|
s->rx_window[b / 8] &= (uint8_t) ~(1u << (b % 8));
|
|
}
|
|
}
|
|
s->rx_highest = id;
|
|
}
|
|
|
|
uint64_t bit = id % SS2022_WINDOW_BITS;
|
|
s->rx_window[bit / 8] |= (uint8_t)(1u << (bit % 8));
|
|
}
|
|
|
|
static ss2022_udp_session_t *
|
|
session_new(cipher_t *cipher)
|
|
{
|
|
ss2022_udp_session_t *s = (ss2022_udp_session_t *)ss_malloc(sizeof(ss2022_udp_session_t));
|
|
memset(s, 0, sizeof(ss2022_udp_session_t));
|
|
s->cipher = cipher;
|
|
ss_ref_init(s, session_destroy);
|
|
return s;
|
|
}
|
|
|
|
static ss2022_udp_session_t *
|
|
session_get(void **slot, cipher_t *cipher)
|
|
{
|
|
ss2022_udp_session_t *s = (ss2022_udp_session_t *)*slot;
|
|
if (s != NULL)
|
|
return s;
|
|
|
|
s = session_new(cipher);
|
|
*slot = s;
|
|
return s;
|
|
}
|
|
|
|
/* Look up (or create) the server-side session for a client session ID. */
|
|
static ss2022_udp_session_t *
|
|
session_by_id(const uint8_t *session_id, cipher_t *cipher)
|
|
{
|
|
if (ss2022_sessions == NULL
|
|
&& cache_create_keyed(&ss2022_sessions, SS2022_SESSION_TABLE_CAP,
|
|
SS2022_SESSION_ID_LEN, session_free_cb) != 0)
|
|
return NULL;
|
|
|
|
ss2022_udp_session_t *s = NULL;
|
|
cache_lookup(ss2022_sessions, (char *)session_id, SS2022_SESSION_ID_LEN,
|
|
(void *)&s);
|
|
if (s != NULL)
|
|
return s;
|
|
|
|
cache_clear(ss2022_sessions, SS2022_SESSION_TTL);
|
|
|
|
s = session_new(cipher);
|
|
/* Transfer the initial reference to the table on successful insertion. */
|
|
if (cache_insert(ss2022_sessions, (char *)session_id,
|
|
SS2022_SESSION_ID_LEN, s) != 0) {
|
|
ss_ref_release(s);
|
|
return NULL;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
void
|
|
aead_2022_udp_session_release(void *slot)
|
|
{
|
|
ss_ref_release(slot);
|
|
}
|
|
|
|
static int
|
|
is_chacha(const cipher_t *cipher)
|
|
{
|
|
return cipher->method == CHACHA20POLY1305IETF2022;
|
|
}
|
|
|
|
/* AES-ECB over the 16-byte separate header, keyed directly with the PSK. */
|
|
static int
|
|
sep_hdr_crypt(const cipher_t *cipher, uint8_t *dst, const uint8_t *src, int enc)
|
|
{
|
|
mbedtls_aes_context aes;
|
|
int err;
|
|
|
|
mbedtls_aes_init(&aes);
|
|
if (enc)
|
|
err = mbedtls_aes_setkey_enc(&aes, cipher->key,
|
|
(unsigned int)cipher->key_len * 8);
|
|
else
|
|
err = mbedtls_aes_setkey_dec(&aes, cipher->key,
|
|
(unsigned int)cipher->key_len * 8);
|
|
if (err == 0)
|
|
err = mbedtls_aes_crypt_ecb(&aes,
|
|
enc ? MBEDTLS_AES_ENCRYPT
|
|
: MBEDTLS_AES_DECRYPT,
|
|
src, dst);
|
|
mbedtls_aes_free(&aes);
|
|
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
|
}
|
|
|
|
/*
|
|
* AEAD over the packet body. For AES methods the session subkey and a nonce
|
|
* taken from the separate header are used; for ChaCha the PSK is used
|
|
* directly with a random 24-byte nonce.
|
|
*/
|
|
static int
|
|
body_seal(const cipher_t *cipher, const uint8_t *key,
|
|
uint8_t *c, size_t *clen, const uint8_t *m, size_t mlen,
|
|
const uint8_t *nonce)
|
|
{
|
|
unsigned long long out_len = 0;
|
|
int err;
|
|
|
|
if (is_chacha(cipher)) {
|
|
err = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
|
c, &out_len, m, mlen, NULL, 0, NULL, nonce, key);
|
|
*clen = (size_t)out_len;
|
|
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
|
}
|
|
|
|
cipher_t tmp_cipher = *cipher;
|
|
cipher_ctx_t tmp_ctx;
|
|
memset(&tmp_ctx, 0, sizeof(tmp_ctx));
|
|
tmp_ctx.cipher = &tmp_cipher;
|
|
|
|
/* Reuse the shared one-shot helper with an mbedTLS GCM context. */
|
|
tmp_ctx.evp = (cipher_evp_t *)ss_malloc(sizeof(cipher_evp_t));
|
|
memset(tmp_ctx.evp, 0, sizeof(cipher_evp_t));
|
|
mbedtls_cipher_init(tmp_ctx.evp);
|
|
const cipher_kt_t *kt = mbedtls_cipher_info_from_string(
|
|
cipher->key_len == 16 ? "AES-128-GCM" : "AES-256-GCM");
|
|
if (kt == NULL || mbedtls_cipher_setup(tmp_ctx.evp, kt) != 0
|
|
|| mbedtls_cipher_setkey(tmp_ctx.evp, key,
|
|
(int)cipher->key_len * 8, MBEDTLS_ENCRYPT) != 0) {
|
|
mbedtls_cipher_free(tmp_ctx.evp);
|
|
ss_free(tmp_ctx.evp);
|
|
return CRYPTO_ERROR;
|
|
}
|
|
|
|
*clen = mlen + SS2022_UDP_TAG_LEN;
|
|
err = aead_cipher_encrypt(&tmp_ctx, c, clen, (uint8_t *)m, mlen,
|
|
NULL, 0, (uint8_t *)nonce, (uint8_t *)key);
|
|
|
|
mbedtls_cipher_free(tmp_ctx.evp);
|
|
ss_free(tmp_ctx.evp);
|
|
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
|
}
|
|
|
|
static int
|
|
body_open(const cipher_t *cipher, const uint8_t *key,
|
|
uint8_t *p, size_t *plen, const uint8_t *c, size_t clen,
|
|
const uint8_t *nonce)
|
|
{
|
|
unsigned long long out_len = 0;
|
|
int err;
|
|
|
|
if (is_chacha(cipher)) {
|
|
err = crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
p, &out_len, NULL, c, clen, NULL, 0, nonce, key);
|
|
*plen = (size_t)out_len;
|
|
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
|
}
|
|
|
|
cipher_t tmp_cipher = *cipher;
|
|
cipher_ctx_t tmp_ctx;
|
|
memset(&tmp_ctx, 0, sizeof(tmp_ctx));
|
|
tmp_ctx.cipher = &tmp_cipher;
|
|
|
|
tmp_ctx.evp = (cipher_evp_t *)ss_malloc(sizeof(cipher_evp_t));
|
|
memset(tmp_ctx.evp, 0, sizeof(cipher_evp_t));
|
|
mbedtls_cipher_init(tmp_ctx.evp);
|
|
const cipher_kt_t *kt = mbedtls_cipher_info_from_string(
|
|
cipher->key_len == 16 ? "AES-128-GCM" : "AES-256-GCM");
|
|
if (kt == NULL || mbedtls_cipher_setup(tmp_ctx.evp, kt) != 0
|
|
|| mbedtls_cipher_setkey(tmp_ctx.evp, key,
|
|
(int)cipher->key_len * 8, MBEDTLS_DECRYPT) != 0) {
|
|
mbedtls_cipher_free(tmp_ctx.evp);
|
|
ss_free(tmp_ctx.evp);
|
|
return CRYPTO_ERROR;
|
|
}
|
|
|
|
*plen = clen - SS2022_UDP_TAG_LEN;
|
|
err = aead_cipher_decrypt(&tmp_ctx, p, plen, (uint8_t *)c, clen,
|
|
NULL, 0, (uint8_t *)nonce, (uint8_t *)key);
|
|
|
|
mbedtls_cipher_free(tmp_ctx.evp);
|
|
ss_free(tmp_ctx.evp);
|
|
return err == 0 ? CRYPTO_OK : CRYPTO_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Encrypt one UDP packet. The plaintext is a SOCKS address followed by the
|
|
* payload, matching what udprelay hands to the legacy encrypt_all.
|
|
*/
|
|
int
|
|
aead_2022_encrypt_udp(buffer_t *plaintext, cipher_t *cipher, size_t capacity,
|
|
void **slot)
|
|
{
|
|
static buffer_t tmp = { 0, 0, 0, NULL };
|
|
|
|
ss2022_udp_session_t *s = session_get(slot, cipher);
|
|
int is_server = (aead_get_role() == CRYPTO_ROLE_SERVER);
|
|
size_t salt_len = cipher->key_len;
|
|
|
|
if (!s->tx_ready) {
|
|
rand_bytes(s->tx_session_id, SS2022_SESSION_ID_LEN);
|
|
s->tx_packet_id = 0;
|
|
ss2022_derive_subkey(cipher->key, salt_len,
|
|
s->tx_session_id, SS2022_SESSION_ID_LEN,
|
|
s->tx_subkey, salt_len);
|
|
s->tx_ready = 1;
|
|
}
|
|
|
|
/* Build the main header followed by the payload. */
|
|
size_t extra = is_server ? SS2022_SESSION_ID_LEN : 0;
|
|
size_t hdr_len = SS2022_UDP_MIN_HDR + extra;
|
|
size_t body_len = hdr_len + plaintext->len;
|
|
|
|
uint8_t *body = (uint8_t *)ss_malloc(body_len);
|
|
size_t o = 0;
|
|
|
|
body[o++] = is_server ? SS2022_UDP_TYPE_SERVER : SS2022_UDP_TYPE_CLIENT;
|
|
store64_be(body + o, (uint64_t)time(NULL));
|
|
o += 8;
|
|
|
|
if (is_server) {
|
|
if (!s->peer_session_known) {
|
|
ss_free(body);
|
|
LOGE("AEAD-2022: no client session to reply to");
|
|
return CRYPTO_ERROR;
|
|
}
|
|
memcpy(body + o, s->peer_session_id, SS2022_SESSION_ID_LEN);
|
|
o += SS2022_SESSION_ID_LEN;
|
|
}
|
|
|
|
store16_be(body + o, 0); /* padding length */
|
|
o += 2;
|
|
|
|
memcpy(body + o, plaintext->data, plaintext->len);
|
|
|
|
int rc = CRYPTO_ERROR;
|
|
|
|
if (is_chacha(cipher)) {
|
|
/*
|
|
* nonce || XChaCha20-Poly1305(psk, session ID || packet ID ||
|
|
* main header || payload)
|
|
*/
|
|
size_t merged_len = SS2022_UDP_SEP_HDR_LEN + body_len;
|
|
uint8_t *merged = (uint8_t *)ss_malloc(merged_len);
|
|
memcpy(merged, s->tx_session_id, SS2022_SESSION_ID_LEN);
|
|
store64_be(merged + SS2022_SESSION_ID_LEN, s->tx_packet_id);
|
|
memcpy(merged + SS2022_UDP_SEP_HDR_LEN, body, body_len);
|
|
|
|
brealloc(&tmp, SS2022_UDP_XNONCE_LEN + merged_len + SS2022_UDP_TAG_LEN,
|
|
capacity);
|
|
rand_bytes(tmp.data, SS2022_UDP_XNONCE_LEN);
|
|
|
|
size_t clen = 0;
|
|
rc = body_seal(cipher, cipher->key,
|
|
(uint8_t *)tmp.data + SS2022_UDP_XNONCE_LEN, &clen,
|
|
merged, merged_len, (uint8_t *)tmp.data);
|
|
sodium_memzero(merged, merged_len);
|
|
ss_free(merged);
|
|
|
|
if (rc == CRYPTO_OK)
|
|
tmp.len = SS2022_UDP_XNONCE_LEN + clen;
|
|
} else {
|
|
uint8_t sep[SS2022_UDP_SEP_HDR_LEN];
|
|
memcpy(sep, s->tx_session_id, SS2022_SESSION_ID_LEN);
|
|
store64_be(sep + SS2022_SESSION_ID_LEN, s->tx_packet_id);
|
|
|
|
brealloc(&tmp, SS2022_UDP_SEP_HDR_LEN + body_len + SS2022_UDP_TAG_LEN,
|
|
capacity);
|
|
|
|
rc = sep_hdr_crypt(cipher, (uint8_t *)tmp.data, sep, 1);
|
|
if (rc == CRYPTO_OK) {
|
|
size_t clen = 0;
|
|
/* nonce is separate_header[4..16] of the plaintext header */
|
|
rc = body_seal(cipher, s->tx_subkey,
|
|
(uint8_t *)tmp.data + SS2022_UDP_SEP_HDR_LEN, &clen,
|
|
body, body_len, sep + 4);
|
|
if (rc == CRYPTO_OK)
|
|
tmp.len = SS2022_UDP_SEP_HDR_LEN + clen;
|
|
}
|
|
}
|
|
|
|
sodium_memzero(body, body_len);
|
|
ss_free(body);
|
|
|
|
if (rc != CRYPTO_OK)
|
|
return CRYPTO_ERROR;
|
|
|
|
s->tx_packet_id++;
|
|
|
|
bswap_data(plaintext, &tmp);
|
|
plaintext->len = tmp.len;
|
|
return CRYPTO_OK;
|
|
}
|
|
|
|
int
|
|
aead_2022_decrypt_udp(buffer_t *ciphertext, cipher_t *cipher, size_t capacity,
|
|
void **slot)
|
|
{
|
|
static buffer_t tmp = { 0, 0, 0, NULL };
|
|
|
|
int is_server = (aead_get_role() == CRYPTO_ROLE_SERVER);
|
|
size_t salt_len = cipher->key_len;
|
|
|
|
uint8_t session_id[SS2022_SESSION_ID_LEN];
|
|
uint64_t packet_id;
|
|
size_t plen = 0;
|
|
|
|
if (is_chacha(cipher)) {
|
|
if (ciphertext->len <= SS2022_UDP_XNONCE_LEN + SS2022_UDP_SEP_HDR_LEN
|
|
+ SS2022_UDP_TAG_LEN)
|
|
return CRYPTO_ERROR;
|
|
|
|
size_t clen = ciphertext->len - SS2022_UDP_XNONCE_LEN;
|
|
brealloc(&tmp, clen, capacity);
|
|
|
|
if (body_open(cipher, cipher->key, (uint8_t *)tmp.data, &plen,
|
|
(uint8_t *)ciphertext->data + SS2022_UDP_XNONCE_LEN, clen,
|
|
(uint8_t *)ciphertext->data) != CRYPTO_OK)
|
|
return CRYPTO_ERROR;
|
|
|
|
if (plen < SS2022_UDP_SEP_HDR_LEN)
|
|
return CRYPTO_ERROR;
|
|
|
|
memcpy(session_id, tmp.data, SS2022_SESSION_ID_LEN);
|
|
packet_id = load64_be((uint8_t *)tmp.data + SS2022_SESSION_ID_LEN);
|
|
|
|
/* strip the merged session ID and packet ID */
|
|
plen -= SS2022_UDP_SEP_HDR_LEN;
|
|
memmove(tmp.data, tmp.data + SS2022_UDP_SEP_HDR_LEN, plen);
|
|
} else {
|
|
if (ciphertext->len <= SS2022_UDP_SEP_HDR_LEN + SS2022_UDP_TAG_LEN)
|
|
return CRYPTO_ERROR;
|
|
|
|
uint8_t sep[SS2022_UDP_SEP_HDR_LEN];
|
|
if (sep_hdr_crypt(cipher, sep, (uint8_t *)ciphertext->data, 0)
|
|
!= CRYPTO_OK)
|
|
return CRYPTO_ERROR;
|
|
|
|
memcpy(session_id, sep, SS2022_SESSION_ID_LEN);
|
|
packet_id = load64_be(sep + SS2022_SESSION_ID_LEN);
|
|
|
|
uint8_t subkey[MAX_KEY_LENGTH];
|
|
ss2022_derive_subkey(cipher->key, salt_len,
|
|
session_id, SS2022_SESSION_ID_LEN,
|
|
subkey, salt_len);
|
|
|
|
size_t clen = ciphertext->len - SS2022_UDP_SEP_HDR_LEN;
|
|
brealloc(&tmp, clen, capacity);
|
|
|
|
int rc = body_open(cipher, subkey, (uint8_t *)tmp.data, &plen,
|
|
(uint8_t *)ciphertext->data + SS2022_UDP_SEP_HDR_LEN,
|
|
clen, sep + 4);
|
|
sodium_memzero(subkey, sizeof(subkey));
|
|
if (rc != CRYPTO_OK)
|
|
return CRYPTO_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Resolve the session. The server routes by client session ID, which is
|
|
* only known now; the client keeps one session per source address, held
|
|
* in the caller's slot.
|
|
*/
|
|
ss2022_udp_session_t *s;
|
|
if (is_server) {
|
|
s = session_by_id(session_id, cipher);
|
|
if (s == NULL)
|
|
return CRYPTO_ERROR;
|
|
} else {
|
|
s = session_get(slot, cipher);
|
|
}
|
|
|
|
/* Main header validation */
|
|
size_t extra = is_server ? 0 : SS2022_SESSION_ID_LEN;
|
|
size_t hdr_len = SS2022_UDP_MIN_HDR + extra;
|
|
if (plen < hdr_len)
|
|
return CRYPTO_ERROR;
|
|
|
|
uint8_t *body = (uint8_t *)tmp.data;
|
|
uint8_t want_type = is_server ? SS2022_UDP_TYPE_CLIENT
|
|
: SS2022_UDP_TYPE_SERVER;
|
|
if (body[0] != want_type)
|
|
return CRYPTO_ERROR;
|
|
|
|
if (ss2022_check_timestamp(load64_be(body + 1)) != CRYPTO_OK)
|
|
return CRYPTO_ERROR;
|
|
|
|
size_t o = 9;
|
|
if (!is_server) {
|
|
/* the server echoes our client session ID; verify it is ours */
|
|
if (!s->tx_ready
|
|
|| memcmp(body + o, s->tx_session_id, SS2022_SESSION_ID_LEN) != 0)
|
|
return CRYPTO_ERROR;
|
|
o += SS2022_SESSION_ID_LEN;
|
|
}
|
|
|
|
uint16_t pad_len = load16_be(body + o);
|
|
o += 2;
|
|
if (o + pad_len > plen)
|
|
return CRYPTO_ERROR;
|
|
o += pad_len;
|
|
|
|
/* Replay protection: new session resets the window. */
|
|
if (!s->rx_ready
|
|
|| memcmp(s->rx_session_id, session_id, SS2022_SESSION_ID_LEN) != 0) {
|
|
memcpy(s->rx_session_id, session_id, SS2022_SESSION_ID_LEN);
|
|
memset(s->rx_window, 0, sizeof(s->rx_window));
|
|
s->rx_highest = 0;
|
|
s->rx_ready = 1;
|
|
/* packet ID 0 is legitimate for a fresh session */
|
|
if (packet_id > 0)
|
|
s->rx_highest = packet_id - 1;
|
|
} else if (window_check(s, packet_id) != CRYPTO_OK) {
|
|
LOGE("AEAD-2022: UDP replay detected");
|
|
return CRYPTO_ERROR;
|
|
}
|
|
window_update(s, packet_id);
|
|
|
|
if (is_server) {
|
|
memcpy(s->peer_session_id, session_id, SS2022_SESSION_ID_LEN);
|
|
s->peer_session_known = 1;
|
|
/* Publish only validated packets and retain the reply session. */
|
|
ss_ref_assign(slot, s);
|
|
}
|
|
|
|
size_t payload_len = plen - o;
|
|
brealloc(ciphertext, payload_len ? payload_len : 1, capacity);
|
|
memcpy(ciphertext->data, body + o, payload_len);
|
|
ciphertext->len = payload_len;
|
|
return CRYPTO_OK;
|
|
}
|