blob: c90572c0bedbbd5406924d0d4b7fa015d0086cda [file] [log] [blame]
// Copyright 2019 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 "tink/integration/gcpkms/gcp_kms_client.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include "grpcpp/channel.h"
#include "grpcpp/create_channel.h"
#include "grpcpp/security/credentials.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "tink/integration/gcpkms/gcp_kms_aead.h"
#include "tink/kms_clients.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "tink/version.h"
namespace crypto {
namespace tink {
namespace integration {
namespace gcpkms {
namespace {
using crypto::tink::Version;
using crypto::tink::util::Status;
using crypto::tink::util::StatusOr;
using google::cloud::kms::v1::KeyManagementService;
using grpc::ChannelArguments;
using grpc::ChannelCredentials;
static constexpr char kKeyUriPrefix[] = "gcp-kms://";
static constexpr char kGcpKmsServer[] = "cloudkms.googleapis.com";
static constexpr char kTinkUserAgentPrefix[] = "Tink/";
StatusOr<std::string> ReadFile(absl::string_view filename) {
std::ifstream input_stream;
input_stream.open(std::string(filename), std::ifstream::in);
if (!input_stream.is_open()) {
return Status(absl::StatusCode::kInvalidArgument,
absl::StrCat("Error reading file ", filename));
}
std::stringstream input;
input << input_stream.rdbuf();
input_stream.close();
return input.str();
}
StatusOr<std::shared_ptr<ChannelCredentials>> GetCredentials(
absl::string_view credentials_path) {
if (credentials_path.empty()) {
auto creds = grpc::GoogleDefaultCredentials();
if (creds == nullptr) {
return Status(absl::StatusCode::kInternal,
"Could not read default credentials");
}
return creds;
}
// Try reading credentials from a file.
auto json_creds_result = ReadFile(credentials_path);
if (!json_creds_result.ok()) return json_creds_result.status();
auto creds =
grpc::ServiceAccountJWTAccessCredentials(json_creds_result.value());
if (creds != nullptr) {
// Creating "empty" 'channel_creds', to convert 'creds'
// to ChannelCredentials via CompositeChannelCredentials().
auto channel_creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
return grpc::CompositeChannelCredentials(channel_creds, creds);
}
return Status(
absl::StatusCode::kInvalidArgument,
absl::StrCat("Could not load credentials from file ", credentials_path));
}
// Returns GCP KMS key name contained in 'key_uri'.
// If 'key_uri' does not refer to an GCP key, returns an empty string.
std::string GetKeyName(absl::string_view key_uri) {
if (!absl::StartsWithIgnoreCase(key_uri, kKeyUriPrefix)) return "";
return std::string(key_uri.substr(std::string(kKeyUriPrefix).length()));
}
} // namespace
// static
StatusOr<std::unique_ptr<GcpKmsClient>> GcpKmsClient::New(
absl::string_view key_uri, absl::string_view credentials_path) {
std::unique_ptr<GcpKmsClient> client(new GcpKmsClient());
// If a specific key is given, create a GCP KMSClient.
if (!key_uri.empty()) {
client->key_name_ = GetKeyName(key_uri);
if (client->key_name_.empty()) {
return Status(absl::StatusCode::kInvalidArgument,
absl::StrCat("Key '", key_uri, "' not supported"));
}
}
// Read credentials.
auto creds_result = GetCredentials(credentials_path);
if (!creds_result.ok()) {
return creds_result.status();
}
// Create a KMS stub.
ChannelArguments args;
args.SetUserAgentPrefix(
absl::StrCat(kTinkUserAgentPrefix, Version::kTinkVersion, " CPP-Python"));
client->kms_stub_ = KeyManagementService::NewStub(
grpc::CreateCustomChannel(kGcpKmsServer, creds_result.value(), args));
return std::move(client);
}
bool GcpKmsClient::DoesSupport(absl::string_view key_uri) const {
if (!key_name_.empty()) {
return key_name_ == GetKeyName(key_uri);
}
return !GetKeyName(key_uri).empty();
}
StatusOr<std::unique_ptr<Aead>> GcpKmsClient::GetAead(
absl::string_view key_uri) const {
if (!DoesSupport(key_uri)) {
if (!key_name_.empty()) {
return Status(absl::StatusCode::kInvalidArgument,
absl::StrCat("This client is bound to ", key_name_,
" and cannot use key ", key_uri));
} else {
return Status(absl::StatusCode::kInvalidArgument,
absl::StrCat("This client does not support key ", key_uri));
}
}
if (!key_name_.empty()) { // This client is bound to a specific key.
return GcpKmsAead::New(key_name_, kms_stub_);
} else { // Create an GCP KMSClient for the given key.
auto key_name = GetKeyName(key_uri);
return GcpKmsAead::New(key_name, kms_stub_);
}
}
Status GcpKmsClient::RegisterNewClient(absl::string_view key_uri,
absl::string_view credentials_path) {
auto client_result = GcpKmsClient::New(key_uri, credentials_path);
if (!client_result.ok()) {
return client_result.status();
}
return KmsClients::Add(std::move(client_result.value()));
}
} // namespace gcpkms
} // namespace integration
} // namespace tink
} // namespace crypto