Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSL Context Callback #1021

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions cpr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ add_library(cpr
response.cpp
redirect.cpp
interceptor.cpp
ssl_ctx.cpp
curlmultiholder.cpp
multiperform.cpp)

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})
Expand Down
108 changes: 107 additions & 1 deletion cpr/callback.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
#include <functional>
#ifdef OPENSSL_BACKEND_USED
#include <cstddef>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#endif // OPENSSL_BACKEND_USED

#include "cpr/callback.h"
#include "cpr/cprtypes.h"
#include <functional>
#include <curl/curl.h>

#ifdef OPENSSL_BACKEND_USED
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pemerr.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>

// 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(<openssl/types.h>)
#include <openssl/types.h>
#else
#include <openssl/ossl_typ.h>
#endif
#endif // OPENSSL_BACKEND_USED

namespace cpr {

Expand All @@ -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 <auto fn>
struct deleter_from_fn {
template <typename T>
constexpr void operator()(T* arg) const {
fn(arg);
}
};

template <typename T, auto fn>
using custom_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
using x509_ptr = custom_unique_ptr<X509, X509_free>;
using bio_ptr = custom_unique_ptr<BIO, BIO_free>;

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<std::ostringstream*>(data);
oss << str;
return static_cast<int>(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<SSL_CTX*>(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<char*>(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
22 changes: 17 additions & 5 deletions cpr/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -543,10 +539,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());
Expand Down
112 changes: 0 additions & 112 deletions cpr/ssl_ctx.cpp

This file was deleted.

4 changes: 4 additions & 0 deletions cpr/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading