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 #include #include +#include #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 src) noexcept; }; + + struct from_hex + { + //! \return An std::vector of unsigned integers from the `src` + static std::vector 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 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 &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list &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 &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list &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 &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list &allowed_certificates = {}, const std::vector> &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 &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list &allowed_certificates = {}, const std::vector> &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 - bool boosted_tcp_server::init_server(uint32_t port, const std::string address, epee::net_utils::ssl_support_t ssl_support, const std::pair &private_key_and_certificate_path, const std::list &allowed_certificates, bool allow_any_cert) + bool boosted_tcp_server::init_server(uint32_t port, const std::string address, epee::net_utils::ssl_support_t ssl_support, const std::pair &private_key_and_certificate_path, const std::list &allowed_certificates, const std::vector> &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(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 - bool boosted_tcp_server::init_server(const std::string port, const std::string& address, epee::net_utils::ssl_support_t ssl_support, const std::pair &private_key_and_certificate_path, const std::list &allowed_certificates, bool allow_any_cert) + bool boosted_tcp_server::init_server(const std::string port, const std::string& address, epee::net_utils::ssl_support_t ssl_support, const std::pair &private_key_and_certificate_path, const std::list &allowed_certificates, const std::vector> &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 m_ssl_private_key_and_certificate_path; std::list m_ssl_allowed_certificates; + std::vector> 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 user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair &private_key_and_certificate_path = {}, const std::list &allowed_ssl_certificates = {}, bool allow_any_cert = false) + bool set_server(const std::string& address, boost::optional user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair &private_key_and_certificate_path = {}, const std::list &allowed_ssl_certificates = {}, const std::vector> &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 user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair &private_key_and_certificate_path = {}, const std::list &allowed_ssl_certificates = {}, bool allow_any_cert = false) + void set_server(std::string host, std::string port, boost::optional user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair &private_key_and_certificate_path = {}, const std::list &allowed_ssl_certificates = {}, const std::vector> &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 user = boost::none, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair &private_key_and_certificate_path = {}, - const std::list &allowed_certificates = std::list(), bool allow_any_cert = false) + std::list allowed_certificates = {}, + std::vector> 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(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 &private_key_and_certificate_path = {}, const std::list &allowed_certificates = std::list(), 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 &private_key_and_certificate_path = {}, std::list allowed_certificates = {}, std::vector> 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 allowed_certificates; + std::vector> 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 &private_key_and_certificate_path, std::list allowed_certificates, bool allow_any_cert); + ssl_context_t create_ssl_context(const std::pair &private_key_and_certificate_path, std::list allowed_certificates, std::vector> allowed_fingerprints, bool allow_any_cert); void use_ssl_certificate(ssl_context_t &ssl_context, const std::pair &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 &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 &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 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 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 &private_key_and_certificate_path, std::list allowed_certificates, bool allow_any_cert) +ssl_context_t create_ssl_context(const std::pair &private_key_and_certificate_path, std::list allowed_certificates, std::vector> 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 &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 &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 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 &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 &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 &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 ssl_allowed_fingerprint_strings = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); + std::vector> 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::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> core_rpc_server::arg_rpc_ssl_allowed_fingerprints = { + "rpc-ssl-allowed-fingerprints" + , "List of certificate fingerprints to allow" + }; + const command_line::arg_descriptor 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 arg_rpc_ssl_private_key; static const command_line::arg_descriptor arg_rpc_ssl_certificate; static const command_line::arg_descriptor> arg_rpc_ssl_allowed_certificates; + static const command_line::arg_descriptor> arg_rpc_ssl_allowed_fingerprints; static const command_line::arg_descriptor arg_rpc_ssl_allow_any_cert; static const command_line::arg_descriptor arg_bootstrap_daemon_address; static const command_line::arg_descriptor arg_bootstrap_daemon_login; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f2c0a7ba9..99b07a2b0 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -241,6 +241,7 @@ struct options { const command_line::arg_descriptor daemon_ssl_private_key = {"daemon-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor daemon_ssl_certificate = {"daemon-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; const command_line::arg_descriptor> 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> daemon_ssl_allowed_fingerprints = {"daemon-ssl-allowed-fingerprints", tools::wallet2::tr("List of valid fingerprints of allowed RPC servers")}; const command_line::arg_descriptor 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 testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false}; @@ -316,6 +317,7 @@ std::unique_ptr 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 make_basic(const boost::program_options::variabl } } + std::vector> 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 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::make_dummy(const boost::program_options::varia } //---------------------------------------------------------------------------------------------------- -bool wallet2::init(std::string daemon_address, boost::optional daemon_login, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair &private_key_and_certificate_path, const std::list &allowed_certificates, bool allow_any_cert) +bool wallet2::init(std::string daemon_address, boost::optional daemon_login, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair &private_key_and_certificate_path, const std::list &allowed_certificates, const std::vector> &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 &private_key_and_certificate_path = {}, - const std::list &allowed_certificates = {}, bool allow_any_cert = false); + const std::list &allowed_certificates = {}, const std::vector> &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 9adfb06c8..43b074c69 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 arg_rpc_ssl_private_key = {"rpc-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor arg_rpc_ssl_certificate = {"rpc-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; const command_line::arg_descriptor> 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> 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> 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::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 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(