From 50be6621116f8ab9ee857b42aa2ca6ab51581fed Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Thu, 1 Oct 2026 15:16:59 -0400 Subject: [PATCH 1/3] wallet: propagate TLS certificate verification settings --- src/common/monero_rpc_connection.cpp | 10 +++++++--- src/common/monero_rpc_connection.h | 3 ++- src/wallet/monero_wallet_full.cpp | 21 +++++++++++++++++---- src/wallet/monero_wallet_full.h | 4 ++++ src/wallet/monero_wallet_rpc.cpp | 18 ++++++++++++------ src/wallet/monero_wallet_rpc.h | 2 ++ 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/common/monero_rpc_connection.cpp b/src/common/monero_rpc_connection.cpp index 0b3c32e9..e4329496 100644 --- a/src/common/monero_rpc_connection.cpp +++ b/src/common/monero_rpc_connection.cpp @@ -98,6 +98,7 @@ namespace monero { boost::lock_guard src_lock(rpc.m_mutex); m_uri = rpc.m_uri; m_proxy_uri = rpc.m_proxy_uri; + m_ssl_verify = rpc.m_ssl_verify; m_zmq_uri = rpc.m_zmq_uri; m_priority = rpc.m_priority; m_timeout_ms = rpc.m_timeout_ms; @@ -129,6 +130,7 @@ namespace monero { if (m_response_time != boost::none) monero_utils::add_json_member("responseTime", m_response_time.get(), allocator, root, value_num); // set bool values + monero_utils::add_json_member("sslVerify", m_ssl_verify, allocator, root); if (m_is_online != boost::none) monero_utils::add_json_member("isOnline", m_is_online.get(), allocator, root); if (m_is_authenticated != boost::none) monero_utils::add_json_member("isAuthenticated", m_is_authenticated.get(), allocator, root); @@ -328,12 +330,12 @@ namespace monero { m_http_client->set_auto_connect(true); } - // only reconfigure when uri/creds/proxy changed since last apply + // only reconfigure when uri/creds/proxy/ssl changed since last apply std::string uri = m_uri.value_or(""); std::string username = m_username.value_or(""); std::string password = m_password.value_or(""); std::string proxy_uri = m_proxy_uri.value_or(""); - auto key = std::make_tuple(uri, username, password, proxy_uri); + auto key = std::make_tuple(uri, username, password, proxy_uri, m_ssl_verify); if (m_applied == key) return; // unchanged, so reuse live connection @@ -346,7 +348,8 @@ namespace monero { } // detect ssl - epee::net_utils::ssl_support_t ssl = uri.rfind("https", 0) == 0 ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled; + epee::net_utils::ssl_options_t ssl = uri.rfind("https", 0) == 0 ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled; + if (!m_ssl_verify) ssl.verification = epee::net_utils::ssl_verification_t::none; if (!m_http_client->set_proxy(proxy_uri)) throw monero_error("Could not set proxy uri: " + proxy_uri); if (!m_http_client->set_server(uri, login, std::move(ssl))) throw monero_error("Could not set uri: " + uri); @@ -431,6 +434,7 @@ namespace monero { else if (key == std::string("username")) connection->m_username = it->second.data(); else if (key == std::string("password")) connection->m_password = it->second.data(); else if (key == std::string("proxyUri") || key == std::string("proxy_uri")) connection->m_proxy_uri = it->second.data(); + else if (key == std::string("sslVerify")) connection->m_ssl_verify = it->second.get_value(); else if (key == std::string("zmqUri")) connection->m_zmq_uri = it->second.data(); else if (key == std::string("priority")) connection->m_priority = it->second.get_value(); else if (key == std::string("timeoutMs")) connection->m_timeout_ms = it->second.get_value(); diff --git a/src/common/monero_rpc_connection.h b/src/common/monero_rpc_connection.h index c6b48e19..35886e88 100644 --- a/src/common/monero_rpc_connection.h +++ b/src/common/monero_rpc_connection.h @@ -113,6 +113,7 @@ namespace monero { boost::optional m_username; boost::optional m_password; boost::optional m_proxy_uri; + bool m_ssl_verify = true; // verify TLS certificates and hostnames boost::optional m_zmq_uri; // TODO: implement zmq listener boost::optional m_timeout_ms; // RPC request timeout in milliseconds boost::optional m_response_time; // automatically set by calling check_connection() @@ -272,7 +273,7 @@ namespace monero { boost::optional m_is_authenticated; mutable std::unique_ptr m_http_client; std::unordered_map m_attributes; - mutable std::tuple m_applied; + mutable std::tuple m_applied{"", "", "", "", true}; void ensure_configured() const; std::string invoke_post(const boost::string_ref uri, const std::string& body, const boost::optional& timeout_ms = boost::none) const; diff --git a/src/wallet/monero_wallet_full.cpp b/src/wallet/monero_wallet_full.cpp index 36800926..1ceb3760 100644 --- a/src/wallet/monero_wallet_full.cpp +++ b/src/wallet/monero_wallet_full.cpp @@ -1217,9 +1217,17 @@ namespace monero { } void monero_wallet_full::set_daemon_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const boost::optional& is_trusted) { + set_daemon_connection(uri, username, password, proxy_uri, is_trusted, true); + } + + void monero_wallet_full::set_daemon_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const boost::optional& is_trusted, bool ssl_verify) { MTRACE("set_daemon_connection(" << uri << ", " << username << ", " << "***" << ", " << proxy_uri << ")"); assert_not_closed(); + // validate before wallet2 changes its daemon and proxy + epee::net_utils::http::url_content parsed{}; + if (!epee::net_utils::parse_url(uri, parsed)) throw std::runtime_error("Failed to initialize wallet with daemon connection"); + // prepare uri, login, and trusted for wallet2 boost::optional login{}; login.emplace(username, password); @@ -1231,27 +1239,32 @@ namespace monero { } // detect ssl TODO: wallet2 does not detect ssl from uri - epee::net_utils::ssl_support_t ssl = uri.rfind("https", 0) == 0 ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled; + epee::net_utils::ssl_options_t ssl = uri.rfind("https", 0) == 0 ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled; + if (!ssl_verify) ssl.verification = epee::net_utils::ssl_verification_t::none; + boost::lock_guard lock(m_daemon_connection_mutex); if (!m_w2->set_daemon(uri, login, trusted, std::move(ssl), proxy_uri)) { throw std::runtime_error("Failed to initialize wallet with daemon connection"); } + m_daemon_proxy_uri = proxy_uri; + m_daemon_ssl_verify = ssl_verify; } void monero_wallet_full::set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted) { assert_not_closed(); if (connection == nullptr) set_daemon_connection("", "", "", "", is_trusted); - else set_daemon_connection(connection->m_uri == boost::none ? "" : connection->m_uri.get(), connection->m_username == boost::none ? "" : connection->m_username.get(), connection->m_password == boost::none ? "" : connection->m_password.get(), connection->m_proxy_uri == boost::none ? "" : connection->m_proxy_uri.get(), is_trusted); + else set_daemon_connection(connection->m_uri == boost::none ? "" : connection->m_uri.get(), connection->m_username == boost::none ? "" : connection->m_username.get(), connection->m_password == boost::none ? "" : connection->m_password.get(), connection->m_proxy_uri == boost::none ? "" : connection->m_proxy_uri.get(), is_trusted, connection->m_ssl_verify); } std::shared_ptr monero_wallet_full::get_daemon_connection() const { MTRACE("monero_wallet_full::get_daemon_connection()"); assert_not_closed(); + boost::lock_guard lock(m_daemon_connection_mutex); if (m_w2->get_daemon_address().empty()) return nullptr; std::shared_ptr connection = std::make_shared(); connection->m_uri = m_w2->get_daemon_address(); - std::string proxy_uri = m_w2->get_daemon_proxy(); - if (!proxy_uri.empty()) connection->m_proxy_uri = proxy_uri; + connection->m_ssl_verify = m_daemon_ssl_verify; + if (!m_daemon_proxy_uri.empty()) connection->m_proxy_uri = m_daemon_proxy_uri; if (m_w2->get_daemon_login()) { if (!m_w2->get_daemon_login()->username.empty()) connection->m_username = m_w2->get_daemon_login()->username; epee::wipeable_string wipeablePassword = m_w2->get_daemon_login()->password; diff --git a/src/wallet/monero_wallet_full.h b/src/wallet/monero_wallet_full.h index 698f4bc3..c4b05a05 100644 --- a/src/wallet/monero_wallet_full.h +++ b/src/wallet/monero_wallet_full.h @@ -277,6 +277,10 @@ namespace monero { private: friend struct wallet2_listener; + mutable boost::mutex m_daemon_connection_mutex; // serialize daemon changes and connection snapshots + std::string m_daemon_proxy_uri; + bool m_daemon_ssl_verify = true; + void set_daemon_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const boost::optional& is_trusted, bool ssl_verify); std::unique_ptr m_w2_listener; // internal wallet implementation listener std::set m_listeners; // external wallet listeners boost::recursive_mutex m_listeners_mutex; // serialize notifications with listener removal, including removal from a callback diff --git a/src/wallet/monero_wallet_rpc.cpp b/src/wallet/monero_wallet_rpc.cpp index 35413e5d..c83f3053 100644 --- a/src/wallet/monero_wallet_rpc.cpp +++ b/src/wallet/monero_wallet_rpc.cpp @@ -531,15 +531,21 @@ namespace monero { params->m_trusted = is_trusted; params->m_ssl_support = "autodetect"; params->m_ssl_options = ssl_options; + if (ssl_options == boost::none && connection != nullptr) { + params->m_ssl_options = monero::ssl_options(); + params->m_ssl_options->m_ssl_allow_any_cert = !connection->m_ssl_verify; + } - m_rpc->send_json_request("set_daemon", params); + // wallet rpc only enforces a ca file or fingerprints if ssl is enabled + if (params->m_ssl_options != boost::none && !params->m_ssl_options->m_ssl_allow_any_cert.value_or(false) && (!params->m_ssl_options->m_ssl_ca_file.value_or("").empty() || !params->m_ssl_options->m_ssl_allowed_fingerprints.empty())) params->m_ssl_support = "enabled"; - if (connection == nullptr || connection->m_uri == boost::none || connection->m_uri->empty()) { - m_daemon_connection = nullptr; - } - else { - m_daemon_connection = connection; + std::shared_ptr daemon_connection; + if (connection != nullptr && connection->m_uri != boost::none && !connection->m_uri->empty()) { + daemon_connection = std::make_shared(*connection); + daemon_connection->m_ssl_verify = !params->m_ssl_options->m_ssl_allow_any_cert.value_or(false); } + m_rpc->send_json_request("set_daemon", params); + m_daemon_connection = daemon_connection; } void monero_wallet_rpc::set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted) { diff --git a/src/wallet/monero_wallet_rpc.h b/src/wallet/monero_wallet_rpc.h index 0ed1697c..a061e1d8 100644 --- a/src/wallet/monero_wallet_rpc.h +++ b/src/wallet/monero_wallet_rpc.h @@ -135,6 +135,8 @@ namespace monero { std::set get_listeners() override; bool is_view_only() const override; std::shared_ptr get_daemon_connection() const override; + // explicit SSL options take precedence; the cache records allow-any-cert, not custom options + // wallet RPC enforces a CA file or fingerprints; otherwise SSL autodetect can accept unverified certificates void set_daemon_connection(const std::shared_ptr& connection, bool is_trusted, const boost::optional& ssl_options); void set_daemon_connection(const std::shared_ptr& connection, const boost::optional& is_trusted = boost::none) override; void set_daemon_connection(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const boost::optional& is_trusted = boost::none) override; From 893dd38347e6ebfe2208e53f65a7bd3ccf2c1e6f Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:52:36 -0400 Subject: [PATCH 2/3] build: update monero-project submodule --- external/monero-project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/monero-project b/external/monero-project index c76165e5..a4fd0404 160000 --- a/external/monero-project +++ b/external/monero-project @@ -1 +1 @@ -Subproject commit c76165e5407a27881eb223bb689c5b8d3b0d076d +Subproject commit a4fd0404e215920445196394f2ecf90763e6a8bf From 1db3477df49cf3f9b4060af10d6836d80a0d277a Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:54:59 -0400 Subject: [PATCH 3/3] docs: update api docs --- ...ro_1_1monero__rpc__connection-members.html | 35 +- ...lassmonero_1_1monero__rpc__connection.html | 9 +- .../monero__rpc__connection_8h_source.html | 145 +++--- .../monero__wallet__full_8h_source.html | 292 +++++------ .../monero__wallet__rpc_8h_source.html | 470 +++++++++--------- 5 files changed, 481 insertions(+), 470 deletions(-) diff --git a/docs/doxygen/classmonero_1_1monero__rpc__connection-members.html b/docs/doxygen/classmonero_1_1monero__rpc__connection-members.html index 5f50d98b..ae0241c1 100644 --- a/docs/doxygen/classmonero_1_1monero__rpc__connection-members.html +++ b/docs/doxygen/classmonero_1_1monero__rpc__connection-members.html @@ -116,23 +116,24 @@ m_priority (defined in monero::monero_rpc_connection)monero::monero_rpc_connection m_proxy_uri (defined in monero::monero_rpc_connection)monero::monero_rpc_connection m_response_time (defined in monero::monero_rpc_connection)monero::monero_rpc_connection - m_timeout_ms (defined in monero::monero_rpc_connection)monero::monero_rpc_connection - m_uri (defined in monero::monero_rpc_connection)monero::monero_rpc_connection - m_username (defined in monero::monero_rpc_connection)monero::monero_rpc_connection - m_zmq_uri (defined in monero::monero_rpc_connection)monero::monero_rpc_connection - monero_rpc_connection(const std::string &uri="", const std::string &username="", const std::string &password="", const std::string &proxy_uri="", const std::string &zmq_uri="", int priority=0, const boost::optional< uint32_t > &timeout_ms=boost::none)monero::monero_rpc_connection - monero_rpc_connection(const monero_rpc_connection &rpc)monero::monero_rpc_connection - send_binary_request(const monero_rpc_request &request, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection - send_json_request(const std::string &path, const std::shared_ptr< serializable_struct > &params=nullptr, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection - send_json_request(const monero_rpc_request &request, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection - send_path_request(const std::string &path, const std::shared_ptr< serializable_struct > &params=nullptr, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection - send_path_request(const monero_rpc_request &request, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection - send_rpc_request(const boost::string_ref uri, const monero_rpc_request &request, monero_rpc_response &response, const boost::optional< uint32_t > &timeout_ms=boost::none, bool binary=false) const (defined in monero::monero_rpc_connection)monero::monero_rpc_connectionprotected - serialize() constmonero::serializable_struct - set_attribute(const std::string &key, const std::string &val)monero::monero_rpc_connection - set_credentials(const std::string &username, const std::string &password)monero::monero_rpc_connection - to_rapidjson_val(rapidjson::Document::AllocatorType &allocator) const overridemonero::monero_rpc_connectionvirtual - ~serializable_struct()=default (defined in monero::serializable_struct)monero::serializable_structvirtual + m_ssl_verify (defined in monero::monero_rpc_connection)monero::monero_rpc_connection + m_timeout_ms (defined in monero::monero_rpc_connection)monero::monero_rpc_connection + m_uri (defined in monero::monero_rpc_connection)monero::monero_rpc_connection + m_username (defined in monero::monero_rpc_connection)monero::monero_rpc_connection + m_zmq_uri (defined in monero::monero_rpc_connection)monero::monero_rpc_connection + monero_rpc_connection(const std::string &uri="", const std::string &username="", const std::string &password="", const std::string &proxy_uri="", const std::string &zmq_uri="", int priority=0, const boost::optional< uint32_t > &timeout_ms=boost::none)monero::monero_rpc_connection + monero_rpc_connection(const monero_rpc_connection &rpc)monero::monero_rpc_connection + send_binary_request(const monero_rpc_request &request, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection + send_json_request(const std::string &path, const std::shared_ptr< serializable_struct > &params=nullptr, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection + send_json_request(const monero_rpc_request &request, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection + send_path_request(const std::string &path, const std::shared_ptr< serializable_struct > &params=nullptr, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection + send_path_request(const monero_rpc_request &request, const boost::optional< uint32_t > &timeout_ms=boost::none) constmonero::monero_rpc_connection + send_rpc_request(const boost::string_ref uri, const monero_rpc_request &request, monero_rpc_response &response, const boost::optional< uint32_t > &timeout_ms=boost::none, bool binary=false) const (defined in monero::monero_rpc_connection)monero::monero_rpc_connectionprotected + serialize() constmonero::serializable_struct + set_attribute(const std::string &key, const std::string &val)monero::monero_rpc_connection + set_credentials(const std::string &username, const std::string &password)monero::monero_rpc_connection + to_rapidjson_val(rapidjson::Document::AllocatorType &allocator) const overridemonero::monero_rpc_connectionvirtual + ~serializable_struct()=default (defined in monero::serializable_struct)monero::serializable_structvirtual