-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Reduce key duplication by enabling hardware RNG #8803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,158 @@ | ||||||||||||||
| #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) { | ||||||||||||||
| 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) | ||||||||||||||
| { | ||||||||||||||
| 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; | ||||||||||||||
| } | ||||||||||||||
| #else | ||||||||||||||
| fillWithRandomDevice(buffer, length); | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How good/bad of a source of entropy is std::random_device on various platforms? STM32 will fall over into this code, for instance. I think we want to just return false here instead. |
||||||||||||||
| 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; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+131
to
+136
|
||||||||||||||
| 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; | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #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 | ||
| * @return true if the buffer was fully populated with entropy, false on failure | ||
| */ | ||
| bool fill(uint8_t *buffer, size_t length); | ||
|
|
||
| /** | ||
| * 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 |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -246,6 +246,24 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId 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); | ||||||
|
||||||
| buffer[i] = static_cast<uint8_t>(value & 0xFF); | |
| buffer[i] = static_cast<uint8_t>(value); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -158,6 +158,12 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified | |||||
| /** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */ | ||||||
| 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. | ||||||
|
||||||
| * @return true if len bytes were produced, false otherwise. | |
| * @return true if length bytes were produced, false otherwise. |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |||||||
| #include <memory.h> | ||||||||
| #include <stdio.h> | ||||||||
| // #include <Adafruit_USBD_Device.h> | ||||||||
| #include "HardwareRNG.h" | ||||||||
| #include "NodeDB.h" | ||||||||
| #include "PowerMon.h" | ||||||||
| #include "error.h" | ||||||||
|
|
@@ -288,15 +289,13 @@ void nrf52Setup() | |||||||
| #endif | ||||||||
|
|
||||||||
| // Init random seed | ||||||||
| union seedParts { | ||||||||
| uint32_t seed32; | ||||||||
| uint8_t seed8[4]; | ||||||||
| } seed; | ||||||||
| nRFCrypto.begin(); | ||||||||
| nRFCrypto.Random.generate(seed.seed8, sizeof(seed.seed8)); | ||||||||
| LOG_DEBUG("Set random seed %u", seed.seed32); | ||||||||
| randomSeed(seed.seed32); | ||||||||
| nRFCrypto.end(); | ||||||||
| uint32_t seed = 0; | ||||||||
| if (!HardwareRNG::seed(seed)) { | ||||||||
| LOG_WARN("Hardware RNG seed unavailable, using PRNG fallback"); | ||||||||
| seed = random(); | ||||||||
|
||||||||
| seed = random(); | |
| // Use a hardware timer value as a fallback seed for better entropy | |
| seed = micros(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually seems like good feedback. We might consider an XOR of the deviceID, MAC address, and micros() as the fallback seed.
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||
| #include "CryptoEngine.h" | ||||||||||||||
| #include "PortduinoGPIO.h" | ||||||||||||||
| #include "SPIChip.h" | ||||||||||||||
| #include "HardwareRNG.h" | ||||||||||||||
| #include "mesh/RF95Interface.h" | ||||||||||||||
| #include "sleep.h" | ||||||||||||||
| #include "target_specific.h" | ||||||||||||||
|
|
@@ -225,7 +226,9 @@ void portduinoSetup() | |||||||||||||
| std::cout << "Running in simulated mode." << std::endl; | ||||||||||||||
| portduino_config.MaxNodes = 200; // Default to 200 nodes | ||||||||||||||
| // 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); | ||||||||||||||
|
Comment on lines
+229
to
+231
|
||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -433,7 +436,9 @@ void portduinoSetup() | |||||||||||||
| } | ||||||||||||||
| 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. | ||||||||||||||
| randomSeed(time(NULL)); | ||||||||||||||
| uint32_t seed = static_cast<uint32_t>(time(NULL)); | ||||||||||||||
| HardwareRNG::seed(seed); | ||||||||||||||
|
Comment on lines
+439
to
+440
|
||||||||||||||
| uint32_t seed = static_cast<uint32_t>(time(NULL)); | |
| HardwareRNG::seed(seed); | |
| uint32_t seed = 0; | |
| if (!HardwareRNG::seed(seed)) { | |
| seed = static_cast<uint32_t>(time(NULL)); // fallback | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||||||||||
| #include "HardwareRNG.h" | ||||||||||||||
| #include "configuration.h" | ||||||||||||||
| #include "hardware/xosc.h" | ||||||||||||||
| #include <hardware/clocks.h> | ||||||||||||||
|
|
@@ -98,10 +99,10 @@ void getMacAddr(uint8_t *dmac) | |||||||||||||
|
|
||||||||||||||
| void rp2040Setup() | ||||||||||||||
| { | ||||||||||||||
| /* 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. | ||||||||||||||
| */ | ||||||||||||||
| randomSeed(rp2040.hwrand32()); | ||||||||||||||
| /* Sets a random seed to make sure we get different random numbers on each boot. */ | ||||||||||||||
| uint32_t seed = rp2040.hwrand32(); | ||||||||||||||
| HardwareRNG::seed(seed); | ||||||||||||||
|
Comment on lines
+103
to
+104
|
||||||||||||||
| uint32_t seed = rp2040.hwrand32(); | |
| HardwareRNG::seed(seed); | |
| uint32_t seed = 0; | |
| if (!HardwareRNG::seed(seed)) { | |
| seed = rp2040.hwrand32(); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Accessing the static
RadioLibInterface::instancepointer without synchronization could lead to race conditions ifHardwareRNG::fill()is called from multiple threads simultaneously, or if it's called while the radio interface is being initialized or destroyed.While the current code checks for null and handles the case gracefully, consider documenting the thread-safety expectations of this function, or adding appropriate synchronization if
fill()can be called from multiple threads.