Skip to content

Commit 7076d20

Browse files
authored
impl: add experimental ca store in memory (#15068)
1 parent ea33e14 commit 7076d20

File tree

5 files changed

+393
-21
lines changed

5 files changed

+393
-21
lines changed

google/cloud/credentials.h

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "google/cloud/options.h"
2020
#include "google/cloud/ssl_certificate.h"
2121
#include "google/cloud/version.h"
22+
#include "absl/strings/string_view.h"
2223
#include <chrono>
2324
#include <memory>
2425
#include <string>
@@ -45,6 +46,25 @@ namespace experimental {
4546
struct ClientSslCertificateOption {
4647
using Type = SslCertificate;
4748
};
49+
50+
/**
51+
* Represents one or more certificates to be added to the CA store in lieu of
52+
* using any CA certificates stored on the filesystem.
53+
*
54+
* @note This option is currently experimental and only works with OpenSSL and
55+
* services using JSON/HTTP transport.
56+
*
57+
* @note Specifying this option disables reading any certificates that may exist
58+
* on the filesystem.
59+
*
60+
* @note Requires libcurl v7.10.6 or later.
61+
*
62+
* @note Not supported on Windows.
63+
*/
64+
struct CAInMemoryOption {
65+
using Type = std::vector<absl::string_view>;
66+
};
67+
4868
} // namespace experimental
4969

5070
/**
@@ -447,11 +467,10 @@ struct CARootsFilePathOption {
447467
};
448468

449469
/// A list of options related to authentication.
450-
using UnifiedCredentialsOptionList =
451-
OptionList<AccessTokenLifetimeOption, CARootsFilePathOption,
452-
DelegatesOption, ScopesOption, LoggingComponentsOption,
453-
UnifiedCredentialsOption,
454-
experimental::ClientSslCertificateOption>;
470+
using UnifiedCredentialsOptionList = OptionList<
471+
AccessTokenLifetimeOption, CARootsFilePathOption, DelegatesOption,
472+
ScopesOption, LoggingComponentsOption, UnifiedCredentialsOption,
473+
experimental::ClientSslCertificateOption, experimental::CAInMemoryOption>;
455474

456475
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
457476
} // namespace cloud

google/cloud/internal/curl_handle_factory.cc

Lines changed: 151 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,93 @@
1515
#include "google/cloud/internal/curl_handle_factory.h"
1616
#include "google/cloud/credentials.h"
1717
#include "google/cloud/internal/curl_options.h"
18+
#include "google/cloud/internal/make_status.h"
19+
#include "google/cloud/log.h"
20+
#include <openssl/err.h>
21+
#include <openssl/ssl.h>
1822
#include <algorithm>
1923
#include <iterator>
2024

2125
namespace google {
2226
namespace cloud {
2327
namespace rest_internal {
2428
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
29+
namespace {
30+
31+
#ifndef _WIN32
32+
struct BIOPtrCleanup {
33+
int operator()(BIO* b) const { return BIO_free(b); }
34+
};
35+
36+
using BioPtr = std::unique_ptr<BIO, BIOPtrCleanup>;
37+
38+
struct X509InfoPtrCleanup {
39+
void operator()(STACK_OF(X509_INFO) * i) const {
40+
return sk_X509_INFO_pop_free(i, X509_INFO_free);
41+
}
42+
};
43+
44+
using X509InfoPtr = std::unique_ptr<STACK_OF(X509_INFO), X509InfoPtrCleanup>;
45+
46+
#endif
47+
48+
Status SetCurlCAInMemory(CurlHandleFactory const& factory, SSL_CTX* ssl_ctx) {
49+
#if _WIN32
50+
return internal::InternalError(
51+
"SSL callback function currently not supported in windows",
52+
GCP_ERROR_INFO());
53+
#else
54+
X509_STORE* cert_store = SSL_CTX_get_cert_store(ssl_ctx);
55+
if (!cert_store) {
56+
return internal::InternalError("SSL_CTX_get_cert_store returned NULL",
57+
GCP_ERROR_INFO());
58+
}
59+
60+
// Add each of the provided certs to the store.
61+
for (auto const& cert : factory.ca_certs()) {
62+
BioPtr buf{BIO_new_mem_buf(cert.data(), static_cast<int>(cert.length()))};
63+
if (!buf) {
64+
return internal::InternalError("BIO_new_mem_buf returned NULL",
65+
GCP_ERROR_INFO());
66+
}
67+
X509InfoPtr info{
68+
PEM_X509_INFO_read_bio(buf.get(), nullptr, nullptr, nullptr)};
69+
if (!info) {
70+
return internal::InternalError("PEM_X509_INFO_read_bio returned NULL",
71+
GCP_ERROR_INFO());
72+
}
73+
74+
for (decltype(sk_X509_INFO_num(info.get())) i = 0;
75+
i < sk_X509_INFO_num(info.get()); ++i) {
76+
X509_INFO* value = sk_X509_INFO_value(info.get(), i);
77+
if (value->x509) {
78+
X509_STORE_add_cert(cert_store, value->x509);
79+
}
80+
if (value->crl) {
81+
X509_STORE_add_crl(cert_store, value->crl);
82+
}
83+
}
84+
}
85+
86+
return {};
87+
#endif
88+
}
89+
90+
} // namespace
91+
92+
extern "C" {
93+
94+
static CURLcode SslCtxFunction( // NOLINT(misc-use-anonymous-namespace)
95+
CURL*, void* ssl_ctx, void* userdata) {
96+
auto* handle_factory = reinterpret_cast<CurlHandleFactory*>(userdata);
97+
auto result = SetCurlCAInMemory(*handle_factory, (SSL_CTX*)ssl_ctx);
98+
if (!result.ok()) {
99+
GCP_LOG(ERROR) << result << "\n";
100+
return CURLE_ABORTED_BY_CALLBACK;
101+
}
102+
return CURLE_OK;
103+
}
104+
}
25105

26106
void CurlHandleFactory::SetCurlStringOption(CURL* handle, CURLoption option_tag,
27107
char const* value) {
@@ -36,15 +116,27 @@ std::shared_ptr<CurlHandleFactory> GetDefaultCurlHandleFactory() {
36116

37117
std::shared_ptr<CurlHandleFactory> GetDefaultCurlHandleFactory(
38118
Options const& options) {
39-
if (!options.get<CARootsFilePathOption>().empty()) {
119+
if (!options.get<CARootsFilePathOption>().empty() ||
120+
!options.get<experimental::CAInMemoryOption>().empty()) {
40121
return std::make_shared<DefaultCurlHandleFactory>(options);
41122
}
123+
42124
return GetDefaultCurlHandleFactory();
43125
}
44126

45127
DefaultCurlHandleFactory::DefaultCurlHandleFactory(Options const& o) {
46-
if (o.has<CARootsFilePathOption>()) cainfo_ = o.get<CARootsFilePathOption>();
47-
if (o.has<CAPathOption>()) capath_ = o.get<CAPathOption>();
128+
if (o.has<experimental::CAInMemoryOption>()) {
129+
ca_certs_ = o.get<experimental::CAInMemoryOption>();
130+
if (ca_certs_.empty()) {
131+
GCP_LOG(FATAL) << internal::InvalidArgumentError(
132+
"No CA certificates specified", GCP_ERROR_INFO());
133+
}
134+
} else {
135+
if (o.has<CARootsFilePathOption>()) {
136+
cainfo_ = o.get<CARootsFilePathOption>();
137+
}
138+
if (o.has<CAPathOption>()) capath_ = o.get<CAPathOption>();
139+
}
48140
}
49141

50142
CurlPtr DefaultCurlHandleFactory::CreateHandle() {
@@ -74,17 +166,36 @@ void DefaultCurlHandleFactory::CleanupMultiHandle(CurlMulti m,
74166
}
75167

76168
void DefaultCurlHandleFactory::SetCurlOptions(CURL* handle) {
77-
if (cainfo_) {
78-
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
79-
}
80-
if (capath_) {
81-
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
169+
if (!ca_certs_.empty()) {
170+
SetCurlStringOption(handle, CURLOPT_CAINFO, nullptr);
171+
SetCurlStringOption(handle, CURLOPT_CAPATH, nullptr);
172+
auto result = curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, this);
173+
if (result != CURLE_OK) {
174+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
175+
GCP_ERROR_INFO());
176+
}
177+
result =
178+
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxFunction);
179+
if (result != CURLE_OK) {
180+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
181+
GCP_ERROR_INFO());
182+
}
183+
} else {
184+
if (cainfo_) {
185+
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
186+
}
187+
if (capath_) {
188+
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
189+
}
82190
}
83191
}
84192

85193
PooledCurlHandleFactory::PooledCurlHandleFactory(std::size_t maximum_size,
86194
Options const& o)
87-
: maximum_size_(maximum_size), cainfo_(CAInfo(o)), capath_(CAPath(o)) {}
195+
: maximum_size_(maximum_size),
196+
cainfo_(CAInfo(o)),
197+
capath_(CAPath(o)),
198+
ca_certs_(CACerts(o)) {}
88199

89200
PooledCurlHandleFactory::~PooledCurlHandleFactory() = default;
90201

@@ -192,11 +303,27 @@ void PooledCurlHandleFactory::CleanupMultiHandle(CurlMulti m,
192303
}
193304

194305
void PooledCurlHandleFactory::SetCurlOptions(CURL* handle) {
195-
if (cainfo_) {
196-
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
197-
}
198-
if (capath_) {
199-
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
306+
if (!ca_certs_.empty()) {
307+
SetCurlStringOption(handle, CURLOPT_CAINFO, nullptr);
308+
SetCurlStringOption(handle, CURLOPT_CAPATH, nullptr);
309+
auto result = curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, this);
310+
if (result != CURLE_OK) {
311+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
312+
GCP_ERROR_INFO());
313+
}
314+
result =
315+
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxFunction);
316+
if (result != CURLE_OK) {
317+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
318+
GCP_ERROR_INFO());
319+
}
320+
} else {
321+
if (cainfo_) {
322+
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
323+
}
324+
if (capath_) {
325+
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
326+
}
200327
}
201328
}
202329

@@ -210,6 +337,16 @@ absl::optional<std::string> PooledCurlHandleFactory::CAPath(Options const& o) {
210337
return o.get<CAPathOption>();
211338
}
212339

340+
std::vector<absl::string_view> PooledCurlHandleFactory::CACerts(
341+
Options const& o) {
342+
if (!o.has<experimental::CAInMemoryOption>()) return {};
343+
if (o.get<experimental::CAInMemoryOption>().empty()) {
344+
GCP_LOG(FATAL) << internal::InvalidArgumentError(
345+
"No CA certificates specified", GCP_ERROR_INFO());
346+
}
347+
return o.get<experimental::CAInMemoryOption>();
348+
}
349+
213350
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
214351
} // namespace rest_internal
215352
} // namespace cloud

google/cloud/internal/curl_handle_factory.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
#include "google/cloud/internal/curl_wrappers.h"
1919
#include "google/cloud/options.h"
2020
#include "google/cloud/version.h"
21+
#include "absl/strings/string_view.h"
2122
#include "absl/types/optional.h"
2223
#include <deque>
2324
#include <mutex>
2425
#include <string>
26+
#include <vector>
2527

2628
namespace google {
2729
namespace cloud {
@@ -54,6 +56,7 @@ class CurlHandleFactory {
5456
// class is in `internal::`.
5557
virtual absl::optional<std::string> cainfo() const = 0;
5658
virtual absl::optional<std::string> capath() const = 0;
59+
virtual std::vector<absl::string_view> const& ca_certs() const = 0;
5760

5861
protected:
5962
// Only virtual for testing purposes.
@@ -90,6 +93,9 @@ class DefaultCurlHandleFactory : public CurlHandleFactory {
9093

9194
absl::optional<std::string> cainfo() const override { return cainfo_; }
9295
absl::optional<std::string> capath() const override { return capath_; }
96+
std::vector<absl::string_view> const& ca_certs() const override {
97+
return ca_certs_;
98+
}
9399

94100
private:
95101
void SetCurlOptions(CURL* handle);
@@ -98,6 +104,7 @@ class DefaultCurlHandleFactory : public CurlHandleFactory {
98104
std::string last_client_ip_address_;
99105
absl::optional<std::string> cainfo_;
100106
absl::optional<std::string> capath_;
107+
std::vector<absl::string_view> ca_certs_;
101108
};
102109

103110
/**
@@ -137,16 +144,21 @@ class PooledCurlHandleFactory : public CurlHandleFactory {
137144

138145
absl::optional<std::string> cainfo() const override { return cainfo_; }
139146
absl::optional<std::string> capath() const override { return capath_; }
147+
std::vector<absl::string_view> const& ca_certs() const override {
148+
return ca_certs_;
149+
}
140150

141151
private:
142152
void SetCurlOptions(CURL* handle);
143153
static absl::optional<std::string> CAInfo(Options const& o);
144154
static absl::optional<std::string> CAPath(Options const& o);
155+
static std::vector<absl::string_view> CACerts(Options const& o);
145156

146157
// These are constant after initialization and thus need no locking.
147158
std::size_t const maximum_size_;
148159
absl::optional<std::string> const cainfo_;
149160
absl::optional<std::string> const capath_;
161+
std::vector<absl::string_view> ca_certs_;
150162

151163
mutable std::mutex handles_mu_;
152164
std::deque<CurlPtr> handles_;

0 commit comments

Comments
 (0)