From 467d2e7605130ef327c4e17097c75957c34d0dc1 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 1 Oct 2026 14:33:45 +0300 Subject: [PATCH] Prepare DM media as authenticated records using target key --- src/dm/dm_crypto.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++ src/dm/dm_crypto.h | 17 +++++++++ 2 files changed, 104 insertions(+) diff --git a/src/dm/dm_crypto.c b/src/dm/dm_crypto.c index 50226472..f77c3c94 100644 --- a/src/dm/dm_crypto.c +++ b/src/dm/dm_crypto.c @@ -147,3 +147,90 @@ int dm_decrypt(const uint8_t key[DM_CONTENT_KEY_SIZE], const uint8_t nonce[DM_NO const uint8_t* ct, size_t ct_len, uint8_t* out, size_t* out_len) { return dm_ccm_crypt(key, nonce, ct, ct_len, out, out_len, 0, NULL); } + +/* Каждая порция имеет собственный nonce, ciphertext неизменен для всех сетевых повторов. */ +int dm_media_encrypt_stream(const uint8_t key[DM_CONTENT_KEY_SIZE], uint64_t author, const uint8_t media_id[16], + FILE* src, FILE* dst, uint64_t* plain_size, uint8_t ciphertext_hash[32]) { + if (!key || !author || !media_id || !src || !dst || src == dst || !plain_size || !ciphertext_hash) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: invalid media encryption arguments", DM_ID); + return -1; + } + *plain_size = 0; + memset(ciphertext_hash, 0, 32); + uint8_t plain[DM_MEDIA_PLAIN_SIZE], cipher[DM_MEDIA_PLAIN_SIZE + DM_TAG_SIZE], nonce[DM_NONCE_SIZE]; + uint64_t part = 0, total = 0; + SHA256_CTX hash; + SHA256_Init(&hash); + for (;;) { + size_t n = fread(plain, 1, sizeof(plain), src); + if (ferror(src)) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: media source read failed part=%llu", DM_ID, (unsigned long long)part); + return -1; + } + if (!n && part) break; + size_t clen = 0; + dm_build_media_nonce(author, media_id, part, nonce); + if (dm_encrypt(key, nonce, plain, n, cipher, &clen) != 0) return -1; + if (fwrite(cipher, 1, clen, dst) != clen) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: media ciphertext write failed part=%llu", DM_ID, (unsigned long long)part); + return -1; + } + SHA256_Update(&hash, cipher, clen); + total += n; + part++; + if (n < sizeof(plain)) break; + } + if (fflush(dst) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: media ciphertext flush failed", DM_ID); + return -1; + } + SHA256_Final(ciphertext_hash, &hash); + *plain_size = total; + DEBUG_DEBUG(DEBUG_CATEGORY_DM, "%s: media encrypted author=%llu plain=%llu records=%llu", DM_ID, + (unsigned long long)author, (unsigned long long)total, (unsigned long long)part); + return 0; +} + +/* Размер и хеш берутся из подписанного manifest; никакого size-only принятия файла. */ +int dm_media_decrypt_stream(const uint8_t key[DM_CONTENT_KEY_SIZE], uint64_t author, const uint8_t media_id[16], + uint64_t plain_size, const uint8_t ciphertext_hash[32], FILE* src, FILE* dst) { + if (!key || !author || !media_id || !ciphertext_hash || !src || !dst || src == dst) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: invalid media decryption arguments", DM_ID); + return -1; + } + uint8_t plain[DM_MEDIA_PLAIN_SIZE], cipher[DM_MEDIA_PLAIN_SIZE + DM_TAG_SIZE], nonce[DM_NONCE_SIZE]; + uint64_t remaining = plain_size, part = 0; + SHA256_CTX hash; + SHA256_Init(&hash); + do { + size_t n = remaining > DM_MEDIA_PLAIN_SIZE ? DM_MEDIA_PLAIN_SIZE : (size_t)remaining; + size_t clen = n + DM_TAG_SIZE, plen = 0; + if (fread(cipher, 1, clen, src) != clen) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: media ciphertext short/read failure part=%llu expected=%zu", DM_ID, + (unsigned long long)part, clen); + return -1; + } + SHA256_Update(&hash, cipher, clen); + dm_build_media_nonce(author, media_id, part, nonce); + if (dm_decrypt(key, nonce, cipher, clen, plain, &plen) != 0) return -1; + if (plen != n || fwrite(plain, 1, plen, dst) != plen) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: media plaintext write/length failure part=%llu", DM_ID, (unsigned long long)part); + return -1; + } + remaining -= n; + part++; + } while (remaining); + uint8_t actual_hash[32]; + SHA256_Final(actual_hash, &hash); + if (fgetc(src) != EOF || ferror(src) || memcmp(actual_hash, ciphertext_hash, 32)) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: media ciphertext size/hash mismatch plain=%llu", DM_ID, (unsigned long long)plain_size); + return -1; + } + if (fflush(dst) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: media plaintext flush failed", DM_ID); + return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_DM, "%s: media verified author=%llu plain=%llu records=%llu", DM_ID, + (unsigned long long)author, (unsigned long long)plain_size, (unsigned long long)part); + return 0; +} diff --git a/src/dm/dm_crypto.h b/src/dm/dm_crypto.h index f59f6f42..2b1c55d8 100644 --- a/src/dm/dm_crypto.h +++ b/src/dm/dm_crypto.h @@ -12,6 +12,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -49,6 +50,22 @@ int dm_encrypt(const uint8_t key[DM_CONTENT_KEY_SIZE], const uint8_t nonce[DM_NO int dm_decrypt(const uint8_t key[DM_CONTENT_KEY_SIZE], const uint8_t nonce[DM_NONCE_SIZE], const uint8_t* ct, size_t ct_len, uint8_t* out, size_t* out_len); +/* Подготовка неизменяемого ciphertext медиа в worker, без БД и сетевых вызовов. + * Используется content_key target, отдельного ключа файла нет. + * Формат: порции по DM_MEDIA_PLAIN_SIZE байт + tag каждой порции; последняя короче. + * Пустой файл содержит один tag. Размер plaintext и SHA256 ciphertext входят в подписанные метаданные. + * src/dst открывает и закрывает владелец; dst — временный файл, при ошибке его нужно отбросить. + * Новый случайный media_id нужен для каждой подготовки, в том числе повторной после ошибки; + * при повторе доставки передаётся уже подготовленный файл, шифрование не повторяется. + * Возвращает 0 после fflush(dst), <0 при ошибке; длительную работу запускать через media_async. */ +int dm_media_encrypt_stream(const uint8_t key[DM_CONTENT_KEY_SIZE], uint64_t author, const uint8_t media_id[16], + FILE* src, FILE* dst, uint64_t* plain_size, uint8_t ciphertext_hash[32]); + +/* Расшифровать в временный dst, проверив все tags, точный размер и хеш ciphertext. + * Публиковать файл разрешено только после успеха и durable сохранения владельцем. */ +int dm_media_decrypt_stream(const uint8_t key[DM_CONTENT_KEY_SIZE], uint64_t author, const uint8_t media_id[16], + uint64_t plain_size, const uint8_t ciphertext_hash[32], FILE* src, FILE* dst); + #ifdef __cplusplus } #endif