diff --git a/src/transport_layer/pkt_normalizer_doc.md b/src/transport_layer/pkt_normalizer_doc.md index 24441b95..29263f84 100644 --- a/src/transport_layer/pkt_normalizer_doc.md +++ b/src/transport_layer/pkt_normalizer_doc.md @@ -21,11 +21,15 @@ etcp->normalizer = pn_init(etcp); etcp->send_input_q = etcp->normalizer->input; // единая точка входа для отправки -// Отправка пакета (пример из тестов): +// Отправка кодограммы cmd || payload через публичный API: struct ll_entry* entry = ll_alloc_lldgram(len); +if (!entry) return -1; memcpy(entry->dgram, data, len); entry->len = len; -queue_data_put(pn->input, entry); +if (etcp_send(etcp, entry) < 0) { + queue_dgram_free(entry); queue_entry_free(entry); + return -1; +} // Приём (через callback на pn->output): // queue_set_callback(pn->output, etcp_int_recv, etcp); @@ -75,7 +79,8 @@ Normalizer не удерживает отдельную OOM-очередь ил **`pn_unpacker_reset_state(pn)`** — сбрасывает только unpacker (recvpart). -**`pn_packer_send(pn, data, len)`** — копирует данные в `ll_alloc_lldgram` и кладёт в `pn->input`. Используется только в юнит-тестах. +`pn_packer_send` — вспомогательная функция тестов из .c, в API заголовка не объявлена. +Сервисы отправляют кодограммы через `etcp_send`, учитывая UP, backpressure и владение entry. ### Внутренние callback-и diff --git a/src/transport_layer/secure_channel.h b/src/transport_layer/secure_channel.h index 4e2dd81c..f561e33c 100644 --- a/src/transport_layer/secure_channel.h +++ b/src/transport_layer/secure_channel.h @@ -53,7 +53,7 @@ struct SC_MYKEYS { // Контекст защищенного канала struct secure_channel { - struct SC_MYKEYS* pk; + struct SC_MYKEYS* pk; // заимствованная локальная пара до конца использования ctx /* Ключ пира (после key exchange) */ uint8_t peer_public_key[SC_PUBKEY_SIZE]; uint8_t session_key[SC_SESSION_KEY_SIZE]; /* Derived session key */ @@ -61,8 +61,8 @@ struct secure_channel { uint8_t initialized; uint8_t peer_key_set; uint8_t session_ready; - uint64_t tx_counter; - uint64_t rx_counter; + uint64_t tx_counter; // успешные encrypt; nonce создаётся независимо через CSPRNG + uint64_t rx_counter; // успешные decrypt после проверки CCM tag и CRC }; // Функции инициализации @@ -126,6 +126,8 @@ sc_status_t sc_ed25519_verify(const uint8_t pubkey[32], const uint8_t* msg, size sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed25519_pubkey_out); +/* SHA256(public_key[32]): первые 8 байт memcpy в uint64_t хоста, старший бит обнуляется. + * public_key — бинарный X25519-ключ; при NULL логирует ошибку и возвращает 0. */ uint64_t sc_derive_node_id_from_pubkey(const uint8_t *public_key); int sc_hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len); diff --git a/src/transport_layer/secure_channel_doc.md b/src/transport_layer/secure_channel_doc.md index d91259f6..6e834114 100644 --- a/src/transport_layer/secure_channel_doc.md +++ b/src/transport_layer/secure_channel_doc.md @@ -4,7 +4,8 @@ Модуль реализует криптографический слой uTun: защищённый канал между двумя узлами на основе **X25519** (Elliptic Curve Diffie-Hellman) для обмена сессионным ключом и **AES-128-CCM** (Counter with CBC-MAC) для аутентифицированного шифрования трафика. Дополнительно предоставляет Ed25519 для цифровых подписей (аутентификация данных, происходящих от конкретного узла) и стриминговое шифрование (AES-128-CTR) для больших потоков данных, где нужна только конфиденциальность без аутентификации. -Это **один из важнейших модулей** проекта — каждый ETCP-пакет, проходящий между узлами, шифруется и аутентифицируется через этот канал. +UDP ETCP использует пакетное AES-128-CCM; STCP использует отдельные AES-128-CTR API +этого модуля и CRC кадров. Сетевые handshake и подтверждение эпох выполняются выше, в transport_layer. **Ключевые криптографические операции:** 1. **X25519 ECDH** — получение общего секрета из своего приватного и чужого публичного ключа @@ -43,18 +44,25 @@ sc_set_peer_public_key(&ctx, peer_pubkey, SC_PEER_PUBKEY_BIN); // или SC_PEE // Теперь ctx.session_ready == 1 // ── Шаг 4: Шифрование / дешифрование ── -uint8_t ciphertext[1500]; -size_t ct_len = sizeof(ciphertext); +if (!plaintext_len) return SC_ERR_INVALID_ARG; +uint8_t ciphertext[plaintext_len + SC_NONCE_SIZE + SC_CRC32_SIZE + SC_TAG_SIZE]; +size_t ct_len = 0; // Шифрование: nonce(13) || AES-CCM(plaintext+crc32) || tag(16) -sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ct_len); +int status = sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ct_len); +if (status != SC_OK) return status; // Дешифрование: проверка тега (аутентификация) → проверка CRC32 → возврат plaintext -uint8_t decrypted[1500]; -size_t pt_len = sizeof(decrypted); -sc_decrypt(&ctx, ciphertext, ct_len, decrypted, &pt_len); +uint8_t decrypted[plaintext_len]; +size_t pt_len = 0; +status = sc_decrypt(&ctx, ciphertext, ct_len, decrypted, &pt_len); +if (status != SC_OK) return status; ``` +ct_len/pt_len — выходные длины, не capacity буферов. Для encrypt требуется +plaintext_len + 33 байта, для decrypt — ciphertext_len - 33. ctx заимствует my_keys; +на каждый передаваемый CCM-пакет выбирается новый 13-байтный nonce через OpenSSL RAND_bytes. + ### 2.2. Обфускация pubkey (INIT-пакеты) При отправке INIT-пакета публичный ключ не передаётся в открытом виде. Вместо этого он обфусцируется через XOR с SHA256-хэшем от соли и публичного ключа пира: @@ -80,7 +88,8 @@ sc_obfuscate_pubkey(salt, my_pubkey, received_obfuscated, decrypted_pubkey); // XOR обратим: obfuscated XOR SHA256(salt||my_pubkey) = исходный pubkey отправителя ``` -**Важный нюанс реализации:** в текущей версии `sc_obfuscate_pubkey()` вычисляет два SHA256-хэша (`SHA256(salt||peer_pubkey)` и `SHA256(peer_pubkey||salt)`), но XOR'ит публичный ключ только с первым. Второй хэш вычисляется, но не используется (возможно, задел на будущее). +В реализации `sc_obfuscate_pubkey()` вычисляются два SHA256-хэша, но 32-байтная +маска использует только первый, `SHA256(salt||peer_pubkey)`. Второй хэш на результат не влияет. ### 2.3. Стриминговое шифрование (AES-128-CTR) @@ -155,7 +164,7 @@ sc_sha_transcode(key, key_len, data, data_len); | `SC_PRIVKEY_SIZE` | 32 | Размер приватного ключа X25519 (байт) | | `SC_PUBKEY_SIZE` | 32 | Размер публичного ключа X25519 (байт) | | `SC_HASH_SIZE` | 32 | Размер SHA256-хэша (байт) | -| `SC_NONCE_SIZE` | **13** | Размер nonce для AES-128-CCM. **Ровно 13 байт — требование CCM!** | +| `SC_NONCE_SIZE` | **13** | Фиксированный размер nonce пакетного протокола этого модуля | | `SC_SHARED_SECRET_SIZE` | 32 | Размер общего секрета X25519 (= SC_HASH_SIZE) | | `SC_SESSION_KEY_SIZE` | 16 | Размер сессионного ключа AES-128 (байт) | | `SC_TAG_SIZE` | 16 | Размер аутентификационного тега CCM (байт) | @@ -180,14 +189,14 @@ sc_sha_transcode(key, key_len, data, data_len); - `uint8_t public_key[32]` — публичный ключ X25519 **`struct secure_channel` (sc_context_t)** — контекст защищённого канала между двумя конкретными узлами: -- `pk` — указатель на `SC_MYKEYS` (локальные ключи) +- `pk` — заимствованный указатель на `SC_MYKEYS` до конца использования контекста - `peer_public_key[32]` — публичный ключ пира (заполняется после ECDH) - `session_key[16]` — производный сессионный ключ AES-128 - `initialized` — флаг: `sc_init_ctx()` вызван - `peer_key_set` — флаг: `sc_set_peer_public_key()` вызван успешно - `session_ready` — флаг: можно шифровать/дешифровать -- `tx_counter` — счётчик отправленных пакетов (используется в nonce) -- `rx_counter` — счётчик принятых пакетов +- `tx_counter` — счётчик успешных encrypt; генерация nonce от него не зависит +- `rx_counter` — счётчик успешных decrypt после проверки CCM tag и CRC **`struct sc_stream_state`** — состояние стримингового шифра (AES-128-CTR): - `ectx` — `EVP_CIPHER_CTX*` (opaque) @@ -205,7 +214,7 @@ sc_sha_transcode(key, key_len, data, data_len); | Функция | Описание | |---|---| -| `sc_init_ctx(ctx, mykeys)` | Инициализирует контекст канала, привязывает локальные ключи. **Не делает ECDH** — только обнуляет состояние. | +| `sc_init_ctx(ctx, mykeys)` | Заимствует локальные ключи, инициализирует флаги и счётчики; ECDH выполняется при установке ключа пира. | | `sc_generate_keypair(keys)` | Генерирует новую пару X25519 через `EVP_PKEY_keygen`. Случайный приватный ключ. | | `sc_init_local_keys(mykeys, pub_hex, priv_hex)` | Загружает ключи из hex-строк (обычно из конфига). Валидирует публичный ключ (не all-zero). | | `sc_set_peer_public_key(ctx, peer_pubkey, mode)` | **Ключевая операция:** выполняет X25519 ECDH, выводит session_key, устанавливает `session_ready=1`. mode: `SC_PEER_PUBKEY_BIN` или `SC_PEER_PUBKEY_HEX`. | @@ -220,7 +229,9 @@ sc_sha_transcode(key, key_len, data, data_len); | `sc_encrypt(ctx, plaintext, pt_len, ciphertext, &ct_len)` | AES-128-CCM шифрование. Формат вывода: `nonce(13) \|\| AES-CCM(plaintext + CRC32(plaintext)) \|\| tag(16)`. Инкрементирует `tx_counter`. | | `sc_decrypt(ctx, ciphertext, ct_len, plaintext, &pt_len)` | AES-128-CCM дешифрование. Проверяет CCM-тег → проверяет CRC32. Инкрементирует `rx_counter` при успехе. | -**Формат nonce:** `SHA256(random_seed(8) || tx_counter(8) || tv_sec(4) || tv_usec(4))` — truncated to 13 bytes. Использует OpenSSL SHA256 (не sha256.h). +**Nonce:** 13 случайных байт из OpenSSL `RAND_bytes` на каждый encrypt. +При отказе RNG возвращается `SC_ERR_CRYPTO`, выходная длина остаётся нулевой. +Nonce не зависит от часов, счётчиков контекста или параллельных экземпляров ядра. **Проверки при дешифровании:** 1. `ciphertext_len >= SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE` (минимум 13+16+4 = 33 байта) @@ -255,7 +266,7 @@ sc_sha_transcode(key, key_len, data, data_len); | `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=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). | @@ -266,7 +277,7 @@ sc_sha_transcode(key, key_len, data, data_len); | Функция | Описание | |---|---| | `sc_derive_ed25519_pubkey(x25519_privkey, ed25519_pubkey_out)` | Из X25519-приватного ключа (32 байта) выводит Ed25519-публичный ключ (32 байта) через `SHA512(x25519_privkey)`. | -| `sc_derive_node_id(private_key)` | `SHA256(private_key)` → первые 8 байт как uint64_t, старший бит обнулён. Уникальный идентификатор узла. | +| `sc_derive_node_id_from_pubkey(public_key)` | SHA256 бинарного X25519 pubkey[32] → первые 8 байт memcpy в uint64_t хоста, старший бит обнулён. При NULL возвращает 0 и логирует ошибку. | #### Утилиты @@ -282,7 +293,7 @@ sc_sha_transcode(key, key_len, data, data_len); | **OpenSSL SHA** (`openssl/sha.h`) | `SHA256_Init/Update/Final` для построения nonce в `sc_build_nonce()` | | **`../lib/sha256.h`** (`sc_sha256_*`) | SHA256 для вывода session_key, stream nonce, obfuscate pubkey, sha_transcode, node_id | | **`../lib/debug_config.h`** | `DEBUG_ERROR`, `DEBUG_WARN`, `DEBUG_INFO`, `DEBUG_DEBUG` (категория `DEBUG_CATEGORY_CRYPTO`) | -| **`../lib/platform_compat.h`** | `random_bytes()` (для соли и random seed), `utun_gettimeofday()` (для nonce) | +| **`../lib/platform_compat.h`** | Общие платформенные определения; CCM nonce генерируется OpenSSL RAND_bytes | | **`crc32.h`** (`crc32_calc`) | Контроль целостности в шифрованных пакетах | ### 3.5. Отсутствующие функции @@ -300,7 +311,7 @@ sc_sha_transcode(key, key_len, data, data_len); └──────────────┴─────────────────────────────────────┴──────────────┘ ``` -- **nonce:** 13 байт, уникальный для каждого пакета (SHA256 от seed + counter + времени) +- **nonce:** 13 случайных байт из OpenSSL RAND_bytes для каждого пакета - **ciphertext:** AES-128-CCM in-place шифрование `plaintext || CRC32(plaintext)` - **tag:** 16 байт, аутентификационный тег CCM (обеспечивает integrity + authenticity)