Update secure channel library API with CRC32 integration

- Add CRC32 checksum support using IEEE 802.3 polynomial
- Update sc_encrypt/sc_decrypt API: combine tag and CRC32 into single buffer
- Add SC_CRC32_SIZE, SC_MAX_OVERHEAD constants and SC_ERR_CRC_FAILED error code
- Implement CRC32 calculation before encryption and verification after decryption
- Update connection.c to use new API with error statistics tracking
- Modify test files for compatibility with new API
- All tests pass successfully
This commit is contained in:
Evgeny committed 2026-01-15 23:13:08 +03:00
1 parent ec49b12a19
commit 8d8fc4be6e
11 files changed
+509 -579

No files matched your search

+60 -30
View File
@@ -65,6 +65,7 @@ static void etcp_tx_callback(epkt_t* epkt, uint8_t* data, uint16_t len, void* ar
static void app_output_callback(ll_queue_t* q, ll_entry_t* entry, void* arg);
static void process_received_packet(conn_handle_t* conn, uint8_t* data, size_t len);
static void update_stats_from_etcp(conn_handle_t* conn);
static void conn_etcp_reset_callback(epkt_t* epkt, void* arg);
/* Bridge functions for data flow */
static void app_input_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg);
@@ -215,6 +216,7 @@ int conn_connect(conn_handle_t* conn,
/* Настройка callback'ов ETCP */
etcp_set_callback(conn->etcp, etcp_tx_callback, conn);
etcp_set_reset_callback(conn->etcp, conn_etcp_reset_callback, conn);
/* Начальная полоса пропускания: максимальная для uint16_t ≈ 6553 байт/мс */
etcp_set_bandwidth(conn->etcp, 65535); /* в единицах 0.1мс: 6553.5 * 10 */
@@ -455,34 +457,34 @@ static void packer_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg)
uint8_t* data_to_send = data;
uint16_t data_len = (uint16_t)len;
uint8_t* encrypted_buffer = NULL;
/* Encrypt if crypto is ready - TEMPORARILY DISABLED */
/* Encrypt if crypto is ready */
if (conn->crypto_ready) {
/* Allocate buffer for ciphertext + tag */
uint8_t* ciphertext = malloc(len);
uint8_t tag[SC_TAG_SIZE];
sc_status_t status = sc_encrypt(&conn->crypto_ctx, data, len, ciphertext, tag);
if (status == SC_OK) {
/* Combine ciphertext and tag into single buffer */
uint8_t* encrypted_data = malloc(len + SC_TAG_SIZE);
if (encrypted_data) {
memcpy(encrypted_data, ciphertext, len);
memcpy(encrypted_data + len, tag, SC_TAG_SIZE);
data_to_send = encrypted_data;
data_len = (uint16_t)(len + SC_TAG_SIZE);
/* Allocate buffer for encrypted data (plaintext + CRC32 + tag) */
size_t encrypted_len = 0;
encrypted_buffer = malloc(len + SC_MAX_OVERHEAD);
if (encrypted_buffer) {
sc_status_t status = sc_encrypt(&conn->crypto_ctx, data, len, encrypted_buffer, &encrypted_len);
if (status == SC_OK && encrypted_len > 0 && encrypted_len <= len + SC_MAX_OVERHEAD) {
data_to_send = encrypted_buffer;
data_len = (uint16_t)encrypted_len;
} else {
/* Encryption failed, free buffer and fall back to plaintext */
free(encrypted_buffer);
encrypted_buffer = NULL;
/* Увеличиваем счетчик ошибок шифрования */
conn->stats.encryption_errors++;
}
/* Free ciphertext buffer */
free(ciphertext);
}
/* If encryption failed, fall back to plaintext */
}
/* Note: etcp_tx_put copies data, we can free entry after call */
int result = etcp_tx_put(conn->etcp, data_to_send, data_len);
/* Free encrypted buffer if it was allocated */
if (conn->crypto_ready && data_to_send != data) {
free(data_to_send);
/* Free encrypted buffer if it was allocated and used */
if (encrypted_buffer && data_to_send == encrypted_buffer) {
free(encrypted_buffer);
}
queue_entry_free(entry_to_process);
@@ -512,22 +514,28 @@ static void etcp_output_bridge(ll_queue_t* q, ll_entry_t* entry, void* arg)
ll_entry_t* new_entry = NULL;
/* Decrypt if crypto is ready - TEMPORARILY DISABLED */
/* Decrypt if crypto is ready */
if (conn->crypto_ready) {
size_t ciphertext_len = len - SC_TAG_SIZE;
uint8_t* ciphertext = data;
uint8_t* tag = data + ciphertext_len;
/* Allocate buffer for plaintext */
uint8_t* plaintext = malloc(ciphertext_len);
/* Allocate buffer for plaintext (max size: len - SC_MAX_OVERHEAD) */
size_t plaintext_len = 0;
uint8_t* plaintext = malloc(len); /* Достаточно места для худшего случая */
if (plaintext) {
sc_status_t status = sc_decrypt(&conn->crypto_ctx, ciphertext, ciphertext_len, tag, plaintext);
if (status == SC_OK) {
sc_status_t status = sc_decrypt(&conn->crypto_ctx, data, len, plaintext, &plaintext_len);
if (status == SC_OK && plaintext_len > 0 && plaintext_len <= len - SC_MAX_OVERHEAD) {
/* Create new entry with plaintext */
new_entry = queue_entry_new(ciphertext_len);
new_entry = queue_entry_new(plaintext_len);
if (new_entry) {
memcpy(ll_entry_data(new_entry), plaintext, ciphertext_len);
memcpy(ll_entry_data(new_entry), plaintext, plaintext_len);
}
} else {
/* Ошибка расшифрования */
if (status == SC_ERR_CRC_FAILED) {
conn->stats.crc_errors++;
} else if (status == SC_ERR_AUTH_FAILED) {
conn->stats.decryption_errors++;
}
/* Другие ошибки (SC_ERR_INVALID_ARG, SC_ERR_NOT_INITIALIZED и т.д.)
не увеличивают счетчики специфических ошибок */
}
free(plaintext);
}
@@ -740,4 +748,26 @@ static void update_stats_from_etcp(conn_handle_t* conn)
conn->stats.jitter_ms = etcp_get_jitter(conn->etcp) / 10;
/* TODO: получение счетчика ретрансмиссий из ETCP */
}
static void conn_etcp_reset_callback(epkt_t* epkt, void* arg)
{
(void)epkt; /* unused parameter */
conn_handle_t* conn = (conn_handle_t*)arg;
if (!conn || !conn->normalizer) {
return;
}
ETCP_LOG("Connection reset callback invoked, resetting pkt_normalizer state\n");
/* Сброс состояния нормализатора пакетов */
if (conn->normalizer->packer) {
pkt_normalizer_reset_state(conn->normalizer->packer);
}
if (conn->normalizer->unpacker) {
pkt_normalizer_reset_state(conn->normalizer->unpacker);
}
/* Note: Данные в очередях приложения (app_input_queue, app_output_queue) не очищаются,
они остаются для повторной отправки после завершения reset handshake */
}
+3
View File
@@ -118,6 +118,9 @@ typedef struct {
uint32_t fragments_assembled;
uint16_t current_rtt_ms; /* Текущее RTT в миллисекундах */
uint16_t jitter_ms; /* Джиттер в миллисекундах */
uint32_t encryption_errors; /* Ошибки шифрования */
uint32_t decryption_errors; /* Ошибки расшифрования */
uint32_t crc_errors; /* Ошибки CRC32 */
} conn_stats_t;
int conn_get_stats(conn_handle_t* conn, conn_stats_t* stats);
+43 -2
View File
@@ -127,6 +127,15 @@ epkt_t* etcp_init(uasync_t* ua) {
epkt->oldest_missing_id = 0;
epkt->missing_since_time = 0;
// Reset state initialization
epkt->reset_pending = 0;
epkt->reset_ack_received = 0;
epkt->reset_timer = NULL;
epkt->reset_retry_count = 0;
epkt->initialized = 0;
epkt->reset_callback = NULL;
epkt->reset_callback_arg = NULL;
// No timers yet
epkt->next_tx_timer = NULL;
epkt->retransmit_timer = NULL;
@@ -184,6 +193,12 @@ void etcp_set_callback(epkt_t* epkt, etcp_tx_callback_t cb, void* arg) {
epkt->tx_callback_arg = arg;
}
void etcp_set_reset_callback(epkt_t* epkt, etcp_reset_callback_t cb, void* arg) {
if (!epkt) return;
epkt->reset_callback = cb;
epkt->reset_callback_arg = arg;
}
// Get output queue
ll_queue_t* etcp_get_output_queue(epkt_t* epkt) {
return epkt ? epkt->output_queue : NULL;
@@ -229,6 +244,11 @@ void etcp_update_window(epkt_t* epkt) {
void etcp_reset(epkt_t* epkt) {
if (!epkt) return;
// Call reset callback before clearing state
if (epkt->reset_callback) {
epkt->reset_callback(epkt, epkt->reset_callback_arg);
}
// Cancel timers
if (epkt->next_tx_timer) {
uasync_cancel_timeout(epkt->ua, epkt->next_tx_timer);
@@ -485,6 +505,18 @@ static void tx_process(epkt_t* epkt) {
data_len = ll_entry_size(entry);
}
// Block data packets during reset handshake (allow service packets: ACKs, retransmit requests)
// Reset packets are sent via etcp_send_reset independently
if (data_packet && epkt->reset_pending && !epkt->reset_ack_received) {
// Data packet during reset handshake, block transmission
queue_entry_put_first(epkt->tx_queue, entry);
// Schedule next attempt after retransmit timer period
if (!epkt->next_tx_timer) {
epkt->next_tx_timer = uasync_set_timeout(epkt->ua, epkt->retrans_timer_period, epkt, tx_timer_callback);
}
return;
}
// Check window size for data packets
if (data_packet && epkt->window_size != (uint32_t)-1) {
if (epkt->unacked_bytes + data_len > epkt->window_size) {
@@ -676,6 +708,11 @@ static void tx_process(epkt_t* epkt) {
static void retransmit_check(epkt_t* epkt) {
if (!epkt) return;
// Skip retransmission checks during reset handshake
if (epkt->reset_pending && !epkt->reset_ack_received) {
return;
}
uint16_t current_time = get_current_timestamp();
// Threshold = RTT * 1.5
uint16_t threshold = epkt->rtt_avg_10 + epkt->rtt_avg_10 / 2;
@@ -890,13 +927,17 @@ int etcp_rx_input(epkt_t* epkt, uint8_t* pkt, uint16_t len) {
ETCP_LOG("Reset request received\n");
// Send reset ACK
etcp_send_reset_ack(epkt);
// Reset our own state
etcp_reset(epkt);
// Reset our own state only if already initialized
if (epkt->initialized) {
etcp_reset(epkt);
}
} else if (hdr == ETCP_RESET_ACK_HEADER) {
// Reset ACK received
ETCP_LOG("Reset ACK received\n");
epkt->reset_ack_received = 1;
epkt->reset_pending = 0;
// Connection is now initialized
epkt->initialized = 1;
if (epkt->reset_timer) {
uasync_cancel_timeout(epkt->ua, epkt->reset_timer);
epkt->reset_timer = NULL;
+16
View File
@@ -31,6 +31,9 @@ typedef struct epkt epkt_t;
// Тип обратного вызова для отправки пакетов через UDP
typedef void (*etcp_tx_callback_t)(epkt_t* epkt, uint8_t* pkt, uint16_t len, void* arg);
// Тип обратного вызова для уведомления о reset
typedef void (*etcp_reset_callback_t)(epkt_t* epkt, void* arg);
// Основная структура ETCP
struct epkt {
// Очереди
@@ -108,6 +111,11 @@ struct epkt {
uint8_t reset_ack_received; // Reset ACK received
void* reset_timer; // Timer for reset retransmission
uint16_t reset_retry_count; // Number of reset retries
// Reset callback and initialization state
uint8_t initialized; // 1 = connection initialized after first reset handshake
etcp_reset_callback_t reset_callback; // Callback for reset notification
void* reset_callback_arg; // Callback argument
};
// Функции API
@@ -133,6 +141,14 @@ void etcp_free(epkt_t* epkt);
*/
void etcp_set_callback(epkt_t* epkt, etcp_tx_callback_t cb, void* arg);
/**
* @brief Установить обратный вызов для уведомления о reset
* @param epkt Экземпляр ETCP
* @param cb Функция обратного вызова
* @param arg Пользовательский аргумент, передаваемый в обратный вызов
*/
void etcp_set_reset_callback(epkt_t* epkt, etcp_reset_callback_t cb, void* arg);
/**
* @brief Обработать полученный UDP пакет
* @param epkt Экземпляр ETCP
+77 -18
View File
@@ -16,6 +16,42 @@
static const struct uECC_Curve_t *curve = NULL;
/* CRC32 implementation (IEEE 802.3 polynomial 0xEDB88320) */
static uint32_t crc32_table[256];
static int crc32_table_initialized = 0;
static void init_crc32_table(void) {
if (crc32_table_initialized) return;
uint32_t polynomial = 0xEDB88320;
for (uint32_t i = 0; i < 256; i++) {
uint32_t crc = i;
for (int j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >> 1) ^ polynomial;
} else {
crc >>= 1;
}
}
crc32_table[i] = crc;
}
crc32_table_initialized = 1;
}
static uint32_t crc32(const uint8_t *data, size_t len) {
if (!crc32_table_initialized) {
init_crc32_table();
}
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < len; i++) {
uint8_t byte = data[i];
uint8_t table_index = (crc ^ byte) & 0xFF;
crc = (crc >> 8) ^ crc32_table[table_index];
}
return crc ^ 0xFFFFFFFF;
}
static int sc_rng(uint8_t *dest, unsigned size)
{
int fd = open("/dev/urandom", O_RDONLY);
@@ -160,15 +196,17 @@ sc_status_t sc_encrypt(sc_context_t *ctx,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
uint8_t *tag)
size_t *ciphertext_len)
{
uint8_t nonce[SC_NONCE_SIZE];
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
TCCcmMode_t c = &ccm_state;
uint8_t combined_output[plaintext_len + SC_TAG_SIZE];
size_t total_plaintext_len = plaintext_len + SC_CRC32_SIZE;
uint8_t plaintext_with_crc[total_plaintext_len];
uint8_t combined_output[total_plaintext_len + SC_TAG_SIZE];
if (!ctx || !plaintext || !ciphertext || !tag) {
if (!ctx || !plaintext || !ciphertext || !ciphertext_len) {
return SC_ERR_INVALID_ARG;
}
@@ -180,6 +218,14 @@ sc_status_t sc_encrypt(sc_context_t *ctx,
return SC_ERR_INVALID_ARG;
}
/* Добавляем CRC32 к данным */
memcpy(plaintext_with_crc, plaintext, plaintext_len);
uint32_t crc = crc32(plaintext, plaintext_len);
plaintext_with_crc[plaintext_len] = (crc >> 0) & 0xFF;
plaintext_with_crc[plaintext_len + 1] = (crc >> 8) & 0xFF;
plaintext_with_crc[plaintext_len + 2] = (crc >> 16) & 0xFF;
plaintext_with_crc[plaintext_len + 3] = (crc >> 24) & 0xFF;
/* Initialize AES key schedule */
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
@@ -196,14 +242,14 @@ sc_status_t sc_encrypt(sc_context_t *ctx,
/* Encrypt and generate tag */
if (tc_ccm_generation_encryption(combined_output, sizeof(combined_output),
NULL, 0, /* no associated data */
plaintext, plaintext_len,
plaintext_with_crc, total_plaintext_len,
c) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
}
/* Split combined output into ciphertext and tag */
memcpy(ciphertext, combined_output, plaintext_len);
memcpy(tag, combined_output + plaintext_len, SC_TAG_SIZE);
/* Copy ciphertext + tag to output buffer */
memcpy(ciphertext, combined_output, total_plaintext_len + SC_TAG_SIZE);
*ciphertext_len = total_plaintext_len + SC_TAG_SIZE;
ctx->tx_counter++;
@@ -213,16 +259,17 @@ sc_status_t sc_encrypt(sc_context_t *ctx,
sc_status_t sc_decrypt(sc_context_t *ctx,
const uint8_t *ciphertext,
size_t ciphertext_len,
const uint8_t *tag,
uint8_t *plaintext)
uint8_t *plaintext,
size_t *plaintext_len)
{
uint8_t nonce[SC_NONCE_SIZE];
struct tc_aes_key_sched_struct sched;
struct tc_ccm_mode_struct ccm_state;
TCCcmMode_t c = &ccm_state;
uint8_t combined_input[ciphertext_len + SC_TAG_SIZE];
size_t total_plaintext_len = ciphertext_len - SC_TAG_SIZE;
uint8_t plaintext_with_crc[total_plaintext_len];
if (!ctx || !ciphertext || !tag || !plaintext) {
if (!ctx || !ciphertext || !plaintext || !plaintext_len) {
return SC_ERR_INVALID_ARG;
}
@@ -230,14 +277,10 @@ sc_status_t sc_decrypt(sc_context_t *ctx,
return SC_ERR_NOT_INITIALIZED;
}
if (ciphertext_len == 0) {
if (ciphertext_len < SC_TAG_SIZE + SC_CRC32_SIZE) {
return SC_ERR_INVALID_ARG;
}
/* Combine ciphertext and tag for CCM input */
memcpy(combined_input, ciphertext, ciphertext_len);
memcpy(combined_input + ciphertext_len, tag, SC_TAG_SIZE);
/* Initialize AES key schedule */
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) {
return SC_ERR_CRYPTO;
@@ -252,13 +295,29 @@ sc_status_t sc_decrypt(sc_context_t *ctx,
}
/* Decrypt and verify tag */
if (tc_ccm_decryption_verification(plaintext, ciphertext_len,
if (tc_ccm_decryption_verification(plaintext_with_crc, total_plaintext_len,
NULL, 0, /* no associated data */
combined_input, ciphertext_len + SC_TAG_SIZE,
ciphertext, ciphertext_len,
c) != TC_CRYPTO_SUCCESS) {
return SC_ERR_AUTH_FAILED;
}
/* Проверяем CRC32 */
size_t data_len = total_plaintext_len - SC_CRC32_SIZE;
uint32_t expected_crc = crc32(plaintext_with_crc, data_len);
uint32_t received_crc = (plaintext_with_crc[data_len] << 0) |
(plaintext_with_crc[data_len + 1] << 8) |
(plaintext_with_crc[data_len + 2] << 16) |
(plaintext_with_crc[data_len + 3] << 24);
if (expected_crc != received_crc) {
return SC_ERR_CRC_FAILED;
}
/* Копируем данные без CRC32 */
memcpy(plaintext, plaintext_with_crc, data_len);
*plaintext_len = data_len;
ctx->rx_counter++;
return SC_OK;
+19 -13
View File
@@ -16,6 +16,8 @@ extern "C" {
#define SC_SESSION_KEY_SIZE 16 /* AES-128 key size */
#define SC_NONCE_SIZE 13
#define SC_TAG_SIZE 16
#define SC_CRC32_SIZE 4 /* CRC-32 checksum */
#define SC_MAX_OVERHEAD (SC_TAG_SIZE + SC_CRC32_SIZE) /* Максимальное расширение пакета */
/* ===== Коды ошибок ===== */
@@ -24,7 +26,8 @@ typedef enum {
SC_ERR_INVALID_ARG,
SC_ERR_CRYPTO,
SC_ERR_NOT_INITIALIZED,
SC_ERR_AUTH_FAILED
SC_ERR_AUTH_FAILED,
SC_ERR_CRC_FAILED
} sc_status_t;
/* ===== Контекст защищённого канала ===== */
@@ -83,34 +86,37 @@ sc_status_t sc_set_peer_public_key(sc_context_t *ctx,
const uint8_t *peer_public_key);
/**
* @brief Зашифровать сообщение
* @brief Зашифровать сообщение с добавлением CRC32
*
* @param ctx Контекст
* @param plaintext Входные данные
* @param plaintext_len Длина
* @param ciphertext Выход (может совпадать с plaintext)
* @param tag MAC-тег (16 байт)
* @param plaintext_len Длина входных данных
* @param ciphertext Выходной буфер (должен быть размером plaintext_len + SC_MAX_OVERHEAD)
* @param ciphertext_len [out] Длина выходных данных (ciphertext + tag + crc32)
* @return SC_OK при успехе, иначе код ошибки
*/
sc_status_t sc_encrypt(sc_context_t *ctx,
const uint8_t *plaintext,
size_t plaintext_len,
uint8_t *ciphertext,
uint8_t *tag);
size_t *ciphertext_len);
/**
* @brief Расшифровать и проверить сообщение
* @brief Расшифровать и проверить сообщение (включая CRC32)
*
* @param ctx Контекст
* @param ciphertext Зашифрованные данные
* @param ciphertext_len Длина
* @param tag MAC-тег
* @param plaintext Выход
* @param ciphertext Зашифрованные данные (ciphertext + tag + crc32)
* @param ciphertext_len Длина входных данных
* @param plaintext Выходной буфер (должен быть размером ciphertext_len - SC_MAX_OVERHEAD)
* @param plaintext_len [out] Длина расшифрованных данных
* @return SC_OK при успехе, SC_ERR_AUTH_FAILED при ошибке аутентификации,
* SC_ERR_CRC_FAILED при несовпадении CRC32
*/
sc_status_t sc_decrypt(sc_context_t *ctx,
const uint8_t *ciphertext,
size_t ciphertext_len,
const uint8_t *tag,
uint8_t *plaintext);
uint8_t *plaintext,
size_t *plaintext_len);
#ifdef __cplusplus
}