Skip to content

Commit e4e0903

Browse files
authored
impl: add experimental support for providing an SSL_CTX_FUNCTION (#15088)
1 parent 7076d20 commit e4e0903

File tree

4 files changed

+261
-50
lines changed

4 files changed

+261
-50
lines changed

google/cloud/internal/curl_handle_factory.cc

Lines changed: 91 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
#include "google/cloud/internal/curl_options.h"
1818
#include "google/cloud/internal/make_status.h"
1919
#include "google/cloud/log.h"
20+
#ifndef _WIN32
2021
#include <openssl/err.h>
2122
#include <openssl/ssl.h>
23+
#endif
2224
#include <algorithm>
2325
#include <iterator>
2426

@@ -42,7 +44,8 @@ struct X509InfoPtrCleanup {
4244
};
4345

4446
using X509InfoPtr = std::unique_ptr<STACK_OF(X509_INFO), X509InfoPtrCleanup>;
45-
47+
#else
48+
using SSL_CTX = void;
4649
#endif
4750

4851
Status SetCurlCAInMemory(CurlHandleFactory const& factory, SSL_CTX* ssl_ctx) {
@@ -91,7 +94,7 @@ Status SetCurlCAInMemory(CurlHandleFactory const& factory, SSL_CTX* ssl_ctx) {
9194

9295
extern "C" {
9396

94-
static CURLcode SslCtxFunction( // NOLINT(misc-use-anonymous-namespace)
97+
static CURLcode SslCtxCAInMemory( // NOLINT(misc-use-anonymous-namespace)
9598
CURL*, void* ssl_ctx, void* userdata) {
9699
auto* handle_factory = reinterpret_cast<CurlHandleFactory*>(userdata);
97100
auto result = SetCurlCAInMemory(*handle_factory, (SSL_CTX*)ssl_ctx);
@@ -101,6 +104,13 @@ static CURLcode SslCtxFunction( // NOLINT(misc-use-anonymous-namespace)
101104
}
102105
return CURLE_OK;
103106
}
107+
108+
static CURLcode SslCtxSanitized( // NOLINT(misc-use-anonymous-namespace)
109+
CURL*, void* ssl_ctx, void* userdata) {
110+
auto* handle_factory = reinterpret_cast<CurlHandleFactory*>(userdata);
111+
return (CURLcode)handle_factory->ssl_ctx_callback()(nullptr, ssl_ctx,
112+
nullptr);
113+
}
104114
}
105115

106116
void CurlHandleFactory::SetCurlStringOption(CURL* handle, CURLoption option_tag,
@@ -125,18 +135,24 @@ std::shared_ptr<CurlHandleFactory> GetDefaultCurlHandleFactory(
125135
}
126136

127137
DefaultCurlHandleFactory::DefaultCurlHandleFactory(Options const& o) {
138+
if (o.has<experimental::SslCtxCallbackOption>()) {
139+
ssl_ctx_callback_ = o.get<experimental::SslCtxCallbackOption>();
140+
return;
141+
}
142+
128143
if (o.has<experimental::CAInMemoryOption>()) {
129144
ca_certs_ = o.get<experimental::CAInMemoryOption>();
130145
if (ca_certs_.empty()) {
131146
GCP_LOG(FATAL) << internal::InvalidArgumentError(
132147
"No CA certificates specified", GCP_ERROR_INFO());
133148
}
134-
} else {
135-
if (o.has<CARootsFilePathOption>()) {
136-
cainfo_ = o.get<CARootsFilePathOption>();
137-
}
138-
if (o.has<CAPathOption>()) capath_ = o.get<CAPathOption>();
149+
return;
139150
}
151+
152+
if (o.has<CARootsFilePathOption>()) {
153+
cainfo_ = o.get<CARootsFilePathOption>();
154+
}
155+
if (o.has<CAPathOption>()) capath_ = o.get<CAPathOption>();
140156
}
141157

142158
CurlPtr DefaultCurlHandleFactory::CreateHandle() {
@@ -166,7 +182,7 @@ void DefaultCurlHandleFactory::CleanupMultiHandle(CurlMulti m,
166182
}
167183

168184
void DefaultCurlHandleFactory::SetCurlOptions(CURL* handle) {
169-
if (!ca_certs_.empty()) {
185+
if (ssl_ctx_callback_) {
170186
SetCurlStringOption(handle, CURLOPT_CAINFO, nullptr);
171187
SetCurlStringOption(handle, CURLOPT_CAPATH, nullptr);
172188
auto result = curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, this);
@@ -175,27 +191,61 @@ void DefaultCurlHandleFactory::SetCurlOptions(CURL* handle) {
175191
GCP_ERROR_INFO());
176192
}
177193
result =
178-
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxFunction);
194+
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxSanitized);
179195
if (result != CURLE_OK) {
180196
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
181197
GCP_ERROR_INFO());
182198
}
183-
} else {
184-
if (cainfo_) {
185-
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
199+
return;
200+
}
201+
202+
if (!ca_certs_.empty()) {
203+
SetCurlStringOption(handle, CURLOPT_CAINFO, nullptr);
204+
SetCurlStringOption(handle, CURLOPT_CAPATH, nullptr);
205+
auto result = curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, this);
206+
if (result != CURLE_OK) {
207+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
208+
GCP_ERROR_INFO());
186209
}
187-
if (capath_) {
188-
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
210+
result =
211+
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxCAInMemory);
212+
if (result != CURLE_OK) {
213+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
214+
GCP_ERROR_INFO());
189215
}
216+
return;
217+
}
218+
219+
if (cainfo_) {
220+
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
221+
}
222+
if (capath_) {
223+
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
190224
}
191225
}
192226

193227
PooledCurlHandleFactory::PooledCurlHandleFactory(std::size_t maximum_size,
194228
Options const& o)
195-
: maximum_size_(maximum_size),
196-
cainfo_(CAInfo(o)),
197-
capath_(CAPath(o)),
198-
ca_certs_(CACerts(o)) {}
229+
: maximum_size_(maximum_size) {
230+
if (o.has<experimental::SslCtxCallbackOption>()) {
231+
ssl_ctx_callback_ = o.get<experimental::SslCtxCallbackOption>();
232+
return;
233+
}
234+
235+
if (o.has<experimental::CAInMemoryOption>()) {
236+
ca_certs_ = o.get<experimental::CAInMemoryOption>();
237+
if (ca_certs_.empty()) {
238+
GCP_LOG(FATAL) << internal::InvalidArgumentError(
239+
"No CA certificates specified", GCP_ERROR_INFO());
240+
}
241+
return;
242+
}
243+
244+
if (o.has<CARootsFilePathOption>()) {
245+
cainfo_ = o.get<CARootsFilePathOption>();
246+
}
247+
if (o.has<CAPathOption>()) capath_ = o.get<CAPathOption>();
248+
}
199249

200250
PooledCurlHandleFactory::~PooledCurlHandleFactory() = default;
201251

@@ -303,7 +353,7 @@ void PooledCurlHandleFactory::CleanupMultiHandle(CurlMulti m,
303353
}
304354

305355
void PooledCurlHandleFactory::SetCurlOptions(CURL* handle) {
306-
if (!ca_certs_.empty()) {
356+
if (ssl_ctx_callback_) {
307357
SetCurlStringOption(handle, CURLOPT_CAINFO, nullptr);
308358
SetCurlStringOption(handle, CURLOPT_CAPATH, nullptr);
309359
auto result = curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, this);
@@ -312,39 +362,37 @@ void PooledCurlHandleFactory::SetCurlOptions(CURL* handle) {
312362
GCP_ERROR_INFO());
313363
}
314364
result =
315-
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxFunction);
365+
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxSanitized);
316366
if (result != CURLE_OK) {
317367
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
318368
GCP_ERROR_INFO());
319369
}
320-
} else {
321-
if (cainfo_) {
322-
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
370+
return;
371+
}
372+
373+
if (!ca_certs_.empty()) {
374+
SetCurlStringOption(handle, CURLOPT_CAINFO, nullptr);
375+
SetCurlStringOption(handle, CURLOPT_CAPATH, nullptr);
376+
auto result = curl_easy_setopt(handle, CURLOPT_SSL_CTX_DATA, this);
377+
if (result != CURLE_OK) {
378+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
379+
GCP_ERROR_INFO());
323380
}
324-
if (capath_) {
325-
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
381+
result =
382+
curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, &SslCtxCAInMemory);
383+
if (result != CURLE_OK) {
384+
GCP_LOG(FATAL) << internal::InternalError(curl_easy_strerror(result),
385+
GCP_ERROR_INFO());
326386
}
387+
return;
327388
}
328-
}
329-
330-
absl::optional<std::string> PooledCurlHandleFactory::CAInfo(Options const& o) {
331-
if (!o.has<CARootsFilePathOption>()) return absl::nullopt;
332-
return o.get<CARootsFilePathOption>();
333-
}
334-
335-
absl::optional<std::string> PooledCurlHandleFactory::CAPath(Options const& o) {
336-
if (!o.has<CAPathOption>()) return absl::nullopt;
337-
return o.get<CAPathOption>();
338-
}
339389

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());
390+
if (cainfo_) {
391+
SetCurlStringOption(handle, CURLOPT_CAINFO, cainfo_->c_str());
392+
}
393+
if (capath_) {
394+
SetCurlStringOption(handle, CURLOPT_CAPATH, capath_->c_str());
346395
}
347-
return o.get<experimental::CAInMemoryOption>();
348396
}
349397

350398
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END

google/cloud/internal/curl_handle_factory.h

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "google/cloud/internal/curl_wrappers.h"
1919
#include "google/cloud/options.h"
20+
#include "google/cloud/rest_options.h"
2021
#include "google/cloud/version.h"
2122
#include "absl/strings/string_view.h"
2223
#include "absl/types/optional.h"
@@ -57,6 +58,7 @@ class CurlHandleFactory {
5758
virtual absl::optional<std::string> cainfo() const = 0;
5859
virtual absl::optional<std::string> capath() const = 0;
5960
virtual std::vector<absl::string_view> const& ca_certs() const = 0;
61+
virtual experimental::SslCtxCallback ssl_ctx_callback() const = 0;
6062

6163
protected:
6264
// Only virtual for testing purposes.
@@ -96,6 +98,9 @@ class DefaultCurlHandleFactory : public CurlHandleFactory {
9698
std::vector<absl::string_view> const& ca_certs() const override {
9799
return ca_certs_;
98100
}
101+
experimental::SslCtxCallback ssl_ctx_callback() const override {
102+
return ssl_ctx_callback_;
103+
}
99104

100105
private:
101106
void SetCurlOptions(CURL* handle);
@@ -105,6 +110,7 @@ class DefaultCurlHandleFactory : public CurlHandleFactory {
105110
absl::optional<std::string> cainfo_;
106111
absl::optional<std::string> capath_;
107112
std::vector<absl::string_view> ca_certs_;
113+
experimental::SslCtxCallback ssl_ctx_callback_;
108114
};
109115

110116
/**
@@ -147,18 +153,19 @@ class PooledCurlHandleFactory : public CurlHandleFactory {
147153
std::vector<absl::string_view> const& ca_certs() const override {
148154
return ca_certs_;
149155
}
156+
experimental::SslCtxCallback ssl_ctx_callback() const override {
157+
return ssl_ctx_callback_;
158+
}
150159

151160
private:
152161
void SetCurlOptions(CURL* handle);
153-
static absl::optional<std::string> CAInfo(Options const& o);
154-
static absl::optional<std::string> CAPath(Options const& o);
155-
static std::vector<absl::string_view> CACerts(Options const& o);
156-
157162
// These are constant after initialization and thus need no locking.
158163
std::size_t const maximum_size_;
159-
absl::optional<std::string> const cainfo_;
160-
absl::optional<std::string> const capath_;
164+
165+
absl::optional<std::string> cainfo_;
166+
absl::optional<std::string> capath_;
161167
std::vector<absl::string_view> ca_certs_;
168+
experimental::SslCtxCallback ssl_ctx_callback_;
162169

163170
mutable std::mutex handles_mu_;
164171
std::deque<CurlPtr> handles_;

google/cloud/rest_options.h

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,48 @@
2525
namespace google {
2626
namespace cloud {
2727
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
28+
namespace experimental {
29+
30+
/**
31+
* Function signature for the libcurl SSL context callback.
32+
*
33+
* This signature matches the prototype declared by libcurl, but its invocation
34+
* is wrapped by the Cloud C++ SDK. This is a precaution to prevent the CURL
35+
* handle from being altered in ways that would cause the SDK to malfunction.
36+
*
37+
* The callback should return CURLE_OK on success and CURLE_ABORTED_BY_CALLBACK
38+
* on error.
39+
*
40+
* @note While the callback defines three pointer parameters, only the ssl_ctx
41+
* pointer will have a non-NULL value when the callback is called.
42+
*/
43+
using SslCtxCallback = std::function<int(void*, void* ssl_ctx, void*)>;
44+
45+
/**
46+
* This option allows the user to specify a function that is registered with
47+
* libcurl as the CURLOPT_SSL_CTX_FUNCTION.
48+
*
49+
* @note This is an advanced option and should only be used when other options
50+
* such as:
51+
* - CAInMemoryOption
52+
* - CAPathOption
53+
* - CARootsFilePathOption
54+
* - ClientSslCertificateOption
55+
* are insufficient.
56+
*
57+
* @note Setting this option causes the following Options to be ignored:
58+
* - CAInMemoryOption
59+
* - CAPathOption
60+
* - CARootsFilePathOption
61+
*
62+
* @note This Option is not currently supported on Windows.
63+
* @note This Option requires libcurl 7.10.6 or higher.
64+
*/
65+
struct SslCtxCallbackOption {
66+
using Type = SslCtxCallback;
67+
};
68+
69+
} // namespace experimental
2870

2971
/**
3072
* Timeout for the server to finish processing the request. This system param
@@ -62,7 +104,8 @@ struct Interface {
62104
/// The complete list of options accepted by `CurlRestClient`
63105
using RestOptionList =
64106
::google::cloud::OptionList<QuotaUserOption, RestTracingOptionsOption,
65-
ServerTimeoutOption, UserIpOption, Interface>;
107+
ServerTimeoutOption, UserIpOption, Interface,
108+
experimental::SslCtxCallbackOption>;
66109

67110
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
68111
} // namespace cloud

0 commit comments

Comments
 (0)