StrongholdManager
Файл:
backend/src/stronghold.rs
Обёртка над IOTA Stronghold — специализированным хранилищем секретов с защитой от side-channel атак. Обеспечивает долгосрочное хранение ключей, Ed25519 операции подписи (ключ не покидает защищённую память) и персистентное состояние приложения.
Типы секретов (SecretType)
| Variant | Алгоритм | Жизненный цикл | Назначение |
|---|---|---|---|
DeviceSignKey | Ed25519 | Generated in-place (generate_device_keypair) | Подпись device-cert'ов и member-op'ов; never extractable |
DeviceBoxKey | X25519 | Generated in-place | Расшифровка входящих WrappedSpaceKey / SealedEnvelope; never extractable |
LibP2PPrivateKey | Ed25519 | Imported (load_ed25519_key) | libp2p noise transport-key; extractable by necessity (см. memory) |
S3AccessKey / S3SecretKey | symmetric | Imported (store_secret) | S3 credentials |
Удалён в slice 17:
SecretType::DevicePrivateKey(вместе с колонкойdevices.private_key).
Структура
| Поле | Тип | Описание |
|---|---|---|
stronghold | Arc<Mutex<Stronghold>> | IOTA Stronghold instance |
snapshot_path | PathBuf | Путь к snapshot-файлу на диске |
client_name | String | "kontinuumapp_client" |
vault_name | Vec<u8> | b"secrets_vault" |
Методы
| Метод | Сигнатура | Описание |
|---|---|---|
| Lifecycle | ||
new | fn(snapshot_path) -> Self | Создать менеджер |
init | fn(&self, password) -> Result<()> | Инициализировать (load/create snapshot) |
vault_exists | fn(&self) -> bool | Существует ли snapshot-файл |
vault_path | fn(&self) -> &PathBuf | Путь к snapshot |
delete_vault_file | fn(&self) -> Result<()> | Удалить файл |
| State (legacy) | ||
save_state | fn(&self, state, password) -> Result<()> | Сохранить в Store |
load_state | fn(&self, password) -> Result<PersistentState> | Загрузить из Store |
clear | fn(&self, password) -> Result<()> | Очистить Store |
| State (secure) | ||
save_state_secure | fn(&self, state, password) -> Result<()> | Store + Vault dual-write |
| Secrets | ||
store_secret | fn(&self, type, secret, password) -> Result<()> | Записать в Vault |
delete_secret | fn(&self, type, password) -> Result<()> | Удалить из Vault |
| Ed25519 | ||
load_ed25519_key | fn(&self, type, key_bytes, password) -> Result<()> | Загрузить ключ в Vault (только LibP2PPrivateKey) |
sign_ed25519 | fn(&self, type, data) -> async Result<Vec<u8>> | Подписать данные |
verify_ed25519 | fn(public_key, signature, data) -> bool | Проверить подпись |
| v0.8 device keys | ||
generate_device_keypair | fn(&self, type: DeviceSignKey/DeviceBoxKey, password) -> async Result<[u8;32]> | Generate Ed25519/X25519 in-place; вернуть pubkey |
read_device_public_key | fn(&self, type: DeviceSignKey/DeviceBoxKey) -> async Result<[u8;32]> | Прочитать pubkey уже-сгенерированного ключа |
unwrap_wrapped_key | fn(&self, type: DeviceBoxKey, &envelope, ad, hkdf_info) -> async Result<Vec<u8>> | Расшифровать WrappedSpaceKey своим DeviceBoxKey |
| Fan-out primitives | ||
seal_for_device_box | fn(recipient_box_pk, plaintext, ad, hkdf_info) -> WrappedSpaceKey | Sender: hybrid X25519 → AES-GCM на один device |
seal_to_recipients | fn(&recipient_box_pks, plaintext, ad, hkdf_info) -> Vec<WrappedSpaceKey> | Sender: fan-out на N устройств |
try_unwrap_any | fn(&self, envelopes, ad, hkdf_info) -> async Result<Vec<u8>> | Receiver: попытаться unwrap'нуть каждый envelope своим DeviceBoxKey, вернуть первый успешный |
| SealedEnvelope (hybrid) | ||
seal_for_recipients | fn(plaintext, ad, &recipient_box_pks, hkdf_info_key_wrap) -> SealedEnvelope | Random content_key → fan-out wrap → AES-GCM payload |
unseal_envelope | fn(&self, &envelope, ad, hkdf_info_key_wrap) -> async Result<Vec<u8>> | try_unwrap_any → content_key → AES-GCM-decrypt |
Зависимости
- IOTA Stronghold — защищённое хранилище
- Blake2b256 — derive ключа шифрования для snapshot
- Ed25519 (через Stronghold Procedures) — подпись
- X25519 + HKDF-SHA256 + AES-256-GCM — для fan-out шифрования / распаковки
SealedEnvelope (hybrid encryption)
Использует seal_for_recipients для encryption и unseal_envelope для decryption. CBOR-сериализуемая структура для transport:
pub struct SealedEnvelope {
pub wrapped_content_keys: Vec<WrappedSpaceKey>, // O(recipients * 60B)
pub content_nonce: [u8; 12],
pub content_tag: [u8; 16],
pub content_ciphertext: Vec<u8>, // O(plaintext)
}Используется в sharing::SharingMessage::Share и meet::MailboxEnvelope. Оба слоя AEAD верифицируют одну и ту же associated_data — drift между sender/receiver fail'ится чисто.
seal_for_recipients
pub fn seal_for_recipients(plaintext: &[u8], associated_data: &[u8], recipient_box_pubkeys: &[[u8; 32]], hkdf_info_key_wrap: &[u8]) -> SealedEnvelope
Hybrid-encrypt arbitrary-length plaintext для всех recipient device-box-pubkey'ев. Pure crypto (no Stronghold dep).
Алгоритм
- Генерирует random 32-байтный
content_key. seal_to_recipients(&recipient_box_pubkeys, &content_key, associated_data, hkdf_info_key_wrap)— fan-out wrapcontent_key(одинWrappedSpaceKeyна recipient).- AES-256-GCM-seal
(plaintext, associated_data)подcontent_keyс random nonce. - Zero
content_keyна стеке.
Если
recipient_box_pubkeys.is_empty()— envelope unsealable никем; вызывающий код должен это проверить.
unseal_envelope
pub async fn unseal_envelope(&self, envelope: &SealedEnvelope, associated_data: &[u8], hkdf_info_key_wrap: &[u8]) -> Result<Vec<u8>>
Receiver-side: расшифровка SealedEnvelope через этот device's DeviceBoxKey.
Алгоритм
try_unwrap_any(self, &envelope.wrapped_content_keys, ad, hkdf_info_key_wrap)— найти envelope, который unwrap'ится этим device-box-pubkey'ем. Если ни один не подходит → error.- AES-256-GCM-open (
content_key,content_nonce,content_ciphertext || content_tag,associated_data). - Zero
content_key.
Failure modes: (a) ни один wrapped_key не наш (не recipient), (b) AAD mismatch, (c) HKDF info drift, (d) tampering с ciphertext — все возвращают error.
new / init
fn new(snapshot_path: PathBuf) -> Selffn init(&self, password: &str) -> Result<()>
Создание и инициализация Stronghold. init идемпотентен — при повторном вызове переиспользует существующий client.
Логика (init)
- Derive ключ шифрования из password (Blake2b256, или scrypt в production)
- Если snapshot-файл существует — загружает его
- Создаёт или загружает client по имени
- Коммитит snapshot
Используется в:
init_stronghold_svc
save_state / load_state
fn save_state(&self, state: &SharedState, password: &str) -> Result<()>fn load_state(&self, password: &str) -> Result<PersistentState>
Legacy-режим: сериализует состояние приложения в JSON и сохраняет/загружает из Stronghold Store (key-value хранилище).
Логика (save_state)
- Блокирует SharedState, сериализует в JSON
- Записывает в Stronghold Store под ключом
"app_state" - Коммитит snapshot на диск
Логика (load_state)
- Derive ключ, загружает snapshot
- Читает из Store по ключу
"app_state" - Десериализует JSON в PersistentState
Используется в:
save_state_to_stronghold_svc,load_state_from_stronghold_svc
save_state_secure
fn save_state_secure(&self, state: &SharedState, password: &str) -> Result<()>
Dual-mode сохранение: публичная конфигурация в Store, чувствительные данные (S3 credentials) в Vault (защищённая память).
Логика
- Сериализует публичную часть состояния → Store
- Извлекает S3 credentials → шифрует → Vault
- Коммитит snapshot
Используется в:
save_state_to_stronghold_secure_svc
load_ed25519_key / sign_ed25519
fn load_ed25519_key(&self, secret_type: &str, key_bytes: &[u8], password: &str) -> Result<()>fn sign_ed25519(&self, secret_type: &str, data: &[u8]) -> Result<Vec<u8>>
Ed25519 операции через Stronghold Procedures. Ключ загружается в защищённую память и никогда не покидает её — подпись выполняется внутри Stronghold.
load_ed25519_key
- Записывает private key bytes в Vault location
- Коммитит snapshot
sign_ed25519
- Находит ключ в Vault по secret_type
- Выполняет Ed25519Sign Procedure (ключ в защищённой памяти)
- Возвращает 64-byte подпись
Используется в:
sign_data_svc,verify_signature_svc
verify_ed25519
fn verify_ed25519(public_key: &[u8], signature: &[u8], data: &[u8]) -> bool
Статический метод верификации Ed25519 подписи. Не требует Stronghold — использует только публичный ключ.
Используется в:
verify_signature_svc
store_secret / delete_secret
fn store_secret(&self, secret_type: &str, secret: &[u8], password: &str) -> Result<()>fn delete_secret(&self, secret_type: &str, password: &str) -> Result<()>
Прямая запись/удаление секретов в Stronghold Vault.
Используется в:
init_stronghold_svc
clear / delete_vault_file
clear(password)— удаляетapp_stateиз Store, коммитит snapshotdelete_vault_file()— физически удаляет snapshot-файл с диска
Используется в:
clear_stronghold_svc,delete_stronghold_vault_svc