Harden ESP32 identity entropy handling

This commit is contained in:
mikecarper committed 2026-08-11 14:25:52 -07:00
1 parent ea3843e0b4
commit a65769c96a
15 files changed
+414 -51

No files matched your search

+5
View File
@@ -15,6 +15,7 @@
#include "soc/rtc.h"
#include "esp_system.h"
#include <driver/rtc_io.h>
#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;
+129
View File
@@ -0,0 +1,129 @@
#include "ESP32TrueRandom.h"
#if defined(ESP32_PLATFORM)
#include <bootloader_random.h>
#include <esp_random.h>
#include <esp_system.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <mbedtls/platform_util.h>
#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
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#if defined(ESP32_PLATFORM)
#include <stddef.h>
#include <stdint.h>
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
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <Identity.h>
#include <stddef.h>
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 <typename Generator>
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
+8 -3
View File
@@ -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
}
};