Require signed and encrypted call audio/video using static identity keys. Fix attachment service ID collisions and media download handle lifetime; isolate LAN tests and audit the shared service registry.
9.6 KiB
Зашифрованные вложения группового чата
В 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 и остальные медиаметаданные в историю канала не записываются.
Контейнер:
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=0x42. Каждый пакет обязан иметь
ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED; промежуточные узлы ключа не видят.
Протокол не создаёт личную беседу и не публикует служебные сообщения в канал.
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 без нового согласия и удаление канала.