F8 (security). Two related fixes to how the wallet decides whether an RPC target is
safe to send Basic-auth credentials to:
- isLocalHost() was matching any host that merely *starts* "127." via
rfind("127.",0)==0, so "127.evil.com" (and "127.0.0.1.attacker", "127.300.0.1",
"1270.0.0.1") were misclassified as loopback and treated as local. It now uses a
strict isExactIPv4Loopback() parser: exactly four 0-255 dot-separated octets with
the first == 127. localhost / ::1 / [::1] handling is unchanged.
- A remote rpchost over plain HTTP (no rpctls=1) previously only produced a
dismissible warning and then sent rpcuser:rpcpassword in cleartext, where a
local-network MITM could capture them. tryConnect() now REFUSES that connection
(clear status line + one-time notification, no creds sent) unless the user opts in
explicitly with rpcallowplaintext=1 in DRAGONX.conf (new
ConnectionConfig::allow_plaintext_remote, parsed in parseConfFile; policy in the
new allowsPlaintextRemote()). Local/embedded daemons and rpctls=1 remotes are
unaffected.
BREAKING: a wallet configured for remote plaintext RPC will stop connecting until
rpcallowplaintext=1 (or rpctls=1) is added to DRAGONX.conf. Must be called out in the
release notes. The Settings-toggle UI is deferred (the conf-key opt-in is the recovery
path; see docs/daemon-startup-hardening.md).
Adds testIsLocalHost and testAllowsPlaintextRemote to test_phase4.cpp; one i18n key
(English) added to i18n.cpp.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
148 lines
4.4 KiB
C++
148 lines
4.4 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <utility>
|
|
|
|
namespace dragonx {
|
|
namespace rpc {
|
|
|
|
/**
|
|
* @brief Connection configuration
|
|
*/
|
|
enum class AuthSource {
|
|
Missing,
|
|
ConfigFile,
|
|
Cookie
|
|
};
|
|
|
|
struct ConnectionConfig {
|
|
std::string host = "127.0.0.1";
|
|
std::string port = "21769";
|
|
std::string rpcuser;
|
|
std::string rpcpassword;
|
|
std::string hush_dir;
|
|
std::string proxy; // SOCKS5 proxy for Tor
|
|
bool use_embedded = true;
|
|
bool use_tls = false;
|
|
bool allow_plaintext_remote = false; // rpcallowplaintext=1 — opt in to plaintext creds to a remote host
|
|
AuthSource auth_source = AuthSource::Missing;
|
|
// Non-empty when autoDetectConfig() could not create the data directory; callers
|
|
// should surface it and abort the connect rather than proceeding blindly.
|
|
std::string dir_error;
|
|
};
|
|
|
|
/**
|
|
* @brief Manages connection to dragonxd
|
|
*
|
|
* Handles auto-detection of DRAGONX.conf, starting embedded daemon,
|
|
* and connection lifecycle.
|
|
*/
|
|
class Connection {
|
|
public:
|
|
Connection();
|
|
~Connection();
|
|
|
|
/**
|
|
* @brief Auto-detect and load connection config
|
|
* @return Config from DRAGONX.conf or defaults
|
|
*/
|
|
static ConnectionConfig autoDetectConfig();
|
|
|
|
/**
|
|
* @brief Get the default DRAGONX.conf location
|
|
*/
|
|
static std::string getDefaultConfPath();
|
|
|
|
/**
|
|
* @brief Get the default DragonX data directory
|
|
*/
|
|
static std::string getDefaultDataDir();
|
|
|
|
/**
|
|
* @brief Parse a DRAGONX.conf file
|
|
* @param path Path to conf file
|
|
* @return Parsed configuration
|
|
*/
|
|
static ConnectionConfig parseConfFile(const std::string& path);
|
|
|
|
/**
|
|
* @brief Check if Sapling params exist
|
|
*/
|
|
static bool verifySaplingParams();
|
|
|
|
// Verify the Sapling params in `dir` against a { filename, expected-sha256-hex } list.
|
|
// Exposed with an injectable dir + digest list so the integrity + marker-cache logic is
|
|
// unit-testable without the real ~48MB params; verifySaplingParams() calls it with the
|
|
// pinned production digests and getSaplingParamsDir().
|
|
static bool verifySaplingParamsIn(
|
|
const std::string& dir,
|
|
const std::vector<std::pair<std::string, std::string>>& digests);
|
|
|
|
/**
|
|
* @brief Get the Sapling params directory
|
|
*/
|
|
static std::string getSaplingParamsDir();
|
|
|
|
/**
|
|
* @brief Create a default DRAGONX.conf file
|
|
* @param path Path to create the file
|
|
* @return true if created successfully
|
|
*/
|
|
static bool createDefaultConfig(const std::string& path);
|
|
|
|
/**
|
|
* @brief Ensure exportdir is set in DRAGONX.conf
|
|
* @param confPath Path to the conf file
|
|
* @return true if exportdir exists or was added
|
|
*/
|
|
static bool ensureExportDir(const std::string& confPath);
|
|
|
|
/**
|
|
* @brief Ensure wallet encryption flags are set in DRAGONX.conf
|
|
* @param confPath Path to the conf file
|
|
* @return true if flags exist or were added
|
|
*/
|
|
static bool ensureEncryptionEnabled(const std::string& confPath);
|
|
|
|
/**
|
|
* @brief Try to read .cookie auth file from the data directory
|
|
* @param dataDir Path to the daemon data directory
|
|
* @param user Output: cookie username (__cookie__)
|
|
* @param password Output: cookie password
|
|
* @return true if cookie file was read successfully
|
|
*/
|
|
static bool readAuthCookie(const std::string& dataDir, std::string& user, std::string& password);
|
|
|
|
/**
|
|
* @brief Build a cookie-auth retry config from a failed config-auth attempt
|
|
*/
|
|
static bool buildCookieAuthConfig(const ConnectionConfig& base, ConnectionConfig& cookieConfig);
|
|
|
|
/**
|
|
* @brief Whether a host is local enough for plaintext HTTP RPC
|
|
*/
|
|
static bool isLocalHost(const std::string& host);
|
|
|
|
/**
|
|
* @brief Whether this config would send RPC credentials over plaintext to a remote host
|
|
*/
|
|
static bool usesPlaintextRemote(const ConnectionConfig& config);
|
|
|
|
// Whether plaintext credentials to a remote host are explicitly allowed (opt-in via the
|
|
// DRAGONX.conf rpcallowplaintext key). Off by default: usesPlaintextRemote() && !this
|
|
// means the connect is refused.
|
|
static bool allowsPlaintextRemote(const ConnectionConfig& config);
|
|
|
|
static const char* authSourceName(AuthSource source);
|
|
|
|
private:
|
|
};
|
|
|
|
} // namespace rpc
|
|
} // namespace dragonx
|