mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
Replace PuTTY's PRNG with a Fortuna-like system.
This tears out the entire previous random-pool system in sshrand.c. In its place is a system pretty close to Ferguson and Schneier's 'Fortuna' generator, with the main difference being that I use SHA-256 instead of AES for the generation side of the system (rationale given in comment). The PRNG implementation lives in sshprng.c, and defines a self- contained data type with no state stored outside the object, so you can instantiate however many of them you like. The old sshrand.c still exists, but in place of the previous random pool system, it's just become a client of sshprng.c, whose job is to hold a single global instance of the PRNG type, and manage its reference count, save file, noise-collection timers and similar administrative business. Advantages of this change include: - Fortuna is designed with a more varied threat model in mind than my old home-grown random pool. For example, after any request for random numbers, it automatically re-seeds itself, so that if the state of the PRNG should be leaked, it won't give enough information to find out what past outputs _were_. - The PRNG type can be instantiated with any hash function; the instance used by the main tools is based on SHA-256, an improvement on the old pool's use of SHA-1. - The new PRNG only uses the completely standard interface to the hash function API, instead of having to have privileged access to the internal SHA-1 block transform function. This will make it easier to revamp the hash code in general, and also it means that hardware-accelerated versions of SHA-256 will automatically be used for the PRNG as well as for everything else. - The new PRNG can be _tested_! Because it has an actual (if not quite explicit) specification for exactly what the output numbers _ought_ to be derived from the hashes of, I can (and have) put tests in cryptsuite that ensure the output really is being derived in the way I think it is. The old pool could have been returning any old nonsense and it would have been very hard to tell for sure.
This commit is contained in:
parent
5087792440
commit
320bf8479f
6
Recipe
6
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
|
||||
|
4
cmdgen.c
4
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);
|
||||
|
||||
|
1
defs.h
1
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;
|
||||
|
6
putty.h
6
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.
|
||||
|
32
ssh.h
32
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 {
|
||||
|
286
sshprng.c
Normal file
286
sshprng.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
347
sshrand.c
347
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 */
|
||||
|
@ -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):
|
||||
|
20
testcrypt.c
20
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)
|
||||
|
12
testcrypt.h
12
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.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user