diff --git a/Recipe b/Recipe index 4b35bd55..651cbe2e 100644 --- a/Recipe +++ b/Recipe @@ -255,7 +255,7 @@ SSHCRYPTO = ARITH sshmd5 sshsha sshsh256 sshsh512 + sshrsa sshdss sshecc + sshdes sshblowf sshaes sshccp ssharcf + sshdh sshcrc sshcrcda sshauxcrypt - + sshhmac + + sshhmac sshprng SSHCOMMON = sshcommon sshrand SSHCRYPTO + sshverstring + sshpubk sshzlib @@ -339,7 +339,7 @@ pageant : [G] winpgnt pageant sshrsa sshpubk sshdes ARITH sshmd5 version puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes ARITH sshmd5 version + sshrand winnoise sshsha winstore MISC winctrls sshrsa sshdss winmisc + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res - + tree234 notiming winhelp winnojmp CONF LIBS wintime sshecc + + tree234 notiming winhelp winnojmp CONF LIBS wintime sshecc sshprng + sshecdsag sshauxcrypt sshhmac winsecur winmiscs pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore @@ -355,7 +355,7 @@ puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal + ux_x11 noterm uxnogtk sessprep cmdline -PUTTYGEN_UNIX = sshrsag sshdssg sshprime sshdes ARITH sshmd5 version +PUTTYGEN_UNIX = sshrsag sshdssg sshprime sshdes ARITH sshmd5 version sshprng + sshrand uxnoise sshsha MISC sshrsa sshdss uxcons uxstore uxmisc + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234 + uxgen notiming CONF sshecc sshecdsag uxnogtk sshauxcrypt sshhmac diff --git a/cmdgen.c b/cmdgen.c index bf6cae39..171214fc 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -682,14 +682,14 @@ int main(int argc, char **argv) else strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm); - random_ref(); entropy = get_random_data(bits / 8, random_device); if (!entropy) { fprintf(stderr, "puttygen: failed to collect entropy, " "could not generate key\n"); return 1; } - random_add_heavynoise(entropy, bits / 8); + random_setup_special(); + random_reseed(make_ptrlen(entropy, bits / 8)); smemclr(entropy, bits/8); sfree(entropy); diff --git a/defs.h b/defs.h index 9e3d3f28..01108176 100644 --- a/defs.h +++ b/defs.h @@ -90,6 +90,7 @@ typedef struct PortFwdManager PortFwdManager; typedef struct PortFwdRecord PortFwdRecord; typedef struct ConnectionLayer ConnectionLayer; +typedef struct prng prng; typedef struct ssh_hashalg ssh_hashalg; typedef struct ssh_hash ssh_hash; typedef struct ssh_kex ssh_kex; diff --git a/putty.h b/putty.h index 3127c5de..fe0e8a22 100644 --- a/putty.h +++ b/putty.h @@ -1702,6 +1702,12 @@ extern int random_active; * calls random_ref on startup and random_unref on shutdown. */ void random_ref(void); void random_unref(void); +/* random_setup_special is used by PuTTYgen. It makes an extra-big + * random number generator. */ +void random_setup_special(); +/* Manually drop a random seed into the random number generator, e.g. + * just before generating a key. */ +void random_reseed(ptrlen seed); /* * Exports from pinger.c. diff --git a/ssh.h b/ssh.h index 9b3d37d7..3a2e0ece 100644 --- a/ssh.h +++ b/ssh.h @@ -867,8 +867,6 @@ extern const char sshver[]; */ extern bool ssh_fallback_cmd(Backend *backend); -void SHATransform(uint32_t *digest, uint32_t *data); - /* * Check of compiler version */ @@ -892,9 +890,35 @@ void SHATransform(uint32_t *digest, uint32_t *data); # undef COMPILER_SUPPORTS_SHA_NI #endif +/* + * The PRNG type, defined in sshprng.c. Visible data fields are + * 'savesize', which suggests how many random bytes you should request + * from a particular PRNG instance to write to putty.rnd, and a + * BinarySink implementation which you can use to write seed data in + * between calling prng_seed_{begin,finish}. + */ +struct prng { + size_t savesize; + BinarySink_IMPLEMENTATION; + /* (also there's a surrounding implementation struct in sshprng.c) */ +}; +prng *prng_new(const ssh_hashalg *hashalg); +void prng_free(prng *p); +void prng_seed_begin(prng *p); +void prng_seed_finish(prng *p); +void prng_read(prng *p, void *vout, size_t size); +void prng_add_entropy(prng *p, unsigned source_id, ptrlen data); + +/* This function must be implemented by the platform, and returns a + * timer in milliseconds that the PRNG can use to know whether it's + * been reseeded too recently to do it again. + * + * The PRNG system has its own special timing function not because its + * timing needs are unusual in the real applications, but simply so + * that testcrypt can mock it to keep the tests deterministic. */ +uint64_t prng_reseed_time_ms(void); + void random_read(void *out, size_t size); -void random_add_noise(void *noise, int length); -void random_add_heavynoise(void *noise, int length); /* Exports from x11fwd.c */ enum { diff --git a/sshprng.c b/sshprng.c new file mode 100644 index 00000000..ee1e0df2 --- /dev/null +++ b/sshprng.c @@ -0,0 +1,286 @@ +/* + * sshprng.c: 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.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. + * + * pending_output is a buffer of size equal to the hash length, + * which receives each of those hashes as it's generated. The + * bytes of the hash are returned in reverse order, just because + * that made it marginally easier to deal with the + * pending_output_remaining field. + */ + ssh_hash *generator; + mp_int *counter; + uint8_t *pending_output; + size_t pending_output_remaining; + + /* + * 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; + pi->pending_output = snewn(pi->hashalg->hlen, uint8_t); + pi->pending_output_remaining = 0; + pi->counter = mp_new(128); + 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); + + sfree(pi->pending_output); + mp_free(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 %zu 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); + + assert(pi->keymaker); + + prngdebug("prng: reseed finish\n"); + + /* + * Actually generate the key. + */ + ssh_hash_final(pi->keymaker, pi->pending_output); + 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, pi->pending_output, pi->hashalg->hlen); + smemclr(pi->pending_output, pi->hashalg->hlen); + + pi->until_reseed = RESEED_DATA_SIZE; + pi->last_reseed_time = prng_reseed_time_ms(); + pi->pending_output_remaining = 0; +} + +static inline void prng_generate(prng_impl *pi) +{ + ssh_hash *h = ssh_hash_copy(pi->generator); + + prngdebug("prng_generate\n"); + + put_byte(h, 'G'); + put_mp_ssh2(h, pi->counter); + mp_add_integer_into(pi->counter, pi->counter, 1); + ssh_hash_final(h, pi->pending_output); + pi->pending_output_remaining = pi->hashalg->hlen; +} + +void prng_read(prng *pr, void *vout, size_t size) +{ + prng_impl *pi = container_of(pr, prng_impl, Prng); + + assert(!pi->keymaker); + + prngdebug("prng_read %zu\n", size); + + uint8_t *out = (uint8_t *)vout; + for (; size > 0; size--) { + if (pi->pending_output_remaining == 0) + prng_generate(pi); + pi->pending_output_remaining--; + *out++ = pi->pending_output[pi->pending_output_remaining]; + pi->pending_output[pi->pending_output_remaining] = 0; + } + + 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=%zu -> 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); + + 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 %zu\n", i); + ssh_hash_final(pi->collectors[i], pi->pending_output); + put_data(&pi->Prng, pi->pending_output, pi->hashalg->hlen); + pi->collectors[i] = ssh_hash_new(pi->hashalg); + if (reseed_index & 1) + break; + reseed_index >>= 1; + } + + prng_seed_finish(&pi->Prng); + } +} diff --git a/sshrand.c b/sshrand.c index bcf6982d..292956e4 100644 --- a/sshrand.c +++ b/sshrand.c @@ -1,5 +1,5 @@ /* - * cryptographic random number generator for PuTTY's ssh client + * sshrand.c: manage the global live PRNG instance. */ #include "putty.h" @@ -9,282 +9,37 @@ /* Collect environmental noise every 5 minutes */ #define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC) -/* - * `pool' itself is a pool of random data which we actually use: we - * return bytes from `pool', at position `poolpos', until `poolpos' - * reaches the end of the pool. At this point we generate more - * random data, by adding noise, stirring well, and resetting - * `poolpos' to point to just past the beginning of the pool (not - * _the_ beginning, since otherwise we'd give away the whole - * contents of our pool, and attackers would just have to guess the - * next lot of noise). - * - * `incomingb' buffers acquired noise data, until it gets full, at - * which point the acquired noise is SHA'ed into `incoming' and - * `incomingb' is cleared. The noise in `incoming' is used as part - * of the noise for each stirring of the pool, in addition to local - * time, process listings, and other such stuff. - */ - -#define HASHINPUT 64 /* 64 bytes SHA input */ -#define HASHSIZE 20 /* 160 bits SHA output */ -#define POOLSIZE 1200 /* size of random pool */ - -struct RandPool { - unsigned char pool[POOLSIZE]; - int poolpos; - - unsigned char incoming[HASHSIZE]; - - unsigned char incomingb[HASHINPUT]; - int incomingpos; - - bool stir_pending; -}; - int random_active = 0; #ifdef FUZZING + /* * Special dummy version of the RNG for use when fuzzing. */ -void random_add_noise(void *noise, int length) { } -void random_add_heavynoise(void *noise, int length) { } +void random_add_noise(NoiseSourceId source, const void *noise, int length) { } void random_ref(void) { } +void random_setup_special(void) { } void random_unref(void) { } void random_read(void *out, size_t size) { - return 0x45; /* Chosen by eight fair coin tosses */ memset(out, 0x45, size); /* Chosen by eight fair coin tosses */ } void random_get_savedata(void **data, int *len) { } + #else /* !FUZZING */ -static struct RandPool pool; -long next_noise_collection; -#ifdef RANDOM_DIAGNOSTICS -int random_diagnostics = 0; -#endif +/* Dummy structure for the sake of having something to expire_timer_context */ +static struct random_timer_context { int dummy; } random_timer_ctx; -static void random_stir(void) +static prng *global_prng; +static unsigned long next_noise_collection; + +void random_add_noise(NoiseSourceId source, const void *noise, int length) { - uint32_t block[HASHINPUT / sizeof(uint32_t)]; - uint32_t digest[HASHSIZE / sizeof(uint32_t)]; - int i, j, k; - - /* - * noise_get_light will call random_add_noise, which may call - * back to here. Prevent recursive stirs. - */ - if (pool.stir_pending) - return; - pool.stir_pending = true; - - noise_get_light(random_add_noise); - -#ifdef RANDOM_DIAGNOSTICS - { - int p, q; - printf("random stir starting\npool:\n"); - for (p = 0; p < POOLSIZE; p += HASHSIZE) { - printf(" "); - for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(uint32_t *)(pool.pool + p + q)); - } - printf("\n"); - } - printf("incoming:\n "); - for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(uint32_t *)(pool.incoming + q)); - } - printf("\nincomingb:\n "); - for (q = 0; q < HASHINPUT; q += 4) { - printf(" %08x", *(uint32_t *)(pool.incomingb + q)); - } - printf("\n"); - random_diagnostics++; - } -#endif - - SHATransform((uint32_t *) pool.incoming, (uint32_t *) pool.incomingb); - pool.incomingpos = 0; - - /* - * Chunks of this code are blatantly endianness-dependent, but - * as it's all random bits anyway, WHO CARES? - */ - memcpy(digest, pool.incoming, sizeof(digest)); - - /* - * Make two passes over the pool. - */ - for (i = 0; i < 2; i++) { - - /* - * We operate SHA in CFB mode, repeatedly adding the same - * block of data to the digest. But we're also fiddling - * with the digest-so-far, so this shouldn't be Bad or - * anything. - */ - memcpy(block, pool.pool, sizeof(block)); - - /* - * Each pass processes the pool backwards in blocks of - * HASHSIZE, just so that in general we get the output of - * SHA before the corresponding input, in the hope that - * things will be that much less predictable that way - * round, when we subsequently return bytes ... - */ - for (j = POOLSIZE; (j -= HASHSIZE) >= 0;) { - /* - * XOR the bit of the pool we're processing into the - * digest. - */ - - for (k = 0; k < lenof(digest); k++) - digest[k] ^= ((uint32_t *) (pool.pool + j))[k]; - - /* - * Munge our unrevealed first block of the pool into - * it. - */ - SHATransform(digest, block); - - /* - * Stick the result back into the pool. - */ - - for (k = 0; k < lenof(digest); k++) - ((uint32_t *) (pool.pool + j))[k] = digest[k]; - } - -#ifdef RANDOM_DIAGNOSTICS - if (i == 0) { - int p, q; - printf("random stir midpoint\npool:\n"); - for (p = 0; p < POOLSIZE; p += HASHSIZE) { - printf(" "); - for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(uint32_t *)(pool.pool + p + q)); - } - printf("\n"); - } - printf("incoming:\n "); - for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(uint32_t *)(pool.incoming + q)); - } - printf("\nincomingb:\n "); - for (q = 0; q < HASHINPUT; q += 4) { - printf(" %08x", *(uint32_t *)(pool.incomingb + q)); - } - printf("\n"); - } -#endif - } - - /* - * Might as well save this value back into `incoming', just so - * there'll be some extra bizarreness there. - */ - SHATransform(digest, block); - memcpy(pool.incoming, digest, sizeof(digest)); - - pool.poolpos = sizeof(pool.incoming); - - pool.stir_pending = false; - -#ifdef RANDOM_DIAGNOSTICS - { - int p, q; - printf("random stir done\npool:\n"); - for (p = 0; p < POOLSIZE; p += HASHSIZE) { - printf(" "); - for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(uint32_t *)(pool.pool + p + q)); - } - printf("\n"); - } - printf("incoming:\n "); - for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(uint32_t *)(pool.incoming + q)); - } - printf("\nincomingb:\n "); - for (q = 0; q < HASHINPUT; q += 4) { - printf(" %08x", *(uint32_t *)(pool.incomingb + q)); - } - printf("\n"); - random_diagnostics--; - } -#endif -} - -void random_add_noise(void *noise, int length) -{ - unsigned char *p = noise; - int i; - if (!random_active) return; - /* - * This function processes HASHINPUT bytes into only HASHSIZE - * bytes, so _if_ we were getting incredibly high entropy - * sources then we would be throwing away valuable stuff. - */ - while (length >= (HASHINPUT - pool.incomingpos)) { - memcpy(pool.incomingb + pool.incomingpos, p, - HASHINPUT - pool.incomingpos); - p += HASHINPUT - pool.incomingpos; - length -= HASHINPUT - pool.incomingpos; - SHATransform((uint32_t *) pool.incoming, (uint32_t *) pool.incomingb); - for (i = 0; i < HASHSIZE; i++) { - pool.pool[pool.poolpos++] ^= pool.incoming[i]; - if (pool.poolpos >= POOLSIZE) - pool.poolpos = 0; - } - if (pool.poolpos < HASHSIZE) - random_stir(); - - pool.incomingpos = 0; - } - - memcpy(pool.incomingb + pool.incomingpos, p, length); - pool.incomingpos += length; -} - -void random_add_heavynoise(void *noise, int length) -{ - unsigned char *p = noise; - int i; - - while (length >= POOLSIZE) { - for (i = 0; i < POOLSIZE; i++) - pool.pool[i] ^= *p++; - random_stir(); - length -= POOLSIZE; - } - - for (i = 0; i < length; i++) - pool.pool[i] ^= *p++; - random_stir(); -} - -static void random_add_heavynoise_bitbybit(void *noise, int length) -{ - unsigned char *p = noise; - int i; - - while (length >= POOLSIZE - pool.poolpos) { - for (i = 0; i < POOLSIZE - pool.poolpos; i++) - pool.pool[pool.poolpos + i] ^= *p++; - random_stir(); - length -= POOLSIZE - pool.poolpos; - pool.poolpos = 0; - } - - for (i = 0; i < length; i++) - pool.pool[i] ^= *p++; - pool.poolpos = i; + prng_add_entropy(global_prng, source, make_ptrlen(noise, length)); } static void random_timer(void *ctx, unsigned long now) @@ -292,22 +47,54 @@ static void random_timer(void *ctx, unsigned long now) if (random_active > 0 && now == next_noise_collection) { noise_regular(); next_noise_collection = - schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); + schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, + &random_timer_ctx); } } +static void random_seed_callback(void *noise, int length) +{ + put_data(global_prng, noise, length); +} + +static void random_create(const ssh_hashalg *hashalg) +{ + assert(!global_prng); + global_prng = prng_new(hashalg); + + prng_seed_begin(global_prng); + noise_get_heavy(random_seed_callback); + prng_seed_finish(global_prng); + + next_noise_collection = + schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, + &random_timer_ctx); + + /* noise_get_heavy probably read our random seed file. + * Therefore (in fact, even if it didn't), we should write a + * fresh one, in case another instance of ourself starts up + * before we finish, and also in case an attacker gets hold of + * the seed data we used. */ + random_save_seed(); +} + void random_ref(void) { - if (!random_active) { - memset(&pool, 0, sizeof(pool)); /* just to start with */ + if (!random_active++) + random_create(&ssh_sha256); +} - noise_get_heavy(random_add_heavynoise_bitbybit); - random_stir(); - - next_noise_collection = - schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, &pool); - } +void random_setup_special() +{ random_active++; + random_create(&ssh_sha512); +} + +void random_reseed(ptrlen seed) +{ + prng_seed_begin(global_prng); + put_datapl(global_prng, seed); + prng_seed_finish(global_prng); } void random_unref(void) @@ -315,31 +102,25 @@ void random_unref(void) assert(random_active > 0); if (random_active == 1) { random_save_seed(); - expire_timer_context(&pool); + expire_timer_context(&random_timer_ctx); + prng_free(global_prng); + global_prng = NULL; } random_active--; } -void random_read(void *vout, size_t size) +void random_read(void *buf, size_t size) { - assert(random_active); - - uint8_t *out = (uint8_t *)vout; - while (size-- > 0) { - if (pool.poolpos >= POOLSIZE) - random_stir(); - - *out++ = pool.pool[pool.poolpos++]; - } + assert(random_active > 0); + prng_read(global_prng, buf, size); } void random_get_savedata(void **data, int *len) { - void *buf = snewn(POOLSIZE / 2, char); - random_stir(); - memcpy(buf, pool.pool + pool.poolpos, POOLSIZE / 2); - *len = POOLSIZE / 2; + void *buf = snewn(global_prng->savesize, char); + random_read(buf, global_prng->savesize); + *len = global_prng->savesize; *data = buf; - random_stir(); } -#endif + +#endif /* FUZZING */ diff --git a/test/cryptsuite.py b/test/cryptsuite.py index f0512b35..6216f4a7 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -1132,6 +1132,46 @@ class crypt(MyTestBase): ssh_cipher_decrypt(cipher, iv[:ivlen]) self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p) + def testPRNG(self): + hashalg = 'sha256' + seed = b"hello, world" + entropy = b'1234567890' * 100 + rev = lambda s: b''.join(reversed(s)) + + # Replicate the generation of some random numbers. to ensure + # they really are the hashes of what they're supposed to be. + pr = prng_new(hashalg) + prng_seed_begin(pr) + prng_seed_update(pr, seed) + prng_seed_finish(pr) + data1 = prng_read(pr, 128) + data2 = prng_read(pr, 127) # a short read shouldn't confuse things + prng_add_entropy(pr, 0, entropy) # forces a reseed + data3 = prng_read(pr, 128) + + key1 = hash_str(hashalg, b'R' + seed) + expected_data1 = ''.join( + rev(hash_str(hashalg, key1 + b'G' + ssh2_mpint(counter))) + for counter in range(4)) + # After prng_read finishes, we expect the PRNG to have + # automatically reseeded itself, so that if its internal state + # is revealed then the previous output can't be reconstructed. + key2 = hash_str(hashalg, key1 + b'R') + expected_data2 = ''.join( + rev(hash_str(hashalg, key2 + b'G' + ssh2_mpint(counter))) + for counter in range(4,8)) + # There will have been another reseed after the second + # prng_read, and then another due to the entropy. + key3 = hash_str(hashalg, key2 + b'R') + key4 = hash_str(hashalg, key3 + b'R' + hash_str(hashalg, entropy)) + expected_data3 = ''.join( + rev(hash_str(hashalg, key4 + b'G' + ssh2_mpint(counter))) + for counter in range(8,12)) + + self.assertEqualBin(data1, expected_data1) + self.assertEqualBin(data2, expected_data2[:127]) + self.assertEqualBin(data3, expected_data3) + class standard_test_vectors(MyTestBase): def testAES(self): def vector(cipher, key, plaintext, ciphertext): diff --git a/testcrypt.c b/testcrypt.c index d278daa8..1aa53aa1 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -55,6 +55,12 @@ void random_read(void *buf, size_t size) fatal_error("No random data in queue"); } +uint64_t prng_reseed_time_ms(void) +{ + static uint64_t previous_time = 0; + return previous_time += 200; +} + #define VALUE_TYPES(X) \ X(string, strbuf *, strbuf_free(v)) \ X(mpint, mp_int *, mp_free(v)) \ @@ -74,6 +80,7 @@ void random_read(void *buf, size_t size) X(ecdh, ecdh_key *, ssh_ecdhkex_freekey(v)) \ X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \ X(rsa, RSAKey *, rsa_free(v)) \ + X(prng, prng *, prng_free(v)) \ /* end of list */ typedef struct Value Value; @@ -853,6 +860,19 @@ strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) } #define aes256_decrypt_pubkey aes256_decrypt_pubkey_wrapper +strbuf *prng_read_wrapper(prng *pr, size_t size) +{ + strbuf *sb = strbuf_new(); + prng_read(pr, strbuf_append(sb, size), size); + return sb; +} +#define prng_read prng_read_wrapper + +void prng_seed_update(prng *pr, ptrlen data) +{ + put_datapl(pr, data); +} + bool crcda_detect(ptrlen packet, ptrlen iv) { if (iv.len != 0 && iv.len != 8) diff --git a/testcrypt.h b/testcrypt.h index e5b4b408..ca13e7c9 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -203,6 +203,18 @@ FUNC1(val_string_asciz, rsa_ssh1_fingerprint, val_rsa) FUNC3(void, rsa_ssh1_public_blob, out_val_string_binarysink, val_rsa, rsaorder) FUNC1(int, rsa_ssh1_public_blob_len, val_string_ptrlen) +/* + * The PRNG type. Similarly to hashes and MACs, I've invented an extra + * function prng_seed_update for putting seed data into the PRNG's + * exposed BinarySink. + */ +FUNC1(val_prng, prng_new, hashalg) +FUNC1(void, prng_seed_begin, val_prng) +FUNC2(void, prng_seed_update, val_prng, val_string_ptrlen) +FUNC1(void, prng_seed_finish, val_prng) +FUNC2(val_string, prng_read, val_prng, uint) +FUNC3(void, prng_add_entropy, val_prng, uint, val_string_ptrlen) + /* * Miscellaneous. */ diff --git a/unix/uxnoise.c b/unix/uxnoise.c index 9b239f77..24023005 100644 --- a/unix/uxnoise.c +++ b/unix/uxnoise.c @@ -82,7 +82,6 @@ void noise_get_heavy(void (*func) (void *, int)) } read_random_seed(func); - random_save_seed(); } void random_save_seed(void) @@ -97,17 +96,6 @@ void random_save_seed(void) } } -/* - * This function is called every time the random pool needs - * stirring, and will acquire the system time. - */ -void noise_get_light(void (*func) (void *, int)) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - func(&tv, sizeof(tv)); -} - /* * This function is called on a timer, and grabs as much changeable * system data as it can quickly get its hands on. @@ -145,3 +133,10 @@ void noise_ultralight(NoiseSourceId id, unsigned long data) random_add_noise(NOISE_SOURCE_TIME, &tv, sizeof(tv)); random_add_noise(id, &data, sizeof(data)); } + +uint64_t prng_reseed_time_ms(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} diff --git a/windows/winnoise.c b/windows/winnoise.c index 0c73321e..c4167ddf 100644 --- a/windows/winnoise.c +++ b/windows/winnoise.c @@ -73,8 +73,6 @@ void noise_get_heavy(void (*func) (void *, int)) } read_random_seed(func); - /* Update the seed immediately, in case another instance uses it. */ - random_save_seed(); } void random_save_seed(void) @@ -89,24 +87,6 @@ void random_save_seed(void) } } -/* - * This function is called every time the random pool needs - * stirring, and will acquire the system time in all available - * forms. - */ -void noise_get_light(void (*func) (void *, int)) -{ - SYSTEMTIME systime; - DWORD adjust[2]; - BOOL rubbish; - - GetSystemTime(&systime); - func(&systime, sizeof(systime)); - - GetSystemTimeAdjustment(&adjust[0], &adjust[1], &rubbish); - func(&adjust, sizeof(adjust)); -} - /* * This function is called on a timer, and it will monitor * frequently changing quantities such as the state of physical and @@ -163,3 +143,12 @@ void noise_ultralight(NoiseSourceId id, unsigned long data) if (QueryPerformanceCounter(&perftime)) random_add_noise(NOISE_SOURCE_PERFCOUNT, &perftime, sizeof(perftime)); } + +uint64_t prng_reseed_time_ms(void) +{ + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + uint64_t value = ft.dwHighDateTime; + value = (value << 32) + ft.dwLowDateTime; + return value / 10000; /* 1 millisecond / 100ns */ +} diff --git a/windows/winpgen.c b/windows/winpgen.c index 154b40f6..48878e04 100644 --- a/windows/winpgen.c +++ b/windows/winpgen.c @@ -1044,7 +1044,8 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, /* * Seed the entropy pool */ - random_add_heavynoise(state->entropy, state->entropy_size); + random_reseed( + make_ptrlen(state->entropy, state->entropy_size)); smemclr(state->entropy, state->entropy_size); sfree(state->entropy); state->collecting_entropy = false; @@ -1172,8 +1173,8 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, * CryptGenRandom, just do that, and go straight * to the key-generation phase. */ - random_add_heavynoise(raw_entropy_buf, - raw_entropy_required); + random_reseed( + make_ptrlen(raw_entropy_buf, raw_entropy_required)); start_generating_key(hwnd, state); } else { /* @@ -1583,7 +1584,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } } - random_ref(); + random_setup_special(); ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; cleanup_exit(ret);