Skip to content

Commit 0a402c7

Browse files
committed
Reduce key duplication by enabling hardware RNG
1 parent 0081cec commit 0a402c7

File tree

8 files changed

+230
-15
lines changed

8 files changed

+230
-15
lines changed

src/mesh/CryptoEngine.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "NodeDB.h"
77
#include "aes-ccm.h"
88
#include "meshUtils.h"
9+
#include "HardwareRNG.h"
910
#include <Crypto.h>
1011
#include <Curve25519.h>
1112
#include <RNG.h>
@@ -25,6 +26,15 @@ void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
2526
{
2627
// Mix in any randomness we can, to make key generation stronger.
2728
CryptRNG.begin(optstr(APP_VERSION));
29+
30+
uint8_t hardwareEntropy[64] = {0};
31+
if (HardwareRNG::fill(hardwareEntropy, sizeof(hardwareEntropy))) {
32+
CryptRNG.stir(hardwareEntropy, sizeof(hardwareEntropy));
33+
} else {
34+
LOG_WARN("Hardware entropy unavailable, falling back to software RNG");
35+
}
36+
memset(hardwareEntropy, 0, sizeof(hardwareEntropy));
37+
2838
if (myNodeInfo.device_id.size == 16) {
2939
CryptRNG.stir(myNodeInfo.device_id.bytes, myNodeInfo.device_id.size);
3040
}

src/mesh/HardwareRNG.cpp

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#include "HardwareRNG.h"
2+
3+
#include <algorithm>
4+
#include <cstring>
5+
#include <random>
6+
7+
#include "configuration.h"
8+
9+
#if HAS_RADIO
10+
#include "RadioLibInterface.h"
11+
#endif
12+
13+
#if defined(ARCH_NRF52)
14+
#include <Adafruit_nRFCrypto.h>
15+
extern Adafruit_nRFCrypto nRFCrypto;
16+
#elif defined(ARCH_ESP32)
17+
#include <esp_system.h>
18+
#elif defined(ARCH_RP2040)
19+
#include <Arduino.h>
20+
#elif defined(ARCH_PORTDUINO)
21+
#include <random>
22+
#include <sys/random.h>
23+
#include <unistd.h>
24+
#endif
25+
26+
namespace HardwareRNG
27+
{
28+
29+
namespace
30+
{
31+
void fillWithRandomDevice(uint8_t *buffer, size_t length)
32+
{
33+
std::random_device rd;
34+
size_t offset = 0;
35+
while (offset < length) {
36+
uint32_t value = rd();
37+
size_t toCopy = std::min(length - offset, sizeof(value));
38+
memcpy(buffer + offset, &value, toCopy);
39+
offset += toCopy;
40+
}
41+
}
42+
43+
#if HAS_RADIO
44+
bool mixWithLoRaEntropy(uint8_t *buffer, size_t length)
45+
{
46+
// Only attempt to pull entropy from the modem if it is initialized and exposes the helper.
47+
// When the radio stack is disabled or has not yet been configured, we simply skip this step
48+
// and return false so callers know no extra mixing occurred.
49+
RadioLibInterface *radio = RadioLibInterface::instance;
50+
if (!radio) {
51+
return false;
52+
}
53+
54+
constexpr size_t chunkSize = 16;
55+
uint8_t scratch[chunkSize];
56+
size_t offset = 0;
57+
bool mixed = false;
58+
59+
while (offset < length) {
60+
size_t toCopy = std::min(length - offset, chunkSize);
61+
62+
// randomBytes() returns false if the modem does not support it or is not ready
63+
// (for instance, when the radio is powered down). We break immediately to avoid
64+
// blocking or returning partially-filled entropy and simply report failure.
65+
if (!radio->randomBytes(scratch, toCopy)) {
66+
break;
67+
}
68+
69+
for (size_t i = 0; i < toCopy; ++i) {
70+
buffer[offset + i] ^= scratch[i];
71+
}
72+
73+
mixed = true;
74+
offset += toCopy;
75+
}
76+
77+
// Avoid leaving the modem-sourced bytes sitting on the stack longer than needed.
78+
if (mixed) {
79+
memset(scratch, 0, sizeof(scratch));
80+
}
81+
82+
return mixed;
83+
}
84+
#endif
85+
} // namespace
86+
87+
bool fill(uint8_t *buffer, size_t length)
88+
{
89+
if (!buffer || length == 0) {
90+
return false;
91+
}
92+
93+
bool filled = false;
94+
95+
#if defined(ARCH_NRF52)
96+
// The Nordic SDK RNG provides cryptographic-quality randomness backed by hardware.
97+
nRFCrypto.begin();
98+
auto result = nRFCrypto.Random.generate(buffer, length);
99+
nRFCrypto.end();
100+
filled = result;
101+
#elif defined(ARCH_ESP32)
102+
// ESP32 exposes a true RNG via esp_fill_random().
103+
esp_fill_random(buffer, length);
104+
filled = true;
105+
#elif defined(ARCH_RP2040)
106+
// RP2040 has a hardware random number generator accessible through the Arduino core.
107+
size_t offset = 0;
108+
while (offset < length) {
109+
uint32_t value = rp2040.hwrand32();
110+
size_t toCopy = std::min(length - offset, sizeof(value));
111+
memcpy(buffer + offset, &value, toCopy);
112+
offset += toCopy;
113+
}
114+
filled = true;
115+
#elif defined(ARCH_PORTDUINO)
116+
// Prefer the host OS RNG first when running under Portduino.
117+
ssize_t generated = ::getrandom(buffer, length, 0);
118+
if (generated == static_cast<ssize_t>(length)) {
119+
filled = true;
120+
}
121+
122+
if (!filled) {
123+
fillWithRandomDevice(buffer, length);
124+
filled = true;
125+
}
126+
#else
127+
fillWithRandomDevice(buffer, length);
128+
filled = true;
129+
#endif
130+
131+
if (!filled) {
132+
// As a last resort, fall back to std::random_device. This should only be reached
133+
// if a platform-specific source was unavailable.
134+
fillWithRandomDevice(buffer, length);
135+
filled = true;
136+
}
137+
138+
#if HAS_RADIO
139+
// Best-effort: if the radio is active and can provide modem entropy, XOR it over the
140+
// buffer to improve overall quality. We ignore failures to keep RNG usable even when
141+
// radio hardware is powered down or uninitialized.
142+
filled = mixWithLoRaEntropy(buffer, length) || filled;
143+
#endif
144+
145+
return filled;
146+
}
147+
148+
bool seed(uint32_t &seedOut)
149+
{
150+
uint32_t candidate = 0;
151+
if (!fill(reinterpret_cast<uint8_t *>(&candidate), sizeof(candidate))) {
152+
return false;
153+
}
154+
seedOut = candidate;
155+
return true;
156+
}
157+
158+
} // namespace HardwareRNG

src/mesh/HardwareRNG.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
6+
namespace HardwareRNG
7+
{
8+
9+
/**
10+
* Fill the provided buffer with random bytes sourced from the most
11+
* appropriate hardware-backed RNG available on the current platform.
12+
*
13+
* @param buffer Destination buffer for random bytes
14+
* @param length Number of bytes to write
15+
* @return true if the buffer was fully populated with entropy, false on failure
16+
*/
17+
bool fill(uint8_t *buffer, size_t length);
18+
19+
/**
20+
* Populate a 32-bit seed value with hardware-backed randomness where possible.
21+
*
22+
* @param seedOut Destination for the generated seed value
23+
* @return true if a seed was produced from a reliable entropy source
24+
*/
25+
bool seed(uint32_t &seedOut);
26+
27+
} // namespace HardwareRNG

src/mesh/RadioLibInterface.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,15 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
246246
return txQueue.find(from, id);
247247
}
248248

249+
bool RadioLibInterface::randomBytes(uint8_t *buffer, size_t length)
250+
{
251+
if (!buffer || length == 0 || !iface) {
252+
return false;
253+
}
254+
255+
return iface->random(buffer, length) == RADIOLIB_ERR_NONE;
256+
}
257+
249258
/** radio helper thread callback.
250259
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
251260
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.

src/mesh/RadioLibInterface.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
158158
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
159159
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
160160

161+
/**
162+
* Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface.
163+
* @return true if len bytes were produced, false otherwise.
164+
*/
165+
bool randomBytes(uint8_t *buffer, size_t length);
166+
161167
private:
162168
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
163169
* doing the transmit */

src/platform/nrf52/main-nrf52.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <memory.h>
1818
#include <stdio.h>
1919
// #include <Adafruit_USBD_Device.h>
20+
#include "HardwareRNG.h"
2021
#include "NodeDB.h"
2122
#include "PowerMon.h"
2223
#include "error.h"
@@ -288,15 +289,13 @@ void nrf52Setup()
288289
#endif
289290

290291
// Init random seed
291-
union seedParts {
292-
uint32_t seed32;
293-
uint8_t seed8[4];
294-
} seed;
295-
nRFCrypto.begin();
296-
nRFCrypto.Random.generate(seed.seed8, sizeof(seed.seed8));
297-
LOG_DEBUG("Set random seed %u", seed.seed32);
298-
randomSeed(seed.seed32);
299-
nRFCrypto.end();
292+
uint32_t seed = 0;
293+
if (!HardwareRNG::seed(seed)) {
294+
LOG_WARN("Hardware RNG seed unavailable, using PRNG fallback");
295+
seed = random();
296+
}
297+
LOG_DEBUG("Set random seed %u", seed);
298+
randomSeed(seed);
300299

301300
// Set up nrfx watchdog. Do not enable the watchdog yet (we do that
302301
// the first time through the main loop), so that other threads can

src/platform/portduino/PortduinoGlue.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "CryptoEngine.h"
22
#include "PortduinoGPIO.h"
33
#include "SPIChip.h"
4+
#include "HardwareRNG.h"
45
#include "mesh/RF95Interface.h"
56
#include "sleep.h"
67
#include "target_specific.h"
@@ -225,7 +226,9 @@ void portduinoSetup()
225226
std::cout << "Running in simulated mode." << std::endl;
226227
portduino_config.MaxNodes = 200; // Default to 200 nodes
227228
// Set the random seed equal to TCPPort to have a different seed per instance
228-
randomSeed(TCPPort);
229+
uint32_t seed = TCPPort;
230+
HardwareRNG::seed(seed);
231+
randomSeed(seed);
229232
return;
230233
}
231234

@@ -433,7 +436,9 @@ void portduinoSetup()
433436
}
434437
printf("MAC ADDRESS: %02X:%02X:%02X:%02X:%02X:%02X\n", dmac[0], dmac[1], dmac[2], dmac[3], dmac[4], dmac[5]);
435438
// Rather important to set this, if not running simulated.
436-
randomSeed(time(NULL));
439+
uint32_t seed = static_cast<uint32_t>(time(NULL));
440+
HardwareRNG::seed(seed);
441+
randomSeed(seed);
437442

438443
std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip);
439444
for (auto i : portduino_config.all_pins) {

src/platform/rp2xx0/main-rp2xx0.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "HardwareRNG.h"
12
#include "configuration.h"
23
#include "hardware/xosc.h"
34
#include <hardware/clocks.h>
@@ -98,10 +99,10 @@ void getMacAddr(uint8_t *dmac)
9899

99100
void rp2040Setup()
100101
{
101-
/* Sets a random seed to make sure we get different random numbers on each boot.
102-
Taken from CPU cycle counter and ROSC oscillator, so should be pretty random.
103-
*/
104-
randomSeed(rp2040.hwrand32());
102+
/* Sets a random seed to make sure we get different random numbers on each boot. */
103+
uint32_t seed = rp2040.hwrand32();
104+
HardwareRNG::seed(seed);
105+
randomSeed(seed);
105106

106107
#ifdef RP2040_SLOW_CLOCK
107108
uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);

0 commit comments

Comments
 (0)