1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00
2018-05-15 19:42:20 +10:00

1005 lines
34 KiB
C++

/*
* Copyright (C) 2015 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "RNG.h"
#include "NoiseSource.h"
#include "ChaCha.h"
#include "Crypto.h"
#include "utility/ProgMemUtil.h"
#include <Arduino.h>
#if defined (__arm__) && defined (__SAM3X8E__)
// The Arduino Due does not have any EEPROM natively on the main chip.
// However, it does have a TRNG and flash memory.
#define RNG_DUE_TRNG 1
#elif defined(__AVR__)
#define RNG_EEPROM 1 // Use EEPROM to save the seed.
#define RNG_WATCHDOG 1 // Harvest entropy from watchdog jitter.
#include <avr/eeprom.h>
#include <avr/wdt.h>
#include <avr/io.h>
#define RNG_EEPROM_ADDRESS (E2END + 1 - RNGClass::SEED_SIZE)
#elif defined(ESP8266)
// ESP8266 does not have EEPROM but it does have SPI flash memory.
// It also has a TRNG register for generating "true" random numbers.
// For now we use the TRNG but don't save the seed in flash memory.
#define RNG_WORD_TRNG 1
#define RNG_WORD_TRNG_GET() (ESP8266_DREG(0x20E44))
#elif defined(ESP32)
// ESP32 has a word-based TRNG and an API for Non-Volatile Storage (NVS).
#define RNG_WORD_TRNG 1
#define RNG_WORD_TRNG_GET() (esp_random())
#define RNG_ESP_NVS 1
#include <nvs.h>
#endif
#include <string.h>
// Throw a warning if there is no built-in hardware random number source.
// If this happens, then you need to do one of two things:
// 1. Edit RNG.cpp to add your platform's hardware TRNG.
// 2. Provide a proper noise source like TransistorNoiseSource
// in your sketch and then comment out the #warning line below.
#if !defined(RNG_DUE_TRNG) && \
!defined(RNG_WATCHDOG) && \
!defined(RNG_WORD_TRNG)
#warning "no hardware random number source detected for this platform"
#endif
/**
* \class RNGClass RNG.h <RNG.h>
* \brief Pseudo random number generator suitable for cryptography.
*
* Random number generators must be seeded properly before they can
* be used or an adversary may be able to predict the random output.
* Seed data may be:
*
* \li Device-specific, for example serial numbers or MAC addresses.
* \li Application-specific, unique to the application. The tag that is
* passed to begin() is an example of an application-specific value.
* \li Noise-based, generated by a hardware random number generator
* that provides unpredictable values from a noise source.
*
* The following example demonstrates how to initialise the random
* number generator:
*
* \code
* #include <SPI.h>
* #include <Ethernet.h>
* #include <Crypto.h>
* #include <RNG.h>
* #include <TransistorNoiseSource.h>
*
* // Noise source to seed the random number generator.
* TransistorNoiseSource noise(A1);
*
* // MAC address for Ethernet communication.
* byte mac_address[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
*
* void setup() {
* // Initialize the Ethernet shield.
* Ethernet.begin(mac_address);
*
* // Initialize the random number generator with the application tag
* // "MyApp 1.0" and load the previous seed from EEPROM.
* RNG.begin("MyApp 1.0");
*
* // Stir in the Ethernet MAC address.
* RNG.stir(mac_address, sizeof(mac_address));
*
* // Add the noise source to the list of sources known to RNG.
* RNG.addNoiseSource(noise);
*
* // ...
* }
* \endcode
*
* The application should regularly call loop() to stir in new data
* from the registered noise sources and to periodically save the seed:
*
* \code
* void loop() {
* // ...
*
* // Perform regular housekeeping on the random number generator.
* RNG.loop();
*
* // ...
* }
* \endcode
*
* The loop() function will automatically save the random number seed on a
* regular basis to the last SEED_SIZE bytes of EEPROM memory. By default
* the seed is saved every hour but this can be changed using setAutoSaveTime().
*
* Keep in mind that saving too often may cause the EEPROM to wear out quicker.
* It is wise to limit saving to once an hour or once a day depending
* upon how long you intend to field the device before replacing it.
* For example, an EEPROM rated for 100k erase/write cycles will last about
* 69 days saving once a minute or 11 years saving once an hour.
*
* The application can still elect to call save() at any time if wants.
* For example, if the application can detect power loss or shutdown
* conditions programmatically, then it may make sense to force a save()
* of the seed upon shutdown.
*
* The Arduino Due does not have EEPROM so RNG saves the seed into
* the last page of system flash memory instead. The RNG class will also
* mix in data from the CPU's built-in True Random Number Generator (TRNG).
* Assuming that the CPU's TRNG is trustworthy, this should be sufficient
* to properly seed the random number generator. It is recommended to
* also mix in data from other noise sources just in case the CPU's TRNG
* is not trustworthy.
*
* \sa NoiseSource
*/
/**
* \brief Global random number generator instance.
*
* \sa RNGClass
*/
RNGClass RNG;
/**
* \var RNGClass::SEED_SIZE
* \brief Size of a saved random number seed in EEPROM space.
*
* The seed is saved into the last SEED_SIZE bytes of EEPROM memory.
* The address is dependent upon the size of EEPROM fitted in the device.
*/
// Number of ChaCha hash rounds to use for random number generation.
#define RNG_ROUNDS 20
// Force a rekey after this many blocks of random data.
#define RNG_REKEY_BLOCKS 16
// Maximum entropy credit that can be contained in the pool.
#define RNG_MAX_CREDITS 384u
/** @cond */
// Imported from Crypto.cpp.
extern uint8_t crypto_crc8(uint8_t tag, const void *data, unsigned size);
// Tag for 256-bit ChaCha20 keys. This will always appear in the
// first 16 bytes of the block. The remaining 48 bytes are the seed.
static const char tagRNG[16] PROGMEM = {
'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
'2', '-', 'b', 'y', 't', 'e', ' ', 'k'
};
// Initialization seed. This is the ChaCha20 output of hashing
// "expand 32-byte k" followed by 48 bytes set to the numbers 1 to 48.
// The ChaCha20 output block is then truncated to the first 48 bytes.
//
// This value is intended to start the RNG in a semi-chaotic state if
// we don't have a previously saved seed in EEPROM.
static const uint8_t initRNG[48] PROGMEM = {
0xB0, 0x2A, 0xAE, 0x7D, 0xEE, 0xCB, 0xBB, 0xB1,
0xFC, 0x03, 0x6F, 0xDD, 0xDC, 0x7D, 0x76, 0x67,
0x0C, 0xE8, 0x1F, 0x0D, 0xA3, 0xA0, 0xAA, 0x1E,
0xB0, 0xBD, 0x72, 0x6B, 0x2B, 0x4C, 0x8A, 0x7E,
0x34, 0xFC, 0x37, 0x60, 0xF4, 0x1E, 0x22, 0xA0,
0x0B, 0xFB, 0x18, 0x84, 0x60, 0xA5, 0x77, 0x72
};
#if defined(RNG_WATCHDOG)
// Use jitter between the watchdog timer and the main CPU clock to
// harvest some entropy on AVR-based systems. This technique comes from:
//
// https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library
//
// The watchdog generates entropy very slowly - it can take around 32 seconds
// to generate 256 bits of entropy credit. This is a "better than nothing"
// entropy source but a real noise source is definitely recommended.
// Helper macros for specific 32-bit shift counts.
#define leftShift3(value) ((value) << 3)
#define leftShift10(value) ((value) << 10)
#define leftShift15(value) ((value) << 15)
#define rightShift6(value) ((value) >> 6)
#define rightShift11(value) ((value) >> 11)
static uint32_t volatile hash = 0;
static uint8_t volatile outBits = 0;
// Watchdog interrupt handler. This fires off every 16ms. We collect
// 32 bits and then pass them off onto RNGClass::loop().
ISR(WDT_vect)
{
// Read the low byte of Timer 1. We assume that the timer was
// initialized by the Arduino startup code for PWM use or that the
// application is free-running Timer 1 for its own purposes.
// Timer 0 is used on systems that don't have a Timer 1.
#if defined(TCNT1L)
unsigned char value = TCNT1L;
#elif defined(TCNT0L)
unsigned char value = TCNT0L;
#else
unsigned char value = TCNT0;
#endif
// Use Jenkin's one-at-a-time hash function to scatter the entropy a bit.
// https://en.wikipedia.org/wiki/Jenkins_hash_function
hash += value;
hash += leftShift10(hash);
hash ^= rightShift6(hash);
++outBits;
}
#endif // RNG_WATCHDOG
/** @endcond */
/**
* \brief Constructs a new random number generator instance.
*
* This constructor must be followed by a call to begin() to
* properly initialize the random number generator.
*
* \sa begin()
*/
RNGClass::RNGClass()
: credits(0)
, firstSave(1)
, initialized(0)
, trngPending(0)
, timer(0)
, timeout(3600000UL) // 1 hour in milliseconds
, count(0)
, trngPosn(0)
{
}
/**
* \brief Destroys this random number generator instance.
*/
RNGClass::~RNGClass()
{
#if defined(RNG_DUE_TRNG)
// Disable the TRNG in the Arduino Due.
REG_TRNG_CR = TRNG_CR_KEY(0x524E47);
#endif
#if defined(RNG_WATCHDOG)
// Disable interrupts and reset the watchdog.
cli();
wdt_reset();
// Clear the "reset due to watchdog" flag.
MCUSR &= ~(1 << WDRF);
// Disable the watchdog.
_WD_CONTROL_REG |= (1 << _WD_CHANGE_BIT) | (1 << WDE);
_WD_CONTROL_REG = 0;
// Re-enable interrupts. The watchdog should be stopped.
sei();
#endif
clean(block);
clean(stream);
}
#if defined(RNG_DUE_TRNG)
// Find the flash memory of interest. Allow for the possibility
// of other SAM-based Arduino variants in the future.
#if defined(IFLASH1_ADDR)
#define RNG_FLASH_ADDR IFLASH1_ADDR
#define RNG_FLASH_SIZE IFLASH1_SIZE
#define RNG_FLASH_PAGE_SIZE IFLASH1_PAGE_SIZE
#define RNG_EFC EFC1
#elif defined(IFLASH0_ADDR)
#define RNG_FLASH_ADDR IFLASH0_ADDR
#define RNG_FLASH_SIZE IFLASH0_SIZE
#define RNG_FLASH_PAGE_SIZE IFLASH0_PAGE_SIZE
#define RNG_EFC EFC0
#else
#define RNG_FLASH_ADDR IFLASH_ADDR
#define RNG_FLASH_SIZE IFLASH_SIZE
#define RNG_FLASH_PAGE_SIZE IFLASH_PAGE_SIZE
#define RNG_EFC EFC
#endif
// Address of the flash page to use for saving the seed on the Due.
// All SAM variants have a page size of 256 bytes or greater so there is
// plenty of room for the 48 byte seed in the last page of flash memory.
#define RNG_SEED_ADDR (RNG_FLASH_ADDR + RNG_FLASH_SIZE - RNG_FLASH_PAGE_SIZE)
#define RNG_SEED_PAGE ((RNG_FLASH_SIZE / RNG_FLASH_PAGE_SIZE) - 1)
// Stir in the unique identifier for the Arduino Due's CPU.
// This function must be in RAM because programs running out of
// flash memory are not allowed to access the unique identifier.
// Info from: http://forum.arduino.cc/index.php?topic=289190.0
__attribute__((section(".ramfunc")))
static void stirUniqueIdentifier(void)
{
uint32_t id[4];
// Start Read Unique Identifier.
RNG_EFC->EEFC_FCR = (0x5A << 24) | EFC_FCMD_STUI;
while ((RNG_EFC->EEFC_FSR & EEFC_FSR_FRDY) != 0)
; // do nothing until FRDY falls.
// Read the identifier.
id[0] = *((const uint32_t *)RNG_FLASH_ADDR);
id[1] = *((const uint32_t *)(RNG_FLASH_ADDR + 4));
id[2] = *((const uint32_t *)(RNG_FLASH_ADDR + 8));
id[3] = *((const uint32_t *)(RNG_FLASH_ADDR + 12));
// Stop Read Unique Identifier.
RNG_EFC->EEFC_FCR = (0x5A << 24) | EFC_FCMD_SPUI;
while ((RNG_EFC->EEFC_FSR & EEFC_FSR_FRDY) == 0)
; // do nothing until FRDY rises.
// Stir the unique identifier into the entropy pool.
RNG.stir((uint8_t *)id, sizeof(id));
}
// Erases the flash page containing the seed and then writes the new seed.
// It is assumed the seed has already been loaded into the latch registers.
__attribute__((section(".ramfunc")))
static void eraseAndWriteSeed()
{
// Execute the "Erase and Write Page" command.
RNG_EFC->EEFC_FCR = (0x5A << 24) | (RNG_SEED_PAGE << 8) | EFC_FCMD_EWP;
// Wait for the FRDY bit to be raised.
while ((RNG_EFC->EEFC_FSR & EEFC_FSR_FRDY) == 0)
; // do nothing until FRDY rises.
}
#endif
/**
* \brief Initializes the random number generator.
*
* \param tag A string that is stirred into the random pool at startup;
* usually this should be a value that is unique to the application and
* version such as "MyApp 1.0" so that different applications do not
* generate the same sequence of values upon first boot.
*
* This function should be followed by calls to addNoiseSource() to
* register the application's noise sources.
*
* \sa addNoiseSource(), stir(), save()
*/
void RNGClass::begin(const char *tag)
{
// Bail out if we have already done this.
if (initialized)
return;
// Initialize the ChaCha20 input block from the saved seed.
memcpy_P(block, tagRNG, sizeof(tagRNG));
memcpy_P(block + 4, initRNG, sizeof(initRNG));
#if defined(RNG_EEPROM)
int address = RNG_EEPROM_ADDRESS;
eeprom_read_block(stream, (const void *)address, SEED_SIZE);
if (crypto_crc8('S', stream, SEED_SIZE - 1) ==
((const uint8_t *)stream)[SEED_SIZE - 1]) {
// We have a saved seed: XOR it with the initialization block.
// Note: the CRC-8 value is included. No point throwing it away.
for (int posn = 0; posn < 12; ++posn)
block[posn + 4] ^= stream[posn];
}
#elif defined(RNG_DUE_TRNG)
// Do we have a seed saved in the last page of flash memory on the Due?
if (crypto_crc8('S', ((const uint32_t *)RNG_SEED_ADDR) + 1, SEED_SIZE)
== ((const uint32_t *)RNG_SEED_ADDR)[0]) {
// XOR the saved seed with the initialization block.
for (int posn = 0; posn < 12; ++posn)
block[posn + 4] ^= ((const uint32_t *)RNG_SEED_ADDR)[posn + 1];
}
// If the device has just been reprogrammed, there will be no saved seed.
// XOR the initialization block with some output from the CPU's TRNG
// to permute the state in a first boot situation after reprogramming.
pmc_enable_periph_clk(ID_TRNG);
REG_TRNG_CR = TRNG_CR_KEY(0x524E47) | TRNG_CR_ENABLE;
REG_TRNG_IDR = TRNG_IDR_DATRDY; // Disable interrupts - we will poll.
mixTRNG();
#endif
#if defined(RNG_ESP_NVS)
// Do we have a seed saved in ESP non-volatile storage (NVS)?
nvs_handle handle = 0;
if (nvs_open("rng", NVS_READONLY, &handle) == 0) {
size_t len = 0;
if (nvs_get_blob(handle, "seed", NULL, &len) == 0 && len == SEED_SIZE) {
uint32_t seed[12];
if (nvs_get_blob(handle, "seed", seed, &len) == 0) {
for (int posn = 0; posn < 12; ++posn)
block[posn + 4] ^= seed[posn];
}
clean(seed);
}
nvs_close(handle);
}
#endif
#if defined(RNG_WORD_TRNG)
// Mix in some output from a word-based TRNG to initialize the state.
mixTRNG();
#endif
// No entropy credits for the saved seed.
credits = 0;
// Trigger an automatic save once the entropy credits max out.
firstSave = 1;
// Rekey the random number generator immediately.
rekey();
// Stir in the supplied tag data but don't credit any entropy to it.
if (tag)
stir((const uint8_t *)tag, strlen(tag));
#if defined(RNG_DUE_TRNG)
// Stir in the unique identifier for the CPU so that different
// devices will give different outputs even without seeding.
stirUniqueIdentifier();
#elif defined(ESP8266)
// ESP8266's have a 32-bit CPU chip ID and 32-bit flash chip ID
// that we can use as a device unique identifier.
uint32_t ids[2];
ids[0] = ESP.getChipId();
ids[1] = ESP.getFlashChipId();
stir((const uint8_t *)ids, sizeof(ids));
#elif defined(ESP32)
// ESP32's have a MAC address that can be used as a device identifier.
uint64_t mac = ESP.getEfuseMac();
stir((const uint8_t *)&mac, sizeof(mac));
#else
// AVR devices don't have anything like a serial number so it is
// difficult to make every device unique. Use the compilation
// time and date to provide a little randomness across applications
// if not across devices running the same pre-compiled application.
tag = __TIME__ __DATE__;
stir((const uint8_t *)tag, strlen(tag));
#endif
#if defined(RNG_WATCHDOG)
// Disable interrupts and reset the watchdog.
cli();
wdt_reset();
// Clear the "reset due to watchdog" flag.
MCUSR &= ~(1 << WDRF);
// Enable the watchdog with the smallest duration (16ms)
// and interrupt-only mode.
_WD_CONTROL_REG |= (1 << _WD_CHANGE_BIT) | (1 << WDE);
_WD_CONTROL_REG = (1 << WDIE);
// Re-enable interrupts. The watchdog should be running.
sei();
#endif
// Re-save the seed to obliterate the previous value and to ensure
// that if the system is reset without a call to save() that we won't
// accidentally generate the same sequence of random data again.
save();
// The RNG has now been initialized.
initialized = 1;
}
/**
* \brief Adds a noise source to the random number generator.
*
* \param source The noise source to add, which will be polled regularly
* by loop() to accumulate noise-based entropy from the source.
*
* RNG supports a maximum of four noise sources. If the application needs
* more than that then the application must poll the noise sources itself by
* calling NoiseSource::stir() directly.
*
* \sa loop(), begin()
*/
void RNGClass::addNoiseSource(NoiseSource &source)
{
#define MAX_NOISE_SOURCES (sizeof(noiseSources) / sizeof(noiseSources[0]))
if (count < MAX_NOISE_SOURCES) {
noiseSources[count++] = &source;
source.added();
}
}
/**
* \brief Sets the amount of time between automatic seed saves.
*
* \param minutes The number of minutes between automatic seed saves.
*
* The default time between automatic seed saves is 1 hour.
*
* This function is intended to help with EEPROM wear by slowing down how
* often seed data is saved as noise is stirred into the random pool.
* The exact period to use depends upon how long you intend to field
* the device before replacing it. For example, an EEPROM rated for
* 100k erase/write cycles will last about 69 days saving once a minute
* or 11 years saving once an hour.
*
* \sa save(), stir()
*/
void RNGClass::setAutoSaveTime(uint16_t minutes)
{
if (!minutes)
minutes = 1; // Just in case.
timeout = ((uint32_t)minutes) * 60000U;
}
/**
* \brief Generates random bytes into a caller-supplied buffer.
*
* \param data Points to the buffer to fill with random bytes.
* \param len Number of bytes to generate.
*
* Calling this function will decrease the amount of entropy in the
* random number pool by \a len * 8 bits. If there isn't enough
* entropy, then this function will still return \a len bytes of
* random data generated from what entropy it does have.
*
* If the application requires a specific amount of entropy before
* generating important values, the available() function can be
* polled to determine when sufficient entropy is available.
*
* \sa available(), stir()
*/
void RNGClass::rand(uint8_t *data, size_t len)
{
// Make sure that the RNG is initialized in case the application
// forgot to call RNG.begin() at startup time.
if (!initialized)
begin(0);
// Decrease the amount of entropy in the pool.
if (len > (credits / 8u))
credits = 0;
else
credits -= len * 8;
// If we have pending TRNG data from the loop() function,
// then force a stir on the state. Otherwise mix in some
// fresh data from the TRNG because it is possible that
// the application forgot to call RNG.loop().
if (trngPending) {
stir(0, 0, 0);
trngPending = 0;
trngPosn = 0;
} else {
mixTRNG();
}
// Generate the random data.
uint8_t count = 0;
while (len > 0) {
// Force a rekey if we have generated too many blocks in this request.
if (count >= RNG_REKEY_BLOCKS) {
rekey();
count = 1;
} else {
++count;
}
// Increment the low counter word and generate a new keystream block.
++(block[12]);
ChaCha::hashCore(stream, block, RNG_ROUNDS);
// Copy the data to the return buffer.
if (len < 64) {
memcpy(data, stream, len);
break;
} else {
memcpy(data, stream, 64);
data += 64;
len -= 64;
}
}
// Force a rekey after every request.
rekey();
}
/**
* \brief Determine if there is sufficient entropy available for a
* specific request size.
*
* \param len The number of bytes of random data that will be requested
* via a call to rand().
* \return Returns true if there is at least \a len * 8 bits of entropy
* in the random number pool, or false if not.
*
* This function can be used by the application to wait for sufficient
* entropy to become available from the system's noise sources before
* generating important values. For example:
*
* \code
* bool haveKey = false;
* byte key[32];
*
* void loop() {
* ...
*
* if (!haveKey && RNG.available(sizeof(key))) {
* RNG.rand(key, sizeof(key));
* haveKey = true;
* }
*
* ...
* }
* \endcode
*
* If \a len is larger than the maximum number of entropy credits supported
* by the random number pool (384 bits, 48 bytes), then the maximum will be
* used instead. For example, asking if 512 bits (64 bytes) are available
* will return true if in reality only 384 bits are available. If this is a
* problem for the application's security requirements, then large requests
* for random data should be broken up into smaller chunks with the
* application waiting for the entropy pool to refill between chunks.
*
* \sa rand()
*/
bool RNGClass::available(size_t len) const
{
if (len >= (RNG_MAX_CREDITS / 8))
return credits >= RNG_MAX_CREDITS;
else
return len <= (credits / 8u);
}
/**
* \brief Stirs additional entropy data into the random pool.
*
* \param data Points to the additional data to be stirred in.
* \param len Number of bytes to be stirred in.
* \param credit The number of bits of entropy to credit for the
* data that is stirred in. Note that this is bits, not bytes.
*
* The maximum credit allowed is \a len * 8 bits, indicating that
* every bit in the input \a data is good and random. Practical noise
* sources are rarely that good, so \a credit will usually be smaller.
* For example, to credit 2 bits of entropy per byte, the function
* would be called as follows:
*
* \code
* RNG.stir(noise_data, noise_bytes, noise_bytes * 2);
* \endcode
*
* If \a credit is zero, then the \a data will be stirred in but no
* entropy credit is given. This is useful for static values like
* serial numbers and MAC addresses that are different between
* devices but highly predictable.
*
* \sa loop()
*/
void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit)
{
// Increase the entropy credit.
if ((credit / 8) >= len && len)
credit = len * 8;
if ((RNG_MAX_CREDITS - credits) > credit)
credits += credit;
else
credits = RNG_MAX_CREDITS;
// Process the supplied input data.
if (len > 0) {
// XOR the data with the ChaCha input block in 48 byte
// chunks and rekey the ChaCha cipher for each chunk to mix
// the data in. This should scatter any "true entropy" in
// the input across the entire block.
while (len > 0) {
size_t templen = len;
if (templen > 48)
templen = 48;
uint8_t *output = ((uint8_t *)block) + 16;
len -= templen;
while (templen > 0) {
*output++ ^= *data++;
--templen;
}
rekey();
}
} else {
// There was no input data, so just force a rekey so we
// get some mixing of the state even without new data.
rekey();
}
// Save if this is the first time we have reached max entropy.
// This provides some protection if the system is powered off before
// the first auto-save timeout occurs.
if (firstSave && credits >= RNG_MAX_CREDITS) {
firstSave = 0;
save();
}
}
/**
* \brief Saves the random seed to EEPROM.
*
* During system startup, noise sources typically won't have accumulated
* much entropy. But startup is usually the time when the system most
* needs to generate random data for session keys, IV's, and the like.
*
* The purpose of this function is to pass some of the accumulated entropy
* from one session to the next after a loss of power. Thus, once the system
* has been running for a while it will get progressively better at generating
* random values and the accumulated entropy will not be completely lost.
*
* Normally it isn't necessary to call save() directly. The loop() function
* will automatically save the seed on a periodic basis (default of 1 hour).
*
* The seed that is saved is generated in such a way that it cannot be used
* to predict random values that were generated previously or subsequently
* in the current session. So a compromise of the EEPROM contents of a
* captured device should not result in compromise of random values
* that have already been generated. However, if power is lost and the
* system restarted, then there will be a short period of time where the
* random state will be predictable from the seed. For this reason it is
* very important to stir() in new noise data at startup.
*
* \sa loop(), stir()
*/
void RNGClass::save()
{
// Generate random data from the current state and save
// that as the seed. Then force a rekey.
++(block[12]);
ChaCha::hashCore(stream, block, RNG_ROUNDS);
#if defined(RNG_EEPROM)
// We shorten the seed from 48 bytes to 47 to leave room for
// the CRC-8 value. We do this to align the data on an 8-byte
// boundary in EERPOM.
int address = RNG_EEPROM_ADDRESS;
eeprom_write_block(stream, (void *)address, SEED_SIZE - 1);
eeprom_write_byte((uint8_t *)(address + SEED_SIZE - 1),
crypto_crc8('S', stream, SEED_SIZE - 1));
#elif defined(RNG_DUE_TRNG)
unsigned posn;
((uint32_t *)(RNG_SEED_ADDR))[0] = crypto_crc8('S', stream, SEED_SIZE);
for (posn = 0; posn < 12; ++posn)
((uint32_t *)(RNG_SEED_ADDR))[posn + 1] = stream[posn];
for (posn = 13; posn < (RNG_FLASH_PAGE_SIZE / 4); ++posn)
((uint32_t *)(RNG_SEED_ADDR))[posn + 13] = 0xFFFFFFFF;
eraseAndWriteSeed();
#elif defined(RNG_ESP_NVS)
// Save the seed into ESP non-volatile storage (NVS).
nvs_handle handle = 0;
if (nvs_open("rng", NVS_READWRITE, &handle) == 0) {
nvs_erase_all(handle);
nvs_set_blob(handle, "seed", stream, SEED_SIZE);
nvs_commit(handle);
nvs_close(handle);
}
#endif
rekey();
timer = millis();
}
/**
* \brief Run periodic housekeeping tasks on the random number generator.
*
* This function must be called on a regular basis from the application's
* main "loop()" function.
*/
void RNGClass::loop()
{
// Stir in the entropy from all registered noise sources.
for (uint8_t posn = 0; posn < count; ++posn)
noiseSources[posn]->stir();
#if defined(RNG_DUE_TRNG)
// If there is data available from the Arudino Due's TRNG, then XOR
// it with the state block and increase the entropy credit. We don't
// call stir() yet because that will seriously slow down the system
// given how fast the TRNG is. Instead we save up the XOR'ed TRNG
// data until the next rand() call and then hash it to generate the
// desired output.
//
// The CPU documentation claims that the TRNG output is very good so
// this should only make the pool more and more random as time goes on.
// However there is a risk that the CPU manufacturer was pressured by
// government or intelligence agencies to insert a back door that
// generates predictable output. Or the manufacturer was overly
// optimistic about their TRNG design and it is actually flawed in a
// way they don't realise.
//
// If you are concerned about such threats, then make sure to mix in
// data from other noise sources. By hashing together the TRNG with
// the other noise data, rand() should produce unpredictable data even
// if one of the sources is actually predictable.
if ((REG_TRNG_ISR & TRNG_ISR_DATRDY) != 0) {
block[4 + trngPosn] ^= REG_TRNG_ODATA;
if (++trngPosn >= 12)
trngPosn = 0;
if (credits < RNG_MAX_CREDITS) {
// Credit 1 bit of entropy for the word. The TRNG should be
// better than this but it is so fast that we want to collect
// up more data before passing it to the application.
++credits;
}
trngPending = 1;
}
#elif defined(RNG_WORD_TRNG)
// Read a word from the TRNG and XOR it into the state.
block[4 + trngPosn] ^= RNG_WORD_TRNG_GET();
if (++trngPosn >= 12)
trngPosn = 0;
if (credits < RNG_MAX_CREDITS) {
// Credit 1 bit of entropy for the word. The TRNG should be
// better than this but it is so fast that we want to collect
// up more data before passing it to the application.
++credits;
}
trngPending = 1;
#elif defined(RNG_WATCHDOG)
// Read the 32 bit buffer from the WDT interrupt.
cli();
if (outBits >= 32) {
uint32_t value = hash;
hash = 0;
outBits = 0;
sei();
// Final steps of the Jenkin's one-at-a-time hash function.
// https://en.wikipedia.org/wiki/Jenkins_hash_function
value += leftShift3(value);
value ^= rightShift11(value);
value += leftShift15(value);
// Credit 1 bit of entropy for each byte of input. It can take
// between 30 and 40 seconds to accumulate 256 bits of credit.
credits += 4;
if (credits > RNG_MAX_CREDITS)
credits = RNG_MAX_CREDITS;
// XOR the word with the state. Stir once we accumulate 48 bytes,
// which happens about once every 6.4 seconds.
block[4 + trngPosn] ^= value;
if (++trngPosn >= 12) {
trngPosn = 0;
trngPending = 0;
stir(0, 0, 0);
} else {
trngPending = 1;
}
} else {
sei();
}
#endif
// Save the seed if the auto-save timer has expired.
if ((millis() - timer) >= timeout)
save();
}
/**
* \brief Destroys the data in the random number pool and the saved seed
* in EEPROM.
*
* This function attempts to throw away any data that could theoretically be
* used to predict previous and future outputs of the random number generator
* if the device is captured, sold, or otherwise compromised.
*
* After this function is called, begin() must be called again to
* re-initialize the random number generator.
*
* \note The rand() and save() functions take some care to manage the
* random number pool in a way that makes prediction of past outputs from a
* captured state very difficult. Future outputs may be predictable if
* noise or other high-entropy data is not mixed in with stir() on a
* regular basis.
*
* \sa begin()
*/
void RNGClass::destroy()
{
clean(block);
clean(stream);
#if defined(RNG_EEPROM)
int address = RNG_EEPROM_ADDRESS;
for (int posn = 0; posn < SEED_SIZE; ++posn)
eeprom_write_byte((uint8_t *)(address + posn), 0xFF);
#elif defined(RNG_DUE_TRNG)
for (unsigned posn = 0; posn < (RNG_FLASH_PAGE_SIZE / 4); ++posn)
((uint32_t *)(RNG_SEED_ADDR))[posn] = 0xFFFFFFFF;
eraseAndWriteSeed();
#elif defined(RNG_ESP_NVS)
nvs_handle handle = 0;
if (nvs_open("rng", NVS_READWRITE, &handle) == 0) {
nvs_erase_all(handle);
nvs_commit(handle);
nvs_close(handle);
}
#endif
initialized = 0;
}
/**
* \brief Rekeys the random number generator.
*/
void RNGClass::rekey()
{
// Rekey the cipher for the next request by generating a new block.
// This is intended to make it difficult to wind the random number
// backwards if the state is captured later. The first 16 bytes of
// "block" remain set to "tagRNG".
++(block[12]);
ChaCha::hashCore(stream, block, RNG_ROUNDS);
memcpy(block + 4, stream, 48);
// Permute the high word of the counter using the system microsecond
// counter to introduce a little bit of non-stir randomness for each
// request. Note: If random data is requested on a predictable schedule
// then this may not help very much. It is still necessary to stir in
// high quality entropy data on a regular basis using stir().
block[13] ^= micros();
}
/**
* \brief Mix in fresh data from the TRNG when rand() is called.
*/
void RNGClass::mixTRNG()
{
#if defined(RNG_DUE_TRNG)
// Mix in 12 words from the Due's TRNG.
for (int posn = 0; posn < 12; ++posn) {
// According to the documentation the TRNG should produce a new
// 32-bit random value every 84 clock cycles. If it still hasn't
// produced a value after 200 iterations, then assume that the
// TRNG is not producing output and stop.
int counter;
for (counter = 0; counter < 200; ++counter) {
if ((REG_TRNG_ISR & TRNG_ISR_DATRDY) != 0)
break;
}
if (counter >= 200)
break;
block[posn + 4] ^= REG_TRNG_ODATA;
}
#elif defined(RNG_WORD_TRNG)
// Read 12 words from the TRNG and XOR them into the state.
for (uint8_t index = 4; index < 16; ++index)
block[index] ^= RNG_WORD_TRNG_GET();
#elif defined(RNG_WATCHDOG)
// Read the pending 32 bit buffer from the WDT interrupt and mix it in.
cli();
if (outBits >= 32) {
uint32_t value = hash;
hash = 0;
outBits = 0;
sei();
// Final steps of the Jenkin's one-at-a-time hash function.
// https://en.wikipedia.org/wiki/Jenkins_hash_function
value += leftShift3(value);
value ^= rightShift11(value);
value += leftShift15(value);
// XOR the word with the state.
block[4] ^= value;
} else {
sei();
}
#endif
}