DeviceManager
Файл:
backend/src/devices/manager.rs
Управление устройствами пользователя. Каждое физическое устройство (desktop, mobile) представлено записью с identifier-ами + публичными ключами; приватные ключи никогда не покидают Stronghold (см. identity-key model).
Identity-key model
После закрытия редизайна (slices 1–17) у каждого устройства есть три durable ключа, все генерируются in-place внутри Stronghold и не извлекаются:
| Ключ | Тип | Назначение | Колонка в devices |
|---|---|---|---|
DeviceSignKey | Ed25519 | Подпись device-cert'ов и member-op'ов | device_sign_pubkey |
DeviceBoxKey | X25519 | Расшифровка входящих SealedEnvelope'ов | device_box_pubkey |
| (libp2p transport) | Ed25519 | Noise handshake для libp2p | device_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 commandload_device_key_into_strongholdтоже удалён — нечего импортировать.
Структура
| Поле | Тип | Описание |
|---|---|---|
db | Arc<Mutex<Connection>> | SQLite соединение |
current_device | Option<Device> | Кэшированное текущее устройство |
Методы
| Метод | Сигнатура | Описание |
|---|---|---|
new | fn(db) -> Result<Self> | Инициализация + создание таблицы |
create_device | fn(&mut self, identity_id) -> Result<Device> | Создать запись устройства (legacy public_key/peer_id для row-id) |
load_current_device | fn(&mut self) -> Result<()> | Загрузить текущее устройство из БД |
get_current_device | fn(&self) -> Option<&Device> | Получить текущее устройство |
has_current_device | fn(&self) -> bool | Есть ли текущее устройство в БД |
is_current_device_loaded | fn(&self) -> bool | Загружено ли в кэш |
list_devices | fn(&self) -> Result<Vec<Device>> | Список устройств текущей identity |
set_device_pubkeys | fn(&self, id, sign_pk, box_pk, transport_pk) -> Result<()> | Записать три device-pubkey'я (one-shot, slice 6) |
set_device_cert | fn(&self, id, cert_bytes) -> Result<()> | Записать CBOR SignedDeviceCert (one-shot) |
get_device_cert | fn(&self, id) -> Result<Option<Vec<u8>>> | Прочитать сертификат |
clear_device_pubkeys_and_cert | fn(&self, id) -> Result<()> | Destructive: занулить все v0.8 поля (для discard) |
add_paired_device | fn(&self, device) -> Result<()> | Добавить/обновить paired устройство |
update_device_name | fn(&self, id, name) -> Result<()> | Обновить имя устройства |
update_device_ip | fn(&self, id, ip) -> Result<()> | Обновить IP-адрес |
update_device_storage | fn(&self, id, storage) -> Result<()> | Обновить информацию о хранилище |
update_device_identity | fn(&self, id, identity_id) -> Result<()> | Привязать к identity |
update_current_device_identity | fn(&mut self, identity_id) -> Result<()> | Привязать текущее устройство |
get_local_ip | fn() -> Option<IpAddr> | Определить локальный IP |
get_storage_info | fn() -> 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 | &str | ID identity для привязки |
Логика
- Генерирует временную Ed25519 ключевую пару (private bytes сбрасываются на стек)
- Вычисляет device_id = blake3(public_key)
- Конвертирует ключ в libp2p формат для PeerId
- Определяет OS, IP, storage
- Сохраняет в БД с
is_current = 1; колонкиdevice_sign_pubkey/device_box_pubkey/device_transport_pubkey/device_certостаютсяNULL - Дропает signing_key — приватных байт нигде не остаётся
- Кэширует Device-объект как
current_device
Используется в:
create_identity_svc,load_current_device_svc
load_current_device
fn load_current_device(&mut self) -> Result<()>
Загружает текущее устройство из БД, обновляет last_seen, IP и storage info.
Логика
- SELECT FROM devices WHERE is_current = 1
- Обновляет last_seen = now
- Обновляет IP через
get_local_ip() - Обновляет storage через
get_storage_info() - Кэширует в
current_device
Используется в:
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.
Логика
- Получает identity_id текущего устройства
- 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 устройств.
Логика
- 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:
#[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-методы