Align crypto documentation with random CCM nonces and public-key node IDs
This commit is contained in:
1 parent
2715faffff
commit
3afd31e561
3 files changed
+42
-24
No files matched your search
@@ -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-и
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in new issue
Block a user