diff --git a/CMakeLists.txt b/CMakeLists.txt index 476f3bc..308d93b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,33 @@ set_target_properties(lib_util PROPERTIES ) add_library(binsrv::lib_util ALIAS lib_util) +# OpenSSL++ files +set(opensslpp_source_files + src/opensslpp/cipher_context_fwd.hpp + src/opensslpp/cipher_context.hpp + src/opensslpp/cipher_context.cpp + + src/opensslpp/crypto_rng.hpp + src/opensslpp/crypto_rng.cpp + + src/opensslpp/core_error_fwd.hpp + src/opensslpp/core_error.hpp + src/opensslpp/core_error.cpp +) +add_library(lib_opensslpp STATIC ${opensslpp_source_files}) +target_link_libraries(lib_opensslpp + PRIVATE + binlog_server_compiler_flags + OpenSSL::Crypto +) +# it is not possible to propagate CXX_EXTENSIONS and CXX_STANDARD_REQUIRED +# via interface library (binlog_server_compiler_flags) +set_target_properties(lib_opensslpp PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) +add_library(binsrv::lib_opensslpp ALIAS lib_opensslpp) + # mysql wrapper library files set(easymysql_source_files src/easymysql/core_error_helpers_private.hpp diff --git a/src/opensslpp/cipher_context.cpp b/src/opensslpp/cipher_context.cpp new file mode 100644 index 0000000..fb23631 --- /dev/null +++ b/src/opensslpp/cipher_context.cpp @@ -0,0 +1,319 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "opensslpp/cipher_context.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "opensslpp/core_error.hpp" + +#include "util/byte_span_fwd.hpp" +#include "util/exception_location_helpers.hpp" + +namespace opensslpp { + +struct cipher_context::native_helper { + [[nodiscard]] static auto deimpl(auto &impl) noexcept { + using cast_type = std::conditional_t< + std::is_const_v>, + const EVP_CIPHER_CTX, EVP_CIPHER_CTX>; + return static_cast(impl.get()); + } + + [[nodiscard]] static const auto * + get_native_cipher_by_name(const std::string &cipher_name) { + const auto *evp_cipher{EVP_get_cipherbyname(cipher_name.c_str())}; + if (evp_cipher == nullptr) { + util::exception_location().raise("unknown cipher name"); + } + const auto mode{EVP_CIPHER_get_mode(evp_cipher)}; + switch (mode) { + case EVP_CIPH_ECB_MODE: + case EVP_CIPH_CBC_MODE: + case EVP_CIPH_CTR_MODE: + case EVP_CIPH_GCM_MODE: + break; + default: + // EVP_CIPH_CFB_MODE + // EVP_CIPH_OFB_MODE + // EVP_CIPH_CCM_MODE + // EVP_CIPH_XTS_MODE + // EVP_CIPH_WRAP_MODE + // EVP_CIPH_OCB_MODE + // EVP_CIPH_SIV_MODE + // EVP_CIPH_STREAM_CIPHER + util::exception_location().raise("unsupported cipher mode"); + } + return evp_cipher; + } + + [[nodiscard]] static std::size_t + get_block_size_in_bytes_internal(const EVP_CIPHER *cipher) { + assert(cipher != nullptr); + return static_cast(EVP_CIPHER_get_block_size(cipher)); + } + [[nodiscard]] static std::size_t + get_key_size_in_bytes_internal(const EVP_CIPHER *cipher) { + assert(cipher != nullptr); + return static_cast(EVP_CIPHER_get_key_length(cipher)); + } + [[nodiscard]] static std::size_t + get_iv_size_in_bytes_internal(const EVP_CIPHER *cipher) { + assert(cipher != nullptr); + return static_cast(EVP_CIPHER_get_iv_length(cipher)); + } +}; + +void cipher_context::impl_deleter::operator()(void *cipher_ctx) const noexcept { + if (cipher_ctx != nullptr) { + EVP_CIPHER_CTX_free(static_cast(cipher_ctx)); + } +} + +cipher_context::cipher_context(cipher_context_mode_type mode, + const std::string &cipher_name, + util::const_byte_span key, + util::const_byte_span ivec, + util::const_byte_span tag) + : impl_{EVP_CIPHER_CTX_new()} { + if (!impl_) { + util::exception_location().raise( + "cannot create cipher context"); + } + const auto *evp_cipher{native_helper::get_native_cipher_by_name(cipher_name)}; + if (std::size(key) != + native_helper::get_key_size_in_bytes_internal(evp_cipher)) { + util::exception_location().raise( + "invalid key size for the specified cipher"); + } + if (std::size(ivec) != + native_helper::get_iv_size_in_bytes_internal(evp_cipher)) { + util::exception_location().raise( + "invalid iv size for the specified cipher"); + } + if (mode == cipher_context_mode_type::encryption) { + if (!tag.empty()) { + util::exception_location().raise( + "tag must not be specified for encryption cipher context"); + } + } + + if (EVP_CipherInit_ex( + native_helper::deimpl(impl_), // context + evp_cipher, // cipher + nullptr, // engine + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(std::data(key)), // key + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(std::data(ivec)), // iv + (mode == cipher_context_mode_type::encryption ? 1 : 0) // enc + ) == 0) { + util::exception_location().raise( + "cannot initialize cipher context"); + } + + if (EVP_CIPHER_CTX_set_padding(native_helper::deimpl(impl_), 0) == 0) { + util::exception_location().raise( + "cannot disable padding for cipher context"); + } + + if (mode == cipher_context_mode_type::decryption) { + if (get_tag_size_in_bytes() != std::size(tag)) { + util::exception_location().raise( + "invalid tag size for the specified cipher"); + } + void *tag_ptr{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + const_cast(static_cast(std::data(tag)))}; + if (EVP_CIPHER_CTX_ctrl(native_helper::deimpl(impl_), // context + EVP_CTRL_GCM_SET_TAG, // type + static_cast(std::size(tag)), // length + tag_ptr // tag + ) == 0) { + util::exception_location().raise( + "cannot set tag for cipher context"); + } + } +} + +[[nodiscard]] cipher_context_mode_type +cipher_context::get_mode() const noexcept { + assert(!is_empty()); + return (EVP_CIPHER_CTX_encrypting(native_helper::deimpl(impl_)) + ? cipher_context_mode_type::encryption + : cipher_context_mode_type::decryption); +} + +[[nodiscard]] std::size_t +cipher_context::get_block_size_in_bytes() const noexcept { + assert(!is_empty()); + return static_cast( + EVP_CIPHER_CTX_get_block_size(native_helper::deimpl(impl_))); +} + +[[nodiscard]] std::size_t +cipher_context::get_key_size_in_bytes() const noexcept { + assert(!is_empty()); + return static_cast( + EVP_CIPHER_CTX_get_key_length(native_helper::deimpl(impl_))); +} + +[[nodiscard]] std::size_t +cipher_context::get_iv_size_in_bytes() const noexcept { + assert(!is_empty()); + return static_cast( + EVP_CIPHER_CTX_get_iv_length(native_helper::deimpl(impl_))); +} + +[[nodiscard]] std::size_t +cipher_context::get_tag_size_in_bytes() const noexcept { + assert(!is_empty()); + return static_cast( + EVP_CIPHER_CTX_get_tag_length(native_helper::deimpl(impl_))); +} + +[[nodiscard]] std::size_t +cipher_context::get_block_size_in_bytes(const std::string &cipher_name) { + return native_helper::get_block_size_in_bytes_internal( + native_helper::get_native_cipher_by_name(cipher_name)); +} + +[[nodiscard]] std::size_t +cipher_context::get_key_size_in_bytes(const std::string &cipher_name) { + return native_helper::get_key_size_in_bytes_internal( + native_helper::get_native_cipher_by_name(cipher_name)); +} + +[[nodiscard]] std::size_t +cipher_context::get_iv_size_in_bytes(const std::string &cipher_name) { + return native_helper::get_iv_size_in_bytes_internal( + native_helper::get_native_cipher_by_name(cipher_name)); +} + +void cipher_context::update(util::const_byte_span input, + util::byte_span output) { + assert(!is_empty()); + + if (std::size(input) % get_block_size_in_bytes() != 0U) { + util::exception_location().raise( + "in cipher context update input size is not a multiple of the block " + "size"); + } + + // in all modes supported by us ('XXX-ECB' with padding disabled, 'XXX-CBC' + // with padding disabled, 'XXX-CTR', and'XXX-GCM') the output length needs to + // be of the same size as the input + if (std::size(output) != std::size(input)) { + util::exception_location().raise( + "in cipher context update the output size does not match the input " + "size"); + } + + if (!std::in_range(std::size(input))) { + util::exception_location().raise( + "in cipher context update input size is out of range"); + } + const auto input_length_native{static_cast(std::size(input))}; + int output_length_native{0}; + if (EVP_CipherUpdate( + native_helper::deimpl(impl_), // context + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(std::data(output)), // output + &output_length_native, // output length + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(std::data(input)), // input + input_length_native // input length + ) == 0) { + util::exception_location().raise( + "cannot update cipher context"); + } + if (!std::in_range(output_length_native)) { + util::exception_location().raise( + "in cipher context update output size is out of range"); + } + const auto output_length{static_cast(output_length_native)}; + if (output_length != std::size(output)) { + util::exception_location().raise( + "in cipher context update the actual output size does not match the " + "expected output size"); + } +} + +void cipher_context::finalize(util::byte_span output_tag) { + assert(!is_empty()); + + const auto mode{get_mode()}; + if (mode == cipher_context_mode_type::decryption) { + if (!output_tag.empty()) { + util::exception_location().raise( + "in cipher context finalize the output tag must only be specified " + "for the encryption mode"); + } + } else { + // cipher_context_mode_type::encryption mode + if (std::size(output_tag) != get_tag_size_in_bytes()) { + util::exception_location().raise( + "in cipher context finalize the output tag size does not match the " + "expected tag size"); + } + } + + using fake_buffer_type = std::array; + fake_buffer_type fake_buffer; + int output_length_native{0}; + if (EVP_CipherFinal_ex( + native_helper::deimpl(impl_), + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(std::data(fake_buffer)), + &output_length_native) == 0) { + util::exception_location().raise( + "cannot finalize cipher context"); + } + if (!std::in_range(output_length_native)) { + util::exception_location().raise( + "in cipher context finalize output size is out of range"); + } + const auto output_length{static_cast(output_length_native)}; + if (output_length != 0U) { + util::exception_location().raise( + "in cipher context finalize the actual output size is not zero"); + } + + if (mode == cipher_context_mode_type::encryption) { + const auto tag_length_native{static_cast(std::size(output_tag))}; + void *const tag_ptr{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + const_cast(static_cast(std::data(output_tag)))}; + if (EVP_CIPHER_CTX_ctrl(native_helper::deimpl(impl_), // context + EVP_CTRL_GCM_GET_TAG, // type + tag_length_native, // length + tag_ptr // tag + ) == 0) { + util::exception_location().raise( + "cannot get tag from cipher context"); + } + } + impl_.reset(); +} + +} // namespace opensslpp diff --git a/src/opensslpp/cipher_context.hpp b/src/opensslpp/cipher_context.hpp new file mode 100644 index 0000000..1ad4645 --- /dev/null +++ b/src/opensslpp/cipher_context.hpp @@ -0,0 +1,97 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPENSSLPP_CIPHER_CONTEXT_HPP +#define OPENSSLPP_CIPHER_CONTEXT_HPP + +#include "opensslpp/cipher_context_fwd.hpp" // IWYU pragma: export + +#include +#include + +#include "util/byte_span_fwd.hpp" + +namespace opensslpp { + +class cipher_context { +public: + cipher_context() noexcept = default; + // * 'mode' must be either cipher_context_mode_type::encryption or + // cipher_context_mode_type::decryption + // * 'cipher_name' must be a valid cipher name supported by OpenSSL + // (currently only 'XXX-ECB', 'XXX-CBC', 'XXX-CTR', and'XXX-GCM') + // * 'key' must be of proper length for the given cipher (see + // get_key_size_in_bytes()) + // * 'ivec' must be of proper length for the given cipher (see + // get_iv_size_in_bytes()) + // ('ivec' is not needed and can be empty for 'XXX-ECB' ciphers) + // * 'tag' is only used for 'XXX-GCM' ciphers and must be of proper length + // for the given cipher (see get_tag_size_in_bytes()) + // + // For 'XXX-ECB' and 'XXX-CBC' we deliberately disable padding as it cannot be + // considered as a reliable way of restoring original data of lengths that are + // not multiples of the cipher's block size. In these modes the 'update()' + // method expects the input to be of length that is a multiple of the cipher's + // block size. + cipher_context( + // no std::string_view for 'cipher' as it needs to be null-terminated + cipher_context_mode_type mode, const std::string &cipher_name, + util::const_byte_span key, util::const_byte_span ivec = {}, + util::const_byte_span tag = {}); + ~cipher_context() noexcept = default; + + cipher_context(const cipher_context &obj) = delete; + cipher_context(cipher_context &&obj) noexcept = default; + + cipher_context &operator=(const cipher_context &obj) = delete; + cipher_context &operator=(cipher_context &&obj) noexcept = default; + + void swap(cipher_context &obj) noexcept { impl_.swap(obj.impl_); } + + [[nodiscard]] bool is_empty() const noexcept { return !impl_; } + + [[nodiscard]] cipher_context_mode_type get_mode() const noexcept; + + [[nodiscard]] std::size_t get_block_size_in_bytes() const noexcept; + [[nodiscard]] std::size_t get_key_size_in_bytes() const noexcept; + [[nodiscard]] std::size_t get_iv_size_in_bytes() const noexcept; + [[nodiscard]] std::size_t get_tag_size_in_bytes() const noexcept; + + [[nodiscard]] static std::size_t + get_block_size_in_bytes(const std::string &cipher_name); + [[nodiscard]] static std::size_t + get_key_size_in_bytes(const std::string &cipher_name); + [[nodiscard]] static std::size_t + get_iv_size_in_bytes(const std::string &cipher_name); + // there is no static version of get_tag_size_in_bytes() as tag size is a + // dynamic property of the cipher context + + // TODO: implement void update_inplace(util::byte_span inoutput) + void update(util::const_byte_span input, util::byte_span output); + void finalize(util::byte_span output_tag = {}); + +private: + struct native_helper; + struct impl_deleter { + void operator()(void *cipher_ctx) const noexcept; + }; + + using impl_ptr = std::unique_ptr; + impl_ptr impl_; +}; + +} // namespace opensslpp + +#endif // OPENSSLPP_CIPHER_CONTEXT_HPP diff --git a/src/opensslpp/cipher_context_fwd.hpp b/src/opensslpp/cipher_context_fwd.hpp new file mode 100644 index 0000000..99700ee --- /dev/null +++ b/src/opensslpp/cipher_context_fwd.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPENSSLPP_CIPHER_CONTEXT_FWD_HPP +#define OPENSSLPP_CIPHER_CONTEXT_FWD_HPP + +#include + +namespace opensslpp { + +enum class cipher_context_mode_type : std::uint8_t { encryption, decryption }; + +class cipher_context; + +} // namespace opensslpp + +#endif // OPENSSLPP_CIPHER_CONTEXT_FWD_HPP diff --git a/src/opensslpp/core_error.cpp b/src/opensslpp/core_error.cpp new file mode 100644 index 0000000..b79eb96 --- /dev/null +++ b/src/opensslpp/core_error.cpp @@ -0,0 +1,67 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "opensslpp/core_error.hpp" + +#include +#include +#include +#include + +#include + +namespace opensslpp { + +[[nodiscard]] std::string +core_error::generate_error_message(std::string_view prefix) { + static constexpr std::string_view prefix_separator{": "}; + static constexpr std::string_view lib_reason_separator{"::"}; + + const auto native_err{ERR_get_error()}; + if (native_err == 0L) { + return (prefix.empty() ? std::string{} : std::string{prefix}); + } + + // ERR_get_error() pops an element from the error queue, so we need to ensure + // that it is empty after the first call. + assert(ERR_get_error() == 0L); + + const char *const lib_error_string{ERR_lib_error_string(native_err)}; + const std::string_view lib_error_string_sv{ + lib_error_string != nullptr ? lib_error_string : ""}; + + const char *reason_error_string{ERR_reason_error_string(native_err)}; + const std::string_view reason_error_string_sv{reason_error_string != nullptr + ? reason_error_string + : ""}; + + const std::size_t max_message_length{ + (prefix.empty() ? 0U : prefix.size() + prefix_separator.size()) + + std::size(lib_error_string_sv) + std::size(lib_reason_separator) + + std::size(reason_error_string_sv)}; + std::string message{}; + message.reserve(max_message_length); + if (!prefix.empty()) { + message += prefix; + message += prefix_separator; + } + message += lib_error_string_sv; + message += lib_reason_separator; + message += reason_error_string_sv; + + return message; +} + +} // namespace opensslpp diff --git a/src/opensslpp/core_error.hpp b/src/opensslpp/core_error.hpp new file mode 100644 index 0000000..db5d5ee --- /dev/null +++ b/src/opensslpp/core_error.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPENSSLPP_CORE_ERROR_HPP +#define OPENSSLPP_CORE_ERROR_HPP + +#include "opensslpp/core_error_fwd.hpp" // IWYU pragma: export + +#include +#include +#include + +namespace opensslpp { + +class core_error : public std::runtime_error { +public: + explicit core_error(std::string_view prefix = {}) + : std::runtime_error{generate_error_message(prefix)} {} + +private: + [[nodiscard]] static std::string + generate_error_message(std::string_view prefix); +}; + +} // namespace opensslpp + +#endif // OPENSSLPP_CORE_ERROR_HPP diff --git a/src/opensslpp/core_error_fwd.hpp b/src/opensslpp/core_error_fwd.hpp new file mode 100644 index 0000000..f54c5b9 --- /dev/null +++ b/src/opensslpp/core_error_fwd.hpp @@ -0,0 +1,25 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPENSSLPP_CORE_ERROR_FWD_HPP +#define OPENSSLPP_CORE_ERROR_FWD_HPP + +namespace opensslpp { + +class core_error; + +} // namespace opensslpp + +#endif // OPENSSLPP_CORE_ERROR_FWD_HPP diff --git a/src/opensslpp/crypto_rng.cpp b/src/opensslpp/crypto_rng.cpp new file mode 100644 index 0000000..271530f --- /dev/null +++ b/src/opensslpp/crypto_rng.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "opensslpp/crypto_rng.hpp" + +#include +#include + +#include + +#include "opensslpp/core_error.hpp" + +#include "util/byte_span_fwd.hpp" +#include "util/exception_location_helpers.hpp" + +namespace opensslpp { + +void crypto_rng::generate(util::byte_span output) { + if (output.empty()) { + return; + } + + if (!std::in_range(std::size(output))) { + util::exception_location().raise( + "crypto rng output size is out of range"); + } + const auto native_output_size{static_cast(std::size(output))}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + if (RAND_bytes(reinterpret_cast(std::data(output)), + native_output_size) != 1) { + util::exception_location().raise( + "cannot generate random bytes using crypto rng"); + } +} + +} // namespace opensslpp diff --git a/src/opensslpp/crypto_rng.hpp b/src/opensslpp/crypto_rng.hpp new file mode 100644 index 0000000..0c12d7f --- /dev/null +++ b/src/opensslpp/crypto_rng.hpp @@ -0,0 +1,30 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef OPENSSLPP_CRYPTO_RNG_HPP +#define OPENSSLPP_CRYPTO_RNG_HPP + +#include "util/byte_span_fwd.hpp" + +namespace opensslpp { + +class crypto_rng { +public: + static void generate(util::byte_span output); +}; + +} // namespace opensslpp + +#endif // OPENSSLPP_CRYPTO_RNG_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4e014e7..10b3ca6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -95,6 +95,32 @@ set_target_properties(event_test PROPERTIES CXX_EXTENSIONS NO ) +add_executable(cipher_context_test cipher_context_test.cpp) +target_include_directories(cipher_context_test PRIVATE "${PROJECT_SOURCE_DIR}/src") +target_link_libraries(cipher_context_test + PRIVATE + binlog_server_compiler_flags + binsrv::lib_opensslpp + Boost::unit_test_framework +) +set_target_properties(cipher_context_test PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) + +add_executable(crypto_rnd_test crypto_rnd_test.cpp) +target_include_directories(crypto_rnd_test PRIVATE "${PROJECT_SOURCE_DIR}/src") +target_link_libraries(crypto_rnd_test + PRIVATE + binlog_server_compiler_flags + binsrv::lib_opensslpp + Boost::unit_test_framework +) +set_target_properties(crypto_rnd_test PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) + set(test_run_options --no_color_output) add_test(NAME byte_span_encoding_test COMMAND byte_span_encoding_test ${test_run_options}) @@ -103,3 +129,5 @@ add_test(NAME tag_test COMMAND tag_test ${test_run_options}) add_test(NAME gtid_test COMMAND gtid_test ${test_run_options}) add_test(NAME gtid_set_test COMMAND gtid_set_test ${test_run_options}) add_test(NAME event_test COMMAND event_test ${test_run_options}) +add_test(NAME cipher_context_test COMMAND cipher_context_test ${test_run_options}) +add_test(NAME crypto_rnd_test COMMAND crypto_rnd_test ${test_run_options}) diff --git a/tests/cipher_context_test.cpp b/tests/cipher_context_test.cpp new file mode 100644 index 0000000..bc18d84 --- /dev/null +++ b/tests/cipher_context_test.cpp @@ -0,0 +1,624 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include +#include +#include +#include +#include + +#define BOOST_TEST_MODULE CipherContextTests +// this include is needed as it provides the 'main()' function +// NOLINTNEXTLINE(misc-include-cleaner) +#include + +#include + +#include + +#include + +#include + +#include "opensslpp/cipher_context.hpp" +#include "opensslpp/core_error.hpp" +#include "opensslpp/crypto_rng.hpp" + +#include "util/byte_span.hpp" + +using buffer_type = std::vector; +static const char *const invalid_cipher_name{"INVALID-CIPHER-NAME"}; + +BOOST_AUTO_TEST_CASE(CipherContextDefaultConstruction) { + const opensslpp::cipher_context empty_ctx{}; + BOOST_CHECK(empty_ctx.is_empty()); +} + +BOOST_AUTO_TEST_CASE(CipherContextValidCipherNameConstruction) { + const std::string cipher_name{"AES-256-CBC"}; + buffer_type key{ + opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)}; + buffer_type ivec{ + opensslpp::cipher_context::get_iv_size_in_bytes(cipher_name)}; + opensslpp::crypto_rng::generate(key); + opensslpp::crypto_rng::generate(ivec); + + const opensslpp::cipher_context empty_ctx( + opensslpp::cipher_context_mode_type::encryption, cipher_name, key, ivec); + BOOST_CHECK(!empty_ctx.is_empty()); +} + +BOOST_AUTO_TEST_CASE(CipherContextInvalidCipherNameConstruction) { + static constexpr std::size_t default_key_size{16U}; + static constexpr std::size_t default_ivec_size{16U}; + buffer_type key(default_key_size); + buffer_type ivec(default_ivec_size); + opensslpp::crypto_rng::generate(key); + opensslpp::crypto_rng::generate(ivec); + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + invalid_cipher_name, key, ivec), + opensslpp::core_error); +} + +BOOST_AUTO_TEST_CASE(CipherContextUnsupportedCipherModeConstruction) { + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + "AES-128-CFB", util::const_byte_span{}, + util::const_byte_span{}), + opensslpp::core_error); +} + +static const std::initializer_list modes{"ECB", "CBC", "CTR", + "GCM"}; +static const std::initializer_list bit_lengths{128U, 192U, 256U}; + +static constexpr std::size_t zeroing_modifier{ + std::numeric_limits::max()}; +static const std::initializer_list size_modifiers{ + 0U, 1U, 16U, zeroing_modifier}; + +BOOST_DATA_TEST_CASE(CipherContextInvalidKeyLengthIVLengthConstruction, + boost::unit_test::data::make(modes) * + boost::unit_test::data::make(bit_lengths) * + boost::unit_test::data::make(size_modifiers) * + boost::unit_test::data::make(size_modifiers), + mode, bit_length, key_size_modifier, ivec_size_modifier) { + const std::string cipher_name{"AES-" + std::to_string(bit_length) + "-" + + mode}; + + const auto valid_key_size{ + opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)}; + const auto valid_ivec_size{ + opensslpp::cipher_context::get_iv_size_in_bytes(cipher_name)}; + + const auto length_adjuster{ + [](std::size_t length, std::size_t modifier) -> std::size_t { + if (modifier == zeroing_modifier) { + return 0U; + } + return length + modifier; + }}; + + const auto adjusted_key_size{ + length_adjuster(valid_key_size, key_size_modifier)}; + const auto adjusted_ivec_size{ + length_adjuster(valid_ivec_size, ivec_size_modifier)}; + + if (adjusted_key_size == valid_key_size && + adjusted_ivec_size == valid_ivec_size) { + // this combination is valid, so we skip it + return; + } + + buffer_type key{adjusted_key_size}; + buffer_type ivec{adjusted_ivec_size}; + opensslpp::crypto_rng::generate(key); + opensslpp::crypto_rng::generate(ivec); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + cipher_name, key, ivec), + opensslpp::core_error); +} + +BOOST_AUTO_TEST_CASE(CipherContextGetBlockSizeStatic) { + [[maybe_unused]] std::size_t ivec_size{}; + BOOST_CHECK_THROW(ivec_size = opensslpp::cipher_context::get_iv_size_in_bytes( + invalid_cipher_name), + opensslpp::core_error); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-128-ECB") == 16U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-192-ECB") == 16U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-256-ECB") == 16U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-128-CBC") == 16U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-192-CBC") == 16U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-256-CBC") == 16U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-128-CTR") == 1U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-192-CTR") == 1U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-256-CTR") == 1U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-128-GCM") == 1U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-192-GCM") == 1U); + BOOST_CHECK( + opensslpp::cipher_context::get_block_size_in_bytes("AES-256-GCM") == 1U); +} + +BOOST_AUTO_TEST_CASE(CipherContextGetKeySizeStatic) { + [[maybe_unused]] std::size_t key_size{}; + BOOST_CHECK_THROW(key_size = opensslpp::cipher_context::get_key_size_in_bytes( + invalid_cipher_name), + opensslpp::core_error); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-128-CBC") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-192-CBC") == + 24U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-256-CBC") == + 32U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-128-CTR") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-192-CTR") == + 24U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-256-CTR") == + 32U); +} + +BOOST_AUTO_TEST_CASE(CipherContextGetIVSizeStatic) { + [[maybe_unused]] std::size_t ivec_size{}; + BOOST_CHECK_THROW(ivec_size = opensslpp::cipher_context::get_iv_size_in_bytes( + invalid_cipher_name), + opensslpp::core_error); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-128-ECB") == + 0U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-192-ECB") == + 0U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-256-ECB") == + 0U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-128-CBC") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-192-CBC") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-256-CBC") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-128-CTR") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-192-CTR") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-256-CTR") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-128-GCM") == + 12U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-192-GCM") == + 12U); + BOOST_CHECK(opensslpp::cipher_context::get_iv_size_in_bytes("AES-256-GCM") == + 12U); +} + +class cipher_context_fixture { +protected: + auto create_encryption_context(opensslpp::cipher_context_mode_type mode, + const std::string &cipher_name) { + key_.resize(opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)); + ivec_.resize(opensslpp::cipher_context::get_iv_size_in_bytes(cipher_name)); + opensslpp::crypto_rng::generate(key_); + opensslpp::crypto_rng::generate(ivec_); + return opensslpp::cipher_context(mode, cipher_name, key_, ivec_); + } + auto create_encryption_context(const std::string &cipher_name) { + return create_encryption_context( + opensslpp::cipher_context_mode_type::encryption, cipher_name); + } + auto create_decryption_context(const std::string &cipher_name) { + return create_encryption_context( + opensslpp::cipher_context_mode_type::decryption, cipher_name); + } + +private: + buffer_type key_; + buffer_type ivec_; +}; + +BOOST_FIXTURE_TEST_CASE(CipherContextGetMode, cipher_context_fixture) { + auto encryption_context{create_encryption_context("AES-128-ECB")}; + BOOST_CHECK(encryption_context.get_mode() == + opensslpp::cipher_context_mode_type::encryption); + auto decryption_context{create_decryption_context("AES-128-ECB")}; + BOOST_CHECK(decryption_context.get_mode() == + opensslpp::cipher_context_mode_type::decryption); +} + +BOOST_FIXTURE_TEST_CASE(CipherContextGetBlockSize, cipher_context_fixture) { + BOOST_CHECK( + create_encryption_context("AES-128-ECB").get_block_size_in_bytes() == + 16U); + BOOST_CHECK( + create_encryption_context("AES-192-ECB").get_block_size_in_bytes() == + 16U); + BOOST_CHECK( + create_encryption_context("AES-256-ECB").get_block_size_in_bytes() == + 16U); + BOOST_CHECK( + create_encryption_context("AES-128-CBC").get_block_size_in_bytes() == + 16U); + BOOST_CHECK( + create_encryption_context("AES-192-CBC").get_block_size_in_bytes() == + 16U); + BOOST_CHECK( + create_encryption_context("AES-256-CBC").get_block_size_in_bytes() == + 16U); + BOOST_CHECK( + create_encryption_context("AES-128-CTR").get_block_size_in_bytes() == 1U); + BOOST_CHECK( + create_encryption_context("AES-192-CTR").get_block_size_in_bytes() == 1U); + BOOST_CHECK( + create_encryption_context("AES-256-CTR").get_block_size_in_bytes() == 1U); + BOOST_CHECK( + create_encryption_context("AES-128-GCM").get_block_size_in_bytes() == 1U); + BOOST_CHECK( + create_encryption_context("AES-192-GCM").get_block_size_in_bytes() == 1U); + BOOST_CHECK( + create_encryption_context("AES-256-GCM").get_block_size_in_bytes() == 1U); +} + +BOOST_FIXTURE_TEST_CASE(CipherContextGetKeySize, cipher_context_fixture) { + BOOST_CHECK( + create_encryption_context("AES-128-CBC").get_key_size_in_bytes() == 16U); + BOOST_CHECK( + create_encryption_context("AES-192-CBC").get_key_size_in_bytes() == 24U); + BOOST_CHECK( + create_encryption_context("AES-256-CBC").get_key_size_in_bytes() == 32U); + BOOST_CHECK( + create_encryption_context("AES-128-CTR").get_key_size_in_bytes() == 16U); + BOOST_CHECK( + create_encryption_context("AES-192-CTR").get_key_size_in_bytes() == 24U); + BOOST_CHECK( + create_encryption_context("AES-256-CTR").get_key_size_in_bytes() == 32U); +} + +BOOST_FIXTURE_TEST_CASE(CipherContextGetIVSize, cipher_context_fixture) { + BOOST_CHECK(create_encryption_context("AES-128-ECB").get_iv_size_in_bytes() == + 0U); + BOOST_CHECK(create_encryption_context("AES-192-ECB").get_iv_size_in_bytes() == + 0U); + BOOST_CHECK(create_encryption_context("AES-256-ECB").get_iv_size_in_bytes() == + 0U); + BOOST_CHECK(create_encryption_context("AES-128-CBC").get_iv_size_in_bytes() == + 16U); + BOOST_CHECK(create_encryption_context("AES-192-CBC").get_iv_size_in_bytes() == + 16U); + BOOST_CHECK(create_encryption_context("AES-256-CBC").get_iv_size_in_bytes() == + 16U); + BOOST_CHECK(create_encryption_context("AES-128-CTR").get_iv_size_in_bytes() == + 16U); + BOOST_CHECK(create_encryption_context("AES-192-CTR").get_iv_size_in_bytes() == + 16U); + BOOST_CHECK(create_encryption_context("AES-256-CTR").get_iv_size_in_bytes() == + 16U); + BOOST_CHECK(create_encryption_context("AES-128-GCM").get_iv_size_in_bytes() == + 12U); + BOOST_CHECK(create_encryption_context("AES-192-GCM").get_iv_size_in_bytes() == + 12U); + BOOST_CHECK(create_encryption_context("AES-256-GCM").get_iv_size_in_bytes() == + 12U); +} + +static const std::initializer_list stream_message_sizes{ + 0U, 1U, 8U, 15U, 16U, 17U, 24U, 31U, 32U, 33U}; +static const std::initializer_list block_message_sizes{0U, 16U, + 32U}; + +// Checking ECB ciphers +// (iv not required, tag not required, block_size != 1) +BOOST_DATA_TEST_CASE(CipherContextRoundtripECB, + boost::unit_test::data::make(bit_lengths) * + boost::unit_test::data::make(block_message_sizes), + bit_length, message_size) { + const std::string cipher_name{"AES-" + std::to_string(bit_length) + "-ECB"}; + + const std::size_t valid_key_size{ + opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)}; + const std::size_t fake_ivec_size{16U}; // ECB mode does not use an IV + static constexpr std::size_t fake_tag_length{ + 16U}; // ECB mode does not use a tag + + buffer_type key{valid_key_size}; + buffer_type fake_ivec{fake_ivec_size}; + buffer_type fake_tag{fake_tag_length}; + opensslpp::crypto_rng::generate(key); + + buffer_type message{message_size}; + buffer_type encrypted_message{message_size}; + buffer_type restored_message{message_size}; + opensslpp::crypto_rng::generate(message); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + cipher_name, key, fake_ivec, fake_tag), + opensslpp::core_error); + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + cipher_name, key, fake_ivec), + opensslpp::core_error); + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + cipher_name, key, {}, fake_tag), + opensslpp::core_error); + opensslpp::cipher_context encryption_context( + opensslpp::cipher_context_mode_type::encryption, cipher_name, key); + BOOST_CHECK(encryption_context.get_tag_size_in_bytes() == 0U); + BOOST_CHECK(encryption_context.get_block_size_in_bytes() != 1U); + message.resize(message_size + 1U); + BOOST_CHECK_THROW(encryption_context.update(message, encrypted_message), + opensslpp::core_error); + message.resize(message_size); + encrypted_message.resize(message_size + 1U); + BOOST_CHECK_THROW(encryption_context.update(message, encrypted_message), + opensslpp::core_error); + encrypted_message.resize(message_size); + encryption_context.update(message, encrypted_message); + BOOST_CHECK_THROW(encryption_context.finalize(fake_tag), + opensslpp::core_error); + encryption_context.finalize(); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::decryption, + cipher_name, key, fake_ivec, fake_tag), + opensslpp::core_error); + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::decryption, + cipher_name, key, fake_ivec), + opensslpp::core_error); + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::decryption, + cipher_name, key, {}, fake_tag), + opensslpp::core_error); + opensslpp::cipher_context decryption_context( + opensslpp::cipher_context_mode_type::decryption, cipher_name, key); + BOOST_CHECK(decryption_context.get_tag_size_in_bytes() == 0U); + BOOST_CHECK(decryption_context.get_block_size_in_bytes() != 1U); + encrypted_message.resize(message_size + 1U); + BOOST_CHECK_THROW( + decryption_context.update(encrypted_message, restored_message), + opensslpp::core_error); + encrypted_message.resize(message_size); + restored_message.resize(message_size + 1U); + BOOST_CHECK_THROW( + decryption_context.update(encrypted_message, restored_message), + opensslpp::core_error); + restored_message.resize(message_size); + decryption_context.update(encrypted_message, restored_message); + BOOST_CHECK_THROW(decryption_context.finalize(fake_tag), + opensslpp::core_error); + decryption_context.finalize(); + + BOOST_CHECK(message == restored_message); +} + +// Checking CBC ciphers +// (iv required, tag not required, block_size != 1) +BOOST_DATA_TEST_CASE(CipherContextRoundtripCBC, + boost::unit_test::data::make(bit_lengths) * + boost::unit_test::data::make(block_message_sizes), + bit_length, message_size) { + const std::string cipher_name{"AES-" + std::to_string(bit_length) + "-CBC"}; + + const std::size_t valid_key_size{ + opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)}; + const std::size_t valid_ivec_size{ + opensslpp::cipher_context::get_iv_size_in_bytes(cipher_name)}; + static constexpr std::size_t fake_tag_length{ + 16U}; // CBC mode does not use a tag + + buffer_type key{valid_key_size}; + buffer_type ivec{valid_ivec_size}; + buffer_type fake_tag{fake_tag_length}; + opensslpp::crypto_rng::generate(key); + opensslpp::crypto_rng::generate(ivec); + + buffer_type message{message_size}; + buffer_type encrypted_message{message_size}; + buffer_type restored_message{message_size}; + opensslpp::crypto_rng::generate(message); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + cipher_name, key, ivec, fake_tag), + opensslpp::core_error); + opensslpp::cipher_context encryption_context( + opensslpp::cipher_context_mode_type::encryption, cipher_name, key, ivec); + BOOST_CHECK(encryption_context.get_tag_size_in_bytes() == 0U); + BOOST_CHECK(encryption_context.get_block_size_in_bytes() != 1U); + message.resize(message_size + 1U); + BOOST_CHECK_THROW(encryption_context.update(message, encrypted_message), + opensslpp::core_error); + message.resize(message_size); + encrypted_message.resize(message_size + 1U); + BOOST_CHECK_THROW(encryption_context.update(message, encrypted_message), + opensslpp::core_error); + encrypted_message.resize(message_size); + encryption_context.update(message, encrypted_message); + BOOST_CHECK_THROW(encryption_context.finalize(fake_tag), + opensslpp::core_error); + encryption_context.finalize(); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::decryption, + cipher_name, key, ivec, fake_tag), + opensslpp::core_error); + opensslpp::cipher_context decryption_context( + opensslpp::cipher_context_mode_type::decryption, cipher_name, key, ivec); + BOOST_CHECK(decryption_context.get_tag_size_in_bytes() == 0U); + BOOST_CHECK(decryption_context.get_block_size_in_bytes() != 1U); + encrypted_message.resize(message_size + 1U); + BOOST_CHECK_THROW( + decryption_context.update(encrypted_message, restored_message), + opensslpp::core_error); + encrypted_message.resize(message_size); + restored_message.resize(message_size + 1U); + BOOST_CHECK_THROW( + decryption_context.update(encrypted_message, restored_message), + opensslpp::core_error); + restored_message.resize(message_size); + decryption_context.update(encrypted_message, restored_message); + BOOST_CHECK_THROW(decryption_context.finalize(fake_tag), + opensslpp::core_error); + decryption_context.finalize(); + + BOOST_CHECK(message == restored_message); +} + +// Checking CTR ciphers +// (iv required, tag not required, block_size == 1) +BOOST_DATA_TEST_CASE(CipherContextRoundtripCTR, + boost::unit_test::data::make(bit_lengths) * + boost::unit_test::data::make(stream_message_sizes), + bit_length, message_size) { + const std::string cipher_name{"AES-" + std::to_string(bit_length) + "-CTR"}; + + const std::size_t valid_key_size{ + opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)}; + const std::size_t valid_ivec_size{ + opensslpp::cipher_context::get_iv_size_in_bytes(cipher_name)}; + static constexpr std::size_t fake_tag_length{ + 16U}; // CTR mode does not use a tag + + buffer_type key{valid_key_size}; + buffer_type ivec{valid_ivec_size}; + buffer_type fake_tag{fake_tag_length}; + opensslpp::crypto_rng::generate(key); + opensslpp::crypto_rng::generate(ivec); + + buffer_type message{message_size}; + buffer_type encrypted_message{message_size}; + buffer_type restored_message{message_size}; + opensslpp::crypto_rng::generate(message); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + cipher_name, key, ivec, fake_tag), + opensslpp::core_error); + opensslpp::cipher_context encryption_context( + opensslpp::cipher_context_mode_type::encryption, cipher_name, key, ivec); + BOOST_CHECK(encryption_context.get_tag_size_in_bytes() == 0U); + BOOST_CHECK(encryption_context.get_block_size_in_bytes() == 1U); + encrypted_message.resize(message_size + 1U); + BOOST_CHECK_THROW(encryption_context.update(message, encrypted_message), + opensslpp::core_error); + encrypted_message.resize(message_size); + encryption_context.update(message, encrypted_message); + BOOST_CHECK_THROW(encryption_context.finalize(fake_tag), + opensslpp::core_error); + encryption_context.finalize(); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::decryption, + cipher_name, key, ivec, fake_tag), + opensslpp::core_error); + opensslpp::cipher_context decryption_context( + opensslpp::cipher_context_mode_type::decryption, cipher_name, key, ivec); + BOOST_CHECK(decryption_context.get_tag_size_in_bytes() == 0U); + BOOST_CHECK(decryption_context.get_block_size_in_bytes() == 1U); + restored_message.resize(message_size + 1U); + BOOST_CHECK_THROW( + decryption_context.update(encrypted_message, restored_message), + opensslpp::core_error); + restored_message.resize(message_size); + decryption_context.update(encrypted_message, restored_message); + BOOST_CHECK_THROW(decryption_context.finalize(fake_tag), + opensslpp::core_error); + decryption_context.finalize(); + + BOOST_CHECK(message == restored_message); +} + +// Checking GCM ciphers +// (iv required, tag required, block_size == 1) +BOOST_DATA_TEST_CASE(CipherContextRoundtripGCM, + boost::unit_test::data::make(bit_lengths) * + boost::unit_test::data::make(stream_message_sizes), + bit_length, message_size) { + const std::string cipher_name{"AES-" + std::to_string(bit_length) + "-GCM"}; + + const std::size_t valid_key_size{ + opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)}; + const std::size_t valid_ivec_size{ + opensslpp::cipher_context::get_iv_size_in_bytes(cipher_name)}; + buffer_type key{valid_key_size}; + buffer_type ivec{valid_ivec_size}; + buffer_type tag{}; + opensslpp::crypto_rng::generate(key); + opensslpp::crypto_rng::generate(ivec); + + buffer_type message{message_size}; + buffer_type encrypted_message{message_size}; + buffer_type restored_message{message_size}; + opensslpp::crypto_rng::generate(message); + + tag.resize(1U); + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::encryption, + cipher_name, key, ivec, tag), + opensslpp::core_error); + opensslpp::cipher_context encryption_context( + opensslpp::cipher_context_mode_type::encryption, cipher_name, key, ivec); + const std::size_t tag_length{encryption_context.get_tag_size_in_bytes()}; + BOOST_CHECK(tag_length != 0U); + BOOST_CHECK(encryption_context.get_block_size_in_bytes() == 1U); + encrypted_message.resize(message_size + 1U); + BOOST_CHECK_THROW(encryption_context.update(message, encrypted_message), + opensslpp::core_error); + encrypted_message.resize(message_size); + encryption_context.update(message, encrypted_message); + BOOST_CHECK_THROW(encryption_context.finalize(), opensslpp::core_error); + tag.resize(tag_length + 1U); + BOOST_CHECK_THROW(encryption_context.finalize(tag), opensslpp::core_error); + tag.resize(tag_length); + encryption_context.finalize(tag); + + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::decryption, + cipher_name, key, ivec), + opensslpp::core_error); + tag.resize(tag_length + 1U); + BOOST_CHECK_THROW( + opensslpp::cipher_context(opensslpp::cipher_context_mode_type::decryption, + cipher_name, key, ivec, tag), + opensslpp::core_error); + tag.resize(tag_length); + opensslpp::cipher_context decryption_context( + opensslpp::cipher_context_mode_type::decryption, cipher_name, key, ivec, + tag); + BOOST_CHECK(decryption_context.get_tag_size_in_bytes() == tag_length); + BOOST_CHECK(decryption_context.get_block_size_in_bytes() == 1U); + restored_message.resize(message_size + 1U); + BOOST_CHECK_THROW( + decryption_context.update(encrypted_message, restored_message), + opensslpp::core_error); + restored_message.resize(message_size); + decryption_context.update(encrypted_message, restored_message); + BOOST_CHECK_THROW(decryption_context.finalize(tag), opensslpp::core_error); + decryption_context.finalize(); + + BOOST_CHECK(message == restored_message); +} diff --git a/tests/crypto_rnd_test.cpp b/tests/crypto_rnd_test.cpp new file mode 100644 index 0000000..1fe5f34 --- /dev/null +++ b/tests/crypto_rnd_test.cpp @@ -0,0 +1,46 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#define BOOST_TEST_MODULE CryptoRndTests +// this include is needed as it provides the 'main()' function +// NOLINTNEXTLINE(misc-include-cleaner) +#include + +#include +#include + +#include + +#include + +#include "opensslpp/crypto_rng.hpp" + +BOOST_AUTO_TEST_CASE(CryptoRndGenerateEmptyBuffer) { + std::array output{}; + BOOST_CHECK_NO_THROW(opensslpp::crypto_rng::generate(output)); +} + +BOOST_AUTO_TEST_CASE(CryptoRndGenerateNonEmptyBuffer) { + constexpr std::size_t output_size{64U}; + + using buffer_type = std::array; + buffer_type first_output; + BOOST_CHECK_NO_THROW(opensslpp::crypto_rng::generate(first_output)); + + buffer_type second_output; + BOOST_CHECK_NO_THROW(opensslpp::crypto_rng::generate(second_output)); + + BOOST_CHECK(first_output != second_output); +}