Skip to content

Commit b7ae0de

Browse files
committed
impl(rest): use PQC algorithm when available
1 parent ce70c7d commit b7ae0de

13 files changed

Lines changed: 357 additions & 5 deletions

google/cloud/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ cc_library(
241241
"@platforms//os:windows": [
242242
"-DEFAULTLIB:bcrypt.lib",
243243
"-DEFAULTLIB:crypt32.lib",
244+
"-DEFAULTLIB:ncrypt.lib",
244245
],
245246
"//conditions:default": [],
246247
}),

google/cloud/google_cloud_cpp_rest_internal.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ google_cloud_cpp_rest_internal_hdrs = [
7575
"internal/rest_retry_loop.h",
7676
"internal/rest_set_metadata.h",
7777
"internal/sign_using_sha256.h",
78+
"internal/ssl_ec_curves.h",
7879
"internal/tracing_http_payload.h",
7980
"internal/tracing_rest_client.h",
8081
"internal/tracing_rest_response.h",
@@ -122,6 +123,7 @@ google_cloud_cpp_rest_internal_srcs = [
122123
"internal/oauth2_universe_domain.cc",
123124
"internal/openssl/parse_service_account_p12_file.cc",
124125
"internal/openssl/sign_using_sha256.cc",
126+
"internal/openssl/ssl_ec_curves.cc",
125127
"internal/populate_rest_options.cc",
126128
"internal/rest_carrier.cc",
127129
"internal/rest_context.cc",
@@ -132,11 +134,13 @@ google_cloud_cpp_rest_internal_srcs = [
132134
"internal/rest_request.cc",
133135
"internal/rest_response.cc",
134136
"internal/rest_set_metadata.cc",
137+
"internal/ssl_ec_curves.cc",
135138
"internal/tracing_http_payload.cc",
136139
"internal/tracing_rest_client.cc",
137140
"internal/tracing_rest_response.cc",
138141
"internal/unified_rest_credentials.cc",
139142
"internal/win32/parse_service_account_p12_file.cc",
140143
"internal/win32/sign_using_sha256.cc",
144+
"internal/win32/ssl_ec_curves.cc",
141145
"internal/win32/win32_helpers.cc",
142146
]

google/cloud/google_cloud_cpp_rest_internal.cmake

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ add_library(
103103
internal/oauth2_universe_domain.h
104104
internal/openssl/parse_service_account_p12_file.cc
105105
internal/openssl/sign_using_sha256.cc
106+
internal/openssl/ssl_ec_curves.cc
106107
internal/parse_service_account_p12_file.h
107108
internal/populate_rest_options.cc
108109
internal/populate_rest_options.h
@@ -129,6 +130,8 @@ add_library(
129130
internal/rest_set_metadata.cc
130131
internal/rest_set_metadata.h
131132
internal/sign_using_sha256.h
133+
internal/ssl_ec_curves.cc
134+
internal/ssl_ec_curves.h
132135
internal/tracing_http_payload.cc
133136
internal/tracing_http_payload.h
134137
internal/tracing_rest_client.cc
@@ -139,6 +142,7 @@ add_library(
139142
internal/unified_rest_credentials.h
140143
internal/win32/parse_service_account_p12_file.cc
141144
internal/win32/sign_using_sha256.cc
145+
internal/win32/ssl_ec_curves.cc
142146
internal/win32/win32_helpers.cc
143147
internal/win32/win32_helpers.h
144148
rest_options.h)
@@ -152,7 +156,7 @@ if (WIN32)
152156
# We use `setsockopt()` directly, which requires the ws2_32 (Winsock2 for
153157
# Windows32?) library on Windows.
154158
target_link_libraries(google_cloud_cpp_rest_internal PUBLIC ws2_32 bcrypt
155-
crypt32)
159+
crypt32 ncrypt)
156160
else ()
157161
# We already require OpenSSL for non-Windows platforms.
158162
target_compile_definitions(
@@ -211,7 +215,8 @@ google_cloud_cpp_add_pkgconfig(
211215
WIN32_LIBS
212216
ws2_32
213217
bcrypt
214-
crypt32)
218+
crypt32
219+
ncrypt)
215220

216221
# Create and install the CMake configuration files.
217222
include(CMakePackageConfigHelpers)
@@ -303,6 +308,7 @@ if (BUILD_TESTING)
303308
internal/rest_response_test.cc
304309
internal/rest_retry_loop_test.cc
305310
internal/rest_set_metadata_test.cc
311+
internal/ssl_ec_curves_test.cc
306312
internal/tracing_http_payload_test.cc
307313
internal/tracing_rest_client_test.cc
308314
internal/tracing_rest_response_test.cc

google/cloud/google_cloud_cpp_rest_internal_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ google_cloud_cpp_rest_internal_unit_tests = [
6666
"internal/rest_response_test.cc",
6767
"internal/rest_retry_loop_test.cc",
6868
"internal/rest_set_metadata_test.cc",
69+
"internal/ssl_ec_curves_test.cc",
6970
"internal/tracing_http_payload_test.cc",
7071
"internal/tracing_rest_client_test.cc",
7172
"internal/tracing_rest_response_test.cc",

google/cloud/internal/curl_impl.cc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,15 @@ std::size_t SpillBuffer::MoveTo(absl::Span<char> dst) {
171171
CurlImpl::CurlImpl(CurlHandle handle,
172172
std::shared_ptr<CurlHandleFactory> factory,
173173
Options const& options)
174+
: CurlImpl(std::move(handle), std::move(factory), options, {}) {}
175+
176+
CurlImpl::CurlImpl(CurlHandle handle,
177+
std::shared_ptr<CurlHandleFactory> factory,
178+
Options const& options, std::string pqc_ec_curves)
174179
: factory_(std::move(factory)),
175180
handle_(std::move(handle)),
176-
multi_(factory_->CreateMultiHandle()) {
181+
multi_(factory_->CreateMultiHandle()),
182+
pqc_ec_curves_(std::move(pqc_ec_curves)) {
177183
CurlInitializeOnce(options);
178184

179185
logging_enabled_ = google::cloud::internal::Contains(
@@ -381,6 +387,13 @@ Status CurlImpl::MakeRequest(HttpMethod method, RestContext& context,
381387
if (!status.ok()) return OnTransferError(context, std::move(status));
382388
}
383389

390+
#if CURL_AT_LEAST_VERSION(7, 73, 0)
391+
if (!pqc_ec_curves_.empty()) {
392+
status = handle_.SetOption(CURLOPT_SSL_EC_CURVES, pqc_ec_curves_.c_str());
393+
if (!status.ok()) return OnTransferError(context, std::move(status));
394+
}
395+
#endif
396+
384397
if (client_ssl_cert_.has_value()) {
385398
#if CURL_AT_LEAST_VERSION(7, 71, 0)
386399
status = handle_.SetOption(CURLOPT_SSL_VERIFYPEER, 1L);

google/cloud/internal/curl_impl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class CurlImpl {
7575

7676
CurlImpl(CurlHandle handle, std::shared_ptr<CurlHandleFactory> factory,
7777
Options const& options);
78+
CurlImpl(CurlHandle handle, std::shared_ptr<CurlHandleFactory> factory,
79+
Options const& options, std::string pqc_ec_curves);
7880
~CurlImpl();
7981

8082
CurlImpl(CurlImpl const&) = delete;
@@ -195,6 +197,8 @@ class CurlImpl {
195197

196198
// Store pending data between WriteCallback() calls.
197199
SpillBuffer spill_;
200+
201+
std::string pqc_ec_curves_;
198202
};
199203

200204
/// Compute the CURLOPT_PROXY setting from @p options.

google/cloud/internal/curl_rest_client.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
#include "google/cloud/internal/curl_rest_response.h"
2222
#include "google/cloud/internal/oauth2_google_credentials.h"
2323
#include "google/cloud/internal/opentelemetry.h"
24+
#include "google/cloud/internal/ssl_ec_curves.h"
2425
#include "google/cloud/internal/tracing_rest_client.h"
2526
#include "google/cloud/internal/unified_rest_credentials.h"
27+
#include "google/cloud/log.h"
2628
#include "absl/strings/match.h"
2729
#include "absl/strings/str_cat.h"
2830
#include "absl/strings/str_join.h"
@@ -115,14 +117,22 @@ CurlRestClient::CurlRestClient(std::string endpoint_address,
115117
if (options_.has<UnifiedCredentialsOption>()) {
116118
credentials_ = MapCredentials(*options_.get<UnifiedCredentialsOption>());
117119
}
120+
auto pqc_curves = GetPqcEcCurves();
121+
if (pqc_curves.ok()) {
122+
pqc_ec_curves_ = *std::move(pqc_curves);
123+
} else {
124+
GCP_LOG(INFO)
125+
<< "Post-quantum cryptography (PQC) algorithms not available: "
126+
<< pqc_curves.status().message();
127+
}
118128
}
119129

120130
StatusOr<std::unique_ptr<CurlImpl>> CurlRestClient::CreateCurlImpl(
121131
RestContext const& context, RestRequest const& request,
122132
Options const& options) {
123133
auto handle = CurlHandle::MakeFromPool(*handle_factory_);
124-
auto impl =
125-
std::make_unique<CurlImpl>(std::move(handle), handle_factory_, options);
134+
auto impl = std::make_unique<CurlImpl>(std::move(handle), handle_factory_,
135+
options, pqc_ec_curves_);
126136
if (credentials_) {
127137
auto auth_headers = credentials_->AuthenticationHeaders(
128138
std::chrono::system_clock::now(), endpoint_address_);

google/cloud/internal/curl_rest_client.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class CurlRestClient : public RestClient {
7979
std::shared_ptr<CurlHandleFactory> handle_factory_;
8080
std::shared_ptr<oauth2_internal::Credentials> credentials_;
8181
Options options_;
82+
std::string pqc_ec_curves_;
8283
};
8384

8485
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef _WIN32
16+
#include "google/cloud/internal/ssl_ec_curves.h"
17+
#include <openssl/ec.h>
18+
#include <openssl/ssl.h>
19+
#include <array>
20+
21+
namespace google {
22+
namespace cloud {
23+
namespace rest_internal {
24+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
25+
26+
std::vector<std::string> AvailableCryptoGroups() {
27+
std::vector<std::string> groups;
28+
#if OPENSSL_VERSION_NUMBER >= 0x30500000L
29+
SSL_CTX* ctx = SSL_CTX_new(TLS_method());
30+
if (!ctx) return groups;
31+
STACK_OF(OPENSSL_CSTRING)* names = sk_OPENSSL_CSTRING_new_null();
32+
if (SSL_CTX_get0_implemented_groups(ctx, 1, names) > 0) {
33+
int const num_names = sk_OPENSSL_CSTRING_num(names);
34+
for (int i = 0; i < num_names; ++i) {
35+
char const* name = sk_OPENSSL_CSTRING_value(names, i);
36+
if (name != nullptr) groups.emplace_back(name);
37+
}
38+
}
39+
sk_OPENSSL_CSTRING_free(names);
40+
SSL_CTX_free(ctx);
41+
#else // OPENSSL_VERSION_NUMBER >= 0x30500000L
42+
size_t const num_curves = EC_get_builtin_curves(nullptr, 0);
43+
std::vector<EC_builtin_curve> builtin(num_curves);
44+
EC_get_builtin_curves(builtin.data(), num_curves);
45+
for (auto const& c : builtin) {
46+
char const* name = OBJ_nid2sn(c.nid);
47+
if (name != nullptr) groups.emplace_back(name);
48+
}
49+
#ifdef OPENSSL_IS_BORINGSSL
50+
// boringssl reports these curves as available, but then errors when trying to
51+
// use them. Remove them from the list.
52+
std::array<std::string_view, 1> constexpr kFiltered{"secp224r1"};
53+
auto const it = std::remove_if(groups.begin(), groups.end(), [&](auto name) {
54+
return std::find(kFiltered.begin(), kFiltered.end(), name) !=
55+
kFiltered.end();
56+
});
57+
groups.erase(it, groups.end());
58+
59+
groups.emplace_back("X25519");
60+
groups.emplace_back("X25519MLKEM768");
61+
groups.emplace_back("X25519Kyber768Draft00");
62+
#endif // OPENSSL_IS_BORINGSSL
63+
#endif // OPENSSL_VERSION_NUMBER >= 0x30500000L
64+
return groups;
65+
}
66+
67+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
68+
} // namespace rest_internal
69+
} // namespace cloud
70+
} // namespace google
71+
#endif // _WIN32
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/ssl_ec_curves.h"
16+
#include "google/cloud/internal/make_status.h"
17+
#include "absl/strings/match.h"
18+
#include "absl/strings/str_join.h"
19+
#include <curl/curl.h>
20+
#include <algorithm>
21+
#include <utility>
22+
23+
namespace google {
24+
namespace cloud {
25+
namespace rest_internal {
26+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
27+
namespace {
28+
29+
// libcurl encodes version Ma.Mi.Pa as (Ma << 16) | (Mi << 8) | Pa.
30+
// CURLOPT_SSL_EC_CURVES was introduced in libcurl version 7.73.0.
31+
constexpr unsigned int kMinimumLibcurlVersionForEcCurves =
32+
(7U << 16) | (73U << 8) | 0U;
33+
34+
bool SupportsSslEcCurves() {
35+
auto* vinfo = curl_version_info(CURLVERSION_NOW);
36+
return vinfo != nullptr &&
37+
vinfo->version_num >= kMinimumLibcurlVersionForEcCurves;
38+
}
39+
40+
} // namespace
41+
42+
StatusOr<std::string> PrependPqcEcCurve(std::vector<std::string> groups,
43+
bool supports_ssl_ec_curves) {
44+
if (!supports_ssl_ec_curves) {
45+
return internal::UnavailableError(
46+
"libcurl version 7.73.0 or later is required to set SSL EC curves.");
47+
}
48+
49+
std::string const target = "X25519MLKEM768";
50+
auto it = std::find_if(groups.begin(), groups.end(),
51+
[&target](std::string const& s) {
52+
return absl::EqualsIgnoreCase(s, target);
53+
});
54+
55+
if (it == groups.end()) {
56+
return internal::UnavailableError(
57+
"X25519MLKEM768 is not supported by the underlying crypto library.");
58+
}
59+
60+
std::string exact_name = *it;
61+
groups.erase(it);
62+
groups.insert(groups.begin(), std::move(exact_name));
63+
64+
return absl::StrJoin(groups, ":");
65+
}
66+
67+
StatusOr<std::string> GetPqcEcCurves() {
68+
return PrependPqcEcCurve(AvailableCryptoGroups(), SupportsSslEcCurves());
69+
}
70+
71+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
72+
} // namespace rest_internal
73+
} // namespace cloud
74+
} // namespace google

0 commit comments

Comments
 (0)