kmsp11/object_loader.cc (227 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 "kmsp11/object_loader.h"
#include "common/status_macros.h"
#include "glog/logging.h"
#include "kmsp11/algorithm_details.h"
#include "kmsp11/util/crypto_utils.h"
#include "kmsp11/util/errors.h"
namespace cloud_kms::kmsp11 {
namespace {
std::string EnumNameOrValue(std::string name, int value) {
return name.empty() ? std::to_string(value) : name;
}
} // namespace
bool ObjectLoader::IsLoadable(const kms_v1::CryptoKey& key) {
switch (key.purpose()) {
case kms_v1::CryptoKey::ASYMMETRIC_DECRYPT:
case kms_v1::CryptoKey::ASYMMETRIC_SIGN:
case kms_v1::CryptoKey::MAC:
case kms_v1::CryptoKey::RAW_ENCRYPT_DECRYPT:
break;
default:
LOG(INFO) << "INFO: key " << key.name()
<< " is not loadable due to unsupported purpose "
<< EnumNameOrValue(
kms_v1::CryptoKey::CryptoKeyPurpose_Name(key.purpose()),
key.purpose());
return false;
}
if (key.version_template().protection_level() !=
kms_v1::ProtectionLevel::HSM &&
key.version_template().protection_level() !=
kms_v1::ProtectionLevel::SOFTWARE) {
LOG(INFO) << "INFO: key " << key.name()
<< " is not loadable due to unsupported protection level "
<< EnumNameOrValue(kms_v1::ProtectionLevel_Name(
key.version_template().protection_level()),
key.version_template().protection_level());
return false;
}
if (key.version_template().protection_level() ==
kms_v1::ProtectionLevel::SOFTWARE &&
!allow_software_keys_) {
LOG(INFO) << "INFO: key " << key.name()
<< " is not loadable because it has protection level = "
"SOFTWARE but only keys with protection level = HSM are "
"allowed. If you want to be able to use software keys, use "
"allow_software_keys in the configuration.";
return false;
}
return true;
}
bool ObjectLoader::IsLoadable(const kms_v1::CryptoKeyVersion& ckv) {
if (ckv.state() != kms_v1::CryptoKeyVersion::ENABLED) {
LOG(INFO) << "INFO: version " << ckv.name()
<< " is not loadable due to unsupported state "
<< EnumNameOrValue(
kms_v1::CryptoKeyVersion::CryptoKeyVersionState_Name(
ckv.state()),
ckv.state());
return false;
}
if (!GetDetails(ckv.algorithm()).ok()) {
LOG(INFO) << "INFO: version " << ckv.name()
<< " is not loadable due to unsupported algorithm "
<< EnumNameOrValue(
kms_v1::CryptoKeyVersion::CryptoKeyVersionAlgorithm_Name(
ckv.algorithm()),
ckv.algorithm());
return false;
}
return true;
}
Key* ObjectLoader::Cache::Get(std::string_view ckv_name) {
auto it = keys_.find(ckv_name);
if (it == keys_.end()) {
return nullptr;
}
return it->second.get();
}
Key* ObjectLoader::Cache::Store(const kms_v1::CryptoKeyVersion& ckv,
std::string_view public_key_der,
std::string_view certificate_der) {
keys_[ckv.name()] = std::make_unique<Key>();
Key* key = keys_[ckv.name()].get();
*key->mutable_crypto_key_version() = ckv;
key->set_public_key_handle(NewHandle());
key->set_private_key_handle(NewHandle());
key->set_public_key_der(std::string(public_key_der));
if (!certificate_der.empty()) {
key->mutable_certificate()->set_x509_der(std::string(certificate_der));
key->mutable_certificate()->set_handle(NewHandle());
}
return key;
}
Key* ObjectLoader::Cache::StoreSecretKey(const kms_v1::CryptoKeyVersion& ckv) {
keys_[ckv.name()] = std::make_unique<Key>();
Key* key = keys_[ckv.name()].get();
*key->mutable_crypto_key_version() = ckv;
key->set_secret_key_handle(NewHandle());
return key;
}
void ObjectLoader::Cache::EvictUnused(const ObjectStoreState& state) {
absl::flat_hash_set<std::string> items_to_retain;
for (const Key& key : state.keys()) {
items_to_retain.insert(key.crypto_key_version().name());
}
auto it = keys_.begin();
while (it != keys_.end()) {
if (items_to_retain.contains(it->first)) {
it++;
continue;
}
if (it->second->public_key_handle() != CK_INVALID_HANDLE) {
allocated_handles_.erase(it->second->public_key_handle());
}
if (it->second->private_key_handle() != CK_INVALID_HANDLE) {
allocated_handles_.erase(it->second->private_key_handle());
}
if (it->second->has_certificate()) {
allocated_handles_.erase(it->second->certificate().handle());
}
if (it->second->secret_key_handle() != CK_INVALID_HANDLE) {
allocated_handles_.erase(it->second->secret_key_handle());
}
keys_.erase(it++);
}
}
CK_OBJECT_HANDLE ObjectLoader::Cache::NewHandle() {
CK_OBJECT_HANDLE handle;
do {
handle = RandomHandle();
} while (allocated_handles_.contains(handle));
allocated_handles_.insert(handle);
return handle;
}
absl::StatusOr<std::unique_ptr<ObjectLoader>> ObjectLoader::New(
std::string_view key_ring_name,
absl::Span<const std::string* const> pem_user_certs, bool generate_certs,
bool allow_software_keys) {
absl::flat_hash_map<std::string, std::string> user_certs;
for (const std::string* const pem_cert : pem_user_certs) {
ASSIGN_OR_RETURN(bssl::UniquePtr<X509> parsed_cert,
ParseX509CertificatePem(*pem_cert));
bssl::UniquePtr<EVP_PKEY> public_key(X509_get_pubkey(parsed_cert.get()));
if (!public_key) {
return NewInternalError(
absl::StrCat("failed to retrieve X.509 certificate's public key: ",
SslErrorToString()),
SOURCE_LOCATION);
}
ASSIGN_OR_RETURN(std::string der_public_key,
MarshalX509PublicKeyDer(public_key.get()));
ASSIGN_OR_RETURN(user_certs[der_public_key],
MarshalX509CertificateDer(parsed_cert.get()));
}
std::unique_ptr<CertAuthority> cert_authority;
if (generate_certs) {
ASSIGN_OR_RETURN(cert_authority, CertAuthority::New());
}
return absl::WrapUnique(new ObjectLoader(key_ring_name, user_certs,
std::move(cert_authority),
allow_software_keys));
}
absl::StatusOr<ObjectStoreState> ObjectLoader::BuildState(
const KmsClient& client) {
// In the initial implementation of Provider::LoopRefresh, there is no danger
// of overlapping calls to BuildState. That said, holding the mutex for the
// duration of BuildState seems like a pretty cheap way to guard against an
// unintentional change that causes BuildState calls to overlap.
absl::MutexLock lock(&cache_mutex_);
ObjectStoreState result;
kms_v1::ListCryptoKeysRequest req;
req.set_parent(key_ring_name_);
CryptoKeysRange keys = client.ListCryptoKeys(req);
for (CryptoKeysRange::iterator it = keys.begin(); it != keys.end(); it++) {
ASSIGN_OR_RETURN(kms_v1::CryptoKey key, *it);
if (!IsLoadable(key)) {
continue;
}
kms_v1::ListCryptoKeyVersionsRequest req;
req.set_parent(key.name());
CryptoKeyVersionsRange v = client.ListCryptoKeyVersions(req);
for (CryptoKeyVersionsRange::iterator it = v.begin(); it != v.end(); it++) {
ASSIGN_OR_RETURN(kms_v1::CryptoKeyVersion ckv, *it);
if (!IsLoadable(ckv)) {
continue;
}
Key* cached_key = cache_.Get(ckv.name());
if (cached_key) {
*result.add_keys() = *cached_key;
continue;
}
if (key.purpose() == kms_v1::CryptoKey::MAC ||
key.purpose() == kms_v1::CryptoKey::RAW_ENCRYPT_DECRYPT) {
*result.add_keys() = *cache_.StoreSecretKey(ckv);
} else {
kms_v1::GetPublicKeyRequest pub_req;
pub_req.set_name(ckv.name());
ASSIGN_OR_RETURN(kms_v1::PublicKey pub_resp,
client.GetPublicKey(pub_req));
ASSIGN_OR_RETURN(bssl::UniquePtr<EVP_PKEY> pub,
ParseX509PublicKeyPem(pub_resp.pem()));
ASSIGN_OR_RETURN(std::string public_key_der,
MarshalX509PublicKeyDer(pub.get()));
std::string cert_der;
if (auto it = user_certs_.find(public_key_der);
it != user_certs_.end()) {
cert_der = it->second;
} else if (cert_authority_) {
ASSIGN_OR_RETURN(bssl::UniquePtr<X509> cert,
cert_authority_->GenerateCert(ckv, pub.get()));
ASSIGN_OR_RETURN(cert_der, MarshalX509CertificateDer(cert.get()));
}
*result.add_keys() = *cache_.Store(ckv, public_key_der, cert_der);
}
}
}
// Compute the unused user certificates by copying all of them, then removing
// the ones that were actually used.
absl::flat_hash_set<std::string> unused_user_certs;
for (const auto& [spki, cert] : user_certs_) {
unused_user_certs.emplace(cert);
}
for (const Key& key : result.keys()) {
unused_user_certs.erase(key.certificate().x509_der());
}
if (unused_user_certs.size() > 0) {
LOG(INFO) << "INFO: one or more provided certificates could not be matched "
"to a KMS key.";
}
cache_.EvictUnused(result);
return result;
}
} // namespace cloud_kms::kmsp11