diff --git a/doc/crypto-rng.dox b/doc/crypto-rng.dox index f15bba67..e57c60b1 100644 --- a/doc/crypto-rng.dox +++ b/doc/crypto-rng.dox @@ -40,6 +40,8 @@ global random number pool. It has the following features: \li Provision for plug-in environmental noise sources and entropy estimation. \li Whitening of noise values to scatter the input noise across the entire random number pool. +\li Built-in support for the True Random Number Generator (TRNG) in the + Arduino Due's CPU. \li Support for mixing in static values like serial numbers and MAC addresses so that otherwise identical devices do not generate the same sequence of random numbers upon first boot. @@ -151,6 +153,14 @@ Because the device may be restarted before the first hour expires, there is a special case in the code: the first time that the entropy pool fills up, a save will be automatically forced. +\note The Arduino Due does not have EEPROM so it cannot save the random +number seed across system restarts. Instead the \link RNGClass RNG\endlink +class will 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. + To use the random number generator properly, there are some regular tasks that must be performed every time around the application's main loop(). Newly accumulated noise must be mixed in and auto-saves must be performed diff --git a/libraries/Crypto/RNG.cpp b/libraries/Crypto/RNG.cpp index bc392236..a6ef7da7 100644 --- a/libraries/Crypto/RNG.cpp +++ b/libraries/Crypto/RNG.cpp @@ -26,7 +26,14 @@ #include "Crypto.h" #include "utility/ProgMemUtil.h" #include +#if defined (__arm__) && defined (__SAM3X8E__) +// The Arduino Due does not have any EEPROM natively on the main chip. +// However, it does have a TRNG so seed saving is not as critical. +#define RNG_NO_EEPROM 1 +#define RNG_DUE_TRNG 1 +#else #include +#endif #include /** @@ -106,6 +113,14 @@ * conditions programmatically, then it may make sense to force a save() * of the seed upon shutdown. * + * \note The Arduino Due does not have EEPROM so it cannot save the random + * number seed across system restarts. Instead the RNG class will 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 */ @@ -171,6 +186,7 @@ RNGClass::RNGClass() , timer(0) , timeout(3600000UL) // 1 hour in milliseconds , count(0) + , trngPosn(0) { } @@ -179,10 +195,47 @@ RNGClass::RNGClass() */ RNGClass::~RNGClass() { +#if defined(RNG_DUE_TRNG) + // Disable the TRNG in the Arduino Due. + REG_TRNG_CR = TRNG_CR_KEY(0x524E47); +#endif clean(block); clean(stream); } +#if defined(RNG_DUE_TRNG) + +// 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. + EFC1->EEFC_FCR = (0x5A << 24) | EFC_FCMD_STUI; + while ((EFC1->EEFC_FSR & EEFC_FSR_FRDY) != 0) + ; // do nothing until FRDY falls. + + // Read the identifier. + id[0] = *((const uint32_t *)IFLASH1_ADDR); + id[1] = *((const uint32_t *)(IFLASH1_ADDR + 4)); + id[2] = *((const uint32_t *)(IFLASH1_ADDR + 8)); + id[3] = *((const uint32_t *)(IFLASH1_ADDR + 12)); + + // Stop Read Unique Identifier. + EFC1->EEFC_FCR = (0x5A << 24) | EFC_FCMD_SPUI; + while ((EFC1->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)); +} + +#endif + /** * \brief Initializes the random number generator. * @@ -207,6 +260,7 @@ void RNGClass::begin(const char *tag, int eepromAddress) // 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_NO_EEPROM) if (eeprom_read_byte((const uint8_t *)address) == 'S') { // We have a saved seed: XOR it with the initialization block. for (int posn = 0; posn < 12; ++posn) { @@ -214,6 +268,27 @@ void RNGClass::begin(const char *tag, int eepromAddress) eeprom_read_dword((const uint32_t *)(address + posn * 4 + 1)); } } +#elif defined(RNG_DUE_TRNG) + // The Arduino Due does not have EEPROM so we instead XOR the + // initialization block with some output from the CPU's TRNG. + int posn, counter; + 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; @@ -228,6 +303,12 @@ void RNGClass::begin(const char *tag, int eepromAddress) 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(); +#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. @@ -478,10 +559,12 @@ void RNGClass::save() { // Generate random data from the current state and save // that as the seed. Then force a rekey. +#if !defined(RNG_NO_EEPROM) ++(block[12]); ChaCha::hashCore(stream, block, RNG_ROUNDS); eeprom_write_block(stream, (void *)(address + 1), 48); eeprom_update_byte((uint8_t *)address, 'S'); +#endif rekey(); timer = millis(); } @@ -498,6 +581,39 @@ void RNGClass::loop() 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; + } + } +#endif + // Save the seed if the auto-save timer has expired. if ((millis() - timer) >= timeout) save(); @@ -526,8 +642,10 @@ void RNGClass::destroy() { clean(block); clean(stream); +#if !defined(RNG_NO_EEPROM) for (int posn = 0; posn < SEED_SIZE; ++posn) eeprom_write_byte((uint8_t *)(address + posn), 0xFF); +#endif } /** diff --git a/libraries/Crypto/RNG.h b/libraries/Crypto/RNG.h index 83740f10..1e959573 100644 --- a/libraries/Crypto/RNG.h +++ b/libraries/Crypto/RNG.h @@ -62,6 +62,7 @@ private: unsigned long timeout; NoiseSource *noiseSources[4]; uint8_t count; + uint8_t trngPosn; void rekey(); }; diff --git a/libraries/Crypto/examples/TestRNG/TestRNG.ino b/libraries/Crypto/examples/TestRNG/TestRNG.ino index 2304a5a0..994f2ea5 100644 --- a/libraries/Crypto/examples/TestRNG/TestRNG.ino +++ b/libraries/Crypto/examples/TestRNG/TestRNG.ino @@ -4,7 +4,7 @@ #include #include #include -#include +//#include // Change "MyApp 1.0" to some other tag for your application // so that different applications will generate different results