refactor: encapsulate vector/array keys into Obfuscation

This commit is contained in:
Lőrinc committed 2025-07-16 14:33:07 -07:00
1 parent 377aab8e5a
commit 478d40afc6
13 files changed
+136 -85

No files matched your search

+2 -2
View File
@@ -13,11 +13,11 @@ static void ObfuscationBench(benchmark::Bench& bench)
{ {
FastRandomContext frc{/*fDeterministic=*/true}; FastRandomContext frc{/*fDeterministic=*/true};
auto data{frc.randbytes<std::byte>(1024)}; auto data{frc.randbytes<std::byte>(1024)};
const auto key{frc.randbytes<Obfuscation::KEY_SIZE>()}; const Obfuscation obfuscation{frc.randbytes<Obfuscation::KEY_SIZE>()};
size_t offset{0}; size_t offset{0};
bench.batch(data.size()).unit("byte").run([&] { bench.batch(data.size()).unit("byte").run([&] {
Obfuscation().Xor(data, key, offset++); // mutated differently each time obfuscation(data, offset++); // mutated differently each time
ankerl::nanobench::doNotOptimizeAway(data); ankerl::nanobench::doNotOptimizeAway(data);
}); });
} }
+5 -6
View File
@@ -174,7 +174,7 @@ void CDBBatch::Clear()
void CDBBatch::WriteImpl(std::span<const std::byte> key, DataStream& ssValue) void CDBBatch::WriteImpl(std::span<const std::byte> key, DataStream& ssValue)
{ {
leveldb::Slice slKey(CharCast(key.data()), key.size()); leveldb::Slice slKey(CharCast(key.data()), key.size());
ssValue.Xor(dbwrapper_private::GetObfuscation(parent)); dbwrapper_private::GetObfuscation(parent)(ssValue);
leveldb::Slice slValue(CharCast(ssValue.data()), ssValue.size()); leveldb::Slice slValue(CharCast(ssValue.data()), ssValue.size());
m_impl_batch->batch.Put(slKey, slValue); m_impl_batch->batch.Put(slKey, slValue);
} }
@@ -249,15 +249,14 @@ CDBWrapper::CDBWrapper(const DBParams& params)
LogPrintf("Finished database compaction of %s\n", fs::PathToString(params.path)); LogPrintf("Finished database compaction of %s\n", fs::PathToString(params.path));
} }
m_obfuscation = std::vector<uint8_t>(Obfuscation::KEY_SIZE, '\000'); // Needed for unobfuscated Read()/Write() below assert(!m_obfuscation); // Needed for unobfuscated Read()/Write() below
if (!Read(OBFUSCATION_KEY_KEY, m_obfuscation) && params.obfuscate && IsEmpty()) { if (!Read(OBFUSCATION_KEY_KEY, m_obfuscation) && params.obfuscate && IsEmpty()) {
// Generate, write and read back the new obfuscation key, making sure we don't obfuscate the key itself // Generate, write and read back the new obfuscation key, making sure we don't obfuscate the key itself
Write(OBFUSCATION_KEY_KEY, FastRandomContext{}.randbytes(Obfuscation::KEY_SIZE)); Write(OBFUSCATION_KEY_KEY, FastRandomContext{}.randbytes(Obfuscation::KEY_SIZE));
Read(OBFUSCATION_KEY_KEY, m_obfuscation); Read(OBFUSCATION_KEY_KEY, m_obfuscation);
LogInfo("Wrote new obfuscation key for %s: %s", fs::PathToString(params.path), HexStr(m_obfuscation)); LogInfo("Wrote new obfuscation key for %s: %s", fs::PathToString(params.path), m_obfuscation.HexKey());
} }
LogInfo("Using obfuscation key for %s: %s", fs::PathToString(params.path), HexStr(m_obfuscation)); LogInfo("Using obfuscation key for %s: %s", fs::PathToString(params.path), m_obfuscation.HexKey());
} }
CDBWrapper::~CDBWrapper() CDBWrapper::~CDBWrapper()
@@ -385,7 +384,7 @@ void CDBIterator::Next() { m_impl_iter->iter->Next(); }
namespace dbwrapper_private { namespace dbwrapper_private {
const std::vector<unsigned char>& GetObfuscation(const CDBWrapper &w) const Obfuscation& GetObfuscation(const CDBWrapper& w)
{ {
return w.m_obfuscation; return w.m_obfuscation;
} }
+6 -8
View File
@@ -18,7 +18,6 @@
#include <optional> #include <optional>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <vector>
static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64; static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64;
static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024; static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024;
@@ -63,8 +62,7 @@ namespace dbwrapper_private {
* Database obfuscation should be considered an implementation detail of the * Database obfuscation should be considered an implementation detail of the
* specific database. * specific database.
*/ */
const std::vector<unsigned char>& GetObfuscation(const CDBWrapper &w); const Obfuscation& GetObfuscation(const CDBWrapper&);
}; // namespace dbwrapper_private }; // namespace dbwrapper_private
bool DestroyDB(const std::string& path_str); bool DestroyDB(const std::string& path_str);
@@ -166,7 +164,7 @@ public:
template<typename V> bool GetValue(V& value) { template<typename V> bool GetValue(V& value) {
try { try {
DataStream ssValue{GetValueImpl()}; DataStream ssValue{GetValueImpl()};
ssValue.Xor(dbwrapper_private::GetObfuscation(parent)); dbwrapper_private::GetObfuscation(parent)(ssValue);
ssValue >> value; ssValue >> value;
} catch (const std::exception&) { } catch (const std::exception&) {
return false; return false;
@@ -179,7 +177,7 @@ struct LevelDBContext;
class CDBWrapper class CDBWrapper
{ {
friend const std::vector<unsigned char>& dbwrapper_private::GetObfuscation(const CDBWrapper &w); friend const Obfuscation& dbwrapper_private::GetObfuscation(const CDBWrapper&);
private: private:
//! holds all leveldb-specific fields of this class //! holds all leveldb-specific fields of this class
std::unique_ptr<LevelDBContext> m_db_context; std::unique_ptr<LevelDBContext> m_db_context;
@@ -187,8 +185,8 @@ private:
//! the name of this database //! the name of this database
std::string m_name; std::string m_name;
//! a key used for optional XOR-obfuscation of the database //! optional XOR-obfuscation of the database
std::vector<unsigned char> m_obfuscation; Obfuscation m_obfuscation;
//! obfuscation key storage key, null-prefixed to avoid collisions //! obfuscation key storage key, null-prefixed to avoid collisions
inline static const std::string OBFUSCATION_KEY_KEY{"\000obfuscate_key", 14}; // explicit size to avoid truncation at leading \0 inline static const std::string OBFUSCATION_KEY_KEY{"\000obfuscate_key", 14}; // explicit size to avoid truncation at leading \0
@@ -223,7 +221,7 @@ public:
} }
try { try {
DataStream ssValue{MakeByteSpan(*strValue)}; DataStream ssValue{MakeByteSpan(*strValue)};
ssValue.Xor(m_obfuscation); m_obfuscation(ssValue);
ssValue >> value; ssValue >> value;
} catch (const std::exception&) { } catch (const std::exception&) {
return false; return false;
+1 -1
View File
@@ -1174,7 +1174,7 @@ static auto InitBlocksdirXorKey(const BlockManager::Options& opts)
}; };
} }
LogInfo("Using obfuscation key for blocksdir *.dat files (%s): '%s'\n", fs::PathToString(opts.blocks_dir), HexStr(obfuscation)); LogInfo("Using obfuscation key for blocksdir *.dat files (%s): '%s'\n", fs::PathToString(opts.blocks_dir), HexStr(obfuscation));
return std::vector<std::byte>{obfuscation.begin(), obfuscation.end()}; return Obfuscation{obfuscation};
} }
BlockManager::BlockManager(const util::SignalInterrupt& interrupt, Options opts) BlockManager::BlockManager(const util::SignalInterrupt& interrupt, Options opts)
+1 -1
View File
@@ -235,7 +235,7 @@ private:
const bool m_prune_mode; const bool m_prune_mode;
const std::vector<std::byte> m_obfuscation; const Obfuscation m_obfuscation;
/** Dirty block index entries. */ /** Dirty block index entries. */
std::set<CBlockIndex*> m_dirty_blockindex; std::set<CBlockIndex*> m_dirty_blockindex;
+2 -3
View File
@@ -64,7 +64,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) { if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) {
file.SetObfuscation({}); file.SetObfuscation({});
} else if (version == MEMPOOL_DUMP_VERSION) { } else if (version == MEMPOOL_DUMP_VERSION) {
std::vector<std::byte> obfuscation(Obfuscation::KEY_SIZE); Obfuscation obfuscation;
file >> obfuscation; file >> obfuscation;
file.SetObfuscation(obfuscation); file.SetObfuscation(obfuscation);
} else { } else {
@@ -183,8 +183,7 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
file << version; file << version;
if (!pool.m_opts.persist_v1_dat) { if (!pool.m_opts.persist_v1_dat) {
std::vector<std::byte> obfuscation(Obfuscation::KEY_SIZE); const Obfuscation obfuscation{FastRandomContext{}.randbytes<Obfuscation::KEY_SIZE>()};
FastRandomContext{}.fillrand(obfuscation);
file << obfuscation; file << obfuscation;
file.SetObfuscation(obfuscation); file.SetObfuscation(obfuscation);
} else { } else {
+9 -10
View File
@@ -10,8 +10,7 @@
#include <array> #include <array>
AutoFile::AutoFile(std::FILE* file, std::vector<std::byte> obfuscation) AutoFile::AutoFile(std::FILE* file, const Obfuscation& obfuscation) : m_file{file}, m_obfuscation{obfuscation}
: m_file{file}, m_obfuscation{std::move(obfuscation)}
{ {
if (!IsNull()) { if (!IsNull()) {
auto pos{std::ftell(m_file)}; auto pos{std::ftell(m_file)};
@@ -22,12 +21,12 @@ AutoFile::AutoFile(std::FILE* file, std::vector<std::byte> obfuscation)
std::size_t AutoFile::detail_fread(std::span<std::byte> dst) std::size_t AutoFile::detail_fread(std::span<std::byte> dst)
{ {
if (!m_file) throw std::ios_base::failure("AutoFile::read: file handle is nullptr"); if (!m_file) throw std::ios_base::failure("AutoFile::read: file handle is nullptr");
size_t ret = std::fread(dst.data(), 1, dst.size(), m_file); const size_t ret = std::fread(dst.data(), 1, dst.size(), m_file);
if (!m_obfuscation.empty()) { if (m_obfuscation) {
if (!m_position.has_value()) throw std::ios_base::failure("AutoFile::read: position unknown"); if (!m_position) throw std::ios_base::failure("AutoFile::read: position unknown");
Obfuscation().Xor(dst.subspan(0, ret), m_obfuscation, *m_position); m_obfuscation(dst.subspan(0, ret), *m_position);
} }
if (m_position.has_value()) *m_position += ret; if (m_position) *m_position += ret;
return ret; return ret;
} }
@@ -82,7 +81,7 @@ void AutoFile::ignore(size_t nSize)
void AutoFile::write(std::span<const std::byte> src) void AutoFile::write(std::span<const std::byte> src)
{ {
if (!m_file) throw std::ios_base::failure("AutoFile::write: file handle is nullptr"); if (!m_file) throw std::ios_base::failure("AutoFile::write: file handle is nullptr");
if (m_obfuscation.empty()) { if (!m_obfuscation) {
if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) { if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) {
throw std::ios_base::failure("AutoFile::write: write failed"); throw std::ios_base::failure("AutoFile::write: write failed");
} }
@@ -102,9 +101,9 @@ void AutoFile::write(std::span<const std::byte> src)
void AutoFile::write_buffer(std::span<std::byte> src) void AutoFile::write_buffer(std::span<std::byte> src)
{ {
if (!m_file) throw std::ios_base::failure("AutoFile::write_buffer: file handle is nullptr"); if (!m_file) throw std::ios_base::failure("AutoFile::write_buffer: file handle is nullptr");
if (m_obfuscation.size()) { if (m_obfuscation) {
if (!m_position) throw std::ios_base::failure("AutoFile::write_buffer: obfuscation position unknown"); if (!m_position) throw std::ios_base::failure("AutoFile::write_buffer: obfuscation position unknown");
Obfuscation().Xor(src, m_obfuscation, *m_position); // obfuscate in-place m_obfuscation(src, *m_position); // obfuscate in-place
} }
if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) { if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) {
throw std::ios_base::failure("AutoFile::write_buffer: write failed"); throw std::ios_base::failure("AutoFile::write_buffer: write failed");
+4 -15
View File
@@ -25,7 +25,6 @@
#include <limits> #include <limits>
#include <optional> #include <optional>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
/* Minimal stream for overwriting and/or appending to an existing byte vector /* Minimal stream for overwriting and/or appending to an existing byte vector
@@ -245,23 +244,13 @@ public:
return (*this); return (*this);
} }
template<typename T> template <typename T>
DataStream& operator>>(T&& obj) DataStream& operator>>(T&& obj)
{ {
::Unserialize(*this, obj); ::Unserialize(*this, obj);
return (*this); return (*this);
} }
/**
* XOR the contents of this stream with a certain key.
*
* @param[in] key The key used to XOR the data in this stream.
*/
void Xor(const std::vector<unsigned char>& key)
{
Obfuscation().Xor(MakeWritableByteSpan(*this), MakeByteSpan(key));
}
/** Compute total memory usage of this object (own memory + any dynamic memory). */ /** Compute total memory usage of this object (own memory + any dynamic memory). */
size_t GetMemoryUsage() const noexcept; size_t GetMemoryUsage() const noexcept;
}; };
@@ -382,12 +371,12 @@ class AutoFile
{ {
protected: protected:
std::FILE* m_file; std::FILE* m_file;
std::vector<std::byte> m_obfuscation; Obfuscation m_obfuscation;
std::optional<int64_t> m_position; std::optional<int64_t> m_position;
bool m_was_written{false}; bool m_was_written{false};
public: public:
explicit AutoFile(std::FILE* file, std::vector<std::byte> obfuscation={}); explicit AutoFile(std::FILE* file, const Obfuscation& obfuscation = {});
~AutoFile() ~AutoFile()
{ {
@@ -435,7 +424,7 @@ public:
bool IsNull() const { return m_file == nullptr; } bool IsNull() const { return m_file == nullptr; }
/** Continue with a different XOR key */ /** Continue with a different XOR key */
void SetObfuscation(std::vector<std::byte> obfuscation) { m_obfuscation = obfuscation; } void SetObfuscation(const Obfuscation& obfuscation) { m_obfuscation = obfuscation; }
/** Implementation detail, only used internally. */ /** Implementation detail, only used internally. */
std::size_t detail_fread(std::span<std::byte> dst); std::size_t detail_fread(std::span<std::byte> dst);
+10 -19
View File
@@ -9,21 +9,12 @@
#include <util/string.h> #include <util/string.h>
#include <memory> #include <memory>
#include <ranges>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
using util::ToString; using util::ToString;
// Test if a string consists entirely of null characters
static bool is_null_key(const std::vector<unsigned char>& key) {
bool isnull = true;
for (unsigned int i = 0; i < key.size(); i++)
isnull &= (key[i] == '\x00');
return isnull;
}
BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup) BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(dbwrapper) BOOST_AUTO_TEST_CASE(dbwrapper)
@@ -33,7 +24,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper)
constexpr size_t CACHE_SIZE{1_MiB}; constexpr size_t CACHE_SIZE{1_MiB};
const fs::path path{m_args.GetDataDirBase() / "dbwrapper"}; const fs::path path{m_args.GetDataDirBase() / "dbwrapper"};
std::vector<uint8_t> obfuscation_key{}; Obfuscation obfuscation;
std::vector<std::pair<uint8_t, uint256>> key_values{}; std::vector<std::pair<uint8_t, uint256>> key_values{};
// Write values // Write values
@@ -42,8 +33,8 @@ BOOST_AUTO_TEST_CASE(dbwrapper)
BOOST_CHECK_EQUAL(obfuscate, !dbw.IsEmpty()); BOOST_CHECK_EQUAL(obfuscate, !dbw.IsEmpty());
// Ensure that we're doing real obfuscation when obfuscate=true // Ensure that we're doing real obfuscation when obfuscate=true
obfuscation_key = dbwrapper_private::GetObfuscation(dbw); obfuscation = dbwrapper_private::GetObfuscation(dbw);
BOOST_CHECK_EQUAL(obfuscate, !is_null_key(obfuscation_key)); BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw));
for (uint8_t k{0}; k < 10; ++k) { for (uint8_t k{0}; k < 10; ++k) {
uint8_t key{k}; uint8_t key{k};
@@ -56,7 +47,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper)
// Verify that the obfuscation key is never obfuscated // Verify that the obfuscation key is never obfuscated
{ {
CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = false}}; CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = false}};
BOOST_CHECK(obfuscation_key == dbwrapper_private::GetObfuscation(dbw)); BOOST_CHECK_EQUAL(obfuscation, dbwrapper_private::GetObfuscation(dbw));
} }
// Read back the values // Read back the values
@@ -64,8 +55,8 @@ BOOST_AUTO_TEST_CASE(dbwrapper)
CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = obfuscate}}; CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = obfuscate}};
// Ensure obfuscation is read back correctly // Ensure obfuscation is read back correctly
BOOST_CHECK(obfuscation_key == dbwrapper_private::GetObfuscation(dbw)); BOOST_CHECK_EQUAL(obfuscation, dbwrapper_private::GetObfuscation(dbw));
BOOST_CHECK_EQUAL(obfuscate, !is_null_key(obfuscation_key)); BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw));
// Verify all written values // Verify all written values
for (const auto& [key, expected_value] : key_values) { for (const auto& [key, expected_value] : key_values) {
@@ -89,7 +80,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_basic_data)
bool res_bool; bool res_bool;
// Ensure that we're doing real obfuscation when obfuscate=true // Ensure that we're doing real obfuscation when obfuscate=true
BOOST_CHECK_EQUAL(obfuscate, !is_null_key(dbwrapper_private::GetObfuscation(dbw))); BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw));
//Simulate block raw data - "b + block hash" //Simulate block raw data - "b + block hash"
std::string key_block = "b" + m_rng.rand256().ToString(); std::string key_block = "b" + m_rng.rand256().ToString();
@@ -264,7 +255,7 @@ BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate)
BOOST_CHECK_EQUAL(res2.ToString(), in.ToString()); BOOST_CHECK_EQUAL(res2.ToString(), in.ToString());
BOOST_CHECK(!odbw.IsEmpty()); BOOST_CHECK(!odbw.IsEmpty());
BOOST_CHECK(is_null_key(dbwrapper_private::GetObfuscation(odbw))); // The key should be an empty string BOOST_CHECK(!dbwrapper_private::GetObfuscation(odbw)); // The key should be an empty string
uint256 in2 = m_rng.rand256(); uint256 in2 = m_rng.rand256();
uint256 res3; uint256 res3;
@@ -301,7 +292,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex)
// Check that the key/val we wrote with unobfuscated wrapper doesn't exist // Check that the key/val we wrote with unobfuscated wrapper doesn't exist
uint256 res2; uint256 res2;
BOOST_CHECK(!odbw.Read(key, res2)); BOOST_CHECK(!odbw.Read(key, res2));
BOOST_CHECK(!is_null_key(dbwrapper_private::GetObfuscation(odbw))); BOOST_CHECK(dbwrapper_private::GetObfuscation(odbw));
uint256 in2 = m_rng.rand256(); uint256 in2 = m_rng.rand256();
uint256 res3; uint256 res3;
+1 -1
View File
@@ -22,7 +22,7 @@ FUZZ_TARGET(autofile)
const auto key_bytes{ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, Obfuscation::KEY_SIZE)}; const auto key_bytes{ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, Obfuscation::KEY_SIZE)};
AutoFile auto_file{ AutoFile auto_file{
fuzzed_file_provider.open(), fuzzed_file_provider.open(),
key_bytes, Obfuscation{std::span{key_bytes}.first<Obfuscation::KEY_SIZE>()},
}; };
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
{ {
+1 -1
View File
@@ -24,7 +24,7 @@ FUZZ_TARGET(buffered_file)
const auto key_bytes{ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, Obfuscation::KEY_SIZE)}; const auto key_bytes{ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, Obfuscation::KEY_SIZE)};
AutoFile fuzzed_file{ AutoFile fuzzed_file{
fuzzed_file_provider.open(), fuzzed_file_provider.open(),
key_bytes, Obfuscation{std::span{key_bytes}.first<Obfuscation::KEY_SIZE>()},
}; };
try { try {
auto n_buf_size = fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096); auto n_buf_size = fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(0, 4096);
+49 -14
View File
@@ -21,10 +21,10 @@ BOOST_FIXTURE_TEST_SUITE(streams_tests, BasicTestingSetup)
// Test that obfuscation can be properly reverted even with random chunk sizes. // Test that obfuscation can be properly reverted even with random chunk sizes.
BOOST_AUTO_TEST_CASE(xor_roundtrip_random_chunks) BOOST_AUTO_TEST_CASE(xor_roundtrip_random_chunks)
{ {
auto apply_random_xor_chunks{[&](std::span<std::byte> target, std::span<const std::byte, Obfuscation::KEY_SIZE> obfuscation) { auto apply_random_xor_chunks{[&](std::span<std::byte> target, const Obfuscation& obfuscation) {
for (size_t offset{0}; offset < target.size();) { for (size_t offset{0}; offset < target.size();) {
const size_t chunk_size{1 + m_rng.randrange(target.size() - offset)}; const size_t chunk_size{1 + m_rng.randrange(target.size() - offset)};
Obfuscation().Xor(target.subspan(offset, chunk_size), obfuscation, offset); obfuscation(target.subspan(offset, chunk_size), offset);
offset += chunk_size; offset += chunk_size;
} }
}}; }};
@@ -35,13 +35,14 @@ BOOST_AUTO_TEST_CASE(xor_roundtrip_random_chunks)
std::vector roundtrip{original}; std::vector roundtrip{original};
const auto key_bytes{m_rng.randbool() ? m_rng.randbytes<Obfuscation::KEY_SIZE>() : std::array<std::byte, Obfuscation::KEY_SIZE>{}}; const auto key_bytes{m_rng.randbool() ? m_rng.randbytes<Obfuscation::KEY_SIZE>() : std::array<std::byte, Obfuscation::KEY_SIZE>{}};
apply_random_xor_chunks(roundtrip, key_bytes); const Obfuscation obfuscation{key_bytes};
apply_random_xor_chunks(roundtrip, obfuscation);
const bool key_all_zeros{std::ranges::all_of( const bool key_all_zeros{std::ranges::all_of(
std::span{key_bytes}.first(std::min(write_size, Obfuscation::KEY_SIZE)), [](auto b) { return b == std::byte{0}; })}; std::span{key_bytes}.first(std::min(write_size, Obfuscation::KEY_SIZE)), [](auto b) { return b == std::byte{0}; })};
BOOST_CHECK(key_all_zeros ? original == roundtrip : original != roundtrip); BOOST_CHECK(key_all_zeros ? original == roundtrip : original != roundtrip);
apply_random_xor_chunks(roundtrip, key_bytes); apply_random_xor_chunks(roundtrip, obfuscation);
BOOST_CHECK(original == roundtrip); BOOST_CHECK(original == roundtrip);
} }
} }
@@ -62,24 +63,58 @@ BOOST_AUTO_TEST_CASE(xor_bytes_reference)
const size_t write_offset{std::min(write_size, m_rng.randrange(Obfuscation::KEY_SIZE * 2))}; // Write unaligned data const size_t write_offset{std::min(write_size, m_rng.randrange(Obfuscation::KEY_SIZE * 2))}; // Write unaligned data
const auto key_bytes{m_rng.randbool() ? m_rng.randbytes<Obfuscation::KEY_SIZE>() : std::array<std::byte, Obfuscation::KEY_SIZE>{}}; const auto key_bytes{m_rng.randbool() ? m_rng.randbytes<Obfuscation::KEY_SIZE>() : std::array<std::byte, Obfuscation::KEY_SIZE>{}};
const std::vector obfuscation{key_bytes.begin(), key_bytes.end()}; const Obfuscation obfuscation{key_bytes};
std::vector expected{m_rng.randbytes<std::byte>(write_size)}; std::vector expected{m_rng.randbytes<std::byte>(write_size)};
std::vector actual{expected}; std::vector actual{expected};
expected_xor(std::span{expected}.subspan(write_offset), key_bytes, key_offset); expected_xor(std::span{expected}.subspan(write_offset), key_bytes, key_offset);
Obfuscation().Xor(std::span{actual}.subspan(write_offset), key_bytes, key_offset); obfuscation(std::span{actual}.subspan(write_offset), key_offset);
BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), actual.begin(), actual.end()); BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), actual.begin(), actual.end());
} }
} }
BOOST_AUTO_TEST_CASE(obfuscation_hexkey)
{
const auto key_bytes{m_rng.randbytes<Obfuscation::KEY_SIZE>()};
const Obfuscation obfuscation{key_bytes};
BOOST_CHECK_EQUAL(obfuscation.HexKey(), HexStr(key_bytes));
}
BOOST_AUTO_TEST_CASE(obfuscation_serialize)
{
const Obfuscation original{m_rng.randbytes<Obfuscation::KEY_SIZE>()};
// Serialization
DataStream ds;
ds << original;
BOOST_CHECK_EQUAL(ds.size(), 1 + Obfuscation::KEY_SIZE); // serialized as a vector
// Deserialization
Obfuscation recovered{};
ds >> recovered;
BOOST_CHECK_EQUAL(recovered.HexKey(), original.HexKey());
}
BOOST_AUTO_TEST_CASE(obfuscation_empty)
{
const Obfuscation null_obf{};
BOOST_CHECK(!null_obf);
const Obfuscation non_null_obf{"ff00ff00ff00ff00"_hex};
BOOST_CHECK(non_null_obf);
}
BOOST_AUTO_TEST_CASE(xor_file) BOOST_AUTO_TEST_CASE(xor_file)
{ {
fs::path xor_path{m_args.GetDataDirBase() / "test_xor.bin"}; fs::path xor_path{m_args.GetDataDirBase() / "test_xor.bin"};
auto raw_file{[&](const auto& mode) { return fsbridge::fopen(xor_path, mode); }}; auto raw_file{[&](const auto& mode) { return fsbridge::fopen(xor_path, mode); }};
const std::vector<uint8_t> test1{1, 2, 3}; const std::vector<uint8_t> test1{1, 2, 3};
const std::vector<uint8_t> test2{4, 5}; const std::vector<uint8_t> test2{4, 5};
const auto obfuscation{"ff00ff00ff00ff00"_hex_v}; const Obfuscation obfuscation{"ff00ff00ff00ff00"_hex};
{ {
// Check errors for missing file // Check errors for missing file
@@ -284,23 +319,23 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor)
// Degenerate case // Degenerate case
{ {
DataStream ds{}; DataStream ds{};
ds.Xor("0000000000000000"_hex_v_u8); Obfuscation{}(ds);
BOOST_CHECK_EQUAL(""s, ds.str()); BOOST_CHECK_EQUAL(""s, ds.str());
} }
{ {
const auto obfuscation{"ffffffffffffffff"_hex_v_u8}; const Obfuscation obfuscation{"ffffffffffffffff"_hex};
DataStream ds{"0ff0"_hex}; DataStream ds{"0ff0"_hex};
ds.Xor(obfuscation); obfuscation(ds);
BOOST_CHECK_EQUAL("\xf0\x0f"s, ds.str()); BOOST_CHECK_EQUAL("\xf0\x0f"s, ds.str());
} }
{ {
const auto obfuscation{"ff0fff0fff0fff0f"_hex_v_u8}; const Obfuscation obfuscation{"ff0fff0fff0fff0f"_hex};
DataStream ds{"f00f"_hex}; DataStream ds{"f00f"_hex};
ds.Xor(obfuscation); obfuscation(ds);
BOOST_CHECK_EQUAL("\x0f\x00"s, ds.str()); BOOST_CHECK_EQUAL("\x0f\x00"s, ds.str());
} }
} }
@@ -613,7 +648,7 @@ BOOST_AUTO_TEST_CASE(buffered_reader_matches_autofile_random_content)
const FlatFilePos pos{0, 0}; const FlatFilePos pos{0, 0};
const FlatFileSeq test_file{m_args.GetDataDirBase(), "buffered_file_test_random", node::BLOCKFILE_CHUNK_SIZE}; const FlatFileSeq test_file{m_args.GetDataDirBase(), "buffered_file_test_random", node::BLOCKFILE_CHUNK_SIZE};
const auto obfuscation{m_rng.randbytes<std::byte>(Obfuscation::KEY_SIZE)}; const Obfuscation obfuscation{m_rng.randbytes<Obfuscation::KEY_SIZE>()};
// Write out the file with random content // Write out the file with random content
{ {
@@ -668,7 +703,7 @@ BOOST_AUTO_TEST_CASE(buffered_writer_matches_autofile_random_content)
const FlatFileSeq test_buffered{m_args.GetDataDirBase(), "buffered_write_test", node::BLOCKFILE_CHUNK_SIZE}; const FlatFileSeq test_buffered{m_args.GetDataDirBase(), "buffered_write_test", node::BLOCKFILE_CHUNK_SIZE};
const FlatFileSeq test_direct{m_args.GetDataDirBase(), "direct_write_test", node::BLOCKFILE_CHUNK_SIZE}; const FlatFileSeq test_direct{m_args.GetDataDirBase(), "direct_write_test", node::BLOCKFILE_CHUNK_SIZE};
const auto obfuscation{m_rng.randbytes<std::byte>(Obfuscation::KEY_SIZE)}; const Obfuscation obfuscation{m_rng.randbytes<Obfuscation::KEY_SIZE>()};
{ {
DataBuffer test_data{m_rng.randbytes<std::byte>(file_size)}; DataBuffer test_data{m_rng.randbytes<std::byte>(file_size)};
+45 -4
View File
@@ -7,19 +7,32 @@
#include <cstdint> #include <cstdint>
#include <span.h> #include <span.h>
#include <tinyformat.h>
#include <util/strencodings.h>
#include <ios>
class Obfuscation class Obfuscation
{ {
public: public:
static constexpr size_t KEY_SIZE{sizeof(uint64_t)}; using KeyType = uint64_t;
static constexpr size_t KEY_SIZE{sizeof(KeyType)};
void Xor(std::span<std::byte> write, std::span<const std::byte> key, size_t key_offset = 0) Obfuscation() : m_key{KEY_SIZE, std::byte{0}} {}
explicit Obfuscation(std::span<const std::byte, KEY_SIZE> key_bytes)
{ {
assert(key.size() == KEY_SIZE); m_key = {key_bytes.begin(), key_bytes.end()};
}
operator bool() const { return ToKey() != 0; }
void operator()(std::span<std::byte> write, size_t key_offset = 0) const
{
assert(m_key.size() == KEY_SIZE);
key_offset %= KEY_SIZE; key_offset %= KEY_SIZE;
for (size_t i = 0, j = key_offset; i != write.size(); i++) { for (size_t i = 0, j = key_offset; i != write.size(); i++) {
write[i] ^= key[j++]; write[i] ^= m_key[j++];
// This potentially acts on very many bytes of data, so it's // This potentially acts on very many bytes of data, so it's
// important that we calculate `j`, i.e. the `key` index in this // important that we calculate `j`, i.e. the `key` index in this
@@ -29,6 +42,34 @@ public:
j = 0; j = 0;
} }
} }
template <typename Stream>
void Serialize(Stream& s) const
{
s << m_key;
}
template <typename Stream>
void Unserialize(Stream& s)
{
s >> m_key;
if (m_key.size() != KEY_SIZE) throw std::ios_base::failure(strprintf("Obfuscation key size should be exactly %s bytes long", KEY_SIZE));
}
std::string HexKey() const
{
return HexStr(m_key);
}
private:
std::vector<std::byte> m_key;
KeyType ToKey() const
{
KeyType key{};
std::memcpy(&key, m_key.data(), KEY_SIZE);
return key;
}
}; };
#endif // BITCOIN_UTIL_OBFUSCATION_H #endif // BITCOIN_UTIL_OBFUSCATION_H