|
5 | 5 | #include <curl/curl.h> |
6 | 6 | #include <iostream> |
7 | 7 | #include <memory> |
8 | | -#include <sstream> |
9 | | -#include <string> |
10 | 8 |
|
11 | 9 | #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION |
12 | 10 |
|
|
30 | 28 | #include <openssl/ossl_typ.h> |
31 | 29 | #endif |
32 | 30 |
|
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) |
35 | 34 | #include <openssl/pemerr.h> |
36 | 35 | #endif |
37 | 36 |
|
@@ -59,56 +58,80 @@ using custom_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>; |
59 | 58 | using x509_ptr = custom_unique_ptr<X509, X509_free>; |
60 | 59 | using bio_ptr = custom_unique_ptr<BIO, BIO_free>; |
61 | 60 |
|
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 | | - |
77 | 61 | CURLcode sslctx_function_load_ca_cert_from_buffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) { |
78 | 62 | // Check arguments |
79 | 63 | if (raw_cert_buf == nullptr || sslctx == nullptr) { |
80 | 64 | std::cerr << "Invalid callback arguments!\n"; |
81 | 65 | return CURLE_ABORTED_BY_CALLBACK; |
82 | 66 | } |
83 | 67 |
|
| 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 | + |
84 | 73 | // 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); |
101 | 108 | return CURLE_ABORTED_BY_CALLBACK; |
102 | 109 | } |
| 110 | + certs_loaded++; |
| 111 | + // Free cert so we can load another one |
| 112 | + X509_free(cert); |
| 113 | + cert = nullptr; |
| 114 | + } |
103 | 115 |
|
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); |
107 | 128 | return CURLE_ABORTED_BY_CALLBACK; |
108 | 129 | } |
109 | | - at_least_got_one = true; |
110 | 130 | } |
111 | 131 |
|
| 132 | + // Free the entire bio chain |
| 133 | + BIO_free(bio); |
| 134 | + |
112 | 135 | // The CA certificate was loaded successfully into the verification storage |
113 | 136 | return CURLE_OK; |
114 | 137 | } |
|
0 commit comments