You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

338 lines
19 KiB

// test_reality_hello.c — тесты модуля reality (REALITY-style ClientHello/ServerHello)
//
// Сценарии:
// 1. Round-trip: клиент собирает ClientHello → сервер проверяет авторизацию и
// собирает ServerHello; проверка структуры обоих (TLS record + handshake,
// echo SessionId).
// 2. Неверный short_id → REALITY_ERR_AUTH.
// 3. Неверный static privkey сервера → REALITY_ERR_AUTH (AES-GCM не сходится).
// 4. Порча байта ClientHello → REALITY_ERR_AUTH (AAD не совпадает).
// 5. Несовпадение версии протокола → REALITY_ERR_AUTH.
// 6. Timestamp вне окна (антиреплей) → REALITY_ERR_AUTH; в окне → OK.
// 7. Ошибки формата (мусор/пусто/неверный тип) → REALITY_ERR_FORMAT.
// 8. Fingerprint-геттер и keygen/pubkey_from_priv.
#include "reality.h"
#include "reality_fingerprint.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
#include <stdio.h>
#include <string.h>
static int test_failed = 0;
#define CHECK(expr, msg) do { \
if (!(expr)) { \
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL: %s", msg); \
test_failed = 1; \
} else { \
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "PASS: %s", msg); \
} \
} while (0)
static uint8_t g_short_id[REALITY_SHORT_ID_SIZE] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
static uint8_t g_short_id_wrong[REALITY_SHORT_ID_SIZE] = { 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8 };
static uint8_t g_version[REALITY_VERSION_SIZE] = { 1, 2, 3 };
static uint8_t g_version_wrong[REALITY_VERSION_SIZE] = { 9, 9, 9 };
static void init_client_cfg(struct reality_client_config *cc,
const uint8_t *server_pub,
const uint8_t *short_id,
const uint8_t *version) {
memset(cc, 0, sizeof(*cc));
memcpy(cc->server_static_pubkey, server_pub, REALITY_AUTH_KEY_SIZE);
memcpy(cc->short_id, short_id, REALITY_SHORT_ID_SIZE);
memcpy(cc->version, version, REALITY_VERSION_SIZE);
snprintf(cc->server_name, sizeof(cc->server_name), "www.microsoft.com");
cc->fingerprint = REALITY_FP_CHROME;
}
static void init_server_cfg(struct reality_server_config *sc,
const uint8_t *server_priv,
const uint8_t *short_id,
const uint8_t *version,
int64_t window) {
memset(sc, 0, sizeof(*sc));
memcpy(sc->static_privkey, server_priv, REALITY_AUTH_KEY_SIZE);
memcpy(sc->short_ids[0], short_id, REALITY_SHORT_ID_SIZE);
sc->short_id_count = 1;
memcpy(sc->version, version, REALITY_VERSION_SIZE);
sc->time_window_sec = window;
sc->fingerprint = REALITY_FP_CHROME;
}
// Собирает структурно-валидный ClientHello (TLS record + handshake), тело которого
// содержит ровно одну extension-запись ext_block/ext_block_len. Для fuzz-проверок
// парсера (malformed key_share и т.п.).
static size_t build_ch_with_ext(uint8_t *out, size_t out_cap,
const uint8_t *ext_block, size_t ext_block_len) {
(void)out_cap;
uint8_t body[REALITY_MAX_CH_SIZE];
size_t b = 0;
body[b++] = 0x03; body[b++] = 0x03; // legacy_version
memset(body + b, 0x11, 32); b += 32; // random
body[b++] = 32; // session_id len
memset(body + b, 0x00, 32); b += 32; // session_id (нули)
body[b++] = 0x00; body[b++] = 0x02; // cipher_suites: 1 суит
body[b++] = 0x13; body[b++] = 0x01; // TLS_AES_128_GCM_SHA256
body[b++] = 0x01; body[b++] = 0x00; // compression (null)
body[b++] = (uint8_t)(ext_block_len >> 8); // extensions len
body[b++] = (uint8_t)(ext_block_len & 0xff);
memcpy(body + b, ext_block, ext_block_len); b += ext_block_len;
size_t body_len = b;
size_t hs_len = 4 + body_len;
out[0] = 0x16; out[1] = 0x03; out[2] = 0x01; // TLS record header
out[3] = (uint8_t)(hs_len >> 8); out[4] = (uint8_t)(hs_len & 0xff);
out[5] = 0x01; // handshake type = ClientHello
out[6] = (uint8_t)(body_len >> 16);
out[7] = (uint8_t)(body_len >> 8);
out[8] = (uint8_t)(body_len & 0xff);
memcpy(out + 9, body, body_len);
return 5 + hs_len;
}
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_REALITY);
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "=== Reality Hello Test ===");
uint8_t srv_priv[REALITY_AUTH_KEY_SIZE], srv_pub[REALITY_AUTH_KEY_SIZE];
CHECK(reality_generate_keypair(srv_priv, srv_pub) == REALITY_OK, "generate server keypair");
{
uint8_t pub2[REALITY_AUTH_KEY_SIZE];
CHECK(reality_pubkey_from_priv(srv_priv, pub2) == REALITY_OK, "pubkey_from_priv");
CHECK(memcmp(srv_pub, pub2, REALITY_AUTH_KEY_SIZE) == 0, "pubkey_from_priv matches");
}
uint8_t other_priv[REALITY_AUTH_KEY_SIZE], other_pub[REALITY_AUTH_KEY_SIZE];
CHECK(reality_generate_keypair(other_priv, other_pub) == REALITY_OK, "generate wrong keypair");
struct reality_client_config cc;
struct reality_server_config sc;
uint8_t ch[REALITY_MAX_CH_SIZE], sh[REALITY_MAX_SH_SIZE];
size_t ch_len = 0, sh_len = 0;
// ── Сценарий 1: round-trip ──
init_client_cfg(&cc, srv_pub, g_short_id, g_version);
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
CHECK(reality_client_hello_build(&cc, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build");
CHECK(ch_len > 5, "client hello non-trivial size");
CHECK(ch[0] == 0x16 && ch[1] == 0x03, "client hello TLS record header");
CHECK(ch[5] == 0x01, "client hello handshake type");
{
// проверка согласованности длины: record length == handshake length + 4
size_t rec_len = ((size_t)ch[3] << 8) | ch[4];
CHECK(rec_len + 5 == ch_len, "client hello record length matches");
}
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_OK, "server hello build (auth OK)");
CHECK(sh[0] == 0x16 && sh[5] == 0x02, "server hello record + handshake type");
{
// echo SessionId: client SessionId в ch[44..76], server echo в sh[44..76]
CHECK(memcmp(ch + 44, sh + 44, REALITY_SESSION_ID_SIZE) == 0, "server echoes client SessionId");
}
// ── Сценарий 2: неверный short_id ──
init_client_cfg(&cc, srv_pub, g_short_id_wrong, g_version);
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
CHECK(reality_client_hello_build(&cc, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build (wrong sid)");
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_ERR_AUTH, "wrong short_id rejected");
// ── Сценарий 3: неверный static privkey сервера ──
init_client_cfg(&cc, srv_pub, g_short_id, g_version);
init_server_cfg(&sc, other_priv, g_short_id, g_version, 60);
CHECK(reality_client_hello_build(&cc, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build (wrong key)");
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_ERR_AUTH, "wrong server key rejected");
// ── Сценарий 4: порча байта ClientHello ──
init_client_cfg(&cc, srv_pub, g_short_id, g_version);
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
CHECK(reality_client_hello_build(&cc, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build (tamper)");
ch[50] ^= 0x01; // портим байт внутри handshake-сообщения
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_ERR_AUTH, "tampered hello rejected");
// ── Сценарий 5: несовпадение версии ──
init_client_cfg(&cc, srv_pub, g_short_id, g_version_wrong);
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
CHECK(reality_client_hello_build(&cc, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build (ver mismatch)");
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_ERR_AUTH, "version mismatch rejected");
// ── Сценарий 6: timestamp вне окна ──
init_client_cfg(&cc, srv_pub, g_short_id, g_version);
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
{
uint32_t now = (uint32_t)time(NULL);
CHECK(reality_client_hello_build_at(&cc, now, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build (ts=now)");
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_OK, "ts=now accepted");
CHECK(reality_client_hello_build_at(&cc, now - 3600, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build (ts=past)");
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_ERR_AUTH, "ts=past rejected (anti-replay)");
CHECK(reality_client_hello_build_at(&cc, now + 3600, ch, sizeof(ch), &ch_len) == REALITY_OK, "client hello build (ts=future)");
CHECK(reality_server_hello_build(&sc, ch, ch_len, sh, sizeof(sh), &sh_len) == REALITY_ERR_AUTH, "ts=future rejected");
}
// ── Сценарий 7: ошибки формата ──
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
{
uint8_t garbage[REALITY_MAX_CH_SIZE];
memset(garbage, 0, sizeof(garbage));
CHECK(reality_server_hello_build(&sc, garbage, 0, sh, sizeof(sh), &sh_len) == REALITY_ERR_FORMAT, "empty input rejected");
CHECK(reality_server_hello_build(&sc, garbage, 100, sh, sizeof(sh), &sh_len) == REALITY_ERR_FORMAT, "all-zero input rejected");
memset(garbage, 0, sizeof(garbage));
garbage[0] = 0x17; // не handshake-запись
CHECK(reality_server_hello_build(&sc, garbage, 100, sh, sizeof(sh), &sh_len) == REALITY_ERR_FORMAT, "wrong record type rejected");
// валидная запись, но не ClientHello
memset(garbage, 0, sizeof(garbage));
garbage[0] = 0x16; garbage[1] = 0x03; garbage[2] = 0x01;
garbage[3] = 0x00; garbage[4] = 0x04; // record length 4
garbage[5] = 0x02; // handshake type = server_hello
CHECK(reality_server_hello_build(&sc, garbage, 9, sh, sizeof(sh), &sh_len) == REALITY_ERR_FORMAT, "non-client-hello rejected");
}
// ── Сценарий 8: fingerprint-геттер ──
CHECK(reality_fingerprint_get(REALITY_FP_CHROME) != NULL, "fingerprint chrome found");
CHECK(reality_fingerprint_get(999) == NULL, "unknown fingerprint = NULL");
// ── Сценарий 9: невалидные аргументы ──
init_client_cfg(&cc, srv_pub, g_short_id, g_version);
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
CHECK(reality_client_hello_build(NULL, ch, sizeof(ch), &ch_len) == REALITY_ERR_ARG, "client build NULL cfg rejected");
CHECK(reality_client_hello_build(&cc, ch, 16, &ch_len) == REALITY_ERR_ARG, "client build small buffer rejected");
CHECK(reality_server_hello_build(&sc, NULL, 0, sh, sizeof(sh), &sh_len) == REALITY_ERR_ARG, "server build NULL input rejected");
// ── Сценарий 9b: malformed key_share (fuzz парсера, без OOB-read) ──
init_server_cfg(&sc, srv_priv, g_short_id, g_version, 60);
{
uint8_t ch2[REALITY_MAX_CH_SIZE], sh2[REALITY_MAX_SH_SIZE];
size_t ch2_len = 0, sh2_len = 0;
uint8_t ks[10];
// shares_len (0xFFFF) не влезает в extension-данные (len=4) → FORMAT, без OOB
memset(ks, 0, sizeof(ks));
ks[0] = 0x00; ks[1] = 0x33; // type = key_share
ks[2] = 0x00; ks[3] = 0x04; // len = 4
ks[4] = 0xFF; ks[5] = 0xFF; // shares_len = 0xFFFF (завышен)
ch2_len = build_ch_with_ext(ch2, sizeof(ch2), ks, 8);
CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_FORMAT,
"key_share shares_len > ext len rejected");
// запись X25519 с klen=32, но без тела ключа → AUTH, без OOB
memset(ks, 0, sizeof(ks));
ks[0] = 0x00; ks[1] = 0x33; // type = key_share
ks[2] = 0x00; ks[3] = 0x06; // len = 6
ks[4] = 0x00; ks[5] = 0x04; // shares_len = 4 (= len-2)
ks[6] = 0x00; ks[7] = 0x1d; // group = X25519
ks[8] = 0x00; ks[9] = 0x20; // klen = 32 (тела нет)
ch2_len = build_ch_with_ext(ch2, sizeof(ch2), ks, sizeof(ks));
CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_AUTH,
"key_share truncated X25519 entry → AUTH");
// цепочка записей не-X25519 с klen=0 (в границах) → AUTH, без OOB
memset(ks, 0, sizeof(ks));
ks[0] = 0x00; ks[1] = 0x33; // type = key_share
ks[2] = 0x00; ks[3] = 0x06; // len = 6
ks[4] = 0x00; ks[5] = 0x04; // shares_len = 4
ks[6] = 0x00; ks[7] = 0x17; // group = secp256r1
ks[8] = 0x00; ks[9] = 0x00; // klen = 0
ch2_len = build_ch_with_ext(ch2, sizeof(ch2), ks, sizeof(ks));
CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_AUTH,
"key_share without X25519 → AUTH");
}
// ── Сценарий 10: парсинг секции [reality] из конфига ──
{
const char *cfg_text =
"[global]\nname=test\n"
"[reality]\n"
"enabled=1\n"
"server_name=www.microsoft.com\n"
"dest=www.microsoft.com:443\n"
"short_id=0102030405060708\n"
"short_ids=0102030405060708,aabbccddeeff0011\n"
"public_key=1111111111111111111111111111111111111111111111111111111111111111\n"
"private_key=2222222222222222222222222222222222222222222222222222222222222222\n"
"version=2.3.4\n"
"time_window=45\n"
"fingerprint=chrome\n";
struct utun_config *uc = parse_config_from_buf(cfg_text, strlen(cfg_text), "mem");
CHECK(uc != NULL, "parse config with [reality]");
if (uc) {
CHECK(uc->global.reality.enabled == 1, "reality enabled parsed");
CHECK(strcmp(uc->global.reality.server_name, "www.microsoft.com") == 0, "server_name parsed");
CHECK(strcmp(uc->global.reality.dest, "www.microsoft.com:443") == 0, "dest parsed");
CHECK(uc->global.reality.has_public_key == 1 && uc->global.reality.has_private_key == 1, "pub/priv keys parsed");
CHECK(uc->global.reality.short_id_count == 2, "short_ids parsed");
CHECK(uc->global.reality.version[0] == 2 && uc->global.reality.version[1] == 3 && uc->global.reality.version[2] == 4, "version parsed");
CHECK(uc->global.reality.time_window_sec == 45, "time_window parsed");
CHECK(uc->global.reality.fingerprint == REALITY_FP_CHROME, "fingerprint parsed");
free_config(uc);
}
}
// ── Сценарий 11: парсинг reality-ключей в секции [client] ──
{
const char *cfg_text =
"[global]\nname=test\n"
"[server: bind_srv]\naddr=127.0.0.1:2000\ntransport=tcp\n"
"[client: c1]\n"
"peer_public_key=1111111111111111111111111111111111111111111111111111111111111111\n"
"link=1.2.3.4:1443\n"
"reality=yes\n"
"server_name=www.microsoft.com\n"
"short_id=0102030405060708\n"
"public_key=2222222222222222222222222222222222222222222222222222222222222222\n"
"version=2.3.4\n"
"fingerprint=chrome\n"
"[client: c2]\n"
"peer_public_key=1111111111111111111111111111111111111111111111111111111111111111\n"
"link=bind_srv:5.6.7.8:1444\n"
"reality=yes\n"
"short_id=aabbccddeeff0011\n";
struct utun_config *uc = parse_config_from_buf(cfg_text, strlen(cfg_text), "mem");
CHECK(uc != NULL, "parse config with [client] reality");
if (uc) {
/* parser prepends: c2 первая, c1 вторая */
struct CFG_CLIENT *c2 = uc->clients;
struct CFG_CLIENT *c1 = c2 ? c2->next : NULL;
CHECK(c2 && strcmp(c2->name, "c2") == 0, "client c2 present");
CHECK(c1 && strcmp(c1->name, "c1") == 0, "client c1 present");
if (c1) {
CHECK(c1->reality_enabled == 1, "c1 reality enabled");
CHECK(strcmp(c1->reality.server_name, "www.microsoft.com") == 0, "c1 server_name");
CHECK(c1->reality.fingerprint == REALITY_FP_CHROME, "c1 fingerprint");
CHECK(c1->reality.version[0] == 2 && c1->reality.version[1] == 3 && c1->reality.version[2] == 4, "c1 version parsed");
{ uint8_t sid[8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
CHECK(memcmp(c1->reality.short_id, sid, 8) == 0, "c1 short_id"); }
{ uint8_t pk[32]; memset(pk, 0x22, 32);
CHECK(memcmp(c1->reality.server_static_pubkey, pk, 32) == 0, "c1 public_key"); }
CHECK(c1->links != NULL && c1->links->local_srv == NULL, "c1 link без bind (local_srv NULL)");
if (c1->links) {
struct sockaddr_in* sin = (struct sockaddr_in*)&c1->links->remote_addr;
CHECK(c1->links->remote_addr.ss_family == AF_INET && ntohs(sin->sin_port) == 1443, "c1 remote 1.2.3.4:1443");
}
}
if (c2) {
CHECK(c2->reality_enabled == 1, "c2 reality enabled");
CHECK(c2->reality.version[0] == 1 && c2->reality.version[1] == 0 && c2->reality.version[2] == 0, "c2 version default 1.0.0");
CHECK(c2->links != NULL && c2->links->local_srv != NULL, "c2 link с bind (local_srv set)");
if (c2->links && c2->links->local_srv)
CHECK(strcmp(c2->links->local_srv->name, "bind_srv") == 0, "c2 bind socket name");
}
free_config(uc);
}
}
if (test_failed) {
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "=== Reality Hello Test: FAILED ===");
return 1;
}
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "=== Reality Hello Test: PASSED ===");
return 0;
}