From a65769c96ae73ae56d99229670b2eeddb29b3c67 Mon Sep 17 00:00:00 2001 From: mikecarper Date: Tue, 11 Aug 2026 14:25:52 -0700 Subject: [PATCH] Harden ESP32 identity entropy handling --- examples/companion_radio/MyMesh.cpp | 27 ++-- examples/kiss_modem/main.cpp | 25 +++- examples/simple_repeater/main.cpp | 26 +++- examples/simple_room_server/main.cpp | 27 +++- examples/simple_secure_chat/main.cpp | 31 ++++- examples/simple_sensor/main.cpp | 27 +++- src/helpers/ESP32Board.h | 5 + src/helpers/ESP32TrueRandom.cpp | 129 ++++++++++++++++++ src/helpers/ESP32TrueRandom.h | 28 ++++ src/helpers/IdentityGeneration.h | 28 ++++ src/helpers/radiolib/RadioLibWrappers.h | 11 +- test/README.md | 1 + .../test_identity_generation.cpp | 80 +++++++++++ variants/generic_espnow/target.cpp | 10 +- variants/sensecap_indicator-espnow/target.cpp | 10 +- 15 files changed, 414 insertions(+), 51 deletions(-) create mode 100644 src/helpers/ESP32TrueRandom.cpp create mode 100644 src/helpers/ESP32TrueRandom.h create mode 100644 src/helpers/IdentityGeneration.h create mode 100644 test/test_identity_generation/test_identity_generation.cpp diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9db83d28..f25a0071 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2,9 +2,14 @@ #include // needed for PlatformIO #include +#include #include "helpers/radiolib/RXPowerSaving.h" #include "helpers/radiolib/RxBoostedGainDefaults.h" +#if defined(ESP32_PLATFORM) +#include +#endif + #ifdef ENABLE_USB_INTERFACE #include #include @@ -1382,15 +1387,21 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe void MyMesh::begin(bool has_display) { BaseChatMesh::begin(); - const bool is_new_install = !_store->loadMainIdentity(self_id); + const bool is_new_install = !_store->loadMainIdentity(self_id) + || mesh::hasReservedIdentityPrefix(self_id); + bool identity_ready = true; if (is_new_install) { - self_id = radio_new_identity(); // create new random identity - int count = 0; - while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes - self_id = radio_new_identity(); - count++; - } - _store->saveMainIdentity(self_id); + identity_ready = mesh::generateUsableLocalIdentity(self_id, radio_new_identity); + if (identity_ready) _store->saveMainIdentity(self_id); + } + +#if defined(ESP32_PLATFORM) + mesh::discardESP32TrueRandom(); +#endif + if (!identity_ready) { + MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting"); + board.reboot(); + return; } // if name is provided as a build flag, use that as default node name instead diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index aab5badc..0202bfb3 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -1,9 +1,14 @@ #include #include #include +#include #include #include "KissModem.h" +#if defined(ESP32_PLATFORM) + #include +#endif + #if defined(NRF52_PLATFORM) #include #elif defined(RP2040_PLATFORM) @@ -48,12 +53,20 @@ void loadOrCreateIdentity() { #error "Filesystem not defined" #endif - if (!store.load("_main", identity)) { - identity = radio_new_identity(); - while (identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF) { - identity = radio_new_identity(); - } - store.save("_main", identity); + const bool needs_identity = !store.load("_main", identity) + || mesh::hasReservedIdentityPrefix(identity); + bool identity_ready = true; + if (needs_identity) { + identity_ready = mesh::generateUsableLocalIdentity(identity, radio_new_identity); + if (identity_ready) store.save("_main", identity); + } + +#if defined(ESP32_PLATFORM) + mesh::discardESP32TrueRandom(); +#endif + if (!identity_ready) { + MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting"); + board.reboot(); } } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 68d079a3..d1b4aeae 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -1,7 +1,11 @@ #include // needed for PlatformIO #include +#include #include "MyMesh.h" +#if defined(ESP32_PLATFORM) + #include +#endif #if defined(ESP32) && MAX_RECENT_REPEATERS > 0 #include #endif @@ -110,14 +114,22 @@ void setup() { #else #error "need to define filesystem" #endif - if (!store.load("_main", the_mesh.self_id)) { + const bool needs_identity = !store.load("_main", the_mesh.self_id) + || mesh::hasReservedIdentityPrefix(the_mesh.self_id); + bool identity_ready = true; + if (needs_identity) { MESH_DEBUG_PRINTLN("Generating new keypair"); - the_mesh.self_id = radio_new_identity(); // create new random identity - int count = 0; - while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes - the_mesh.self_id = radio_new_identity(); count++; - } - store.save("_main", the_mesh.self_id); + identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity); + if (identity_ready) store.save("_main", the_mesh.self_id); + } + +#if defined(ESP32_PLATFORM) + mesh::discardESP32TrueRandom(); +#endif + if (!identity_ready) { + MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting"); + board.reboot(); + return; } // Print the running firmware version at boot so it's visible after an OTA diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 05c2f8bc..49c37e0e 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -1,8 +1,13 @@ #include // needed for PlatformIO #include +#include #include "MyMesh.h" +#if defined(ESP32_PLATFORM) + #include +#endif + #ifdef ETHERNET_ENABLED #define ETHERNET_CLI_BANNER "MeshCore Room Server CLI" #include @@ -78,13 +83,21 @@ void setup() { #else #error "need to define filesystem" #endif - if (!store.load("_main", the_mesh.self_id)) { - the_mesh.self_id = radio_new_identity(); // create new random identity - int count = 0; - while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes - the_mesh.self_id = radio_new_identity(); count++; - } - store.save("_main", the_mesh.self_id); + const bool needs_identity = !store.load("_main", the_mesh.self_id) + || mesh::hasReservedIdentityPrefix(the_mesh.self_id); + bool identity_ready = true; + if (needs_identity) { + identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity); + if (identity_ready) store.save("_main", the_mesh.self_id); + } + +#if defined(ESP32_PLATFORM) + mesh::discardESP32TrueRandom(); +#endif + if (!identity_ready) { + MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting"); + board.reboot(); + return; } Serial.print("Room ID: "); diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 0b3eb4ac..afc88f3b 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -10,9 +10,13 @@ #endif #include +#include #include #include #include +#if defined(ESP32_PLATFORM) + #include +#endif #include #include @@ -310,7 +314,10 @@ public: #else IdentityStore store(fs, "/identity"); #endif - if (!store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name))) { // legacy: node_name was from identity file + const bool needs_identity = !store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name)) + || mesh::hasReservedIdentityPrefix(self_id); // legacy: node_name was from identity file + bool identity_ready = true; + if (needs_identity) { // Need way to get some entropy to seed RNG Serial.println("Press ENTER to generate key:"); char c = 0; @@ -319,12 +326,22 @@ public: } ((StdRNG *)getRNG())->begin(millis()); - self_id = mesh::LocalIdentity(getRNG()); // create new random identity - int count = 0; - while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes - self_id = mesh::LocalIdentity(getRNG()); count++; - } - store.save("_main", self_id); + #if defined(ESP32_PLATFORM) + identity_ready = mesh::generateUsableLocalIdentity(self_id, radio_new_identity); + #else + identity_ready = mesh::generateUsableLocalIdentity( + self_id, [this]() { return mesh::LocalIdentity(getRNG()); }); + #endif + if (identity_ready) store.save("_main", self_id); + } + + #if defined(ESP32_PLATFORM) + mesh::discardESP32TrueRandom(); + #endif + if (!identity_ready) { + MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting"); + board.reboot(); + return; } // load persisted prefs diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index e041dd3b..e8e853ec 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -1,4 +1,9 @@ #include "SensorMesh.h" +#include + +#if defined(ESP32_PLATFORM) + #include +#endif #ifdef DISPLAY_CLASS #include "UITask.h" @@ -100,14 +105,22 @@ void setup() { #else #error "need to define filesystem" #endif - if (!store.load("_main", the_mesh.self_id)) { + const bool needs_identity = !store.load("_main", the_mesh.self_id) + || mesh::hasReservedIdentityPrefix(the_mesh.self_id); + bool identity_ready = true; + if (needs_identity) { MESH_DEBUG_PRINTLN("Generating new keypair"); - the_mesh.self_id = radio_new_identity(); // create new random identity - int count = 0; - while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes - the_mesh.self_id = radio_new_identity(); count++; - } - store.save("_main", the_mesh.self_id); + identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity); + if (identity_ready) store.save("_main", the_mesh.self_id); + } + +#if defined(ESP32_PLATFORM) + mesh::discardESP32TrueRandom(); +#endif + if (!identity_ready) { + MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting"); + board.reboot(); + return; } Serial.print("Sensor ID: "); diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index f2e52016..f282845d 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -15,6 +15,7 @@ #include "soc/rtc.h" #include "esp_system.h" #include +#include "ESP32TrueRandom.h" #if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT && \ (!defined(ARDUINO_USB_MODE) || !ARDUINO_USB_MODE) @@ -38,6 +39,10 @@ protected: public: void begin() { + // Arduino's early init hook normally captured this before initVariant(). + // Keep this idempotent fallback before this class touches ADC peripherals. + mesh::initializeESP32TrueRandom(); + // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; diff --git a/src/helpers/ESP32TrueRandom.cpp b/src/helpers/ESP32TrueRandom.cpp new file mode 100644 index 00000000..ab724535 --- /dev/null +++ b/src/helpers/ESP32TrueRandom.cpp @@ -0,0 +1,129 @@ +#include "ESP32TrueRandom.h" + +#if defined(ESP32_PLATFORM) + +#include +#include +#include +#include +#include +#include + +#include "IdentityGeneration.h" + +namespace { + +constexpr size_t TRUE_RANDOM_POOL_SIZE = + SEED_SIZE * mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS; + +enum TrueRandomState : uint8_t { + TRUE_RANDOM_UNINITIALIZED, + TRUE_RANDOM_READY, + TRUE_RANDOM_DISCARDED +}; + +uint8_t true_random_pool[TRUE_RANDOM_POOL_SIZE]; +size_t true_random_offset = 0; +TrueRandomState true_random_state = TRUE_RANDOM_UNINITIALIZED; + +StaticSemaphore_t true_random_mutex_storage; +SemaphoreHandle_t true_random_mutex = NULL; +portMUX_TYPE true_random_mutex_init_mux = portMUX_INITIALIZER_UNLOCKED; + +SemaphoreHandle_t getTrueRandomMutex() { + // Always enter the cross-core critical section: an unlocked preliminary + // pointer read would itself race a first caller creating the mutex. + portENTER_CRITICAL(&true_random_mutex_init_mux); + if (true_random_mutex == NULL) { + true_random_mutex = xSemaphoreCreateMutexStatic(&true_random_mutex_storage); + } + SemaphoreHandle_t mutex = true_random_mutex; + portEXIT_CRITICAL(&true_random_mutex_init_mux); + return mutex; +} + +bool tryMixESP32TrueRandom(uint8_t* dest, size_t size) { + if (size == 0) return true; + if (dest == NULL) return false; + + SemaphoreHandle_t mutex = getTrueRandomMutex(); + if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) return false; + + const bool valid_offset = true_random_offset <= sizeof(true_random_pool); + const size_t available = valid_offset + ? sizeof(true_random_pool) - true_random_offset + : 0; + if (true_random_state != TRUE_RANDOM_READY || size > available) { + xSemaphoreGive(mutex); + return false; + } + + for (size_t i = 0; i < size; ++i) { + dest[i] ^= true_random_pool[true_random_offset + i]; + } + mbedtls_platform_zeroize(&true_random_pool[true_random_offset], size); + true_random_offset += size; + + xSemaphoreGive(mutex); + return true; +} + +} // namespace + +namespace mesh { + +void initializeESP32TrueRandom() { + SemaphoreHandle_t mutex = getTrueRandomMutex(); + if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) return; + + if (true_random_state != TRUE_RANDOM_UNINITIALIZED) { + xSemaphoreGive(mutex); + return; + } + + // ESP-IDF guarantees true RNG output while this SAR ADC entropy source is + // enabled. Arduino's init hook below runs before variant and board setup. + bootloader_random_enable(); + esp_fill_random(true_random_pool, sizeof(true_random_pool)); + bootloader_random_disable(); + + true_random_offset = 0; + true_random_state = TRUE_RANDOM_READY; + xSemaphoreGive(mutex); +} + +void mixESP32TrueRandom(uint8_t* dest, size_t size) { + if (tryMixESP32TrueRandom(dest, size)) return; + + // A software or radio source may already have populated dest. Never let + // those bytes escape as pseudo-only identity material. + if (dest != NULL && size > 0) mbedtls_platform_zeroize(dest, size); + discardESP32TrueRandom(); + esp_restart(); +} + +void discardESP32TrueRandom() { + SemaphoreHandle_t mutex = getTrueRandomMutex(); + if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) { + mbedtls_platform_zeroize(true_random_pool, sizeof(true_random_pool)); + true_random_offset = sizeof(true_random_pool); + true_random_state = TRUE_RANDOM_DISCARDED; + return; + } + + mbedtls_platform_zeroize(true_random_pool, sizeof(true_random_pool)); + true_random_offset = sizeof(true_random_pool); + true_random_state = TRUE_RANDOM_DISCARDED; + xSemaphoreGive(mutex); +} + +} // namespace mesh + +// Arduino-ESP32 calls this weak hook before initVariant() and setup(). Defining +// it here makes entropy capture independent of every board subclass's begin() +// ordering, including future variants which initialize ADC or RF early. +extern "C" void init() { + mesh::initializeESP32TrueRandom(); +} + +#endif diff --git a/src/helpers/ESP32TrueRandom.h b/src/helpers/ESP32TrueRandom.h new file mode 100644 index 00000000..b0c1d5b8 --- /dev/null +++ b/src/helpers/ESP32TrueRandom.h @@ -0,0 +1,28 @@ +#pragma once + +#if defined(ESP32_PLATFORM) + +#include +#include + +namespace mesh { + +// Capture true hardware entropy during Arduino's early initialization, before +// variant, board, ADC, Wi-Fi, or Bluetooth setup. ESP32Board::begin() calls +// this again as an idempotent fallback. +void initializeESP32TrueRandom(); + +// XOR previously captured true hardware entropy into every requested byte. +// This is fail-closed: if a complete hardware block is unavailable, dest is +// securely erased and the ESP32 restarts instead of returning pseudo-only +// output. +void mixESP32TrueRandom(uint8_t* dest, size_t size); + +// Securely erase and permanently close the startup pool once the persisted or +// newly generated identity is ready. It cannot be reopened later because ADC +// or RF peripherals may have started by then. +void discardESP32TrueRandom(); + +} // namespace mesh + +#endif diff --git a/src/helpers/IdentityGeneration.h b/src/helpers/IdentityGeneration.h new file mode 100644 index 00000000..afb76677 --- /dev/null +++ b/src/helpers/IdentityGeneration.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace mesh { + +// Keep identity generation bounded so platforms which cache startup entropy +// can provision an exact amount. Eleven attempts makes exhaustion vanishingly +// unlikely while still allowing callers to fail closed. +constexpr size_t MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS = 11; + +inline bool hasReservedIdentityPrefix(const Identity& identity) { + return identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF; +} + +template +bool generateUsableLocalIdentity(LocalIdentity& identity, Generator generator) { + for (size_t attempt = 0; + attempt < MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS; + ++attempt) { + identity = generator(); + if (!hasReservedIdentityPrefix(identity)) return true; + } + return false; +} + +} // namespace mesh diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 19fb1914..32e43ab0 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -15,6 +15,9 @@ #ifdef USE_CC310_HW_CRYPTO #include "../NRF52Crypto.h" #endif +#ifdef ESP32_PLATFORM +#include "../ESP32TrueRandom.h" +#endif struct PacketMillis { uint32_t preambleMillis; // preamble-detect -> header-valid deadline uint32_t payloadMillis; // header-valid -> rx-done deadline @@ -217,14 +220,16 @@ public: RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { } void random(uint8_t* dest, size_t sz) override { - // Preserve the existing radio/PRNG entropy on every platform. On nRF52840, - // independently generated CC310 bytes are mixed in without becoming the - // sole source of randomness. + // Preserve the existing radio/PRNG entropy on every platform. Independent + // hardware entropy is mixed in without becoming the sole source. for (size_t i = 0; i < sz; i++) { dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF); } #ifdef USE_CC310_HW_CRYPTO mesh::mixCC310Random(dest, sz); +#endif +#ifdef ESP32_PLATFORM + mesh::mixESP32TrueRandom(dest, sz); #endif } }; diff --git a/test/README.md b/test/README.md index 592c655a..c9dc9957 100644 --- a/test/README.md +++ b/test/README.md @@ -43,6 +43,7 @@ does not reflect the GoogleTest count -- run the built binary directly | `test_flood_filter_policy` | `src/helpers/FloodFilterPolicy.h` | unordered blacklist matching; ordered 1/2/3-byte pbyte rule prefixes; original incoming scope classes and canonical region-name identity; channel-authentication cache key comparison; priority ordering and terminal stop masks; bridge-bucket and regionless channel-target selector encoding; `require=region` and per-channel scope-gate truth tables; fast/slow timing; adding, replacing, and preserving packet scope | | `test_logical_message_cache` | `src/helpers/LogicalMessageCache.h` | bounded logical-message mapping; stable retry timestamps; exact older retries after newer messages; stale and same-timestamp mismatch rejection | | `test_cli_command_utils` | `src/helpers/CLICommandUtils.h`, `src/helpers/ContactListOrder.h`, `src/helpers/TerminalCommandTracker.h`, `src/helpers/TerminalDisplayFilter.h` | terminal verb/argument/path parsing; routed receive labels; quiet display defaults and independent emergency filtering; favorite-first contact ordering; single-command reply matching, round-trip timing, and rollover-safe expiration | +| `test_identity_generation` | `src/helpers/IdentityGeneration.h` | reserved-prefix rejection; bounded retries; final provisioned attempt; fail-closed exhaustion | | `test_remote_cli_reply_cache` | `src/helpers/RemoteCliReplyCache.h`, `src/helpers/RemoteCliRequest.h`, `src/helpers/RemoteCliTimeout.h` | authenticated logical-request matching; bounded recent-reply history; backward-compatible retry identity; 300% response timeout; empty-response completion; on-air truncation and clearing | | `test_companion_frame_queue` | `src/helpers/CompanionFrameQueue.h` | response/required/best-effort classification; reserved capacity; stable priority; safe eviction; message-waiting coalescing | | `test_serial_mode_switch` | `src/helpers/ArduinoSerialInterface.cpp`, `src/helpers/MultiSerialInterface.h` | independent terminal/seeder control-sequence recognition across reads and binary-frame boundaries; passthrough ownership of USB input and suppression of binary output; Bluetooth-only connection and pairing-request routing | diff --git a/test/test_identity_generation/test_identity_generation.cpp b/test/test_identity_generation/test_identity_generation.cpp new file mode 100644 index 00000000..0f65f3d5 --- /dev/null +++ b/test/test_identity_generation/test_identity_generation.cpp @@ -0,0 +1,80 @@ +#include + +#include "helpers/IdentityGeneration.h" + +namespace { + +mesh::LocalIdentity identityWithPrefix(uint8_t prefix) { + mesh::LocalIdentity identity; + memset(identity.pub_key, 0x5A, sizeof(identity.pub_key)); + identity.pub_key[0] = prefix; + return identity; +} + +} // namespace + +TEST(IdentityGeneration, AcceptsFirstUsableIdentity) { + mesh::LocalIdentity identity; + size_t calls = 0; + + const bool generated = mesh::generateUsableLocalIdentity( + identity, [&calls]() { + ++calls; + return identityWithPrefix(0x42); + }); + + EXPECT_TRUE(generated); + EXPECT_EQ(1U, calls); + EXPECT_EQ(0x42, identity.pub_key[0]); +} + +TEST(IdentityGeneration, RetriesBothReservedPrefixes) { + mesh::LocalIdentity identity; + size_t calls = 0; + + const bool generated = mesh::generateUsableLocalIdentity( + identity, [&calls]() { + const uint8_t prefixes[] = {0x00, 0xFF, 0x7E}; + return identityWithPrefix(prefixes[calls++]); + }); + + EXPECT_TRUE(generated); + EXPECT_EQ(3U, calls); + EXPECT_EQ(0x7E, identity.pub_key[0]); +} + +TEST(IdentityGeneration, AcceptsFinalProvisionedAttempt) { + mesh::LocalIdentity identity; + size_t calls = 0; + + const bool generated = mesh::generateUsableLocalIdentity( + identity, [&calls]() { + ++calls; + return identityWithPrefix( + calls == mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS ? 0x23 : 0x00); + }); + + EXPECT_TRUE(generated); + EXPECT_EQ(mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS, calls); + EXPECT_EQ(0x23, identity.pub_key[0]); +} + +TEST(IdentityGeneration, FailsClosedAfterProvisionedAttempts) { + mesh::LocalIdentity identity; + size_t calls = 0; + + const bool generated = mesh::generateUsableLocalIdentity( + identity, [&calls]() { + ++calls; + return identityWithPrefix(0xFF); + }); + + EXPECT_FALSE(generated); + EXPECT_EQ(mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS, calls); + EXPECT_TRUE(mesh::hasReservedIdentityPrefix(identity)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/variants/generic_espnow/target.cpp b/variants/generic_espnow/target.cpp index f447523e..0b34a2e1 100644 --- a/variants/generic_espnow/target.cpp +++ b/variants/generic_espnow/target.cpp @@ -1,6 +1,7 @@ #include #include "target.h" #include +#include ESP32Board board; @@ -17,12 +18,15 @@ bool radio_init() { return true; // success } -// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG: -// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html +// Combine the normal software source with true entropy captured before RF/ADC +// initialization. The hardware source is never read in pseudo-random-only mode. class ESP_RNG : public mesh::RNG { public: void random(uint8_t* dest, size_t sz) override { - esp_fill_random(dest, sz); + for (size_t i = 0; i < sz; ++i) { + dest[i] = (::random(0, 256) & 0xFF); + } + mesh::mixESP32TrueRandom(dest, sz); } }; diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp index c84d1883..1271b354 100644 --- a/variants/sensecap_indicator-espnow/target.cpp +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -1,6 +1,7 @@ #include #include "target.h" #include +#include ESP32Board board; @@ -29,12 +30,15 @@ bool radio_init() { return true; // success } -// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG: -// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html +// Combine the normal software source with true entropy captured before RF/ADC +// initialization. The hardware source is never read in pseudo-random-only mode. class ESP_RNG : public mesh::RNG { public: void random(uint8_t* dest, size_t sz) override { - esp_fill_random(dest, sz); + for (size_t i = 0; i < sz; ++i) { + dest[i] = (::random(0, 256) & 0xFF); + } + mesh::mixESP32TrueRandom(dest, sz); } };