diff --git a/src/transport_layer/secure_channel.h b/src/transport_layer/secure_channel.h index f561e33c..73ce9936 100644 --- a/src/transport_layer/secure_channel.h +++ b/src/transport_layer/secure_channel.h @@ -117,6 +117,7 @@ struct sc_stream_sign_state { sc_status_t sc_stream_sign_init(sc_context_t *ctx, struct sc_stream_sign_state *state); sc_status_t sc_stream_sign_verify_init(struct sc_stream_sign_state *state, const uint8_t *ed25519_pubkey); 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. */ sc_status_t sc_stream_sign_final(struct sc_stream_sign_state *state, uint8_t *sig_out, size_t *sig_len); sc_status_t sc_stream_sign_verify(struct sc_stream_sign_state *state, const uint8_t *sig, size_t sig_len); void sc_stream_sign_cleanup(struct sc_stream_sign_state *state); diff --git a/src/transport_layer/secure_channel_doc.md b/src/transport_layer/secure_channel_doc.md index 6e834114..494349ce 100644 --- a/src/transport_layer/secure_channel_doc.md +++ b/src/transport_layer/secure_channel_doc.md @@ -123,18 +123,20 @@ int ok = sc_ed25519_verify(pubkey, msg, msg_len, sig); // SC_OK при успе **Стриминговая (потоковая) подпись:** SHA-512 аккумулятор + one-shot Ed25519 в финале. Подходит для данных любого размера (память O(1)): ```c struct sc_stream_sign_state state; +uint8_t stream_sig[SC_SIGN_SIZE]; +size_t sig_len = sizeof(stream_sig); // Подпись sc_stream_sign_init(&ctx, &state); sc_stream_sign_update(&state, chunk1, len1); sc_stream_sign_update(&state, chunk2, len2); -sc_stream_sign_final(&state, sig, &sig_len); // автоматический cleanup +sc_stream_sign_final(&state, stream_sig, &sig_len); // sig_len — capacity на входе, длина подписи на выходе; cleanup // Верификация sc_stream_sign_verify_init(&state, ed25519_pubkey); sc_stream_sign_update(&state, chunk1, len1); sc_stream_sign_update(&state, chunk2, len2); -int result = sc_stream_sign_verify(&state, sig, sig_len); // SC_OK или SC_ERR_AUTH_FAILED +int result = sc_stream_sign_verify(&state, stream_sig, sig_len); // SC_OK или SC_ERR_AUTH_FAILED ``` **Деривация Ed25519-ключа из X25519:** `sc_derive_ed25519_pubkey(x25519_privkey, ed25519_pubkey)` — SHA-512 от X25519-приватного ключа даёт Ed25519-приватный ключ (32 байта), из которого извлекается Ed25519-публичный ключ. Это позволяет одному ключу обслуживать и ECDH, и подписи. @@ -142,8 +144,8 @@ int result = sc_stream_sign_verify(&state, sig, sig_len); // SC_OK или SC_ER ### 2.5. Деривация node_id ```c -uint64_t node_id = sc_derive_node_id(private_key); -// SHA256(private_key) → первые 8 байт, старший бит обнулён (0x7FFFFFFFFFFFFFFF) +uint64_t node_id = sc_derive_node_id_from_pubkey(public_key); +// SHA256(X25519 public_key[32]) → первые 8 байт memcpy в uint64_t хоста, старший бит обнулён ``` ### 2.6. SHA256-based транскодирование @@ -266,7 +268,7 @@ Nonce не зависит от часов, счётчиков контекста | `sc_stream_sign_init(ctx, state)` | Инициализирует подпись. Извлекает Ed25519-ключ из X25519 через `SHA512(x25519_privkey)`. | | `sc_stream_sign_verify_init(state, ed25519_pubkey)` | Инициализирует верификацию с переданным Ed25519 pubkey. | | `sc_stream_sign_update(state, data, len)` | SHA-512 update (инкрементальный). Нулевая длина — no-op. | -| `sc_stream_sign_final(state, sig_out, &sig_len)` | Завершает SHA-512 → one-shot Ed25519 подпись хэша → **автоматический cleanup**. При успехе `sig_len=64`. | +| `sc_stream_sign_final(state, sig_out, &sig_len)` | Завершает SHA-512 → one-shot Ed25519 подпись хэша → **автоматический cleanup**. На входе sig_len задаёт capacity sig_out (не менее 64), при успехе на выходе 64. | | `sc_stream_sign_verify(state, sig, sig_len)` | Завершает SHA-512 → one-shot Ed25519 проверка → **автоматический cleanup**. Возвращает `SC_OK` или `SC_ERR_AUTH_FAILED`. | | `sc_stream_sign_cleanup(state)` | Ручная очистка (если не вызван final/verify). | @@ -353,15 +355,14 @@ Nonce не зависит от часов, счётчиков контекста ``` - `session_ready` становится 1 только после успешного ECDH через `sc_set_peer_public_key()` -- Контекст привязан к конкретному пиру (peer_public_key фиксирован) -- Для смены пира нужен новый контекст +- Успешный `sc_set_peer_public_key` сохраняет ключ текущего пира и заново вычисляет session_key - Счётчики не синхронизируются между сторонами — каждая сторона считает свои tx/rx независимо --- ## 6. Примечания по безопасности -- **Nonce должен быть уникальным** для каждого шифрования с одним ключом. Здесь nonce строится из `SHA256(random_seed || counter || время)`. При случайном seed и монотонном counter'е коллизии крайне маловероятны. +- **CCM nonce** — 13 байт из OpenSSL RAND_bytes; при отказе CSPRNG encrypt возвращает ошибку без готового пакета. - **CRC32 — не криптографическая защита.** Он дополнительно ловит битовые ошибки после расшифровки (belt-and-suspenders), но основная защита целостности — CCM-тег. - **Обфускация pubkey — не шифрование.** Это XOR с детерминированным хэшем, обратима для любого, кто знает salt и второй pubkey. Защищает от пассивного наблюдения (не видно публичный ключ в открытом виде), но не от активной подмены. -- **OpenSSL EVP API** используется для всех криптоопераций (реализации FIPS-сертифицированы при использовании FIPS-провайдера). +- X25519, AES-CCM, AES-CTR и Ed25519 выполняются через OpenSSL EVP.