Reduce key duplication by enabling hardware RNG (#8803)
* Reduce key duplication by enabling hardware RNG * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Use micros() for worst case random seed for nrf52 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Minor cleanup, remove dead code and clarify comment * trunk * Add useRadioEntropy bool, default false. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
This commit is contained in:
co-authored by
GitHub
Ben Meadors
Copilot
Jonathan Bennett
parent
16dcafa7fb
commit
77f378dd53
@@ -4,6 +4,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||||
|
#include "HardwareRNG.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "aes-ccm.h"
|
#include "aes-ccm.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
@@ -26,6 +27,15 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
|
|||||||
{
|
{
|
||||||
// Mix in any randomness we can, to make key generation stronger.
|
// Mix in any randomness we can, to make key generation stronger.
|
||||||
CryptRNG.begin(optstr(APP_VERSION));
|
CryptRNG.begin(optstr(APP_VERSION));
|
||||||
|
|
||||||
|
uint8_t hardwareEntropy[64] = {0};
|
||||||
|
if (HardwareRNG::fill(hardwareEntropy, sizeof(hardwareEntropy), true)) {
|
||||||
|
CryptRNG.stir(hardwareEntropy, sizeof(hardwareEntropy));
|
||||||
|
} else {
|
||||||
|
LOG_WARN("Hardware entropy unavailable, falling back to software RNG");
|
||||||
|
}
|
||||||
|
memset(hardwareEntropy, 0, sizeof(hardwareEntropy));
|
||||||
|
|
||||||
if (myNodeInfo.device_id.size == 16) {
|
if (myNodeInfo.device_id.size == 16) {
|
||||||
CryptRNG.stir(myNodeInfo.device_id.bytes, myNodeInfo.device_id.size);
|
CryptRNG.stir(myNodeInfo.device_id.bytes, myNodeInfo.device_id.size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
#include "HardwareRNG.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#if HAS_RADIO
|
||||||
|
#include "RadioLibInterface.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ARCH_NRF52)
|
||||||
|
#include <Adafruit_nRFCrypto.h>
|
||||||
|
extern Adafruit_nRFCrypto nRFCrypto;
|
||||||
|
#elif defined(ARCH_ESP32)
|
||||||
|
#include <esp_system.h>
|
||||||
|
#elif defined(ARCH_RP2040)
|
||||||
|
#include <Arduino.h>
|
||||||
|
#elif defined(ARCH_PORTDUINO)
|
||||||
|
#include <random>
|
||||||
|
#include <sys/random.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace HardwareRNG
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void fillWithRandomDevice(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
std::random_device rd;
|
||||||
|
size_t offset = 0;
|
||||||
|
while (offset < length) {
|
||||||
|
uint32_t value = rd();
|
||||||
|
size_t toCopy = std::min(length - offset, sizeof(value));
|
||||||
|
memcpy(buffer + offset, &value, toCopy);
|
||||||
|
offset += toCopy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HAS_RADIO
|
||||||
|
bool mixWithLoRaEntropy(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
// Only attempt to pull entropy from the modem if it is initialized and exposes the helper.
|
||||||
|
// When the radio stack is disabled or has not yet been configured, we simply skip this step
|
||||||
|
// and return false so callers know no extra mixing occurred.
|
||||||
|
RadioLibInterface *radio = RadioLibInterface::instance;
|
||||||
|
if (!radio) {
|
||||||
|
LOG_ERROR("No radio instance available to provide entropy");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t chunkSize = 16;
|
||||||
|
uint8_t scratch[chunkSize];
|
||||||
|
size_t offset = 0;
|
||||||
|
bool mixed = false;
|
||||||
|
|
||||||
|
while (offset < length) {
|
||||||
|
size_t toCopy = std::min(length - offset, chunkSize);
|
||||||
|
|
||||||
|
// randomBytes() returns false if the modem does not support it or is not ready
|
||||||
|
// (for instance, when the radio is powered down). We break immediately to avoid
|
||||||
|
// blocking or returning partially-filled entropy and simply report failure.
|
||||||
|
if (!radio->randomBytes(scratch, toCopy)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < toCopy; ++i) {
|
||||||
|
buffer[offset + i] ^= scratch[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
mixed = true;
|
||||||
|
offset += toCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid leaving the modem-sourced bytes sitting on the stack longer than needed.
|
||||||
|
if (mixed) {
|
||||||
|
memset(scratch, 0, sizeof(scratch));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mixed;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy)
|
||||||
|
{
|
||||||
|
if (!buffer || length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool filled = false;
|
||||||
|
|
||||||
|
#if defined(ARCH_NRF52)
|
||||||
|
// The Nordic SDK RNG provides cryptographic-quality randomness backed by hardware.
|
||||||
|
nRFCrypto.begin();
|
||||||
|
auto result = nRFCrypto.Random.generate(buffer, length);
|
||||||
|
nRFCrypto.end();
|
||||||
|
filled = result;
|
||||||
|
#elif defined(ARCH_ESP32)
|
||||||
|
// ESP32 exposes a true RNG via esp_fill_random().
|
||||||
|
esp_fill_random(buffer, length);
|
||||||
|
filled = true;
|
||||||
|
#elif defined(ARCH_RP2040)
|
||||||
|
// RP2040 has a hardware random number generator accessible through the Arduino core.
|
||||||
|
size_t offset = 0;
|
||||||
|
while (offset < length) {
|
||||||
|
uint32_t value = rp2040.hwrand32();
|
||||||
|
size_t toCopy = std::min(length - offset, sizeof(value));
|
||||||
|
memcpy(buffer + offset, &value, toCopy);
|
||||||
|
offset += toCopy;
|
||||||
|
}
|
||||||
|
filled = true;
|
||||||
|
#elif defined(ARCH_PORTDUINO)
|
||||||
|
// Prefer the host OS RNG first when running under Portduino.
|
||||||
|
ssize_t generated = ::getrandom(buffer, length, 0);
|
||||||
|
if (generated == static_cast<ssize_t>(length)) {
|
||||||
|
filled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filled) {
|
||||||
|
fillWithRandomDevice(buffer, length);
|
||||||
|
filled = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!filled) {
|
||||||
|
// As a last resort, fall back to std::random_device. This should only be reached
|
||||||
|
// if a platform-specific source was unavailable.
|
||||||
|
fillWithRandomDevice(buffer, length);
|
||||||
|
filled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HAS_RADIO
|
||||||
|
if (useRadioEntropy) {
|
||||||
|
// Best-effort: if the radio is active and can provide modem entropy, XOR it over the
|
||||||
|
// buffer to improve overall quality. We consider the filling a success if either a
|
||||||
|
// good platform RNG or the modem RNG provided data, so we return true as long as at
|
||||||
|
// least one of those steps succeeded.
|
||||||
|
filled = mixWithLoRaEntropy(buffer, length) || filled;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return filled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool seed(uint32_t &seedOut)
|
||||||
|
{
|
||||||
|
uint32_t candidate = 0;
|
||||||
|
if (!fill(reinterpret_cast<uint8_t *>(&candidate), sizeof(candidate), true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seedOut = candidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HardwareRNG
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace HardwareRNG
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the provided buffer with random bytes sourced from the most
|
||||||
|
* appropriate hardware-backed RNG available on the current platform.
|
||||||
|
*
|
||||||
|
* @param buffer Destination buffer for random bytes
|
||||||
|
* @param length Number of bytes to write
|
||||||
|
* @param useRadioEntropy If true, attempt to mix radio entropy into the output as well.
|
||||||
|
* @return true if the buffer was fully populated with entropy, false on failure
|
||||||
|
*/
|
||||||
|
bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate a 32-bit seed value with hardware-backed randomness where possible.
|
||||||
|
*
|
||||||
|
* @param seedOut Destination for the generated seed value
|
||||||
|
* @return true if a seed was produced from a reliable entropy source
|
||||||
|
*/
|
||||||
|
bool seed(uint32_t &seedOut);
|
||||||
|
|
||||||
|
} // namespace HardwareRNG
|
||||||
@@ -246,6 +246,24 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
|
|||||||
return txQueue.find(from, id);
|
return txQueue.find(from, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RadioLibInterface::randomBytes(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
if (!buffer || length == 0 || !iface) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older RadioLib versions only expose random(min, max), so fill the buffer byte-by-byte.
|
||||||
|
for (size_t i = 0; i < length; ++i) {
|
||||||
|
int32_t value = iface->random(0, 255);
|
||||||
|
if (value < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buffer[i] = static_cast<uint8_t>(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/** radio helper thread callback.
|
/** radio helper thread callback.
|
||||||
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
|
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
|
||||||
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
|
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
|
||||||
@@ -587,4 +605,4 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
|
|||||||
|
|
||||||
return res == RADIOLIB_ERR_NONE;
|
return res == RADIOLIB_ERR_NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,6 +172,12 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
|||||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||||
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
|
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface.
|
||||||
|
* @return true if len bytes were produced, false otherwise.
|
||||||
|
*/
|
||||||
|
bool randomBytes(uint8_t *buffer, size_t length);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
|
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
|
||||||
* doing the transmit */
|
* doing the transmit */
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <nrfx_wdt.h>
|
#include <nrfx_wdt.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
// #include <Adafruit_USBD_Device.h>
|
// #include <Adafruit_USBD_Device.h>
|
||||||
|
#include "HardwareRNG.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerMon.h"
|
#include "PowerMon.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
@@ -398,15 +399,14 @@ void nrf52Setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Init random seed
|
// Init random seed
|
||||||
union seedParts {
|
uint32_t seed = 0;
|
||||||
uint32_t seed32;
|
if (!HardwareRNG::seed(seed)) {
|
||||||
uint8_t seed8[4];
|
LOG_WARN("Hardware RNG seed unavailable, using PRNG fallback");
|
||||||
} seed;
|
// Use a hardware timer value as a fallback seed for better entropy
|
||||||
nRFCrypto.begin();
|
seed = micros();
|
||||||
nRFCrypto.Random.generate(seed.seed8, sizeof(seed.seed8));
|
}
|
||||||
LOG_DEBUG("Set random seed %u", seed.seed32);
|
LOG_DEBUG("Set random seed %u", seed);
|
||||||
randomSeed(seed.seed32);
|
randomSeed(seed);
|
||||||
nRFCrypto.end();
|
|
||||||
|
|
||||||
// Set up nrfx watchdog. Do not enable the watchdog yet (we do that
|
// Set up nrfx watchdog. Do not enable the watchdog yet (we do that
|
||||||
// the first time through the main loop), so that other threads can
|
// the first time through the main loop), so that other threads can
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "CryptoEngine.h"
|
#include "CryptoEngine.h"
|
||||||
|
#include "HardwareRNG.h"
|
||||||
#include "PortduinoGPIO.h"
|
#include "PortduinoGPIO.h"
|
||||||
#include "SPIChip.h"
|
#include "SPIChip.h"
|
||||||
#include "mesh/RF95Interface.h"
|
#include "mesh/RF95Interface.h"
|
||||||
@@ -233,7 +234,9 @@ void portduinoSetup()
|
|||||||
std::cout << "Running in simulated mode." << std::endl;
|
std::cout << "Running in simulated mode." << std::endl;
|
||||||
portduino_config.MaxNodes = 200; // Default to 200 nodes
|
portduino_config.MaxNodes = 200; // Default to 200 nodes
|
||||||
// Set the random seed equal to TCPPort to have a different seed per instance
|
// Set the random seed equal to TCPPort to have a different seed per instance
|
||||||
randomSeed(TCPPort);
|
uint32_t seed = TCPPort;
|
||||||
|
HardwareRNG::seed(seed);
|
||||||
|
randomSeed(seed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,7 +515,9 @@ void portduinoSetup()
|
|||||||
#endif
|
#endif
|
||||||
printf("MAC ADDRESS: %02X:%02X:%02X:%02X:%02X:%02X\n", dmac[0], dmac[1], dmac[2], dmac[3], dmac[4], dmac[5]);
|
printf("MAC ADDRESS: %02X:%02X:%02X:%02X:%02X:%02X\n", dmac[0], dmac[1], dmac[2], dmac[3], dmac[4], dmac[5]);
|
||||||
// Rather important to set this, if not running simulated.
|
// Rather important to set this, if not running simulated.
|
||||||
randomSeed(time(NULL));
|
uint32_t seed = static_cast<uint32_t>(time(NULL));
|
||||||
|
HardwareRNG::seed(seed);
|
||||||
|
randomSeed(seed);
|
||||||
|
|
||||||
std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip);
|
std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "HardwareRNG.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "hardware/xosc.h"
|
#include "hardware/xosc.h"
|
||||||
#include <hardware/clocks.h>
|
#include <hardware/clocks.h>
|
||||||
@@ -98,10 +99,12 @@ void getMacAddr(uint8_t *dmac)
|
|||||||
|
|
||||||
void rp2040Setup()
|
void rp2040Setup()
|
||||||
{
|
{
|
||||||
/* Sets a random seed to make sure we get different random numbers on each boot.
|
/* Sets a random seed to make sure we get different random numbers on each boot. */
|
||||||
Taken from CPU cycle counter and ROSC oscillator, so should be pretty random.
|
uint32_t seed = 0;
|
||||||
*/
|
if (!HardwareRNG::seed(seed)) {
|
||||||
randomSeed(rp2040.hwrand32());
|
seed = rp2040.hwrand32();
|
||||||
|
}
|
||||||
|
randomSeed(seed);
|
||||||
|
|
||||||
#ifdef RP2040_SLOW_CLOCK
|
#ifdef RP2040_SLOW_CLOCK
|
||||||
uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);
|
uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);
|
||||||
|
|||||||
Reference in New Issue
Block a user