2 changed files with 129 additions and 0 deletions
@ -0,0 +1,108 @@
|
||||
/* Формат v1: magic:8, crypto_UUID:16, plain_size:8BE, meta_size:2BE, body_hash:32,
|
||||
* encrypted_meta, encrypted_file. Metadata nonce имеет part=UINT64_MAX. |
||||
* Полный подписанный hash защищает заголовок вместе с обеими частями ciphertext. */ |
||||
#include "encrypted_file.h" |
||||
#include "media_crypto.h" |
||||
#include "media_async.h" |
||||
#include "../../lib/platform_compat.h" |
||||
#include "../../lib/debug_config.h" |
||||
#include <openssl/crypto.h> |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#ifdef _WIN32 |
||||
#include <io.h> |
||||
#endif |
||||
|
||||
#define EF_HEADER 66 |
||||
#define EF_META (32 + ATTACHMENT_WIRE_MAX) |
||||
|
||||
/* Гарантировать сохранение файла перед публикацией или подтверждением. */ |
||||
int encrypted_file_sync(const char* path) { |
||||
FILE* f = fopen(path, "rb+"); |
||||
if (!f) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "encrypted file: sync open failed path=%s: %s", path, strerror(errno)); return -1; } |
||||
#ifdef _WIN32 |
||||
int rc = _commit(_fileno(f)); |
||||
#else |
||||
int rc = fsync(fileno(f)); |
||||
#endif |
||||
if (fclose(f)) rc = -1; |
||||
if (rc) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "encrypted file: sync failed path=%s: %s", path, strerror(errno)); |
||||
return rc ? -1 : 0; |
||||
} |
||||
|
||||
/* Записать контейнер; частичный destination удаляется при любой ошибке. */ |
||||
int encrypted_file_write(const char* source, const char* destination, const uint8_t key[32], |
||||
uint64_t author, const uint8_t id[16], const struct attachment_info* info, const char* type) { |
||||
FILE* in = NULL; FILE* out = NULL; |
||||
int rc = -1; |
||||
uint8_t header[EF_HEADER] = {0}, meta[EF_META] = {0}, cipher[EF_META + 16], nonce[13], hash[32]; |
||||
size_t length = 0; |
||||
uint64_t size = 0; |
||||
if (!source || !destination || !strcmp(source, destination) || !key || !author || !id || |
||||
!type || strlen(type) >= 32 || attachment_validate(info)) goto done; |
||||
int n = attachment_encode(info, meta + 32, ATTACHMENT_WIRE_MAX); |
||||
if (n < 0) goto done; |
||||
memcpy(meta, type, strlen(type)); |
||||
media_cipher_nonce(author, id, UINT64_MAX, nonce); |
||||
if (media_ccm_encrypt(key, nonce, meta, (size_t)n + 32, cipher, &length)) goto done; |
||||
in = fopen(source, "rb"); out = fopen(destination, "wb"); |
||||
if (!in || !out) goto done; |
||||
memcpy(header, "UTENC001", 8); memcpy(header + 8, id, 16); |
||||
header[32] = (uint8_t)(length >> 8); header[33] = (uint8_t)length; |
||||
if (fwrite(header, 1, sizeof(header), out) != sizeof(header) || fwrite(cipher, 1, length, out) != length || |
||||
media_encrypt_stream(key, author, id, in, out, &size, hash)) goto done; |
||||
for (unsigned i = 0; i < 8; i++) header[24 + i] = (uint8_t)(size >> (56 - i * 8)); |
||||
memcpy(header + 34, hash, 32); |
||||
if (fseeko(out, 0, SEEK_SET) || fwrite(header, 1, sizeof(header), out) != sizeof(header) || fflush(out)) goto done; |
||||
rc = 0; |
||||
done: |
||||
OPENSSL_cleanse(meta, sizeof(meta)); |
||||
if (in && fclose(in)) rc = -1; |
||||
if (out && fclose(out)) rc = -1; |
||||
if (!rc) rc = encrypted_file_sync(destination); |
||||
if (rc) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "encrypted file: preparation failed source=%s: %s", source ? source : "?", strerror(errno)); |
||||
if (out && remove(destination) && errno != ENOENT) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "encrypted file: partial cleanup failed"); |
||||
} else DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "encrypted file: prepared author=%llu bytes=%llu", (unsigned long long)author, |
||||
(unsigned long long)size); |
||||
return rc; |
||||
} |
||||
|
||||
/* Проверить подписанный hash, метаданные и все порции до разрешения публикации. */ |
||||
int encrypted_file_read(const char* source, const char* destination, const uint8_t key[32], uint64_t author, |
||||
const uint8_t id[16], const uint8_t hash[32], struct attachment_info* info, char type[32]) { |
||||
FILE* in = NULL; FILE* out = NULL; |
||||
int rc = -1; |
||||
uint8_t actual[32], header[EF_HEADER], meta[EF_META], cipher[EF_META + 16], nonce[13]; |
||||
size_t length = 0; |
||||
uint64_t size = 0; |
||||
if (!source || !destination || !strcmp(source, destination) || !key || !author || !id || !hash || !info || !type || |
||||
ma_sha256_file(source, actual) || CRYPTO_memcmp(actual, hash, 32)) goto done; |
||||
in = fopen(source, "rb"); |
||||
if (!in || fread(header, 1, sizeof(header), in) != sizeof(header) || memcmp(header, "UTENC001", 8) || |
||||
memcmp(header + 8, id, 16)) goto done; |
||||
size_t encrypted_size = ((size_t)header[32] << 8) | header[33]; |
||||
if (encrypted_size < 32 + 11 + 16 || encrypted_size > sizeof(cipher) || |
||||
fread(cipher, 1, encrypted_size, in) != encrypted_size) goto done; |
||||
media_cipher_nonce(author, id, UINT64_MAX, nonce); |
||||
if (media_ccm_decrypt(key, nonce, cipher, encrypted_size, meta, &length) || length < 32 || |
||||
!memchr(meta, 0, 32) || !meta[0] || attachment_decode(meta + 32, length - 32, info)) goto done; |
||||
for (unsigned i = 0; i < 32 && meta[i]; i++) if (meta[i] < 33 || meta[i] > 126 || meta[i] == '"' || meta[i] == '\\') goto done; |
||||
memcpy(type, meta, 32); |
||||
for (unsigned i = 0; i < 8; i++) size = (size << 8) | header[24 + i]; |
||||
out = fopen(destination, "wb"); |
||||
if (!out || media_decrypt_stream(key, author, id, size, header + 34, in, out)) goto done; |
||||
rc = 0; |
||||
done: |
||||
OPENSSL_cleanse(meta, sizeof(meta)); |
||||
if (in && fclose(in)) rc = -1; |
||||
if (out && fclose(out)) rc = -1; |
||||
if (!rc) rc = encrypted_file_sync(destination); |
||||
if (rc) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "encrypted file: verification/decryption failed source=%s", source ? source : "?"); |
||||
if (out && remove(destination) && errno != ENOENT) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "encrypted file: rejected output cleanup failed"); |
||||
} else DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "encrypted file: verified author=%llu bytes=%llu", (unsigned long long)author, |
||||
(unsigned long long)size); |
||||
return rc; |
||||
} |
||||
@ -0,0 +1,21 @@
|
||||
/* Контейнер группового вложения: публичный заголовок, зашифрованные метаданные и
|
||||
* порции AES-256-CCM. Полный SHA256 контейнера берётся из подписанного сообщения. |
||||
* Все функции файловые, вызываются только в media_async worker. Выход decrypt — |
||||
* временный файл; публиковать его можно только после успеха и durable сохранения. */ |
||||
#ifndef ENCRYPTED_FILE_H |
||||
#define ENCRYPTED_FILE_H |
||||
#include "attachment.h" |
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
#define ENCRYPTED_ATTACHMENT_TYPE "application/x-utun-encrypted" |
||||
int encrypted_file_write(const char* source, const char* destination, const uint8_t key[32], |
||||
uint64_t author, const uint8_t id[16], const struct attachment_info* info, const char* type); |
||||
int encrypted_file_read(const char* source, const char* destination, const uint8_t key[32], uint64_t author, |
||||
const uint8_t id[16], const uint8_t hash[32], struct attachment_info* info, char type[32]); |
||||
/* Flush/fsync и close проверяет владелец; этот примитив сохраняет уже закрытый файл. */ |
||||
int encrypted_file_sync(const char* path); |
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif |
||||
Loading…
Reference in new issue