# HG changeset patch # User Matt Johnston # Date 1340983183 -28800 # Node ID 167fdc091c05429a3bc2a81e76c5b2b6ac381b7a # Parent 63f8d6c469cf51624c9a48dbac1f2ae9b4cd82b6 Improve RNG seeding. Try to read from /dev/urandom multiple times, take input from extra sources, and use /dev/random when generating private keys diff -r 63f8d6c469cf -r 167fdc091c05 common-runopts.c --- a/common-runopts.c Thu May 17 00:26:12 2012 +0800 +++ b/common-runopts.c Fri Jun 29 23:19:43 2012 +0800 @@ -29,6 +29,7 @@ #include "dbutil.h" #include "auth.h" #include "algo.h" +#include "random.h" runopts opts; /* GLOBAL */ @@ -45,6 +46,9 @@ goto out; } buf_setpos(buf, 0); + + addrandom(buf_getptr(buf, buf->len), buf->len); + if (buf_get_priv_key(buf, hostkey, type) == DROPBEAR_FAILURE) { goto out; } diff -r 63f8d6c469cf -r 167fdc091c05 gendss.c --- a/gendss.c Thu May 17 00:26:12 2012 +0800 +++ b/gendss.c Fri Jun 29 23:19:43 2012 +0800 @@ -57,6 +57,7 @@ m_mp_init_multi(key->p, key->q, key->g, key->y, key->x, NULL); seedrandom(); + seedstrongrandom(); getq(key); getp(key, size); diff -r 63f8d6c469cf -r 167fdc091c05 genrsa.c --- a/genrsa.c Thu May 17 00:26:12 2012 +0800 +++ b/genrsa.c Fri Jun 29 23:19:43 2012 +0800 @@ -56,6 +56,7 @@ &pminus, &lcm, &qminus, NULL); seedrandom(); + seedstrongrandom(); if (mp_set_int(key->e, RSA_E) != MP_OKAY) { fprintf(stderr, "RSA generation failed\n"); diff -r 63f8d6c469cf -r 167fdc091c05 options.h --- a/options.h Thu May 17 00:26:12 2012 +0800 +++ b/options.h Fri Jun 29 23:19:43 2012 +0800 @@ -204,20 +204,14 @@ * return the password on standard output */ /*#define ENABLE_CLI_ASKPASS_HELPER*/ -/* Random device to use - define either DROPBEAR_RANDOM_DEV or - * DROPBEAR_PRNGD_SOCKET. - * DROPBEAR_RANDOM_DEV is recommended on hosts with a good /dev/(u)random, - * otherwise use run prngd (or egd if you want), specifying the socket. - * The device will be queried for a few dozen bytes of seed a couple of times - * per session (or more for very long-lived sessions). */ +/* Source for randomness. This must be able to provide hundreds of bytes per SSH + * connection without blocking. In addition /dev/random is used for seeding + * rsa/dss key generation */ +#define DROPBEAR_URANDOM_DEV "/dev/urandom" -/* We'll use /dev/urandom by default, since /dev/random is too much hassle. - * If system developers aren't keeping seeds between boots nor getting - * any entropy from somewhere it's their own fault. */ -#define DROPBEAR_RANDOM_DEV "/dev/urandom" +/* Set this to use PRNGD or EGD instead of /dev/urandom or /dev/random */ +/*#define DROPBEAR_PRNGD_SOCKET "/var/run/dropbear-rng"*/ -/* prngd must be manually set up to produce output */ -/*#define DROPBEAR_PRNGD_SOCKET "/var/run/dropbear-rng"*/ /* Specify the number of clients we will allow to be connected but * not yet authenticated. After this limit, connections are rejected */ diff -r 63f8d6c469cf -r 167fdc091c05 random.c --- a/random.c Thu May 17 00:26:12 2012 +0800 +++ b/random.c Fri Jun 29 23:19:43 2012 +0800 @@ -27,19 +27,16 @@ #include "dbutil.h" #include "bignum.h" -static int donerandinit = 0; - /* this is used to generate unique output from the same hashpool */ static uint32_t counter = 0; /* the max value for the counter, so it won't integer overflow */ #define MAX_COUNTER 1<<30 -static unsigned char hashpool[SHA1_HASH_SIZE]; +static unsigned char hashpool[SHA1_HASH_SIZE] = {0}; +static int donerandinit = 0; #define INIT_SEED_SIZE 32 /* 256 bits */ -static void readrand(unsigned char* buf, unsigned int buflen); - /* The basic setup is we read some data from /dev/(u)random or prngd and hash it * into hashpool. To read data, we hash together current hashpool contents, * and a counter. We feed more data in by hashing the current pool and new @@ -50,120 +47,190 @@ * */ -static void readrand(unsigned char* buf, unsigned int buflen) { - +/* Pass len=0 to hash an entire file */ +static int +process_file(hash_state *hs, const char *filename, + unsigned int len, int prngd) +{ static int already_blocked = 0; int readfd; - unsigned int readpos; - int readlen; -#ifdef DROPBEAR_PRNGD_SOCKET - struct sockaddr_un egdsock; - char egdcmd[2]; -#endif - -#ifdef DROPBEAR_RANDOM_DEV - readfd = open(DROPBEAR_RANDOM_DEV, O_RDONLY); - if (readfd < 0) { - dropbear_exit("Couldn't open random device"); - } -#endif + unsigned int readcount; + int ret = DROPBEAR_FAILURE; #ifdef DROPBEAR_PRNGD_SOCKET - readfd = connect_unix(DROPBEAR_PRNGD_SOCKET); - - if (readfd < 0) { - dropbear_exit("Couldn't open random device"); + if (prngd) + { + readfd = connect_unix(filename); + } + else +#endif + { + readfd = open(filename, O_RDONLY); } - if (buflen > 255) - dropbear_exit("Can't request more than 255 bytes from egd"); - egdcmd[0] = 0x02; /* blocking read */ - egdcmd[1] = (unsigned char)buflen; - if (write(readfd, egdcmd, 2) < 0) - dropbear_exit("Can't send command to egd"); -#endif + if (readfd < 0) { + goto out; + } - /* read the actual random data */ - readpos = 0; - do { + readcount = 0; + while (readcount < len) + { + int readlen, wantread; + unsigned char readbuf[128]; if (!already_blocked) { int ret; - struct timeval timeout; + struct timeval timeout = { .tv_sec = 2, .tv_usec = 0}; fd_set read_fds; - timeout.tv_sec = 2; /* two seconds should be enough */ - timeout.tv_usec = 0; - FD_ZERO(&read_fds); FD_SET(readfd, &read_fds); ret = select(readfd + 1, &read_fds, NULL, NULL, &timeout); if (ret == 0) { - dropbear_log(LOG_INFO, "Warning: Reading the random source seems to have blocked.\nIf you experience problems, you probably need to find a better entropy source."); + dropbear_log(LOG_WARNING, "Warning: Reading the randomness source '%s' seems to have blocked.\nYou may need to find a better entropy source.", filename); already_blocked = 1; } } - readlen = read(readfd, &buf[readpos], buflen - readpos); + + wantread = MIN(sizeof(readbuf), len-readcount); + +#ifdef DROPBEAR_PRNGD_SOCKET + if (prngd) + { + char egdcmd[2]; + egdcmd[0] = 0x02; /* blocking read */ + egdcmd[1] = (unsigned char)wantread; + if (write(readfd, egdcmd, 2) < 0) + { + dropbear_exit("Can't send command to egd"); + } + } +#endif + + readlen = read(readfd, readbuf, wantread); if (readlen <= 0) { if (readlen < 0 && errno == EINTR) { continue; } - dropbear_exit("Error reading random source"); + if (readlen == 0 && len == 0) + { + /* whole file was read as requested */ + break; + } + goto out; } - readpos += readlen; - } while (readpos < buflen); - - close (readfd); + sha1_process(hs, readbuf, readlen); + readcount += readlen; + } + ret = DROPBEAR_SUCCESS; +out: + close(readfd); + return ret; } -/* initialise the prng from /dev/(u)random or prngd */ -void seedrandom() { - - unsigned char readbuf[INIT_SEED_SIZE]; - +void addrandom(char * buf, int len) +{ hash_state hs; - /* initialise so that things won't warn about - * hashing an undefined buffer */ - if (!donerandinit) { - m_burn(hashpool, sizeof(hashpool)); - } - - /* get the seed data */ - readrand(readbuf, sizeof(readbuf)); - /* hash in the new seed data */ sha1_init(&hs); + /* existing state (zeroes on startup) */ sha1_process(&hs, (void*)hashpool, sizeof(hashpool)); - sha1_process(&hs, (void*)readbuf, sizeof(readbuf)); + + /* new */ + sha1_process(&hs, buf, len); + sha1_done(&hs, hashpool); +} + +static void write_urandom() +{ +#ifndef DROPBEAR_PRNGD_SOCKET + /* This is opportunistic, don't worry about failure */ + unsigned char buf[INIT_SEED_SIZE]; + FILE *f = fopen(DROPBEAR_URANDOM_DEV, "w"); + genrandom(buf, sizeof(buf)); + fwrite(buf, sizeof(buf), 1, f); + fclose(f); +#endif +} + +/* add entropy from the stronger, blocking source /dev/random. Only used + * for generating persistent private keys (RSA and DSS) */ +void seedstrongrandom() +{ + /* We assume that PRNGD is a strong source, so don't need to do anything here */ +#ifndef DROPBEAR_PRNGD_SOCKET + hash_state hs; + + sha1_process(&hs, (void*)hashpool, sizeof(hashpool)); + if (process_file(&hs, "/dev/random", INIT_SEED_SIZE, 0) + != DROPBEAR_SUCCESS) { + dropbear_exit("Failure reading random device %s", "/dev/random"); + } + + sha1_done(&hs, hashpool); +#endif +} + +/* Initialise the prng from /dev/urandom or prngd. This function can + * be called multiple times */ +void seedrandom() { + + hash_state hs; + + pid_t pid; + struct timeval tv; + clock_t clockval; + + /* hash in the new seed data */ + sha1_init(&hs); + /* existing state */ + sha1_process(&hs, (void*)hashpool, sizeof(hashpool)); + +#ifdef DROPBEAR_PRNGD_SOCKET + if (process_file(&hs, DROPBEAR_PRNGD_SOCKET, INIT_SEED_SIZE, 1) + != DROPBEAR_SUCCESS) { + dropbear_exit("Failure reading random device %s", + DROPBEAR_PRNGD_SOCKET); + } +#else + /* non-blocking random source (probably /dev/urandom) */ + if (process_file(&hs, DROPBEAR_URANDOM_DEV, INIT_SEED_SIZE, 0) + != DROPBEAR_SUCCESS) { + dropbear_exit("Failure reading random device %s", + DROPBEAR_URANDOM_DEV); + } +#endif + + /* A few other sources to fall back on. Add more here for other platforms */ +#ifdef __linux__ + /* Seems to be a reasonable source of entropy from timers */ + process_file(&hs, "/proc/timer_list", 0, 0); + /* Might help on systems with wireless */ + process_file(&hs, "/proc/interrupts", 0, 0); +#endif + + pid = getpid(); + sha1_process(&hs, (void*)&pid, sizeof(pid)); + + gettimeofday(&tv, NULL); + sha1_process(&hs, (void*)&tv, sizeof(tv)); + + clockval = clock(); + sha1_process(&hs, (void*)&clockval, sizeof(clockval)); + + /* When a private key is read by the client or server it will + * be added to the hashpool - see runopts.c */ + sha1_done(&hs, hashpool); counter = 0; donerandinit = 1; -} -/* hash the current random pool with some unique identifiers - * for this process and point-in-time. this is used to separate - * the random pools for fork()ed processes. */ -void reseedrandom() { - - pid_t pid; - hash_state hs; - struct timeval tv; - - if (!donerandinit) { - dropbear_exit("seedrandom not done"); - } - - pid = getpid(); - gettimeofday(&tv, NULL); - - sha1_init(&hs); - sha1_process(&hs, (void*)hashpool, sizeof(hashpool)); - sha1_process(&hs, (void*)&pid, sizeof(pid)); - sha1_process(&hs, (void*)&tv, sizeof(tv)); - sha1_done(&hs, hashpool); + /* Feed it all back into /dev/urandom - this might help if Dropbear + * is running from inetd and gets new state each time */ + write_urandom(); } /* return len bytes of pseudo-random data */ diff -r 63f8d6c469cf -r 167fdc091c05 random.h --- a/random.h Thu May 17 00:26:12 2012 +0800 +++ b/random.h Fri Jun 29 23:19:43 2012 +0800 @@ -27,10 +27,10 @@ struct mp_int; +void seedstrongrandom(); void seedrandom(); -void reseedrandom(); -void genrandom(unsigned char* buf, int len); -void addrandom(unsigned char* buf, int len); +void genrandom(unsigned char* buf, unsigned int len); +void addrandom(char * buf, unsigned int len); void gen_random_mpint(mp_int *max, mp_int *rand); #endif /* _RANDOM_H_ */ diff -r 63f8d6c469cf -r 167fdc091c05 svr-chansession.c --- a/svr-chansession.c Thu May 17 00:26:12 2012 +0800 +++ b/svr-chansession.c Fri Jun 29 23:19:43 2012 +0800 @@ -871,7 +871,7 @@ svr_opts.hostkey = NULL; /* overwrite the prng state */ - reseedrandom(); + seedrandom(); #endif /* clear environment */ diff -r 63f8d6c469cf -r 167fdc091c05 svr-main.c --- a/svr-main.c Thu May 17 00:26:12 2012 +0800 +++ b/svr-main.c Fri Jun 29 23:19:43 2012 +0800 @@ -254,6 +254,8 @@ goto out; } + seedrandom(); + if (pipe(childpipe) < 0) { TRACE(("error creating child pipe")) goto out; @@ -267,8 +269,11 @@ if (fork_ret < 0) { dropbear_log(LOG_WARNING, "Error forking: %s", strerror(errno)); goto out; + } - } else if (fork_ret > 0) { + addrandom(&fork_ret, sizeof(fork_ret)); + + if (fork_ret > 0) { /* parent */ childpipes[conn_idx] = childpipe[0]; diff -r 63f8d6c469cf -r 167fdc091c05 svr-session.c --- a/svr-session.c Thu May 17 00:26:12 2012 +0800 +++ b/svr-session.c Fri Jun 29 23:19:43 2012 +0800 @@ -75,7 +75,6 @@ void svr_session(int sock, int childpipe) { char *host, *port; size_t len; - reseedrandom(); crypto_init(); common_session_init(sock, sock); diff -r 63f8d6c469cf -r 167fdc091c05 sysoptions.h --- a/sysoptions.h Thu May 17 00:26:12 2012 +0800 +++ b/sysoptions.h Fri Jun 29 23:19:43 2012 +0800 @@ -182,14 +182,6 @@ #error "You can't turn on PASSWORD and PAM auth both at once. Fix it in options.h" #endif -#if defined(DROPBEAR_RANDOM_DEV) && defined(DROPBEAR_PRNGD_SOCKET) -#error "You can't turn on DROPBEAR_PRNGD_SOCKET and DROPBEAR_RANDOM_DEV at once" -#endif - -#if !defined(DROPBEAR_RANDOM_DEV) && !defined(DROPBEAR_PRNGD_SOCKET) -#error "You must choose one of DROPBEAR_PRNGD_SOCKET or DROPBEAR_RANDOM_DEV in options.h" -#endif - /* We use dropbear_client and dropbear_server as shortcuts to avoid redundant * code, if we're just compiling as client or server */ #if defined(DROPBEAR_SERVER) && defined(DROPBEAR_CLIENT)