|
|
|
@ -25,6 +25,8 @@ |
|
|
|
#include <time.h> |
|
|
|
#include <time.h> |
|
|
|
#include <openssl/evp.h> |
|
|
|
#include <openssl/evp.h> |
|
|
|
#include <openssl/kdf.h> |
|
|
|
#include <openssl/kdf.h> |
|
|
|
|
|
|
|
#include <openssl/hmac.h> |
|
|
|
|
|
|
|
#include <openssl/crypto.h> |
|
|
|
|
|
|
|
|
|
|
|
// TLS-константы
|
|
|
|
// TLS-константы
|
|
|
|
#define TLS_RECORD_HANDSHAKE 0x16 |
|
|
|
#define TLS_RECORD_HANDSHAKE 0x16 |
|
|
|
@ -43,6 +45,8 @@ |
|
|
|
|
|
|
|
|
|
|
|
#define REALITY_HKDF_INFO "REALITY" |
|
|
|
#define REALITY_HKDF_INFO "REALITY" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int session_derive(struct reality_session *s, const uint8_t peer[32]); |
|
|
|
|
|
|
|
|
|
|
|
// ─── Буфер записи с контролем границ ───
|
|
|
|
// ─── Буфер записи с контролем границ ───
|
|
|
|
struct rbuf { |
|
|
|
struct rbuf { |
|
|
|
uint8_t *p; |
|
|
|
uint8_t *p; |
|
|
|
@ -89,6 +93,7 @@ out: |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int reality_generate_keypair(uint8_t priv[32], uint8_t pub[32]) { |
|
|
|
int reality_generate_keypair(uint8_t priv[32], uint8_t pub[32]) { |
|
|
|
|
|
|
|
if (!priv || !pub) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid keypair output"); return REALITY_ERR_ARG; } |
|
|
|
return reality_gen_keypair(priv, pub) == 0 ? REALITY_OK : REALITY_ERR_CRYPTO; |
|
|
|
return reality_gen_keypair(priv, pub) == 0 ? REALITY_OK : REALITY_ERR_CRYPTO; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -114,19 +119,19 @@ void reality_config_set_defaults(struct reality_config *cfg) { |
|
|
|
memset(cfg->private_key, 0, sizeof(cfg->private_key)); |
|
|
|
memset(cfg->private_key, 0, sizeof(cfg->private_key)); |
|
|
|
cfg->has_public_key = 0; |
|
|
|
cfg->has_public_key = 0; |
|
|
|
cfg->has_private_key = 0; |
|
|
|
cfg->has_private_key = 0; |
|
|
|
cfg->version[0] = 1; cfg->version[1] = 0; cfg->version[2] = 0; |
|
|
|
cfg->version[0] = 2; cfg->version[1] = 0; cfg->version[2] = 0; |
|
|
|
cfg->time_window_sec = 30; |
|
|
|
cfg->time_window_sec = 30; |
|
|
|
cfg->relay_idle_timeout_sec = 5; |
|
|
|
cfg->relay_idle_timeout_sec = 5; |
|
|
|
cfg->fingerprint = REALITY_FP_CHROME; |
|
|
|
cfg->fingerprint = REALITY_FP_OPENSSL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void reality_client_config_set_defaults(struct reality_client_config *cfg) { |
|
|
|
void reality_client_config_set_defaults(struct reality_client_config *cfg) { |
|
|
|
if (!cfg) return; |
|
|
|
if (!cfg) return; |
|
|
|
memset(cfg->server_static_pubkey, 0, sizeof(cfg->server_static_pubkey)); |
|
|
|
memset(cfg->server_static_pubkey, 0, sizeof(cfg->server_static_pubkey)); |
|
|
|
memset(cfg->short_id, 0, sizeof(cfg->short_id)); |
|
|
|
memset(cfg->short_id, 0, sizeof(cfg->short_id)); |
|
|
|
cfg->version[0] = 1; cfg->version[1] = 0; cfg->version[2] = 0; |
|
|
|
cfg->version[0] = 2; cfg->version[1] = 0; cfg->version[2] = 0; |
|
|
|
cfg->server_name[0] = '\0'; |
|
|
|
cfg->server_name[0] = '\0'; |
|
|
|
cfg->fingerprint = REALITY_FP_CHROME; |
|
|
|
cfg->fingerprint = REALITY_FP_OPENSSL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int reality_x25519(const uint8_t priv[32], const uint8_t pub[32], uint8_t shared[32]) { |
|
|
|
static int reality_x25519(const uint8_t priv[32], const uint8_t pub[32], uint8_t shared[32]) { |
|
|
|
@ -149,26 +154,6 @@ out: |
|
|
|
return rc; |
|
|
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int reality_hkdf_sha256(const uint8_t *ikm, size_t ikm_len, |
|
|
|
|
|
|
|
const uint8_t *salt, size_t salt_len, |
|
|
|
|
|
|
|
const uint8_t *info, size_t info_len, |
|
|
|
|
|
|
|
uint8_t *out, size_t out_len) { |
|
|
|
|
|
|
|
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); |
|
|
|
|
|
|
|
if (!ctx) return -1; |
|
|
|
|
|
|
|
int rc = -1; |
|
|
|
|
|
|
|
if (EVP_PKEY_derive_init(ctx) <= 0) goto out; |
|
|
|
|
|
|
|
if (EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256()) <= 0) goto out; |
|
|
|
|
|
|
|
if (EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, (int)salt_len) <= 0) goto out; |
|
|
|
|
|
|
|
if (EVP_PKEY_CTX_set1_hkdf_key(ctx, ikm, (int)ikm_len) <= 0) goto out; |
|
|
|
|
|
|
|
if (EVP_PKEY_CTX_add1_hkdf_info(ctx, info, (int)info_len) <= 0) goto out; |
|
|
|
|
|
|
|
size_t len = out_len; |
|
|
|
|
|
|
|
if (EVP_PKEY_derive(ctx, out, &len) <= 0 || len != out_len) goto out; |
|
|
|
|
|
|
|
rc = 0; |
|
|
|
|
|
|
|
out: |
|
|
|
|
|
|
|
EVP_PKEY_CTX_free(ctx); |
|
|
|
|
|
|
|
return rc; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int reality_aes_gcm_seal(const uint8_t key[32], const uint8_t nonce[12], |
|
|
|
static int reality_aes_gcm_seal(const uint8_t key[32], const uint8_t nonce[12], |
|
|
|
const uint8_t *aad, size_t aad_len, |
|
|
|
const uint8_t *aad, size_t aad_len, |
|
|
|
const uint8_t *plain, size_t plain_len, |
|
|
|
const uint8_t *plain, size_t plain_len, |
|
|
|
@ -250,21 +235,45 @@ static int reality_build_client_hello_body(const struct reality_client_config *c |
|
|
|
if (rbuf_put(&b, cfg->server_name, name_len) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put(&b, cfg->server_name, name_len) < 0) return REALITY_ERR_ARG; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t point_formats[] = {0, 11, 0, 4, 3, 0, 1, 2}; |
|
|
|
|
|
|
|
if (rbuf_put(&b, point_formats, sizeof(point_formats))) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
|
|
|
|
// supported_groups
|
|
|
|
// supported_groups
|
|
|
|
{ |
|
|
|
{ |
|
|
|
int count = 0; |
|
|
|
int count = 0; |
|
|
|
for (const uint16_t *g = fp->supported_groups; *g != 0; g++) count++; |
|
|
|
for (const uint16_t *g = fp->supported_groups; *g != 0; g++) count++; |
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_SUPPORTED_GROUPS) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_SUPPORTED_GROUPS) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(2 + count * 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(count * 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(count * 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
for (const uint16_t *g = fp->supported_groups; *g != 0; g++) |
|
|
|
for (const uint16_t *g = fp->supported_groups; *g != 0; g++) |
|
|
|
if (rbuf_put_u16(&b, *g) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, *g) < 0) return REALITY_ERR_ARG; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t ticket[] = {0, 35, 0, 0}; |
|
|
|
|
|
|
|
if (rbuf_put(&b, ticket, sizeof(ticket))) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
// ALPN
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
size_t alpn_list_len = 0; |
|
|
|
|
|
|
|
for (const char *const *a = fp->alpn; *a; a++) alpn_list_len += 1 + strlen(*a); |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_ALPN) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(alpn_list_len + 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, (uint16_t)alpn_list_len) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
for (const char *const *a = fp->alpn; *a; a++) { |
|
|
|
|
|
|
|
size_t l = strlen(*a); |
|
|
|
|
|
|
|
if (rbuf_put_u8(&b, (uint8_t)l) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put(&b, *a, l) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t legacy_extensions[] = {0, 22, 0, 0, 0, 23, 0, 0}; |
|
|
|
|
|
|
|
if (rbuf_put(&b, legacy_extensions, sizeof(legacy_extensions))) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
|
|
|
|
// signature_algorithms
|
|
|
|
// signature_algorithms
|
|
|
|
{ |
|
|
|
{ |
|
|
|
int count = 0; |
|
|
|
int count = 0; |
|
|
|
for (const uint16_t *s = fp->sig_algs; *s != 0; s++) count++; |
|
|
|
for (const uint16_t *s = fp->sig_algs; *s != 0; s++) count++; |
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_SIG_ALGS) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_SIG_ALGS) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(2 + count * 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(count * 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(count * 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
for (const uint16_t *s = fp->sig_algs; *s != 0; s++) |
|
|
|
for (const uint16_t *s = fp->sig_algs; *s != 0; s++) |
|
|
|
if (rbuf_put_u16(&b, *s) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, *s) < 0) return REALITY_ERR_ARG; |
|
|
|
@ -290,20 +299,8 @@ static int reality_build_client_hello_body(const struct reality_client_config *c |
|
|
|
if (rbuf_put_u16(&b, 32) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, 32) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put(&b, eph_pub, 32) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put(&b, eph_pub, 32) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
|
|
|
|
// ALPN
|
|
|
|
const uint8_t certificate_compression[] = {0, 27, 0, 5, 4, 0, 1, 0, 3}; |
|
|
|
{ |
|
|
|
if (rbuf_put(&b, certificate_compression, sizeof(certificate_compression))) return REALITY_ERR_ARG; |
|
|
|
size_t alpn_list_len = 0; |
|
|
|
|
|
|
|
for (const char *const *a = fp->alpn; *a; a++) alpn_list_len += 1 + strlen(*a); |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_ALPN) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, (uint16_t)(alpn_list_len + 2)) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put_u16(&b, (uint16_t)alpn_list_len) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
for (const char *const *a = fp->alpn; *a; a++) { |
|
|
|
|
|
|
|
size_t l = strlen(*a); |
|
|
|
|
|
|
|
if (rbuf_put_u8(&b, (uint8_t)l) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
if (rbuf_put(&b, *a, l) < 0) return REALITY_ERR_ARG; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rbuf_patch_u16(&b, ext_len_pos, (uint16_t)(b.len - ext_len_pos - 2)); |
|
|
|
rbuf_patch_u16(&b, ext_len_pos, (uint16_t)(b.len - ext_len_pos - 2)); |
|
|
|
*body_len = b.len; |
|
|
|
*body_len = b.len; |
|
|
|
return REALITY_OK; |
|
|
|
return REALITY_OK; |
|
|
|
@ -314,7 +311,7 @@ int reality_client_hello_build(const struct reality_client_config *cfg, |
|
|
|
return reality_client_hello_build_at(cfg, (uint32_t)time(NULL), out, out_cap, out_len); |
|
|
|
return reality_client_hello_build_at(cfg, (uint32_t)time(NULL), out, out_cap, out_len); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int reality_client_hello_build_at(const struct reality_client_config *cfg, uint32_t now, |
|
|
|
static int client_hello_build(struct reality_session *session, const struct reality_client_config *cfg, uint32_t now, |
|
|
|
uint8_t *out, size_t out_cap, size_t *out_len) { |
|
|
|
uint8_t *out, size_t out_cap, size_t *out_len) { |
|
|
|
if (!cfg || !out || !out_len || out_cap < REALITY_MAX_CH_SIZE) { |
|
|
|
if (!cfg || !out || !out_len || out_cap < REALITY_MAX_CH_SIZE) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: invalid args"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: invalid args"); |
|
|
|
@ -325,37 +322,34 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3 |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: unknown fingerprint %d", cfg->fingerprint); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: unknown fingerprint %d", cfg->fingerprint); |
|
|
|
return REALITY_ERR_ARG; |
|
|
|
return REALITY_ERR_ARG; |
|
|
|
} |
|
|
|
} |
|
|
|
if (cfg->server_name[0] == '\0') { |
|
|
|
if (cfg->server_name[0] == '\0' || !memchr(cfg->server_name, 0, sizeof(cfg->server_name))) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: empty server_name"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: empty server_name"); |
|
|
|
return REALITY_ERR_ARG; |
|
|
|
return REALITY_ERR_ARG; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 1. эфемерный ключ + random
|
|
|
|
// 1. эфемерный ключ + random
|
|
|
|
uint8_t eph_priv[32], eph_pub[32], random32[32]; |
|
|
|
uint8_t eph_priv[32] = {0}, eph_pub[32], random32[32], shared[32] = {0}, auth_key[32] = {0}, plaintext[16] = {0}; |
|
|
|
|
|
|
|
int rc = REALITY_ERR_CRYPTO; |
|
|
|
if (reality_gen_keypair(eph_priv, eph_pub) != 0) { |
|
|
|
if (reality_gen_keypair(eph_priv, eph_pub) != 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: X25519 keygen failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: X25519 keygen failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
if (random_bytes(random32, sizeof(random32)) != 0) { |
|
|
|
if (random_bytes(random32, sizeof(random32)) != 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: random_bytes failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: random_bytes failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. shared + AuthKey
|
|
|
|
// 2. shared + AuthKey
|
|
|
|
uint8_t shared[32], auth_key[32]; |
|
|
|
|
|
|
|
if (reality_x25519(eph_priv, cfg->server_static_pubkey, shared) != 0) { |
|
|
|
if (reality_x25519(eph_priv, cfg->server_static_pubkey, shared) != 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: X25519 derive failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: X25519 derive failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
if (reality_hkdf_sha256(shared, sizeof(shared), random32, 20, |
|
|
|
if (reality_kdf(shared, sizeof(shared), random32, 20, REALITY_HKDF_INFO, auth_key, sizeof(auth_key)) != 0) { |
|
|
|
(const uint8_t *)REALITY_HKDF_INFO, sizeof(REALITY_HKDF_INFO) - 1, |
|
|
|
|
|
|
|
auth_key, sizeof(auth_key)) != 0) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: HKDF failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: HKDF failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 3. plaintext[0:16] = version + reserved + timestamp + short_id
|
|
|
|
// 3. plaintext[0:16] = version + reserved + timestamp + short_id
|
|
|
|
uint8_t plaintext[16]; |
|
|
|
|
|
|
|
memcpy(plaintext, cfg->version, 3); |
|
|
|
memcpy(plaintext, cfg->version, 3); |
|
|
|
plaintext[3] = 0; |
|
|
|
plaintext[3] = 0; |
|
|
|
plaintext[4] = (uint8_t)(now >> 24); |
|
|
|
plaintext[4] = (uint8_t)(now >> 24); |
|
|
|
@ -365,16 +359,16 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3 |
|
|
|
memcpy(plaintext + 8, cfg->short_id, REALITY_SHORT_ID_SIZE); |
|
|
|
memcpy(plaintext + 8, cfg->short_id, REALITY_SHORT_ID_SIZE); |
|
|
|
|
|
|
|
|
|
|
|
// 4. собрать body (SessionId = нули)
|
|
|
|
// 4. собрать body (SessionId = нули)
|
|
|
|
uint8_t body[REALITY_MAX_CH_SIZE]; |
|
|
|
uint8_t body[2048]; |
|
|
|
size_t body_len = 0; |
|
|
|
size_t body_len = 0; |
|
|
|
int rc = reality_build_client_hello_body(cfg, fp, random32, eph_pub, body, sizeof(body), &body_len); |
|
|
|
rc = reality_build_client_hello_body(cfg, fp, random32, eph_pub, body, sizeof(body), &body_len); |
|
|
|
if (rc != REALITY_OK) { |
|
|
|
if (rc != REALITY_OK) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: body build failed rc=%d", rc); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: body build failed rc=%d", rc); |
|
|
|
return rc; |
|
|
|
goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 5. handshake-сообщение = 0x01 + length + body (SessionId на позиции 39..71)
|
|
|
|
// 5. handshake-сообщение = 0x01 + length + body (SessionId на позиции 39..71)
|
|
|
|
uint8_t hs[REALITY_MAX_CH_SIZE]; |
|
|
|
uint8_t hs[2048]; |
|
|
|
size_t hs_len = 4 + body_len; |
|
|
|
size_t hs_len = 4 + body_len; |
|
|
|
hs[0] = TLS_HANDSHAKE_CLIENT_HELLO; |
|
|
|
hs[0] = TLS_HANDSHAKE_CLIENT_HELLO; |
|
|
|
hs[1] = (uint8_t)(body_len >> 16); |
|
|
|
hs[1] = (uint8_t)(body_len >> 16); |
|
|
|
@ -387,7 +381,7 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3 |
|
|
|
if (reality_aes_gcm_seal(auth_key, random32 + 20, hs, hs_len, |
|
|
|
if (reality_aes_gcm_seal(auth_key, random32 + 20, hs, hs_len, |
|
|
|
plaintext, 16, seal_out, seal_out + 16) != 0) { |
|
|
|
plaintext, 16, seal_out, seal_out + 16) != 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: AES-GCM seal failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_client_hello_build: AES-GCM seal failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
memcpy(hs + 39, seal_out, 32); // SessionId = ciphertext || tag
|
|
|
|
memcpy(hs + 39, seal_out, 32); // SessionId = ciphertext || tag
|
|
|
|
|
|
|
|
|
|
|
|
@ -401,15 +395,21 @@ int reality_client_hello_build_at(const struct reality_client_config *cfg, uint3 |
|
|
|
memcpy(out + 5, hs, hs_len); |
|
|
|
memcpy(out + 5, hs, hs_len); |
|
|
|
*out_len = total; |
|
|
|
*out_len = total; |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, |
|
|
|
if (session) { |
|
|
|
"client hello built: total=%zu sn=%s ver=%d.%d.%d short_id=%02x%02x%02x%02x%02x%02x%02x%02x", |
|
|
|
memcpy(session->ephemeral_private, eph_priv, 32); |
|
|
|
total, cfg->server_name, cfg->version[0], cfg->version[1], cfg->version[2], |
|
|
|
memcpy(session->auth_key, auth_key, 32); |
|
|
|
plaintext[8], plaintext[9], plaintext[10], plaintext[11], |
|
|
|
memcpy(session->session_id, seal_out, 32); |
|
|
|
plaintext[12], plaintext[13], plaintext[14], plaintext[15]); |
|
|
|
if (reality_transcript_add(session, hs, hs_len)) { rc = REALITY_ERR_CRYPTO; goto out; } |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "client auth_key=%02x%02x%02x%02x... shared=%02x%02x%02x%02x...", |
|
|
|
} |
|
|
|
auth_key[0], auth_key[1], auth_key[2], auth_key[3], |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "client hello built: total=%zu sni=%s version=%u.%u.%u", total, |
|
|
|
shared[0], shared[1], shared[2], shared[3]); |
|
|
|
cfg->server_name, cfg->version[0], cfg->version[1], cfg->version[2]); |
|
|
|
return REALITY_OK; |
|
|
|
rc = REALITY_OK; |
|
|
|
|
|
|
|
out: |
|
|
|
|
|
|
|
OPENSSL_cleanse(eph_priv, sizeof(eph_priv)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(shared, sizeof(shared)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(auth_key, sizeof(auth_key)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(plaintext, sizeof(plaintext)); |
|
|
|
|
|
|
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ─── Сервер: разбор ClientHello ───
|
|
|
|
// ─── Сервер: разбор ClientHello ───
|
|
|
|
@ -421,79 +421,84 @@ struct reality_ch_parsed { |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Возвращает REALITY_OK / REALITY_ERR_FORMAT.
|
|
|
|
// Возвращает REALITY_OK / REALITY_ERR_FORMAT.
|
|
|
|
|
|
|
|
static uint16_t read_u16(const uint8_t *p) { return ((uint16_t)p[0] << 8) | p[1]; } |
|
|
|
|
|
|
|
|
|
|
|
static int reality_parse_client_hello(const uint8_t *ch, size_t ch_len, |
|
|
|
static int reality_parse_client_hello(const uint8_t *ch, size_t ch_len, |
|
|
|
const uint8_t **hs_out, size_t *hs_len_out, |
|
|
|
const uint8_t **hs_out, size_t *hs_len_out, |
|
|
|
struct reality_ch_parsed *p) { |
|
|
|
struct reality_ch_parsed *p) { |
|
|
|
if (!ch || ch_len < 5 || ch[0] != TLS_RECORD_HANDSHAKE) return REALITY_ERR_FORMAT; |
|
|
|
if (!ch || ch_len < 9 || ch[0] != 22 || ch[1] != 3 || (ch[2] != 1 && ch[2] != 3) || |
|
|
|
size_t rec_len = ((size_t)ch[3] << 8) | ch[4]; |
|
|
|
read_u16(ch + 3) != ch_len - 5 || ch[5] != 1) return REALITY_ERR_FORMAT; |
|
|
|
if (ch_len < 5 + rec_len) return REALITY_ERR_FORMAT; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t *hs = ch + 5; |
|
|
|
const uint8_t *hs = ch + 5; |
|
|
|
size_t hs_len = rec_len; |
|
|
|
size_t n = ch_len - 5; |
|
|
|
if (hs_len < 4 || hs[0] != TLS_HANDSHAKE_CLIENT_HELLO) return REALITY_ERR_FORMAT; |
|
|
|
if (4 + ((size_t)hs[1] << 16) + ((size_t)hs[2] << 8) + hs[3] != n || n < 73 || |
|
|
|
size_t body_len = ((size_t)hs[1] << 16) | ((size_t)hs[2] << 8) | hs[3]; |
|
|
|
hs[4] != 3 || hs[5] != 3 || hs[38] != 32) return REALITY_ERR_FORMAT; |
|
|
|
if (hs_len < 4 + body_len) return REALITY_ERR_FORMAT; |
|
|
|
p->random = hs + 6; p->session_id = hs + 39; p->key_share = NULL; |
|
|
|
|
|
|
|
size_t pos = 71, suites = read_u16(hs + pos); |
|
|
|
const uint8_t *b = hs + 4; |
|
|
|
pos += 2; |
|
|
|
size_t bl = body_len; |
|
|
|
if (!suites || suites % 2 || suites > n - pos) return REALITY_ERR_FORMAT; |
|
|
|
if (bl < 2 + 32 + 1) return REALITY_ERR_FORMAT; |
|
|
|
int cipher_ok = 0, version_ok = 0; |
|
|
|
b += 2; // legacy_version
|
|
|
|
for (size_t i = 0; i < suites; i += 2) if (read_u16(hs + pos + i) == 0x1301) cipher_ok = 1; |
|
|
|
p->random = b; b += 32; |
|
|
|
pos += suites; |
|
|
|
uint8_t sid_len = *b; b += 1; |
|
|
|
if (n - pos < 4 || hs[pos] != 1 || hs[pos + 1] != 0) return REALITY_ERR_FORMAT; |
|
|
|
if (sid_len != 32 || bl < 2 + 32 + 1 + 32) return REALITY_ERR_FORMAT; |
|
|
|
pos += 2; |
|
|
|
p->session_id = b; b += 32; |
|
|
|
size_t extensions = read_u16(hs + pos); |
|
|
|
|
|
|
|
pos += 2; |
|
|
|
// cipher suites
|
|
|
|
if (extensions != n - pos) return REALITY_ERR_FORMAT; |
|
|
|
if (bl < (size_t)(b - (hs + 4)) + 2) return REALITY_ERR_FORMAT; |
|
|
|
uint8_t seen[8192] = {0}; |
|
|
|
uint16_t cs_len = (uint16_t)((b[0] << 8) | b[1]); b += 2; |
|
|
|
while (pos < n) { |
|
|
|
if (bl < (size_t)(b - (hs + 4)) + cs_len + 1) return REALITY_ERR_FORMAT; |
|
|
|
if (n - pos < 4) return REALITY_ERR_FORMAT; |
|
|
|
b += cs_len; // cipher suites
|
|
|
|
unsigned type = read_u16(hs + pos); |
|
|
|
uint8_t comp_len = *b; b += 1; |
|
|
|
size_t len = read_u16(hs + pos + 2); |
|
|
|
if (bl < (size_t)(b - (hs + 4)) + comp_len + 2) return REALITY_ERR_FORMAT; |
|
|
|
pos += 4; |
|
|
|
b += comp_len; // compression
|
|
|
|
if (len > n - pos || (seen[type / 8] & (1 << (type % 8)))) return REALITY_ERR_FORMAT; |
|
|
|
|
|
|
|
seen[type / 8] |= (1 << (type % 8)); |
|
|
|
// extensions
|
|
|
|
const uint8_t *e = hs + pos; |
|
|
|
uint16_t ext_len = (uint16_t)((b[0] << 8) | b[1]); b += 2; |
|
|
|
if (type == TLS_EXT_SUPPORTED_GROUPS || type == TLS_EXT_SIG_ALGS) { |
|
|
|
const uint8_t *ext = b; |
|
|
|
if (len < 4 || read_u16(e) != len - 2 || len % 2) return REALITY_ERR_FORMAT; |
|
|
|
size_t remaining = ext_len; |
|
|
|
} else if (type == TLS_EXT_SUPPORTED_VERSIONS) { |
|
|
|
const uint8_t *end = ext + remaining; |
|
|
|
if (len < 3 || e[0] != len - 1 || !(len % 2)) return REALITY_ERR_FORMAT; |
|
|
|
if (end > hs + 4 + bl) return REALITY_ERR_FORMAT; |
|
|
|
for (size_t i = 1; i < len; i += 2) if (read_u16(e + i) == 0x0304) version_ok = 1; |
|
|
|
|
|
|
|
} else if (type == TLS_EXT_KEY_SHARE) { |
|
|
|
p->key_share = NULL; |
|
|
|
if (len < 2 || read_u16(e) != len - 2) return REALITY_ERR_FORMAT; |
|
|
|
while (remaining >= 4) { |
|
|
|
size_t i = 2; |
|
|
|
uint16_t type = (uint16_t)((ext[0] << 8) | ext[1]); |
|
|
|
while (i < len) { |
|
|
|
uint16_t len = (uint16_t)((ext[2] << 8) | ext[3]); |
|
|
|
if (len - i < 4) return REALITY_ERR_FORMAT; |
|
|
|
if (remaining < 4 + len) return REALITY_ERR_FORMAT; |
|
|
|
unsigned group = read_u16(e + i); |
|
|
|
if (type == TLS_EXT_KEY_SHARE && len >= 4) { |
|
|
|
size_t size = read_u16(e + i + 2); |
|
|
|
uint16_t shares_len = (uint16_t)((ext[4] << 8) | ext[5]); |
|
|
|
i += 4; |
|
|
|
if ((size_t)shares_len > (size_t)(len - 2)) return REALITY_ERR_FORMAT; |
|
|
|
if (!size || size > len - i) return REALITY_ERR_FORMAT; |
|
|
|
const uint8_t *s = ext + 6; |
|
|
|
if (group == TLS_GROUP_X25519) { |
|
|
|
size_t sl = shares_len; |
|
|
|
if (size != 32 || p->key_share) return REALITY_ERR_FORMAT; |
|
|
|
while (sl >= 4) { |
|
|
|
p->key_share = e + i; |
|
|
|
uint16_t group = (uint16_t)((s[0] << 8) | s[1]); |
|
|
|
|
|
|
|
uint16_t klen = (uint16_t)((s[2] << 8) | s[3]); |
|
|
|
|
|
|
|
if (sl < 4 + klen) break; |
|
|
|
|
|
|
|
if (group == TLS_GROUP_X25519 && klen == 32) { |
|
|
|
|
|
|
|
p->key_share = s + 4; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
s += 4 + klen; |
|
|
|
i += size; |
|
|
|
sl -= 4 + klen; |
|
|
|
} |
|
|
|
|
|
|
|
} else if (type == TLS_EXT_ALPN) { |
|
|
|
|
|
|
|
if (len < 4 || read_u16(e) != len - 2) return REALITY_ERR_FORMAT; |
|
|
|
|
|
|
|
size_t i = 2; |
|
|
|
|
|
|
|
while (i < len) { size_t size = e[i++]; if (!size || size > len - i) return REALITY_ERR_FORMAT; i += size; } |
|
|
|
|
|
|
|
} else if (type == TLS_EXT_SERVER_NAME) { |
|
|
|
|
|
|
|
if (len < 6 || read_u16(e) != len - 2) return REALITY_ERR_FORMAT; |
|
|
|
|
|
|
|
size_t i = 2; |
|
|
|
|
|
|
|
while (i < len) { |
|
|
|
|
|
|
|
if (len - i < 3) return REALITY_ERR_FORMAT; |
|
|
|
|
|
|
|
size_t size = read_u16(e + i + 1); i += 3; |
|
|
|
|
|
|
|
if (!size || size > len - i) return REALITY_ERR_FORMAT; |
|
|
|
|
|
|
|
i += size; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
ext += 4 + len; |
|
|
|
pos += len; |
|
|
|
remaining -= 4 + len; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!cipher_ok || !version_ok) return REALITY_ERR_AUTH; |
|
|
|
*hs_out = hs; |
|
|
|
*hs_out = hs; *hs_len_out = n; |
|
|
|
*hs_len_out = hs_len; |
|
|
|
|
|
|
|
return REALITY_OK; |
|
|
|
return REALITY_OK; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int reality_server_hello_build_at(const struct reality_server_config *cfg, uint32_t now_sec, |
|
|
|
static int server_hello_build(struct reality_session *session, const struct reality_server_config *cfg, uint32_t now_sec, |
|
|
|
const uint8_t *ch, size_t ch_len, |
|
|
|
const uint8_t *ch, size_t ch_len, |
|
|
|
uint8_t *out, size_t out_cap, size_t *out_len) { |
|
|
|
uint8_t *out, size_t out_cap, size_t *out_len) { |
|
|
|
if (!cfg || !ch || !out || !out_len || out_cap < REALITY_MAX_SH_SIZE) { |
|
|
|
if (!cfg || !ch || !out || !out_len || out_cap < REALITY_MAX_SH_SIZE || cfg->short_id_count < 1 || |
|
|
|
|
|
|
|
cfg->short_id_count > REALITY_MAX_SHORT_IDS || cfg->time_window_sec < 0 || cfg->time_window_sec > 3600) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: invalid args"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: invalid args"); |
|
|
|
return REALITY_ERR_ARG; |
|
|
|
return REALITY_ERR_ARG; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -512,30 +517,27 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// shared + AuthKey
|
|
|
|
// shared + AuthKey
|
|
|
|
uint8_t shared[32], auth_key[32]; |
|
|
|
uint8_t shared[32] = {0}, auth_key[32] = {0}, plain[16] = {0}, eph_priv[32] = {0}, eph_pub[32], srv_random[32]; |
|
|
|
if (reality_x25519(cfg->static_privkey, p.key_share, shared) != 0) { |
|
|
|
if (reality_x25519(cfg->static_privkey, p.key_share, shared) != 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: X25519 derive failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: X25519 derive failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
if (reality_hkdf_sha256(shared, sizeof(shared), p.random, 20, |
|
|
|
if (reality_kdf(shared, sizeof(shared), p.random, 20, REALITY_HKDF_INFO, auth_key, sizeof(auth_key)) != 0) { |
|
|
|
(const uint8_t *)REALITY_HKDF_INFO, sizeof(REALITY_HKDF_INFO) - 1, |
|
|
|
|
|
|
|
auth_key, sizeof(auth_key)) != 0) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: HKDF failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: HKDF failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// AAD = hs с обнулённым SessionId [39:71]
|
|
|
|
// AAD = hs с обнулённым SessionId [39:71]
|
|
|
|
uint8_t aad[REALITY_MAX_CH_SIZE]; |
|
|
|
uint8_t aad[REALITY_MAX_CH_SIZE]; |
|
|
|
if (hs_len > sizeof(aad)) return REALITY_ERR_FORMAT; |
|
|
|
if (hs_len > sizeof(aad)) { rc = REALITY_ERR_FORMAT; goto out; } |
|
|
|
memcpy(aad, hs, hs_len); |
|
|
|
memcpy(aad, hs, hs_len); |
|
|
|
memset(aad + 39, 0, 32); |
|
|
|
memset(aad + 39, 0, 32); |
|
|
|
|
|
|
|
|
|
|
|
// расшифровать SessionId[0:16]=ciphertext, [16:32]=tag
|
|
|
|
// расшифровать SessionId[0:16]=ciphertext, [16:32]=tag
|
|
|
|
uint8_t plain[16]; |
|
|
|
|
|
|
|
if (reality_aes_gcm_open(auth_key, p.random + 20, aad, hs_len, |
|
|
|
if (reality_aes_gcm_open(auth_key, p.random + 20, aad, hs_len, |
|
|
|
p.session_id, 16, p.session_id + 16, plain) != 0) { |
|
|
|
p.session_id, 16, p.session_id + 16, plain) != 0) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: AES-GCM auth failed"); |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: AES-GCM auth failed"); |
|
|
|
return REALITY_ERR_AUTH; |
|
|
|
rc = REALITY_ERR_AUTH; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// сверка версии
|
|
|
|
// сверка версии
|
|
|
|
@ -543,7 +545,7 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3 |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, |
|
|
|
"reality_server_hello_build: version mismatch got=%d.%d.%d res=%d want=%d.%d.%d", |
|
|
|
"reality_server_hello_build: version mismatch got=%d.%d.%d res=%d want=%d.%d.%d", |
|
|
|
plain[0], plain[1], plain[2], plain[3], cfg->version[0], cfg->version[1], cfg->version[2]); |
|
|
|
plain[0], plain[1], plain[2], plain[3], cfg->version[0], cfg->version[1], cfg->version[2]); |
|
|
|
return REALITY_ERR_AUTH; |
|
|
|
rc = REALITY_ERR_AUTH; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
// сверка timestamp
|
|
|
|
// сверка timestamp
|
|
|
|
uint32_t ts = ((uint32_t)plain[4] << 24) | ((uint32_t)plain[5] << 16) | |
|
|
|
uint32_t ts = ((uint32_t)plain[4] << 24) | ((uint32_t)plain[5] << 16) | |
|
|
|
@ -553,58 +555,61 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3 |
|
|
|
if (diff < -(int64_t)cfg->time_window_sec || diff > (int64_t)cfg->time_window_sec) { |
|
|
|
if (diff < -(int64_t)cfg->time_window_sec || diff > (int64_t)cfg->time_window_sec) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: timestamp out of window ts=%u now=%lld diff=%lld", |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: timestamp out of window ts=%u now=%lld diff=%lld", |
|
|
|
ts, (long long)now, (long long)diff); |
|
|
|
ts, (long long)now, (long long)diff); |
|
|
|
return REALITY_ERR_AUTH; |
|
|
|
rc = REALITY_ERR_AUTH; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
// сверка short_id
|
|
|
|
// сверка short_id
|
|
|
|
int sid_ok = 0; |
|
|
|
int sid_ok = 0; |
|
|
|
for (int i = 0; i < cfg->short_id_count; i++) { |
|
|
|
for (int i = 0; i < cfg->short_id_count; i++) { |
|
|
|
if (memcmp(plain + 8, cfg->short_ids[i], REALITY_SHORT_ID_SIZE) == 0) { sid_ok = 1; break; } |
|
|
|
if (CRYPTO_memcmp(plain + 8, cfg->short_ids[i], REALITY_SHORT_ID_SIZE) == 0) { sid_ok = 1; break; } |
|
|
|
} |
|
|
|
} |
|
|
|
if (!sid_ok) { |
|
|
|
if (!sid_ok) { |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: short_id not in list"); |
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: short_id not in list"); |
|
|
|
return REALITY_ERR_AUTH; |
|
|
|
rc = REALITY_ERR_AUTH; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: auth OK short_id=%02x%02x%02x%02x%02x%02x%02x%02x ts=%u", |
|
|
|
if (session) { |
|
|
|
plain[8], plain[9], plain[10], plain[11], plain[12], plain[13], plain[14], plain[15], ts); |
|
|
|
session->auth_timestamp = ts; |
|
|
|
|
|
|
|
memcpy(session->auth_key, auth_key, 32); |
|
|
|
|
|
|
|
if (reality_transcript_add(session, hs, hs_len)) { rc = REALITY_ERR_CRYPTO; goto out; } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "client hello authenticated: timestamp=%u", ts); |
|
|
|
|
|
|
|
|
|
|
|
// ─── собрать ServerHello ───
|
|
|
|
// ─── собрать ServerHello ───
|
|
|
|
const struct reality_fingerprint *fp = reality_fingerprint_get(cfg->fingerprint); |
|
|
|
const struct reality_fingerprint *fp = reality_fingerprint_get(cfg->fingerprint); |
|
|
|
if (!fp) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: unknown fingerprint"); return REALITY_ERR_ARG; } |
|
|
|
if (!fp) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "unknown server fingerprint"); rc = REALITY_ERR_ARG; goto out; } |
|
|
|
|
|
|
|
|
|
|
|
uint8_t eph_priv[32], eph_pub[32], srv_random[32]; |
|
|
|
|
|
|
|
if (reality_gen_keypair(eph_priv, eph_pub) != 0) { |
|
|
|
if (reality_gen_keypair(eph_priv, eph_pub) != 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: keygen failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: keygen failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
(void)eph_priv; |
|
|
|
|
|
|
|
if (random_bytes(srv_random, sizeof(srv_random)) != 0) { |
|
|
|
if (random_bytes(srv_random, sizeof(srv_random)) != 0) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: random_bytes failed"); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "reality_server_hello_build: random_bytes failed"); |
|
|
|
return REALITY_ERR_CRYPTO; |
|
|
|
rc = REALITY_ERR_CRYPTO; goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ServerHello body
|
|
|
|
// ServerHello body
|
|
|
|
uint8_t sh_body[256]; |
|
|
|
uint8_t sh_body[256]; |
|
|
|
struct rbuf b = { sh_body, sizeof(sh_body), 0 }; |
|
|
|
struct rbuf b = { sh_body, sizeof(sh_body), 0 }; |
|
|
|
if (rbuf_put_u16(&b, 0x0303) < 0) return REALITY_ERR_ARG; // legacy_version
|
|
|
|
if (rbuf_put_u16(&b, 0x0303) < 0) { rc = REALITY_ERR_ARG; goto out; } // legacy_version
|
|
|
|
if (rbuf_put(&b, srv_random, 32) < 0) return REALITY_ERR_ARG; // random
|
|
|
|
if (rbuf_put(&b, srv_random, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } // random
|
|
|
|
if (rbuf_put_u8(&b, 32) < 0) return REALITY_ERR_ARG; // session_id_echo len
|
|
|
|
if (rbuf_put_u8(&b, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } // session_id_echo len
|
|
|
|
if (rbuf_put(&b, p.session_id, 32) < 0) return REALITY_ERR_ARG; // echo SessionId клиента
|
|
|
|
if (rbuf_put(&b, p.session_id, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } // echo SessionId клиента
|
|
|
|
if (rbuf_put_u16(&b, fp->server_cipher) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, fp->server_cipher) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
if (rbuf_put_u8(&b, 0) < 0) return REALITY_ERR_ARG; // compression = null
|
|
|
|
if (rbuf_put_u8(&b, 0) < 0) { rc = REALITY_ERR_ARG; goto out; } // compression = null
|
|
|
|
|
|
|
|
|
|
|
|
size_t ext_len_pos = b.len; |
|
|
|
size_t ext_len_pos = b.len; |
|
|
|
if (rbuf_put_u16(&b, 0) < 0) return REALITY_ERR_ARG; // placeholder
|
|
|
|
if (rbuf_put_u16(&b, 0) < 0) { rc = REALITY_ERR_ARG; goto out; } // placeholder
|
|
|
|
// supported_versions
|
|
|
|
// supported_versions
|
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_SUPPORTED_VERSIONS) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_SUPPORTED_VERSIONS) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
if (rbuf_put_u16(&b, 2) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, 2) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
if (rbuf_put_u16(&b, TLS_VERSION_1_3) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, TLS_VERSION_1_3) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
// key_share (server)
|
|
|
|
// key_share (server)
|
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_KEY_SHARE) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, TLS_EXT_KEY_SHARE) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
if (rbuf_put_u16(&b, 36) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, 36) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
if (rbuf_put_u16(&b, TLS_GROUP_X25519) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, TLS_GROUP_X25519) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
if (rbuf_put_u16(&b, 32) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put_u16(&b, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
if (rbuf_put(&b, eph_pub, 32) < 0) return REALITY_ERR_ARG; |
|
|
|
if (rbuf_put(&b, eph_pub, 32) < 0) { rc = REALITY_ERR_ARG; goto out; } |
|
|
|
rbuf_patch_u16(&b, ext_len_pos, (uint16_t)(b.len - ext_len_pos - 2)); |
|
|
|
rbuf_patch_u16(&b, ext_len_pos, (uint16_t)(b.len - ext_len_pos - 2)); |
|
|
|
size_t sh_body_len = b.len; |
|
|
|
size_t sh_body_len = b.len; |
|
|
|
|
|
|
|
|
|
|
|
@ -622,8 +627,18 @@ int reality_server_hello_build_at(const struct reality_server_config *cfg, uint3 |
|
|
|
memcpy(out + 9, sh_body, sh_body_len); |
|
|
|
memcpy(out + 9, sh_body, sh_body_len); |
|
|
|
*out_len = 5 + sh_hs_len; |
|
|
|
*out_len = 5 + sh_hs_len; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (session) { |
|
|
|
|
|
|
|
memcpy(session->ephemeral_private, eph_priv, 32); |
|
|
|
|
|
|
|
if (reality_transcript_add(session, out + 5, sh_hs_len) || session_derive(session, p.key_share)) { rc = REALITY_ERR_CRYPTO; goto out; } |
|
|
|
|
|
|
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "server hello built: total=%zu", *out_len); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "server hello built: total=%zu", *out_len); |
|
|
|
return REALITY_OK; |
|
|
|
rc = REALITY_OK; |
|
|
|
|
|
|
|
out: |
|
|
|
|
|
|
|
OPENSSL_cleanse(shared, sizeof(shared)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(auth_key, sizeof(auth_key)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(plain, sizeof(plain)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(eph_priv, sizeof(eph_priv)); |
|
|
|
|
|
|
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int reality_server_hello_build(const struct reality_server_config *cfg, |
|
|
|
int reality_server_hello_build(const struct reality_server_config *cfg, |
|
|
|
@ -631,3 +646,110 @@ int reality_server_hello_build(const struct reality_server_config *cfg, |
|
|
|
uint8_t *out, size_t out_cap, size_t *out_len) { |
|
|
|
uint8_t *out, size_t out_cap, size_t *out_len) { |
|
|
|
return reality_server_hello_build_at(cfg, (uint32_t)time(NULL), ch, ch_len, out, out_cap, out_len); |
|
|
|
return reality_server_hello_build_at(cfg, (uint32_t)time(NULL), ch, ch_len, out, out_cap, out_len); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int reality_client_hello_build_at(const struct reality_client_config *cfg, uint32_t now, |
|
|
|
|
|
|
|
uint8_t *out, size_t cap, size_t *written) { |
|
|
|
|
|
|
|
return client_hello_build(NULL, cfg, now, out, cap, written); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int reality_server_hello_build_at(const struct reality_server_config *cfg, uint32_t now, |
|
|
|
|
|
|
|
const uint8_t *ch, size_t len, uint8_t *out, size_t cap, size_t *written) { |
|
|
|
|
|
|
|
return server_hello_build(NULL, cfg, now, ch, len, out, cap, written); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void reality_session_handshake_done(struct reality_session *s) { |
|
|
|
|
|
|
|
EVP_MD_CTX_free(s->transcript); |
|
|
|
|
|
|
|
s->transcript = NULL; |
|
|
|
|
|
|
|
OPENSSL_cleanse(s->finished_send, 32); |
|
|
|
|
|
|
|
OPENSSL_cleanse(s->finished_recv, 32); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void reality_session_cleanup(struct reality_session *s) { |
|
|
|
|
|
|
|
if (!s) return; |
|
|
|
|
|
|
|
EVP_MD_CTX_free(s->transcript); |
|
|
|
|
|
|
|
OPENSSL_cleanse(s, sizeof(*s)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int reality_transcript_add(struct reality_session *s, const uint8_t *data, size_t len) { |
|
|
|
|
|
|
|
if (!s->transcript) { |
|
|
|
|
|
|
|
s->transcript = EVP_MD_CTX_new(); |
|
|
|
|
|
|
|
if (!s->transcript || EVP_DigestInit_ex(s->transcript, EVP_sha256(), NULL) != 1) goto fail; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (EVP_DigestUpdate(s->transcript, data, len) == 1) return 0; |
|
|
|
|
|
|
|
fail: |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "handshake transcript update failed: size=%zu", len); |
|
|
|
|
|
|
|
return -1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int transcript_digest(struct reality_session *s, uint8_t out[32]) { |
|
|
|
|
|
|
|
EVP_MD_CTX *copy = EVP_MD_CTX_new(); |
|
|
|
|
|
|
|
unsigned n = 0; |
|
|
|
|
|
|
|
int ok = copy && s->transcript && EVP_MD_CTX_copy_ex(copy, s->transcript) == 1 && |
|
|
|
|
|
|
|
EVP_DigestFinal_ex(copy, out, &n) == 1 && n == 32; |
|
|
|
|
|
|
|
EVP_MD_CTX_free(copy); |
|
|
|
|
|
|
|
if (!ok) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "handshake transcript digest failed"); |
|
|
|
|
|
|
|
return ok ? 0 : -1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int session_derive(struct reality_session *s, const uint8_t peer[32]) { |
|
|
|
|
|
|
|
uint8_t ikm[64], hash[32], master[32], c2s[32], s2c[32]; |
|
|
|
|
|
|
|
memcpy(ikm, s->auth_key, 32); |
|
|
|
|
|
|
|
int rc = -1; |
|
|
|
|
|
|
|
if (reality_x25519(s->ephemeral_private, peer, ikm + 32) || transcript_digest(s, hash) || |
|
|
|
|
|
|
|
reality_kdf(ikm, 64, hash, 32, "uTun-reality-record-v2", master, 32) || |
|
|
|
|
|
|
|
reality_kdf(master, 32, NULL, 0, "client-traffic", c2s, 32) || |
|
|
|
|
|
|
|
reality_kdf(master, 32, NULL, 0, "server-traffic", s2c, 32) || |
|
|
|
|
|
|
|
reality_traffic_init(&s->send, s->is_server ? s2c : c2s) || |
|
|
|
|
|
|
|
reality_traffic_init(&s->recv, s->is_server ? c2s : s2c) || |
|
|
|
|
|
|
|
reality_kdf(s->send.secret, 32, NULL, 0, "finished", s->finished_send, 32) || |
|
|
|
|
|
|
|
reality_kdf(s->recv.secret, 32, NULL, 0, "finished", s->finished_recv, 32)) goto out; |
|
|
|
|
|
|
|
s->keys_ready = 1; |
|
|
|
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_REALITY, "record keys ready: role=%s", s->is_server ? "server" : "client"); |
|
|
|
|
|
|
|
rc = 0; |
|
|
|
|
|
|
|
out: |
|
|
|
|
|
|
|
OPENSSL_cleanse(ikm, sizeof(ikm)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(master, sizeof(master)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(c2s, sizeof(c2s)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(s2c, sizeof(s2c)); |
|
|
|
|
|
|
|
OPENSSL_cleanse(s->ephemeral_private, 32); |
|
|
|
|
|
|
|
OPENSSL_cleanse(s->auth_key, 32); |
|
|
|
|
|
|
|
if (rc) DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "record key derivation failed"); |
|
|
|
|
|
|
|
return rc; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int reality_finished(struct reality_session *s, int sending, uint8_t out[32]) { |
|
|
|
|
|
|
|
uint8_t hash[32]; |
|
|
|
|
|
|
|
unsigned n = 0; |
|
|
|
|
|
|
|
if (!s->keys_ready || transcript_digest(s, hash) || |
|
|
|
|
|
|
|
!HMAC(EVP_sha256(), sending ? s->finished_send : s->finished_recv, 32, hash, 32, out, &n) || n != 32) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "Finished calculation failed: sending=%d", sending); |
|
|
|
|
|
|
|
return -1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int reality_session_client_start(struct reality_session *s, const struct reality_client_config *cfg, uint32_t now, |
|
|
|
|
|
|
|
uint8_t *out, size_t cap, size_t *written) { |
|
|
|
|
|
|
|
if (!s || s->transcript || s->keys_ready) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "client Hello context already used or missing"); return REALITY_ERR_ARG; } |
|
|
|
|
|
|
|
return client_hello_build(s, cfg, now, out, cap, written); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int reality_session_server_start(struct reality_session *s, const struct reality_server_config *cfg, uint32_t now, |
|
|
|
|
|
|
|
const uint8_t *ch, size_t len, uint8_t *out, size_t cap, size_t *written) { |
|
|
|
|
|
|
|
if (!s || s->transcript || s->keys_ready) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "server Hello context already used or missing"); return REALITY_ERR_ARG; } |
|
|
|
|
|
|
|
s->is_server = 1; |
|
|
|
|
|
|
|
return server_hello_build(s, cfg, now, ch, len, out, cap, written); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int reality_session_client_accept(struct reality_session *s, const uint8_t *sh, size_t len) { |
|
|
|
|
|
|
|
/* Единственный допустимый ServerHello: предложенные AES-128-GCM, X25519 и TLS 1.3. */ |
|
|
|
|
|
|
|
if (!s || !sh || !s->transcript || s->keys_ready || len != 127 || sh[0] != 22 || sh[1] != 3 || sh[2] != 3 || sh[3] != 0 || sh[4] != 122 || |
|
|
|
|
|
|
|
sh[5] != 2 || sh[6] || sh[7] || sh[8] != 118 || sh[9] != 3 || sh[10] != 3 || sh[43] != 32 || |
|
|
|
|
|
|
|
CRYPTO_memcmp(sh + 44, s->session_id, 32) || sh[76] != 0x13 || sh[77] != 1 || sh[78] || |
|
|
|
|
|
|
|
sh[79] || sh[80] != 46 || memcmp(sh + 81, "\x00\x2b\x00\x02\x03\x04\x00\x33\x00\x24\x00\x1d\x00\x20", 14)) { |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "ServerHello validation failed: size=%zu", len); |
|
|
|
|
|
|
|
return REALITY_ERR_FORMAT; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (reality_transcript_add(s, sh + 5, len - 5) || session_derive(s, sh + 95)) return REALITY_ERR_CRYPTO; |
|
|
|
|
|
|
|
return REALITY_OK; |
|
|
|
|
|
|
|
} |
|
|
|
|