Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix RPC secure (TLS) #2424

Merged
merged 3 commits into from
Dec 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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