diff options
author | ambrosin <ambrosin@google.com> | 2023-05-03 07:35:42 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2023-05-03 07:37:08 -0700 |
commit | 723cb4ca67a8ee5cc57efd50ed0585771c7280cc (patch) | |
tree | 11d6f2ca51065801d049bf4afe74b5065828a942 /cc/aead | |
parent | 264e9ebfafce77f3e610a4b7914ec9d5314366b1 (diff) | |
download | tink-723cb4ca67a8ee5cc57efd50ed0585771c7280cc.tar.gz |
Avoid copying EVP_CIPHER_CTX from partial context in CordAesGcmBoringSsl if OpenSSL >= 3.0
Copying the context fails with OpenSSL 3.0, which doesn't provide a `dupctx` function for aead ciphers (see [1], [2]).
* Rename `context_` into `partial_context_`.
* Introduce helper functions that take care of trying to crated a context starting from `partial_context_` for BoringSSL and OpenSSL 1.1.1, and creating the context from scratch with OpenSSL 3.0+.
* Pass `const util::SecretData&` to `CordAesGcmBoringSsl::New`
[1] https://github.com/openssl/openssl/blob/eb52450f5151e8e78743ab05de21a344823316f5/crypto/evp/evp_enc.c#L1427
[2] https://github.com/openssl/openssl/blob/cac250755efd0c40cc6127a0e4baceb8d226c7e3/providers/implementations/include/prov/ciphercommon_aead.h#L30
PiperOrigin-RevId: 529085627
Diffstat (limited to 'cc/aead')
-rw-r--r-- | cc/aead/internal/BUILD.bazel | 2 | ||||
-rw-r--r-- | cc/aead/internal/CMakeLists.txt | 2 | ||||
-rw-r--r-- | cc/aead/internal/cord_aes_gcm_boringssl.cc | 147 | ||||
-rw-r--r-- | cc/aead/internal/cord_aes_gcm_boringssl.h | 17 |
4 files changed, 121 insertions, 47 deletions
diff --git a/cc/aead/internal/BUILD.bazel b/cc/aead/internal/BUILD.bazel index 7671b70c6..9cccade56 100644 --- a/cc/aead/internal/BUILD.bazel +++ b/cc/aead/internal/BUILD.bazel @@ -61,13 +61,11 @@ cc_library( "//internal:ssl_unique_ptr", "//subtle:random", "//subtle:subtle_util", - "//util:errors", "//util:secret_data", "//util:status", "//util:statusor", "@boringssl//:crypto", "@com_google_absl//absl/status", - "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:cord", ], ) diff --git a/cc/aead/internal/CMakeLists.txt b/cc/aead/internal/CMakeLists.txt index 67e2b1aec..4a6cc413d 100644 --- a/cc/aead/internal/CMakeLists.txt +++ b/cc/aead/internal/CMakeLists.txt @@ -53,14 +53,12 @@ tink_cc_library( DEPS tink::aead::internal::aead_util absl::status - absl::strings absl::cord crypto tink::aead::cord_aead tink::internal::ssl_unique_ptr tink::subtle::random tink::subtle::subtle_util - tink::util::errors tink::util::secret_data tink::util::status tink::util::statusor diff --git a/cc/aead/internal/cord_aes_gcm_boringssl.cc b/cc/aead/internal/cord_aes_gcm_boringssl.cc index 321e9bf4c..4356bee45 100644 --- a/cc/aead/internal/cord_aes_gcm_boringssl.cc +++ b/cc/aead/internal/cord_aes_gcm_boringssl.cc @@ -17,22 +17,18 @@ #include "tink/aead/internal/cord_aes_gcm_boringssl.h" #include <cstdint> -#include <iterator> #include <memory> #include <string> #include <utility> -#include <vector> #include "absl/status/status.h" #include "absl/strings/cord.h" #include "openssl/evp.h" -#include "openssl/err.h" #include "tink/aead/cord_aead.h" #include "tink/aead/internal/aead_util.h" #include "tink/internal/ssl_unique_ptr.h" #include "tink/subtle/random.h" #include "tink/subtle/subtle_util.h" -#include "tink/util/errors.h" #include "tink/util/secret_data.h" #include "tink/util/status.h" #include "tink/util/statusor.h" @@ -47,8 +43,8 @@ constexpr int kTagSizeInBytes = 16; // Set the IV `iv` for the given `context`. if `encryption` is true, set the // context for encryption, and for decryption otherwise. -util::Status SetIv(EVP_CIPHER_CTX* context, absl::string_view iv, - bool encryption) { +util::Status SetIvAndDirection(EVP_CIPHER_CTX* context, absl::string_view iv, + bool encryption) { const int encryption_flag = encryption ? 1 : 0; // Set the IV size. if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, iv.size(), @@ -67,28 +63,100 @@ util::Status SetIv(EVP_CIPHER_CTX* context, absl::string_view iv, return util::OkStatus(); } +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L +// Returns a new EVP_CIPHER_CTX for encryption (`encryption` == true) or +// decryption (`encryption` == false). It tries to skip part of the +// initialization copying `partial_context`. +util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> NewContextFromPartial( + EVP_CIPHER_CTX* partial_context, absl::string_view iv, bool encryption) { + internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new()); + if (context == nullptr) { + return util::Status(absl::StatusCode::kInternal, + "EVP_CIPHER_CTX_new failed"); + } + // Try making a copy of `partial_context` to skip some pre-computations. + // + // NOTE: With BoringSSL and OpenSSL 1.1.1 EVP_CIPHER_CTX_copy makes a copy + // of the `cipher_data` field of `context` as well, which contains the key + // material and IV (see [1] and [2]). + // + // [1]https://github.com/google/boringssl/blob/4c8bcf0da2951cacd8ed8eaa7fd2df4b22fca23b/crypto/fipsmodule/cipher/cipher.c#L116 + // [2]https://github.com/openssl/openssl/blob/830bf8e1e4749ad65c51b6a1d0d769ae689404ba/crypto/evp/evp_enc.c#L703 + if (EVP_CIPHER_CTX_copy(context.get(), partial_context) <= 0) { + return util::Status(absl::StatusCode::kInternal, + "EVP_CIPHER_CTX_copy failed"); + } + util::Status res = + SetIvAndDirection(context.get(), iv, /*encryption=*/encryption); + if (!res.ok()) { + return res; + } + return std::move(context); +} +#else +// Returns a new EVP_CIPHER_CTX for encryption (`encryption` == true) or +// decryption (`encryption` == false) with given `key` and `iv`. +// +// NOTE: Copying the context fails with OpenSSL 3.0, which doesn't provide a +// `dupctx` function for aead ciphers (see [1], [2]). +// +// [1]https://github.com/openssl/openssl/blob/eb52450f5151e8e78743ab05de21a344823316f5/crypto/evp/evp_enc.c#L1427 +// [2]https://github.com/openssl/openssl/blob/cac250755efd0c40cc6127a0e4baceb8d226c7e3/providers/implementations/include/prov/ciphercommon_aead.h#L30 +util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> NewContext( + const util::SecretData& key, absl::string_view iv, bool encryption) { + internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new()); + if (context == nullptr) { + return util::Status(absl::StatusCode::kInternal, + "EVP_CIPHER_CTX_new failed"); + } + util::StatusOr<const EVP_CIPHER*> cipher = + internal::GetAesGcmCipherForKeySize(key.size()); + if (!cipher.ok()) { + return cipher.status(); + } + if (EVP_CipherInit_ex(context.get(), *cipher, /*impl=*/nullptr, + reinterpret_cast<const uint8_t*>(key.data()), + /*iv=*/nullptr, /*enc=*/1) <= 0) { + return util::Status(absl::StatusCode::kInternal, + "Context initialization failed"); + } + util::Status res = + SetIvAndDirection(context.get(), iv, /*encryption=*/encryption); + if (!res.ok()) { + return res; + } + return std::move(context); +} +#endif + } // namespace -util::StatusOr<std::unique_ptr<CordAead> > CordAesGcmBoringSsl::New( - util::SecretData key_value) { +util::StatusOr<std::unique_ptr<CordAead>> CordAesGcmBoringSsl::New( + const util::SecretData& key_value) { util::StatusOr<const EVP_CIPHER*> cipher = internal::GetAesGcmCipherForKeySize(key_value.size()); if (!cipher.ok()) { return cipher.status(); } - internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new()); - // Initialize the cipher now to have some precomputations on the key. The - // direction (enc/dec) is not important since it will be overwritten later. - if (EVP_CipherInit_ex(context.get(), *cipher, /*engine=*/nullptr, + internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context(EVP_CIPHER_CTX_new()); + // Initialize a partial context for the cipher to allow OpenSSL/BoringSSL + // making some precomputations on the key. Encrypt and Decrypt will try making + // a copy of this context to avoid doing the same initializations again and to + // guarantee thread safety. + // + // NOTE: It doesn't matter at this point if we set the direction to encryption + // or decryption, it will be overwritten later any time we call + // EVP_CipherInit_ex. + if (EVP_CipherInit_ex(partial_context.get(), *cipher, /*engine=*/nullptr, reinterpret_cast<const uint8_t*>(&key_value[0]), /*iv=*/nullptr, /*enc=*/1) <= 0) { return util::Status(absl::StatusCode::kInternal, "Context initialization failed"); } - std::unique_ptr<CordAead> aead = - absl::WrapUnique(new CordAesGcmBoringSsl(std::move(context))); + std::unique_ptr<CordAead> aead = absl::WrapUnique( + new CordAesGcmBoringSsl(std::move(partial_context), key_value)); return std::move(aead); } @@ -96,18 +164,21 @@ util::StatusOr<absl::Cord> CordAesGcmBoringSsl::Encrypt( absl::Cord plaintext, absl::Cord associated_data) const { std::string iv = subtle::Random::GetRandomBytes(kIvSizeInBytes); - internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new()); - EVP_CIPHER_CTX_copy(context.get(), context_.get()); - - util::Status res = SetIv(context.get(), iv, /*encryption=*/true); - if (!res.ok()) { - return res; +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L + util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context = + NewContextFromPartial(partial_context_.get(), iv, /*encryption=*/true); +#else + util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context = + NewContext(key_, iv, /*encryption=*/true); +#endif + if (!context.ok()) { + return context.status(); } int len = 0; // Process AAD. for (auto ad_chunk : associated_data.Chunks()) { - if (!EVP_EncryptUpdate(context.get(), /*out=*/nullptr, &len, + if (!EVP_EncryptUpdate(context->get(), /*out=*/nullptr, &len, reinterpret_cast<const uint8_t*>(ad_chunk.data()), ad_chunk.size())) { return util::Status(absl::StatusCode::kInternal, "Encryption failed"); @@ -124,7 +195,7 @@ util::StatusOr<absl::Cord> CordAesGcmBoringSsl::Encrypt( for (auto plaintext_chunk : plaintext.Chunks()) { if (!EVP_EncryptUpdate( - context.get(), + context->get(), reinterpret_cast<uint8_t*>(&(buffer[ciphertext_buffer_offset])), &len, reinterpret_cast<const uint8_t*>(plaintext_chunk.data()), plaintext_chunk.size())) { @@ -132,13 +203,14 @@ util::StatusOr<absl::Cord> CordAesGcmBoringSsl::Encrypt( } ciphertext_buffer_offset += plaintext_chunk.size(); } - if (!EVP_EncryptFinal_ex(context.get(), nullptr, &len)) { + if (!EVP_EncryptFinal_ex(context->get(), nullptr, &len)) { return util::Status(absl::StatusCode::kInternal, "Encryption failed"); } std::string tag; subtle::ResizeStringUninitialized(&tag, kTagSizeInBytes); - if (!EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_GCM_GET_TAG, kTagSizeInBytes, + if (!EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_GCM_GET_TAG, + kTagSizeInBytes, reinterpret_cast<uint8_t*>(&tag[0]))) { return util::Status(absl::StatusCode::kInternal, "Encryption failed"); } @@ -157,23 +229,26 @@ util::StatusOr<absl::Cord> CordAesGcmBoringSsl::Decrypt( return util::Status(absl::StatusCode::kInternal, "Ciphertext too short"); } - // First bytes contain IV. + // First bytes contain the IV. std::string iv = std::string(ciphertext.Subcord(0, kIvSizeInBytes)); absl::Cord raw_ciphertext = ciphertext.Subcord( kIvSizeInBytes, ciphertext.size() - kIvSizeInBytes - kTagSizeInBytes); - internal::SslUniquePtr<EVP_CIPHER_CTX> context(EVP_CIPHER_CTX_new()); - EVP_CIPHER_CTX_copy(context.get(), context_.get()); - - util::Status res = SetIv(context.get(), iv, /*encryption=*/false); - if (!res.ok()) { - return res; +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER < 0x30000000L + util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context = + NewContextFromPartial(partial_context_.get(), iv, /*encryption=*/false); +#else + util::StatusOr<internal::SslUniquePtr<EVP_CIPHER_CTX>> context = + NewContext(key_, iv, /*encryption=*/false); +#endif + if (!context.ok()) { + return context.status(); } int len = 0; // Process associated data. for (auto ad_chunk : associated_data.Chunks()) { - if (!EVP_DecryptUpdate(context.get(), nullptr, &len, + if (!EVP_DecryptUpdate(context->get(), /*out=*/nullptr, &len, reinterpret_cast<const uint8_t*>(ad_chunk.data()), ad_chunk.size())) { return util::Status(absl::StatusCode::kInternal, "Decryption failed"); @@ -192,7 +267,7 @@ util::StatusOr<absl::Cord> CordAesGcmBoringSsl::Decrypt( }); for (auto ct_chunk : raw_ciphertext.Chunks()) { - if (!EVP_DecryptUpdate(context.get(), + if (!EVP_DecryptUpdate(context->get(), reinterpret_cast<uint8_t*>( &plaintext_buffer[plaintext_buffer_offset]), &len, @@ -207,13 +282,13 @@ util::StatusOr<absl::Cord> CordAesGcmBoringSsl::Decrypt( std::string tag = std::string( ciphertext.Subcord(ciphertext.size() - kTagSizeInBytes, kTagSizeInBytes)); - if (!EVP_CIPHER_CTX_ctrl(context.get(), EVP_CTRL_GCM_SET_TAG, kTagSizeInBytes, - &tag[0])) { + if (!EVP_CIPHER_CTX_ctrl(context->get(), EVP_CTRL_GCM_SET_TAG, + kTagSizeInBytes, &tag[0])) { return util::Status(absl::StatusCode::kInternal, "Could not set authentication tag"); } // Verify authentication tag. - if (!EVP_DecryptFinal_ex(context.get(), nullptr, &len)) { + if (!EVP_DecryptFinal_ex(context->get(), nullptr, &len)) { return util::Status(absl::StatusCode::kInternal, "Authentication failed"); } return result; diff --git a/cc/aead/internal/cord_aes_gcm_boringssl.h b/cc/aead/internal/cord_aes_gcm_boringssl.h index 9dcd2881e..273b52329 100644 --- a/cc/aead/internal/cord_aes_gcm_boringssl.h +++ b/cc/aead/internal/cord_aes_gcm_boringssl.h @@ -20,12 +20,10 @@ #include <memory> #include <utility> -#include "absl/strings/string_view.h" #include "openssl/evp.h" #include "tink/aead/cord_aead.h" #include "tink/internal/ssl_unique_ptr.h" #include "tink/util/secret_data.h" -#include "tink/util/status.h" #include "tink/util/statusor.h" namespace crypto { @@ -35,7 +33,7 @@ namespace internal { class CordAesGcmBoringSsl : public CordAead { public: static crypto::tink::util::StatusOr<std::unique_ptr<CordAead>> New( - util::SecretData key_value); + const util::SecretData& key_value); crypto::tink::util::StatusOr<absl::Cord> Encrypt( absl::Cord plaintext, absl::Cord associated_data) const override; @@ -44,10 +42,15 @@ class CordAesGcmBoringSsl : public CordAead { absl::Cord ciphertext, absl::Cord associated_data) const override; private: - explicit CordAesGcmBoringSsl(internal::SslUniquePtr<EVP_CIPHER_CTX> context) - : context_(std::move(context)) {} - - internal::SslUniquePtr<EVP_CIPHER_CTX> context_; + explicit CordAesGcmBoringSsl( + internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context, + const util::SecretData& key) + : partial_context_(std::move(partial_context)), key_(key) {} + + // Partially-initialized EVP_CIPHER_CTX context that is copied for every + // Encrypt/Decrypt operation. + internal::SslUniquePtr<EVP_CIPHER_CTX> partial_context_; + util::SecretData key_; }; } // namespace internal |