From 1765c9718d22a4c049061d0882b5344eeb7937ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Love=20H=C3=B6rnquist=20=C3=85strand?= Date: Wed, 17 Jan 2007 00:24:33 +0000 Subject: [PATCH] Add fortuna based on Marko Kreen s pgcrypt, no enabled yet git-svn-id: svn://svn.h5l.se/heimdal/trunk/heimdal@19942 ec53bebd-3082-4978-b11e-865c3cabbd6b --- lib/des/ChangeLog | 8 + lib/des/Makefile.am | 5 +- lib/des/rand-fortuna.c | 546 +++++++++++++++++++++++++++++++++++++++++ lib/des/rand-unix.c | 16 +- lib/des/rand.c | 1 + lib/des/rand.h | 4 + lib/des/randi.h | 43 ++++ lib/des/test_rand.c | 127 ++++++++++ 8 files changed, 745 insertions(+), 5 deletions(-) create mode 100644 lib/des/rand-fortuna.c create mode 100644 lib/des/randi.h create mode 100644 lib/des/test_rand.c diff --git a/lib/des/ChangeLog b/lib/des/ChangeLog index 5dfbf3a9a..c57660a5c 100644 --- a/lib/des/ChangeLog +++ b/lib/des/ChangeLog @@ -1,5 +1,13 @@ +2007-01-17 Love Hörnquist Åstrand + + * fortuna: Add fortuna based on Marko Kreen's pgcrypt, no enabled yet + 2007-01-11 Love Hörnquist Åstrand + * test_rsa.c: if RAND is unhappy, don't run the tests. + + * test_engine_dso.c: if RAND is unhappy, don't run the tests. + * imath/imath.c: Update to imath-1.8 from Michael Fromberger Fixed a bug in s_udiv() affecting the computation of quotient diff --git a/lib/des/Makefile.am b/lib/des/Makefile.am index 26c62f194..4394423b1 100644 --- a/lib/des/Makefile.am +++ b/lib/des/Makefile.am @@ -60,7 +60,9 @@ PROGRAM_TESTS = \ SCRIPT_TESTS = \ test_crypto -check_PROGRAMS = $(PROGRAM_TESTS) test_rsa +noinst_PROGRAMS = test_rand + +check_PROGRAMS = $(PROGRAM_TESTS) test_rsa check_SCRIPTS = $(SCRIPT_TESTS) TESTS = $(PROGRAM_TESTS) $(SCRIPT_TESTS) @@ -98,6 +100,7 @@ libhcrypto_la_SOURCES = \ pkcs12.c \ rand.c \ rand.h \ + rand-fortuna.c \ rand-unix.c \ rc2.c \ rc2.h \ diff --git a/lib/des/rand-fortuna.c b/lib/des/rand-fortuna.c new file mode 100644 index 000000000..69da625d4 --- /dev/null +++ b/lib/des/rand-fortuna.c @@ -0,0 +1,546 @@ +/* + * fortuna.c + * Fortuna-like PRNG. + * + * Copyright (c) 2005 Marko Kreen + * 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. + * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $PostgreSQL: pgsql/contrib/pgcrypto/fortuna.c,v 1.8 2006/10/04 00:29:46 momjian Exp $ + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +RCSID("$Id$"); + +#include +#include +#include + +#include + +#include "randi.h" +#include "aes.h" +#include "sha.h" + +/* + * Why Fortuna-like: There does not seem to be any definitive reference + * on Fortuna in the net. Instead this implementation is based on + * following references: + * + * http://en.wikipedia.org/wiki/Fortuna_(PRNG) + * - Wikipedia article + * http://jlcooke.ca/random/ + * - Jean-Luc Cooke Fortuna-based /dev/random driver for Linux. + */ + +/* + * There is some confusion about whether and how to carry forward + * the state of the pools. Seems like original Fortuna does not + * do it, resetting hash after each request. I guess expecting + * feeding to happen more often that requesting. This is absolutely + * unsuitable for pgcrypto, as nothing asynchronous happens here. + * + * J.L. Cooke fixed this by feeding previous hash to new re-initialized + * hash context. + * + * Fortuna predecessor Yarrow requires ability to query intermediate + * 'final result' from hash, without affecting it. + * + * This implementation uses the Yarrow method - asking intermediate + * results, but continuing with old state. + */ + + +/* + * Algorithm parameters + */ + +#define NUM_POOLS 32 + +/* in microseconds */ +#define RESEED_INTERVAL 100000 /* 0.1 sec */ + +/* for one big request, reseed after this many bytes */ +#define RESEED_BYTES (1024*1024) + +/* + * Skip reseed if pool 0 has less than this many + * bytes added since last reseed. + */ +#define POOL0_FILL (256/8) + +/* + * Algorithm constants + */ + +/* Both cipher key size and hash result size */ +#define BLOCK 32 + +/* cipher block size */ +#define CIPH_BLOCK 16 + +/* for internal wrappers */ +#define MD_CTX SHA256_CTX +#define CIPH_CTX AES_KEY + +struct fortuna_state +{ + unsigned char counter[CIPH_BLOCK]; + unsigned char result[CIPH_BLOCK]; + unsigned char key[BLOCK]; + MD_CTX pool[NUM_POOLS]; + CIPH_CTX ciph; + unsigned reseed_count; + struct timeval last_reseed_time; + unsigned pool0_bytes; + unsigned rnd_pos; + int tricks_done; +}; +typedef struct fortuna_state FState; + + +/* + * Use our own wrappers here. + * - Need to get intermediate result from digest, without affecting it. + * - Need re-set key on a cipher context. + * - Algorithms are guaranteed to exist. + * - No memory allocations. + */ + +static void +ciph_init(CIPH_CTX * ctx, const unsigned char *key, int klen) +{ + AES_set_encrypt_key(key, klen * 8, ctx); +} + +static void +ciph_encrypt(CIPH_CTX * ctx, const unsigned char *in, unsigned char *out) +{ + AES_encrypt(in, out, ctx); +} + +static void +md_init(MD_CTX * ctx) +{ + SHA256_Init(ctx); +} + +static void +md_update(MD_CTX * ctx, const unsigned char *data, int len) +{ + SHA256_Update(ctx, data, len); +} + +static void +md_result(MD_CTX * ctx, unsigned char *dst) +{ + SHA256_CTX tmp; + + memcpy(&tmp, ctx, sizeof(*ctx)); + SHA256_Final(dst, &tmp); + memset(&tmp, 0, sizeof(tmp)); +} + +/* + * initialize state + */ +static void +init_state(FState * st) +{ + int i; + + memset(st, 0, sizeof(*st)); + for (i = 0; i < NUM_POOLS; i++) + md_init(&st->pool[i]); +} + +/* + * Endianess does not matter. + * It just needs to change without repeating. + */ +static void +inc_counter(FState * st) +{ + uint32_t *val = (uint32_t *) st->counter; + + if (++val[0]) + return; + if (++val[1]) + return; + if (++val[2]) + return; + ++val[3]; +} + +/* + * This is called 'cipher in counter mode'. + */ +static void +encrypt_counter(FState * st, unsigned char *dst) +{ + ciph_encrypt(&st->ciph, st->counter, dst); + inc_counter(st); +} + + +/* + * The time between reseed must be at least RESEED_INTERVAL + * microseconds. + */ +static int +enough_time_passed(FState * st) +{ + int ok; + struct timeval tv; + struct timeval *last = &st->last_reseed_time; + + gettimeofday(&tv, NULL); + + /* check how much time has passed */ + ok = 0; + if (tv.tv_sec > last->tv_sec + 1) + ok = 1; + else if (tv.tv_sec == last->tv_sec + 1) + { + if (1000000 + tv.tv_usec - last->tv_usec >= RESEED_INTERVAL) + ok = 1; + } + else if (tv.tv_usec - last->tv_usec >= RESEED_INTERVAL) + ok = 1; + + /* reseed will happen, update last_reseed_time */ + if (ok) + memcpy(last, &tv, sizeof(tv)); + + memset(&tv, 0, sizeof(tv)); + + return ok; +} + +/* + * generate new key from all the pools + */ +static void +reseed(FState * st) +{ + unsigned k; + unsigned n; + MD_CTX key_md; + unsigned char buf[BLOCK]; + + /* set pool as empty */ + st->pool0_bytes = 0; + + /* + * Both #0 and #1 reseed would use only pool 0. Just skip #0 then. + */ + n = ++st->reseed_count; + + /* + * The goal: use k-th pool only 1/(2^k) of the time. + */ + md_init(&key_md); + for (k = 0; k < NUM_POOLS; k++) + { + md_result(&st->pool[k], buf); + md_update(&key_md, buf, BLOCK); + + if (n & 1 || !n) + break; + n >>= 1; + } + + /* add old key into mix too */ + md_update(&key_md, st->key, BLOCK); + + /* now we have new key */ + md_result(&key_md, st->key); + + /* use new key */ + ciph_init(&st->ciph, st->key, BLOCK); + + memset(&key_md, 0, sizeof(key_md)); + memset(buf, 0, BLOCK); +} + +/* + * Pick a random pool. This uses key bytes as random source. + */ +static unsigned +get_rand_pool(FState * st) +{ + unsigned rnd; + + /* + * This slightly prefers lower pools - thats OK. + */ + rnd = st->key[st->rnd_pos] % NUM_POOLS; + + st->rnd_pos++; + if (st->rnd_pos >= BLOCK) + st->rnd_pos = 0; + + return rnd; +} + +/* + * update pools + */ +static void +add_entropy(FState * st, const unsigned char *data, unsigned len) +{ + unsigned pos; + unsigned char hash[BLOCK]; + MD_CTX md; + + /* hash given data */ + md_init(&md); + md_update(&md, data, len); + md_result(&md, hash); + + /* + * Make sure the pool 0 is initialized, then update randomly. + */ + if (st->reseed_count == 0) + pos = 0; + else + pos = get_rand_pool(st); + md_update(&st->pool[pos], hash, BLOCK); + + if (pos == 0) + st->pool0_bytes += len; + + memset(hash, 0, BLOCK); + memset(&md, 0, sizeof(md)); +} + +/* + * Just take 2 next blocks as new key + */ +static void +rekey(FState * st) +{ + encrypt_counter(st, st->key); + encrypt_counter(st, st->key + CIPH_BLOCK); + ciph_init(&st->ciph, st->key, BLOCK); +} + +/* + * Hide public constants. (counter, pools > 0) + * + * This can also be viewed as spreading the startup + * entropy over all of the components. + */ +static void +startup_tricks(FState * st) +{ + int i; + unsigned char buf[BLOCK]; + + /* Use next block as counter. */ + encrypt_counter(st, st->counter); + + /* Now shuffle pools, excluding #0 */ + for (i = 1; i < NUM_POOLS; i++) + { + encrypt_counter(st, buf); + encrypt_counter(st, buf + CIPH_BLOCK); + md_update(&st->pool[i], buf, BLOCK); + } + memset(buf, 0, BLOCK); + + /* Hide the key. */ + rekey(st); + + /* This can be done only once. */ + st->tricks_done = 1; +} + +static void +extract_data(FState * st, unsigned count, unsigned char *dst) +{ + unsigned n; + unsigned block_nr = 0; + + /* Should we reseed? */ + if (st->pool0_bytes >= POOL0_FILL || st->reseed_count == 0) + if (enough_time_passed(st)) + reseed(st); + + /* Do some randomization on first call */ + if (!st->tricks_done) + startup_tricks(st); + + while (count > 0) + { + /* produce bytes */ + encrypt_counter(st, st->result); + + /* copy result */ + if (count > CIPH_BLOCK) + n = CIPH_BLOCK; + else + n = count; + memcpy(dst, st->result, n); + dst += n; + count -= n; + + /* must not give out too many bytes with one key */ + block_nr++; + if (block_nr > (RESEED_BYTES / CIPH_BLOCK)) + { + rekey(st); + block_nr = 0; + } + } + /* Set new key for next request. */ + rekey(st); +} + +/* + * public interface + */ + +static FState main_state; +static int init_done; +static int have_entropy; + +/* + * Try our best to do an inital seed + */ +#define INIT_BYTES 128 + +static int +fortuna_reseed(void) +{ + if (!init_done) + abort(); + + { + unsigned char buf[INIT_BYTES]; + if (_hc_rand_unix_bytes(buf, sizeof(buf)) == 1) { + add_entropy(&main_state, buf, sizeof(buf)); + memset(buf, 0, sizeof(buf)); + } + } +#ifdef HAVE_ARC4RANDOM + { + uint32_t buf[INIT_BYTES / sizeof(uint32_t)]; + int i; + + for (i = 0; i < sizeof(buf)/sizeof(buf[0]); i++) + buf[i] = arc4random(); + add_entropy(&main_state, (void *)buf, sizeof(buf)); + } +#endif + /* Add EGD thingy here */ + { + pid_t pid = getpid(); + add_entropy(&main_state, (void *)&pid, sizeof(pid)); + } + { + struct timeval tv; + gettimeofday(&tv, NULL); + add_entropy(&main_state, (void *)&tv, sizeof(tv)); + } + { + uid_t u = getuid(); + add_entropy(&main_state, (void *)&u, sizeof(u)); + } + return 1; +} + +static int +fortuna_init(void) +{ + if (!init_done) + { + init_state(&main_state); + init_done = 1; + } + if (!have_entropy) + have_entropy = fortuna_reseed(); + return (init_done && have_entropy); +} + + + +static void +fortuna_seed(const void *indata, int size) +{ + fortuna_init(); + add_entropy(&main_state, indata, size); +} + +static int +fortuna_bytes(unsigned char *outdata, int size) +{ + if (!fortuna_init()) + return 0; + extract_data(&main_state, size, outdata); + return 1; +} + +static void +fortuna_cleanup(void) +{ + init_done = 0; + have_entropy = 0; + memset(&main_state, 0, sizeof(main_state)); +} + +static void +fortuna_add(const void *indata, int size, double entropi) +{ + fortuna_seed(indata, size); +} + +static int +fortuna_pseudorand(unsigned char *outdata, int size) +{ + return fortuna_bytes(outdata, size); +} + +static int +fortuna_status(void) +{ + return fortuna_init() ? 1 : 0; +} + +const RAND_METHOD hc_rand_fortuna_method = { + fortuna_seed, + fortuna_bytes, + fortuna_cleanup, + fortuna_add, + fortuna_pseudorand, + fortuna_status +}; + +const RAND_METHOD * +RAND_fortuna_method(void) +{ + return &hc_rand_fortuna_method; +} diff --git a/lib/des/rand-unix.c b/lib/des/rand-unix.c index 2f9aef963..df30d04cb 100644 --- a/lib/des/rand-unix.c +++ b/lib/des/rand-unix.c @@ -43,6 +43,8 @@ RCSID("$Id$"); #include +#include "randi.h" + /* * Unix /dev/random */ @@ -84,8 +86,8 @@ unix_seed(const void *indata, int size) } -static int -unix_bytes(unsigned char *outdata, int size) +int +_hc_rand_unix_bytes(unsigned char *outdata, int size) { ssize_t count; int fd; @@ -127,7 +129,7 @@ unix_add(const void *indata, int size, double entropi) static int unix_pseudorand(unsigned char *outdata, int size) { - return unix_bytes(outdata, size); + return _hc_rand_unix_bytes(outdata, size); } static int @@ -145,9 +147,15 @@ unix_status(void) const RAND_METHOD hc_rand_unix_method = { unix_seed, - unix_bytes, + _hc_rand_unix_bytes, unix_cleanup, unix_add, unix_pseudorand, unix_status }; + +const RAND_METHOD * +RAND_unix_method(void) +{ + return &hc_rand_unix_method; +} diff --git a/lib/des/rand.c b/lib/des/rand.c index a45ec1277..9a1281f2b 100644 --- a/lib/des/rand.c +++ b/lib/des/rand.c @@ -43,6 +43,7 @@ RCSID("$Id$"); #include +extern RAND_METHOD hc_rand_fortuna_method; extern RAND_METHOD hc_rand_unix_method; static const RAND_METHOD *selected_meth = &hc_rand_unix_method; diff --git a/lib/des/rand.h b/lib/des/rand.h index 3c90c6f7a..97de9eb38 100644 --- a/lib/des/rand.h +++ b/lib/des/rand.h @@ -57,6 +57,7 @@ typedef struct RAND_METHOD RAND_METHOD; #define RAND_write_file hc_RAND_write_file #define RAND_status hc_RAND_status #define RAND_egd hc_RAND_egd +#define RAND_fortuna_method hc_RAND_fortuna_method /* * @@ -93,4 +94,7 @@ int RAND_status(void); int RAND_egd(const char *); +const RAND_METHOD * RAND_fortuna_method(void); +const RAND_METHOD * RAND_unix_method(void); + #endif /* _HEIM_RAND_H */ diff --git a/lib/des/randi.h b/lib/des/randi.h new file mode 100644 index 000000000..abed684eb --- /dev/null +++ b/lib/des/randi.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * 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. + * + * 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. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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. + */ + +/* + * $Id$ + */ + +#ifndef _HEIM_RANDI_H +#define _HEIM_RANDI_H 1 + +int _hc_rand_unix_bytes(unsigned char *, int); + +#endif /* _HEIM_RANDI_H */ diff --git a/lib/des/test_rand.c b/lib/des/test_rand.c new file mode 100644 index 000000000..4dc0fed7f --- /dev/null +++ b/lib/des/test_rand.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2007 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * 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. + * + * 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. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef RCSID +RCSID("$Id$"); +#endif + +#include + +#include +#include + +#include "rand.h" + + +/* + * + */ + +static int version_flag; +static int help_flag; +static int len = 1024 * 1024; + +static struct getargs args[] = { + { "length", 0, arg_integer, &len, + "length", NULL }, + { "version", 0, arg_flag, &version_flag, + "print version", NULL }, + { "help", 0, arg_flag, &help_flag, + NULL, NULL } +}; + +/* + * + */ + +/* + * + */ + +static void +usage (int ret) +{ + arg_printusage (args, + sizeof(args)/sizeof(*args), + NULL, + "out-random-file"); + exit (ret); +} + +int +main(int argc, char **argv) +{ + int idx = 0; + char *buffer; + + setprogname(argv[0]); + + if(getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &idx)) + usage(1); + + if (help_flag) + usage(0); + + if(version_flag){ + print_version(NULL); + exit(0); + } + + argc -= idx; + argv += idx; + + if (argc < 1) + usage(1); + + buffer = emalloc(len); + + RAND_set_rand_method(RAND_fortuna_method()); + + RAND_seed(buffer, len); + + if (RAND_status() != 1) + errx(1, "random not ready yet"); + + if (RAND_bytes(buffer, len) != 1) + errx(1, "RAND_bytes"); + + rk_dumpdata(argv[0], buffer, len); + + free(buffer); + + return 0; +}