Ad Nihilum 0.4.3

Состоялся релиз Ad Nihilum 0.4.3 — минималистичного сервиса для обмена зашифрованными сообщениями по принципу «прочитал — сжег», ориентированного в первую очередь на self-hosting.
Cервер выступает лишь в роли глухого хранилища. Шифрование и расшифровка происходят исключительно на стороне клиента, в браузере (через AES-GCM).
Особенности
- локальное зашифрование и расшифрование, сервер никогда не видит ключа;
- поддержка дополнительного слоя шифрования паролем, о котором (1) не может узнать сервер, (2) нельзя узнать по передаваемой ссылке;
- проект содержит порядка 2200 строк серверного кода на Си и 600 строк клиентского кода на JS, что упрощает аудит;
- Ad Nihilum зависит только от
libmicrohttpd. Для генерации кодов QR поставляется модифицированная версия QRCode.js; - прилагается инструкция по быстрому поднятию локального сервиса без внешнего IP;
- Ad Nihilum работает и на Android, приложен соответствующий скрипт для сборки в Termux;
- однопоточный и синхронный сервер.
Изменения
Масштабный редизайн
- Разделены страницы отправки и получения сообщений, соответствуюзий клиентский код.
- Вообще дизайн был сильно изменён и скорректирован с учётом пожеланий лорчаню
- «Простой» и локальный клиенты:
- добавлен клиент с упрощённым дизайном https://adnihilum.net/simple;
- клиентские страницы можно сохранить локально и использовать из
file://.
Браузерные политики
- внедрен CSP для борьбы с XSS;
- сервер шлёт HSTS.
Прочие изменения
- проект переимнован из Epha-ots;
- куплен домен adnihilum.net;
- TLS обеспечивается при помощи Let«s Encrypt, это стоит иметь ввиду (денег нет);
- упрощена работа с файлами;
- переход на
fat-pointerы; - пофикшены мелкие баги;
- авто-
jsminifyи сборка клиентских файлов при сборке через CMake.
Обзор протокола
Сгенерировать три случайных значения: ключ K, вектор инициализации N и соль S. K — 256 бит, N — 96 бит, S — 128 бит.
Вывести ID из K и S с помощью HKDF на основе SHA-256.
Сформировать строку дополнительных аутентифицированных данных aad — это просто строка вида: id=ID
Если пользователь задал пароль:
- вывести Pk из пароля и S с помощью PBKDF2, SHA-256, 800000 итераций;
- одна и та же соль используется для всего;
- зашифровать данные с помощью AES-GCM, используя ключ Pk, IV/nonce N и передав aad.
Добавить двухбайтовый тег к уже зашифрованным данным, если пароль был, либо к исходным данным, если пароля не было.
Первый байт имеет значение: он показывает, были ли данные зашифрованы паролем:
0x73— данные зашифрованы паролем;0x13— данные не зашифрованы паролем.
Второй байт — постоянное значение 0x37.
Снова зашифровать результат с помощью AES-GCM, используя тот же iv = N и тот же aad. Это даёт финальный шифротекст ct.
Склеить байты в строку: blob = N .. S .. ct
Отправить blob на сервер вместе с ID. Сервер возвращает blob по этому ID и не может его подменить: клиент сначала проверит ID с использованием N и K ещё до расшифровки, а затем — через aad.
Клиент хранит K. K никогда не отправляется на сервер. Pk тоже не отправляется; всё, что связано с паролем, очищается из памяти.
Клиент формирует ссылку: origin/#ID/K
Здесь ID и K — строки в формате base64url.
Когда получатель открывает ссылку:
- браузер отбрасывает всё, что начинается с
#; это называетсяlocation.hash; - клиентское приложение загружается с сервера;
- по моему мнению, это главная дыра: мы фактически снова упираемся в то, что «TLS дырявый»;
- однако ничто не мешает хранить клиент офлайн;
- в идеале должен быть отдельный standalone-клиент.
Клиентский JavaScript проверяет location.hash, и если там есть ID и K, он загружает данные с сервера.
Затем он проверяет их, расшифровывает и, если нужно, запрашивает пароль и расшифровывает ещё раз.
Лицензия
Проект распространяется под GPLv3.
>>> Страница проекта на GitHub
>>> Сервис
