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); +}