kmsp11/session.cc (524 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/session.h" #include <regex> #include "absl/container/flat_hash_set.h" #include "absl/strings/str_join.h" #include "common/kms_client.h" #include "common/status_macros.h" #include "kmsp11/kmsp11.h" #include "kmsp11/util/errors.h" namespace cloud_kms::kmsp11 { namespace { absl::Status SessionReadOnlyError(const SourceLocation& source_location) { return NewError(absl::StatusCode::kFailedPrecondition, "session is read-only", CKR_SESSION_READ_ONLY, source_location); } struct KeyGenerationParams { std::string label; AlgorithmDetails algorithm; std::optional<kms_v1::ProtectionLevel> protection_level; }; absl::StatusOr<KeyGenerationParams> ExtractKeyGenerationParams( absl::Span<const CK_ATTRIBUTE> prv_template, bool allow_software_keys) { std::optional<std::string> label; std::optional<kms_v1::CryptoKeyVersion::CryptoKeyVersionAlgorithm> algorithm; std::optional<kms_v1::ProtectionLevel> protection_level; for (const CK_ATTRIBUTE& attr : prv_template) { switch (attr.type) { case CKA_LABEL: label = std::string(reinterpret_cast<char*>(attr.pValue), attr.ulValueLen); static std::regex* label_regexp = new std::regex("[a-zA-Z0-9_-]{1,63}"); if (!std::regex_match(*label, *label_regexp)) { return NewInvalidArgumentError( "CKA_LABEL must be a valid Cloud KMS CryptoKey ID", CKR_ATTRIBUTE_VALUE_INVALID, SOURCE_LOCATION); } break; case CKA_KMS_ALGORITHM: if (attr.ulValueLen != sizeof(CK_ULONG)) { return NewInvalidArgumentError( absl::StrFormat("CKA_KMS_ALGORITHM value should be CK_ULONG " "(size=%d, got=%d)", sizeof(CK_ULONG), attr.ulValueLen), CKR_ATTRIBUTE_VALUE_INVALID, SOURCE_LOCATION); } algorithm = kms_v1::CryptoKeyVersion::CryptoKeyVersionAlgorithm( *static_cast<CK_ULONG*>(attr.pValue)); break; case CKA_KMS_PROTECTION_LEVEL: if (attr.ulValueLen != sizeof(CK_ULONG)) { return NewInvalidArgumentError( absl::StrFormat( "CKA_KMS_PROTECTION_LEVEL value should be CK_ULONG " "(size=%d, got=%d)", sizeof(CK_ULONG), attr.ulValueLen), CKR_ATTRIBUTE_VALUE_INVALID, SOURCE_LOCATION); } protection_level = kms_v1::ProtectionLevel(*static_cast<CK_ULONG*>(attr.pValue)); if (protection_level != kms_v1::SOFTWARE && protection_level != kms_v1::HSM) { return NewInvalidArgumentError( absl::StrFormat("CKA_KMS_PROTECTION_LEVEL value should be " "1(SOFTWARE) or 2(HSM) " ", got=%d", attr.ulValueLen), CKR_ATTRIBUTE_VALUE_INVALID, SOURCE_LOCATION); } if (protection_level == kms_v1::SOFTWARE && !allow_software_keys) { return NewInvalidArgumentError( "CKA_KMS_PROTECTION_LEVEL cannot be SOFTWARE because only keys " "with protection level = HSM are " "allowed. If you want to be able to create software keys, use " "allow_software_keys in the " "configuration.", CKR_ATTRIBUTE_VALUE_INVALID, SOURCE_LOCATION); } break; default: return NewInvalidArgumentError( absl::StrFormat( "this token does not permit specifying attribute type %#x", attr.type), CKR_TEMPLATE_INCONSISTENT, SOURCE_LOCATION); } } if (!label.has_value()) { return NewInvalidArgumentError("CKA_LABEL must be specified for the key", CKR_TEMPLATE_INCOMPLETE, SOURCE_LOCATION); } if (!algorithm.has_value()) { return NewInvalidArgumentError( "CKA_KMS_ALGORITHM must be specified for the key", CKR_TEMPLATE_INCOMPLETE, SOURCE_LOCATION); } absl::StatusOr<AlgorithmDetails> algorithm_details = GetDetails(*algorithm); if (!algorithm_details.ok()) { return NewInvalidArgumentError(algorithm_details.status().message(), CKR_ATTRIBUTE_VALUE_INVALID, SOURCE_LOCATION); } return KeyGenerationParams{*label, *algorithm_details, protection_level}; } absl::StatusOr<kms_v1::CryptoKeyVersion> CreateNewVersionOfExistingKey( const KmsClient& client, const kms_v1::CryptoKey& crypto_key, const KeyGenerationParams& gen_params, bool allow_software_keys) { if (crypto_key.purpose() != gen_params.algorithm.purpose) { return NewError( absl::StatusCode::kInvalidArgument, absl::StrFormat("key attribute mismatch when attempting to create " "new version of existing key: " "current purpose=%d, requested purpose=%d", crypto_key.purpose(), gen_params.algorithm.purpose), CKR_ARGUMENTS_BAD, SOURCE_LOCATION); } if (crypto_key.version_template().algorithm() != gen_params.algorithm.algorithm) { return NewError( absl::StatusCode::kInvalidArgument, absl::StrFormat("key attribute mismatch when attempting to create " "new version of existing key: " "current algorithm=%d, requested algorithm=%d", crypto_key.version_template().algorithm(), gen_params.algorithm.algorithm), CKR_ARGUMENTS_BAD, SOURCE_LOCATION); } // Check that if the protection level is specified in the key generation // params, it matches the protection level of the crypto key. if (gen_params.protection_level.has_value() && *gen_params.protection_level != crypto_key.version_template().protection_level()) { return NewError( absl::StatusCode::kInvalidArgument, absl::StrFormat("key attribute mismatch when attempting to create " "new version of existing key: " "current protection_level=%d, " "requested protection_level=%d", crypto_key.version_template().protection_level(), *gen_params.protection_level), CKR_ARGUMENTS_BAD, SOURCE_LOCATION); } // Check the crypto key's protection level against the allowed protection // levels. absl::flat_hash_set<kms_v1::ProtectionLevel> allowed_protection_levels = { kms_v1::HSM}; if (allow_software_keys) allowed_protection_levels.insert(kms_v1::SOFTWARE); if (!allowed_protection_levels.contains( crypto_key.version_template().protection_level())) { return NewError( absl::StatusCode::kInvalidArgument, absl::StrFormat("key attribute mismatch when attempting to create " "new version of existing key: " "current protection_level=%d, " "allowed protection_level=%s", crypto_key.version_template().protection_level(), absl::StrJoin(allowed_protection_levels, " or ")), CKR_ARGUMENTS_BAD, SOURCE_LOCATION); } kms_v1::CreateCryptoKeyVersionRequest req; req.set_parent(crypto_key.name()); return client.CreateCryptoKeyVersionAndWait(req); } absl::StatusOr<CryptoKeyAndVersion> CreateKeyAndVersion( const KmsClient& client, std::string_view key_ring_name, const KeyGenerationParams& gen_params, bool experimental_create_multiple_versions, bool allow_software_keys) { if (experimental_create_multiple_versions) { kms_v1::GetCryptoKeyRequest req; req.set_name(absl::StrCat(key_ring_name, "/cryptoKeys/", gen_params.label)); absl::StatusOr<kms_v1::CryptoKey> ck = client.GetCryptoKey(req); switch (ck.status().code()) { case absl::StatusCode::kOk: { ASSIGN_OR_RETURN(kms_v1::CryptoKeyVersion ckv, CreateNewVersionOfExistingKey(client, *ck, gen_params, allow_software_keys)); return CryptoKeyAndVersion{*ck, ckv}; } case absl::StatusCode::kNotFound: // The CryptoKey doesn't exist; continue on to create it. break; default: return ck.status(); } } kms_v1::CreateCryptoKeyRequest req; req.set_parent(std::string(key_ring_name)); req.set_crypto_key_id(gen_params.label); req.mutable_crypto_key()->set_purpose(gen_params.algorithm.purpose); req.mutable_crypto_key()->mutable_version_template()->set_algorithm( gen_params.algorithm.algorithm); // Unless otherwise specified in the key generation params, we generate keys // with protection level = HSM. if (gen_params.protection_level.has_value()) { req.mutable_crypto_key()->mutable_version_template()->set_protection_level( *gen_params.protection_level); } else { req.mutable_crypto_key()->mutable_version_template()->set_protection_level( kms_v1::HSM); } absl::StatusOr<CryptoKeyAndVersion> key_and_version = client.CreateCryptoKeyAndWaitForFirstVersion(req); if (absl::IsAlreadyExists(key_and_version.status())) { if (experimental_create_multiple_versions) { // If we choose to make this experiment a full-fledged feature, we should // gracefully handle the case where the CryptoKey is created by another // (thread|process|caller) while this request is in flight. // // That recursive logic will be sort of ugly and complicated; just // returning CKR_DEVICE_ERROR/AlreadyExists here seems fine for the // purposes of the experiment. return key_and_version.status(); } return NewError( absl::StatusCode::kAlreadyExists, absl::StrFormat("key with label %s already exists: %s", gen_params.label, key_and_version.status().message()), CKR_ARGUMENTS_BAD, SOURCE_LOCATION); } return key_and_version; } } // namespace CK_SESSION_INFO Session::info() const { bool is_read_write = session_type_ == SessionType::kReadWrite; CK_STATE state; if (token_->is_logged_in()) { state = is_read_write ? CKS_RW_USER_FUNCTIONS : CKS_RO_USER_FUNCTIONS; } else { state = is_read_write ? CKS_RW_PUBLIC_SESSION : CKS_RO_PUBLIC_SESSION; } CK_FLAGS flags = CKF_SERIAL_SESSION; if (is_read_write) { flags |= CKF_RW_SESSION; } return CK_SESSION_INFO{ token_->slot_id(), // slotID state, // state flags, // flags 0, // ulDeviceError }; } void Session::ReleaseOperation() { absl::MutexLock l(&op_mutex_); op_ = std::nullopt; } absl::Status Session::FindObjectsInit( absl::Span<const CK_ATTRIBUTE> attributes) { absl::MutexLock l(&op_mutex_); if (op_.has_value()) { return OperationActiveError(SOURCE_LOCATION); } std::vector<CK_OBJECT_HANDLE> results = token_->FindObjects([&attributes](const Object& o) -> bool { for (const CK_ATTRIBUTE& attr : attributes) { if (!o.attributes().Contains(attr)) { return false; } } return true; }); op_ = FindOp(results); return absl::OkStatus(); } absl::StatusOr<absl::Span<const CK_OBJECT_HANDLE>> Session::FindObjects( size_t max_count) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<FindOp>(*op_)) { return OperationNotInitializedError("find", SOURCE_LOCATION); } FindOp& op = std::get<FindOp>(*op_); return op.Next(max_count); } absl::Status Session::FindObjectsFinal() { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<FindOp>(*op_)) { return OperationNotInitializedError("find", SOURCE_LOCATION); } op_ = std::nullopt; return absl::OkStatus(); } absl::Status Session::DecryptInit(std::shared_ptr<Object> key, CK_MECHANISM* mechanism) { absl::MutexLock l(&op_mutex_); if (op_.has_value()) { return OperationActiveError(SOURCE_LOCATION); } ASSIGN_OR_RETURN(op_, NewDecryptOp(key, mechanism)); return absl::OkStatus(); } absl::StatusOr<absl::Span<const uint8_t>> Session::Decrypt( absl::Span<const uint8_t> ciphertext) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<DecryptOp>(*op_)) { return OperationNotInitializedError("decrypt", SOURCE_LOCATION); } return std::get<DecryptOp>(*op_)->Decrypt(kms_client_, ciphertext); } absl::Status Session::DecryptUpdate(absl::Span<const uint8_t> ciphertext) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<DecryptOp>(*op_)) { return OperationNotInitializedError("decrypt", SOURCE_LOCATION); } return std::get<DecryptOp>(*op_)->DecryptUpdate(kms_client_, ciphertext); } absl::StatusOr<absl::Span<const uint8_t>> Session::DecryptFinal() { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<DecryptOp>(*op_)) { return OperationNotInitializedError("decrypt", SOURCE_LOCATION); } return std::get<DecryptOp>(*op_)->DecryptFinal(kms_client_); } absl::Status Session::EncryptInit(std::shared_ptr<Object> key, CK_MECHANISM* mechanism) { absl::MutexLock l(&op_mutex_); if (op_.has_value()) { return OperationActiveError(SOURCE_LOCATION); } ASSIGN_OR_RETURN(op_, NewEncryptOp(key, mechanism)); return absl::OkStatus(); } absl::StatusOr<absl::Span<const uint8_t>> Session::Encrypt( absl::Span<const uint8_t> plaintext) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<EncryptOp>(*op_)) { return OperationNotInitializedError("encrypt", SOURCE_LOCATION); } return std::get<EncryptOp>(*op_)->Encrypt(kms_client_, plaintext); } absl::Status Session::EncryptUpdate(absl::Span<const uint8_t> plaintext) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<EncryptOp>(*op_)) { return OperationNotInitializedError("encrypt", SOURCE_LOCATION); } return std::get<EncryptOp>(*op_)->EncryptUpdate(kms_client_, plaintext); } absl::StatusOr<absl::Span<const uint8_t>> Session::EncryptFinal() { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<EncryptOp>(*op_)) { return OperationNotInitializedError("encrypt", SOURCE_LOCATION); } return std::get<EncryptOp>(*op_)->EncryptFinal(kms_client_); } absl::Status Session::SignInit(std::shared_ptr<Object> key, CK_MECHANISM* mechanism) { absl::MutexLock l(&op_mutex_); if (op_.has_value()) { return OperationActiveError(SOURCE_LOCATION); } ASSIGN_OR_RETURN(op_, NewSignOp(key, mechanism)); return absl::OkStatus(); } absl::Status Session::Sign(absl::Span<const uint8_t> digest, absl::Span<uint8_t> signature) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<SignOp>(*op_)) { return OperationNotInitializedError("sign", SOURCE_LOCATION); } return std::get<SignOp>(*op_)->Sign(kms_client_, digest, signature); } absl::Status Session::SignUpdate(absl::Span<const uint8_t> data) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<SignOp>(*op_)) { return OperationNotInitializedError("sign", SOURCE_LOCATION); } return std::get<SignOp>(*op_)->SignUpdate(kms_client_, data); } absl::Status Session::SignFinal(absl::Span<uint8_t> signature) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<SignOp>(*op_)) { return OperationNotInitializedError("sign", SOURCE_LOCATION); } return std::get<SignOp>(*op_)->SignFinal(kms_client_, signature); } absl::StatusOr<size_t> Session::SignatureLength() { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<SignOp>(*op_)) { return OperationNotInitializedError("sign", SOURCE_LOCATION); } return std::get<SignOp>(*op_)->signature_length(); } absl::Status Session::VerifyInit(std::shared_ptr<Object> key, CK_MECHANISM* mechanism) { absl::MutexLock l(&op_mutex_); if (op_.has_value()) { return OperationActiveError(SOURCE_LOCATION); } ASSIGN_OR_RETURN(op_, NewVerifyOp(key, mechanism)); return absl::OkStatus(); } absl::Status Session::Verify(absl::Span<const uint8_t> digest, absl::Span<const uint8_t> signature) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<VerifyOp>(*op_)) { return OperationNotInitializedError("verify", SOURCE_LOCATION); } return std::get<VerifyOp>(*op_)->Verify(kms_client_, digest, signature); } absl::Status Session::VerifyUpdate(absl::Span<const uint8_t> data) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<VerifyOp>(*op_)) { return OperationNotInitializedError("verify", SOURCE_LOCATION); } return std::get<VerifyOp>(*op_)->VerifyUpdate(kms_client_, data); } absl::Status Session::VerifyFinal(absl::Span<const uint8_t> signature) { absl::MutexLock l(&op_mutex_); if (!op_.has_value() || !std::holds_alternative<VerifyOp>(*op_)) { return OperationNotInitializedError("verify", SOURCE_LOCATION); } return std::get<VerifyOp>(*op_)->VerifyFinal(kms_client_, signature); } absl::StatusOr<AsymmetricHandleSet> Session::GenerateKeyPair( const CK_MECHANISM& mechanism, absl::Span<const CK_ATTRIBUTE> public_key_attrs, absl::Span<const CK_ATTRIBUTE> private_key_attrs, bool experimental_create_multiple_versions, bool allow_software_keys) { if (session_type_ == SessionType::kReadOnly) { return SessionReadOnlyError(SOURCE_LOCATION); } switch (mechanism.mechanism) { case CKM_RSA_PKCS_KEY_PAIR_GEN: case CKM_EC_KEY_PAIR_GEN: break; default: return InvalidMechanismError(mechanism.mechanism, "GenerateKeyPair", SOURCE_LOCATION); } if (mechanism.pParameter || mechanism.ulParameterLen > 0) { return InvalidMechanismParamError( "key generation mechanisms do not take parameters", SOURCE_LOCATION); } if (!public_key_attrs.empty()) { return NewInvalidArgumentError( "this token does not accept public key attributes", CKR_TEMPLATE_INCONSISTENT, SOURCE_LOCATION); } ASSIGN_OR_RETURN( KeyGenerationParams prv_gen_params, ExtractKeyGenerationParams(private_key_attrs, allow_software_keys)); if (prv_gen_params.algorithm.key_gen_mechanism != mechanism.mechanism) { return NewInvalidArgumentError("algorithm mismatches keygen mechanism", CKR_TEMPLATE_INCONSISTENT, SOURCE_LOCATION); } ASSIGN_OR_RETURN( CryptoKeyAndVersion key_and_version, CreateKeyAndVersion(*kms_client_, token_->key_ring_name(), prv_gen_params, experimental_create_multiple_versions, allow_software_keys)); RETURN_IF_ERROR(token_->RefreshState(*kms_client_)); AsymmetricHandleSet result; ASSIGN_OR_RETURN(result.public_key_handle, token_->FindSingleObject([&](const Object& o) -> bool { return o.kms_key_name() == key_and_version.crypto_key_version.name() && o.object_class() == CKO_PUBLIC_KEY; })); ASSIGN_OR_RETURN(result.private_key_handle, token_->FindSingleObject([&](const Object& o) -> bool { return o.kms_key_name() == key_and_version.crypto_key_version.name() && o.object_class() == CKO_PRIVATE_KEY; })); return result; } absl::StatusOr<CK_OBJECT_HANDLE> Session::GenerateKey( const CK_MECHANISM& mechanism, absl::Span<const CK_ATTRIBUTE> secret_key_attrs, bool experimental_create_multiple_versions, bool allow_software_keys) { if (session_type_ == SessionType::kReadOnly) { return SessionReadOnlyError(SOURCE_LOCATION); } switch (mechanism.mechanism) { case CKM_GENERIC_SECRET_KEY_GEN: case CKM_AES_KEY_GEN: break; default: return InvalidMechanismError(mechanism.mechanism, "GenerateKey", SOURCE_LOCATION); } if (mechanism.pParameter || mechanism.ulParameterLen > 0) { return InvalidMechanismParamError( "key generation mechanisms do not take parameters", SOURCE_LOCATION); } ASSIGN_OR_RETURN( KeyGenerationParams gen_params, ExtractKeyGenerationParams(secret_key_attrs, allow_software_keys)); if (gen_params.algorithm.key_gen_mechanism != mechanism.mechanism) { return NewInvalidArgumentError("algorithm mismatches keygen mechanism", CKR_TEMPLATE_INCONSISTENT, SOURCE_LOCATION); } ASSIGN_OR_RETURN( CryptoKeyAndVersion key_and_version, CreateKeyAndVersion(*kms_client_, token_->key_ring_name(), gen_params, experimental_create_multiple_versions, allow_software_keys)); RETURN_IF_ERROR(token_->RefreshState(*kms_client_)); return token_->FindSingleObject([&](const Object& o) -> bool { return o.kms_key_name() == key_and_version.crypto_key_version.name() && o.object_class() == CKO_SECRET_KEY; }); } absl::Status Session::DestroyObject(std::shared_ptr<Object> key) { if (session_type_ == SessionType::kReadOnly) { return SessionReadOnlyError(SOURCE_LOCATION); } CK_BBOOL ck_true = CK_TRUE; if (!key->attributes().Contains( CK_ATTRIBUTE{CKA_DESTROYABLE, &ck_true, sizeof(ck_true)})) { return FailedPreconditionError("the selected object is not destroyable", CKR_ACTION_PROHIBITED, SOURCE_LOCATION); } kms_v1::DestroyCryptoKeyVersionRequest req; req.set_name(std::string(key->kms_key_name())); RETURN_IF_ERROR(kms_client_->DestroyCryptoKeyVersion(req)); RETURN_IF_ERROR(token_->RefreshState(*kms_client_)); return absl::OkStatus(); } absl::Status Session::GenerateRandom(absl::Span<uint8_t> buffer) { if (buffer.size() < 8 || buffer.size() > 1024) { return NewError( absl::StatusCode::kInvalidArgument, "GenerateRandom buffer length must be between 8 and 1024 bytes", CKR_ARGUMENTS_BAD, SOURCE_LOCATION); } kms_v1::GenerateRandomBytesRequest req; req.set_protection_level(kms_v1::HSM); req.set_length_bytes(buffer.size()); req.set_location(*ExtractLocationName(token_->key_ring_name())); ASSIGN_OR_RETURN(kms_v1::GenerateRandomBytesResponse resp, kms_client_->GenerateRandomBytes(req)); if (resp.data().size() != buffer.size()) { return NewInternalError( absl::StrFormat("requested %d bytes of data from KMS but received %d", buffer.size(), resp.data().size()), SOURCE_LOCATION); } std::copy(resp.data().begin(), resp.data().end(), buffer.data()); return absl::OkStatus(); } } // namespace cloud_kms::kmsp11