From 35c675a7fda01e06be25738fc6131ed1d9d72c70 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
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 |  57 +++++++++----
 contrib/pgcrypto/pgp-s2k.c   |   8 +-
 5 files changed, 193 insertions(+), 88 deletions(-)

diff --git a/contrib/pgcrypto/fortuna.c b/contrib/pgcrypto/fortuna.c
index b02618430e2..4645076109f 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 b576eb981d1..12e0c56832d 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 93085dbf6d3..bfe4eeb2b74 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 5d4c0518f4c..b64027b73e8 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;
 
-	px_md_finish(md, sha1);
-	px_md_free(md);
+	/*
+	 * Try to make the feeding unpredictable.
+	 * 
+	 * Prefer data over keys, as it's rather likely
+	 * that key is same in several calls.
+	 */
 
-	res = px_add_entropy(sha1, 20);
-	memset(sha1, 0, 20);
+	/* 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);
 
-	if (res < 0)
-		ereport(NOTICE, (errmsg("add_entropy: %s", px_strerror(res))));
+	/* chance: 5/8 */
+	if (data3 && rnd[2] >= 160)
+		add_block_entropy(md, data3);
+
+	px_md_free(md);
+	memset(rnd, 0, sizeof(rnd));
 }
 
 /*
diff --git a/contrib/pgcrypto/pgp-s2k.c b/contrib/pgcrypto/pgp-s2k.c
index 2d1b92c2d44..cbde42a13b5 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);
-- 
GitLab