From 9063a7cc3c837fd54196a7619dab908d065e68ac Mon Sep 17 00:00:00 2001 From: Hannah Sauermann Date: Mon, 10 Nov 2025 22:25:36 +0100 Subject: [PATCH] Added support for setting CURLOPT_SSLCERT_BLOB --- cpr/session.cpp | 14 +++++++++++++ include/cpr/ssl_options.h | 44 +++++++++++++++++++++++++++++++++++++++ test/ssl_tests.cpp | 21 +++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/cpr/session.cpp b/cpr/session.cpp index 82cfd8ba0..8c93c56c3 100644 --- a/cpr/session.cpp +++ b/cpr/session.cpp @@ -528,6 +528,20 @@ void Session::SetSslOptions(const SslOptions& options) { curl_easy_setopt(curl_->handle, CURLOPT_SSLCERTTYPE, options.cert_type.c_str()); } } +#if SUPPORT_CURLOPT_SSLCERT_BLOB + else if(!options.cert_blob.empty()) { + std::string cert_blob(options.cert_blob); + curl_blob blob{}; + // NOLINTNEXTLINE (readability-container-data-pointer) + blob.data = &cert_blob[0]; + blob.len = cert_blob.length(); + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl_->handle, CURLOPT_SSLCERT_BLOB, &blob); + if (!options.cert_type.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSLCERTTYPE, options.cert_type.c_str()); + } + } +#endif if (!options.key_file.empty()) { curl_easy_setopt(curl_->handle, CURLOPT_SSLKEY, options.key_file.c_str()); if (!options.key_type.empty()) { diff --git a/include/cpr/ssl_options.h b/include/cpr/ssl_options.h index 571d6c8ab..d7d43d60c 100644 --- a/include/cpr/ssl_options.h +++ b/include/cpr/ssl_options.h @@ -70,6 +70,9 @@ #ifndef SUPPORT_CURLOPT_CAINFO_BLOB #define SUPPORT_CURLOPT_CAINFO_BLOB LIBCURL_VERSION_NUM >= 0x074D00 // 7.77.0 #endif +#ifndef SUPPORT_CURLOPT_SSLCERT_BLOB +#define SUPPORT_CURLOPT_SSLCERT_BLOB LIBCURL_VERSION_NUM >= 0x074700 // 7.71.0 +#endif namespace cpr { @@ -117,6 +120,38 @@ class DerCert : public CertFile { } }; + +#if SUPPORT_CURLOPT_SSLCERT_BLOB +class CertBlob { +public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + CertBlob(std::string&& p_blob) : blob(std::move(p_blob)) {} + + virtual ~CertBlob() = default; + + std::string blob; + + virtual const char* GetCertType() const { + return "PEM"; + } +}; + +using PemBlob = CertBlob; + +class DerBlob : public CertBlob { +public: + template + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + DerBlob(BlobType&& p_blob) : CertBlob(std::move(p_blob)) {} + + ~DerBlob() override = default; + + const char* GetCertType() const override { + return "DER"; + } +}; +#endif + // specify private keyfile for TLS and SSL client cert class KeyFile { public: @@ -423,6 +458,9 @@ class NoRevoke { struct SslOptions { // We don't use fs::path here, as this leads to problems using windows std::string cert_file; +#if SUPPORT_CURLOPT_SSLCERT_BLOB + util::SecureString cert_blob; +#endif std::string cert_type; // We don't use fs::path here, as this leads to problems using windows std::string key_file; @@ -472,6 +510,12 @@ struct SslOptions { cert_file = opt.filename.string(); cert_type = opt.GetCertType(); } +#if SUPPORT_CURLOPT_SSLCERT_BLOB + void SetOption(const ssl::CertBlob& opt) { + cert_blob = opt.blob; + cert_type = opt.GetCertType(); + } +#endif void SetOption(const ssl::KeyFile& opt) { key_file = opt.filename.string(); key_type = opt.GetKeyType(); diff --git a/test/ssl_tests.cpp b/test/ssl_tests.cpp index 78e42faff..b6afc666b 100644 --- a/test/ssl_tests.cpp +++ b/test/ssl_tests.cpp @@ -159,6 +159,27 @@ TEST(SslTests, LoadCertFromBufferTestSimpel) { } #endif +#if SUPPORT_CURLOPT_SSLCERT_BLOB +TEST(SslTests, LoadCertFromBlobTestSimpel) { + 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 keyPath{baseDirPath + "keys/"}; + std::string crtBuffer = loadFileContent(crtPath + "client.crt"); + SslOptions sslOpts = Ssl(ssl::CaInfo{crtPath + "ca-bundle.crt"}, ssl::CertBlob{std::move(crtBuffer)}, ssl::KeyFile{keyPath + "client.key"}, ssl::VerifyPeer{true}, ssl::VerifyHost{true}, ssl::VerifyStatus{false}); + Response response = cpr::Get(url, sslOpts, Timeout{5000}, Verbose{}); + std::string expected_text = "Hello world!"; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code) << response.error.message; +} +#endif + #if SUPPORT_CURLOPT_SSLKEY_BLOB TEST(SslTests, LoadKeyFromBlobTestSimpel) { std::this_thread::sleep_for(std::chrono::seconds(1));