Files
utun2/doc/encrypted_attachments.md
T

155 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Зашифрованные вложения группового чата
В desktop chatgui после выбора файла или перетаскивания в чат рядом с его именем
появляется флажок «Зашифровать вложение». Он применяется к выбранному файлу,
изображению или видео. Удаление, замена файла и переход в другую беседу сбрасывают
флажок. Выбор фиксируется вместе с адресатом при отправке, до подготовки.
В DM флажок скрыт: используется существующее автоматическое E2E-шифрование лички.
Android показывает зашифрованное вложение отдельной карточкой со статусом запроса
ключа. После одобрения карточка обновляется на исходный файл, изображение, видео
или голосовое. В интерфейс передаётся только расшифрованный путь; контейнер `.uenc`
нельзя открыть или сохранить как готовое вложение. До расшифровки цитата показывает
название «Зашифрованное вложение», после — обычный preview исходного вложения.
Список чатов сохраняет общее название зашифрованного вложения вместо manifest.
Автор генерирует независимые случайные crypto_UUID и ключ AES-256 на каждую
подготовку. Ключ записывается в локальную SQLite до шифрования. Готовый контейнер
передаётся через обычные media_index/media_delivery. UUID транспортного медиа
и crypto_UUID имеют разные назначения: первый идентифицирует сетевые блоки,
второй — контейнер и ключ. Повтор доставки передаёт прежний контейнер.
## Подписанное сообщение и контейнер
В подписанном сообщении `ct=application/x-utun-encrypted`, базовое имя тела —
`<crypto_UUID:32hex>.uenc`. Остальные поля manifest описывают шифротекст:
размер, SHA256 всего контейнера, UUID и подписи блоков. Ключ, исходное имя,
MIME, waveform и остальные медиаметаданные в историю канала не записываются.
Контейнер:
```text
magic "UTENC001":8
crypto_UUID:16
plaintext_size:8BE
encrypted_metadata_size:2BE
SHA256(encrypted_file_body):32
encrypted_metadata:variable
encrypted_file_body:variable
```
Метаданные содержат MIME в 32 байтах и сериализованный `attachment_info`.
MIME — непустая NUL-строка длиной1..31 байт: ASCII33..126 без двойной кавычки
и обратной косой черты. Общий `encrypted_file_validate_type` применяется в send,
write и read; это проверка представления поля, без полного разбора MIME-грамматики.
Send отклоняет неверное значение до создания задачи/ключа/worker, write — до файловых
операций. Read проверяет расшифрованное аутентифицированное поле и при ошибке сохраняет
прежние info/type. Сырые неверные строки в диагностику не выводятся.
Каждая часть шифруется AES-256-CCM с тегом 16 байт и nonce 13 байт.
Nonce метаданных использует `part=UINT64_MAX`; файл — порции 32768 байт
с номерами от нуля. Общие примитивы находятся в `media_async/media_crypto`;
формат существующего DM при их выделении не менялся.
Получатель проверяет подписанный SHA256 полного контейнера, UUID, tag метаданных,
все tags файла, точный размер и hash тела. Результат пишется во временный файл
и публикуется после успешной проверки и durable rename. Подготовка файлов
и криптография выполняются в media_async worker.
`encrypted_file_write/read` возвращают закрытый проверенный `file_io_temp`,
не изменяя конечное назначение. Задача владеет им до publish/discard; запись идёт
в эксклюзивно созданный соседний файл с правами 0600 на POSIX. Одинаковые файлы,
включая `/./`, hardlink и конечную ссылку назначения, отклоняются до подготовки;
публикация повторно проверяет идентичность. При ошибке read не выдаёт новые метаданные.
Режим `FILE_IO_DURABLE` действует весь срок temp: flush/sync открытого файла/close,
затем rename и сохранение каталога. После успешного rename ошибка directory sync
не откатывает установленный результат; temp уже отсоединён от владельца.
Completion не удаляет файлы по заранее вычисленному имени: очистка учитывает,
какие артефакты задача действительно создала или опубликовала.
Реестр `file_io` защищает активные temp от очистки кеша, включая время после close.
Восстановление до запуска workers и очистка после их завершения удаляют оставшиеся
`<UUID>.uenc.tmp.<UUID>` / `<UUID>.file.tmp.<UUID>` в `media/decrypted`.
Конечные файлы и имена других форматов этот проход сохраняет; активные temp другого
instance того же процесса остаются защищены общим реестром.
## Хранение и разрешения
Локальные таблицы не регистрируются в db_sync или Merkle:
- `attachment_keys`: ключ автора/получателя, связь с общим message_id,
crypto_UUID, hash и пути двух копий, расшифрованные метаданные и состояние.
- `attachment_key_requests`: один запрос на `(channel,message_id,peer)`,
направление, решение и дата создания.
- `attachment_key_outbox`: команда, адресат и время следующей попытки.
Шифротекст хранится в `media/<channel>/<crypto_UUID>.uenc`, локальная открытая
копия — в `decrypted/<channel>/<crypto_UUID>.file`. В media_files и объявлениях
держателей остаётся только шифротекст, включая backfill после restart.
Квота медиакеша учитывает обе копии; очистка кеша не удаляет ключ. При повторном
открытии удалённая открытая копия восстанавливается с сохранённым ключом.
После полной проверенной загрузки обычный клиент автоматически запрашивает
ключ автора. Фоновая копия узла с подписанной ролью `storage=yes` запрос не
создаёт; ручная пользовательская загрузка на таком узле запрашивает доступ.
Сетевая классификация `peers.storage` для этого не используется.
В шапке desktop-клиента кнопка «Запросы расшифровки» видна при наличии запросов без решения
и показывает их число.
Список содержит участника, вложение, дату и отдельные кнопки выдачи/отказа.
Двойной клик переводит к исходному сообщению. После выдачи видны ожидание доставки
и подтверждённая доставка ключа; отказ также сохраняется.
## Адресный протокол
Router-сервис `ETCP_RT_ID_ATTACHMENT_KEYS=0x42`. Каждый пакет обязан иметь
`ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED`; промежуточные узлы ключа не видят.
Протокол не создаёт личную беседу и не публикует служебные сообщения в канал.
```text
magic "AK01":4 | command:1 | message_id:32 | crypto_UUID:16 | cipher_hash:32
GRANT дополнительно содержит key:32
REQUEST=1, REQUEST_ACK=2, GRANT=3, DENY=4, RESULT_ACK=5
```
Автор принимает запрос только на собственное опубликованное сообщение с
сохранённым ключом. Участие запрашивающего проверяется по полному подписанному
мемберу, join и дереву приглашений; удалённые мемберы отклоняются. Проверка
повторяется при решении и перед каждой отправкой GRANT. Получатель принимает
ответ только от автора, по своему существующему запросу и точной идентичности
контейнера. Конфликтующие ключи и результаты отклоняются.
REQUEST_ACK отправляется после сохранения запроса. GRANT/DENY появляются
в outbox только после сохранения решения. Полученный ключ сохраняется до
RESULT_ACK и до запуска расшифровки. Повторы возвращают прежнее решение и не
создают новых строк. Таймер обрабатывает ограниченную порцию очереди с
backpressure и повтором через 5 секунд; restart продолжает очереди.
Для доставки нужен живой маршрут обеих сторон: офлайн-mailbox этого сервиса
не реализован. Запрос ждёт появления автора; решение ждёт появления получателя.
Уже выданный ключ невозможно отозвать у получателя. Защита содержимого не
скрывает автора, канал, время и размер шифротекста. Автоматическая публикация
транскрипции зашифрованного голосового в канал не запускается.
## Проверки
`test_encrypted_file`: границы порций, пустой файл, metadata roundtrip,
неверные ключ/автор/UUID/hash, подмена метаданных с пересчитанным внешним hash,
лишние байты, alias обоих API, сохранность существующего назначения/метаданных,
23 сценария отказов I/O с контролем fd/temp, включая ошибку после rename.
Для MIME проверяются все256 значений первого байта, NULL и границы поля,
восемь ранних отказов writer с сохранением файлов/output, три допустимых roundtrip
и семь аутентифицированных некорректных wire-полей с сохранением info/type/target.
`test_media_primitives` проверяет оба режима temp и границу durable-публикации;
`test_attachment_send` проверяет восемь отказов MIME до ключей/worker/файлов,
затем останавливает сервис после настоящего crypto rename до completion.
`test_encrypted_attachment`: реальные A–S–B/C, скачивание с S при выключенном A,
перезапуски автора с запросами и получателя с ключом, выдача B и отказ C,
отсутствие ключа на S/C, отрицательные control-пакеты, повтор GRANT,
восстановление удалённого plaintext без нового согласия и удаление канала.
При startup/restart дополнительно проверяются оба дерева: stale temp удаляются,
открытый и закрытый активные temp, конечные файлы и чужие имена сохраняются.