181 lines
10 KiB
C
181 lines
10 KiB
C
#include <string.h>
|
|
|
|
#include <openssl/crypto.h>
|
|
#include <openssl/evp.h>
|
|
|
|
#include "../../lib/debug_config.h"
|
|
#include "../../lib/mem.h"
|
|
#include "stream_block.h"
|
|
|
|
struct stream_encoder {
|
|
struct stream_descriptor descriptor;
|
|
struct rs_codec *rs;
|
|
uint8_t private_key[SC_PRIVKEY_SIZE];
|
|
uint8_t public_key[SC_PUBKEY_SIZE];
|
|
uint8_t *input;
|
|
uint8_t *wire;
|
|
};
|
|
|
|
/* Проверить ключ трансляции и параметры до расчёта размеров/выделения памяти. */
|
|
int stream_descriptor_validate(const struct stream_descriptor *d) {
|
|
if (!d || !d->group_id || !d->source_node_id || !d->source_epoch || !d->n || d->r >= d->n
|
|
|| d->block_size <= RS_HEADER_SIZE || d->block_size > RS_MAX_BLOCK_SIZE) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "descriptor invalid: present=%d group=%llx source=%llx epoch=%llx n=%u r=%u size=%u",
|
|
d != NULL, d ? (unsigned long long)d->group_id : 0, d ? (unsigned long long)d->source_node_id : 0,
|
|
d ? (unsigned long long)d->source_epoch : 0, d ? d->n : 0, d ? d->r : 0, d ? d->block_size : 0);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
return STREAM_OK;
|
|
}
|
|
|
|
size_t stream_block_size(const struct stream_descriptor *d) {
|
|
return stream_descriptor_validate(d) == STREAM_OK ? d->block_size + STREAM_BLOCK_OVERHEAD : 0;
|
|
}
|
|
|
|
size_t stream_input_size(const struct stream_descriptor *d) {
|
|
return stream_descriptor_validate(d) == STREAM_OK ? (size_t)(d->n - d->r) * (d->block_size - RS_HEADER_SIZE) : 0;
|
|
}
|
|
|
|
/* Создать рабочую память RS и получить публичный ключ именно переданного Ed25519 seed. */
|
|
int stream_encoder_create(struct stream_encoder **out, const struct stream_descriptor *d, const uint8_t private_key[32]) {
|
|
if (!out || *out || !private_key || stream_descriptor_validate(d) != STREAM_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encoder create invalid arguments out=%p key=%p", (void *)out, (const void *)private_key);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
struct stream_encoder *encoder = u_calloc(1, sizeof(*encoder));
|
|
if (!encoder) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encoder allocation failed source=%llx", (unsigned long long)d->source_node_id);
|
|
return STREAM_ERR_MEMORY;
|
|
}
|
|
encoder->descriptor = *d;
|
|
memcpy(encoder->private_key, private_key, sizeof(encoder->private_key));
|
|
EVP_PKEY *key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, private_key, SC_PRIVKEY_SIZE);
|
|
size_t public_length = SC_PUBKEY_SIZE;
|
|
int key_ok = key && EVP_PKEY_get_raw_public_key(key, encoder->public_key, &public_length) == 1 && public_length == SC_PUBKEY_SIZE;
|
|
EVP_PKEY_free(key);
|
|
if (!key_ok) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encoder Ed25519 key initialization failed source=%llx",
|
|
(unsigned long long)d->source_node_id);
|
|
stream_encoder_destroy(encoder);
|
|
return STREAM_ERR_CRYPTO;
|
|
}
|
|
if (rs_init(&encoder->rs, d->n, d->r, d->block_size) != RS_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encoder RS initialization failed n=%u r=%u size=%u", d->n, d->r, d->block_size);
|
|
stream_encoder_destroy(encoder);
|
|
return STREAM_ERR_FEC;
|
|
}
|
|
encoder->input = u_malloc((uint32_t)stream_input_size(d));
|
|
encoder->wire = u_malloc((uint32_t)(d->n * stream_block_size(d)));
|
|
if (!encoder->input || !encoder->wire) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encoder working memory allocation failed n=%u size=%u", d->n, d->block_size);
|
|
stream_encoder_destroy(encoder);
|
|
return STREAM_ERR_MEMORY;
|
|
}
|
|
*out = encoder;
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "encoder created group=%llx source=%llx epoch=%llx FEC=%u+%u block=%zu input=%zu",
|
|
(unsigned long long)d->group_id, (unsigned long long)d->source_node_id, (unsigned long long)d->source_epoch,
|
|
d->n - d->r, d->r, stream_block_size(d), stream_input_size(d));
|
|
return STREAM_OK;
|
|
}
|
|
|
|
/* Очистить секретный seed и рабочее содержимое до возврата памяти allocator. */
|
|
void stream_encoder_destroy(struct stream_encoder *encoder) {
|
|
if (!encoder) return;
|
|
DEBUG_INFO(DEBUG_CATEGORY_STREAM, "encoder destroy source=%llx epoch=%llx",
|
|
(unsigned long long)encoder->descriptor.source_node_id, (unsigned long long)encoder->descriptor.source_epoch);
|
|
OPENSSL_cleanse(encoder->private_key, sizeof(encoder->private_key));
|
|
if (encoder->input) OPENSSL_cleanse(encoder->input, stream_input_size(&encoder->descriptor));
|
|
u_free(encoder->input);
|
|
u_free(encoder->wire);
|
|
rs_free(encoder->rs);
|
|
u_free(encoder);
|
|
}
|
|
|
|
const uint8_t *stream_encoder_public_key(const struct stream_encoder *encoder) {
|
|
if (!encoder) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encoder public key: null context"); return NULL; }
|
|
return encoder->public_key;
|
|
}
|
|
|
|
/* Сформировать канонический заголовок, RS-кодограммы и подпись каждого блока. */
|
|
int stream_encoder_encode(struct stream_encoder *encoder, uint64_t seq, uint16_t timestamp_ms, uint8_t flags,
|
|
const uint8_t *input, size_t length, const uint8_t **blocks) {
|
|
if (!encoder || !input || !blocks || !length || seq == UINT64_MAX || (flags & ~STREAM_BLOCK_TIME_RESET)
|
|
|| length > stream_input_size(&encoder->descriptor)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encode invalid arguments seq=%llu len=%zu flags=%u", (unsigned long long)seq, length, flags);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
const struct stream_descriptor *d = &encoder->descriptor;
|
|
size_t wire_size = stream_block_size(d);
|
|
uint8_t *rs_blocks[RS_MAX_BLOCKS];
|
|
memcpy(encoder->input, input, length);
|
|
memset(encoder->input + length, 0, stream_input_size(d) - length);
|
|
for (unsigned i = 0; i < d->n; i++) {
|
|
uint8_t *wire = encoder->wire + i * wire_size;
|
|
memcpy(wire, STREAM_BLOCK_DOMAIN, STREAM_BLOCK_DOMAIN_SIZE);
|
|
stream_write_be(wire + STREAM_BLOCK_GROUP_OFF, d->group_id, 8);
|
|
stream_write_be(wire + STREAM_BLOCK_SOURCE_OFF, d->source_node_id, 8);
|
|
stream_write_be(wire + STREAM_BLOCK_EPOCH_OFF, d->source_epoch, 8);
|
|
wire[STREAM_BLOCK_N_OFF] = d->n;
|
|
wire[STREAM_BLOCK_R_OFF] = d->r;
|
|
stream_write_be(wire + STREAM_BLOCK_SIZE_OFF, d->block_size, 2);
|
|
stream_write_be(wire + STREAM_BLOCK_TIMESTAMP_OFF, timestamp_ms, 2);
|
|
stream_write_be(wire + STREAM_BLOCK_LENGTH_OFF, length, 4);
|
|
wire[STREAM_BLOCK_FLAGS_OFF] = flags;
|
|
rs_blocks[i] = wire + STREAM_BLOCK_RS_OFF;
|
|
}
|
|
if (rs_encode(encoder->rs, seq, encoder->input, rs_blocks) != RS_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encode RS failed seq=%llu", (unsigned long long)seq);
|
|
return STREAM_ERR_FEC;
|
|
}
|
|
for (unsigned i = 0; i < d->n; i++) {
|
|
uint8_t *wire = encoder->wire + i * wire_size;
|
|
if (sc_ed25519_sign(encoder->private_key, wire, wire_size - SC_SIGN_SIZE, wire + wire_size - SC_SIGN_SIZE) != SC_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "encode signature failed seq=%llu sub=%u", (unsigned long long)seq, i);
|
|
return STREAM_ERR_CRYPTO;
|
|
}
|
|
}
|
|
for (unsigned i = 0; i < d->n; i++) blocks[i] = encoder->wire + i * wire_size;
|
|
DEBUG_TRACE(DEBUG_CATEGORY_STREAM, "encoded seq=%llu timestamp=%u len=%zu n=%u flags=%u",
|
|
(unsigned long long)seq, timestamp_ms, length, d->n, flags);
|
|
return STREAM_OK;
|
|
}
|
|
|
|
/* Проверить размеры, область подписи, дескриптор, метаданные и подпись до публикации view. */
|
|
int stream_block_verify(const struct stream_descriptor *d, const uint8_t public_key[32], const uint8_t *wire,
|
|
size_t length, struct stream_block_view *view) {
|
|
if (!public_key || !wire || !view || stream_descriptor_validate(d) != STREAM_OK) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "verify invalid arguments length=%zu", length);
|
|
return STREAM_ERR_ARGUMENT;
|
|
}
|
|
if (length != stream_block_size(d) || memcmp(wire, STREAM_BLOCK_DOMAIN, STREAM_BLOCK_DOMAIN_SIZE)
|
|
|| stream_read_be(wire + STREAM_BLOCK_GROUP_OFF, 8) != d->group_id
|
|
|| stream_read_be(wire + STREAM_BLOCK_SOURCE_OFF, 8) != d->source_node_id
|
|
|| stream_read_be(wire + STREAM_BLOCK_EPOCH_OFF, 8) != d->source_epoch
|
|
|| wire[STREAM_BLOCK_N_OFF] != d->n || wire[STREAM_BLOCK_R_OFF] != d->r
|
|
|| stream_read_be(wire + STREAM_BLOCK_SIZE_OFF, 2) != d->block_size) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "verify format/descriptor mismatch source=%llx epoch=%llx length=%zu expected=%zu",
|
|
(unsigned long long)d->source_node_id, (unsigned long long)d->source_epoch, length, stream_block_size(d));
|
|
return STREAM_ERR_FORMAT;
|
|
}
|
|
struct stream_block_view parsed = {
|
|
.seq = stream_read_be(wire + STREAM_BLOCK_SEQ_OFF, 8),
|
|
.data_length = (uint32_t)stream_read_be(wire + STREAM_BLOCK_LENGTH_OFF, 4),
|
|
.timestamp_ms = (uint16_t)stream_read_be(wire + STREAM_BLOCK_TIMESTAMP_OFF, 2),
|
|
.subblock = wire[STREAM_BLOCK_SUB_OFF], .flags = wire[STREAM_BLOCK_FLAGS_OFF], .rs = wire + STREAM_BLOCK_RS_OFF
|
|
};
|
|
if (parsed.seq == UINT64_MAX || parsed.subblock >= d->n || !parsed.data_length
|
|
|| parsed.data_length > stream_input_size(d) || (parsed.flags & ~STREAM_BLOCK_TIME_RESET)) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "verify invalid metadata seq=%llu sub=%u length=%u flags=%u",
|
|
(unsigned long long)parsed.seq, parsed.subblock, parsed.data_length, parsed.flags);
|
|
return STREAM_ERR_FORMAT;
|
|
}
|
|
if (sc_ed25519_verify(public_key, wire, length - SC_SIGN_SIZE, wire + length - SC_SIGN_SIZE) != SC_OK) {
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "verify source signature failed source=%llx epoch=%llx seq=%llu sub=%u",
|
|
(unsigned long long)d->source_node_id, (unsigned long long)d->source_epoch,
|
|
(unsigned long long)parsed.seq, parsed.subblock);
|
|
return STREAM_ERR_SIGNATURE;
|
|
}
|
|
*view = parsed;
|
|
return STREAM_OK;
|
|
}
|