#include "HardwareRNG.h" #include #include #include #include "configuration.h" #if HAS_RADIO #include "RadioLibInterface.h" #endif #if defined(ARCH_NRF52) #include extern Adafruit_nRFCrypto nRFCrypto; #elif defined(ARCH_ESP32) #include #elif defined(ARCH_RP2040) #include #elif defined(ARCH_PORTDUINO) #include #include #ifdef __linux__ #include // getrandom() #elif defined(_WIN32) // Order is load-bearing, hence the blank line: bcrypt.h uses LONG/ULONG from // windows.h and does not include it itself. #include #include // BCryptGenRandom() #else #include // arc4random_buf() on Darwin/BSD #endif #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) { // This path can run during portduinoSetup() before the console is initialized, // both for unit-test binaries and the simulator's meshtasticd; LOG_* dereferences `console`. if (console) { 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. #ifdef __linux__ ssize_t generated = ::getrandom(buffer, length, 0); if (generated == static_cast(length)) { filled = true; } #elif defined(_WIN32) // No getrandom/arc4random on Windows; BCryptGenRandom is the documented CSPRNG. // Preferred over std::random_device, whose libstdc++ Windows backend reports entropy() == 0. if (BCryptGenRandom(NULL, buffer, static_cast(length), BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0) { // STATUS_SUCCESS filled = true; } #elif defined(__EMSCRIPTEN__) // Browser/wasm: no getrandom/arc4random - fall through to std::random_device, // which emscripten backs with crypto.getRandomValues(). #else // arc4random_buf is available on Darwin/BSD and cannot fail. ::arc4random_buf(buffer, length); filled = true; #endif 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(&candidate), sizeof(candidate), true)) { return false; } seedOut = candidate; return true; } } // namespace HardwareRNG