aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2019-03-05 16:21:30 +0200
committerRiccardo Spagni <ric@spagni.net>2019-03-05 16:21:30 +0200
commit5bbbe3902b4ee77ca1eb23edc0b5495812353b1f (patch)
tree16ab3b2aedec9e6b68ee8254434fbb937ecb37f3
parentMerge pull request #5119 (diff)
parentepee: add SSL support (diff)
downloadmonero-5bbbe3902b4ee77ca1eb23edc0b5495812353b1f.tar.xz
Merge pull request #4852
057c279c epee: add SSL support (Martijn Otto)
-rw-r--r--contrib/epee/include/hex.h7
-rw-r--r--contrib/epee/include/net/abstract_tcp_server2.h4
-rw-r--r--contrib/epee/include/net/abstract_tcp_server2.inl10
-rw-r--r--contrib/epee/include/net/http_client.h10
-rw-r--r--contrib/epee/include/net/http_server_impl_base.h6
-rw-r--r--contrib/epee/include/net/net_helper.h8
-rw-r--r--contrib/epee/include/net/net_ssl.h5
-rw-r--r--contrib/epee/src/hex.cpp63
-rw-r--r--contrib/epee/src/net_ssl.cpp82
-rw-r--r--src/rpc/core_rpc_server.cpp12
-rw-r--r--src/rpc/core_rpc_server.h1
-rw-r--r--src/wallet/wallet2.cpp12
-rw-r--r--src/wallet/wallet2.h3
-rw-r--r--src/wallet/wallet_rpc_server.cpp7
-rw-r--r--tests/unit_tests/epee_utils.cpp19
15 files changed, 200 insertions, 49 deletions
diff --git a/contrib/epee/include/hex.h b/contrib/epee/include/hex.h
index 901c666a9..250432173 100644
--- a/contrib/epee/include/hex.h
+++ b/contrib/epee/include/hex.h
@@ -32,6 +32,7 @@
#include <cstdint>
#include <iosfwd>
#include <string>
+#include <boost/utility/string_ref.hpp>
#include "wipeable_string.h"
#include "span.h"
@@ -68,4 +69,10 @@ namespace epee
//! 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;
};
+
+ struct from_hex
+ {
+ //! \return An std::vector of unsigned integers from the `src`
+ static std::vector<uint8_t> vector(boost::string_ref src);
+ };
}
diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h
index 76773192e..ec08c0f4b 100644
--- a/contrib/epee/include/net/abstract_tcp_server2.h
+++ b/contrib/epee/include/net/abstract_tcp_server2.h
@@ -228,8 +228,8 @@ namespace net_utils
std::map<std::string, t_connection_type> 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(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(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 = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, bool allow_any_cert = false);
/// 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());
diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl
index 39d6911e3..f5548c585 100644
--- a/contrib/epee/include/net/abstract_tcp_server2.inl
+++ b/contrib/epee/include/net/abstract_tcp_server2.inl
@@ -900,7 +900,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
m_thread_index(0),
m_connection_type( connection_type ),
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();
m_thread_name_prefix = "NET";
@@ -939,14 +939,14 @@ PRAGMA_WARNING_DISABLE_VS(4355)
}
//---------------------------------------------------------------------------------
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();
m_stop_signal_sent = false;
m_port = port;
m_address = address;
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).
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);
@@ -980,7 +980,7 @@ PRAGMA_WARNING_DISABLE_VS(4355)
PUSH_WARNINGS
DISABLE_GCC_WARNING(maybe-uninitialized)
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;
@@ -988,7 +988,7 @@ DISABLE_GCC_WARNING(maybe-uninitialized)
MERROR("Failed to convert port no = " << port);
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
//---------------------------------------------------------------------------------
diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h
index 34b3ac06c..58a8e6888 100644
--- a/contrib/epee/include/net/http_client.h
+++ b/contrib/epee/include/net/http_client.h
@@ -278,6 +278,7 @@ namespace net_utils
epee::net_utils::ssl_support_t m_ssl_support;
std::pair<std::string, std::string> m_ssl_private_key_and_certificate_path;
std::list<std::string> m_ssl_allowed_certificates;
+ std::vector<std::vector<uint8_t>> m_ssl_allowed_fingerprints;
bool m_ssl_allow_any_cert;
public:
@@ -302,16 +303,16 @@ namespace net_utils
const std::string &get_host() const { return m_host_buff; };
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{};
const bool r = parse_url(address, parsed);
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;
}
- 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);
disconnect();
@@ -321,8 +322,9 @@ namespace net_utils
m_ssl_support = ssl_support;
m_ssl_private_key_and_certificate_path = private_key_and_certificate_path;
m_ssl_allowed_certificates = allowed_ssl_certificates;
+ m_ssl_allowed_fingerprints = allowed_ssl_fingerprints;
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)
diff --git a/contrib/epee/include/net/http_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h
index 236067580..0922f21f2 100644
--- a/contrib/epee/include/net/http_server_impl_base.h
+++ b/contrib/epee/include/net/http_server_impl_base.h
@@ -61,7 +61,9 @@ namespace epee
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,
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
@@ -78,7 +80,7 @@ namespace epee
m_net_server.get_config_object().m_user = std::move(user);
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)
{
LOG_ERROR("Failed to bind server");
diff --git a/contrib/epee/include/net/net_helper.h b/contrib/epee/include/net/net_helper.h
index 5d9bb61cf..742cf916e 100644
--- a/contrib/epee/include/net/net_helper.h
+++ b/contrib/epee/include/net/net_helper.h
@@ -93,7 +93,7 @@ namespace net_utils
m_deadline(m_io_service),
m_shutdowned(0),
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))
{
@@ -118,12 +118,12 @@ namespace net_utils
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)
- m_ctx = {boost::asio::ssl::context(boost::asio::ssl::context::sslv23), {}};
+ m_ctx = {boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}, {}};
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;
}
diff --git a/contrib/epee/include/net/net_ssl.h b/contrib/epee/include/net/net_ssl.h
index 9ae1883af..ca53d3667 100644
--- a/contrib/epee/include/net/net_ssl.h
+++ b/contrib/epee/include/net/net_ssl.h
@@ -50,16 +50,17 @@ namespace net_utils
{
boost::asio::ssl::context context;
std::list<std::string> allowed_certificates;
+ std::vector<std::vector<uint8_t>> allowed_fingerprints;
bool allow_any_cert;
};
// https://security.stackexchange.com/questions/34780/checking-client-hello-for-https-classification
constexpr size_t get_ssl_magic_size() { return 9; }
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);
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_support_from_string(ssl_support_t &ssl, boost::string_ref s);
}
diff --git a/contrib/epee/src/hex.cpp b/contrib/epee/src/hex.cpp
index 5c8acc8be..8421dcae9 100644
--- a/contrib/epee/src/hex.cpp
+++ b/contrib/epee/src/hex.cpp
@@ -83,4 +83,67 @@ namespace epee
{
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;
+ }
}
diff --git a/contrib/epee/src/net_ssl.cpp b/contrib/epee/src/net_ssl.cpp
index 941799078..9a62dd3c2 100644
--- a/contrib/epee/src/net_ssl.cpp
+++ b/contrib/epee/src/net_ssl.cpp
@@ -154,13 +154,19 @@ bool create_ssl_certificate(std::string &pkey_buffer, std::string &cert_buffer)
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
- ssl_context.context.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2);
- ssl_context.context.set_default_verify_paths();
+ // only allow tls v1.2 and up
+ ssl_context.context.set_options(boost::asio::ssl::context::default_workarounds);
+ 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
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
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#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");
if (private_key_and_certificate_path.second.empty())
@@ -225,7 +231,7 @@ bool is_ssl(const unsigned char *data, size_t len)
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();
if (!sctx)
@@ -240,23 +246,51 @@ bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const std::li
return false;
}
- BIO *bio_cert = BIO_new(BIO_s_mem());
- openssl_bio bio_cert_deleter{bio_cert};
- bool success = PEM_write_bio_X509(bio_cert, cert);
- if (!success)
- {
- MERROR("Failed to print certificate");
- 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;
}
- BUF_MEM *buf = NULL;
- BIO_get_mem_ptr(bio_cert, &buf);
- if (!buf || !buf->data || !buf->length)
- {
- MERROR("Failed to write certificate: " << ERR_get_error());
- return false;
+
+ if (!ssl_context.allowed_certificates.empty()) {
+ BIO *bio_cert = BIO_new(BIO_s_mem());
+ bool success = PEM_write_bio_X509(bio_cert, cert);
+ if (!success)
+ {
+ BIO_free(bio_cert);
+ MERROR("Failed to print certificate");
+ return false;
+ }
+ BUF_MEM *buf = NULL;
+ BIO_get_mem_ptr(bio_cert, &buf);
+ if (!buf || !buf->data || !buf->length)
+ {
+ BIO_free(bio_cert);
+ MERROR("Failed to write certificate: " << ERR_get_error());
+ return false;
+ }
+ std::string certificate(std::string(buf->data, buf->length));
+ 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;
}
- std::string certificate(std::string(buf->data, buf->length));
- return std::find(allowed_certificates.begin(), allowed_certificates.end(), certificate) != allowed_certificates.end();
+
+ // 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)
@@ -276,7 +310,7 @@ bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socke
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");
return false;
@@ -289,7 +323,7 @@ bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socke
socket.handshake(type, ec);
if (ec)
{
- MERROR("handshake failed, connection dropped");
+ MERROR("handshake failed, connection dropped: " << ec.message());
return false;
}
if (!ssl_context.allow_any_cert && !verified)
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index d18774149..fa27c259d 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -80,6 +80,7 @@ namespace cryptonote
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_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_bootstrap_daemon_address);
command_line::add_arg(desc, arg_bootstrap_daemon_login);
@@ -156,12 +157,16 @@ namespace cryptonote
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);
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(
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)"
};
+ 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 = {
"rpc-ssl-allow-any-cert"
, "Allow any peer certificate, rather than just those on the allowed list"
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index da1907af2..54fce3cd9 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -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_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_fingerprints;
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_login;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index a476ecdc5..085e6075f 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -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_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_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> 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};
@@ -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_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_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 = command_line::get_arg(vm, opts.daemon_ssl);
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));
- 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);
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_certificate);
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.testnet);
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);
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_login = std::move(daemon_login);
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
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index ea1172f40..b7ebe03d9 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -680,7 +680,8 @@ namespace tools
bool trusted_daemon = true,
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 = {}, 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(); }
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 110777c4f..90afa91fd 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -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_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_fingerprints = {"rpc-ssl-allowed-fingerprints", tools::wallet2::tr("List of certificate fingerprints to allow")};
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_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_fingerprints = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints);
auto rpc_ssl = command_line::get_arg(vm, arg_rpc_ssl);
epee::net_utils::ssl_support_t rpc_ssl_support;
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");
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(
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)
);
}
//------------------------------------------------------------------------------------------------------------------------------
diff --git a/tests/unit_tests/epee_utils.cpp b/tests/unit_tests/epee_utils.cpp
index 18fb262c2..9a32d149a 100644
--- a/tests/unit_tests/epee_utils.cpp
+++ b/tests/unit_tests/epee_utils.cpp
@@ -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)
{
EXPECT_EQ(