VaultManager
Файл:
backend/src/vault/manager.rs
Криптографическое хранилище секретов с PIN-авторизацией. Обеспечивает шифрование/дешифровку через ChaCha20Poly1305, derive ключей через Argon2, rate limiting попыток PIN и activity-based автоблокировку.
ValidatedPin
PIN валидируется на границе (в методах setup_with_pin, unlock_with_pin, change_pin) через newtype ValidatedPin (файл: vault/pin_encryption.rs):
pub struct ValidatedPin(Zeroizing<String>);
impl ValidatedPin {
pub fn new(pin: &str) -> Result<Self> {
// Ровно 4 ASCII-цифры, иначе ошибка
}
pub fn as_str(&self) -> &str { ... }
}- Конструируется один раз при входе в boundary-метод
- Передаётся по ссылке (
&ValidatedPin) во все внутренние функции (derive_key,encrypt,decrypt,wrap_master_key_with_pin,unwrap_master_key_with_pin) - Содержимое автоматически зануляется при drop (
Zeroizing)
Структура
| Поле | Тип | Описание |
|---|---|---|
db | Arc<Mutex<Connection>> | SQLite соединение |
master_key | Arc<Mutex<Option<Zeroizing<[u8; 32]>>>> | Master key (только когда разблокирован) |
pin_encryption | PinEncryption | Rate limiting и lockout PIN |
session_state | Arc<Mutex<SessionState>> | Состояние сессии (auto-lock, background op mode) |
event_bus | Option<EventBus> | Для событий vault_locked и vault_unlocked |
Методы
| Метод | Сигнатура | Описание |
|---|---|---|
| Setup | ||
new | fn(db) -> Result<Self> | Инициализация + создание таблиц |
set_event_bus | fn(&mut self, bus) | Установить event bus |
is_setup | fn(&self) -> Result<bool> | Настроен ли vault (есть vault_config) |
setup_with_pin | fn(&mut self, pin) -> Result<()> | Быстрая настройка без recovery |
setup_with_recovery_phrase | fn(&mut self, phrase, pin) -> Result<()> | Полная настройка |
| Lock/Unlock | ||
unlock_with_pin | fn(&mut self, pin) -> Result<UnlockResult> | Разблокировка по PIN |
unlock_with_recovery_phrase | fn(&mut self, phrase) -> Result<()> | Разблокировка по фразе |
is_unlocked | fn(&self) -> bool | Чистая проверка (без side effects) |
lock | fn(&mut self) -> Result<()> | Заблокировать (zeroize master key) |
| PIN | ||
change_pin | fn(&mut self, new_pin) -> Result<()> | Сменить PIN |
get_pin_state | fn(&self) -> Result<PinState> | Состояние попыток |
reset_pin_state | fn(&mut self) -> Result<()> | Сбросить попытки |
| Secrets | ||
save_secret | fn(&self, id, data) -> Result<()> | Зашифровать и сохранить |
load_secret | fn(&self, id) -> Result<Zeroizing<Vec<u8>>> | Расшифровать и загрузить |
has_secret | fn(&self, id) -> bool | Проверить наличие |
delete_secret | fn(&self, id) -> Result<()> | Удалить секрет |
list_secrets | fn(&self) -> Result<Vec<String>> | Список ID секретов |
| Recovery | ||
save_encrypted_recovery_phrase | fn(&self, words) -> Result<()> | Зашифровать и сохранить фразу |
has_encrypted_recovery_phrase | fn(&self) -> bool | Есть ли фраза |
load_recovery_phrase | fn(&self) -> Result<Zeroizing<String>> | Расшифровать фразу |
delete_recovery_phrase | fn(&self) -> Result<()> | Удалить фразу |
| Session | ||
touch_activity | fn(&self) | Обновить timestamp (с guard на locked) |
check_and_lock_if_expired | fn(&self) -> bool | Проверить таймаут и заблокировать |
set_auto_lock_timeout | fn(&self, seconds) | Таймаут автоблокировки (persist в БД) |
get_auto_lock_timeout | fn(&self) -> u64 | Получить текущий таймаут |
| Background Operations | ||
set_background_operation_mode | fn(&self, mode) | Режим фоновых операций (persist в БД) |
get_background_operation_mode | fn(&self) -> BackgroundOperationMode | Получить текущий режим |
issue_scoped_key | fn(&self, secret_id) -> Result<ScopedKey> | Выдать производный ключ для фоновой задачи |
| Admin | ||
reset | fn(&mut self) -> Result<()> | Полный сброс vault |
Зависимости
- ChaCha20Poly1305 — симметричное шифрование секретов
- Argon2 — derive ключей из PIN и recovery phrase
- SQLite — таблицы
vault_config,encrypted_secrets,vault_pin_state,vault_settings
setup_with_recovery_phrase
fn setup_with_recovery_phrase(&mut self, phrase: &RecoveryPhrase, pin: &str) -> Result<()>
Полная настройка vault: генерирует master key из recovery phrase через Argon2, оборачивает его PIN'ом.
Логика
- Derive master key из recovery phrase (Argon2id, random salt)
- Генерирует random nonce для ChaCha20Poly1305
- Derive PIN key из PIN (Argon2id, другой salt)
- Шифрует master key PIN key'ом (ChaCha20Poly1305)
- Сохраняет в БД: salt, nonce, wrapped_master_key
- Кэширует master key в памяти (vault разблокирован)
Используется в:
setup_vault_with_recovery_svc,setup_pin_svc
unlock_with_pin
fn unlock_with_pin(&mut self, pin: &str) -> Result<UnlockResult>
Разблокирует vault по PIN. Реализует rate limiting: после нескольких неудачных попыток — временная блокировка.
Возвращает
UnlockResult { success: bool, remaining_attempts: u32 }
Логика
- Проверяет lockout (не заблокирован ли по времени)
- Загружает vault_config из БД (salt, nonce, wrapped_master_key)
- Derive pin_key из PIN через Argon2
- Decrypt wrapped_master_key с pin_key
- Если успешно — кэширует master_key, сбрасывает failed_attempts
- Если ошибка — инкрементирует failed_attempts, проверяет lockout
Используется в:
unlock_vault_with_pin_svc,reset_pin_state_svc,reset_vault_svc
lock
fn lock(&mut self) -> Result<()>
Блокирует vault: зануляет master key в памяти, эмитит событие vault_locked.
Логика
self.master_key = None(Zeroizing автоматически зануляет память)- Эмитит
vault_lockedчерез event bus
Используется в:
lock_vault_svc
is_unlocked
fn is_unlocked(&self) -> bool
Чистая проверка состояния: возвращает true если master key в памяти. Без побочных эффектов, без проверки таймаута. Можно вызывать из любого места.
Используется в:
is_unlocked_svc,get_vault_status_svc,vault_heartbeat_svc
save_secret / load_secret
fn save_secret(&self, secret_id: &str, data: &[u8]) -> Result<()>fn load_secret(&self, secret_id: &str) -> Result<Zeroizing<Vec<u8>>>
Шифрование/дешифровка секретов с master key. Каждый секрет имеет индивидуальные salt и nonce. Операции атомарно захватывают master_key lock — нет TOCTOU и нет touch_activity() (операции с секретами не продлевают сессию).
save_secret
- Атомарно захватывает
master_keylock (или ошибка "Vault is locked") - Генерирует random salt + nonce
- Encrypt data с master_key + nonce (ChaCha20Poly1305)
- INSERT/REPLACE INTO encrypted_secrets
load_secret
- SELECT FROM encrypted_secrets WHERE secret_id = ?
- Атомарно захватывает
master_keylock (или ошибка "Vault is locked") - Decrypt encrypted_data
Используется в:
setup_pin_svc,sign_data_svc, внутренне при паринге
save_encrypted_recovery_phrase / load_recovery_phrase
Управление recovery phrase. Фраза шифруется master key'ом и хранится в vault_config.
save_encrypted_recovery_phrase— шифрует фразу и сохраняет в vault_configload_recovery_phrase— расшифровывает и возвращает (vault должен быть unlocked)delete_recovery_phrase— удаляет после подтверждения бэкапа
Используется в:
setup_pin_svc,get_recovery_phrase_svc,confirm_recovery_backup_svc
touch_activity / check_and_lock_if_expired / set_auto_lock_timeout
Activity-based автоблокировка. Подробнее: Internals: Auto-lock, PIN Rate Limiting.
touch_activity()— обновляет timestamp последней активности. Не работает на locked vault (guard). Вызывается из 5 мест:POST /heartbeat,setup_with_pin,setup_with_recovery_phrase,unlock_with_pin,unlock_with_recovery_phrasecheck_and_lock_if_expired()— проверяет таймаут и блокирует vault при истечении. Вызывается только background timer (каждые 5 секунд). Имеет ранний выход при locked vaultset_auto_lock_timeout(seconds)— устанавливает таймаут (0 = отключено). Persist в таблицуvault_settingsget_auto_lock_timeout()— возвращает текущий таймаут
Используется в:
vault_heartbeat_svc,set_auto_lock_timeout_svc,get_vault_status_svc, auto-lock timer
set_background_operation_mode / get_background_operation_mode
fn set_background_operation_mode(&self, mode: BackgroundOperationMode)fn get_background_operation_mode(&self) -> BackgroundOperationMode
Управление режимом фоновых операций при заблокированном vault. Persist в таблицу vault_settings.
BackgroundOperationMode
| Значение | Описание |
|---|---|
Continue | (default) Фоновая задача получает ScopedKey при старте, продолжает работу при auto-lock |
Pause | Фоновая задача использует кэшированные ключи из менеджеров, при lock — пауза, при unlock — resume |
Используется в:
get_background_operation_mode_svc,set_background_operation_mode_svc,SharingManager.start_sharing
issue_scoped_key
fn issue_scoped_key(&self, secret_id: &str) -> Result<ScopedKey>
Выдаёт производный ключ (ScopedKey) для фоновой задачи. Содержит конкретный секрет (identity signing key, S3 creds и т.д.), не master key. ScopedKey живёт независимо от состояния vault — при auto-lock ключ остаётся рабочим. Зануляется (Zeroize) при drop.
Логика
- Загружает секрет через
load_secret(secret_id) - Оборачивает в
ScopedKeyс timestamp выдачи - При drop — логирует время жизни ключа
Используется в:
SharingManager.start_sharing(режим Continue)
reset
fn reset(&mut self) -> Result<()>
Полный сброс vault: удаляет все таблицы и данные.
Логика
- Зануляет master key
- DELETE FROM vault_config
- DELETE FROM encrypted_secrets
- DELETE FROM vault_pin_state
Используется в:
reset_vault_svc