common/kms_client.cc (453 lines of code) (raw):
// Copyright 2021 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
//
// http://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.
#include "common/kms_client.h"
#include "absl/crc/crc32c.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "cloudkms_grpc_service_config.h"
#include "common/backoff.h"
#include "common/openssl.h"
#include "common/platform.h"
#include "common/source_location.h"
#include "common/status_macros.h"
#include "grpcpp/client_context.h"
#include "grpcpp/create_channel.h"
#include "grpcpp/security/credentials.h"
namespace cloud_kms {
namespace {
// clang-format off
// Sample value:
// `cloud-kms-pkcs11/0.21 (amd64; BoringSSL; Linux/4.15.0-1096-gcp-x86_64; glibc/2.23)`
// clang-format on
std::string ComputeUserAgentPrefix(UserAgent user_agent,
const int version_major,
const int version_minor) {
// New user agents need to be registered in Concord, see cl/315314203.
switch (user_agent) {
case UserAgent::kPkcs11:
return absl::StrFormat(
"cloud-kms-pkcs11/%d.%d (%s; %s%s; %s)", version_major, version_minor,
GetTargetPlatform(), OpenSSL_version(OPENSSL_VERSION),
FIPS_mode() == 1 ? " FIPS" : "", GetHostPlatformInfo());
case UserAgent::kCng:
return absl::StrFormat(
"cloud-kms-cng/%d.%d (%s; %s%s; %s)", version_major, version_minor,
GetTargetPlatform(), OpenSSL_version(OPENSSL_VERSION),
FIPS_mode() == 1 ? " FIPS" : "", GetHostPlatformInfo());
}
}
absl::StatusOr<std::string> GetDigestString(const kms_v1::Digest& digest) {
switch (digest.digest_case()) {
case kms_v1::Digest::kSha256:
return digest.sha256();
break;
case kms_v1::Digest::kSha384:
return digest.sha384();
break;
case kms_v1::Digest::kSha512:
return digest.sha512();
default:
return absl::InternalError(
absl::StrFormat("at %s: could not get digest field of the request",
SOURCE_LOCATION.ToString()));
}
}
uint32_t ComputeCRC32C(std::string_view data) {
return static_cast<uint32_t>(absl::ComputeCrc32c(data));
}
bool CRC32CMatches(std::string_view data, uint32_t crc32c) {
return crc32c == ComputeCRC32C(data);
}
} // namespace
void KmsClient::AddContextSettings(grpc::ClientContext* ctx,
std::string_view relative_resource,
std::string_view resource_name,
absl::Time rpc_deadline) const {
// See https://cloud.google.com/kms/docs/grpc
ctx->AddMetadata("x-goog-request-params",
absl::StrCat(relative_resource, "=", resource_name));
ctx->set_deadline(absl::ToChronoTime(rpc_deadline));
if (!user_project_override_.empty()) {
ctx->AddMetadata("x-goog-user-project", user_project_override_);
}
if (!rpc_feature_flags_.empty()) {
ctx->AddMetadata("x-cloud-kms-features", rpc_feature_flags_);
}
}
absl::Status KmsClient::DecorateStatus(absl::Status& status) const {
if (error_decorator_.has_value()) {
(*error_decorator_)(status);
}
return status;
}
KmsClient::KmsClient(const Options& options)
: rpc_timeout_(options.rpc_timeout),
rpc_feature_flags_(options.rpc_feature_flags),
user_project_override_(options.user_project_override),
error_decorator_(options.error_decorator) {
grpc::ChannelArguments args;
args.SetUserAgentPrefix(ComputeUserAgentPrefix(
options.user_agent, options.version_major, options.version_minor));
args.SetServiceConfigJSON(std::string(kDefaultCloudKmsGrpcServiceConfig));
std::shared_ptr<grpc::Channel> channel = grpc::CreateCustomChannel(
std::string(options.endpoint_address), options.creds, args);
kms_stub_ = kms_v1::KeyManagementService::NewStub(channel);
}
absl::StatusOr<kms_v1::AsymmetricDecryptResponse> KmsClient::AsymmetricDecrypt(
kms_v1::AsymmetricDecryptRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
request.mutable_ciphertext_crc32c()->set_value(
ComputeCRC32C(request.ciphertext()));
kms_v1::AsymmetricDecryptResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->AsymmetricDecrypt(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
if (!CRC32CMatches(response.plaintext(),
response.plaintext_crc32c().value())) {
rpc_result = absl::InternalError(absl::StrFormat(
"at %s: the response crc32c did not match the expected checksum value",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
if (!response.verified_ciphertext_crc32c()) {
rpc_result = absl::InternalError(
absl::StrFormat("at %s: the server did not verify the checksum values "
"provided in the request",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::AsymmetricSignResponse> KmsClient::AsymmetricSign(
kms_v1::AsymmetricSignRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
bool use_data = false;
if (!request.data().empty()) {
use_data = true;
request.mutable_data_crc32c()->set_value(ComputeCRC32C(request.data()));
} else {
absl::StatusOr<std::string> digest_string =
GetDigestString(request.digest());
if (!digest_string.ok()) {
absl::Status status = digest_string.status();
return DecorateStatus(status);
}
request.mutable_digest_crc32c()->set_value(ComputeCRC32C(*digest_string));
}
kms_v1::AsymmetricSignResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->AsymmetricSign(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
if (!CRC32CMatches(response.signature(),
response.signature_crc32c().value())) {
rpc_result = absl::InternalError(absl::StrFormat(
"at %s: the response crc32c did not match the expected checksum value",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
if (use_data && !response.verified_data_crc32c()) {
rpc_result = absl::InternalError(
absl::StrFormat("at %s: the server did not verify the checksum values "
"provided in the request",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
if (!use_data && !response.verified_digest_crc32c()) {
rpc_result = absl::InternalError(
absl::StrFormat("at %s: the server did not verify the checksum values "
"provided in the request",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::MacSignResponse> KmsClient::MacSign(
kms_v1::MacSignRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
request.mutable_data_crc32c()->set_value(ComputeCRC32C(request.data()));
kms_v1::MacSignResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->MacSign(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
if (!CRC32CMatches(response.mac(), response.mac_crc32c().value())) {
rpc_result = absl::InternalError(absl::StrFormat(
"at %s: the response crc32c did not match the expected checksum value",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
if (!response.verified_data_crc32c()) {
rpc_result = absl::InternalError(
absl::StrFormat("at %s: the server did not verify the checksum values "
"provided in the request",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::MacVerifyResponse> KmsClient::MacVerify(
kms_v1::MacVerifyRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
request.mutable_data_crc32c()->set_value(ComputeCRC32C(request.data()));
request.mutable_mac_crc32c()->set_value(ComputeCRC32C(request.mac()));
kms_v1::MacVerifyResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->MacVerify(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
if (response.success() != response.verified_success_integrity()) {
rpc_result = absl::InternalError(absl::StrFormat(
"at %s: the response crc32c did not match the expected checksum value",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
if (!response.verified_data_crc32c() || !response.verified_mac_crc32c()) {
rpc_result = absl::InternalError(
absl::StrFormat("at %s: the server did not verify the checksum values "
"provided in the request",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::RawDecryptResponse> KmsClient::RawDecrypt(
kms_v1::RawDecryptRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
request.mutable_ciphertext_crc32c()->set_value(
ComputeCRC32C(request.ciphertext()));
request.mutable_initialization_vector_crc32c()->set_value(
ComputeCRC32C(request.initialization_vector()));
request.mutable_additional_authenticated_data_crc32c()->set_value(
ComputeCRC32C(request.additional_authenticated_data()));
kms_v1::RawDecryptResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->RawDecrypt(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
if (!CRC32CMatches(response.plaintext(),
response.plaintext_crc32c().value())) {
rpc_result = absl::InternalError(absl::StrFormat(
"at %s: the response crc32c did not match the expected checksum value",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::RawEncryptResponse> KmsClient::RawEncrypt(
kms_v1::RawEncryptRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
request.mutable_plaintext_crc32c()->set_value(
ComputeCRC32C(request.plaintext()));
request.mutable_additional_authenticated_data_crc32c()->set_value(
ComputeCRC32C(request.additional_authenticated_data()));
request.mutable_initialization_vector_crc32c()->set_value(
ComputeCRC32C(request.initialization_vector()));
kms_v1::RawEncryptResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->RawEncrypt(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
if (!CRC32CMatches(response.ciphertext(),
response.ciphertext_crc32c().value())) {
rpc_result = absl::InternalError(absl::StrFormat(
"at %s: the response crc32c did not match the expected checksum value",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
if (!response.verified_plaintext_crc32c() ||
!response.verified_additional_authenticated_data_crc32c() ||
!response.verified_initialization_vector_crc32c()) {
rpc_result = absl::InternalError(
absl::StrFormat("at %s: the server did not verify the checksum values "
"provided in the request",
SOURCE_LOCATION.ToString()));
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::CryptoKey> KmsClient::CreateCryptoKey(
const kms_v1::CreateCryptoKeyRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "parent", request.parent());
kms_v1::CryptoKey response;
absl::Status rpc_result =
ToStatus(kms_stub_->CreateCryptoKey(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<CryptoKeyAndVersion>
KmsClient::CreateCryptoKeyAndWaitForFirstVersion(
const kms_v1::CreateCryptoKeyRequest& request) const {
absl::Time deadline = absl::Now() + rpc_timeout_;
ASSIGN_OR_RETURN(kms_v1::CryptoKey ck, CreateCryptoKey(request));
kms_v1::CryptoKeyVersion ckv;
if (ck.has_primary()) {
ckv = ck.primary();
} else {
std::string name = absl::StrCat(ck.name(), "/cryptoKeyVersions/1");
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", name, deadline);
kms_v1::GetCryptoKeyVersionRequest get_ckv_req;
get_ckv_req.set_name(name);
absl::Status rpc_result =
ToStatus(kms_stub_->GetCryptoKeyVersion(&ctx, get_ckv_req, &ckv));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
}
RETURN_IF_ERROR(WaitForGeneration(ckv, deadline));
return CryptoKeyAndVersion{ck, ckv};
}
absl::StatusOr<kms_v1::CryptoKeyVersion>
KmsClient::CreateCryptoKeyVersionAndWait(
const kms_v1::CreateCryptoKeyVersionRequest& request) const {
absl::Time deadline = absl::Now() + rpc_timeout_;
grpc::ClientContext ctx;
AddContextSettings(&ctx, "parent", request.parent(), deadline);
kms_v1::CryptoKeyVersion response;
absl::Status rpc_result =
ToStatus(kms_stub_->CreateCryptoKeyVersion(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
RETURN_IF_ERROR(WaitForGeneration(response, deadline));
return response;
}
absl::StatusOr<kms_v1::CryptoKeyVersion> KmsClient::DestroyCryptoKeyVersion(
const kms_v1::DestroyCryptoKeyVersionRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
kms_v1::CryptoKeyVersion response;
absl::Status rpc_result =
ToStatus(kms_stub_->DestroyCryptoKeyVersion(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::CryptoKey> KmsClient::GetCryptoKey(
const kms_v1::GetCryptoKeyRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
kms_v1::CryptoKey response;
absl::Status rpc_result =
ToStatus(kms_stub_->GetCryptoKey(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::CryptoKeyVersion> KmsClient::GetCryptoKeyVersion(
const kms_v1::GetCryptoKeyVersionRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
kms_v1::CryptoKeyVersion response;
absl::Status rpc_result =
ToStatus(kms_stub_->GetCryptoKeyVersion(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
}
absl::StatusOr<kms_v1::PublicKey> KmsClient::GetPublicKey(
const kms_v1::GetPublicKeyRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", request.name());
kms_v1::PublicKey response;
absl::Status rpc_result =
ToStatus(kms_stub_->GetPublicKey(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
}
CryptoKeysRange KmsClient::ListCryptoKeys(
const kms_v1::ListCryptoKeysRequest& request) const {
return CryptoKeysRange(
request,
[this](const kms_v1::ListCryptoKeysRequest& request)
-> absl::StatusOr<kms_v1::ListCryptoKeysResponse> {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "parent", request.parent());
kms_v1::ListCryptoKeysResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->ListCryptoKeys(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
},
[](kms_v1::ListCryptoKeysResponse response)
-> std::vector<kms_v1::CryptoKey> {
std::vector<kms_v1::CryptoKey> result(response.crypto_keys_size());
auto& keys = *response.mutable_crypto_keys();
std::move(keys.begin(), keys.end(), result.begin());
return result;
});
}
CryptoKeyVersionsRange KmsClient::ListCryptoKeyVersions(
const kms_v1::ListCryptoKeyVersionsRequest& request) const {
return CryptoKeyVersionsRange(
request,
[this](const kms_v1::ListCryptoKeyVersionsRequest& request)
-> absl::StatusOr<kms_v1::ListCryptoKeyVersionsResponse> {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "parent", request.parent());
kms_v1::ListCryptoKeyVersionsResponse response;
absl::Status rpc_result = ToStatus(
kms_stub_->ListCryptoKeyVersions(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
},
[](kms_v1::ListCryptoKeyVersionsResponse response)
-> std::vector<kms_v1::CryptoKeyVersion> {
std::vector<kms_v1::CryptoKeyVersion> result(
response.crypto_key_versions_size());
auto& versions = *response.mutable_crypto_key_versions();
std::move(versions.begin(), versions.end(), result.begin());
return result;
});
}
absl::StatusOr<kms_v1::GenerateRandomBytesResponse>
KmsClient::GenerateRandomBytes(
const kms_v1::GenerateRandomBytesRequest& request) const {
grpc::ClientContext ctx;
AddContextSettings(&ctx, "location", request.location());
kms_v1::GenerateRandomBytesResponse response;
absl::Status rpc_result =
ToStatus(kms_stub_->GenerateRandomBytes(&ctx, request, &response));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
return response;
}
absl::Status KmsClient::WaitForGeneration(kms_v1::CryptoKeyVersion& ckv,
absl::Time deadline) const {
// The time for newly generated HSM keys to flip to enabled in (real) KMS
// varies from 10-40ish milliseconds depending on key type.
constexpr absl::Duration kMinDelay = absl::Milliseconds(20);
constexpr absl::Duration kMaxDelay = absl::Seconds(1);
int tries = 0;
while (ckv.state() == kms_v1::CryptoKeyVersion::PENDING_GENERATION) {
absl::SleepFor(ComputeBackoff(kMinDelay, kMaxDelay, tries++));
grpc::ClientContext ctx;
AddContextSettings(&ctx, "name", ckv.name(), deadline);
kms_v1::GetCryptoKeyVersionRequest req;
req.set_name(ckv.name());
absl::Status rpc_result =
ToStatus(kms_stub_->GetCryptoKeyVersion(&ctx, req, &ckv));
if (!rpc_result.ok()) {
return DecorateStatus(rpc_result);
}
}
return absl::OkStatus();
}
} // namespace cloud_kms