diff --git a/repertory/librepertory/src/providers/encrypt/encrypt_provider.cpp b/repertory/librepertory/src/providers/encrypt/encrypt_provider.cpp index c383a32c..864edf8b 100644 --- a/repertory/librepertory/src/providers/encrypt/encrypt_provider.cpp +++ b/repertory/librepertory/src/providers/encrypt/encrypt_provider.cpp @@ -806,11 +806,11 @@ auto encrypt_provider::read_file_bytes(const std::string &api_path, } else if (not reader_lookup_.contains(file_data.source_path)) { auto info{std::make_shared()}; info->reader = std::make_unique( - api_path, file_data.source_path, [&stop_requested]() -> bool { return stop_requested || app_config::get_stop_requested(); }, - cfg.encryption_token, std::move(file_data.iv_list)); + api_path, file_data.source_path, cfg.encryption_token, + std::move(file_data.iv_list)); reader_lookup_[file_data.source_path] = info; } diff --git a/support/include/utils/config.hpp b/support/include/utils/config.hpp index 0d7348bc..63dab05f 100644 --- a/support/include/utils/config.hpp +++ b/support/include/utils/config.hpp @@ -425,6 +425,8 @@ using vlc_string_t = std::unique_ptr; namespace repertory { using data_buffer = std::vector; +using data_span = std::span; +using data_cspan = std::span; using mutex_lock = std::lock_guard; using recur_mutex_lock = std::lock_guard; using stop_type = std::atomic_bool; diff --git a/support/include/utils/encrypting_reader.hpp b/support/include/utils/encrypting_reader.hpp index 0170589c..1dd93135 100644 --- a/support/include/utils/encrypting_reader.hpp +++ b/support/include/utils/encrypting_reader.hpp @@ -38,14 +38,15 @@ public: std::optional relative_parent_path, std::size_t error_return = 0U); - encrypting_reader(std::string_view encrypted_file_path, - std::string_view source_path, - stop_type_callback stop_requested_cb, - std::string_view token, std::size_t error_return = 0U); + encrypting_reader(stop_type_callback stop_requested_cb, + std::string_view encrypted_file_path, + std::string_view source_path, std::string_view token, + std::size_t error_return = 0U); encrypting_reader( + stop_type_callback stop_requested_cb, std::string_view encrypted_file_path, std::string_view source_path, - stop_type_callback stop_requested_cb, std::string_view token, + std::string_view token, std::vector> iv_list, @@ -57,16 +58,37 @@ public: std::optional relative_parent_path, std::size_t error_return = 0U); - encrypting_reader(std::string_view encrypted_file_path, - std::string_view source_path, - stop_type_callback stop_requested_cb, - std::string_view token, kdf_config cfg, - std::size_t error_return = 0U); + encrypting_reader(stop_type_callback stop_requested_cb, + std::string_view encrypted_file_path, + std::string_view source_path, std::string_view token, + kdf_config cfg, std::size_t error_return = 0U); encrypting_reader( + stop_type_callback stop_requested_cb, std::string_view encrypted_file_path, std::string_view source_path, - stop_type_callback stop_requested_cb, std::string_view token, - kdf_config cfg, + std::string_view token, kdf_config cfg, + std::vector> + iv_list, + std::size_t error_return = 0U); + + encrypting_reader(std::string_view file_name, std::string_view source_path, + stop_type_callback stop_requested_cb, + const utils::hash::hash_256_t &master_key, + const kdf_config &cfg, + std::optional relative_parent_path, + std::size_t error_return = 0U); + + encrypting_reader(stop_type_callback stop_requested_cb, + std::string_view encrypted_file_path, + std::string_view source_path, + const utils::hash::hash_256_t &master_key, + const kdf_config &cfg, std::size_t error_return = 0U); + + encrypting_reader( + stop_type_callback stop_requested_cb, + std::string_view encrypted_file_path, std::string_view source_path, + const utils::hash::hash_256_t &master_key, const kdf_config &cfg, std::vector> iv_list, @@ -82,10 +104,13 @@ public: public: using iostream = std::basic_iostream>; + using kdf_pair_t = std::pair; + using key_pair_t = + std::pair; using streambuf = std::basic_streambuf>; private: - utils::hash::hash_256_t key_; + key_pair_t keys_; stop_type_callback stop_requested_cb_; size_t error_return_; std::unique_ptr source_file_; @@ -97,7 +122,7 @@ private: private: std::unordered_map chunk_buffers_; - std::optional kdf_header_; + std::optional kdf_headers_; std::size_t last_data_chunk_{}; std::size_t last_data_chunk_size_{}; std::uint64_t read_offset_{}; @@ -113,6 +138,11 @@ private: void common_initialize(bool procces_iv_list); + void common_initialize_kdf_data(const kdf_config &cfg, + const utils::hash::hash_256_t &master_key); + + void common_initialize_kdf_path(const utils::hash::hash_256_t &master_key); + void create_encrypted_paths(std::string_view file_name, std::optional relative_parent_path); @@ -157,7 +187,11 @@ public: return iv_list_; } - [[nodiscard]] auto get_kdf_config() const -> std::optional; + [[nodiscard]] auto get_kdf_config_for_data() const + -> std::optional; + + [[nodiscard]] auto get_kdf_config_for_path() const + -> std::optional; [[nodiscard]] auto get_stop_requested() const -> bool { return stop_requested_cb_(); diff --git a/support/include/utils/encryption.hpp b/support/include/utils/encryption.hpp index 118bd61a..dbc65ee1 100644 --- a/support/include/utils/encryption.hpp +++ b/support/include/utils/encryption.hpp @@ -25,6 +25,9 @@ #include "utils/config.hpp" +#if defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON) +#include "utils/collection.hpp" +#endif // defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON) #include "utils/error.hpp" #include "utils/hash.hpp" @@ -53,7 +56,7 @@ enum class opslimit_level : std::uint8_t { }; [[nodiscard]] inline auto get_memlimit(memlimit_level memlimit) -> size_t { - constexpr const auto mib512{512ULL * 1024ULL * 1024ULL}; + constexpr auto mib512{512ULL * 1024ULL * 1024ULL}; switch (memlimit) { case memlimit_level::level1: @@ -88,25 +91,64 @@ enum class opslimit_level : std::uint8_t { return crypto_pwhash_OPSLIMIT_MODERATE; } +enum class kdf_context : std::uint8_t { + data, + path, + undefined, +}; +using kdf_ctx_t = std::array; + +namespace kdf { +constexpr inline std::array< + kdf_ctx_t, static_cast(kdf_context::undefined) + 1U> + KDF_CTXS{ + { + {'D', 'A', 'T', 'A', '_', 'C', 'T', 'X'}, + {'F', 'I', 'L', 'E', '_', 'C', 'T', 'X'}, + {'D', 'E', 'F', 'L', '_', 'C', 'T', 'X'}, + }, + }; +} // namespace kdf + +[[nodiscard]] constexpr inline auto get_kdf_context_name(kdf_context ctx) + -> kdf_ctx_t { + const auto idx = static_cast(ctx); + return idx < kdf::KDF_CTXS.size() ? kdf::KDF_CTXS.at(idx) + : kdf::KDF_CTXS.back(); +} + #pragma pack(push, 1) struct kdf_config final { - static constexpr std::uint32_t repertory_magic{0x52505432}; using salt_t = std::array; - std::uint32_t magic{repertory_magic}; kdf_version version{kdf_version::v1}; kdf_type kdf{kdf_type::argon2id}; memlimit_level memlimit{memlimit_level::level3}; opslimit_level opslimit{opslimit_level::level2}; + std::uint64_t unique_id{}; salt_t salt{}; std::uint64_t checksum{}; - [[nodiscard]] static auto from_header(std::span data, - kdf_config &cfg) -> bool; + template + [[nodiscard]] auto create_subkey(kdf_context ctx, std::size_t unique_id_, + const hash_t &master_key) const + -> std::pair { + hash_t sub_key; + crypto_kdf_derive_from_key(sub_key.data(), sub_key.size(), unique_id_, + get_kdf_context_name(ctx).data(), + master_key.data()); + auto cfg = *this; + cfg.unique_id = unique_id_; + cfg.checksum = cfg.generate_checksum(); + return {sub_key, cfg}; + } + + [[nodiscard]] static auto from_header(data_cspan data, kdf_config &cfg) + -> bool; [[nodiscard]] auto generate_checksum() const -> std::uint64_t; - void generate_salt(); + void seal(); [[nodiscard]] static constexpr auto size() -> std::size_t { return sizeof(kdf_config); @@ -160,21 +202,18 @@ template template [[nodiscard]] inline bool -detect_and_recreate_key(string_t password, - std::span header, hash_t &key, +detect_and_recreate_key(string_t password, data_cspan header, hash_t &key, std::optional &cfg); template [[nodiscard]] inline bool -detect_and_recreate_key(std::string_view password, - std::span header, hash_t &key, - std::optional &cfg); +detect_and_recreate_key(std::string_view password, data_cspan header, + hash_t &key, std::optional &cfg); template [[nodiscard]] inline bool -detect_and_recreate_key(std::wstring_view password, - std::span header, hash_t &key, - std::optional &cfg); +detect_and_recreate_key(std::wstring_view password, data_cspan header, + hash_t &key, std::optional &cfg); [[nodiscard]] auto decrypt_file_name(std::string_view encryption_token, std::string &file_name) -> bool; @@ -396,7 +435,7 @@ read_encrypted_range(const http_range &range, template auto create_key_argon2id(string_t password, kdf_config &cfg, utils::hash::hash_256_t &key) -> bool { - cfg.generate_salt(); + cfg.seal(); return recreate_key_argon2id(password, cfg, key); } @@ -531,8 +570,7 @@ inline auto recreate_key(std::wstring_view password, const kdf_config &cfg) } template -inline bool detect_and_recreate_key(string_t password, - std::span header, +inline bool detect_and_recreate_key(string_t password, data_cspan header, hash_t &key, std::optional &cfg) { if (header.size() >= kdf_config::size()) { @@ -550,8 +588,7 @@ inline bool detect_and_recreate_key(string_t password, template inline bool detect_and_recreate_key(std::string_view password, - std::span header, - hash_t &key, + data_cspan header, hash_t &key, std::optional &cfg) { return detect_and_recreate_key(password, header, key, cfg); @@ -559,14 +596,96 @@ inline bool detect_and_recreate_key(std::string_view password, template inline bool detect_and_recreate_key(std::wstring_view password, - std::span header, - hash_t &key, + data_cspan header, hash_t &key, std::optional &cfg) { return detect_and_recreate_key(password, header, key, cfg); } + #endif // defined(PROJECT_ENABLE_BOOST) } // namespace repertory::utils::encryption +#if defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON) +NLOHMANN_JSON_NAMESPACE_BEGIN + +namespace kdf { +inline constexpr std::string_view JSON_CHECKSUM{"checksum"}; +inline constexpr std::string_view JSON_KDF{"kdf"}; +inline constexpr std::string_view JSON_MEMLIMIT{"memlimit"}; +inline constexpr std::string_view JSON_OPSLIMIT{"opslimit"}; +inline constexpr std::string_view JSON_SALT{"salt"}; +inline constexpr std::string_view JSON_UNIQUE_ID{"unique_id"}; +inline constexpr std::string_view JSON_VERSION{"version"}; +} // namespace kdf + +template <> +struct adl_serializer { + static void + to_json(json &data, + const repertory::utils::encryption::kdf_config::salt_t &value) { + data = repertory::utils::collection::to_hex_string(value); + } + + static void + from_json(const json &data, + repertory::utils::encryption::kdf_config::salt_t &value) { + REPERTORY_USES_FUNCTION_NAME(); + + repertory::data_buffer buffer{}; + if (not repertory::utils::collection::from_hex_string( + data.get(), buffer)) { + throw repertory::utils::error::create_exception( + function_name, { + "failed to convert hex string to salt", + data.get(), + }); + } + + if (buffer.size() != value.size()) { + throw repertory::utils::error::create_exception( + function_name, { + "unexpected length for salt after hex conversion", + "expected", + std::to_string(value.size()), + "actual", + std::to_string(buffer.size()), + }); + } + + std::copy_n(buffer.begin(), value.size(), value.begin()); + } +}; + +template <> struct adl_serializer { + static void to_json(json &data, + const repertory::utils::encryption::kdf_config &value) { + data[kdf::JSON_CHECKSUM] = value.checksum; + data[kdf::JSON_KDF] = value.kdf; + data[kdf::JSON_MEMLIMIT] = value.memlimit; + data[kdf::JSON_OPSLIMIT] = value.opslimit; + data[kdf::JSON_SALT] = value.salt; + data[kdf::JSON_UNIQUE_ID] = value.unique_id; + data[kdf::JSON_VERSION] = value.version; + } + + static void from_json(const json &data, + repertory::utils::encryption::kdf_config &value) { + data.at(kdf::JSON_CHECKSUM).get_to(value.checksum); + data.at(kdf::JSON_KDF) + .get_to(value.kdf); + data.at(kdf::JSON_MEMLIMIT) + .get_to(value.memlimit); + data.at(kdf::JSON_OPSLIMIT) + .get_to(value.opslimit); + data.at(kdf::JSON_SALT) + .get_to(value.salt); + data.at(kdf::JSON_UNIQUE_ID).get_to(value.unique_id); + data.at(kdf::JSON_VERSION) + .get_to(value.version); + } +}; +NLOHMANN_JSON_NAMESPACE_END +#endif // defined(PROJECT_ENABLE_BOOST) && defined(PROJECT_ENABLE_JSON) + #endif // defined(PROJECT_ENABLE_LIBSODIUM) #endif // REPERTORY_INCLUDE_UTILS_ENCRYPTION_HPP_ diff --git a/support/include/utils/ttl_cache.hpp b/support/include/utils/ttl_cache.hpp index 5c751122..cd9d9807 100644 --- a/support/include/utils/ttl_cache.hpp +++ b/support/include/utils/ttl_cache.hpp @@ -12,7 +12,7 @@ public: using entry_t = atomic_t; using entry_ptr_t = std::shared_ptr; - static constexpr const auto default_expiration{duration(60000U)}; + static constexpr auto default_expiration{duration(60000U)}; private: struct entry final { diff --git a/support/src/utils/encrypting_reader.cpp b/support/src/utils/encrypting_reader.cpp index aee47b38..2e26786d 100644 --- a/support/src/utils/encrypting_reader.cpp +++ b/support/src/utils/encrypting_reader.cpp @@ -181,7 +181,8 @@ encrypting_reader::encrypting_reader( std::string_view file_name, std::string_view source_path, stop_type_callback stop_requested_cb, std::string_view token, std::optional relative_parent_path, std::size_t error_return) - : key_(utils::encryption::generate_key(token)), + : keys_(utils::encryption::generate_key(token), + utils::encryption::generate_key(token)), stop_requested_cb_(std::move(stop_requested_cb)), error_return_(error_return), source_file_(utils::file::file::open_or_create_file(source_path, true)) { @@ -189,12 +190,13 @@ encrypting_reader::encrypting_reader( create_encrypted_paths(file_name, relative_parent_path); } -encrypting_reader::encrypting_reader(std::string_view encrypted_file_path, +encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb, + std::string_view encrypted_file_path, std::string_view source_path, - stop_type_callback stop_requested_cb, std::string_view token, std::size_t error_return) - : key_(utils::encryption::generate_key(token)), + : keys_(utils::encryption::generate_key(token), + utils::encryption::generate_key(token)), stop_requested_cb_(std::move(stop_requested_cb)), error_return_(error_return), source_file_(utils::file::file::open_or_create_file(source_path, true)), @@ -205,13 +207,14 @@ encrypting_reader::encrypting_reader(std::string_view encrypted_file_path, } encrypting_reader::encrypting_reader( - std::string_view encrypted_file_path, std::string_view source_path, - stop_type_callback stop_requested_cb, std::string_view token, + stop_type_callback stop_requested_cb, std::string_view encrypted_file_path, + std::string_view source_path, std::string_view token, std::vector< std::array> iv_list, std::size_t error_return) - : key_(utils::encryption::generate_key(token)), + : keys_(utils::encryption::generate_key(token), + utils::encryption::generate_key(token)), stop_requested_cb_(std::move(stop_requested_cb)), error_return_(error_return), source_file_(utils::file::file::open_or_create_file(source_path, true)), @@ -227,48 +230,50 @@ encrypting_reader::encrypting_reader( stop_type_callback stop_requested_cb, std::string_view token, kdf_config cfg, std::optional relative_parent_path, std::size_t error_return) - : key_( - utils::encryption::generate_key(token, cfg)), + : keys_( + utils::encryption::generate_key(token, cfg), + utils::encryption::generate_key(token)), stop_requested_cb_(std::move(stop_requested_cb)), error_return_(error_return), source_file_(utils::file::file::open_or_create_file(source_path, true)) { - kdf_header_ = cfg.to_header(); + kdf_headers_ = {cfg.to_header(), cfg.to_header()}; data_buffer result; utils::encryption::encrypt_data( - key_, reinterpret_cast(file_name.data()), + keys_.second, reinterpret_cast(file_name.data()), file_name.size(), result); common_initialize(true); create_encrypted_paths(file_name, relative_parent_path); } -encrypting_reader::encrypting_reader(std::string_view encrypted_file_path, +encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb, + std::string_view encrypted_file_path, std::string_view source_path, - stop_type_callback stop_requested_cb, std::string_view token, kdf_config cfg, std::size_t error_return) - : key_( - utils::encryption::generate_key(token, cfg)), + : keys_( + utils::encryption::generate_key(token, cfg), + utils::encryption::generate_key(token)), stop_requested_cb_(std::move(stop_requested_cb)), error_return_(error_return), source_file_(utils::file::file::open_or_create_file(source_path, true)), encrypted_file_name_( utils::path::strip_to_file_name(std::string{encrypted_file_path})), encrypted_file_path_(encrypted_file_path) { - kdf_header_ = cfg.to_header(); + kdf_headers_ = {cfg.to_header(), cfg.to_header()}; common_initialize(true); } encrypting_reader::encrypting_reader( - std::string_view encrypted_file_path, std::string_view source_path, - stop_type_callback stop_requested_cb, std::string_view token, - kdf_config cfg, + stop_type_callback stop_requested_cb, std::string_view encrypted_file_path, + std::string_view source_path, std::string_view token, kdf_config cfg, std::vector< std::array> iv_list, std::size_t error_return) - : key_( - utils::encryption::generate_key(token, cfg)), + : keys_( + utils::encryption::generate_key(token, cfg), + utils::encryption::generate_key(token)), stop_requested_cb_(std::move(stop_requested_cb)), error_return_(error_return), source_file_(utils::file::file::open_or_create_file(source_path, true)), @@ -276,12 +281,74 @@ encrypting_reader::encrypting_reader( utils::path::strip_to_file_name(std::string{encrypted_file_path})), encrypted_file_path_(encrypted_file_path), iv_list_(std::move(iv_list)) { - kdf_header_ = cfg.to_header(); + kdf_headers_ = {cfg.to_header(), cfg.to_header()}; + common_initialize(false); +} + +encrypting_reader::encrypting_reader( + std::string_view file_name, std::string_view source_path, + stop_type_callback stop_requested_cb, + const utils::hash::hash_256_t &master_key, const kdf_config &cfg, + std::optional relative_parent_path, std::size_t error_return) + : stop_requested_cb_(std::move(stop_requested_cb)), + error_return_(error_return), + source_file_(utils::file::file::open_or_create_file(source_path, true)) { + common_initialize_kdf_data(cfg, master_key); + + kdf_config path_cfg; + std::tie(keys_.second, path_cfg) = cfg.create_subkey( + kdf_context::path, utils::generate_secure_random(), + master_key); + kdf_headers_->second = path_cfg.to_header(); + + data_buffer result; + utils::encryption::encrypt_data( + keys_.second, reinterpret_cast(file_name.data()), + file_name.size(), result); + common_initialize(true); + create_encrypted_paths(file_name, relative_parent_path); +} + +encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb, + std::string_view encrypted_file_path, + std::string_view source_path, + const utils::hash::hash_256_t &master_key, + const kdf_config &cfg, + std::size_t error_return) + : keys_(), + stop_requested_cb_(std::move(stop_requested_cb)), + error_return_(error_return), + source_file_(utils::file::file::open_or_create_file(source_path, true)), + encrypted_file_name_( + utils::path::strip_to_file_name(std::string{encrypted_file_path})), + encrypted_file_path_(encrypted_file_path) { + common_initialize_kdf_data(cfg, master_key); + common_initialize_kdf_path(master_key); + common_initialize(true); +} + +encrypting_reader::encrypting_reader( + stop_type_callback stop_requested_cb, std::string_view encrypted_file_path, + std::string_view source_path, const utils::hash::hash_256_t &master_key, + const kdf_config &cfg, + std::vector< + std::array> + iv_list, + std::size_t error_return) + : stop_requested_cb_(std::move(stop_requested_cb)), + error_return_(error_return), + source_file_(utils::file::file::open_or_create_file(source_path, true)), + encrypted_file_name_( + utils::path::strip_to_file_name(std::string{encrypted_file_path})), + encrypted_file_path_(encrypted_file_path), + iv_list_(std::move(iv_list)) { + common_initialize_kdf_data(cfg, master_key); + common_initialize_kdf_path(master_key); common_initialize(false); } encrypting_reader::encrypting_reader(const encrypting_reader &reader) - : key_(reader.key_), + : keys_(reader.keys_), stop_requested_cb_(reader.stop_requested_cb_), error_return_(reader.error_return_), source_file_( @@ -290,7 +357,7 @@ encrypting_reader::encrypting_reader(const encrypting_reader &reader) encrypted_file_path_(reader.encrypted_file_path_), iv_list_(reader.iv_list_), chunk_buffers_(reader.chunk_buffers_), - kdf_header_(reader.kdf_header_), + kdf_headers_(reader.kdf_headers_), last_data_chunk_(reader.last_data_chunk_), last_data_chunk_size_(reader.last_data_chunk_size_), read_offset_(reader.read_offset_), @@ -307,46 +374,6 @@ encrypting_reader::encrypting_reader(const encrypting_reader &reader) } } -void encrypting_reader::common_initialize(bool procces_iv_list) { - REPERTORY_USES_FUNCTION_NAME(); - - if (not *source_file_) { - throw utils::error::create_exception(function_name, - { - "file open failed", - source_file_->get_path(), - }); - } - - auto opt_size = source_file_->size(); - if (not opt_size.has_value()) { - throw utils::error::create_exception(function_name, - { - "failed to get file size", - source_file_->get_path(), - }); - } - auto file_size = opt_size.value(); - - auto total_chunks = utils::divide_with_ceiling( - file_size, static_cast(data_chunk_size_)); - total_size_ = file_size + (total_chunks * encryption_header_size) + - (kdf_header_.has_value() ? kdf_header_->size() : 0U); - last_data_chunk_ = total_chunks - 1U; - last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size - : (file_size % data_chunk_size_) == 0U - ? data_chunk_size_ - : file_size % data_chunk_size_; - if (not procces_iv_list) { - return; - } - - iv_list_.resize(total_chunks); - for (auto &data : iv_list_) { - randombytes_buf(data.data(), data.size()); - } -} - auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size, bool uses_kdf) -> std::uint64_t { @@ -382,15 +409,88 @@ auto encrypting_reader::calculate_encrypted_size(std::string_view source_path, (uses_kdf ? kdf_config::size() : 0U); } +void encrypting_reader::common_initialize(bool procces_iv_list) { + REPERTORY_USES_FUNCTION_NAME(); + + if (not *source_file_) { + throw utils::error::create_exception(function_name, + { + "file open failed", + source_file_->get_path(), + }); + } + + auto opt_size = source_file_->size(); + if (not opt_size.has_value()) { + throw utils::error::create_exception(function_name, + { + "failed to get file size", + source_file_->get_path(), + }); + } + auto file_size = opt_size.value(); + + auto total_chunks = utils::divide_with_ceiling( + file_size, static_cast(data_chunk_size_)); + total_size_ = file_size + (total_chunks * encryption_header_size) + + (kdf_headers_.has_value() ? kdf_headers_->first.size() : 0U); + last_data_chunk_ = total_chunks - 1U; + last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size + : (file_size % data_chunk_size_) == 0U + ? data_chunk_size_ + : file_size % data_chunk_size_; + if (not procces_iv_list) { + return; + } + + iv_list_.resize(total_chunks); + for (auto &data : iv_list_) { + randombytes_buf(data.data(), data.size()); + } +} + +void encrypting_reader::common_initialize_kdf_data( + const kdf_config &cfg, const utils::hash::hash_256_t &master_key) { + auto [data_key, data_cfg] = cfg.create_subkey( + kdf_context::data, utils::generate_secure_random(), + master_key); + keys_.first = std::move(data_key); + kdf_headers_ = {data_cfg.to_header(), {}}; +} + +void encrypting_reader::common_initialize_kdf_path( + const utils::hash::hash_256_t &master_key) { + REPERTORY_USES_FUNCTION_NAME(); + + data_buffer buffer; + if (not utils::collection::from_hex_string(encrypted_file_path_, buffer)) { + throw utils::error::create_exception( + function_name, {"failed to convert encrypted path from hex to bytes"}); + } + + kdf_config path_cfg; + if (not kdf_config::from_header(buffer, path_cfg)) { + throw utils::error::create_exception( + function_name, {"failed to create path kdf config from header"}); + } + + utils::hash::hash_256_t path_key; + std::tie(path_key, std::ignore) = + path_cfg.create_subkey(kdf_context::path, path_cfg.unique_id, master_key); + + kdf_headers_->second = path_cfg.to_header(); +} + void encrypting_reader::create_encrypted_paths( std::string_view file_name, std::optional relative_parent_path) { data_buffer result; utils::encryption::encrypt_data( - key_, reinterpret_cast(file_name.data()), + keys_.second, reinterpret_cast(file_name.data()), file_name.size(), result); - if (kdf_header_.has_value()) { - result.insert(result.begin(), kdf_header_->begin(), kdf_header_->end()); + if (kdf_headers_.has_value()) { + result.insert(result.begin(), kdf_headers_->second.begin(), + kdf_headers_->second.end()); } encrypted_file_name_ = utils::collection::to_hex_string(result); @@ -403,10 +503,11 @@ void encrypting_reader::create_encrypted_paths( utils::string::split(relative_parent_path.value(), utils::path::directory_seperator, false)) { utils::encryption::encrypt_data( - key_, reinterpret_cast(part.c_str()), + keys_.second, reinterpret_cast(part.c_str()), strnlen(part.c_str(), part.size()), result); - if (kdf_header_.has_value()) { - result.insert(result.begin(), kdf_header_->begin(), kdf_header_->end()); + if (kdf_headers_.has_value()) { + result.insert(result.begin(), kdf_headers_->second.begin(), + kdf_headers_->second.end()); } encrypted_file_path_ += '/' + utils::collection::to_hex_string(result); @@ -421,15 +522,35 @@ auto encrypting_reader::create_iostream() const std::make_unique(*this)); } -auto encrypting_reader::get_kdf_config() const -> std::optional { +auto encrypting_reader::get_kdf_config_for_data() const + -> std::optional { REPERTORY_USES_FUNCTION_NAME(); - if (not kdf_header_.has_value()) { + if (not kdf_headers_.has_value()) { return std::nullopt; } kdf_config cfg; - if (not kdf_config::from_header(kdf_header_.value(), cfg)) { + if (not kdf_config::from_header(kdf_headers_->first, cfg)) { + throw utils::error::create_exception(function_name, + { + "invalid kdf header", + }); + } + + return cfg; +} + +auto encrypting_reader::get_kdf_config_for_path() const + -> std::optional { + REPERTORY_USES_FUNCTION_NAME(); + + if (not kdf_headers_.has_value()) { + return std::nullopt; + } + + kdf_config cfg; + if (not kdf_config::from_header(kdf_headers_->second, cfg)) { throw utils::error::create_exception(function_name, { "invalid kdf header", @@ -443,6 +564,8 @@ auto encrypting_reader::reader_function(char *buffer, size_t size, size_t nitems) -> size_t { REPERTORY_USES_FUNCTION_NAME(); + std::span dest(buffer, size); + auto read_size = static_cast(size) * static_cast(nitems); if (read_size == 0U) { @@ -453,17 +576,17 @@ auto encrypting_reader::reader_function(char *buffer, size_t size, std::size_t total_read{}; auto total_size{total_size_}; - if (kdf_header_.has_value()) { - total_size -= kdf_header_->size(); + if (kdf_headers_.has_value()) { + auto &hdr = kdf_headers_->first; + total_size -= hdr.size(); - if (read_offset < kdf_header_->size()) { + if (read_offset < hdr.size()) { auto to_read{ - utils::calculate_read_size(kdf_header_->size(), read_size, - read_offset), + utils::calculate_read_size(hdr.size(), read_size, read_offset), }; read_offset_ += to_read; - std::memcpy(buffer, &kdf_header_->data()[read_offset], to_read); + std::memcpy(&dest[total_read], &hdr.at(read_offset), to_read); if (read_size - to_read == 0) { return to_read; } @@ -471,9 +594,8 @@ auto encrypting_reader::reader_function(char *buffer, size_t size, read_offset = 0U; read_size -= to_read; total_read += to_read; - buffer += to_read; } else { - read_offset -= kdf_header_->size(); + read_offset -= hdr.size(); } } @@ -501,8 +623,8 @@ auto encrypting_reader::reader_function(char *buffer, size_t size, static_cast(data_chunk_size_), &bytes_read); if (ret) { - utils::encryption::encrypt_data(iv_list_.at(chunk), key_, file_data, - chunk_buffer); + utils::encryption::encrypt_data(iv_list_.at(chunk), keys_.first, + file_data, chunk_buffer); } } else if (chunk != 0U) { chunk_buffers_.erase(chunk - 1U); @@ -510,7 +632,7 @@ auto encrypting_reader::reader_function(char *buffer, size_t size, auto &chunk_buffer = chunk_buffers_[chunk]; auto to_read = std::min(chunk_buffer.size() - chunk_offset, remain); - std::memcpy(buffer + total_read, &chunk_buffer[chunk_offset], to_read); + std::memcpy(&dest[total_read], &chunk_buffer[chunk_offset], to_read); total_read += to_read; remain -= to_read; chunk_offset = 0U; diff --git a/support/src/utils/encryption.cpp b/support/src/utils/encryption.cpp index 96990b08..d8efd0a8 100644 --- a/support/src/utils/encryption.cpp +++ b/support/src/utils/encryption.cpp @@ -28,11 +28,24 @@ #include "utils/hash.hpp" #include "utils/path.hpp" +namespace { +constexpr auto resize_by(repertory::data_span &data, std::size_t /* size */) + -> repertory::data_span & { + return data; +} + +auto resize_by(repertory::data_buffer &data, std::size_t size) + -> repertory::data_buffer & { + data.resize(data.size() + size); + return data; +} +} // namespace + namespace repertory::utils::encryption { auto kdf_config::to_header() const -> data_buffer { kdf_config tmp{*this}; tmp.checksum = boost::endian::native_to_big(tmp.checksum); - tmp.magic = boost::endian::native_to_big(tmp.magic); + tmp.unique_id = boost::endian::native_to_big(tmp.unique_id); data_buffer ret(size()); std::memcpy(ret.data(), &tmp, ret.size()); @@ -49,24 +62,15 @@ auto kdf_config::generate_checksum() const -> std::uint64_t { return *reinterpret_cast(hash.data()); } -void kdf_config::generate_salt() { - randombytes_buf(salt.data(), salt.size()); - checksum = generate_checksum(); -} - -auto kdf_config::from_header(std::span data, - kdf_config &cfg) -> bool { +auto kdf_config::from_header(data_cspan data, kdf_config &cfg) -> bool { if (data.size() < kdf_config::size()) { return false; } std::memcpy(&cfg, data.data(), kdf_config::size()); - cfg.magic = boost::endian::big_to_native(cfg.magic); - if (cfg.magic != repertory_magic) { - return false; - } cfg.checksum = boost::endian::big_to_native(cfg.checksum); + cfg.unique_id = boost::endian::big_to_native(cfg.unique_id); return cfg.version == kdf_version::v1 && cfg.kdf == kdf_type::argon2id && cfg.memlimit >= memlimit_level::level1 && cfg.memlimit <= memlimit_level::level4 && @@ -75,6 +79,11 @@ auto kdf_config::from_header(std::span data, cfg.checksum == cfg.generate_checksum(); } +void kdf_config::seal() { + randombytes_buf(salt.data(), salt.size()); + checksum = generate_checksum(); +} + auto decrypt_file_path(std::string_view encryption_token, std::string &file_path) -> bool { std::vector decrypted_parts; @@ -140,16 +149,6 @@ auto decrypt_file_name(std::string_view encryption_token, const kdf_config &cfg, file_name); } -constexpr auto resize_by(std::span &data, std::size_t size) - -> std::span & { - return data; -} - -static auto resize_by(data_buffer &data, std::size_t size) -> data_buffer & { - data.resize(data.size() + size); - return data; -} - template [[nodiscard]] auto read_encrypted_range(http_range range, const utils::hash::hash_256_t &key, @@ -215,12 +214,12 @@ read_encrypted_range(http_range range, const utils::hash::hash_256_t &key, auto data_size = static_cast(std::min( remain, static_cast(data_chunk_size - source_offset))); - resize_by(data, data_size); std::copy(std::next(source_buffer.begin(), static_cast(source_offset)), std::next(source_buffer.begin(), static_cast(source_offset + data_size)), - std::next(data.begin(), static_cast(bytes_read))); + std::next(resize_by(data, data_size).begin(), + static_cast(bytes_read))); remain -= data_size; bytes_read += data_size; source_offset = 0U; @@ -243,8 +242,8 @@ auto read_encrypted_range(const http_range &range, const http_range &range, const utils::hash::hash_256_t &key, bool uses_kdf, reader_func_t reader_func, std::uint64_t total_size, unsigned char *data, std::size_t size, std::size_t &bytes_read) -> bool { - std::span dest_buffer(data, size); - return read_encrypted_range>( + data_span dest_buffer(data, size); + return read_encrypted_range( range, key, reader_func, total_size, dest_buffer, uses_kdf ? kdf_config::size() : 0U, bytes_read); } diff --git a/support/src/utils/string.cpp b/support/src/utils/string.cpp index 4eb56c28..e62d105b 100644 --- a/support/src/utils/string.cpp +++ b/support/src/utils/string.cpp @@ -51,7 +51,7 @@ auto from_utf8(std::string_view str) -> std::wstring { while (idx < len) { UChar32 uni_ch{}; U8_NEXT(str_ptr, idx, len, uni_ch); - if (uni_ch < 0 || !U_IS_UNICODE_CHAR(uni_ch)) { + if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) { throw std::runtime_error("from_utf8: invalid UTF-8 sequence"); } std::array units{}; @@ -71,7 +71,7 @@ auto from_utf8(std::string_view str) -> std::wstring { while (idx < len) { UChar32 uni_ch{}; U8_NEXT(str_ptr, idx, len, uni_ch); - if (uni_ch < 0 || !U_IS_UNICODE_CHAR(uni_ch)) { + if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) { throw std::runtime_error("from_utf8: invalid UTF-8 sequence"); } out.push_back(static_cast(uni_ch)); @@ -183,7 +183,7 @@ auto to_utf8(std::wstring_view str) -> std::string { while (idx < len) { UChar32 uni_ch{}; U16_NEXT(u16, idx, len, uni_ch); - if (uni_ch < 0 || !U_IS_UNICODE_CHAR(uni_ch)) { + if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) { throw std::runtime_error("to_utf8: invalid UTF-16 sequence"); } std::array buf{}; @@ -197,9 +197,9 @@ auto to_utf8(std::wstring_view str) -> std::string { static_cast(off)); } #else // WCHAR_MAX > 0xFFFF - for (auto cur_ch : str) { + for (const auto &cur_ch : str) { auto uni_char{static_cast(cur_ch)}; - if (!U_IS_UNICODE_CHAR(uni_char)) { + if (not U_IS_UNICODE_CHAR(uni_char)) { throw std::runtime_error("to_utf8: invalid Unicode scalar value"); } std::array buf{}; diff --git a/support/src/utils/unix.cpp b/support/src/utils/unix.cpp index b6f59f14..694e34db 100644 --- a/support/src/utils/unix.cpp +++ b/support/src/utils/unix.cpp @@ -32,8 +32,8 @@ namespace { std::vector groups{}; #if defined(__APPLE__) - constexpr const int buffer_count{8}; - constexpr const int max_group_count{1024}; + constexpr int buffer_count{8}; + constexpr int max_group_count{1024}; groups.resize(buffer_count); std::size_t orig_count{0U}; diff --git a/support/test/src/utils/encrypting_reader_test.cpp b/support/test/src/utils/encrypting_reader_test.cpp index 32170aec..96bab1fa 100644 --- a/support/test/src/utils/encrypting_reader_test.cpp +++ b/support/test/src/utils/encrypting_reader_test.cpp @@ -35,7 +35,8 @@ TEST(utils_encrypting_reader, read_file_data) { EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token); + "test.dat", source_file.get_path(), get_stop_requested, token, + std::nullopt); for (std::uint8_t i = 0U; i < 8U; i++) { data_buffer buffer( @@ -76,14 +77,14 @@ TEST(utils_encrypting_reader, read_file_data_using_argon2id) { EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token, cfg); + "test.dat", source_file.get_path(), get_stop_requested, token, cfg, + std::nullopt); std::array hdr; EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function( reinterpret_cast(hdr.data()), hdr.size(), 1U, &reader)); EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg)); - // EXPECT_EQ(cfg, reader.get_kdf_config().value()); for (std::uint8_t i = 0U; i < 8U; i++) { data_buffer buffer( @@ -98,7 +99,62 @@ TEST(utils_encrypting_reader, read_file_data_using_argon2id) { data_buffer decrypted_data; EXPECT_TRUE(utils::encryption::decrypt_data( - token, *reader.get_kdf_config(), buffer, decrypted_data)); + token, *reader.get_kdf_config_for_data(), buffer, decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + utils::encryption::encrypting_reader::get_data_chunk_size() * i, + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } +} + +TEST(utils_encrypting_reader, read_file_data_using_argon2id_master_key) { + const auto token = std::string("moose"); + utils::encryption::kdf_config cfg; + + auto master_key = + utils::encryption::generate_key(token, cfg); + + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, master_key, cfg, + std::nullopt); + + std::array hdr; + EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function( + reinterpret_cast(hdr.data()), hdr.size(), + 1U, &reader)); + EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg)); + + for (std::uint8_t i = 0U; i < 8U; i++) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size()); + for (std::uint8_t j = 0U; j < 2U; j++) { + ASSERT_EQ( + buffer.size() / 2U, + utils::encryption::encrypting_reader::reader_function( + reinterpret_cast(&buffer[(buffer.size() / 2U) * j]), + buffer.size() / 2U, 1U, &reader)); + } + + auto data_cfg = *reader.get_kdf_config_for_data(); + utils::hash::hash_256_t data_key; + std::tie(data_key, std::ignore) = cfg.create_subkey( + utils::encryption::kdf_context::data, data_cfg.unique_id, master_key); + data_buffer decrypted_data; + EXPECT_TRUE( + utils::encryption::decrypt_data(data_key, buffer, decrypted_data)); EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), decrypted_data.size()); @@ -122,7 +178,8 @@ TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks) { EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token); + "test.dat", source_file.get_path(), get_stop_requested, token, + std::nullopt); for (std::uint8_t i = 0U; i < 8U; i += 2U) { data_buffer buffer( @@ -172,14 +229,14 @@ TEST(utils_encrypting_reader, EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token, cfg); + "test.dat", source_file.get_path(), get_stop_requested, token, cfg, + std::nullopt); std::array hdr; EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function( reinterpret_cast(hdr.data()), hdr.size(), 1U, &reader)); EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg)); - // EXPECT_EQ(cfg, reader.get_kdf_config().value()); for (std::uint8_t i = 0U; i < 8U; i += 2U) { data_buffer buffer( @@ -194,7 +251,72 @@ TEST(utils_encrypting_reader, data_buffer decrypted_data; const auto offset = (j * (buffer.size() / 2U)); EXPECT_TRUE(utils::encryption::decrypt_data( - token, *reader.get_kdf_config(), + token, *reader.get_kdf_config_for_data(), + data_buffer( + std::next(buffer.begin(), static_cast(offset)), + std::next(buffer.begin(), static_cast( + offset + (buffer.size() / 2U)))), + decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + (utils::encryption::encrypting_reader::get_data_chunk_size() * i) + + (j * + utils::encryption::encrypting_reader::get_data_chunk_size()), + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } + } +} + +TEST(utils_encrypting_reader, + read_file_data_in_multiple_chunks_using_argon2id_master_key) { + const auto token = std::string("moose"); + utils::encryption::kdf_config cfg; + + auto master_key = + utils::encryption::generate_key(token, cfg); + + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, master_key, cfg, + std::nullopt); + + std::array hdr; + EXPECT_EQ(hdr.size(), utils::encryption::encrypting_reader::reader_function( + reinterpret_cast(hdr.data()), hdr.size(), + 1U, &reader)); + EXPECT_TRUE(utils::encryption::kdf_config::from_header(hdr, cfg)); + + for (std::uint8_t i = 0U; i < 8U; i += 2U) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size() * + 2U); + EXPECT_EQ(buffer.size(), + utils::encryption::encrypting_reader::reader_function( + reinterpret_cast(buffer.data()), buffer.size(), 1U, + &reader)); + + auto data_cfg = *reader.get_kdf_config_for_data(); + utils::hash::hash_256_t data_key; + std::tie(data_key, std::ignore) = cfg.create_subkey( + utils::encryption::kdf_context::data, data_cfg.unique_id, master_key); + + for (std::uint8_t j = 0U; j < 2U; j++) { + data_buffer decrypted_data; + const auto offset = (j * (buffer.size() / 2U)); + EXPECT_TRUE(utils::encryption::decrypt_data( + data_key, data_buffer( std::next(buffer.begin(), static_cast(offset)), std::next(buffer.begin(), static_cast( @@ -226,7 +348,8 @@ TEST(utils_encrypting_reader, read_file_data_as_stream) { EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token); + "test.dat", source_file.get_path(), get_stop_requested, token, + std::nullopt); auto io_stream = reader.create_iostream(); EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); EXPECT_TRUE(io_stream->good()); @@ -280,7 +403,8 @@ TEST(utils_encrypting_reader, read_file_data_as_stream_using_argon2id) { EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token, cfg); + "test.dat", source_file.get_path(), get_stop_requested, token, cfg, + std::nullopt); auto io_stream = reader.create_iostream(); EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); EXPECT_TRUE(io_stream->good()); @@ -310,7 +434,73 @@ TEST(utils_encrypting_reader, read_file_data_as_stream_using_argon2id) { data_buffer decrypted_data; EXPECT_TRUE(utils::encryption::decrypt_data( - token, *reader.get_kdf_config(), buffer, decrypted_data)); + token, *reader.get_kdf_config_for_data(), buffer, decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + utils::encryption::encrypting_reader::get_data_chunk_size() * i, + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } +} + +TEST(utils_encrypting_reader, + read_file_data_as_stream_using_argon2id_master_key) { + const auto token = std::string("moose"); + utils::encryption::kdf_config cfg; + + auto master_key = + utils::encryption::generate_key(token, cfg); + + auto &source_file = test::create_random_file( + 8U * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, master_key, cfg, + std::nullopt); + auto io_stream = reader.create_iostream(); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); + EXPECT_TRUE(io_stream->good()); + EXPECT_EQ(reader.get_total_size(), + static_cast(io_stream->tellg())); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail()); + EXPECT_TRUE(io_stream->good()); + + for (std::uint8_t i = 0U; i < 8U; i++) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size()); + EXPECT_FALSE( + io_stream + ->seekg(static_cast( + i * buffer.size() + utils::encryption::kdf_config::size())) + .fail()); + EXPECT_TRUE(io_stream->good()); + for (std::uint8_t j = 0U; j < 2U; j++) { + EXPECT_FALSE( + io_stream + ->read( + reinterpret_cast(&buffer[(buffer.size() / 2U) * j]), + static_cast(buffer.size()) / 2U) + .fail()); + EXPECT_TRUE(io_stream->good()); + } + + auto data_cfg = *reader.get_kdf_config_for_data(); + utils::hash::hash_256_t data_key; + std::tie(data_key, std::ignore) = cfg.create_subkey( + utils::encryption::kdf_context::data, data_cfg.unique_id, master_key); + + data_buffer decrypted_data; + EXPECT_TRUE( + utils::encryption::decrypt_data(data_key, buffer, decrypted_data)); EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), decrypted_data.size()); @@ -334,7 +524,8 @@ TEST(utils_encrypting_reader, read_file_data_in_multiple_chunks_as_stream) { EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token); + "test.dat", source_file.get_path(), get_stop_requested, token, + std::nullopt); auto io_stream = reader.create_iostream(); EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); EXPECT_TRUE(io_stream->good()); @@ -392,7 +583,8 @@ TEST(utils_encrypting_reader, EXPECT_TRUE(source_file); if (source_file) { utils::encryption::encrypting_reader reader( - "test.dat", source_file.get_path(), get_stop_requested, token, cfg); + "test.dat", source_file.get_path(), get_stop_requested, token, cfg, + std::nullopt); auto io_stream = reader.create_iostream(); EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); EXPECT_TRUE(io_stream->good()); @@ -420,7 +612,79 @@ TEST(utils_encrypting_reader, data_buffer decrypted_data; const auto offset = (j * (buffer.size() / 2U)); EXPECT_TRUE(utils::encryption::decrypt_data( - token, *reader.get_kdf_config(), + token, *reader.get_kdf_config_for_data(), + data_buffer( + std::next(buffer.begin(), static_cast(offset)), + std::next(buffer.begin(), static_cast( + offset + (buffer.size() / 2U)))), + decrypted_data)); + + EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(), + decrypted_data.size()); + + std::size_t bytes_read{}; + data_buffer file_data(decrypted_data.size()); + EXPECT_TRUE(source_file.read( + file_data, + (utils::encryption::encrypting_reader::get_data_chunk_size() * i) + + (j * + utils::encryption::encrypting_reader::get_data_chunk_size()), + &bytes_read)); + EXPECT_EQ(0, std::memcmp(file_data.data(), decrypted_data.data(), + file_data.size())); + } + } + } +} + +TEST(utils_encrypting_reader, + read_file_data_in_multiple_chunks_as_stream_using_argon2id_master_key) { + const auto token = std::string("moose"); + utils::encryption::kdf_config cfg; + + auto master_key = + utils::encryption::generate_key(token, cfg); + + auto &source_file = test::create_random_file( + 8u * utils::encryption::encrypting_reader::get_data_chunk_size()); + EXPECT_TRUE(source_file); + if (source_file) { + utils::encryption::encrypting_reader reader( + "test.dat", source_file.get_path(), get_stop_requested, master_key, cfg, + std::nullopt); + auto io_stream = reader.create_iostream(); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::end).fail()); + EXPECT_TRUE(io_stream->good()); + EXPECT_EQ(reader.get_total_size(), + static_cast(io_stream->tellg())); + EXPECT_FALSE(io_stream->seekg(0, std::ios_base::beg).fail()); + EXPECT_TRUE(io_stream->good()); + + EXPECT_FALSE(io_stream + ->seekg(static_cast( + utils::encryption::kdf_config::size())) + .fail()); + + for (std::uint8_t i = 0U; i < 8U; i += 2U) { + data_buffer buffer( + utils::encryption::encrypting_reader::get_encrypted_chunk_size() * + 2U); + EXPECT_FALSE(io_stream + ->read(reinterpret_cast(buffer.data()), + static_cast(buffer.size())) + .fail()); + EXPECT_TRUE(io_stream->good()); + + auto data_cfg = *reader.get_kdf_config_for_data(); + utils::hash::hash_256_t data_key; + std::tie(data_key, std::ignore) = cfg.create_subkey( + utils::encryption::kdf_context::data, data_cfg.unique_id, master_key); + + for (std::uint8_t j = 0U; j < 2U; j++) { + data_buffer decrypted_data; + const auto offset = (j * (buffer.size() / 2U)); + EXPECT_TRUE(utils::encryption::decrypt_data( + data_key, data_buffer( std::next(buffer.begin(), static_cast(offset)), std::next(buffer.begin(), static_cast( diff --git a/support/test/src/utils/encryption_kdf_config_test.cpp b/support/test/src/utils/encryption_kdf_config_test.cpp index 09b94ca5..b70620f8 100644 --- a/support/test/src/utils/encryption_kdf_config_test.cpp +++ b/support/test/src/utils/encryption_kdf_config_test.cpp @@ -27,40 +27,41 @@ namespace repertory { TEST(utils_encryption_kdf_config, can_construct_using_default_constructor) { utils::encryption::kdf_config cfg; - EXPECT_EQ(utils::encryption::kdf_config::repertory_magic, cfg.magic); EXPECT_EQ(utils::encryption::kdf_version::v1, cfg.version); EXPECT_EQ(utils::encryption::kdf_type::argon2id, cfg.kdf); EXPECT_EQ(utils::encryption::memlimit_level::level3, cfg.memlimit); EXPECT_EQ(utils::encryption::opslimit_level::level2, cfg.opslimit); EXPECT_EQ(utils::encryption::kdf_config::salt_t{}, cfg.salt); + EXPECT_EQ(0U, cfg.unique_id); EXPECT_EQ(0U, cfg.checksum); } -TEST(utils_encryption_kdf_config, can_generate_salt) { +TEST(utils_encryption_kdf_config, can_seal) { utils::encryption::kdf_config cfg; - cfg.generate_salt(); + cfg.seal(); EXPECT_NE(utils::encryption::kdf_config::salt_t{}, cfg.salt); auto orig_salt = cfg.salt; - cfg.generate_salt(); + cfg.seal(); EXPECT_NE(orig_salt, cfg.salt); } TEST(utils_encryption_kdf_config, can_generate_checksum) { utils::encryption::kdf_config cfg; - EXPECT_EQ(8853559678329530327ULL, cfg.generate_checksum()); + EXPECT_EQ(13087047540462255120ULL, cfg.generate_checksum()); } -TEST(utils_encryption_kdf_config, generate_salt_calculates_checksum) { +TEST(utils_encryption_kdf_config, seal_calculates_checksum) { utils::encryption::kdf_config cfg; - cfg.generate_salt(); + cfg.seal(); EXPECT_NE(0U, cfg.checksum); } TEST(utils_encryption_kdf_config, can_create_header_and_restore) { utils::encryption::kdf_config cfg; - cfg.generate_salt(); + cfg.unique_id = 2U; + cfg.seal(); auto hdr = cfg.to_header(); EXPECT_EQ(utils::encryption::kdf_config::size(), hdr.size()); @@ -70,30 +71,20 @@ TEST(utils_encryption_kdf_config, can_create_header_and_restore) { auto restored_hdr = restored_cfg.to_header(); EXPECT_EQ(hdr, restored_hdr); - EXPECT_EQ(cfg.magic, restored_cfg.magic); EXPECT_EQ(cfg.version, restored_cfg.version); EXPECT_EQ(cfg.kdf, restored_cfg.kdf); EXPECT_EQ(cfg.memlimit, restored_cfg.memlimit); EXPECT_EQ(cfg.opslimit, restored_cfg.opslimit); EXPECT_EQ(cfg.salt, restored_cfg.salt); EXPECT_EQ(cfg.checksum, restored_cfg.checksum); + EXPECT_EQ(cfg.unique_id, restored_cfg.unique_id); EXPECT_EQ(cfg, restored_cfg); } -TEST(utils_encryption_kdf_config, header_restore_fails_if_magic_is_invalid) { - utils::encryption::kdf_config cfg; - cfg.magic = 0x11; - cfg.generate_salt(); - - auto hdr = cfg.to_header(); - utils::encryption::kdf_config restored_cfg; - EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg)); -} - TEST(utils_encryption_kdf_config, header_restore_fails_if_version_is_invalid) { utils::encryption::kdf_config cfg; cfg.version = static_cast(0x11); - cfg.generate_salt(); + cfg.seal(); auto hdr = cfg.to_header(); utils::encryption::kdf_config restored_cfg; @@ -103,7 +94,7 @@ TEST(utils_encryption_kdf_config, header_restore_fails_if_version_is_invalid) { TEST(utils_encryption_kdf_config, header_restore_fails_if_kdf_is_invalid) { utils::encryption::kdf_config cfg; cfg.kdf = static_cast(0x11); - cfg.generate_salt(); + cfg.seal(); auto hdr = cfg.to_header(); utils::encryption::kdf_config restored_cfg; @@ -113,7 +104,7 @@ TEST(utils_encryption_kdf_config, header_restore_fails_if_kdf_is_invalid) { TEST(utils_encryption_kdf_config, header_restore_fails_if_memlimit_is_invalid) { utils::encryption::kdf_config cfg; cfg.memlimit = static_cast(0x11); - cfg.generate_salt(); + cfg.seal(); auto hdr = cfg.to_header(); utils::encryption::kdf_config restored_cfg; @@ -123,7 +114,7 @@ TEST(utils_encryption_kdf_config, header_restore_fails_if_memlimit_is_invalid) { TEST(utils_encryption_kdf_config, header_restore_fails_if_opslimit_is_invalid) { utils::encryption::kdf_config cfg; cfg.opslimit = static_cast(0x11); - cfg.generate_salt(); + cfg.seal(); auto hdr = cfg.to_header(); utils::encryption::kdf_config restored_cfg; @@ -132,13 +123,280 @@ TEST(utils_encryption_kdf_config, header_restore_fails_if_opslimit_is_invalid) { TEST(utils_encryption_kdf_config, header_restore_fails_if_salt_is_invalid) { utils::encryption::kdf_config cfg; - cfg.generate_salt(); + cfg.seal(); cfg.salt = utils::encryption::kdf_config::salt_t{}; auto hdr = cfg.to_header(); utils::encryption::kdf_config restored_cfg; EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg)); } + +TEST(utils_encryption_kdf_config, header_restore_fails_if_id_is_invalid) { + utils::encryption::kdf_config cfg; + cfg.seal(); + cfg.unique_id = 22U; + + auto hdr = cfg.to_header(); + utils::encryption::kdf_config restored_cfg; + EXPECT_FALSE(utils::encryption::kdf_config::from_header(hdr, restored_cfg)); +} + +TEST(utils_encryption_kdf_config, create_subkey_sets_id_and_updates_checksum) { + using hash_t = utils::hash::hash_256_t; + + utils::encryption::kdf_config cfg; + cfg.seal(); + + hash_t master_key = + utils::encryption::generate_key("root-master-key"); + + constexpr std::size_t sub_id = 42; + auto [subkey, out_cfg] = cfg.create_subkey( + utils::encryption::kdf_context::path, sub_id, master_key); + + EXPECT_NE(subkey, hash_t{}); + EXPECT_NE(subkey, master_key); + + EXPECT_EQ(out_cfg.unique_id, static_cast(sub_id)); + EXPECT_EQ(out_cfg.checksum, out_cfg.generate_checksum()); + + EXPECT_EQ(out_cfg.version, cfg.version); + EXPECT_EQ(out_cfg.kdf, cfg.kdf); + EXPECT_EQ(out_cfg.memlimit, cfg.memlimit); + EXPECT_EQ(out_cfg.opslimit, cfg.opslimit); + EXPECT_EQ(out_cfg.salt, cfg.salt); +} + +TEST(utils_encryption_kdf_config, + create_subkey_is_deterministic_for_same_inputs) { + using hash_t = utils::hash::hash_256_t; + + utils::encryption::kdf_config cfg; + cfg.seal(); + + hash_t master_key = + utils::encryption::generate_key("root-master-key"); + + constexpr auto ctx = utils::encryption::kdf_context::data; + constexpr std::size_t sub_id = 7; + + auto [k1, c1] = cfg.create_subkey(ctx, sub_id, master_key); + auto [k2, c2] = cfg.create_subkey(ctx, sub_id, master_key); + + EXPECT_EQ(k1, k2); + EXPECT_EQ(c1.unique_id, c2.unique_id); + EXPECT_EQ(c1.checksum, c2.checksum); + EXPECT_EQ(c1, c2); +} + +TEST(utils_encryption_kdf_config, create_subkey_varies_with_different_id) { + using hash_t = utils::hash::hash_256_t; + + utils::encryption::kdf_config cfg; + cfg.seal(); + + hash_t master_key = + utils::encryption::generate_key("root-master-key"); + + constexpr auto ctx = utils::encryption::kdf_context::data; + + auto [k1, c1] = cfg.create_subkey(ctx, 1, master_key); + auto [k2, c2] = cfg.create_subkey(ctx, 2, master_key); + + EXPECT_NE(k1, k2); + EXPECT_NE(c1.unique_id, c2.unique_id); + EXPECT_NE(c1.checksum, c2.checksum); + + EXPECT_EQ(c1.version, c2.version); + EXPECT_EQ(c1.kdf, c2.kdf); + EXPECT_EQ(c1.memlimit, c2.memlimit); + EXPECT_EQ(c1.opslimit, c2.opslimit); + EXPECT_EQ(c1.salt, c2.salt); +} + +TEST(utils_encryption_kdf_config, create_subkey_varies_with_different_context) { + using hash_t = utils::hash::hash_256_t; + + utils::encryption::kdf_config cfg; + cfg.seal(); + + hash_t master_key = + utils::encryption::generate_key("root-master-key"); + + constexpr std::size_t sub_id = 123; + + auto [ka, ca] = cfg.create_subkey( + utils::encryption::kdf_context::data, sub_id, master_key); + auto [kb, cb] = cfg.create_subkey( + utils::encryption::kdf_context::path, sub_id, master_key); + + EXPECT_NE(ka, kb); + EXPECT_EQ(ca.unique_id, cb.unique_id); + EXPECT_EQ(ca.checksum, cb.checksum); + EXPECT_EQ(ca, cb); +} + +TEST(utils_encryption_kdf_config, + create_subkey_with_undefined_context_uses_fallback) { + using hash_t = utils::hash::hash_256_t; + + utils::encryption::kdf_config cfg; + cfg.seal(); + + hash_t master_key = + utils::encryption::generate_key("root-master-key"); + + constexpr std::size_t sub_id = 55; + + auto [k_def, c_def] = cfg.create_subkey( + utils::encryption::kdf_context::undefined, sub_id, master_key); + auto [k_dat, c_dat] = cfg.create_subkey( + utils::encryption::kdf_context::data, sub_id, master_key); + + EXPECT_NE(k_def, hash_t{}); + EXPECT_NE(k_dat, hash_t{}); + EXPECT_NE(k_def, k_dat); + + EXPECT_EQ(c_def, c_dat); +} + +#if defined(PROJECT_ENABLE_JSON) +TEST(utils_encryption_kdf_config, can_convert_kdf_config_to_and_from_json) { + utils::encryption::kdf_config cfg; + cfg.unique_id = 2U; + cfg.seal(); + + nlohmann::json json_kdf(cfg); + + auto cfg2 = json_kdf.get(); + EXPECT_EQ(cfg, cfg2); +} +#endif // defined(PROJECT_ENABLE_JSON) + +TEST(utils_encryption_kdf_config, equality) { + { + utils::encryption::kdf_config cfg; + utils::encryption::kdf_config cfg2; + + EXPECT_EQ(cfg, cfg2); + } + + { + utils::encryption::kdf_config cfg; + utils::encryption::kdf_config cfg2{cfg}; + + EXPECT_EQ(cfg, cfg2); + } + + { + utils::encryption::kdf_config cfg; + cfg.seal(); + + utils::encryption::kdf_config cfg2{cfg}; + + EXPECT_EQ(cfg, cfg2); + } + + { + utils::encryption::kdf_config cfg; + utils::encryption::kdf_config cfg2; + cfg2 = cfg; + + EXPECT_EQ(cfg, cfg2); + } + + { + utils::encryption::kdf_config cfg; + cfg.seal(); + + utils::encryption::kdf_config cfg2; + cfg2 = cfg; + + EXPECT_EQ(cfg, cfg2); + } +} + +TEST(utils_encryption_kdf_config, sealed_is_not_equal_to_unsealed) { + utils::encryption::kdf_config cfg; + cfg.seal(); + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, sealed_is_not_equal_to_sealed) { + utils::encryption::kdf_config cfg; + cfg.seal(); + + utils::encryption::kdf_config cfg2; + cfg2.seal(); + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, is_not_equal_to_different_id) { + utils::encryption::kdf_config cfg; + cfg.unique_id = 2UL; + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, is_not_equal_to_different_version) { + utils::encryption::kdf_config cfg; + cfg.version = static_cast(0x11); + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, is_not_equal_to_different_kdf) { + utils::encryption::kdf_config cfg; + cfg.kdf = static_cast(0x11); + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, is_not_equal_to_different_memlimit) { + utils::encryption::kdf_config cfg; + cfg.memlimit = static_cast(0x11); + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, is_not_equal_to_different_opslimit) { + utils::encryption::kdf_config cfg; + cfg.opslimit = static_cast(0x11); + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, is_not_equal_to_different_salt) { + utils::encryption::kdf_config cfg; + cfg.salt[0U] = 1U; + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} + +TEST(utils_encryption_kdf_config, is_not_equal_to_different_checksum) { + utils::encryption::kdf_config cfg; + cfg.checksum = 2U; + + utils::encryption::kdf_config cfg2; + + EXPECT_NE(cfg, cfg2); +} } // namespace repertory #endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) diff --git a/support/test/src/utils/encryption_test.cpp b/support/test/src/utils/encryption_test.cpp index 967b9488..94a39bd2 100644 --- a/support/test/src/utils/encryption_test.cpp +++ b/support/test/src/utils/encryption_test.cpp @@ -375,6 +375,10 @@ TEST(utils_encryption, encrypt_data_pointer_using_argon2id) { // TEST(utils_encryption, decrypt_file_name_using_argon2id) {} // TEST(utils_encryption, decrypt_file_path_using_argon2id) {} +// +// TEST(utils_encryption, decrypt_file_name_using_argon2id_master_key) {} + +// TEST(utils_encryption, decrypt_file_path_using_argon2id_master_key) {} #endif // defined(PROJECT_ENABLE_BOOST) } // namespace repertory