From 99755eaa0cb2cb861041b552eec8641f0b7a886b Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Mon, 12 Mar 2018 21:48:24 +0100 Subject: [PATCH] sys: random: add fortuna PRNG --- Makefile.dep | 10 +- sys/Makefile.dep | 3 + sys/include/random.h | 1 + sys/random/Makefile | 4 + sys/random/fortuna.c | 98 ++++++++++++ sys/random/fortuna/LICENSE.txt | 22 +++ sys/random/fortuna/Makefile | 1 + sys/random/fortuna/fortuna.c | 274 +++++++++++++++++++++++++++++++++ sys/random/fortuna/fortuna.h | 232 ++++++++++++++++++++++++++++ 9 files changed, 644 insertions(+), 1 deletion(-) create mode 100644 sys/Makefile.dep create mode 100644 sys/random/fortuna.c create mode 100644 sys/random/fortuna/LICENSE.txt create mode 100755 sys/random/fortuna/Makefile create mode 100755 sys/random/fortuna/fortuna.c create mode 100755 sys/random/fortuna/fortuna.h diff --git a/Makefile.dep b/Makefile.dep index 9b9dcd53ab..cb63f4a1a0 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -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 diff --git a/sys/Makefile.dep b/sys/Makefile.dep new file mode 100644 index 0000000000..b2e06357c4 --- /dev/null +++ b/sys/Makefile.dep @@ -0,0 +1,3 @@ +ifneq (,$(filter prng_fortuna,$(USEMODULE))) + CFLAGS += -DCRYPTO_AES +endif diff --git a/sys/include/random.h b/sys/include/random.h index b33bb8c45d..48b978782f 100644 --- a/sys/include/random.h +++ b/sys/include/random.h @@ -21,6 +21,7 @@ * - Mersenne Twister * - Simple Park-Miller PRNG * - Musl C PRNG + * - Fortuna (CS)PRNG */ #ifndef RANDOM_H diff --git a/sys/random/Makefile b/sys/random/Makefile index 4e66ead042..4436add645 100644 --- a/sys/random/Makefile +++ b/sys/random/Makefile @@ -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 diff --git a/sys/random/fortuna.c b/sys/random/fortuna.c new file mode 100644 index 0000000000..01ac4fa5db --- /dev/null +++ b/sys/random/fortuna.c @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2016-2018 Bas Stottelaar + * + * 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 + * @} + */ + +#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; +} diff --git a/sys/random/fortuna/LICENSE.txt b/sys/random/fortuna/LICENSE.txt new file mode 100644 index 0000000000..2036fd7bf8 --- /dev/null +++ b/sys/random/fortuna/LICENSE.txt @@ -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. diff --git a/sys/random/fortuna/Makefile b/sys/random/fortuna/Makefile new file mode 100755 index 0000000000..48422e909a --- /dev/null +++ b/sys/random/fortuna/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/random/fortuna/fortuna.c b/sys/random/fortuna/fortuna.c new file mode 100755 index 0000000000..2a60865978 --- /dev/null +++ b/sys/random/fortuna/fortuna.c @@ -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); +} diff --git a/sys/random/fortuna/fortuna.h b/sys/random/fortuna/fortuna.h new file mode 100755 index 0000000000..5282e8a9a0 --- /dev/null +++ b/sys/random/fortuna/fortuna.h @@ -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 */ +/** @} */