Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/prng.c')
-rw-r--r--crypto/prng.c287
1 files changed, 287 insertions, 0 deletions
diff --git a/crypto/prng.c b/crypto/prng.c
new file mode 100644
index 00000000..247d1dcf
--- /dev/null
+++ b/crypto/prng.c
@@ -0,0 +1,287 @@
+/*
+ * PuTTY's cryptographic pseudorandom number generator.
+ *
+ * This module just defines the PRNG object type and its methods. The
+ * usual global instance of it is managed by sshrand.c.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+#include "mpint_i.h"
+
+#ifdef PRNG_DIAGNOSTICS
+#define prngdebug debug
+#else
+#define prngdebug(...) ((void)0)
+#endif
+
+/*
+ * This random number generator is based on the 'Fortuna' design by
+ * Niels Ferguson and Bruce Schneier. The biggest difference is that I
+ * use SHA-256 in place of a block cipher: the generator side of the
+ * system works by computing HASH(key || counter) instead of
+ * ENCRYPT(counter, key).
+ *
+ * Rationale: the Fortuna description itself suggests that using
+ * SHA-256 would be nice but people wouldn't accept it because it's
+ * too slow - but PuTTY isn't a heavy enough user of random numbers to
+ * make that a serious worry. In fact even with SHA-256 this generator
+ * is faster than the one we previously used. Also the Fortuna
+ * description worries about periodic rekeying to avoid the barely
+ * detectable pattern of never repeating a cipher block - but with
+ * SHA-256, even that shouldn't be a worry, because the output
+ * 'blocks' are twice the size, and also SHA-256 has no guarantee of
+ * bijectivity, so it surely _could_ be possible to generate the same
+ * block from two counter values. Thirdly, Fortuna has to have a hash
+ * function anyway, for reseeding and entropy collection, so reusing
+ * the same one means it only depends on one underlying primitive and
+ * can be easily reinstantiated with a larger hash function if you
+ * decide you'd like to do that on a particular occasion.
+ */
+
+#define NCOLLECTORS 32
+#define RESEED_DATA_SIZE 64
+
+typedef struct prng_impl prng_impl;
+struct prng_impl {
+ prng Prng;
+
+ const ssh_hashalg *hashalg;
+
+ /*
+ * Generation side:
+ *
+ * 'generator' is a hash object with the current key preloaded
+ * into it. The counter-mode generation is achieved by copying
+ * that hash object, appending the counter value to the copy, and
+ * calling ssh_hash_final.
+ */
+ ssh_hash *generator;
+ BignumInt counter[128 / BIGNUM_INT_BITS];
+
+ /*
+ * When re-seeding the generator, you call prng_seed_begin(),
+ * which sets up a hash object in 'keymaker'. You write your new
+ * seed data into it (which you can do by calling put_data on the
+ * PRNG object itself) and then call prng_seed_finish(), which
+ * finalises this hash and uses the output to set up the new
+ * generator.
+ *
+ * The keymaker hash preimage includes the previous key, so if you
+ * just want to change keys for the sake of not keeping the same
+ * one for too long, you don't have to put any extra seed data in
+ * at all.
+ */
+ ssh_hash *keymaker;
+
+ /*
+ * Collection side:
+ *
+ * There are NCOLLECTORS hash objects collecting entropy. Each
+ * separately numbered entropy source puts its output into those
+ * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,...,
+ * that is to say, each entropy source has a separate counter
+ * which is incremented every time that source generates an event,
+ * and the event data is added to the collector corresponding to
+ * the index of the lowest set bit in the current counter value.
+ *
+ * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and
+ * it's not at least 100ms since the last reseed), the PRNG is
+ * reseeded, with seed data on reseed #n taken from the first j
+ * collectors, where j is one more than the number of factors of 2
+ * in n. That is, collector #0 is used in every reseed; #1 in
+ * every other one, #2 in every fourth, etc.
+ *
+ * 'until_reseed' counts the amount of data that still needs to be
+ * added to collector #0 before a reseed will be triggered.
+ */
+ uint32_t source_counters[NOISE_MAX_SOURCES];
+ ssh_hash *collectors[NCOLLECTORS];
+ size_t until_reseed;
+ uint32_t reseeds;
+ uint64_t last_reseed_time;
+};
+
+static void prng_seed_BinarySink_write(
+ BinarySink *bs, const void *data, size_t len);
+
+prng *prng_new(const ssh_hashalg *hashalg)
+{
+ prng_impl *pi = snew(prng_impl);
+
+ memset(pi, 0, sizeof(prng_impl));
+ pi->hashalg = hashalg;
+ pi->keymaker = NULL;
+ pi->generator = NULL;
+ memset(pi->counter, 0, sizeof(pi->counter));
+ for (size_t i = 0; i < NCOLLECTORS; i++)
+ pi->collectors[i] = ssh_hash_new(pi->hashalg);
+ pi->until_reseed = 0;
+ BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write);
+
+ pi->Prng.savesize = pi->hashalg->hlen * 4;
+
+ return &pi->Prng;
+}
+
+void prng_free(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+ smemclr(pi->counter, sizeof(pi->counter));
+ for (size_t i = 0; i < NCOLLECTORS; i++)
+ ssh_hash_free(pi->collectors[i]);
+ if (pi->generator)
+ ssh_hash_free(pi->generator);
+ if (pi->keymaker)
+ ssh_hash_free(pi->keymaker);
+ smemclr(pi, sizeof(*pi));
+ sfree(pi);
+}
+
+void prng_seed_begin(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+ assert(!pi->keymaker);
+
+ prngdebug("prng: reseed begin\n");
+
+ /*
+ * Make a hash instance that will generate the key for the new one.
+ */
+ if (pi->generator) {
+ pi->keymaker = pi->generator;
+ pi->generator = NULL;
+ } else {
+ pi->keymaker = ssh_hash_new(pi->hashalg);
+ }
+
+ put_byte(pi->keymaker, 'R');
+}
+
+static void prng_seed_BinarySink_write(
+ BinarySink *bs, const void *data, size_t len)
+{
+ prng *pr = BinarySink_DOWNCAST(bs, prng);
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ assert(pi->keymaker);
+ prngdebug("prng: got %"SIZEu" bytes of seed\n", len);
+ put_data(pi->keymaker, data, len);
+}
+
+void prng_seed_finish(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ unsigned char buf[MAX_HASH_LEN];
+
+ assert(pi->keymaker);
+
+ prngdebug("prng: reseed finish\n");
+
+ /*
+ * Actually generate the key.
+ */
+ ssh_hash_final(pi->keymaker, buf);
+ pi->keymaker = NULL;
+
+ /*
+ * Load that key into a fresh hash instance, which will become the
+ * new generator.
+ */
+ assert(!pi->generator);
+ pi->generator = ssh_hash_new(pi->hashalg);
+ put_data(pi->generator, buf, pi->hashalg->hlen);
+
+ pi->until_reseed = RESEED_DATA_SIZE;
+ pi->last_reseed_time = prng_reseed_time_ms();
+
+ smemclr(buf, sizeof(buf));
+}
+
+static inline void prng_generate(prng_impl *pi, void *outbuf)
+{
+ ssh_hash *h = ssh_hash_copy(pi->generator);
+
+ prngdebug("prng_generate\n");
+ put_byte(h, 'G');
+ for (unsigned i = 0; i < 128; i += 8)
+ put_byte(h, pi->counter[i/BIGNUM_INT_BITS] >> (i%BIGNUM_INT_BITS));
+ BignumCarry c = 1;
+ for (unsigned i = 0; i < lenof(pi->counter); i++)
+ BignumADC(pi->counter[i], c, pi->counter[i], 0, c);
+ ssh_hash_final(h, outbuf);
+}
+
+void prng_read(prng *pr, void *vout, size_t size)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ unsigned char buf[MAX_HASH_LEN];
+
+ assert(!pi->keymaker);
+
+ prngdebug("prng_read %"SIZEu"\n", size);
+
+ uint8_t *out = (uint8_t *)vout;
+ while (size > 0) {
+ prng_generate(pi, buf);
+ size_t to_use = size > pi->hashalg->hlen ? pi->hashalg->hlen : size;
+ memcpy(out, buf, to_use);
+ out += to_use;
+ size -= to_use;
+ }
+
+ smemclr(buf, sizeof(buf));
+
+ prng_seed_begin(&pi->Prng);
+ prng_seed_finish(&pi->Prng);
+}
+
+void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+ assert(source_id < NOISE_MAX_SOURCES);
+ uint32_t counter = ++pi->source_counters[source_id];
+
+ size_t index = 0;
+ while (index+1 < NCOLLECTORS && !(counter & 1)) {
+ counter >>= 1;
+ index++;
+ }
+
+ prngdebug("prng_add_entropy source=%u size=%"SIZEu" -> collector %zi\n",
+ source_id, data.len, index);
+
+ put_datapl(pi->collectors[index], data);
+
+ if (index == 0)
+ pi->until_reseed = (pi->until_reseed < data.len ? 0 :
+ pi->until_reseed - data.len);
+
+ if (pi->until_reseed == 0 &&
+ prng_reseed_time_ms() - pi->last_reseed_time >= 100) {
+ prng_seed_begin(&pi->Prng);
+
+ unsigned char buf[MAX_HASH_LEN];
+ uint32_t reseed_index = ++pi->reseeds;
+ prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index);
+ for (size_t i = 0; i < NCOLLECTORS; i++) {
+ prngdebug("emptying collector %"SIZEu"\n", i);
+ ssh_hash_digest(pi->collectors[i], buf);
+ put_data(&pi->Prng, buf, pi->hashalg->hlen);
+ ssh_hash_reset(pi->collectors[i]);
+ if (reseed_index & 1)
+ break;
+ reseed_index >>= 1;
+ }
+ smemclr(buf, sizeof(buf));
+ prng_seed_finish(&pi->Prng);
+ }
+}
+
+size_t prng_seed_bits(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ return pi->hashalg->hlen * 8;
+}