TLS support for RPC

This commit is contained in:
cryptocode 2018-02-20 02:00:18 +01:00 committed by androm3da
commit 8840c9dbef
7 changed files with 436 additions and 58 deletions

View file

@ -16,6 +16,8 @@ endif()
set (RAIBLOCKS_GUI OFF CACHE BOOL "")
set (RAIBLOCKS_TEST OFF CACHE BOOL "")
set (RAIBLOCKS_SECURE_RPC OFF CACHE BOOL "")
option(RAIBLOCKS_ASAN_INT "Enable ASan+UBSan+Integer overflow" OFF)
option(RAIBLOCKS_ASAN "Enable ASan+UBSan" OFF)
option(RAIBLOCKS_SIMD_OPTIMIZATIONS "Enable CPU-specific SIMD optimizations (SSE/AVX or NEON, e.g.)" OFF)
@ -103,6 +105,18 @@ if (RAIBLOCKS_GUI)
include_directories (${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS})
endif (RAIBLOCKS_GUI)
if (RAIBLOCKS_SECURE_RPC)
find_package (OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})
add_definitions (-DRAIBLOCKS_SECURE_RPC)
message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")
message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
message("OpenSSL lib: ${OPENSSL_SSL_LIBRARY}")
message("Crypto lib: ${OPENSSL_CRYPTO_LIBRARY}")
else ()
set (OPENSSL_LIBRARIES "")
endif (RAIBLOCKS_SECURE_RPC)
include_directories (${CMAKE_SOURCE_DIR})
set(Boost_USE_STATIC_LIBS ON)
@ -244,6 +258,10 @@ file (WRITE ${CMAKE_BINARY_DIR}/bootstrap_weights.cpp "#include <cstddef>\n"
" size_t rai_bootstrap_weights_size = sizeof(rai_bootstrap_weights) - 1;\n"
"}\n")
if (RAIBLOCKS_SECURE_RPC)
set (SECURE_RPC_SOURCE rai/node/rpc_secure.cpp rai/node/rpc_secure.hpp)
endif ()
add_library (secure
${PLATFORM_SECURE_SOURCE}
${CMAKE_BINARY_DIR}/bootstrap_weights.cpp
@ -273,6 +291,7 @@ add_library (rai_lib_static STATIC ${RAI_LIB_SOURCES})
add_library (node
${PLATFORM_NODE_SOURCE}
${SECURE_RPC_SOURCE}
rai/node/bootstrap.cpp
rai/node/bootstrap.hpp
rai/node/common.cpp
@ -375,23 +394,23 @@ else (WIN32)
endif (WIN32)
if (RAIBLOCKS_TEST)
target_link_libraries (core_test node secure lmdb ed25519 rai_lib_static argon2 ${CRYPTOPP_LIBRARY} gtest_main gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (core_test node secure lmdb ed25519 rai_lib_static argon2 ${OPENSSL_LIBRARIES} ${CRYPTOPP_LIBRARY} gtest_main gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (slow_test node secure lmdb ed25519 rai_lib_static argon2 ${CRYPTOPP_LIBRARY} gtest_main gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (slow_test node secure lmdb ed25519 rai_lib_static argon2 ${OPENSSL_LIBRARIES} ${CRYPTOPP_LIBRARY} gtest_main gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS})
endif (RAIBLOCKS_TEST)
if (RAIBLOCKS_GUI)
target_link_libraries (qt_test node secure lmdb ed25519 rai_lib_static qt argon2 ${CRYPTOPP_LIBRARY} gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Test ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (qt_test node secure lmdb ed25519 rai_lib_static qt argon2 ${OPENSSL_LIBRARIES} ${CRYPTOPP_LIBRARY} gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Test ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (qt_system node secure lmdb ed25519 rai_lib_static qt argon2 ${CRYPTOPP_LIBRARY} gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (qt_system node secure lmdb ed25519 rai_lib_static qt argon2 ${OPENSSL_LIBRARIES} ${CRYPTOPP_LIBRARY} gtest libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (nano_wallet node secure lmdb ed25519 rai_lib_static qt argon2 ${CRYPTOPP_LIBRARY} libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS} ${PLATFORM_WALLET_LIBS})
target_link_libraries (nano_wallet node secure lmdb ed25519 rai_lib_static qt argon2 ${OPENSSL_LIBRARIES} ${CRYPTOPP_LIBRARY} libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets ${QT_QTGUI_LIBRARY} ${PLATFORM_LIBS} ${PLATFORM_WALLET_LIBS})
endif (RAIBLOCKS_GUI)
target_link_libraries (rai_lib ed25519 xxhash blake2 ${CRYPTOPP_LIBRARY})
target_link_libraries (rai_lib_static ed25519 xxhash blake2 ${CRYPTOPP_LIBRARY})
target_link_libraries (rai_node node secure lmdb ed25519 rai_lib_static argon2 ${CRYPTOPP_LIBRARY} libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS})
target_link_libraries (rai_node node secure lmdb ed25519 rai_lib_static argon2 ${OPENSSL_LIBRARIES} ${CRYPTOPP_LIBRARY} libminiupnpc-static ${Boost_ATOMIC_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_DATE_TIME_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_LOG_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_LOG_SETUP_LIBRARY} ${Boost_THREAD_LIBRARY} ${PLATFORM_LIBS})
set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE)
if (RAIBLOCKS_GUI)

View file

@ -1,12 +1,53 @@
#include <rai/node/rpc.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <rai/node/rpc.hpp>
#include <rai/lib/interface.h>
#include <rai/node/node.hpp>
#include <ed25519-donna/ed25519.h>
#ifdef RAIBLOCKS_SECURE_RPC
#include <rai/node/rpc_secure.hpp>
#endif
rai::rpc_secure_config::rpc_secure_config () :
enable (false),
verbose_logging (false)
{
}
void rai::rpc_secure_config::serialize_json (boost::property_tree::ptree & tree_a) const
{
tree_a.put ("enable", enable);
tree_a.put ("verbose_logging", verbose_logging);
tree_a.put ("server_key_passphrase", server_key_passphrase);
tree_a.put ("server_cert_path", server_cert_path);
tree_a.put ("server_key_path", server_key_path);
tree_a.put ("server_dh_path", server_dh_path);
tree_a.put ("client_certs_path", client_certs_path);
}
bool rai::rpc_secure_config::deserialize_json (boost::property_tree::ptree const & tree_a)
{
auto error (false);
try
{
enable = tree_a.get<bool> ("enable");
verbose_logging = tree_a.get<bool> ("verbose_logging");
server_key_passphrase = tree_a.get<std::string> ("server_key_passphrase");
server_cert_path = tree_a.get<std::string> ("server_cert_path");
server_key_path = tree_a.get<std::string> ("server_key_path");
server_dh_path = tree_a.get<std::string> ("server_dh_path");
client_certs_path = tree_a.get<std::string> ("client_certs_path");
}
catch (std::runtime_error const &)
{
error = true;
}
return error;
}
rai::rpc_config::rpc_config () :
address (boost::asio::ip::address_v6::loopback ()),
port (rai::rpc::rpc_port),
@ -39,27 +80,36 @@ bool rai::rpc_config::deserialize_json (boost::property_tree::ptree const & tree
auto result (false);
try
{
auto address_l (tree_a.get<std::string> ("address"));
auto port_l (tree_a.get<std::string> ("port"));
enable_control = tree_a.get<bool> ("enable_control");
auto frontier_request_limit_l (tree_a.get<std::string> ("frontier_request_limit"));
auto chain_request_limit_l (tree_a.get<std::string> ("chain_request_limit"));
try
auto rpc_secure_l (tree_a.get_child_optional ("secure"));
if (rpc_secure_l)
{
port = std::stoul (port_l);
result = port > std::numeric_limits<uint16_t>::max ();
frontier_request_limit = std::stoull (frontier_request_limit_l);
chain_request_limit = std::stoull (chain_request_limit_l);
result = secure.deserialize_json (rpc_secure_l.get ());
}
catch (std::logic_error const &)
if (!result)
{
result = true;
}
boost::system::error_code ec;
address = boost::asio::ip::address_v6::from_string (address_l, ec);
if (ec)
{
result = true;
auto address_l (tree_a.get<std::string> ("address"));
auto port_l (tree_a.get<std::string> ("port"));
enable_control = tree_a.get<bool> ("enable_control");
auto frontier_request_limit_l (tree_a.get<std::string> ("frontier_request_limit"));
auto chain_request_limit_l (tree_a.get<std::string> ("chain_request_limit"));
try
{
port = std::stoul (port_l);
result = port > std::numeric_limits<uint16_t>::max ();
frontier_request_limit = std::stoull (frontier_request_limit_l);
chain_request_limit = std::stoull (chain_request_limit_l);
}
catch (std::logic_error const &)
{
result = true;
}
boost::system::error_code ec;
address = boost::asio::ip::address_v6::from_string (address_l, ec);
if (ec)
{
result = true;
}
}
}
catch (std::runtime_error const &)
@ -74,7 +124,11 @@ acceptor (service_a),
config (config_a),
node (node_a)
{
auto endpoint (rai::tcp_endpoint (config_a.address, config_a.port));
}
void rai::rpc::start ()
{
auto endpoint (rai::tcp_endpoint (config.address, config.port));
acceptor.open (endpoint.protocol ());
acceptor.set_option (boost::asio::ip::tcp::acceptor::reuse_address (true));
@ -87,18 +141,20 @@ node (node_a)
}
acceptor.listen ();
node_a.observers.blocks.add ([this](std::shared_ptr<rai::block> block_a, rai::account const & account_a, rai::amount const &) {
node.observers.blocks.add ([this](std::shared_ptr<rai::block> block_a, rai::account const & account_a, rai::amount const &) {
observer_action (account_a);
});
accept ();
}
void rai::rpc::start ()
void rai::rpc::accept ()
{
auto connection (std::make_shared<rai::rpc_connection> (node, *this));
acceptor.async_accept (connection->socket, [this, connection](boost::system::error_code const & ec) {
if (!ec)
{
start ();
accept ();
connection->parse_connection ();
}
else
@ -138,15 +194,15 @@ void rai::rpc::observer_action (rai::account const & account_a)
}
}
namespace
{
void error_response (std::function<void(boost::property_tree::ptree const &)> response_a, std::string const & message_a)
void rai::error_response (std::function<void(boost::property_tree::ptree const &)> response_a, std::string const & message_a)
{
boost::property_tree::ptree response_l;
response_l.put ("error", message_a);
response_a (response_l);
}
namespace
{
bool decode_unsigned (std::string const & text, uint64_t & number)
{
bool result;
@ -4114,6 +4170,23 @@ socket (node_a.service)
}
void rai::rpc_connection::parse_connection ()
{
read ();
}
void rai::rpc_connection::write_result (std::string body, unsigned version)
{
res.set ("Content-Type", "application/json");
res.set ("Access-Control-Allow-Origin", "*");
res.set ("Access-Control-Allow-Headers", "Accept, Accept-Language, Content-Language, Content-Type");
res.set ("Connection", "close");
res.result (boost::beast::http::status::ok);
res.body () = body;
res.version (version);
res.prepare_payload ();
}
void rai::rpc_connection::read ()
{
auto this_l (shared_from_this ());
boost::beast::http::async_read (socket, buffer, request, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
@ -4127,17 +4200,10 @@ void rai::rpc_connection::parse_connection ()
boost::property_tree::write_json (ostream, tree_a);
ostream.flush ();
auto body (ostream.str ());
this_l->res.set ("Content-Type", "application/json");
this_l->res.set ("Access-Control-Allow-Origin", "*");
this_l->res.set ("Access-Control-Allow-Headers", "Accept, Accept-Language, Content-Language, Content-Type");
this_l->res.set ("Connection", "close");
this_l->res.result (boost::beast::http::status::ok);
this_l->res.body () = body;
this_l->res.version (version);
this_l->res.prepare_payload ();
//boost::beast::http::prepare (this_l->res);
this_l->write_result (body, version);
boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
});
if (this_l->node->config.logging.log_rpc ())
{
BOOST_LOG (this_l->node->log) << boost::str (boost::format ("RPC request %2% completed in: %1% microseconds") % std::chrono::duration_cast<std::chrono::microseconds> (std::chrono::steady_clock::now () - start).count () % boost::io::group (std::hex, std::showbase, reinterpret_cast<uintptr_t> (this_l.get ())));
@ -4154,6 +4220,10 @@ void rai::rpc_connection::parse_connection ()
}
});
}
else
{
BOOST_LOG (this_l->node->log) << "RPC read error: " << ec.message ();
}
});
}
@ -4664,3 +4734,23 @@ void rai::payment_observer::complete (rai::payment_status status)
rpc.payment_observers.erase (account);
}
}
std::unique_ptr<rai::rpc> rai::get_rpc (boost::asio::io_service & service_a, rai::node & node_a, rai::rpc_config const & config_a)
{
std::unique_ptr<rpc> impl;
if (config_a.secure.enable)
{
#ifdef RAIBLOCKS_SECURE_RPC
impl.reset (new rpc_secure (service_a, node_a, config_a));
#else
std::cerr << "RPC configured for TLS, but the node is not compiled with TLS support" << std::endl;
#endif
}
else
{
impl.reset (new rpc (service_a, node_a, config_a));
}
return impl;
}

View file

@ -1,21 +1,40 @@
#pragma once
#include <rai/node/utility.hpp>
//#include <boost/beast/http.hpp>
//#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast.hpp>
#include <atomic>
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <atomic>
#include <rai/node/utility.hpp>
#include <unordered_map>
namespace rai
{
void error_response (std::function<void(boost::property_tree::ptree const &)> response_a, std::string const & message_a);
class node;
/** Configuration options for RPC TLS */
class rpc_secure_config
{
public:
rpc_secure_config ();
void serialize_json (boost::property_tree::ptree &) const;
bool deserialize_json (boost::property_tree::ptree const &);
/** If true, enable TLS */
bool enable;
/** If true, log certificate verification details */
bool verbose_logging;
/** 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;
};
class rpc_config
{
public:
@ -28,6 +47,7 @@ public:
bool enable_control;
uint64_t frontier_request_limit;
uint64_t chain_request_limit;
rpc_secure_config secure;
};
enum class payment_status
{
@ -46,6 +66,7 @@ class rpc
public:
rpc (boost::asio::io_service &, rai::node &, rai::rpc_config const &);
void start ();
virtual void accept ();
void stop ();
void observer_action (rai::account const &);
boost::asio::ip::tcp::acceptor acceptor;
@ -60,7 +81,9 @@ class rpc_connection : public std::enable_shared_from_this<rai::rpc_connection>
{
public:
rpc_connection (rai::node &, rai::rpc &);
void parse_connection ();
virtual void parse_connection ();
virtual void read ();
virtual void write_result (std::string body, unsigned version);
std::shared_ptr<rai::node> node;
rai::rpc & rpc;
boost::asio::ip::tcp::socket socket;
@ -191,4 +214,6 @@ public:
boost::property_tree::ptree request;
std::function<void(boost::property_tree::ptree const &)> response;
};
/** Returns the correct RPC implementation based on TLS configuration */
std::unique_ptr<rai::rpc> get_rpc (boost::asio::io_service & service_a, rai::node & node_a, rai::rpc_config const & config_a);
}

194
rai/node/rpc_secure.cpp Normal file
View file

@ -0,0 +1,194 @@
#include <rai/node/node.hpp>
#include <rai/node/rpc_secure.hpp>
bool rai::rpc_secure::on_verify_certificate (bool preverified, boost::asio::ssl::verify_context & ctx)
{
X509_STORE_CTX * cts = ctx.native_handle ();
switch (cts->error)
{
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
BOOST_LOG (node.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:
BOOST_LOG (node.log) << "TLS: Certificate not yet valid";
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
BOOST_LOG (node.log) << "TLS: Certificate expired";
break;
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
if (config.secure.verbose_logging)
{
BOOST_LOG (node.log) << "TLS: self signed certificate in chain";
}
// Allow self-signed certificates
preverified = true;
break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
BOOST_LOG (node.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 (cts->error != 0)
{
BOOST_LOG (node.log) << "TLS: Error: " << cts->error;
BOOST_LOG (node.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);
BOOST_LOG (node.log) << "TLS: Verifying: " << subject_name;
BOOST_LOG (node.log) << "TLS: Verification: " << preverified;
}
else if (!preverified)
{
BOOST_LOG (node.log) << "TLS: Pre-verification failed. Turn on verbose logging for more information.";
}
return preverified;
}
void rai::rpc_secure::load_certs (boost::asio::ssl::context & context_a)
{
// 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.size () > 0)
{
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 (boost::bind (&rai::rpc_secure::on_verify_certificate, this, _1, _2));
}
}
rai::rpc_secure::rpc_secure (boost::asio::io_service & service_a, rai::node & node_a, rai::rpc_config const & config_a) :
rpc (service_a, node_a, config_a),
ssl_context (boost::asio::ssl::context::tlsv12_server)
{
load_certs (ssl_context);
}
void rai::rpc_secure::accept ()
{
auto connection (std::make_shared<rai::rpc_connection_secure> (node, *this));
acceptor.async_accept (connection->socket, [this, connection](boost::system::error_code const & ec) {
if (!ec)
{
accept ();
connection->parse_connection ();
}
else
{
BOOST_LOG (this->node.log) << boost::str (boost::format ("Error accepting RPC connections: %1%") % ec);
}
});
}
rai::rpc_connection_secure::rpc_connection_secure (rai::node & node_a, rai::rpc_secure & rpc_a) :
rai::rpc_connection (node_a, rpc_a),
stream (socket, rpc_a.ssl_context)
{
}
void rai::rpc_connection_secure::parse_connection ()
{
// Perform the SSL handshake
stream.async_handshake (boost::asio::ssl::stream_base::server,
std::bind (
&rai::rpc_connection_secure::handle_handshake,
std::static_pointer_cast<rai::rpc_connection_secure> (shared_from_this ()),
std::placeholders::_1));
}
void rai::rpc_connection_secure::on_shutdown (const boost::system::error_code & error)
{
// No-op. We initiate the shutdown (since the RPC server kills the connection after each request)
// and we'll thus get an expected EOF error. If the client disconnects, a short-read error will be expected.
}
void rai::rpc_connection_secure::handle_handshake (const boost::system::error_code & error)
{
if (!error)
{
read ();
}
else
{
BOOST_LOG (node->log) << "TLS: Handshake error: " << error.message ();
}
}
void rai::rpc_connection_secure::read ()
{
auto this_l (std::static_pointer_cast<rai::rpc_connection_secure> (shared_from_this ()));
boost::beast::http::async_read (stream, buffer, request, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
if (!ec)
{
this_l->node->background ([this_l]() {
auto start (std::chrono::steady_clock::now ());
auto version (this_l->request.version ());
auto response_handler ([this_l, version, start](boost::property_tree::ptree const & tree_a) {
std::stringstream ostream;
boost::property_tree::write_json (ostream, tree_a);
ostream.flush ();
auto body (ostream.str ());
this_l->write_result (body, version);
boost::beast::http::async_write (this_l->stream, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
// Perform the SSL shutdown
this_l->stream.async_shutdown (
std::bind (
&rai::rpc_connection_secure::on_shutdown,
this_l,
std::placeholders::_1));
});
if (this_l->node->config.logging.log_rpc ())
{
BOOST_LOG (this_l->node->log) << boost::str (boost::format ("TLS: RPC request %2% completed in: %1% microseconds") % std::chrono::duration_cast<std::chrono::microseconds> (std::chrono::steady_clock::now () - start).count () % boost::io::group (std::hex, std::showbase, reinterpret_cast<uintptr_t> (this_l.get ())));
}
});
if (this_l->request.method () == boost::beast::http::verb::post)
{
auto handler (std::make_shared<rai::rpc_handler> (*this_l->node, this_l->rpc, this_l->request.body (), response_handler));
handler->process_request ();
}
else
{
error_response (response_handler, "Can only POST requests");
}
});
}
else
{
BOOST_LOG (this_l->node->log) << "TLS: Read error: " << ec.message () << std::endl;
}
});
}

50
rai/node/rpc_secure.hpp Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <rai/node/rpc.hpp>
namespace rai
{
/**
* Specialization of rai::rpc with TLS support
*/
class rpc_secure : public rpc
{
public:
rpc_secure (boost::asio::io_service & service_a, rai::node & node_a, rai::rpc_config const & config_a);
/** Starts accepting connections */
virtual 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;
};
/**
* Specialization of rai::rpc_connection for establishing TLS connections.
* Handshakes with client certificates are supported.
*/
class rpc_connection_secure : public rpc_connection
{
public:
rpc_connection_secure (rai::node &, rai::rpc_secure &);
virtual void parse_connection () override;
virtual void read () override;
/** The TLS handshake callback */
void handle_handshake (const boost::system::error_code & error);
/** The TLS async shutdown callback */
void on_shutdown (const boost::system::error_code & error);
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket &> stream;
};
}

View file

@ -119,10 +119,10 @@ void rai_daemon::daemon::run (boost::filesystem::path const & data_path)
if (!init.error ())
{
node->start ();
rai::rpc rpc (service, *node, config.rpc);
if (config.rpc_enable)
std::unique_ptr<rai::rpc> rpc = get_rpc (service, *node, config.rpc);
if (rpc && config.rpc_enable)
{
rpc.start ();
rpc->start ();
}
runner.reset (new rai::thread_runner (service, node->config.io_threads));
runner->join ();

View file

@ -252,14 +252,14 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost
assert (wallet->exists (config.account));
update_config (config, config_path, config_file);
node->start ();
rai::rpc rpc (service, *node, config.rpc);
if (config.rpc_enable)
std::unique_ptr<rai::rpc> rpc = get_rpc (service, *node, config.rpc);
if (rpc && config.rpc_enable)
{
rpc.start ();
rpc->start ();
}
rai::thread_runner runner (service, node->config.io_threads);
QObject::connect (&application, &QApplication::aboutToQuit, [&]() {
rpc.stop ();
rpc->stop ();
node->stop ();
});
application.postEvent (&processor, new rai_qt::eventloop_event ([&]() {