#include "platform/esp/common/config_preferences.h" #include "platform/esp/common/identity_generation_recovery.h" #include "platform/esp/common/identity_native_key_validation.h" #include #include #include #include #include namespace { constexpr const char* kPrefsNs = "lxmf_ident"; constexpr const char* kEncPubKey = "enc_pub"; constexpr const char* kEncPrivKey = "enc_priv"; constexpr const char* kSigPubKey = "sig_pub"; constexpr const char* kSigPrivKey = "sig_priv"; bool isAllZero(const uint8_t* data, size_t length) { for (size_t i = 0; i < length; ++i) if (data[i]) return false; return true; } class LxmfIdentity { public: static constexpr size_t kSigPubKeySize = 32; std::array enc_pub_{}, enc_priv_{}, sig_pub_{}; std::array sig_priv_{}; unsigned recomputed = 0; uint32_t author_generation_ = 0; std::array identity_hash_{}; bool loadFromPrefs(); void recomputeDerivedFields() { ++recomputed; identity_hash_.fill(5); } }; // Exact production loader; only derived-address calculation and NVS I/O are fixtures. #include "identity_load_actual.inc" } // namespace void testIdentityStoredKeyValidation() { LxmfIdentity source; source.enc_priv_.fill(24); source.enc_priv_[31] = 0x58; assert(Curve25519::eval(source.enc_pub_.data(), source.enc_priv_.data(), nullptr)); std::array seed{}; seed.fill(3); ed25519_create_keypair(source.sig_pub_.data(), source.sig_priv_.data(), seed.data()); auto& storage = fake_nvs::values[kPrefsNs]; storage[kEncPubKey] = std::vector(source.enc_pub_.begin(), source.enc_pub_.end()); storage[kEncPrivKey] = std::vector(source.enc_priv_.begin(), source.enc_priv_.end()); storage[kSigPubKey] = std::vector(source.sig_pub_.begin(), source.sig_pub_.end()); storage[kSigPrivKey] = std::vector(source.sig_priv_.begin(), source.sig_priv_.end()); const auto original = storage; LxmfIdentity valid; assert(valid.loadFromPrefs() && valid.recomputed == 1); for (const char* key : {kEncPubKey, kEncPrivKey, kSigPubKey, kSigPrivKey}) { storage = original; std::get>(storage[key])[11] ^= 1; const auto damaged = storage; LxmfIdentity invalid; assert(!invalid.loadFromPrefs() && invalid.recomputed == 0); assert(storage == damaged); } storage = original; std::get>(storage[kEncPrivKey])[0] |= 1; LxmfIdentity unclamped; assert(!unclamped.loadFromPrefs()); storage = original; std::array combined_pub{}; std::array combined_priv{}; std::array work{}; std::memcpy(combined_pub.data(), source.enc_pub_.data(), 32); std::memcpy(combined_pub.data() + 32, source.sig_pub_.data(), 32); std::memcpy(combined_priv.data(), source.enc_priv_.data(), 32); std::memcpy(combined_priv.data() + 32, source.sig_priv_.data(), 64); using namespace platform::esp::common; assert(commitIdentityRecord(5, 5, combined_pub.data(), combined_priv.data(), work.data(), work.size(), validateNativeIdentityKeys) == IdentityRecordResult::Committed); // New record is authoritative even when legacy encryption keys are damaged. std::get>(storage[kEncPrivKey])[11] ^= 1; LxmfIdentity restored; assert(restored.loadFromPrefs() && restored.author_generation_ == 6 && restored.enc_priv_ == source.enc_priv_); const auto record = storage["native_v1"]; std::get>(storage["native_v1"])[8] ^= 1; LxmfIdentity corrupt_record; assert(!corrupt_record.loadFromPrefs() && corrupt_record.recomputed == 0); storage["native_v1"] = record; auto& epoch = std::get>(fake_nvs::values["tm_author"]["epoch"]); epoch[4] ^= 1; LxmfIdentity collision; assert(!collision.loadFromPrefs() && collision.author_generation_ == 0); assert(fake_nvs::handles.empty()); }