fix(wallet-switch): cluster A — identity/secret teardown on wallet switch

From the wallet-switching audit (HIGH-severity cross-wallet leaks):

- Chat identity leak: the full-node switch (switchToWallet) and seed-migration
  adopt reset state_ but NOT the HushChat identity, so wallet A's decrypted
  conversations surfaced under wallet B and outgoing chat was signed with A's
  keypair. Factor the existing teardown into App::resetChatSession() and call it
  on both wallet-change paths (the lite path already reset it via
  rebuildLiteWallet, which now uses the helper too).
- Global PIN vault: the PIN quick-unlock vault was a single vault.dat, so after
  a switch wallet A's stored passphrase was offered/applied to encrypted wallet
  B. SecureVault is now scoped per wallet (vault-<walletfile>.dat); the default
  wallet keeps the legacy vault.dat for back-compat. vault_ is constructed for
  the active wallet and re-scoped on switch, so B has its own (empty) vault.
- Lock-screen state: switching now clears the carried-over failed-attempt
  counter + lockout timer and secure-zeroes the passphrase/PIN entry buffers so
  the previous wallet's unlock state can't apply to the new one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 12:29:45 -05:00
parent 6f0f95f4fb
commit f546d3e2b1
5 changed files with 67 additions and 21 deletions

View File

@@ -334,8 +334,9 @@ bool App::init()
// Ensure ObsidianDragon config directory and template files exist
util::Platform::ensureObsidianDragonSetup();
// Initialize PIN vault
vault_ = std::make_unique<util::SecureVault>();
// Initialize PIN vault, scoped to the active wallet so one wallet's stored passphrase is never
// offered for another (the default wallet keeps the legacy vault.dat).
vault_ = std::make_unique<util::SecureVault>(settings_ ? settings_->getActiveWalletFile() : "");
// Theme is now applied via SkinManager below after UISchema loads.
// The old SetThemeById() C++ fallback is no longer needed at startup
@@ -634,12 +635,7 @@ void App::rebuildLiteWallet(bool force)
// A rebuilt controller may back a different wallet (server switch / re-open); drop any chat
// identity + decrypted messages, lock the DB, and re-arm provisioning so it re-derives (and
// reloads the right wallet's history) from the newly opened wallet's seed.
chat_service_.clearIdentity();
chat_service_.store().clear();
chat_db_.lock();
chat_identity_provisioned_ = false;
chat_identity_fetch_in_flight_ = false;
chat_identity_unavailable_ = false;
resetChatSession();
}
void App::update()