fix(security): scrub in-memory key/passphrase copies in the wallet secret paths (W4-1, W4-3, W2-3)
The wallet-hardening memzero cluster. Uses the file's established sodium_memzero pattern (matching the existing lambda-capture scrub at app_network.cpp:2885 and the JSON scrub at :4025) rather than a new type, since importPrivateKey/sweepPrivateKey are fund-moving code. - W4-1 importPrivateKey / sweepPrivateKey: the spending/viewing key was copied ≥3× (calling frame -> worker-lambda capture -> JSON params) and never scrubbed. Now zeroed on all paths: the calling-frame copy after the worker post, the lambda's captured copy (lambda made mutable, zeroed once the request is sent), and the request params copy. - W4-3 exportAllKeys / backupWallet: the concatenated all-keys buffer is now zeroed after the consumer uses it, and the backup is written via Platform::writeFileAtomically(..., restrictPermissions=true) — atomic and owner-only (0600) — instead of a umask-default std::ofstream that left it world-readable. - W2-3 decrypt-wallet passphrase: std::move-captured into the worker lambda (no plaintext copy left in the calling frame) and sodium_memzero'd right after unlockWallet, its only use. Not unit-testable (no observable RPC effect — the key value sent to the daemon is unchanged; only post-use memory zeroing is added). Build-clean; ctest 1/1. See docs/wallet-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,8 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <sodium.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
@@ -1480,12 +1482,14 @@ void App::renderDecryptWalletDialog() {
|
||||
|
||||
// Run entire decrypt flow on worker thread
|
||||
if (worker_) {
|
||||
worker_->post([this, passphrase]() -> rpc::RPCWorker::MainCb {
|
||||
worker_->post([this, passphrase = std::move(passphrase)]() mutable -> rpc::RPCWorker::MainCb {
|
||||
WalletSecurityDecryptRpcAdapter decryptRpc(rpc_.get(),
|
||||
[this](rpc::RPCClient& client, const char* context) {
|
||||
return sendStopCommandSafely(client, context);
|
||||
});
|
||||
auto unlock = services::WalletSecurityWorkflowExecutor::unlockWallet(passphrase, decryptRpc);
|
||||
// Scrub the passphrase — unlock is its only use in this flow.
|
||||
if (!passphrase.empty()) sodium_memzero(&passphrase[0], passphrase.size());
|
||||
if (!unlock.ok) {
|
||||
return [this]() {
|
||||
wallet_security_workflow_.failEntry("Incorrect passphrase");
|
||||
|
||||
Reference in New Issue
Block a user