X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/ba6e6b64033b1f9de49feccb5c9cd438354481f7..0f00dc4c8eb47e67bc0f148c2dd109f73a451e0a:/rand/rand.c diff --git a/rand/rand.c b/rand/rand.c new file mode 100644 index 0000000..fa6dab8 --- /dev/null +++ b/rand/rand.c @@ -0,0 +1,584 @@ +/* -*-c-*- + * + * Secure random number generator + * + * (c) 1998 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Catacomb. + * + * Catacomb is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Catacomb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Catacomb; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include +#include +#include + +#include +#include + +#include "arena.h" +#include "blowfish-cbc.h" +#include "paranoia.h" +#include "rand.h" +#include "rmd160.h" +#include "rmd160-hmac.h" + +/*----- Static variables --------------------------------------------------*/ + +static const grand_ops gops; + +typedef struct gctx { + grand r; + rand_pool p; +} gctx; + +static gctx *pool = 0; /* Default random pool */ + +/*----- Macros ------------------------------------------------------------*/ + +#define RAND_RESOLVE(r) do { \ + if ((r) == RAND_GLOBAL) { \ + if (!pool) \ + pool = (gctx *)rand_create(); \ + (r) = &pool->p; \ + } \ +} while (0) + +#define TIMER(r) do { \ + if ((r)->s && (r)->s->timer) \ + (r)->s->timer(r); \ +} while (0) + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @rand_init@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * + * Returns: --- + * + * Use: Initializes a randomness pool. The pool doesn't start out + * very random: that's your job to sort out. A good suggestion + * would be to attach an appropriate noise source and call + * @rand_seed@. + */ + +void rand_init(rand_pool *r) +{ + RAND_RESOLVE(r); + memset(r->pool, 0, sizeof(r->pool)); + memset(r->buf, 0, sizeof(r->buf)); + r->i = 0; + r->irot = 0; + r->ibits = r->obits = 0; + r->o = RAND_SECSZ; + r->s = 0; + rmd160_hmacinit(&r->k, 0, 0); + rand_gate(r); +} + +/* --- @rand_noisesrc@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * @const rand_source *s@ = pointer to source definition + * + * Returns: --- + * + * Use: Sets a noise source for a randomness pool. When the pool's + * estimate of good random bits falls to zero, the @getnoise@ + * function is called, passing the pool handle as an argument. + * It is expected to increase the number of good bits by at + * least one, because it'll be called over and over again until + * there are enough bits to satisfy the caller. The @timer@ + * function is called frequently throughout the generator's + * operation. + */ + +void rand_noisesrc(rand_pool *r, const rand_source *s) +{ + RAND_RESOLVE(r); + r->s = s; +} + +/* --- @rand_seed@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * @unsigned bits@ = number of bits to ensure + * + * Returns: --- + * + * Use: Ensures that there are at least @bits@ good bits of entropy + * in the pool. It is recommended that you call this after + * initializing a new pool. Requesting @bits > RAND_IBITS@ is + * doomed to failure (and is an error). + */ + +void rand_seed(rand_pool *r, unsigned bits) +{ + RAND_RESOLVE(r); + + assert(((void)"bits pointlessly large in rand_seed", bits <= RAND_IBITS)); + assert(((void)"no noise source in rand_seed", r->s)); + + while (r->ibits < bits) + r->s->getnoise(r); + rand_gate(r); +} + +/* --- @rand_key@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * @const void *k@ = pointer to key data + * @size_t sz@ = size of key data + * + * Returns: --- + * + * Use: Sets the secret key for a randomness pool. The key is used + * when mixing in new random bits. + */ + +void rand_key(rand_pool *r, const void *k, size_t sz) +{ + RAND_RESOLVE(r); + rmd160_hmacinit(&r->k, k, sz); +} + +/* --- @rand_add@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * @const void *p@ = pointer a buffer of data to add + * @size_t sz@ = size of the data buffer + * @unsigned goodbits@ = number of good bits estimated in buffer + * + * Returns: --- + * + * Use: Mixes the data in the buffer with the contents of the + * pool. The estimate of the number of good bits is added to + * the pool's own count. The mixing operation is not + * cryptographically strong. However, data in the input pool + * isn't output directly, only through the one-way gating + * operation, so that shouldn't matter. + */ + +void rand_add(rand_pool *r, const void *p, size_t sz, unsigned goodbits) +{ + const octet *c = p; + int i, rot; + +#if RAND_POOLSZ != 128 +# error Polynomial in rand_add is out of date. Fix it. +#endif + + RAND_RESOLVE(r); + + i = r->i; rot = r->irot; + + while (sz) { + octet o = *c++; + r->pool[i] ^= (ROL8(o, rot) ^ + r->pool[(i + 1) % RAND_POOLSZ] ^ + r->pool[(i + 2) % RAND_POOLSZ] ^ + r->pool[(i + 7) % RAND_POOLSZ]); + rot = (rot + 5) & 7; + i++; if (i >= RAND_POOLSZ) i -= RAND_POOLSZ; + sz--; + } + + r->i = i; + r->irot = rot; + r->ibits += goodbits; + if (r->ibits > RAND_IBITS) + r->ibits = RAND_IBITS; +} + +/* --- @rand_goodbits@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * + * Returns: Estimate of the number of good bits remaining in the pool. + */ + +unsigned rand_goodbits(rand_pool *r) +{ + RAND_RESOLVE(r); + return (r->ibits + r->obits); +} + +/* --- @rand_gate@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * + * Returns: --- + * + * Use: Mixes up the entire state of the generator in a nonreversible + * way. + */ + +void rand_gate(rand_pool *r) +{ + octet mac[RMD160_HASHSZ]; + + RAND_RESOLVE(r); + TIMER(r); + + /* --- Hash up all the data in the pool --- */ + + { + rmd160_macctx mc; + + rmd160_macinit(&mc, &r->k); + rmd160_machash(&mc, r->pool, sizeof(r->pool)); + rmd160_machash(&mc, r->buf, sizeof(r->buf)); + rmd160_macdone(&mc, mac); + BURN(mc); + } + + /* --- Now mangle all of the data based on the hash --- */ + + { + blowfish_cbcctx bc; + + blowfish_cbcinit(&bc, mac, sizeof(mac), 0); + blowfish_cbcencrypt(&bc, r->pool, r->pool, sizeof(r->pool)); + blowfish_cbcencrypt(&bc, r->buf, r->buf, sizeof(r->buf)); + BURN(bc); + } + + /* --- Reset the various state variables --- */ + + r->o = RAND_SECSZ; + r->obits += r->ibits; + if (r->obits > RAND_OBITS) { + r->ibits = r->obits - r->ibits; + r->obits = RAND_OBITS; + } else + r->ibits = 0; + TIMER(r); +} + +/* --- @rand_stretch@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * + * Returns: --- + * + * Use: Stretches the contents of the output buffer by transforming + * it in a nonreversible way. This doesn't add any entropy + * worth speaking about, but it works well enough when the + * caller doesn't care about that sort of thing. + */ + +void rand_stretch(rand_pool *r) +{ + octet mac[RMD160_HASHSZ]; + + RAND_RESOLVE(r); + TIMER(r); + + /* --- Hash up all the data in the buffer --- */ + + { + rmd160_macctx mc; + + rmd160_macinit(&mc, &r->k); + rmd160_machash(&mc, r->pool, sizeof(r->pool)); + rmd160_machash(&mc, r->buf, sizeof(r->buf)); + rmd160_macdone(&mc, mac); + BURN(mc); + } + + /* --- Now mangle the buffer based on that hash --- */ + + { + blowfish_cbcctx bc; + + blowfish_cbcinit(&bc, mac, sizeof(mac), 0); + blowfish_cbcencrypt(&bc, r->buf, r->buf, sizeof(r->buf)); + BURN(bc); + } + + /* --- Reset the various state variables --- */ + + r->o = RAND_SECSZ; + TIMER(r); +} + +/* --- @rand_get@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * @void *p@ = pointer to output buffer + * @size_t sz@ = size of output buffer + * + * Returns: --- + * + * Use: Gets random data from the pool. The pool's contents can't be + * determined from the output of this function; nor can the + * output data be determined from a knowledge of the data input + * to the pool wihtout also having knowledge of the secret key. + * The good bits counter is decremented, although no special + * action is taken if it reaches zero. + */ + +void rand_get(rand_pool *r, void *p, size_t sz) +{ + octet *o = p; + + RAND_RESOLVE(r); + TIMER(r); + + if (!sz) + return; + for (;;) { + if (r->o + sz <= RAND_BUFSZ) { + memcpy(o, r->buf + r->o, sz); + r->o += sz; + break; + } else { + size_t chunk = RAND_BUFSZ - r->o; + if (chunk) { + memcpy(o, r->buf + r->o, chunk); + sz -= chunk; + o += chunk; + } + rand_stretch(r); + } + } + + if (r->obits > sz * 8) + r->obits -= sz * 8; + else + r->obits = 0; +} + +/* --- @rand_getgood@ --- * + * + * Arguments: @rand_pool *r@ = pointer to a randomness pool + * @void *p@ = pointer to output buffer + * @size_t sz@ = size of output buffer + * + * Returns: --- + * + * Use: Gets random data from the pool, ensuring that there are + * enough good bits. This interface isn't recommended: it makes + * the generator slow, and doesn't provide much more security + * than @rand_get@, assuming you've previously done a + * @rand_seed@. + */ + +void rand_getgood(rand_pool *r, void *p, size_t sz) +{ + octet *o = p; + + RAND_RESOLVE(r); + + if (!sz) + return; + if (!r->s || !r->s->getnoise) { + rand_get(r, p, sz); + return; + } + TIMER(r); + + while (sz) { + size_t chunk = sz; + + if (chunk * 8 > r->obits) { + if (chunk * 8 > r->ibits + r->obits) + do r->s->getnoise(r); while (r->ibits + r->obits < 256); + rand_gate(r); + if (chunk * 8 > r->obits) + chunk = r->obits / 8; + } + + if (chunk + r->o > RAND_BUFSZ) + chunk = RAND_BUFSZ - r->o; + + memcpy(o, r->buf + r->o, chunk); + r->o += chunk; + r->obits -= chunk * 8; + o += chunk; + sz -= chunk; + } +} + +/*----- Generic random number generator interface -------------------------*/ + +#define GRESOLVE(g, r) do { \ + if (r != &rand_global) \ + g = (gctx *)r; \ + else { \ + if (!pool) \ + pool = (gctx *)rand_create(); \ + g = pool; \ + } \ +} while (0) + +static void gdestroy(grand *r) +{ + gctx *g; + GRESOLVE(g, r); + if (g != pool) { + BURN(*g); + S_DESTROY(g); + } +} + +static int gmisc(grand *r, unsigned op, ...) +{ + gctx *g; + va_list ap; + int rc = 0; + va_start(ap, op); + + GRESOLVE(g, r); + switch (op) { + case GRAND_CHECK: + switch (va_arg(ap, unsigned)) { + case GRAND_CHECK: + case GRAND_SEEDINT: + case GRAND_SEEDUINT32: + case GRAND_SEEDBLOCK: + case GRAND_SEEDRAND: + case RAND_GATE: + case RAND_STRETCH: + case RAND_KEY: + case RAND_NOISESRC: + case RAND_SEED: + case RAND_TIMER: + case RAND_GOODBITS: + case RAND_ADD: + rc = 1; + break; + default: + rc = 0; + break; + } + break; + case GRAND_SEEDINT: { + unsigned u = va_arg(ap, unsigned); + rand_add(&g->p, &u, sizeof(u), sizeof(u)); + } break; + case GRAND_SEEDUINT32: { + uint32 i = va_arg(ap, uint32); + rand_add(&g->p, &i, sizeof(i), 4); + } break; + case GRAND_SEEDBLOCK: { + const void *p = va_arg(ap, const void *); + size_t sz = va_arg(ap, size_t); + rand_add(&g->p, p, sz, sz); + } break; + case GRAND_SEEDRAND: { + grand *rr = va_arg(ap, grand *); + octet buf[16]; + rr->ops->fill(rr, buf, sizeof(buf)); + rand_add(&g->p, buf, sizeof(buf), 8); + } break; + case RAND_GATE: + rand_gate(&g->p); + break; + case RAND_STRETCH: + rand_stretch(&g->p); + break; + case RAND_KEY: { + const void *k = va_arg(ap, const void *); + size_t sz = va_arg(ap, size_t); + rand_key(&g->p, k, sz); + } break; + case RAND_NOISESRC: + rand_noisesrc(&g->p, va_arg(ap, const rand_source *)); + break; + case RAND_SEED: + rand_seed(&g->p, va_arg(ap, unsigned)); + break; + case RAND_TIMER: + TIMER(&g->p); + break; + case RAND_GOODBITS: + rc = rand_goodbits(&g->p); + break; + case RAND_ADD: { + const void *p = va_arg(ap, const void *); + size_t sz = va_arg(ap, size_t); + unsigned goodbits = va_arg(ap, unsigned); + rand_add(&g->p, p, sz, goodbits); + } break; + default: + GRAND_BADOP; + break; + } + + va_end(ap); + return (rc); +} + +static octet gbyte(grand *r) +{ + gctx *g; + octet o; + GRESOLVE(g, r); + rand_getgood(&g->p, &o, 1); + return (o); +} + +static uint32 gword(grand *r) +{ + gctx *g; + octet b[4]; + GRESOLVE(g, r); + rand_getgood(&g->p, &b, sizeof(b)); + return (LOAD32(b)); +} + +static void gfill(grand *r, void *p, size_t sz) +{ + gctx *g; + GRESOLVE(g, r); + rand_get(&g->p, p, sz); +} + +static const grand_ops gops = { + "rand", + GRAND_CRYPTO, 0, + gmisc, gdestroy, + gword, gbyte, gword, grand_range, gfill +}; + +grand rand_global = { &gops }; + +/* --- @rand_create@ --- * + * + * Arguments: --- + * + * Returns: Pointer to a generic generator. + * + * Use: Constructs a generic generator interface over a Catacomb + * entropy pool generator. + */ + +grand *rand_create(void) +{ + gctx *g = S_CREATE(gctx); + g->r.ops = &gops; + rand_init(&g->p); + return (&g->r); +} + +/*----- That's all, folks -------------------------------------------------*/