Files
QSafe/README.md
T

105 lines
28 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.
# QSafe
Приложение для Android и Windows для хранения паролей с зашифрованной синхронизацией на собственный Raspberry Pi. Android 1.1.0, Windows 1.0.4, самостоятельная реализация. SafeInCloud используется только как источник XML-импорта; его закрытый формат `.db` не поддерживается.
## Windows
Готовое приложение: `artifacts/QSafe-Windows-1.0.4/QSafe.exe`, переносимый архив: `artifacts/QSafe-Windows-1.0.4.zip`. Сборка x64 включает .NET и работает без установки. Есть тёмный интерфейс, поиск, категории, избранное, редактор карточек, корзина, генератор, XML-импорт, резервные копии и синхронизация с тем же Pi.
Для существующей Android-базы выбрать **Подключить Pi и синхронизировать**, импортировать личный `connection.qsafe`, сохранить настройки и ввести существующий мастер-пароль. Для знакомства доступна отдельная демонстрационная база с вымышленными данными. Настоящая база находится в `%LOCALAPPDATA%\Kusoft\QSafe`; последние 10 зашифрованных сохранений остаются в `backups`.
Автосинхронизация Windows работает при разблокированном хранилище раз в минуту только внутри домашнего Wi-Fi. Это же обязательное ограничение действует для ручной синхронизации и восстановления базы. Вне дома приложение работает автономно. Ключ подключения защищён Windows DPAPI, мастер-пароль не сохраняется. Вход по Windows Hello и автозаполнение браузеров пока отсутствуют. EXE не подписан. Подробности, сборка и проверки: [windows/README.md](windows/README.md).
## Начало работы
1. Установить `artifacts/QSafe-1.1.0.apk` на Android 9 или новее либо скачать версию 1.1.0 из [QSafe в Аргусе](https://argus.kusoft.xyz/?app=qsafe). Публичная загрузка проверяется по размеру, SHA-256 и подписи APK. Устанавливать поверх прежней версии, без удаления приложения и очистки данных.
2. На новом устройстве нажать **Подключить Pi и синхронизировать**, выбрать обновлённый личный файл `connection-lan.qsafe` и проверить локальный IP Pi, SSID и отпечаток сертификата. На уже настроенном Android импортировать его через настройки подключения. Старые файлы с внешним адресом базы больше не принимаются. Это ключ доступа к синхронизации. Не публиковать файл и не отправлять его в чат.
3. Ввести мастер-пароль существующей базы и нажать **Разблокировать**: зашифрованная база скачивается с Pi и проверяется на устройстве. Пока настройка не завершена, её можно повторить после перезапуска. Для пустого Pi в первом подключении выбрать **Создать новое хранилище** и задать пароль от 12 символов.
4. После создания или восстановления базы на стартовом экране остаются поле мастер-пароля и кнопка **Разблокировать**. При включённом входе по отпечатку системный запрос появляется автоматически; повторить его можно кнопкой разблокировки с пустым полем пароля.
5. Вход по отпечатку предлагается после входа мастер-паролем на устройстве с зарегистрированным отпечатком. Его также можно включить или выключить переключателем **☰ → Защита → Вход по отпечатку**. Изменение зарегистрированных отпечатков аннулирует биометрический ключ; мастер-пароль остаётся способом входа.
## Карточки и обновления
Поле поиска и меню **☰** закреплены над списком. Карточки имеют локальные пиктограммы категорий; обращения к внешним сервисам иконок и передача доменов не выполняются. Постоянная кнопка **+** открывает выбор шаблона: сайт или приложение, банковская карта, электронная почта, Wi-Fi, сервер, документ, секретная заметка или своя карточка.
На Android **☰** открывает полноценную страницу меню с разделами **Внешний вид**, **Синхронизация**, **Защита**, **Данные** и **Приложение**. Автосинхронизация, фоновая синхронизация, вход по отпечатку и автозаполнение имеют отдельные переключатели. Состояние автозаполнения берётся из Android; включение подтверждается в системных настройках, выключение отключает только службу QSafe. При выключении фоновой синхронизации отменяются её задачи и сетевые подписки, а новые события не ставят отключённую функцию в очередь. Подключение Pi, домашняя сеть, импорт и резервные копии открываются отдельными страницами. В настройках Pi пустое поле ключа сохраняет уже установленный ключ.
**☰ → Внешний вид → Тема приложения** предлагает **Как в системе**, **Светлая** и **Тёмная**. Выбор применяется сразу и сохраняется на устройстве. Оформление охватывает список, карточки, формы, генератор и диалоги. В системном режиме смена темы Android не сбрасывает открытый редактор и его несохранённые данные.
Просмотр и редактирование карточки открываются полноценными страницами. Нажатие на поле копирует его значение; секретные поля скрыты до нажатия на значок глаза. Кнопка назад возвращает к списку, а редактор предупреждает о несохранённых изменениях. Если синхронизация получила новую версию во время редактирования, сохраняются обе версии. Ручная синхронизация и блокировка находятся в меню **☰**; автоматическая синхронизация сохранена.
**Сгенерировать пароль** в редакторе Android и Windows открывает отдельное защищённое окно с видимым результатом. Режимы: **Сложный** (16–64 символа, по умолчанию 24; специальные символы можно отключить), **Только цифры** (4–64 цифры, по умолчанию 12) и **Запоминающийся** (2–12 случайных английских слов, по умолчанию 8; цифры и специальные знаки включены по умолчанию и отключаются отдельно). Цифры и знаки размещаются случайно перед первым словом, между словами или после последнего слова; порядок цифр и знаков также случаен. Слова остаются целыми и начинаются с заглавной буквы. Примеры: `57+LemonRiver`, `Lemon7+River5`, `LemonRiver+57`. В последнем режиме длина выбирается числом слов; рядом показано итоговое число символов. Длина запоминается отдельно для каждого режима до закрытия окна. Изменение настроек и **Другой вариант** обновляют предпросмотр; только **Использовать пароль** заменяет поле карточки. Отмена сохраняет прежнее значение. Результат не копируется в буфер автоматически. Слова и цифры выбираются криптографическим генератором случайных чисел; общий встроенный список содержит 256 слов и не требует сети.
**☰ → Проверить обновления** проверяет stable-релиз в Аргусе по HTTPS. Перед установкой проверяются размер и SHA-256 загрузки, имя пакета, числовой код версии и совпадение сертификата подписи с установленным приложением. APK хранится во внутреннем кэше; установщику передаётся доступ только к этому файлу. При необходимости Android предлагает разрешить установку обновлений из QSafe, затем показывает обычное подтверждение установки. Мастер-пароль и база в каталог обновлений не передаются.
## Домашний Wi-Fi
Открыть **☰ → Синхронизация → Домашний Wi-Fi**, указать точное имя домашней сети (SSID) и сохранить. Фоновая работа включается отдельным переключателем **Фоновая синхронизация** в разделе синхронизации. При нехватке параметров или разрешений открывается страница настройки сети. Для владельца этого Pi домашняя сеть — `gen`; личный файл подключения может передать эту настройку автоматически. В публичном APK имя сети и ключ подключения не зашиты.
Чтобы Android разрешал читать SSID при закрытом приложении, нужны точный доступ к местоположению, системная геолокация и на Android 10+ разрешение местоположения **«Всегда»**. Приложение показывает недостающие разрешения и кнопки перехода к настройкам. Координаты не запрашиваются, история перемещений не ведётся. При неизвестном SSID, недостающих разрешениях или подключении к другой сети фоновая синхронизация не выполняется.
Подключение к Wi-Fi отслеживается системным `ConnectivityManager`: живой callback обрабатывает повторные подключения, а `PendingIntent` позволяет принять событие после завершения процесса. После потери сети ожидание события регистрируется заново. Передача запускается отдельной задачей `JobScheduler` и привязывается к Wi-Fi-соединению, поэтому не переходит на мобильный интернет при обрыве Wi-Fi. Резервная периодическая задача раз в 15 минут восстанавливает работу при пропущенных событиях; экономия энергии и ограничения производителя могут задержать запуск. После принудительной остановки приложения в системных настройках его нужно открыть заново.
Заблокированное приложение не читает мастер-ключ и открытые пароли. Оно отправляет только готовую зашифрованную базу с условием совпадения известной серверной версии. Изменённая копия Pi загружается в отдельный файл `pending.qsafe`, не заменяя доверенную локальную базу. При следующей разблокировке проверяются GCM-тег и принадлежность хранилищу, затем выполняется объединение. Повреждённая копия не заменяет локальные данные. Конфликтующие изменения сохраняются обеими версиями; окончательное объединение требует входа в хранилище. Все подключения к базе — автоматические, ручные и восстановление — требуют домашний Wi-Fi, локальный IPv4 Pi в той же подсети и закреплённый сертификат. Отключение фоновых задач не отменяет это ограничение. Интернет в домашней сети для базы не требуется. Обновления Android через Аргус остаются отдельным разрешённым внешним каналом.
## Перенос из SafeInCloud
В SafeInCloud для Windows открыть **Файл → Экспорт → XML**. Если установленная мобильная версия предлагает XML-экспорт, можно использовать его. Скопировать XML на телефон, открыть в QSafe **Настройки → Импорт из SafeInCloud (XML)** и проверить число найденных карточек перед подтверждением.
Переносятся все поля с типами и значениями, заметки, метки и избранное. Удалённые карточки и шаблоны пропускаются с явным счётчиком. Повторный импорт неизменённых карточек не создаёт дублей. Изменённая после предыдущего импорта карточка будет добавлена отдельной копией. Исходный XML каждой карточки, включая вложения и дополнительные данные, сохраняется внутри зашифрованной базы. Просмотр вложений и истории в интерфейсе первой версии не реализован.
XML-экспорт содержит **незашифрованные пароли**. QSafe читает его локально и шифрует перед сохранением; сервер получает только шифротекст. После сверки количества карточек и нескольких записей удалить открытый экспорт с устройств. Сохранить исходную базу SafeInCloud до завершения проверки. Фактический перенос пользовательских паролей требует файла экспорта и подтверждения импорта в приложении; автоматического доступа к SafeInCloud нет.
## Защита и синхронизация
- AES-256-GCM, случайный 96-битный nonce для каждого сохранения; заголовок файла включён в аутентифицируемые данные.
- PBKDF2-HMAC-SHA256, 600 000 итераций, случайная 128-битная соль. Используются стандартные криптографические API Java/Android. Ограничения размера и параметров проверяются до выработки ключа.
- Мастер-пароль и ключ базы не записываются на диск и не передаются серверу. В оперативной памяти расшифрованные данные доступны во время работы; гарантировать стирание всех копий строк в управляемой памяти Android нельзя.
- Ключ подключения к Pi зашифрован ключом Android Keystore. Вход по биометрии отдельно оборачивает ключ базы аппаратным/системным ключом с обязательной сильной биометрической авторизацией. Мастер-пароль остаётся способом восстановления.
- Блокировка при выходе из приложения и через 2 минуты бездействия. Все страницы и диалоги QSafe защищены `FLAG_SECURE` до показа окна; снимки в недавних приложениях также отключены на Android 13+. Содержимое форм не сохраняется в состоянии Activity. Буфер обмена помечается как чувствительный и очищается через 30 секунд, если приложение ещё работает и содержимое не заменено другим приложением.
- Системные резервные копии/перенос данных Android отключены. Доступен ручной экспорт зашифрованного `.qsafe` и восстановление с проверкой мастер-пароля.
- Для базы HTTPS проверяет IP, срок действия и закреплённый SHA-256 сертификата Pi; DNS, прокси, редиректы и сетевые загрузки сертификатов не используются. Незащищённый HTTP запрещён. Обновления APK через Аргус используют обычную системную проверку HTTPS и проверку подписи пакета.
- Pi хранит только аутентифицированный зашифрованный файл, его SHA-256 и предыдущие версии. Сервер технически не может проверить GCM-тег без клиентского ключа; целостность проверяет клиент.
- `ETag` и обязательные `If-Match`/`If-None-Match` предотвращают молчаливое затирание конкурентных записей. Клиент сравнивает общую исходную версию; независимые изменения объединяются, конкурентные правки сохраняются отдельными карточками. Конфликт удаления с редактированием сохраняет отредактированную карточку с пометкой.
- На телефоне остаются 10 предыдущих зашифрованных копий. На Pi — до 100 предыдущих версий с ограничением 256 МиБ. Ежедневная проверенная копия SQLite хранится отдельно на SD-карте: до 30 копий и 512 МиБ. Обе копии находятся на одном Pi; это не защита от потери всего устройства.
Приложение не проходило независимый аудит безопасности. Оно не заявляет полного функционального соответствия SafeInCloud. Смена мастер-пароля, passkeys и генерация TOTP пока не реализованы.
## Автозаполнение
Включается пользователем в **☰ → Защита → Автозаполнение** с подтверждением Android. Тем же переключателем служба QSafe выключается. Для каждого запроса требуется разблокировка и подтверждение выбранной карточки с показом получателя. Поддерживаются приложения с подсказками Android `username`/`emailAddress`/`password`. Для веб-сайтов поддерживается только выбранный по умолчанию браузер, HTTPS и точное совпадение домена с полем сайта в карточке. Неоднозначные домены и другие браузеры отклоняются. Автоматическое сохранение новых паролей из сторонних приложений пока не реализовано. Совместимость зависит от конкретного приложения/браузера.
## Pi
Сервер базы: `https://192.168.0.185:18443/qsafe/`, health: `/health`, база: `/v1/vault`. Caddy слушает только `192.168.0.185:18443` и принимает клиентов из `192.168.0.0/24`; бэкенд остаётся на `127.0.0.1:18792`. Публичный путь `/qsafe/*` на Аргусе возвращает 404. Каталог и загрузка обновлений Android через Аргус сохранены. Доступ к базе — по случайному 256-битному Bearer-ключу, сервер хранит только его SHA-256. APK ключей подключения, домашнего SSID и отпечатка пользовательского сертификата не содержит.
Локальный TLS и закрытие публичного маршрута настраивает `deploy/lan-only.py --ip 192.168.0.185`; откат конфигурации сохраняется в `/var/backups/qsafe-lan/`. Личный `.private/connection-lan.qsafe` содержит локальный URL, прежний ключ доступа, `homeWifiSsid` и `certificateSha256` и не входит в APK/ZIP. Файлы базы и истории при переносе маршрута не изменяются.
| Назначение | Путь / служба |
|---|---|
| Приложение сервера | `/opt/qsafe/server.py` |
| Зашифрованная база и история | `/mnt/data/qsafe/data/vault.sqlite3` |
| Хеш ключа подключения | `/etc/qsafe/token.sha256` |
| Служба | `qsafe.service` |
| Резервные копии на SD-карте | `/var/backups/qsafe` |
| Таймер | `qsafe-backup.timer`, ежедневно около 04:25 по времени Pi |
| Откат Caddy | `/var/backups/qsafe-install` |
Сервер запускается от отдельного пользователя `qsafe`, запись разрешена только в каталог базы; требуется смонтированный внешний диск. Для остановки: `sudo systemctl disable --now qsafe.service qsafe-backup.timer`. Данные при этом сохраняются. Для восстановления старой версии файла из SQLite следует сначала остановить клиентов и службу, сохранить текущую базу, затем выгрузить выбранное поле `history.body` в `.qsafe` и восстановить его через приложение. Никогда не редактировать зашифрованное содержимое вручную.
## Сборка и проверка
JDK 17, Android SDK 35, Gradle 8.14. В Windows при пути с кириллицей использовать временный диск, например `subst R: "путь-к-проекту"`, затем выполнять Gradle из `R:\`. Настроить `local.properties` с путём к Android SDK.
```powershell
.\gradlew.bat :app:testDebugUnitTest :app:assembleDebug :app:assembleDebugAndroidTest :app:lintDebug :app:assembleRelease --no-daemon
```
Подпись релиза использует `.private/signing.properties` и личный `.p12`; ключи, пароли, соединения и локальные настройки исключены из Git. Ключ подписи необходимо сохранить для дальнейших обновлений.
Тесты JVM проверяют шифрование, неверный пароль, повреждение, генератор, импорт и конфликты. Серверные тесты: `python3 -m unittest -v test_server` из `server/`. Android-тесты используют отдельный временный HTTPS-сервер и синтетические пароли; рабочая база пользователя в них не участвует. Файлы `deploy/qa-start.sh` и `deploy/qa-stop.sh` включают и выключают только временный маршрут проверки.
Источники решений: [Android Cryptography](https://developer.android.com/privacy-and-security/cryptography), [Android Autofill](https://developer.android.com/identity/autofill/autofill-services), [OWASP PBKDF2](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html), [описание XML SafeInCloud от автора парсера](https://github.com/eduncan911/safeincloud).
Фоновая сеть: [ConnectivityManager и PendingIntent](https://developer.android.com/reference/android/net/ConnectivityManager), [доступ к SSID в WifiInfo](https://developer.android.com/reference/android/net/wifi/WifiInfo), [JobInfo](https://developer.android.com/reference/android/app/job/JobInfo.Builder).