freebsd-dev/sys/dev/random/fortuna.c
Mark Murray 10cb24248a This is the much-discussed major upgrade to the random(4) device, known to you all as /dev/random.
This code has had an extensive rewrite and a good series of reviews, both by the author and other parties. This means a lot of code has been simplified. Pluggable structures for high-rate entropy generators are available, and it is most definitely not the case that /dev/random can be driven by only a hardware souce any more. This has been designed out of the device. Hardware sources are stirred into the CSPRNG (Yarrow, Fortuna) like any other entropy source. Pluggable modules may be written by third parties for additional sources.

The harvesting structures and consequently the locking have been simplified. Entropy harvesting is done in a more general way (the documentation for this will follow). There is some GREAT entropy to be had in the UMA allocator, but it is disabled for now as messing with that is likely to annoy many people.

The venerable (but effective) Yarrow algorithm, which is no longer supported by its authors now has an alternative, Fortuna. For now, Yarrow is retained as the default algorithm, but this may be changed using a kernel option. It is intended to make Fortuna the default algorithm for 11.0. Interested parties are encouraged to read ISBN 978-0-470-47424-2 "Cryptography Engineering" By Ferguson, Schneier and Kohno for Fortuna's gory details. Heck, read it anyway.

Many thanks to Arthur Mesh who did early grunt work, and who got caught in the crossfire rather more than he deserved to.

My thanks also to folks who helped me thresh this out on whiteboards and in the odd "Hallway track", or otherwise.

My Nomex pants are on. Let the feedback commence!

Reviewed by:	trasz,des(partial),imp(partial?),rwatson(partial?)
Approved by:	so(des)
2014-10-30 21:21:53 +00:00

434 lines
12 KiB
C

/*-
* Copyright (c) 2013-2014 Mark R V Murray
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer
* in this position and unchanged.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#ifdef _KERNEL
#include "opt_random.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/random.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <machine/cpu.h>
#include <crypto/rijndael/rijndael-api-fst.h>
#include <crypto/sha2/sha2.h>
#include <dev/random/hash.h>
#include <dev/random/randomdev.h>
#include <dev/random/random_adaptors.h>
#include <dev/random/random_harvestq.h>
#include <dev/random/uint128.h>
#include <dev/random/fortuna.h>
#else /* !_KERNEL */
#include <sys/param.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include "unit_test.h"
#include <crypto/rijndael/rijndael-api-fst.h>
#include <crypto/sha2/sha2.h>
#include <dev/random/hash.h>
#include <dev/random/uint128.h>
#include <dev/random/fortuna.h>
#endif /* _KERNEL */
#if !defined(RANDOM_YARROW) && !defined(RANDOM_FORTUNA)
#define RANDOM_YARROW
#elif defined(RANDOM_YARROW) && defined(RANDOM_FORTUNA)
#error "Must define either RANDOM_YARROW or RANDOM_FORTUNA"
#endif
#if defined(RANDOM_FORTUNA)
#define NPOOLS 32
#define MINPOOLSIZE 64
#define DEFPOOLSIZE 256
#define MAXPOOLSIZE 65536
/* This algorithm (and code) presumes that KEYSIZE is twice as large as BLOCKSIZE */
CTASSERT(BLOCKSIZE == sizeof(uint128_t));
CTASSERT(KEYSIZE == 2*BLOCKSIZE);
/* This is the beastie that needs protecting. It contains all of the
* state that we are excited about.
* Exactly one is instantiated.
*/
static struct fortuna_state {
/* P_i */
struct pool {
u_int length;
struct randomdev_hash hash;
} pool[NPOOLS];
/* ReseedCnt */
u_int reseedcount;
/* C - 128 bits */
union {
uint8_t byte[BLOCKSIZE];
uint128_t whole;
} counter;
/* K */
struct randomdev_key key;
/* Extras */
u_int minpoolsize;
/* Extras for the OS */
#ifdef _KERNEL
/* For use when 'pacing' the reseeds */
sbintime_t lasttime;
#endif
} fortuna_state;
/* The random_reseed_mtx mutex protects seeding and polling/blocking. */
static mtx_t random_reseed_mtx;
static struct fortuna_start_cache {
uint8_t junk[PAGE_SIZE];
size_t length;
struct randomdev_hash hash;
} fortuna_start_cache;
#ifdef _KERNEL
static struct sysctl_ctx_list random_clist;
RANDOM_CHECK_UINT(minpoolsize, MINPOOLSIZE, MAXPOOLSIZE);
#endif
void
random_fortuna_init_alg(void)
{
int i;
#ifdef _KERNEL
struct sysctl_oid *random_fortuna_o;
#endif
memset(fortuna_start_cache.junk, 0, sizeof(fortuna_start_cache.junk));
fortuna_start_cache.length = 0U;
randomdev_hash_init(&fortuna_start_cache.hash);
/* Set up a lock for the reseed process */
#ifdef _KERNEL
mtx_init(&random_reseed_mtx, "reseed mutex", NULL, MTX_DEF);
#else /* !_KERNEL */
mtx_init(&random_reseed_mtx, mtx_plain);
#endif /* _KERNEL */
#ifdef _KERNEL
/* Fortuna parameters. Do not adjust these unless you have
* have a very good clue about what they do!
*/
random_fortuna_o = SYSCTL_ADD_NODE(&random_clist,
SYSCTL_STATIC_CHILDREN(_kern_random),
OID_AUTO, "fortuna", CTLFLAG_RW, 0,
"Fortuna Parameters");
SYSCTL_ADD_PROC(&random_clist,
SYSCTL_CHILDREN(random_fortuna_o), OID_AUTO,
"minpoolsize", CTLTYPE_UINT|CTLFLAG_RW,
&fortuna_state.minpoolsize, DEFPOOLSIZE,
random_check_uint_minpoolsize, "IU",
"Minimum pool size necessary to cause a reseed automatically");
fortuna_state.lasttime = 0U;
#endif
fortuna_state.minpoolsize = DEFPOOLSIZE;
/* F&S - InitializePRNG() */
/* F&S - P_i = \epsilon */
for (i = 0; i < NPOOLS; i++) {
randomdev_hash_init(&fortuna_state.pool[i].hash);
fortuna_state.pool[i].length = 0U;
}
/* F&S - ReseedCNT = 0 */
fortuna_state.reseedcount = 0U;
/* F&S - InitializeGenerator() */
/* F&S - C = 0 */
uint128_clear(&fortuna_state.counter.whole);
/* F&S - K = 0 */
memset(&fortuna_state.key, 0, sizeof(fortuna_state.key));
}
void
random_fortuna_deinit_alg(void)
{
mtx_destroy(&random_reseed_mtx);
memset(&fortuna_state, 0, sizeof(fortuna_state));
}
/* F&S - AddRandomEvent() */
/* Process a single stochastic event off the harvest queue */
void
random_fortuna_process_event(struct harvest_event *event)
{
u_int pl;
/* We must be locked for all this as plenty of state gets messed with */
mtx_lock(&random_reseed_mtx);
/* Accumulate the event into the appropriate pool
* where each event carries the destination information
*/
/* F&S - P_i = P_i|<harvested stuff> */
/* The hash_init and hash_finish are done in random_fortuna_read() below */
pl = event->he_destination % NPOOLS;
randomdev_hash_iterate(&fortuna_state.pool[pl].hash, event, sizeof(*event));
/* No point in counting above the outside maximum */
fortuna_state.pool[pl].length += event->he_size;
fortuna_state.pool[pl].length = MIN(fortuna_state.pool[pl].length, MAXPOOLSIZE);
/* Done with state-messing */
mtx_unlock(&random_reseed_mtx);
}
/* F&S - Reseed() */
/* Reseed Mutex is held */
static void
reseed(uint8_t *junk, u_int length)
{
struct randomdev_hash context;
uint8_t hash[KEYSIZE], temp[KEYSIZE];
KASSERT(fortuna_state.minpoolsize > 0, ("random: Fortuna threshold = 0"));
#ifdef _KERNEL
mtx_assert(&random_reseed_mtx, MA_OWNED);
#endif
/* F&S - temp = H(K|s) */
randomdev_hash_init(&context);
randomdev_hash_iterate(&context, &fortuna_state.key, sizeof(fortuna_state.key));
randomdev_hash_iterate(&context, junk, length);
randomdev_hash_finish(&context, temp);
/* F&S - hash = H(temp) */
randomdev_hash_init(&context);
randomdev_hash_iterate(&context, temp, KEYSIZE);
randomdev_hash_finish(&context, hash);
/* F&S - K = hash */
randomdev_encrypt_init(&fortuna_state.key, temp);
memset(temp, 0, sizeof(temp));
memset(hash, 0, sizeof(hash));
/* Unblock the device if it was blocked due to being unseeded */
if (uint128_is_zero(fortuna_state.counter.whole))
random_adaptor_unblock();
/* F&S - C = C + 1 */
uint128_increment(&fortuna_state.counter.whole);
}
/* F&S - GenerateBlocks() */
/* Reseed Mutex is held, and buf points to a whole number of blocks. */
static __inline void
random_fortuna_genblocks(uint8_t *buf, u_int blockcount)
{
u_int i;
for (i = 0u; i < blockcount; i++) {
/* F&S - r = r|E(K,C) */
randomdev_encrypt(&fortuna_state.key, fortuna_state.counter.byte, buf, BLOCKSIZE);
buf += BLOCKSIZE;
/* F&S - C = C + 1 */
uint128_increment(&fortuna_state.counter.whole);
}
}
/* F&S - PseudoRandomData() */
/* Reseed Mutex is held, and buf points to a whole number of blocks. */
static __inline void
random_fortuna_genrandom(uint8_t *buf, u_int bytecount)
{
static uint8_t temp[BLOCKSIZE*(KEYSIZE/BLOCKSIZE)];
u_int blockcount;
/* F&S - assert(n < 2^20) */
KASSERT((bytecount <= (1 << 20)), ("invalid single read request to fortuna of %d bytes", bytecount));
/* F&S - r = first-n-bytes(GenerateBlocks(ceil(n/16))) */
blockcount = (bytecount + BLOCKSIZE - 1)/BLOCKSIZE;
random_fortuna_genblocks(buf, blockcount);
/* F&S - K = GenerateBlocks(2) */
random_fortuna_genblocks(temp, KEYSIZE/BLOCKSIZE);
randomdev_encrypt_init(&fortuna_state.key, temp);
memset(temp, 0, sizeof(temp));
}
/* F&S - RandomData() */
/* Used to return processed entropy from the PRNG */
/* The argument buf points to a whole number of blocks. */
void
random_fortuna_read(uint8_t *buf, u_int bytecount)
{
#ifdef _KERNEL
sbintime_t thistime;
#endif
struct randomdev_hash context;
uint8_t s[NPOOLS*KEYSIZE], temp[KEYSIZE];
int i;
u_int seedlength;
/* We must be locked for all this as plenty of state gets messed with */
mtx_lock(&random_reseed_mtx);
/* if buf == NULL and bytecount == 0 then this is the pre-read. */
/* if buf == NULL and bytecount != 0 then this is the post-read; ignore. */
if (buf == NULL) {
if (bytecount == 0) {
if (fortuna_state.pool[0].length >= fortuna_state.minpoolsize
#ifdef _KERNEL
/* F&S - Use 'getsbinuptime()' to prevent reseed-spamming. */
&& ((thistime = getsbinuptime()) - fortuna_state.lasttime > hz/10)
#endif
) {
#ifdef _KERNEL
fortuna_state.lasttime = thistime;
#endif
seedlength = 0U;
/* F&S - ReseedCNT = ReseedCNT + 1 */
fortuna_state.reseedcount++;
/* s = \epsilon by default */
for (i = 0; i < NPOOLS; i++) {
/* F&S - if Divides(ReseedCnt, 2^i) ... */
if ((fortuna_state.reseedcount % (1 << i)) == 0U) {
seedlength += KEYSIZE;
/* F&S - temp = (P_i) */
randomdev_hash_finish(&fortuna_state.pool[i].hash, temp);
/* F&S - P_i = \epsilon */
randomdev_hash_init(&fortuna_state.pool[i].hash);
fortuna_state.pool[i].length = 0U;
/* F&S - s = s|H(temp) */
randomdev_hash_init(&context);
randomdev_hash_iterate(&context, temp, KEYSIZE);
randomdev_hash_finish(&context, s + i*KEYSIZE);
}
else
break;
}
#ifdef RANDOM_DEBUG
printf("random: active reseed: reseedcount [%d] ", fortuna_state.reseedcount);
for (i = 0; i < NPOOLS; i++)
printf(" %d", fortuna_state.pool[i].length);
printf("\n");
#endif
/* F&S */
reseed(s, seedlength);
/* Clean up */
memset(s, 0, seedlength);
seedlength = 0U;
memset(temp, 0, sizeof(temp));
memset(&context, 0, sizeof(context));
}
}
}
/* if buf != NULL do a regular read. */
else
random_fortuna_genrandom(buf, bytecount);
mtx_unlock(&random_reseed_mtx);
}
/* Internal function to hand external entropy to the PRNG */
void
random_fortuna_write(uint8_t *buf, u_int count)
{
uint8_t temp[KEYSIZE];
int i;
uintmax_t timestamp;
timestamp = get_cyclecount();
randomdev_hash_iterate(&fortuna_start_cache.hash, &timestamp, sizeof(timestamp));
randomdev_hash_iterate(&fortuna_start_cache.hash, buf, count);
timestamp = get_cyclecount();
randomdev_hash_iterate(&fortuna_start_cache.hash, &timestamp, sizeof(timestamp));
randomdev_hash_finish(&fortuna_start_cache.hash, temp);
for (i = 0; i < KEYSIZE; i++)
fortuna_start_cache.junk[(fortuna_start_cache.length + i)%PAGE_SIZE] ^= temp[i];
fortuna_start_cache.length += KEYSIZE;
#ifdef RANDOM_DEBUG
printf("random: %s - ", __func__);
for (i = 0; i < KEYSIZE; i++)
printf("%02X", temp[i]);
printf("\n");
#endif
memset(temp, 0, KEYSIZE);
/* We must be locked for all this as plenty of state gets messed with */
mtx_lock(&random_reseed_mtx);
randomdev_hash_init(&fortuna_start_cache.hash);
reseed(fortuna_start_cache.junk, MIN(PAGE_SIZE, fortuna_start_cache.length));
memset(fortuna_start_cache.junk, 0, sizeof(fortuna_start_cache.junk));
mtx_unlock(&random_reseed_mtx);
}
void
random_fortuna_reseed(void)
{
/* CWOT */
}
int
random_fortuna_seeded(void)
{
return (!uint128_is_zero(fortuna_state.counter.whole));
}
#endif /* RANDOM_FORTUNA */