Skip to content

StrongholdManager

Файл: backend/src/stronghold.rs

Обёртка над IOTA Stronghold — специализированным хранилищем секретов с защитой от side-channel атак. Обеспечивает долгосрочное хранение ключей, Ed25519 операции подписи (ключ не покидает защищённую память) и персистентное состояние приложения.

Типы секретов (SecretType)

VariantАлгоритмЖизненный циклНазначение
DeviceSignKeyEd25519Generated in-place (generate_device_keypair)Подпись device-cert'ов и member-op'ов; never extractable
DeviceBoxKeyX25519Generated in-placeРасшифровка входящих WrappedSpaceKey / SealedEnvelope; never extractable
LibP2PPrivateKeyEd25519Imported (load_ed25519_key)libp2p noise transport-key; extractable by necessity (см. memory)
S3AccessKey / S3SecretKeysymmetricImported (store_secret)S3 credentials

Удалён в slice 17: SecretType::DevicePrivateKey (вместе с колонкой devices.private_key).

Структура

ПолеТипОписание
strongholdArc<Mutex<Stronghold>>IOTA Stronghold instance
snapshot_pathPathBufПуть к snapshot-файлу на диске
client_nameString"kontinuumapp_client"
vault_nameVec<u8>b"secrets_vault"

Методы

МетодСигнатураОписание
Lifecycle
newfn(snapshot_path) -> SelfСоздать менеджер
initfn(&self, password) -> Result<()>Инициализировать (load/create snapshot)
vault_existsfn(&self) -> boolСуществует ли snapshot-файл
vault_pathfn(&self) -> &PathBufПуть к snapshot
delete_vault_filefn(&self) -> Result<()>Удалить файл
State (legacy)
save_statefn(&self, state, password) -> Result<()>Сохранить в Store
load_statefn(&self, password) -> Result<PersistentState>Загрузить из Store
clearfn(&self, password) -> Result<()>Очистить Store
State (secure)
save_state_securefn(&self, state, password) -> Result<()>Store + Vault dual-write
Secrets
store_secretfn(&self, type, secret, password) -> Result<()>Записать в Vault
delete_secretfn(&self, type, password) -> Result<()>Удалить из Vault
Ed25519
load_ed25519_keyfn(&self, type, key_bytes, password) -> Result<()>Загрузить ключ в Vault (только LibP2PPrivateKey)
sign_ed25519fn(&self, type, data) -> async Result<Vec<u8>>Подписать данные
verify_ed25519fn(public_key, signature, data) -> boolПроверить подпись
v0.8 device keys
generate_device_keypairfn(&self, type: DeviceSignKey/DeviceBoxKey, password) -> async Result<[u8;32]>Generate Ed25519/X25519 in-place; вернуть pubkey
read_device_public_keyfn(&self, type: DeviceSignKey/DeviceBoxKey) -> async Result<[u8;32]>Прочитать pubkey уже-сгенерированного ключа
unwrap_wrapped_keyfn(&self, type: DeviceBoxKey, &envelope, ad, hkdf_info) -> async Result<Vec<u8>>Расшифровать WrappedSpaceKey своим DeviceBoxKey
Fan-out primitives
seal_for_device_boxfn(recipient_box_pk, plaintext, ad, hkdf_info) -> WrappedSpaceKeySender: hybrid X25519 → AES-GCM на один device
seal_to_recipientsfn(&recipient_box_pks, plaintext, ad, hkdf_info) -> Vec<WrappedSpaceKey>Sender: fan-out на N устройств
try_unwrap_anyfn(&self, envelopes, ad, hkdf_info) -> async Result<Vec<u8>>Receiver: попытаться unwrap'нуть каждый envelope своим DeviceBoxKey, вернуть первый успешный
SealedEnvelope (hybrid)
seal_for_recipientsfn(plaintext, ad, &recipient_box_pks, hkdf_info_key_wrap) -> SealedEnvelopeRandom content_key → fan-out wrap → AES-GCM payload
unseal_envelopefn(&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:

rust
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).

Алгоритм

  1. Генерирует random 32-байтный content_key.
  2. seal_to_recipients(&recipient_box_pubkeys, &content_key, associated_data, hkdf_info_key_wrap) — fan-out wrap content_key (один WrappedSpaceKey на recipient).
  3. AES-256-GCM-seal (plaintext, associated_data) под content_key с random nonce.
  4. 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.

Алгоритм

  1. try_unwrap_any(self, &envelope.wrapped_content_keys, ad, hkdf_info_key_wrap) — найти envelope, который unwrap'ится этим device-box-pubkey'ем. Если ни один не подходит → error.
  2. AES-256-GCM-open (content_key, content_nonce, content_ciphertext || content_tag, associated_data).
  3. 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)

  1. Derive ключ шифрования из password (Blake2b256, или scrypt в production)
  2. Если snapshot-файл существует — загружает его
  3. Создаёт или загружает client по имени
  4. Коммитит snapshot
plantuml Diagram

Используется в: 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)

  1. Блокирует SharedState, сериализует в JSON
  2. Записывает в Stronghold Store под ключом "app_state"
  3. Коммитит snapshot на диск

Логика (load_state)

  1. Derive ключ, загружает snapshot
  2. Читает из Store по ключу "app_state"
  3. Десериализует 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 (защищённая память).

Логика

  1. Сериализует публичную часть состояния → Store
  2. Извлекает S3 credentials → шифрует → Vault
  3. Коммитит 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

  1. Записывает private key bytes в Vault location
  2. Коммитит snapshot

sign_ed25519

  1. Находит ключ в Vault по secret_type
  2. Выполняет Ed25519Sign Procedure (ключ в защищённой памяти)
  3. Возвращает 64-byte подпись
plantuml Diagram

Используется в: 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, коммитит snapshot
  • delete_vault_file() — физически удаляет snapshot-файл с диска

Используется в: clear_stronghold_svc, delete_stronghold_vault_svc