From a7fe2e1bbed62824eb9039d285d6be6f7b5bba00 Mon Sep 17 00:00:00 2001 From: Yura Sorokin Date: Fri, 7 Aug 2026 17:50:04 +0200 Subject: [PATCH] PBS-39 feature: Add binlog encryption config and keyring support (part 7) https://perconadev.atlassian.net/browse/PBS-39 Added more diagnostics for keyring data, encryption configuration parameter and their combinations - We now check that 'cipher' parameter in the keyring record is known to OpenSSL and is of 'ECB', 'CBC', 'CRT', or 'GCM' mode. - We now check that the actual length of the 'data_hex' matches the one derived from the 'cipher' (i.g., 256 bit / 32 bytes / 64 hexadecimal characters for 'AES-256-CTR'). - We now check the uniqueness of the key IDs ('id' field) amongst keyring records. - We also make sure that random file keys (of length that corresponds to the '' configuration parameter) can be encrypted with the active KEK ('' configuration parameter). For instance, if active data cipher is 'AES-192-CRT' (key length 24 bytes), then the active KEK cannot be of 'ECB' or 'CBC' mode as these ciphers can only encrypt data of length that is a multiple of the block size (16 bytes). 'opensslpp::cipher_context' class extended with: - 'get_mode()' (both static and non-static versions) that returns the cipher mode ('ECB', 'CBC', 'CRT', or 'GCM'). - 'is_cipher_name_known()' (static) - returns true if this cipher name is known to OpenSSL. - 'is_mode_supported()' (static) - returns true if the mode is one of the 'ECB', 'CBC', 'CRT', or 'GCM'. - 'is_cipher_name_supported()' (static) - returns true if this cipher name is known to OpenSSL and its mode is supported. 'cipher_context_test.cpp' (BOOST_TEST_MODULE CipherContextTests) unit test extended with the following test cases. - 'CipherContextGetModeStatic' / 'CipherContextGetMode' that check for the behavior of the 'opensslpp::cipher_context::get_mode()' methods (both static and non-static). - 'CipherContextCTRResume' that checks for "resume streaming" operation for 'XXX-NNN-CTR' ciphers - creating a new encryption context, updating it with some data, finalizing, creating a new context with offset, updating it with more data, and finalizing. The result of this operation should be identical to simple encryption of the concatenated data blocks from the first and the second updates. --- CMakeLists.txt | 4 + README.md | 9 +- src/binsrv/encryption_config.cpp | 12 +- src/binsrv/keyring_record.cpp | 42 +++ src/binsrv/keyring_record.hpp | 1 + src/binsrv/keyring_record_collection.cpp | 19 +- src/binsrv/storage.cpp | 28 +- src/opensslpp/cipher_context.cpp | 220 ++++++++++++--- src/opensslpp/cipher_context.hpp | 28 +- src/opensslpp/cipher_context_fwd.hpp | 5 +- src/opensslpp/cipher_mode_type.hpp | 102 +++++++ src/opensslpp/cipher_mode_type_fwd.hpp | 45 +++ tests/cipher_context_test.cpp | 342 +++++++++++++++++------ 13 files changed, 689 insertions(+), 168 deletions(-) create mode 100644 src/binsrv/keyring_record.cpp create mode 100644 src/opensslpp/cipher_mode_type.hpp create mode 100644 src/opensslpp/cipher_mode_type_fwd.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bede84e..1a94f50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,9 @@ add_library(binsrv::lib_util ALIAS lib_util) # OpenSSL++ files set(opensslpp_source_files + src/opensslpp/cipher_mode_type_fwd.hpp + src/opensslpp/cipher_mode_type.hpp + src/opensslpp/cipher_context_fwd.hpp src/opensslpp/cipher_context.hpp src/opensslpp/cipher_context.cpp @@ -531,6 +534,7 @@ set(binsrv_source_files src/binsrv/keyring_record_fwd.hpp src/binsrv/keyring_record.hpp + src/binsrv/keyring_record.cpp src/binsrv/main_config_fwd.hpp src/binsrv/main_config.hpp diff --git a/README.md b/README.md index 374733e..3e54a81 100644 --- a/README.md +++ b/README.md @@ -647,7 +647,9 @@ If this section is present, then all the binlog data files will be encrypted bef - `` - specifies the encryption format (currently only `generic` is supported). - `` - specifies location of the keyring JSON data file (currently only 'file://' scheme is supported meaning that the file should be taken from the local file sytem from the path specified in this URI, e.g. `file:///var/lib/pbs/keyring/keyring_data.json`). - `` - specifies the ID of the key that must be used as a key-encryption-key (KEK). This ID must be present in the keyring. -- `` - specifies the data-encryption cipher name used binlog data file encryption (e.g. `AES-256-CTR`). +- `` - specifies the data-encryption cipher name used binlog data file encryption (e.g. `AES-256-CTR`). The cipher name specified here must be in `CTR` mode. + +Please also notice that not all combinations of the `cipher` and KEK identified by `kek_id` are supported. For instance, if the cipher from the keyring record identified by `kek_id` is either `AES-NNN-ECB` or `AES-NNN-CBC`, then they can encrypt only file keys with lengths that are a multiple of `16` bytes. In other words, in this case it is OK for `` to be `XXX-128-CTR` or `XXX-256-CTR`, but not OK to be `XXX-192-CTR`. ##### Keyring file format ```json @@ -669,11 +671,14 @@ If this section is present, then all the binlog data files will be encrypted bef ``` Keyring JSON file should represent a top-level JSON object with the following keys. - `version` - currently should always be equal to `1`. -- `keys` - should be an array of objects tith the following keys +- `keys` - should be an array of objects with the following keys - `id` - a unique string identifier of the key in the keyring. - `cipher` - the name of the symmetric cypher which should be used with this key (e.g `AES-256-GCM`). - `data_hex` - key bytes in hex format (typically `16`, `24`, or `32` bytes, meaning `32`, `48`, or `64` characters) +Make sure that the mode of the `cipher` is one of the `ECB`, `CBC`, `CTR`, or `GCM`. Also, make sure that the key size identified from the cipher name matches the actual `data_hex` length (for instance, for `AES-256-GCM`, the key length should be `256` bits, meaning `32` bytes, meaning `64` hexadecimal characters). +As for the `algoritm` part of the cipher name, PBS has been tested with `AES`, `AREA`, and `CAMELLIA`. However, other algorithms be supported as well. + ### Resuming previous operation Running the utility for the second time (in any mode) results in resuming streaming from the position at which the previous run finished. diff --git a/src/binsrv/encryption_config.cpp b/src/binsrv/encryption_config.cpp index bcf6972..afeaa77 100644 --- a/src/binsrv/encryption_config.cpp +++ b/src/binsrv/encryption_config.cpp @@ -21,6 +21,9 @@ #include "util/exception_location_helpers.hpp" +#include "opensslpp/cipher_context.hpp" +#include "opensslpp/cipher_mode_type.hpp" + namespace binsrv { void encryption_config::validate() const { @@ -28,8 +31,13 @@ void encryption_config::validate() const { util::exception_location().raise( "error validating storage encryption config: unsupported format"); } - // TODO: make sure that data encryption cipher is supported by OpenSSL - // and has CTR mode + + if (opensslpp::cipher_context::get_mode(get<"cipher">()) != + opensslpp::cipher_mode_type::ctr) { + util::exception_location().raise( + "error validating storage encryption config: only CTR mode is " + "supported for data encryption cipher"); + } } } // namespace binsrv diff --git a/src/binsrv/keyring_record.cpp b/src/binsrv/keyring_record.cpp new file mode 100644 index 0000000..09f0192 --- /dev/null +++ b/src/binsrv/keyring_record.cpp @@ -0,0 +1,42 @@ +// 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 "binsrv/keyring_record.hpp" + +#include +#include + +#include "opensslpp/cipher_context.hpp" +#include "util/exception_location_helpers.hpp" + +namespace binsrv { + +void keyring_record::validate() const { + const auto &cipher_name = get<"cipher">(); + + const auto &key_id = get<"id">(); + if (!opensslpp::cipher_context::is_cipher_name_supported(cipher_name)) { + util::exception_location().raise( + "unsupported cipher in keyring record: '" + key_id + "'"); + } + + if (get<"data_hex">().get_size() != + opensslpp::cipher_context::get_key_size_in_bytes(cipher_name)) { + util::exception_location().raise( + "key data length mismatch in keyring record '" + key_id + "'"); + } +} + +} // namespace binsrv diff --git a/src/binsrv/keyring_record.hpp b/src/binsrv/keyring_record.hpp index 281149c..6da1082 100644 --- a/src/binsrv/keyring_record.hpp +++ b/src/binsrv/keyring_record.hpp @@ -41,6 +41,7 @@ struct [[nodiscard]] keyring_record result += ')'; return result; } + void validate() const; }; } // namespace binsrv diff --git a/src/binsrv/keyring_record_collection.cpp b/src/binsrv/keyring_record_collection.cpp index c715662..5331a3b 100644 --- a/src/binsrv/keyring_record_collection.cpp +++ b/src/binsrv/keyring_record_collection.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -62,14 +63,20 @@ void keyring_record_collection::validate() const { util::exception_location().raise( "unsupported keyring record collection version"); } - // TODO: make sure that all the keys have unique IDs + const auto &keys{root().get<"keys">()}; + + using key_id_container = std::unordered_set; + key_id_container unique_ids{}; - // TODO: make sure that all keys have ciphers known in OpenSSL - // (currently only ECB, CBC, CTR, GCM modes are supported by - // opensslpp::cipher_context) + for (const auto &key : keys) { + const auto &key_id = key.get<"id">(); + if (!unique_ids.insert(key_id).second) { + util::exception_location().raise( + "duplicate key id in keyring record collection: '" + key_id + "'"); + } - // TODO: make sure that all keys have data_hex values of the correct length - // that matches with the cipher + key.validate(); + } } [[nodiscard]] std::string keyring_record_collection::get_description() const { diff --git a/src/binsrv/storage.cpp b/src/binsrv/storage.cpp index c8c0285..f29e1f1 100644 --- a/src/binsrv/storage.cpp +++ b/src/binsrv/storage.cpp @@ -146,14 +146,24 @@ storage::storage(const storage_config &config, util::exception_location().raise( "keyring does not contain the specified KEK ID"); } - // TODO: make sure that random file keys (of length that corresponds to the - // active data cipher) can be encrypted with the active KEK - - // for instance, if active data cipher is AES-192-CRT (key length 24 - // bytes), then the active KEK cannot be of ECB or CBC mode as these - // ciphers can only encrypt data of length that is a multiple of the - // block size (16 bytes) active_kek_id_ = kek_id; active_data_cipher_ = encryption_config->get<"cipher">(); + + // make sure that random file keys (of length that corresponds to the + // active data cipher) can be encrypted with the active KEK - + // for instance, if active data cipher is AES-192-CRT (key length 24 + // bytes), then the active KEK cannot be of ECB or CBC mode as these + // ciphers can only encrypt data of length that is a multiple of the + // block size (16 bytes) + const auto &keyring_record{keyring_->get_key(active_kek_id_)}; + if (opensslpp::cipher_context::get_key_size_in_bytes(active_data_cipher_) % + opensslpp::cipher_context::get_block_size_in_bytes( + keyring_record.get<"cipher">()) != + 0U) { + util::exception_location().raise( + "active data cipher key length is not compatible with the active " + "KEK cipher block size"); + } } backend_ = storage_backend_factory::create(config); @@ -896,7 +906,7 @@ storage::generate_binlog_encryption_record() const { // creating an encryption context with the KEK cipher, the KEK, and // the IV for file key encryption opensslpp::cipher_context file_key_encryption_context{ - opensslpp::cipher_context_mode_type::encryption, kek_cipher, kek, + opensslpp::cipher_context_operation_type::encryption, kek_cipher, kek, iv_for_file_key_encryption_v}; // identify the size of the file key encryption tag from the encryption @@ -970,7 +980,7 @@ void storage::write_data_to_stream( // creating a context for the file key decryption opensslpp::cipher_context file_key_decryption_context{ - opensslpp::cipher_context_mode_type::decryption, kek_cipher, kek, + opensslpp::cipher_context_operation_type::decryption, kek_cipher, kek, iv_for_file_key_encryption_v, tag_of_file_key_encryption_v}; util::hex_value_storage file_key_decrypted{ std::size(encryption_record->file_key_encrypted_with_kek)}; @@ -982,7 +992,7 @@ void storage::write_data_to_stream( // key (decrypted previously), and the IV for data encryption auto data_encryption_context{opensslpp::cipher_context::create_with_offset( - offset, opensslpp::cipher_context_mode_type::encryption, + offset, opensslpp::cipher_context_operation_type::encryption, encryption_record->data_cipher, file_key_decrypted, encryption_record->iv_for_data_encryption)}; diff --git a/src/opensslpp/cipher_context.cpp b/src/opensslpp/cipher_context.cpp index c50b4aa..e5e5780 100644 --- a/src/opensslpp/cipher_context.cpp +++ b/src/opensslpp/cipher_context.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -33,6 +35,7 @@ #include #include +#include "opensslpp/cipher_mode_type.hpp" #include "opensslpp/core_error.hpp" #include "util/byte_span_fwd.hpp" @@ -49,44 +52,60 @@ struct cipher_context::native_helper { } [[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) { + get_cipher_by_name_internal(const std::string &cipher_name) noexcept { + return EVP_get_cipherbyname(cipher_name.c_str()); + } + [[nodiscard]] static cipher_mode_type + convert_mode_internal(int native_mode) noexcept { + switch (native_mode) { case EVP_CIPH_ECB_MODE: + return cipher_mode_type::ecb; case EVP_CIPH_CBC_MODE: + return cipher_mode_type::cbc; + case EVP_CIPH_CFB_MODE: + return cipher_mode_type::cfb; + case EVP_CIPH_OFB_MODE: + return cipher_mode_type::ofb; case EVP_CIPH_CTR_MODE: + return cipher_mode_type::ctr; case EVP_CIPH_GCM_MODE: - break; + return cipher_mode_type::gcm; + case EVP_CIPH_CCM_MODE: + return cipher_mode_type::ccm; + case EVP_CIPH_XTS_MODE: + return cipher_mode_type::xts; + case EVP_CIPH_WRAP_MODE: + return cipher_mode_type::wrap; + case EVP_CIPH_OCB_MODE: + return cipher_mode_type::ocb; + case EVP_CIPH_SIV_MODE: + return cipher_mode_type::siv; 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 cipher_mode_type::delimiter; + } + } + + [[nodiscard]] static const auto * + get_validated_cipher_by_name_internal(const std::string &cipher_name) { + const auto *cipher{get_cipher_by_name_internal(cipher_name)}; + if (cipher == nullptr) { + util::exception_location().raise("unknown cipher name"); } - return evp_cipher; + return cipher; } [[nodiscard]] static std::size_t - get_block_size_in_bytes_internal(const EVP_CIPHER *cipher) { + get_block_size_in_bytes_internal(const EVP_CIPHER *cipher) noexcept { 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) { + get_key_size_in_bytes_internal(const EVP_CIPHER *cipher) noexcept { 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) { + get_iv_size_in_bytes_internal(const EVP_CIPHER *cipher) noexcept { assert(cipher != nullptr); return static_cast(EVP_CIPHER_get_iv_length(cipher)); } @@ -98,7 +117,7 @@ void cipher_context::impl_deleter::operator()(void *cipher_ctx) const noexcept { } } -cipher_context::cipher_context(cipher_context_mode_type mode, +cipher_context::cipher_context(cipher_context_operation_type operation, const std::string &cipher_name, util::const_byte_span key, util::const_byte_span ivec, @@ -108,7 +127,14 @@ cipher_context::cipher_context(cipher_context_mode_type mode, util::exception_location().raise( "cannot create cipher context"); } - const auto *evp_cipher{native_helper::get_native_cipher_by_name(cipher_name)}; + const auto *evp_cipher{ + native_helper::get_validated_cipher_by_name_internal(cipher_name)}; + const auto mode{ + native_helper::convert_mode_internal(EVP_CIPHER_get_mode(evp_cipher))}; + if (!is_mode_supported(mode)) { + util::exception_location().raise("unsupported cipher mode"); + } + if (std::size(key) != native_helper::get_key_size_in_bytes_internal(evp_cipher)) { util::exception_location().raise( @@ -119,7 +145,7 @@ cipher_context::cipher_context(cipher_context_mode_type mode, util::exception_location().raise( "invalid iv size for the specified cipher"); } - if (mode == cipher_context_mode_type::encryption) { + if (operation == cipher_context_operation_type::encryption) { if (!tag.empty()) { util::exception_location().raise( "tag must not be specified for encryption cipher context"); @@ -134,7 +160,8 @@ cipher_context::cipher_context(cipher_context_mode_type mode, 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 + (operation == cipher_context_operation_type::encryption ? 1 + : 0) // enc ) == 0) { util::exception_location().raise( "cannot initialize cipher context"); @@ -145,7 +172,7 @@ cipher_context::cipher_context(cipher_context_mode_type mode, "cannot disable padding for cipher context"); } - if (mode == cipher_context_mode_type::decryption) { + if (operation == cipher_context_operation_type::decryption) { if (get_tag_size_in_bytes() != std::size(tag)) { util::exception_location().raise( "invalid tag size for the specified cipher"); @@ -164,12 +191,18 @@ cipher_context::cipher_context(cipher_context_mode_type mode, } } -[[nodiscard]] cipher_context_mode_type -cipher_context::get_mode() const noexcept { +[[nodiscard]] cipher_context_operation_type +cipher_context::get_operation() const noexcept { assert(!is_empty()); return (EVP_CIPHER_CTX_encrypting(native_helper::deimpl(impl_)) - ? cipher_context_mode_type::encryption - : cipher_context_mode_type::decryption); + ? cipher_context_operation_type::encryption + : cipher_context_operation_type::decryption); +} + +[[nodiscard]] cipher_mode_type cipher_context::get_mode() const noexcept { + assert(!is_empty()); + return native_helper::convert_mode_internal( + EVP_CIPHER_CTX_get_mode(native_helper::deimpl(impl_))); } [[nodiscard]] std::size_t @@ -200,22 +233,69 @@ cipher_context::get_tag_size_in_bytes() const noexcept { EVP_CIPHER_CTX_get_tag_length(native_helper::deimpl(impl_))); } +[[nodiscard]] bool +cipher_context::is_cipher_name_known(const std::string &cipher_name) noexcept { + return native_helper::get_cipher_by_name_internal(cipher_name) != nullptr; +} + +[[nodiscard]] bool +cipher_context::is_mode_supported(cipher_mode_type mode) noexcept { + switch (mode) { + case cipher_mode_type::ecb: + case cipher_mode_type::cbc: + case cipher_mode_type::ctr: + case cipher_mode_type::gcm: + return true; + default: + // cipher_mode_type::cfb: + // cipher_mode_type::ofb: + // cipher_mode_type::ccm: + // cipher_mode_type::xts: + // cipher_mode_type::wrap: + // cipher_mode_type::ocb: + // cipher_mode_type::siv: + return false; + } +} + +[[nodiscard]] bool cipher_context::is_cipher_name_supported( + const std::string &cipher_name) noexcept { + const auto *evp_cipher{ + native_helper::get_cipher_by_name_internal(cipher_name)}; + if (evp_cipher == nullptr) { + return false; + } + const auto mode{ + native_helper::convert_mode_internal(EVP_CIPHER_get_mode(evp_cipher))}; + return is_mode_supported(mode); +} + +[[nodiscard]] cipher_mode_type +cipher_context::get_mode(const std::string &cipher_name) noexcept { + const auto *evp_cipher{ + native_helper::get_cipher_by_name_internal(cipher_name)}; + if (evp_cipher == nullptr) { + return cipher_mode_type::delimiter; + } + return native_helper::convert_mode_internal(EVP_CIPHER_get_mode(evp_cipher)); +} + [[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)); + native_helper::get_validated_cipher_by_name_internal(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)); + native_helper::get_validated_cipher_by_name_internal(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)); + native_helper::get_validated_cipher_by_name_internal(cipher_name)); } void cipher_context::extract_updated_iv(util::byte_span ivec) { @@ -292,15 +372,15 @@ void cipher_context::update(util::const_byte_span input, void cipher_context::finalize(util::byte_span output_tag) { assert(!is_empty()); - const auto mode{get_mode()}; - if (mode == cipher_context_mode_type::decryption) { + const auto operation{get_operation()}; + if (operation == cipher_context_operation_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 + // cipher_context_operation_type::encryption operation 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 " @@ -329,7 +409,7 @@ void cipher_context::finalize(util::byte_span output_tag) { "in cipher context finalize the actual output size is not zero"); } - if (mode == cipher_context_mode_type::encryption) { + if (operation == cipher_context_operation_type::encryption) { const auto tag_size_native{static_cast(std::size(output_tag))}; void *const tag_ptr{ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) @@ -347,15 +427,27 @@ void cipher_context::finalize(util::byte_span output_tag) { } cipher_context cipher_context::create_with_offset( - std::uint64_t offset, cipher_context_mode_type mode, + std::uint64_t offset, cipher_context_operation_type operation, const std::string &cipher_name, util::const_byte_span key, util::const_byte_span ivec, util::const_byte_span tag) { if (offset == 0ULL) { // early return when offset is zero to avoid unnecessary copying - return cipher_context{mode, cipher_name, key, ivec, tag}; + return cipher_context{operation, cipher_name, key, ivec, tag}; } - // TODO: check if this function is called for one of the CTR ciphers + static constexpr cipher_mode_type expected_mode{cipher_mode_type::ctr}; + static constexpr cipher_mode_type non_streaming_mode{cipher_mode_type::ecb}; + + if (get_mode(cipher_name) != expected_mode) { + util::exception_location().raise( + "in cipher context creation with offset the specified cipher is not a " + "CTR cipher"); + } + if (std::size(ivec) != get_iv_size_in_bytes(cipher_name)) { + util::exception_location().raise( + "in cipher context creation with offset the size of the iv does not " + "match the expected iv size for the specified cipher"); + } if (std::size(ivec) < sizeof(std::uint64_t)) { util::exception_location().raise( "in cipher context creation with offset the size of the iv is too " @@ -368,13 +460,49 @@ cipher_context cipher_context::create_with_offset( // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(original_ivec_ptr))}; - // this is true for most of the algorithms, including AES - // we cannot use get_block_size_in_bytes() because it returns 1 for CTR and // GCM - // TODO: rework with calling get_block_size_in_bytes() with modified cipher - // name (change the mode part to "-ECB") - static constexpr std::uint64_t block_size{16ULL}; + // a helper lambda that modifies the mode part of the provided cipher name + // from expected 'expected_mode' ('CTR') to 'non_streaming_mode' ('ECB') + // preserving the case + const auto cipher_name_mofifier{[](std::string &inplace_cipher_name) -> bool { + static constexpr char delimiter{'-'}; + const auto pos{inplace_cipher_name.rfind(delimiter)}; + if (pos == std::string::npos) { + return false; + } + const auto extracted_mode_str{inplace_cipher_name.substr(pos + 1)}; + std::string expected_mode_str{to_string_view(expected_mode)}; + bool expected_mode_found{false}; + bool upper_case_extracted_mode{false}; + if (extracted_mode_str == expected_mode_str) { + expected_mode_found = true; + } else { + boost::algorithm::to_upper(expected_mode_str); + if (extracted_mode_str == expected_mode_str) { + expected_mode_found = true; + upper_case_extracted_mode = true; + } + } + if (!expected_mode_found) { + return false; + } + inplace_cipher_name.resize(pos + 1); + std::string new_mode{to_string_view(non_streaming_mode)}; + if (upper_case_extracted_mode) { + boost::algorithm::to_upper(new_mode); + } + inplace_cipher_name += new_mode; + return true; + }}; + std::string modified_cipher_name{cipher_name}; + if (!cipher_name_mofifier(modified_cipher_name)) { + util::exception_location().raise( + "in cipher context creation with offset the specified cipher name does " + "not have the expected format"); + } + const std::uint64_t block_size{get_block_size_in_bytes(modified_cipher_name)}; counter += offset / block_size; using buffer_type = boost::container::static_vector< @@ -389,8 +517,10 @@ cipher_context cipher_context::create_with_offset( boost::endian::store_big_u64(reinterpret_cast(dest_ptr), counter); - cipher_context result{mode, cipher_name, key, modified_ivec, tag}; + cipher_context result{operation, cipher_name, key, modified_ivec, tag}; + // TODO: consider using EVP_CIPHER_CTX_set_num() instead of a dummy block + // trick buffer_type source_dummy_block{offset % block_size, boost::container::default_init}; buffer_type dest_dummy_block{offset % block_size}; diff --git a/src/opensslpp/cipher_context.hpp b/src/opensslpp/cipher_context.hpp index 469d2c4..311ed19 100644 --- a/src/opensslpp/cipher_context.hpp +++ b/src/opensslpp/cipher_context.hpp @@ -21,6 +21,8 @@ #include #include +#include "opensslpp/cipher_mode_type_fwd.hpp" + #include "util/byte_span_fwd.hpp" namespace opensslpp { @@ -28,8 +30,8 @@ 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 + // * 'operation' must be either cipher_context_operation_type::encryption or + // cipher_context_operation_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 @@ -47,7 +49,7 @@ class cipher_context { // 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, + cipher_context_operation_type operation, const std::string &cipher_name, util::const_byte_span key, util::const_byte_span ivec = {}, util::const_byte_span tag = {}); ~cipher_context() noexcept = default; @@ -62,13 +64,21 @@ class cipher_context { [[nodiscard]] bool is_empty() const noexcept { return !impl_; } - [[nodiscard]] cipher_context_mode_type get_mode() const noexcept; + [[nodiscard]] cipher_context_operation_type get_operation() const noexcept; + [[nodiscard]] cipher_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 bool + is_cipher_name_known(const std::string &cipher_name) noexcept; + [[nodiscard]] static bool is_mode_supported(cipher_mode_type mode) noexcept; + [[nodiscard]] static bool + is_cipher_name_supported(const std::string &cipher_name) noexcept; + [[nodiscard]] static cipher_mode_type + get_mode(const std::string &cipher_name) noexcept; [[nodiscard]] static std::size_t get_block_size_in_bytes(const std::string &cipher_name); [[nodiscard]] static std::size_t @@ -86,12 +96,10 @@ class cipher_context { void update(util::const_byte_span input, util::byte_span output); void finalize(util::byte_span output_tag = {}); - static cipher_context create_with_offset(std::uint64_t offset, - 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 = {}); + static cipher_context create_with_offset( + std::uint64_t offset, cipher_context_operation_type operation, + const std::string &cipher_name, util::const_byte_span key, + util::const_byte_span ivec = {}, util::const_byte_span tag = {}); private: struct native_helper; diff --git a/src/opensslpp/cipher_context_fwd.hpp b/src/opensslpp/cipher_context_fwd.hpp index 99700ee..4316c3a 100644 --- a/src/opensslpp/cipher_context_fwd.hpp +++ b/src/opensslpp/cipher_context_fwd.hpp @@ -20,7 +20,10 @@ namespace opensslpp { -enum class cipher_context_mode_type : std::uint8_t { encryption, decryption }; +enum class cipher_context_operation_type : std::uint8_t { + encryption, + decryption +}; class cipher_context; diff --git a/src/opensslpp/cipher_mode_type.hpp b/src/opensslpp/cipher_mode_type.hpp new file mode 100644 index 0000000..b80c4ee --- /dev/null +++ b/src/opensslpp/cipher_mode_type.hpp @@ -0,0 +1,102 @@ +// 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_MODE_TYPE_HPP +#define OPENSSLPP_CIPHER_MODE_TYPE_HPP + +#include "opensslpp/cipher_mode_type_fwd.hpp" // IWYU pragma: export + +#include +#include +#include +#include +#include +#include + +#include "util/conversion_helpers.hpp" + +namespace opensslpp { + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +// clang-format off +#define OPENSSLPP_CIPHER_MODE_TYPE_X_SEQUENCE() \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(ecb), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(cbc), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(cfb), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(ofb), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(ctr), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(gcm), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(ccm), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(xts), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(wrap), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(ocb), \ + OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(siv) +// clang-format on + +#define OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(X) X +enum class cipher_mode_type : std::uint8_t { + OPENSSLPP_CIPHER_MODE_TYPE_X_SEQUENCE(), + delimiter +}; +#undef OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO + +inline std::string_view to_string_view(cipher_mode_type mode) noexcept { + using namespace std::string_view_literals; +#define OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO(X) #X##sv + static constexpr std::array labels{OPENSSLPP_CIPHER_MODE_TYPE_X_SEQUENCE(), + ""sv}; +#undef OPENSSLPP_CIPHER_MODE_TYPE_X_MACRO + const auto index{ + util::enum_to_index(std::min(cipher_mode_type::delimiter, mode))}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + return labels[index]; +} +#undef OPENSSLPP_CIPHER_MODE_TYPE_X_SEQUENCE +// NOLINTEND(cppcoreguidelines-macro-usage) + +template + requires std::same_as +std::basic_ostream & +operator<<(std::basic_ostream &output, cipher_mode_type mode) { + return output << to_string_view(mode); +} + +template + requires std::same_as +std::basic_istream & +operator>>(std::basic_istream &input, cipher_mode_type &mode) { + std::string mode_str; + input >> mode_str; + if (!input) { + return input; + } + std::size_t index{0U}; + const auto max_index = util::enum_to_index(cipher_mode_type::delimiter); + while (index < max_index && + to_string_view(util::index_to_enum(index)) != + mode_str) { + ++index; + } + if (index < max_index) { + mode = util::index_to_enum(index); + } else { + input.setstate(std::ios_base::failbit); + } + return input; +} + +} // namespace opensslpp + +#endif // OPENSSLPP_CIPHER_MODE_TYPE_HPP diff --git a/src/opensslpp/cipher_mode_type_fwd.hpp b/src/opensslpp/cipher_mode_type_fwd.hpp new file mode 100644 index 0000000..5c6ab20 --- /dev/null +++ b/src/opensslpp/cipher_mode_type_fwd.hpp @@ -0,0 +1,45 @@ +// 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_MODE_TYPE_FWD_HPP +#define OPENSSLPP_CIPHER_MODE_TYPE_FWD_HPP + +#include +#include +#include + +#include "util/nv_tuple_json_support.hpp" + +namespace opensslpp { + +enum class cipher_mode_type : std::uint8_t; + +template + requires std::same_as +std::basic_ostream & +operator<<(std::basic_ostream &output, cipher_mode_type mode); + +template + requires std::same_as +std::basic_istream & +operator>>(std::basic_istream &input, cipher_mode_type &mode); + +} // namespace opensslpp + +template <> +struct util::is_string_convertible + : std::true_type {}; + +#endif // OPENSSLPP_CIPHER_MODE_TYPE_FWD_HPP diff --git a/tests/cipher_context_test.cpp b/tests/cipher_context_test.cpp index a3475ea..b28ea4d 100644 --- a/tests/cipher_context_test.cpp +++ b/tests/cipher_context_test.cpp @@ -33,13 +33,15 @@ #include #include "opensslpp/cipher_context.hpp" +#include "opensslpp/cipher_mode_type.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"}; +static const char *const invalid_cipher_name{"INVALID-256-BBB"}; +static const char *const unsupported_cipher_name{"AES-128-CFB"}; BOOST_AUTO_TEST_CASE(CipherContextDefaultConstruction) { const opensslpp::cipher_context empty_ctx{}; @@ -56,7 +58,8 @@ BOOST_AUTO_TEST_CASE(CipherContextValidCipherNameConstruction) { opensslpp::crypto_rng::generate(ivec); const opensslpp::cipher_context empty_ctx( - opensslpp::cipher_context_mode_type::encryption, cipher_name, key, ivec); + opensslpp::cipher_context_operation_type::encryption, cipher_name, key, + ivec); BOOST_CHECK(!empty_ctx.is_empty()); } @@ -67,18 +70,18 @@ BOOST_AUTO_TEST_CASE(CipherContextInvalidCipherNameConstruction) { 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_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::encryption, + unsupported_cipher_name, util::const_byte_span{}, + util::const_byte_span{}), + opensslpp::core_error); } static const std::initializer_list modes{"ECB", "CBC", "CTR", @@ -128,10 +131,39 @@ BOOST_DATA_TEST_CASE(CipherContextInvalidKeyLengthIVLengthConstruction, 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_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::encryption, + cipher_name, key, ivec), + opensslpp::core_error); +} + +BOOST_AUTO_TEST_CASE(CipherContextGetModeStatic) { + BOOST_CHECK(opensslpp::cipher_context::get_mode(invalid_cipher_name) == + opensslpp::cipher_mode_type::delimiter); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-128-ECB") == + opensslpp::cipher_mode_type::ecb); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-192-ECB") == + opensslpp::cipher_mode_type::ecb); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-256-ECB") == + opensslpp::cipher_mode_type::ecb); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-128-CBC") == + opensslpp::cipher_mode_type::cbc); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-192-CBC") == + opensslpp::cipher_mode_type::cbc); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-256-CBC") == + opensslpp::cipher_mode_type::cbc); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-128-CTR") == + opensslpp::cipher_mode_type::ctr); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-192-CTR") == + opensslpp::cipher_mode_type::ctr); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-256-CTR") == + opensslpp::cipher_mode_type::ctr); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-128-GCM") == + opensslpp::cipher_mode_type::gcm); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-192-GCM") == + opensslpp::cipher_mode_type::gcm); + BOOST_CHECK(opensslpp::cipher_context::get_mode("AES-256-GCM") == + opensslpp::cipher_mode_type::gcm); } BOOST_AUTO_TEST_CASE(CipherContextGetBlockSizeStatic) { @@ -229,7 +261,7 @@ BOOST_AUTO_TEST_CASE(CipherContextGetIVSizeStatic) { class cipher_context_fixture { protected: - auto create_encryption_context(opensslpp::cipher_context_mode_type mode, + auto create_encryption_context(opensslpp::cipher_context_operation_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)); @@ -239,11 +271,11 @@ class cipher_context_fixture { } auto create_encryption_context(const std::string &cipher_name) { return create_encryption_context( - opensslpp::cipher_context_mode_type::encryption, cipher_name); + opensslpp::cipher_context_operation_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); + opensslpp::cipher_context_operation_type::decryption, cipher_name); } private: @@ -251,13 +283,40 @@ class cipher_context_fixture { buffer_type ivec_; }; -BOOST_FIXTURE_TEST_CASE(CipherContextGetMode, cipher_context_fixture) { +BOOST_FIXTURE_TEST_CASE(CipherContextGetOperation, cipher_context_fixture) { auto encryption_context{create_encryption_context("AES-128-ECB")}; - BOOST_CHECK(encryption_context.get_mode() == - opensslpp::cipher_context_mode_type::encryption); + BOOST_CHECK(encryption_context.get_operation() == + opensslpp::cipher_context_operation_type::encryption); auto decryption_context{create_decryption_context("AES-128-ECB")}; - BOOST_CHECK(decryption_context.get_mode() == - opensslpp::cipher_context_mode_type::decryption); + BOOST_CHECK(decryption_context.get_operation() == + opensslpp::cipher_context_operation_type::decryption); +} + +BOOST_FIXTURE_TEST_CASE(CipherContextGetMode, cipher_context_fixture) { + BOOST_CHECK(create_encryption_context("AES-128-ECB").get_mode() == + opensslpp::cipher_mode_type::ecb); + BOOST_CHECK(create_encryption_context("AES-192-ECB").get_mode() == + opensslpp::cipher_mode_type::ecb); + BOOST_CHECK(create_encryption_context("AES-256-ECB").get_mode() == + opensslpp::cipher_mode_type::ecb); + BOOST_CHECK(create_encryption_context("AES-128-CBC").get_mode() == + opensslpp::cipher_mode_type::cbc); + BOOST_CHECK(create_encryption_context("AES-192-CBC").get_mode() == + opensslpp::cipher_mode_type::cbc); + BOOST_CHECK(create_encryption_context("AES-256-CBC").get_mode() == + opensslpp::cipher_mode_type::cbc); + BOOST_CHECK(create_encryption_context("AES-128-CTR").get_mode() == + opensslpp::cipher_mode_type::ctr); + BOOST_CHECK(create_encryption_context("AES-192-CTR").get_mode() == + opensslpp::cipher_mode_type::ctr); + BOOST_CHECK(create_encryption_context("AES-256-CTR").get_mode() == + opensslpp::cipher_mode_type::ctr); + BOOST_CHECK(create_encryption_context("AES-128-GCM").get_mode() == + opensslpp::cipher_mode_type::gcm); + BOOST_CHECK(create_encryption_context("AES-192-GCM").get_mode() == + opensslpp::cipher_mode_type::gcm); + BOOST_CHECK(create_encryption_context("AES-256-GCM").get_mode() == + opensslpp::cipher_mode_type::gcm); } BOOST_FIXTURE_TEST_CASE(CipherContextGetBlockSize, cipher_context_fixture) { @@ -350,9 +409,10 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripECB, 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 + // ECB mode does not use an IV + const std::size_t fake_ivec_size{16U}; + // ECB mode does not use a tag + static constexpr std::size_t fake_tag_length{16U}; buffer_type key{valid_key_size}; buffer_type fake_ivec{fake_ivec_size}; @@ -364,20 +424,20 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripECB, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::encryption, + cipher_name, key, fake_ivec, fake_tag), + opensslpp::core_error); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::encryption, + cipher_name, key, fake_ivec), + opensslpp::core_error); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::encryption, + cipher_name, key, {}, fake_tag), + opensslpp::core_error); opensslpp::cipher_context encryption_context( - opensslpp::cipher_context_mode_type::encryption, cipher_name, key); + opensslpp::cipher_context_operation_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); @@ -393,20 +453,20 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripECB, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::decryption, + cipher_name, key, fake_ivec, fake_tag), + opensslpp::core_error); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::decryption, + cipher_name, key, fake_ivec), + opensslpp::core_error); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_type::decryption, + cipher_name, key, {}, fake_tag), + opensslpp::core_error); opensslpp::cipher_context decryption_context( - opensslpp::cipher_context_mode_type::decryption, cipher_name, key); + opensslpp::cipher_context_operation_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); @@ -439,8 +499,8 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripCBC, 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 + // CBC mode does not use a tag + static constexpr std::size_t fake_tag_length{16U}; buffer_type key{valid_key_size}; buffer_type ivec{valid_ivec_size}; @@ -453,12 +513,13 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripCBC, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + opensslpp::cipher_context_operation_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); @@ -474,12 +535,13 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripCBC, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + opensslpp::cipher_context_operation_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); @@ -512,8 +574,8 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripCTR, 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 + // CTR mode does not use a tag + static constexpr std::size_t fake_tag_length{16U}; buffer_type key{valid_key_size}; buffer_type ivec{valid_ivec_size}; @@ -526,12 +588,13 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripCTR, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + opensslpp::cipher_context_operation_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); @@ -543,12 +606,13 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripCTR, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + opensslpp::cipher_context_operation_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); @@ -588,12 +652,13 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripGCM, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + opensslpp::cipher_context_operation_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); @@ -608,19 +673,19 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripGCM, 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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + BOOST_CHECK_THROW(opensslpp::cipher_context( + opensslpp::cipher_context_operation_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); + opensslpp::cipher_context_operation_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); @@ -656,17 +721,108 @@ BOOST_DATA_TEST_CASE(CipherContextUpdatedIVCTR, opensslpp::crypto_rng::generate(message); opensslpp::cipher_context encryption_context( - opensslpp::cipher_context_mode_type::encryption, cipher_name, key, ivec); + opensslpp::cipher_context_operation_type::encryption, cipher_name, key, + ivec); encryption_context.update(message, encrypted_message); buffer_type updated_ivec{valid_ivec_size}; encryption_context.extract_updated_iv(updated_ivec); auto fast_encryption_context{opensslpp::cipher_context::create_with_offset( - std::size(message), opensslpp::cipher_context_mode_type::encryption, + std::size(message), opensslpp::cipher_context_operation_type::encryption, cipher_name, key, ivec)}; buffer_type fast_updated_ivec{valid_ivec_size}; fast_encryption_context.extract_updated_iv(fast_updated_ivec); BOOST_CHECK(updated_ivec == fast_updated_ivec); } + +BOOST_DATA_TEST_CASE(CipherContextCTRResume, + 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)}; + + buffer_type key{valid_key_size}; + buffer_type ivec{valid_ivec_size}; + opensslpp::crypto_rng::generate(key); + opensslpp::crypto_rng::generate(ivec); + + const std::size_t first_part_size{message_size / 2U}; + const std::size_t second_part_size{message_size - first_part_size}; + + buffer_type message{message_size}; + opensslpp::crypto_rng::generate(message); + + const util::const_byte_span message_v{message}; + const util::const_byte_span message_first_part_v{ + message_v.first(first_part_size)}; + const util::const_byte_span message_second_part_v{ + message_v.last(second_part_size)}; + + // single pass encryption + buffer_type encrypted_message_single_pass{message_size}; + { + opensslpp::cipher_context encryption_context( + opensslpp::cipher_context_operation_type::encryption, cipher_name, key, + ivec); + encryption_context.update(message_v, encrypted_message_single_pass); + encryption_context.finalize(); + } + + // encrypting with 2 update calls + buffer_type encrypted_message_partial_updates{message_size}; + { + const util::byte_span encrypted_message_partial_updates_v{ + encrypted_message_partial_updates}; + const util::byte_span encrypted_message_partial_updates_first_part_v{ + encrypted_message_partial_updates_v.first(first_part_size)}; + const util::byte_span encrypted_message_partial_updates_second_part_v{ + encrypted_message_partial_updates_v.last(second_part_size)}; + + opensslpp::cipher_context encryption_context( + opensslpp::cipher_context_operation_type::encryption, cipher_name, key, + ivec); + encryption_context.update(message_first_part_v, + encrypted_message_partial_updates_first_part_v); + encryption_context.update(message_second_part_v, + encrypted_message_partial_updates_second_part_v); + encryption_context.finalize(); + } + + BOOST_CHECK(encrypted_message_partial_updates == + encrypted_message_single_pass); + + // encrypting with context re-creation (resume) + buffer_type encrypted_message_resume{message_size}; + { + const util::byte_span encrypted_message_resume_v{encrypted_message_resume}; + const util::byte_span encrypted_message_resume_first_part_v{ + encrypted_message_resume_v.first(first_part_size)}; + const util::byte_span encrypted_message_resume_second_part_v{ + encrypted_message_resume_v.last(second_part_size)}; + + opensslpp::cipher_context initial_encryption_context( + opensslpp::cipher_context_operation_type::encryption, cipher_name, key, + ivec); + initial_encryption_context.update(message_first_part_v, + encrypted_message_resume_first_part_v); + initial_encryption_context.finalize(); + + auto resumed_encryption_context{ + opensslpp::cipher_context::create_with_offset( + first_part_size, + opensslpp::cipher_context_operation_type::encryption, cipher_name, + key, ivec)}; + resumed_encryption_context.update(message_second_part_v, + encrypted_message_resume_second_part_v); + resumed_encryption_context.finalize(); + } + + BOOST_CHECK(encrypted_message_resume == encrypted_message_single_pass); +}