1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

sys: random: add fortuna PRNG

This commit is contained in:
Bas Stottelaar 2018-03-12 21:48:24 +01:00
parent 32413f872e
commit 99755eaa0c
9 changed files with 644 additions and 1 deletions

View File

@ -5,7 +5,8 @@ OLD_USEPKG := $(sort $(USEPKG))
# include board dependencies
-include $(RIOTBOARD)/$(BOARD)/Makefile.dep
# pull dependencies from drivers
# pull dependencies from sys and drivers
include $(RIOTBASE)/sys/Makefile.dep
include $(RIOTBASE)/drivers/Makefile.dep
ifneq (,$(filter cbor_ctime,$(USEMODULE)))
@ -615,6 +616,13 @@ ifneq (,$(filter random,$(USEMODULE)))
USEMODULE += prng_tinymt32
endif
ifneq (,$(filter prng_fortuna,$(USEMODULE)))
USEMODULE += fortuna
USEMODULE += hashes
USEMODULE += crypto
USEMODULE += xtimer
endif
ifneq (,$(filter prng_tinymt32,$(USEMODULE)))
USEMODULE += tinymt32
endif

3
sys/Makefile.dep Normal file
View File

@ -0,0 +1,3 @@
ifneq (,$(filter prng_fortuna,$(USEMODULE)))
CFLAGS += -DCRYPTO_AES
endif

View File

@ -21,6 +21,7 @@
* - Mersenne Twister
* - Simple Park-Miller PRNG
* - Musl C PRNG
* - Fortuna (CS)PRNG
*/
#ifndef RANDOM_H

View File

@ -3,6 +3,10 @@ SRC := random.c
BASE_MODULE := prng
SUBMODULES := 1
ifneq (,$(filter prng_fortuna,$(USEMODULE)))
DIRS += fortuna
endif
ifneq (,$(filter prng_tinymt32,$(USEMODULE)))
DIRS += tinymt32
endif

98
sys/random/fortuna.c Normal file
View File

@ -0,0 +1,98 @@
/**
* Copyright (C) 2016-2018 Bas Stottelaar <basstottelaar@gmail.com>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup sys_random
* @{
* @file
*
* @brief Glue-code for Fortuna PRNG.
*
* @author Bas Stottelaar <basstottelaar@gmail.com>
* @}
*/
#include "log.h"
#include "mutex.h"
#include "fortuna/fortuna.h"
/**
* @brief This holds the PRNG state.
*/
static fortuna_state_t fortuna_state;
/**
* @brief Initialize the PRNG with a given number of bytes.
*/
static void _init(uint8_t *in, size_t bytes)
{
fortuna_seed_t seed;
/* ensure a seed of proper length is available, which may not be the case
with a small input */
memset(seed, 0, sizeof(seed));
for (int i = 0; i < (int) bytes; i++) {
seed[i % sizeof(seed)] ^= in[i];
}
/* initialize the PRNG state */
fortuna_init(&fortuna_state);
/* update the PRNG seed (seed will be overwritten by design!) */
fortuna_update_seed(&fortuna_state, &seed);
}
/**
* @brief Wrapper for fortuna_random_data that supports reading more than
* FORTUNA_RESEED_LIMIT and also checks the result.
*/
static void _read(uint8_t *out, size_t bytes)
{
do {
/* read at most chunk bytes to not exhaust the state at once */
size_t chunk = (bytes < FORTUNA_RESEED_LIMIT) ?
bytes : FORTUNA_RESEED_LIMIT;
int res = fortuna_random_data(&fortuna_state, out, chunk);
if (res == -1) {
LOG_ERROR("random: reading more bytes than allowed.\n");
}
else if (res == -2) {
LOG_ERROR("random: PRNG not initialized.\n");
}
else if (res == -3) {
LOG_ERROR("random: unknown error.\n");
}
/* advance bytes and buffer */
bytes -= chunk;
out += chunk;
} while (bytes > FORTUNA_RESEED_LIMIT);
}
void random_init_by_array(uint32_t init_key[], int key_length)
{
_init((uint8_t *) init_key, sizeof(uint32_t) * key_length);
}
void random_init(uint32_t s)
{
_init((uint8_t *) &s, sizeof(s));
}
uint32_t random_uint32(void)
{
uint32_t data;
_read((uint8_t *) &data, sizeof(data));
return data;
}

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2016 Brandon Lin
Copyright (c) 2016-2018 Bas Stottelaar
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.

1
sys/random/fortuna/Makefile Executable file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

274
sys/random/fortuna/fortuna.c Executable file
View File

@ -0,0 +1,274 @@
/**
* @brief Fortuna PRNG implementation.
*
* The MIT License applies to this software. See the included LICENSE.txt file
* for more information.
*/
#include "fortuna.h"
/**
* @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;
}
/*
* 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
/* set last reseed to ensure initial time diff is correct */
state->last_reseed = xtimer_now_usec64();
#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
if (state->pools[0].len >= FORTUNA_MIN_POOL_SIZE &&
(xtimer_now_usec64() - state->last_reseed) > FORTUNA_RESEED_INTERVAL) {
#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 | (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
state->last_reseed = xtimer_now_usec64();
#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);
}

232
sys/random/fortuna/fortuna.h Executable file
View File

@ -0,0 +1,232 @@
/**
* @brief Fortuna PRNG implementation.
*
* The MIT License applies to this software. See the included LICENSE.txt file
* for more information.
*/
/*
* This is not your general purpose PRNG: it is hungry for memory.
*
* See https://www.schneier.com/cryptography/paperfiles/fortuna.pdf for the
* implementation details. Code insipred by https://github.com/blin00/os (MIT
* licensed), but heavily modified, stripped and improved for RIOT-OS.
*
* The implementation follows the paper, with a few exceptions:
*
* - Initialization of the generator and PRNG is combined in fortuna_init.
* - The check if the generator is seeded is moved from fortuna_generate_blocks
* to fortuna_pseudo_random_data for optimization reasons.
* - In fortuna_random_data, the check on state->reseeds == 0 is not performed.
* It would never provide a seed whenever the PRNG is initialized with a seed
* file, when entropy sources are not available (yet). It would still fail
* whenever fortuna_pseudo_random_data checks if the generator is seeded! See
* https://crypto.stackexchange.com/q/56390 for more information.
*/
#ifndef FORTUNA_H
#define FORTUNA_H
#include "xtimer.h"
#include "mutex.h"
#include "crypto/aes.h"
#include "hashes/sha256.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @bief Number of pools to use, which may not exceed 32. More pools means more
* memory usage, but makes it harder for an attacker to influence the
* state. The recommended value is 32, as discussed in section 9.5.2.
*/
#ifndef FORTUNA_POOLS
#define FORTUNA_POOLS (32U)
#endif
/**
* @brief Minimum number of bytes that should be in a pool. Higher values will
* delay reseeds even if good random entropy is collected. In section
* 9.5.5, a suitable value of 64 bytes is suggested.
*/
#ifndef FORTUNA_MIN_POOL_SIZE
#define FORTUNA_MIN_POOL_SIZE (64U)
#endif
/**
* @brief Definition of the seed file size used to initialize the PRNG. The
* default value of 64 bytes is suggested by section 9.6.1.
*/
#ifndef FORTUNA_SEED_SIZE
#define FORTUNA_SEED_SIZE (64U)
#endif
/**
* @brief Reseed interval in us. After this interval, the PRNG must be
* reseeded. Per section 9.5.5, the recommended value is 100ms. Set to
* zero to disable this security feature.
*/
#ifndef FORTUNA_RESEED_INTERVAL
#define FORTUNA_RESEED_INTERVAL (0)
#endif
/**
* @brief Reseed limit in bytes. After this number of bytes, the PRNG must be
* reseeded. Per section 9.4.4, the recommended value is 2^20 bytes
* (=1 MB). Set to zero to disable this security feature.
*/
#ifndef FORTUNA_RESEED_LIMIT
#define FORTUNA_RESEED_LIMIT (1U << 20)
#endif
/**
* @brief Fortuna requires a block cipher with a 256-bit key to minimize
* permutation attacks.
*
* @note RIOT-OS does not (yet) support AES-256. Therefore, at the expense of
* security, AES-128 is used instead. An detailed explanation on the
* effects can be found at https://crypto.stackexchange.com/a/5736.
*/
#ifndef FORTUNA_AES_KEY_SIZE
#define FORTUNA_AES_KEY_SIZE (16U)
#endif
/**
* @brief Use a mutex to lock concurrent access when running sensitive
* operations in multi-threaded applications.
*/
#ifndef FORTUNA_LOCK
#define FORTUNA_LOCK (1)
#endif
/**
* @brief When set, perform additional instructions to clear temporary
* variables to prevent leaking sensitive information.
*/
#ifndef FORTUNA_CLEANUP
#define FORTUNA_CLEANUP (1)
#endif
/**
* @brief Generator counter and key.
*/
typedef struct {
uint8_t key[32];
union {
struct {
uint64_t l;
uint64_t h;
} split;
uint8_t bytes[16];
} counter;
} fortuna_generator_t;
/**
* @brief Pool for storing entropy.
*/
typedef struct {
sha256_context_t ctx;
uint32_t len;
} fortuna_pool_t;
/**
* @brief The Fortuna state.
*/
typedef struct {
fortuna_generator_t gen;
fortuna_pool_t pools[FORTUNA_POOLS];
uint32_t reseeds;
#if FORTUNA_RESEED_INTERVAL > 0
uint64_t last_reseed;
#endif
#if FORTUNA_LOCK
mutex_t lock;
#endif
} fortuna_state_t;
/**
* @brief Type definition of a Fortuna seed file.
*/
typedef uint32_t fortuna_seed_t[FORTUNA_SEED_SIZE];
/**
* @brief Initialize the Fortuna PRNG state.
*
* It is possible to use this method to clear an existing state.
*
* @param[inout] state PRNG state
*
* @return zero on succesful initialization
* @return non-zero on error
*/
int fortuna_init(fortuna_state_t *state);
/**
* @brief Read random bytes from the PRNG. The number of bytes may not exceed
* FORTUNA_RESEED_LIMIT bytes.
*
* @param[inout] state PRNG state
* @param[out] out pointer to buffer
* @param[in] bytes number of bytes to write in buffer
*
* @return zero on success
* @return -1 on reading more that FORTUNA_RESEED_LIMIT bytes
* @return -2 on reading from unseeded PRNG
* @return -3 on other error
*/
int fortuna_random_data(fortuna_state_t *state, uint8_t *out, size_t bytes);
/**
* @brief Add a entropy of a random event to one PRNG pool. The pool must
* exist and the source length must be 1-32 bytes.
*
* @param[inout] state PRNG state
* @param[in] data pointer to entropy source
* @param[in] length length of entropy source
* @param[in] source source identifier (each source has its own ID)
* @param[in] pool pool number to add entropy to
*
* @return zero on success
* @return -1 on zero bytes or more than 32 bytes
* @return -2 on invalid pool number
*/
int fortuna_add_random_event(fortuna_state_t *state, const uint8_t *data,
uint8_t length, uint8_t source, uint8_t pool);
/**
* @brief Generate 64 bytes from the PRNG and write that to an output
* buffer for use on next startup.
*
* This method must be invoked before shutting down the PRNG (e.g. on system
* shutdown).
*
* @param[inout] state PRNG state
* @param[out] data pointer to output buffer for the seed
*
* @return zero on success
*/
int fortuna_write_seed(fortuna_state_t *state, fortuna_seed_t *out);
/**
* @brief Reseed the PRNG using a seed. By design, this value will be
* overwritten after use.
*
* This method should be invoked once on PRNG startup, in case a seed is
* available.
*
* @param[inout] state PRNG state
* @param[inout] data pointer to input and output buffer for the seed
*
* @return zero on success
*/
int fortuna_update_seed(fortuna_state_t *state, fortuna_seed_t *inout);
#ifdef __cplusplus
}
#endif
#endif /* FORTUNA_H */
/** @} */