From 4054ec6af0214dcc4e9b2d62f66771ccc720d76d Mon Sep 17 00:00:00 2001 From: Seva Date: Fri, 2 Aug 2019 12:33:07 -0400 Subject: [PATCH] opendht: add ssl to build system http: enforce ssl everywhere --- CMakeLists.txt | 25 +++++++++++-- cmake/FindCrypto.cmake | 21 +++++++++++ cmake/FindOpenSSL.cmake | 22 ++++++++++++ include/opendht/http.h | 33 +++++++++++++++-- src/http.cpp | 78 ++++++++++++++++++++++++++++++++--------- 5 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 cmake/FindCrypto.cmake create mode 100644 cmake/FindOpenSSL.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b1a37568..a0a98f04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ option (OPENDHT_PROXY_SERVER "Enable DHT proxy server, use Restinio and jsoncpp" option (OPENDHT_PUSH_NOTIFICATIONS "Enable push notifications support" OFF) option (OPENDHT_PROXY_SERVER_IDENTITY "Allow clients to use the node identity" OFF) option (OPENDHT_PROXY_CLIENT "Enable DHT proxy client, use Restinio and jsoncpp" OFF) +option (OPENDHT_PROXY_OPENSSL "Build DHT proxy with OpenSSL" ON) option (OPENDHT_INDEX "Build DHT indexation feature" OFF) option (OPENDHT_TESTS "Add unit tests executable" OFF) @@ -68,6 +69,22 @@ if (OPENDHT_PROXY_SERVER OR OPENDHT_PROXY_CLIENT) if (NOT Jsoncpp_FOUND) message(SEND_ERROR "Jsoncpp is required for DHT proxy support") endif() + if (OPENDHT_PROXY_OPENSSL) + find_package(OpenSSL REQUIRED) + if (NOT OpenSSL_FOUND) + message(SEND_ERROR "OpenSSL is required for DHT proxy as specified") + else () + message("OpenSSL found") + add_library(ssl SHARED IMPORTED) + add_definitions(-DOPENDHT_PROXY_OPENSSL) + find_package(Crypto REQUIRED) + if (NOT Crypto_FOUND) + message(SEND_ERROR "libcrypto is required for openssl") + else () + add_library(crypto SHARED IMPORTED) + endif() + endif() + endif() endif() # Build flags @@ -112,6 +129,9 @@ endif () if (Jsoncpp_INCLUDE_DIRS) include_directories (SYSTEM "${Jsoncpp_INCLUDE_DIRS}") endif () +if (OpenSSL_INCLUDE_DIR) + include_directories (SYSTEM "${OpenSSL_INCLUDE_DIR}") +endif () link_directories (${Nettle_LIBRARY_DIRS}) link_directories (${Jsoncpp_LIBRARY_DIRS}) include_directories ( @@ -267,7 +287,8 @@ if (OPENDHT_STATIC) target_link_libraries(opendht-static PRIVATE ${argon2_LIBRARIES} PUBLIC ${CMAKE_THREAD_LIBS_INIT} ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} - ${Jsoncpp_LIBRARIES} ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY}) + ${Jsoncpp_LIBRARIES} ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY} + ${Crypto_LIBRARIES} ${OpenSSL_LIBRARY}) install (TARGETS opendht-static DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht) endif () @@ -288,7 +309,7 @@ if (OPENDHT_SHARED) target_link_libraries(opendht PUBLIC ${CMAKE_THREAD_LIBS_INIT} PRIVATE ${GNUTLS_LIBRARIES} ${Nettle_LIBRARIES} - ${Jsoncpp_LIBRARIES} + ${Jsoncpp_LIBRARIES} ${Crypto_LIBRARIES} ${OpenSSL_LIBRARY} ${FMT_LIBRARY} ${HTTP_PARSER_LIBRARY}) install (TARGETS opendht DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT opendht) diff --git a/cmake/FindCrypto.cmake b/cmake/FindCrypto.cmake new file mode 100644 index 00000000..51c6f6c2 --- /dev/null +++ b/cmake/FindCrypto.cmake @@ -0,0 +1,21 @@ +if (NOT Crypto_FOUND) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_search_module(Crypto REQUIRED libcrypto) + endif() + find_library (Crypto_LIBRARY + NAMES crypto + HINTS ${Crypto_ROOT_DIR} + ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 + PATHS /usr/local/lib + /usr/lib + ) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Crypto DEFAULT_MSG Crypto_LIBRARY) + if (Crypto_LIBRARY) + set(Crypto_LIBRARIES ${Crypto_LIBRARY}) + message("Found libcrypto library: " ${Crypto_LIBRARIES}) + set(Crypto_FOUND TRUE) + endif() +endif() diff --git a/cmake/FindOpenSSL.cmake b/cmake/FindOpenSSL.cmake new file mode 100644 index 00000000..d5f939af --- /dev/null +++ b/cmake/FindOpenSSL.cmake @@ -0,0 +1,22 @@ +if (NOT OpenSSL_FOUND) + find_path (OpenSSL_INCLUDE_DIR openssl + HINTS + "/usr/include" + "/usr/local/include" + "/opt/local/include") + find_library (OpenSSL_LIBRARY + NAMES ssl openssl + HINTS ${OpenSSL_ROOT_DIR} + ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 + PATHS /usr/local/lib + /usr/lib + ) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(OpenSSL DEFAULT_MSG OpenSSL_INCLUDE_DIR OpenSSL_LIBRARY) + if (OpenSSL_INCLUDE_DIR AND OpenSSL_LIBRARY) + message("Found OpenSSL library: " ${OpenSSL_LIBRARY}) + message("Found OpenSSL includes: " ${OpenSSL_INCLUDE_DIR}) + set(OpenSSL_FOUND TRUE) + endif() +endif() diff --git a/include/opendht/http.h b/include/opendht/http.h index 743ae561..d141cdb5 100644 --- a/include/opendht/http.h +++ b/include/opendht/http.h @@ -19,9 +19,13 @@ #pragma once #include +#ifdef OPENDHT_PROXY_OPENSSL +#include "asio/ssl.hpp" +#endif #include #include #include +#include #include #include #include @@ -30,10 +34,22 @@ namespace http { using HandlerCb = std::function; +#ifdef OPENDHT_PROXY_OPENSSL +using socket_t = restinio::impl::tls_socket_t; +//using socket_t = asio::ssl::stream; +#else +using socket_t = asio::ip::tcp::socket; +#endif + class OPENDHT_PUBLIC Connection { public: - Connection(asio::io_context& ctx, std::shared_ptr logger = {}); +#ifdef OPENDHT_PROXY_OPENSSL + Connection(asio::io_context& ctx, asio::ssl::context_base::method&& ssl_method, + std::shared_ptr l = {}); +#else + Connection(asio::io_context& ctx, std::shared_ptr l = {}); +#endif ~Connection(); unsigned int id(); @@ -48,7 +64,11 @@ public: std::string read_bytes(const size_t bytes); std::string read_until(const char delim); - asio::ip::tcp::socket& socket(); +#ifdef OPENDHT_PROXY_OPENSSL + socket_t& get_socket(); +#else + socket_t& get_socket(); +#endif void timeout(const std::chrono::seconds timeout, HandlerCb cb = {}); @@ -58,7 +78,13 @@ private: unsigned int id_; static unsigned int ids_; - asio::ip::tcp::socket socket_; + asio::io_context& ctx_; + std::unique_ptr socket_; +#ifdef OPENDHT_PROXY_OPENSSL + std::unique_ptr ssl_ctx_; + asio::ssl::context::options ssl_options_; +#endif + asio::ip::tcp::endpoint endpoint_; asio::streambuf write_buf_; @@ -174,6 +200,7 @@ public: ~Request(); unsigned int id() const; + void set_connection(std::shared_ptr connection); std::shared_ptr get_connection() const; void set_logger(std::shared_ptr logger); diff --git a/src/http.cpp b/src/http.cpp index 811a7e3d..dad5cc9d 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -32,13 +32,31 @@ constexpr char JSON_VALUE_DELIM = '\n'; unsigned int Connection::ids_ = 1; -Connection::Connection(asio::io_context& ctx, std::shared_ptr logger): - id_(Connection::ids_++), socket_(ctx), logger_(logger) +#ifdef OPENDHT_PROXY_OPENSSL +Connection::Connection(asio::io_context& ctx, asio::ssl::context_base::method&& ssl_method, + std::shared_ptr l) + : id_(Connection::ids_++), ctx_(ctx), logger_(l) +{ + ssl_ctx_ = std::make_unique(std::move(ssl_method)); + ssl_ctx_->set_default_verify_paths(); + ssl_ctx_->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert); + /* + ssl_ctx_->use_certificate_chain_file(chain_file); + ssl_ctx_->use_private_key_file(key_file, asio::ssl::context::pem); + get_socket().set_verify_callback(asio::ssl::rfc2818_verification(host_name)); + socket_ = std::make_unique(ctx_, ssl_ctx_.get()); + */ +} +#else +Connection::Connection(asio::io_context& ctx, std::shared_ptr l) + : id_(Connection::ids_++), ctx_(ctx), socket_(new socket_t(ctx)), logger_(l) {} +#endif Connection::~Connection() { close(); + socket_.reset(); } unsigned int @@ -50,7 +68,7 @@ Connection::id() bool Connection::is_open() { - return socket_.is_open(); + return get_socket().is_open(); } bool @@ -94,11 +112,19 @@ Connection::read_until(const char delim) return content; } -asio::ip::tcp::socket& -Connection::socket() +#ifdef OPENDHT_PROXY_OPENSSL +socket_t& +Connection::get_socket() { - return socket_; + return *socket_; } +#else +socket_t& +Connection::get_socket() +{ + return *socket_; +} +#endif void Connection::timeout(const std::chrono::seconds timeout, HandlerCb cb) @@ -109,7 +135,7 @@ Connection::timeout(const std::chrono::seconds timeout, HandlerCb cb) return; } if (!timeout_timer_) - timeout_timer_ = std::make_unique(socket_.get_io_context()); + timeout_timer_ = std::make_unique(ctx_); timeout_timer_->expires_at(std::chrono::steady_clock::now() + timeout); timeout_timer_->async_wait([this, cb](const asio::error_code &ec){ if (ec == asio::error::operation_aborted) @@ -126,7 +152,7 @@ Connection::timeout(const std::chrono::seconds timeout, HandlerCb cb) void Connection::close() { - socket_.close(); + get_socket().close(); } // connection listener @@ -310,6 +336,12 @@ Request::id() const return id_; } +void +Request::set_connection(std::shared_ptr connection) +{ + conn_ = connection; +} + std::shared_ptr Request::get_connection() const { @@ -530,11 +562,23 @@ Request::connect(std::vector&& endpoints, HandlerCb cb) eps.append(endpoint.address().to_string() + " "); logger_->d("[http:request:%i] [connect] begin endpoints { %s}", id_, eps.c_str()); } - conn_ = std::make_shared(ctx_); +#ifdef OPENDHT_PROXY_OPENSSL + if (service_ == "https" or service_ == "443"){ + if (!conn_){ + logger_->e("[http:request:%i] [connect] undefined SSL connection", id_); + terminate(asio::error::connection_aborted); + } + } + conn_ = std::make_shared(ctx_, asio::ssl::context::sslv23); + asio::async_connect(conn_->get_socket().lowest_layer(), +#else + conn_ = std::make_shared(ctx_); + asio::async_connect(conn_->get_socket(), +#endif + std::move(endpoints), [this, cb] + (const asio::error_code& ec, const asio::ip::tcp::endpoint& endpoint){ // try to connect to any until one works - asio::async_connect(conn_->socket(), std::move(endpoints), [this, cb] - (const asio::error_code& ec, const asio::ip::tcp::endpoint& endpoint){ if (ec == asio::error::operation_aborted) return; else if (ec){ @@ -600,7 +644,7 @@ Request::post() // send the request notify_state_change(State::SENDING); - asio::async_write(conn_->socket(), conn_->input(), + asio::async_write(conn_->get_socket(), conn_->input(), std::bind(&Request::handle_request, this, std::placeholders::_1)); } @@ -651,7 +695,7 @@ Request::handle_request(const asio::error_code& ec) // read response notify_state_change(State::RECEIVING); - asio::async_read_until(conn_->socket(), conn_->data(), HTTP_HEADER_DELIM, + asio::async_read_until(conn_->get_socket(), conn_->data(), HTTP_HEADER_DELIM, std::bind(&Request::handle_response_header, this, std::placeholders::_1)); } @@ -704,7 +748,7 @@ Request::handle_response_header(const asio::error_code& ec) terminate(asio::error::eof); } else { // more chunks to come (don't add the missing \n from std::getline) - asio::async_read(conn_->socket(), conn_->data(), + asio::async_read(conn_->get_socket(), conn_->data(), asio::transfer_exactly(content_length - response_.body.size()), std::bind(&Request::handle_response_body, this, std::placeholders::_1, std::placeholders::_2)); } @@ -712,7 +756,7 @@ Request::handle_response_header(const asio::error_code& ec) // server wants to keep sending or we have content-length defined else if (response_.headers[HTTP_HEADER_CONNECTION] == HTTP_HEADER_CONNECTION_KEEP_ALIVE) { - asio::async_read_until(conn_->socket(), conn_->data(), JSON_VALUE_DELIM, + asio::async_read_until(conn_->get_socket(), conn_->data(), JSON_VALUE_DELIM, std::bind(&Request::handle_response_body, this, std::placeholders::_1, std::placeholders::_2)); } else if (connection_type_ == restinio::http_connection_header_t::close) @@ -767,12 +811,12 @@ Request::handle_response_body(const asio::error_code& ec, const size_t bytes) } // has content-length else if (content_length_it != response_.headers.end() and response_.body.size() != content_length) - asio::async_read(conn_->socket(), conn_->data(), + asio::async_read(conn_->get_socket(), conn_->data(), asio::transfer_exactly(content_length - response_.body.size()), std::bind(&Request::handle_response_body, this, std::placeholders::_1, std::placeholders::_2)); // server wants to keep sending else if (response_.headers[HTTP_HEADER_CONNECTION] == HTTP_HEADER_CONNECTION_KEEP_ALIVE){ - asio::async_read_until(conn_->socket(), conn_->data(), JSON_VALUE_DELIM, + asio::async_read_until(conn_->get_socket(), conn_->data(), JSON_VALUE_DELIM, std::bind(&Request::handle_response_body, this, std::placeholders::_1, std::placeholders::_2)); } else if (connection_type_ == restinio::http_connection_header_t::close)