From 044171a0519093719b5f54e5fd23c5870aad493a Mon Sep 17 00:00:00 2001 From: Yura Sorokin Date: Mon, 3 Aug 2026 00:02:20 +0200 Subject: [PATCH] PBS-39 feature: Add binlog encryption config and keyring support (part 3) https://perconadev.atlassian.net/browse/PBS-39 'binsrv::binlog_file_metadata' class extended with additional optional field 'encryption' of class 'binsrv::binlog_file_encryption_metadata' that consists of two fields: - 'file_key_envelope' holding info about file key encryption, - 'file_data_envelope' holding info about binlog data file encryption. Added new 'binsrv::file_key_envelope' class with the following fields: - 'kek_id' - the identifier of the key from the keyring that was used as KEK for file key encryption operation, - 'iv_hex' - the initialization vector that was used for this file key encryption operation (optional, should be set for every mode apart from 'XXX-ECB'), - 'data_hex' - the result of the key-encryption operation, - 'tag_hex' - the AEAD tag calculated as the result of this file key encryption operation (optional, is set only in 'XXX_GCM' encryption modes). Added new 'binsrv::file_data_envelope' class with the following fields: - 'cipher' - the name of the encryption cipher used for binlog file data encryption operation (e.g. 'AES-256-CTR'), - 'iv_hex' - the initialization vector that was used for this binlog file data encryption operation (always present as file data encryption operation requires a streaming cipher like 'XXX-CTR' or 'XXX-GCM'), - 'tag_hex' - the AEAD tag calculated as the result of this binlog file data encryption operation (optional, is set only in 'XXX-GCM' encryption modes). 'binsrv::basic_keyring' interface extended with one more method 'contains()' which checks if a key with the specified identifier present in the keyring. 'binsrv::storage::binlog_record' internal class extended with additional optional 'encryption' field of class 'binsrv::storage::binlog_encryption_record' which holds info from the envelopes above in the form more suitable for encryption / decryption operations (unhexed). If encryption is enabled in the main configuration file each JSON binlog metadata file will now include additional 'encryption' subelement. 'save_binlog_metadata()' / 'load_binlog_metadata()' methods in the 'binsrv::storage' class extended with converting encryption envelopes from / to internal 'binsrv::storage::binlog_encryption_record' class instances. As an intermediate step added fake file key / iv generation functions, file key encryption and file key encryption tag calculation functions, fake file data encryption and file data encryption tag calculation functionality to the 'binsrv::storage::generate_binlog_encryption_record()' method. At the next step these functions should be replaced with proper OpenSSL calls. --- CMakeLists.txt | 13 +- src/binsrv/basic_keyring.cpp | 7 +- src/binsrv/basic_keyring.hpp | 6 +- .../binlog_file_encryption_metadata.hpp | 38 +++ .../binlog_file_encryption_metadata_fwd.hpp | 29 ++ src/binsrv/binlog_file_metadata.cpp | 9 +- src/binsrv/binlog_file_metadata.hpp | 5 +- src/binsrv/file_data_envelope.hpp | 39 +++ src/binsrv/file_data_envelope_fwd.hpp | 25 ++ src/binsrv/file_key_envelope.hpp | 40 +++ src/binsrv/file_key_envelope_fwd.hpp | 25 ++ src/binsrv/file_keyring.cpp | 5 +- src/binsrv/file_keyring.hpp | 3 +- src/binsrv/keyring_record_collection.cpp | 23 +- src/binsrv/keyring_record_collection.hpp | 4 + src/binsrv/storage.cpp | 250 +++++++++++++++++- src/binsrv/storage.hpp | 20 +- src/util/hex_value.cpp | 46 +++- src/util/hex_value.hpp | 16 +- src/util/hex_value_fwd.hpp | 11 + 20 files changed, 569 insertions(+), 45 deletions(-) create mode 100644 src/binsrv/binlog_file_encryption_metadata.hpp create mode 100644 src/binsrv/binlog_file_encryption_metadata_fwd.hpp create mode 100644 src/binsrv/file_data_envelope.hpp create mode 100644 src/binsrv/file_data_envelope_fwd.hpp create mode 100644 src/binsrv/file_key_envelope.hpp create mode 100644 src/binsrv/file_key_envelope_fwd.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a6b09b8..1d8cb95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -455,6 +455,9 @@ set(binsrv_source_files src/binsrv/binlog_file_metadata.hpp src/binsrv/binlog_file_metadata.cpp + src/binsrv/binlog_file_encryption_metadata_fwd.hpp + src/binsrv/binlog_file_encryption_metadata.hpp + src/binsrv/cout_logger.hpp src/binsrv/cout_logger.cpp @@ -468,12 +471,18 @@ set(binsrv_source_files src/binsrv/exception_handling_helpers.hpp src/binsrv/exception_handling_helpers.cpp - src/binsrv/file_logger.hpp - src/binsrv/file_logger.cpp + src/binsrv/file_data_envelope_fwd.hpp + src/binsrv/file_data_envelope.hpp + + src/binsrv/file_key_envelope_fwd.hpp + src/binsrv/file_key_envelope.hpp src/binsrv/file_keyring.hpp src/binsrv/file_keyring.cpp + src/binsrv/file_logger.hpp + src/binsrv/file_logger.cpp + src/binsrv/filesystem_storage_backend.hpp src/binsrv/filesystem_storage_backend.cpp diff --git a/src/binsrv/basic_keyring.cpp b/src/binsrv/basic_keyring.cpp index 20077b2..0ad9820 100644 --- a/src/binsrv/basic_keyring.cpp +++ b/src/binsrv/basic_keyring.cpp @@ -24,10 +24,15 @@ namespace binsrv { basic_keyring::~basic_keyring() = default; +[[nodiscard]] bool basic_keyring::contains(std::string_view key_id) const { + return do_contains(key_id); +} + [[nodiscard]] const keyring_record & -basic_keyring::get_key(std::string_view key_id) { +basic_keyring::get_key(std::string_view key_id) const { return do_get_key(key_id); } + [[nodiscard]] std::string basic_keyring::get_description() const { return do_get_description(); } diff --git a/src/binsrv/basic_keyring.hpp b/src/binsrv/basic_keyring.hpp index 6ec3b50..c1dcbaf 100644 --- a/src/binsrv/basic_keyring.hpp +++ b/src/binsrv/basic_keyring.hpp @@ -34,12 +34,14 @@ class [[nodiscard]] basic_keyring { virtual ~basic_keyring(); - [[nodiscard]] const keyring_record &get_key(std::string_view key_id); + [[nodiscard]] bool contains(std::string_view key_id) const; + [[nodiscard]] const keyring_record &get_key(std::string_view key_id) const; [[nodiscard]] std::string get_description() const; private: + [[nodiscard]] virtual bool do_contains(std::string_view key_id) const = 0; [[nodiscard]] virtual const keyring_record & - do_get_key(std::string_view key_id) = 0; + do_get_key(std::string_view key_id) const = 0; [[nodiscard]] virtual std::string do_get_description() const = 0; }; diff --git a/src/binsrv/binlog_file_encryption_metadata.hpp b/src/binsrv/binlog_file_encryption_metadata.hpp new file mode 100644 index 0000000..5281f8f --- /dev/null +++ b/src/binsrv/binlog_file_encryption_metadata.hpp @@ -0,0 +1,38 @@ +// 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 BINSRV_BINLOG_FILE_ENCRYPTION_METADATA_HPP +#define BINSRV_BINLOG_FILE_ENCRYPTION_METADATA_HPP + +#include "binsrv/binlog_file_encryption_metadata_fwd.hpp" // IWYU pragma: export + +#include "binsrv/file_data_envelope.hpp" // IWYU pragma: export +#include "binsrv/file_key_envelope.hpp" // IWYU pragma: export + +#include "util/nv_tuple.hpp" + +namespace binsrv { + +class [[nodiscard]] binlog_file_encryption_metadata + : public util::nv_tuple< + // clang-format off + util::nv<"file_key_envelope", file_key_envelope>, + util::nv<"file_data_envelope", file_data_envelope> + // clang-format on + > {}; + +} // namespace binsrv + +#endif // BINSRV_BINLOG_FILE_ENCRYPTION_METADATA_HPP diff --git a/src/binsrv/binlog_file_encryption_metadata_fwd.hpp b/src/binsrv/binlog_file_encryption_metadata_fwd.hpp new file mode 100644 index 0000000..d100748 --- /dev/null +++ b/src/binsrv/binlog_file_encryption_metadata_fwd.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_BINLOG_FILE_ENCRYPTION_METADATA_FWD_HPP +#define BINSRV_BINLOG_FILE_ENCRYPTION_METADATA_FWD_HPP + +#include + +namespace binsrv { + +class binlog_file_encryption_metadata; +using optional_binlog_file_encryption_metadata = + std::optional; + +} // namespace binsrv + +#endif // BINSRV_BINLOG_FILE_ENCRYPTION_METADATA_FWD_HPP diff --git a/src/binsrv/binlog_file_metadata.cpp b/src/binsrv/binlog_file_metadata.cpp index e5cfc12..48977e7 100644 --- a/src/binsrv/binlog_file_metadata.cpp +++ b/src/binsrv/binlog_file_metadata.cpp @@ -30,7 +30,14 @@ namespace binsrv { binlog_file_metadata::binlog_file_metadata() - : impl_{{expected_binlog_file_metadata_version}, {}, {}, {}, {}, {}, {}} {} + : impl_{{expected_binlog_file_metadata_version}, + {}, + {}, + {}, + {}, + {}, + {}, + {}} {} binlog_file_metadata::binlog_file_metadata(std::string_view data) : impl_{} { auto json_value = boost::json::parse(data); diff --git a/src/binsrv/binlog_file_metadata.hpp b/src/binsrv/binlog_file_metadata.hpp index c180aea..e0bb168 100644 --- a/src/binsrv/binlog_file_metadata.hpp +++ b/src/binsrv/binlog_file_metadata.hpp @@ -22,6 +22,8 @@ #include #include +#include "binsrv/binlog_file_encryption_metadata.hpp" // IWYU pragma: export + #include "binsrv/events/common_types.hpp" #include "binsrv/gtids/gtid_set.hpp" @@ -47,7 +49,8 @@ class [[nodiscard]] binlog_file_metadata { util::nv<"added_gtids", gtids::optional_gtid_set>, util::nv<"min_timestamp", util::ctime_timestamp>, util::nv<"max_timestamp", util::ctime_timestamp>, - util::nv<"last_sequence_number", events::seq_no_t> + util::nv<"last_sequence_number", events::seq_no_t>, + util::nv<"encryption", optional_binlog_file_encryption_metadata> // clang-format on >; diff --git a/src/binsrv/file_data_envelope.hpp b/src/binsrv/file_data_envelope.hpp new file mode 100644 index 0000000..9934350 --- /dev/null +++ b/src/binsrv/file_data_envelope.hpp @@ -0,0 +1,39 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_FILE_DATA_ENVELOPE_HPP +#define BINSRV_FILE_DATA_ENVELOPE_HPP + +#include "binsrv/file_data_envelope_fwd.hpp" // IWYU pragma: export + +#include + +#include "util/hex_value.hpp" +#include "util/nv_tuple.hpp" + +namespace binsrv { + +class [[nodiscard]] file_data_envelope + : public util::nv_tuple< + // clang-format off + util::nv<"cipher", std::string>, + util::nv<"iv_hex", util::hex_value>, + util::nv<"tag_hex", util::optional_hex_value> + // clang-format on + > {}; + +} // namespace binsrv + +#endif // BINSRV_FILE_DATA_ENVELOPE_HPP diff --git a/src/binsrv/file_data_envelope_fwd.hpp b/src/binsrv/file_data_envelope_fwd.hpp new file mode 100644 index 0000000..648e58f --- /dev/null +++ b/src/binsrv/file_data_envelope_fwd.hpp @@ -0,0 +1,25 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_FILE_DATA_ENVELOPE_FWD_HPP +#define BINSRV_FILE_DATA_ENVELOPE_FWD_HPP + +namespace binsrv { + +class file_data_envelope; + +} // namespace binsrv + +#endif // BINSRV_FILE_DATA_ENVELOPE_FWD_HPP diff --git a/src/binsrv/file_key_envelope.hpp b/src/binsrv/file_key_envelope.hpp new file mode 100644 index 0000000..88a35cb --- /dev/null +++ b/src/binsrv/file_key_envelope.hpp @@ -0,0 +1,40 @@ +// 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 BINSRV_FILE_KEY_ENVELOPE_HPP +#define BINSRV_FILE_KEY_ENVELOPE_HPP + +#include "binsrv/file_key_envelope_fwd.hpp" // IWYU pragma: export + +#include + +#include "util/hex_value.hpp" +#include "util/nv_tuple.hpp" + +namespace binsrv { + +class [[nodiscard]] file_key_envelope + : public util::nv_tuple< + // clang-format off + util::nv<"kek_id", std::string>, + util::nv<"data_hex", util::hex_value>, + util::nv<"iv_hex", util::optional_hex_value>, + util::nv<"tag_hex", util::optional_hex_value> + // clang-format on + > {}; + +} // namespace binsrv + +#endif // BINSRV_FILE_KEY_ENVELOPE_HPP diff --git a/src/binsrv/file_key_envelope_fwd.hpp b/src/binsrv/file_key_envelope_fwd.hpp new file mode 100644 index 0000000..ed0a9e5 --- /dev/null +++ b/src/binsrv/file_key_envelope_fwd.hpp @@ -0,0 +1,25 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_FILE_KEY_ENVELOPE_FWD_HPP +#define BINSRV_FILE_KEY_ENVELOPE_FWD_HPP + +namespace binsrv { + +class file_key_envelope; + +} // namespace binsrv + +#endif // BINSRV_FILE_KEY_ENVELOPE_FWD_HPP diff --git a/src/binsrv/file_keyring.cpp b/src/binsrv/file_keyring.cpp index 92bb846..e6b4363 100644 --- a/src/binsrv/file_keyring.cpp +++ b/src/binsrv/file_keyring.cpp @@ -84,8 +84,11 @@ file_keyring::file_keyring(std::string_view keyring_uri) : key_file_path_{} { file_keyring::~file_keyring() = default; +[[nodiscard]] bool file_keyring::do_contains(std::string_view key_id) const { + return keyring_records_->contains_key(key_id); +} [[nodiscard]] const keyring_record & -file_keyring::do_get_key(std::string_view key_id) { +file_keyring::do_get_key(std::string_view key_id) const { return keyring_records_->get_key(key_id); } [[nodiscard]] std::string file_keyring::do_get_description() const { diff --git a/src/binsrv/file_keyring.hpp b/src/binsrv/file_keyring.hpp index 54efd92..7b7a80a 100644 --- a/src/binsrv/file_keyring.hpp +++ b/src/binsrv/file_keyring.hpp @@ -49,8 +49,9 @@ class [[nodiscard]] file_keyring final : public basic_keyring { std::filesystem::path key_file_path_; keyring_record_collection_ptr keyring_records_; + [[nodiscard]] bool do_contains(std::string_view key_id) const override; [[nodiscard]] const keyring_record & - do_get_key(std::string_view key_id) override; + do_get_key(std::string_view key_id) const override; [[nodiscard]] std::string do_get_description() const override; }; diff --git a/src/binsrv/keyring_record_collection.cpp b/src/binsrv/keyring_record_collection.cpp index c5a8b9d..609ac3c 100644 --- a/src/binsrv/keyring_record_collection.cpp +++ b/src/binsrv/keyring_record_collection.cpp @@ -42,18 +42,19 @@ keyring_record_collection::keyring_record_collection(std::string_view file_name) validate(); } +[[nodiscard]] bool +keyring_record_collection::contains_key(std::string_view key_id) const { + return find_key_internal(key_id) != std::cend(root().get<"keys">()); +} + [[nodiscard]] const keyring_record & keyring_record_collection::get_key(std::string_view key_id) const { - const auto &keys{root().get<"keys">()}; - const auto key_it = - std::ranges::find_if(keys, [key_id](const keyring_record &key) { - return key.get<"id">() == key_id; - }); - if (key_it == std::end(keys)) { + const auto fnd_it{find_key_internal(key_id)}; + if (fnd_it == std::cend(root().get<"keys">())) { util::exception_location().raise( "key not found in the keyring record collection"); } - return *key_it; + return *fnd_it; } void keyring_record_collection::validate() const { @@ -75,4 +76,12 @@ void keyring_record_collection::validate() const { return result; } +[[nodiscard]] keyring_record_collection::key_collection::const_iterator +keyring_record_collection::find_key_internal(std::string_view key_id) const { + const auto &keys{root().get<"keys">()}; + return std::ranges::find_if(keys, [key_id](const keyring_record &key) { + return key.get<"id">() == key_id; + }); +} + } // namespace binsrv diff --git a/src/binsrv/keyring_record_collection.hpp b/src/binsrv/keyring_record_collection.hpp index 1fd58d1..74c769b 100644 --- a/src/binsrv/keyring_record_collection.hpp +++ b/src/binsrv/keyring_record_collection.hpp @@ -44,6 +44,8 @@ class [[nodiscard]] keyring_record_collection { explicit keyring_record_collection(std::string_view file_name); [[nodiscard]] const auto &root() const noexcept { return impl_; } + + [[nodiscard]] bool contains_key(std::string_view key_id) const; [[nodiscard]] const keyring_record &get_key(std::string_view key_id) const; [[nodiscard]] std::string get_description() const; @@ -51,6 +53,8 @@ class [[nodiscard]] keyring_record_collection { impl_type impl_; void validate() const; + [[nodiscard]] key_collection::const_iterator + find_key_internal(std::string_view key_id) const; }; } // namespace binsrv diff --git a/src/binsrv/storage.cpp b/src/binsrv/storage.cpp index 0c59172..3d29d91 100644 --- a/src/binsrv/storage.cpp +++ b/src/binsrv/storage.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,11 +32,14 @@ #include #include +#include + #include "binsrv/basic_keyring.hpp" #include "binsrv/basic_storage_backend.hpp" #include "binsrv/binlog_file_metadata.hpp" #include "binsrv/encryption_format_type_fwd.hpp" #include "binsrv/keyring_factory.hpp" +#include "binsrv/keyring_record.hpp" #include "binsrv/replication_mode_type.hpp" #include "binsrv/storage_backend_factory.hpp" #include "binsrv/storage_config.hpp" @@ -48,6 +53,7 @@ #include "binsrv/gtids/gtid_set.hpp" #include "util/byte_span.hpp" +#include "util/conversion_helpers.hpp" #include "util/ctime_timestamp.hpp" #include "util/exception_location_helpers.hpp" @@ -75,7 +81,14 @@ storage::storage(const storage_config &config, encryption_format = encryption_config->get<"format">(); encryption_format_ = encryption_format; keyring_ = keyring_factory::create(encryption_config->get<"keyring_uri">()); - active_kek_ = keyring_->get_key(encryption_config->get<"kek_id">()); + const auto &kek_id{encryption_config->get<"kek_id">()}; + if (!keyring_->contains(kek_id)) { + 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 + active_kek_id_ = kek_id; active_data_cipher_ = encryption_config->get<"cipher">(); } @@ -425,8 +438,9 @@ storage::purge_binlogs(const events::composite_binlog_name &target) { } [[nodiscard]] std::string storage::get_active_kek_description() const { - return is_encryption_enabled() ? active_kek_.get_description() - : "active KEK is not set"; + return is_encryption_enabled() + ? keyring_->get_key(active_kek_id_).get_description() + : "active KEK is not set"; } void storage::ensure_streaming_mode() const { @@ -465,10 +479,11 @@ void storage::update_last_checkpoint_info() { added_binlog_gtids = gtids::gtid_set{}; } - binlog_records_.emplace_back(binlog_name, events::magic_binlog_offset, - std::move(previous_binlog_gtids), - std::move(added_binlog_gtids), - util::ctime_timestamp_range{}); + binlog_records_.emplace_back( + 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()); save_binlog_metadata(get_current_binlog_record()); save_binlog_index(); return open_binlog_status::created; @@ -647,6 +662,48 @@ void storage::save_metadata() const { backend_->get_object(generate_binlog_metadata_name(binlog_name))}; binlog_file_metadata metadata{content}; + const auto encryption_info_extractor{ + [](const optional_binlog_file_encryption_metadata &encryption_metadata) + -> optional_binlog_encryption_record { + if (!encryption_metadata.has_value()) { + return std::nullopt; + } + binlog_encryption_record encryption_record{}; + + const auto &file_key_envelope{ + encryption_metadata->get<"file_key_envelope">()}; + encryption_record.kek_id = file_key_envelope.get<"kek_id">(); + const auto file_key_raw{file_key_envelope.get<"data_hex">().get_data()}; + encryption_record.file_key_encrypted_with_kek.assign( + std::cbegin(file_key_raw), std::cend(file_key_raw)); + if (file_key_envelope.get<"iv_hex">().has_value()) { + const auto file_key_iv_raw{ + file_key_envelope.get<"iv_hex">()->get_data()}; + encryption_record.iv_for_file_key_encryption.emplace( + std::cbegin(file_key_iv_raw), std::cend(file_key_iv_raw)); + } + if (file_key_envelope.get<"tag_hex">().has_value()) { + const auto file_key_tag_raw{ + file_key_envelope.get<"tag_hex">()->get_data()}; + encryption_record.tag_of_file_key_encryption.emplace( + std::cbegin(file_key_tag_raw), std::cend(file_key_tag_raw)); + } + + const auto &file_data_envelope{ + encryption_metadata->get<"file_data_envelope">()}; + encryption_record.data_cipher = file_data_envelope.get<"cipher">(); + const auto file_data_iv_raw{ + file_data_envelope.get<"iv_hex">().get_data()}; + encryption_record.iv_for_data_encryption.assign( + std::cbegin(file_data_iv_raw), std::cend(file_data_iv_raw)); + if (file_data_envelope.get<"tag_hex">().has_value()) { + const auto file_data_tag_raw{ + file_data_envelope.get<"tag_hex">()->get_data()}; + encryption_record.tag_of_data_encryption.emplace( + std::cbegin(file_data_tag_raw), std::cend(file_data_tag_raw)); + } + return encryption_record; + }}; return binlog_record{ .name = binlog_name, .size = metadata.root().get<"size">(), @@ -654,7 +711,9 @@ void storage::save_metadata() const { .added_gtids = metadata.root().get<"added_gtids">(), .timestamps = {metadata.root().get<"min_timestamp">(), metadata.root().get<"max_timestamp">()}, - .last_sequence_number = metadata.root().get<"last_sequence_number">()}; + .last_sequence_number = metadata.root().get<"last_sequence_number">(), + .encryption = + encryption_info_extractor(metadata.root().get<"encryption">())}; } void storage::validate_binlog_metadata(const binlog_record &record) const { @@ -695,6 +754,34 @@ void storage::save_binlog_metadata(const binlog_record &record) const { metadata.root().get<"max_timestamp">() = util::ctime_timestamp{record.timestamps.get_max_timestamp()}; metadata.root().get<"last_sequence_number">() = record.last_sequence_number; + const auto &record_encryption{record.encryption}; + if (record_encryption.has_value()) { + binlog_file_encryption_metadata encryption_metadata{}; + + auto &file_key_envelope{encryption_metadata.get<"file_key_envelope">()}; + file_key_envelope.get<"kek_id">() = record_encryption->kek_id; + file_key_envelope.get<"data_hex">() = + record_encryption->file_key_encrypted_with_kek; + if (record_encryption->iv_for_file_key_encryption.has_value()) { + file_key_envelope.get<"iv_hex">() = + *record_encryption->iv_for_file_key_encryption; + } + if (record_encryption->tag_of_file_key_encryption.has_value()) { + file_key_envelope.get<"tag_hex">() = + *record_encryption->tag_of_file_key_encryption; + } + + auto &file_data_envelope{encryption_metadata.get<"file_data_envelope">()}; + file_data_envelope.get<"cipher">() = record_encryption->data_cipher; + file_data_envelope.get<"iv_hex">() = + record_encryption->iv_for_data_encryption; + if (record_encryption->tag_of_data_encryption.has_value()) { + file_data_envelope.get<"tag_hex">() = + *record_encryption->tag_of_data_encryption; + } + + metadata.root().get<"encryption">() = std::move(encryption_metadata); + } const auto content{metadata.str()}; backend_->put_object(generate_binlog_metadata_name(record.name), util::as_const_byte_span(content)); @@ -759,4 +846,151 @@ void storage::load_and_validate_binlog_metadata_set( } } +[[nodiscard]] storage::optional_binlog_encryption_record +storage::generate_binlog_encryption_record() const { + if (!is_encryption_enabled()) { + return std::nullopt; + } + + const auto &keyring_record{keyring_->get_key(active_kek_id_)}; + 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)}; + 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); + 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)}; + + 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)}; + + 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)}; + + binlog_encryption_record encryption_record{ + .kek_id = active_kek_id_, + .file_key_encrypted_with_kek = file_key_encryption_result.first, + .iv_for_file_key_encryption = iv_for_file_key_encryption, + .tag_of_file_key_encryption = file_key_encryption_result.second, + .data_cipher = active_data_cipher_, + .iv_for_data_encryption = iv_for_data_encryption, + .tag_of_data_encryption = data_encryption_result.second}; + + return encryption_record; +} + } // namespace binsrv diff --git a/src/binsrv/storage.hpp b/src/binsrv/storage.hpp index 3dcd685..38ca530 100644 --- a/src/binsrv/storage.hpp +++ b/src/binsrv/storage.hpp @@ -27,7 +27,6 @@ #include "binsrv/basic_keyring_fwd.hpp" #include "binsrv/basic_storage_backend_fwd.hpp" #include "binsrv/encryption_format_type_fwd.hpp" -#include "binsrv/keyring_record.hpp" #include "binsrv/replication_mode_type_fwd.hpp" #include "binsrv/storage_config_fwd.hpp" @@ -41,11 +40,23 @@ #include "util/byte_span_fwd.hpp" #include "util/ctime_timestamp_fwd.hpp" #include "util/ctime_timestamp_range.hpp" +#include "util/hex_value.hpp" namespace binsrv { class [[nodiscard]] storage { private: + struct binlog_encryption_record { + std::string kek_id; + util::hex_value_storage file_key_encrypted_with_kek; + util::optional_hex_value_storage iv_for_file_key_encryption; + util::optional_hex_value_storage tag_of_file_key_encryption; + std::string data_cipher; + util::hex_value_storage iv_for_data_encryption; + util::optional_hex_value_storage tag_of_data_encryption; + }; + using optional_binlog_encryption_record = + std::optional; struct binlog_record { // binlog file name events::composite_binlog_name name; @@ -60,6 +71,8 @@ class [[nodiscard]] storage { // sequence_number of the last transaction seen in this file - // used for GTID rewrite-mode resume state persistence events::seq_no_t last_sequence_number{0ULL}; + // optional encryption parameters + optional_binlog_encryption_record encryption{}; }; using binlog_record_container = std::vector; @@ -183,7 +196,7 @@ class [[nodiscard]] storage { storage_construction_mode_type construction_mode_; basic_keyring_ptr keyring_; optional_encryption_format_type encryption_format_; - keyring_record active_kek_; + std::string active_kek_id_; std::string active_data_cipher_{}; basic_storage_backend_ptr backend_; @@ -266,6 +279,9 @@ class [[nodiscard]] storage { void load_and_validate_binlog_metadata_set( const storage_object_name_container &object_names, const storage_object_name_container &object_metadata_names); + + [[nodiscard]] optional_binlog_encryption_record + generate_binlog_encryption_record() const; }; } // namespace binsrv diff --git a/src/util/hex_value.cpp b/src/util/hex_value.cpp index e846a2b..a067e40 100644 --- a/src/util/hex_value.cpp +++ b/src/util/hex_value.cpp @@ -36,20 +36,26 @@ namespace util { hex_value::hex_value(const_byte_span data) : data_{std::begin(data), std::end(data)} {} -hex_value::hex_value(std::string_view value_hex) { - if ((std::size(value_hex) % 2U) != 0U) { - exception_location().raise( - "invalid hex_value length"); - } +hex_value::hex_value(std::string_view value_hex) { assign(value_hex); } - data_.resize(std::size(value_hex) / 2U); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - char *const out_it{reinterpret_cast(std::data(data_))}; - try { - boost::algorithm::unhex(value_hex, out_it); - } catch (const std::exception &) { +hex_value &hex_value::operator=(const_byte_span data) { + assign(data); + return *this; +} + +hex_value &hex_value::operator=(std::string_view value_hex) { + assign(value_hex); + return *this; +} + +void hex_value::assign(const_byte_span data) { + data_.assign(std::begin(data), std::end(data)); +} + +void hex_value::assign(std::string_view value_hex) { + if (!assign_internal(value_hex)) { exception_location().raise( - "invalid hex_value characters"); + "invalid hex_value character sequence"); } } @@ -70,6 +76,22 @@ hex_value::hex_value(std::string_view value_hex) { return result; } +[[nodiscard]] bool hex_value::assign_internal(std::string_view value_hex) { + if ((std::size(value_hex) % 2U) != 0U) { + return false; + } + + data_.resize(std::size(value_hex) / 2U); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + char *const out_it{reinterpret_cast(std::data(data_))}; + try { + boost::algorithm::unhex(value_hex, out_it); + } catch (const std::exception &) { + return false; + } + return true; +} + std::ostream &operator<<(std::ostream &output, const hex_value &value) { return output << value.to_hex_string(); } diff --git a/src/util/hex_value.hpp b/src/util/hex_value.hpp index 3350761..5fe2304 100644 --- a/src/util/hex_value.hpp +++ b/src/util/hex_value.hpp @@ -30,16 +30,16 @@ namespace util { class [[nodiscard]] hex_value { public: - // small_vector is expected to have 24 bytes overhead - static constexpr std::size_t inline_capacity{40U}; - // TODO: in c++26 change to std::inplace_vector - using buffer_type = - boost::container::small_vector; - hex_value() = default; explicit hex_value(const_byte_span data); explicit hex_value(std::string_view value_hex); + hex_value &operator=(const_byte_span data); + hex_value &operator=(std::string_view value_hex); + + void assign(const_byte_span data); + void assign(std::string_view value_hex); + [[nodiscard]] static bool try_parse(std::string_view value_sv, hex_value &value) noexcept; @@ -57,7 +57,9 @@ class [[nodiscard]] hex_value { friend bool operator==(const hex_value &, const hex_value &) = default; private: - buffer_type data_; + hex_value_storage data_; + + [[nodiscard]] bool assign_internal(std::string_view value_hex); }; } // namespace util diff --git a/src/util/hex_value_fwd.hpp b/src/util/hex_value_fwd.hpp index db7c907..f6ce85b 100644 --- a/src/util/hex_value_fwd.hpp +++ b/src/util/hex_value_fwd.hpp @@ -17,12 +17,23 @@ #define UTIL_HEX_VALUE_FWD_HPP #include +#include + +#include #include "util/nv_tuple_json_support.hpp" namespace util { +// small_vector is expected to have 24 bytes overhead +inline constexpr std::size_t expected_max_hex_value_length{40U}; +// TODO: in c++26 change to std::inplace_vector +using hex_value_storage = + boost::container::small_vector; +using optional_hex_value_storage = std::optional; + class hex_value; +using optional_hex_value = std::optional; std::ostream &operator<<(std::ostream &output, const hex_value &value);