diff options
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | Makefile.in | 11 | ||||
-rw-r--r-- | configure.ac | 38 | ||||
-rw-r--r-- | entropy.c | 926 | ||||
-rw-r--r-- | pathnames.h | 10 | ||||
-rw-r--r-- | ssh-rand-helper.c | 805 |
6 files changed, 918 insertions, 884 deletions
@@ -1,3 +1,13 @@ | |||
1 | 20011222 | ||
2 | - (djm) Ignore fix & patchlevel in OpenSSL version check. Patch from | ||
3 | solar@openwall.com | ||
4 | - (djm) Rework entropy code. If the OpenSSL PRNG is has not been | ||
5 | internally seeded, execute a subprogram "ssh-rand-helper" to obtain | ||
6 | some entropy for us. Rewrite the old in-process entropy collecter as | ||
7 | an example ssh-rand-helper. | ||
8 | - (djm) Always perform ssh_prng_cmds path lookups in configure, even if | ||
9 | we don't end up using ssh_prng_cmds (so we always get a valid file) | ||
10 | |||
1 | 20011221 | 11 | 20011221 |
2 | - (djm) Add option to gnome-ssh-askpass to stop it from grabbing the X | 12 | - (djm) Add option to gnome-ssh-askpass to stop it from grabbing the X |
3 | server. I have found this necessary to avoid server hangs with X input | 13 | server. I have found this necessary to avoid server hangs with X input |
@@ -7086,4 +7096,4 @@ | |||
7086 | - Wrote replacements for strlcpy and mkdtemp | 7096 | - Wrote replacements for strlcpy and mkdtemp |
7087 | - Released 1.0pre1 | 7097 | - Released 1.0pre1 |
7088 | 7098 | ||
7089 | $Id: ChangeLog,v 1.1705 2001/12/21 04:00:19 djm Exp $ | 7099 | $Id: ChangeLog,v 1.1706 2001/12/23 14:41:47 djm Exp $ |
diff --git a/Makefile.in b/Makefile.in index f484dc3a0..7a722b456 100644 --- a/Makefile.in +++ b/Makefile.in | |||
@@ -1,4 +1,4 @@ | |||
1 | # $Id: Makefile.in,v 1.190 2001/11/11 23:34:23 djm Exp $ | 1 | # $Id: Makefile.in,v 1.191 2001/12/23 14:41:48 djm Exp $ |
2 | 2 | ||
3 | prefix=@prefix@ | 3 | prefix=@prefix@ |
4 | exec_prefix=@exec_prefix@ | 4 | exec_prefix=@exec_prefix@ |
@@ -23,7 +23,8 @@ PATHS= -DETCDIR=\"$(sysconfdir)\" \ | |||
23 | -D_PATH_SSH_PROGRAM=\"$(SSH_PROGRAM)\" \ | 23 | -D_PATH_SSH_PROGRAM=\"$(SSH_PROGRAM)\" \ |
24 | -D_PATH_SSH_ASKPASS_DEFAULT=\"$(ASKPASS_PROGRAM)\" \ | 24 | -D_PATH_SSH_ASKPASS_DEFAULT=\"$(ASKPASS_PROGRAM)\" \ |
25 | -D_PATH_SFTP_SERVER=\"$(SFTP_SERVER)\" \ | 25 | -D_PATH_SFTP_SERVER=\"$(SFTP_SERVER)\" \ |
26 | -D_PATH_SSH_PIDDIR=\"$(piddir)\" | 26 | -D_PATH_SSH_PIDDIR=\"$(piddir)\" \ |
27 | -DSSH_RAND_HELPER=\"$(libexecdir)/ssh-rand-helper\" | ||
27 | 28 | ||
28 | CC=@CC@ | 29 | CC=@CC@ |
29 | LD=@LD@ | 30 | LD=@LD@ |
@@ -44,7 +45,7 @@ INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ | |||
44 | 45 | ||
45 | @NO_SFTP@SFTP_PROGS=sftp-server$(EXEEXT) sftp$(EXEEXT) | 46 | @NO_SFTP@SFTP_PROGS=sftp-server$(EXEEXT) sftp$(EXEEXT) |
46 | 47 | ||
47 | TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) $(SFTP_PROGS) | 48 | TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} $(SFTP_PROGS) |
48 | 49 | ||
49 | LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o dh.o dispatch.o mac.o hostfile.o key.o kex.o kexdh.o kexgex.o log.o match.o misc.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o scard.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o uuencode.o xmalloc.o | 50 | LIBSSH_OBJS=atomicio.o authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o cipher.o compat.o compress.o crc32.o deattack.o dh.o dispatch.o mac.o hostfile.o key.o kex.o kexdh.o kexgex.o log.o match.o misc.o mpaux.o nchan.o packet.o radix.o rijndael.o entropy.o readpass.o rsa.o scard.o ssh-dss.o ssh-rsa.o tildexpand.o ttymodes.o uidswap.o uuencode.o xmalloc.o |
50 | 51 | ||
@@ -121,6 +122,9 @@ sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o | |||
121 | sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-int.o sftp-common.o sftp-glob.o | 122 | sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-int.o sftp-common.o sftp-glob.o |
122 | $(LD) -o $@ sftp.o sftp-client.o sftp-common.o sftp-int.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) | 123 | $(LD) -o $@ sftp.o sftp-client.o sftp-common.o sftp-int.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) |
123 | 124 | ||
125 | ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) ssh-rand-helper.o | ||
126 | $(LD) -o $@ ssh-rand-helper.o atomicio.o log.o xmalloc.o $(LDFLAGS) -lopenbsd-compat $(LIBS) | ||
127 | |||
124 | # test driver for the loginrec code - not built by default | 128 | # test driver for the loginrec code - not built by default |
125 | logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o | 129 | logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o |
126 | $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) | 130 | $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) |
@@ -197,6 +201,7 @@ install-files: scard-install | |||
197 | $(INSTALL) -m 0755 -s ssh-keygen $(DESTDIR)$(bindir)/ssh-keygen | 201 | $(INSTALL) -m 0755 -s ssh-keygen $(DESTDIR)$(bindir)/ssh-keygen |
198 | $(INSTALL) -m 0755 -s ssh-keyscan $(DESTDIR)$(bindir)/ssh-keyscan | 202 | $(INSTALL) -m 0755 -s ssh-keyscan $(DESTDIR)$(bindir)/ssh-keyscan |
199 | $(INSTALL) -m 0755 -s sshd $(DESTDIR)$(sbindir)/sshd | 203 | $(INSTALL) -m 0755 -s sshd $(DESTDIR)$(sbindir)/sshd |
204 | $(INSTALL) -m 0755 -s ssh-rand-helper $(DESTDIR)$(libexecdir)/ssh-rand-helper | ||
200 | @NO_SFTP@$(INSTALL) -m 0755 -s sftp $(DESTDIR)$(bindir)/sftp | 205 | @NO_SFTP@$(INSTALL) -m 0755 -s sftp $(DESTDIR)$(bindir)/sftp |
201 | @NO_SFTP@$(INSTALL) -m 0755 -s sftp-server $(DESTDIR)$(SFTP_SERVER) | 206 | @NO_SFTP@$(INSTALL) -m 0755 -s sftp-server $(DESTDIR)$(SFTP_SERVER) |
202 | $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 | 207 | $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 |
diff --git a/configure.ac b/configure.ac index ae2b6a7a5..35cda594d 100644 --- a/configure.ac +++ b/configure.ac | |||
@@ -1,4 +1,4 @@ | |||
1 | # $Id: configure.ac,v 1.6 2001/12/07 17:20:48 mouring Exp $ | 1 | i# $Id: configure.ac,v 1.7 2001/12/23 14:41:48 djm Exp $ |
2 | 2 | ||
3 | AC_INIT | 3 | AC_INIT |
4 | AC_CONFIG_SRCDIR([ssh.c]) | 4 | AC_CONFIG_SRCDIR([ssh.c]) |
@@ -1598,28 +1598,28 @@ AC_ARG_WITH(prngd-socket, | |||
1598 | INSTALL_SSH_PRNG_CMDS="" | 1598 | INSTALL_SSH_PRNG_CMDS="" |
1599 | rm -f prng_commands | 1599 | rm -f prng_commands |
1600 | if (test -z "$RANDOM_POOL" && test -z "$PRNGD") ; then | 1600 | if (test -z "$RANDOM_POOL" && test -z "$PRNGD") ; then |
1601 | # Use these commands to collect entropy | ||
1602 | OSSH_PATH_ENTROPY_PROG(PROG_LS, ls) | ||
1603 | OSSH_PATH_ENTROPY_PROG(PROG_NETSTAT, netstat) | ||
1604 | OSSH_PATH_ENTROPY_PROG(PROG_ARP, arp) | ||
1605 | OSSH_PATH_ENTROPY_PROG(PROG_IFCONFIG, ifconfig) | ||
1606 | OSSH_PATH_ENTROPY_PROG(PROG_JSTAT, jstat) | ||
1607 | OSSH_PATH_ENTROPY_PROG(PROG_PS, ps) | ||
1608 | OSSH_PATH_ENTROPY_PROG(PROG_SAR, sar) | ||
1609 | OSSH_PATH_ENTROPY_PROG(PROG_W, w) | ||
1610 | OSSH_PATH_ENTROPY_PROG(PROG_WHO, who) | ||
1611 | OSSH_PATH_ENTROPY_PROG(PROG_LAST, last) | ||
1612 | OSSH_PATH_ENTROPY_PROG(PROG_LASTLOG, lastlog) | ||
1613 | OSSH_PATH_ENTROPY_PROG(PROG_DF, df) | ||
1614 | OSSH_PATH_ENTROPY_PROG(PROG_VMSTAT, vmstat) | ||
1615 | OSSH_PATH_ENTROPY_PROG(PROG_UPTIME, uptime) | ||
1616 | OSSH_PATH_ENTROPY_PROG(PROG_IPCS, ipcs) | ||
1617 | OSSH_PATH_ENTROPY_PROG(PROG_TAIL, tail) | ||
1618 | |||
1619 | INSTALL_SSH_PRNG_CMDS="yes" | 1601 | INSTALL_SSH_PRNG_CMDS="yes" |
1620 | fi | 1602 | fi |
1621 | AC_SUBST(INSTALL_SSH_PRNG_CMDS) | 1603 | AC_SUBST(INSTALL_SSH_PRNG_CMDS) |
1622 | 1604 | ||
1605 | # These programs are used to gather entropy from | ||
1606 | OSSH_PATH_ENTROPY_PROG(PROG_LS, ls) | ||
1607 | OSSH_PATH_ENTROPY_PROG(PROG_NETSTAT, netstat) | ||
1608 | OSSH_PATH_ENTROPY_PROG(PROG_ARP, arp) | ||
1609 | OSSH_PATH_ENTROPY_PROG(PROG_IFCONFIG, ifconfig) | ||
1610 | OSSH_PATH_ENTROPY_PROG(PROG_JSTAT, jstat) | ||
1611 | OSSH_PATH_ENTROPY_PROG(PROG_PS, ps) | ||
1612 | OSSH_PATH_ENTROPY_PROG(PROG_SAR, sar) | ||
1613 | OSSH_PATH_ENTROPY_PROG(PROG_W, w) | ||
1614 | OSSH_PATH_ENTROPY_PROG(PROG_WHO, who) | ||
1615 | OSSH_PATH_ENTROPY_PROG(PROG_LAST, last) | ||
1616 | OSSH_PATH_ENTROPY_PROG(PROG_LASTLOG, lastlog) | ||
1617 | OSSH_PATH_ENTROPY_PROG(PROG_DF, df) | ||
1618 | OSSH_PATH_ENTROPY_PROG(PROG_VMSTAT, vmstat) | ||
1619 | OSSH_PATH_ENTROPY_PROG(PROG_UPTIME, uptime) | ||
1620 | OSSH_PATH_ENTROPY_PROG(PROG_IPCS, ipcs) | ||
1621 | OSSH_PATH_ENTROPY_PROG(PROG_TAIL, tail) | ||
1622 | |||
1623 | 1623 | ||
1624 | AC_ARG_WITH(mantype, | 1624 | AC_ARG_WITH(mantype, |
1625 | [ --with-mantype=man|cat|doc Set man page type], | 1625 | [ --with-mantype=man|cat|doc Set man page type], |
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (c) 2000 Damien Miller. All rights reserved. | 2 | * Copyright (c) 2001 Damien Miller. All rights reserved. |
3 | * | 3 | * |
4 | * Redistribution and use in source and binary forms, with or without | 4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions | 5 | * modification, are permitted provided that the following conditions |
@@ -25,14 +25,8 @@ | |||
25 | #include "includes.h" | 25 | #include "includes.h" |
26 | 26 | ||
27 | #include <openssl/rand.h> | 27 | #include <openssl/rand.h> |
28 | #include <openssl/sha.h> | ||
29 | #include <openssl/crypto.h> | 28 | #include <openssl/crypto.h> |
30 | 29 | ||
31 | /* SunOS 4.4.4 needs this */ | ||
32 | #ifdef HAVE_FLOATINGPOINT_H | ||
33 | # include <floatingpoint.h> | ||
34 | #endif /* HAVE_FLOATINGPOINT_H */ | ||
35 | |||
36 | #include "ssh.h" | 30 | #include "ssh.h" |
37 | #include "misc.h" | 31 | #include "misc.h" |
38 | #include "xmalloc.h" | 32 | #include "xmalloc.h" |
@@ -40,878 +34,108 @@ | |||
40 | #include "pathnames.h" | 34 | #include "pathnames.h" |
41 | #include "log.h" | 35 | #include "log.h" |
42 | 36 | ||
43 | RCSID("$Id: entropy.c,v 1.38 2001/08/06 06:51:49 djm Exp $"); | ||
44 | |||
45 | #ifndef offsetof | ||
46 | # define offsetof(type, member) ((size_t) &((type *)0)->member) | ||
47 | #endif | ||
48 | |||
49 | /* Number of times to pass through command list gathering entropy */ | ||
50 | #define NUM_ENTROPY_RUNS 1 | ||
51 | |||
52 | /* Scale entropy estimates back by this amount on subsequent runs */ | ||
53 | #define SCALE_PER_RUN 10.0 | ||
54 | |||
55 | /* Minimum number of commands to be considered valid */ | ||
56 | #define MIN_ENTROPY_SOURCES 16 | ||
57 | |||
58 | #define WHITESPACE " \t\n" | ||
59 | |||
60 | #ifndef RUSAGE_SELF | ||
61 | # define RUSAGE_SELF 0 | ||
62 | #endif | ||
63 | #ifndef RUSAGE_CHILDREN | ||
64 | # define RUSAGE_CHILDREN 0 | ||
65 | #endif | ||
66 | |||
67 | #if defined(_POSIX_SAVED_IDS) && !defined(BROKEN_SAVED_UIDS) | ||
68 | # define SAVED_IDS_WORK_WITH_SETEUID | ||
69 | #endif | ||
70 | |||
71 | static void | ||
72 | check_openssl_version(void) | ||
73 | { | ||
74 | if (SSLeay() != OPENSSL_VERSION_NUMBER) | ||
75 | fatal("OpenSSL version mismatch. Built against %lx, you " | ||
76 | "have %lx", OPENSSL_VERSION_NUMBER, SSLeay()); | ||
77 | } | ||
78 | |||
79 | #if defined(PRNGD_SOCKET) || defined(PRNGD_PORT) | ||
80 | # define USE_PRNGD | ||
81 | #endif | ||
82 | |||
83 | #if defined(USE_PRNGD) || defined(RANDOM_POOL) | ||
84 | |||
85 | #ifdef USE_PRNGD | ||
86 | /* Collect entropy from PRNGD/EGD */ | ||
87 | int | ||
88 | get_random_bytes(unsigned char *buf, int len) | ||
89 | { | ||
90 | int fd; | ||
91 | char msg[2]; | ||
92 | #ifdef PRNGD_PORT | ||
93 | struct sockaddr_in addr; | ||
94 | #else | ||
95 | struct sockaddr_un addr; | ||
96 | #endif | ||
97 | int addr_len, rval, errors; | ||
98 | mysig_t old_sigpipe; | ||
99 | |||
100 | memset(&addr, '\0', sizeof(addr)); | ||
101 | |||
102 | #ifdef PRNGD_PORT | ||
103 | addr.sin_family = AF_INET; | ||
104 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
105 | addr.sin_port = htons(PRNGD_PORT); | ||
106 | addr_len = sizeof(struct sockaddr_in); | ||
107 | #else /* use IP socket PRNGD_SOCKET instead */ | ||
108 | /* Sanity checks */ | ||
109 | if (sizeof(PRNGD_SOCKET) > sizeof(addr.sun_path)) | ||
110 | fatal("Random pool path is too long"); | ||
111 | if (len > 255) | ||
112 | fatal("Too many bytes to read from PRNGD"); | ||
113 | |||
114 | addr.sun_family = AF_UNIX; | ||
115 | strlcpy(addr.sun_path, PRNGD_SOCKET, sizeof(addr.sun_path)); | ||
116 | addr_len = offsetof(struct sockaddr_un, sun_path) + | ||
117 | sizeof(PRNGD_SOCKET); | ||
118 | #endif | ||
119 | |||
120 | old_sigpipe = mysignal(SIGPIPE, SIG_IGN); | ||
121 | |||
122 | errors = rval = 0; | ||
123 | reopen: | ||
124 | #ifdef PRNGD_PORT | ||
125 | fd = socket(addr.sin_family, SOCK_STREAM, 0); | ||
126 | if (fd == -1) { | ||
127 | error("Couldn't create AF_INET socket: %s", strerror(errno)); | ||
128 | goto done; | ||
129 | } | ||
130 | #else | ||
131 | fd = socket(addr.sun_family, SOCK_STREAM, 0); | ||
132 | if (fd == -1) { | ||
133 | error("Couldn't create AF_UNIX socket: %s", strerror(errno)); | ||
134 | goto done; | ||
135 | } | ||
136 | #endif | ||
137 | |||
138 | if (connect(fd, (struct sockaddr*)&addr, addr_len) == -1) { | ||
139 | #ifdef PRNGD_PORT | ||
140 | error("Couldn't connect to PRNGD port %d: %s", | ||
141 | PRNGD_PORT, strerror(errno)); | ||
142 | #else | ||
143 | error("Couldn't connect to PRNGD socket \"%s\": %s", | ||
144 | addr.sun_path, strerror(errno)); | ||
145 | #endif | ||
146 | goto done; | ||
147 | } | ||
148 | |||
149 | /* Send blocking read request to PRNGD */ | ||
150 | msg[0] = 0x02; | ||
151 | msg[1] = len; | ||
152 | |||
153 | if (atomicio(write, fd, msg, sizeof(msg)) != sizeof(msg)) { | ||
154 | if (errno == EPIPE && errors < 10) { | ||
155 | close(fd); | ||
156 | errors++; | ||
157 | goto reopen; | ||
158 | } | ||
159 | error("Couldn't write to PRNGD socket: %s", | ||
160 | strerror(errno)); | ||
161 | goto done; | ||
162 | } | ||
163 | |||
164 | if (atomicio(read, fd, buf, len) != len) { | ||
165 | if (errno == EPIPE && errors < 10) { | ||
166 | close(fd); | ||
167 | errors++; | ||
168 | goto reopen; | ||
169 | } | ||
170 | error("Couldn't read from PRNGD socket: %s", | ||
171 | strerror(errno)); | ||
172 | goto done; | ||
173 | } | ||
174 | |||
175 | rval = 1; | ||
176 | done: | ||
177 | mysignal(SIGPIPE, old_sigpipe); | ||
178 | if (fd != -1) | ||
179 | close(fd); | ||
180 | return(rval); | ||
181 | } | ||
182 | #else /* !USE_PRNGD */ | ||
183 | #ifdef RANDOM_POOL | ||
184 | /* Collect entropy from /dev/urandom or pipe */ | ||
185 | static int | ||
186 | get_random_bytes(unsigned char *buf, int len) | ||
187 | { | ||
188 | int random_pool; | ||
189 | |||
190 | random_pool = open(RANDOM_POOL, O_RDONLY); | ||
191 | if (random_pool == -1) { | ||
192 | error("Couldn't open random pool \"%s\": %s", | ||
193 | RANDOM_POOL, strerror(errno)); | ||
194 | return(0); | ||
195 | } | ||
196 | |||
197 | if (atomicio(read, random_pool, buf, len) != len) { | ||
198 | error("Couldn't read from random pool \"%s\": %s", | ||
199 | RANDOM_POOL, strerror(errno)); | ||
200 | close(random_pool); | ||
201 | return(0); | ||
202 | } | ||
203 | |||
204 | close(random_pool); | ||
205 | |||
206 | return(1); | ||
207 | } | ||
208 | #endif /* RANDOM_POOL */ | ||
209 | #endif /* USE_PRNGD */ | ||
210 | |||
211 | /* | 37 | /* |
212 | * Seed OpenSSL's random number pool from Kernel random number generator | 38 | * Portable OpenSSH PRNG seeding: |
213 | * or PRNGD/EGD | 39 | * If OpenSSL has not "internally seeded" itself (e.g. pulled data from |
40 | * /dev/random), then we execute a "ssh-rand-helper" program which | ||
41 | * collects entropy and writes it to stdout. The child program must | ||
42 | * write at least RANDOM_SEED_SIZE bytes. The child is run with stderr | ||
43 | * attached, so error/debugging output should be visible. | ||
44 | * | ||
45 | * XXX: we should tell the child how many bytes we need. | ||
214 | */ | 46 | */ |
215 | void | ||
216 | seed_rng(void) | ||
217 | { | ||
218 | unsigned char buf[32]; | ||
219 | 47 | ||
220 | debug("Seeding random number generator"); | 48 | #define RANDOM_SEED_SIZE 48 |
221 | 49 | ||
222 | if (!get_random_bytes(buf, sizeof(buf))) { | 50 | RCSID("$Id: entropy.c,v 1.39 2001/12/23 14:41:48 djm Exp $"); |
223 | if (!RAND_status()) | ||
224 | fatal("Entropy collection failed and entropy exhausted"); | ||
225 | } else { | ||
226 | RAND_add(buf, sizeof(buf), sizeof(buf)); | ||
227 | } | ||
228 | 51 | ||
229 | memset(buf, '\0', sizeof(buf)); | 52 | static uid_t original_uid, original_euid; |
230 | } | ||
231 | 53 | ||
232 | void | 54 | void |
233 | init_rng(void) | 55 | seed_rng(void) |
234 | { | ||
235 | check_openssl_version(); | ||
236 | } | ||
237 | |||
238 | #else /* defined(USE_PRNGD) || defined(RANDOM_POOL) */ | ||
239 | |||
240 | /* | ||
241 | * FIXME: proper entropy estimations. All current values are guesses | ||
242 | * FIXME: (ATL) do estimates at compile time? | ||
243 | * FIXME: More entropy sources | ||
244 | */ | ||
245 | |||
246 | /* slow command timeouts (all in milliseconds) */ | ||
247 | /* static int entropy_timeout_default = ENTROPY_TIMEOUT_MSEC; */ | ||
248 | static int entropy_timeout_current = ENTROPY_TIMEOUT_MSEC; | ||
249 | |||
250 | static int prng_seed_saved = 0; | ||
251 | static int prng_initialised = 0; | ||
252 | uid_t original_uid; | ||
253 | |||
254 | typedef struct | ||
255 | { | ||
256 | /* Proportion of data that is entropy */ | ||
257 | double rate; | ||
258 | /* Counter goes positive if this command times out */ | ||
259 | unsigned int badness; | ||
260 | /* Increases by factor of two each timeout */ | ||
261 | unsigned int sticky_badness; | ||
262 | /* Path to executable */ | ||
263 | char *path; | ||
264 | /* argv to pass to executable */ | ||
265 | char *args[5]; | ||
266 | /* full command string (debug) */ | ||
267 | char *cmdstring; | ||
268 | } entropy_source_t; | ||
269 | |||
270 | double stir_from_system(void); | ||
271 | double stir_from_programs(void); | ||
272 | double stir_gettimeofday(double entropy_estimate); | ||
273 | double stir_clock(double entropy_estimate); | ||
274 | double stir_rusage(int who, double entropy_estimate); | ||
275 | double hash_output_from_command(entropy_source_t *src, char *hash); | ||
276 | |||
277 | /* this is initialised from a file, by prng_read_commands() */ | ||
278 | entropy_source_t *entropy_sources = NULL; | ||
279 | |||
280 | double | ||
281 | stir_from_system(void) | ||
282 | { | ||
283 | double total_entropy_estimate; | ||
284 | long int i; | ||
285 | |||
286 | total_entropy_estimate = 0; | ||
287 | |||
288 | i = getpid(); | ||
289 | RAND_add(&i, sizeof(i), 0.5); | ||
290 | total_entropy_estimate += 0.1; | ||
291 | |||
292 | i = getppid(); | ||
293 | RAND_add(&i, sizeof(i), 0.5); | ||
294 | total_entropy_estimate += 0.1; | ||
295 | |||
296 | i = getuid(); | ||
297 | RAND_add(&i, sizeof(i), 0.0); | ||
298 | i = getgid(); | ||
299 | RAND_add(&i, sizeof(i), 0.0); | ||
300 | |||
301 | total_entropy_estimate += stir_gettimeofday(1.0); | ||
302 | total_entropy_estimate += stir_clock(0.5); | ||
303 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 2.0); | ||
304 | |||
305 | return(total_entropy_estimate); | ||
306 | } | ||
307 | |||
308 | double | ||
309 | stir_from_programs(void) | ||
310 | { | ||
311 | int i; | ||
312 | int c; | ||
313 | double entropy_estimate; | ||
314 | double total_entropy_estimate; | ||
315 | char hash[SHA_DIGEST_LENGTH]; | ||
316 | |||
317 | total_entropy_estimate = 0; | ||
318 | for(i = 0; i < NUM_ENTROPY_RUNS; i++) { | ||
319 | c = 0; | ||
320 | while (entropy_sources[c].path != NULL) { | ||
321 | |||
322 | if (!entropy_sources[c].badness) { | ||
323 | /* Hash output from command */ | ||
324 | entropy_estimate = hash_output_from_command(&entropy_sources[c], hash); | ||
325 | |||
326 | /* Scale back entropy estimate according to command's rate */ | ||
327 | entropy_estimate *= entropy_sources[c].rate; | ||
328 | |||
329 | /* Upper bound of entropy estimate is SHA_DIGEST_LENGTH */ | ||
330 | if (entropy_estimate > SHA_DIGEST_LENGTH) | ||
331 | entropy_estimate = SHA_DIGEST_LENGTH; | ||
332 | |||
333 | /* Scale back estimates for subsequent passes through list */ | ||
334 | entropy_estimate /= SCALE_PER_RUN * (i + 1.0); | ||
335 | |||
336 | /* Stir it in */ | ||
337 | RAND_add(hash, sizeof(hash), entropy_estimate); | ||
338 | |||
339 | debug3("Got %0.2f bytes of entropy from '%s'", entropy_estimate, | ||
340 | entropy_sources[c].cmdstring); | ||
341 | |||
342 | total_entropy_estimate += entropy_estimate; | ||
343 | |||
344 | /* Execution times should be a little unpredictable */ | ||
345 | total_entropy_estimate += stir_gettimeofday(0.05); | ||
346 | total_entropy_estimate += stir_clock(0.05); | ||
347 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 0.1); | ||
348 | total_entropy_estimate += stir_rusage(RUSAGE_CHILDREN, 0.1); | ||
349 | } else { | ||
350 | debug2("Command '%s' disabled (badness %d)", | ||
351 | entropy_sources[c].cmdstring, entropy_sources[c].badness); | ||
352 | |||
353 | if (entropy_sources[c].badness > 0) | ||
354 | entropy_sources[c].badness--; | ||
355 | } | ||
356 | |||
357 | c++; | ||
358 | } | ||
359 | } | ||
360 | |||
361 | return(total_entropy_estimate); | ||
362 | } | ||
363 | |||
364 | double | ||
365 | stir_gettimeofday(double entropy_estimate) | ||
366 | { | ||
367 | struct timeval tv; | ||
368 | |||
369 | if (gettimeofday(&tv, NULL) == -1) | ||
370 | fatal("Couldn't gettimeofday: %s", strerror(errno)); | ||
371 | |||
372 | RAND_add(&tv, sizeof(tv), entropy_estimate); | ||
373 | |||
374 | return(entropy_estimate); | ||
375 | } | ||
376 | |||
377 | double | ||
378 | stir_clock(double entropy_estimate) | ||
379 | { | ||
380 | #ifdef HAVE_CLOCK | ||
381 | clock_t c; | ||
382 | |||
383 | c = clock(); | ||
384 | RAND_add(&c, sizeof(c), entropy_estimate); | ||
385 | |||
386 | return(entropy_estimate); | ||
387 | #else /* _HAVE_CLOCK */ | ||
388 | return(0); | ||
389 | #endif /* _HAVE_CLOCK */ | ||
390 | } | ||
391 | |||
392 | double | ||
393 | stir_rusage(int who, double entropy_estimate) | ||
394 | { | ||
395 | #ifdef HAVE_GETRUSAGE | ||
396 | struct rusage ru; | ||
397 | |||
398 | if (getrusage(who, &ru) == -1) | ||
399 | return(0); | ||
400 | |||
401 | RAND_add(&ru, sizeof(ru), entropy_estimate); | ||
402 | |||
403 | return(entropy_estimate); | ||
404 | #else /* _HAVE_GETRUSAGE */ | ||
405 | return(0); | ||
406 | #endif /* _HAVE_GETRUSAGE */ | ||
407 | } | ||
408 | |||
409 | |||
410 | static int | ||
411 | _get_timeval_msec_difference(struct timeval *t1, struct timeval *t2) { | ||
412 | int secdiff, usecdiff; | ||
413 | |||
414 | secdiff = t2->tv_sec - t1->tv_sec; | ||
415 | usecdiff = (secdiff*1000000) + (t2->tv_usec - t1->tv_usec); | ||
416 | return (int)(usecdiff / 1000); | ||
417 | } | ||
418 | |||
419 | double | ||
420 | hash_output_from_command(entropy_source_t *src, char *hash) | ||
421 | { | 56 | { |
422 | static int devnull = -1; | 57 | int devnull; |
423 | int p[2]; | 58 | int p[2]; |
424 | fd_set rdset; | ||
425 | int cmd_eof = 0, error_abort = 0; | ||
426 | struct timeval tv_start, tv_current; | ||
427 | int msec_elapsed = 0; | ||
428 | pid_t pid; | 59 | pid_t pid; |
429 | int status; | 60 | int ret; |
430 | char buf[16384]; | 61 | unsigned char buf[RANDOM_SEED_SIZE]; |
431 | int bytes_read; | ||
432 | int total_bytes_read; | ||
433 | SHA_CTX sha; | ||
434 | |||
435 | debug3("Reading output from \'%s\'", src->cmdstring); | ||
436 | |||
437 | if (devnull == -1) { | ||
438 | devnull = open("/dev/null", O_RDWR); | ||
439 | if (devnull == -1) | ||
440 | fatal("Couldn't open /dev/null: %s", strerror(errno)); | ||
441 | } | ||
442 | |||
443 | if (pipe(p) == -1) | ||
444 | fatal("Couldn't open pipe: %s", strerror(errno)); | ||
445 | |||
446 | (void)gettimeofday(&tv_start, NULL); /* record start time */ | ||
447 | |||
448 | switch (pid = fork()) { | ||
449 | case -1: /* Error */ | ||
450 | close(p[0]); | ||
451 | close(p[1]); | ||
452 | fatal("Couldn't fork: %s", strerror(errno)); | ||
453 | /* NOTREACHED */ | ||
454 | case 0: /* Child */ | ||
455 | dup2(devnull, STDIN_FILENO); | ||
456 | dup2(p[1], STDOUT_FILENO); | ||
457 | dup2(p[1], STDERR_FILENO); | ||
458 | close(p[0]); | ||
459 | close(p[1]); | ||
460 | close(devnull); | ||
461 | |||
462 | setuid(original_uid); | ||
463 | execv(src->path, (char**)(src->args)); | ||
464 | debug("(child) Couldn't exec '%s': %s", src->cmdstring, | ||
465 | strerror(errno)); | ||
466 | _exit(-1); | ||
467 | default: /* Parent */ | ||
468 | break; | ||
469 | } | ||
470 | |||
471 | RAND_add(&pid, sizeof(&pid), 0.0); | ||
472 | |||
473 | close(p[1]); | ||
474 | |||
475 | /* Hash output from child */ | ||
476 | SHA1_Init(&sha); | ||
477 | total_bytes_read = 0; | ||
478 | |||
479 | while (!error_abort && !cmd_eof) { | ||
480 | int ret; | ||
481 | struct timeval tv; | ||
482 | int msec_remaining; | ||
483 | |||
484 | (void) gettimeofday(&tv_current, 0); | ||
485 | msec_elapsed = _get_timeval_msec_difference(&tv_start, &tv_current); | ||
486 | if (msec_elapsed >= entropy_timeout_current) { | ||
487 | error_abort=1; | ||
488 | continue; | ||
489 | } | ||
490 | msec_remaining = entropy_timeout_current - msec_elapsed; | ||
491 | |||
492 | FD_ZERO(&rdset); | ||
493 | FD_SET(p[0], &rdset); | ||
494 | tv.tv_sec = msec_remaining / 1000; | ||
495 | tv.tv_usec = (msec_remaining % 1000) * 1000; | ||
496 | |||
497 | ret = select(p[0]+1, &rdset, NULL, NULL, &tv); | ||
498 | |||
499 | RAND_add(&tv, sizeof(tv), 0.0); | ||
500 | |||
501 | switch (ret) { | ||
502 | case 0: | ||
503 | /* timer expired */ | ||
504 | error_abort = 1; | ||
505 | break; | ||
506 | case 1: | ||
507 | /* command input */ | ||
508 | do { | ||
509 | bytes_read = read(p[0], buf, sizeof(buf)); | ||
510 | } while (bytes_read == -1 && errno == EINTR); | ||
511 | RAND_add(&bytes_read, sizeof(&bytes_read), 0.0); | ||
512 | if (bytes_read == -1) { | ||
513 | error_abort = 1; | ||
514 | break; | ||
515 | } else if (bytes_read) { | ||
516 | SHA1_Update(&sha, buf, bytes_read); | ||
517 | total_bytes_read += bytes_read; | ||
518 | } else { | ||
519 | cmd_eof = 1; | ||
520 | } | ||
521 | break; | ||
522 | case -1: | ||
523 | default: | ||
524 | /* error */ | ||
525 | debug("Command '%s': select() failed: %s", src->cmdstring, | ||
526 | strerror(errno)); | ||
527 | error_abort = 1; | ||
528 | break; | ||
529 | } | ||
530 | } | ||
531 | |||
532 | SHA1_Final(hash, &sha); | ||
533 | |||
534 | close(p[0]); | ||
535 | |||
536 | debug3("Time elapsed: %d msec", msec_elapsed); | ||
537 | |||
538 | if (waitpid(pid, &status, 0) == -1) { | ||
539 | error("Couldn't wait for child '%s' completion: %s", src->cmdstring, | ||
540 | strerror(errno)); | ||
541 | return(0.0); | ||
542 | } | ||
543 | |||
544 | RAND_add(&status, sizeof(&status), 0.0); | ||
545 | |||
546 | if (error_abort) { | ||
547 | /* closing p[0] on timeout causes the entropy command to | ||
548 | * SIGPIPE. Take whatever output we got, and mark this command | ||
549 | * as slow */ | ||
550 | debug2("Command '%s' timed out", src->cmdstring); | ||
551 | src->sticky_badness *= 2; | ||
552 | src->badness = src->sticky_badness; | ||
553 | return(total_bytes_read); | ||
554 | } | ||
555 | |||
556 | if (WIFEXITED(status)) { | ||
557 | if (WEXITSTATUS(status)==0) { | ||
558 | return(total_bytes_read); | ||
559 | } else { | ||
560 | debug2("Command '%s' exit status was %d", src->cmdstring, | ||
561 | WEXITSTATUS(status)); | ||
562 | src->badness = src->sticky_badness = 128; | ||
563 | return (0.0); | ||
564 | } | ||
565 | } else if (WIFSIGNALED(status)) { | ||
566 | debug2("Command '%s' returned on uncaught signal %d !", src->cmdstring, | ||
567 | status); | ||
568 | src->badness = src->sticky_badness = 128; | ||
569 | return(0.0); | ||
570 | } else | ||
571 | return(0.0); | ||
572 | } | ||
573 | |||
574 | /* | ||
575 | * prng seedfile functions | ||
576 | */ | ||
577 | int | ||
578 | prng_check_seedfile(char *filename) { | ||
579 | |||
580 | struct stat st; | ||
581 | |||
582 | /* FIXME raceable: eg replace seed between this stat and subsequent open */ | ||
583 | /* Not such a problem because we don't trust the seed file anyway */ | ||
584 | if (lstat(filename, &st) == -1) { | ||
585 | /* Give up on hard errors */ | ||
586 | if (errno != ENOENT) | ||
587 | debug("WARNING: Couldn't stat random seed file \"%s\": %s", | ||
588 | filename, strerror(errno)); | ||
589 | 62 | ||
590 | return(0); | 63 | if (RAND_status() == 1) { |
591 | } | 64 | debug3("RNG is ready, skipping seeding"); |
592 | |||
593 | /* regular file? */ | ||
594 | if (!S_ISREG(st.st_mode)) | ||
595 | fatal("PRNG seedfile %.100s is not a regular file", filename); | ||
596 | |||
597 | /* mode 0600, owned by root or the current user? */ | ||
598 | if (((st.st_mode & 0177) != 0) || !(st.st_uid == original_uid)) { | ||
599 | debug("WARNING: PRNG seedfile %.100s must be mode 0600, owned by uid %d", | ||
600 | filename, getuid()); | ||
601 | return(0); | ||
602 | } | ||
603 | |||
604 | return(1); | ||
605 | } | ||
606 | |||
607 | void | ||
608 | prng_write_seedfile(void) { | ||
609 | int fd; | ||
610 | char seed[1024]; | ||
611 | char filename[1024]; | ||
612 | struct passwd *pw; | ||
613 | |||
614 | /* Don't bother if we have already saved a seed */ | ||
615 | if (prng_seed_saved) | ||
616 | return; | 65 | return; |
617 | |||
618 | setuid(original_uid); | ||
619 | |||
620 | prng_seed_saved = 1; | ||
621 | |||
622 | pw = getpwuid(original_uid); | ||
623 | if (pw == NULL) | ||
624 | fatal("Couldn't get password entry for current user (%i): %s", | ||
625 | original_uid, strerror(errno)); | ||
626 | |||
627 | /* Try to ensure that the parent directory is there */ | ||
628 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
629 | _PATH_SSH_USER_DIR); | ||
630 | mkdir(filename, 0700); | ||
631 | |||
632 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
633 | SSH_PRNG_SEED_FILE); | ||
634 | |||
635 | debug("writing PRNG seed to file %.100s", filename); | ||
636 | |||
637 | RAND_bytes(seed, sizeof(seed)); | ||
638 | |||
639 | /* Don't care if the seed doesn't exist */ | ||
640 | prng_check_seedfile(filename); | ||
641 | |||
642 | if ((fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0600)) == -1) { | ||
643 | debug("WARNING: couldn't access PRNG seedfile %.100s (%.100s)", | ||
644 | filename, strerror(errno)); | ||
645 | } else { | ||
646 | if (atomicio(write, fd, &seed, sizeof(seed)) != sizeof(seed)) | ||
647 | fatal("problem writing PRNG seedfile %.100s (%.100s)", filename, | ||
648 | strerror(errno)); | ||
649 | |||
650 | close(fd); | ||
651 | } | ||
652 | } | ||
653 | |||
654 | void | ||
655 | prng_read_seedfile(void) { | ||
656 | int fd; | ||
657 | char seed[1024]; | ||
658 | char filename[1024]; | ||
659 | struct passwd *pw; | ||
660 | |||
661 | pw = getpwuid(original_uid); | ||
662 | if (pw == NULL) | ||
663 | fatal("Couldn't get password entry for current user (%i): %s", | ||
664 | original_uid, strerror(errno)); | ||
665 | |||
666 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
667 | SSH_PRNG_SEED_FILE); | ||
668 | |||
669 | debug("loading PRNG seed from file %.100s", filename); | ||
670 | |||
671 | if (!prng_check_seedfile(filename)) { | ||
672 | verbose("Random seed file not found or not valid, ignoring."); | ||
673 | return; | ||
674 | } | ||
675 | |||
676 | /* open the file and read in the seed */ | ||
677 | fd = open(filename, O_RDONLY); | ||
678 | if (fd == -1) | ||
679 | fatal("could not open PRNG seedfile %.100s (%.100s)", filename, | ||
680 | strerror(errno)); | ||
681 | |||
682 | if (atomicio(read, fd, &seed, sizeof(seed)) != sizeof(seed)) { | ||
683 | verbose("invalid or short read from PRNG seedfile %.100s - ignoring", | ||
684 | filename); | ||
685 | memset(seed, '\0', sizeof(seed)); | ||
686 | } | ||
687 | close(fd); | ||
688 | |||
689 | /* stir in the seed, with estimated entropy zero */ | ||
690 | RAND_add(&seed, sizeof(seed), 0.0); | ||
691 | } | ||
692 | |||
693 | |||
694 | /* | ||
695 | * entropy command initialisation functions | ||
696 | */ | ||
697 | int | ||
698 | prng_read_commands(char *cmdfilename) | ||
699 | { | ||
700 | FILE *f; | ||
701 | char *cp; | ||
702 | char line[1024]; | ||
703 | char cmd[1024]; | ||
704 | char path[256]; | ||
705 | int linenum; | ||
706 | int num_cmds = 64; | ||
707 | int cur_cmd = 0; | ||
708 | double est; | ||
709 | entropy_source_t *entcmd; | ||
710 | |||
711 | f = fopen(cmdfilename, "r"); | ||
712 | if (!f) { | ||
713 | fatal("couldn't read entropy commands file %.100s: %.100s", | ||
714 | cmdfilename, strerror(errno)); | ||
715 | } | 66 | } |
716 | 67 | ||
717 | entcmd = (entropy_source_t *)xmalloc(num_cmds * sizeof(entropy_source_t)); | 68 | debug3("Seeing PRNG from %s", SSH_RAND_HELPER); |
718 | memset(entcmd, '\0', num_cmds * sizeof(entropy_source_t)); | ||
719 | |||
720 | /* Read in file */ | ||
721 | linenum = 0; | ||
722 | while (fgets(line, sizeof(line), f)) { | ||
723 | int arg; | ||
724 | char *argv; | ||
725 | |||
726 | linenum++; | ||
727 | |||
728 | /* skip leading whitespace, test for blank line or comment */ | ||
729 | cp = line + strspn(line, WHITESPACE); | ||
730 | if ((*cp == 0) || (*cp == '#')) | ||
731 | continue; /* done with this line */ | ||
732 | |||
733 | /* First non-whitespace char should be double quote delimiting */ | ||
734 | /* commandline */ | ||
735 | if (*cp != '"') { | ||
736 | error("bad entropy command, %.100s line %d", cmdfilename, | ||
737 | linenum); | ||
738 | continue; | ||
739 | } | ||
740 | |||
741 | /* first token, command args (incl. argv[0]) in double quotes */ | ||
742 | cp = strtok(cp, "\""); | ||
743 | if (cp == NULL) { | ||
744 | error("missing or bad command string, %.100s line %d -- ignored", | ||
745 | cmdfilename, linenum); | ||
746 | continue; | ||
747 | } | ||
748 | strlcpy(cmd, cp, sizeof(cmd)); | ||
749 | |||
750 | /* second token, full command path */ | ||
751 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | ||
752 | error("missing command path, %.100s line %d -- ignored", | ||
753 | cmdfilename, linenum); | ||
754 | continue; | ||
755 | } | ||
756 | |||
757 | /* did configure mark this as dead? */ | ||
758 | if (strncmp("undef", cp, 5) == 0) | ||
759 | continue; | ||
760 | |||
761 | strlcpy(path, cp, sizeof(path)); | ||
762 | 69 | ||
763 | /* third token, entropy rate estimate for this command */ | 70 | if ((devnull = open("/dev/null", O_RDWR)) == -1) |
764 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | 71 | fatal("Couldn't open /dev/null: %s", strerror(errno)); |
765 | error("missing entropy estimate, %.100s line %d -- ignored", | 72 | if (pipe(p) == -1) |
766 | cmdfilename, linenum); | 73 | fatal("pipe: %s", strerror(errno)); |
767 | continue; | 74 | |
768 | } | 75 | if ((pid = fork()) == -1) |
769 | est = strtod(cp, &argv); | 76 | fatal("Couldn't fork: %s", strerror(errno)); |
770 | 77 | if (pid == 0) { | |
771 | /* end of line */ | 78 | dup2(devnull, STDIN_FILENO); |
772 | if ((cp = strtok(NULL, WHITESPACE)) != NULL) { | 79 | dup2(p[1], STDOUT_FILENO); |
773 | error("garbage at end of line %d in %.100s -- ignored", linenum, | 80 | /* Keep stderr open for errors */ |
774 | cmdfilename); | 81 | close(p[0]); |
775 | continue; | 82 | close(p[1]); |
776 | } | 83 | close(devnull); |
777 | 84 | ||
778 | /* save the command for debug messages */ | 85 | if (original_uid != original_euid && |
779 | entcmd[cur_cmd].cmdstring = xstrdup(cmd); | 86 | setuid(original_uid) == -1) { |
780 | 87 | fprintf(stderr, "(rand child) setuid: %s\n", | |
781 | /* split the command args */ | 88 | strerror(errno)); |
782 | cp = strtok(cmd, WHITESPACE); | 89 | _exit(1); |
783 | arg = 0; | ||
784 | argv = NULL; | ||
785 | do { | ||
786 | char *s = (char*)xmalloc(strlen(cp) + 1); | ||
787 | strncpy(s, cp, strlen(cp) + 1); | ||
788 | entcmd[cur_cmd].args[arg] = s; | ||
789 | arg++; | ||
790 | } while ((arg < 5) && (cp = strtok(NULL, WHITESPACE))); | ||
791 | |||
792 | if (strtok(NULL, WHITESPACE)) | ||
793 | error("ignored extra command elements (max 5), %.100s line %d", | ||
794 | cmdfilename, linenum); | ||
795 | |||
796 | /* Copy the command path and rate estimate */ | ||
797 | entcmd[cur_cmd].path = xstrdup(path); | ||
798 | entcmd[cur_cmd].rate = est; | ||
799 | |||
800 | /* Initialise other values */ | ||
801 | entcmd[cur_cmd].sticky_badness = 1; | ||
802 | |||
803 | cur_cmd++; | ||
804 | |||
805 | /* If we've filled the array, reallocate it twice the size */ | ||
806 | /* Do this now because even if this we're on the last command, | ||
807 | we need another slot to mark the last entry */ | ||
808 | if (cur_cmd == num_cmds) { | ||
809 | num_cmds *= 2; | ||
810 | entcmd = xrealloc(entcmd, num_cmds * sizeof(entropy_source_t)); | ||
811 | } | 90 | } |
91 | |||
92 | execl(SSH_RAND_HELPER, "ssh-rand-helper", NULL); | ||
93 | fprintf(stderr, "(rand child) Couldn't exec '%s': %s\n", | ||
94 | SSH_RAND_HELPER, strerror(errno)); | ||
95 | _exit(1); | ||
812 | } | 96 | } |
813 | 97 | ||
814 | /* zero the last entry */ | 98 | close(devnull); |
815 | memset(&entcmd[cur_cmd], '\0', sizeof(entropy_source_t)); | 99 | close(p[1]); |
816 | |||
817 | /* trim to size */ | ||
818 | entropy_sources = xrealloc(entcmd, (cur_cmd+1) * sizeof(entropy_source_t)); | ||
819 | |||
820 | debug("Loaded %d entropy commands from %.100s", cur_cmd, cmdfilename); | ||
821 | |||
822 | return (cur_cmd >= MIN_ENTROPY_SOURCES); | ||
823 | } | ||
824 | |||
825 | /* | ||
826 | * Write a keyfile at exit | ||
827 | */ | ||
828 | void | ||
829 | prng_seed_cleanup(void *junk) | ||
830 | { | ||
831 | prng_write_seedfile(); | ||
832 | } | ||
833 | |||
834 | /* | ||
835 | * Conditionally Seed OpenSSL's random number pool from | ||
836 | * syscalls and program output | ||
837 | */ | ||
838 | void | ||
839 | seed_rng(void) | ||
840 | { | ||
841 | mysig_t old_sigchld_handler; | ||
842 | |||
843 | if (!prng_initialised) | ||
844 | fatal("RNG not initialised"); | ||
845 | 100 | ||
846 | /* Make sure some other sigchld handler doesn't reap our entropy */ | 101 | memset(buf, '\0', sizeof(buf)); |
847 | /* commands */ | 102 | ret = atomicio(read, p[0], buf, sizeof(buf)); |
848 | old_sigchld_handler = mysignal(SIGCHLD, SIG_DFL); | 103 | if (ret == -1) |
104 | fatal("Couldn't read from ssh-rand-helper: %s", | ||
105 | strerror(errno)); | ||
106 | if (ret != sizeof(buf)) | ||
107 | fatal("ssh-rand-helper child produced insufficient data"); | ||
849 | 108 | ||
850 | debug("Seeded RNG with %i bytes from programs", | 109 | close(p[0]); |
851 | (int)stir_from_programs()); | ||
852 | debug("Seeded RNG with %i bytes from system calls", | ||
853 | (int)stir_from_system()); | ||
854 | 110 | ||
855 | if (!RAND_status()) | 111 | if (waitpid(pid, &ret, 0) == -1) |
856 | fatal("Not enough entropy in RNG"); | 112 | fatal("Couldn't wait for ssh-rand-helper completion: %s", |
113 | strerror(errno)); | ||
857 | 114 | ||
858 | mysignal(SIGCHLD, old_sigchld_handler); | 115 | /* We don't mind if the child exits upon a SIGPIPE */ |
116 | if (!WIFEXITED(ret) && | ||
117 | (!WIFSIGNALED(ret) || WTERMSIG(ret) != SIGPIPE)) | ||
118 | fatal("ssh-rand-helper terminated abnormally"); | ||
119 | if (WEXITSTATUS(ret) != 0) | ||
120 | fatal("ssh-rand-helper exit with exit status %d", ret); | ||
859 | 121 | ||
860 | if (!RAND_status()) | 122 | RAND_add(buf, sizeof(buf), sizeof(buf)); |
861 | fatal("Couldn't initialise builtin random number generator -- exiting."); | 123 | memset(buf, '\0', sizeof(buf)); |
862 | } | 124 | } |
863 | 125 | ||
864 | void | 126 | void |
865 | init_rng(void) | 127 | init_rng(void) |
866 | { | 128 | { |
867 | int original_euid; | ||
868 | |||
869 | check_openssl_version(); | ||
870 | |||
871 | original_uid = getuid(); | ||
872 | original_euid = geteuid(); | ||
873 | |||
874 | /* Read in collection commands */ | ||
875 | if (!prng_read_commands(SSH_PRNG_COMMAND_FILE)) | ||
876 | fatal("PRNG initialisation failed -- exiting."); | ||
877 | |||
878 | /* Set ourselves up to save a seed upon exit */ | ||
879 | prng_seed_saved = 0; | ||
880 | |||
881 | /* Give up privs while reading seed file */ | ||
882 | #ifdef SAVED_IDS_WORK_WITH_SETEUID | ||
883 | if ((original_uid != original_euid) && (seteuid(original_uid) == -1)) | ||
884 | fatal("Couldn't give up privileges"); | ||
885 | #else /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
886 | /* | 129 | /* |
887 | * Propagate the privileged uid to all of our uids. | 130 | * OpenSSL version numbers: MNNFFPPS: major minor fix patch status |
888 | * Set the effective uid to the given (unprivileged) uid. | 131 | * We match major, minor, fix and status (not patch) |
889 | */ | 132 | */ |
890 | if (original_uid != original_euid && (setuid(original_euid) == -1 || | 133 | if ((SSLeay() ^ OPENSSL_VERSION_NUMBER) & ~0xff0L) |
891 | seteuid(original_uid) == -1)) | 134 | fatal("OpenSSL version mismatch. Built against %lx, you " |
892 | fatal("Couldn't give up privileges"); | 135 | "have %lx", OPENSSL_VERSION_NUMBER, SSLeay()); |
893 | #endif /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
894 | |||
895 | prng_read_seedfile(); | ||
896 | |||
897 | #ifdef SAVED_IDS_WORK_WITH_SETEUID | ||
898 | if ((original_uid != original_euid) && (seteuid(original_euid) == -1)) | ||
899 | fatal("Couldn't restore privileges"); | ||
900 | #else /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
901 | /* | ||
902 | * We are unable to restore the real uid to its unprivileged value. | ||
903 | * Propagate the real uid (usually more privileged) to effective uid | ||
904 | * as well. | ||
905 | */ | ||
906 | if (original_uid != original_euid && (seteuid(original_euid) == -1 || | ||
907 | setuid(original_uid) == -1)) | ||
908 | fatal("Couldn't restore privileges"); | ||
909 | #endif /* SAVED_IDS_WORK_WITH_SETEUID */ | ||
910 | |||
911 | fatal_add_cleanup(prng_seed_cleanup, NULL); | ||
912 | atexit(prng_write_seedfile); | ||
913 | 136 | ||
914 | prng_initialised = 1; | 137 | if ((original_uid = getuid()) == -1) |
138 | fatal("getuid: %s", strerror(errno)); | ||
139 | if ((original_euid = geteuid()) == -1) | ||
140 | fatal("geteuid: %s", strerror(errno)); | ||
915 | } | 141 | } |
916 | |||
917 | #endif /* defined(USE_PRNGD) || defined(RANDOM_POOL) */ | ||
diff --git a/pathnames.h b/pathnames.h index 1b223e392..feb2d0cf8 100644 --- a/pathnames.h +++ b/pathnames.h | |||
@@ -154,13 +154,3 @@ | |||
154 | #ifndef ASKPASS_PROGRAM | 154 | #ifndef ASKPASS_PROGRAM |
155 | #define ASKPASS_PROGRAM "/usr/lib/ssh/ssh-askpass" | 155 | #define ASKPASS_PROGRAM "/usr/lib/ssh/ssh-askpass" |
156 | #endif /* ASKPASS_PROGRAM */ | 156 | #endif /* ASKPASS_PROGRAM */ |
157 | |||
158 | /* | ||
159 | * Relevant only when using builtin PRNG. | ||
160 | */ | ||
161 | #ifndef SSH_PRNG_SEED_FILE | ||
162 | # define SSH_PRNG_SEED_FILE _PATH_SSH_USER_DIR"/prng_seed" | ||
163 | #endif /* SSH_PRNG_SEED_FILE */ | ||
164 | #ifndef SSH_PRNG_COMMAND_FILE | ||
165 | # define SSH_PRNG_COMMAND_FILE ETCDIR "/ssh_prng_cmds" | ||
166 | #endif /* SSH_PRNG_COMMAND_FILE */ | ||
diff --git a/ssh-rand-helper.c b/ssh-rand-helper.c new file mode 100644 index 000000000..5b7a9fc61 --- /dev/null +++ b/ssh-rand-helper.c | |||
@@ -0,0 +1,805 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2001 Damien Miller. All rights reserved. | ||
3 | * | ||
4 | * Redistribution and use in source and binary forms, with or without | ||
5 | * modification, are permitted provided that the following conditions | ||
6 | * are met: | ||
7 | * 1. Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * 2. Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * | ||
13 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | ||
14 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
15 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
16 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
17 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
18 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
19 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
20 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
22 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
23 | */ | ||
24 | |||
25 | #include "includes.h" | ||
26 | |||
27 | #include <openssl/rand.h> | ||
28 | #include <openssl/sha.h> | ||
29 | #include <openssl/crypto.h> | ||
30 | |||
31 | /* SunOS 4.4.4 needs this */ | ||
32 | #ifdef HAVE_FLOATINGPOINT_H | ||
33 | # include <floatingpoint.h> | ||
34 | #endif /* HAVE_FLOATINGPOINT_H */ | ||
35 | |||
36 | #include "misc.h" | ||
37 | #include "xmalloc.h" | ||
38 | #include "atomicio.h" | ||
39 | #include "pathnames.h" | ||
40 | #include "log.h" | ||
41 | |||
42 | RCSID("$Id: ssh-rand-helper.c,v 1.1 2001/12/23 14:41:48 djm Exp $"); | ||
43 | |||
44 | #define RANDOM_SEED_SIZE 48 | ||
45 | |||
46 | #ifndef SSH_PRNG_SEED_FILE | ||
47 | # define SSH_PRNG_SEED_FILE _PATH_SSH_USER_DIR"/prng_seed" | ||
48 | #endif /* SSH_PRNG_SEED_FILE */ | ||
49 | #ifndef SSH_PRNG_COMMAND_FILE | ||
50 | # define SSH_PRNG_COMMAND_FILE ETCDIR "/ssh_prng_cmds" | ||
51 | #endif /* SSH_PRNG_COMMAND_FILE */ | ||
52 | |||
53 | |||
54 | #ifndef offsetof | ||
55 | # define offsetof(type, member) ((size_t) &((type *)0)->member) | ||
56 | #endif | ||
57 | |||
58 | /* Number of times to pass through command list gathering entropy */ | ||
59 | #define NUM_ENTROPY_RUNS 1 | ||
60 | |||
61 | /* Scale entropy estimates back by this amount on subsequent runs */ | ||
62 | #define SCALE_PER_RUN 10.0 | ||
63 | |||
64 | /* Minimum number of commands to be considered valid */ | ||
65 | #define MIN_ENTROPY_SOURCES 16 | ||
66 | |||
67 | #define WHITESPACE " \t\n" | ||
68 | |||
69 | #ifndef RUSAGE_SELF | ||
70 | # define RUSAGE_SELF 0 | ||
71 | #endif | ||
72 | #ifndef RUSAGE_CHILDREN | ||
73 | # define RUSAGE_CHILDREN 0 | ||
74 | #endif | ||
75 | |||
76 | #if defined(PRNGD_SOCKET) || defined(PRNGD_PORT) | ||
77 | # define USE_PRNGD | ||
78 | #endif | ||
79 | |||
80 | #ifdef USE_PRNGD | ||
81 | /* Collect entropy from PRNGD/EGD */ | ||
82 | int | ||
83 | get_random_bytes(unsigned char *buf, int len) | ||
84 | { | ||
85 | int fd; | ||
86 | char msg[2]; | ||
87 | #ifdef PRNGD_PORT | ||
88 | struct sockaddr_in addr; | ||
89 | #else | ||
90 | struct sockaddr_un addr; | ||
91 | #endif | ||
92 | int addr_len, rval, errors; | ||
93 | mysig_t old_sigpipe; | ||
94 | |||
95 | if (len > 255) | ||
96 | fatal("Too many bytes to read from PRNGD"); | ||
97 | |||
98 | memset(&addr, '\0', sizeof(addr)); | ||
99 | |||
100 | #ifdef PRNGD_PORT | ||
101 | addr.sin_family = AF_INET; | ||
102 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
103 | addr.sin_port = htons(PRNGD_PORT); | ||
104 | addr_len = sizeof(struct sockaddr_in); | ||
105 | #else /* use IP socket PRNGD_SOCKET instead */ | ||
106 | /* Sanity checks */ | ||
107 | if (sizeof(PRNGD_SOCKET) > sizeof(addr.sun_path)) | ||
108 | fatal("Random pool path is too long"); | ||
109 | |||
110 | addr.sun_family = AF_UNIX; | ||
111 | strlcpy(addr.sun_path, PRNGD_SOCKET, sizeof(addr.sun_path)); | ||
112 | addr_len = offsetof(struct sockaddr_un, sun_path) + | ||
113 | sizeof(PRNGD_SOCKET); | ||
114 | #endif | ||
115 | |||
116 | old_sigpipe = mysignal(SIGPIPE, SIG_IGN); | ||
117 | |||
118 | errors = rval = 0; | ||
119 | reopen: | ||
120 | #ifdef PRNGD_PORT | ||
121 | fd = socket(addr.sin_family, SOCK_STREAM, 0); | ||
122 | if (fd == -1) { | ||
123 | error("Couldn't create AF_INET socket: %s", strerror(errno)); | ||
124 | goto done; | ||
125 | } | ||
126 | #else | ||
127 | fd = socket(addr.sun_family, SOCK_STREAM, 0); | ||
128 | if (fd == -1) { | ||
129 | error("Couldn't create AF_UNIX socket: %s", strerror(errno)); | ||
130 | goto done; | ||
131 | } | ||
132 | #endif | ||
133 | |||
134 | if (connect(fd, (struct sockaddr*)&addr, addr_len) == -1) { | ||
135 | #ifdef PRNGD_PORT | ||
136 | error("Couldn't connect to PRNGD port %d: %s", | ||
137 | PRNGD_PORT, strerror(errno)); | ||
138 | #else | ||
139 | error("Couldn't connect to PRNGD socket \"%s\": %s", | ||
140 | addr.sun_path, strerror(errno)); | ||
141 | #endif | ||
142 | goto done; | ||
143 | } | ||
144 | |||
145 | /* Send blocking read request to PRNGD */ | ||
146 | msg[0] = 0x02; | ||
147 | msg[1] = len; | ||
148 | |||
149 | if (atomicio(write, fd, msg, sizeof(msg)) != sizeof(msg)) { | ||
150 | if (errno == EPIPE && errors < 10) { | ||
151 | close(fd); | ||
152 | errors++; | ||
153 | goto reopen; | ||
154 | } | ||
155 | error("Couldn't write to PRNGD socket: %s", | ||
156 | strerror(errno)); | ||
157 | goto done; | ||
158 | } | ||
159 | |||
160 | if (atomicio(read, fd, buf, len) != len) { | ||
161 | if (errno == EPIPE && errors < 10) { | ||
162 | close(fd); | ||
163 | errors++; | ||
164 | goto reopen; | ||
165 | } | ||
166 | error("Couldn't read from PRNGD socket: %s", | ||
167 | strerror(errno)); | ||
168 | goto done; | ||
169 | } | ||
170 | |||
171 | rval = 1; | ||
172 | done: | ||
173 | mysignal(SIGPIPE, old_sigpipe); | ||
174 | if (fd != -1) | ||
175 | close(fd); | ||
176 | return(rval); | ||
177 | } | ||
178 | |||
179 | static void | ||
180 | seed_openssl_rng(void) | ||
181 | { | ||
182 | unsigned char buf[RANDOM_SEED_SIZE]; | ||
183 | |||
184 | if (!get_random_bytes(buf, sizeof(buf))) | ||
185 | fatal("Entropy collection failed"); | ||
186 | |||
187 | RAND_add(buf, sizeof(buf), sizeof(buf)); | ||
188 | memset(buf, '\0', sizeof(buf)); | ||
189 | } | ||
190 | |||
191 | #else /* USE_PRNGD */ | ||
192 | |||
193 | /* | ||
194 | * FIXME: proper entropy estimations. All current values are guesses | ||
195 | * FIXME: (ATL) do estimates at compile time? | ||
196 | * FIXME: More entropy sources | ||
197 | */ | ||
198 | |||
199 | /* slow command timeouts (all in milliseconds) */ | ||
200 | /* static int entropy_timeout_default = ENTROPY_TIMEOUT_MSEC; */ | ||
201 | static int entropy_timeout_current = ENTROPY_TIMEOUT_MSEC; | ||
202 | |||
203 | typedef struct | ||
204 | { | ||
205 | /* Proportion of data that is entropy */ | ||
206 | double rate; | ||
207 | /* Counter goes positive if this command times out */ | ||
208 | unsigned int badness; | ||
209 | /* Increases by factor of two each timeout */ | ||
210 | unsigned int sticky_badness; | ||
211 | /* Path to executable */ | ||
212 | char *path; | ||
213 | /* argv to pass to executable */ | ||
214 | char *args[5]; | ||
215 | /* full command string (debug) */ | ||
216 | char *cmdstring; | ||
217 | } entropy_source_t; | ||
218 | |||
219 | double stir_from_system(void); | ||
220 | double stir_from_programs(void); | ||
221 | double stir_gettimeofday(double entropy_estimate); | ||
222 | double stir_clock(double entropy_estimate); | ||
223 | double stir_rusage(int who, double entropy_estimate); | ||
224 | double hash_output_from_command(entropy_source_t *src, char *hash); | ||
225 | |||
226 | /* this is initialised from a file, by prng_read_commands() */ | ||
227 | entropy_source_t *entropy_sources = NULL; | ||
228 | |||
229 | double | ||
230 | stir_from_system(void) | ||
231 | { | ||
232 | double total_entropy_estimate; | ||
233 | long int i; | ||
234 | |||
235 | total_entropy_estimate = 0; | ||
236 | |||
237 | i = getpid(); | ||
238 | RAND_add(&i, sizeof(i), 0.5); | ||
239 | total_entropy_estimate += 0.1; | ||
240 | |||
241 | i = getppid(); | ||
242 | RAND_add(&i, sizeof(i), 0.5); | ||
243 | total_entropy_estimate += 0.1; | ||
244 | |||
245 | i = getuid(); | ||
246 | RAND_add(&i, sizeof(i), 0.0); | ||
247 | i = getgid(); | ||
248 | RAND_add(&i, sizeof(i), 0.0); | ||
249 | |||
250 | total_entropy_estimate += stir_gettimeofday(1.0); | ||
251 | total_entropy_estimate += stir_clock(0.5); | ||
252 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 2.0); | ||
253 | |||
254 | return(total_entropy_estimate); | ||
255 | } | ||
256 | |||
257 | double | ||
258 | stir_from_programs(void) | ||
259 | { | ||
260 | int i; | ||
261 | int c; | ||
262 | double entropy_estimate; | ||
263 | double total_entropy_estimate; | ||
264 | char hash[SHA_DIGEST_LENGTH]; | ||
265 | |||
266 | total_entropy_estimate = 0; | ||
267 | for(i = 0; i < NUM_ENTROPY_RUNS; i++) { | ||
268 | c = 0; | ||
269 | while (entropy_sources[c].path != NULL) { | ||
270 | |||
271 | if (!entropy_sources[c].badness) { | ||
272 | /* Hash output from command */ | ||
273 | entropy_estimate = hash_output_from_command(&entropy_sources[c], hash); | ||
274 | |||
275 | /* Scale back entropy estimate according to command's rate */ | ||
276 | entropy_estimate *= entropy_sources[c].rate; | ||
277 | |||
278 | /* Upper bound of entropy estimate is SHA_DIGEST_LENGTH */ | ||
279 | if (entropy_estimate > SHA_DIGEST_LENGTH) | ||
280 | entropy_estimate = SHA_DIGEST_LENGTH; | ||
281 | |||
282 | /* Scale back estimates for subsequent passes through list */ | ||
283 | entropy_estimate /= SCALE_PER_RUN * (i + 1.0); | ||
284 | |||
285 | /* Stir it in */ | ||
286 | RAND_add(hash, sizeof(hash), entropy_estimate); | ||
287 | |||
288 | debug3("Got %0.2f bytes of entropy from '%s'", entropy_estimate, | ||
289 | entropy_sources[c].cmdstring); | ||
290 | |||
291 | total_entropy_estimate += entropy_estimate; | ||
292 | |||
293 | /* Execution times should be a little unpredictable */ | ||
294 | total_entropy_estimate += stir_gettimeofday(0.05); | ||
295 | total_entropy_estimate += stir_clock(0.05); | ||
296 | total_entropy_estimate += stir_rusage(RUSAGE_SELF, 0.1); | ||
297 | total_entropy_estimate += stir_rusage(RUSAGE_CHILDREN, 0.1); | ||
298 | } else { | ||
299 | debug2("Command '%s' disabled (badness %d)", | ||
300 | entropy_sources[c].cmdstring, entropy_sources[c].badness); | ||
301 | |||
302 | if (entropy_sources[c].badness > 0) | ||
303 | entropy_sources[c].badness--; | ||
304 | } | ||
305 | |||
306 | c++; | ||
307 | } | ||
308 | } | ||
309 | |||
310 | return(total_entropy_estimate); | ||
311 | } | ||
312 | |||
313 | double | ||
314 | stir_gettimeofday(double entropy_estimate) | ||
315 | { | ||
316 | struct timeval tv; | ||
317 | |||
318 | if (gettimeofday(&tv, NULL) == -1) | ||
319 | fatal("Couldn't gettimeofday: %s", strerror(errno)); | ||
320 | |||
321 | RAND_add(&tv, sizeof(tv), entropy_estimate); | ||
322 | |||
323 | return(entropy_estimate); | ||
324 | } | ||
325 | |||
326 | double | ||
327 | stir_clock(double entropy_estimate) | ||
328 | { | ||
329 | #ifdef HAVE_CLOCK | ||
330 | clock_t c; | ||
331 | |||
332 | c = clock(); | ||
333 | RAND_add(&c, sizeof(c), entropy_estimate); | ||
334 | |||
335 | return(entropy_estimate); | ||
336 | #else /* _HAVE_CLOCK */ | ||
337 | return(0); | ||
338 | #endif /* _HAVE_CLOCK */ | ||
339 | } | ||
340 | |||
341 | double | ||
342 | stir_rusage(int who, double entropy_estimate) | ||
343 | { | ||
344 | #ifdef HAVE_GETRUSAGE | ||
345 | struct rusage ru; | ||
346 | |||
347 | if (getrusage(who, &ru) == -1) | ||
348 | return(0); | ||
349 | |||
350 | RAND_add(&ru, sizeof(ru), entropy_estimate); | ||
351 | |||
352 | return(entropy_estimate); | ||
353 | #else /* _HAVE_GETRUSAGE */ | ||
354 | return(0); | ||
355 | #endif /* _HAVE_GETRUSAGE */ | ||
356 | } | ||
357 | |||
358 | |||
359 | static int | ||
360 | _get_timeval_msec_difference(struct timeval *t1, struct timeval *t2) { | ||
361 | int secdiff, usecdiff; | ||
362 | |||
363 | secdiff = t2->tv_sec - t1->tv_sec; | ||
364 | usecdiff = (secdiff*1000000) + (t2->tv_usec - t1->tv_usec); | ||
365 | return (int)(usecdiff / 1000); | ||
366 | } | ||
367 | |||
368 | double | ||
369 | hash_output_from_command(entropy_source_t *src, char *hash) | ||
370 | { | ||
371 | static int devnull = -1; | ||
372 | int p[2]; | ||
373 | fd_set rdset; | ||
374 | int cmd_eof = 0, error_abort = 0; | ||
375 | struct timeval tv_start, tv_current; | ||
376 | int msec_elapsed = 0; | ||
377 | pid_t pid; | ||
378 | int status; | ||
379 | char buf[16384]; | ||
380 | int bytes_read; | ||
381 | int total_bytes_read; | ||
382 | SHA_CTX sha; | ||
383 | |||
384 | debug3("Reading output from \'%s\'", src->cmdstring); | ||
385 | |||
386 | if (devnull == -1) { | ||
387 | devnull = open("/dev/null", O_RDWR); | ||
388 | if (devnull == -1) | ||
389 | fatal("Couldn't open /dev/null: %s", strerror(errno)); | ||
390 | } | ||
391 | |||
392 | if (pipe(p) == -1) | ||
393 | fatal("Couldn't open pipe: %s", strerror(errno)); | ||
394 | |||
395 | (void)gettimeofday(&tv_start, NULL); /* record start time */ | ||
396 | |||
397 | switch (pid = fork()) { | ||
398 | case -1: /* Error */ | ||
399 | close(p[0]); | ||
400 | close(p[1]); | ||
401 | fatal("Couldn't fork: %s", strerror(errno)); | ||
402 | /* NOTREACHED */ | ||
403 | case 0: /* Child */ | ||
404 | dup2(devnull, STDIN_FILENO); | ||
405 | dup2(p[1], STDOUT_FILENO); | ||
406 | dup2(p[1], STDERR_FILENO); | ||
407 | close(p[0]); | ||
408 | close(p[1]); | ||
409 | close(devnull); | ||
410 | |||
411 | execv(src->path, (char**)(src->args)); | ||
412 | debug("(child) Couldn't exec '%s': %s", src->cmdstring, | ||
413 | strerror(errno)); | ||
414 | _exit(-1); | ||
415 | default: /* Parent */ | ||
416 | break; | ||
417 | } | ||
418 | |||
419 | RAND_add(&pid, sizeof(&pid), 0.0); | ||
420 | |||
421 | close(p[1]); | ||
422 | |||
423 | /* Hash output from child */ | ||
424 | SHA1_Init(&sha); | ||
425 | total_bytes_read = 0; | ||
426 | |||
427 | while (!error_abort && !cmd_eof) { | ||
428 | int ret; | ||
429 | struct timeval tv; | ||
430 | int msec_remaining; | ||
431 | |||
432 | (void) gettimeofday(&tv_current, 0); | ||
433 | msec_elapsed = _get_timeval_msec_difference(&tv_start, &tv_current); | ||
434 | if (msec_elapsed >= entropy_timeout_current) { | ||
435 | error_abort=1; | ||
436 | continue; | ||
437 | } | ||
438 | msec_remaining = entropy_timeout_current - msec_elapsed; | ||
439 | |||
440 | FD_ZERO(&rdset); | ||
441 | FD_SET(p[0], &rdset); | ||
442 | tv.tv_sec = msec_remaining / 1000; | ||
443 | tv.tv_usec = (msec_remaining % 1000) * 1000; | ||
444 | |||
445 | ret = select(p[0]+1, &rdset, NULL, NULL, &tv); | ||
446 | |||
447 | RAND_add(&tv, sizeof(tv), 0.0); | ||
448 | |||
449 | switch (ret) { | ||
450 | case 0: | ||
451 | /* timer expired */ | ||
452 | error_abort = 1; | ||
453 | break; | ||
454 | case 1: | ||
455 | /* command input */ | ||
456 | do { | ||
457 | bytes_read = read(p[0], buf, sizeof(buf)); | ||
458 | } while (bytes_read == -1 && errno == EINTR); | ||
459 | RAND_add(&bytes_read, sizeof(&bytes_read), 0.0); | ||
460 | if (bytes_read == -1) { | ||
461 | error_abort = 1; | ||
462 | break; | ||
463 | } else if (bytes_read) { | ||
464 | SHA1_Update(&sha, buf, bytes_read); | ||
465 | total_bytes_read += bytes_read; | ||
466 | } else { | ||
467 | cmd_eof = 1; | ||
468 | } | ||
469 | break; | ||
470 | case -1: | ||
471 | default: | ||
472 | /* error */ | ||
473 | debug("Command '%s': select() failed: %s", src->cmdstring, | ||
474 | strerror(errno)); | ||
475 | error_abort = 1; | ||
476 | break; | ||
477 | } | ||
478 | } | ||
479 | |||
480 | SHA1_Final(hash, &sha); | ||
481 | |||
482 | close(p[0]); | ||
483 | |||
484 | debug3("Time elapsed: %d msec", msec_elapsed); | ||
485 | |||
486 | if (waitpid(pid, &status, 0) == -1) { | ||
487 | error("Couldn't wait for child '%s' completion: %s", src->cmdstring, | ||
488 | strerror(errno)); | ||
489 | return(0.0); | ||
490 | } | ||
491 | |||
492 | RAND_add(&status, sizeof(&status), 0.0); | ||
493 | |||
494 | if (error_abort) { | ||
495 | /* closing p[0] on timeout causes the entropy command to | ||
496 | * SIGPIPE. Take whatever output we got, and mark this command | ||
497 | * as slow */ | ||
498 | debug2("Command '%s' timed out", src->cmdstring); | ||
499 | src->sticky_badness *= 2; | ||
500 | src->badness = src->sticky_badness; | ||
501 | return(total_bytes_read); | ||
502 | } | ||
503 | |||
504 | if (WIFEXITED(status)) { | ||
505 | if (WEXITSTATUS(status)==0) { | ||
506 | return(total_bytes_read); | ||
507 | } else { | ||
508 | debug2("Command '%s' exit status was %d", src->cmdstring, | ||
509 | WEXITSTATUS(status)); | ||
510 | src->badness = src->sticky_badness = 128; | ||
511 | return (0.0); | ||
512 | } | ||
513 | } else if (WIFSIGNALED(status)) { | ||
514 | debug2("Command '%s' returned on uncaught signal %d !", src->cmdstring, | ||
515 | status); | ||
516 | src->badness = src->sticky_badness = 128; | ||
517 | return(0.0); | ||
518 | } else | ||
519 | return(0.0); | ||
520 | } | ||
521 | |||
522 | /* | ||
523 | * prng seedfile functions | ||
524 | */ | ||
525 | int | ||
526 | prng_check_seedfile(char *filename) { | ||
527 | |||
528 | struct stat st; | ||
529 | |||
530 | /* FIXME raceable: eg replace seed between this stat and subsequent open */ | ||
531 | /* Not such a problem because we don't trust the seed file anyway */ | ||
532 | if (lstat(filename, &st) == -1) { | ||
533 | /* Give up on hard errors */ | ||
534 | if (errno != ENOENT) | ||
535 | debug("WARNING: Couldn't stat random seed file \"%s\": %s", | ||
536 | filename, strerror(errno)); | ||
537 | |||
538 | return(0); | ||
539 | } | ||
540 | |||
541 | /* regular file? */ | ||
542 | if (!S_ISREG(st.st_mode)) | ||
543 | fatal("PRNG seedfile %.100s is not a regular file", filename); | ||
544 | |||
545 | /* mode 0600, owned by root or the current user? */ | ||
546 | if (((st.st_mode & 0177) != 0) || !(st.st_uid == getuid())) { | ||
547 | debug("WARNING: PRNG seedfile %.100s must be mode 0600, owned by uid %d", | ||
548 | filename, getuid()); | ||
549 | return(0); | ||
550 | } | ||
551 | |||
552 | return(1); | ||
553 | } | ||
554 | |||
555 | void | ||
556 | prng_write_seedfile(void) { | ||
557 | int fd; | ||
558 | char seed[1024]; | ||
559 | char filename[1024]; | ||
560 | struct passwd *pw; | ||
561 | |||
562 | pw = getpwuid(getuid()); | ||
563 | if (pw == NULL) | ||
564 | fatal("Couldn't get password entry for current user (%i): %s", | ||
565 | getuid(), strerror(errno)); | ||
566 | |||
567 | /* Try to ensure that the parent directory is there */ | ||
568 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
569 | _PATH_SSH_USER_DIR); | ||
570 | mkdir(filename, 0700); | ||
571 | |||
572 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
573 | SSH_PRNG_SEED_FILE); | ||
574 | |||
575 | debug("writing PRNG seed to file %.100s", filename); | ||
576 | |||
577 | RAND_bytes(seed, sizeof(seed)); | ||
578 | |||
579 | /* Don't care if the seed doesn't exist */ | ||
580 | prng_check_seedfile(filename); | ||
581 | |||
582 | if ((fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0600)) == -1) { | ||
583 | debug("WARNING: couldn't access PRNG seedfile %.100s (%.100s)", | ||
584 | filename, strerror(errno)); | ||
585 | } else { | ||
586 | if (atomicio(write, fd, &seed, sizeof(seed)) != sizeof(seed)) | ||
587 | fatal("problem writing PRNG seedfile %.100s (%.100s)", filename, | ||
588 | strerror(errno)); | ||
589 | close(fd); | ||
590 | } | ||
591 | } | ||
592 | |||
593 | void | ||
594 | prng_read_seedfile(void) { | ||
595 | int fd; | ||
596 | char seed[1024]; | ||
597 | char filename[1024]; | ||
598 | struct passwd *pw; | ||
599 | |||
600 | pw = getpwuid(getuid()); | ||
601 | if (pw == NULL) | ||
602 | fatal("Couldn't get password entry for current user (%i): %s", | ||
603 | getuid(), strerror(errno)); | ||
604 | |||
605 | snprintf(filename, sizeof(filename), "%.512s/%s", pw->pw_dir, | ||
606 | SSH_PRNG_SEED_FILE); | ||
607 | |||
608 | debug("loading PRNG seed from file %.100s", filename); | ||
609 | |||
610 | if (!prng_check_seedfile(filename)) { | ||
611 | verbose("Random seed file not found or not valid, ignoring."); | ||
612 | return; | ||
613 | } | ||
614 | |||
615 | /* open the file and read in the seed */ | ||
616 | fd = open(filename, O_RDONLY); | ||
617 | if (fd == -1) | ||
618 | fatal("could not open PRNG seedfile %.100s (%.100s)", filename, | ||
619 | strerror(errno)); | ||
620 | |||
621 | if (atomicio(read, fd, &seed, sizeof(seed)) != sizeof(seed)) { | ||
622 | verbose("invalid or short read from PRNG seedfile %.100s - ignoring", | ||
623 | filename); | ||
624 | memset(seed, '\0', sizeof(seed)); | ||
625 | } | ||
626 | close(fd); | ||
627 | |||
628 | /* stir in the seed, with estimated entropy zero */ | ||
629 | RAND_add(&seed, sizeof(seed), 0.0); | ||
630 | } | ||
631 | |||
632 | |||
633 | /* | ||
634 | * entropy command initialisation functions | ||
635 | */ | ||
636 | int | ||
637 | prng_read_commands(char *cmdfilename) | ||
638 | { | ||
639 | FILE *f; | ||
640 | char *cp; | ||
641 | char line[1024]; | ||
642 | char cmd[1024]; | ||
643 | char path[256]; | ||
644 | int linenum; | ||
645 | int num_cmds = 64; | ||
646 | int cur_cmd = 0; | ||
647 | double est; | ||
648 | entropy_source_t *entcmd; | ||
649 | |||
650 | f = fopen(cmdfilename, "r"); | ||
651 | if (!f) { | ||
652 | fatal("couldn't read entropy commands file %.100s: %.100s", | ||
653 | cmdfilename, strerror(errno)); | ||
654 | } | ||
655 | |||
656 | entcmd = (entropy_source_t *)xmalloc(num_cmds * sizeof(entropy_source_t)); | ||
657 | memset(entcmd, '\0', num_cmds * sizeof(entropy_source_t)); | ||
658 | |||
659 | /* Read in file */ | ||
660 | linenum = 0; | ||
661 | while (fgets(line, sizeof(line), f)) { | ||
662 | int arg; | ||
663 | char *argv; | ||
664 | |||
665 | linenum++; | ||
666 | |||
667 | /* skip leading whitespace, test for blank line or comment */ | ||
668 | cp = line + strspn(line, WHITESPACE); | ||
669 | if ((*cp == 0) || (*cp == '#')) | ||
670 | continue; /* done with this line */ | ||
671 | |||
672 | /* First non-whitespace char should be double quote delimiting */ | ||
673 | /* commandline */ | ||
674 | if (*cp != '"') { | ||
675 | error("bad entropy command, %.100s line %d", cmdfilename, | ||
676 | linenum); | ||
677 | continue; | ||
678 | } | ||
679 | |||
680 | /* first token, command args (incl. argv[0]) in double quotes */ | ||
681 | cp = strtok(cp, "\""); | ||
682 | if (cp == NULL) { | ||
683 | error("missing or bad command string, %.100s line %d -- ignored", | ||
684 | cmdfilename, linenum); | ||
685 | continue; | ||
686 | } | ||
687 | strlcpy(cmd, cp, sizeof(cmd)); | ||
688 | |||
689 | /* second token, full command path */ | ||
690 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | ||
691 | error("missing command path, %.100s line %d -- ignored", | ||
692 | cmdfilename, linenum); | ||
693 | continue; | ||
694 | } | ||
695 | |||
696 | /* did configure mark this as dead? */ | ||
697 | if (strncmp("undef", cp, 5) == 0) | ||
698 | continue; | ||
699 | |||
700 | strlcpy(path, cp, sizeof(path)); | ||
701 | |||
702 | /* third token, entropy rate estimate for this command */ | ||
703 | if ((cp = strtok(NULL, WHITESPACE)) == NULL) { | ||
704 | error("missing entropy estimate, %.100s line %d -- ignored", | ||
705 | cmdfilename, linenum); | ||
706 | continue; | ||
707 | } | ||
708 | est = strtod(cp, &argv); | ||
709 | |||
710 | /* end of line */ | ||
711 | if ((cp = strtok(NULL, WHITESPACE)) != NULL) { | ||
712 | error("garbage at end of line %d in %.100s -- ignored", linenum, | ||
713 | cmdfilename); | ||
714 | continue; | ||
715 | } | ||
716 | |||
717 | /* save the command for debug messages */ | ||
718 | entcmd[cur_cmd].cmdstring = xstrdup(cmd); | ||
719 | |||
720 | /* split the command args */ | ||
721 | cp = strtok(cmd, WHITESPACE); | ||
722 | arg = 0; | ||
723 | argv = NULL; | ||
724 | do { | ||
725 | char *s = (char*)xmalloc(strlen(cp) + 1); | ||
726 | strncpy(s, cp, strlen(cp) + 1); | ||
727 | entcmd[cur_cmd].args[arg] = s; | ||
728 | arg++; | ||
729 | } while ((arg < 5) && (cp = strtok(NULL, WHITESPACE))); | ||
730 | |||
731 | if (strtok(NULL, WHITESPACE)) | ||
732 | error("ignored extra command elements (max 5), %.100s line %d", | ||
733 | cmdfilename, linenum); | ||
734 | |||
735 | /* Copy the command path and rate estimate */ | ||
736 | entcmd[cur_cmd].path = xstrdup(path); | ||
737 | entcmd[cur_cmd].rate = est; | ||
738 | |||
739 | /* Initialise other values */ | ||
740 | entcmd[cur_cmd].sticky_badness = 1; | ||
741 | |||
742 | cur_cmd++; | ||
743 | |||
744 | /* If we've filled the array, reallocate it twice the size */ | ||
745 | /* Do this now because even if this we're on the last command, | ||
746 | we need another slot to mark the last entry */ | ||
747 | if (cur_cmd == num_cmds) { | ||
748 | num_cmds *= 2; | ||
749 | entcmd = xrealloc(entcmd, num_cmds * sizeof(entropy_source_t)); | ||
750 | } | ||
751 | } | ||
752 | |||
753 | /* zero the last entry */ | ||
754 | memset(&entcmd[cur_cmd], '\0', sizeof(entropy_source_t)); | ||
755 | |||
756 | /* trim to size */ | ||
757 | entropy_sources = xrealloc(entcmd, (cur_cmd+1) * sizeof(entropy_source_t)); | ||
758 | |||
759 | debug("Loaded %d entropy commands from %.100s", cur_cmd, cmdfilename); | ||
760 | |||
761 | return (cur_cmd >= MIN_ENTROPY_SOURCES); | ||
762 | } | ||
763 | |||
764 | static void | ||
765 | seed_openssl_rng(void) | ||
766 | { | ||
767 | /* Read in collection commands */ | ||
768 | if (!prng_read_commands(SSH_PRNG_COMMAND_FILE)) | ||
769 | fatal("PRNG initialisation failed -- exiting."); | ||
770 | |||
771 | prng_read_seedfile(); | ||
772 | |||
773 | debug("Seeded RNG with %i bytes from programs", | ||
774 | (int)stir_from_programs()); | ||
775 | debug("Seeded RNG with %i bytes from system calls", | ||
776 | (int)stir_from_system()); | ||
777 | |||
778 | prng_write_seedfile(); | ||
779 | } | ||
780 | |||
781 | #endif /* USE_PRNGD */ | ||
782 | |||
783 | int | ||
784 | main(int argc, char **argv) | ||
785 | { | ||
786 | unsigned char buf[48]; | ||
787 | int ret; | ||
788 | |||
789 | /* XXX: need some debugging mode */ | ||
790 | log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1); | ||
791 | |||
792 | seed_openssl_rng(); | ||
793 | |||
794 | if (!RAND_status()) | ||
795 | fatal("Not enough entropy in RNG"); | ||
796 | |||
797 | RAND_bytes(buf, sizeof(buf)); | ||
798 | |||
799 | ret = atomicio(write, STDOUT_FILENO, buf, sizeof(buf)); | ||
800 | |||
801 | memset(buf, '\0', sizeof(buf)); | ||
802 | |||
803 | return ret == sizeof(buf) ? 0 : 1; | ||
804 | } | ||
805 | |||