media: share bounded content type validation in encrypted containers
This commit is contained in:
1 parent
86b7f9cf44
commit
f63d640c95
2 files changed
+30
-7
No files matched your search
@@ -14,20 +14,38 @@
|
||||
#define EF_HEADER 66
|
||||
#define EF_META (32 + ATTACHMENT_WIRE_MAX)
|
||||
|
||||
/* Одно ограничение поля для отправителя и аутентифицированных метаданных reader. */
|
||||
int encrypted_file_validate_type(const char* type) {
|
||||
size_t length = type ? strnlen(type, 32) : 0;
|
||||
if (!length || length == 32) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "encrypted file: invalid content type length=%zu present=%d allowed=1..31", length, !!type);
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
unsigned byte = (unsigned char)type[i];
|
||||
if (byte < 33 || byte > 126 || byte == '"' || byte == '\\') {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "encrypted file: invalid content type byte index=%zu value=0x%02x length=%zu", i, byte, length);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "encrypted file: content type validated length=%zu", length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Подготовить контейнер в эксклюзивном temp; конечный target не открывается для записи. */
|
||||
int encrypted_file_write(const char* source, const char* target, struct file_io_temp* output, const uint8_t key[32],
|
||||
uint64_t author, const uint8_t id[16], const struct attachment_info* info, const char* type) {
|
||||
if (!output || output->file || output->temporary[0]) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "encrypted file: write requires inactive output owner"); return -1;
|
||||
}
|
||||
if (encrypted_file_validate_type(type)) return -1;
|
||||
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;
|
||||
const char* reason = "invalid arguments";
|
||||
if (!source || !target || !key || !author || !id ||
|
||||
!type || strlen(type) >= 32 || attachment_validate(info)) goto done;
|
||||
if (!source || !target || !key || !author || !id || attachment_validate(info)) goto done;
|
||||
reason = "source/target alias or invalid file type";
|
||||
if (file_io_paths(source, target)) goto done;
|
||||
reason = "cannot open source";
|
||||
@@ -100,10 +118,9 @@ int encrypted_file_read(const char* source, const char* target, struct file_io_t
|
||||
reason = "metadata authentication failed";
|
||||
if (media_ccm_decrypt(key, nonce, cipher, encrypted_size, meta, &length)) goto done;
|
||||
reason = "invalid attachment metadata";
|
||||
if (length < 32 ||
|
||||
!memchr(meta, 0, 32) || !meta[0] || attachment_decode(meta + 32, length - 32, &result)) goto done;
|
||||
if (length < 32 || attachment_decode(meta + 32, length - 32, &result)) goto done;
|
||||
reason = "invalid content type";
|
||||
for (unsigned i = 0; i < 32 && meta[i]; i++) if (meta[i] < 33 || meta[i] > 126 || meta[i] == '"' || meta[i] == '\\') goto done;
|
||||
if (encrypted_file_validate_type((const char*)meta)) goto done;
|
||||
memcpy(result_type, meta, 32);
|
||||
for (unsigned i = 0; i < 8; i++) size = (size << 8) | header[24 + i];
|
||||
reason = "cannot open temporary plaintext";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* Контейнер группового вложения: публичный заголовок, зашифрованные метаданные и
|
||||
* порции AES-256-CCM. Полный SHA256 контейнера берётся из подписанного сообщения.
|
||||
* Вызывать только в media_async worker. Write/read готовят отдельный durable temp,
|
||||
* Write/read вызывать только в media_async worker: они готовят отдельный durable temp,
|
||||
* конечное имя публикует владелец через file_io_temp_publish. Source не изменяется. */
|
||||
#ifndef ENCRYPTED_FILE_H
|
||||
#define ENCRYPTED_FILE_H
|
||||
@@ -10,11 +10,17 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
#define ENCRYPTED_ATTACHMENT_TYPE "application/x-utun-encrypted"
|
||||
/* Ограничение MIME существующего контейнера: NUL-строка длиной 1..31 байт,
|
||||
* ASCII 33..126 без двойной кавычки и обратной косой черты. NULL/нет NUL в первых 32 байтах → -1.
|
||||
* 0 — подходит, -1 — ошибка с диагностикой без вывода сырой строки. Без I/O, допустим вызов в uasync.
|
||||
* Это проверка представления поля контейнера, а не полный разбор MIME-грамматики. */
|
||||
int encrypted_file_validate_type(const char* type);
|
||||
/* source/target — UTF-8 пути разных файлов; target отсутствует либо regular без конечной ссылки.
|
||||
* output заранее обнулён и имеет стабильный адрес до publish/discard; активный output отклоняется без очистки.
|
||||
* 0 — output содержит закрытый проверенный temp рядом с target, target ещё не изменён.
|
||||
* -1 — ошибка; source/target сохранены, собственный temp передан discard (ошибки удаления логируются).
|
||||
* Ключ — 32 байта, UUID — 16, author!=0.
|
||||
* Ключ — 32 байта, UUID — 16, author!=0. type должен пройти encrypted_file_validate_type;
|
||||
* неверный type отвергается до файловых операций и не меняет output/source/target.
|
||||
* Пути и файлы не подменяются во время вызова; публикация повторно проверяет их идентичность. */
|
||||
int encrypted_file_write(const char* source, const char* target, struct file_io_temp* output, const uint8_t key[32],
|
||||
uint64_t author, const uint8_t id[16], const struct attachment_info* info, const char* type);
|
||||
|
||||
Reference in new issue
Block a user