From 325ee8f941cc78fc6fe5b044363340c531045a60 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Sat, 27 Jan 2024 12:00:33 +0100 Subject: [PATCH 1/9] Initial SSL ctx callback implementation --- cpr/session.cpp | 20 +++++++++++++++++- cpr/ssl_ctx.cpp | 19 +++++++++-------- include/cpr/callback.h | 1 - include/cpr/session.h | 22 ++++++++++++++++++-- include/cpr/ssl_ctx.h | 46 +++++++++++++++++++++++++++++++++--------- 5 files changed, 85 insertions(+), 23 deletions(-) diff --git a/cpr/session.cpp b/cpr/session.cpp index b2263ec4b..b7db2587e 100644 --- a/cpr/session.cpp +++ b/cpr/session.cpp @@ -282,6 +282,12 @@ void Session::SetLimitRate(const LimitRate& limit_rate) { curl_easy_setopt(curl_->handle, CURLOPT_MAX_SEND_SPEED_LARGE, limit_rate.uprate); } +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +void Session::SetSslCtxCallback(const SslCtxCallback& ssl_ctx) { + cbs_->sslctxcb_ = ssl_ctx; +} +#endif + void Session::SetReadCallback(const ReadCallback& read) { cbs_->readcb_ = read; curl_easy_setopt(curl_->handle, CURLOPT_INFILESIZE_LARGE, read.size); @@ -543,9 +549,18 @@ void Session::SetSslOptions(const SslOptions& options) { #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION #ifdef OPENSSL_BACKEND_USED if (!options.ca_buffer.empty()) { - curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function_load_ca_cert_from_buffer); + if (cbs_->sslctxcb_.callback) { + throw std::logic_error{"Using both cpr::SslCtxCallback and SslOptions::ca_buffer at the same time is not supported. Use either one. To implement SslOptions::ca_buffer take a look into cpr/ssl_ctx.{cpp,h}."}; + } + + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, tryLoadCaCertFromBuffer); curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_DATA, options.ca_buffer.c_str()); } + + if (cbs_->sslctxcb_.callback) { + cbs_->sslctxcb_.SetCurlHolder(curl_); + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, &cbs_->sslctxcb_); + } #endif #endif if (!options.crl_file.empty()) { @@ -991,6 +1006,9 @@ void Session::prepareBodyPayloadOrMultipart() const { void Session::SetOption(const Resolve& resolve) { SetResolve(resolve); } void Session::SetOption(const std::vector& resolves) { SetResolves(resolves); } void Session::SetOption(const ReadCallback& read) { SetReadCallback(read); } +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + void Session::SetOption(const SslCtxCallback& ssl_ctx) { SetSslCtxCallback(ssl_ctx); } +#endif void Session::SetOption(const HeaderCallback& header) { SetHeaderCallback(header); } void Session::SetOption(const WriteCallback& write) { SetWriteCallback(write); } void Session::SetOption(const ProgressCallback& progress) { SetProgressCallback(progress); } diff --git a/cpr/ssl_ctx.cpp b/cpr/ssl_ctx.cpp index fb309009c..4a5ca3610 100644 --- a/cpr/ssl_ctx.cpp +++ b/cpr/ssl_ctx.cpp @@ -32,15 +32,6 @@ namespace cpr { -/** - * The ssl_ctx parameter is actually a pointer to the SSL library's SSL_CTX for OpenSSL. - * If an error is returned from the callback no attempt to establish a connection is made and - * the perform operation will return the callback's error code. - * - * Sources: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html - * https://curl.se/libcurl/c/CURLOPT_SSL_CTX_DATA.html - */ - template struct deleter_from_fn { template @@ -66,7 +57,15 @@ inline std::string get_openssl_print_errors() { return oss.str(); } -CURLcode sslctx_function_load_ca_cert_from_buffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) { +/** + * The ssl_ctx parameter is actually a pointer to the SSL library's SSL_CTX for OpenSSL. + * If an error is returned from the callback no attempt to establish a connection is made and + * the perform operation will return the callback's error code. + * + * Sources: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + * https://curl.se/libcurl/c/CURLOPT_SSL_CTX_DATA.html + */ +CURLcode tryLoadCaCertFromBuffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) { // Check arguments if (raw_cert_buf == nullptr || sslctx == nullptr) { std::cerr << "Invalid callback arguments!\n"; diff --git a/include/cpr/callback.h b/include/cpr/callback.h index b20ce2ad0..304cce4c0 100644 --- a/include/cpr/callback.h +++ b/include/cpr/callback.h @@ -10,7 +10,6 @@ #include namespace cpr { - class ReadCallback { public: ReadCallback() = default; diff --git a/include/cpr/session.h b/include/cpr/session.h index df1324707..e52916510 100644 --- a/include/cpr/session.h +++ b/include/cpr/session.h @@ -1,10 +1,10 @@ #ifndef CPR_SESSION_H #define CPR_SESSION_H -#include #include #include #include +#include #include #include #include @@ -36,11 +36,11 @@ #include "cpr/reserve_size.h" #include "cpr/resolve.h" #include "cpr/response.h" +#include "cpr/ssl_ctx.h" #include "cpr/ssl_options.h" #include "cpr/timeout.h" #include "cpr/unix_socket.h" #include "cpr/user_agent.h" -#include "cpr/util.h" #include "cpr/verbose.h" namespace cpr { @@ -91,6 +91,20 @@ class Session : public std::enable_shared_from_this { void SetVerifySsl(const VerifySsl& verify); void SetUnixSocket(const UnixSocket& unix_socket); void SetSslOptions(const SslOptions& options); +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + /** + * This callback function gets called by libcurl just before the initialization of an SSL connection + * after having processed all other SSL related options to give a last chance to an application + * to modify the behavior of the SSL initialization. + * + * If an error is returned from the callback no attempt to establish a connection is made + * and the perform operation returns the callback's error code. + * For no error return CURLE_OK from inside 'curl/curl.h' + * + * More/Source: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + **/ + void SetSslCtxCallback(const SslCtxCallback& ssl_ctx); +#endif void SetReadCallback(const ReadCallback& read); void SetHeaderCallback(const HeaderCallback& header); void SetWriteCallback(const WriteCallback& write); @@ -141,6 +155,9 @@ class Session : public std::enable_shared_from_this { void SetOption(Body&& body); void SetOption(const Body& body); void SetOption(const ReadCallback& read); +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + void SetOption(const SslCtxCallback& ssl_ctx); +#endif void SetOption(const HeaderCallback& header); void SetOption(const WriteCallback& write); void SetOption(const ProgressCallback& progress); @@ -249,6 +266,7 @@ class Session : public std::enable_shared_from_this { * Ensures that the "Transfer-Encoding" is set to "chunked", if not overriden in header_. **/ ReadCallback readcb_; + SslCtxCallback sslctxcb_; HeaderCallback headercb_; WriteCallback writecb_; ProgressCallback progresscb_; diff --git a/include/cpr/ssl_ctx.h b/include/cpr/ssl_ctx.h index b6bc81190..d907ac61b 100644 --- a/include/cpr/ssl_ctx.h +++ b/include/cpr/ssl_ctx.h @@ -2,22 +2,50 @@ #define CPR_SSL_CTX_H #include "cpr/ssl_options.h" -#include +#include #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#include "curlholder.h" +#include + namespace cpr { /** - * This callback function loads a CA certificate from raw_cert_buf and gets called by libcurl - * just before the initialization of an SSL connection. - * The raw_cert_buf argument is set with the CURLOPT_SSL_CTX_DATA option and has to be a nul - * terminated buffer. + * This callback function gets called by libcurl just before the initialization of an SSL connection + * after having processed all other SSL related options to give a last chance to an application + * to modify the behavior of the SSL initialization. * - * Sources: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html - * https://curl.se/libcurl/c/CURLOPT_SSL_CTX_DATA.html - */ -CURLcode sslctx_function_load_ca_cert_from_buffer(CURL* curl, void* sslctx, void* raw_cert_buf); + * If an error is returned from the callback no attempt to establish a connection is made + * and the perform operation returns the callback's error code. + * For no error return CURLE_OK from inside 'curl/curl.h' + * + * More/Source: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + **/ +class SslCtxCallback { + public: + std::function& curl_holder, void* ssl_ctx, intptr_t userdata)> callback{}; + intptr_t userdata{}; + std::shared_ptr curl_holder{nullptr}; + + SslCtxCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + SslCtxCallback(std::function& p_curl_holder, void* p_ssl_ctx, intptr_t p_userdata)>& p_callback, intptr_t p_userdata = 0) : callback(p_callback), userdata(p_userdata) {} + + CURLcode operator()(CURL* p_curl, void* p_ssl_ctx, void* p_clientp) const { + // We use our own way of passing arguments curl and the client pointer to the function. + assert(p_curl == curl_holder->handle); + assert(!p_clientp); + + return callback(curl_holder, p_ssl_ctx, userdata); + } + + void SetCurlHolder(const std::shared_ptr& p_curl_holder) { + this->curl_holder = p_curl_holder; + } +}; + +CURLcode tryLoadCaCertFromBuffer(CURL* curl, void* sslctx, void* raw_cert_buf); } // Namespace cpr From 9fdee2f87331d57cca6a7c07995e909a112e4d44 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Sun, 28 Jan 2024 12:36:29 +0100 Subject: [PATCH 2/9] Migrated the SSL context callback into callback.h --- CMakeLists.txt | 9 ++++- cpr/CMakeLists.txt | 3 +- cpr/callback.cpp | 79 ++++++++++++++++++++++++++++++++++++++- cpr/session.cpp | 27 +++++-------- cpr/util.cpp | 4 ++ include/CMakeLists.txt | 1 - include/cpr/callback.h | 53 +++++++++++++++++++++++++- include/cpr/cpr.h | 1 - include/cpr/session.h | 21 +---------- include/cpr/ssl_ctx.h | 54 -------------------------- include/cpr/ssl_options.h | 26 ++++++++++--- include/cpr/util.h | 3 +- test/ssl_tests.cpp | 40 ++++++++++++++++++++ 13 files changed, 215 insertions(+), 106 deletions(-) delete mode 100644 include/cpr/ssl_ctx.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 70d3296d6..7dd922487 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,11 +172,18 @@ if(CPR_ENABLE_SSL) endif() if(SSL_BACKEND_USED STREQUAL "OpenSSL") -# Fix missing OpenSSL includes for Windows since in 'ssl_ctx.cpp' we include OpenSSL directly +# Fix missing OpenSSL includes for Windows since in 'ssl_options.cpp' we include OpenSSL directly find_package(OpenSSL REQUIRED) add_compile_definitions(OPENSSL_BACKEND_USED) endif() +if (SSL_BACKEND_USED STREQUAL "OpenSSL" OR SSL_BACKEND_USED STREQUAL "MbedTLS") + # CURLOPT_SSL_CTX_FUNCTION works for libcurl powered by OpenSSL, wolfSSL, mbedTLS or BearSSL. + # If libcurl was built against another SSL library this functionality is absent. + # Ref: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + add_compile_definitions(CPR_SSL_CTX_CALLBACK_ENABLED) +endif () + # Curl configuration if(CPR_USE_SYSTEM_CURL) if(CPR_ENABLE_SSL) diff --git a/cpr/CMakeLists.txt b/cpr/CMakeLists.txt index 0c7083c4e..72ac9dabc 100644 --- a/cpr/CMakeLists.txt +++ b/cpr/CMakeLists.txt @@ -26,7 +26,6 @@ add_library(cpr response.cpp redirect.cpp interceptor.cpp - ssl_ctx.cpp curlmultiholder.cpp multiperform.cpp) @@ -34,7 +33,7 @@ add_library(cpr::cpr ALIAS cpr) target_link_libraries(cpr PUBLIC ${CURL_LIB}) # todo should be private, but first dependencies in ssl_options need to be removed -# Fix missing OpenSSL includes for Windows since in 'ssl_ctx.cpp' we include OpenSSL directly +# Fix missing OpenSSL includes for Windows since in 'ssl_options.cpp' we include OpenSSL directly if(SSL_BACKEND_USED STREQUAL "OpenSSL") target_link_libraries(cpr PRIVATE OpenSSL::SSL) target_include_directories(cpr PRIVATE ${OPENSSL_INCLUDE_DIR}) diff --git a/cpr/callback.cpp b/cpr/callback.cpp index f2d257a2c..3fb4e0b0c 100644 --- a/cpr/callback.cpp +++ b/cpr/callback.cpp @@ -1,6 +1,27 @@ +#include +#include + #include "cpr/callback.h" #include "cpr/cprtypes.h" -#include +#include + +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#include +#include +#include +#include +#include + +// openssl/types.h was added in later version of openssl and is therefore not always available. +// This is for example the case on Ubuntu 20.04. +// We try to include it if available to satisfy clang-tidy. +// Ref: https://github.com/openssl/openssl/commit/50cd4768c6b89c757645f28519236bb989216f8d +#if __has_include() +#include +#else +#include +#endif +#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION namespace cpr { @@ -11,4 +32,60 @@ bool CancellationCallback::operator()(cpr_pf_arg_t dltotal, cpr_pf_arg_t dlnow, const bool cont_operation{!cancellation_state->load()}; return user_cb ? (cont_operation && (*user_cb)(dltotal, dlnow, ultotal, ulnow)) : cont_operation; } + +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +namespace ssl { +/** + * The ssl_ctx parameter is actually a pointer to the SSL library's SSL_CTX for OpenSSL. + * If an error is returned from the callback no attempt to establish a connection is made and + * the perform operation will return the callback's error code. + * + * Sources: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + * https://curl.se/libcurl/c/CURLOPT_SSL_CTX_DATA.html + */ +CURLcode tryLoadCaCertFromBuffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) { + // Check arguments + if (raw_cert_buf == nullptr || sslctx == nullptr) { + std::cerr << "CPR SSL context invalid callback arguments!\n"; + return CURLE_ABORTED_BY_CALLBACK; + } + + // Setup pointer + X509_STORE* store = nullptr; + X509* cert = nullptr; + BIO* bio = nullptr; + char* cert_buf = static_cast(raw_cert_buf); + + // Create a memory BIO using the data of cert_buf. + // Note: It is assumed, that cert_buf is nul terminated and its length is determined by strlen. + bio = BIO_new_mem_buf(cert_buf, -1); + + // Load the PEM formatted certicifate into an X509 structure which OpenSSL can use. + PEM_read_bio_X509(bio, &cert, nullptr, nullptr); + if (cert == nullptr) { + std::cerr << "CPR SSL context PEM_read_bio_X509 failed!\n"; + return CURLE_ABORTED_BY_CALLBACK; + } + + // Get a pointer to the current certificate verification storage + store = SSL_CTX_get_cert_store(static_cast(sslctx)); + + // Add the loaded certificate to the verification storage + const int status = X509_STORE_add_cert(store, cert); + if (status == 0) { + std::cerr << "CPR SSL context error adding certificate!\n"; + return CURLE_ABORTED_BY_CALLBACK; + } + + // Decrement the reference count of the X509 structure cert and frees it up + X509_free(cert); + + // Free the entire bio chain + BIO_free(bio); + + // The CA certificate was loaded successfully into the verification storage + return CURLE_OK; +} +} // namespace ssl +#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION } // namespace cpr diff --git a/cpr/session.cpp b/cpr/session.cpp index b7db2587e..bbbe8d02f 100644 --- a/cpr/session.cpp +++ b/cpr/session.cpp @@ -57,10 +57,6 @@ #include "cpr/util.h" #include "cpr/verbose.h" -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION -#include "cpr/ssl_ctx.h" -#endif - namespace cpr { // Ignored here since libcurl reqires a long: @@ -282,12 +278,6 @@ void Session::SetLimitRate(const LimitRate& limit_rate) { curl_easy_setopt(curl_->handle, CURLOPT_MAX_SEND_SPEED_LARGE, limit_rate.uprate); } -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION -void Session::SetSslCtxCallback(const SslCtxCallback& ssl_ctx) { - cbs_->sslctxcb_ = ssl_ctx; -} -#endif - void Session::SetReadCallback(const ReadCallback& read) { cbs_->readcb_ = read; curl_easy_setopt(curl_->handle, CURLOPT_INFILESIZE_LARGE, read.size); @@ -549,17 +539,21 @@ void Session::SetSslOptions(const SslOptions& options) { #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION #ifdef OPENSSL_BACKEND_USED if (!options.ca_buffer.empty()) { - if (cbs_->sslctxcb_.callback) { - throw std::logic_error{"Using both cpr::SslCtxCallback and SslOptions::ca_buffer at the same time is not supported. Use either one. To implement SslOptions::ca_buffer take a look into cpr/ssl_ctx.{cpp,h}."}; + if (options.ssl_ctx_cb.callback) { + throw std::logic_error{"Using both cpr::SslCtxCallback and SslOptions::ca_buffer at the same time is not supported. Use either one. To implement SslOptions::ca_buffer take a look into cpr/ssl_options.cpp."}; } - curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, tryLoadCaCertFromBuffer); + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, ssl::tryLoadCaCertFromBuffer); curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_DATA, options.ca_buffer.c_str()); } - if (cbs_->sslctxcb_.callback) { + if (options.ssl_ctx_cb.callback) { + cbs_->sslctxcb_ = options.ssl_ctx_cb; cbs_->sslctxcb_.SetCurlHolder(curl_); - curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, &cbs_->sslctxcb_); + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, cpr::util::sslCtxUserFunction); + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_DATA, &cbs_->sslctxcb_); + } else if (options.ca_buffer.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, nullptr); } #endif #endif @@ -1006,9 +1000,6 @@ void Session::prepareBodyPayloadOrMultipart() const { void Session::SetOption(const Resolve& resolve) { SetResolve(resolve); } void Session::SetOption(const std::vector& resolves) { SetResolves(resolves); } void Session::SetOption(const ReadCallback& read) { SetReadCallback(read); } -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION - void Session::SetOption(const SslCtxCallback& ssl_ctx) { SetSslCtxCallback(ssl_ctx); } -#endif void Session::SetOption(const HeaderCallback& header) { SetHeaderCallback(header); } void Session::SetOption(const WriteCallback& write) { SetWriteCallback(write); } void Session::SetOption(const ProgressCallback& progress) { SetProgressCallback(progress); } diff --git a/cpr/util.cpp b/cpr/util.cpp index a33c138d1..dc02a339e 100644 --- a/cpr/util.cpp +++ b/cpr/util.cpp @@ -156,6 +156,10 @@ int debugUserFunction(CURL* /*handle*/, curl_infotype type, char* data, size_t s return 0; } +CURLcode sslCtxUserFunction(CURL* curl, void* sslctx, const ssl::SslCtxCallback* ctx) { + return (*ctx)(curl, sslctx); +} + /** * Creates a temporary CurlHolder object and uses it to escape the given string. * If you plan to use this methode on a regular basis think about creating a CurlHolder diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index dc704d56a..c6424cf12 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -34,7 +34,6 @@ target_sources(cpr PRIVATE cpr/response.h cpr/session.h cpr/singleton.h - cpr/ssl_ctx.h cpr/ssl_options.h cpr/threadpool.h cpr/timeout.h diff --git a/include/cpr/callback.h b/include/cpr/callback.h index 304cce4c0..37f6bd99c 100644 --- a/include/cpr/callback.h +++ b/include/cpr/callback.h @@ -1,14 +1,26 @@ #ifndef CPR_CALLBACK_H #define CPR_CALLBACK_H -#include "cprtypes.h" +#include "cpr/cprtypes.h" +#include "cpr/curlholder.h" #include -#include +#include #include #include #include +/** + * Needs to be defined here instead of inside ssl_options.h to avoid having to include ssl_options.h leading to a circular include. + **/ +#ifdef CPR_SSL_CTX_CALLBACK_ENABLED +#ifndef SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#define SUPPORT_CURLOPT_SSL_CTX_FUNCTION LIBCURL_VERSION_NUM >= 0x070B00 // 7.11.0 +#else +#define SUPPORT_CURLOPT_SSL_CTX_FUNCTION false +#endif +#endif + namespace cpr { class ReadCallback { public: @@ -105,7 +117,44 @@ class CancellationCallback { std::optional> user_cb; }; +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +namespace ssl { +/** + * This callback function gets called by libcurl just before the initialization of an SSL connection + * after having processed all other SSL related options to give a last chance to an application + * to modify the behavior of the SSL initialization. + * + * If an error is returned from the callback no attempt to establish a connection is made + * and the perform operation returns the callback's error code. + * For no error return CURLE_OK from inside 'curl/curl.h' + * + * More/Source: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + **/ +class SslCtxCallback { + public: + std::function& curl_holder, void* ssl_ctx, intptr_t userdata)> callback{}; + intptr_t userdata{}; + std::shared_ptr curl_holder{nullptr}; + + SslCtxCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + SslCtxCallback(const std::function& p_curl_holder, void* p_ssl_ctx, intptr_t p_userdata)>& p_callback, intptr_t p_userdata = 0) : callback(p_callback), userdata(p_userdata) {} + + CURLcode operator()(CURL* p_curl, void* p_ssl_ctx) const { + // We use our own way of passing arguments curl and the client pointer to the function. + assert(p_curl == curl_holder->handle); + + return callback(curl_holder, p_ssl_ctx, userdata); + } + void SetCurlHolder(const std::shared_ptr& p_curl_holder) { + this->curl_holder = p_curl_holder; + } +}; + +CURLcode tryLoadCaCertFromBuffer(CURL* curl, void* sslctx, void* raw_cert_buf); +} // namespace ssl +#endif } // namespace cpr #endif diff --git a/include/cpr/cpr.h b/include/cpr/cpr.h index fbad1726a..e6f1bf357 100644 --- a/include/cpr/cpr.h +++ b/include/cpr/cpr.h @@ -32,7 +32,6 @@ #include "cpr/resolve.h" #include "cpr/response.h" #include "cpr/session.h" -#include "cpr/ssl_ctx.h" #include "cpr/ssl_options.h" #include "cpr/status_codes.h" #include "cpr/timeout.h" diff --git a/include/cpr/session.h b/include/cpr/session.h index e52916510..b166d7a0d 100644 --- a/include/cpr/session.h +++ b/include/cpr/session.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "cpr/accept_encoding.h" #include "cpr/async_wrapper.h" @@ -36,7 +37,6 @@ #include "cpr/reserve_size.h" #include "cpr/resolve.h" #include "cpr/response.h" -#include "cpr/ssl_ctx.h" #include "cpr/ssl_options.h" #include "cpr/timeout.h" #include "cpr/unix_socket.h" @@ -91,20 +91,6 @@ class Session : public std::enable_shared_from_this { void SetVerifySsl(const VerifySsl& verify); void SetUnixSocket(const UnixSocket& unix_socket); void SetSslOptions(const SslOptions& options); -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION - /** - * This callback function gets called by libcurl just before the initialization of an SSL connection - * after having processed all other SSL related options to give a last chance to an application - * to modify the behavior of the SSL initialization. - * - * If an error is returned from the callback no attempt to establish a connection is made - * and the perform operation returns the callback's error code. - * For no error return CURLE_OK from inside 'curl/curl.h' - * - * More/Source: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html - **/ - void SetSslCtxCallback(const SslCtxCallback& ssl_ctx); -#endif void SetReadCallback(const ReadCallback& read); void SetHeaderCallback(const HeaderCallback& header); void SetWriteCallback(const WriteCallback& write); @@ -155,9 +141,6 @@ class Session : public std::enable_shared_from_this { void SetOption(Body&& body); void SetOption(const Body& body); void SetOption(const ReadCallback& read); -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION - void SetOption(const SslCtxCallback& ssl_ctx); -#endif void SetOption(const HeaderCallback& header); void SetOption(const WriteCallback& write); void SetOption(const ProgressCallback& progress); @@ -266,7 +249,7 @@ class Session : public std::enable_shared_from_this { * Ensures that the "Transfer-Encoding" is set to "chunked", if not overriden in header_. **/ ReadCallback readcb_; - SslCtxCallback sslctxcb_; + ssl::SslCtxCallback sslctxcb_; HeaderCallback headercb_; WriteCallback writecb_; ProgressCallback progresscb_; diff --git a/include/cpr/ssl_ctx.h b/include/cpr/ssl_ctx.h deleted file mode 100644 index d907ac61b..000000000 --- a/include/cpr/ssl_ctx.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CPR_SSL_CTX_H -#define CPR_SSL_CTX_H - -#include "cpr/ssl_options.h" -#include - -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION - -#include "curlholder.h" -#include - -namespace cpr { - -/** - * This callback function gets called by libcurl just before the initialization of an SSL connection - * after having processed all other SSL related options to give a last chance to an application - * to modify the behavior of the SSL initialization. - * - * If an error is returned from the callback no attempt to establish a connection is made - * and the perform operation returns the callback's error code. - * For no error return CURLE_OK from inside 'curl/curl.h' - * - * More/Source: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html - **/ -class SslCtxCallback { - public: - std::function& curl_holder, void* ssl_ctx, intptr_t userdata)> callback{}; - intptr_t userdata{}; - std::shared_ptr curl_holder{nullptr}; - - SslCtxCallback() = default; - // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) - SslCtxCallback(std::function& p_curl_holder, void* p_ssl_ctx, intptr_t p_userdata)>& p_callback, intptr_t p_userdata = 0) : callback(p_callback), userdata(p_userdata) {} - - CURLcode operator()(CURL* p_curl, void* p_ssl_ctx, void* p_clientp) const { - // We use our own way of passing arguments curl and the client pointer to the function. - assert(p_curl == curl_holder->handle); - assert(!p_clientp); - - return callback(curl_holder, p_ssl_ctx, userdata); - } - - void SetCurlHolder(const std::shared_ptr& p_curl_holder) { - this->curl_holder = p_curl_holder; - } -}; - -CURLcode tryLoadCaCertFromBuffer(CURL* curl, void* sslctx, void* raw_cert_buf); - -} // Namespace cpr - -#endif - -#endif diff --git a/include/cpr/ssl_options.h b/include/cpr/ssl_options.h index 41942a486..8ab3f4d26 100644 --- a/include/cpr/ssl_options.h +++ b/include/cpr/ssl_options.h @@ -1,9 +1,7 @@ #ifndef CPR_SSLOPTIONS_H #define CPR_SSLOPTIONS_H -#include #include -#include #include "cpr/filesystem.h" #include @@ -63,9 +61,6 @@ #ifndef SUPPORT_CURLOPT_SSLKEY_BLOB #define SUPPORT_CURLOPT_SSLKEY_BLOB LIBCURL_VERSION_NUM >= 0x074700 // 7.71.0 #endif -#ifndef SUPPORT_CURLOPT_SSL_CTX_FUNCTION -#define SUPPORT_CURLOPT_SSL_CTX_FUNCTION LIBCURL_VERSION_NUM >= 0x070B00 // 7.11.0 -#endif namespace cpr { @@ -406,7 +401,6 @@ class NoRevoke { bool enabled = false; }; - } // namespace ssl struct SslOptions { @@ -454,6 +448,10 @@ struct SslOptions { bool session_id_cache = true; #endif +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + ssl::SslCtxCallback ssl_ctx_cb{}; +#endif + ~SslOptions() noexcept { #if SUPPORT_CURLOPT_SSLKEY_BLOB util::secureStringClear(key_blob); @@ -590,6 +588,22 @@ struct SslOptions { session_id_cache = opt.enabled; } #endif +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + /** + * This callback function gets called by libcurl just before the initialization of an SSL connection + * after having processed all other SSL related options to give a last chance to an application + * to modify the behavior of the SSL initialization. + * + * If an error is returned from the callback no attempt to establish a connection is made + * and the perform operation returns the callback's error code. + * For no error return CURLE_OK from inside 'curl/curl.h' + * + * More/Source: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + **/ + void SetOption(const ssl::SslCtxCallback& opt) { + ssl_ctx_cb = opt; + } +#endif }; namespace priv { diff --git a/include/cpr/util.h b/include/cpr/util.h index cc3d81923..355482f73 100644 --- a/include/cpr/util.h +++ b/include/cpr/util.h @@ -8,7 +8,7 @@ #include "cpr/callback.h" #include "cpr/cookies.h" #include "cpr/cprtypes.h" -#include "cpr/curlholder.h" +#include namespace cpr::util { @@ -19,6 +19,7 @@ size_t headerUserFunction(char* ptr, size_t size, size_t nmemb, const HeaderCall size_t writeFunction(char* ptr, size_t size, size_t nmemb, std::string* data); size_t writeFileFunction(char* ptr, size_t size, size_t nmemb, std::ofstream* file); size_t writeUserFunction(char* ptr, size_t size, size_t nmemb, const WriteCallback* write); +CURLcode sslCtxUserFunction(CURL* curl, void* sslctx, const ssl::SslCtxCallback* ctx); template int progressUserFunction(const T* progress, cpr_pf_arg_t dltotal, cpr_pf_arg_t dlnow, cpr_pf_arg_t ultotal, cpr_pf_arg_t ulnow) { diff --git a/test/ssl_tests.cpp b/test/ssl_tests.cpp index 19ea4e4c8..c43f25a7e 100644 --- a/test/ssl_tests.cpp +++ b/test/ssl_tests.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include @@ -9,6 +11,7 @@ #include "cpr/filesystem.h" #include "cpr/ssl_options.h" +#include "curl/curl.h" #include "httpsServer.hpp" @@ -157,6 +160,43 @@ TEST(SslTests, LoadCertFromBufferTestSimpel) { EXPECT_EQ(200, response.status_code); EXPECT_EQ(ErrorCode::OK, response.error.code) << response.error.message; } + +TEST(SslTests, LoadCertFromBufferAndCallbackThrowTest) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + Url url{server->GetBaseUrl() + "/hello.html"}; + + std::string baseDirPath{server->getBaseDirPath()}; + std::string crtPath{baseDirPath + "certificates/"}; + std::string certBuffer = loadCertificateFromFile(crtPath + "root-ca.crt"); + + cpr::ssl::SslCtxCallback sslCtxCb{[](const std::shared_ptr& /*curl_holder*/, void* /*ssl_ctx*/, intptr_t /*userdata*/) { return CURLE_OK; }}; + + SslOptions sslOpts = Ssl(ssl::CaBuffer{std::move(certBuffer)}, sslCtxCb); + + EXPECT_THROW(cpr::Get(url, sslOpts, Verbose{}), std::logic_error); +} + +TEST(SslTests, SslCtxCallbackTest) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + Url url{server->GetBaseUrl() + "/hello.html"}; + + size_t count{0}; + cpr::ssl::SslCtxCallback sslCtxCb{[](const std::shared_ptr& /*curl_holder*/, void* /*ssl_ctx*/, intptr_t userdata) { + // NOLINTNEXTLINE (cppcoreguidelines-pro-type-reinterpret-cast) + size_t* count = reinterpret_cast(userdata); + (*count)++; + return CURLE_OK; + }, + // NOLINTNEXTLINE (cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(&count)}; + + SslOptions sslOpts = Ssl(sslCtxCb); + + cpr::Get(url, sslOpts); + EXPECT_EQ(count, 1); +} #endif fs::path GetBasePath(const std::string& execPath) { From 5bda06e9199ec02c4a45f2365fcf2faa45a5a9d5 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Sun, 28 Jan 2024 12:51:50 +0100 Subject: [PATCH 3/9] Fixed unused parameter --- include/cpr/callback.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/cpr/callback.h b/include/cpr/callback.h index 37f6bd99c..0d7a6f7dd 100644 --- a/include/cpr/callback.h +++ b/include/cpr/callback.h @@ -143,6 +143,7 @@ class SslCtxCallback { CURLcode operator()(CURL* p_curl, void* p_ssl_ctx) const { // We use our own way of passing arguments curl and the client pointer to the function. assert(p_curl == curl_holder->handle); + (void) p_curl; return callback(curl_holder, p_ssl_ctx, userdata); } From e9d1d430750fade76ced730e9cb0c1486f6467e6 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Sun, 28 Jan 2024 13:09:50 +0100 Subject: [PATCH 4/9] Fixed non OpenSSl based builds --- CMakeLists.txt | 2 +- cpr/callback.cpp | 10 ++++++---- cpr/session.cpp | 5 ++++- include/cpr/callback.h | 2 ++ include/cpr/ssl_options.h | 6 ++++++ 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dd922487..192eceff4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,7 @@ cpr_option(CPR_ENABLE_SSL "Enables or disables the SSL backend. Required to perf cpr_option(CPR_FORCE_OPENSSL_BACKEND "Force to use the OpenSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) cpr_option(CPR_FORCE_WINSSL_BACKEND "Force to use the WinSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) cpr_option(CPR_FORCE_DARWINSSL_BACKEND "Force to use the DarwinSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) -cpr_option(CPR_FORCE_MBEDTLS_BACKEND "Force to use the Mbed TLS backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) +cpr_option(CPR_FORCE_MBEDTLS_BACKEND "Force to use the Mbed TLS backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." ON) cpr_option(CPR_ENABLE_LINTING "Set to ON to enable clang linting." OFF) cpr_option(CPR_ENABLE_CPPCHECK "Set to ON to enable Cppcheck static analysis. Requires CPR_BUILD_TESTS and CPR_BUILD_TESTS_SSL to be OFF to prevent checking google tests source code." OFF) cpr_option(CPR_BUILD_TESTS "Set to ON to build cpr tests." OFF) diff --git a/cpr/callback.cpp b/cpr/callback.cpp index 3fb4e0b0c..2ba1e197c 100644 --- a/cpr/callback.cpp +++ b/cpr/callback.cpp @@ -1,11 +1,13 @@ #include +#ifdef OPENSSL_BACKEND_USED #include +#endif // OPENSSL_BACKEND_USED #include "cpr/callback.h" #include "cpr/cprtypes.h" #include -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#ifdef OPENSSL_BACKEND_USED #include #include #include @@ -21,7 +23,7 @@ #else #include #endif -#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#endif // OPENSSL_BACKEND_USED namespace cpr { @@ -33,7 +35,7 @@ bool CancellationCallback::operator()(cpr_pf_arg_t dltotal, cpr_pf_arg_t dlnow, return user_cb ? (cont_operation && (*user_cb)(dltotal, dlnow, ultotal, ulnow)) : cont_operation; } -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#ifdef OPENSSL_BACKEND_USED namespace ssl { /** * The ssl_ctx parameter is actually a pointer to the SSL library's SSL_CTX for OpenSSL. @@ -87,5 +89,5 @@ CURLcode tryLoadCaCertFromBuffer(CURL* /*curl*/, void* sslctx, void* raw_cert_bu return CURLE_OK; } } // namespace ssl -#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#endif // OPENSSL_BACKEND_USED } // namespace cpr diff --git a/cpr/session.cpp b/cpr/session.cpp index bbbe8d02f..90cc61b01 100644 --- a/cpr/session.cpp +++ b/cpr/session.cpp @@ -546,13 +546,16 @@ void Session::SetSslOptions(const SslOptions& options) { curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, ssl::tryLoadCaCertFromBuffer); curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_DATA, options.ca_buffer.c_str()); } +#endif if (options.ssl_ctx_cb.callback) { cbs_->sslctxcb_ = options.ssl_ctx_cb; cbs_->sslctxcb_.SetCurlHolder(curl_); curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, cpr::util::sslCtxUserFunction); curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_DATA, &cbs_->sslctxcb_); - } else if (options.ca_buffer.empty()) { + } +#ifdef OPENSSL_BACKEND_USED + else if (options.ca_buffer.empty()) { curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, nullptr); } #endif diff --git a/include/cpr/callback.h b/include/cpr/callback.h index 0d7a6f7dd..e8c75ac38 100644 --- a/include/cpr/callback.h +++ b/include/cpr/callback.h @@ -153,7 +153,9 @@ class SslCtxCallback { } }; +#ifdef OPENSSL_BACKEND_USED CURLcode tryLoadCaCertFromBuffer(CURL* curl, void* sslctx, void* raw_cert_buf); +#endif } // namespace ssl #endif } // namespace cpr diff --git a/include/cpr/ssl_options.h b/include/cpr/ssl_options.h index 8ab3f4d26..19368e82d 100644 --- a/include/cpr/ssl_options.h +++ b/include/cpr/ssl_options.h @@ -320,6 +320,7 @@ class CaPath { }; #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#ifdef OPENSSL_BACKEND_USED class CaBuffer { public: // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) @@ -327,6 +328,7 @@ class CaBuffer { const std::string buffer; }; +#endif // OPENSSL_BACKEND_USED #endif // specify a Certificate Revocation List file @@ -436,7 +438,9 @@ struct SslOptions { // We don't use fs::path here, as this leads to problems using windows std::string ca_path; #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#ifdef OPENSSL_BACKEND_USED std::string ca_buffer; +#endif // OPENSSL_BACKEND_USED #endif // We don't use fs::path here, as this leads to problems using windows std::string crl_file; @@ -568,9 +572,11 @@ struct SslOptions { ca_path = opt.filename.string(); } #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#ifdef OPENSSL_BACKEND_USED void SetOption(const ssl::CaBuffer& opt) { ca_buffer = opt.buffer; } +#endif // OPENSSL_BACKEND_USED #endif void SetOption(const ssl::Crl& opt) { crl_file = opt.filename.string(); From 8483041db3db09c2d7b94aea1207eea1ccd91526 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Sun, 28 Jan 2024 13:53:01 +0100 Subject: [PATCH 5/9] Reverted CPR_FORCE_MBEDTLS_BACKEND back to OFF --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 192eceff4..7dd922487 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,7 @@ cpr_option(CPR_ENABLE_SSL "Enables or disables the SSL backend. Required to perf cpr_option(CPR_FORCE_OPENSSL_BACKEND "Force to use the OpenSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) cpr_option(CPR_FORCE_WINSSL_BACKEND "Force to use the WinSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) cpr_option(CPR_FORCE_DARWINSSL_BACKEND "Force to use the DarwinSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) -cpr_option(CPR_FORCE_MBEDTLS_BACKEND "Force to use the Mbed TLS backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." ON) +cpr_option(CPR_FORCE_MBEDTLS_BACKEND "Force to use the Mbed TLS backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) cpr_option(CPR_ENABLE_LINTING "Set to ON to enable clang linting." OFF) cpr_option(CPR_ENABLE_CPPCHECK "Set to ON to enable Cppcheck static analysis. Requires CPR_BUILD_TESTS and CPR_BUILD_TESTS_SSL to be OFF to prevent checking google tests source code." OFF) cpr_option(CPR_BUILD_TESTS "Set to ON to build cpr tests." OFF) From a4f8cc47287f03ab5cd1c3addde1a73dc9d6d848 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Sun, 28 Jan 2024 14:04:51 +0100 Subject: [PATCH 6/9] cppcheck suppress --- cpr/callback.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpr/callback.cpp b/cpr/callback.cpp index 2ba1e197c..bee2f8309 100644 --- a/cpr/callback.cpp +++ b/cpr/callback.cpp @@ -18,6 +18,7 @@ // This is for example the case on Ubuntu 20.04. // We try to include it if available to satisfy clang-tidy. // Ref: https://github.com/openssl/openssl/commit/50cd4768c6b89c757645f28519236bb989216f8d +// cppcheck-suppress preprocessorErrorDirective #if __has_include() #include #else From 15acf042f4ccdfcedaa5a9fb5667746dad0e7e6c Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Fri, 24 May 2024 16:32:28 +0200 Subject: [PATCH 7/9] Fixed ssl context callback support --- cpr/callback.cpp | 77 ++++++++++++++++++++------------ cpr/ssl_ctx.cpp | 111 ----------------------------------------------- 2 files changed, 50 insertions(+), 138 deletions(-) delete mode 100644 cpr/ssl_ctx.cpp diff --git a/cpr/callback.cpp b/cpr/callback.cpp index bee2f8309..2b7d75abd 100644 --- a/cpr/callback.cpp +++ b/cpr/callback.cpp @@ -1,6 +1,7 @@ #include #ifdef OPENSSL_BACKEND_USED #include +#include #endif // OPENSSL_BACKEND_USED #include "cpr/callback.h" @@ -9,7 +10,9 @@ #ifdef OPENSSL_BACKEND_USED #include +#include #include +#include #include #include #include @@ -38,6 +41,31 @@ bool CancellationCallback::operator()(cpr_pf_arg_t dltotal, cpr_pf_arg_t dlnow, #ifdef OPENSSL_BACKEND_USED namespace ssl { +template +struct deleter_from_fn { + template + constexpr void operator()(T* arg) const { + fn(arg); + } +}; + +template +using custom_unique_ptr = std::unique_ptr>; +using x509_ptr = custom_unique_ptr; +using bio_ptr = custom_unique_ptr; + +inline std::string get_openssl_print_errors() { + std::ostringstream oss; + ERR_print_errors_cb( + [](char const* str, size_t len, void* data) -> int { + auto& oss = *static_cast(data); + oss << str; + return static_cast(len); + }, + &oss); + return oss.str(); +} + /** * The ssl_ctx parameter is actually a pointer to the SSL library's SSL_CTX for OpenSSL. * If an error is returned from the callback no attempt to establish a connection is made and @@ -49,43 +77,38 @@ namespace ssl { CURLcode tryLoadCaCertFromBuffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) { // Check arguments if (raw_cert_buf == nullptr || sslctx == nullptr) { - std::cerr << "CPR SSL context invalid callback arguments!\n"; + std::cerr << "Invalid callback arguments!\n"; return CURLE_ABORTED_BY_CALLBACK; } - // Setup pointer - X509_STORE* store = nullptr; - X509* cert = nullptr; - BIO* bio = nullptr; - char* cert_buf = static_cast(raw_cert_buf); + // Get a pointer to the current certificate verification storage + auto* store = SSL_CTX_get_cert_store(static_cast(sslctx)); // Create a memory BIO using the data of cert_buf. // Note: It is assumed, that cert_buf is nul terminated and its length is determined by strlen. - bio = BIO_new_mem_buf(cert_buf, -1); + const bio_ptr bio{BIO_new_mem_buf(static_cast(raw_cert_buf), -1)}; - // Load the PEM formatted certicifate into an X509 structure which OpenSSL can use. - PEM_read_bio_X509(bio, &cert, nullptr, nullptr); - if (cert == nullptr) { - std::cerr << "CPR SSL context PEM_read_bio_X509 failed!\n"; - return CURLE_ABORTED_BY_CALLBACK; - } - - // Get a pointer to the current certificate verification storage - store = SSL_CTX_get_cert_store(static_cast(sslctx)); + bool at_least_got_one = false; + for (;;) { + // Load the PEM formatted certicifate into an X509 structure which OpenSSL can use. + const x509_ptr x{PEM_read_bio_X509_AUX(bio.get(), nullptr, nullptr, nullptr)}; + if (x == nullptr) { + if ((ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) && at_least_got_one) { + ERR_clear_error(); + break; + } + std::cerr << "PEM_read_bio_X509_AUX failed: \n" << get_openssl_print_errors() << '\n'; + return CURLE_ABORTED_BY_CALLBACK; + } - // Add the loaded certificate to the verification storage - const int status = X509_STORE_add_cert(store, cert); - if (status == 0) { - std::cerr << "CPR SSL context error adding certificate!\n"; - return CURLE_ABORTED_BY_CALLBACK; + // Add the loaded certificate to the verification storage + if (X509_STORE_add_cert(store, x.get()) == 0) { + std::cerr << "X509_STORE_add_cert failed: \n" << get_openssl_print_errors() << '\n'; + return CURLE_ABORTED_BY_CALLBACK; + } + at_least_got_one = true; } - // Decrement the reference count of the X509 structure cert and frees it up - X509_free(cert); - - // Free the entire bio chain - BIO_free(bio); - // The CA certificate was loaded successfully into the verification storage return CURLE_OK; } diff --git a/cpr/ssl_ctx.cpp b/cpr/ssl_ctx.cpp deleted file mode 100644 index 4a5ca3610..000000000 --- a/cpr/ssl_ctx.cpp +++ /dev/null @@ -1,111 +0,0 @@ - -#include "cpr/ssl_ctx.h" -#include "cpr/ssl_options.h" -#include -#include -#include -#include -#include -#include - -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION - -#ifdef OPENSSL_BACKEND_USED - -#include -#include -#include -#include -#include -#include -#include - -// openssl/types.h was added in later version of openssl and is therefore not always available. -// This is for example the case on Ubuntu 20.04. -// We try to include it if available to satisfy clang-tidy. -// Ref: https://github.com/openssl/openssl/commit/50cd4768c6b89c757645f28519236bb989216f8d -#if __has_include() -#include -#else -#include -#endif - -namespace cpr { - -template -struct deleter_from_fn { - template - constexpr void operator()(T* arg) const { - fn(arg); - } -}; - -template -using custom_unique_ptr = std::unique_ptr>; -using x509_ptr = custom_unique_ptr; -using bio_ptr = custom_unique_ptr; - -inline std::string get_openssl_print_errors() { - std::ostringstream oss; - ERR_print_errors_cb( - [](char const* str, size_t len, void* data) -> int { - auto& oss = *static_cast(data); - oss << str; - return static_cast(len); - }, - &oss); - return oss.str(); -} - -/** - * The ssl_ctx parameter is actually a pointer to the SSL library's SSL_CTX for OpenSSL. - * If an error is returned from the callback no attempt to establish a connection is made and - * the perform operation will return the callback's error code. - * - * Sources: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html - * https://curl.se/libcurl/c/CURLOPT_SSL_CTX_DATA.html - */ -CURLcode tryLoadCaCertFromBuffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) { - // Check arguments - if (raw_cert_buf == nullptr || sslctx == nullptr) { - std::cerr << "Invalid callback arguments!\n"; - return CURLE_ABORTED_BY_CALLBACK; - } - - // Get a pointer to the current certificate verification storage - auto* store = SSL_CTX_get_cert_store(static_cast(sslctx)); - - // Create a memory BIO using the data of cert_buf. - // Note: It is assumed, that cert_buf is nul terminated and its length is determined by strlen. - const bio_ptr bio{BIO_new_mem_buf(static_cast(raw_cert_buf), -1)}; - - bool at_least_got_one = false; - for (;;) { - // Load the PEM formatted certicifate into an X509 structure which OpenSSL can use. - const x509_ptr x{PEM_read_bio_X509_AUX(bio.get(), nullptr, nullptr, nullptr)}; - if (x == nullptr) { - if ((ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) && at_least_got_one) { - ERR_clear_error(); - break; - } - std::cerr << "PEM_read_bio_X509_AUX failed: \n" << get_openssl_print_errors() << '\n'; - return CURLE_ABORTED_BY_CALLBACK; - } - - // Add the loaded certificate to the verification storage - if (X509_STORE_add_cert(store, x.get()) == 0) { - std::cerr << "X509_STORE_add_cert failed: \n" << get_openssl_print_errors() << '\n'; - return CURLE_ABORTED_BY_CALLBACK; - } - at_least_got_one = true; - } - - // The CA certificate was loaded successfully into the verification storage - return CURLE_OK; -} - -} // namespace cpr - -#endif // OPENSSL_BACKEND_USED - -#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION From 3cfff0dca93fa110202ffefb1f06eba728e0e2aa Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Thu, 30 May 2024 09:16:03 +0200 Subject: [PATCH 8/9] Clang-Tidy --- cpr/callback.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpr/callback.cpp b/cpr/callback.cpp index 2b7d75abd..3b0ab5418 100644 --- a/cpr/callback.cpp +++ b/cpr/callback.cpp @@ -1,7 +1,10 @@ #include #ifdef OPENSSL_BACKEND_USED +#include #include +#include #include +#include #endif // OPENSSL_BACKEND_USED #include "cpr/callback.h" From ddb2a2e2ab5cd11f171429041e7b4e0085b20325 Mon Sep 17 00:00:00 2001 From: Fabian Sauter Date: Thu, 26 Sep 2024 17:52:11 +0200 Subject: [PATCH 9/9] Clang-Format --- include/cpr/session.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cpr/session.h b/include/cpr/session.h index b166d7a0d..24e3f8dea 100644 --- a/include/cpr/session.h +++ b/include/cpr/session.h @@ -4,10 +4,10 @@ #include #include #include -#include #include #include #include +#include #include #include