crypto: simplify entropy path after audit review

- Lift duplicated identity-gen block from main_companion.cpp +
  main_repeater.cpp into ZephyrRNG::generateFirstBootIdentity().
  Both mains shrink from ~40 lines to a 3-line helper call.
- Add LocalIdentity::fromSeed() so seed-derived keygen doesn't need
  a one-shot RNG wrapper; delete SeededRNG.
- Drop the per-byte ADC sampling loop: getBattMilliVolts() does an
  8-sample average + 10ms regulator settle internally, costing
  300-480ms of real wall-time and actively destroying the LSB jitter
  it was meant to harvest. Jitter mixer already dwarfs it.
- Centralize the printk + sys_reboot pattern as
  Utils::cryptoPanicReboot(); drop the 2000ms pre-reboot k_msleep
  (printk is synchronous, sleep just blocked the mesh thread on
  the ZephyrRNG::random() retry-failure path).
- Inline sample_cpu_jitter health check via online scalars instead
  of a 512-byte deltas[] array. Saves 1.5KB stack churn across boot
  and tracks every sample instead of only the first 128.
- extract_via_aes_ctr now uses Utils::sha256 instead of open-coding
  psa_hash_compute.
This commit is contained in:
liquidraver
2026-05-29 07:58:54 +02:00
parent b692ca72ed
commit 799d694914
8 changed files with 119 additions and 170 deletions
+6
View File
@@ -54,6 +54,12 @@ LocalIdentity::LocalIdentity(RNG *rng)
uint8_t seed[SEED_SIZE];
rng->random(seed, SEED_SIZE);
ed25519_create_keypair(pub_key, prv_key, seed);
Utils::secureZeroize(seed, sizeof(seed));
}
void LocalIdentity::fromSeed(const uint8_t seed[SEED_SIZE])
{
ed25519_create_keypair(pub_key, prv_key, seed);
}
bool LocalIdentity::validatePrivateKey(const uint8_t prv[64])
+13
View File
@@ -6,6 +6,8 @@
#include <mesh/Utils.h>
#include <psa/crypto.h>
#include <string.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephcore_utils, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
@@ -205,6 +207,17 @@ bool Utils::constantTimeEqual(const void *a, const void *b, size_t n)
return result == 0;
}
void Utils::cryptoPanicReboot(const char *msg)
{
/* No pre-reboot k_msleep: printk is synchronous on RTT/UART so the
* line is already on the wire by the time sys_reboot fires, and the
* 2-second delay we used to do here just blocked the mesh thread on
* the rare-but-realistic ZephyrRNG::random() retry failure path. */
printk("crypto panic: %s — rebooting\n", msg ? msg : "(no detail)");
sys_reboot(SYS_REBOOT_COLD);
for (;;) { /* sys_reboot is FUNC_NORETURN, but satisfy [[noreturn]] */ }
}
void Utils::secureZeroize(void *buf, size_t n)
{
/* Volatile pointer prevents the compiler from eliminating the
+4 -42
View File
@@ -660,51 +660,13 @@ int main(void)
LOG_INF("Added default Public channel");
}
/* Load or generate identity.
*
* First-boot keygen uses ZephyrRNG::mixIdentitySeed() — a layered
* entropy mixer combining sys_csrand_get + HWINFO unique ID + ADC
* LSB noise + CPU cycle-counter jitter, conditioned via SHA-512.
* This compensates for ESP32's hardware TRNG being only seeded
* after WiFi/BT radio init (identity gen happens before bt_enable). */
/* Load or generate identity. First-boot keygen runs the layered
* entropy mixer + Ed25519 derive + reserved-prefix guard inside
* ZephyrRNG::generateFirstBootIdentity. */
mesh::LocalIdentity self_identity;
if (!data_store.loadMainIdentity(self_identity)) {
/* Sample ADC LSB noise — best-effort independent physical
* source. Boards without battery ADC return 0; jitter remains
* the primary entropy source either way. */
uint8_t adc_noise[32] = {0};
for (size_t i = 0; i < sizeof(adc_noise); i++) {
adc_noise[i] = (uint8_t)zephyr_board.getBattMilliVolts();
k_msleep(1);
}
uint8_t seed[32];
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed),
adc_noise, sizeof(adc_noise));
{
mesh::SeededRNG seed_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&seed_rng);
}
/* Ensure pub_key[0] is not reserved (0x00 or 0xFF in MeshCore protocol).
* With a properly mixed seed this almost never triggers; the
* cap+reboot is a safety net against pathological entropy failure. */
int attempt = 0;
while (self_identity.pub_key[0] == 0x00 || self_identity.pub_key[0] == 0xFF) {
if (++attempt > 100) {
LOG_ERR("Identity gen stuck on reserved prefix; rebooting");
k_msleep(2000);
sys_reboot(SYS_REBOOT_COLD);
}
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed));
mesh::SeededRNG retry_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&retry_rng);
}
mesh::ZephyrRNG::generateFirstBootIdentity(self_identity);
data_store.saveMainIdentity(self_identity);
mesh::Utils::secureZeroize(seed, sizeof(seed));
mesh::Utils::secureZeroize(adc_noise, sizeof(adc_noise));
}
companion_mesh.self_id = self_identity;
+4 -44
View File
@@ -454,55 +454,15 @@ int main(void)
lora_radio.setTxDoneCallback(lora_tx_done_callback, nullptr);
repeater_mesh.setTxQueuedCallback(tx_queued_callback, nullptr);
/* Load or generate identity BEFORE begin().
*
* First-boot keygen uses ZephyrRNG::mixIdentitySeed() — a layered
* entropy mixer combining sys_csrand_get + HWINFO unique ID + ADC
* LSB noise + CPU cycle-counter jitter, conditioned via SHA-512.
* This compensates for ESP32's hardware TRNG being only seeded
* after WiFi/BT radio init (which on a repeater is on-demand for
* WiFi OTA — there's no guaranteed radio activity at boot). */
/* Load or generate identity BEFORE begin(). First-boot keygen runs
* the layered entropy mixer + Ed25519 derive + reserved-prefix
* guard inside ZephyrRNG::generateFirstBootIdentity. */
mesh::LocalIdentity self_identity;
if (!data_store.loadIdentity(self_identity)) {
LOG_INF("No identity found, generating new keypair...");
/* Sample ADC LSB noise — best-effort independent physical
* source. Boards without battery ADC return 0; jitter remains
* the primary entropy source either way. */
uint8_t adc_noise[32] = {0};
for (size_t i = 0; i < sizeof(adc_noise); i++) {
adc_noise[i] = (uint8_t)zephyr_board.getBattMilliVolts();
k_msleep(1);
}
uint8_t seed[32];
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed),
adc_noise, sizeof(adc_noise));
{
mesh::SeededRNG seed_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&seed_rng);
}
/* Ensure pub_key[0] is not reserved (0x00 or 0xFF in MeshCore protocol).
* With a properly mixed seed this almost never triggers; the
* cap+reboot is a safety net against pathological entropy failure. */
int attempt = 0;
while (self_identity.pub_key[0] == 0x00 || self_identity.pub_key[0] == 0xFF) {
if (++attempt > 100) {
LOG_ERR("Identity gen stuck on reserved prefix; rebooting");
k_msleep(2000);
sys_reboot(SYS_REBOOT_COLD);
}
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed));
mesh::SeededRNG retry_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&retry_rng);
}
mesh::ZephyrRNG::generateFirstBootIdentity(self_identity);
data_store.saveIdentity(self_identity);
LOG_INF("New identity saved");
mesh::Utils::secureZeroize(seed, sizeof(seed));
mesh::Utils::secureZeroize(adc_noise, sizeof(adc_noise));
}
repeater_mesh.self_id = self_identity;