From 5378c45516efeab91885f5e75175d070bd31fd05 Mon Sep 17 00:00:00 2001 From: ghostway Date: Sat, 9 Dec 2023 14:51:03 +0200 Subject: [PATCH] [seraphis_wallet]: encrypted file --- src/seraphis_wallet/encrypted_file.h | 119 +++++++++++++++++++++++++++ tests/unit_tests/CMakeLists.txt | 3 +- tests/unit_tests/encrypted_file.cpp | 55 +++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/seraphis_wallet/encrypted_file.h create mode 100644 tests/unit_tests/encrypted_file.cpp diff --git a/src/seraphis_wallet/encrypted_file.h b/src/seraphis_wallet/encrypted_file.h new file mode 100644 index 0000000000..baf29379fb --- /dev/null +++ b/src/seraphis_wallet/encrypted_file.h @@ -0,0 +1,119 @@ +#pragma once + +#include "crypto/chacha.h" +#include "crypto/crypto.h" +#include "file_io_utils.h" +#include "serialization/binary_archive.h" +#include "serialization/containers.h" +#include "serialization/crypto.h" +#include "serialization/serialization.h" +#include "serialization/string.h" +#include "storages/portable_storage_template_helper.h" +#include "string_coding.h" + +#include + +struct EncryptedFile +{ + std::string encrypted_data; + crypto::chacha_iv iv; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + FIELD(encrypted_data) + FIELD(iv) + END_SERIALIZE() +}; + +template bool read_encrypted_file(std::string path, const crypto::chacha_key &key, T &struct_out) +{ + std::string buf; + if (!epee::file_io_utils::load_file_to_string(path, buf)) + return false; + + EncryptedFile file; + + binary_archive file_ar{epee::strspan(buf)}; + if (!::serialization::serialize(file_ar, file)) + return false; + + std::string decrypted_data; + decrypted_data.resize(file.encrypted_data.size()); + crypto::chacha20(file.encrypted_data.data(), file.encrypted_data.size(), key, file.iv, &decrypted_data[0]); + + binary_archive ar{epee::strspan(decrypted_data)}; + + if (!::serialization::serialize(ar, struct_out)) + return false; + + return true; +} + +// NOTE: if this were c++20, Concepts and `require` could be used to make this one function +template bool read_encrypted_file_json(std::string path, const crypto::chacha_key &key, T &struct_out) +{ + std::string buf; + if (!epee::file_io_utils::load_file_to_string(path, buf)) + return false; + + EncryptedFile file; + + binary_archive file_ar{epee::strspan(buf)}; + if (!::serialization::serialize(file_ar, file)) + return false; + + std::string decrypted_data; + decrypted_data.resize(file.encrypted_data.size()); + crypto::chacha20(file.encrypted_data.data(), file.encrypted_data.size(), key, file.iv, &decrypted_data[0]); + + return epee::serialization::load_t_from_json(struct_out, decrypted_data); +} + +template bool write_encrypted_file(std::string path, const crypto::chacha_key &key, T &struct_in) +{ + std::stringstream data_oss; + binary_archive data_ar(data_oss); + if (!::serialization::serialize(data_ar, struct_in)) + return false; + + std::string buf = data_oss.str(); + + EncryptedFile file = {}; + file.iv = crypto::rand(); + + std::string encrypted_data; + encrypted_data.resize(buf.size()); + + crypto::chacha20(std::move(buf.data()), buf.size(), key, file.iv, &encrypted_data[0]); + + file.encrypted_data = encrypted_data; + + std::stringstream file_oss; + binary_archive file_ar(file_oss); + if (!::serialization::serialize(file_ar, file)) + return false; + + return epee::file_io_utils::save_string_to_file(path, file_oss.str()); +} + +template bool write_encrypted_file_json(std::string path, const crypto::chacha_key &key, T &struct_in) +{ + std::string struct_json = epee::serialization::store_t_to_json(struct_in); + + EncryptedFile file = {}; + file.iv = crypto::rand(); + + std::string encrypted_data; + encrypted_data.resize(struct_json.size()); + + crypto::chacha20(std::move(struct_json.data()), struct_json.size(), key, file.iv, &encrypted_data[0]); + + file.encrypted_data = encrypted_data; + + std::stringstream file_oss; + binary_archive file_ar(file_oss); + if (!::serialization::serialize(file_ar, file)) + return false; + + return epee::file_io_utils::save_string_to_file(path, file_oss.str()); +} diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index ddcf64f7f5..7f75f414a2 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -118,7 +118,8 @@ set(unit_tests_sources aligned.cpp rpc_version_str.cpp x25519.cpp - zmq_rpc.cpp) + zmq_rpc.cpp + encrypted_file.cpp) set(unit_tests_headers unit_tests_utils.h) diff --git a/tests/unit_tests/encrypted_file.cpp b/tests/unit_tests/encrypted_file.cpp new file mode 100644 index 0000000000..2b350dcdea --- /dev/null +++ b/tests/unit_tests/encrypted_file.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +#include "crypto/chacha.h" +#include "seraphis_wallet/encrypted_file.h" +#include "serialization/serialization.h" +#include "serialization/keyvalue_serialization.h" + +struct test_s +{ + std::string data; + + BEGIN_SERIALIZE() + FIELD(data) + END_SERIALIZE() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(data) + END_KV_SERIALIZE_MAP() +}; + +TEST(EncryptedFile, ReadWriteBlob) +{ + boost::filesystem::path temp_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + const std::string tmp_path = temp_file.native(); + + test_s test{.data = "monero is awesome"}; + + crypto::chacha_key key; + crypto::generate_chacha_key("monero is double awesome", key, 1); + + ASSERT_TRUE(write_encrypted_file(tmp_path, key, test)); + + ASSERT_TRUE(read_encrypted_file(tmp_path, key, test)); + + ASSERT_TRUE(test.data == "monero is awesome"); +} + +TEST(EncryptedFile, ReadWriteJson) +{ + boost::filesystem::path temp_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + const std::string tmp_path = temp_file.native(); + + test_s test{.data = "monero is awesome!"}; + + crypto::chacha_key key; + crypto::generate_chacha_key("monero is double awesome", key, 1); + + ASSERT_TRUE(write_encrypted_file_json(tmp_path, key, test)); + + ASSERT_TRUE(read_encrypted_file_json(tmp_path, key, test)); + + ASSERT_TRUE(test.data == "monero is awesome!"); +}