// 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 "tink/aead/internal/ssl_aead.h" #include #include #include #include #include #include #include "absl/cleanup/cleanup.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" #include "openssl/crypto.h" #include "openssl/evp.h" #include "tink/aead/internal/aead_util.h" #include "tink/internal/err_util.h" #include "tink/internal/ssl_unique_ptr.h" #include "tink/internal/util.h" #include "tink/util/secret_data.h" #include "tink/util/status.h" #include "tink/util/statusor.h" namespace crypto { namespace tink { namespace internal { ABSL_CONST_INIT const int kXchacha20Poly1305TagSizeInBytes = 16; ABSL_CONST_INIT const int kAesGcmTagSizeInBytes = 16; ABSL_CONST_INIT const int kAesGcmSivTagSizeInBytes = 16; namespace { // Encrypts/Decrypts `data` and writes the result into `out`. The direction // (encrypt/decrypt) is given by `context`. `out` is assumed to be large enough // to hold the encrypted/decrypted content. util::StatusOr UpdateCipher(EVP_CIPHER_CTX *context, absl::string_view data, absl::Span out) { // We encrypt/decrypt in chunks of at most MAX int. const int64_t kMaxChunkSize = std::numeric_limits::max(); // Keep track of the bytes written to out. int64_t total_written_bytes = 0; // In practical cases data.size() is assumed to fit into a int64_t. int64_t left_to_update = data.size(); while (left_to_update > 0) { const int chunk_size = std::min(kMaxChunkSize, left_to_update); auto *buffer_ptr = reinterpret_cast(out.data() + total_written_bytes); absl::string_view data_chunk = data.substr(total_written_bytes, chunk_size); int written_bytes = 0; if (EVP_CipherUpdate(context, buffer_ptr, &written_bytes, reinterpret_cast(data_chunk.data()), data_chunk.size()) <= 0) { const bool is_encrypting = EVP_CIPHER_CTX_encrypting(context) == 1; return util::Status( absl::StatusCode::kInternal, absl::StrCat(is_encrypting ? "Encryption" : "Decryption", " failed")); } left_to_update -= written_bytes; total_written_bytes += written_bytes; } return total_written_bytes; } class OpenSslOneShotAeadImpl : public SslOneShotAead { public: explicit OpenSslOneShotAeadImpl(const util::SecretData &key, const EVP_CIPHER *cipher, size_t tag_size) : key_(key), cipher_(cipher), tag_size_(tag_size) {} util::StatusOr Encrypt(absl::string_view plaintext, absl::string_view associated_data, absl::string_view iv, absl::Span out) const override { absl::string_view plaintext_data = internal::EnsureStringNonNull(plaintext); absl::string_view ad = internal::EnsureStringNonNull(associated_data); const int64_t min_out_buff_size = CiphertextSize(plaintext.size()); if (out.size() < min_out_buff_size) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Encryption buffer too small; expected at least ", min_out_buff_size, " bytes, got ", out.size())); } if (BuffersOverlap(plaintext, absl::string_view(out.data(), out.size()))) { return util::Status(absl::StatusCode::kInvalidArgument, "Plaintext and output buffer must not overlap"); } if (associated_data.size() > std::numeric_limits::max()) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Associated data too large; expected at most ", std::numeric_limits::max(), " got ", associated_data.size())); } util::StatusOr> context = GetContext(iv, /*encryption=*/true); if (!context.ok()) { return context.status(); } // Set the associated data. int len = 0; if (EVP_EncryptUpdate(context->get(), /*out=*/nullptr, &len, reinterpret_cast(ad.data()), ad.size()) <= 0) { return util::Status(absl::StatusCode::kInternal, "Failed to set associated data"); } util::StatusOr raw_ciphertext_bytes = UpdateCipher(context->get(), plaintext_data, out); if (!raw_ciphertext_bytes.ok()) { return raw_ciphertext_bytes.status(); } if (EVP_EncryptFinal_ex(context->get(), /*out=*/nullptr, &len) <= 0) { return util::Status(absl::StatusCode::kInternal, "Finalization failed"); } // Write the tag after the ciphertext. absl::Span tag = out.subspan(*raw_ciphertext_bytes, tag_size_); if (EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_AEAD_GET_TAG, tag_size_, reinterpret_cast(tag.data())) <= 0) { return util::Status(absl::StatusCode::kInternal, "Failed to get the tag"); } return *raw_ciphertext_bytes + tag_size_; } util::StatusOr Decrypt(absl::string_view ciphertext, absl::string_view associated_data, absl::string_view iv, absl::Span out) const override { absl::string_view ad = internal::EnsureStringNonNull(associated_data); if (ciphertext.size() < tag_size_) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Ciphertext buffer too small; expected at least ", tag_size_, " got ", ciphertext.size())); } const int64_t min_out_buff_size = PlaintextSize(ciphertext.size()); if (out.size() < min_out_buff_size) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Output buffer too small; expected at least ", min_out_buff_size, " got ", out.size())); } if (BuffersOverlap(ciphertext, absl::string_view(out.data(), out.size()))) { return util::Status(absl::StatusCode::kInvalidArgument, "Ciphertext and output buffer must not overlap"); } if (associated_data.size() > std::numeric_limits::max()) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Associated data too large; expected at most ", std::numeric_limits::max(), " got ", associated_data.size())); } util::StatusOr> context = GetContext(iv, /*encryption=*/false); if (!context.ok()) { return context.status(); } int len = 0; // Add the associated data. if (EVP_DecryptUpdate(context->get(), /*out=*/nullptr, &len, reinterpret_cast(ad.data()), ad.size()) <= 0) { return util::Status(absl::StatusCode::kInternal, "Failed to set associated_data"); } const int64_t raw_ciphertext_size = ciphertext.size() - tag_size_; // "Unpack" the ciphertext. absl::string_view raw_ciphertext = ciphertext.substr(0, raw_ciphertext_size); // This copy is needed since EVP_CIPHER_CTX_ctrl requires a non-const // pointer even if the EVP_CTRL_AEAD_SET_TAG operation doesn't modify the // content of the buffer. auto tag = std::string(ciphertext.substr(raw_ciphertext_size, tag_size_)); // Set the tag. if (EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_AEAD_SET_TAG, tag_size_, reinterpret_cast(&tag[0])) <= 0) { return util::Status(absl::StatusCode::kInternal, "Could not set authentication tag"); } // If out.empty() accessing the 0th element would result in an out of // bound violation. This makes sure we pass a pointer to at least one byte // when calling into OpenSSL. char buffer_if_size_is_zero = '\0'; auto out_buffer = absl::Span(&buffer_if_size_is_zero, /*length=*/1); if (!out.empty()) { out_buffer = out.subspan(0, min_out_buff_size - tag_size_); } // Zero the plaintext buffer in case decryption fails before returning an // error. auto output_eraser = absl::MakeCleanup([out] { OPENSSL_cleanse(out.data(), out.size()); }); util::StatusOr written_bytes = UpdateCipher(context->get(), raw_ciphertext, out_buffer); if (!written_bytes.ok()) { return written_bytes.status(); } if (!EVP_DecryptFinal_ex(context->get(), /*out=*/nullptr, &len)) { return util::Status(absl::StatusCode::kInternal, "Authentication failed"); } // Decryption executed correctly, cancel cleanup on the output buffer. std::move(output_eraser).Cancel(); return *written_bytes; } int64_t CiphertextSize(int64_t plaintext_length) const override { return plaintext_length + tag_size_; } int64_t PlaintextSize(int64_t ciphertext_length) const override { if (ciphertext_length < tag_size_) { return 0; } return ciphertext_length - tag_size_; } private: // Returns a new EVP_CIPHER_CTX for encryption (`ecryption` == true) or // decryption (`encryption` == false). util::StatusOr> GetContext( absl::string_view iv, bool encryption) const { internal::SslUniquePtr context(EVP_CIPHER_CTX_new()); if (context == nullptr) { return util::Status(absl::StatusCode::kInternal, "EVP_CIPHER_CTX_new failed"); } const int encryption_flag = encryption ? 1 : 0; if (EVP_CipherInit_ex(context.get(), cipher_, /*impl=*/nullptr, /*key=*/nullptr, /*iv=*/nullptr, encryption_flag) <= 0) { return util::Status( absl::StatusCode::kInternal, absl::StrCat("Failed initializializing context for ", encryption ? "encryption" : "decryption")); } // Set the size for IV first, then set the IV bytes. if (EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_AEAD_SET_IVLEN, iv.size(), /*ptr=*/nullptr) <= 0) { return util::Status( absl::StatusCode::kInternal, absl::StrCat("Failed stting size of the IV to ", iv.size())); } if (EVP_CipherInit_ex(context.get(), /*cipher=*/nullptr, /*impl=*/nullptr, reinterpret_cast(key_.data()), reinterpret_cast(iv.data()), encryption_flag) <= 0) { return util::Status( absl::StatusCode::kInternal, absl::StrCat("Failed to set key of size ", key_.size(), "and IV of size ", iv.size())); } return std::move(context); } const util::SecretData key_; const EVP_CIPHER *cipher_; const size_t tag_size_; }; #ifdef OPENSSL_IS_BORINGSSL // Implementation of the one-shot AEAD cypter. This is purposely internal to // an anonymous namespace to disallow direct use of this class other than // through the Create* functions below. class BoringSslOneShotAeadImpl : public SslOneShotAead { public: explicit BoringSslOneShotAeadImpl( internal::SslUniquePtr context, size_t tag_size) : context_(std::move(context)), tag_size_(tag_size) {} util::StatusOr Encrypt(absl::string_view plaintext, absl::string_view associated_data, absl::string_view iv, absl::Span out) const override { // BoringSSL expects a non-null pointer for associated_data, // regardless of whether the size is 0. plaintext = internal::EnsureStringNonNull(plaintext); associated_data = internal::EnsureStringNonNull(associated_data); iv = internal::EnsureStringNonNull(iv); if (BuffersOverlap(plaintext, absl::string_view(out.data(), out.size()))) { return util::Status(absl::StatusCode::kInvalidArgument, "Plaintext and output buffer must not overlap"); } const int64_t min_out_buff_size = CiphertextSize(plaintext.size()); if (out.size() < min_out_buff_size) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Output buffer too small; expected at least ", min_out_buff_size, " got ", out.size())); } size_t out_len = 0; if (!EVP_AEAD_CTX_seal( context_.get(), reinterpret_cast(&out[0]), &out_len, out.size(), reinterpret_cast(iv.data()), iv.size(), reinterpret_cast(plaintext.data()), plaintext.size(), /*ad=*/reinterpret_cast(associated_data.data()), /*ad_len=*/associated_data.size())) { return util::Status( absl::StatusCode::kInternal, absl::StrCat("Encryption failed: ", internal::GetSslErrors())); } return out_len; } util::StatusOr Decrypt(absl::string_view ciphertext, absl::string_view associated_data, absl::string_view iv, absl::Span out) const override { ciphertext = internal::EnsureStringNonNull(ciphertext); associated_data = internal::EnsureStringNonNull(associated_data); iv = internal::EnsureStringNonNull(iv); if (BuffersOverlap(ciphertext, absl::string_view(out.data(), out.size()))) { return util::Status(absl::StatusCode::kInvalidArgument, "Ciphertext and output buffer must not overlap"); } if (ciphertext.size() < tag_size_) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Ciphertext buffer too small; expected at least ", tag_size_, " got ", ciphertext.size())); } const int64_t min_out_buff_size = PlaintextSize(ciphertext.size()); if (out.size() < min_out_buff_size) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Output buffer too small; expected at least ", min_out_buff_size, " got ", out.size())); } // If out.empty() accessing the 0th element would result in an out of // bound violation. This makes sure we pass a pointer to at least one byte // when calling into OpenSSL. uint8_t buffer_if_size_is_zero; uint8_t *buffer_ptr = &buffer_if_size_is_zero; if (!out.empty()) { buffer_ptr = reinterpret_cast(&out[0]); } size_t out_len = 0; if (!EVP_AEAD_CTX_open( context_.get(), buffer_ptr, &out_len, out.size(), reinterpret_cast(iv.data()), iv.size(), reinterpret_cast(ciphertext.data()), ciphertext.size(), /*ad=*/reinterpret_cast(associated_data.data()), /*ad_len=*/associated_data.size())) { return util::Status( absl::StatusCode::kInternal, absl::StrCat("Authentication failed: ", internal::GetSslErrors())); } return out_len; } int64_t CiphertextSize(int64_t plaintext_length) const override { return plaintext_length + tag_size_; } int64_t PlaintextSize(int64_t ciphertext_length) const override { if (ciphertext_length < tag_size_) { return 0; } return ciphertext_length - tag_size_; } private: const internal::SslUniquePtr context_; const size_t tag_size_; }; #endif } // namespace util::StatusOr> CreateAesGcmOneShotCrypter( const util::SecretData &key) { #ifdef OPENSSL_IS_BORINGSSL util::StatusOr aead_cipher = GetAesGcmAeadForKeySize(key.size()); if (!aead_cipher.ok()) { return aead_cipher.status(); } internal::SslUniquePtr context(EVP_AEAD_CTX_new( *aead_cipher, key.data(), key.size(), kAesGcmTagSizeInBytes)); if (context == nullptr) { return util::Status( absl::StatusCode::kInternal, absl::StrCat("EVP_AEAD_CTX_new failed: ", internal::GetSslErrors())); } return {absl::make_unique(std::move(context), kAesGcmTagSizeInBytes)}; #else util::StatusOr aead_cipher = GetAesGcmCipherForKeySize(key.size()); if (!aead_cipher.ok()) { return aead_cipher.status(); } return absl::make_unique(key, *aead_cipher, kAesGcmTagSizeInBytes); #endif } util::StatusOr> CreateAesGcmSivOneShotCrypter( const util::SecretData &key) { #ifdef OPENSSL_IS_BORINGSSL util::StatusOr aead_cipher = GetAesGcmSivAeadCipherForKeySize(key.size()); if (!aead_cipher.ok()) { return aead_cipher.status(); } internal::SslUniquePtr context(EVP_AEAD_CTX_new( *aead_cipher, key.data(), key.size(), kAesGcmTagSizeInBytes)); if (context == nullptr) { return util::Status(absl::StatusCode::kInternal, absl::StrCat("EVP_AEAD_CTX_new initialization Failed: ", internal::GetSslErrors())); } return {absl::make_unique( std::move(context), kAesGcmSivTagSizeInBytes)}; #else return util::Status(absl::StatusCode::kUnimplemented, "AES-GCM-SIV is unimplemented for OpenSSL"); #endif } util::StatusOr> CreateXchacha20Poly1305OneShotCrypter(const util::SecretData &key) { #ifdef OPENSSL_IS_BORINGSSL if (key.size() != 32) { return util::Status( absl::StatusCode::kInvalidArgument, absl::StrCat("Invalid key size; valid values are {32} bytes, got ", key.size())); } internal::SslUniquePtr context( EVP_AEAD_CTX_new(EVP_aead_xchacha20_poly1305(), key.data(), key.size(), kAesGcmTagSizeInBytes)); if (context == nullptr) { return util::Status(absl::StatusCode::kInternal, absl::StrCat("EVP_AEAD_CTX_new initialization Failed: ", internal::GetSslErrors())); } return {absl::make_unique( std::move(context), kXchacha20Poly1305TagSizeInBytes)}; #else return util::Status(absl::StatusCode::kUnimplemented, "Xchacha20-Poly1305 is unimplemented for OpenSSL"); #endif } } // namespace internal } // namespace tink } // namespace crypto