Skip to content

Commit

Permalink
Fix RPC secure (TLS) (#2424)
Browse files Browse the repository at this point in the history
* Fix RPC secure (TLS)

* Use explicit template instantiation

* Add NANO_SECURE_RPC guards
  • Loading branch information
cryptocode authored Dec 4, 2019
1 parent b6844af commit f7cd523
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 51 deletions.
55 changes: 32 additions & 23 deletions nano/rpc/rpc_connection.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#include <nano/lib/config.hpp>
#include <nano/lib/json_error_response.hpp>
#include <nano/lib/logger_mt.hpp>
#include <nano/lib/rpc_handler_interface.hpp>
#include <nano/lib/rpcconfig.hpp>
#include <nano/rpc/rpc_connection.hpp>
#include <nano/rpc/rpc_handler.hpp>

#include <boost/algorithm/string/predicate.hpp>
#ifdef NANO_SECURE_RPC
#include <boost/asio/ssl/stream.hpp>
#endif
#include <boost/format.hpp>

nano::rpc_connection::rpc_connection (nano::rpc_config const & rpc_config, boost::asio::io_context & io_ctx, nano::logger_mt & logger, nano::rpc_handler_interface & rpc_handler_interface) :
Expand All @@ -22,7 +21,7 @@ rpc_handler_interface (rpc_handler_interface)

void nano::rpc_connection::parse_connection ()
{
read ();
read (socket);
}

void nano::rpc_connection::prepare_head (unsigned version, boost::beast::http::status status)
Expand Down Expand Up @@ -51,12 +50,19 @@ void nano::rpc_connection::write_result (std::string body, unsigned version, boo
}
}

void nano::rpc_connection::read ()
void nano::rpc_connection::write_completion_handler (std::shared_ptr<nano::rpc_connection> rpc_connection)
{
// Intentional no-op
}

template <typename STREAM_TYPE>
void nano::rpc_connection::read (STREAM_TYPE & stream)
{
auto this_l (shared_from_this ());
auto header_parser (std::make_shared<boost::beast::http::request_parser<boost::beast::http::empty_body>> ());
header_parser->body_limit (rpc_config.max_request_size);
boost::beast::http::async_read_header (socket, buffer, *header_parser, boost::asio::bind_executor (strand, [this_l, header_parser](boost::system::error_code const & ec, size_t bytes_transferred) {

boost::beast::http::async_read_header (stream, buffer, *header_parser, boost::asio::bind_executor (strand, [this_l, &stream, header_parser](boost::system::error_code const & ec, size_t bytes_transferred) {
if (!ec)
{
if (boost::iequals (header_parser->get ()[boost::beast::http::field::expect], "100-continue"))
Expand All @@ -65,45 +71,46 @@ void nano::rpc_connection::read ()
continue_response->version (11);
continue_response->result (boost::beast::http::status::continue_);
continue_response->set (boost::beast::http::field::server, "nano");
boost::beast::http::async_write (this_l->socket, *continue_response, boost::asio::bind_executor (this_l->strand, [this_l, continue_response](boost::system::error_code const & ec, size_t bytes_transferred) {}));
boost::beast::http::async_write (stream, *continue_response, boost::asio::bind_executor (this_l->strand, [this_l, continue_response](boost::system::error_code const & ec, size_t bytes_transferred) {}));
}

this_l->parse_request (header_parser);
this_l->parse_request (stream, header_parser);
}
else
{
this_l->logger.always_log ("RPC header error: ", ec.message ());

// Respond with the reason for the invalid header
auto response_handler ([this_l](std::string const & tree_a) {
auto response_handler ([this_l, &stream](std::string const & tree_a) {
this_l->write_result (tree_a, 11);
boost::beast::http::async_write (this_l->socket, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
boost::beast::http::async_write (stream, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
this_l->write_completion_handler (this_l);
}));
});
json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ());
nano::json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ());
}
}));
}

void nano::rpc_connection::parse_request (std::shared_ptr<boost::beast::http::request_parser<boost::beast::http::empty_body>> header_parser)
template <typename STREAM_TYPE>
void nano::rpc_connection::parse_request (STREAM_TYPE & stream, std::shared_ptr<boost::beast::http::request_parser<boost::beast::http::empty_body>> header_parser)
{
auto this_l (shared_from_this ());
auto body_parser (std::make_shared<boost::beast::http::request_parser<boost::beast::http::string_body>> (std::move (*header_parser)));
boost::beast::http::async_read (socket, buffer, *body_parser, boost::asio::bind_executor (strand, [this_l, body_parser](boost::system::error_code const & ec, size_t bytes_transferred) {
boost::beast::http::async_read (stream, buffer, *body_parser, boost::asio::bind_executor (strand, [this_l, body_parser, &stream](boost::system::error_code const & ec, size_t bytes_transferred) {
if (!ec)
{
this_l->io_ctx.post ([this_l, body_parser]() {
this_l->io_ctx.post ([this_l, body_parser, &stream]() {
auto & req (body_parser->get ());
auto start (std::chrono::steady_clock::now ());
auto version (req.version ());
std::stringstream ss;
ss << std::hex << std::showbase << reinterpret_cast<uintptr_t> (this_l.get ());
auto request_id = ss.str ();
auto response_handler ([this_l, version, start, request_id](std::string const & tree_a) {
auto response_handler ([this_l, version, start, request_id, &stream](std::string const & tree_a) {
auto body = tree_a;
this_l->write_result (body, version);
boost::beast::http::async_write (this_l->socket, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
boost::beast::http::async_write (stream, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
this_l->write_completion_handler (this_l);
}));

Expand All @@ -124,14 +131,14 @@ void nano::rpc_connection::parse_request (std::shared_ptr<boost::beast::http::re
{
this_l->prepare_head (version);
this_l->res.prepare_payload ();
boost::beast::http::async_write (this_l->socket, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
boost::beast::http::async_write (stream, this_l->res, boost::asio::bind_executor (this_l->strand, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) {
this_l->write_completion_handler (this_l);
}));
break;
}
default:
{
json_error_response (response_handler, "Can only POST requests");
nano::json_error_response (response_handler, "Can only POST requests");
break;
}
}
Expand All @@ -144,7 +151,9 @@ void nano::rpc_connection::parse_request (std::shared_ptr<boost::beast::http::re
}));
}

void nano::rpc_connection::write_completion_handler (std::shared_ptr<nano::rpc_connection> rpc_connection)
{
// Intentional no-op
}
template void nano::rpc_connection::read (socket_type &);
template void nano::rpc_connection::parse_request (socket_type &, std::shared_ptr<boost::beast::http::request_parser<boost::beast::http::empty_body>>);
#ifdef NANO_SECURE_RPC
template void nano::rpc_connection::read (boost::asio::ssl::stream<socket_type &> &);
template void nano::rpc_connection::parse_request (boost::asio::ssl::stream<socket_type &> &, std::shared_ptr<boost::beast::http::request_parser<boost::beast::http::empty_body>>);
#endif
14 changes: 11 additions & 3 deletions nano/rpc/rpc_connection.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#pragma once

#include <nano/lib/json_error_response.hpp>
#include <nano/lib/logger_mt.hpp>
#include <nano/lib/rpcconfig.hpp>
#include <nano/rpc/rpc_handler.hpp>

#include <boost/algorithm/string/predicate.hpp>
#include <boost/asio/strand.hpp>
#include <boost/beast.hpp>

Expand Down Expand Up @@ -29,9 +33,6 @@ class rpc_connection : public std::enable_shared_from_this<nano::rpc_connection>
virtual void write_completion_handler (std::shared_ptr<nano::rpc_connection> rpc_connection);
void prepare_head (unsigned version, boost::beast::http::status status = boost::beast::http::status::ok);
void write_result (std::string body, unsigned version, boost::beast::http::status status = boost::beast::http::status::ok);
void parse_request (std::shared_ptr<boost::beast::http::request_parser<boost::beast::http::empty_body>> header_parser);

void read ();

socket_type socket;
boost::beast::flat_buffer buffer;
Expand All @@ -42,5 +43,12 @@ class rpc_connection : public std::enable_shared_from_this<nano::rpc_connection>
nano::logger_mt & logger;
nano::rpc_config const & rpc_config;
nano::rpc_handler_interface & rpc_handler_interface;

protected:
template <typename STREAM_TYPE>
void read (STREAM_TYPE & stream);

template <typename STREAM_TYPE>
void parse_request (STREAM_TYPE & stream, std::shared_ptr<boost::beast::http::request_parser<boost::beast::http::empty_body>> header_parser);
};
}
6 changes: 3 additions & 3 deletions nano/rpc/rpc_connection_secure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ void nano::rpc_connection_secure::parse_connection ()
// Perform the SSL handshake
auto this_l = std::static_pointer_cast<nano::rpc_connection_secure> (shared_from_this ());
stream.async_handshake (boost::asio::ssl::stream_base::server,
[this_l](auto & ec) {
boost::asio::bind_executor (this_l->strand, [this_l](auto & ec) {
this_l->handle_handshake (ec);
});
}));
}

void nano::rpc_connection_secure::on_shutdown (const boost::system::error_code & error)
Expand All @@ -29,7 +29,7 @@ void nano::rpc_connection_secure::handle_handshake (const boost::system::error_c
{
if (!error)
{
read ();
read (stream);
}
else
{
Expand Down
53 changes: 31 additions & 22 deletions nano/rpc/rpc_secure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,33 +61,42 @@ bool nano::rpc_secure::on_verify_certificate (bool preverified, boost::asio::ssl

void nano::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;
});
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);
// 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.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);
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)
// 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)
{
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);
});
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);
}
}

Expand Down

0 comments on commit f7cd523

Please sign in to comment.