Implement secure key via KDF for transparent data encryption/decryption #60

This commit is contained in:
2025-08-30 11:10:44 -05:00
parent faaf7648a8
commit 8979e6e2a4
4 changed files with 151 additions and 102 deletions

View File

@@ -76,6 +76,11 @@ private:
[[nodiscard]] auto decrypt_object_name(std::string &object_name) const [[nodiscard]] auto decrypt_object_name(std::string &object_name) const
-> api_error; -> api_error;
[[nodiscard]] auto
get_kdf_config_from_meta(const std::string &api_path,
utils::encryption::kdf_config &cfg) const
-> api_error;
[[nodiscard]] auto get_last_modified(bool directory, [[nodiscard]] auto get_last_modified(bool directory,
const std::string &api_path, const std::string &api_path,
std::uint64_t &last_modified) const std::uint64_t &last_modified) const

View File

@@ -640,6 +640,23 @@ auto s3_provider::get_file_list(api_file_list &list, std::string &marker) const
return api_error::error; return api_error::error;
} }
auto s3_provider::get_kdf_config_from_meta(
const std::string &api_path, utils::encryption::kdf_config &cfg) const
-> api_error {
std::string kdf_str;
auto ret = get_item_meta(api_path, META_KDF, kdf_str);
if (ret != api_error::success) {
return ret;
}
if (kdf_str.empty()) {
return api_error::item_not_found;
}
cfg = nlohmann::json::parse(kdf_str).get<utils::encryption::kdf_config>();
return api_error::success;
}
auto s3_provider::get_last_modified(bool directory, const std::string &api_path, auto s3_provider::get_last_modified(bool directory, const std::string &api_path,
std::uint64_t &last_modified) const std::uint64_t &last_modified) const
-> api_error { -> api_error {
@@ -1078,46 +1095,47 @@ auto s3_provider::start(api_item_added_callback api_item_added,
auto ret = base_provider::start(api_item_added, mgr); auto ret = base_provider::start(api_item_added, mgr);
if (ret && not cfg.encryption_token.empty()) { if (ret && not cfg.encryption_token.empty()) {
std::string kdf_str; auto res = get_kdf_config_from_meta("/", master_kdf_cfg_);
auto res = get_item_meta("/", META_KDF, kdf_str); switch (res) {
ret = res == api_error::success; case api_error::item_not_found: {
if (ret) { try {
if (kdf_str.empty()) { if (not search_keys_for_kdf(cfg.encryption_token)) {
try { if (get_directory_item_count("/") == 0U) {
if (not search_keys_for_kdf(cfg.encryption_token)) { legacy_bucket_ = false;
if (get_directory_item_count("/") == 0U) { master_kdf_cfg_.seal();
legacy_bucket_ = false; master_key_ =
master_kdf_cfg_.seal(); utils::encryption::generate_key<utils::hash::hash_256_t>(
master_key_ = cfg.encryption_token, master_kdf_cfg_);
utils::encryption::generate_key<utils::hash::hash_256_t>(
cfg.encryption_token, master_kdf_cfg_);
res = set_item_meta("/", META_KDF, res = set_item_meta("/", META_KDF,
nlohmann::json(master_kdf_cfg_).dump()); nlohmann::json(master_kdf_cfg_).dump());
ret = res == api_error::success; if (res != api_error::success) {
if (not ret) { utils::error::raise_api_path_error(function_name, "/", res,
utils::error::raise_api_path_error(function_name, "/", res, "set kdf in meta failed");
"set kdf in meta failed"); ret = false;
}
} }
} }
} catch (const std::exception &e) {
utils::error::raise_error(function_name, e, "exception occurred");
ret = false;
}
} else {
master_kdf_cfg_ =
nlohmann::json::parse(kdf_str).get<utils::encryption::kdf_config>();
if (not utils::encryption::recreate_key_argon2id(
cfg.encryption_token, master_kdf_cfg_, master_key_)) {
ret = false;
utils::error::raise_error(function_name,
"failed to recreate master key from kdf");
} }
} catch (const std::exception &e) {
utils::error::raise_error(function_name, e, "exception occurred");
ret = false;
} }
} else { } break;
case api_error::success: {
if (not utils::encryption::recreate_key_argon2id(
cfg.encryption_token, master_kdf_cfg_, master_key_)) {
utils::error::raise_error(function_name,
"failed to recreate master key from kdf");
ret = false;
}
} break;
default: {
utils::error::raise_api_path_error(function_name, "/", res, utils::error::raise_api_path_error(function_name, "/", res,
"get kdf from meta failed"); "get kdf from meta failed");
ret = false;
}
} }
if (not ret) { if (not ret) {
@@ -1226,10 +1244,10 @@ auto s3_provider::read_file_bytes(const std::string &api_path, std::size_t size,
try { try {
const auto &cfg{get_s3_config()}; const auto &cfg{get_s3_config()};
bool encrypted{not cfg.encryption_token.empty()}; bool is_encrypted{not cfg.encryption_token.empty()};
std::string key; std::string key;
if (encrypted) { if (is_encrypted) {
auto res{get_item_meta(api_path, META_KEY, key)}; auto res{get_item_meta(api_path, META_KEY, key)};
if (res != api_error::success) { if (res != api_error::success) {
return res; return res;
@@ -1237,7 +1255,7 @@ auto s3_provider::read_file_bytes(const std::string &api_path, std::size_t size,
} }
auto object_name{ auto object_name{
utils::path::create_api_path(encrypted ? key : api_path), utils::path::create_api_path(is_encrypted ? key : api_path),
}; };
const auto read_bytes = const auto read_bytes =
@@ -1305,14 +1323,14 @@ auto s3_provider::read_file_bytes(const std::string &api_path, std::size_t size,
return res; return res;
}; };
if (not encrypted) { if (not is_encrypted) {
return read_bytes(size, offset, data); return read_bytes(size, offset, data);
} }
std::string temp; std::string size_str;
auto res{get_item_meta(api_path, META_SIZE, temp)}; auto ret{get_item_meta(api_path, META_SIZE, size_str)};
if (res != api_error::success) { if (ret != api_error::success) {
return res; return ret;
} }
utils::hash::hash_256_t data_key; utils::hash::hash_256_t data_key;
@@ -1320,21 +1338,23 @@ auto s3_provider::read_file_bytes(const std::string &api_path, std::size_t size,
data_key = utils::encryption::generate_key<utils::hash::hash_256_t>( data_key = utils::encryption::generate_key<utils::hash::hash_256_t>(
cfg.encryption_token); cfg.encryption_token);
} else { } else {
res = get_item_meta(api_path, META_KDF, temp); utils::encryption::kdf_config data_cfg;
if (res != api_error::success) { ret = get_kdf_config_from_meta(api_path, data_cfg);
return res; if (ret != api_error::success) {
return ret;
} }
auto data_cfg =
nlohmann::json::parse(temp).get<utils::encryption::kdf_config>();
data_key = data_cfg.recreate_subkey(utils::encryption::kdf_context::data, data_key = data_cfg.recreate_subkey(utils::encryption::kdf_context::data,
master_key_); master_key_);
} }
auto total_size{utils::string::to_uint64(temp)}; auto total_size{utils::string::to_uint64(size_str)};
return utils::encryption::read_encrypted_range( return utils::encryption::read_encrypted_range(
{.begin = offset, .end = offset + size - 1U}, data_key, {
not legacy_bucket_, .begin = offset,
.end = offset + size - 1U,
},
data_key, not legacy_bucket_,
[&](data_buffer &ct_buffer, std::uint64_t start_offset, [&](data_buffer &ct_buffer, std::uint64_t start_offset,
std::uint64_t end_offset) -> bool { std::uint64_t end_offset) -> bool {
return read_bytes((end_offset - start_offset + 1U), return read_bytes((end_offset - start_offset + 1U),

View File

@@ -133,10 +133,20 @@ struct kdf_config final {
[[nodiscard]] auto create_subkey(kdf_context ctx, std::size_t unique_id_, [[nodiscard]] auto create_subkey(kdf_context ctx, std::size_t unique_id_,
const hash_t &master_key) const const hash_t &master_key) const
-> std::pair<hash_t, kdf_config> { -> std::pair<hash_t, kdf_config> {
REPERTORY_USES_FUNCTION_NAME();
hash_t sub_key; hash_t sub_key;
crypto_kdf_derive_from_key(sub_key.data(), sub_key.size(), unique_id_, auto res = crypto_kdf_derive_from_key(
get_kdf_context_name(ctx).data(), sub_key.data(), sub_key.size(), unique_id_,
master_key.data()); get_kdf_context_name(ctx).data(), master_key.data());
if (res != 0) {
throw repertory::utils::error::create_exception(
function_name, {
"failed to create sub-key",
std::to_string(res),
});
}
auto cfg = *this; auto cfg = *this;
cfg.unique_id = unique_id_; cfg.unique_id = unique_id_;
cfg.checksum = cfg.generate_checksum(); cfg.checksum = cfg.generate_checksum();
@@ -146,10 +156,20 @@ struct kdf_config final {
template <typename hash_t> template <typename hash_t>
[[nodiscard]] auto recreate_subkey(kdf_context ctx, [[nodiscard]] auto recreate_subkey(kdf_context ctx,
const hash_t &master_key) const -> hash_t { const hash_t &master_key) const -> hash_t {
REPERTORY_USES_FUNCTION_NAME();
hash_t sub_key; hash_t sub_key;
crypto_kdf_derive_from_key(sub_key.data(), sub_key.size(), unique_id, auto res = crypto_kdf_derive_from_key(
get_kdf_context_name(ctx).data(), sub_key.data(), sub_key.size(), unique_id,
master_key.data()); get_kdf_context_name(ctx).data(), master_key.data());
if (res != 0) {
throw repertory::utils::error::create_exception(
function_name, {
"failed to recreate sub-key",
std::to_string(res),
});
}
return sub_key; return sub_key;
} }

View File

@@ -41,13 +41,13 @@ namespace repertory::utils::file {
[[nodiscard]] auto create_temp_name(std::string_view file_part) -> std::string; [[nodiscard]] auto create_temp_name(std::string_view file_part) -> std::string;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto create_temp_name(std::wstring_view file_part)
create_temp_name(std::wstring_view file_part) -> std::wstring; -> std::wstring;
// INFO: has test // INFO: has test
[[nodiscard]] inline auto [[nodiscard]] inline auto
directory_exists_in_path(std::string_view path, directory_exists_in_path(std::string_view path, std::string_view sub_directory)
std::string_view sub_directory) -> bool; -> bool;
// INFO: has test // INFO: has test
[[nodiscard]] inline auto [[nodiscard]] inline auto
@@ -55,45 +55,46 @@ directory_exists_in_path(std::wstring_view path,
std::wstring_view sub_directory) -> bool; std::wstring_view sub_directory) -> bool;
// INFO: has test // INFO: has test
[[nodiscard]] inline auto [[nodiscard]] inline auto file_exists_in_path(std::string_view path,
file_exists_in_path(std::string_view path, std::string_view file_name) -> bool; std::string_view file_name)
-> bool;
// INFO: has test // INFO: has test
[[nodiscard]] inline auto [[nodiscard]] inline auto file_exists_in_path(std::wstring_view path,
file_exists_in_path(std::wstring_view path, std::wstring_view file_name)
std::wstring_view file_name) -> bool; -> bool;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto get_free_drive_space(std::string_view path)
get_free_drive_space(std::string_view path) -> std::optional<std::uint64_t>; -> std::optional<std::uint64_t>;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto get_free_drive_space(std::wstring_view path)
get_free_drive_space(std::wstring_view path) -> std::optional<std::uint64_t>; -> std::optional<std::uint64_t>;
// INFO: has test // INFO: has test
[[nodiscard]] auto get_time(std::string_view path, [[nodiscard]] auto get_time(std::string_view path, time_type type)
time_type type) -> std::optional<std::uint64_t>; -> std::optional<std::uint64_t>;
// INFO: has test // INFO: has test
[[nodiscard]] auto get_time(std::wstring_view path, [[nodiscard]] auto get_time(std::wstring_view path, time_type type)
time_type type) -> std::optional<std::uint64_t>; -> std::optional<std::uint64_t>;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto get_times(std::string_view path)
get_times(std::string_view path) -> std::optional<file_times>; -> std::optional<file_times>;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto get_times(std::wstring_view path)
get_times(std::wstring_view path) -> std::optional<file_times>; -> std::optional<file_times>;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto get_total_drive_space(std::string_view path)
get_total_drive_space(std::string_view path) -> std::optional<std::uint64_t>; -> std::optional<std::uint64_t>;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto get_total_drive_space(std::wstring_view path)
get_total_drive_space(std::wstring_view path) -> std::optional<std::uint64_t>; -> std::optional<std::uint64_t>;
#if defined(PROJECT_ENABLE_LIBDSM) #if defined(PROJECT_ENABLE_LIBDSM)
[[nodiscard]] auto [[nodiscard]] auto
@@ -101,20 +102,20 @@ smb_create_and_validate_relative_path(std::string_view smb_path,
std::string_view rel_path) -> std::string; std::string_view rel_path) -> std::string;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto smb_create_relative_path(std::string_view smb_path)
smb_create_relative_path(std::string_view smb_path) -> std::string; -> std::string;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto smb_create_search_path(std::string_view smb_path)
smb_create_search_path(std::string_view smb_path) -> std::string; -> std::string;
// INFO: has test // INFO: has test
[[nodiscard]] auto [[nodiscard]] auto smb_create_smb_path(std::string_view smb_path,
smb_create_smb_path(std::string_view smb_path, std::string_view rel_path)
std::string_view rel_path) -> std::string; -> std::string;
[[nodiscard]] auto [[nodiscard]] auto smb_get_parent_path(std::string_view smb_path)
smb_get_parent_path(std::string_view smb_path) -> std::string; -> std::string;
[[nodiscard]] auto smb_get_root_path(std::string_view smb_path) -> std::string; [[nodiscard]] auto smb_get_root_path(std::string_view smb_path) -> std::string;
@@ -143,27 +144,30 @@ read_json_file(std::string_view path, nlohmann::json &data,
std::optional<std::string_view> password = std::nullopt) -> bool; std::optional<std::string_view> password = std::nullopt) -> bool;
// INFO: has test // INFO: has test
[[nodiscard]] auto read_json_file( [[nodiscard]] auto
std::wstring_view path, nlohmann::json &data, read_json_file(std::wstring_view path, nlohmann::json &data,
std::optional<std::wstring_view> password = std::nullopt) -> bool; std::optional<std::wstring_view> password = std::nullopt)
-> bool;
// INFO: has test // INFO: has test
[[nodiscard]] auto write_json_file( [[nodiscard]] auto
std::string_view path, const nlohmann::json &data, write_json_file(std::string_view path, const nlohmann::json &data,
std::optional<std::string_view> password = std::nullopt) -> bool; std::optional<std::string_view> password = std::nullopt)
-> bool;
// INFO: has test // INFO: has test
[[nodiscard]] auto write_json_file( [[nodiscard]] auto
std::wstring_view path, const nlohmann::json &data, write_json_file(std::wstring_view path, const nlohmann::json &data,
std::optional<std::wstring_view> password = std::nullopt) -> bool; std::optional<std::wstring_view> password = std::nullopt)
-> bool;
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST) #else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
// INFO: has test // INFO: has test
[[nodiscard]] auto read_json_file(std::string_view path, [[nodiscard]] auto read_json_file(std::string_view path, nlohmann::json &data)
nlohmann::json &data) -> bool; -> bool;
// INFO: has test // INFO: has test
[[nodiscard]] auto read_json_file(std::wstring_view path, [[nodiscard]] auto read_json_file(std::wstring_view path, nlohmann::json &data)
nlohmann::json &data) -> bool; -> bool;
// INFO: has test // INFO: has test
[[nodiscard]] auto write_json_file(std::string_view path, [[nodiscard]] auto write_json_file(std::string_view path,