Skip to content

DeviceManager

Файл: backend/src/devices/manager.rs

Управление устройствами пользователя. Каждое физическое устройство (desktop, mobile) представлено записью с identifier-ами + публичными ключами; приватные ключи никогда не покидают Stronghold (см. identity-key model).

Identity-key model

После закрытия редизайна (slices 1–17) у каждого устройства есть три durable ключа, все генерируются in-place внутри Stronghold и не извлекаются:

КлючТипНазначениеКолонка в devices
DeviceSignKeyEd25519Подпись device-cert'ов и member-op'овdevice_sign_pubkey
DeviceBoxKeyX25519Расшифровка входящих SealedEnvelope'овdevice_box_pubkey
(libp2p transport)Ed25519Noise handshake для libp2pdevice_transport_pubkey

Плюс подписанный сертификат SignedDeviceCert в колонке device_cert — содержит {account_id, account_root_pubkey, device_id, все три device-pubkey'я, issued_at, parent} и подписан issuer-устройством (первое устройство подписывает само себя через account_root_sk; последующие — cross-sign через DeviceSignKey уже-доверенного устройства).

Legacy private_key BLOB колонка физически удалена (slice 17). Tauri command load_device_key_into_stronghold тоже удалён — нечего импортировать.

Структура

ПолеТипОписание
dbArc<Mutex<Connection>>SQLite соединение
current_deviceOption<Device>Кэшированное текущее устройство

Методы

МетодСигнатураОписание
newfn(db) -> Result<Self>Инициализация + создание таблицы
create_devicefn(&mut self, identity_id) -> Result<Device>Создать запись устройства (legacy public_key/peer_id для row-id)
load_current_devicefn(&mut self) -> Result<()>Загрузить текущее устройство из БД
get_current_devicefn(&self) -> Option<&Device>Получить текущее устройство
has_current_devicefn(&self) -> boolЕсть ли текущее устройство в БД
is_current_device_loadedfn(&self) -> boolЗагружено ли в кэш
list_devicesfn(&self) -> Result<Vec<Device>>Список устройств текущей identity
set_device_pubkeysfn(&self, id, sign_pk, box_pk, transport_pk) -> Result<()>Записать три device-pubkey'я (one-shot, slice 6)
set_device_certfn(&self, id, cert_bytes) -> Result<()>Записать CBOR SignedDeviceCert (one-shot)
get_device_certfn(&self, id) -> Result<Option<Vec<u8>>>Прочитать сертификат
clear_device_pubkeys_and_certfn(&self, id) -> Result<()>Destructive: занулить все v0.8 поля (для discard)
add_paired_devicefn(&self, device) -> Result<()>Добавить/обновить paired устройство
update_device_namefn(&self, id, name) -> Result<()>Обновить имя устройства
update_device_ipfn(&self, id, ip) -> Result<()>Обновить IP-адрес
update_device_storagefn(&self, id, storage) -> Result<()>Обновить информацию о хранилище
update_device_identityfn(&self, id, identity_id) -> Result<()>Привязать к identity
update_current_device_identityfn(&mut self, identity_id) -> Result<()>Привязать текущее устройство
get_local_ipfn() -> Option<IpAddr>Определить локальный IP
get_storage_infofn() -> Option<StorageInfo>Информация о диске

Удалены в slice 17: get_device_keypair, get_device_signing_key, get_device_private_key_bytes, materialize_device_key, clear_device_key, scrub_legacy_plaintext_key. Подпись и DH теперь идут через StrongholdManager.

Зависимости

  • IdentityManager — identity_id + account_root_pubkey для привязки устройства
  • StrongholdManager — генерация и использование DeviceSignKey/DeviceBoxKey
  • SQLite — таблица devices

create_device

fn create_device(&mut self, identity_id: &str) -> Result<Device>

Создаёт запись устройства. Generates Ed25519 keypair только для получения row-identifier'ов (device_id = blake3(public_key), PeerId через libp2p) и сразу дропает private bytes — никакого persistent private-key cache. Криптографическая идентичность устройства появляется позже, при первом vault unlock'е, когда StrongholdManager сгенерирует DeviceSignKey/DeviceBoxKey внутри Stronghold (см. init_first_device_on_unlock).

Параметры

ИмяТипОписание
identity_id&strID identity для привязки

Логика

  1. Генерирует временную Ed25519 ключевую пару (private bytes сбрасываются на стек)
  2. Вычисляет device_id = blake3(public_key)
  3. Конвертирует ключ в libp2p формат для PeerId
  4. Определяет OS, IP, storage
  5. Сохраняет в БД с is_current = 1; колонки device_sign_pubkey / device_box_pubkey / device_transport_pubkey / device_cert остаются NULL
  6. Дропает signing_key — приватных байт нигде не остаётся
  7. Кэширует Device-объект как current_device
plantuml Diagram

Используется в: create_identity_svc, load_current_device_svc


load_current_device

fn load_current_device(&mut self) -> Result<()>

Загружает текущее устройство из БД, обновляет last_seen, IP и storage info.

Логика

  1. SELECT FROM devices WHERE is_current = 1
  2. Обновляет last_seen = now
  3. Обновляет IP через get_local_ip()
  4. Обновляет storage через get_storage_info()
  5. Кэширует в current_device
plantuml Diagram

Используется в: load_current_device_svc


get_current_device / has_current_device / is_current_device_loaded

Геттеры для текущего устройства:

  • get_current_device() — из кэша в памяти
  • has_current_device() — проверяет БД (SELECT COUNT)
  • is_current_device_loaded() — проверяет кэш

Используется в: get_current_device_svc, has_current_device_svc, start_sharing_svc


list_devices

fn list_devices(&self) -> Result<Vec<Device>>

Возвращает все устройства, привязанные к текущей identity.

Логика

  1. Получает identity_id текущего устройства
  2. SELECT FROM devices WHERE identity_id = ?

Используется в: list_devices_svc


set_device_pubkeys / set_device_cert / get_device_cert

fn set_device_pubkeys(&self, device_id: &str, sign_pk: [u8;32], box_pk: [u8;32], transport_pk: [u8;32]) -> Result<()>fn set_device_cert(&self, device_id: &str, cert_bytes: Vec<u8>) -> Result<()>fn get_device_cert(&self, device_id: &str) -> Result<Option<Vec<u8>>>

Запись и чтение v0.8 ключевых полей устройства. One-shot semantics: повторный вызов с теми же байтами идемпотентен, с другими — отклоняется (см. clear_device_pubkeys_and_cert для destructive reset).

Вызывается из init_first_device_on_unlock (host-side, slice 6/11) и из PairingManager::joiner_apply_join_response (joiner-side, slice 14/15).

clear_device_pubkeys_and_cert

fn clear_device_pubkeys_and_cert(&self, device_id: &str) -> Result<()>

Destructive: зануляет device_sign_pubkey, device_box_pubkey, device_transport_pubkey, device_cert для указанного device. Снимает one-shot guard, позволяя записать другие значения. Используется UX-flow'ом «начать с нуля» / «выкинуть аккаунт» (см. также удаление соответствующих Stronghold-секретов).


add_paired_device

fn add_paired_device(&self, device: Device) -> Result<()>

Добавляет или обновляет удалённое устройство, полученное при паринге. Приватный ключ НЕ сохраняется для remote устройств.

Логика

  1. INSERT OR REPLACE INTO devices (без private_key, is_current = 0)

Используется в: approve_pairing_svc, confirm_pairing_svc


update_device_name / update_device_ip / update_device_storage / update_device_identity

Обновление полей устройства:

  • update_device_name — имя устройства
  • update_device_ip — IP-адрес (при обнаружении через mDNS)
  • update_device_storage — использование диска
  • update_device_identity — привязка к другой identity (при паринге)
  • update_current_device_identity — обновляет identity текущего устройства + кэш

Используется в: update_device_name_svc, confirm_pairing_svc, внутренне в SharingManager при синхронизации


get_local_ip

fn get_local_ip() -> Option<IpAddr>

Определяет локальный IP-адрес устройства через UDP socket trick: создаёт UDP сокет к 8.8.8.8:80, считывает local_addr.


get_storage_info

fn get_storage_info() -> Option<StorageInfo>

Определяет доступное/общее место на диске. Приоритет: DB_PATH → домашняя директория → /. Единицы автоматически: GB (< 1TB) или TB.

StorageInfo (Invariant Preservation)

StorageInfo (файл: devices/types.rs) использует приватные поля с конструктором, гарантирующим инвариант used <= total:

rust
#[serde(from = "StorageInfoRaw")]
pub struct StorageInfo {
    used: u64,   // private
    total: u64,  // private
    unit: StorageUnit,  // private
}

impl StorageInfo {
    pub fn new(used: u64, total: u64, unit: StorageUnit) -> Self {
        Self { used: used.min(total), total, unit }  // инвариант: used ≤ total
    }
    pub fn used(&self) -> u64 { ... }
    pub fn total(&self) -> u64 { ... }
    pub fn unit(&self) -> &StorageUnit { ... }
}
  • Десериализация через #[serde(from = "StorageInfoRaw")] — автоматическая валидация при получении данных от пиров
  • Доступ к полям только через getter-методы