diff --git a/CMakeLists.txt b/CMakeLists.txt index 30208bcd5..5c8ac65b3 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..3221f169c 100644 --- a/cpr/callback.cpp +++ b/cpr/callback.cpp @@ -1,6 +1,36 @@ +#include +#ifdef OPENSSL_BACKEND_USED +#include +#include +#include +#include +#include +#endif // OPENSSL_BACKEND_USED + #include "cpr/callback.h" #include "cpr/cprtypes.h" -#include +#include + +#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 +// cppcheck-suppress preprocessorErrorDirective +#if __has_include() +#include +#else +#include +#endif +#endif // OPENSSL_BACKEND_USED namespace cpr { @@ -11,4 +41,80 @@ 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; } + +#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; + +static 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 ssl +#endif // OPENSSL_BACKEND_USED } // namespace cpr diff --git a/cpr/session.cpp b/cpr/session.cpp index fc9df926c..56845a046 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: @@ -530,10 +526,26 @@ 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 (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, 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_); + } +#ifdef OPENSSL_BACKEND_USED + else if (options.ca_buffer.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, nullptr); + } +#endif #endif if (!options.crl_file.empty()) { curl_easy_setopt(curl_->handle, CURLOPT_CRLFILE, options.crl_file.c_str()); diff --git a/cpr/ssl_ctx.cpp b/cpr/ssl_ctx.cpp deleted file mode 100644 index dcc1cdec8..000000000 --- a/cpr/ssl_ctx.cpp +++ /dev/null @@ -1,115 +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 { - -/** - * 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 - 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; - -namespace { -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(); -} - -} // namespace - -CURLcode sslctx_function_load_ca_cert_from_buffer(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 diff --git a/cpr/util.cpp b/cpr/util.cpp index a33c138d1..d2fb8bb9d 100644 --- a/cpr/util.cpp +++ b/cpr/util.cpp @@ -156,6 +156,12 @@ int debugUserFunction(CURL* /*handle*/, curl_infotype type, char* data, size_t s return 0; } +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +CURLcode sslCtxUserFunction(CURL* curl, void* sslctx, const ssl::SslCtxCallback* ctx) { + return (*ctx)(curl, sslctx); +} +#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION + /** * 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 b20ce2ad0..e8c75ac38 100644 --- a/include/cpr/callback.h +++ b/include/cpr/callback.h @@ -1,16 +1,27 @@ #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 -namespace cpr { +/** + * 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: ReadCallback() = default; @@ -106,7 +117,47 @@ 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); + (void) p_curl; + return callback(curl_holder, p_ssl_ctx, userdata); + } + + void SetCurlHolder(const std::shared_ptr& p_curl_holder) { + this->curl_holder = p_curl_holder; + } +}; + +#ifdef OPENSSL_BACKEND_USED +CURLcode tryLoadCaCertFromBuffer(CURL* curl, void* sslctx, void* raw_cert_buf); +#endif +} // 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 89c0e1d98..cce362ee6 100644 --- a/include/cpr/session.h +++ b/include/cpr/session.h @@ -1,14 +1,15 @@ #ifndef CPR_SESSION_H #define CPR_SESSION_H -#include #include #include #include #include #include #include +#include #include +#include #include "cpr/accept_encoding.h" #include "cpr/async_wrapper.h" @@ -40,7 +41,6 @@ #include "cpr/timeout.h" #include "cpr/unix_socket.h" #include "cpr/user_agent.h" -#include "cpr/util.h" #include "cpr/verbose.h" namespace cpr { @@ -251,6 +251,9 @@ class Session : public std::enable_shared_from_this { * Ensures that the "Transfer-Encoding" is set to "chunked", if not overriden in header_. **/ ReadCallback readcb_; +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + ssl::SslCtxCallback sslctxcb_; +#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION 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 b6bc81190..000000000 --- a/include/cpr/ssl_ctx.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef CPR_SSL_CTX_H -#define CPR_SSL_CTX_H - -#include "cpr/ssl_options.h" -#include - -#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION - -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. - * - * 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); - -} // Namespace cpr - -#endif - -#endif diff --git a/include/cpr/ssl_options.h b/include/cpr/ssl_options.h index 41942a486..19368e82d 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 { @@ -325,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) @@ -332,6 +328,7 @@ class CaBuffer { const std::string buffer; }; +#endif // OPENSSL_BACKEND_USED #endif // specify a Certificate Revocation List file @@ -406,7 +403,6 @@ class NoRevoke { bool enabled = false; }; - } // namespace ssl struct SslOptions { @@ -442,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; @@ -454,6 +452,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); @@ -570,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(); @@ -590,6 +594,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..369a0d742 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,9 @@ 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); +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +CURLcode sslCtxUserFunction(CURL* curl, void* sslctx, const ssl::SslCtxCallback* ctx); +#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION 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 c77f452fa..1a8ba3ca5 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 = loadFileContent(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 #if SUPPORT_CURLOPT_SSLKEY_BLOB