From 3d78dbfa95d06b907cb0b44547ff694816a2c9c0 Mon Sep 17 00:00:00 2001 From: cryptocode Date: Wed, 4 Dec 2019 12:09:30 +0100 Subject: [PATCH 1/3] Fix RPC secure (TLS) --- nano/rpc/rpc_connection.cpp | 99 +-------------------------- nano/rpc/rpc_connection.hpp | 104 ++++++++++++++++++++++++++++- nano/rpc/rpc_connection_secure.cpp | 6 +- nano/rpc/rpc_secure.cpp | 53 +++++++++------ 4 files changed, 136 insertions(+), 126 deletions(-) diff --git a/nano/rpc/rpc_connection.cpp b/nano/rpc/rpc_connection.cpp index 751033ab2a..acc8b9d6c1 100644 --- a/nano/rpc/rpc_connection.cpp +++ b/nano/rpc/rpc_connection.cpp @@ -1,12 +1,8 @@ #include -#include -#include #include -#include #include #include -#include #include 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) : @@ -22,7 +18,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) @@ -51,99 +47,6 @@ void nano::rpc_connection::write_result (std::string body, unsigned version, boo } } -void nano::rpc_connection::read () -{ - auto this_l (shared_from_this ()); - auto header_parser (std::make_shared> ()); - 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) { - if (!ec) - { - if (boost::iequals (header_parser->get ()[boost::beast::http::field::expect], "100-continue")) - { - auto continue_response (std::make_shared> ()); - 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) {})); - } - - this_l->parse_request (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) { - 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) { - this_l->write_completion_handler (this_l); - })); - }); - json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ()); - } - })); -} - -void nano::rpc_connection::parse_request (std::shared_ptr> header_parser) -{ - auto this_l (shared_from_this ()); - auto body_parser (std::make_shared> (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) { - if (!ec) - { - this_l->io_ctx.post ([this_l, body_parser]() { - 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 (this_l.get ()); - auto request_id = ss.str (); - auto response_handler ([this_l, version, start, request_id](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) { - this_l->write_completion_handler (this_l); - })); - - std::stringstream ss; - ss << "RPC request " << request_id << " completed in: " << std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () << " microseconds"; - this_l->logger.always_log (ss.str ().c_str ()); - }); - auto method = req.method (); - switch (method) - { - case boost::beast::http::verb::post: - { - auto handler (std::make_shared (this_l->rpc_config, req.body (), request_id, response_handler, this_l->rpc_handler_interface, this_l->logger)); - handler->process_request (); - break; - } - case boost::beast::http::verb::options: - { - 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) { - this_l->write_completion_handler (this_l); - })); - break; - } - default: - { - json_error_response (response_handler, "Can only POST requests"); - break; - } - } - }); - } - else - { - this_l->logger.always_log ("RPC read error: ", ec.message ()); - } - })); -} - void nano::rpc_connection::write_completion_handler (std::shared_ptr rpc_connection) { // Intentional no-op diff --git a/nano/rpc/rpc_connection.hpp b/nano/rpc/rpc_connection.hpp index ee0287fb9c..8db10b1678 100644 --- a/nano/rpc/rpc_connection.hpp +++ b/nano/rpc/rpc_connection.hpp @@ -1,7 +1,11 @@ #pragma once +#include +#include +#include #include +#include #include #include @@ -29,9 +33,6 @@ class rpc_connection : public std::enable_shared_from_this virtual void write_completion_handler (std::shared_ptr 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> header_parser); - - void read (); socket_type socket; boost::beast::flat_buffer buffer; @@ -42,5 +43,102 @@ class rpc_connection : public std::enable_shared_from_this nano::logger_mt & logger; nano::rpc_config const & rpc_config; nano::rpc_handler_interface & rpc_handler_interface; + +protected: + template + void read (STREAM_TYPE & stream) + { + auto this_l (shared_from_this ()); + auto header_parser (std::make_shared> ()); + header_parser->body_limit (rpc_config.max_request_size); + + 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")) + { + auto continue_response (std::make_shared> ()); + 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 (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 (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, &stream](std::string const & tree_a) { + this_l->write_result (tree_a, 11); + 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); + })); + }); + nano::json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ()); + } + })); + } + + template + void parse_request (STREAM_TYPE & stream, std::shared_ptr> header_parser) + { + auto this_l (shared_from_this ()); + auto body_parser (std::make_shared> (std::move (*header_parser))); + 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, &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 (this_l.get ()); + auto request_id = ss.str (); + 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 (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); + })); + + std::stringstream ss; + ss << "RPC request " << request_id << " completed in: " << std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () << " microseconds"; + this_l->logger.always_log (ss.str ().c_str ()); + }); + auto method = req.method (); + switch (method) + { + case boost::beast::http::verb::post: + { + auto handler (std::make_shared (this_l->rpc_config, req.body (), request_id, response_handler, this_l->rpc_handler_interface, this_l->logger)); + handler->process_request (); + break; + } + case boost::beast::http::verb::options: + { + this_l->prepare_head (version); + this_l->res.prepare_payload (); + 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: + { + nano::json_error_response (response_handler, "Can only POST requests"); + break; + } + } + }); + } + else + { + this_l->logger.always_log ("RPC read error: ", ec.message ()); + } + })); + } }; } diff --git a/nano/rpc/rpc_connection_secure.cpp b/nano/rpc/rpc_connection_secure.cpp index cb1998f88c..d69efde4a9 100644 --- a/nano/rpc/rpc_connection_secure.cpp +++ b/nano/rpc/rpc_connection_secure.cpp @@ -14,9 +14,9 @@ void nano::rpc_connection_secure::parse_connection () // Perform the SSL handshake auto this_l = std::static_pointer_cast (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) @@ -29,7 +29,7 @@ void nano::rpc_connection_secure::handle_handshake (const boost::system::error_c { if (!error) { - read (); + read (stream); } else { diff --git a/nano/rpc/rpc_secure.cpp b/nano/rpc/rpc_secure.cpp index df13ea4508..b404d491f0 100644 --- a/nano/rpc/rpc_secure.cpp +++ b/nano/rpc/rpc_secure.cpp @@ -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); } } From fef7726866683892366afd89af2290f3484201e9 Mon Sep 17 00:00:00 2001 From: cryptocode Date: Wed, 4 Dec 2019 13:43:11 +0100 Subject: [PATCH 2/3] Use explicit template instantiation --- nano/rpc/rpc_connection.cpp | 102 ++++++++++++++++++++++++++++++++++++ nano/rpc/rpc_connection.hpp | 94 +-------------------------------- 2 files changed, 104 insertions(+), 92 deletions(-) diff --git a/nano/rpc/rpc_connection.cpp b/nano/rpc/rpc_connection.cpp index acc8b9d6c1..64d938ca6c 100644 --- a/nano/rpc/rpc_connection.cpp +++ b/nano/rpc/rpc_connection.cpp @@ -3,6 +3,7 @@ #include #include +#include #include 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) : @@ -51,3 +52,104 @@ void nano::rpc_connection::write_completion_handler (std::shared_ptr +void nano::rpc_connection::read (STREAM_TYPE & stream) +{ + auto this_l (shared_from_this ()); + auto header_parser (std::make_shared> ()); + header_parser->body_limit (rpc_config.max_request_size); + + 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")) + { + auto continue_response (std::make_shared> ()); + 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 (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 (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, &stream](std::string const & tree_a) { + this_l->write_result (tree_a, 11); + 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); + })); + }); + nano::json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ()); + } + })); +} + +template +void nano::rpc_connection::parse_request (STREAM_TYPE & stream, std::shared_ptr> header_parser) +{ + auto this_l (shared_from_this ()); + auto body_parser (std::make_shared> (std::move (*header_parser))); + 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, &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 (this_l.get ()); + auto request_id = ss.str (); + 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 (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); + })); + + std::stringstream ss; + ss << "RPC request " << request_id << " completed in: " << std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () << " microseconds"; + this_l->logger.always_log (ss.str ().c_str ()); + }); + auto method = req.method (); + switch (method) + { + case boost::beast::http::verb::post: + { + auto handler (std::make_shared (this_l->rpc_config, req.body (), request_id, response_handler, this_l->rpc_handler_interface, this_l->logger)); + handler->process_request (); + break; + } + case boost::beast::http::verb::options: + { + this_l->prepare_head (version); + this_l->res.prepare_payload (); + 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: + { + nano::json_error_response (response_handler, "Can only POST requests"); + break; + } + } + }); + } + else + { + this_l->logger.always_log ("RPC read error: ", ec.message ()); + } + })); +} + +template void nano::rpc_connection::read (boost::asio::ssl::stream &); +template void nano::rpc_connection::read (socket_type &); +template void nano::rpc_connection::parse_request (boost::asio::ssl::stream &, std::shared_ptr>); +template void nano::rpc_connection::parse_request (socket_type &, std::shared_ptr>); diff --git a/nano/rpc/rpc_connection.hpp b/nano/rpc/rpc_connection.hpp index 8db10b1678..50ab9bf9f3 100644 --- a/nano/rpc/rpc_connection.hpp +++ b/nano/rpc/rpc_connection.hpp @@ -46,99 +46,9 @@ class rpc_connection : public std::enable_shared_from_this protected: template - void read (STREAM_TYPE & stream) - { - auto this_l (shared_from_this ()); - auto header_parser (std::make_shared> ()); - header_parser->body_limit (rpc_config.max_request_size); - - 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")) - { - auto continue_response (std::make_shared> ()); - 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 (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 (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, &stream](std::string const & tree_a) { - this_l->write_result (tree_a, 11); - 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); - })); - }); - nano::json_error_response (response_handler, std::string ("Invalid header: ") + ec.message ()); - } - })); - } + void read (STREAM_TYPE & stream); template - void parse_request (STREAM_TYPE & stream, std::shared_ptr> header_parser) - { - auto this_l (shared_from_this ()); - auto body_parser (std::make_shared> (std::move (*header_parser))); - 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, &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 (this_l.get ()); - auto request_id = ss.str (); - 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 (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); - })); - - std::stringstream ss; - ss << "RPC request " << request_id << " completed in: " << std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () << " microseconds"; - this_l->logger.always_log (ss.str ().c_str ()); - }); - auto method = req.method (); - switch (method) - { - case boost::beast::http::verb::post: - { - auto handler (std::make_shared (this_l->rpc_config, req.body (), request_id, response_handler, this_l->rpc_handler_interface, this_l->logger)); - handler->process_request (); - break; - } - case boost::beast::http::verb::options: - { - this_l->prepare_head (version); - this_l->res.prepare_payload (); - 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: - { - nano::json_error_response (response_handler, "Can only POST requests"); - break; - } - } - }); - } - else - { - this_l->logger.always_log ("RPC read error: ", ec.message ()); - } - })); - } + void parse_request (STREAM_TYPE & stream, std::shared_ptr> header_parser); }; } From 5087f1dc2ea0c5576fc64ff682778cabdd3fb133 Mon Sep 17 00:00:00 2001 From: cryptocode Date: Wed, 4 Dec 2019 14:19:11 +0100 Subject: [PATCH 3/3] Add NANO_SECURE_RPC guards --- nano/rpc/rpc_connection.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nano/rpc/rpc_connection.cpp b/nano/rpc/rpc_connection.cpp index 64d938ca6c..54c75b7d20 100644 --- a/nano/rpc/rpc_connection.cpp +++ b/nano/rpc/rpc_connection.cpp @@ -3,7 +3,9 @@ #include #include +#ifdef NANO_SECURE_RPC #include +#endif #include 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) : @@ -149,7 +151,9 @@ void nano::rpc_connection::parse_request (STREAM_TYPE & stream, std::shared_ptr< })); } -template void nano::rpc_connection::read (boost::asio::ssl::stream &); template void nano::rpc_connection::read (socket_type &); -template void nano::rpc_connection::parse_request (boost::asio::ssl::stream &, std::shared_ptr>); template void nano::rpc_connection::parse_request (socket_type &, std::shared_ptr>); +#ifdef NANO_SECURE_RPC +template void nano::rpc_connection::read (boost::asio::ssl::stream &); +template void nano::rpc_connection::parse_request (boost::asio::ssl::stream &, std::shared_ptr>); +#endif