Secure websocket support and certificate config improvements. (#3032)
This commit is contained in:
parent
6442495a25
commit
1e60488192
22 changed files with 631 additions and 151 deletions
|
@ -1,5 +1,6 @@
|
|||
#include <nano/lib/jsonconfig.hpp>
|
||||
#include <nano/lib/rpcconfig.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/lib/tomlconfig.hpp>
|
||||
#include <nano/node/daemonconfig.hpp>
|
||||
#include <nano/secure/utility.hpp>
|
||||
|
@ -903,3 +904,61 @@ TEST (toml, daemon_read_config)
|
|||
ASSERT_EQ (error.get_message (), expected_message2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Deserialize an tls config with non-default values */
|
||||
TEST (toml, tls_config_deserialize_no_defaults)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
// A config file with values that differs from devnet defaults
|
||||
ss << R"toml(
|
||||
enable_https=true
|
||||
enable_wss=true
|
||||
verbose_logging=true
|
||||
server_cert_path="xyz.cert.pem"
|
||||
server_key_path="xyz.key.pem"
|
||||
server_key_passphrase="xyz"
|
||||
server_dh_path="xyz.pem"
|
||||
)toml";
|
||||
|
||||
nano::tomlconfig toml;
|
||||
toml.read (ss);
|
||||
nano::tls_config conf;
|
||||
nano::tls_config defaults;
|
||||
conf.deserialize_toml (toml);
|
||||
|
||||
ASSERT_FALSE (toml.get_error ()) << toml.get_error ().get_message ();
|
||||
|
||||
ASSERT_NE (conf.enable_https, defaults.enable_https);
|
||||
ASSERT_NE (conf.enable_wss, defaults.enable_wss);
|
||||
ASSERT_NE (conf.verbose_logging, defaults.verbose_logging);
|
||||
ASSERT_NE (conf.server_cert_path, defaults.server_cert_path);
|
||||
ASSERT_NE (conf.server_key_path, defaults.server_key_path);
|
||||
ASSERT_NE (conf.server_key_passphrase, defaults.server_key_passphrase);
|
||||
ASSERT_NE (conf.server_dh_path, defaults.server_dh_path);
|
||||
}
|
||||
|
||||
/** Empty tls config file should match a default config object, and there should be no required values. */
|
||||
TEST (toml, tls_config_defaults)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
// A config with no values
|
||||
ss << R"toml()toml";
|
||||
|
||||
nano::tomlconfig toml;
|
||||
toml.read (ss);
|
||||
nano::tls_config conf;
|
||||
nano::tls_config defaults;
|
||||
conf.deserialize_toml (toml);
|
||||
|
||||
ASSERT_FALSE (toml.get_error ()) << toml.get_error ().get_message ();
|
||||
|
||||
ASSERT_EQ (conf.enable_https, defaults.enable_wss);
|
||||
ASSERT_EQ (conf.enable_wss, defaults.enable_wss);
|
||||
ASSERT_EQ (conf.verbose_logging, defaults.verbose_logging);
|
||||
ASSERT_EQ (conf.server_cert_path, defaults.server_cert_path);
|
||||
ASSERT_EQ (conf.server_key_path, defaults.server_key_path);
|
||||
ASSERT_EQ (conf.server_key_passphrase, defaults.server_key_passphrase);
|
||||
ASSERT_EQ (conf.server_dh_path, defaults.server_dh_path);
|
||||
}
|
||||
|
|
|
@ -71,6 +71,8 @@ add_library(
|
|||
threading.cpp
|
||||
timer.hpp
|
||||
timer.cpp
|
||||
tlsconfig.hpp
|
||||
tlsconfig.cpp
|
||||
tomlconfig.hpp
|
||||
tomlconfig.cpp
|
||||
utility.hpp
|
||||
|
|
|
@ -304,8 +304,14 @@ std::string get_qtwallet_toml_config_path (boost::filesystem::path const & data_
|
|||
{
|
||||
return (data_path / "config-qtwallet.toml").string ();
|
||||
}
|
||||
|
||||
std::string get_access_toml_config_path (boost::filesystem::path const & data_path)
|
||||
{
|
||||
return (data_path / "config-access.toml").string ();
|
||||
}
|
||||
|
||||
std::string get_tls_toml_config_path (boost::filesystem::path const & data_path)
|
||||
{
|
||||
return (data_path / "config-tls.toml").string ();
|
||||
}
|
||||
} // namespace nano
|
||||
|
|
|
@ -281,6 +281,7 @@ std::string get_node_toml_config_path (boost::filesystem::path const & data_path
|
|||
std::string get_rpc_toml_config_path (boost::filesystem::path const & data_path);
|
||||
std::string get_access_toml_config_path (boost::filesystem::path const & data_path);
|
||||
std::string get_qtwallet_toml_config_path (boost::filesystem::path const & data_path);
|
||||
std::string get_tls_toml_config_path (boost::filesystem::path const & data_path);
|
||||
|
||||
/** Checks if we are running inside a valgrind instance */
|
||||
bool running_within_valgrind ();
|
||||
|
|
|
@ -153,7 +153,7 @@ nano::error nano::rpc_config::deserialize_toml (nano::tomlconfig & toml)
|
|||
auto rpc_secure_l (toml.get_optional_child ("secure"));
|
||||
if (rpc_secure_l)
|
||||
{
|
||||
secure.deserialize_toml (*rpc_secure_l);
|
||||
return nano::error ("The RPC secure configuration has moved to config-tls.toml. Please update the configuration.");
|
||||
}
|
||||
|
||||
boost::asio::ip::address_v6 address_l;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <nano/lib/config.hpp>
|
||||
#include <nano/lib/errors.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
@ -19,8 +20,12 @@ namespace nano
|
|||
{
|
||||
class jsonconfig;
|
||||
class tomlconfig;
|
||||
class tls_config;
|
||||
|
||||
/** Configuration options for RPC TLS */
|
||||
/**
|
||||
* Configuration options for RPC TLS.
|
||||
* @note This is deprecated, but kept for a few versions in order to yield a config error message on startup if it's used.
|
||||
*/
|
||||
class rpc_secure_config final
|
||||
{
|
||||
public:
|
||||
|
@ -29,6 +34,7 @@ public:
|
|||
nano::error serialize_toml (nano::tomlconfig &) const;
|
||||
nano::error deserialize_toml (nano::tomlconfig &);
|
||||
|
||||
private:
|
||||
/** If true, enable TLS */
|
||||
bool enable{ false };
|
||||
/** If true, log certificate verification details */
|
||||
|
@ -85,6 +91,8 @@ public:
|
|||
uint8_t max_json_depth{ 20 };
|
||||
uint64_t max_request_size{ 32 * 1024 * 1024 };
|
||||
nano::rpc_logging_config rpc_logging;
|
||||
/** Optional TLS config */
|
||||
std::shared_ptr<nano::tls_config> tls_config;
|
||||
static unsigned json_version ()
|
||||
{
|
||||
return 1;
|
||||
|
|
188
nano/lib/tlsconfig.cpp
Normal file
188
nano/lib/tlsconfig.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include <nano/lib/config.hpp>
|
||||
#include <nano/lib/logger_mt.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/lib/tomlconfig.hpp>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace nano
|
||||
{
|
||||
nano::error nano::tls_config::serialize_toml (nano::tomlconfig & toml) const
|
||||
{
|
||||
toml.put ("enable_https", enable_https, "Enable or disable https:// support.\ntype:bool");
|
||||
toml.put ("enable_wss", enable_wss, "Enable or disable wss:// support.\ntype:bool");
|
||||
toml.put ("verbose_logging", verbose_logging, "Enable or disable verbose TLS logging.\ntype:bool");
|
||||
toml.put ("server_key_passphrase", server_key_passphrase, "Server key passphrase.\ntype:string");
|
||||
toml.put ("server_cert_path", server_cert_path, "Directory containing certificates.\ntype:string,path");
|
||||
toml.put ("server_key_path", server_key_path, "Path to server key PEM file.\ntype:string,path");
|
||||
toml.put ("server_dh_path", server_dh_path, "Path to Diffie-Hellman params file.\ntype:string,path");
|
||||
toml.put ("client_certs_path", client_certs_path, "Directory containing optional client certificates.\ntype:string,path");
|
||||
return toml.get_error ();
|
||||
}
|
||||
|
||||
nano::error nano::tls_config::deserialize_toml (nano::tomlconfig & toml)
|
||||
{
|
||||
toml.get<bool> ("enable_https", enable_https);
|
||||
toml.get<bool> ("enable_wss", enable_wss);
|
||||
toml.get<bool> ("verbose_logging", verbose_logging);
|
||||
toml.get<std::string> ("server_key_passphrase", server_key_passphrase);
|
||||
toml.get<std::string> ("server_cert_path", server_cert_path);
|
||||
toml.get<std::string> ("server_key_path", server_key_path);
|
||||
toml.get<std::string> ("server_dh_path", server_dh_path);
|
||||
toml.get<std::string> ("client_certs_path", client_certs_path);
|
||||
return toml.get_error ();
|
||||
}
|
||||
|
||||
#ifdef NANO_SECURE_RPC
|
||||
namespace
|
||||
{
|
||||
bool on_verify_certificate (bool preverified, boost::asio::ssl::verify_context & ctx, nano::tls_config & config_a, nano::logger_mt & logger_a)
|
||||
{
|
||||
X509_STORE_CTX * cts = ctx.native_handle ();
|
||||
auto error (X509_STORE_CTX_get_error (cts));
|
||||
switch (error)
|
||||
{
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
|
||||
logger_a.always_log ("TLS: Unable to get issuer");
|
||||
break;
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
|
||||
logger_a.always_log ("TLS: Certificate not yet valid");
|
||||
break;
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
|
||||
logger_a.always_log ("TLS: Certificate expired");
|
||||
break;
|
||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
||||
if (config_a.verbose_logging)
|
||||
{
|
||||
logger_a.always_log ("TLS: Self-signed certificate in chain");
|
||||
}
|
||||
|
||||
// Allow self-signed certificates
|
||||
preverified = true;
|
||||
break;
|
||||
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
||||
logger_a.always_log ("TLS: Self-signed certificate not in the list of trusted certs (forgot to subject-hash certificate filename?)");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (config_a.verbose_logging)
|
||||
{
|
||||
if (error != 0)
|
||||
{
|
||||
logger_a.always_log ("TLS: Error: ", X509_verify_cert_error_string (error));
|
||||
logger_a.always_log ("TLS: Error chain depth : ", X509_STORE_CTX_get_error_depth (cts));
|
||||
}
|
||||
|
||||
X509 * cert = X509_STORE_CTX_get_current_cert (cts);
|
||||
char subject_name[512];
|
||||
X509_NAME_oneline (X509_get_subject_name (cert), subject_name, sizeof (subject_name) - 1);
|
||||
logger_a.always_log ("TLS: Verifying: ", subject_name);
|
||||
logger_a.always_log ("TLS: Verification: ", preverified);
|
||||
}
|
||||
else if (!preverified)
|
||||
{
|
||||
logger_a.always_log ("TLS: Pre-verification failed. Turn on verbose logging for more information.");
|
||||
}
|
||||
|
||||
return preverified;
|
||||
}
|
||||
|
||||
void load_certs (nano::tls_config & config_a, nano::logger_mt & logger_a)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This is called if the key is password protected
|
||||
config_a.ssl_context.set_password_callback (
|
||||
[&config_a] (std::size_t,
|
||||
boost::asio::ssl::context_base::password_purpose) {
|
||||
return config_a.server_key_passphrase;
|
||||
});
|
||||
|
||||
// The following two options disables the session cache and enables stateless session resumption.
|
||||
// This is necessary because of the way the RPC server abruptly terminate connections.
|
||||
SSL_CTX_set_session_cache_mode (config_a.ssl_context.native_handle (), SSL_SESS_CACHE_OFF);
|
||||
SSL_CTX_set_options (config_a.ssl_context.native_handle (), SSL_OP_NO_TICKET);
|
||||
|
||||
config_a.ssl_context.set_options (
|
||||
boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use);
|
||||
|
||||
config_a.ssl_context.use_certificate_chain_file (config_a.server_cert_path);
|
||||
config_a.ssl_context.use_private_key_file (config_a.server_key_path, boost::asio::ssl::context::pem);
|
||||
config_a.ssl_context.use_tmp_dh_file (config_a.server_dh_path);
|
||||
|
||||
// Verify client certificates?
|
||||
if (!config_a.client_certs_path.empty ())
|
||||
{
|
||||
config_a.ssl_context.set_verify_mode (boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_peer);
|
||||
config_a.ssl_context.add_verify_path (config_a.client_certs_path);
|
||||
config_a.ssl_context.set_verify_callback ([&config_a, &logger_a] (auto preverified, auto & ctx) {
|
||||
return on_verify_certificate (preverified, ctx, config_a, logger_a);
|
||||
});
|
||||
}
|
||||
|
||||
logger_a.always_log ("TLS: successfully configured");
|
||||
}
|
||||
catch (boost::system::system_error const & err)
|
||||
{
|
||||
auto error (boost::str (boost::format ("Could not load certificate information: %1%. Make sure the paths and the passphrase in config-tls.toml are correct.") % err.what ()));
|
||||
std::cerr << error << std::endl;
|
||||
logger_a.always_log (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
nano::error read_tls_config_toml (boost::filesystem::path const & data_path_a, nano::tls_config & config_a, nano::logger_mt & logger_a, std::vector<std::string> const & config_overrides)
|
||||
{
|
||||
nano::error error;
|
||||
auto toml_config_path = nano::get_tls_toml_config_path (data_path_a);
|
||||
|
||||
// Parse and deserialize
|
||||
nano::tomlconfig toml;
|
||||
|
||||
std::stringstream config_overrides_stream;
|
||||
for (auto const & entry : config_overrides)
|
||||
{
|
||||
config_overrides_stream << entry << std::endl;
|
||||
}
|
||||
config_overrides_stream << std::endl;
|
||||
|
||||
// Make sure we don't create an empty toml file if it doesn't exist. Running without a tls toml file is the default.
|
||||
if (!error)
|
||||
{
|
||||
if (boost::filesystem::exists (toml_config_path))
|
||||
{
|
||||
error = toml.read (config_overrides_stream, toml_config_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
error = toml.read (config_overrides_stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (!error)
|
||||
{
|
||||
error = config_a.deserialize_toml (toml);
|
||||
}
|
||||
|
||||
if (!error && (config_a.enable_https || config_a.enable_wss))
|
||||
{
|
||||
#ifdef NANO_SECURE_RPC
|
||||
load_certs (config_a, logger_a);
|
||||
#else
|
||||
auto msg ("https or wss is enabled in the TLS configuration, but the node is not built with NANO_SECURE_RPC");
|
||||
std::cerr << msg << std::endl;
|
||||
logger_a.always_log (msg);
|
||||
std::exit (1);
|
||||
#endif
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
63
nano/lib/tlsconfig.hpp
Normal file
63
nano/lib/tlsconfig.hpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <nano/lib/config.hpp>
|
||||
#include <nano/lib/errors.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef NANO_SECURE_RPC
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#endif
|
||||
|
||||
namespace boost::filesystem
|
||||
{
|
||||
class path;
|
||||
}
|
||||
|
||||
namespace nano
|
||||
{
|
||||
class logger_mt;
|
||||
class jsonconfig;
|
||||
class tomlconfig;
|
||||
|
||||
/** Configuration options for secure RPC and WebSocket connections */
|
||||
class tls_config final
|
||||
{
|
||||
public:
|
||||
nano::error serialize_toml (nano::tomlconfig &) const;
|
||||
nano::error deserialize_toml (nano::tomlconfig &);
|
||||
|
||||
/** If true, enable TLS for RPC (only allow https, otherwise only allow http) */
|
||||
bool enable_https{ false };
|
||||
|
||||
/** If true, enable TLS for WebSocket (only allow wss, otherwise only allow ws) */
|
||||
bool enable_wss{ false };
|
||||
|
||||
/** If true, log certificate verification details */
|
||||
bool verbose_logging{ false };
|
||||
|
||||
/** Must be set if the private key PEM is password protected */
|
||||
std::string server_key_passphrase;
|
||||
|
||||
/** Path to certificate- or chain file. Must be PEM formatted. */
|
||||
std::string server_cert_path;
|
||||
|
||||
/** Path to private key file. Must be PEM formatted.*/
|
||||
std::string server_key_path;
|
||||
|
||||
/** Path to dhparam file */
|
||||
std::string server_dh_path;
|
||||
|
||||
/** Optional path to directory containing client certificates */
|
||||
std::string client_certs_path;
|
||||
|
||||
#ifdef NANO_SECURE_RPC
|
||||
/** The context needs to be shared between sessions to make resumption work */
|
||||
boost::asio::ssl::context ssl_context{ boost::asio::ssl::context::tlsv12_server };
|
||||
#endif
|
||||
};
|
||||
|
||||
nano::error read_tls_config_toml (boost::filesystem::path const & data_path_a, nano::tls_config & config_a, nano::logger_mt & logger_a, std::vector<std::string> const & config_overrides = std::vector<std::string> ());
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#include <nano/boost/process/child.hpp>
|
||||
#include <nano/lib/signal_manager.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/lib/utility.hpp>
|
||||
#include <nano/nano_node/daemon.hpp>
|
||||
#include <nano/node/cli.hpp>
|
||||
|
@ -90,6 +91,19 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano::
|
|||
{
|
||||
config.node.logging.init (data_path);
|
||||
nano::logger_mt logger{ config.node.logging.min_time_between_log_output };
|
||||
|
||||
auto tls_config (std::make_shared<nano::tls_config> ());
|
||||
error = nano::read_tls_config_toml (data_path, *tls_config, logger);
|
||||
if (error)
|
||||
{
|
||||
std::cerr << error.get_message () << std::endl;
|
||||
std::exit (1);
|
||||
}
|
||||
else
|
||||
{
|
||||
config.node.websocket_config.tls_config = tls_config;
|
||||
}
|
||||
|
||||
boost::asio::io_context io_ctx;
|
||||
auto opencl (nano::opencl_work::create (config.opencl_enable, config.opencl, logger, config.node.network_params.work));
|
||||
nano::work_pool opencl_work (config.node.network_params.network, config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl] (nano::work_version const version_a, nano::root const & root_a, uint64_t difficulty_a, std::atomic<int> & ticket_a) {
|
||||
|
@ -157,6 +171,8 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano::
|
|||
std::cout << error.get_message () << std::endl;
|
||||
std::exit (1);
|
||||
}
|
||||
|
||||
rpc_config.tls_config = tls_config;
|
||||
rpc_handler = std::make_unique<nano::inprocess_rpc_handler> (*node, ipc_server, config.rpc, [&ipc_server, &workers = node->workers, &io_ctx] () {
|
||||
ipc_server.stop ();
|
||||
workers.add_timed_task (std::chrono::steady_clock::now () + std::chrono::seconds (3), [&io_ctx] () {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <nano/lib/errors.hpp>
|
||||
#include <nano/lib/signal_manager.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/lib/utility.hpp>
|
||||
#include <nano/node/cli.hpp>
|
||||
#include <nano/node/ipc/ipc_server.hpp>
|
||||
|
@ -46,6 +47,20 @@ void run (boost::filesystem::path const & data_path, std::vector<std::string> co
|
|||
if (!error)
|
||||
{
|
||||
logging_init (data_path);
|
||||
nano::logger_mt logger;
|
||||
|
||||
auto tls_config (std::make_shared<nano::tls_config> ());
|
||||
error = nano::read_tls_config_toml (data_path, *tls_config, logger);
|
||||
if (error)
|
||||
{
|
||||
std::cerr << error.get_message () << std::endl;
|
||||
std::exit (1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rpc_config.tls_config = tls_config;
|
||||
}
|
||||
|
||||
boost::asio::io_context io_ctx;
|
||||
nano::signal_manager sigman;
|
||||
try
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <nano/lib/errors.hpp>
|
||||
#include <nano/lib/rpcconfig.hpp>
|
||||
#include <nano/lib/threading.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/lib/tomlconfig.hpp>
|
||||
#include <nano/lib/utility.hpp>
|
||||
#include <nano/lib/walletconfig.hpp>
|
||||
|
@ -101,6 +102,19 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost
|
|||
config.node.logging.init (data_path);
|
||||
nano::logger_mt logger{ config.node.logging.min_time_between_log_output };
|
||||
|
||||
auto tls_config (std::make_shared<nano::tls_config> ());
|
||||
error = nano::read_tls_config_toml (data_path, *tls_config, logger);
|
||||
if (error)
|
||||
{
|
||||
splash->hide ();
|
||||
show_error (error.get_message ());
|
||||
std::exit (1);
|
||||
}
|
||||
else
|
||||
{
|
||||
config.node.websocket_config.tls_config = tls_config;
|
||||
}
|
||||
|
||||
boost::asio::io_context io_ctx;
|
||||
nano::thread_runner runner (io_ctx, config.node.io_threads);
|
||||
|
||||
|
@ -142,6 +156,7 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost
|
|||
wallet_config.account = wallet->deterministic_insert (transaction);
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert (wallet->exists (wallet_config.account));
|
||||
write_wallet_config (wallet_config, data_path);
|
||||
node->start ();
|
||||
|
@ -172,8 +187,11 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost
|
|||
auto error = nano::read_rpc_config_toml (data_path, rpc_config, flags.rpc_config_overrides);
|
||||
if (error)
|
||||
{
|
||||
splash->hide ();
|
||||
show_error (error.get_message ());
|
||||
std::exit (1);
|
||||
}
|
||||
rpc_config.tls_config = tls_config;
|
||||
rpc_handler = std::make_unique<nano::inprocess_rpc_handler> (*node, ipc, config.rpc);
|
||||
rpc = nano::get_rpc (io_ctx, rpc_config, *rpc_handler);
|
||||
rpc->start ();
|
||||
|
|
|
@ -146,6 +146,8 @@ add_library(
|
|||
websocket.cpp
|
||||
websocketconfig.hpp
|
||||
websocketconfig.cpp
|
||||
websocket_stream.hpp
|
||||
websocket_stream.cpp
|
||||
write_database_queue.hpp
|
||||
write_database_queue.cpp
|
||||
xorshift.hpp)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <nano/lib/cli.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/lib/tomlconfig.hpp>
|
||||
#include <nano/node/cli.hpp>
|
||||
#include <nano/node/common.hpp>
|
||||
|
@ -61,7 +62,7 @@ void nano::add_node_options (boost::program_options::options_description & descr
|
|||
("rebuild_database", "Rebuild LMDB database with vacuum for best compaction")
|
||||
("migrate_database_lmdb_to_rocksdb", "Migrates LMDB database to RocksDB")
|
||||
("diagnostics", "Run internal diagnostics")
|
||||
("generate_config", boost::program_options::value<std::string> (), "Write configuration to stdout, populated with defaults suitable for this system. Pass the configuration type node or rpc. See also use_defaults.")
|
||||
("generate_config", boost::program_options::value<std::string> (), "Write configuration to stdout, populated with defaults suitable for this system. Pass the configuration type node, rpc or tls. See also use_defaults.")
|
||||
("key_create", "Generates a adhoc random keypair and prints it to stdout")
|
||||
("key_expand", "Derive public key and account number from <key>")
|
||||
("wallet_add_adhoc", "Insert <key> in to <wallet>")
|
||||
|
@ -684,6 +685,12 @@ std::error_code nano::handle_node_options (boost::program_options::variables_map
|
|||
nano::rpc_config config{ nano::dev::network_params.network };
|
||||
config.serialize_toml (toml);
|
||||
}
|
||||
else if (type == "tls")
|
||||
{
|
||||
valid_type = true;
|
||||
nano::tls_config config;
|
||||
config.serialize_toml (toml);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Invalid configuration type " << type << ". Must be node or rpc." << std::endl;
|
||||
|
|
|
@ -128,7 +128,7 @@ nano::node::node (boost::asio::io_context & io_ctx_a, boost::filesystem::path co
|
|||
if (config.websocket_config.enabled)
|
||||
{
|
||||
auto endpoint_l (nano::tcp_endpoint (boost::asio::ip::make_address_v6 (config.websocket_config.address), config.websocket_config.port));
|
||||
websocket_server = std::make_shared<nano::websocket::listener> (logger, wallets, io_ctx, endpoint_l);
|
||||
websocket_server = std::make_shared<nano::websocket::listener> (config.websocket_config.tls_config, logger, wallets, io_ctx, endpoint_l);
|
||||
this->websocket_server->run ();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <nano/boost/asio/bind_executor.hpp>
|
||||
#include <nano/boost/asio/dispatch.hpp>
|
||||
#include <nano/boost/asio/strand.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/lib/work.hpp>
|
||||
#include <nano/node/transport/transport.hpp>
|
||||
#include <nano/node/wallet.hpp>
|
||||
|
@ -232,10 +233,19 @@ bool nano::websocket::vote_options::should_filter (nano::websocket::message cons
|
|||
return should_filter_l;
|
||||
}
|
||||
|
||||
nano::websocket::session::session (nano::websocket::listener & listener_a, socket_type socket_a) :
|
||||
ws_listener (listener_a), ws (std::move (socket_a)), strand (ws.get_executor ())
|
||||
#ifdef NANO_SECURE_RPC
|
||||
|
||||
nano::websocket::session::session (nano::websocket::listener & listener_a, socket_type socket_a, boost::asio::ssl::context & ctx_a) :
|
||||
ws_listener (listener_a), ws (std::move (socket_a), ctx_a)
|
||||
{
|
||||
ws_listener.get_logger ().try_log ("Websocket: secure session started");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
nano::websocket::session::session (nano::websocket::listener & listener_a, socket_type socket_a) :
|
||||
ws_listener (listener_a), ws (std::move (socket_a))
|
||||
{
|
||||
ws.text (true);
|
||||
ws_listener.get_logger ().try_log ("Websocket: session started");
|
||||
}
|
||||
|
||||
|
@ -253,7 +263,7 @@ nano::websocket::session::~session ()
|
|||
void nano::websocket::session::handshake ()
|
||||
{
|
||||
auto this_l (shared_from_this ());
|
||||
ws.async_accept ([this_l] (boost::system::error_code const & ec) {
|
||||
ws.handshake ([this_l] (boost::system::error_code const & ec) {
|
||||
if (!ec)
|
||||
{
|
||||
// Start reading incoming messages
|
||||
|
@ -271,7 +281,7 @@ void nano::websocket::session::close ()
|
|||
ws_listener.get_logger ().try_log ("Websocket: session closing");
|
||||
|
||||
auto this_l (shared_from_this ());
|
||||
boost::asio::dispatch (strand,
|
||||
boost::asio::dispatch (ws.get_strand (),
|
||||
[this_l] () {
|
||||
boost::beast::websocket::close_reason reason;
|
||||
reason.code = boost::beast::websocket::close_code::normal;
|
||||
|
@ -289,7 +299,7 @@ void nano::websocket::session::write (nano::websocket::message message_a)
|
|||
{
|
||||
lk.unlock ();
|
||||
auto this_l (shared_from_this ());
|
||||
boost::asio::post (strand,
|
||||
boost::asio::post (ws.get_strand (),
|
||||
[message_a, this_l] () {
|
||||
bool write_in_progress = !this_l->send_queue.empty ();
|
||||
this_l->send_queue.emplace_back (message_a);
|
||||
|
@ -307,7 +317,6 @@ void nano::websocket::session::write_queued_messages ()
|
|||
auto this_l (shared_from_this ());
|
||||
|
||||
ws.async_write (nano::shared_const_buffer (msg),
|
||||
boost::asio::bind_executor (strand,
|
||||
[this_l] (boost::system::error_code ec, std::size_t bytes_transferred) {
|
||||
this_l->send_queue.pop_front ();
|
||||
if (!ec)
|
||||
|
@ -317,16 +326,15 @@ void nano::websocket::session::write_queued_messages ()
|
|||
this_l->write_queued_messages ();
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
void nano::websocket::session::read ()
|
||||
{
|
||||
auto this_l (shared_from_this ());
|
||||
|
||||
boost::asio::post (strand, [this_l] () {
|
||||
boost::asio::post (ws.get_strand (), [this_l] () {
|
||||
this_l->ws.async_read (this_l->read_buffer,
|
||||
boost::asio::bind_executor (this_l->strand,
|
||||
[this_l] (boost::system::error_code ec, std::size_t bytes_transferred) {
|
||||
if (!ec)
|
||||
{
|
||||
|
@ -353,7 +361,7 @@ void nano::websocket::session::read ()
|
|||
{
|
||||
this_l->ws_listener.get_logger ().try_log ("Websocket: read failed: ", ec.message ());
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -541,7 +549,8 @@ void nano::websocket::listener::stop ()
|
|||
sessions.clear ();
|
||||
}
|
||||
|
||||
nano::websocket::listener::listener (nano::logger_mt & logger_a, nano::wallets & wallets_a, boost::asio::io_context & io_ctx_a, boost::asio::ip::tcp::endpoint endpoint_a) :
|
||||
nano::websocket::listener::listener (std::shared_ptr<nano::tls_config> const & tls_config_a, nano::logger_mt & logger_a, nano::wallets & wallets_a, boost::asio::io_context & io_ctx_a, boost::asio::ip::tcp::endpoint endpoint_a) :
|
||||
tls_config (tls_config_a),
|
||||
logger (logger_a),
|
||||
wallets (wallets_a),
|
||||
acceptor (io_ctx_a),
|
||||
|
@ -590,7 +599,18 @@ void nano::websocket::listener::on_accept (boost::system::error_code ec)
|
|||
else
|
||||
{
|
||||
// Create the session and initiate websocket handshake
|
||||
auto session (std::make_shared<nano::websocket::session> (*this, std::move (socket)));
|
||||
std::shared_ptr<nano::websocket::session> session;
|
||||
if (tls_config && tls_config->enable_wss)
|
||||
{
|
||||
#ifdef NANO_SECURE_RPC
|
||||
session = std::make_shared<nano::websocket::session> (*this, std::move (socket), tls_config->ssl_context);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
session = std::make_shared<nano::websocket::session> (*this, std::move (socket));
|
||||
}
|
||||
|
||||
sessions_mutex.lock ();
|
||||
sessions.push_back (session);
|
||||
// Clean up expired sessions
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <nano/boost/asio/strand.hpp>
|
||||
#include <nano/boost/beast/core.hpp>
|
||||
#include <nano/boost/beast/websocket.hpp>
|
||||
#include <nano/lib/blocks.hpp>
|
||||
#include <nano/lib/numbers.hpp>
|
||||
#include <nano/lib/work.hpp>
|
||||
#include <nano/node/common.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/websocket_stream.hpp>
|
||||
#include <nano/secure/common.hpp>
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
@ -20,15 +18,6 @@
|
|||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
/* Boost v1.70 introduced breaking changes; the conditional compilation allows 1.6x to be supported as well. */
|
||||
#if BOOST_VERSION < 107000
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
#define beast_buffers boost::beast::buffers
|
||||
#else
|
||||
using socket_type = boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::io_context::executor_type>;
|
||||
#define beast_buffers boost::beast::make_printable
|
||||
#endif
|
||||
|
||||
namespace nano
|
||||
{
|
||||
class wallets;
|
||||
|
@ -36,6 +25,7 @@ class logger_mt;
|
|||
class vote;
|
||||
class election_status;
|
||||
class telemetry_data;
|
||||
class tls_config;
|
||||
enum class election_status_type : uint8_t;
|
||||
namespace websocket
|
||||
{
|
||||
|
@ -233,8 +223,13 @@ namespace websocket
|
|||
friend class listener;
|
||||
|
||||
public:
|
||||
#ifdef NANO_SECURE_RPC
|
||||
/** Constructor that takes ownership over \p socket_a and creates an SSL stream */
|
||||
explicit session (nano::websocket::listener & listener_a, socket_type socket_a, boost::asio::ssl::context & ctx_a);
|
||||
#endif
|
||||
/** Constructor that takes ownership over \p socket_a */
|
||||
explicit session (nano::websocket::listener & listener_a, socket_type socket_a);
|
||||
|
||||
~session ();
|
||||
|
||||
/** Perform Websocket handshake and start reading messages */
|
||||
|
@ -252,12 +247,10 @@ namespace websocket
|
|||
private:
|
||||
/** The owning listener */
|
||||
nano::websocket::listener & ws_listener;
|
||||
/** Websocket */
|
||||
boost::beast::websocket::stream<socket_type> ws;
|
||||
/** Websocket stream, supporting both plain and tls connections */
|
||||
nano::websocket::stream ws;
|
||||
/** Buffer for received messages */
|
||||
boost::beast::multi_buffer read_buffer;
|
||||
/** All websocket operations that are thread unsafe must go through a strand. */
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand;
|
||||
/** Outgoing messages. The send queue is protected by accessing it only through the strand */
|
||||
std::deque<message> send_queue;
|
||||
|
||||
|
@ -286,7 +279,7 @@ namespace websocket
|
|||
class listener final : public std::enable_shared_from_this<listener>
|
||||
{
|
||||
public:
|
||||
listener (nano::logger_mt & logger_a, nano::wallets & wallets_a, boost::asio::io_context & io_ctx_a, boost::asio::ip::tcp::endpoint endpoint_a);
|
||||
listener (std::shared_ptr<nano::tls_config> const & tls_config_a, nano::logger_mt & logger_a, nano::wallets & wallets_a, boost::asio::io_context & io_ctx_a, boost::asio::ip::tcp::endpoint endpoint_a);
|
||||
|
||||
/** Start accepting connections */
|
||||
void run ();
|
||||
|
@ -335,6 +328,7 @@ namespace websocket
|
|||
/** Removes from subscription count of a specific topic*/
|
||||
void decrease_subscriber_count (nano::websocket::topic const & topic_a);
|
||||
|
||||
std::shared_ptr<nano::tls_config> tls_config;
|
||||
nano::logger_mt & logger;
|
||||
nano::wallets & wallets;
|
||||
boost::asio::ip::tcp::acceptor acceptor;
|
||||
|
|
126
nano/node/websocket_stream.cpp
Normal file
126
nano/node/websocket_stream.cpp
Normal file
|
@ -0,0 +1,126 @@
|
|||
#include <nano/node/websocket_stream.hpp>
|
||||
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
/** Type-erasing wrapper for tls and non-tls websocket streams */
|
||||
template <typename stream_type>
|
||||
class stream_wrapper : public nano::websocket::websocket_stream_concept
|
||||
{
|
||||
public:
|
||||
#ifdef NANO_SECURE_RPC
|
||||
stream_wrapper (socket_type socket_a, boost::asio::ssl::context & ctx_a) :
|
||||
ws (std::move (socket_a), ctx_a), strand (ws.get_executor ())
|
||||
{
|
||||
is_tls = true;
|
||||
ws.text (true);
|
||||
}
|
||||
#endif
|
||||
|
||||
stream_wrapper (socket_type socket_a) :
|
||||
ws (std::move (socket_a)), strand (ws.get_executor ())
|
||||
{
|
||||
ws.text (true);
|
||||
}
|
||||
|
||||
void handshake (std::function<void (boost::system::error_code const & ec)> callback_a) override
|
||||
{
|
||||
if (is_tls)
|
||||
{
|
||||
ssl_handshake (callback_a);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Websocket handshake
|
||||
ws.async_accept ([callback_a] (boost::system::error_code const & ec) {
|
||||
callback_a (ec);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ssl_handshake (std::function<void (boost::system::error_code const & ec)> callback_a)
|
||||
{
|
||||
#ifdef NANO_SECURE_RPC
|
||||
// Only perform TLS handshakes for TLS streams
|
||||
if constexpr (std::is_same<wss_type, stream_type>::value)
|
||||
{
|
||||
ws.next_layer ().async_handshake (boost::asio::ssl::stream_base::server, [this, callback_a] (boost::system::error_code const & ec) {
|
||||
if (!ec)
|
||||
{
|
||||
// Websocket handshake
|
||||
this->ws.async_accept ([callback_a] (boost::system::error_code const & ec) {
|
||||
callback_a (ec);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
callback_a (ec);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> & get_strand () override
|
||||
{
|
||||
return strand;
|
||||
}
|
||||
|
||||
void close (boost::beast::websocket::close_reason const & reason_a, boost::system::error_code & ec_a) override
|
||||
{
|
||||
ws.close (reason_a, ec_a);
|
||||
}
|
||||
|
||||
void async_write (nano::shared_const_buffer const & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a) override
|
||||
{
|
||||
ws.async_write (buffer_a, boost::asio::bind_executor (strand, callback_a));
|
||||
}
|
||||
|
||||
void async_read (boost::beast::multi_buffer & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a) override
|
||||
{
|
||||
ws.async_read (buffer_a, boost::asio::bind_executor (strand, callback_a));
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_tls{ false };
|
||||
stream_type ws;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand;
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef NANO_SECURE_RPC
|
||||
nano::websocket::stream::stream (socket_type socket_a, boost::asio::ssl::context & ctx_a)
|
||||
{
|
||||
impl = std::make_unique<stream_wrapper<wss_type>> (std::move (socket_a), ctx_a);
|
||||
}
|
||||
#endif
|
||||
nano::websocket::stream::stream (socket_type socket_a)
|
||||
{
|
||||
impl = std::make_unique<stream_wrapper<ws_type>> (std::move (socket_a));
|
||||
}
|
||||
|
||||
[[nodiscard]] boost::asio::strand<boost::asio::io_context::executor_type> & nano::websocket::stream::get_strand ()
|
||||
{
|
||||
return impl->get_strand ();
|
||||
}
|
||||
|
||||
void nano::websocket::stream::handshake (std::function<void (boost::system::error_code const & ec)> callback_a)
|
||||
{
|
||||
impl->handshake (callback_a);
|
||||
}
|
||||
|
||||
void nano::websocket::stream::close (boost::beast::websocket::close_reason const & reason_a, boost::system::error_code & ec_a)
|
||||
{
|
||||
impl->close (reason_a, ec_a);
|
||||
}
|
||||
|
||||
void nano::websocket::stream::async_write (nano::shared_const_buffer const & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a)
|
||||
{
|
||||
impl->async_write (buffer_a, callback_a);
|
||||
}
|
||||
|
||||
void nano::websocket::stream::async_read (boost::beast::multi_buffer & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a)
|
||||
{
|
||||
impl->async_read (buffer_a, callback_a);
|
||||
}
|
62
nano/node/websocket_stream.hpp
Normal file
62
nano/node/websocket_stream.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <nano/boost/asio/strand.hpp>
|
||||
#include <nano/boost/beast/core.hpp>
|
||||
#include <nano/boost/beast/websocket.hpp>
|
||||
#include <nano/lib/asio.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
/* Boost v1.70 introduced breaking changes; the conditional compilation allows 1.6x to be supported as well. */
|
||||
#if BOOST_VERSION < 107000
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
#define beast_buffers boost::beast::buffers
|
||||
#else
|
||||
using socket_type = boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::io_context::executor_type>;
|
||||
#define beast_buffers boost::beast::make_printable
|
||||
#endif
|
||||
using ws_type = boost::beast::websocket::stream<socket_type>;
|
||||
|
||||
#ifdef NANO_SECURE_RPC
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
#include <boost/beast/websocket/ssl.hpp>
|
||||
using wss_type = boost::beast::websocket::stream<boost::beast::ssl_stream<socket_type>>;
|
||||
#endif
|
||||
|
||||
namespace nano::websocket
|
||||
{
|
||||
/** The minimal stream interface needed by the Nano websocket implementation */
|
||||
class websocket_stream_concept
|
||||
{
|
||||
public:
|
||||
virtual ~websocket_stream_concept () = default;
|
||||
virtual boost::asio::strand<boost::asio::io_context::executor_type> & get_strand () = 0;
|
||||
virtual void handshake (std::function<void (boost::system::error_code const & ec)> callback_a) = 0;
|
||||
virtual void close (boost::beast::websocket::close_reason const & reason_a, boost::system::error_code & ec_a) = 0;
|
||||
virtual void async_write (nano::shared_const_buffer const & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a) = 0;
|
||||
virtual void async_read (boost::beast::multi_buffer & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Beast websockets doesn't provide a common base type for tls and non-tls streams, so we use
|
||||
* the type erasure idiom to be able to use both kinds of streams through a common type.
|
||||
*/
|
||||
class stream final : public websocket_stream_concept
|
||||
{
|
||||
public:
|
||||
#ifdef NANO_SECURE_RPC
|
||||
stream (socket_type socket_a, boost::asio::ssl::context & ctx_a);
|
||||
#endif
|
||||
stream (socket_type socket_a);
|
||||
|
||||
[[nodiscard]] boost::asio::strand<boost::asio::io_context::executor_type> & get_strand () override;
|
||||
void handshake (std::function<void (boost::system::error_code const & ec)> callback_a) override;
|
||||
void close (boost::beast::websocket::close_reason const & reason_a, boost::system::error_code & ec_a) override;
|
||||
void async_write (nano::shared_const_buffer const & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a) override;
|
||||
void async_read (boost::beast::multi_buffer & buffer_a, std::function<void (boost::system::error_code, std::size_t)> callback_a) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<websocket_stream_concept> impl;
|
||||
};
|
||||
}
|
|
@ -3,10 +3,13 @@
|
|||
#include <nano/lib/config.hpp>
|
||||
#include <nano/lib/errors.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace nano
|
||||
{
|
||||
class jsonconfig;
|
||||
class tomlconfig;
|
||||
class tls_config;
|
||||
namespace websocket
|
||||
{
|
||||
/** websocket configuration */
|
||||
|
@ -22,6 +25,8 @@ namespace websocket
|
|||
bool enabled{ false };
|
||||
uint16_t port;
|
||||
std::string address;
|
||||
/** Optional TLS config */
|
||||
std::shared_ptr<nano::tls_config> tls_config;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <nano/boost/asio/bind_executor.hpp>
|
||||
#include <nano/lib/rpc_handler_interface.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/rpc/rpc.hpp>
|
||||
#include <nano/rpc/rpc_connection.hpp>
|
||||
|
||||
|
@ -82,12 +83,10 @@ std::unique_ptr<nano::rpc> nano::get_rpc (boost::asio::io_context & io_ctx_a, na
|
|||
{
|
||||
std::unique_ptr<rpc> impl;
|
||||
|
||||
if (config_a.secure.enable)
|
||||
if (config_a.tls_config && config_a.tls_config->enable_https)
|
||||
{
|
||||
#ifdef NANO_SECURE_RPC
|
||||
impl = std::make_unique<rpc_secure> (io_ctx_a, config_a, rpc_handler_interface_a);
|
||||
#else
|
||||
std::cerr << "RPC configured for TLS, but the node is not compiled with TLS support" << std::endl;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <nano/boost/asio/bind_executor.hpp>
|
||||
#include <nano/lib/tlsconfig.hpp>
|
||||
#include <nano/rpc/rpc_connection_secure.hpp>
|
||||
#include <nano/rpc/rpc_secure.hpp>
|
||||
|
||||
|
@ -7,112 +8,14 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
bool nano::rpc_secure::on_verify_certificate (bool preverified, boost::asio::ssl::verify_context & ctx)
|
||||
{
|
||||
X509_STORE_CTX * cts = ctx.native_handle ();
|
||||
auto error (X509_STORE_CTX_get_error (cts));
|
||||
switch (error)
|
||||
{
|
||||
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
|
||||
logger.always_log ("TLS: Unable to get issuer");
|
||||
break;
|
||||
case X509_V_ERR_CERT_NOT_YET_VALID:
|
||||
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
|
||||
logger.always_log ("TLS: Certificate not yet valid");
|
||||
break;
|
||||
case X509_V_ERR_CERT_HAS_EXPIRED:
|
||||
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
|
||||
logger.always_log ("TLS: Certificate expired");
|
||||
break;
|
||||
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
||||
if (config.secure.verbose_logging)
|
||||
{
|
||||
logger.always_log ("TLS: self signed certificate in chain");
|
||||
}
|
||||
|
||||
// Allow self-signed certificates
|
||||
preverified = true;
|
||||
break;
|
||||
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
||||
logger.always_log ("TLS: Self signed certificate not in the list of trusted certs (forgot to subject-hash certificate filename?)");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.secure.verbose_logging)
|
||||
{
|
||||
if (error != 0)
|
||||
{
|
||||
logger.always_log ("TLS: Error: ", X509_verify_cert_error_string (error));
|
||||
logger.always_log ("TLS: Error chain depth : ", X509_STORE_CTX_get_error_depth (cts));
|
||||
}
|
||||
|
||||
X509 * cert = X509_STORE_CTX_get_current_cert (cts);
|
||||
char subject_name[512];
|
||||
X509_NAME_oneline (X509_get_subject_name (cert), subject_name, sizeof (subject_name) - 1);
|
||||
logger.always_log ("TLS: Verifying: ", subject_name);
|
||||
logger.always_log ("TLS: Verification: ", preverified);
|
||||
}
|
||||
else if (!preverified)
|
||||
{
|
||||
logger.always_log ("TLS: Pre-verification failed. Turn on verbose logging for more information.");
|
||||
}
|
||||
|
||||
return preverified;
|
||||
}
|
||||
|
||||
void nano::rpc_secure::load_certs (boost::asio::ssl::context & context_a)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This is called if the key is password protected
|
||||
context_a.set_password_callback (
|
||||
[this] (std::size_t,
|
||||
boost::asio::ssl::context_base::password_purpose) {
|
||||
return config.secure.server_key_passphrase;
|
||||
});
|
||||
|
||||
// The following two options disables the session cache and enables stateless session resumption.
|
||||
// This is necessary because of the way the RPC server abruptly terminate connections.
|
||||
SSL_CTX_set_session_cache_mode (context_a.native_handle (), SSL_SESS_CACHE_OFF);
|
||||
SSL_CTX_set_options (context_a.native_handle (), SSL_OP_NO_TICKET);
|
||||
|
||||
context_a.set_options (
|
||||
boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use);
|
||||
|
||||
context_a.use_certificate_chain_file (config.secure.server_cert_path);
|
||||
context_a.use_private_key_file (config.secure.server_key_path, boost::asio::ssl::context::pem);
|
||||
context_a.use_tmp_dh_file (config.secure.server_dh_path);
|
||||
|
||||
// Verify client certificates?
|
||||
if (!config.secure.client_certs_path.empty ())
|
||||
{
|
||||
context_a.set_verify_mode (boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_peer);
|
||||
context_a.add_verify_path (config.secure.client_certs_path);
|
||||
context_a.set_verify_callback ([this] (auto preverified, auto & ctx) {
|
||||
return this->on_verify_certificate (preverified, ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (boost::system::system_error const & err)
|
||||
{
|
||||
auto error (boost::str (boost::format ("Could not load certificate information: %1%. Make sure the paths in the secure rpc configuration are correct.") % err.what ()));
|
||||
std::cerr << error << std::endl;
|
||||
logger.always_log (error);
|
||||
}
|
||||
}
|
||||
|
||||
nano::rpc_secure::rpc_secure (boost::asio::io_context & context_a, nano::rpc_config const & config_a, nano::rpc_handler_interface & rpc_handler_interface_a) :
|
||||
rpc (context_a, config_a, rpc_handler_interface_a),
|
||||
ssl_context (boost::asio::ssl::context::tlsv12_server)
|
||||
rpc (context_a, config_a, rpc_handler_interface_a)
|
||||
{
|
||||
load_certs (ssl_context);
|
||||
}
|
||||
|
||||
void nano::rpc_secure::accept ()
|
||||
{
|
||||
auto connection (std::make_shared<nano::rpc_connection_secure> (config, io_ctx, logger, rpc_handler_interface, this->ssl_context));
|
||||
auto connection (std::make_shared<nano::rpc_connection_secure> (config, io_ctx, logger, rpc_handler_interface, config.tls_config->ssl_context));
|
||||
acceptor.async_accept (connection->socket, boost::asio::bind_executor (connection->strand, [this, connection] (boost::system::error_code const & ec) {
|
||||
if (ec != boost::asio::error::operation_aborted && acceptor.is_open ())
|
||||
{
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#pragma once
|
||||
#include <nano/rpc/rpc.hpp>
|
||||
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
|
||||
namespace boost
|
||||
{
|
||||
namespace asio
|
||||
|
@ -23,17 +21,5 @@ public:
|
|||
|
||||
/** Starts accepting connections */
|
||||
void accept () override;
|
||||
|
||||
/** Installs the server certificate, key and DH, and optionally sets up client certificate verification */
|
||||
void load_certs (boost::asio::ssl::context & ctx);
|
||||
|
||||
/**
|
||||
* If client certificates are used, this is called to verify them.
|
||||
* @param preverified The TLS preverification status. The callback may revalidate, such as accepting self-signed certs.
|
||||
*/
|
||||
bool on_verify_certificate (bool preverified, boost::asio::ssl::verify_context & ctx);
|
||||
|
||||
/** The context needs to be shared between sessions to make resumption work */
|
||||
boost::asio::ssl::context ssl_context;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue