Merge pull request #4852

057c279c epee: add SSL support (Martijn Otto)
This commit is contained in:
Riccardo Spagni 2019-03-05 16:21:30 +02:00
commit 5bbbe3902b
No known key found for this signature in database
GPG key ID: 55432DF31CCD4FCD
15 changed files with 200 additions and 49 deletions

View file

@ -32,6 +32,7 @@
#include <cstdint> #include <cstdint>
#include <iosfwd> #include <iosfwd>
#include <string> #include <string>
#include <boost/utility/string_ref.hpp>
#include "wipeable_string.h" #include "wipeable_string.h"
#include "span.h" #include "span.h"
@ -68,4 +69,10 @@ namespace epee
//! Write `src` bytes as hex to `out`. `out` must be twice the length //! Write `src` bytes as hex to `out`. `out` must be twice the length
static void buffer_unchecked(char* out, const span<const std::uint8_t> src) noexcept; static void buffer_unchecked(char* out, const span<const std::uint8_t> src) noexcept;
}; };
struct from_hex
{
//! \return An std::vector of unsigned integers from the `src`
static std::vector<uint8_t> vector(boost::string_ref src);
};
} }

View file

@ -228,8 +228,8 @@ namespace net_utils
std::map<std::string, t_connection_type> server_type_map; std::map<std::string, t_connection_type> server_type_map;
void create_server_type_map(); void create_server_type_map();
bool init_server(uint32_t port, const std::string address = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list<std::string> &allowed_certificates = {}, bool allow_any_cert = false); bool init_server(uint32_t port, const std::string address = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, bool allow_any_cert = false);
bool init_server(const std::string port, const std::string& address = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list<std::string> &allowed_certificates = {}, bool allow_any_cert = false); bool init_server(const std::string port, const std::string& address = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, bool allow_any_cert = false);
/// Run the server's io_service loop. /// Run the server's io_service loop.
bool run_server(size_t threads_count, bool wait = true, const boost::thread::attributes& attrs = boost::thread::attributes()); bool run_server(size_t threads_count, bool wait = true, const boost::thread::attributes& attrs = boost::thread::attributes());

View file

@ -900,7 +900,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
m_thread_index(0), m_thread_index(0),
m_connection_type( connection_type ), m_connection_type( connection_type ),
new_connection_(), new_connection_(),
m_ssl_context({boost::asio::ssl::context(boost::asio::ssl::context::sslv23), {}}) m_ssl_context({boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}})
{ {
create_server_type_map(); create_server_type_map();
m_thread_name_prefix = "NET"; m_thread_name_prefix = "NET";
@ -939,14 +939,14 @@ PRAGMA_WARNING_DISABLE_VS(4355)
} }
//--------------------------------------------------------------------------------- //---------------------------------------------------------------------------------
template<class t_protocol_handler> template<class t_protocol_handler>
bool boosted_tcp_server<t_protocol_handler>::init_server(uint32_t port, const std::string address, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, bool allow_any_cert) bool boosted_tcp_server<t_protocol_handler>::init_server(uint32_t port, const std::string address, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert)
{ {
TRY_ENTRY(); TRY_ENTRY();
m_stop_signal_sent = false; m_stop_signal_sent = false;
m_port = port; m_port = port;
m_address = address; m_address = address;
if (ssl_support != epee::net_utils::ssl_support_t::e_ssl_support_disabled) if (ssl_support != epee::net_utils::ssl_support_t::e_ssl_support_disabled)
m_ssl_context = create_ssl_context(private_key_and_certificate_path, allowed_certificates, allow_any_cert); m_ssl_context = create_ssl_context(private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert);
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::resolver::query query(address, boost::lexical_cast<std::string>(port), boost::asio::ip::tcp::resolver::query::canonical_name); boost::asio::ip::tcp::resolver::query query(address, boost::lexical_cast<std::string>(port), boost::asio::ip::tcp::resolver::query::canonical_name);
@ -980,7 +980,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
PUSH_WARNINGS PUSH_WARNINGS
DISABLE_GCC_WARNING(maybe-uninitialized) DISABLE_GCC_WARNING(maybe-uninitialized)
template<class t_protocol_handler> template<class t_protocol_handler>
bool boosted_tcp_server<t_protocol_handler>::init_server(const std::string port, const std::string& address, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, bool allow_any_cert) bool boosted_tcp_server<t_protocol_handler>::init_server(const std::string port, const std::string& address, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert)
{ {
uint32_t p = 0; uint32_t p = 0;
@ -988,7 +988,7 @@ DISABLE_GCC_WARNING(maybe-uninitialized)
MERROR("Failed to convert port no = " << port); MERROR("Failed to convert port no = " << port);
return false; return false;
} }
return this->init_server(p, address, ssl_support, private_key_and_certificate_path, allowed_certificates, allow_any_cert); return this->init_server(p, address, ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert);
} }
POP_WARNINGS POP_WARNINGS
//--------------------------------------------------------------------------------- //---------------------------------------------------------------------------------

View file

@ -278,6 +278,7 @@ namespace net_utils
epee::net_utils::ssl_support_t m_ssl_support; epee::net_utils::ssl_support_t m_ssl_support;
std::pair<std::string, std::string> m_ssl_private_key_and_certificate_path; std::pair<std::string, std::string> m_ssl_private_key_and_certificate_path;
std::list<std::string> m_ssl_allowed_certificates; std::list<std::string> m_ssl_allowed_certificates;
std::vector<std::vector<uint8_t>> m_ssl_allowed_fingerprints;
bool m_ssl_allow_any_cert; bool m_ssl_allow_any_cert;
public: public:
@ -302,16 +303,16 @@ namespace net_utils
const std::string &get_host() const { return m_host_buff; }; const std::string &get_host() const { return m_host_buff; };
const std::string &get_port() const { return m_port; }; const std::string &get_port() const { return m_port; };
bool set_server(const std::string& address, boost::optional<login> user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::list<std::string> &allowed_ssl_certificates = {}, bool allow_any_cert = false) bool set_server(const std::string& address, boost::optional<login> user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::list<std::string> &allowed_ssl_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_ssl_fingerprints = {}, bool allow_any_cert = false)
{ {
http::url_content parsed{}; http::url_content parsed{};
const bool r = parse_url(address, parsed); const bool r = parse_url(address, parsed);
CHECK_AND_ASSERT_MES(r, false, "failed to parse url: " << address); CHECK_AND_ASSERT_MES(r, false, "failed to parse url: " << address);
set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user), ssl_support, private_key_and_certificate_path, allowed_ssl_certificates, allow_any_cert); set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user), ssl_support, private_key_and_certificate_path, allowed_ssl_certificates, allowed_ssl_fingerprints, allow_any_cert);
return true; return true;
} }
void set_server(std::string host, std::string port, boost::optional<login> user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::list<std::string> &allowed_ssl_certificates = {}, bool allow_any_cert = false) void set_server(std::string host, std::string port, boost::optional<login> user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::list<std::string> &allowed_ssl_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_ssl_fingerprints = {}, bool allow_any_cert = false)
{ {
CRITICAL_REGION_LOCAL(m_lock); CRITICAL_REGION_LOCAL(m_lock);
disconnect(); disconnect();
@ -321,8 +322,9 @@ namespace net_utils
m_ssl_support = ssl_support; m_ssl_support = ssl_support;
m_ssl_private_key_and_certificate_path = private_key_and_certificate_path; m_ssl_private_key_and_certificate_path = private_key_and_certificate_path;
m_ssl_allowed_certificates = allowed_ssl_certificates; m_ssl_allowed_certificates = allowed_ssl_certificates;
m_ssl_allowed_fingerprints = allowed_ssl_fingerprints;
m_ssl_allow_any_cert = allow_any_cert; m_ssl_allow_any_cert = allow_any_cert;
m_net_client.set_ssl(m_ssl_support, m_ssl_private_key_and_certificate_path, m_ssl_allowed_certificates, m_ssl_allow_any_cert); m_net_client.set_ssl(m_ssl_support, m_ssl_private_key_and_certificate_path, m_ssl_allowed_certificates, m_ssl_allowed_fingerprints, m_ssl_allow_any_cert);
} }
bool connect(std::chrono::milliseconds timeout) bool connect(std::chrono::milliseconds timeout)

View file

@ -61,7 +61,9 @@ namespace epee
boost::optional<net_utils::http::login> user = boost::none, boost::optional<net_utils::http::login> user = boost::none,
epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect,
const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::pair<std::string, std::string> &private_key_and_certificate_path = {},
const std::list<std::string> &allowed_certificates = std::list<std::string>(), bool allow_any_cert = false) std::list<std::string> allowed_certificates = {},
std::vector<std::vector<uint8_t>> allowed_fingerprints = {},
bool allow_any_cert = false)
{ {
//set self as callback handler //set self as callback handler
@ -78,7 +80,7 @@ namespace epee
m_net_server.get_config_object().m_user = std::move(user); m_net_server.get_config_object().m_user = std::move(user);
MGINFO("Binding on " << bind_ip << ":" << bind_port); MGINFO("Binding on " << bind_ip << ":" << bind_port);
bool res = m_net_server.init_server(bind_port, bind_ip, ssl_support, private_key_and_certificate_path, allowed_certificates, allow_any_cert); bool res = m_net_server.init_server(bind_port, bind_ip, ssl_support, private_key_and_certificate_path, std::move(allowed_certificates), std::move(allowed_fingerprints), allow_any_cert);
if(!res) if(!res)
{ {
LOG_ERROR("Failed to bind server"); LOG_ERROR("Failed to bind server");

View file

@ -93,7 +93,7 @@ namespace net_utils
m_deadline(m_io_service), m_deadline(m_io_service),
m_shutdowned(0), m_shutdowned(0),
m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_autodetect), m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_autodetect),
m_ctx({boost::asio::ssl::context(boost::asio::ssl::context::sslv23), {}}), m_ctx({boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}}),
m_ssl_socket(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service,m_ctx.context)) m_ssl_socket(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service,m_ctx.context))
{ {
@ -118,12 +118,12 @@ namespace net_utils
catch(...) { /* ignore */ } catch(...) { /* ignore */ }
} }
inline void set_ssl(epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::list<std::string> &allowed_certificates = std::list<std::string>(), bool allow_any_cert = false) inline void set_ssl(epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, std::list<std::string> allowed_certificates = {}, std::vector<std::vector<uint8_t>> allowed_fingerprints = {}, bool allow_any_cert = false)
{ {
if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_disabled) if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_disabled)
m_ctx = {boost::asio::ssl::context(boost::asio::ssl::context::sslv23), {}}; m_ctx = {boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}, {}};
else else
m_ctx = create_ssl_context(private_key_and_certificate_path, allowed_certificates, allow_any_cert); m_ctx = create_ssl_context(private_key_and_certificate_path, std::move(allowed_certificates), std::move(allowed_fingerprints), allow_any_cert);
m_ssl_support = ssl_support; m_ssl_support = ssl_support;
} }

View file

@ -50,16 +50,17 @@ namespace net_utils
{ {
boost::asio::ssl::context context; boost::asio::ssl::context context;
std::list<std::string> allowed_certificates; std::list<std::string> allowed_certificates;
std::vector<std::vector<uint8_t>> allowed_fingerprints;
bool allow_any_cert; bool allow_any_cert;
}; };
// https://security.stackexchange.com/questions/34780/checking-client-hello-for-https-classification // https://security.stackexchange.com/questions/34780/checking-client-hello-for-https-classification
constexpr size_t get_ssl_magic_size() { return 9; } constexpr size_t get_ssl_magic_size() { return 9; }
bool is_ssl(const unsigned char *data, size_t len); bool is_ssl(const unsigned char *data, size_t len);
ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &private_key_and_certificate_path, std::list<std::string> allowed_certificates, bool allow_any_cert); ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &private_key_and_certificate_path, std::list<std::string> allowed_certificates, std::vector<std::vector<uint8_t>> allowed_fingerprints, bool allow_any_cert);
void use_ssl_certificate(ssl_context_t &ssl_context, const std::pair<std::string, std::string> &private_key_and_certificate_path); void use_ssl_certificate(ssl_context_t &ssl_context, const std::pair<std::string, std::string> &private_key_and_certificate_path);
bool create_ssl_certificate(std::string &pkey_buffer, std::string &cert_buffer); bool create_ssl_certificate(std::string &pkey_buffer, std::string &cert_buffer);
bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const std::list<std::string> &allowed_certificates); bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const ssl_context_t &ssl_context);
bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const epee::net_utils::ssl_context_t &ssl_context); bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const epee::net_utils::ssl_context_t &ssl_context);
bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s); bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s);
} }

View file

@ -83,4 +83,67 @@ namespace epee
{ {
return write_hex(out, src); return write_hex(out, src);
} }
std::vector<uint8_t> from_hex::vector(boost::string_ref src)
{
// should we include a specific character
auto include = [](char input) {
// we ignore spaces and colons
return !std::isspace(input) && input != ':';
};
// the number of relevant characters to decode
auto count = std::count_if(src.begin(), src.end(), include);
// this must be a multiple of two, otherwise we have a truncated input
if (count % 2) {
throw std::length_error{ "Invalid hexadecimal input length" };
}
std::vector<uint8_t> result;
result.reserve(count / 2);
// the data to work with (std::string is always null-terminated)
auto data = src.data();
// convert a single hex character to an unsigned integer
auto char_to_int = [](const char *input) {
switch (std::tolower(*input)) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': return 10;
case 'b': return 11;
case 'c': return 12;
case 'd': return 13;
case 'e': return 14;
case 'f': return 15;
default: throw std::range_error{ "Invalid hexadecimal input" };
}
};
// keep going until we reach the end
while (data[0] != '\0') {
// skip unwanted characters
if (!include(data[0])) {
++data;
continue;
}
// convert two matching characters to int
auto high = char_to_int(data++);
auto low = char_to_int(data++);
result.push_back(high << 4 | low);
}
return result;
}
} }

View file

@ -154,13 +154,19 @@ bool create_ssl_certificate(std::string &pkey_buffer, std::string &cert_buffer)
return success; return success;
} }
ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &private_key_and_certificate_path, std::list<std::string> allowed_certificates, bool allow_any_cert) ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &private_key_and_certificate_path, std::list<std::string> allowed_certificates, std::vector<std::vector<uint8_t>> allowed_fingerprints, bool allow_any_cert)
{ {
ssl_context_t ssl_context({boost::asio::ssl::context(boost::asio::ssl::context::sslv23), std::move(allowed_certificates)}); ssl_context_t ssl_context{boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), std::move(allowed_certificates), std::move(allowed_fingerprints)};
// disable sslv2 // only allow tls v1.2 and up
ssl_context.context.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2); ssl_context.context.set_options(boost::asio::ssl::context::default_workarounds);
ssl_context.context.set_default_verify_paths(); ssl_context.context.set_options(boost::asio::ssl::context::no_sslv2);
ssl_context.context.set_options(boost::asio::ssl::context::no_sslv3);
ssl_context.context.set_options(boost::asio::ssl::context::no_tlsv1);
ssl_context.context.set_options(boost::asio::ssl::context::no_tlsv1_1);
// only allow a select handful of tls v1.3 and v1.2 ciphers to be used
SSL_CTX_set_cipher_list(ssl_context.context.native_handle(), "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-CHACHA20-POLY1305");
// set options on the SSL context for added security // set options on the SSL context for added security
SSL_CTX *ctx = ssl_context.context.native_handle(); SSL_CTX *ctx = ssl_context.context.native_handle();
@ -179,7 +185,7 @@ ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &priv
#ifdef SSL_OP_NO_COMPRESSION #ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION); SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif #endif
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); // https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices ssl_context.context.set_default_verify_paths();
CHECK_AND_ASSERT_THROW_MES(private_key_and_certificate_path.first.empty() == private_key_and_certificate_path.second.empty(), "private key and certificate must be either both given or both empty"); CHECK_AND_ASSERT_THROW_MES(private_key_and_certificate_path.first.empty() == private_key_and_certificate_path.second.empty(), "private key and certificate must be either both given or both empty");
if (private_key_and_certificate_path.second.empty()) if (private_key_and_certificate_path.second.empty())
@ -225,7 +231,7 @@ bool is_ssl(const unsigned char *data, size_t len)
return false; return false;
} }
bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const std::list<std::string> &allowed_certificates) bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const ssl_context_t &ssl_context)
{ {
X509_STORE_CTX *sctx = ctx.native_handle(); X509_STORE_CTX *sctx = ctx.native_handle();
if (!sctx) if (!sctx)
@ -240,11 +246,32 @@ bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const std::li
return false; return false;
} }
// can we check the certificate against a list of fingerprints?
if (!ssl_context.allowed_fingerprints.empty()) {
// buffer for the certificate digest and the size of the result
std::vector<uint8_t> digest(EVP_MAX_MD_SIZE);
unsigned int size{ 0 };
// create the digest from the certificate
if (!X509_digest(cert, EVP_sha1(), digest.data(), &size)) {
MERROR("Failed to create certificate fingerprint");
return false;
}
// strip unnecessary bytes from the digest
digest.resize(size);
// is the certificate fingerprint inside the list of allowed fingerprints?
if (std::find(ssl_context.allowed_fingerprints.begin(), ssl_context.allowed_fingerprints.end(), digest) != ssl_context.allowed_fingerprints.end())
return true;
}
if (!ssl_context.allowed_certificates.empty()) {
BIO *bio_cert = BIO_new(BIO_s_mem()); BIO *bio_cert = BIO_new(BIO_s_mem());
openssl_bio bio_cert_deleter{bio_cert};
bool success = PEM_write_bio_X509(bio_cert, cert); bool success = PEM_write_bio_X509(bio_cert, cert);
if (!success) if (!success)
{ {
BIO_free(bio_cert);
MERROR("Failed to print certificate"); MERROR("Failed to print certificate");
return false; return false;
} }
@ -252,11 +279,18 @@ bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const std::li
BIO_get_mem_ptr(bio_cert, &buf); BIO_get_mem_ptr(bio_cert, &buf);
if (!buf || !buf->data || !buf->length) if (!buf || !buf->data || !buf->length)
{ {
BIO_free(bio_cert);
MERROR("Failed to write certificate: " << ERR_get_error()); MERROR("Failed to write certificate: " << ERR_get_error());
return false; return false;
} }
std::string certificate(std::string(buf->data, buf->length)); std::string certificate(std::string(buf->data, buf->length));
return std::find(allowed_certificates.begin(), allowed_certificates.end(), certificate) != allowed_certificates.end(); BIO_free(bio_cert);
if (std::find(ssl_context.allowed_certificates.begin(), ssl_context.allowed_certificates.end(), certificate) != ssl_context.allowed_certificates.end())
return true;
}
// if either checklist is non-empty we must have failed it
return ssl_context.allowed_fingerprints.empty() && ssl_context.allowed_certificates.empty();
} }
bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const epee::net_utils::ssl_context_t &ssl_context) bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const epee::net_utils::ssl_context_t &ssl_context)
@ -276,7 +310,7 @@ bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socke
return false; return false;
} }
} }
if (!ssl_context.allow_any_cert && !ssl_context.allowed_certificates.empty() && !is_certificate_allowed(ctx, ssl_context.allowed_certificates)) if (!ssl_context.allow_any_cert && !is_certificate_allowed(ctx, ssl_context))
{ {
MERROR("Certificate is not in the allowed list, connection droppped"); MERROR("Certificate is not in the allowed list, connection droppped");
return false; return false;
@ -289,7 +323,7 @@ bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socke
socket.handshake(type, ec); socket.handshake(type, ec);
if (ec) if (ec)
{ {
MERROR("handshake failed, connection dropped"); MERROR("handshake failed, connection dropped: " << ec.message());
return false; return false;
} }
if (!ssl_context.allow_any_cert && !verified) if (!ssl_context.allow_any_cert && !verified)

View file

@ -80,6 +80,7 @@ namespace cryptonote
command_line::add_arg(desc, arg_rpc_ssl_private_key); command_line::add_arg(desc, arg_rpc_ssl_private_key);
command_line::add_arg(desc, arg_rpc_ssl_certificate); command_line::add_arg(desc, arg_rpc_ssl_certificate);
command_line::add_arg(desc, arg_rpc_ssl_allowed_certificates); command_line::add_arg(desc, arg_rpc_ssl_allowed_certificates);
command_line::add_arg(desc, arg_rpc_ssl_allowed_fingerprints);
command_line::add_arg(desc, arg_rpc_ssl_allow_any_cert); command_line::add_arg(desc, arg_rpc_ssl_allow_any_cert);
command_line::add_arg(desc, arg_bootstrap_daemon_address); command_line::add_arg(desc, arg_bootstrap_daemon_address);
command_line::add_arg(desc, arg_bootstrap_daemon_login); command_line::add_arg(desc, arg_bootstrap_daemon_login);
@ -156,12 +157,16 @@ namespace cryptonote
ssl_allowed_certificates.back() = std::string(); ssl_allowed_certificates.back() = std::string();
} }
} }
const std::vector<std::string> ssl_allowed_fingerprint_strings = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints);
std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ ssl_allowed_fingerprint_strings.size() };
std::transform(ssl_allowed_fingerprint_strings.begin(), ssl_allowed_fingerprint_strings.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector);
const bool ssl_allow_any_cert = command_line::get_arg(vm, arg_rpc_ssl_allow_any_cert); const bool ssl_allow_any_cert = command_line::get_arg(vm, arg_rpc_ssl_allow_any_cert);
auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); };
return epee::http_server_impl_base<core_rpc_server, connection_context>::init( return epee::http_server_impl_base<core_rpc_server, connection_context>::init(
rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login),
ssl_support, std::make_pair(ssl_private_key, ssl_certificate), ssl_allowed_certificates, ssl_allow_any_cert ssl_support, std::make_pair(ssl_private_key, ssl_certificate), std::move(ssl_allowed_certificates), std::move(ssl_allowed_fingerprints), ssl_allow_any_cert
); );
} }
//------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------
@ -2369,6 +2374,11 @@ namespace cryptonote
, "List of paths to PEM format certificates of allowed peers (all allowed if empty)" , "List of paths to PEM format certificates of allowed peers (all allowed if empty)"
}; };
const command_line::arg_descriptor<std::vector<std::string>> core_rpc_server::arg_rpc_ssl_allowed_fingerprints = {
"rpc-ssl-allowed-fingerprints"
, "List of certificate fingerprints to allow"
};
const command_line::arg_descriptor<bool> core_rpc_server::arg_rpc_ssl_allow_any_cert = { const command_line::arg_descriptor<bool> core_rpc_server::arg_rpc_ssl_allow_any_cert = {
"rpc-ssl-allow-any-cert" "rpc-ssl-allow-any-cert"
, "Allow any peer certificate, rather than just those on the allowed list" , "Allow any peer certificate, rather than just those on the allowed list"

View file

@ -60,6 +60,7 @@ namespace cryptonote
static const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key; static const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key;
static const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate; static const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate;
static const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates; static const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates;
static const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_fingerprints;
static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert; static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert;
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address;
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login;

View file

@ -241,6 +241,7 @@ struct options {
const command_line::arg_descriptor<std::string> daemon_ssl_private_key = {"daemon-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor<std::string> daemon_ssl_private_key = {"daemon-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""};
const command_line::arg_descriptor<std::string> daemon_ssl_certificate = {"daemon-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; const command_line::arg_descriptor<std::string> daemon_ssl_certificate = {"daemon-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""};
const command_line::arg_descriptor<std::vector<std::string>> daemon_ssl_allowed_certificates = {"daemon-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers")}; const command_line::arg_descriptor<std::vector<std::string>> daemon_ssl_allowed_certificates = {"daemon-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers")};
const command_line::arg_descriptor<std::vector<std::string>> daemon_ssl_allowed_fingerprints = {"daemon-ssl-allowed-fingerprints", tools::wallet2::tr("List of valid fingerprints of allowed RPC servers")};
const command_line::arg_descriptor<bool> daemon_ssl_allow_any_cert = {"daemon-ssl-allow-any-cert", tools::wallet2::tr("Allow any SSL certificate from the daemon"), false}; const command_line::arg_descriptor<bool> daemon_ssl_allow_any_cert = {"daemon-ssl-allow-any-cert", tools::wallet2::tr("Allow any SSL certificate from the daemon"), false};
const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false};
const command_line::arg_descriptor<bool> stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false}; const command_line::arg_descriptor<bool> stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false};
@ -316,6 +317,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
auto daemon_ssl_private_key = command_line::get_arg(vm, opts.daemon_ssl_private_key); auto daemon_ssl_private_key = command_line::get_arg(vm, opts.daemon_ssl_private_key);
auto daemon_ssl_certificate = command_line::get_arg(vm, opts.daemon_ssl_certificate); auto daemon_ssl_certificate = command_line::get_arg(vm, opts.daemon_ssl_certificate);
auto daemon_ssl_allowed_certificates = command_line::get_arg(vm, opts.daemon_ssl_allowed_certificates); auto daemon_ssl_allowed_certificates = command_line::get_arg(vm, opts.daemon_ssl_allowed_certificates);
auto daemon_ssl_allowed_fingerprints = command_line::get_arg(vm, opts.daemon_ssl_allowed_fingerprints);
auto daemon_ssl_allow_any_cert = command_line::get_arg(vm, opts.daemon_ssl_allow_any_cert); auto daemon_ssl_allow_any_cert = command_line::get_arg(vm, opts.daemon_ssl_allow_any_cert);
auto daemon_ssl = command_line::get_arg(vm, opts.daemon_ssl); auto daemon_ssl = command_line::get_arg(vm, opts.daemon_ssl);
epee::net_utils::ssl_support_t ssl_support; epee::net_utils::ssl_support_t ssl_support;
@ -382,8 +384,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
} }
} }
std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() };
std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector);
std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended)); std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended));
wallet->init(std::move(daemon_address), std::move(login), 0, *trusted_daemon, ssl_support, std::make_pair(daemon_ssl_private_key, daemon_ssl_certificate), ssl_allowed_certificates, daemon_ssl_allow_any_cert); wallet->init(std::move(daemon_address), std::move(login), 0, *trusted_daemon, ssl_support, std::make_pair(daemon_ssl_private_key, daemon_ssl_certificate), ssl_allowed_certificates, ssl_allowed_fingerprints, daemon_ssl_allow_any_cert);
boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
wallet->set_ring_database(ringdb_path.string()); wallet->set_ring_database(ringdb_path.string());
@ -1044,6 +1049,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
command_line::add_arg(desc_params, opts.daemon_ssl_private_key); command_line::add_arg(desc_params, opts.daemon_ssl_private_key);
command_line::add_arg(desc_params, opts.daemon_ssl_certificate); command_line::add_arg(desc_params, opts.daemon_ssl_certificate);
command_line::add_arg(desc_params, opts.daemon_ssl_allowed_certificates); command_line::add_arg(desc_params, opts.daemon_ssl_allowed_certificates);
command_line::add_arg(desc_params, opts.daemon_ssl_allowed_fingerprints);
command_line::add_arg(desc_params, opts.daemon_ssl_allow_any_cert); command_line::add_arg(desc_params, opts.daemon_ssl_allow_any_cert);
command_line::add_arg(desc_params, opts.testnet); command_line::add_arg(desc_params, opts.testnet);
command_line::add_arg(desc_params, opts.stagenet); command_line::add_arg(desc_params, opts.stagenet);
@ -1096,7 +1102,7 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, bool allow_any_cert) bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert)
{ {
m_checkpoints.init_default_checkpoints(m_nettype); m_checkpoints.init_default_checkpoints(m_nettype);
if(m_http_client.is_connected()) if(m_http_client.is_connected())
@ -1106,7 +1112,7 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::
m_daemon_address = std::move(daemon_address); m_daemon_address = std::move(daemon_address);
m_daemon_login = std::move(daemon_login); m_daemon_login = std::move(daemon_login);
m_trusted_daemon = trusted_daemon; m_trusted_daemon = trusted_daemon;
return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl_support, private_key_and_certificate_path, allowed_certificates, allow_any_cert); return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert);
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
bool wallet2::is_deterministic() const bool wallet2::is_deterministic() const

View file

@ -680,7 +680,8 @@ namespace tools
bool trusted_daemon = true, bool trusted_daemon = true,
epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect,
const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::pair<std::string, std::string> &private_key_and_certificate_path = {},
const std::list<std::string> &allowed_certificates = {}, bool allow_any_cert = false); const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {},
bool allow_any_cert = false);
void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); } void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); }

View file

@ -67,6 +67,7 @@ namespace
const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key = {"rpc-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key = {"rpc-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""};
const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate = {"rpc-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate = {"rpc-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""};
const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates = {"rpc-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers (all allowed if empty)")}; const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates = {"rpc-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers (all allowed if empty)")};
const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_fingerprints = {"rpc-ssl-allowed-fingerprints", tools::wallet2::tr("List of certificate fingerprints to allow")};
constexpr const char default_rpc_username[] = "monero"; constexpr const char default_rpc_username[] = "monero";
@ -240,6 +241,7 @@ namespace tools
auto rpc_ssl_private_key = command_line::get_arg(vm, arg_rpc_ssl_private_key); auto rpc_ssl_private_key = command_line::get_arg(vm, arg_rpc_ssl_private_key);
auto rpc_ssl_certificate = command_line::get_arg(vm, arg_rpc_ssl_certificate); auto rpc_ssl_certificate = command_line::get_arg(vm, arg_rpc_ssl_certificate);
auto rpc_ssl_allowed_certificates = command_line::get_arg(vm, arg_rpc_ssl_allowed_certificates); auto rpc_ssl_allowed_certificates = command_line::get_arg(vm, arg_rpc_ssl_allowed_certificates);
auto rpc_ssl_allowed_fingerprints = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints);
auto rpc_ssl = command_line::get_arg(vm, arg_rpc_ssl); auto rpc_ssl = command_line::get_arg(vm, arg_rpc_ssl);
epee::net_utils::ssl_support_t rpc_ssl_support; epee::net_utils::ssl_support_t rpc_ssl_support;
if (!epee::net_utils::ssl_support_from_string(rpc_ssl_support, rpc_ssl)) if (!epee::net_utils::ssl_support_from_string(rpc_ssl_support, rpc_ssl))
@ -258,11 +260,14 @@ namespace tools
} }
} }
std::vector<std::vector<uint8_t>> allowed_fingerprints{ rpc_ssl_allowed_fingerprints.size() };
std::transform(rpc_ssl_allowed_fingerprints.begin(), rpc_ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector);
m_net_server.set_threads_prefix("RPC"); m_net_server.set_threads_prefix("RPC");
auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); };
return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init(
rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login),
rpc_ssl_support, std::make_pair(rpc_ssl_private_key, rpc_ssl_certificate), allowed_certificates rpc_ssl_support, std::make_pair(rpc_ssl_private_key, rpc_ssl_certificate), std::move(allowed_certificates), std::move(allowed_fingerprints)
); );
} }
//------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------

View file

@ -389,6 +389,25 @@ TEST(ToHex, String)
} }
TEST(FromHex, String)
{
// the source data to encode and decode
std::vector<uint8_t> source{{ 0x00, 0xFF, 0x0F, 0xF0 }};
// encode and decode the data
auto hex = epee::to_hex::string({ source.data(), source.size() });
auto decoded = epee::from_hex::vector(hex);
// encoded should be twice the size and should decode to the exact same data
EXPECT_EQ(source.size() * 2, hex.size());
EXPECT_EQ(source, decoded);
// we will now create a padded hex string, we want to explicitly allow
// decoding it this way also, ignoring spaces and colons between the numbers
hex.assign("00:ff 0f:f0");
EXPECT_EQ(source, epee::from_hex::vector(hex));
}
TEST(ToHex, Array) TEST(ToHex, Array)
{ {
EXPECT_EQ( EXPECT_EQ(