kmsp11/operation/aes_cbc.cc (260 lines of code) (raw):

// Copyright 2022 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/operation/aes_cbc.h" #include "absl/cleanup/cleanup.h" #include "common/status_macros.h" #include "kmsp11/kmsp11.h" #include "kmsp11/object.h" #include "kmsp11/operation/preconditions.h" #include "kmsp11/util/crypto_utils.h" #include "kmsp11/util/errors.h" #include "kmsp11/util/padding.h" #include "kmsp11/util/string_utils.h" namespace cloud_kms::kmsp11 { namespace { // Enum representing the padding mode used by the specified mechanism. enum class PaddingMode { kNone, kPkcs7 }; constexpr size_t kIvBytes = 16; constexpr size_t kBlockSize = 16; constexpr size_t kMaxPlaintextBytes = 64 * 1024; constexpr size_t kMaxCiphertextBytes = kMaxPlaintextBytes + 16; // An implementation of EncrypterInterface that generates AES-CBC ciphertexts // using Cloud KMS. class AesCbcEncrypter : public EncrypterInterface { public: AesCbcEncrypter(std::shared_ptr<Object> object, absl::Span<uint8_t> iv, PaddingMode padding) : object_(object), iv_(iv.begin(), iv.end()), padding_mode_(padding) {} absl::StatusOr<absl::Span<const uint8_t>> Encrypt( KmsClient* client, absl::Span<const uint8_t> ciphertext) override; absl::Status EncryptUpdate(KmsClient* client, absl::Span<const uint8_t> plaintext_part) override; absl::StatusOr<absl::Span<const uint8_t>> EncryptFinal( KmsClient* client) override; virtual ~AesCbcEncrypter() {} private: absl::StatusOr<absl::Span<const uint8_t>> EncryptInternal( KmsClient* client, absl::Span<const uint8_t> plaintext); std::shared_ptr<Object> object_; std::vector<uint8_t> iv_; PaddingMode padding_mode_; std::optional<std::vector<uint8_t, ZeroDeallocator<uint8_t>>> plaintext_; // for multi-part std::vector<uint8_t> ciphertext_; }; absl::StatusOr<absl::Span<const uint8_t>> AesCbcEncrypter::Encrypt( KmsClient* client, absl::Span<const uint8_t> plaintext) { if (plaintext_) { return FailedPreconditionError( "Encrypt cannot be used to terminate a multi-part encryption operation", CKR_FUNCTION_FAILED, SOURCE_LOCATION); } if (plaintext.size() > kMaxPlaintextBytes) { return NewInvalidArgumentError( absl::StrFormat( "plaintext length (%d bytes) exceeds maximum allowed %d", plaintext.size(), kMaxPlaintextBytes), CKR_DATA_LEN_RANGE, SOURCE_LOCATION); } return EncryptInternal(client, plaintext); } absl::Status AesCbcEncrypter::EncryptUpdate( KmsClient* client, absl::Span<const uint8_t> plaintext_part) { if (!plaintext_) { plaintext_.emplace(); } if (plaintext_part.size() + plaintext_->size() > kMaxPlaintextBytes) { return NewInvalidArgumentError( absl::StrFormat("plaintext length (%u bytes) exceeds maximum " "allowed (%u bytes)", plaintext_part.size() + plaintext_->size(), kMaxPlaintextBytes), CKR_DATA_LEN_RANGE, SOURCE_LOCATION); } plaintext_->reserve(plaintext_->size() + plaintext_part.size()); plaintext_->insert(plaintext_->end(), plaintext_part.begin(), plaintext_part.end()); return absl::OkStatus(); } absl::StatusOr<absl::Span<const uint8_t>> AesCbcEncrypter::EncryptFinal( KmsClient* client) { if (!plaintext_) { return FailedPreconditionError( "EncryptUpdate needs to be called prior to terminating a multi-part " "encryption operation", CKR_FUNCTION_FAILED, SOURCE_LOCATION); } return EncryptInternal(client, *plaintext_); } absl::StatusOr<absl::Span<const uint8_t>> AesCbcEncrypter::EncryptInternal( KmsClient* client, absl::Span<const uint8_t> plaintext) { if (padding_mode_ == PaddingMode::kNone && plaintext.size() % kBlockSize != 0) { return NewInvalidArgumentError( absl::StrFormat("plaintext length (%u bytes) should be a multiple of " "the block size (%u bytes)", plaintext.size(), kBlockSize), CKR_DATA_LEN_RANGE, SOURCE_LOCATION); } kms_v1::RawEncryptRequest req; req.set_name(std::string(object_->kms_key_name())); std::vector<uint8_t> padded_plaintext; switch (padding_mode_) { case PaddingMode::kPkcs7: padded_plaintext = Pad(plaintext); req.set_plaintext( std::string(reinterpret_cast<const char*>(padded_plaintext.data()), padded_plaintext.size())); break; case PaddingMode::kNone: req.set_plaintext(std::string( reinterpret_cast<const char*>(plaintext.data()), plaintext.size())); break; default: return NewInternalError("unsupported padding mode", SOURCE_LOCATION); } req.set_initialization_vector( std::string(reinterpret_cast<const char*>(iv_.data()), iv_.size())); ASSIGN_OR_RETURN(kms_v1::RawEncryptResponse resp, client->RawEncrypt(req)); if (req.initialization_vector() != resp.initialization_vector()) { return NewInternalError( "the IV returned by the server does not match user-supplied IV", SOURCE_LOCATION); } ciphertext_.resize(resp.ciphertext().size()); std::copy_n(resp.ciphertext().begin(), resp.ciphertext().size(), ciphertext_.begin()); return ciphertext_; } // An implementation of DecrypterInterface that decrypts AES-CBC ciphertexts // using Cloud KMS. class AesCbcDecrypter : public DecrypterInterface { public: AesCbcDecrypter(std::shared_ptr<Object> object, absl::Span<uint8_t> iv, PaddingMode padding) : object_(object), iv_(iv.begin(), iv.end()), padding_mode_(padding) {} absl::StatusOr<absl::Span<const uint8_t>> Decrypt( KmsClient* client, absl::Span<const uint8_t> ciphertext) override; absl::Status DecryptUpdate( KmsClient* client, absl::Span<const uint8_t> ciphertext_part) override; absl::StatusOr<absl::Span<const uint8_t>> DecryptFinal( KmsClient* client) override; virtual ~AesCbcDecrypter() {} private: absl::StatusOr<absl::Span<const uint8_t>> DecryptInternal( KmsClient* client, absl::Span<const uint8_t> ciphertext); std::shared_ptr<Object> object_; std::vector<uint8_t> iv_; PaddingMode padding_mode_; std::optional<std::vector<uint8_t>> ciphertext_; // for multi-part std::unique_ptr<std::string, ZeroDelete<std::string>> plaintext_; }; absl::StatusOr<absl::Span<const uint8_t>> AesCbcDecrypter::Decrypt( KmsClient* client, absl::Span<const uint8_t> ciphertext) { if (ciphertext.size() > kMaxCiphertextBytes) { return NewInvalidArgumentError( absl::StrFormat( "ciphertext length (%u bytes) exceeds maximum allowed %u", ciphertext.size(), kMaxCiphertextBytes), CKR_DATA_LEN_RANGE, SOURCE_LOCATION); } return DecryptInternal(client, ciphertext); } absl::Status AesCbcDecrypter::DecryptUpdate( KmsClient* client, absl::Span<const uint8_t> ciphertext_part) { if (!ciphertext_) { ciphertext_.emplace(); } if (ciphertext_part.size() + ciphertext_->size() > kMaxCiphertextBytes) { return NewInvalidArgumentError( absl::StrFormat("ciphertext length (%u bytes) exceeds maximum " "allowed (%u bytes)", ciphertext_part.size() + ciphertext_->size(), kMaxCiphertextBytes), CKR_DATA_LEN_RANGE, SOURCE_LOCATION); } ciphertext_->reserve(ciphertext_->size() + ciphertext_part.size()); ciphertext_->insert(ciphertext_->end(), ciphertext_part.begin(), ciphertext_part.end()); return absl::OkStatus(); } absl::StatusOr<absl::Span<const uint8_t>> AesCbcDecrypter::DecryptFinal( KmsClient* client) { if (!ciphertext_) { return FailedPreconditionError( "DecryptUpdate needs to be called prior to terminating a multi-part " "decryption operation", CKR_FUNCTION_FAILED, SOURCE_LOCATION); } return DecryptInternal(client, *ciphertext_); } absl::StatusOr<absl::Span<const uint8_t>> AesCbcDecrypter::DecryptInternal( KmsClient* client, absl::Span<const uint8_t> ciphertext) { kms_v1::RawDecryptRequest req; req.set_name(std::string(object_->kms_key_name())); req.set_ciphertext(std::string( reinterpret_cast<const char*>(ciphertext.data()), ciphertext.size())); req.set_initialization_vector( std::string(reinterpret_cast<const char*>(iv_.data()), iv_.size())); ASSIGN_OR_RETURN(kms_v1::RawDecryptResponse resp, client->RawDecrypt(req)); plaintext_.reset(resp.release_plaintext()); absl::Span<const uint8_t> full_plaintext( reinterpret_cast<const uint8_t*>(plaintext_->data()), plaintext_->size()); switch (padding_mode_) { case PaddingMode::kNone: return full_plaintext; case PaddingMode::kPkcs7: return Unpad(full_plaintext); } } } // namespace absl::StatusOr<std::unique_ptr<EncrypterInterface>> NewAesCbcEncrypter( std::shared_ptr<Object> key, const CK_MECHANISM* mechanism) { RETURN_IF_ERROR(CheckKeyPreconditions(CKK_AES, CKO_SECRET_KEY, mechanism->mechanism, key.get())); if (!mechanism->pParameter || mechanism->ulParameterLen != kIvBytes) { return InvalidMechanismParamError( absl::StrFormat("the initialization vector must be %u bytes long", kIvBytes), SOURCE_LOCATION); } CK_BYTE* iv; switch (mechanism->mechanism) { case CKM_AES_CBC: iv = reinterpret_cast<CK_BYTE*>(mechanism->pParameter); return std::make_unique<AesCbcEncrypter>( key, absl::MakeSpan(iv, mechanism->ulParameterLen), PaddingMode::kNone); case CKM_AES_CBC_PAD: iv = reinterpret_cast<CK_BYTE*>(mechanism->pParameter); return std::make_unique<AesCbcEncrypter>( key, absl::MakeSpan(iv, mechanism->ulParameterLen), PaddingMode::kPkcs7); default: return NewInternalError( absl::StrFormat("Mechanism %#x not supported for AES-CBC encryption", mechanism->mechanism), SOURCE_LOCATION); } } absl::StatusOr<std::unique_ptr<DecrypterInterface>> NewAesCbcDecrypter( std::shared_ptr<Object> key, const CK_MECHANISM* mechanism) { RETURN_IF_ERROR(CheckKeyPreconditions(CKK_AES, CKO_SECRET_KEY, mechanism->mechanism, key.get())); if (!mechanism->pParameter || mechanism->ulParameterLen != kIvBytes) { return InvalidMechanismParamError( absl::StrFormat("the initialization vector must be %u bytes long", kIvBytes), SOURCE_LOCATION); } CK_BYTE* iv = reinterpret_cast<CK_BYTE*>(mechanism->pParameter); switch (mechanism->mechanism) { case CKM_AES_CBC: return std::make_unique<AesCbcDecrypter>( key, absl::MakeSpan(iv, mechanism->ulParameterLen), PaddingMode::kNone); case CKM_AES_CBC_PAD: return std::make_unique<AesCbcDecrypter>( key, absl::MakeSpan(iv, mechanism->ulParameterLen), PaddingMode::kPkcs7); default: return NewInternalError( absl::StrFormat("Mechanism %#x not supported for AES-CBC decryption", mechanism->mechanism), SOURCE_LOCATION); } } } // namespace cloud_kms::kmsp11