ClickHouse/src/Functions/FunctionsAES.h
2020-10-14 16:52:59 +03:00

679 lines
29 KiB
C++

#pragma once
#if !defined(ARCADIA_BUILD)
# include <Common/config.h>
#endif
#if USE_SSL
#include <DataTypes/DataTypeString.h>
#include <Columns/ColumnString.h>
#include <Functions/IFunction.h>
#include <Functions/FunctionFactory.h>
#include <Functions/FunctionHelpers.h>
#include <fmt/format.h>
#include <openssl/evp.h>
#include <openssl/engine.h>
#include <string_view>
#include <functional>
#include <initializer_list>
#include <string.h>
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
}
namespace OpenSSLDetails
{
[[noreturn]] void onError(std::string error_message);
StringRef foldEncryptionKeyInMySQLCompatitableMode(size_t cipher_key_size, const StringRef & key, std::array<char, EVP_MAX_KEY_LENGTH> & folded_key);
const EVP_CIPHER * getCipherByName(const StringRef & name);
enum class CompatibilityMode
{
MySQL,
OpenSSL
};
enum class CipherMode
{
MySQLCompatibility, // with key folding
OpenSSLCompatibility, // just as regular openssl's enc application does (AEAD modes, like GCM and CCM are not supported)
RFC5116_AEAD_AES_GCM // AEAD GCM with custom IV length and tag (HMAC) appended to the ciphertext, see https://tools.ietf.org/html/rfc5116#section-5.1
};
template <CipherMode mode>
struct KeyHolder
{
inline StringRef setKey(size_t cipher_key_size, const StringRef & key) const
{
if (key.size != cipher_key_size)
throw DB::Exception(fmt::format("Invalid key size: {} expected {}", key.size, cipher_key_size),
DB::ErrorCodes::BAD_ARGUMENTS);
return key;
}
};
template <>
struct KeyHolder<CipherMode::MySQLCompatibility>
{
inline StringRef setKey(size_t cipher_key_size, const StringRef & key)
{
if (key.size < cipher_key_size)
throw DB::Exception(fmt::format("Invalid key size: {} expected {}", key.size, cipher_key_size),
DB::ErrorCodes::BAD_ARGUMENTS);
// MySQL does something fancy with the keys that are too long,
// ruining compatibility with OpenSSL and not improving security.
// But we have to do the same to be compatitable with MySQL.
// see https://github.com/mysql/mysql-server/blob/8.0/router/src/harness/src/my_aes_openssl.cc#L71
// (my_aes_create_key function)
return foldEncryptionKeyInMySQLCompatitableMode(cipher_key_size, key, folded_key);
}
~KeyHolder()
{
OPENSSL_cleanse(folded_key.data(), folded_key.size());
}
private:
std::array<char, EVP_MAX_KEY_LENGTH> folded_key;
};
template <CompatibilityMode compatibility_mode>
inline void validateCipherMode(const EVP_CIPHER * evp_cipher)
{
if constexpr (compatibility_mode == CompatibilityMode::MySQL)
{
switch (EVP_CIPHER_mode(evp_cipher))
{
case EVP_CIPH_ECB_MODE: [[fallthrough]];
case EVP_CIPH_CBC_MODE: [[fallthrough]];
case EVP_CIPH_CFB_MODE: [[fallthrough]];
case EVP_CIPH_OFB_MODE:
return;
}
}
else if constexpr (compatibility_mode == CompatibilityMode::OpenSSL)
{
switch (EVP_CIPHER_mode(evp_cipher))
{
case EVP_CIPH_ECB_MODE: [[fallthrough]];
case EVP_CIPH_CBC_MODE: [[fallthrough]];
case EVP_CIPH_CFB_MODE: [[fallthrough]];
case EVP_CIPH_OFB_MODE: [[fallthrough]];
case EVP_CIPH_CTR_MODE: [[fallthrough]];
case EVP_CIPH_GCM_MODE:
return;
}
}
throw DB::Exception("Unsupported cipher mode " + std::string(EVP_CIPHER_name(evp_cipher)), DB::ErrorCodes::BAD_ARGUMENTS);
}
template <CipherMode mode>
inline void validateIV(const StringRef & iv_value, const size_t cipher_iv_size)
{
// In MySQL mode we don't care if IV is longer than expected, only if shorter.
if ((mode == CipherMode::MySQLCompatibility && iv_value.size != 0 && iv_value.size < cipher_iv_size)
|| (mode == CipherMode::OpenSSLCompatibility && iv_value.size != 0 && iv_value.size != cipher_iv_size))
throw DB::Exception(fmt::format("Invalid IV size: {} expected {}", iv_value.size, cipher_iv_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
}
namespace DB
{
template <typename Impl>
class FunctionEncrypt : public IFunction
{
public:
static constexpr OpenSSLDetails::CompatibilityMode compatibility_mode = Impl::compatibility_mode;
static constexpr auto name = Impl::name;
static FunctionPtr create(const Context &) { return std::make_shared<FunctionEncrypt>(); }
private:
using CipherMode = OpenSSLDetails::CipherMode;
String getName() const override { return name; }
bool isVariadic() const override { return true; }
size_t getNumberOfArguments() const override { return 0; }
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0}; }
bool useDefaultImplementationForConstants() const override { return true; }
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
{
auto optional_args = FunctionArgumentDescriptors{
{"IV", isStringOrFixedString, nullptr, "Initialization vector binary string"},
};
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::OpenSSL)
{
optional_args.emplace_back(FunctionArgumentDescriptor{
"AAD", isStringOrFixedString, nullptr, "Additional authenticated data binary string for GCM mode"
});
}
validateFunctionArgumentTypes(*this, arguments,
FunctionArgumentDescriptors{
{"mode", isStringOrFixedString, isColumnConst, "encryption mode string"},
{"input", nullptr, nullptr, "plaintext"},
{"key", isStringOrFixedString, nullptr, "encryption key binary string"},
},
optional_args
);
return std::make_shared<DataTypeString>();
}
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) const override
{
using namespace OpenSSLDetails;
const auto mode = block[arguments[0]].column->getDataAt(0);
if (mode.size == 0 || !std::string_view(mode).starts_with("aes-"))
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
auto evp_cipher = getCipherByName(mode);
if (evp_cipher == nullptr)
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
const auto cipher_mode = EVP_CIPHER_mode(evp_cipher);
const auto input_column = block[arguments[1]].column;
const auto key_column = block[arguments[2]].column;
OpenSSLDetails::validateCipherMode<compatibility_mode>(evp_cipher);
ColumnPtr result_column;
if (arguments.size() <= 3)
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr);
else
{
const auto iv_column = block[arguments[3]].column;
if (compatibility_mode != OpenSSLDetails::CompatibilityMode::MySQL && EVP_CIPHER_iv_length(evp_cipher) == 0)
throw Exception(mode.toString() + " does not support IV", ErrorCodes::BAD_ARGUMENTS);
if (arguments.size() <= 4)
{
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr);
}
else
{
if (cipher_mode != EVP_CIPH_GCM_MODE)
throw Exception("AAD can be only set for GCM-mode", ErrorCodes::BAD_ARGUMENTS);
const auto aad_column = block[arguments[4]].column;
result_column = doEncrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
block[result].column = std::move(result_column);
}
template <typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doEncrypt(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
const IvColumnType & iv_column,
const AadColumnType & aad_column)
{
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL)
{
return doEncryptImpl<CipherMode::MySQLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
if (EVP_CIPHER_mode(evp_cipher) == EVP_CIPH_GCM_MODE)
{
return doEncryptImpl<CipherMode::RFC5116_AEAD_AES_GCM>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
return doEncryptImpl<CipherMode::OpenSSLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
return nullptr;
}
template <CipherMode mode, typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doEncryptImpl(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
[[maybe_unused]] const IvColumnType & iv_column,
[[maybe_unused]] const AadColumnType & aad_column)
{
using namespace OpenSSLDetails;
auto evp_ctx_ptr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
auto evp_ctx = evp_ctx_ptr.get();
const auto block_size = static_cast<size_t>(EVP_CIPHER_block_size(evp_cipher));
const auto key_size = static_cast<size_t>(EVP_CIPHER_key_length(evp_cipher));
[[maybe_unused]] const auto iv_size = static_cast<size_t>(EVP_CIPHER_iv_length(evp_cipher));
const auto tag_size = 16; // https://tools.ietf.org/html/rfc5116#section-5.1
auto encrypted_result_column = ColumnString::create();
auto & encrypted_result_column_data = encrypted_result_column->getChars();
auto & encrypted_result_column_offsets = encrypted_result_column->getOffsets();
{
size_t resulting_size = 0;
// for modes with block_size > 1, plaintext is padded up to a block_size,
// which may result in allocating to much for block_size = 1.
// That may lead later to reading unallocated data from underlying PaddedPODArray
// due to assumption that it is safe to read up to 15 bytes past end.
const auto pad_to_next_block = block_size == 1 ? 0 : 1;
for (size_t r = 0; r < input_rows_count; ++r)
{
resulting_size += (input_column->getDataAt(r).size / block_size + pad_to_next_block) * block_size + 1;
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
resulting_size += tag_size;
}
#if defined(MEMORY_SANITIZER)
encrypted_result_column_data.resize_fill(resulting_size, 0xFF);
#else
encrypted_result_column_data.resize(resulting_size);
#endif
}
auto encrypted = encrypted_result_column_data.data();
KeyHolder<mode> key_holder;
for (size_t r = 0; r < input_rows_count; ++r)
{
const auto key_value = key_holder.setKey(key_size, key_column->getDataAt(r));
auto iv_value = StringRef{};
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<IvColumnType>>)
{
iv_value = iv_column->getDataAt(r);
}
const auto input_value = input_column->getDataAt(r);
auto aad_value = StringRef{};
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM && !std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
{
aad_value = aad_column->getDataAt(r);
}
if constexpr (mode != CipherMode::MySQLCompatibility)
{
// in GCM mode IV can be of arbitrary size (>0).
if ((mode == CipherMode::RFC5116_AEAD_AES_GCM && iv_value.size == 0)
|| (mode != CipherMode::RFC5116_AEAD_AES_GCM && iv_value.size != iv_size))
{
throw Exception("Invalid IV size " + std::to_string(iv_value.size) + " != expected size " + std::to_string(iv_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
if (key_value.size != key_size)
{
throw Exception("Invalid key size " + std::to_string(key_value.size) + " != expected size " + std::to_string(key_size),
DB::ErrorCodes::BAD_ARGUMENTS);
}
}
// Avoid extra work on empty ciphertext/plaintext for some ciphers
if (!(input_value.size == 0 && block_size == 1 && mode != CipherMode::RFC5116_AEAD_AES_GCM))
{
// 1: Init CTX
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
// 1.a.1: Init CTX with custom IV length and optionally with AAD
if (EVP_EncryptInit_ex(evp_ctx, evp_cipher, nullptr, nullptr, nullptr) != 1)
onError("Failed to initialize encryption context with cipher");
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_value.size, nullptr) != 1)
onError("Failed to set custom IV length to " + std::to_string(iv_value.size));
if (EVP_EncryptInit_ex(evp_ctx, nullptr, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to set key and IV");
// 1.a.2 Set AAD
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
{
const auto aad_data = aad_column->getDataAt(r);
int tmp_len = 0;
if (aad_data.size != 0 && EVP_EncryptUpdate(evp_ctx, nullptr, &tmp_len,
reinterpret_cast<const unsigned char *>(aad_data.data), aad_data.size) != 1)
onError("Failed to set AAD data");
}
}
else
{
// 1.b: Init CTX
validateIV<mode>(iv_value, iv_size);
if (EVP_EncryptInit_ex(evp_ctx, evp_cipher, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to initialize cipher context");
}
int output_len = 0;
// 2: Feed the data to the cipher
if (EVP_EncryptUpdate(evp_ctx,
reinterpret_cast<unsigned char*>(encrypted), &output_len,
reinterpret_cast<const unsigned char*>(input_value.data), static_cast<int>(input_value.size)) != 1)
onError("Failed to encrypt");
encrypted += output_len;
// 3: retrieve encrypted data (ciphertext)
if (EVP_EncryptFinal_ex(evp_ctx,
reinterpret_cast<unsigned char*>(encrypted), &output_len) != 1)
onError("Failed to fetch ciphertext");
encrypted += output_len;
// 4: optionally retrieve a tag and append it to the ciphertext (RFC5116):
// https://tools.ietf.org/html/rfc5116#section-5.1
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_GET_TAG, tag_size, encrypted) != 1)
onError("Failed to retrieve GCM tag");
encrypted += tag_size;
}
}
*encrypted = '\0';
++encrypted;
encrypted_result_column_offsets.push_back(encrypted - encrypted_result_column_data.data());
}
// in case of block size of 1, we overestimate buffer required for encrypted data, fix it up.
if (!encrypted_result_column_offsets.empty() && encrypted_result_column_data.size() > encrypted_result_column_offsets.back())
{
encrypted_result_column_data.resize(encrypted_result_column_offsets.back());
}
encrypted_result_column->validate();
return encrypted_result_column;
}
};
/// AES_decrypt(string, key, block_mode[, init_vector])
template <typename Impl>
class FunctionDecrypt : public IFunction
{
public:
static constexpr OpenSSLDetails::CompatibilityMode compatibility_mode = Impl::compatibility_mode;
static constexpr auto name = Impl::name;
static FunctionPtr create(const Context &) { return std::make_shared<FunctionDecrypt>(); }
private:
using CipherMode = OpenSSLDetails::CipherMode;
String getName() const override { return name; }
bool isVariadic() const override { return true; }
size_t getNumberOfArguments() const override { return 0; }
ColumnNumbers getArgumentsThatAreAlwaysConstant() const override { return {0}; }
bool useDefaultImplementationForConstants() const override { return true; }
DataTypePtr getReturnTypeImpl(const ColumnsWithTypeAndName & arguments) const override
{
auto optional_args = FunctionArgumentDescriptors{
{"IV", isStringOrFixedString, nullptr, "Initialization vector binary string"},
};
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::OpenSSL)
{
optional_args.emplace_back(FunctionArgumentDescriptor{
"AAD", isStringOrFixedString, nullptr, "Additional authenticated data binary string for GCM mode"
});
}
validateFunctionArgumentTypes(*this, arguments,
FunctionArgumentDescriptors{
{"mode", isStringOrFixedString, isColumnConst, "decryption mode string"},
{"input", nullptr, nullptr, "ciphertext"},
{"key", isStringOrFixedString, nullptr, "decryption key binary string"},
},
optional_args
);
return std::make_shared<DataTypeString>();
}
void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t input_rows_count) const override
{
using namespace OpenSSLDetails;
const auto mode = block[arguments[0]].column->getDataAt(0);
if (mode.size == 0 || !std::string_view(mode).starts_with("aes-"))
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
auto evp_cipher = getCipherByName(mode);
if (evp_cipher == nullptr)
throw Exception("Invalid mode: " + mode.toString(), ErrorCodes::BAD_ARGUMENTS);
OpenSSLDetails::validateCipherMode<compatibility_mode>(evp_cipher);
const auto input_column = block[arguments[1]].column;
const auto key_column = block[arguments[2]].column;
ColumnPtr result_column;
if (arguments.size() <= 3)
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, nullptr, nullptr);
else
{
const auto iv_column = block[arguments[3]].column;
if (compatibility_mode != OpenSSLDetails::CompatibilityMode::MySQL && EVP_CIPHER_iv_length(evp_cipher) == 0)
throw Exception(mode.toString() + " does not support IV", ErrorCodes::BAD_ARGUMENTS);
if (arguments.size() <= 4)
{
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, nullptr);
}
else
{
if (EVP_CIPHER_mode(evp_cipher) != EVP_CIPH_GCM_MODE)
throw Exception("AAD can be only set for GCM-mode", ErrorCodes::BAD_ARGUMENTS);
const auto aad_column = block[arguments[4]].column;
result_column = doDecrypt(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
block[result].column = std::move(result_column);
}
template <typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doDecrypt(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
const IvColumnType & iv_column,
const AadColumnType & aad_column)
{
if constexpr (compatibility_mode == OpenSSLDetails::CompatibilityMode::MySQL)
{
return doDecryptImpl<CipherMode::MySQLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
const auto cipher_mode = EVP_CIPHER_mode(evp_cipher);
if (cipher_mode == EVP_CIPH_GCM_MODE)
{
return doDecryptImpl<CipherMode::RFC5116_AEAD_AES_GCM>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
else
{
return doDecryptImpl<CipherMode::OpenSSLCompatibility>(evp_cipher, input_rows_count, input_column, key_column, iv_column, aad_column);
}
}
return nullptr;
}
template <CipherMode mode, typename InputColumnType, typename KeyColumnType, typename IvColumnType, typename AadColumnType>
static ColumnPtr doDecryptImpl(const EVP_CIPHER * evp_cipher,
size_t input_rows_count,
const InputColumnType & input_column,
const KeyColumnType & key_column,
[[maybe_unused]] const IvColumnType & iv_column,
[[maybe_unused]] const AadColumnType & aad_column)
{
using namespace OpenSSLDetails;
auto evp_ctx_ptr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free);
auto evp_ctx = evp_ctx_ptr.get();
[[maybe_unused]] const auto block_size = static_cast<size_t>(EVP_CIPHER_block_size(evp_cipher));
[[maybe_unused]] const auto iv_size = static_cast<size_t>(EVP_CIPHER_iv_length(evp_cipher));
const auto key_size = static_cast<size_t>(EVP_CIPHER_key_length(evp_cipher));
const auto tag_size = 16; // https://tools.ietf.org/html/rfc5116#section-5.1
auto decrypted_result_column = ColumnString::create();
auto & decrypted_result_column_data = decrypted_result_column->getChars();
auto & decrypted_result_column_offsets = decrypted_result_column->getOffsets();
{
size_t resulting_size = 0;
for (size_t r = 0; r < input_rows_count; ++r)
{
resulting_size += input_column->getDataAt(r).size + 1;
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
resulting_size -= tag_size;
}
#if defined(MEMORY_SANITIZER)
// Pre-fill result column with values to prevent MSAN from dropping dead on
// aes-X-ecb mode with "WARNING: MemorySanitizer: use-of-uninitialized-value".
// This is most likely to be caused by the underlying assembler implementation:
// see crypto/aes/aesni-x86_64.s, function aesni_ecb_encrypt
// which msan seems to fail instrument correctly.
decrypted_result_column_data.resize_fill(resulting_size, 0xFF);
#else
decrypted_result_column_data.resize(resulting_size);
#endif
}
auto decrypted = decrypted_result_column_data.data();
KeyHolder<mode> key_holder;
for (size_t r = 0; r < input_rows_count; ++r)
{
// 0: prepare key if required
auto key_value = key_holder.setKey(key_size, key_column->getDataAt(r));
auto iv_value = StringRef{};
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<IvColumnType>>)
{
iv_value = iv_column->getDataAt(r);
}
auto input_value = input_column->getDataAt(r);
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
// empty plaintext results in empty ciphertext + tag, means there should be atleast tag_size bytes.
if (input_value.size < tag_size)
throw Exception(fmt::format("Encrypted data is too short: only {} bytes, "
"should contain at least {} bytes of a tag.",
input_value.size, block_size, tag_size), ErrorCodes::BAD_ARGUMENTS);
input_value.size -= tag_size;
}
// Avoid extra work on empty ciphertext/plaintext for some ciphers
if (!(input_value.size == 0 && block_size == 1 && mode != CipherMode::RFC5116_AEAD_AES_GCM))
{
// 1: Init CTX
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
if (EVP_DecryptInit_ex(evp_ctx, evp_cipher, nullptr, nullptr, nullptr) != 1)
onError("Failed to initialize cipher context 1");
// 1.a.1 : Set custom IV length
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_IVLEN, iv_value.size, nullptr) != 1)
onError("Failed to set custom IV length to " + std::to_string(iv_value.size));
// 1.a.1 : Init CTX with key and IV
if (EVP_DecryptInit_ex(evp_ctx, nullptr, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to set key and IV");
// 1.a.2: Set AAD if present
if constexpr (!std::is_same_v<nullptr_t, std::decay_t<AadColumnType>>)
{
const auto aad_data = aad_column->getDataAt(r);
int tmp_len = 0;
if (aad_data.size != 0 && EVP_DecryptUpdate(evp_ctx, nullptr, &tmp_len,
reinterpret_cast<const unsigned char *>(aad_data.data), aad_data.size) != 1)
onError("Failed to sed AAD data");
}
}
else
{
// 1.b: Init CTX
validateIV<mode>(iv_value, iv_size);
if (EVP_DecryptInit_ex(evp_ctx, evp_cipher, nullptr,
reinterpret_cast<const unsigned char*>(key_value.data),
reinterpret_cast<const unsigned char*>(iv_value.data)) != 1)
onError("Failed to initialize cipher context");
}
// 2: Feed the data to the cipher
int output_len = 0;
if (EVP_DecryptUpdate(evp_ctx,
reinterpret_cast<unsigned char*>(decrypted), &output_len,
reinterpret_cast<const unsigned char*>(input_value.data), static_cast<int>(input_value.size)) != 1)
onError("Failed to decrypt");
decrypted += output_len;
// 3: optionally get tag from the ciphertext (RFC5116) and feed it to the context
if constexpr (mode == CipherMode::RFC5116_AEAD_AES_GCM)
{
void * tag = const_cast<void *>(reinterpret_cast<const void *>(input_value.data + input_value.size));
if (EVP_CIPHER_CTX_ctrl(evp_ctx, EVP_CTRL_AEAD_SET_TAG, tag_size, tag) != 1)
onError("Failed to set tag");
}
// 4: retrieve encrypted data (ciphertext)
if (EVP_DecryptFinal_ex(evp_ctx,
reinterpret_cast<unsigned char*>(decrypted), &output_len) != 1)
onError("Failed to decrypt");
decrypted += output_len;
}
*decrypted = '\0';
++decrypted;
decrypted_result_column_offsets.push_back(decrypted - decrypted_result_column_data.data());
}
// in case we overestimate buffer required for decrypted data, fix it up.
if (!decrypted_result_column_offsets.empty() && decrypted_result_column_data.size() > decrypted_result_column_offsets.back())
{
decrypted_result_column_data.resize(decrypted_result_column_offsets.back());
}
decrypted_result_column->validate();
return decrypted_result_column;
}
};
}
#endif