TLS support for RPC
This commit is contained in:
parent
df64b236e1
commit
8840c9dbef
7 changed files with 436 additions and 58 deletions
|
@ -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)
|
||||
|
|
162
rai/node/rpc.cpp
162
rai/node/rpc.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
194
rai/node/rpc_secure.cpp
Normal 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
50
rai/node/rpc_secure.hpp
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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 ();
|
||||
|
|
|
@ -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 ([&]() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue