Files
utun2/doc/encrypted_attachments.md
T

119 lines
9.6 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`
нельзя открыть или сохранить как готовое вложение. Цитаты и список чатов показывают
название «Зашифрованное вложение» вместо 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`.
Каждая часть шифруется 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.
## Хранение и разрешения
Локальные таблицы не регистрируются в 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` для этого не используется.
В шапке канала кнопка «Запросы расшифровки» показывает число запросов без решения.
Список содержит участника, вложение, дату и отдельные кнопки выдачи/отказа.
Двойной клик переводит к исходному сообщению. После выдачи видны ожидание доставки
и подтверждённая доставка ключа; отказ также сохраняется.
## Адресный протокол
Router-сервис `ETCP_RT_ID_ATTACHMENT_KEYS=0x40`. Каждый пакет обязан иметь
`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,
лишние байты и отсутствие частичного plaintext после отказа.
`test_encrypted_attachment`: реальные A–S–B/C, скачивание с S при выключенном A,
перезапуски автора с запросами и получателя с ключом, выдача B и отказ C,
отсутствие ключа на S/C, отрицательные control-пакеты, повтор GRANT,
восстановление удалённого plaintext без нового согласия и удаление канала.