diff --git a/.gitignore b/.gitignore index 1b563a4a4faa9..e4ab153f432d6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ cmake-build-*/ # `google-cloud-cpp` developers use this file to configure the development # workflow build. .cloudcxxrc + +# Ignore the file with user-defined CMake presets +CMakeUserPresets.json diff --git a/ci/cloudbuild/builds/cmake-install.sh b/ci/cloudbuild/builds/cmake-install.sh index 605d614605bb4..269369c1d0e65 100755 --- a/ci/cloudbuild/builds/cmake-install.sh +++ b/ci/cloudbuild/builds/cmake-install.sh @@ -94,6 +94,7 @@ expected_dirs+=( ./include/google/cloud/gkehub/v1/multiclusteringress ./include/google/cloud/grpc_utils ./include/google/cloud/internal + ./include/google/cloud/internal/win32 # no RPC services in google/cloud/metastore/logging ./include/google/cloud/metastore/logging ./include/google/cloud/metastore/logging/v1 diff --git a/cmake/AddPkgConfig.cmake b/cmake/AddPkgConfig.cmake index 9eca93113dfe2..0712cc1761b9c 100644 --- a/cmake/AddPkgConfig.cmake +++ b/cmake/AddPkgConfig.cmake @@ -42,7 +42,10 @@ endmacro () # * ARGN: the names of any pkgconfig modules the generated module depends on # function (google_cloud_cpp_add_pkgconfig library name description) - cmake_parse_arguments(_opt "WITH_SHORT_TARGET" "" "" ${ARGN}) + cmake_parse_arguments( + _opt "WITH_SHORT_TARGET" "" + "LIBS;WIN32_LIBS;NON_WIN32_LIBS;WIN32_REQUIRES;NON_WIN32_REQUIRES" + ${ARGN}) if (_opt_WITH_SHORT_TARGET) set(target "${library}") else () @@ -60,6 +63,24 @@ function (google_cloud_cpp_add_pkgconfig library name description) else () set(GOOGLE_CLOUD_CPP_PC_LIBS "-lgoogle_cloud_cpp_${library}") endif () + list(TRANSFORM _opt_LIBS PREPEND "-l" OUTPUT_VARIABLE _opt_LIBS) + string(JOIN " " GOOGLE_CLOUD_CPP_PC_LIBS "${GOOGLE_CLOUD_CPP_PC_LIBS}" + ${_opt_LIBS}) + if (WIN32) + list(TRANSFORM _opt_WIN32_LIBS PREPEND "-l" OUTPUT_VARIABLE + _opt_WIN32_LIBS) + string(JOIN " " GOOGLE_CLOUD_CPP_PC_LIBS "${GOOGLE_CLOUD_CPP_PC_LIBS}" + ${_opt_WIN32_LIBS}) + string(JOIN " " GOOGLE_CLOUD_CPP_PC_REQUIRES + "${GOOGLE_CLOUD_CPP_PC_REQUIRES}" ${_opt_WIN32_REQUIRES}) + else () + list(TRANSFORM _opt_NON_WIN32_LIBS PREPEND "-l" OUTPUT_VARIABLE + _opt_NON_WIN32_LIBS) + string(JOIN " " GOOGLE_CLOUD_CPP_PC_LIBS "${GOOGLE_CLOUD_CPP_PC_LIBS}" + ${_opt_NON_WIN32_LIBS}) + string(JOIN " " GOOGLE_CLOUD_CPP_PC_REQUIRES + "${GOOGLE_CLOUD_CPP_PC_REQUIRES}" ${_opt_NON_WIN32_REQUIRES}) + endif () get_target_property(target_defs ${target} INTERFACE_COMPILE_DEFINITIONS) if (target_defs) foreach (def ${target_defs}) diff --git a/google/cloud/BUILD.bazel b/google/cloud/BUILD.bazel index 5ba7166df13ed..33cb29a85ee6a 100644 --- a/google/cloud/BUILD.bazel +++ b/google/cloud/BUILD.bazel @@ -76,6 +76,19 @@ cc_library( ], "//conditions:default": [], }), + linkopts = select({ + "@platforms//os:windows": [ + "-DEFAULTLIB:bcrypt.lib", + ], + "//conditions:default": [], + }), + local_defines = select({ + "@platforms//os:windows": [ + "WIN32_LEAN_AND_MEAN", + "_WIN32_WINNT=0x0A00", + ], + "//conditions:default": [], + }), target_compatible_with = select( { ":enable_opentelemetry_valid": [], @@ -99,7 +112,6 @@ to your build command, or set this value in your `.bazelrc` file(s). "//:__pkg__", ], deps = [ - "@boringssl//:crypto", "@com_google_absl//absl/base", "@com_google_absl//absl/functional:function_ref", "@com_google_absl//absl/strings", @@ -113,6 +125,11 @@ to your build command, or set this value in your `.bazelrc` file(s). "@io_opentelemetry_cpp//api", ], "//conditions:default": [], + }) + select({ + "@platforms//os:windows": [], + "//conditions:default": [ + "@boringssl//:crypto", + ], }), ) @@ -265,24 +282,34 @@ cc_library( name = "google_cloud_cpp_rest_internal", srcs = google_cloud_cpp_rest_internal_srcs, hdrs = google_cloud_cpp_rest_internal_hdrs, - # These are needed to use the BoringSSL libraries, and should be set in - # any downstream dependency too. - defines = select({ + linkopts = select({ + "@platforms//os:windows": [ + "-DEFAULTLIB:bcrypt.lib", + "-DEFAULTLIB:crypt32.lib", + ], + "//conditions:default": [], + }), + local_defines = select({ "@platforms//os:windows": [ "WIN32_LEAN_AND_MEAN", + "_WIN32_WINNT=0x0A00", ], "//conditions:default": [], }), visibility = ["//:__subpackages__"], deps = [ ":google_cloud_cpp_common", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_github_nlohmann_json//:nlohmann_json", "@com_google_absl//absl/functional:function_ref", "@com_google_absl//absl/types:span", - ], + ] + select({ + "@platforms//os:windows": [], + "//conditions:default": [ + "@boringssl//:crypto", + "@boringssl//:ssl", + ], + }), ) [cc_test( diff --git a/google/cloud/config-rest.cmake.in b/google/cloud/config-rest.cmake.in index 0e712e688ff20..7d1a7e908da2e 100644 --- a/google/cloud/config-rest.cmake.in +++ b/google/cloud/config-rest.cmake.in @@ -17,6 +17,8 @@ find_dependency(google_cloud_cpp_common) find_dependency(absl) find_dependency(CURL) find_dependency(nlohmann_json) -find_dependency(OpenSSL) +if (NOT WIN32) + find_dependency(OpenSSL) +endif () include("${CMAKE_CURRENT_LIST_DIR}/google_cloud_cpp_rest_internal-targets.cmake") diff --git a/google/cloud/google_cloud_cpp_common.cmake b/google/cloud/google_cloud_cpp_common.cmake index 5bbc510e183d0..c6126b4b9f757 100644 --- a/google/cloud/google_cloud_cpp_common.cmake +++ b/google/cloud/google_cloud_cpp_common.cmake @@ -14,7 +14,9 @@ # limitations under the License. # ~~~ -find_package(OpenSSL REQUIRED) +if (NOT WIN32) + find_package(OpenSSL REQUIRED) +endif () # Generate the version information from the CMake values. configure_file(internal/version_info.h.in @@ -179,8 +181,14 @@ target_link_libraries( absl::str_format absl::time absl::variant - Threads::Threads - OpenSSL::Crypto) + Threads::Threads) +if (WIN32) + target_compile_definitions(google_cloud_cpp_common + PRIVATE WIN32_LEAN_AND_MEAN) + target_link_libraries(google_cloud_cpp_common PUBLIC bcrypt) +else () + target_link_libraries(google_cloud_cpp_common PUBLIC OpenSSL::Crypto) +endif () if (opentelemetry IN_LIST GOOGLE_CLOUD_CPP_ENABLE) find_package(opentelemetry-cpp CONFIG) @@ -249,8 +257,11 @@ google_cloud_cpp_add_pkgconfig( "absl_time" "absl_time_zone" "absl_variant" - "openssl" - "${GOOGLE_CLOUD_CPP_OPENTELEMETRY_API}") + "${GOOGLE_CLOUD_CPP_OPENTELEMETRY_API}" + NON_WIN32_REQUIRES + openssl + WIN32_LIBS + bcrypt) # Create and install the CMake configuration files. configure_file("config.cmake.in" "google_cloud_cpp_common-config.cmake" @ONLY) diff --git a/google/cloud/google_cloud_cpp_rest_internal.bzl b/google/cloud/google_cloud_cpp_rest_internal.bzl index 6fa9eb6ee0aef..109186a31c932 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.bzl +++ b/google/cloud/google_cloud_cpp_rest_internal.bzl @@ -54,7 +54,7 @@ google_cloud_cpp_rest_internal_hdrs = [ "internal/oauth2_refreshing_credentials_wrapper.h", "internal/oauth2_service_account_credentials.h", "internal/oauth2_universe_domain.h", - "internal/openssl_util.h", + "internal/parse_service_account_p12_file.h", "internal/populate_rest_options.h", "internal/rest_carrier.h", "internal/rest_client.h", @@ -65,10 +65,12 @@ google_cloud_cpp_rest_internal_hdrs = [ "internal/rest_request.h", "internal/rest_response.h", "internal/rest_retry_loop.h", + "internal/sign_using_sha256.h", "internal/tracing_http_payload.h", "internal/tracing_rest_client.h", "internal/tracing_rest_response.h", "internal/unified_rest_credentials.h", + "internal/win32/win32_helpers.h", "rest_options.h", ] @@ -105,7 +107,8 @@ google_cloud_cpp_rest_internal_srcs = [ "internal/oauth2_refreshing_credentials_wrapper.cc", "internal/oauth2_service_account_credentials.cc", "internal/oauth2_universe_domain.cc", - "internal/openssl_util.cc", + "internal/openssl/parse_service_account_p12_file.cc", + "internal/openssl/sign_using_sha256.cc", "internal/populate_rest_options.cc", "internal/rest_carrier.cc", "internal/rest_context.cc", @@ -117,4 +120,7 @@ google_cloud_cpp_rest_internal_srcs = [ "internal/tracing_rest_client.cc", "internal/tracing_rest_response.cc", "internal/unified_rest_credentials.cc", + "internal/win32/parse_service_account_p12_file.cc", + "internal/win32/sign_using_sha256.cc", + "internal/win32/win32_helpers.cc", ] diff --git a/google/cloud/google_cloud_cpp_rest_internal.cmake b/google/cloud/google_cloud_cpp_rest_internal.cmake index 5da42523bffd8..66a511e11d905 100644 --- a/google/cloud/google_cloud_cpp_rest_internal.cmake +++ b/google/cloud/google_cloud_cpp_rest_internal.cmake @@ -16,7 +16,9 @@ include(IncludeNlohmannJson) find_package(CURL REQUIRED) -find_package(OpenSSL REQUIRED) +if (NOT WIN32) + find_package(OpenSSL REQUIRED) +endif () # the library add_library( @@ -90,8 +92,9 @@ add_library( internal/oauth2_service_account_credentials.h internal/oauth2_universe_domain.cc internal/oauth2_universe_domain.h - internal/openssl_util.cc - internal/openssl_util.h + internal/openssl/parse_service_account_p12_file.cc + internal/openssl/sign_using_sha256.cc + internal/parse_service_account_p12_file.h internal/populate_rest_options.cc internal/populate_rest_options.h internal/rest_carrier.cc @@ -109,6 +112,7 @@ add_library( internal/rest_response.cc internal/rest_response.h internal/rest_retry_loop.h + internal/sign_using_sha256.h internal/tracing_http_payload.cc internal/tracing_http_payload.h internal/tracing_rest_client.cc @@ -117,15 +121,25 @@ add_library( internal/tracing_rest_response.h internal/unified_rest_credentials.cc internal/unified_rest_credentials.h + internal/win32/parse_service_account_p12_file.cc + internal/win32/sign_using_sha256.cc + internal/win32/win32_helpers.cc + internal/win32/win32_helpers.h rest_options.h) target_link_libraries( google_cloud_cpp_rest_internal PUBLIC absl::span google-cloud-cpp::common CURL::libcurl - nlohmann_json::nlohmann_json OpenSSL::SSL OpenSSL::Crypto) + nlohmann_json::nlohmann_json) if (WIN32) + target_compile_definitions(google_cloud_cpp_rest_internal + PRIVATE WIN32_LEAN_AND_MEAN) # We use `setsockopt()` directly, which requires the ws2_32 (Winsock2 for # Windows32?) library on Windows. - target_link_libraries(google_cloud_cpp_rest_internal PUBLIC ws2_32) + target_link_libraries(google_cloud_cpp_rest_internal PUBLIC ws2_32 bcrypt + crypt32) +else () + target_link_libraries(google_cloud_cpp_rest_internal PUBLIC OpenSSL::SSL + OpenSSL::Crypto) endif () google_cloud_cpp_add_common_options(google_cloud_cpp_rest_internal) target_include_directories( @@ -165,9 +179,17 @@ google_cloud_cpp_install_headers(google_cloud_cpp_rest_internal include/google/cloud) google_cloud_cpp_add_pkgconfig( - rest_internal "REST library for the Google Cloud C++ Client Library" + rest_internal + "REST library for the Google Cloud C++ Client Library" "Provides REST Transport for the Google Cloud C++ Client Library." - "google_cloud_cpp_common" "libcurl" "openssl") + "google_cloud_cpp_common" + "libcurl" + NON_WIN32_REQUIRES + openssl + WIN32_LIBS + ws2_32 + bcrypt + crypt32) # Create and install the CMake configuration files. include(CMakePackageConfigHelpers) diff --git a/google/cloud/internal/curl_wrappers.cc b/google/cloud/internal/curl_wrappers.cc index 71dd98a666ac9..7e9aef6137728 100644 --- a/google/cloud/internal/curl_wrappers.cc +++ b/google/cloud/internal/curl_wrappers.cc @@ -20,8 +20,10 @@ #include "google/cloud/log.h" #include "absl/strings/match.h" #include "absl/strings/str_split.h" +#ifndef _WIN32 #include #include +#endif #include #include #include @@ -40,6 +42,9 @@ namespace { // LibreSSL calls itself OpenSSL > 2.0, but it really is based on SSL 1.0.2 // and requires locks. #define GOOGLE_CLOUD_CPP_SSL_REQUIRES_LOCKS 1 +#elif defined(_WIN32) +// We don't use OpenSSL on Windows. +#define GOOGLE_CLOUD_CPP_SSL_REQUIRES_LOCKS 0 #elif OPENSSL_VERSION_NUMBER < 0x10100000L // Older than version 1.1.0 // Before 1.1.0 OpenSSL requires locks to be used by multiple threads. #define GOOGLE_CLOUD_CPP_SSL_REQUIRES_LOCKS 1 diff --git a/google/cloud/internal/curl_wrappers_locking_already_present_test.cc b/google/cloud/internal/curl_wrappers_locking_already_present_test.cc index 114863f9460df..9c06b99191042 100644 --- a/google/cloud/internal/curl_wrappers_locking_already_present_test.cc +++ b/google/cloud/internal/curl_wrappers_locking_already_present_test.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifndef _WIN32 #include "google/cloud/internal/curl_options.h" #include "google/cloud/internal/curl_wrappers.h" #include @@ -42,3 +43,4 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace rest_internal } // namespace cloud } // namespace google +#endif // _WIN32 diff --git a/google/cloud/internal/make_jwt_assertion.cc b/google/cloud/internal/make_jwt_assertion.cc index 756330b445527..eacea0bec5adc 100644 --- a/google/cloud/internal/make_jwt_assertion.cc +++ b/google/cloud/internal/make_jwt_assertion.cc @@ -14,7 +14,7 @@ #include "google/cloud/internal/make_jwt_assertion.h" #include "google/cloud/internal/base64_transforms.h" -#include "google/cloud/internal/openssl_util.h" +#include "google/cloud/internal/sign_using_sha256.h" namespace google { namespace cloud { diff --git a/google/cloud/internal/oauth2_google_credentials.cc b/google/cloud/internal/oauth2_google_credentials.cc index 2014975f84bc1..b0a366cf12aef 100644 --- a/google/cloud/internal/oauth2_google_credentials.cc +++ b/google/cloud/internal/oauth2_google_credentials.cc @@ -22,6 +22,7 @@ #include "google/cloud/internal/oauth2_google_application_default_credentials_file.h" #include "google/cloud/internal/oauth2_http_client_factory.h" #include "google/cloud/internal/oauth2_service_account_credentials.h" +#include "google/cloud/internal/parse_service_account_p12_file.h" #include "google/cloud/internal/throw_delegate.h" #include #include diff --git a/google/cloud/internal/oauth2_service_account_credentials.cc b/google/cloud/internal/oauth2_service_account_credentials.cc index 50a34c3822db4..b54da63d1d97e 100644 --- a/google/cloud/internal/oauth2_service_account_credentials.cc +++ b/google/cloud/internal/oauth2_service_account_credentials.cc @@ -19,22 +19,14 @@ #include "google/cloud/internal/make_status.h" #include "google/cloud/internal/oauth2_google_credentials.h" #include "google/cloud/internal/oauth2_universe_domain.h" -#include "google/cloud/internal/openssl_util.h" #include "google/cloud/internal/rest_response.h" +#include "google/cloud/internal/sign_using_sha256.h" #include -#include -#include -#include namespace google { namespace cloud { namespace oauth2_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN -namespace { - -auto constexpr kP12PrivateKeyIdMarker = "--unknown--"; - -} // namespace using ::google::cloud::internal::MakeJWTAssertionNoThrow; @@ -240,121 +232,13 @@ StatusOr ServiceAccountCredentials::universe_domain( return universe_domain(); } -#include "google/cloud/internal/disable_msvc_crt_secure_warnings.inc" -StatusOr ParseServiceAccountP12File( - std::string const& source) { - OpenSSL_add_all_algorithms(); - - PKCS12* p12_raw = [](std::string const& source) { - auto bio = std::unique_ptr( - BIO_new_file(source.c_str(), "rb"), &BIO_free); - if (!bio) return static_cast(nullptr); - return d2i_PKCS12_bio(bio.get(), nullptr); - }(source); - - std::unique_ptr p12(p12_raw, &PKCS12_free); - - auto capture_openssl_errors = []() { - std::string msg; - while (auto code = ERR_get_error()) { - // OpenSSL guarantees that 256 bytes is enough: - // https://www.openssl.org/docs/man1.1.1/man3/ERR_error_string_n.html - // https://www.openssl.org/docs/man1.0.2/man3/ERR_error_string_n.html - // we could not find a macro or constant to replace the 256 literal. - auto constexpr kMaxOpenSslErrorLength = 256; - std::array buf{}; - ERR_error_string_n(code, buf.data(), buf.size()); - msg += buf.data(); - } - return msg; - }; - - if (p12 == nullptr) { - std::string msg = "Cannot open PKCS#12 file (" + source + "): "; - msg += capture_openssl_errors(); - return Status(StatusCode::kInvalidArgument, msg); - } - - EVP_PKEY* pkey_raw; - X509* cert_raw; - if (PKCS12_parse(p12.get(), "notasecret", &pkey_raw, &cert_raw, nullptr) != - 1) { - std::string msg = "Cannot parse PKCS#12 file (" + source + "): "; - msg += capture_openssl_errors(); - return Status(StatusCode::kInvalidArgument, msg); - } - - std::unique_ptr pkey(pkey_raw, - &EVP_PKEY_free); - std::unique_ptr cert(cert_raw, &X509_free); - - if (pkey_raw == nullptr) { - return Status(StatusCode::kInvalidArgument, - "No private key found in PKCS#12 file (" + source + ")"); - } - if (cert_raw == nullptr) { - return Status(StatusCode::kInvalidArgument, - "No private key found in PKCS#12 file (" + source + ")"); - } - - // This is automatically deleted by `cert`. - X509_NAME* name = X509_get_subject_name(cert.get()); - - std::string service_account_id = [&name]() -> std::string { - auto openssl_free = [](void* addr) { OPENSSL_free(addr); }; - std::unique_ptr oneline( - X509_NAME_oneline(name, nullptr, 0), openssl_free); - // We expect the name to be simply CN/ followed by a (small) number of - // digits. - if (strncmp("/CN=", oneline.get(), 4) != 0) { - return ""; - } - return oneline.get() + 4; - }(); - - if (service_account_id.find_first_not_of("0123456789") != std::string::npos || - service_account_id.empty()) { - return Status( - StatusCode::kInvalidArgument, - "Invalid PKCS#12 file (" + source + - "): service account id missing or not not formatted correctly"); - } - - std::unique_ptr mem_io(BIO_new(BIO_s_mem()), - &BIO_free); - - if (PEM_write_bio_PKCS8PrivateKey(mem_io.get(), pkey.get(), nullptr, nullptr, - 0, nullptr, nullptr) == 0) { - std::string msg = - "Cannot print private key in PKCS#12 file (" + source + "): "; - msg += capture_openssl_errors(); - return Status(StatusCode::kUnknown, msg); - } - - // This buffer belongs to the BIO chain and is freed upon its destruction. - BUF_MEM* buf_mem; - BIO_get_mem_ptr(mem_io.get(), &buf_mem); - - std::string private_key(buf_mem->data, buf_mem->length); - - return ServiceAccountCredentialsInfo{std::move(service_account_id), - kP12PrivateKeyIdMarker, - std::move(private_key), - GoogleOAuthRefreshEndpoint(), - /*scopes=*/{}, - /*subject=*/{}, - /*enable_self_signed_jwt=*/false, - /*universe_domain=*/{}}; -} -#include "google/cloud/internal/diagnostics_pop.inc" - bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info) { // Custom universe domains are only supported with JWT, not OAuth tokens. if (info.universe_domain.has_value() && info.universe_domain != GoogleDefaultUniverseDomain()) { return false; } - if (info.private_key_id == kP12PrivateKeyIdMarker || + if (info.private_key_id == P12PrivateKeyIdMarker() || !info.enable_self_signed_jwt) { return true; } diff --git a/google/cloud/internal/oauth2_service_account_credentials.h b/google/cloud/internal/oauth2_service_account_credentials.h index 4302b03a3d5a7..77178bba60cda 100644 --- a/google/cloud/internal/oauth2_service_account_credentials.h +++ b/google/cloud/internal/oauth2_service_account_credentials.h @@ -31,6 +31,8 @@ namespace cloud { namespace oauth2_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +inline char const* P12PrivateKeyIdMarker() { return "--unknown--"; } + /** * Overrides the token uri provided by the service account credentials key * file. @@ -57,10 +59,6 @@ struct ServiceAccountCredentialsInfo { /// OAuth2. bool ServiceAccountUseOAuth(ServiceAccountCredentialsInfo const& info); -/// Parses the contents of a P12 keyfile into a ServiceAccountCredentialsInfo. -StatusOr ParseServiceAccountP12File( - std::string const& source); - /// Parses the contents of a JSON keyfile into a ServiceAccountCredentialsInfo. StatusOr ParseServiceAccountCredentials( std::string const& content, std::string const& source, diff --git a/google/cloud/internal/oauth2_service_account_credentials_test.cc b/google/cloud/internal/oauth2_service_account_credentials_test.cc index bcd16776b8b6d..f8b8a8fccfa84 100644 --- a/google/cloud/internal/oauth2_service_account_credentials_test.cc +++ b/google/cloud/internal/oauth2_service_account_credentials_test.cc @@ -16,8 +16,8 @@ #include "google/cloud/internal/base64_transforms.h" #include "google/cloud/internal/oauth2_credential_constants.h" #include "google/cloud/internal/oauth2_universe_domain.h" -#include "google/cloud/internal/openssl_util.h" #include "google/cloud/internal/random.h" +#include "google/cloud/internal/sign_using_sha256.h" #include "google/cloud/testing_util/chrono_output.h" #include "google/cloud/testing_util/mock_http_payload.h" #include "google/cloud/testing_util/mock_rest_client.h" diff --git a/google/cloud/internal/openssl/parse_service_account_p12_file.cc b/google/cloud/internal/openssl/parse_service_account_p12_file.cc new file mode 100644 index 0000000000000..674fd10facbd9 --- /dev/null +++ b/google/cloud/internal/openssl/parse_service_account_p12_file.cc @@ -0,0 +1,138 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _WIN32 +#include "google/cloud/internal/parse_service_account_p12_file.h" +#include +#include +#include +#include +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +StatusOr ParseServiceAccountP12File( + std::string const& source) { + OpenSSL_add_all_algorithms(); + + PKCS12* p12_raw = [](std::string const& source) { + auto bio = std::unique_ptr( + BIO_new_file(source.c_str(), "rb"), &BIO_free); + if (!bio) return static_cast(nullptr); + return d2i_PKCS12_bio(bio.get(), nullptr); + }(source); + + std::unique_ptr p12(p12_raw, &PKCS12_free); + + auto capture_openssl_errors = []() { + std::string msg; + while (auto code = ERR_get_error()) { + // OpenSSL guarantees that 256 bytes is enough: + // https://www.openssl.org/docs/man1.1.1/man3/ERR_error_string_n.html + // https://www.openssl.org/docs/man1.0.2/man3/ERR_error_string_n.html + // we could not find a macro or constant to replace the 256 literal. + auto constexpr kMaxOpenSslErrorLength = 256; + std::array buf{}; + ERR_error_string_n(code, buf.data(), buf.size()); + msg += buf.data(); + } + return msg; + }; + + if (p12 == nullptr) { + std::string msg = "Cannot open PKCS#12 file (" + source + "): "; + msg += capture_openssl_errors(); + return Status(StatusCode::kInvalidArgument, msg); + } + + EVP_PKEY* pkey_raw; + X509* cert_raw; + if (PKCS12_parse(p12.get(), "notasecret", &pkey_raw, &cert_raw, nullptr) != + 1) { + std::string msg = "Cannot parse PKCS#12 file (" + source + "): "; + msg += capture_openssl_errors(); + return Status(StatusCode::kInvalidArgument, msg); + } + + std::unique_ptr pkey(pkey_raw, + &EVP_PKEY_free); + std::unique_ptr cert(cert_raw, &X509_free); + + if (pkey_raw == nullptr) { + return Status(StatusCode::kInvalidArgument, + "No private key found in PKCS#12 file (" + source + ")"); + } + if (cert_raw == nullptr) { + return Status(StatusCode::kInvalidArgument, + "No certificate found in PKCS#12 file (" + source + ")"); + } + + // This is automatically deleted by `cert`. + X509_NAME* name = X509_get_subject_name(cert.get()); + + std::string service_account_id = [&name]() -> std::string { + auto openssl_free = [](void* addr) { OPENSSL_free(addr); }; + std::unique_ptr oneline( + X509_NAME_oneline(name, nullptr, 0), openssl_free); + // We expect the name to be simply CN/ followed by a (small) number of + // digits. + if (strncmp("/CN=", oneline.get(), 4) != 0) { + return ""; + } + return oneline.get() + 4; + }(); + + if (service_account_id.find_first_not_of("0123456789") != std::string::npos || + service_account_id.empty()) { + return Status( + StatusCode::kInvalidArgument, + "Invalid PKCS#12 file (" + source + + "): service account id missing or not not formatted correctly"); + } + + std::unique_ptr mem_io(BIO_new(BIO_s_mem()), + &BIO_free); + + if (PEM_write_bio_PKCS8PrivateKey(mem_io.get(), pkey.get(), nullptr, nullptr, + 0, nullptr, nullptr) == 0) { + std::string msg = + "Cannot print private key in PKCS#12 file (" + source + "): "; + msg += capture_openssl_errors(); + return Status(StatusCode::kUnknown, msg); + } + + // This buffer belongs to the BIO chain and is freed upon its destruction. + BUF_MEM* buf_mem; + BIO_get_mem_ptr(mem_io.get(), &buf_mem); + + std::string private_key(buf_mem->data, buf_mem->length); + + return ServiceAccountCredentialsInfo{std::move(service_account_id), + P12PrivateKeyIdMarker(), + std::move(private_key), + GoogleOAuthRefreshEndpoint(), + /*scopes=*/{}, + /*subject=*/{}, + /*enable_self_signed_jwt=*/false, + /*universe_domain=*/{}}; +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google +#endif diff --git a/google/cloud/internal/openssl_util.cc b/google/cloud/internal/openssl/sign_using_sha256.cc similarity index 97% rename from google/cloud/internal/openssl_util.cc rename to google/cloud/internal/openssl/sign_using_sha256.cc index 32e8fcb14c10d..c4aab6073283c 100644 --- a/google/cloud/internal/openssl_util.cc +++ b/google/cloud/internal/openssl/sign_using_sha256.cc @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "google/cloud/internal/openssl_util.h" +#ifndef _WIN32 +#include "google/cloud/internal/sign_using_sha256.h" #include "google/cloud/internal/base64_transforms.h" +#include "google/cloud/internal/make_status.h" #include #include #include @@ -154,3 +156,4 @@ StatusOr> SignUsingSha256( GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace cloud } // namespace google +#endif diff --git a/google/cloud/internal/parse_service_account_p12_file.h b/google/cloud/internal/parse_service_account_p12_file.h new file mode 100644 index 0000000000000..a9056deeb32f1 --- /dev/null +++ b/google/cloud/internal/parse_service_account_p12_file.h @@ -0,0 +1,37 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_PARSE_SERVICE_ACCOUNT_P12_FILE_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_PARSE_SERVICE_ACCOUNT_P12_FILE_H + +#include "google/cloud/internal/oauth2_service_account_credentials.h" +#include "google/cloud/status_or.h" +#include "google/cloud/version.h" +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +/// Parses the contents of a P12 keyfile into a ServiceAccountCredentialsInfo. +StatusOr ParseServiceAccountP12File( + std::string const& source); + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_PARSE_SERVICE_ACCOUNT_P12_FILE_H diff --git a/google/cloud/internal/sha256_hash.cc b/google/cloud/internal/sha256_hash.cc index 91c77d02b2121..1485c7ce45912 100644 --- a/google/cloud/internal/sha256_hash.cc +++ b/google/cloud/internal/sha256_hash.cc @@ -13,7 +13,12 @@ // limitations under the License. #include "google/cloud/internal/sha256_hash.h" +#ifdef _WIN32 +#include +#include +#else #include +#endif // _WIN32 #include namespace google { @@ -23,14 +28,21 @@ namespace internal { namespace { Sha256Type Sha256Hash(void const* data, std::size_t count) { - std::array digest; Sha256Type hash; +#ifdef _WIN32 + BCryptHash(BCRYPT_SHA256_ALG_HANDLE, nullptr, 0, + static_cast(const_cast(data)), + static_cast(count), hash.data(), + static_cast(hash.size())); +#else + std::array digest; static_assert(EVP_MAX_MD_SIZE >= hash.size(), "EVP_MAX_MD_SIZE is too small"); unsigned int size = 0; EVP_Digest(data, count, digest.data(), &size, EVP_sha256(), nullptr); std::copy_n(digest.begin(), std::min(size, hash.size()), hash.begin()); +#endif // _WIN32 return hash; } } // namespace diff --git a/google/cloud/internal/sha256_hmac.cc b/google/cloud/internal/sha256_hmac.cc index f190101cf2e4d..015649fa7465f 100644 --- a/google/cloud/internal/sha256_hmac.cc +++ b/google/cloud/internal/sha256_hmac.cc @@ -13,8 +13,13 @@ // limitations under the License. #include "google/cloud/internal/sha256_hmac.h" +#ifdef _WIN32 +#include +#include +#else #include #include +#endif // _WIN32 #include #include #include @@ -25,25 +30,32 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN namespace internal { namespace { -static_assert(EVP_MAX_MD_SIZE >= Sha256Type().size(), - "EVP_MAX_MD_SIZE is too small"); static_assert(std::is_same::value, "When `std::uint8_t` exists it must be `unsigned char`"); template Sha256Type Sha256HmacImpl(absl::Span key, unsigned char const* data, std::size_t count) { - std::array digest; Sha256Type hash; +#ifdef _WIN32 + BCryptHash(BCRYPT_HMAC_SHA256_ALG_HANDLE, + reinterpret_cast(const_cast(key.data())), + static_cast(key.size()), const_cast(data), + static_cast(count), hash.data(), + static_cast(hash.size())); +#else + static_assert(EVP_MAX_MD_SIZE >= Sha256Type().size(), + "EVP_MAX_MD_SIZE is too small"); + std::array digest; unsigned int size = 0; HMAC(EVP_sha256(), key.data(), static_cast(key.size()), data, count, digest.data(), &size); std::copy_n(digest.begin(), std::min(size, hash.size()), hash.begin()); +#endif return hash; } - } // namespace Sha256Type Sha256Hmac(std::string const& key, absl::Span data) { diff --git a/google/cloud/internal/openssl_util.h b/google/cloud/internal/sign_using_sha256.h similarity index 86% rename from google/cloud/internal/openssl_util.h rename to google/cloud/internal/sign_using_sha256.h index 44791c91fd1b9..b6975d37b8d31 100644 --- a/google/cloud/internal/openssl_util.h +++ b/google/cloud/internal/sign_using_sha256.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OPENSSL_UTIL_H -#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OPENSSL_UTIL_H +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_SIGN_USING_SHA256_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_SIGN_USING_SHA256_H #include "google/cloud/status_or.h" #include "google/cloud/version.h" @@ -41,4 +41,4 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace cloud } // namespace google -#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_OPENSSL_UTIL_H +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_SIGN_USING_SHA256_H diff --git a/google/cloud/internal/win32/parse_service_account_p12_file.cc b/google/cloud/internal/win32/parse_service_account_p12_file.cc new file mode 100644 index 0000000000000..e1a1103a40cca --- /dev/null +++ b/google/cloud/internal/win32/parse_service_account_p12_file.cc @@ -0,0 +1,328 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef _WIN32 +#include "google/cloud/internal/parse_service_account_p12_file.h" +#include "google/cloud/internal/make_status.h" +#include "google/cloud/internal/win32/win32_helpers.h" +#include "absl/types/span.h" +#include +#include +#include +#include +#include + +namespace google { +namespace cloud { +namespace oauth2_internal { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +using ::google::cloud::internal::FormatWin32Errors; +using ::google::cloud::internal::InvalidArgumentError; + +namespace { + +struct HCertStoreDeleter { + void operator()(HCERTSTORE store) const { CertCloseStore(store, 0); } +}; + +using UniqueCertStore = + std::unique_ptr, HCertStoreDeleter>; + +struct CertContextDeleter { + void operator()(PCCERT_CONTEXT cert) const { + CertFreeCertificateContext(cert); + } +}; + +using UniqueCertContext = + std::unique_ptr, CertContextDeleter>; + +/// A wrapper around `std::unique_ptr` that has a get() method that returns a +/// native integer type. This is needed because some Windows API handles are +/// declared as native integers instead of pointers. +template +class UniquePtrReinterpretProxy { + static_assert(sizeof(T) == sizeof(void*), + "T must be the same size as a pointer"); + + public: + explicit UniquePtrReinterpretProxy(T ptr) + : ptr_(reinterpret_cast(ptr), {}) {} + + T get() const { return reinterpret_cast(ptr_.get()); } + + private: + std::unique_ptr ptr_; +}; + +struct HCryptProvDeleter { + void operator()(void* key) const { + CryptReleaseContext(reinterpret_cast(key), 0); + } +}; + +struct HCryptKeyDeleter { + void operator()(void* key) const { + CryptDestroyKey(reinterpret_cast(key)); + } +}; + +using UniqueCryptProv = + UniquePtrReinterpretProxy; + +using UniqueCryptKey = UniquePtrReinterpretProxy; + +StatusOr OpenP12File(std::string const& source) { + // Read the PKCS#12 file into memory. + std::vector data; + { + std::ifstream file(source, std::ios::binary); + if (!file.is_open()) { + return InvalidArgumentError( + absl::StrCat("Cannot open PKCS#12 file (", source, ")"), + GCP_ERROR_INFO()); + } + data.assign(std::istreambuf_iterator{file}, {}); + if (file.bad()) { + return InvalidArgumentError( + absl::StrCat("Cannot read PKCS#12 file (", source, ")"), + GCP_ERROR_INFO()); + } + } + // DWORD is 32-bits big while size_t can have a size of 64 bits. + // Both types are unsigned, which means that casting will not be + // undefined behavior if the size is > 4GB. Such huge p12 files + // should not practically exist, and if encountered the OS will + // get a truncated view of them, and fail to read them. + CRYPT_DATA_BLOB dataBlob = {static_cast(data.size()), data.data()}; + // Import the PKCS#12 file into a certificate store. + HCERTSTORE certstore_raw = + PFXImportCertStore(&dataBlob, L"notasecret", CRYPT_EXPORTABLE); + if (certstore_raw == nullptr) { + return InvalidArgumentError( + FormatWin32Errors("Cannot parse PKCS#12 file (", source, "): "), + GCP_ERROR_INFO()); + } + return UniqueCertStore(certstore_raw); +} + +StatusOr GetCertificate(HCERTSTORE certstore, + std::string const& source) { + // Get the certificate from the store. + PCCERT_CONTEXT cert_raw = CertFindCertificateInStore( + certstore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, + nullptr, nullptr); + if (cert_raw == nullptr) { + return InvalidArgumentError( + absl::StrCat("No certificate found in PKCS#12 file (", source, ")"), + GCP_ERROR_INFO()); + } + return UniqueCertContext(cert_raw); +} + +std::string GetCertificateCommonName(PCCERT_CONTEXT cert) { + DWORD size = CertGetNameStringA(cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + nullptr, nullptr, 0); + std::string result(size, '\0'); + CertGetNameStringA(cert, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, + &result[0], size); + result.pop_back(); // remove the null terminator + return result; +} + +StatusOr GetCertificatePrivateKey(PCCERT_CONTEXT cert, + DWORD& dwKeySpec, + std::string const& source) { + HCRYPTPROV prov_raw; + BOOL pfCallerFreeProvOrNCryptKey; + if (!CryptAcquireCertificatePrivateKey(cert, CRYPT_ACQUIRE_SILENT_FLAG, + nullptr, &prov_raw, &dwKeySpec, + &pfCallerFreeProvOrNCryptKey)) { + return InvalidArgumentError( + FormatWin32Errors("No private key found in PKCS#12 file (", source, + "): "), + GCP_ERROR_INFO()); + } + // According to documentation of CryptAcquireCertificatePrivateKey, + // pfCallerFreeProvOrNCryptKey will always be true in our case so + // we don't need to check it. + return UniqueCryptProv(prov_raw); +} + +StatusOr GetKeyFromProvider(HCRYPTPROV prov, DWORD dwKeySpec, + std::string const& source) { + HCRYPTKEY pkey_raw; + if (!CryptGetUserKey(prov, dwKeySpec, &pkey_raw)) { + return InvalidArgumentError( + FormatWin32Errors("No private key found in PKCS#12 file (", source, + "): "), + GCP_ERROR_INFO()); + } + return UniqueCryptKey(pkey_raw); +} + +StatusOr> ExportPrivateKey(HCRYPTKEY pkey, + std::string const& source) { + DWORD exported_key_length; + if (!CryptExportKey(pkey, 0, PRIVATEKEYBLOB, 0, nullptr, + &exported_key_length)) { + return InvalidArgumentError( + FormatWin32Errors("Could not export private key from PKCS#12 file (", + source, "): "), + GCP_ERROR_INFO()); + } + std::vector exported_key(exported_key_length); + // We don't have to check again for errors; we already did in the previous + // call to get the buffer size. Same with calls to CryptEncodeObjectEx and + // CryptBinaryToStringA below. + CryptExportKey(pkey, 0, PRIVATEKEYBLOB, 0, exported_key.data(), + &exported_key_length); + return exported_key; +} + +StatusOr> EncodeRsaPrivateKey( + absl::Span exported_key, std::string const& source) { + // Encode the blob to PKCS#1 format. + DWORD pkcs1_encoded_length; + if (!CryptEncodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, exported_key.data(), 0, + nullptr, nullptr, &pkcs1_encoded_length)) { + return InvalidArgumentError( + FormatWin32Errors("Could not encode private key from PKCS#12 file (", + source, "): "), + GCP_ERROR_INFO()); + } + std::vector pkcs1_encoded(pkcs1_encoded_length); + CryptEncodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, exported_key.data(), 0, nullptr, + pkcs1_encoded.data(), &pkcs1_encoded_length); + return pkcs1_encoded; +} + +StatusOr> EncodeRsaPkcs8PrivateKey( + absl::Span pkcs1_encoded, std::string const& source) { + CRYPT_PRIVATE_KEY_INFO private_key_info; + private_key_info.Version = 0; + private_key_info.Algorithm.pszObjId = szOID_RSA_RSA; + private_key_info.Algorithm.Parameters.cbData = 0; + private_key_info.Algorithm.Parameters.pbData = nullptr; + private_key_info.PrivateKey.cbData = static_cast(pkcs1_encoded.size()); + private_key_info.PrivateKey.pbData = const_cast(pkcs1_encoded.data()); + private_key_info.pAttributes = nullptr; + DWORD pkcs8_encoded_length; + if (!CryptEncodeObjectEx(X509_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, + &private_key_info, 0, nullptr, nullptr, + &pkcs8_encoded_length)) { + return InvalidArgumentError( + FormatWin32Errors("Could not encode private key from PKCS#12 file (", + source, "): "), + GCP_ERROR_INFO()); + } + std::vector pkcs8_encoded(pkcs8_encoded_length); + CryptEncodeObjectEx(X509_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, + &private_key_info, 0, nullptr, pkcs8_encoded.data(), + &pkcs8_encoded_length); + return pkcs8_encoded; +} + +StatusOr> Base64Encode(absl::Span blob, + std::string const& source) { + DWORD base64_length; + if (!CryptBinaryToStringA(blob.data(), static_cast(blob.size()), + CRYPT_STRING_BASE64 | CRYPT_STRING_NOCR, nullptr, + &base64_length)) { + return InvalidArgumentError( + FormatWin32Errors( + "Could not base64 encode private key from PKCS#12 file (", source, + "): "), + GCP_ERROR_INFO()); + } + std::vector private_key(base64_length); + CryptBinaryToStringA(blob.data(), static_cast(blob.size()), + CRYPT_STRING_BASE64 | CRYPT_STRING_NOCR, + private_key.data(), &base64_length); + private_key.pop_back(); // remove the null terminator + return private_key; +} + +} // namespace + +StatusOr ParseServiceAccountP12File( + std::string const& source) { + // Open the PKCS#12 file. + auto certstore = OpenP12File(source); + if (!certstore) return std::move(certstore).status(); + + // Get the certificate from the store. + auto cert = GetCertificate(certstore->get(), source); + if (!cert) return std::move(cert).status(); + + // Get the service account ID from the certificate's common name. + std::string service_account_id = GetCertificateCommonName(cert->get()); + + // Validate the service account ID. + if (service_account_id.find_first_not_of("0123456789") != std::string::npos || + service_account_id.empty()) { + return InvalidArgumentError( + absl::StrCat( + "Invalid PKCS#12 file (", source, + "): service account id missing or not not formatted correctly"), + GCP_ERROR_INFO()); + } + + // Get a provider that has the private key of the certificate. + // Make sure that the provider outlives the HCRYPTKEY. + DWORD dwKeySpec; + auto prov = GetCertificatePrivateKey(cert->get(), dwKeySpec, source); + if (!prov) return std::move(prov).status(); + + // Get the private key from the provider. + auto pkey = GetKeyFromProvider(prov->get(), dwKeySpec, source); + if (!pkey) return std::move(pkey).status(); + + // Export the private key from the certificate to a blob. + auto exported_key = ExportPrivateKey(pkey->get(), source); + if (!exported_key) return std::move(exported_key).status(); + + // Encode the blob to PKCS#1 format. + auto pkcs1_encoded = EncodeRsaPrivateKey(*exported_key, source); + if (!pkcs1_encoded) return std::move(pkcs1_encoded).status(); + + // Wrap the PKCS#1 encoded private key in a PKCS#8 structure. + auto pkcs8_encoded = EncodeRsaPkcs8PrivateKey(*pkcs1_encoded, source); + if (!pkcs8_encoded) return std::move(pkcs8_encoded).status(); + + // Convert to base64. + auto private_key = Base64Encode(*pkcs8_encoded, source); + if (!private_key) return std::move(private_key).status(); + + return ServiceAccountCredentialsInfo{ + std::move(service_account_id), + P12PrivateKeyIdMarker(), + absl::StrCat("-----BEGIN PRIVATE KEY-----\n", + absl::string_view(private_key->data(), private_key->size()), + "-----END PRIVATE KEY-----\n"), + GoogleOAuthRefreshEndpoint(), + /*scopes=*/{}, + /*subject=*/{}, + /*enable_self_signed_jwt=*/false, + /*universe_domain=*/{}}; +} + +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace oauth2_internal +} // namespace cloud +} // namespace google +#endif diff --git a/google/cloud/internal/win32/sign_using_sha256.cc b/google/cloud/internal/win32/sign_using_sha256.cc new file mode 100644 index 0000000000000..3abcfad869cfc --- /dev/null +++ b/google/cloud/internal/win32/sign_using_sha256.cc @@ -0,0 +1,158 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef _WIN32 +#include "google/cloud/internal/sign_using_sha256.h" +#include "google/cloud/internal/absl_str_cat_quiet.h" +#include "google/cloud/internal/make_status.h" +#include "google/cloud/internal/sha256_hash.h" +#include "google/cloud/internal/win32/win32_helpers.h" +#include "absl/strings/string_view.h" +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace cloud { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace internal { + +namespace { +StatusOr> DecodePem(std::string const& pem_contents) { + DWORD buffer_size = 0; + if (!CryptStringToBinaryA( + pem_contents.c_str(), static_cast(pem_contents.size()), + CRYPT_STRING_BASE64HEADER, nullptr, &buffer_size, nullptr, nullptr)) { + return InvalidArgumentError( + FormatWin32Errors( + "Invalid ServiceAccountCredentials - could not parse PEM to " + "get private key: "), + GCP_ERROR_INFO()); + } + std::vector buffer(buffer_size); + CryptStringToBinaryA( + pem_contents.c_str(), static_cast(pem_contents.size()), + CRYPT_STRING_BASE64HEADER, buffer.data(), &buffer_size, nullptr, nullptr); + return buffer; +} + +StatusOr> GetCngPrivateKeyBlobFromPkcsBuffer( + std::vector pkcs8_buffer) { + PCRYPT_PRIVATE_KEY_INFO private_key_info_raw; + DWORD private_key_info_size; + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, + pkcs8_buffer.data(), static_cast(pkcs8_buffer.size()), + CRYPT_DECODE_NOCOPY_FLAG | CRYPT_DECODE_ALLOC_FLAG, nullptr, + &private_key_info_raw, &private_key_info_size)) { + return InvalidArgumentError( + FormatWin32Errors( + "Invalid ServiceAccountCredentials - could not parse PKCS#8 to " + "get private key: "), + GCP_ERROR_INFO()); + } + std::unique_ptr + private_key_info(private_key_info_raw, &LocalFree); + if (absl::string_view(private_key_info->Algorithm.pszObjId) != + szOID_RSA_RSA) { + return InvalidArgumentError( + absl::StrCat("Invalid ServiceAccountCredentials - not an RSA key, " + "algorithm is: ", + absl::string_view(private_key_info->Algorithm.pszObjId)), + GCP_ERROR_INFO()); + } + DWORD rsa_blob_size; + if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + CNG_RSA_PRIVATE_KEY_BLOB, + private_key_info->PrivateKey.pbData, + private_key_info->PrivateKey.cbData, 0, nullptr, + nullptr, &rsa_blob_size)) { + return InvalidArgumentError( + FormatWin32Errors( + "Invalid ServiceAccountCredentials - could not decode RSA key: "), + GCP_ERROR_INFO()); + } + std::vector rsa_blob(rsa_blob_size); + CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CNG_RSA_PRIVATE_KEY_BLOB, + private_key_info->PrivateKey.pbData, private_key_info->PrivateKey.cbData, + 0, nullptr, rsa_blob.data(), &rsa_blob_size); + return rsa_blob; +} + +StatusOr, + decltype(&BCryptDestroyKey)>> +CreateRsaBCryptKey(std::vector buffer) { + BCRYPT_KEY_HANDLE key_handle; + if (BCryptImportKeyPair(BCRYPT_RSA_ALG_HANDLE, nullptr, + BCRYPT_RSAPRIVATE_BLOB, &key_handle, buffer.data(), + static_cast(buffer.size()), + 0) != STATUS_SUCCESS) { + return InvalidArgumentError( + FormatWin32Errors( + "Invalid ServiceAccountCredentials - could not import RSA key: "), + GCP_ERROR_INFO()); + } + return std::unique_ptr, + decltype(&BCryptDestroyKey)>(key_handle, + &BCryptDestroyKey); +} + +StatusOr> SignSha256Digest(BCRYPT_KEY_HANDLE key, + Sha256Type const& digest) { + DWORD signature_size; + BCRYPT_PKCS1_PADDING_INFO padding_info; + padding_info.pszAlgId = BCRYPT_SHA256_ALGORITHM; + if (BCryptSignHash(key, &padding_info, const_cast(digest.data()), + static_cast(digest.size()), nullptr, 0, + &signature_size, BCRYPT_PAD_PKCS1) != STATUS_SUCCESS) { + return InvalidArgumentError( + FormatWin32Errors( + "Invalid ServiceAccountCredentials - could not sign blob: "), + GCP_ERROR_INFO()); + } + std::vector signature(signature_size); + BCryptSignHash(key, &padding_info, const_cast(digest.data()), + static_cast(digest.size()), signature.data(), + signature_size, &signature_size, BCRYPT_PAD_PKCS1); + return signature; +} + +} // namespace + +StatusOr> SignUsingSha256( + std::string const& str, std::string const& pem_contents) { + auto pem_buffer = DecodePem(pem_contents); + if (!pem_buffer) return std::move(pem_buffer).status(); + + auto rsa_blob = GetCngPrivateKeyBlobFromPkcsBuffer(std::move(*pem_buffer)); + if (!rsa_blob) return std::move(rsa_blob).status(); + + auto key = CreateRsaBCryptKey(std::move(*rsa_blob)); + if (!key) return std::move(key).status(); + + auto hash = Sha256Hash(str); + + return SignSha256Digest(key->get(), hash); +} + +} // namespace internal +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace cloud +} // namespace google +#endif // _WIN32 diff --git a/google/cloud/internal/win32/win32_helpers.cc b/google/cloud/internal/win32/win32_helpers.cc new file mode 100644 index 0000000000000..8771283337d08 --- /dev/null +++ b/google/cloud/internal/win32/win32_helpers.cc @@ -0,0 +1,46 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef _WIN32 +#include "google/cloud/internal/win32/win32_helpers.h" +#include +#include + +namespace google { +namespace cloud { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace internal { + +static_assert(std::is_same_v, + "DWORD is not unsigned long"); + +std::string FormatWin32ErrorsImpl( + absl::FunctionRef f) { + auto last_error = GetLastError(); + LPSTR message_buffer_raw = nullptr; + auto size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&message_buffer_raw, 0, nullptr); + std::unique_ptr message_buffer(message_buffer_raw, + &LocalFree); + return f(absl::string_view(message_buffer.get(), size), last_error); +} + +} // namespace internal +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace cloud +} // namespace google +#endif // _WIN32 diff --git a/google/cloud/internal/win32/win32_helpers.h b/google/cloud/internal/win32/win32_helpers.h new file mode 100644 index 0000000000000..bd9384e6432b3 --- /dev/null +++ b/google/cloud/internal/win32/win32_helpers.h @@ -0,0 +1,53 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_WIN32_WIN32_HELPERS_H +#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_WIN32_WIN32_HELPERS_H + +#ifdef _WIN32 +#include "google/cloud/internal/absl_str_cat_quiet.h" +#include "google/cloud/version.h" +#include "absl/functional/function_ref.h" +#include "absl/strings/string_view.h" +#include + +namespace google { +namespace cloud { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace internal { + +std::string FormatWin32ErrorsImpl( + absl::FunctionRef); + +/** + * Formats the last Win32 error into a human-readable string. + * + * @param prefixes A list of string-like objects to prepend to the error + * message. + */ +template +std::string FormatWin32Errors(AV&&... prefixes) { + return FormatWin32ErrorsImpl([&](absl::string_view msg, unsigned long ec) { + return absl::StrCat(std::forward(prefixes)..., msg, " (error code ", ec, + ")"); + }); +} + +} // namespace internal +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace cloud +} // namespace google +#endif // _WIN32 + +#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_WIN32_WIN32_HELPERS_H diff --git a/google/cloud/oauth2/config.cmake.in b/google/cloud/oauth2/config.cmake.in index 0bb410af137f8..6db02a4a4304f 100644 --- a/google/cloud/oauth2/config.cmake.in +++ b/google/cloud/oauth2/config.cmake.in @@ -18,6 +18,5 @@ find_dependency(google_cloud_cpp_rest_internal) find_dependency(absl) find_dependency(CURL) find_dependency(nlohmann_json) -find_dependency(OpenSSL) include("${CMAKE_CURRENT_LIST_DIR}/google_cloud_cpp_oauth2-targets.cmake") diff --git a/google/cloud/storage/BUILD.bazel b/google/cloud/storage/BUILD.bazel index d60f062fbe4c6..2f42825e29e28 100644 --- a/google/cloud/storage/BUILD.bazel +++ b/google/cloud/storage/BUILD.bazel @@ -24,10 +24,9 @@ package(default_visibility = ["//visibility:private"]) licenses(["notice"]) # Apache 2.0 -# These are needed to use the BoringSSL libraries, and should be set in any -# downstream dependency too. GOOGLE_CLOUD_STORAGE_WIN_DEFINES = [ "WIN32_LEAN_AND_MEAN", + "_WIN32_WINNT=0x0A00", ] config_setting( @@ -46,12 +45,13 @@ cc_library( srcs = google_cloud_cpp_storage_grpc_srcs, hdrs = google_cloud_cpp_storage_grpc_hdrs, defines = ["GOOGLE_CLOUD_CPP_STORAGE_HAVE_GRPC"] + select({ - "@platforms//os:windows": GOOGLE_CLOUD_STORAGE_WIN_DEFINES, - "//conditions:default": [], - }) + select({ ":ctype_cord_workaround_enabled": ["GOOGLE_CLOUD_CPP_ENABLE_CTYPE_CORD_WORKAROUND"], "//conditions:default": [], }), + local_defines = select({ + "@platforms//os:windows": GOOGLE_CLOUD_STORAGE_WIN_DEFINES, + "//conditions:default": [], + }), visibility = [ ":__subpackages__", "//:__pkg__", @@ -59,8 +59,6 @@ cc_library( deps = [ ":google_cloud_cpp_storage", "//:grpc_utils", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_github_google_crc32c//:crc32c", "@com_github_nlohmann_json//:nlohmann_json", @@ -105,7 +103,13 @@ cc_library( name = "google_cloud_cpp_storage", srcs = google_cloud_cpp_storage_srcs, hdrs = google_cloud_cpp_storage_hdrs, - defines = select({ + linkopts = select({ + "@platforms//os:windows": [ + "-DEFAULTLIB:bcrypt.lib", + ], + "//conditions:default": [], + }), + local_defines = select({ "@platforms//os:windows": GOOGLE_CLOUD_STORAGE_WIN_DEFINES, "//conditions:default": [], }), @@ -116,8 +120,6 @@ cc_library( deps = [ "//:common", "//google/cloud:google_cloud_cpp_rest_internal", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_github_google_crc32c//:crc32c", "@com_github_nlohmann_json//:nlohmann_json", @@ -126,7 +128,13 @@ cc_library( "@com_google_absl//absl/time", "@com_google_absl//absl/types:span", "@com_google_absl//absl/types:variant", - ], + ] + select({ + "@platforms//os:windows": [], + "//conditions:default": [ + "@boringssl//:crypto", + "@boringssl//:ssl", + ], + }), ) filegroup( @@ -165,8 +173,6 @@ cc_library( "//google/cloud:google_cloud_cpp_mocks", "//google/cloud/testing_util:google_cloud_cpp_testing_private", "//google/cloud/testing_util:google_cloud_cpp_testing_rest_private", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_google_googletest//:gtest_main", ], @@ -184,11 +190,15 @@ cc_library( ":storage_client_testing", "//:common", "//google/cloud/testing_util:google_cloud_cpp_testing_private", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_google_googletest//:gtest_main", - ], + ] + select({ + "@platforms//os:windows": [], + "//conditions:default": [ + "@boringssl//:crypto", + "@boringssl//:ssl", + ], + }), ) for test in storage_client_unit_tests] [cc_test( @@ -205,8 +215,6 @@ cc_library( "//:mocks", "//google/cloud/testing_util:google_cloud_cpp_testing_grpc_private", "//google/cloud/testing_util:google_cloud_cpp_testing_private", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", diff --git a/google/cloud/storage/benchmarks/BUILD.bazel b/google/cloud/storage/benchmarks/BUILD.bazel index 8264652650d63..0f8213e26fad9 100644 --- a/google/cloud/storage/benchmarks/BUILD.bazel +++ b/google/cloud/storage/benchmarks/BUILD.bazel @@ -31,8 +31,6 @@ cc_library( "//:storage", "//google/cloud/storage:storage_client_testing", "//google/cloud/testing_util:google_cloud_cpp_testing_private", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_google_googleapis//google/storage/v2:storage_cc_grpc", "@com_google_googleapis//google/storage/v2:storage_cc_proto", @@ -53,8 +51,6 @@ cc_library( "//:storage", "//google/cloud/storage:storage_client_testing", "//google/cloud/testing_util:google_cloud_cpp_testing_private", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_google_absl//absl/strings", ], @@ -73,8 +69,6 @@ cc_library( "//:storage", "//google/cloud/storage:storage_client_testing", "//google/cloud/testing_util:google_cloud_cpp_testing_private", - "@boringssl//:crypto", - "@boringssl//:ssl", "@com_github_curl_curl//:curl", "@com_google_googletest//:gtest_main", ], diff --git a/google/cloud/storage/config-grpc.cmake.in b/google/cloud/storage/config-grpc.cmake.in index 3cdfe88b203fb..bf5c36fd51124 100644 --- a/google/cloud/storage/config-grpc.cmake.in +++ b/google/cloud/storage/config-grpc.cmake.in @@ -21,7 +21,9 @@ find_dependency(absl) find_dependency(CURL) find_dependency(Crc32c) find_dependency(nlohmann_json) -find_dependency(OpenSSL) +if (NOT WIN32) + find_dependency(OpenSSL) +endif () find_dependency(ZLIB) include("${CMAKE_CURRENT_LIST_DIR}/storage-targets.cmake") diff --git a/google/cloud/storage/config.cmake.in b/google/cloud/storage/config.cmake.in index fb5a1330cddcc..9f10f775d01bf 100644 --- a/google/cloud/storage/config.cmake.in +++ b/google/cloud/storage/config.cmake.in @@ -19,7 +19,9 @@ find_dependency(absl) find_dependency(CURL) find_dependency(Crc32c) find_dependency(nlohmann_json) -find_dependency(OpenSSL) +if (NOT WIN32) + find_dependency(OpenSSL) +endif () find_dependency(ZLIB) # Some versions of FindCURL do not define CURL::libcurl, so we define it ourselves. diff --git a/google/cloud/storage/google_cloud_cpp_storage.bzl b/google/cloud/storage/google_cloud_cpp_storage.bzl index 7e01845c058aa..a97ce6da7af6e 100644 --- a/google/cloud/storage/google_cloud_cpp_storage.bzl +++ b/google/cloud/storage/google_cloud_cpp_storage.bzl @@ -212,6 +212,7 @@ google_cloud_cpp_storage_srcs = [ "internal/object_read_streambuf.cc", "internal/object_requests.cc", "internal/object_write_streambuf.cc", + "internal/openssl/hash_function_impl.cc", "internal/patch_builder.cc", "internal/patch_builder_details.cc", "internal/policy_document_request.cc", @@ -227,6 +228,7 @@ google_cloud_cpp_storage_srcs = [ "internal/storage_connection.cc", "internal/tracing_connection.cc", "internal/unified_rest_credentials.cc", + "internal/win32/hash_function_impl.cc", "lifecycle_rule.cc", "list_buckets_reader.cc", "list_hmac_keys_reader.cc", diff --git a/google/cloud/storage/google_cloud_cpp_storage.cmake b/google/cloud/storage/google_cloud_cpp_storage.cmake index 1b81d488e2068..ce3e1475f1ec4 100644 --- a/google/cloud/storage/google_cloud_cpp_storage.cmake +++ b/google/cloud/storage/google_cloud_cpp_storage.cmake @@ -15,7 +15,9 @@ # ~~~ find_package(CURL REQUIRED) -find_package(OpenSSL REQUIRED) +if (NOT WIN32) + find_package(OpenSSL REQUIRED) +endif () # the client library add_library( @@ -155,6 +157,7 @@ add_library( internal/object_requests.h internal/object_write_streambuf.cc internal/object_write_streambuf.h + internal/openssl/hash_function_impl.cc internal/patch_builder.cc internal/patch_builder.h internal/patch_builder_details.cc @@ -187,6 +190,7 @@ add_library( internal/unified_rest_credentials.cc internal/unified_rest_credentials.h internal/well_known_parameters_impl.h + internal/win32/hash_function_impl.cc lifecycle_rule.cc lifecycle_rule.h list_buckets_reader.cc @@ -263,12 +267,15 @@ target_link_libraries( nlohmann_json::nlohmann_json Crc32c::crc32c CURL::libcurl - Threads::Threads - OpenSSL::Crypto) + Threads::Threads) if (WIN32) + target_compile_definitions(google_cloud_cpp_storage + PRIVATE WIN32_LEAN_AND_MEAN) # We use `setsockopt()` directly, which requires the ws2_32 (Winsock2 for # Windows32?) library on Windows. - target_link_libraries(google_cloud_cpp_storage PUBLIC ws2_32) + target_link_libraries(google_cloud_cpp_storage PUBLIC ws2_32 bcrypt) +else () + target_link_libraries(google_cloud_cpp_storage PUBLIC OpenSSL::Crypto) endif () google_cloud_cpp_add_common_options(google_cloud_cpp_storage) target_include_directories( @@ -320,36 +327,25 @@ install( google_cloud_cpp_install_headers(google_cloud_cpp_storage include/google/cloud/storage) -# Cannot use google_cloud_cpp_add_pkgconfig() for this library. There is a -# horrible hack here, adding -lcrc32c to the 'Libs:` entry. We should be adding -# this library to the `Requires:` line, but it does not create pkg-config -# modules. -set(GOOGLE_CLOUD_CPP_PC_NAME "The Google Cloud Storage C++ Client Library") -set(GOOGLE_CLOUD_CPP_PC_DESCRIPTION - "Provides C++ APIs to access Google Cloud Storage.") -string(JOIN " " GOOGLE_CLOUD_CPP_PC_LIBS "-lgoogle_cloud_cpp_storage" - "-lcrc32c") -string( - JOIN - " " - GOOGLE_CLOUD_CPP_PC_REQUIRES +google_cloud_cpp_add_pkgconfig( + "storage" + "The Google Cloud Storage C++ Client Library" + "Provides C++ APIs to access Google Cloud Storage." "google_cloud_cpp_common" "google_cloud_cpp_rest_internal" - "libcurl openssl" + "libcurl" "absl_cord" "absl_strings" "absl_str_format" "absl_time" - "absl_variant") - -# Create and install the pkg-config files. -google_cloud_cpp_set_pkgconfig_paths() -configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/config.pc.in" - "google_cloud_cpp_storage.pc" @ONLY) -install( - FILES "${CMAKE_CURRENT_BINARY_DIR}/google_cloud_cpp_storage.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" - COMPONENT google_cloud_cpp_development) + "absl_variant" + NON_WIN32_REQUIRES + openssl + LIBS + crc32c + WIN32_LIBS + ws2_32 + bcrypt) # Create and install the CMake configuration files. include(CMakePackageConfigHelpers) diff --git a/google/cloud/storage/internal/base64.cc b/google/cloud/storage/internal/base64.cc index 72399f4c44d6c..cc6ae95550724 100644 --- a/google/cloud/storage/internal/base64.cc +++ b/google/cloud/storage/internal/base64.cc @@ -14,7 +14,6 @@ #include "google/cloud/storage/internal/base64.h" #include "google/cloud/internal/base64_transforms.h" -#include "google/cloud/internal/openssl_util.h" #include namespace google { diff --git a/google/cloud/storage/internal/grpc/object_request_parser_test.cc b/google/cloud/storage/internal/grpc/object_request_parser_test.cc index c24adb8181f1c..e4ed7750f51ec 100644 --- a/google/cloud/storage/internal/grpc/object_request_parser_test.cc +++ b/google/cloud/storage/internal/grpc/object_request_parser_test.cc @@ -1365,11 +1365,10 @@ TEST(GrpcObjectRequestParser, MaybeFinalizeUploadChunkRequest) { bool with_md5) -> std::shared_ptr { if (with_crc32c && with_md5) { return std::make_shared( - std::make_unique(), - std::make_unique()); + std::make_unique(), MD5HashFunction::Create()); } if (with_crc32c) return std::make_shared(); - if (with_md5) return std::make_shared(); + if (with_md5) return MD5HashFunction::Create(); return std::make_shared(); }; diff --git a/google/cloud/storage/internal/hash_function.cc b/google/cloud/storage/internal/hash_function.cc index cae842d0f6432..5d3ec634a31e0 100644 --- a/google/cloud/storage/internal/hash_function.cc +++ b/google/cloud/storage/internal/hash_function.cc @@ -42,7 +42,7 @@ std::unique_ptr CreateHashFunction( md5 = std::make_unique( HashValues{/*.crc32c=*/{}, /*.md5=*/std::move(md5_v)}); } else if (!md5_disabled.value_or(false)) { - md5 = std::make_unique(); + md5 = MD5HashFunction::Create(); } if (!crc32c && !md5) return std::make_unique(); @@ -67,10 +67,9 @@ std::unique_ptr CreateHashFunction( return std::make_unique(); } if (disable_md5) return std::make_unique(); - if (disable_crc32c) return std::make_unique(); + if (disable_crc32c) return MD5HashFunction::Create(); return std::make_unique( - std::make_unique(), - std::make_unique()); + std::make_unique(), MD5HashFunction::Create()); } std::unique_ptr CreateHashFunction( diff --git a/google/cloud/storage/internal/hash_function_impl.cc b/google/cloud/storage/internal/hash_function_impl.cc index b1658041df7fc..85350fd216fc7 100644 --- a/google/cloud/storage/internal/hash_function_impl.cc +++ b/google/cloud/storage/internal/hash_function_impl.cc @@ -15,7 +15,6 @@ #include "google/cloud/storage/internal/hash_function_impl.h" #include "google/cloud/storage/internal/base64.h" #include "google/cloud/storage/internal/crc32c.h" -#include "google/cloud/storage/internal/object_requests.h" #include "google/cloud/internal/big_endian.h" #include "google/cloud/internal/make_status.h" @@ -30,27 +29,6 @@ using ::google::cloud::internal::InvalidArgumentError; using ::google::cloud::storage_internal::Crc32c; using ::google::cloud::storage_internal::ExtendCrc32c; -using ContextPtr = std::unique_ptr; - -ContextPtr CreateDigestCtx() { -// The name of the function to create and delete EVP_MD_CTX objects changed -// with OpenSSL 1.1.0. -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) // Older than version 1.1.0. - return ContextPtr(EVP_MD_CTX_create()); -#else - return ContextPtr(EVP_MD_CTX_new()); -#endif -} - -void DeleteDigestCtx(EVP_MD_CTX* context) { -// The name of the function to free an EVP_MD_CTX changed in OpenSSL 1.1.0. -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) // Older than version 1.1.0. - EVP_MD_CTX_destroy(context); -#else - EVP_MD_CTX_free(context); -#endif // OPENSSL_VERSION_NUMBER >= 0x10100000L -} - template bool AlreadyHashed(std::int64_t offset, Buffer const& buffer, std::int64_t minimum_offset) { @@ -117,14 +95,6 @@ HashValues CompositeFunction::Finish() { return Merge(a_->Finish(), b_->Finish()); } -MD5HashFunction::MD5HashFunction() : impl_(CreateDigestCtx()) { - EVP_DigestInit_ex(impl_.get(), EVP_md5(), nullptr); -} - -void MD5HashFunction::Update(absl::string_view buffer) { - EVP_DigestUpdate(impl_.get(), buffer.data(), buffer.size()); -} - Status MD5HashFunction::Update(std::int64_t offset, absl::string_view buffer) { if (offset == minimum_offset_) { Update(buffer); @@ -155,24 +125,11 @@ Status MD5HashFunction::Update(std::int64_t offset, absl::Cord const& buffer, HashValues MD5HashFunction::Finish() { if (hashes_.has_value()) return *hashes_; - std::vector hash(EVP_MD_size(EVP_md5())); - unsigned int len = 0; - // Note: EVP_DigestFinal_ex() consumes an `unsigned char*` for the output - // array. On some platforms (read PowerPC and ARM), the default `char` is - // unsigned. In those platforms it is possible that - // `std::uint8_t != unsigned char` and the `reinterpret_cast<>` is needed. It - // should be safe in any case. - EVP_DigestFinal_ex(impl_.get(), reinterpret_cast(hash.data()), - &len); - hash.resize(len); + Hash hash = FinishImpl(); hashes_ = HashValues{/*.crc32c=*/{}, /*.md5=*/Base64Encode(hash)}; return *hashes_; } -void MD5HashFunction::ContextDeleter::operator()(EVP_MD_CTX* context) { - DeleteDigestCtx(context); -} - void Crc32cHashFunction::Update(absl::string_view buffer) { current_ = ExtendCrc32c(current_, buffer); } diff --git a/google/cloud/storage/internal/hash_function_impl.h b/google/cloud/storage/internal/hash_function_impl.h index 959897e5cefc0..e9e1754f0da5c 100644 --- a/google/cloud/storage/internal/hash_function_impl.h +++ b/google/cloud/storage/internal/hash_function_impl.h @@ -18,7 +18,6 @@ #include "google/cloud/storage/internal/hash_function.h" #include "google/cloud/storage/version.h" #include "absl/types/optional.h" -#include #include #include #include @@ -74,13 +73,15 @@ class CompositeFunction : public HashFunction { */ class MD5HashFunction : public HashFunction { public: - MD5HashFunction(); + MD5HashFunction() = default; MD5HashFunction(MD5HashFunction const&) = delete; MD5HashFunction& operator=(MD5HashFunction const&) = delete; + static std::unique_ptr Create(); + std::string Name() const override { return "md5"; } - void Update(absl::string_view buffer) override; + void Update(absl::string_view buffer) override = 0; Status Update(std::int64_t offset, absl::string_view buffer) override; Status Update(std::int64_t offset, absl::string_view buffer, std::uint32_t buffer_crc) override; @@ -88,12 +89,12 @@ class MD5HashFunction : public HashFunction { std::uint32_t buffer_crc) override; HashValues Finish() override; - struct ContextDeleter { - void operator()(EVP_MD_CTX*); - }; + protected: + // (8 bits per byte) * 16 bytes = 128 bits + using Hash = std::array; + virtual Hash FinishImpl() = 0; private: - std::unique_ptr impl_; std::int64_t minimum_offset_ = 0; absl::optional hashes_; }; diff --git a/google/cloud/storage/internal/hash_function_impl_test.cc b/google/cloud/storage/internal/hash_function_impl_test.cc index 24e9ead4d49ad..38a1aad72e612 100644 --- a/google/cloud/storage/internal/hash_function_impl_test.cc +++ b/google/cloud/storage/internal/hash_function_impl_test.cc @@ -140,83 +140,83 @@ TEST(HashFunctionImplTest, Crc32cCord) { } TEST(HashFunctionImplTest, MD5Empty) { - MD5HashFunction function; - auto result = std::move(function).Finish(); + auto function = MD5HashFunction::Create(); + auto result = std::move(function)->Finish(); EXPECT_THAT(result.crc32c, IsEmpty()); EXPECT_THAT(result.md5, kEmptyStringMD5Hash); } TEST(HashFunctionImplTest, MD5Quick) { - MD5HashFunction function; - function.Update("The quick"); - function.Update(" brown"); - function.Update(" fox jumps over the lazy dog"); - auto result = std::move(function).Finish(); + auto function = MD5HashFunction::Create(); + function->Update("The quick"); + function->Update(" brown"); + function->Update(" fox jumps over the lazy dog"); + auto result = std::move(function)->Finish(); EXPECT_THAT(result.crc32c, IsEmpty()); EXPECT_THAT(result.md5, kQuickFoxMD5Hash); } TEST(HashFunctionImplTest, MD5StringView) { - MD5HashFunction function; + auto function = MD5HashFunction::Create(); auto payload = absl::string_view{kQuickFox}; for (std::size_t pos = 0; pos < payload.size(); pos += 5) { auto message = payload.substr(pos, 5); - EXPECT_STATUS_OK(function.Update(pos, message)); - EXPECT_STATUS_OK(function.Update(pos, message)); - EXPECT_THAT(function.Update(pos, payload), + EXPECT_STATUS_OK(function->Update(pos, message)); + EXPECT_STATUS_OK(function->Update(pos, message)); + EXPECT_THAT(function->Update(pos, payload), StatusIs(StatusCode::kInvalidArgument)); } - auto actual = function.Finish(); + auto actual = function->Finish(); EXPECT_THAT(actual.crc32c, IsEmpty()); EXPECT_THAT(actual.md5, kQuickFoxMD5Hash); - actual = function.Finish(); + actual = function->Finish(); EXPECT_THAT(actual.crc32c, IsEmpty()); EXPECT_THAT(actual.md5, kQuickFoxMD5Hash); } TEST(HashFunctionImplTest, MD5StringViewWithCrc) { - MD5HashFunction function; + auto function = MD5HashFunction::Create(); auto payload = absl::string_view{kQuickFox}; for (std::size_t pos = 0; pos < payload.size(); pos += 5) { auto message = payload.substr(pos, 5); auto const unused = std::uint32_t{0}; - EXPECT_STATUS_OK(function.Update(pos, message, unused)); - EXPECT_STATUS_OK(function.Update(pos, message, unused)); - EXPECT_THAT(function.Update(pos, payload, unused), + EXPECT_STATUS_OK(function->Update(pos, message, unused)); + EXPECT_STATUS_OK(function->Update(pos, message, unused)); + EXPECT_THAT(function->Update(pos, payload, unused), StatusIs(StatusCode::kInvalidArgument)); } - auto actual = function.Finish(); + auto actual = function->Finish(); EXPECT_THAT(actual.crc32c, IsEmpty()); EXPECT_THAT(actual.md5, kQuickFoxMD5Hash); - actual = function.Finish(); + actual = function->Finish(); EXPECT_THAT(actual.crc32c, IsEmpty()); EXPECT_THAT(actual.md5, kQuickFoxMD5Hash); } TEST(HashFunctionImplTest, MD5Cord) { - MD5HashFunction function; + auto function = MD5HashFunction::Create(); auto payload = absl::Cord(absl::string_view{kQuickFox}); for (std::size_t pos = 0; pos < payload.size(); pos += 5) { auto message = payload.Subcord(pos, 5); auto const unused = std::uint32_t{0}; - EXPECT_STATUS_OK(function.Update(pos, message, unused)); - EXPECT_STATUS_OK(function.Update(pos, message, unused)); - EXPECT_THAT(function.Update(pos, payload, unused), + EXPECT_STATUS_OK(function->Update(pos, message, unused)); + EXPECT_STATUS_OK(function->Update(pos, message, unused)); + EXPECT_THAT(function->Update(pos, payload, unused), StatusIs(StatusCode::kInvalidArgument)); } - auto const actual = function.Finish(); + auto const actual = function->Finish(); EXPECT_THAT(actual.crc32c, IsEmpty()); EXPECT_THAT(actual.md5, kQuickFoxMD5Hash); - auto const a2 = function.Finish(); + auto const a2 = function->Finish(); EXPECT_THAT(a2.crc32c, IsEmpty()); EXPECT_THAT(a2.md5, kQuickFoxMD5Hash); } TEST(HashFunctionImplTest, CompositeEmpty) { - CompositeFunction function(std::make_unique(), + CompositeFunction function(MD5HashFunction::Create(), std::make_unique()); auto result = std::move(function).Finish(); EXPECT_THAT(result.crc32c, kEmptyStringCrc32cChecksum); @@ -224,7 +224,7 @@ TEST(HashFunctionImplTest, CompositeEmpty) { } TEST(HashFunctionImplTest, CompositeQuick) { - CompositeFunction function(std::make_unique(), + CompositeFunction function(MD5HashFunction::Create(), std::make_unique()); function.Update("The quick"); function.Update(" brown"); diff --git a/google/cloud/storage/internal/hash_validator_test.cc b/google/cloud/storage/internal/hash_validator_test.cc index 5d1a950a21a7d..060d89592b660 100644 --- a/google/cloud/storage/internal/hash_validator_test.cc +++ b/google/cloud/storage/internal/hash_validator_test.cc @@ -63,8 +63,8 @@ TEST(MD5HashValidator, Empty) { MD5HashValidator validator; validator.ProcessHashValues( HashValues{/*.crc32c=*/{}, /*.md5=*/kEmptyStringMD5Hash}); - auto result = std::move(validator).Finish( - HashEmpty(std::make_unique())); + auto result = + std::move(validator).Finish(HashEmpty(MD5HashFunction::Create())); EXPECT_THAT(result.received.crc32c, IsEmpty()); EXPECT_THAT(result.received.md5, kEmptyStringMD5Hash); EXPECT_EQ(result.computed.crc32c, result.received.crc32c); @@ -76,8 +76,8 @@ TEST(MD5HashValidator, Simple) { MD5HashValidator validator; validator.ProcessHashValues( HashValues{/*.crc32c=*/{}, /*.md5=*/""}); - auto result = std::move(validator).Finish( - HashQuick(std::make_unique())); + auto result = + std::move(validator).Finish(HashQuick(MD5HashFunction::Create())); EXPECT_THAT(result.received.crc32c, IsEmpty()); EXPECT_THAT(result.received.md5, ""); EXPECT_TRUE(result.is_mismatch); @@ -87,8 +87,8 @@ TEST(MD5HashValidator, MultipleHashesMd5AtEnd) { MD5HashValidator validator; validator.ProcessHashValues(HashValues{/*.crc32c=*/"", /*.md5=*/""}); - auto result = std::move(validator).Finish( - HashQuick(std::make_unique())); + auto result = + std::move(validator).Finish(HashQuick(MD5HashFunction::Create())); EXPECT_THAT(result.received.crc32c, IsEmpty()); EXPECT_THAT(result.received.md5, ""); EXPECT_TRUE(result.is_mismatch); @@ -139,8 +139,7 @@ TEST(CompositeHashValidator, Empty) { /*.md5=*/kEmptyStringMD5Hash}); auto result = std::move(validator).Finish(HashEmpty(std::make_unique( - std::make_unique(), - std::make_unique()))); + std::make_unique(), MD5HashFunction::Create()))); EXPECT_THAT(result.received.crc32c, kEmptyStringCrc32cChecksum); EXPECT_THAT(result.received.md5, kEmptyStringMD5Hash); EXPECT_FALSE(result.is_mismatch); @@ -156,8 +155,7 @@ TEST(CompositeHashValidator, Simple) { /*.md5=*/""}); auto result = std::move(validator).Finish(HashQuick(std::make_unique( - std::make_unique(), - std::make_unique()))); + std::make_unique(), MD5HashFunction::Create()))); EXPECT_THAT(result.received.crc32c, ""); EXPECT_THAT(result.received.md5, ""); EXPECT_TRUE(result.is_mismatch); @@ -175,8 +173,7 @@ TEST(CompositeHashValidator, ProcessMetadata) { validator.ProcessMetadata(object_metadata); auto result = std::move(validator).Finish(HashQuick(std::make_unique( - std::make_unique(), - std::make_unique()))); + std::make_unique(), MD5HashFunction::Create()))); EXPECT_THAT(result.received.crc32c, kQuickFoxCrc32cChecksum); EXPECT_THAT(result.received.md5, kQuickFoxMD5Hash); EXPECT_FALSE(result.is_mismatch); @@ -189,8 +186,7 @@ TEST(CompositeHashValidator, Missing) { /*.md5=*/{}}); auto result = std::move(validator).Finish(HashQuick(std::make_unique( - std::make_unique(), - std::make_unique()))); + std::make_unique(), MD5HashFunction::Create()))); EXPECT_THAT(result.received.crc32c, kQuickFoxCrc32cChecksum); EXPECT_THAT(result.received.md5, IsEmpty()); EXPECT_FALSE(result.is_mismatch); diff --git a/google/cloud/storage/internal/make_jwt_assertion.cc b/google/cloud/storage/internal/make_jwt_assertion.cc index 868f03afa7376..18030f46c04d1 100644 --- a/google/cloud/storage/internal/make_jwt_assertion.cc +++ b/google/cloud/storage/internal/make_jwt_assertion.cc @@ -14,7 +14,7 @@ #include "google/cloud/storage/internal/make_jwt_assertion.h" #include "google/cloud/storage/internal/base64.h" -#include "google/cloud/internal/openssl_util.h" +#include "google/cloud/internal/sign_using_sha256.h" namespace google { namespace cloud { diff --git a/google/cloud/storage/internal/md5hash.cc b/google/cloud/storage/internal/md5hash.cc index 6a31587ab637d..53ea3ddcff38b 100644 --- a/google/cloud/storage/internal/md5hash.cc +++ b/google/cloud/storage/internal/md5hash.cc @@ -13,9 +13,14 @@ // limitations under the License. #include "google/cloud/storage/internal/md5hash.h" -#include #include #include +#ifdef _WIN32 +#include +#include +#else +#include +#endif // _WIN32 namespace google { namespace cloud { @@ -23,6 +28,14 @@ namespace storage_internal { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN std::vector MD5Hash(absl::string_view payload) { +#ifdef _WIN32 + std::vector digest(16); + BCryptHash(BCRYPT_MD5_ALG_HANDLE, nullptr, 0, + reinterpret_cast(const_cast(payload.data())), + static_cast(payload.size()), digest.data(), + static_cast(digest.size())); + return digest; +#else std::array digest; unsigned int size = 0; @@ -30,6 +43,7 @@ std::vector MD5Hash(absl::string_view payload) { nullptr); return std::vector{digest.begin(), std::next(digest.begin(), size)}; +#endif // _WIN32 } GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END diff --git a/google/cloud/storage/internal/openssl/hash_function_impl.cc b/google/cloud/storage/internal/openssl/hash_function_impl.cc new file mode 100644 index 0000000000000..82fefc7bf48e3 --- /dev/null +++ b/google/cloud/storage/internal/openssl/hash_function_impl.cc @@ -0,0 +1,87 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _WIN32 +#include "google/cloud/storage/internal/hash_function_impl.h" +#include +#include + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace internal { +namespace { +struct ContextDeleter { + void operator()(EVP_MD_CTX* context) const { +// The name of the function to free an EVP_MD_CTX changed in OpenSSL 1.1.0. +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) // Older than version 1.1.0. + EVP_MD_CTX_destroy(context); +#else + EVP_MD_CTX_free(context); +#endif // OPENSSL_VERSION_NUMBER >= 0x10100000L + } +}; + +using ContextPtr = + std::unique_ptr, ContextDeleter>; + +ContextPtr CreateDigestCtx() { +// The name of the function to create and delete EVP_MD_CTX objects changed +// with OpenSSL 1.1.0. +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) // Older than version 1.1.0. + return ContextPtr(EVP_MD_CTX_create()); +#else + return ContextPtr(EVP_MD_CTX_new()); +#endif +} + +class OpenSslMD5HashFunction : public MD5HashFunction { + public: + OpenSslMD5HashFunction() : impl_(CreateDigestCtx()) { + EVP_DigestInit_ex(impl_.get(), EVP_md5(), nullptr); + } + + void Update(absl::string_view buffer) override { + EVP_DigestUpdate(impl_.get(), buffer.data(), buffer.size()); + } + + Hash FinishImpl() override { + Hash hash; + unsigned int len = 0; + // Note: EVP_DigestFinal_ex() consumes an `unsigned char*` for the output + // array. On some platforms (read PowerPC and ARM), the default `char` is + // unsigned. In those platforms it is possible that + // `std::uint8_t != unsigned char` and the `reinterpret_cast<>` is needed. + // It should be safe in any case. + EVP_DigestFinal_ex(impl_.get(), + reinterpret_cast(hash.data()), &len); + return hash; + } + + private: + ContextPtr impl_; +}; +} // namespace + +std::unique_ptr MD5HashFunction::Create() { + return std::make_unique(); +} + +} // namespace internal +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google +#endif // _WIN32 diff --git a/google/cloud/storage/internal/win32/hash_function_impl.cc b/google/cloud/storage/internal/win32/hash_function_impl.cc new file mode 100644 index 0000000000000..655c9f09917c1 --- /dev/null +++ b/google/cloud/storage/internal/win32/hash_function_impl.cc @@ -0,0 +1,71 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef _WIN32 +#include "google/cloud/storage/internal/hash_function_impl.h" +#include +#include +#include + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace internal { +namespace { +struct ContextDeleter { + void operator()(BCRYPT_HASH_HANDLE h) const { BCryptDestroyHash(h); } +}; + +using ContextPtr = + std::unique_ptr, ContextDeleter>; + +ContextPtr CreateMD5HashCtx() { + BCRYPT_HASH_HANDLE hHash = nullptr; + BCryptCreateHash(BCRYPT_MD5_ALG_HANDLE, &hHash, nullptr, 0, nullptr, 0, 0); + return ContextPtr(hHash); +} + +class BCryptMD5HashFunction : public MD5HashFunction { + public: + BCryptMD5HashFunction() : impl_(CreateMD5HashCtx()) {} + + void Update(absl::string_view buffer) override { + BCryptHashData(impl_.get(), + reinterpret_cast(const_cast(buffer.data())), + static_cast(buffer.size()), 0); + } + + Hash FinishImpl() override { + MD5HashFunction::Hash hash; + BCryptFinishHash(impl_.get(), reinterpret_cast(hash.data()), + static_cast(hash.size()), 0); + return hash; + } + + private: + ContextPtr impl_; +}; +} // namespace + +std::unique_ptr MD5HashFunction::Create() { + return std::make_unique(); +} + +} // namespace internal +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google +#endif // _WIN32 diff --git a/google/cloud/storage/oauth2/service_account_credentials.cc b/google/cloud/storage/oauth2/service_account_credentials.cc index 759df1a8a62f1..70d4860eb3ca5 100644 --- a/google/cloud/storage/oauth2/service_account_credentials.cc +++ b/google/cloud/storage/oauth2/service_account_credentials.cc @@ -19,10 +19,9 @@ #include "google/cloud/internal/absl_str_join_quiet.h" #include "google/cloud/internal/oauth2_cached_credentials.h" #include "google/cloud/internal/oauth2_service_account_credentials.h" +#include "google/cloud/internal/parse_service_account_p12_file.h" +#include "google/cloud/internal/sign_using_sha256.h" #include -#include -#include -#include #include #include diff --git a/google/cloud/storage/oauth2/service_account_credentials.h b/google/cloud/storage/oauth2/service_account_credentials.h index c365364ef3233..122b65c79a4c7 100644 --- a/google/cloud/storage/oauth2/service_account_credentials.h +++ b/google/cloud/storage/oauth2/service_account_credentials.h @@ -26,8 +26,8 @@ #include "google/cloud/internal/curl_handle_factory.h" #include "google/cloud/internal/getenv.h" #include "google/cloud/internal/oauth2_service_account_credentials.h" -#include "google/cloud/internal/openssl_util.h" #include "google/cloud/internal/sha256_hash.h" +#include "google/cloud/internal/sign_using_sha256.h" #include "google/cloud/optional.h" #include "google/cloud/status_or.h" #include "absl/types/optional.h" diff --git a/google/cloud/storage/oauth2/service_account_credentials_test.cc b/google/cloud/storage/oauth2/service_account_credentials_test.cc index 52c6fac27383b..debbee16580af 100644 --- a/google/cloud/storage/oauth2/service_account_credentials_test.cc +++ b/google/cloud/storage/oauth2/service_account_credentials_test.cc @@ -20,8 +20,8 @@ #include "google/cloud/storage/testing/write_base64.h" #include "google/cloud/internal/base64_transforms.h" #include "google/cloud/internal/filesystem.h" -#include "google/cloud/internal/openssl_util.h" #include "google/cloud/internal/random.h" +#include "google/cloud/internal/sign_using_sha256.h" #include "google/cloud/testing_util/mock_fake_clock.h" #include "google/cloud/testing_util/scoped_environment.h" #include "google/cloud/testing_util/status_matchers.h" diff --git a/vcpkg.json b/vcpkg.json index 84a6ca441eb07..9580c28496fcc 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -24,6 +24,10 @@ "host": true }, "grpc", + { + "name": "openssl", + "platform": "!windows" + }, "protobuf", "nlohmann-json", "benchmark",