diff --git a/CMakeLists.txt b/CMakeLists.txt index 308d93b..bede84e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,7 +184,7 @@ add_library(lib_opensslpp STATIC ${opensslpp_source_files}) target_link_libraries(lib_opensslpp PRIVATE binlog_server_compiler_flags - OpenSSL::Crypto + Boost::headers OpenSSL::Crypto ) # it is not possible to propagate CXX_EXTENSIONS and CXX_STANDARD_REQUIRED # via interface library (binlog_server_compiler_flags) @@ -594,6 +594,7 @@ target_link_libraries(binlog_server binsrv::lib_gtids binsrv::lib_events binsrv::lib_models + binsrv::lib_opensslpp Boost::headers Boost::json Boost::url aws-cpp-sdk-s3-crt ) diff --git a/src/binsrv/encryption_config.cpp b/src/binsrv/encryption_config.cpp index daf0596..bcf6972 100644 --- a/src/binsrv/encryption_config.cpp +++ b/src/binsrv/encryption_config.cpp @@ -28,6 +28,8 @@ 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 } } // namespace binsrv diff --git a/src/binsrv/keyring_record_collection.cpp b/src/binsrv/keyring_record_collection.cpp index 609ac3c..c715662 100644 --- a/src/binsrv/keyring_record_collection.cpp +++ b/src/binsrv/keyring_record_collection.cpp @@ -62,6 +62,14 @@ 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 + + // TODO: make sure that all keys have ciphers known in OpenSSL + // (currently only ECB, CBC, CTR, GCM modes are supported by + // opensslpp::cipher_context) + + // TODO: make sure that all keys have data_hex values of the correct length + // that matches with the cipher } [[nodiscard]] std::string keyring_record_collection::get_description() const { diff --git a/src/binsrv/storage.cpp b/src/binsrv/storage.cpp index 768f5cf..c8c0285 100644 --- a/src/binsrv/storage.cpp +++ b/src/binsrv/storage.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -32,8 +31,6 @@ #include #include -#include - #include "binsrv/basic_keyring.hpp" #include "binsrv/basic_storage_backend.hpp" #include "binsrv/binlog_file_metadata.hpp" @@ -54,8 +51,10 @@ #include "binsrv/models/binlog_file_encryption_record.hpp" +#include "opensslpp/cipher_context.hpp" +#include "opensslpp/crypto_rng.hpp" + #include "util/byte_span.hpp" -#include "util/conversion_helpers.hpp" #include "util/ctime_timestamp.hpp" #include "util/exception_location_helpers.hpp" @@ -147,8 +146,12 @@ storage::storage(const storage_config &config, util::exception_location().raise( "keyring does not contain the specified KEK ID"); } - // TODO: validate that the length of the KEK in the keyring record match - // the length specified in the cipher name + // 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">(); } @@ -529,9 +532,11 @@ void storage::update_last_checkpoint_info() { [[nodiscard]] open_binlog_status storage::open_new_binlog_file_internal( const events::composite_binlog_name &binlog_name) { + + auto encryption_record{generate_binlog_encryption_record()}; // writing the magic binlog footprint only if this is a newly // created file - backend_->write_data_to_stream(events::magic_binlog_payload); + write_data_to_stream(events::magic_binlog_payload, encryption_record, 0ULL); gtids::optional_gtid_set previous_binlog_gtids{}; gtids::optional_gtid_set added_binlog_gtids{}; @@ -544,7 +549,7 @@ void storage::update_last_checkpoint_info() { binlog_name, events::magic_binlog_offset, std::move(previous_binlog_gtids), std::move(added_binlog_gtids), util::ctime_timestamp_range{}, events::seq_no_t{}, - generate_binlog_encryption_record()); + std::move(encryption_record)); save_binlog_metadata(get_current_binlog_record()); save_binlog_index(); return open_binlog_status::created; @@ -558,7 +563,9 @@ storage::open_existing_binlog_file_internal(std::uint64_t open_stream_offset) { : open_binlog_status::opened_with_data_present; } assert(open_stream_offset == 0ULL); - backend_->write_data_to_stream(events::magic_binlog_payload); + + write_data_to_stream(events::magic_binlog_payload, + get_current_binlog_record().encryption, 0ULL); get_current_binlog_record().size = events::magic_binlog_offset; return open_binlog_status::opened_empty; } @@ -573,7 +580,9 @@ void storage::flush_event_buffer_internal() { last_transaction_boundary_position_in_event_buffer_}; // writing bytes from // the beginning of the event buffer - backend_->write_data_to_stream(transactions_data); + write_data_to_stream(transactions_data, + get_current_binlog_record().encryption, + get_current_binlog_record().size); get_current_binlog_record().size += last_transaction_boundary_position_in_event_buffer_; if (is_in_gtid_replication_mode()) { @@ -851,145 +860,149 @@ storage::generate_binlog_encryption_record() const { return std::nullopt; } + // we identify the KEK record in the keyring by the active KEK ID, + // specified in the main configuration file + // ('' parameter) const auto &keyring_record{keyring_->get_key(active_kek_id_)}; + + // identifying the the cipher name and the key data from the + // keyring record - this data will be used to encrypt random file + // keys generated for new binlog data files const auto &kek_cipher{keyring_record.get<"cipher">()}; - const auto &kek_data{keyring_record.get<"data_hex">().get_data()}; - - // identify initialization vector length based on the cipher - // TODO: rework with proper OpenSSL "EVP_CIPHER_iv_length()" call - const auto iv_length_helper{[](const std::string &cipher) -> std::size_t { - const auto normalized_cipher{boost::algorithm::to_upper_copy(cipher)}; - - static constexpr std::size_t no_iv_length{0U}; - static constexpr std::size_t generic_iv_length{16U}; - static constexpr std::size_t gcm_iv_length{12U}; - if (normalized_cipher.ends_with("-ECB")) { - return no_iv_length; - } - if (normalized_cipher.ends_with("-GCM")) { - return gcm_iv_length; - } - if (normalized_cipher.ends_with("-CBC") || - normalized_cipher.ends_with("-CTR")) { - return generic_iv_length; - } - util::exception_location().raise( - "unsupported cipher mode: " + cipher); - }}; - - // identify the length of the key based on the cipher - // TODO: rework with proper OpenSSL "EVP_CIPHER_key_length()" call - const auto key_length_helper{[](const std::string &cipher) -> std::size_t { - static constexpr std::size_t x_128_key_length{16U}; - static constexpr std::size_t x_192_key_length{24U}; - static constexpr std::size_t x_256_key_length{32U}; - if (cipher.find("-128-") != std::string::npos) { - return x_128_key_length; - } - if (cipher.find("-192-") != std::string::npos) { - return x_192_key_length; - } - if (cipher.find("-256-") != std::string::npos) { - return x_256_key_length; - } - util::exception_location().raise( - "unsupported cipher length: " + cipher); - }}; - // identify the length of the tag based on the cipher - // TODO: rework with proper OpenSSL call - const auto tag_length_helper{[](const std::string &cipher) -> std::size_t { - const auto normalized_cipher{boost::algorithm::to_upper_copy(cipher)}; - static constexpr std::size_t no_tag_length{0U}; - static constexpr std::size_t gcm_tag_length{16U}; - - if (normalized_cipher.ends_with("-GCM")) { - return gcm_tag_length; - } - if (normalized_cipher.ends_with("-ECB") || - normalized_cipher.ends_with("-CBC") || - normalized_cipher.ends_with("-CTR")) { - return no_tag_length; - } - util::exception_location().raise( - "unsupported cipher mode: " + cipher); - }}; - - // generating a random blob - // TODO: rework with proper OpenSSL RAND_bytes() call - const auto random_blob_helper{ - [](std::size_t length) -> util::hex_value_storage { - static std::random_device rd_instance; - util::hex_value_storage result(length); - std::ranges::generate(result, []() { - return util::from_underlying( - static_cast(rd_instance())); - }); - return result; - }}; - - const auto iv_length_for_file_key_encryption{iv_length_helper(kek_cipher)}; + const auto &kek{keyring_record.get<"data_hex">().get_data()}; + + // identify the size of the IV that will be used for file key + // encryption based on the KEK cipher; if the KEK cipher is in ECB mode, then + // the IV is not used and its size will be 0 + const auto iv_size_for_file_key_encryption{ + opensslpp::cipher_context::get_iv_size_in_bytes(kek_cipher)}; util::optional_hex_value_storage iv_for_file_key_encryption{}; util::const_byte_span iv_for_file_key_encryption_v{}; - if (iv_length_for_file_key_encryption != 0U) { - iv_for_file_key_encryption = - random_blob_helper(iv_length_for_file_key_encryption); + if (iv_size_for_file_key_encryption != 0U) { + // generating random IV for file key encryption + iv_for_file_key_encryption.emplace(iv_size_for_file_key_encryption); + opensslpp::crypto_rng::generate(*iv_for_file_key_encryption); iv_for_file_key_encryption_v = *iv_for_file_key_encryption; } - const auto file_key_length{key_length_helper(kek_cipher)}; - const auto file_key_data{random_blob_helper(file_key_length)}; - - // TODO: implement encryption using OpenSSL EVP interface - const auto encrypt_helper{ - [&tag_length_helper]( - const std::string &cipher, - // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) - [[maybe_unused]] util::const_byte_span key_data, - [[maybe_unused]] util::const_byte_span iv_data, - util::const_byte_span data) - -> std::pair { - util::optional_hex_value_storage tag{}; - const std::size_t tag_size{tag_length_helper(cipher)}; - if (tag_size != 0U) { - tag = util::hex_value_storage(tag_size); - std::ranges::generate( - *tag, [counter{static_cast(0U)}]() mutable { - return util::from_underlying(counter++); - }); - } - util::hex_value_storage encrypted_data(std::size(data)); - std::ranges::transform( - data, std::begin(encrypted_data), [](std::byte element) { - return std::byte{ - static_cast(util::to_underlying(element) + 1U)}; - }); - - return {encrypted_data, tag}; - }}; - - const auto file_key_encryption_result{encrypt_helper( - kek_cipher, kek_data, iv_for_file_key_encryption_v, file_key_data)}; + // identify the size of the file key based on the active data cipher + const auto file_key_size{ + opensslpp::cipher_context::get_key_size_in_bytes(active_data_cipher_)}; - const auto iv_length_for_data_encryption{ - iv_length_helper(active_data_cipher_)}; - util::hex_value_storage iv_for_data_encryption{ - random_blob_helper(iv_length_for_data_encryption)}; + // generating random file key + util::hex_value_storage file_key{file_key_size}; + opensslpp::crypto_rng::generate(file_key); + + // 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, + iv_for_file_key_encryption_v}; + + // identify the size of the file key encryption tag from the encryption + // (should be non-zero only for GCM modes) + const auto file_key_encryption_tag_size{ + file_key_encryption_context.get_tag_size_in_bytes()}; + // provisioning the optional storage for the file key encryption tag + util::optional_hex_value_storage tag_of_file_key_encryption{}; + util::byte_span tag_of_file_key_encryption_v{}; + if (file_key_encryption_tag_size != 0U) { + tag_of_file_key_encryption.emplace(file_key_encryption_tag_size); + tag_of_file_key_encryption_v = *tag_of_file_key_encryption; + } - util::hex_value_storage dummy_data{}; - const auto data_encryption_result{encrypt_helper( - active_data_cipher_, file_key_data, iv_for_data_encryption, dummy_data)}; + // performing the file key encryption and finalizing the tag (if any) + util::hex_value_storage file_key_encrypted_with_kek{file_key_size}; + file_key_encryption_context.update(file_key, file_key_encrypted_with_kek); + file_key_encryption_context.finalize(tag_of_file_key_encryption_v); + // identifying the size of the IV that will be used for data encryption based + // on the active data cipher + const auto iv_length_for_data_encryption{ + opensslpp::cipher_context::get_iv_size_in_bytes(active_data_cipher_)}; + // generating random IV for file data encryption + util::hex_value_storage iv_for_data_encryption{iv_length_for_data_encryption}; + opensslpp::crypto_rng::generate(iv_for_data_encryption); + + // the tag of data encryption will be generated during the actual data + // encryption binlog_encryption_record encryption_record{ .kek_id = active_kek_id_, - .file_key_encrypted_with_kek = file_key_encryption_result.first, + .file_key_encrypted_with_kek = file_key_encrypted_with_kek, .iv_for_file_key_encryption = iv_for_file_key_encryption, - .tag_of_file_key_encryption = file_key_encryption_result.second, + .tag_of_file_key_encryption = tag_of_file_key_encryption, .data_cipher = active_data_cipher_, .iv_for_data_encryption = iv_for_data_encryption, - .tag_of_data_encryption = data_encryption_result.second}; + .tag_of_data_encryption = {}}; return encryption_record; } +void storage::write_data_to_stream( + util::const_byte_span data, + const optional_binlog_encryption_record &encryption_record, + std::uint64_t offset) { + if (!encryption_record.has_value()) { + // an early return when no encryption is needed + backend_->write_data_to_stream(data); + return; + } + + // as for security reasons our intent is to not store file keys in plaintext + // permanently, we need to decrypt the file key with the KEK before we can + // use it for data encryption. + + const auto &keyring_record{keyring_->get_key(encryption_record->kek_id)}; + + const auto &kek_cipher{keyring_record.get<"cipher">()}; + const auto &kek{keyring_record.get<"data_hex">().get_data()}; + + util::const_byte_span iv_for_file_key_encryption_v{}; + if (encryption_record->iv_for_file_key_encryption.has_value()) { + iv_for_file_key_encryption_v = + *encryption_record->iv_for_file_key_encryption; + }; + util::const_byte_span tag_of_file_key_encryption_v{}; + if (encryption_record->tag_of_file_key_encryption.has_value()) { + tag_of_file_key_encryption_v = + *encryption_record->tag_of_file_key_encryption; + } + + // creating a context for the file key decryption + opensslpp::cipher_context file_key_decryption_context{ + opensslpp::cipher_context_mode_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)}; + file_key_decryption_context.update( + encryption_record->file_key_encrypted_with_kek, file_key_decrypted); + file_key_decryption_context.finalize(); + + // creating an context for data encryption with the data cipher, the file + // 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, + encryption_record->data_cipher, file_key_decrypted, + encryption_record->iv_for_data_encryption)}; + + util::optional_hex_value_storage tag_of_data_encryption{}; + util::byte_span tag_of_data_encryption_v{}; + const auto data_encryption_tag_size{ + data_encryption_context.get_tag_size_in_bytes()}; + if (data_encryption_tag_size != 0U) { + tag_of_data_encryption.emplace(data_encryption_tag_size); + tag_of_data_encryption_v = *tag_of_data_encryption; + } + + util::hex_value_storage encrypted_data{std::size(data)}; + data_encryption_context.update(data, encrypted_data); + data_encryption_context.finalize(tag_of_data_encryption_v); + + backend_->write_data_to_stream(encrypted_data); + + // TODO: update file data encryption tag here, if one day we decide to + // support GCM mode for file data encryption +} + } // namespace binsrv diff --git a/src/binsrv/storage.hpp b/src/binsrv/storage.hpp index 1939ea3..174fef0 100644 --- a/src/binsrv/storage.hpp +++ b/src/binsrv/storage.hpp @@ -288,6 +288,11 @@ class [[nodiscard]] storage { [[nodiscard]] optional_binlog_encryption_record generate_binlog_encryption_record() const; + + void write_data_to_stream( + util::const_byte_span data, + const optional_binlog_encryption_record &encryption_record, + std::uint64_t offset); }; } // namespace binsrv diff --git a/src/opensslpp/cipher_context.cpp b/src/opensslpp/cipher_context.cpp index fb23631..c50b4aa 100644 --- a/src/opensslpp/cipher_context.cpp +++ b/src/opensslpp/cipher_context.cpp @@ -15,13 +15,21 @@ #include "opensslpp/cipher_context.hpp" +#include #include #include #include +#include +#include #include #include #include +#include +#include + +#include + #include #include @@ -210,6 +218,28 @@ cipher_context::get_iv_size_in_bytes(const std::string &cipher_name) { native_helper::get_native_cipher_by_name(cipher_name)); } +void cipher_context::extract_updated_iv(util::byte_span ivec) { + assert(!is_empty()); + if (std::size(ivec) != get_iv_size_in_bytes()) { + util::exception_location().raise( + "in cipher context invalid buffer size for extracting updated iv"); + } + + if (!std::in_range(std::size(ivec))) { + util::exception_location().raise( + "in cipher context buffer size is out of range for extracting updated " + "iv"); + } + if (EVP_CIPHER_CTX_get_updated_iv( + native_helper::deimpl(impl_), + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(std::data(ivec)), + std::size(ivec)) == 0) { + util::exception_location().raise( + "cannot get updated iv for cipher context"); + } +} + void cipher_context::update(util::const_byte_span input, util::byte_span output) { assert(!is_empty()); @@ -233,26 +263,26 @@ void cipher_context::update(util::const_byte_span input, util::exception_location().raise( "in cipher context update input size is out of range"); } - const auto input_length_native{static_cast(std::size(input))}; - int output_length_native{0}; + const auto input_size_native{static_cast(std::size(input))}; + int output_size_native{0}; if (EVP_CipherUpdate( native_helper::deimpl(impl_), // context // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(std::data(output)), // output - &output_length_native, // output length + &output_size_native, // output length // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(std::data(input)), // input - input_length_native // input length + input_size_native // input length ) == 0) { util::exception_location().raise( "cannot update cipher context"); } - if (!std::in_range(output_length_native)) { + if (!std::in_range(output_size_native)) { util::exception_location().raise( "in cipher context update output size is out of range"); } - const auto output_length{static_cast(output_length_native)}; - if (output_length != std::size(output)) { + const auto output_size{static_cast(output_size_native)}; + if (output_size != std::size(output)) { util::exception_location().raise( "in cipher context update the actual output size does not match the " "expected output size"); @@ -280,33 +310,33 @@ void cipher_context::finalize(util::byte_span output_tag) { using fake_buffer_type = std::array; fake_buffer_type fake_buffer; - int output_length_native{0}; + int output_size_native{0}; if (EVP_CipherFinal_ex( native_helper::deimpl(impl_), // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(std::data(fake_buffer)), - &output_length_native) == 0) { + &output_size_native) == 0) { util::exception_location().raise( "cannot finalize cipher context"); } - if (!std::in_range(output_length_native)) { + if (!std::in_range(output_size_native)) { util::exception_location().raise( "in cipher context finalize output size is out of range"); } - const auto output_length{static_cast(output_length_native)}; - if (output_length != 0U) { + const auto output_size{static_cast(output_size_native)}; + if (output_size != 0U) { util::exception_location().raise( "in cipher context finalize the actual output size is not zero"); } if (mode == cipher_context_mode_type::encryption) { - const auto tag_length_native{static_cast(std::size(output_tag))}; + const auto tag_size_native{static_cast(std::size(output_tag))}; void *const tag_ptr{ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) const_cast(static_cast(std::data(output_tag)))}; if (EVP_CIPHER_CTX_ctrl(native_helper::deimpl(impl_), // context EVP_CTRL_GCM_GET_TAG, // type - tag_length_native, // length + tag_size_native, // length tag_ptr // tag ) == 0) { util::exception_location().raise( @@ -316,4 +346,57 @@ void cipher_context::finalize(util::byte_span output_tag) { impl_.reset(); } +cipher_context 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) { + if (offset == 0ULL) { + // early return when offset is zero to avoid unnecessary copying + return cipher_context{mode, cipher_name, key, ivec, tag}; + } + + // TODO: check if this function is called for one of the CTR ciphers + 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 " + "small"); + } + + const std::byte *original_ivec_ptr{std::data(ivec)}; + std::advance(original_ivec_ptr, std::size(ivec) - sizeof(std::uint64_t)); + std::uint64_t counter{boost::endian::load_big_u64( + // 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}; + counter += offset / block_size; + + using buffer_type = boost::container::static_vector< + std::byte, std::max(EVP_MAX_IV_LENGTH, EVP_MAX_BLOCK_LENGTH)>; + buffer_type modified_ivec{std::size(ivec)}; + auto *dest_ptr{std::data(modified_ivec)}; + + const auto *source_en{std::data(ivec)}; + std::advance(source_en, std::size(ivec) - sizeof(std::uint64_t)); + dest_ptr = std::copy(std::data(ivec), source_en, dest_ptr); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + boost::endian::store_big_u64(reinterpret_cast(dest_ptr), + counter); + + cipher_context result{mode, cipher_name, key, modified_ivec, tag}; + + buffer_type source_dummy_block{offset % block_size, + boost::container::default_init}; + buffer_type dest_dummy_block{offset % block_size}; + result.update(source_dummy_block, dest_dummy_block); + + return result; +} + } // namespace opensslpp diff --git a/src/opensslpp/cipher_context.hpp b/src/opensslpp/cipher_context.hpp index 1ad4645..469d2c4 100644 --- a/src/opensslpp/cipher_context.hpp +++ b/src/opensslpp/cipher_context.hpp @@ -78,10 +78,21 @@ class cipher_context { // there is no static version of get_tag_size_in_bytes() as tag size is a // dynamic property of the cipher context + // this is not a const method as underlying EVP_CIPHER_CTX_get_updated_iv() + // accepts non-const EVP_CIPHER_CTX pointer + void extract_updated_iv(util::byte_span ivec); + // TODO: implement void update_inplace(util::byte_span inoutput) void update(util::const_byte_span input, util::byte_span output); void finalize(util::byte_span output_tag = {}); + 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 = {}); + private: struct native_helper; struct impl_deleter { diff --git a/tests/cipher_context_test.cpp b/tests/cipher_context_test.cpp index bc18d84..a3475ea 100644 --- a/tests/cipher_context_test.cpp +++ b/tests/cipher_context_test.cpp @@ -170,6 +170,12 @@ BOOST_AUTO_TEST_CASE(CipherContextGetKeySizeStatic) { BOOST_CHECK_THROW(key_size = opensslpp::cipher_context::get_key_size_in_bytes( invalid_cipher_name), opensslpp::core_error); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-128-ECB") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-192-ECB") == + 24U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-256-ECB") == + 32U); BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-128-CBC") == 16U); BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-192-CBC") == @@ -182,6 +188,12 @@ BOOST_AUTO_TEST_CASE(CipherContextGetKeySizeStatic) { 24U); BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-256-CTR") == 32U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-128-GCM") == + 16U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-192-GCM") == + 24U); + BOOST_CHECK(opensslpp::cipher_context::get_key_size_in_bytes("AES-256-GCM") == + 32U); } BOOST_AUTO_TEST_CASE(CipherContextGetIVSizeStatic) { @@ -622,3 +634,39 @@ BOOST_DATA_TEST_CASE(CipherContextRoundtripGCM, BOOST_CHECK(message == restored_message); } + +BOOST_DATA_TEST_CASE(CipherContextUpdatedIVCTR, + 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); + + buffer_type message{message_size}; + buffer_type encrypted_message{message_size}; + opensslpp::crypto_rng::generate(message); + + opensslpp::cipher_context encryption_context( + opensslpp::cipher_context_mode_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, + 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); +}