This is an automated email from the ASF dual-hosted git repository. wzhou pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/impala.git
commit a61b90f86091ed9f190fd3a23d64ae99f887e411 Author: pranavyl <[email protected]> AuthorDate: Fri Jun 23 23:05:21 2023 -0700 IMPALA-13039: AES Encryption/ Decryption Support in Impala AES (Advanced Encryption Standard) crypto functions are widely recognized and respected encryption algorithm used to protect sensitive data which operate by transforming plaintext data into ciphertext using a symmetric key, ensuring confidentiality and integrity. This standard speciļ¬es the Rijndael algorithm, a symmetric block cipher that can process data blocks of 128 bits, using cipher keys with lengths of 128 and 256 bits. The patch makes use of the EVP_*() algorithms from the OpenSSL library. The patch includes: 1. AES-GCM, AES-CTR, and AES-CFB encryption functionalities and AES-GCM, AES-ECB, AES-CTR, and AES-CFB decryption functionalities. 2. Support for both 128-bit and 256-bit key sizes for GCM and ECB modes. 3. Enhancements to EncryptionKey class to accommodate various AES modes. The aes_encrypt() and aes_decrypt() functions serve as entry points for encryption and decryption operations, handling encryption and decryption based on user-provided keys, AES modes, and initialization vectors (IVs). The implementation includes key length validation and IV vector size checks to ensure data integrity and confidentiality. Multiple AES modes: GCM, CFB, CTR for encryption, and GCM, CFB, CTR and ECB for decryption are supported to provide flexibility and compatibility with various use cases and OpenSSL features. AES-GCM is set as the default mode due to its strong security properties. AES-CTR and AES-CFB are provided as fallbacks for environments where AES-GCM may not be supported. Note that AES-GCM is not available in OpenSSL versions prior to 1.0.1, so having multiple methods ensures broader compatibility. Testing: The patch is thouroughly tested and the tests are included in exprs.test. Change-Id: I3902f2b1d95da4d06995cbd687e79c48e16190c9 Reviewed-on: http://gerrit.cloudera.org:8080/20447 Reviewed-by: Daniel Becker <[email protected]> Tested-by: Impala Public Jenkins <[email protected]> --- be/src/exprs/string-functions-ir.cc | 92 +++-- be/src/exprs/string-functions.cc | 183 +++++++++ be/src/exprs/string-functions.h | 16 +- be/src/runtime/tmp-file-mgr.cc | 2 +- be/src/udf/udf-ir.cc | 2 +- be/src/util/openssl-util-test.cc | 57 ++- be/src/util/openssl-util.cc | 240 ++++++++--- be/src/util/openssl-util.h | 124 ++++-- common/function-registry/impala_functions.py | 6 + .../queries/QueryTest/encryption_exprs.test | 456 +++++++++++++++++++++ tests/query_test/test_exprs.py | 6 + 11 files changed, 1048 insertions(+), 136 deletions(-) diff --git a/be/src/exprs/string-functions-ir.cc b/be/src/exprs/string-functions-ir.cc index 0a2319483..e9803f809 100644 --- a/be/src/exprs/string-functions-ir.cc +++ b/be/src/exprs/string-functions-ir.cc @@ -155,8 +155,8 @@ StringVal StringFunctions::Space(FunctionContext* context, const BigIntVal& len) if (len.val <= 0) return StringVal(); if (len.val > StringVal::MAX_LENGTH) { context->SetError(Substitute(ERROR_CHARACTER_LIMIT_EXCEEDED, - "space() result", - PrettyPrinter::Print(StringVal::MAX_LENGTH, TUnit::BYTES)).c_str()); + "space() result", + PrettyPrinter::Print(StringVal::MAX_LENGTH, TUnit::BYTES)).c_str()); return StringVal::null(); } StringVal result(context, len.val); @@ -259,7 +259,7 @@ StringVal StringFunctions::Rpad(FunctionContext* context, const StringVal& str, IntVal StringFunctions::Length(FunctionContext* context, const StringVal& str) { if (str.is_null) return IntVal::null(); - if (context->impl()->GetConstFnAttr(FunctionContextImpl::UTF8_MODE)) { + if (context->impl()->GetConstFnAttr(FunctionContextImpl::UTF8_MODE, 0)) { return Utf8Length(context, str); } return IntVal(str.len); @@ -1255,8 +1255,8 @@ StringVal StringFunctions::Concat( if (total_size > StringVal::MAX_LENGTH) { context->SetError(Substitute(ERROR_CHARACTER_LIMIT_EXCEEDED, - "Concatenated string length", - PrettyPrinter::Print(StringVal::MAX_LENGTH, TUnit::BYTES)).c_str()); + "Concatenated string length", + PrettyPrinter::Print(StringVal::MAX_LENGTH, TUnit::BYTES)).c_str()); return StringVal::null(); } @@ -1303,8 +1303,8 @@ StringVal StringFunctions::ConcatWs(FunctionContext* context, const StringVal& s if (total_size > StringVal::MAX_LENGTH) { context->SetError(Substitute(ERROR_CHARACTER_LIMIT_EXCEEDED, - "Concatenated string length", - PrettyPrinter::Print(StringVal::MAX_LENGTH, TUnit::BYTES)).c_str()); + "Concatenated string length", + PrettyPrinter::Print(StringVal::MAX_LENGTH, TUnit::BYTES)).c_str()); return StringVal::null(); } @@ -1435,7 +1435,7 @@ StringVal StringFunctions::ParseUrlKey(FunctionContext* ctx, const StringVal& ur StringValue result; if (!UrlParser::ParseUrlKey(StringValue::FromStringVal(url), url_part, - StringValue::FromStringVal(key), &result)) { + StringValue::FromStringVal(key), &result)) { // url is malformed, or url_part is invalid. if (url_part == UrlParser::INVALID) { stringstream ss; @@ -1715,8 +1715,8 @@ DoubleVal StringFunctions::JaroSimilarity( } double m = static_cast<double>(matching_characters); double jaro_similarity = 1.0 / 3.0 * ( m / static_cast<double>(s1len) - + m / static_cast<double>(s2len) - + (m - transpositions) / m ); + + m / static_cast<double>(s2len) + + (m - transpositions) / m ); ctx->Free(reinterpret_cast<uint8_t*>(s1_matching)); ctx->Free(reinterpret_cast<uint8_t*>(s2_matching)); @@ -1734,27 +1734,27 @@ DoubleVal StringFunctions::JaroDistance( } DoubleVal StringFunctions::JaroWinklerDistance(FunctionContext* ctx, - const StringVal& s1, const StringVal& s2) { + const StringVal& s1, const StringVal& s2) { return StringFunctions::JaroWinklerDistance(ctx, s1, s2, - DoubleVal(0.1), DoubleVal(0.7)); + DoubleVal(0.1), DoubleVal(0.7)); } DoubleVal StringFunctions::JaroWinklerDistance(FunctionContext* ctx, - const StringVal& s1, const StringVal& s2, - const DoubleVal& scaling_factor) { + const StringVal& s1, const StringVal& s2, + const DoubleVal& scaling_factor) { return StringFunctions::JaroWinklerDistance(ctx, s1, s2, - scaling_factor, DoubleVal(0.7)); + scaling_factor, DoubleVal(0.7)); } // Based on https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance // Implements Jaro-Winkler distance // Extended with boost_theshold: Winkler's modification only applies if Jaro exceeds it DoubleVal StringFunctions::JaroWinklerDistance(FunctionContext* ctx, - const StringVal& s1, const StringVal& s2, - const DoubleVal& scaling_factor, const DoubleVal& boost_threshold) { + const StringVal& s1, const StringVal& s2, + const DoubleVal& scaling_factor, const DoubleVal& boost_threshold) { DoubleVal jaro_winkler_similarity = StringFunctions::JaroWinklerSimilarity( - ctx, s1, s2, scaling_factor, boost_threshold); + ctx, s1, s2, scaling_factor, boost_threshold); if (jaro_winkler_similarity.is_null) return DoubleVal::null(); if (jaro_winkler_similarity.val == -1.0) return DoubleVal(-1.0); @@ -1762,24 +1762,24 @@ DoubleVal StringFunctions::JaroWinklerDistance(FunctionContext* ctx, } DoubleVal StringFunctions::JaroWinklerSimilarity(FunctionContext* ctx, - const StringVal& s1, const StringVal& s2) { + const StringVal& s1, const StringVal& s2) { return StringFunctions::JaroWinklerSimilarity(ctx, s1, s2, - DoubleVal(0.1), DoubleVal(0.7)); + DoubleVal(0.1), DoubleVal(0.7)); } DoubleVal StringFunctions::JaroWinklerSimilarity(FunctionContext* ctx, - const StringVal& s1, const StringVal& s2, - const DoubleVal& scaling_factor) { + const StringVal& s1, const StringVal& s2, + const DoubleVal& scaling_factor) { return StringFunctions::JaroWinklerSimilarity(ctx, s1, s2, - scaling_factor, DoubleVal(0.7)); + scaling_factor, DoubleVal(0.7)); } // Based on https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance // Implements Jaro-Winkler similarity // Extended with boost_theshold: Winkler's modification only applies if Jaro exceeds it DoubleVal StringFunctions::JaroWinklerSimilarity(FunctionContext* ctx, - const StringVal& s1, const StringVal& s2, - const DoubleVal& scaling_factor, const DoubleVal& boost_threshold) { + const StringVal& s1, const StringVal& s2, + const DoubleVal& scaling_factor, const DoubleVal& boost_threshold) { constexpr int MAX_PREFIX_LENGTH = 4; int s1len = s1.len; @@ -1813,12 +1813,12 @@ DoubleVal StringFunctions::JaroWinklerSimilarity(FunctionContext* ctx, int common_length = std::min(MAX_PREFIX_LENGTH, std::min(s1len, s2len)); int common_prefix = 0; while (common_prefix < common_length && - s1.ptr[common_prefix] == s2.ptr[common_prefix]) { + s1.ptr[common_prefix] == s2.ptr[common_prefix]) { common_prefix++; } jaro_winkler_similarity += common_prefix * scaling_factor.val * - (1.0 - jaro_similarity.val); + (1.0 - jaro_similarity.val); } return DoubleVal(jaro_winkler_similarity); } @@ -1881,8 +1881,8 @@ IntVal StringFunctions::DamerauLevenshtein( l_cost = 1; } d[i][j] = std::min(d[i - 1][j - 1] + l_cost, // substitution - std::min(d[i][j - 1] + 1, // insertion - d[i - 1][j] + 1) // deletion + std::min(d[i][j - 1] + 1, // insertion + d[i - 1][j] + 1) // deletion ); if (i > 1 && j > 1 && s1.ptr[i - 1] == s2.ptr[j - 2] && s1.ptr[i - 2] == s2.ptr[j - 1]) { @@ -1934,4 +1934,38 @@ StringVal StringFunctions::PrettyPrintMemory(FunctionContext* context, return prettyPrint(context, bytes, TUnit::BYTES); } +// The state is a bool used to reduce excessive logs. +void StringFunctions::AesPrepare(FunctionContext* context, + FunctionContext::FunctionStateScope scope) { + if (scope != FunctionContext::THREAD_LOCAL) return; + bool* state = reinterpret_cast<bool*>(context->Allocate(sizeof(bool))); + if (state == nullptr) { + context->AddWarning("Failed to allocate memory for function state."); + return; + } + *state = false; + context->SetFunctionState(scope, state); +} + +void StringFunctions::AesClose(FunctionContext* context, + FunctionContext::FunctionStateScope scope) { + if (scope != FunctionContext::THREAD_LOCAL) return; + bool* state = reinterpret_cast<bool*>( + context->GetFunctionState(FunctionContext::THREAD_LOCAL)); + if (state != nullptr) { + context->Free(reinterpret_cast<uint8_t*>(state)); + context->SetFunctionState(scope, nullptr); + } +} + +// Implementation details and comments are provided in string-functions.cc file. +StringVal StringFunctions::AesDecrypt(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv) { + return AesDecryptImpl(ctx, expr, key, mode, iv); +} + +StringVal StringFunctions::AesEncrypt(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv) { + return AesEncryptImpl(ctx, expr, key, mode, iv); +} } diff --git a/be/src/exprs/string-functions.cc b/be/src/exprs/string-functions.cc index d1bc8341a..60835d7ea 100644 --- a/be/src/exprs/string-functions.cc +++ b/be/src/exprs/string-functions.cc @@ -20,13 +20,19 @@ #include "exprs/string-functions.h" +#include <openssl/bio.h> +#include <openssl/evp.h> + +#include "gutil/strings/stringpiece.h" #include <gutil/strings/util.h> #include <rapidjson/document.h> #include <rapidjson/stringbuffer.h> #include <rapidjson/error/en.h> #include <rapidjson/writer.h> +#include <string> #include "exprs/anyval-util.h" +#include "util/openssl-util.h" #include "util/string-util.h" #include "util/string-parser.h" @@ -401,4 +407,181 @@ StringVal StringFunctions::GetJsonObjectImpl(FunctionContext* ctx, return ToStringVal(ctx, queue, &allocator); } + +// Initializes the EncryptionKey for AES Encryption/ Decryption by validating arguments. +Status InitializeEncryptionKey(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv, bool is_encrypt, + EncryptionKey* encryption_key) { + if (key.is_null) { + return Status(Substitute("Key cannot be NULL.")); + } + if (key.len != 16 && key.len != 32) { + return Status(Substitute("AES only supports 128 and 256 bit key lengths.")); + } + AES_CIPHER_MODE cipher_mode; + + // If user passed a "NULL" field in mode, default AES encryption mode is chosen. + if (mode.is_null) { + cipher_mode = EncryptionKey::GetSupportedDefaultMode(); + bool* state = reinterpret_cast<bool*>( + ctx->GetFunctionState(FunctionContext::THREAD_LOCAL)); + if (state == nullptr || *state == false) { + VLOG_QUERY << "No AES mode was specified by user. Using " << + EncryptionKey::ModeToString(cipher_mode) << " mode as default."; + if (state != nullptr) { + *state = true; + } + } + } else { + cipher_mode = EncryptionKey::StringToMode + (std::string_view(reinterpret_cast<const char*>(mode.ptr), mode.len)); + } + + if (cipher_mode == AES_CIPHER_MODE::INVALID) { + return Status(Substitute("Invalid AES 'mode': $0", StringPiece + (reinterpret_cast<const char*>(mode.ptr), mode.len)).c_str()); + } + + bool is_ecb = (cipher_mode == AES_CIPHER_MODE::AES_256_ECB + || cipher_mode == AES_CIPHER_MODE::AES_128_ECB); + + // Check if iv is null in case of non ECB modes. + if (!is_ecb && iv.is_null) { + return Status(Substitute("IV vector required for $0 mode", + EncryptionKey::ModeToString(cipher_mode)).c_str()); + } + + // Check if IV vector size is valid (<= AES_BLOCK_SIZE) in case of non ECB modes. + if (!is_ecb && iv.len > AES_BLOCK_SIZE) { + return Status(Substitute("IV vector size is greater than 16 bytes.")); + } + + // ECB mode is not supported for Encryption. + if (is_encrypt && is_ecb) { + return Status(Substitute("ECB mode is not supported for encryption.")); + } + + // Initialize key and IV. + Status status = encryption_key->InitializeFields(key.ptr, key.len, + iv.ptr, iv.len, cipher_mode); + + return status; +} + +// An entrypoint to perform AES decryption on a given expression string. +// GCM mode expects expression, key, AES mode and iv vector. +// CTR and CFB modes expect expression, key, AES mode and iv vector. +// ECB mode expects expression, key, AES mode. +// If the mode passed by the user is supported by Impala (a valid member of +// AES_CIPHER_MODE except for INVALID) but not supported by the OpenSSL +// library used internally, then the default mode of the library is chosen. +// +// This is different from the case where the user enters a mode that Impala does not +// support (e.g., a nonexistent or invalid mode). In such cases, the mode is +// considered invalid, and an error is returned. +// +// Description of the modes supported: +// AES-GCM (Advanced Encryption Standard Galois/Counter Mode) is a mode of operation +// for symmetric key cryptographic block ciphers. It combines the AES block cipher +// with the Galois/Counter Mode (GCM) operation for authenticated encryption. +// AES-GCM provides both confidentiality and integrity, making it suitable for secure +// communication and storage. Due to its security features and efficiency, +// AES-GCM is chosen as the default choice for the current implementation. +// +// AES-ECB (Electronic Codebook) mode is a basic mode of operation for the AES +// block cipher. In ECB mode, each block of plaintext is encrypted independently with +// the same key, resulting in identical plaintext blocks producing identical ciphertext +// blocks. It is included for bringing compatibility with legacy systems. +// NOTE: This mode is only supported for decryption. +// +// CTR Mode: +// AES-CTR (Counter) mode is a mode of operation for block ciphers that turns a block +// cipher into a stream cipher. It works by encrypting a counter value to produce a +// stream of key material, which is then XORed with the plaintext to produce the +// ciphertext. CTR mode offers parallel encryption and decryption. +// +// CFB Mode: +// AES-CFB (Cipher Feedback) mode is another mode of operation for block ciphers, where +// ciphertext feedback is used to create a stream of key material. It operates on a +// block-by-block basis, where the previous ciphertext block is encrypted and then XORed +// with the plaintext to produce the next ciphertext block. +StringVal StringFunctions::AesDecryptImpl(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv) { + if (expr.is_null) { + return StringVal::null(); + } + EncryptionKey encryption_key; + Status status = InitializeEncryptionKey(ctx, expr, key, mode, iv, false, + &encryption_key); + if (!status.ok()) { + ctx->SetError(status.msg().msg().data()); + return StringVal::null(); + } + int64_t len = expr.len; + + // Remove spaces and set value of gcm_tag in case of GCM mode + if (encryption_key.IsGcmMode()) { + len -= AES_BLOCK_SIZE; + encryption_key.SetGcmTag(expr.ptr + len); + } + + StringVal result = StringVal(ctx, len); + + // Decrypt the input + int64_t out_len = 0; + status = encryption_key.Decrypt(expr.ptr, len, result.ptr, &out_len); + if (!status.ok()) { + ctx->SetError("AES decryption failed"); + return StringVal::null(); + } + const int64_t len_diff = result.len - out_len; + DCHECK(len_diff == 0 || (encryption_key.IsEcbMode() && 0 < len_diff && + len_diff <= AES_BLOCK_SIZE)); + result.len = out_len; + return result; +} + +// An entrypoint to perform AES encryption on a given expression string. In contrast +// to AesDecryptImpl(), it does not support ECB modes. For other details, see the +// comment at AesDecryptImpl(). +StringVal StringFunctions::AesEncryptImpl(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv) { + if (expr.is_null) { + return StringVal::null(); + } + EncryptionKey encryption_key; + Status status = InitializeEncryptionKey(ctx, expr, key, mode, iv, true, + &encryption_key); + if (!status.ok()) { + ctx->SetError(status.msg().msg().data()); + return StringVal::null(); + } + + // Calculate expected output length + int expected_out_len = expr.len; + + // Append space for gcm_tag in case of GCM mode + if (encryption_key.IsGcmMode()) { + expected_out_len += AES_BLOCK_SIZE; + } + + // Allocate buffer for output + StringVal result = StringVal(ctx, expected_out_len); + // Encrypt the input + int64_t out_len = 0; + status = encryption_key.Encrypt(expr.ptr, expr.len, result.ptr, + &out_len); + if (!status.ok()) { + ctx->SetError("AES encryption failed."); + return StringVal::null(); + } + // Append gcm_tag to encrypted buffer + if (encryption_key.IsGcmMode()) { + encryption_key.GetGcmTag(result.ptr + out_len); + out_len += AES_BLOCK_SIZE; + } + // Ensure expected output length matches actual output length + DCHECK_EQ(expected_out_len, out_len); + return result; +} } diff --git a/be/src/exprs/string-functions.h b/be/src/exprs/string-functions.h index 2121caf28..8636d54f6 100644 --- a/be/src/exprs/string-functions.h +++ b/be/src/exprs/string-functions.h @@ -135,6 +135,20 @@ class StringFunctions { /// Cleans up the work done by TrimPrepare above. static void TrimClose(FunctionContext*, FunctionContext::FunctionStateScope); + /// AES encryption functions in Impala, using openSSL libraries. + static void AesPrepare(FunctionContext* context, + FunctionContext::FunctionStateScope scope); + static StringVal AesDecrypt(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv); + static StringVal AesEncrypt(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv); + static StringVal AesDecryptImpl(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv); + static StringVal AesEncryptImpl(FunctionContext* ctx, const StringVal& expr, + const StringVal& key, const StringVal& mode, const StringVal& iv); + static void AesClose(FunctionContext* context, + FunctionContext::FunctionStateScope scope); + /// Trims occurrences of the characters in 'chars_to_trim' string from /// the beginning of string 'str'. static StringVal LTrimString(FunctionContext* ctx, const StringVal& str, @@ -265,4 +279,4 @@ class StringFunctions { static StringVal DoUtf8TrimString(const StringVal& str, const TrimContext& trim_ctx); }; } -#endif +#endif \ No newline at end of file diff --git a/be/src/runtime/tmp-file-mgr.cc b/be/src/runtime/tmp-file-mgr.cc index dfcba68c6..e25bc2ca0 100644 --- a/be/src/runtime/tmp-file-mgr.cc +++ b/be/src/runtime/tmp-file-mgr.cc @@ -2083,7 +2083,7 @@ Status TmpWriteHandle::EncryptAndHash( counters == nullptr ? nullptr : counters->encryption_time); // Since we're using GCM/CTR/CFB mode, we must take care not to reuse a // key/IV pair. Regenerate a new key and IV for every data buffer we write. - key_.InitializeRandom(); + RETURN_IF_ERROR(key_.InitializeRandom(AES_BLOCK_SIZE, key_.GetSupportedDefaultMode())); RETURN_IF_ERROR(key_.Encrypt(buffer.data(), buffer.len(), buffer.data())); if (!key_.IsGcmMode()) { diff --git a/be/src/udf/udf-ir.cc b/be/src/udf/udf-ir.cc index 58f8257b9..91201dc58 100644 --- a/be/src/udf/udf-ir.cc +++ b/be/src/udf/udf-ir.cc @@ -59,4 +59,4 @@ uint8_t* FnCtxAllocateForResults(FunctionContext* ctx, int64_t byte_size) { assert(ctx != nullptr); uint8_t* ptr = ctx->impl()->AllocateForResults(byte_size); return ptr; -} +} \ No newline at end of file diff --git a/be/src/util/openssl-util-test.cc b/be/src/util/openssl-util-test.cc index 9700cfe27..bfc302c02 100644 --- a/be/src/util/openssl-util-test.cc +++ b/be/src/util/openssl-util-test.cc @@ -57,7 +57,8 @@ class OpenSSLUtilTest : public ::testing::Test { void TestEncryptionDecryption(const int64_t buffer_size) { vector<uint8_t> original(buffer_size); - vector<uint8_t> scratch(buffer_size); // Scratch buffer for in-place encryption. + // Scratch buffer for in-place encryption. + vector<uint8_t> scratch(buffer_size + AES_BLOCK_SIZE); if (buffer_size % 8 == 0) { GenerateRandomData(original.data(), buffer_size); } else { @@ -65,18 +66,32 @@ class OpenSSLUtilTest : public ::testing::Test { } // Check all the modes - AES_CIPHER_MODE modes[] = {AES_256_GCM, AES_256_CTR, AES_256_CFB}; + AES_CIPHER_MODE modes[] = { + AES_CIPHER_MODE::AES_256_GCM, + AES_CIPHER_MODE::AES_256_CTR, + AES_CIPHER_MODE::AES_256_CFB, + AES_CIPHER_MODE::AES_256_ECB, + AES_CIPHER_MODE::AES_128_GCM, + AES_CIPHER_MODE::AES_128_ECB, + }; for (auto m : modes) { memcpy(scratch.data(), original.data(), buffer_size); EncryptionKey key; - key.InitializeRandom(); - key.SetCipherMode(m); + ASSERT_OK(key.InitializeRandom(AES_BLOCK_SIZE, m)); + + int64_t encrypted_length; + if (key.IsEcbMode() && buffer_size > numeric_limits<int>::max() - AES_BLOCK_SIZE) { + ASSERT_ERROR_MSG(key.Encrypt(scratch.data(),buffer_size, scratch.data()), + "Input buffer length exceeds the supported length for ECB mode."); + continue; + } + ASSERT_OK(key.Encrypt(scratch.data(), buffer_size, scratch.data(), + &encrypted_length)); - ASSERT_OK(key.Encrypt(scratch.data(), buffer_size, scratch.data())); // Check that encryption did something ASSERT_NE(0, memcmp(original.data(), scratch.data(), buffer_size)); - ASSERT_OK(key.Decrypt(scratch.data(), buffer_size, scratch.data())); + ASSERT_OK(key.Decrypt(scratch.data(), encrypted_length, scratch.data())); // Check that we get the original data back. ASSERT_EQ(0, memcmp(original.data(), scratch.data(), buffer_size)); } @@ -94,14 +109,19 @@ TEST_F(OpenSSLUtilTest, Encryption) { vector<uint8_t> decrypted(buffer_size); GenerateRandomData(original.data(), buffer_size); - // Check both CTR & CFB - AES_CIPHER_MODE modes[] = {AES_256_GCM, AES_256_CTR, AES_256_CFB}; + // Check GCM, CTR and CFB + AES_CIPHER_MODE modes[] = { + AES_CIPHER_MODE::AES_256_GCM, + AES_CIPHER_MODE::AES_256_CTR, + AES_CIPHER_MODE::AES_256_CFB, + AES_CIPHER_MODE::AES_128_GCM + }; for (auto m : modes) { // Iterate multiple times to ensure that key regeneration works correctly. EncryptionKey key; for (int i = 0; i < 2; ++i) { - key.InitializeRandom(); // Generate a new key for each iteration. - key.SetCipherMode(m); + // Generate a new key for each iteration. + ASSERT_OK(key.InitializeRandom(AES_BLOCK_SIZE, m)); // Check that OpenSSL is happy with the amount of entropy we're feeding it. DCHECK_EQ(1, RAND_status()); @@ -120,6 +140,18 @@ TEST_F(OpenSSLUtilTest, Encryption) { } } +/// Test to check whether key and mode are validated in InitializeFields(). +TEST_F(OpenSSLUtilTest, ValidateInitialize) { + EncryptionKey key; + uint8_t IV[AES_BLOCK_SIZE] = {}; + uint8_t key16bits[16] = {}; + Status status_initialize_fields = key.InitializeFields + (key16bits,16, IV, AES_BLOCK_SIZE, AES_CIPHER_MODE::AES_256_GCM); + ASSERT_FALSE(status_initialize_fields.ok()); + ASSERT_OK(key.InitializeFields(key16bits, + 16, IV, AES_BLOCK_SIZE, AES_CIPHER_MODE::AES_128_GCM)); +} + /// Test that encryption and decryption work in-place. TEST_F(OpenSSLUtilTest, EncryptInPlace) { const int buffer_size = 1024 * 1024; @@ -145,8 +177,7 @@ TEST_F(OpenSSLUtilTest, GcmIntegrity) { vector<uint8_t> buffer(buffer_size); EncryptionKey key; - key.InitializeRandom(); - key.SetCipherMode(AES_256_GCM); + ASSERT_OK(key.InitializeRandom(AES_BLOCK_SIZE, AES_CIPHER_MODE::AES_256_GCM)); // Even it has been set as GCM mode, it may fall back to other modes. // Check if GCM mode is supported at runtime. @@ -205,7 +236,7 @@ TEST_F(OpenSSLUtilTest, RandSeeding) { DCHECK_EQ(1, RAND_status()); EncryptionKey key; - key.InitializeRandom(); + ASSERT_OK(key.InitializeRandom(AES_BLOCK_SIZE, key.GetSupportedDefaultMode())); } } } diff --git a/be/src/util/openssl-util.cc b/be/src/util/openssl-util.cc index 8d9a647c5..7de123da1 100644 --- a/be/src/util/openssl-util.cc +++ b/be/src/util/openssl-util.cc @@ -17,8 +17,12 @@ #include "util/openssl-util.h" +#include <boost/algorithm/string.hpp> +#include <string_view> + #include <limits.h> #include <sstream> +#include <iostream> #include <glog/logging.h> #include <openssl/err.h> @@ -56,6 +60,9 @@ const EVP_CIPHER* EVP_aes_256_ctr(); ATTRIBUTE_WEAK const EVP_CIPHER* EVP_aes_256_gcm(); + +ATTRIBUTE_WEAK +const EVP_CIPHER* EVP_aes_128_gcm(); } namespace impala { @@ -75,7 +82,7 @@ int MaxSupportedTlsVersion() { return SSLv23_method()->version; #else // OpenSSL 1.1+ doesn't let us detect the supported TLS version at runtime. Assume - // that the OpenSSL library we're linked against supports only up to TLS1.2 + // that the OpenSSL library we're linked against supports only up to TLS1.2. return TLS1_2_VERSION; #endif } @@ -187,7 +194,47 @@ bool AuthenticationHash::Verify( return memcmp(signature, out, HashLen()) == 0; } -void EncryptionKey::InitializeRandom() { +int GetKeyLenForMode(AES_CIPHER_MODE m) { + switch (m) { + case AES_CIPHER_MODE::AES_128_ECB: + case AES_CIPHER_MODE::AES_128_GCM: + return 16; + case AES_CIPHER_MODE::AES_256_ECB: + case AES_CIPHER_MODE::AES_256_GCM: + case AES_CIPHER_MODE::AES_256_CTR: + case AES_CIPHER_MODE::AES_256_CFB: + return 32; + default: + return -1; + } +} + +Status ValidateModeAndKeyLength(AES_CIPHER_MODE m, int key_len) { + if (m == AES_CIPHER_MODE::INVALID) { + return Status("Invalid AES mode specified."); + } + + int expected_key_len = GetKeyLenForMode(m); + DCHECK_GT(expected_key_len, 0); + if (key_len != expected_key_len) { + return Status("Mismatch between mode and key length."); + } + + return Status::OK(); +} + +Status EncryptionKey::InitializeRandom(int iv_len, AES_CIPHER_MODE m) { + mode_ = m; + if (!IsModeSupported(m)) { + mode_ = GetSupportedDefaultMode(); + LOG(WARNING) << Substitute("$0 is not supported, fall back to $1.", + ModeToString(m), ModeToString(mode_)); + } + + if (m == AES_CIPHER_MODE::INVALID) { + return Status("Invalid AES mode specified."); + } + uint64_t next_key_num = keys_generated.Add(1); if (next_key_num % RNG_RESEED_INTERVAL == 0) { SeedOpenSSLRNG(); @@ -196,63 +243,92 @@ void EncryptionKey::InitializeRandom() { RAND_bytes(iv_, sizeof(iv_)); memset(gcm_tag_, 0, sizeof(gcm_tag_)); initialized_ = true; + key_length_ = GetKeyLenForMode(mode_); + iv_length_ = iv_len; + return Status::OK(); } -Status EncryptionKey::Encrypt(const uint8_t* data, int64_t len, uint8_t* out) { - return EncryptInternal(true, data, len, out); +Status EncryptionKey::Encrypt(const uint8_t* data, int64_t len, uint8_t* out, + int64_t* out_len) { + return EncryptInternal(true, data, len, out, out_len); } -Status EncryptionKey::Decrypt(const uint8_t* data, int64_t len, uint8_t* out) { - return EncryptInternal(false, data, len, out); +Status EncryptionKey::Decrypt(const uint8_t* data, int64_t len, uint8_t* out, + int64_t* out_len) { + return EncryptInternal(false, data, len, out, out_len); } Status EncryptionKey::EncryptInternal( - bool encrypt, const uint8_t* data, int64_t len, uint8_t* out) { + bool encrypt, const uint8_t* data, int64_t len, uint8_t* out, int64_t* out_len) { DCHECK(initialized_); DCHECK_GE(len, 0); + if (IsEcbMode()) { + if ((encrypt && len > numeric_limits<int>::max() - AES_BLOCK_SIZE) + || (!encrypt && len > numeric_limits<int>::max())) { + return Status("Input buffer length exceeds the supported length for ECB mode."); + } + } + const char* err_context = encrypt ? "encrypting" : "decrypting"; - // Create and initialize the context for encryption - ScopedEVPCipherCtx ctx(0); - - // Start encryption/decryption. We use a 256-bit AES key, and the cipher block mode - // is either CTR or CFB(stream cipher), both of which support arbitrary length - // ciphertexts - it doesn't have to be a multiple of 16 bytes. Additionally, CTR - // mode is well-optimized(instruction level parallelism) with hardware acceleration - // on x86 and PowerPC + // Create and initialize the context for encryption. + // If it is ECB mode then padding will be enabled + // for this ctx, otherwise it will be disabled. + bool padding_enabled = IsEcbMode(); + int padding_flag = padding_enabled ? 1 : 0; + ScopedEVPCipherCtx ctx(padding_flag); + + // Start encryption/decryption. We use a 128/256-bit AES key and support GCM, CTR + // CFB and ECB for encryption and decryption. When the cipher block mode is either + // GCM, CTR or CFB(stream cipher), it supports arbitrary length ciphertexts - it + // doesn't have to be a multiple of 16 bytes. While for ECB decryption, + // the length has to be multiples of 16. Additionally, CTR mode is + // well-optimized (instruction level parallelism) with hardware acceleration + // on x86 and PowerPC. + + // In the first initialization, only evpCipher is initialized, and in the second + // initialization, the key and IV vector are set. This approach is necessary because a + // variable-length IV vector is used. Therefore, in GCM mode, the IV length must be + // initialized before setting the IV vector. const EVP_CIPHER* evpCipher = GetCipher(); - int success = encrypt ? EVP_EncryptInit_ex(ctx.ctx, evpCipher, NULL, NULL, NULL) : - EVP_DecryptInit_ex(ctx.ctx, evpCipher, NULL, NULL, NULL); + DCHECK(evpCipher != nullptr); + int success = encrypt ? + EVP_EncryptInit_ex(ctx.ctx, evpCipher, nullptr, nullptr, nullptr): + EVP_DecryptInit_ex(ctx.ctx, evpCipher, nullptr, nullptr, nullptr); if (success != 1) { return OpenSSLErr(encrypt ? "EVP_EncryptInit_ex" : "EVP_DecryptInit_ex", err_context); } + if (IsGcmMode()) { - if (EVP_CIPHER_CTX_ctrl(ctx.ctx, EVP_CTRL_GCM_SET_IVLEN, AES_BLOCK_SIZE, NULL) - != 1) { + // Set iv_vector for GCM mode. + if (EVP_CIPHER_CTX_ctrl(ctx.ctx, EVP_CTRL_GCM_SET_IVLEN, iv_length_, nullptr)!= 1) { return OpenSSLErr("EVP_CIPHER_CTX_ctrl", err_context); } } // setting iv after changing iv len, see https://github.com/openssl/openssl/pull/22590 - success = encrypt ? EVP_EncryptInit_ex(ctx.ctx, NULL, NULL, key_, iv_) : - EVP_DecryptInit_ex(ctx.ctx, NULL, NULL, key_, iv_); + success = encrypt ? EVP_EncryptInit_ex(ctx.ctx, nullptr, nullptr, key_, iv_): + EVP_DecryptInit_ex(ctx.ctx, nullptr, nullptr, key_, iv_); if (success != 1) { return OpenSSLErr(encrypt ? "EVP_EncryptInit_ex" : "EVP_DecryptInit_ex", err_context); } - // The OpenSSL encryption APIs use ints for buffer lengths for some reason. To support - // larger buffers we need to chunk larger buffers into smaller parts. - int64_t offset = 0; - while (offset < len) { - int in_len = static_cast<int>(min<int64_t>(len - offset, numeric_limits<int>::max())); - int out_len; + // The OpenSSL encryption APIs use INT for buffer lengths. To support larger buffers, + // larger buffers need to be chunked into smaller parts. + int64_t output_offset = 0; + int64_t input_offset = 0; + while (input_offset < len) { + int in_len = static_cast<int>(min<int64_t>(len - input_offset, + numeric_limits<int>::max())); + int output_len; + uint8_t* out_addr = out + output_offset; + const uint8_t* in_addr = data + input_offset; success = encrypt ? - EVP_EncryptUpdate(ctx.ctx, out + offset, &out_len, data + offset, in_len) : - EVP_DecryptUpdate(ctx.ctx, out + offset, &out_len, data + offset, in_len); + EVP_EncryptUpdate(ctx.ctx, out_addr, &output_len, in_addr, in_len) : + EVP_DecryptUpdate(ctx.ctx, out_addr, &output_len, in_addr, in_len); if (success != 1) { return OpenSSLErr(encrypt ? "EVP_EncryptUpdate" : "EVP_DecryptUpdate", err_context); } - // This is safe because we're using CTR/CFB mode without padding. - DCHECK_EQ(in_len, out_len); - offset += in_len; + output_offset += output_len; + input_offset += in_len; } if (IsGcmMode() && !encrypt) { @@ -265,45 +341,70 @@ Status EncryptionKey::EncryptInternal( // Finalize encryption or decryption. int final_out_len; - success = encrypt ? EVP_EncryptFinal_ex(ctx.ctx, out + offset, &final_out_len) : - EVP_DecryptFinal_ex(ctx.ctx, out + offset, &final_out_len); + success = encrypt ? EVP_EncryptFinal_ex(ctx.ctx, out + output_offset, &final_out_len) : + EVP_DecryptFinal_ex(ctx.ctx, out + output_offset, &final_out_len); + if (success != 1) { return OpenSSLErr(encrypt ? "EVP_EncryptFinal" : "EVP_DecryptFinal", err_context); } + if (out_len != nullptr) *out_len = output_offset + final_out_len; + if (IsGcmMode() && encrypt) { if (EVP_CIPHER_CTX_ctrl(ctx.ctx, EVP_CTRL_GCM_GET_TAG, AES_BLOCK_SIZE, gcm_tag_) != 1) { return OpenSSLErr("EVP_CIPHER_CTX_ctrl", err_context); } } - // Again safe due to GCM/CTR/CFB with no padding - DCHECK_EQ(final_out_len, 0); + DCHECK_EQ(ERR_peek_error(), 0) << "Did not clear OpenSSL error queue"; return Status::OK(); } const EVP_CIPHER* EncryptionKey::GetCipher() const { // use weak symbol to avoid compiling error on OpenSSL 1.0.0 environment - if (mode_ == AES_256_CTR) return EVP_aes_256_ctr(); - if (mode_ == AES_256_GCM) return EVP_aes_256_gcm(); - - return EVP_aes_256_cfb(); + switch (mode_) { + case AES_CIPHER_MODE::AES_256_CTR: return EVP_aes_256_ctr(); + case AES_CIPHER_MODE::AES_256_GCM: return EVP_aes_256_gcm(); + case AES_CIPHER_MODE::AES_256_CFB: return EVP_aes_256_cfb(); + case AES_CIPHER_MODE::AES_256_ECB: return EVP_aes_256_ecb(); + case AES_CIPHER_MODE::AES_128_GCM: return EVP_aes_128_gcm(); + case AES_CIPHER_MODE::AES_128_ECB: return EVP_aes_128_ecb(); + default: return nullptr; + } } -void EncryptionKey::SetCipherMode(AES_CIPHER_MODE m) { +Status EncryptionKey::InitializeFields(const uint8_t* key, int key_len, const uint8_t* iv, + int iv_len, AES_CIPHER_MODE m) { mode_ = m; - if (!IsModeSupported(m)) { mode_ = GetSupportedDefaultMode(); LOG(WARNING) << Substitute("$0 is not supported, fall back to $1.", ModeToString(m), ModeToString(mode_)); } + Status status = ValidateModeAndKeyLength(m, key_len); + RETURN_IF_ERROR(status); + + key_length_ = key_len; + iv_length_ = iv_len; + memcpy(key_, key, key_length_); + if (iv_length_ != 0) { + memcpy(iv_, iv, iv_length_); + } + initialized_ = true; + return Status::OK(); +} + +void EncryptionKey::SetGcmTag(const uint8_t* tag) { + memcpy(gcm_tag_, tag, AES_BLOCK_SIZE); +} + +void EncryptionKey::GetGcmTag(uint8_t* out) const { + memcpy(out, gcm_tag_, AES_BLOCK_SIZE); } bool EncryptionKey::IsModeSupported(AES_CIPHER_MODE m) { switch (m) { - case AES_256_GCM: // It becomes a bit tricky for GCM mode, because GCM mode is enabled since // OpenSSL 1.0.1, but the tag validation only works since 1.0.1d. We have // to make sure that OpenSSL version >= 1.0.1d for GCM. So we need @@ -313,34 +414,67 @@ bool EncryptionKey::IsModeSupported(AES_CIPHER_MODE m) { // will try to fall back to CTR mode, so it is not ideal but is OK to use // SSLeay() for GCM mode here since in the worst case, we will be using // AES_256_CTR in a system that supports AES_256_GCM. + case AES_CIPHER_MODE::AES_256_GCM: return (CpuInfo::IsSupported(CpuInfo::PCLMULQDQ) && SSLeay() >= OPENSSL_VERSION_1_0_1D && EVP_aes_256_gcm); - case AES_256_CTR: + case AES_CIPHER_MODE::AES_128_GCM: + return (CpuInfo::IsSupported(CpuInfo::PCLMULQDQ) + && SSLeay() >= OPENSSL_VERSION_1_0_1D && EVP_aes_128_gcm); + + case AES_CIPHER_MODE::AES_256_CTR: // If TLS1.2 is supported, then we're on a verison of OpenSSL that // supports AES-256-CTR. return (MaxSupportedTlsVersion() >= TLS1_2_VERSION && EVP_aes_256_ctr); - case AES_256_CFB: + case AES_CIPHER_MODE::AES_256_CFB: + case AES_CIPHER_MODE::AES_256_ECB: + case AES_CIPHER_MODE::AES_128_ECB: return true; - + case AES_CIPHER_MODE::INVALID: + return false; default: return false; } } AES_CIPHER_MODE EncryptionKey::GetSupportedDefaultMode() { - if (IsModeSupported(AES_256_GCM)) return AES_256_GCM; - if (IsModeSupported(AES_256_CTR)) return AES_256_CTR; - return AES_256_CFB; + if (IsModeSupported(AES_CIPHER_MODE::AES_256_GCM)) { + return AES_CIPHER_MODE::AES_256_GCM; + } + if (IsModeSupported(AES_CIPHER_MODE::AES_256_CTR)) { + return AES_CIPHER_MODE::AES_256_CTR; + } + return AES_CIPHER_MODE::AES_256_CFB; } const string EncryptionKey::ModeToString(AES_CIPHER_MODE m) { switch(m) { - case AES_256_GCM: return "AES-GCM"; - case AES_256_CTR: return "AES-CTR"; - case AES_256_CFB: return "AES-CFB"; + case AES_CIPHER_MODE::AES_256_GCM: return std::string(AES_256_GCM_STR); + case AES_CIPHER_MODE::AES_256_CTR: return std::string(AES_256_CTR_STR); + case AES_CIPHER_MODE::AES_256_CFB: return std::string(AES_256_CFB_STR); + case AES_CIPHER_MODE::AES_256_ECB: return std::string(AES_256_ECB_STR); + case AES_CIPHER_MODE::AES_128_GCM: return std::string(AES_128_GCM_STR); + case AES_CIPHER_MODE::AES_128_ECB: return std::string(AES_128_ECB_STR); + case AES_CIPHER_MODE::INVALID: return "INVALID"; } - return "Unknown mode"; + return "INVALID"; } + +AES_CIPHER_MODE EncryptionKey::StringToMode(std:: string_view str) { + if (boost::iequals(str, AES_256_GCM_STR)) { + return AES_CIPHER_MODE::AES_256_GCM; + } else if (boost::iequals(str, AES_256_CTR_STR)) { + return AES_CIPHER_MODE::AES_256_CTR; + } else if (boost::iequals(str, AES_256_CFB_STR)) { + return AES_CIPHER_MODE::AES_256_CFB; + } else if (boost::iequals(str, AES_256_ECB_STR)) { + return AES_CIPHER_MODE::AES_256_ECB; + } else if (boost::iequals(str, AES_128_GCM_STR)) { + return AES_CIPHER_MODE::AES_128_GCM; + } else if (boost::iequals(str, AES_128_ECB_STR)) { + return AES_CIPHER_MODE::AES_128_ECB; + } + return AES_CIPHER_MODE::INVALID; } +} \ No newline at end of file diff --git a/be/src/util/openssl-util.h b/be/src/util/openssl-util.h index 0864e5fb6..88f2c4d1d 100644 --- a/be/src/util/openssl-util.h +++ b/be/src/util/openssl-util.h @@ -17,12 +17,13 @@ #pragma once +#include <string_view> + #include <openssl/aes.h> #include <openssl/crypto.h> #include <openssl/evp.h> #include <openssl/sha.h> #include <openssl/ssl.h> - #include "common/status.h" namespace impala { @@ -69,12 +70,27 @@ inline bool IsFIPSMode() { #endif }; -enum AES_CIPHER_MODE { +// Enum of all the AES modes that are currently supported. INVALID is used whenever +// user inputs a wrong AES Mode, for example, AES_128_CTF etc. +enum class AES_CIPHER_MODE { AES_256_CFB, AES_256_CTR, - AES_256_GCM + AES_256_GCM, + AES_256_ECB, + AES_128_GCM, + AES_128_ECB, + INVALID }; +// AES cipher mode strings as constexpr static std::string_view used in StringToMode() +// and ModeToString() functions for string comparisons. +static constexpr std::string_view AES_256_GCM_STR = "AES_256_GCM"; +static constexpr std::string_view AES_256_CTR_STR = "AES_256_CTR"; +static constexpr std::string_view AES_256_CFB_STR = "AES_256_CFB"; +static constexpr std::string_view AES_256_ECB_STR = "AES_256_ECB"; +static constexpr std::string_view AES_128_GCM_STR = "AES_128_GCM"; +static constexpr std::string_view AES_128_ECB_STR = "AES_128_ECB"; + /// The hash of a data buffer used for checking integrity. A SHA256 hash is used /// internally. class IntegrityHash { @@ -117,14 +133,14 @@ class AuthenticationHash { /// The key and initialization vector (IV) required to encrypt and decrypt a buffer of /// data. This should be regenerated for each buffer of data. /// -/// We use AES with a 256-bit key and GCM/CTR/CFB cipher block mode, which gives us a -/// stream cipher that can support arbitrary-length ciphertexts. The mode is chosen -/// depends on the OpenSSL version & the hardware support at runtime. The IV is used as -/// an input to the cipher as the "block to supply before the first block of plaintext". -/// This is required because all ciphers (except the weak ECB) are built such that each -/// block depends on the output from the previous block. Since the first block doesn't -/// have a previous block, we supply this IV. Think of it as starting off the chain of -/// encryption. +/// AES is employed with a 256-bit/128-bit key, and the cipher block mode is chosen +/// from GCM, CTR, CFB, or ECB based on the OpenSSL version and hardware support +/// at runtime. This configuration yields a cipher capable of handling +/// arbitrary-length ciphertexts (except ECB). The IV serves as input to the cipher, +/// acting as the "block to supply before the first block of plaintext." This is necessary +/// because all ciphers (except ECB) are designed such that each block depends on +/// the output from the preceding block. Since the first block lacks a previous +/// block, the IV is supplied to initiate the encryption chain. /// /// Notes for GCM: /// (1) GCM mode was supported since OpenSSL 1.0.1, however the tag verification @@ -137,54 +153,84 @@ class AuthenticationHash { class EncryptionKey { public: - EncryptionKey() : initialized_(false) { mode_ = GetSupportedDefaultMode(); } + EncryptionKey() : initialized_(false) { mode_ = AES_CIPHER_MODE::INVALID; } /// Initializes a key for temporary use with randomly generated data, and clears the /// tag for GCM mode. Reinitializes with new random values if the key was already /// initialized. We use AES-GCM/AES-CTR/AES-CFB mode so key/IV pairs should not be /// reused. This function automatically reseeds the RNG periodically, so callers do /// not need to do it. - void InitializeRandom(); + Status InitializeRandom(int iv_len, AES_CIPHER_MODE mode); /// Encrypts a buffer of input data 'data' of length 'len' into an output buffer 'out'. - /// Exactly 'len' bytes will be written to 'out'. This key must be initialized before - /// calling. Operates in-place if 'in' == 'out', otherwise the buffers must not overlap. - /// For GCM mode, the hash tag will be kept inside(gcm_tag_ variable). - Status Encrypt(const uint8_t* data, int64_t len, uint8_t* out) WARN_UNUSED_RESULT; - - /// Decrypts a buffer of input data 'data' of length 'len' that was encrypted with this - /// key into an output buffer 'out'. Exactly 'len' bytes will be written to 'out'. - /// This key must be initialized before calling. Operates in-place if 'in' == 'out', - /// otherwise the buffers must not overlap. For GCM mode, the hash tag, which is - /// computed during encryption, will be used for intgerity verification. - Status Decrypt(const uint8_t* data, int64_t len, uint8_t* out) WARN_UNUSED_RESULT; - - /// Specify a cipher mode. Currently used only for testing but maybe in future we - /// can provide a configuration option for the end user who can choose a preferred - /// mode(GCM, CTR, CFB...) based on their software/hardware environment. - /// If not supported, fall back to the supported mode at runtime. - void SetCipherMode(AES_CIPHER_MODE m); - - /// If is GCM mode at runtime - bool IsGcmMode() const { return mode_ == AES_256_GCM; } - - /// Returns the a default mode which is supported at runtime. If GCM mode + /// The 'out' buffer must contain sufficient length to hold the extra padding block in + /// case of ECB mode. In other modes, the 'out' buffer should at least be as big as the + /// length of the 'data' buffer. This key must be initialized before calling this + /// function. If 'in' == 'out', the operation is performed in-place; otherwise, the + /// buffers must not overlap. In GCM mode, the hash tag is kept internally (in the + /// 'gcm_tag_' variable). 'out_len' (if not NULL) will be set to the output length. + Status Encrypt(const uint8_t* data, int64_t len, uint8_t* out, + int64_t* out_len = nullptr) WARN_UNUSED_RESULT; + + /// Decrypts a buffer of input data 'data' of length 'len', encrypted with this key, + /// into an output buffer 'out'.'out' buffer should be at least as big as the + /// length of the 'data' buffer. + /// Prior to calling this function, the key must be initialized. If 'in' == 'out', the + /// operation is performed in-place; otherwise, the buffers must not overlap. In GCM + /// mode, the hash tag computed during encryption is used for integrity verification. + /// 'out_len' (if not NULL) will be set to the output length. + Status Decrypt(const uint8_t* data, int64_t len, uint8_t* out, + int64_t* out_len = nullptr) WARN_UNUSED_RESULT; + + /// If it is GCM mode at runtime + bool IsGcmMode() const { + return mode_ == AES_CIPHER_MODE::AES_256_GCM + || mode_ == AES_CIPHER_MODE::AES_128_GCM; + } + + /// If it is ECB mode at runtime. + bool IsEcbMode() const { + return mode_ == AES_CIPHER_MODE::AES_256_ECB + || mode_ == AES_CIPHER_MODE::AES_128_ECB; + } + + /// It initializes 'key_' and 'iv_'. + /// Specifies a cipher mode. It gives an option to configure this mode for end users, + /// allowing them to choose a preferred mode (e.g., GCM, CTR, CFB) based on their + /// software/hardware environment. If the specified mode is not supported, the + /// implementation falls back to a supported mode at runtime. + Status InitializeFields(const uint8_t* key, int key_len, const uint8_t* iv, int iv_len, + AES_CIPHER_MODE mode); + + /// Getter function for 'gcm_tag_' which is required for encryption using GCM mode. + void GetGcmTag(uint8_t* out) const; + /// Setter function for setting 'gcm_tag_' which is required for decryption using GCM + /// mode. + void SetGcmTag(const uint8_t* tag); + + /// Returns the default mode which is supported at runtime. If GCM mode /// is supported, return AES_256_GCM as the default. If GCM is not supported, /// but CTR is still supported, return AES_256_CTR. When both GCM and /// CTR modes are not supported, return AES_256_CFB. + /// ECB mode is not used unless requested by the user specifically. static AES_CIPHER_MODE GetSupportedDefaultMode(); /// Converts mode type to string. static const std::string ModeToString(AES_CIPHER_MODE m); + static AES_CIPHER_MODE StringToMode(std::string_view str); private: /// Helper method that encrypts/decrypts if 'encrypt' is true/false respectively. /// A buffer of input data 'data' of length 'len' is encrypted/decrypted with this - /// key into an output buffer 'out'. Exactly 'len' bytes will be written to 'out'. + /// key into an output buffer 'out'. + /// The 'out' buffer must contain sufficient length to hold the extra padding block in + /// case of ECB mode. In other modes, 'out' buffer should be at least as big as the + /// length of the 'data' buffer. /// This key must be initialized before calling. Operates in-place if 'in' == 'out', /// otherwise the buffers must not overlap. + /// 'out_len' (if not NULL) will be set to the output length. Status EncryptInternal(bool encrypt, const uint8_t* data, int64_t len, - uint8_t* out) WARN_UNUSED_RESULT; + uint8_t* out, int64_t* out_len) WARN_UNUSED_RESULT; /// Check if mode m is supported at runtime static bool IsModeSupported(AES_CIPHER_MODE m); @@ -198,9 +244,11 @@ class EncryptionKey { /// An AES 256-bit key. uint8_t key_[32]; + int key_length_; /// An initialization vector to feed as the first block to AES. uint8_t iv_[AES_BLOCK_SIZE]; + int iv_length_; /// Tag for GCM mode uint8_t gcm_tag_[AES_BLOCK_SIZE]; @@ -209,4 +257,4 @@ class EncryptionKey { AES_CIPHER_MODE mode_; }; -} +} \ No newline at end of file diff --git a/common/function-registry/impala_functions.py b/common/function-registry/impala_functions.py index f2ed85f97..eb7a53197 100644 --- a/common/function-registry/impala_functions.py +++ b/common/function-registry/impala_functions.py @@ -522,6 +522,12 @@ visible_functions = [ [['ai_generate_text_default'], 'STRING', ['STRING'], 'impala::AiFunctions::AiGenerateTextDefault'], [['bytes'], 'INT', ['STRING'], 'impala::StringFunctions::Bytes'], + [['aes_encrypt'], 'STRING', ['STRING', 'STRING', 'STRING', 'STRING'], 'impala::StringFunctions::AesEncrypt', + '_ZN6impala15StringFunctions10AesPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE', + '_ZN6impala15StringFunctions8AesCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'], + [['aes_decrypt'], 'STRING', ['STRING', 'STRING', 'STRING', 'STRING'], 'impala::StringFunctions::AesDecrypt', + '_ZN6impala15StringFunctions10AesPrepareEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE', + '_ZN6impala15StringFunctions8AesCloseEPN10impala_udf15FunctionContextENS2_18FunctionStateScopeE'], [['length'], 'INT', ['STRING'], 'impala::StringFunctions::Length'], [['length'], 'INT', ['BINARY'], 'impala::StringFunctions::Bytes'], [['length'], 'INT', ['CHAR'], 'impala::StringFunctions::CharLength'], diff --git a/testdata/workloads/functional-query/queries/QueryTest/encryption_exprs.test b/testdata/workloads/functional-query/queries/QueryTest/encryption_exprs.test new file mode 100644 index 000000000..e3d004096 --- /dev/null +++ b/testdata/workloads/functional-query/queries/QueryTest/encryption_exprs.test @@ -0,0 +1,456 @@ +==== +---- QUERY +# AES encryption/decryption examples: +select aes_decrypt(base64decode('y6Ss+zCYObpCbgfWfyNWTw=='),'1234567890123456','AES_128_ECB',''); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('BQGHoM3lqYcsurCRq3PlUw=='),'1234567890123456','AES_128_ECB',''); +---- RESULTS +'' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('E1zl+pDv/GY4JLk254KAIQ=='),'12345678901234567890123456789012','AES_256_ECB',''); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('ABC', '1234567890123456','AES_128_GCM','1234567890123456')); +---- RESULTS +'x+am+BIqtrEK9FpC/zrvpOycjQ==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('x+am+BIqtrEK9FpC/zrvpOycjQ=='),'1234567890123456','AES_128_GCM','1234567890123456'); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('', '1234567890123456','AES_128_GCM','1234567890123456')); +---- RESULTS +'moMhTz224yot8uRtksO+pw==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('moMhTz224yot8uRtksO+pw=='),'1234567890123456','AES_128_GCM','1234567890123456'); +---- RESULTS +'' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_GCM','1234567890123456')); +---- RESULTS +'F/DLkSwEikFOlqzXVCysy1JX7Q==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256_GCM','1234567890123456'); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_GCM','1234567890123456'), +'12345678901234567890123456789012','AES_256_GCM','1234567890123456'); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt(aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='), +'12345678901234567890123456789012','AES_256_GCM','1234567890123456'),'12345678901234567890123456789012','AES_256_GCM', +'1234567890123456')); +---- RESULTS +'F/DLkSwEikFOlqzXVCysy1JX7Q==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('ofMshL41XkG6NQ+v6bawAQ=='),'1234567890123456','AES_128_ECB',''); +---- RESULTS +'Hello World!' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('The quick brown fox jumps over the lazy dog', '1234567890123456','AES_128_GCM', +'1234567890123456')); +---- RESULTS +'0syAnPYY0qT/RNtD7s0UkV/4P7HSl8xI1ZCz9AINV3r5W0xLkUs/gAlXWD+i6TIx+WiWiuipZJMXLmM=' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('0syAnPYY0qT/RNtD7s0UkV/4P7HSl8xI1ZCz9AINV3r5W0xLkUs/gAlXWD+i6TIx+WiWiuipZJMXLmM='), +'1234567890123456','AES_128_GCM','1234567890123456'); +---- RESULTS +'The quick brown fox jumps over the lazy dog' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('Impala', '1234567890123456','AES_128_GCM','12345678901')); +---- RESULTS +'sBDdvFMLqNtJvuHUP8s7oWaH0NWXNQ==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('sBDdvFMLqNtJvuHUP8s7oWaH0NWXNQ=='),'1234567890123456','AES_128_GCM','12345678901'); +---- RESULTS +'Impala' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(aes_encrypt('Impala', '1234567890123456','AES_128_GCM','12345678901'), +'1234567890123456','AES_128_GCM','12345678901'); +---- RESULTS +'Impala' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('impalaaaaaaaaaaaaaaaaaaaa', '12345678901234567890123456789012','AES_256_CFB', +'1234567890123456')); +---- RESULTS +'pkw4Y8WLkJnACfAL4R4BNUBzdnUeUo7vTw==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('pkw4Y8WLkJnACfAL4R4BNUBzdnUeUo7vTw=='),'12345678901234567890123456789012', +'AES_256_CFB','1234567890123456'); +---- RESULTS +'impalaaaaaaaaaaaaaaaaaaaa' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('', '12345678901234567890123456789012','AES_256_CFB','1234567890123456')); +---- RESULTS +'' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode(''),'12345678901234567890123456789012','AES_256_CFB','1234567890123456'); +---- RESULTS +'' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('impalaaaaaaaaaaaaaaaaaaaa', '12345678901234567890123456789012','AES_256_CTR', +'1234567890123456')); +---- RESULTS +'pkw4Y8WLkJnACfAL4R4BNb0HYMEjpcA63A==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('pkw4Y8WLkJnACfAL4R4BNb0HYMEjpcA63A=='),'12345678901234567890123456789012', +'AES_256_CTR','1234567890123456'); +---- RESULTS +'impalaaaaaaaaaaaaaaaaaaaa' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt('', '12345678901234567890123456789012','AES_256_CTR','1234567890123456')); +---- RESULTS +'' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode(''),'12345678901234567890123456789012','AES_256_CTR','1234567890123456'); +---- RESULTS +'' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt((aes_encrypt('impalaaaaaaaaaaaaaaaaaaaa', '12345678901234567890123456789012','AES_256_CFB', +'1234567890123456')),'12345678901234567890123456789012','AES_256_CFB','1234567890123456'); +---- RESULTS +'impalaaaaaaaaaaaaaaaaaaaa' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt((aes_encrypt('impalaaaaaaaaaaaaaaaaaaaa', '12345678901234567890123456789012','AES_256_CTR', +'1234567890123456')),'12345678901234567890123456789012','AES_256_CTR','1234567890123456'); +---- RESULTS +'impalaaaaaaaaaaaaaaaaaaaa' +---- TYPES +STRING +==== +---- QUERY +# Encryption/ decryption when mode is NULL, defaulting to GCM mode. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012',NULL,'1234567890123456')); +---- RESULTS +'F/DLkSwEikFOlqzXVCysy1JX7Q==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_GCM','1234567890123456'), +'12345678901234567890123456789012',NULL,'1234567890123456'); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012',NULL,'1234567890123456'); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +# Encryption/ decryption with expr as NULL. +select base64encode(aes_encrypt(NULL, '12345678901234567890123456789012','AES_256_GCM','1234567890123456')); +---- RESULTS +'NULL' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(NULL,'12345678901234567890123456789012','AES_256_GCM','1234567890123456'); +---- RESULTS +'NULL' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt(NULL, '12345678901234567890123456789012','AES_256_CTR','1234567890123456')); +---- RESULTS +'NULL' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(NULL,'12345678901234567890123456789012','AES_256_CTR','1234567890123456'); +---- RESULTS +'NULL' +---- TYPES +STRING +==== +---- QUERY +select base64encode(aes_encrypt(NULL, '12345678901234567890123456789012','AES_256_CFB','1234567890123456')); +---- RESULTS +'NULL' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(NULL,'12345678901234567890123456789012','AES_256_CFB','1234567890123456'); +---- RESULTS +'NULL' +---- TYPES +STRING +==== +---- QUERY +# Key cannot be NULL. +select base64encode(aes_encrypt('ABC',NULL,'AES_256_CFB','1234567890123456')); +---- RESULTS +---- CATCH +Key cannot be NULL. +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),NULL,'AES_256_CFB','1234567890123456'); +---- RESULTS +---- CATCH +UDF ERROR: Key cannot be NULL. +==== +---- QUERY +# Key cannot be NULL, with the default mode GCM. +select base64encode(aes_encrypt('ABC',NULL,'AES_256_GCM','1234567890123456')); +---- RESULTS +---- CATCH +Key cannot be NULL. +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),NULL,'AES_256_GCM','1234567890123456'); +---- RESULTS +---- CATCH +UDF ERROR: Key cannot be NULL. +==== +---- QUERY +# ECB not supported for AES encryption. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_ECB','1234567890123456')); +---- RESULTS +---- CATCH +ECB mode is not supported for encryption. +==== +---- QUERY +# iv cannot be NULL for GCM mode. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_GCM',NULL)); +---- RESULTS +---- CATCH +IV vector required for AES_256_GCM mode +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256_GCM',NULL); +---- RESULTS +---- CATCH +UDF ERROR: IV vector required for AES_256_GCM mode +==== +---- QUERY +# iv cannot be NULL for CFB mode. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_CFB',NULL)); +---- RESULTS +---- CATCH +IV vector required for AES_256_CFB mode +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256_CFB',NULL); +---- RESULTS +---- CATCH +UDF ERROR: IV vector required for AES_256_CFB mode +==== +---- QUERY +# iv cannot be NULL for CTR mode. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_CTR',NULL)); +---- RESULTS +---- CATCH +IV vector required for AES_256_CTR mode +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256_CTR',NULL); +---- RESULTS +---- CATCH +UDF ERROR: IV vector required for AES_256_CTR mode +==== +---- QUERY +# Error resulting due to user entered incorrect mode. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_CTB','1234567890123456')); +---- RESULTS +---- CATCH +Invalid AES 'mode': AES_256_CTB +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256_CTB', +'1234567890123456'); +---- RESULTS +---- CATCH +UDF ERROR: Invalid AES 'mode': AES_256_CTB +==== +---- QUERY +# Error with incorrect key length. +select base64encode(aes_encrypt('ABC', '123456789012345678901234567890121','AES_256_GCM','1234567890123456')); +---- RESULTS +---- CATCH +AES only supports 128 and 256 bit key lengths +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'123456789012345678901234567890121','AES_256_GCM', +'1234567890123456'); +---- RESULTS +---- CATCH +UDF ERROR: AES only supports 128 and 256 bit key lengths +==== +---- QUERY +# Error with incorrect iv length. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_GCM','12345678901234567')); +---- RESULTS +---- CATCH +IV vector size is greater than 16 bytes +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256_GCM', +'12345678901234567'); +---- RESULTS +---- CATCH +UDF ERROR: IV vector size is greater than 16 bytes +==== +---- QUERY +# Prefixes of supported modes are not accepted +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','','1234567890123456')); +---- RESULTS +---- CATCH +Invalid AES 'mode': +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','', +'1234567890123456'); +---- RESULTS +---- CATCH +UDF ERROR: Invalid AES 'mode': +==== +---- QUERY +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256','1234567890123456')); +---- RESULTS +---- CATCH +Invalid AES 'mode': AES_256 +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256', +'1234567890123456'); +---- RESULTS +---- CATCH +UDF ERROR: Invalid AES 'mode': AES_256 +==== +---- QUERY +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','AES_256_GC','1234567890123456')); +---- RESULTS +---- CATCH +Invalid AES 'mode': AES_256_GC +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','AES_256_GC', +'1234567890123456'); +---- RESULTS +---- CATCH +UDF ERROR: Invalid AES 'mode': AES_256_GC +==== +---- QUERY +# Modes are case-insensitive. +select base64encode(aes_encrypt('ABC', '12345678901234567890123456789012','aes_256_gcm','1234567890123456')); +---- RESULTS +'F/DLkSwEikFOlqzXVCysy1JX7Q==' +---- TYPES +STRING +==== +---- QUERY +select aes_decrypt(base64decode('F/DLkSwEikFOlqzXVCysy1JX7Q=='),'12345678901234567890123456789012','aes_256_gcm', +'1234567890123456'); +---- RESULTS +'ABC' +---- TYPES +STRING +==== +---- QUERY +select count(*) from functional.alltypes where string_col = aes_decrypt(aes_encrypt(string_col, '1234567890123456', +'AES_128_GCM', '1234567890123456'), '1234567890123456', 'AES_128_GCM', '1234567890123456'); +---- RESULTS +7300 +---- TYPES +BIGINT +==== +---- QUERY +select count(*) from functional_parquet.alltypes where CAST(timestamp_col AS STRING) = +aes_decrypt(aes_encrypt(CAST(timestamp_col AS STRING), '1234567890123456', 'AES_128_GCM', '1234567890123456'), +'1234567890123456', 'AES_128_GCM', '1234567890123456'); +---- RESULTS +7300 +---- TYPES +BIGINT +==== \ No newline at end of file diff --git a/tests/query_test/test_exprs.py b/tests/query_test/test_exprs.py index 572277fed..bdfa188bb 100644 --- a/tests/query_test/test_exprs.py +++ b/tests/query_test/test_exprs.py @@ -72,6 +72,12 @@ class TestExprs(ImpalaTestSuite): vector.get_value('enable_expr_rewrites') self.run_test_case('QueryTest/special-strings', vector) + def test_encryption_exprs(self, vector): + """Test handling encryption/ decryption functionality""" + vector.get_value('exec_option')['enable_expr_rewrites'] = \ + vector.get_value('enable_expr_rewrites') + self.run_test_case('QueryTest/encryption_exprs', vector) + # Tests very deep expression trees and expressions with many children. Impala defines # a 'safe' upper bound on the expr depth and the number of expr children in the # FE Expr.java and any changes to those limits should be reflected in this test.
