mirror of
https://github.com/taigrr/arduinolibs
synced 2025-01-18 04:33:12 -08:00
885 lines
31 KiB
C++
885 lines
31 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_ESP8266 1
|
|
#define RNG_ESP8266_GET_TRNG() (ESP8266_DREG(0x20E44))
|
|
#endif
|
|
#include <string.h>
|
|
|
|
/**
|
|
* \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 384
|
|
|
|
/** @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)
|
|
, 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)
|
|
{
|
|
// 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?
|
|
int posn, counter;
|
|
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 (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.
|
|
for (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.
|
|
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;
|
|
}
|
|
#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(RNG_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));
|
|
#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();
|
|
}
|
|
|
|
/**
|
|
* \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)
|
|
{
|
|
// Decrease the amount of entropy in the pool.
|
|
if (len > (credits / 8))
|
|
credits = 0;
|
|
else
|
|
credits -= len * 8;
|
|
|
|
// 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 / 8);
|
|
}
|
|
|
|
/**
|
|
* \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();
|
|
#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;
|
|
}
|
|
}
|
|
#elif defined(RNG_ESP8266)
|
|
// Read a word from the ESP8266's TRNG and XOR it into the state.
|
|
block[4 + trngPosn] ^= RNG_ESP8266_GET_TRNG();
|
|
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;
|
|
}
|
|
#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);
|
|
|
|
// 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;
|
|
|
|
// Credit 1 bit of entropy for each byte of input. It can take
|
|
// between 30 and 40 seconds to accumulate 256 bits of credit.
|
|
stir(0, 0, 48);
|
|
}
|
|
} 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();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* \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);
|
|
|
|
#if defined(RNG_ESP8266)
|
|
// XOR in some data from the ESP8266's TRNG every time we re-key.
|
|
// This makes the RNG safer if the application forgets to call loop().
|
|
for (uint8_t posn = 0; posn < 12; ++posn)
|
|
block[posn + 4] ^= RNG_ESP8266_GET_TRNG();
|
|
#else
|
|
// 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();
|
|
#endif
|
|
}
|