mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
1af2462404
Use a timer to required a reseed, enable use of plain ztimer (no now64)
310 lines
7.2 KiB
C
310 lines
7.2 KiB
C
/**
|
|
* @brief Fortuna PRNG implementation.
|
|
*
|
|
* The MIT License applies to this software. See the included LICENSE.txt file
|
|
* for more information.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "fortuna.h"
|
|
#include "kernel_defines.h"
|
|
#if FORTUNA_RESEED_INTERVAL_MS > 0 && IS_USED(MODULE_FORTUNA_RESEED)
|
|
#include "atomic_utils.h"
|
|
#if IS_USED(MODULE_ZTIMER_MSEC)
|
|
#include "ztimer.h"
|
|
#else
|
|
#include "xtimer.h"
|
|
#endif
|
|
#endif
|
|
|
|
/**
|
|
* @brief Helper to increment the 128-bit counter (see section 9.4).
|
|
*/
|
|
static inline void fortuna_increment_counter(fortuna_state_t *state)
|
|
{
|
|
state->gen.counter.split.l++;
|
|
|
|
/* on overflow of low, increment high */
|
|
if (state->gen.counter.split.l == 0) {
|
|
state->gen.counter.split.h++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Corresponds to section 9.4.2.
|
|
*/
|
|
static void fortuna_reseed(fortuna_state_t *state, const uint8_t *seed,
|
|
size_t length)
|
|
{
|
|
sha256_context_t ctx;
|
|
|
|
sha256_init(&ctx);
|
|
sha256_update(&ctx, state->gen.key, 32);
|
|
sha256_update(&ctx, seed, length);
|
|
sha256_final(&ctx, state->gen.key);
|
|
|
|
/* if the generator was unseeded, this will mark it as seeded */
|
|
fortuna_increment_counter(state);
|
|
|
|
#if FORTUNA_CLEANUP
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Corresponds to section 9.4.3.
|
|
*/
|
|
static int fortuna_generate_blocks(fortuna_state_t *state, uint8_t *out,
|
|
size_t blocks)
|
|
{
|
|
cipher_context_t cipher;
|
|
|
|
/* check if generator has been seeded */
|
|
if (state->gen.counter.split.l == 0 && state->gen.counter.split.h == 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* initialize cipher based on state */
|
|
int res = aes_init(&cipher, state->gen.key, FORTUNA_AES_KEY_SIZE);
|
|
|
|
if (res != CIPHER_INIT_SUCCESS) {
|
|
#if FORTUNA_CLEANUP
|
|
memset(&cipher, 0, sizeof(cipher));
|
|
#endif
|
|
return -2;
|
|
}
|
|
|
|
for (size_t i = 0; i < blocks; i++) {
|
|
aes_encrypt(&cipher, state->gen.counter.bytes, out + (i * 16));
|
|
fortuna_increment_counter(state);
|
|
}
|
|
|
|
#if FORTUNA_CLEANUP
|
|
memset(&cipher, 0, sizeof(cipher));
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Corresponds to section 9.4.4.
|
|
*/
|
|
static int fortuna_pseudo_random_data(fortuna_state_t *state, uint8_t *out,
|
|
size_t bytes)
|
|
{
|
|
uint8_t buf[16];
|
|
|
|
#if FORTUNA_RESEED_LIMIT
|
|
/* maximum number of bytes per read is FORTUNA_RESEED_LIMIT */
|
|
if (bytes > FORTUNA_RESEED_LIMIT) {
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
/* check if generator has been seeded */
|
|
if (state->gen.counter.split.l == 0 && state->gen.counter.split.h == 0) {
|
|
return -2;
|
|
}
|
|
|
|
/* generate blocks per 16 bytes */
|
|
size_t blocks = bytes / 16;
|
|
|
|
if (fortuna_generate_blocks(state, out, blocks)) {
|
|
#if FORTUNA_CLEANUP
|
|
memset(buf, 0, sizeof(buf));
|
|
#endif
|
|
return -3;
|
|
}
|
|
|
|
/* generate one block for the remaining bytes */
|
|
size_t remaining = bytes % 16;
|
|
|
|
if (remaining) {
|
|
if (fortuna_generate_blocks(state, buf, 1)) {
|
|
#if FORTUNA_CLEANUP
|
|
memset(buf, 0, sizeof(buf));
|
|
#endif
|
|
return -3;
|
|
}
|
|
|
|
memcpy(out + (blocks * 16), buf, remaining);
|
|
}
|
|
|
|
/* switch to a new key to avoid later compromises of this output */
|
|
if (fortuna_generate_blocks(state, state->gen.key, 2)) {
|
|
#if FORTUNA_CLEANUP
|
|
memset(buf, 0, sizeof(buf));
|
|
#endif
|
|
return -3;
|
|
}
|
|
|
|
#if FORTUNA_CLEANUP
|
|
memset(buf, 0, sizeof(buf));
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if FORTUNA_RESEED_INTERVAL_MS > 0 && IS_USED(MODULE_FORTUNA_RESEED)
|
|
void _reseed_callback(void *arg)
|
|
{
|
|
fortuna_state_t *state = (fortuna_state_t *) arg;
|
|
state->needs_reseed = 1;
|
|
}
|
|
|
|
static void _reseed_timer_set(fortuna_state_t *state)
|
|
{
|
|
atomic_store_u8(&state->needs_reseed, 0);
|
|
#if IS_USED(MODULE_ZTIMER_MSEC)
|
|
ztimer_set(ZTIMER_MSEC, &state->reseed_timer, FORTUNA_RESEED_INTERVAL_MS);
|
|
#else
|
|
xtimer_set(&state->reseed_timer, FORTUNA_RESEED_INTERVAL_MS * US_PER_MS);
|
|
#endif
|
|
}
|
|
|
|
static void _reseed_timer_init(fortuna_state_t *state) {
|
|
/* initialize reseed timer */
|
|
state->reseed_timer.callback = _reseed_callback;
|
|
state->reseed_timer.arg = state;
|
|
_reseed_timer_set(state);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Corresponds to section 9.4.1 and 9.5.4.
|
|
*/
|
|
int fortuna_init(fortuna_state_t *state)
|
|
{
|
|
/* set everything to zero, then initialize the pools */
|
|
memset(state, 0, sizeof(fortuna_state_t));
|
|
|
|
for (int i = 0; i < (int) FORTUNA_POOLS; i++) {
|
|
sha256_init(&state->pools[i].ctx);
|
|
}
|
|
|
|
#if FORTUNA_RESEED_INTERVAL_MS > 0 && IS_USED(MODULE_FORTUNA_RESEED)
|
|
/* reseed time init if required */
|
|
_reseed_timer_init(state);
|
|
#endif
|
|
|
|
#if FORTUNA_LOCK
|
|
/* initialize the lock */
|
|
mutex_init(&state->lock);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Corresponds to section 9.5.5.
|
|
*/
|
|
int fortuna_random_data(fortuna_state_t *state, uint8_t *out, size_t bytes)
|
|
{
|
|
uint8_t buf[FORTUNA_POOLS * 32];
|
|
|
|
#if FORTUNA_LOCK
|
|
mutex_lock(&state->lock);
|
|
#endif
|
|
|
|
/* reseed the generator if needed, before returning data */
|
|
#if FORTUNA_RESEED_INTERVAL_MS > 0 && IS_USED(MODULE_FORTUNA_RESEED)
|
|
if (state->pools[0].len >= FORTUNA_MIN_POOL_SIZE &&
|
|
atomic_load_u8(&state->needs_reseed)) {
|
|
#else
|
|
if (state->pools[0].len >= FORTUNA_MIN_POOL_SIZE) {
|
|
#endif
|
|
state->reseeds++;
|
|
size_t len = 0;
|
|
|
|
for (int i = 0; i < (int) FORTUNA_POOLS; i++) {
|
|
if (state->reseeds | ((uint32_t)1 << i)) {
|
|
sha256_final(&state->pools[i].ctx, &buf[len]);
|
|
sha256_init(&state->pools[i].ctx);
|
|
state->pools[i].len = 0;
|
|
|
|
/* append length of SHA-256 hash */
|
|
len += 32;
|
|
}
|
|
}
|
|
|
|
fortuna_reseed(state, buf, len);
|
|
|
|
#if FORTUNA_RESEED_INTERVAL_MS > 0 && IS_USED(MODULE_FORTUNA_RESEED)
|
|
_reseed_timer_set(state);
|
|
#endif
|
|
|
|
#if FORTUNA_CLEANUP
|
|
memset(buf, 0, sizeof(buf));
|
|
#endif
|
|
}
|
|
|
|
/* read bytes from the generator */
|
|
int res = fortuna_pseudo_random_data(state, out, bytes);
|
|
|
|
#if FORTUNA_LOCK
|
|
mutex_unlock(&state->lock);
|
|
#endif
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Corresponds to section 9.5.6.
|
|
*/
|
|
int fortuna_add_random_event(fortuna_state_t *state, const uint8_t *data,
|
|
uint8_t length, uint8_t source, uint8_t pool)
|
|
{
|
|
if (length < 1 || length > 32) {
|
|
return -1;
|
|
}
|
|
|
|
if (pool >= FORTUNA_POOLS) {
|
|
return -2;
|
|
}
|
|
|
|
#if FORTUNA_LOCK
|
|
mutex_lock(&state->lock);
|
|
#endif
|
|
|
|
uint8_t header[2];
|
|
header[0] = source;
|
|
header[1] = length;
|
|
sha256_update(&state->pools[pool].ctx, header, 2);
|
|
sha256_update(&state->pools[pool].ctx, (uint8_t *) data, length);
|
|
state->pools[pool].len += length;
|
|
|
|
#if FORTUNA_LOCK
|
|
mutex_unlock(&state->lock);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Corresponds to section 9.6.2.
|
|
*/
|
|
int fortuna_write_seed(fortuna_state_t *state, fortuna_seed_t *out)
|
|
{
|
|
return fortuna_random_data(state, (uint8_t *)out, FORTUNA_SEED_SIZE);
|
|
}
|
|
|
|
/*
|
|
* Corresponds to section 9.6.2.
|
|
*/
|
|
int fortuna_update_seed(fortuna_state_t *state, fortuna_seed_t *inout)
|
|
{
|
|
#if FORTUNA_LOCK
|
|
mutex_lock(&state->lock);
|
|
#endif
|
|
|
|
/* reseed using the provided seed */
|
|
fortuna_reseed(state, (uint8_t *)inout, FORTUNA_SEED_SIZE);
|
|
|
|
#if FORTUNA_LOCK
|
|
mutex_unlock(&state->lock);
|
|
#endif
|
|
|
|
/* the seed file must be overwritten by a new seed file */
|
|
return fortuna_random_data(state, (uint8_t *)inout, FORTUNA_SEED_SIZE);
|
|
}
|