Сервер: миграция 001 со всей схемой storage.md, store на modernc.org/sqlite (WAL, foreign_keys, один писатель), фоновая чистка раз в час, argon2id с параметрами ADR-021 и сверкой constant-time, сессии по SHA-256 токена, cookie bare_session, глобальная проверка Origin, девять эндпоинтов аккаунта. Ник в журнал не попадает: для /api/ пишется шаблон маршрута. Клиент: crypto.js по crypto.md построчно — мастер из пароля, два независимых ключа из мастера, ключевой блоб с ником в AAD, отпечаток от сырой точки; db.js со всеми хранилищами версии 1; экран входа и регистрации, настройки со сменой пароля, выходом и удалением аккаунта. Пароль не покидает клиент: проверено на боевом сервере — ни пароля, ни priv.d ни в одном теле запроса, вход на втором устройстве даёт тот же отпечаток. ADR-027: код internal для 500, причина только в журнале. ADR-028: тексты состояний клиента сведены в ui.md. ADR-029: вход под другим ником стирает историю только после подтверждения. ADR-030: верхняя граница итераций KDF, проверка границ на обеих сторонах. ADR-031: служебный выход перед повторным входом не заканчивает сеанс. ADR-032: каталог состояния 0700, файлы базы 0600. Прямые зависимости: modernc.org/sqlite, golang.org/x/crypto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
3.1 KiB
ADR-030: Верхняя граница итераций KDF и проверка границ на клиенте
Уточняет ADR-013: нижняя граница остаётся, к ней добавляется верхняя.
Контекст
ADR-013 задаёт целевое число итераций PBKDF2 и нижнюю границу, верхней нет. iter — единственное поле ключевого блоба, которое сервер разбирает сам и потом сам же раздаёт клиентам через GET /api/kdf, то есть отвечает за его вменяемость. Регистрация одним запросом с iter = 10^12 принималась: аккаунт после этого нельзя ни открыть, ни удалить — обе операции начинаются с PBKDF2, который не заканчивается.
С другой стороны, клиент брал число итераций из GET /api/kdf и GET /api/config как есть и считал по нему authKey, который тут же уходит на сервер. Нижнюю границу не проверял никто, кроме сервера, и только у блоба — а PBKDF2 считает клиент, и проверить параметр перед вычислением может только он.
Решение
- Границы числа итераций — от 600 000 до 10 000 000. Верхняя — порядок над целевым значением 1 000 000: запас на повышение и предел, за которым вход перестаёт заканчиваться.
- Сервер отвергает ключевой блоб с
iterвне границ:400 invalid,field: blob. - Клиент проверяет границы до
deriveBits: и число изGET /api/kdfиGET /api/config, иiterпри разборе блоба. Число от сервера вне границ — «параметры ключа не совпали»;iterблоба вне границ — «ключ аккаунта повреждён», как любой другой дефект его формы (ADR-028). - Границы записаны в
docs/crypto.mdрядом с целевым значением.
Следствия
- Аккаунт с неоткрываемым
iterзавести нельзя. - Ослабить KDF ответом
/api/kdfтоже нельзя: границу держат обе стороны, и клиентская стоит раньше вычисления. Активно-злонамеренный оператор остаётся вне модели угроз — он подменит и сам клиент. - Поднять целевое значение выше верхней границы без правки границы не выйдет. Это и требуется: такое повышение — решение, а не настройка.