diff --git a/Release/CMakeLists.txt b/Release/CMakeLists.txt index 5bf68a5e01..fa47306d98 100644 --- a/Release/CMakeLists.txt +++ b/Release/CMakeLists.txt @@ -15,6 +15,16 @@ set(CPPREST_VERSION_REVISION 12) enable_testing() +set(CPPREST_NO_OPENSSL OFF CACHE BOOL "Exclude OpenSSL. Implies CPPREST_EXCLUDE_WEBSOCKETS and CPPREST_EXCLUDE_LISTENER.") +set(CPPREST_EXCLUDE_LISTENER OFF CACHE BOOL "Exclude listerner functionality.") + +if(CPPREST_NO_OPENSSL) + message(STATUS "OpenSSL disabled; disabling websockets and http listener.") + set(CPPREST_HTTP_LISTENER_IMPL none CACHE STRING "Internal use.") + set(CPPREST_EXCLUDE_WEBSOCKETS ON CACHE BOOL "Exclude websockets functionality.") + set(BUILD_SAMPLES OFF CACHE BOOL "Build sample applications.") +endif() + set(WERROR ON CACHE BOOL "Treat Warnings as Errors.") set(CPPREST_EXCLUDE_WEBSOCKETS OFF CACHE BOOL "Exclude websockets functionality.") set(CPPREST_EXCLUDE_COMPRESSION OFF CACHE BOOL "Exclude compression functionality.") @@ -61,8 +71,12 @@ endif() include(cmake/cpprest_find_boost.cmake) include(cmake/cpprest_find_zlib.cmake) -include(cmake/cpprest_find_openssl.cmake) -include(cmake/cpprest_find_websocketpp.cmake) +if(CPPREST_NO_OPENSSL) + include(cmake/cpprest_find_botan.cmake) +else() + include(cmake/cpprest_find_openssl.cmake) + include(cmake/cpprest_find_websocketpp.cmake) +endif() include(cmake/cpprest_find_brotli.cmake) include(CheckIncludeFiles) include(GNUInstallDirs) diff --git a/Release/cmake/cpprest_find_botan.cmake b/Release/cmake/cpprest_find_botan.cmake new file mode 100644 index 0000000000..257a4fa9fc --- /dev/null +++ b/Release/cmake/cpprest_find_botan.cmake @@ -0,0 +1,90 @@ +function(cpprest_find_botan) + if(TARGET cpprestsdk_botan_internal) + return() + endif() + + INCLUDE (FindPackageHandleStandardArgs) + + SET (_BOTAN_POSSIBLE_DIRS ${BOTAN_ROOT_DIR}) + + IF (WIN32) + SET (_BOTAN_POSSIBLE_DIRS ${_BOTAN_POSSIBLE_DIRS} "C:\\usr\\local" "C:\\usr") + ENDIF (WIN32) + + # Note: after 1.11.34, botan library moved to semantic versioning (a.k.a. 2.0) + SET (_BOTAN_POSSIBLE_INCLUDE_SUFFIXES include include/botan-1.10 include/botan-1.11 include/botan-2.0 include/botan-2) + SET (_BOTAN_POSSIBLE_LIB_SUFFIXES lib) + + FIND_PATH (BOTAN_ROOT_DIR + NAMES botan/botan.h + PATHS ${_BOTAN_POSSIBLE_DIRS} + PATH_SUFFIXES ${_BOTAN_POSSIBLE_INCLUDE_SUFFIXES} + DOC "Botan root directory") + + FIND_PATH (BOTAN_INCLUDE_DIR + NAMES botan/botan.h + PATHS ${BOTAN_ROOT_DIR} + PATH_SUFFIXES ${_BOTAN_POSSIBLE_INCLUDE_SUFFIXES} + DOC "Botan include directory") + + FIND_LIBRARY (BOTAN_LIBRARY_DEBUG + NAMES botand botand-1.10 botand-1.11 botand-2 + PATHS ${_BOTAN_POSSIBLE_DIRS} + PATH_SUFFIXES ${_BOTAN_POSSIBLE_LIB_SUFFIXES} + DOC "Botan debug library") + + FIND_LIBRARY (BOTAN_LIBRARY_RELEASE + NAMES botan botan-1.10 botan-1.11 botan-2 + PATHS ${_BOTAN_POSSIBLE_DIRS} + PATH_SUFFIXES ${_BOTAN_POSSIBLE_LIB_SUFFIXES} + DOC "Botan release library") + + IF (NOT DEFINED BOTAN_LIBRARIES) + IF (BOTAN_LIBRARY_DEBUG AND BOTAN_LIBRARY_RELEASE) + SET (BOTAN_LIBRARIES + optimized ${BOTAN_LIBRARY_RELEASE} + debug ${BOTAN_LIBRARY_DEBUG}) + ELSEIF (BOTAN_LIBRARY_RELEASE) + SET (BOTAN_LIBRARIES ${BOTAN_LIBRARY_RELEASE}) + ENDIF (BOTAN_LIBRARY_DEBUG AND BOTAN_LIBRARY_RELEASE) + ENDIF (NOT DEFINED BOTAN_LIBRARIES) + + IF (BOTAN_INCLUDE_DIR) + SET (BOTAN_INCLUDE_DIRS ${BOTAN_INCLUDE_DIR}) + + SET (_BOTAN_VERSION_HEADER ${BOTAN_INCLUDE_DIR}/botan/build.h) + + IF (EXISTS ${_BOTAN_VERSION_HEADER}) + FILE (STRINGS ${_BOTAN_VERSION_HEADER} _BOTAN_VERSION_TMP REGEX + "#define BOTAN_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+") + + STRING (REGEX REPLACE + ".*#define BOTAN_VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" BOTAN_VERSION_MAJOR + ${_BOTAN_VERSION_TMP}) + STRING (REGEX REPLACE + ".*#define BOTAN_VERSION_MINOR[ \t]+([0-9]+).*" "\\1" BOTAN_VERSION_MINOR + ${_BOTAN_VERSION_TMP}) + STRING (REGEX REPLACE + ".*#define BOTAN_VERSION_PATCH[ \t]+([0-9]+).*" "\\1" BOTAN_VERSION_PATCH + ${_BOTAN_VERSION_TMP}) + + SET (BOTAN_VERSION_COUNT 3) + SET (BOTAN_VERSION + ${BOTAN_VERSION_MAJOR}.${BOTAN_VERSION_MINOR}.${BOTAN_VERSION_PATCH}) + ENDIF (EXISTS ${_BOTAN_VERSION_HEADER}) + ENDIF (BOTAN_INCLUDE_DIR) + + MARK_AS_ADVANCED (BOTAN_ROOT_DIR BOTAN_INCLUDE_DIR BOTAN_LIBRARY_DEBUG + BOTAN_LIBRARY_RELEASE) + + FIND_PACKAGE_HANDLE_STANDARD_ARGS (Botan REQUIRED_VARS BOTAN_INCLUDE_DIRS + BOTAN_LIBRARIES VERSION_VAR BOTAN_VERSION) + + add_library(Botan INTERFACE) + target_include_directories(Botan INTERFACE ${BOTAN_INCLUDE_DIR}) + target_link_libraries(Botan INTERFACE ${BOTAN_LIBRARIES}) + + add_library(cpprestsdk_botan_internal INTERFACE) + target_link_libraries(cpprestsdk_botan_internal INTERFACE "$") + target_include_directories(cpprestsdk_botan_internal INTERFACE "$") +endfunction() diff --git a/Release/include/cpprest/http_client.h b/Release/include/cpprest/http_client.h index 743e2b5635..eb9211c157 100644 --- a/Release/include/cpprest/http_client.h +++ b/Release/include/cpprest/http_client.h @@ -61,6 +61,11 @@ typedef void* native_handle; #include "cpprest/oauth2.h" #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) + +#if defined(CPPREST_BOTAN_SSL) +#include +#else // CPPREST_BOTAN_SSL + #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" @@ -69,6 +74,9 @@ typedef void* native_handle; #if defined(__clang__) #pragma clang diagnostic pop #endif + +#endif // CPPREST_BOTAN_SSL + #endif /// The web namespace contains functionality common to multiple protocols like HTTP and WebSockets. @@ -86,6 +94,12 @@ namespace client using web::credentials; using web::web_proxy; +#if defined(CPPREST_BOTAN_SSL) +using ssl_context_t = Botan::TLS::Context; +#else +using ssl_context_t = boost::asio::ssl::context; +#endif + /// /// HTTP client configuration class, used to set the possible configuration options /// used to create an http_client instance. @@ -334,7 +348,7 @@ class http_client_config /// /// A user callback allowing for customization of the ssl context at construction /// time. - void set_ssl_context_callback(const std::function& callback) + void set_ssl_context_callback(const std::function& callback) { m_ssl_context_callback = callback; } @@ -342,7 +356,7 @@ class http_client_config /// /// Gets the user's callback to allow for customization of the ssl context. /// - const std::function& get_ssl_context_callback() const + const std::function& get_ssl_context_callback() const { return m_ssl_context_callback; } @@ -386,7 +400,7 @@ class http_client_config std::function m_set_user_nativesessionhandle_options; #if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_CLIENT_ASIO) - std::function m_ssl_context_callback; + std::function m_ssl_context_callback; bool m_tlsext_sni_enabled; #endif #if defined(_WIN32) && !defined(__cplusplus_winrt) @@ -402,6 +416,8 @@ class http_pipeline; class http_client { public: + using ssl_context = ssl_context_t; + /// /// Creates a new http_client connected to specified uri. /// diff --git a/Release/src/CMakeLists.txt b/Release/src/CMakeLists.txt index 89d2bc55c3..2919324214 100644 --- a/Release/src/CMakeLists.txt +++ b/Release/src/CMakeLists.txt @@ -127,10 +127,18 @@ endif() # Http client component if(CPPREST_HTTP_CLIENT_IMPL STREQUAL "asio") cpprest_find_boost() - cpprest_find_openssl() - target_compile_definitions(cpprest PUBLIC -DCPPREST_FORCE_HTTP_CLIENT_ASIO) + if(CPPREST_NO_OPENSSL) + cpprest_find_botan() + set(CPPREST_BOTAN_SSL_FLAG "-DCPPREST_BOTAN_SSL") + set(CPPREST_SSL_LINK_LIB cpprestsdk_botan_internal) + else() + cpprest_find_openssl() + set(CPPREST_BOTAN_SSL_FLAG "") + set(CPPREST_SSL_LINK_LIB cpprestsdk_openssl_internal) + endif() + target_compile_definitions(cpprest PUBLIC ${CPPREST_BOTAN_SSL_FLAG} -DCPPREST_FORCE_HTTP_CLIENT_ASIO) target_sources(cpprest PRIVATE http/client/http_client_asio.cpp http/client/x509_cert_utilities.cpp) - target_link_libraries(cpprest PUBLIC cpprestsdk_boost_internal cpprestsdk_openssl_internal) + target_link_libraries(cpprest PUBLIC cpprestsdk_boost_internal ${CPPREST_SSL_LINK_LIB}) elseif(CPPREST_HTTP_CLIENT_IMPL STREQUAL "winhttp") target_link_libraries(cpprest PRIVATE httpapi.lib @@ -248,6 +256,7 @@ if(CPPREST_INSTALL) set(CPPREST_USES_ZLIB OFF) set(CPPREST_USES_BROTLI OFF) set(CPPREST_USES_OPENSSL OFF) + set(CPPREST_USES_BOTAN OFF) set(CPPREST_TARGETS cpprest) if(TARGET cpprestsdk_boost_internal) @@ -266,6 +275,10 @@ if(CPPREST_INSTALL) list(APPEND CPPREST_TARGETS cpprestsdk_openssl_internal) set(CPPREST_USES_OPENSSL ON) endif() + if(TARGET cpprestsdk_botan_internal) + list(APPEND CPPREST_TARGETS cpprestsdk_botan_internal) + set(CPPREST_USES_BOTAN ON) + endif() if(TARGET cpprestsdk_websocketpp_internal) list(APPEND CPPREST_TARGETS cpprestsdk_websocketpp_internal) endif() diff --git a/Release/src/http/client/http_client_asio.cpp b/Release/src/http/client/http_client_asio.cpp index 1bcb335e27..9fa33296fe 100644 --- a/Release/src/http/client/http_client_asio.cpp +++ b/Release/src/http/client/http_client_asio.cpp @@ -25,12 +25,19 @@ #pragma clang diagnostic ignored "-Wunused-local-typedef" #pragma clang diagnostic ignored "-Winfinite-recursion" #endif + #include +#include +#include + +#if defined(CPPREST_BOTAN_SSL) +#include +#else // ^^^ CPPREST_BOTAN_SSL // !CPPREST_BOTAN_SSL vvv #include #include #include -#include -#include +#endif // CPPREST_BOTAN_SSL + #if defined(__clang__) #pragma clang diagnostic pop #endif @@ -87,8 +94,7 @@ namespace { const std::string CRLF("\r\n"); -std::string calc_cn_host(const web::http::uri& baseUri, - const web::http::http_headers& requestHeaders) +std::string calc_cn_host(const web::http::uri& baseUri, const web::http::http_headers& requestHeaders) { std::string result; if (baseUri.scheme() == U("https")) @@ -142,9 +148,20 @@ static std::string generate_base64_userpass(const ::web::credentials& creds) class asio_connection_pool; +#if defined(CPPREST_BOTAN_SSL) +using ssl_stream_t = Botan::TLS::Stream; +using ssl_handshake_type_t = Botan::TLS::Connection_Side; +const ssl_handshake_type_t ssl_handshake_client_side = ssl_handshake_type_t::CLIENT; +#else +using ssl_stream_t = boost::asio::ssl::stream; +using ssl_handshake_type_t = boost::asio::ssl::stream_base::handshake_type; +const ssl_handshake_type_t ssl_handshake_client_side = ssl_handshake_type_t::client; +#endif + class asio_connection { friend class asio_client; + using ssl_context_t = http_client::ssl_context; public: asio_connection(boost::asio::io_service& io_service) @@ -161,11 +178,16 @@ class asio_connection ~asio_connection() { close(); } // This simply instantiates the internal state to support ssl. It does not perform the handshake. - void upgrade_to_ssl(std::string&& cn_hostname, - const std::function& ssl_context_callback) + void upgrade_to_ssl(std::string&& cn_hostname, const std::function& ssl_context_callback) { std::lock_guard lock(m_socket_lock); assert(!is_ssl()); + m_cn_hostname = std::move(cn_hostname); +#if defined(CPPREST_BOTAN_SSL) + Botan::TLS::Context ssl_context; + ssl_context_callback(ssl_context); + ssl_context.serverInfo = Botan::TLS::Server_Information(m_cn_hostname); +#else // ^^^ CPPREST_BOTAN_SSL // !CPPREST_BOTAN_SSL vvv boost::asio::ssl::context ssl_context(boost::asio::ssl::context::sslv23); ssl_context.set_default_verify_paths(); ssl_context.set_options(boost::asio::ssl::context::default_workarounds); @@ -173,9 +195,8 @@ class asio_connection { ssl_context_callback(ssl_context); } - m_ssl_stream = utility::details::make_unique>( - m_socket, ssl_context); - m_cn_hostname = std::move(cn_hostname); +#endif // CPPREST_BOTAN_SSL + m_ssl_stream = utility::details::make_unique(m_socket, ssl_context); } void close() @@ -232,6 +253,7 @@ class asio_connection // server due to inactivity. Unfortunately, the exact error we get // in this case depends on the Boost.Asio version used. #if BOOST_ASIO_VERSION >= 101008 + // TODO if (boost::asio::ssl::error::stream_truncated == ec) return true; #else // Asio < 1.10.8 didn't have ssl::error::stream_truncated if (boost::system::error_code(ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ), @@ -258,14 +280,27 @@ class asio_connection handler(boost::asio::error::operation_aborted); } + +#if defined(CPPREST_BOTAN_SSL) + template + void async_handshake(ssl_handshake_type_t type, + const http_client_config&, + const HandshakeHandler& handshake_handler) + { + std::lock_guard lock(m_socket_lock); + assert(is_ssl()); + m_ssl_stream->async_handshake(type, handshake_handler); + } +#else // ^^^ CPPREST_BOTAN_SSL // !CPPREST_BOTAN_SSL vvv template - void async_handshake(boost::asio::ssl::stream_base::handshake_type type, + void async_handshake(ssl_handshake_type_t type, const http_client_config& config, const HandshakeHandler& handshake_handler, const CertificateHandler& cert_handler) { std::lock_guard lock(m_socket_lock); assert(is_ssl()); + // This is configured via the Botan::TLS::Context callback in the Botan case // Check to turn on/off server certificate verification. if (config.validate_certificates()) @@ -283,9 +318,9 @@ class asio_connection { SSL_set_tlsext_host_name(m_ssl_stream->native_handle(), &m_cn_hostname[0]); } - m_ssl_stream->async_handshake(type, handshake_handler); } +#endif // CPPREST_BOTAN_SSL template void async_write(ConstBufferSequence& buffer, const Handler& writeHandler) @@ -337,7 +372,7 @@ class asio_connection // as normal message processing. std::mutex m_socket_lock; tcp::socket m_socket; - std::unique_ptr> m_ssl_stream; + std::unique_ptr m_ssl_stream; std::string m_cn_hostname; bool m_is_reused; @@ -1062,21 +1097,23 @@ class asio_context final : public request_context, public std::enable_shared_fro { const auto weakCtx = std::weak_ptr(shared_from_this()); m_connection->async_handshake( - boost::asio::ssl::stream_base::client, + ssl_handshake_client_side, m_http_client->client_config(), - boost::bind(&asio_context::handle_handshake, shared_from_this(), boost::asio::placeholders::error), - + boost::bind(&asio_context::handle_handshake, shared_from_this(), boost::asio::placeholders::error) +#if !defined(CPPREST_BOTAN_SSL) // Use a weak_ptr since the verify_callback is stored until the connection is // destroyed. This avoids creating a circular reference since we pool connection // objects. - [weakCtx](bool preverified, boost::asio::ssl::verify_context& verify_context) { + , [weakCtx](bool preverified, boost::asio::ssl::verify_context& verify_context) { auto this_request = weakCtx.lock(); if (this_request) { return this_request->handle_cert_verification(preverified, verify_context); } return false; - }); + } +#endif + ); } else { @@ -1096,7 +1133,7 @@ class asio_context final : public request_context, public std::enable_shared_fro } else { - report_error("Error in SSL handshake", ec, httpclient_errorcode_context::handshake); + report_error("Error in SSL handshake: " + ec.message(), ec, httpclient_errorcode_context::handshake); } } diff --git a/Release/tests/functional/http/client/client_construction.cpp b/Release/tests/functional/http/client/client_construction.cpp index 1229b2cfd7..4bb0ece93b 100644 --- a/Release/tests/functional/http/client/client_construction.cpp +++ b/Release/tests/functional/http/client/client_construction.cpp @@ -181,7 +181,7 @@ SUITE(client_construction) http_client_config config; bool called = false; - config.set_ssl_context_callback([&called](boost::asio::ssl::context& ctx) { called = true; }); + config.set_ssl_context_callback([&called](http_client::ssl_context& ctx) { called = true; }); http_client client("https://www.google.com/", config); @@ -202,7 +202,7 @@ SUITE(client_construction) http_client_config config; bool called = false; - config.set_ssl_context_callback([&called](boost::asio::ssl::context& ctx) { called = true; }); + config.set_ssl_context_callback([&called](http_client::ssl_context& ctx) { called = true; }); http_client client("http://www.google.com/", config); diff --git a/Release/tests/functional/http/listener/listener_construction_tests.cpp b/Release/tests/functional/http/listener/listener_construction_tests.cpp index 1e93ef56ae..9c34ac9627 100644 --- a/Release/tests/functional/http/listener/listener_construction_tests.cpp +++ b/Release/tests/functional/http/listener/listener_construction_tests.cpp @@ -425,7 +425,7 @@ SUITE(listener_construction_tests) } } -#if !defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO) +#if (!defined(_WIN32) && !defined(__cplusplus_winrt) || defined(CPPREST_FORCE_HTTP_LISTENER_ASIO)) && !defined(CPPREST_BOTAN_SSL) TEST_FIXTURE(uri_address, create_https_listener_get, "Ignore", "github 209") {