1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00

Make the RNG class more robust if the app doesn't call begin() or loop()

This commit is contained in:
Rhys Weatherley 2018-04-02 07:07:58 +10:00
parent fca80f28fd
commit b1ac67efb6
2 changed files with 94 additions and 26 deletions

View File

@ -244,6 +244,8 @@ ISR(WDT_vect)
RNGClass::RNGClass() RNGClass::RNGClass()
: credits(0) : credits(0)
, firstSave(1) , firstSave(1)
, initialized(0)
, trngPending(0)
, timer(0) , timer(0)
, timeout(3600000UL) // 1 hour in milliseconds , timeout(3600000UL) // 1 hour in milliseconds
, count(0) , count(0)
@ -365,6 +367,10 @@ static void eraseAndWriteSeed()
*/ */
void RNGClass::begin(const char *tag) 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. // Initialize the ChaCha20 input block from the saved seed.
memcpy_P(block, tagRNG, sizeof(tagRNG)); memcpy_P(block, tagRNG, sizeof(tagRNG));
memcpy_P(block + 4, initRNG, sizeof(initRNG)); memcpy_P(block + 4, initRNG, sizeof(initRNG));
@ -380,11 +386,10 @@ void RNGClass::begin(const char *tag)
} }
#elif defined(RNG_DUE_TRNG) #elif defined(RNG_DUE_TRNG)
// Do we have a seed saved in the last page of flash memory on the Due? // 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) if (crypto_crc8('S', ((const uint32_t *)RNG_SEED_ADDR) + 1, SEED_SIZE)
== ((const uint32_t *)RNG_SEED_ADDR)[0]) { == ((const uint32_t *)RNG_SEED_ADDR)[0]) {
// XOR the saved seed with the initialization block. // XOR the saved seed with the initialization block.
for (posn = 0; posn < 12; ++posn) for (int posn = 0; posn < 12; ++posn)
block[posn + 4] ^= ((const uint32_t *)RNG_SEED_ADDR)[posn + 1]; block[posn + 4] ^= ((const uint32_t *)RNG_SEED_ADDR)[posn + 1];
} }
@ -394,19 +399,10 @@ void RNGClass::begin(const char *tag)
pmc_enable_periph_clk(ID_TRNG); pmc_enable_periph_clk(ID_TRNG);
REG_TRNG_CR = TRNG_CR_KEY(0x524E47) | TRNG_CR_ENABLE; REG_TRNG_CR = TRNG_CR_KEY(0x524E47) | TRNG_CR_ENABLE;
REG_TRNG_IDR = TRNG_IDR_DATRDY; // Disable interrupts - we will poll. REG_TRNG_IDR = TRNG_IDR_DATRDY; // Disable interrupts - we will poll.
for (posn = 0; posn < 12; ++posn) { mixTRNG();
// According to the documentation the TRNG should produce a new #elif defined(RNG_ESP8266)
// 32-bit random value every 84 clock cycles. If it still hasn't // Mix in some output from the ESP8266's TRNG to initialize the state.
// produced a value after 200 iterations, then assume that the mixTRNG();
// 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 #endif
// No entropy credits for the saved seed. // No entropy credits for the saved seed.
@ -463,6 +459,9 @@ void RNGClass::begin(const char *tag)
// that if the system is reset without a call to save() that we won't // that if the system is reset without a call to save() that we won't
// accidentally generate the same sequence of random data again. // accidentally generate the same sequence of random data again.
save(); save();
// The RNG has now been initialized.
initialized = 1;
} }
/** /**
@ -528,12 +527,29 @@ void RNGClass::setAutoSaveTime(uint16_t minutes)
*/ */
void RNGClass::rand(uint8_t *data, size_t len) 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. // Decrease the amount of entropy in the pool.
if (len > (credits / 8)) if (len > (credits / 8))
credits = 0; credits = 0;
else else
credits -= len * 8; 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. // Generate the random data.
uint8_t count = 0; uint8_t count = 0;
while (len > 0) { while (len > 0) {
@ -774,6 +790,7 @@ void RNGClass::loop()
// up more data before passing it to the application. // up more data before passing it to the application.
++credits; ++credits;
} }
trngPending = 1;
} }
#elif defined(RNG_ESP8266) #elif defined(RNG_ESP8266)
// Read a word from the ESP8266's TRNG and XOR it into the state. // Read a word from the ESP8266's TRNG and XOR it into the state.
@ -786,6 +803,7 @@ void RNGClass::loop()
// up more data before passing it to the application. // up more data before passing it to the application.
++credits; ++credits;
} }
trngPending = 1;
#elif defined(RNG_WATCHDOG) #elif defined(RNG_WATCHDOG)
// Read the 32 bit buffer from the WDT interrupt. // Read the 32 bit buffer from the WDT interrupt.
cli(); cli();
@ -801,15 +819,21 @@ void RNGClass::loop()
value ^= rightShift11(value); value ^= rightShift11(value);
value += leftShift15(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, // XOR the word with the state. Stir once we accumulate 48 bytes,
// which happens about once every 6.4 seconds. // which happens about once every 6.4 seconds.
block[4 + trngPosn] ^= value; block[4 + trngPosn] ^= value;
if (++trngPosn >= 12) { if (++trngPosn >= 12) {
trngPosn = 0; trngPosn = 0;
trngPending = 0;
// Credit 1 bit of entropy for each byte of input. It can take stir(0, 0, 0);
// between 30 and 40 seconds to accumulate 256 bits of credit. } else {
stir(0, 0, 48); trngPending = 1;
} }
} else { } else {
sei(); sei();
@ -868,17 +892,58 @@ void RNGClass::rekey()
ChaCha::hashCore(stream, block, RNG_ROUNDS); ChaCha::hashCore(stream, block, RNG_ROUNDS);
memcpy(block + 4, stream, 48); 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 // Permute the high word of the counter using the system microsecond
// counter to introduce a little bit of non-stir randomness for each // counter to introduce a little bit of non-stir randomness for each
// request. Note: If random data is requested on a predictable schedule // 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 // then this may not help very much. It is still necessary to stir in
// high quality entropy data on a regular basis using stir(). // high quality entropy data on a regular basis using stir().
block[13] ^= micros(); 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_ESP8266)
// Read 12 words from the ESP8266's TRNG and XOR them into the state.
for (uint8_t index = 4; index < 16; ++index)
block[index] ^= RNG_ESP8266_GET_TRNG();
#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 #endif
} }

View File

@ -55,8 +55,10 @@ public:
private: private:
uint32_t block[16]; uint32_t block[16];
uint32_t stream[16]; uint32_t stream[16];
uint16_t credits : 15; uint16_t credits : 13;
uint16_t firstSave : 1; uint16_t firstSave : 1;
uint16_t initialized : 1;
uint16_t trngPending : 1;
unsigned long timer; unsigned long timer;
unsigned long timeout; unsigned long timeout;
NoiseSource *noiseSources[4]; NoiseSource *noiseSources[4];
@ -64,6 +66,7 @@ private:
uint8_t trngPosn; uint8_t trngPosn;
void rekey(); void rekey();
void mixTRNG();
}; };
extern RNGClass RNG; extern RNGClass RNG;