freebsd-dev/sys/dev/random/yarrow.c
Mark Murray b79ad7e642 Remove NOBLOCKRANDOM as a compile-time option. Instead, provide
exactly the same functionality via a sysctl, making this feature
a run-time option.

The default is 1(ON), which means that /dev/random device will
NOT block at startup.

setting kern.random.sys.seeded to 0(OFF) will cause /dev/random
to block until the next reseed, at which stage the sysctl
will be changed back to 1(ON).

While I'm here, clean up the sysctls, and make them dynamic.
Reviewed by:		des
Tested on Alpha by:	obrien
2001-01-14 17:50:15 +00:00

549 lines
14 KiB
C

/*-
* Copyright (c) 2000 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.
*
* $FreeBSD$
*/
/* NOTE NOTE NOTE - This is not finished! It will supply numbers, but
* it is not yet cryptographically secure!!
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/libkern.h>
#include <sys/mutex.h>
#include <sys/selinfo.h>
#include <sys/random.h>
#include <sys/types.h>
#include <sys/unistd.h>
#include <machine/atomic.h>
#include <machine/cpu.h>
#include <crypto/blowfish/blowfish.h>
#include <dev/random/hash.h>
#include <dev/random/yarrow.h>
/* #define DEBUG */
/* #define DEBUG1 */ /* Very noisy - prints plenty harvesting stats */
static void generator_gate(void);
static void reseed(int);
static void random_harvest_internal(u_int64_t, void *, u_int, u_int, u_int, enum esource);
static void random_kthread(void *);
/* Structure holding the entropy state */
struct random_state random_state;
/* These are used to queue harvested packets of entropy. The entropy
* buffer size is pretty arbitrary.
*/
struct harvest {
u_int64_t somecounter; /* fast counter for clock jitter */
u_char entropy[HARVESTSIZE]; /* the harvested entropy */
u_int size, bits, frac; /* stats about the entropy */
enum esource source; /* stats about the entropy */
u_int pool; /* which pool this goes into */
};
/* Ring buffer holding harvested entropy */
static struct harvestring {
struct mtx lockout_mtx;
int head;
int tail;
struct harvest data[HARVEST_RING_SIZE];
} harvestring;
/* The reseed thread mutex */
static struct mtx random_reseed_mtx;
/* <0 to end the kthread, 0 to let it run */
static int random_kthread_control = 0;
static struct proc *random_kthread_proc;
static void
random_kthread(void *arg /* NOTUSED */)
{
int pl, src, overthreshhold[2], head, newtail;
struct harvest *event;
struct source *source;
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("OWNERSHIP Giant == %d sched_lock == %d\n",
mtx_owned(&Giant), mtx_owned(&sched_lock));
mtx_exit(&Giant, MTX_DEF);
#endif
for (pl = 0; pl < 2; pl++)
yarrow_hash_init(&random_state.pool[pl].hash, NULL, 0);
for (;;) {
head = atomic_load_acq_int(&harvestring.head);
newtail = (harvestring.tail + 1) % HARVEST_RING_SIZE;
if (harvestring.tail == head)
tsleep(&harvestring.head, PUSER, "rndslp", hz/10);
else {
#ifdef DEBUG1
mtx_enter(&Giant, MTX_DEF);
printf("HARVEST src=%d bits=%d/%d pool=%d count=%lld\n",
event->source, event->bits, event->frac,
event->pool, event->somecounter);
mtx_exit(&Giant, MTX_DEF);
#endif
/* Suck the harvested entropy out of the queue and hash
* it into the appropriate pool.
*/
event = &harvestring.data[harvestring.tail];
harvestring.tail = newtail;
source = &random_state.pool[event->pool].source[event->source];
yarrow_hash_iterate(&random_state.pool[event->pool].hash,
event->entropy, sizeof(event->entropy));
yarrow_hash_iterate(&random_state.pool[event->pool].hash,
&event->somecounter, sizeof(event->somecounter));
source->frac += event->frac;
source->bits += event->bits + source->frac/1024;
source->frac %= 1024;
/* Count the over-threshold sources in each pool */
for (pl = 0; pl < 2; pl++) {
overthreshhold[pl] = 0;
for (src = 0; src < ENTROPYSOURCE; src++) {
if (random_state.pool[pl].source[src].bits
> random_state.pool[pl].thresh)
overthreshhold[pl]++;
}
}
/* if any fast source over threshhold, reseed */
if (overthreshhold[FAST])
reseed(FAST);
/* if enough slow sources are over threshhold, reseed */
if (overthreshhold[SLOW] >= random_state.slowoverthresh)
reseed(SLOW);
}
/* Is the thread scheduled for a shutdown? */
if (random_kthread_control != 0) {
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Random kthread setting terminate\n");
mtx_exit(&Giant, MTX_DEF);
#endif
random_set_wakeup_exit(&random_kthread_control);
/* NOTREACHED */
break;
}
}
}
int
random_init(void)
{
int error;
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Random initialise\n");
mtx_exit(&Giant, MTX_DEF);
#endif
/* This can be turned off by the very paranoid
* a reseed will turn it back on.
*/
random_state.seeded = 1;
random_state.gengateinterval = 10;
random_state.bins = 10;
random_state.pool[0].thresh = 100;
random_state.pool[1].thresh = 160;
random_state.slowoverthresh = 2;
random_state.which = FAST;
/* Initialise the mutexes */
mtx_init(&random_reseed_mtx, "random reseed", MTX_DEF);
mtx_init(&harvestring.lockout_mtx, "random harvest", MTX_DEF);
harvestring.head = 0;
harvestring.tail = 0;
/* Start the hash/reseed thread */
error = kthread_create(random_kthread, NULL,
&random_kthread_proc, RFHIGHPID, "random");
if (error != 0)
return error;
/* Register the randomness harvesting routine */
random_init_harvester(random_harvest_internal, read_random_real);
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Random initalise finish\n");
mtx_exit(&Giant, MTX_DEF);
#endif
return 0;
}
void
random_deinit(void)
{
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Random deinitalise\n");
mtx_exit(&Giant, MTX_DEF);
#endif
/* Deregister the randomness harvesting routine */
random_deinit_harvester();
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Random deinitalise waiting for thread to terminate\n");
mtx_exit(&Giant, MTX_DEF);
#endif
/* Command the hash/reseed thread to end and wait for it to finish */
mtx_enter(&harvestring.lockout_mtx, MTX_DEF);
random_kthread_control = -1;
msleep((void *)&random_kthread_control, &harvestring.lockout_mtx, PUSER,
"rndend", 0);
mtx_exit(&harvestring.lockout_mtx, MTX_DEF);
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Random deinitalise removing mutexes\n");
mtx_exit(&Giant, MTX_DEF);
#endif
/* Remove the mutexes */
mtx_destroy(&random_reseed_mtx);
mtx_destroy(&harvestring.lockout_mtx);
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Random deinitalise finish\n");
mtx_exit(&Giant, MTX_DEF);
#endif
}
static void
reseed(int fastslow)
{
/* Interrupt-context stack is a limited resource; make large
* structures static.
*/
static u_char v[TIMEBIN][KEYSIZE]; /* v[i] */
static struct yarrowhash context;
u_char hash[KEYSIZE]; /* h' */
u_char temp[KEYSIZE];
int i, j;
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Reseed type %d\n", fastslow);
mtx_exit(&Giant, MTX_DEF);
#endif
/* The reseed task must not be jumped on */
mtx_enter(&random_reseed_mtx, MTX_DEF);
/* 1. Hash the accumulated entropy into v[0] */
yarrow_hash_init(&context, NULL, 0);
/* Feed the slow pool hash in if slow */
if (fastslow == SLOW)
yarrow_hash_iterate(&context,
&random_state.pool[SLOW].hash, sizeof(struct yarrowhash));
yarrow_hash_iterate(&context,
&random_state.pool[FAST].hash, sizeof(struct yarrowhash));
/* 2. Compute hash values for all v. _Supposed_ to be computationally
* intensive.
*/
if (random_state.bins > TIMEBIN)
random_state.bins = TIMEBIN;
for (i = 1; i < random_state.bins; i++) {
yarrow_hash_init(&context, NULL, 0);
/* v[i] #= h(v[i-1]) */
yarrow_hash_iterate(&context, v[i - 1], KEYSIZE);
/* v[i] #= h(v[0]) */
yarrow_hash_iterate(&context, v[0], KEYSIZE);
/* v[i] #= h(i) */
yarrow_hash_iterate(&context, &i, sizeof(int));
/* Return the hashval */
yarrow_hash_finish(&context, v[i]);
}
/* 3. Compute a new key; h' is the identity function here;
* it is not being ignored!
*/
yarrow_hash_init(&context, NULL, 0);
yarrow_hash_iterate(&context, &random_state.key, KEYSIZE);
for (i = 1; i < random_state.bins; i++)
yarrow_hash_iterate(&context, &v[i], KEYSIZE);
yarrow_hash_finish(&context, temp);
yarrow_encrypt_init(&random_state.key, temp, KEYSIZE);
/* 4. Recompute the counter */
random_state.counter = 0;
yarrow_encrypt(&random_state.key, &random_state.counter, temp,
sizeof(random_state.counter));
memcpy(&random_state.counter, temp, random_state.counter);
/* 5. Reset entropy estimate accumulators to zero */
for (i = 0; i <= fastslow; i++) {
for (j = 0; j < ENTROPYSOURCE; j++) {
if (random_state.pool[i].source[j].bits >
random_state.pool[i].thresh) {
random_state.pool[i].source[j].bits = 0;
random_state.pool[i].source[j].frac = 0;
}
}
}
/* 6. Wipe memory of intermediate values */
memset((void *)v, 0, sizeof(v));
memset((void *)temp, 0, sizeof(temp));
memset((void *)hash, 0, sizeof(hash));
/* 7. Dump to seed file */
/* XXX Not done here yet */
/* Release the reseed mutex */
mtx_exit(&random_reseed_mtx, MTX_DEF);
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Reseed finish\n");
mtx_exit(&Giant, MTX_DEF);
#endif
if (!random_state.seeded) {
random_state.seeded = 1;
selwakeup(&random_state.rsel);
wakeup(&random_state);
}
}
u_int
read_random_real(void *buf, u_int count)
{
static u_int64_t genval;
static int cur = 0;
static int gate = 1;
u_int i;
u_int retval;
/* The reseed task must not be jumped on */
mtx_enter(&random_reseed_mtx, MTX_DEF);
if (gate) {
generator_gate();
random_state.outputblocks = 0;
gate = 0;
}
if (count >= sizeof(random_state.counter)) {
retval = 0;
for (i = 0; i < count; i += sizeof(random_state.counter)) {
random_state.counter++;
yarrow_encrypt(&random_state.key, &random_state.counter,
&genval, sizeof(random_state.counter));
memcpy((char *)buf + i, &genval,
sizeof(random_state.counter));
if (++random_state.outputblocks >= random_state.gengateinterval) {
generator_gate();
random_state.outputblocks = 0;
}
retval += sizeof(random_state.counter);
}
}
else {
if (!cur) {
random_state.counter++;
yarrow_encrypt(&random_state.key, &random_state.counter,
&genval, sizeof(random_state.counter));
memcpy(buf, &genval, count);
cur = sizeof(random_state.counter) - count;
if (++random_state.outputblocks >= random_state.gengateinterval) {
generator_gate();
random_state.outputblocks = 0;
}
retval = count;
}
else {
retval = cur < count ? cur : count;
memcpy(buf,
(char *)&genval +
(sizeof(random_state.counter) - cur),
retval);
cur -= retval;
}
}
mtx_exit(&random_reseed_mtx, MTX_DEF);
return retval;
}
void
write_random(void *buf, u_int count)
{
u_int i;
/* Break the input up into HARVESTSIZE chunks.
* The writer has too much control here, so "estimate" the
* the entropy as zero.
*/
for (i = 0; i < count; i += HARVESTSIZE) {
random_harvest_internal(get_cyclecount(), (char *)buf + i,
HARVESTSIZE, 0, 0, RANDOM_WRITE);
}
/* Maybe the loop iterated at least once */
if (i > count)
i -= HARVESTSIZE;
/* Get the last bytes even if the input length is not
* a multiple of HARVESTSIZE.
*/
count %= HARVESTSIZE;
if (count) {
random_harvest_internal(get_cyclecount(), (char *)buf + i,
count, 0, 0, RANDOM_WRITE);
}
}
static void
generator_gate(void)
{
int i;
u_char temp[KEYSIZE];
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Generator gate\n");
mtx_exit(&Giant, MTX_DEF);
#endif
for (i = 0; i < KEYSIZE; i += sizeof(random_state.counter)) {
random_state.counter++;
yarrow_encrypt(&random_state.key, &random_state.counter,
&(temp[i]), sizeof(random_state.counter));
}
yarrow_encrypt_init(&random_state.key, temp, KEYSIZE);
memset((void *)temp, 0, KEYSIZE);
#ifdef DEBUG
mtx_enter(&Giant, MTX_DEF);
printf("Generator gate finish\n");
mtx_exit(&Giant, MTX_DEF);
#endif
}
/* Entropy harvesting routine. This is supposed to be fast; do
* not do anything slow in here!
*/
static void
random_harvest_internal(u_int64_t somecounter, void *entropy, u_int count,
u_int bits, u_int frac, enum esource origin)
{
struct harvest *harvest;
int newhead, tail;
#ifdef DEBUG1
mtx_enter(&Giant, MTX_DEF);
printf("Random harvest\n");
mtx_exit(&Giant, MTX_DEF);
#endif
if (origin < ENTROPYSOURCE) {
/* Add the harvested data to the ring buffer, but
* do not block.
*/
if (mtx_try_enter(&harvestring.lockout_mtx, MTX_DEF)) {
tail = atomic_load_acq_int(&harvestring.tail);
newhead = (harvestring.head + 1) % HARVEST_RING_SIZE;
if (newhead != tail) {
harvest = &harvestring.data[harvestring.head];
/* toggle the pool for next insertion */
harvest->pool = random_state.which;
random_state.which = !random_state.which;
/* Stuff the harvested data into the ring */
harvest->somecounter = somecounter;
count = count > HARVESTSIZE ? HARVESTSIZE : count;
memcpy(harvest->entropy, entropy, count);
harvest->size = count;
harvest->bits = bits;
harvest->frac = frac;
harvest->source = origin;
/* Bump the ring counter and shake the reseed
* process
*/
harvestring.head = newhead;
wakeup(&harvestring.head);
}
mtx_exit(&harvestring.lockout_mtx, MTX_DEF);
}
}
}
/* Helper routine to perform explicit reseeds */
void
random_reseed(void)
{
reseed(FAST);
}