From 35c675a7fda01e06be25738fc6131ed1d9d72c70 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 18 Jul 2005 17:12:54 +0000 Subject: [PATCH] Fortuna fixes. Marko Kreen --- contrib/pgcrypto/fortuna.c | 161 +++++++++++++++++++++++------------ contrib/pgcrypto/fortuna.h | 10 +-- contrib/pgcrypto/internal.c | 45 ++++++++-- contrib/pgcrypto/pgp-pgsql.c | 59 +++++++++---- contrib/pgcrypto/pgp-s2k.c | 8 +- 5 files changed, 194 insertions(+), 89 deletions(-) diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c index b02618430e..4645076109 100644 --- a/contrib/pgcrypto/fortuna.c +++ b/contrib/pgcrypto/fortuna.c @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $PostgreSQL: pgsql/contrib/pgcrypto/fortuna.c,v 1.3 2005/07/18 17:09:01 tgl Exp $ + * $PostgreSQL: pgsql/contrib/pgcrypto/fortuna.c,v 1.4 2005/07/18 17:12:54 tgl Exp $ */ #include "postgres.h" @@ -94,14 +94,16 @@ /* 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 */ -/* max sources */ -#define MAX_SOURCES 8 - /* Both cipher key size and hash result size */ #define BLOCK 32 @@ -118,9 +120,11 @@ struct fortuna_state { uint8 key[BLOCK]; MD_CTX pool[NUM_POOLS]; CIPH_CTX ciph; - unsigned source_pos[MAX_SOURCES]; unsigned reseed_count; struct timeval last_reseed_time; + unsigned pool0_bytes; + unsigned rnd_pos; + int counter_init; }; typedef struct fortuna_state FState; @@ -161,7 +165,6 @@ static void md_result(MD_CTX *ctx, uint8 *dst) memset(&tmp, 0, sizeof(tmp)); } - /* * initialize state */ @@ -173,6 +176,32 @@ static void init_state(FState *st) md_init(&st->pool[i]); } +/* + * Endianess does not matter. + * It just needs to change without repeating. + */ +static void inc_counter(FState *st) +{ + uint32 *val = (uint32*)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, uint8 *dst) +{ + ciph_encrypt(&st->ciph, st->counter, dst); + inc_counter(st); +} + + /* * The time between reseed must be at least RESEED_INTERVAL * microseconds. @@ -207,9 +236,8 @@ static void reseed(FState *st) MD_CTX key_md; uint8 buf[BLOCK]; - /* check frequency */ - if (too_often(st)) - return; + /* set pool as empty */ + st->pool0_bytes = 0; /* * Both #0 and #1 reseed would use only pool 0. @@ -243,50 +271,81 @@ static void reseed(FState *st) 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, unsigned src_id, const uint8 *data, unsigned len) +static void add_entropy(FState *st, const uint8 *data, unsigned len) { unsigned pos; uint8 hash[BLOCK]; MD_CTX md; - /* just in case there's a bug somewhere */ - if (src_id >= MAX_SOURCES) - src_id = USER_ENTROPY; - /* hash given data */ md_init(&md); md_update(&md, data, len); md_result(&md, hash); - /* update pools round-robin manner */ - pos = st->source_pos[src_id]; + /* + * Make sure the pool 0 is initialized, + * then update randomly. + */ + if (st->reseed_count == 0 && st->pool0_bytes < POOL0_FILL) + pos = 0; + else + pos = get_rand_pool(st); md_update( &st->pool[pos], hash, BLOCK); - if (++pos >= NUM_POOLS) - pos = 0; - st->source_pos[src_id] = pos; + if (pos == 0) + st->pool0_bytes += len; memset(hash, 0, BLOCK); memset(&md, 0, sizeof(md)); } /* - * Endianess does not matter. - * It just needs to change without repeating. + * Just take 2 next blocks as new key */ -static void inc_counter(FState *st) +static void rekey(FState *st) { - uint32 *val = (uint32*)st->counter; - if (++val[0]) - return; - if (++val[1]) - return; - if (++val[2]) - return; - ++val[3]; + encrypt_counter(st, st->key); + encrypt_counter(st, st->key + CIPH_BLOCK); + ciph_init(&st->ciph, st->key, BLOCK); +} + +/* + * Fortuna relies on AES standing known-plaintext attack. + * In case it does not, slow down the attacker by initialising + * the couter to random value. + */ +static void init_counter(FState *st) +{ + /* Use next block as counter. */ + encrypt_counter(st, st->counter); + + /* Hide the key. */ + rekey(st); + + /* The counter can be shuffled only once. */ + st->counter_init = 1; } static void extract_data(FState *st, unsigned count, uint8 *dst) @@ -294,31 +353,17 @@ static void extract_data(FState *st, unsigned count, uint8 *dst) unsigned n; unsigned block_nr = 0; - /* - * Every request should be with different key, - * if possible. - */ - reseed(st); + /* Can we reseed? */ + if (st->pool0_bytes >= POOL0_FILL && !too_often(st)) + reseed(st); - /* - * If the reseed didn't happen, don't use the old data - * rather encrypt again. - */ + /* Is counter initialized? */ + if (!st->counter_init) + init_counter(st); while (count > 0) { - /* must not give out too many bytes with one key */ - if (block_nr > (RESEED_BYTES / CIPH_BLOCK)) - { - reseed(st); - block_nr = 0; - } - /* produce bytes */ - ciph_encrypt(&st->ciph, st->counter, st->result); - block_nr++; - - /* prepare for next time */ - inc_counter(st); + encrypt_counter(st, st->result); /* copy result */ if (count > CIPH_BLOCK) @@ -328,7 +373,17 @@ static void extract_data(FState *st, unsigned count, uint8 *dst) 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); } /* @@ -338,7 +393,7 @@ static void extract_data(FState *st, unsigned count, uint8 *dst) static FState main_state; static int init_done = 0; -void fortuna_add_entropy(unsigned src_id, const uint8 *data, unsigned len) +void fortuna_add_entropy(const uint8 *data, unsigned len) { if (!init_done) { @@ -347,7 +402,7 @@ void fortuna_add_entropy(unsigned src_id, const uint8 *data, unsigned len) } if (!data || !len) return; - add_entropy(&main_state, src_id, data, len); + add_entropy(&main_state, data, len); } void fortuna_get_bytes(unsigned len, uint8 *dst) diff --git a/contrib/pgcrypto/fortuna.h b/contrib/pgcrypto/fortuna.h index b576eb981d..12e0c56832 100644 --- a/contrib/pgcrypto/fortuna.h +++ b/contrib/pgcrypto/fortuna.h @@ -26,20 +26,14 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $PostgreSQL: pgsql/contrib/pgcrypto/fortuna.h,v 1.1 2005/07/10 13:46:28 momjian Exp $ + * $PostgreSQL: pgsql/contrib/pgcrypto/fortuna.h,v 1.2 2005/07/18 17:12:54 tgl Exp $ */ #ifndef __FORTUNA_H #define __FORTUNA_H -/* - * Event source ID's - */ -#define SYSTEM_ENTROPY 0 -#define USER_ENTROPY 1 - void fortuna_get_bytes(unsigned len, uint8 *dst); -void fortuna_add_entropy(unsigned src_id, const uint8 *data, unsigned len); +void fortuna_add_entropy(const uint8 *data, unsigned len); #endif diff --git a/contrib/pgcrypto/internal.c b/contrib/pgcrypto/internal.c index 93085dbf6d..bfe4eeb2b7 100644 --- a/contrib/pgcrypto/internal.c +++ b/contrib/pgcrypto/internal.c @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $PostgreSQL: pgsql/contrib/pgcrypto/internal.c,v 1.21 2005/07/18 17:09:01 tgl Exp $ + * $PostgreSQL: pgsql/contrib/pgcrypto/internal.c,v 1.22 2005/07/18 17:12:54 tgl Exp $ */ #include "postgres.h" @@ -42,9 +42,22 @@ #include "fortuna.h" /* - * How often to try to acquire system entropy. (In seconds) + * System reseeds should be separated at least this much. */ -#define SYSTEM_RESEED_FREQ (3*60*60) +#define SYSTEM_RESEED_MIN (20*60) /* 20 min */ +/* + * How often to roll dice. + */ +#define SYSTEM_RESEED_CHECK_TIME (10*60) /* 10 min */ +/* + * The chance is x/256 that the reseed happens. + */ +#define SYSTEM_RESEED_CHANCE (4) /* 256/4 * 10min ~ 10h */ + +/* + * If this much time has passed, force reseed. + */ +#define SYSTEM_RESEED_MAX (12*60*60) /* 12h */ #ifndef MD5_DIGEST_LENGTH @@ -823,20 +836,40 @@ px_get_pseudo_random_bytes(uint8 *dst, unsigned count) } static time_t seed_time = 0; +static time_t check_time = 0; static void system_reseed(void) { uint8 buf[1024]; int n; time_t t; + int skip = 1; t = time(NULL); - if (seed_time && (t - seed_time) < SYSTEM_RESEED_FREQ) + + if (seed_time == 0) + skip = 0; + else if ((t - seed_time) < SYSTEM_RESEED_MIN) + skip = 1; + else if ((t - seed_time) > SYSTEM_RESEED_MAX) + skip = 0; + else if (!check_time || (t - check_time) > SYSTEM_RESEED_CHECK_TIME) + { + check_time = t; + + /* roll dice */ + px_get_random_bytes(buf, 1); + skip = buf[0] >= SYSTEM_RESEED_CHANCE; + } + /* clear 1 byte */ + memset(buf, 0, sizeof(buf)); + + if (skip) return; n = px_acquire_system_randomness(buf); if (n > 0) - fortuna_add_entropy(SYSTEM_ENTROPY, buf, n); + fortuna_add_entropy(buf, n); seed_time = t; memset(buf, 0, sizeof(buf)); @@ -854,7 +887,7 @@ int px_add_entropy(const uint8 *data, unsigned count) { system_reseed(); - fortuna_add_entropy(USER_ENTROPY, data, count); + fortuna_add_entropy(data, count); return 0; } diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c index 5d4c0518f4..b64027b73e 100644 --- a/contrib/pgcrypto/pgp-pgsql.c +++ b/contrib/pgcrypto/pgp-pgsql.c @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $PostgreSQL: pgsql/contrib/pgcrypto/pgp-pgsql.c,v 1.2 2005/07/11 15:07:59 tgl Exp $ + * $PostgreSQL: pgsql/contrib/pgcrypto/pgp-pgsql.c,v 1.3 2005/07/18 17:12:54 tgl Exp $ */ #include "postgres.h" @@ -86,6 +86,22 @@ PG_FUNCTION_INFO_V1(pg_dearmor); } \ } while (0) +/* + * Mix a block of data into RNG. + */ +static void add_block_entropy(PX_MD *md, text *data) +{ + uint8 sha1[20]; + + px_md_reset(md); + px_md_update(md, VARDATA(data), VARSIZE(data) - VARHDRSZ); + px_md_finish(md, sha1); + + px_add_entropy(sha1, 20); + + memset(sha1, 0, 20); +} + /* * Mix user data into RNG. It is for user own interests to have * RNG state shuffled. @@ -93,31 +109,38 @@ PG_FUNCTION_INFO_V1(pg_dearmor); static void add_entropy(text *data1, text *data2, text *data3) { PX_MD *md; - uint8 sha1[20]; - int res; + uint8 rnd[3]; if (!data1 && !data2 && !data3) return; - res = px_find_digest("sha1", &md); - if (res < 0) + if (px_get_random_bytes(rnd, 3) < 0) return; - if (data1) - px_md_update(md, VARDATA(data1), VARSIZE(data1) - VARHDRSZ); - if (data2) - px_md_update(md, VARDATA(data2), VARSIZE(data2) - VARHDRSZ); - if (data3) - px_md_update(md, VARDATA(data3), VARSIZE(data3) - VARHDRSZ); + if (px_find_digest("sha1", &md) < 0) + return; + + /* + * Try to make the feeding unpredictable. + * + * Prefer data over keys, as it's rather likely + * that key is same in several calls. + */ + + /* chance: 7/8 */ + if (data1 && rnd[0] >= 32) + add_block_entropy(md, data1); + + /* chance: 5/8 */ + if (data2 && rnd[1] >= 160) + add_block_entropy(md, data2); + + /* chance: 5/8 */ + if (data3 && rnd[2] >= 160) + add_block_entropy(md, data3); - px_md_finish(md, sha1); px_md_free(md); - - res = px_add_entropy(sha1, 20); - memset(sha1, 0, 20); - - if (res < 0) - ereport(NOTICE, (errmsg("add_entropy: %s", px_strerror(res)))); + memset(rnd, 0, sizeof(rnd)); } /* diff --git a/contrib/pgcrypto/pgp-s2k.c b/contrib/pgcrypto/pgp-s2k.c index 2d1b92c2d4..cbde42a13b 100644 --- a/contrib/pgcrypto/pgp-s2k.c +++ b/contrib/pgcrypto/pgp-s2k.c @@ -26,7 +26,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $PostgreSQL: pgsql/contrib/pgcrypto/pgp-s2k.c,v 1.2 2005/07/11 15:07:59 tgl Exp $ + * $PostgreSQL: pgsql/contrib/pgcrypto/pgp-s2k.c,v 1.3 2005/07/18 17:12:54 tgl Exp $ */ #include "postgres.h" @@ -225,13 +225,13 @@ pgp_s2k_fill(PGP_S2K *s2k, int mode,int digest_algo) case 0: break; case 1: - res = px_get_random_bytes(s2k->salt, PGP_S2K_SALT); + res = px_get_pseudo_random_bytes(s2k->salt, PGP_S2K_SALT); break; case 3: - res = px_get_random_bytes(s2k->salt, PGP_S2K_SALT); + res = px_get_pseudo_random_bytes(s2k->salt, PGP_S2K_SALT); if (res < 0) break; - res = px_get_random_bytes(&tmp, 1); + res = px_get_pseudo_random_bytes(&tmp, 1); if (res < 0) break; s2k->iter = decide_count(tmp);