Skip to content

Commit 2a393ff

Browse files
authored
Merge pull request #1224 from afjoseph/master
Load all certs in a CaBuffer
2 parents 59105d8 + 38ab39b commit 2a393ff

File tree

1 file changed

+62
-39
lines changed

1 file changed

+62
-39
lines changed

cpr/ssl_ctx.cpp

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
#include <curl/curl.h>
66
#include <iostream>
77
#include <memory>
8-
#include <sstream>
9-
#include <string>
108

119
#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION
1210

@@ -30,8 +28,9 @@
3028
#include <openssl/ossl_typ.h>
3129
#endif
3230

33-
// openssl/pemerr.h was added in 1.1.1a
34-
#if OPENSSL_VERSION_NUMBER >= 0x1010101fL
31+
// openssl/pemerr.h was added in 1.1.1a, but not in BoringSSL
32+
// Ref https://github.com/libcpr/cpr/issues/333#issuecomment-2425104338
33+
#if OPENSSL_VERSION_NUMBER >= 0x1010101fL && !defined(OPENSSL_IS_BORINGSSL)
3534
#include <openssl/pemerr.h>
3635
#endif
3736

@@ -59,56 +58,80 @@ using custom_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
5958
using x509_ptr = custom_unique_ptr<X509, X509_free>;
6059
using bio_ptr = custom_unique_ptr<BIO, BIO_free>;
6160

62-
namespace {
63-
inline std::string get_openssl_print_errors() {
64-
std::ostringstream oss;
65-
ERR_print_errors_cb(
66-
[](char const* str, size_t len, void* data) -> int {
67-
auto& oss = *static_cast<std::ostringstream*>(data);
68-
oss << str;
69-
return static_cast<int>(len);
70-
},
71-
&oss);
72-
return oss.str();
73-
}
74-
75-
} // namespace
76-
7761
CURLcode sslctx_function_load_ca_cert_from_buffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) {
7862
// Check arguments
7963
if (raw_cert_buf == nullptr || sslctx == nullptr) {
8064
std::cerr << "Invalid callback arguments!\n";
8165
return CURLE_ABORTED_BY_CALLBACK;
8266
}
8367

68+
// Create a memory BIO using the data of cert_buf
69+
// Note: It is assumed, that cert_buf is nul terminated and its length is determined by strlen
70+
char* cert_buf = static_cast<char*>(raw_cert_buf);
71+
BIO* bio = BIO_new_mem_buf(cert_buf, -1);
72+
8473
// Get a pointer to the current certificate verification storage
85-
auto* store = SSL_CTX_get_cert_store(static_cast<SSL_CTX*>(sslctx));
86-
87-
// Create a memory BIO using the data of cert_buf.
88-
// Note: It is assumed, that cert_buf is nul terminated and its length is determined by strlen.
89-
const bio_ptr bio{BIO_new_mem_buf(static_cast<char*>(raw_cert_buf), -1)};
90-
91-
bool at_least_got_one = false;
92-
for (;;) {
93-
// Load the PEM formatted certicifate into an X509 structure which OpenSSL can use.
94-
const x509_ptr x{PEM_read_bio_X509_AUX(bio.get(), nullptr, nullptr, nullptr)};
95-
if (x == nullptr) {
96-
if ((ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) && at_least_got_one) {
97-
ERR_clear_error();
98-
break;
99-
}
100-
std::cerr << "PEM_read_bio_X509_AUX failed: \n" << get_openssl_print_errors() << '\n';
74+
X509_STORE* store = SSL_CTX_get_cert_store(static_cast<SSL_CTX*>(sslctx));
75+
if (store == nullptr) {
76+
std::cerr << "SSL_CTX_get_cert_store failed!\n";
77+
ERR_print_errors_fp(stderr);
78+
BIO_free(bio);
79+
return CURLE_ABORTED_BY_CALLBACK;
80+
}
81+
82+
// Load the PEM formatted certicifate into an X509 structure which OpenSSL can use
83+
// PEM_read_bio_X509 can read multiple certificates from the same buffer in a loop.
84+
// The buffer should be in PEM format, which is a base64 encoded format
85+
// with header and footer lines like
86+
//
87+
// CA 1
88+
// ============
89+
// -----BEGIN CERTIFICATE-----
90+
// ... base64 data ...
91+
// -----END CERTIFICATE-----
92+
//
93+
// CA 2
94+
// ============
95+
// -----BEGIN CERTIFICATE-----
96+
// ... base64 data ...
97+
// -----END CERTIFICATE-----
98+
//
99+
size_t certs_loaded = 0;
100+
X509* cert = nullptr;
101+
while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr) {
102+
const int status = X509_STORE_add_cert(store, cert);
103+
// Fail if any loaded cert is invalid
104+
if (status == 0) {
105+
std::cerr << "[CPR] while adding certificate to store\n";
106+
ERR_print_errors_fp(stderr);
107+
BIO_free(bio);
101108
return CURLE_ABORTED_BY_CALLBACK;
102109
}
110+
certs_loaded++;
111+
// Free cert so we can load another one
112+
X509_free(cert);
113+
cert = nullptr;
114+
}
103115

104-
// Add the loaded certificate to the verification storage
105-
if (X509_STORE_add_cert(store, x.get()) == 0) {
106-
std::cerr << "X509_STORE_add_cert failed: \n" << get_openssl_print_errors() << '\n';
116+
// NOLINTNEXTLINE(google-runtime-int) Ignored here since it is an API return value
117+
const unsigned long err = ERR_peek_last_error();
118+
if (certs_loaded == 0 && err != 0) {
119+
// Check if the error is just EOF or an actual parsing error
120+
if (ERR_GET_LIB(err) == ERR_LIB_PEM && ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
121+
// This is expected if the buffer was empty or contains no valid
122+
// PEM certs
123+
std::cerr << "No PEM certificates found or end of stream\n";
124+
} else {
125+
std::cerr << "PEM_read_bio_X509 failed after loading " << certs_loaded << " certificates\n";
126+
ERR_print_errors_fp(stderr);
127+
BIO_free(bio);
107128
return CURLE_ABORTED_BY_CALLBACK;
108129
}
109-
at_least_got_one = true;
110130
}
111131

132+
// Free the entire bio chain
133+
BIO_free(bio);
134+
112135
// The CA certificate was loaded successfully into the verification storage
113136
return CURLE_OK;
114137
}

0 commit comments

Comments
 (0)