From 5d11f90cd4d5f35e2b1c971710ced21b1b0f4c7b Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 01:02:10 +0200 Subject: [PATCH] Make streaming signature initialization and cleanup safe --- src/transport_layer/secure_channel.c | 78 ++++++++++++++++++---------- src/transport_layer/secure_channel.h | 19 +++++-- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/transport_layer/secure_channel.c b/src/transport_layer/secure_channel.c index 13c28ab4..8a580d00 100644 --- a/src/transport_layer/secure_channel.c +++ b/src/transport_layer/secure_channel.c @@ -397,42 +397,57 @@ uint64_t sc_derive_node_id_from_pubkey(const uint8_t *public_key) { return node_id; } +// Создаёт ресурсы подписи локально; публикует state только после полной инициализации SHA-512. sc_status_t sc_stream_sign_init(sc_context_t *ctx, struct sc_stream_sign_state *state) { if (!ctx || !state) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; } if (!ctx->initialized || !ctx->pk) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "ctx not initialized"); return SC_ERR_NOT_INITIALIZED; } - memset(state, 0, sizeof(*state)); - if (sc_derive_ed25519_key(ctx->pk->private_key, (EVP_PKEY**)&state->pkey) != SC_OK) return SC_ERR_CRYPTO; - EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); - if (!md_ctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_MD_CTX_new failed"); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO; } - if (EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestInit_ex failed"); - EVP_MD_CTX_free(md_ctx); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO; + if (state->initialized) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream sign init: state=%p already active, mode=%s", (void *)state, state->is_sign ? "sign" : "verify"); + return SC_ERR_INVALID_ARG; } - state->md_ctx = md_ctx; - state->is_sign = 1; - state->initialized = 1; - DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "ok"); + EVP_PKEY *pkey = NULL; + EVP_MD_CTX *md_ctx = NULL; + if (sc_derive_ed25519_key(ctx->pk->private_key, &pkey) != SC_OK) return SC_ERR_CRYPTO; + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream sign init: EVP_MD_CTX_new failed"); goto fail; } + if (EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream sign init: EVP_DigestInit_ex failed"); + goto fail; + } + *state = (struct sc_stream_sign_state){.md_ctx = md_ctx, .pkey = pkey, .initialized = 1, .is_sign = 1}; + DEBUG_DEBUG(DEBUG_CATEGORY_CRYPTO, "stream sign init: state=%p mode=sign", (void *)state); return SC_OK; +fail: + EVP_MD_CTX_free(md_ctx); + EVP_PKEY_free(pkey); + return SC_ERR_CRYPTO; } +// Создаёт ресурсы проверки локально; при отказе пустой state не меняется. sc_status_t sc_stream_sign_verify_init(struct sc_stream_sign_state *state, const uint8_t *ed25519_pubkey) { if (!state || !ed25519_pubkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; } - memset(state, 0, sizeof(*state)); - state->pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed25519_pubkey, SC_PUBKEY_SIZE); - if (!state->pkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_PKEY_new_raw_public_key failed"); return SC_ERR_CRYPTO; } - EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); - if (!md_ctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_MD_CTX_new failed"); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO; } - if (EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestInit_ex failed"); - EVP_MD_CTX_free(md_ctx); EVP_PKEY_free((EVP_PKEY*)state->pkey); return SC_ERR_CRYPTO; + if (state->initialized) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream verify init: state=%p already active, mode=%s", (void *)state, state->is_sign ? "sign" : "verify"); + return SC_ERR_INVALID_ARG; } - state->md_ctx = md_ctx; - state->is_sign = 0; - state->initialized = 1; - DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "ok"); + EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed25519_pubkey, SC_PUBKEY_SIZE); + if (!pkey) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream verify init: EVP_PKEY_new_raw_public_key failed"); return SC_ERR_CRYPTO; } + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream verify init: EVP_MD_CTX_new failed"); goto fail; } + if (EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream verify init: EVP_DigestInit_ex failed"); + goto fail; + } + *state = (struct sc_stream_sign_state){.md_ctx = md_ctx, .pkey = pkey, .initialized = 1, .is_sign = 0}; + DEBUG_DEBUG(DEBUG_CATEGORY_CRYPTO, "stream verify init: state=%p mode=verify", (void *)state); return SC_OK; +fail: + EVP_MD_CTX_free(md_ctx); + EVP_PKEY_free(pkey); + return SC_ERR_CRYPTO; } +// Добавляет фрагмент в SHA-512; нулевая длина ничего не меняет. sc_status_t sc_stream_sign_update(struct sc_stream_sign_state *state, const uint8_t *data, size_t len) { if (len == 0) return SC_OK; if (!state || !data) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; } @@ -444,9 +459,14 @@ sc_status_t sc_stream_sign_update(struct sc_stream_sign_state *state, const uint return SC_OK; } +// Проверяет выходной буфер до финализации; начатая финализация всегда освобождает state. sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *sig_out, size_t *sig_len) { if (!state || !sig_out || !sig_len) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; } if (!state->initialized || !state->is_sign) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "not initialized for sign"); return SC_ERR_NOT_INITIALIZED; } + if (*sig_len < SC_SIGN_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "stream sign final: capacity=%zu requires=%u", *sig_len, (unsigned)SC_SIGN_SIZE); + return SC_ERR_INVALID_ARG; + } uint8_t hash[EVP_MAX_MD_SIZE]; unsigned hash_len = sizeof(hash); if (EVP_DigestFinal_ex((EVP_MD_CTX *)state->md_ctx, hash, &hash_len) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "EVP_DigestFinal_ex failed"); @@ -464,6 +484,7 @@ sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *si return SC_OK; } +// Проверяет подпись накопленного хеша; после начала финализации освобождает state при любом результате. sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint8_t *sig, size_t sig_len) { if (!state || !sig) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; } if (!state->initialized || state->is_sign) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "not initialized for verify"); return SC_ERR_NOT_INITIALIZED; } @@ -484,11 +505,16 @@ sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint return SC_OK; } +// Освобождает принадлежащие state ресурсы и возвращает его в полностью пустое состояние. void sc_stream_sign_cleanup(struct sc_stream_sign_state *state) { if (!state) return; - if (state->md_ctx) { EVP_MD_CTX_free((EVP_MD_CTX *)state->md_ctx); state->md_ctx = NULL; } - if (state->pkey) { EVP_PKEY_free((EVP_PKEY *)state->pkey); state->pkey = NULL; } - state->initialized = 0; + if (state->md_ctx || state->pkey) { + DEBUG_DEBUG(DEBUG_CATEGORY_CRYPTO, "stream sign cleanup: state=%p active=%u mode=%s", + (void *)state, (unsigned)state->initialized, state->is_sign ? "sign" : "verify"); + } + EVP_MD_CTX_free((EVP_MD_CTX *)state->md_ctx); + EVP_PKEY_free((EVP_PKEY *)state->pkey); + *state = (struct sc_stream_sign_state){0}; } // ── One-shot Ed25519 helpers (raw keys, any context) ── diff --git a/src/transport_layer/secure_channel.h b/src/transport_layer/secure_channel.h index 0558f650..49732fe0 100644 --- a/src/transport_layer/secure_channel.h +++ b/src/transport_layer/secure_channel.h @@ -104,22 +104,35 @@ void sc_stream_cleanup(struct sc_stream_state *state); // --- Streaming Ed25519 signature --- // SHA-512 инкрементально (EVP_DigestUpdate) + Ed25519 подпись хеша (one-shot EVP_DigestSign). // Память O(1), не зависит от размера данных. +// Перед первым использованием state = {0}; активный state имеет одного владельца и не копируется. #define SC_SIGN_SIZE 64 // Ed25519 signature size struct sc_stream_sign_state { void *md_ctx; // EVP_MD_CTX* — SHA-512 аккумулятор void *pkey; // EVP_PKEY* — Ed25519 ключ - uint8_t initialized; - uint8_t is_sign; // 1=sign, 0=verify + uint8_t initialized; // 1 — ресурсы готовы; 0 — пустое состояние + uint8_t is_sign; // В активном состоянии 1=sign, 0=verify; после cleanup обнуляется }; +/* init — только для пустого state. Ошибка оставляет его пустым; повторный init активного state + * возвращает SC_ERR_INVALID_ARG без изменения ресурсов и хеша. Оба init: SC_OK / отрицательный SC_ERR_*. + * Подпись использует ключ из initialized ctx->pk; peer/ECDH не требуются, ctx нужен только на время init. */ sc_status_t sc_stream_sign_init(sc_context_t *ctx, struct sc_stream_sign_state *state); +/* ed25519_pubkey — обязательный бинарный ключ[32], копируется OpenSSL во внутренний ресурс state. */ sc_status_t sc_stream_sign_verify_init(struct sc_stream_sign_state *state, const uint8_t *ed25519_pubkey); +/* Добавляет data[len] в активный state; len=0 — no-op, допускает NULL. SC_OK / отрицательный SC_ERR_*. + * При ошибке EVP update владелец должен вызвать cleanup; продолжение хеширования не гарантируется. */ sc_status_t sc_stream_sign_update(struct sc_stream_sign_state *state, const uint8_t *data, size_t len); -/* sig_len задаёт capacity sig_out (не менее 64), при успехе получает длину 64; завершение освобождает sign state. */ +/* sig_len задаёт capacity sig_out (не менее 64), при успехе получает длину 64. SC_OK / отрицательный SC_ERR_*. + * NULL аргументы, недостаточная capacity и неверный режим оставляют state и выходные буферы без изменений. + * После начала финализации освобождает sign state при любом результате; sig_out/sig_len действительны при SC_OK. */ sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *sig_out, size_t *sig_len); +/* Проверяет sig[sig_len] для накопленного хеша в режиме verify; обычная подпись имеет длину 64. + * SC_OK / отрицательный SC_ERR_*; NULL аргументы/неверный режим сохраняют state, + * после начала финализации state освобождается при любом результате, включая неверную подпись. */ sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint8_t *sig, size_t sig_len); +/* Освобождает ресурсы и обнуляет все поля; допускает NULL, пустой state и повторные вызовы. */ void sc_stream_sign_cleanup(struct sc_stream_sign_state *state); sc_status_t sc_ed25519_sign(const uint8_t privkey[32], const uint8_t* msg, size_t msg_len, uint8_t sig_out[64]);