summaryrefslogtreecommitdiff
path: root/debian/patches/ssh-vulnkey.patch
diff options
context:
space:
mode:
authorColin Watson <cjwatson@debian.org>2014-02-09 23:45:24 +0000
committerColin Watson <cjwatson@debian.org>2014-02-09 23:47:26 +0000
commitd62fa90d496ae9532d8c1426b177e12d3c5ac03b (patch)
tree3179fea9631a318c8a0782dedc7cd690f201af69 /debian/patches/ssh-vulnkey.patch
parentd26565af8589d88f824b26f31da493f1056efcf4 (diff)
parentb65a0ded7a8cfe7d351e28266d7851216d679e05 (diff)
Drop ssh-vulnkey
Drop ssh-vulnkey and the associated ssh/ssh-add/sshd integration code, leaving only basic configuration file compatibility, since it has been nearly six years since the original vulnerability and this code is not likely to be of much value any more. See https://lists.debian.org/debian-devel/2013/09/msg00240.html for my full reasoning.
Diffstat (limited to 'debian/patches/ssh-vulnkey.patch')
-rw-r--r--debian/patches/ssh-vulnkey.patch1419
1 files changed, 0 insertions, 1419 deletions
diff --git a/debian/patches/ssh-vulnkey.patch b/debian/patches/ssh-vulnkey.patch
deleted file mode 100644
index ae262083d..000000000
--- a/debian/patches/ssh-vulnkey.patch
+++ /dev/null
@@ -1,1419 +0,0 @@
1From 8909ff0e3cd07d1b042d1be1c8b8828dbf6c9a83 Mon Sep 17 00:00:00 2001
2From: Colin Watson <cjwatson@ubuntu.com>
3Date: Sun, 9 Feb 2014 16:09:50 +0000
4Subject: Reject vulnerable keys to mitigate Debian OpenSSL flaw
5
6In 2008, Debian (and derived distributions such as Ubuntu) shipped an
7OpenSSL package with a flawed random number generator, causing OpenSSH to
8generate only a very limited set of keys which were subject to private half
9precomputation. To mitigate this, this patch checks key authentications
10against a blacklist of known-vulnerable keys, and adds a new ssh-vulnkey
11program which can be used to explicitly check keys against that blacklist.
12See CVE-2008-0166.
13
14Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1469
15Last-Update: 2013-09-14
16
17Patch-Name: ssh-vulnkey.patch
18---
19 Makefile.in | 17 ++-
20 auth-rh-rsa.c | 2 +-
21 auth-rsa.c | 2 +-
22 auth.c | 27 +++-
23 auth.h | 2 +-
24 auth2-hostbased.c | 2 +-
25 auth2-pubkey.c | 5 +-
26 authfile.c | 136 +++++++++++++++++++
27 authfile.h | 2 +
28 pathnames.h | 7 +
29 readconf.c | 9 ++
30 readconf.h | 1 +
31 servconf.c | 11 +-
32 servconf.h | 1 +
33 ssh-add.1 | 5 +
34 ssh-add.c | 10 +-
35 ssh-keygen.1 | 1 +
36 ssh-vulnkey.1 | 242 ++++++++++++++++++++++++++++++++++
37 ssh-vulnkey.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
38 ssh.1 | 1 +
39 ssh.c | 18 ++-
40 ssh_config.5 | 17 +++
41 sshconnect2.c | 4 +-
42 sshd.8 | 1 +
43 sshd.c | 5 +
44 sshd_config.5 | 14 ++
45 26 files changed, 913 insertions(+), 15 deletions(-)
46 create mode 100644 ssh-vulnkey.1
47 create mode 100644 ssh-vulnkey.c
48
49diff --git a/Makefile.in b/Makefile.in
50index f979926..b8f5099 100644
51--- a/Makefile.in
52+++ b/Makefile.in
53@@ -26,6 +26,7 @@ ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
54 SFTP_SERVER=$(libexecdir)/sftp-server
55 SSH_KEYSIGN=$(libexecdir)/ssh-keysign
56 SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
57+SSH_DATADIR=$(datadir)/ssh
58 PRIVSEP_PATH=@PRIVSEP_PATH@
59 SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@
60 STRIP_OPT=@STRIP_OPT@
61@@ -37,7 +38,8 @@ PATHS= -DSSHDIR=\"$(sysconfdir)\" \
62 -D_PATH_SSH_KEY_SIGN=\"$(SSH_KEYSIGN)\" \
63 -D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \
64 -D_PATH_SSH_PIDDIR=\"$(piddir)\" \
65- -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\"
66+ -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \
67+ -D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\"
68
69 CC=@CC@
70 LD=@LD@
71@@ -61,7 +63,7 @@ LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@
72 EXEEXT=@EXEEXT@
73 MANFMT=@MANFMT@
74
75-TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT)
76+TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-vulnkey$(EXEEXT)
77
78 LIBSSH_OBJS=authfd.o authfile.o bufaux.o bufbn.o buffer.o \
79 canohost.o channels.o cipher.o cipher-aes.o \
80@@ -96,8 +98,8 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \
81 sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
82 sandbox-seccomp-filter.o
83
84-MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-keysign.8.out ssh-pkcs11-helper.8.out sshd_config.5.out ssh_config.5.out
85-MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-keysign.8 ssh-pkcs11-helper.8 sshd_config.5 ssh_config.5
86+MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-keysign.8.out ssh-pkcs11-helper.8.out ssh-vulnkey.1.out sshd_config.5.out ssh_config.5.out
87+MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-keysign.8 ssh-pkcs11-helper.8 ssh-vulnkey.1 sshd_config.5 ssh_config.5
88 MANTYPE = @MANTYPE@
89
90 CONFIGFILES=sshd_config.out ssh_config.out moduli.out
91@@ -176,6 +178,9 @@ sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o s
92 sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o
93 $(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT)
94
95+ssh-vulnkey$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-vulnkey.o
96+ $(LD) -o $@ ssh-vulnkey.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
97+
98 # test driver for the loginrec code - not built by default
99 logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o
100 $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS)
101@@ -272,6 +277,7 @@ install-files:
102 $(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
103 $(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
104 $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
105+ $(INSTALL) -m 0755 $(STRIP_OPT) ssh-vulnkey$(EXEEXT) $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
106 $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
107 $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
108 $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
109@@ -286,6 +292,7 @@ install-files:
110 $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
111 $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
112 $(INSTALL) -m 644 ssh-pkcs11-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-pkcs11-helper.8
113+ $(INSTALL) -m 644 ssh-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
114 -rm -f $(DESTDIR)$(bindir)/slogin
115 ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin
116 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1
117@@ -367,6 +374,7 @@ uninstall:
118 -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT)
119 -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT)
120 -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
121+ -rm -f $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
122 -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT)
123 -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
124 -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
125@@ -379,6 +387,7 @@ uninstall:
126 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1
127 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1
128 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1
129+ -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
130 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8
131 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
132 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
133diff --git a/auth-rh-rsa.c b/auth-rh-rsa.c
134index b21a0f4..891ec32 100644
135--- a/auth-rh-rsa.c
136+++ b/auth-rh-rsa.c
137@@ -44,7 +44,7 @@ auth_rhosts_rsa_key_allowed(struct passwd *pw, char *cuser, char *chost,
138 {
139 HostStatus host_status;
140
141- if (auth_key_is_revoked(client_host_key))
142+ if (auth_key_is_revoked(client_host_key, 0))
143 return 0;
144
145 /* Check if we would accept it using rhosts authentication. */
146diff --git a/auth-rsa.c b/auth-rsa.c
147index 545aa49..6ed152c 100644
148--- a/auth-rsa.c
149+++ b/auth-rsa.c
150@@ -237,7 +237,7 @@ rsa_key_allowed_in_file(struct passwd *pw, char *file,
151 free(fp);
152
153 /* Never accept a revoked key */
154- if (auth_key_is_revoked(key))
155+ if (auth_key_is_revoked(key, 0))
156 break;
157
158 /* We have found the desired key. */
159diff --git a/auth.c b/auth.c
160index 9a36f1d..6662e9a 100644
161--- a/auth.c
162+++ b/auth.c
163@@ -59,6 +59,7 @@
164 #include "servconf.h"
165 #include "key.h"
166 #include "hostfile.h"
167+#include "authfile.h"
168 #include "auth.h"
169 #include "auth-options.h"
170 #include "canohost.h"
171@@ -657,10 +658,34 @@ getpwnamallow(const char *user)
172
173 /* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */
174 int
175-auth_key_is_revoked(Key *key)
176+auth_key_is_revoked(Key *key, int hostkey)
177 {
178 char *key_fp;
179
180+ if (blacklisted_key(key, &key_fp) == 1) {
181+ if (options.permit_blacklisted_keys) {
182+ if (hostkey)
183+ error("Host key %s blacklisted (see "
184+ "ssh-vulnkey(1)); continuing anyway",
185+ key_fp);
186+ else
187+ logit("Public key %s from %s blacklisted (see "
188+ "ssh-vulnkey(1)); continuing anyway",
189+ key_fp, get_remote_ipaddr());
190+ free(key_fp);
191+ } else {
192+ if (hostkey)
193+ error("Host key %s blacklisted (see "
194+ "ssh-vulnkey(1))", key_fp);
195+ else
196+ logit("Public key %s from %s blacklisted (see "
197+ "ssh-vulnkey(1))",
198+ key_fp, get_remote_ipaddr());
199+ free(key_fp);
200+ return 1;
201+ }
202+ }
203+
204 if (options.revoked_keys_file == NULL)
205 return 0;
206 switch (ssh_krl_file_contains_key(options.revoked_keys_file, key)) {
207diff --git a/auth.h b/auth.h
208index 5b6824f..ec95460 100644
209--- a/auth.h
210+++ b/auth.h
211@@ -191,7 +191,7 @@ char *authorized_principals_file(struct passwd *);
212
213 FILE *auth_openkeyfile(const char *, struct passwd *, int);
214 FILE *auth_openprincipals(const char *, struct passwd *, int);
215-int auth_key_is_revoked(Key *);
216+int auth_key_is_revoked(Key *, int);
217
218 HostStatus
219 check_key_in_hostfiles(struct passwd *, Key *, const char *,
220diff --git a/auth2-hostbased.c b/auth2-hostbased.c
221index a344dcc..3a17f1b 100644
222--- a/auth2-hostbased.c
223+++ b/auth2-hostbased.c
224@@ -150,7 +150,7 @@ hostbased_key_allowed(struct passwd *pw, const char *cuser, char *chost,
225 int len;
226 char *fp;
227
228- if (auth_key_is_revoked(key))
229+ if (auth_key_is_revoked(key, 0))
230 return 0;
231
232 resolvedname = get_canonical_hostname(options.use_dns);
233diff --git a/auth2-pubkey.c b/auth2-pubkey.c
234index 2b3ecb1..12eb8a6 100644
235--- a/auth2-pubkey.c
236+++ b/auth2-pubkey.c
237@@ -647,9 +647,10 @@ user_key_allowed(struct passwd *pw, Key *key)
238 u_int success, i;
239 char *file;
240
241- if (auth_key_is_revoked(key))
242+ if (auth_key_is_revoked(key, 0))
243 return 0;
244- if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key))
245+ if (key_is_cert(key) &&
246+ auth_key_is_revoked(key->cert->signature_key, 0))
247 return 0;
248
249 success = user_cert_trusted_ca(pw, key);
250diff --git a/authfile.c b/authfile.c
251index 63ae16b..9833591 100644
252--- a/authfile.c
253+++ b/authfile.c
254@@ -68,6 +68,7 @@
255 #include "rsa.h"
256 #include "misc.h"
257 #include "atomicio.h"
258+#include "pathnames.h"
259
260 #define MAX_KEY_FILE_SIZE (1024 * 1024)
261
262@@ -944,3 +945,138 @@ key_in_file(Key *key, const char *filename, int strict_type)
263 return ret;
264 }
265
266+/* Scan a blacklist of known-vulnerable keys in blacklist_file. */
267+static int
268+blacklisted_key_in_file(Key *key, const char *blacklist_file, char **fp)
269+{
270+ int fd = -1;
271+ char *dgst_hex = NULL;
272+ char *dgst_packed = NULL, *p;
273+ int i;
274+ size_t line_len;
275+ struct stat st;
276+ char buf[256];
277+ off_t start, lower, upper;
278+ int ret = 0;
279+
280+ debug("Checking blacklist file %s", blacklist_file);
281+ fd = open(blacklist_file, O_RDONLY);
282+ if (fd < 0) {
283+ ret = -1;
284+ goto out;
285+ }
286+
287+ dgst_hex = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
288+ /* Remove all colons */
289+ dgst_packed = xcalloc(1, strlen(dgst_hex) + 1);
290+ for (i = 0, p = dgst_packed; dgst_hex[i]; i++)
291+ if (dgst_hex[i] != ':')
292+ *p++ = dgst_hex[i];
293+ /* Only compare least-significant 80 bits (to keep the blacklist
294+ * size down)
295+ */
296+ line_len = strlen(dgst_packed + 12);
297+ if (line_len > 32)
298+ goto out;
299+
300+ /* Skip leading comments */
301+ start = 0;
302+ for (;;) {
303+ ssize_t r;
304+ char *newline;
305+
306+ r = atomicio(read, fd, buf, sizeof(buf));
307+ if (r <= 0)
308+ goto out;
309+ if (buf[0] != '#')
310+ break;
311+
312+ newline = memchr(buf, '\n', sizeof(buf));
313+ if (!newline)
314+ goto out;
315+ start += newline + 1 - buf;
316+ if (lseek(fd, start, SEEK_SET) < 0)
317+ goto out;
318+ }
319+
320+ /* Initialise binary search record numbers */
321+ if (fstat(fd, &st) < 0)
322+ goto out;
323+ lower = 0;
324+ upper = (st.st_size - start) / (line_len + 1);
325+
326+ while (lower != upper) {
327+ off_t cur;
328+ int cmp;
329+
330+ cur = lower + (upper - lower) / 2;
331+
332+ /* Read this line and compare to digest; this is
333+ * overflow-safe since cur < max(off_t) / (line_len + 1) */
334+ if (lseek(fd, start + cur * (line_len + 1), SEEK_SET) < 0)
335+ break;
336+ if (atomicio(read, fd, buf, line_len) != line_len)
337+ break;
338+ cmp = memcmp(buf, dgst_packed + 12, line_len);
339+ if (cmp < 0) {
340+ if (cur == lower)
341+ break;
342+ lower = cur;
343+ } else if (cmp > 0) {
344+ if (cur == upper)
345+ break;
346+ upper = cur;
347+ } else {
348+ debug("Found %s in blacklist", dgst_hex);
349+ ret = 1;
350+ break;
351+ }
352+ }
353+
354+out:
355+ free(dgst_packed);
356+ if (ret != 1 && dgst_hex) {
357+ free(dgst_hex);
358+ dgst_hex = NULL;
359+ }
360+ if (fp)
361+ *fp = dgst_hex;
362+ if (fd >= 0)
363+ close(fd);
364+ return ret;
365+}
366+
367+/*
368+ * Scan blacklists of known-vulnerable keys. If a vulnerable key is found,
369+ * its fingerprint is returned in *fp, unless fp is NULL.
370+ */
371+int
372+blacklisted_key(Key *key, char **fp)
373+{
374+ Key *public;
375+ char *blacklist_file;
376+ int ret, ret2;
377+
378+ public = key_demote(key);
379+ if (public->type == KEY_RSA1)
380+ public->type = KEY_RSA;
381+
382+ xasprintf(&blacklist_file, "%s.%s-%u",
383+ _PATH_BLACKLIST, key_type(public), key_size(public));
384+ ret = blacklisted_key_in_file(public, blacklist_file, fp);
385+ free(blacklist_file);
386+ if (ret > 0) {
387+ key_free(public);
388+ return ret;
389+ }
390+
391+ xasprintf(&blacklist_file, "%s.%s-%u",
392+ _PATH_BLACKLIST_CONFIG, key_type(public), key_size(public));
393+ ret2 = blacklisted_key_in_file(public, blacklist_file, fp);
394+ free(blacklist_file);
395+ if (ret2 > ret)
396+ ret = ret2;
397+
398+ key_free(public);
399+ return ret;
400+}
401diff --git a/authfile.h b/authfile.h
402index 78349be..3f2bdcb 100644
403--- a/authfile.h
404+++ b/authfile.h
405@@ -28,4 +28,6 @@ Key *key_load_private_pem(int, int, const char *, char **);
406 int key_perm_ok(int, const char *);
407 int key_in_file(Key *, const char *, int);
408
409+int blacklisted_key(Key *key, char **fp);
410+
411 #endif
412diff --git a/pathnames.h b/pathnames.h
413index 5027fba..47f7867 100644
414--- a/pathnames.h
415+++ b/pathnames.h
416@@ -18,6 +18,10 @@
417 #define SSHDIR ETCDIR "/ssh"
418 #endif
419
420+#ifndef _PATH_SSH_DATADIR
421+#define _PATH_SSH_DATADIR "/usr/share/ssh"
422+#endif
423+
424 #ifndef _PATH_SSH_PIDDIR
425 #define _PATH_SSH_PIDDIR "/var/run"
426 #endif
427@@ -44,6 +48,9 @@
428 /* Backwards compatibility */
429 #define _PATH_DH_PRIMES SSHDIR "/primes"
430
431+#define _PATH_BLACKLIST _PATH_SSH_DATADIR "/blacklist"
432+#define _PATH_BLACKLIST_CONFIG SSHDIR "/blacklist"
433+
434 #ifndef _PATH_SSH_PROGRAM
435 #define _PATH_SSH_PROGRAM "/usr/bin/ssh"
436 #endif
437diff --git a/readconf.c b/readconf.c
438index 2695fd6..22e5a3a 100644
439--- a/readconf.c
440+++ b/readconf.c
441@@ -128,6 +128,7 @@ typedef enum {
442 oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
443 oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
444 oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
445+ oUseBlacklistedKeys,
446 oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
447 oClearAllForwardings, oNoHostAuthenticationForLocalhost,
448 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
449@@ -161,6 +162,7 @@ static struct {
450 { "passwordauthentication", oPasswordAuthentication },
451 { "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
452 { "kbdinteractivedevices", oKbdInteractiveDevices },
453+ { "useblacklistedkeys", oUseBlacklistedKeys },
454 { "rsaauthentication", oRSAAuthentication },
455 { "pubkeyauthentication", oPubkeyAuthentication },
456 { "dsaauthentication", oPubkeyAuthentication }, /* alias */
457@@ -523,6 +525,10 @@ parse_flag:
458 intptr = &options->challenge_response_authentication;
459 goto parse_flag;
460
461+ case oUseBlacklistedKeys:
462+ intptr = &options->use_blacklisted_keys;
463+ goto parse_flag;
464+
465 case oGssAuthentication:
466 intptr = &options->gss_authentication;
467 goto parse_flag;
468@@ -1210,6 +1216,7 @@ initialize_options(Options * options)
469 options->kbd_interactive_devices = NULL;
470 options->rhosts_rsa_authentication = -1;
471 options->hostbased_authentication = -1;
472+ options->use_blacklisted_keys = -1;
473 options->batch_mode = -1;
474 options->check_host_ip = -1;
475 options->strict_host_key_checking = -1;
476@@ -1320,6 +1327,8 @@ fill_default_options(Options * options)
477 options->rhosts_rsa_authentication = 0;
478 if (options->hostbased_authentication == -1)
479 options->hostbased_authentication = 0;
480+ if (options->use_blacklisted_keys == -1)
481+ options->use_blacklisted_keys = 0;
482 if (options->batch_mode == -1)
483 options->batch_mode = 0;
484 if (options->check_host_ip == -1)
485diff --git a/readconf.h b/readconf.h
486index 675b35d..a508151 100644
487--- a/readconf.h
488+++ b/readconf.h
489@@ -59,6 +59,7 @@ typedef struct {
490 int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
491 char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */
492 int zero_knowledge_password_authentication; /* Try jpake */
493+ int use_blacklisted_keys; /* If true, send */
494 int batch_mode; /* Batch mode: do not ask for passwords. */
495 int check_host_ip; /* Also keep track of keys for IP address */
496 int strict_host_key_checking; /* Strict host key checking. */
497diff --git a/servconf.c b/servconf.c
498index c938ae3..9155a8b 100644
499--- a/servconf.c
500+++ b/servconf.c
501@@ -114,6 +114,7 @@ initialize_server_options(ServerOptions *options)
502 options->password_authentication = -1;
503 options->kbd_interactive_authentication = -1;
504 options->challenge_response_authentication = -1;
505+ options->permit_blacklisted_keys = -1;
506 options->permit_empty_passwd = -1;
507 options->permit_user_env = -1;
508 options->use_login = -1;
509@@ -257,6 +258,8 @@ fill_default_server_options(ServerOptions *options)
510 options->kbd_interactive_authentication = 0;
511 if (options->challenge_response_authentication == -1)
512 options->challenge_response_authentication = 1;
513+ if (options->permit_blacklisted_keys == -1)
514+ options->permit_blacklisted_keys = 0;
515 if (options->permit_empty_passwd == -1)
516 options->permit_empty_passwd = 0;
517 if (options->permit_user_env == -1)
518@@ -338,7 +341,7 @@ typedef enum {
519 sListenAddress, sAddressFamily,
520 sPrintMotd, sPrintLastLog, sIgnoreRhosts,
521 sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
522- sStrictModes, sEmptyPasswd, sTCPKeepAlive,
523+ sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive,
524 sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
525 sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
526 sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
527@@ -451,6 +454,7 @@ static struct {
528 { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
529 { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
530 { "strictmodes", sStrictModes, SSHCFG_GLOBAL },
531+ { "permitblacklistedkeys", sPermitBlacklistedKeys, SSHCFG_GLOBAL },
532 { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL },
533 { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL },
534 { "uselogin", sUseLogin, SSHCFG_GLOBAL },
535@@ -1158,6 +1162,10 @@ process_server_config_line(ServerOptions *options, char *line,
536 intptr = &options->tcp_keep_alive;
537 goto parse_flag;
538
539+ case sPermitBlacklistedKeys:
540+ intptr = &options->permit_blacklisted_keys;
541+ goto parse_flag;
542+
543 case sEmptyPasswd:
544 intptr = &options->permit_empty_passwd;
545 goto parse_flag;
546@@ -2036,6 +2044,7 @@ dump_config(ServerOptions *o)
547 dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost);
548 dump_cfg_fmtint(sStrictModes, o->strict_modes);
549 dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive);
550+ dump_cfg_fmtint(sPermitBlacklistedKeys, o->permit_blacklisted_keys);
551 dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd);
552 dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env);
553 dump_cfg_fmtint(sUseLogin, o->use_login);
554diff --git a/servconf.h b/servconf.h
555index ab6e346..f655c5b 100644
556--- a/servconf.h
557+++ b/servconf.h
558@@ -121,6 +121,7 @@ typedef struct {
559 int challenge_response_authentication;
560 int zero_knowledge_password_authentication;
561 /* If true, permit jpake auth */
562+ int permit_blacklisted_keys; /* If true, permit */
563 int permit_empty_passwd; /* If false, do not permit empty
564 * passwords. */
565 int permit_user_env; /* If true, read ~/.ssh/environment */
566diff --git a/ssh-add.1 b/ssh-add.1
567index 44846b6..d394b26 100644
568--- a/ssh-add.1
569+++ b/ssh-add.1
570@@ -81,6 +81,10 @@ environment variable must contain the name of its socket for
571 .Nm
572 to work.
573 .Pp
574+Any keys recorded in the blacklist of known-compromised keys (see
575+.Xr ssh-vulnkey 1 )
576+will be refused.
577+.Pp
578 The options are as follows:
579 .Bl -tag -width Ds
580 .It Fl c
581@@ -186,6 +190,7 @@ is unable to contact the authentication agent.
582 .Xr ssh 1 ,
583 .Xr ssh-agent 1 ,
584 .Xr ssh-keygen 1 ,
585+.Xr ssh-vulnkey 1 ,
586 .Xr sshd 8
587 .Sh AUTHORS
588 OpenSSH is a derivative of the original and free
589diff --git a/ssh-add.c b/ssh-add.c
590index 5e8166f..b309582 100644
591--- a/ssh-add.c
592+++ b/ssh-add.c
593@@ -167,7 +167,7 @@ static int
594 add_file(AuthenticationConnection *ac, const char *filename, int key_only)
595 {
596 Key *private, *cert;
597- char *comment = NULL;
598+ char *comment = NULL, *fp;
599 char msg[1024], *certpath = NULL;
600 int fd, perms_ok, ret = -1;
601 Buffer keyblob;
602@@ -243,6 +243,14 @@ add_file(AuthenticationConnection *ac, const char *filename, int key_only)
603 } else {
604 fprintf(stderr, "Could not add identity: %s\n", filename);
605 }
606+ if (blacklisted_key(private, &fp) == 1) {
607+ fprintf(stderr, "Public key %s blacklisted (see "
608+ "ssh-vulnkey(1)); refusing to add it\n", fp);
609+ free(fp);
610+ key_free(private);
611+ free(comment);
612+ return -1;
613+ }
614
615 /* Skip trying to load the cert if requested */
616 if (key_only)
617diff --git a/ssh-keygen.1 b/ssh-keygen.1
618index 0d55854..144be7d 100644
619--- a/ssh-keygen.1
620+++ b/ssh-keygen.1
621@@ -809,6 +809,7 @@ The file format is described in
622 .Xr ssh 1 ,
623 .Xr ssh-add 1 ,
624 .Xr ssh-agent 1 ,
625+.Xr ssh-vulnkey 1 ,
626 .Xr moduli 5 ,
627 .Xr sshd 8
628 .Rs
629diff --git a/ssh-vulnkey.1 b/ssh-vulnkey.1
630new file mode 100644
631index 0000000..bcb9d31
632--- /dev/null
633+++ b/ssh-vulnkey.1
634@@ -0,0 +1,242 @@
635+.\" Copyright (c) 2008 Canonical Ltd. All rights reserved.
636+.\"
637+.\" Redistribution and use in source and binary forms, with or without
638+.\" modification, are permitted provided that the following conditions
639+.\" are met:
640+.\" 1. Redistributions of source code must retain the above copyright
641+.\" notice, this list of conditions and the following disclaimer.
642+.\" 2. Redistributions in binary form must reproduce the above copyright
643+.\" notice, this list of conditions and the following disclaimer in the
644+.\" documentation and/or other materials provided with the distribution.
645+.\"
646+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
647+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
648+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
649+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
650+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
651+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
652+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
653+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
654+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
655+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
656+.\"
657+.Dd $Mdocdate: May 12 2008 $
658+.Dt SSH-VULNKEY 1
659+.Os
660+.Sh NAME
661+.Nm ssh-vulnkey
662+.Nd check blacklist of compromised keys
663+.Sh SYNOPSIS
664+.Nm
665+.Op Fl q | Fl v
666+.Ar file ...
667+.Nm
668+.Fl a
669+.Sh DESCRIPTION
670+.Nm
671+checks a key against a blacklist of compromised keys.
672+.Pp
673+A substantial number of keys are known to have been generated using a broken
674+version of OpenSSL distributed by Debian which failed to seed its random
675+number generator correctly.
676+Keys generated using these OpenSSL versions should be assumed to be
677+compromised.
678+This tool may be useful in checking for such keys.
679+.Pp
680+Keys that are compromised cannot be repaired; replacements must be generated
681+using
682+.Xr ssh-keygen 1 .
683+Make sure to update
684+.Pa authorized_keys
685+files on all systems where compromised keys were permitted to authenticate.
686+.Pp
687+The argument list will be interpreted as a list of paths to public key files
688+or
689+.Pa authorized_keys
690+files.
691+If no suitable file is found at a given path,
692+.Nm
693+will append
694+.Pa .pub
695+and retry, in case it was given a private key file.
696+If no files are given as arguments,
697+.Nm
698+will check
699+.Pa ~/.ssh/id_rsa ,
700+.Pa ~/.ssh/id_dsa ,
701+.Pa ~/.ssh/identity ,
702+.Pa ~/.ssh/authorized_keys
703+and
704+.Pa ~/.ssh/authorized_keys2 ,
705+as well as the system's host keys if readable.
706+.Pp
707+If
708+.Dq -
709+is given as an argument,
710+.Nm
711+will read from standard input.
712+This can be used to process output from
713+.Xr ssh-keyscan 1 ,
714+for example:
715+.Pp
716+.Dl $ ssh-keyscan -t rsa remote.example.org | ssh-vulnkey -
717+.Pp
718+Unless the
719+.Cm PermitBlacklistedKeys
720+option is used,
721+.Xr sshd 8
722+will reject attempts to authenticate with keys in the compromised list.
723+.Pp
724+The output from
725+.Nm
726+looks like this:
727+.Pp
728+.Bd -literal -offset indent
729+/etc/ssh/ssh_host_key:1: COMPROMISED: RSA1 2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx root@host
730+/home/user/.ssh/id_dsa:1: Not blacklisted: DSA 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx /home/user/.ssh/id_dsa.pub
731+/home/user/.ssh/authorized_keys:3: Unknown (blacklist file not installed): RSA 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx user@host
732+.Ed
733+.Pp
734+Each line is of the following format (any lines beginning with
735+.Dq #
736+should be ignored by scripts):
737+.Pp
738+.Dl Ar filename : Ns Ar line : Ar status : Ar type Ar size Ar fingerprint Ar comment
739+.Pp
740+It is important to distinguish between the possible values of
741+.Ar status :
742+.Pp
743+.Bl -tag -width Ds
744+.It COMPROMISED
745+These keys are listed in a blacklist file, normally because their
746+corresponding private keys are well-known.
747+Replacements must be generated using
748+.Xr ssh-keygen 1 .
749+.It Not blacklisted
750+A blacklist file exists for this key type and size, but this key is not
751+listed in it.
752+Unless there is some particular reason to believe otherwise, this key
753+may be used safely.
754+(Note that DSA keys used with the broken version of OpenSSL distributed
755+by Debian may be compromised in the event that anyone captured a network
756+trace, even if they were generated with a secure version of OpenSSL.)
757+.It Unknown (blacklist file not installed)
758+No blacklist file exists for this key type and size.
759+You should find a suitable published blacklist and install it before
760+deciding whether this key is safe to use.
761+.El
762+.Pp
763+The options are as follows:
764+.Bl -tag -width Ds
765+.It Fl a
766+Check keys of all users on the system.
767+You will typically need to run
768+.Nm
769+as root to use this option.
770+For each user,
771+.Nm
772+will check
773+.Pa ~/.ssh/id_rsa ,
774+.Pa ~/.ssh/id_dsa ,
775+.Pa ~/.ssh/identity ,
776+.Pa ~/.ssh/authorized_keys
777+and
778+.Pa ~/.ssh/authorized_keys2 .
779+It will also check the system's host keys.
780+.It Fl q
781+Quiet mode.
782+Normally,
783+.Nm
784+outputs the fingerprint of each key scanned, with a description of its
785+status.
786+This option suppresses that output.
787+.It Fl v
788+Verbose mode.
789+Normally,
790+.Nm
791+does not output anything for keys that are not listed in their corresponding
792+blacklist file (although it still produces output for keys for which there
793+is no blacklist file, since their status is unknown).
794+This option causes
795+.Nm
796+to produce output for all keys.
797+.El
798+.Sh EXIT STATUS
799+.Nm
800+will exit zero if any of the given keys were in the compromised list,
801+otherwise non-zero.
802+.Sh BLACKLIST FILE FORMAT
803+The blacklist file may start with comments, on lines starting with
804+.Dq # .
805+After these initial comments, it must follow a strict format:
806+.Pp
807+.Bl -bullet -offset indent -compact
808+.It
809+All the lines must be exactly the same length (20 characters followed by a
810+newline) and must be in sorted order.
811+.It
812+Each line must consist of the lower-case hexadecimal MD5 key fingerprint,
813+without colons, and with the first 12 characters removed (that is, the least
814+significant 80 bits of the fingerprint).
815+.El
816+.Pp
817+The key fingerprint may be generated using
818+.Xr ssh-keygen 1 :
819+.Pp
820+.Dl $ ssh-keygen -l -f /path/to/key
821+.Pp
822+This strict format is necessary to allow the blacklist file to be checked
823+quickly, using a binary-search algorithm.
824+.Sh FILES
825+.Bl -tag -width Ds
826+.It Pa ~/.ssh/id_rsa
827+If present, contains the protocol version 2 RSA authentication identity of
828+the user.
829+.It Pa ~/.ssh/id_dsa
830+If present, contains the protocol version 2 DSA authentication identity of
831+the user.
832+.It Pa ~/.ssh/identity
833+If present, contains the protocol version 1 RSA authentication identity of
834+the user.
835+.It Pa ~/.ssh/authorized_keys
836+If present, lists the public keys (RSA/DSA) that can be used for logging in
837+as this user.
838+.It Pa ~/.ssh/authorized_keys2
839+Obsolete name for
840+.Pa ~/.ssh/authorized_keys .
841+This file may still be present on some old systems, but should not be
842+created if it is missing.
843+.It Pa /etc/ssh/ssh_host_rsa_key
844+If present, contains the protocol version 2 RSA identity of the system.
845+.It Pa /etc/ssh/ssh_host_dsa_key
846+If present, contains the protocol version 2 DSA identity of the system.
847+.It Pa /etc/ssh/ssh_host_key
848+If present, contains the protocol version 1 RSA identity of the system.
849+.It Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
850+If present, lists the blacklisted keys of type
851+.Ar TYPE
852+.Pf ( Dq RSA
853+or
854+.Dq DSA )
855+and bit length
856+.Ar LENGTH .
857+The format of this file is described above.
858+RSA1 keys are converted to RSA before being checked in the blacklist.
859+Note that the fingerprints of RSA1 keys are computed differently, so you
860+will not be able to find them in the blacklist by hand.
861+.It Pa /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
862+Same as
863+.Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH ,
864+but may be edited by the system administrator to add new blacklist entries.
865+.El
866+.Sh SEE ALSO
867+.Xr ssh-keygen 1 ,
868+.Xr sshd 8
869+.Sh AUTHORS
870+.An -nosplit
871+.An Colin Watson Aq cjwatson@ubuntu.com
872+.Pp
873+Florian Weimer suggested the option to check keys of all users, and the idea
874+of processing
875+.Xr ssh-keyscan 1
876+output.
877diff --git a/ssh-vulnkey.c b/ssh-vulnkey.c
878new file mode 100644
879index 0000000..ca1a5be
880--- /dev/null
881+++ b/ssh-vulnkey.c
882@@ -0,0 +1,386 @@
883+/*
884+ * Copyright (c) 2008 Canonical Ltd. All rights reserved.
885+ *
886+ * Redistribution and use in source and binary forms, with or without
887+ * modification, are permitted provided that the following conditions
888+ * are met:
889+ * 1. Redistributions of source code must retain the above copyright
890+ * notice, this list of conditions and the following disclaimer.
891+ * 2. Redistributions in binary form must reproduce the above copyright
892+ * notice, this list of conditions and the following disclaimer in the
893+ * documentation and/or other materials provided with the distribution.
894+ *
895+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
896+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
897+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
898+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
899+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
900+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
901+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
902+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
903+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
904+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
905+ */
906+
907+#include "includes.h"
908+
909+#include <sys/types.h>
910+#include <sys/stat.h>
911+
912+#include <errno.h>
913+#include <string.h>
914+#include <stdio.h>
915+#include <fcntl.h>
916+#include <unistd.h>
917+
918+#include <openssl/evp.h>
919+
920+#include "xmalloc.h"
921+#include "ssh.h"
922+#include "log.h"
923+#include "key.h"
924+#include "authfile.h"
925+#include "pathnames.h"
926+#include "uidswap.h"
927+#include "misc.h"
928+
929+extern char *__progname;
930+
931+/* Default files to check */
932+static char *default_host_files[] = {
933+ _PATH_HOST_RSA_KEY_FILE,
934+ _PATH_HOST_DSA_KEY_FILE,
935+ _PATH_HOST_KEY_FILE,
936+ NULL
937+};
938+static char *default_files[] = {
939+ _PATH_SSH_CLIENT_ID_RSA,
940+ _PATH_SSH_CLIENT_ID_DSA,
941+ _PATH_SSH_CLIENT_IDENTITY,
942+ _PATH_SSH_USER_PERMITTED_KEYS,
943+ _PATH_SSH_USER_PERMITTED_KEYS2,
944+ NULL
945+};
946+
947+static int verbosity = 0;
948+
949+static int some_keys = 0;
950+static int some_unknown = 0;
951+static int some_compromised = 0;
952+
953+static void
954+usage(void)
955+{
956+ fprintf(stderr, "usage: %s [-aqv] [file ...]\n", __progname);
957+ fprintf(stderr, "Options:\n");
958+ fprintf(stderr, " -a Check keys of all users.\n");
959+ fprintf(stderr, " -q Quiet mode.\n");
960+ fprintf(stderr, " -v Verbose mode.\n");
961+ exit(1);
962+}
963+
964+static void
965+describe_key(const char *filename, u_long linenum, const char *msg,
966+ Key *key, const char *comment, int min_verbosity)
967+{
968+ char *fp;
969+
970+ fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
971+ if (verbosity >= min_verbosity) {
972+ if (strchr(filename, ':'))
973+ printf("\"%s\"", filename);
974+ else
975+ printf("%s", filename);
976+ printf(":%lu: %s: %s %u %s %s\n", linenum, msg,
977+ key_type(key), key_size(key), fp, comment);
978+ }
979+ free(fp);
980+}
981+
982+static int
983+do_key(const char *filename, u_long linenum,
984+ Key *key, const char *comment)
985+{
986+ Key *public;
987+ int blacklist_status;
988+ int ret = 1;
989+
990+ some_keys = 1;
991+
992+ public = key_demote(key);
993+ if (public->type == KEY_RSA1)
994+ public->type = KEY_RSA;
995+
996+ blacklist_status = blacklisted_key(public, NULL);
997+ if (blacklist_status == -1) {
998+ describe_key(filename, linenum,
999+ "Unknown (blacklist file not installed)", key, comment, 0);
1000+ some_unknown = 1;
1001+ } else if (blacklist_status == 1) {
1002+ describe_key(filename, linenum,
1003+ "COMPROMISED", key, comment, 0);
1004+ some_compromised = 1;
1005+ ret = 0;
1006+ } else
1007+ describe_key(filename, linenum,
1008+ "Not blacklisted", key, comment, 1);
1009+
1010+ key_free(public);
1011+
1012+ return ret;
1013+}
1014+
1015+static int
1016+do_filename(const char *filename, int quiet_open)
1017+{
1018+ FILE *f;
1019+ char line[SSH_MAX_PUBKEY_BYTES];
1020+ char *cp;
1021+ u_long linenum = 0;
1022+ Key *key;
1023+ char *comment = NULL;
1024+ int found = 0, ret = 1;
1025+
1026+ /* Copy much of key_load_public's logic here so that we can read
1027+ * several keys from a single file (e.g. authorized_keys).
1028+ */
1029+
1030+ if (strcmp(filename, "-") != 0) {
1031+ int save_errno;
1032+ f = fopen(filename, "r");
1033+ save_errno = errno;
1034+ if (!f) {
1035+ char pubfile[MAXPATHLEN];
1036+ if (strlcpy(pubfile, filename, sizeof pubfile) <
1037+ sizeof(pubfile) &&
1038+ strlcat(pubfile, ".pub", sizeof pubfile) <
1039+ sizeof(pubfile))
1040+ f = fopen(pubfile, "r");
1041+ }
1042+ errno = save_errno; /* earlier errno is more useful */
1043+ if (!f) {
1044+ if (!quiet_open)
1045+ perror(filename);
1046+ return -1;
1047+ }
1048+ if (verbosity > 0)
1049+ printf("# %s\n", filename);
1050+ } else
1051+ f = stdin;
1052+ while (read_keyfile_line(f, filename, line, sizeof(line),
1053+ &linenum) != -1) {
1054+ int i;
1055+ char *space;
1056+ int type;
1057+ char *end;
1058+
1059+ /* Chop trailing newline. */
1060+ i = strlen(line) - 1;
1061+ if (line[i] == '\n')
1062+ line[i] = '\0';
1063+
1064+ /* Skip leading whitespace, empty and comment lines. */
1065+ for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
1066+ ;
1067+ if (!*cp || *cp == '\n' || *cp == '#')
1068+ continue;
1069+
1070+ /* Cope with ssh-keyscan output and options in
1071+ * authorized_keys files.
1072+ */
1073+ space = strchr(cp, ' ');
1074+ if (!space)
1075+ continue;
1076+ *space = '\0';
1077+ type = key_type_from_name(cp);
1078+ *space = ' ';
1079+ /* Leading number (RSA1) or valid type (RSA/DSA) indicates
1080+ * that we have no host name or options to skip.
1081+ */
1082+ if ((strtol(cp, &end, 10) == 0 || *end != ' ') &&
1083+ type == KEY_UNSPEC) {
1084+ int quoted = 0;
1085+
1086+ for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
1087+ if (*cp == '\\' && cp[1] == '"')
1088+ cp++; /* Skip both */
1089+ else if (*cp == '"')
1090+ quoted = !quoted;
1091+ }
1092+ /* Skip remaining whitespace. */
1093+ for (; *cp == ' ' || *cp == '\t'; cp++)
1094+ ;
1095+ if (!*cp)
1096+ continue;
1097+ }
1098+
1099+ /* Read and process the key itself. */
1100+ key = key_new(KEY_RSA1);
1101+ if (key_read(key, &cp) == 1) {
1102+ while (*cp == ' ' || *cp == '\t')
1103+ cp++;
1104+ if (!do_key(filename, linenum,
1105+ key, *cp ? cp : filename))
1106+ ret = 0;
1107+ found = 1;
1108+ } else {
1109+ key_free(key);
1110+ key = key_new(KEY_UNSPEC);
1111+ if (key_read(key, &cp) == 1) {
1112+ while (*cp == ' ' || *cp == '\t')
1113+ cp++;
1114+ if (!do_key(filename, linenum,
1115+ key, *cp ? cp : filename))
1116+ ret = 0;
1117+ found = 1;
1118+ }
1119+ }
1120+ key_free(key);
1121+ }
1122+ if (f != stdin)
1123+ fclose(f);
1124+
1125+ if (!found && filename) {
1126+ key = key_load_public(filename, &comment);
1127+ if (key) {
1128+ if (!do_key(filename, 1, key, comment))
1129+ ret = 0;
1130+ found = 1;
1131+ }
1132+ free(comment);
1133+ }
1134+
1135+ return ret;
1136+}
1137+
1138+static int
1139+do_host(int quiet_open)
1140+{
1141+ int i;
1142+ struct stat st;
1143+ int ret = 1;
1144+
1145+ for (i = 0; default_host_files[i]; i++) {
1146+ if (stat(default_host_files[i], &st) < 0 && errno == ENOENT)
1147+ continue;
1148+ if (!do_filename(default_host_files[i], quiet_open))
1149+ ret = 0;
1150+ }
1151+
1152+ return ret;
1153+}
1154+
1155+static int
1156+do_user(const char *dir)
1157+{
1158+ int i;
1159+ char *file;
1160+ struct stat st;
1161+ int ret = 1;
1162+
1163+ for (i = 0; default_files[i]; i++) {
1164+ xasprintf(&file, "%s/%s", dir, default_files[i]);
1165+ if (stat(file, &st) < 0 && errno == ENOENT) {
1166+ free(file);
1167+ continue;
1168+ }
1169+ if (!do_filename(file, 0))
1170+ ret = 0;
1171+ free(file);
1172+ }
1173+
1174+ return ret;
1175+}
1176+
1177+int
1178+main(int argc, char **argv)
1179+{
1180+ int opt, all_users = 0;
1181+ int ret = 1;
1182+ extern int optind;
1183+
1184+ /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1185+ sanitise_stdfd();
1186+
1187+ __progname = ssh_get_progname(argv[0]);
1188+
1189+ SSLeay_add_all_algorithms();
1190+ log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1);
1191+
1192+ /* We don't need the RNG ourselves, but symbol references here allow
1193+ * ld to link us properly.
1194+ */
1195+ seed_rng();
1196+
1197+ while ((opt = getopt(argc, argv, "ahqv")) != -1) {
1198+ switch (opt) {
1199+ case 'a':
1200+ all_users = 1;
1201+ break;
1202+ case 'q':
1203+ verbosity--;
1204+ break;
1205+ case 'v':
1206+ verbosity++;
1207+ break;
1208+ case 'h':
1209+ default:
1210+ usage();
1211+ }
1212+ }
1213+
1214+ if (all_users) {
1215+ struct passwd *pw;
1216+
1217+ if (!do_host(0))
1218+ ret = 0;
1219+
1220+ while ((pw = getpwent()) != NULL) {
1221+ if (pw->pw_dir) {
1222+ temporarily_use_uid(pw);
1223+ if (!do_user(pw->pw_dir))
1224+ ret = 0;
1225+ restore_uid();
1226+ }
1227+ }
1228+ } else if (optind == argc) {
1229+ struct passwd *pw;
1230+
1231+ if (!do_host(1))
1232+ ret = 0;
1233+
1234+ if ((pw = getpwuid(geteuid())) == NULL)
1235+ fprintf(stderr, "No user found with uid %u\n",
1236+ (u_int)geteuid());
1237+ else {
1238+ if (!do_user(pw->pw_dir))
1239+ ret = 0;
1240+ }
1241+ } else {
1242+ while (optind < argc)
1243+ if (!do_filename(argv[optind++], 0))
1244+ ret = 0;
1245+ }
1246+
1247+ if (verbosity >= 0) {
1248+ if (some_unknown) {
1249+ printf("#\n");
1250+ printf("# The status of some keys on your system is unknown.\n");
1251+ printf("# You may need to install additional blacklist files.\n");
1252+ }
1253+ if (some_compromised) {
1254+ printf("#\n");
1255+ printf("# Some keys on your system have been compromised!\n");
1256+ printf("# You must replace them using ssh-keygen(1).\n");
1257+ }
1258+ if (some_unknown || some_compromised) {
1259+ printf("#\n");
1260+ printf("# See the ssh-vulnkey(1) manual page for further advice.\n");
1261+ } else if (some_keys && verbosity > 0) {
1262+ printf("#\n");
1263+ printf("# No blacklisted keys!\n");
1264+ }
1265+ }
1266+
1267+ return ret;
1268+}
1269diff --git a/ssh.1 b/ssh.1
1270index 62292cc..66a7007 100644
1271--- a/ssh.1
1272+++ b/ssh.1
1273@@ -1447,6 +1447,7 @@ if an error occurred.
1274 .Xr ssh-agent 1 ,
1275 .Xr ssh-keygen 1 ,
1276 .Xr ssh-keyscan 1 ,
1277+.Xr ssh-vulnkey 1 ,
1278 .Xr tun 4 ,
1279 .Xr hosts.equiv 5 ,
1280 .Xr ssh_config 5 ,
1281diff --git a/ssh.c b/ssh.c
1282index 87233bc..567248d 100644
1283--- a/ssh.c
1284+++ b/ssh.c
1285@@ -1525,7 +1525,7 @@ ssh_session2(void)
1286 static void
1287 load_public_identity_files(void)
1288 {
1289- char *filename, *cp, thishost[NI_MAXHOST];
1290+ char *filename, *cp, thishost[NI_MAXHOST], *fp;
1291 char *pwdir = NULL, *pwname = NULL;
1292 int i = 0;
1293 Key *public;
1294@@ -1583,6 +1583,22 @@ load_public_identity_files(void)
1295 public = key_load_public(filename, NULL);
1296 debug("identity file %s type %d", filename,
1297 public ? public->type : -1);
1298+ if (public && blacklisted_key(public, &fp) == 1) {
1299+ if (options.use_blacklisted_keys)
1300+ logit("Public key %s blacklisted (see "
1301+ "ssh-vulnkey(1)); continuing anyway", fp);
1302+ else
1303+ logit("Public key %s blacklisted (see "
1304+ "ssh-vulnkey(1)); refusing to send it",
1305+ fp);
1306+ free(fp);
1307+ if (!options.use_blacklisted_keys) {
1308+ key_free(public);
1309+ free(filename);
1310+ filename = NULL;
1311+ public = NULL;
1312+ }
1313+ }
1314 free(options.identity_files[i]);
1315 identity_files[n_ids] = filename;
1316 identity_keys[n_ids] = public;
1317diff --git a/ssh_config.5 b/ssh_config.5
1318index e72919a..8d806c7 100644
1319--- a/ssh_config.5
1320+++ b/ssh_config.5
1321@@ -1229,6 +1229,23 @@ is not specified, it defaults to
1322 .Dq any .
1323 The default is
1324 .Dq any:any .
1325+.It Cm UseBlacklistedKeys
1326+Specifies whether
1327+.Xr ssh 1
1328+should use keys recorded in its blacklist of known-compromised keys (see
1329+.Xr ssh-vulnkey 1 )
1330+for authentication.
1331+If
1332+.Dq yes ,
1333+then attempts to use compromised keys for authentication will be logged but
1334+accepted.
1335+It is strongly recommended that this be used only to install new authorized
1336+keys on the remote system, and even then only with the utmost care.
1337+If
1338+.Dq no ,
1339+then attempts to use compromised keys for authentication will be prevented.
1340+The default is
1341+.Dq no .
1342 .It Cm UsePrivilegedPort
1343 Specifies whether to use a privileged port for outgoing connections.
1344 The argument must be
1345diff --git a/sshconnect2.c b/sshconnect2.c
1346index 0b13530..93818c9 100644
1347--- a/sshconnect2.c
1348+++ b/sshconnect2.c
1349@@ -1491,6 +1491,8 @@ pubkey_prepare(Authctxt *authctxt)
1350
1351 /* list of keys stored in the filesystem and PKCS#11 */
1352 for (i = 0; i < options.num_identity_files; i++) {
1353+ if (options.identity_files[i] == NULL)
1354+ continue;
1355 key = options.identity_keys[i];
1356 if (key && key->type == KEY_RSA1)
1357 continue;
1358@@ -1608,7 +1610,7 @@ userauth_pubkey(Authctxt *authctxt)
1359 debug("Offering %s public key: %s", key_type(id->key),
1360 id->filename);
1361 sent = send_pubkey_test(authctxt, id);
1362- } else if (id->key == NULL) {
1363+ } else if (id->key == NULL && id->filename) {
1364 debug("Trying private key: %s", id->filename);
1365 id->key = load_identity_file(id->filename,
1366 id->userprovided);
1367diff --git a/sshd.8 b/sshd.8
1368index b0c7ab6..a604429 100644
1369--- a/sshd.8
1370+++ b/sshd.8
1371@@ -954,6 +954,7 @@ The content of this file is not sensitive; it can be world-readable.
1372 .Xr ssh-agent 1 ,
1373 .Xr ssh-keygen 1 ,
1374 .Xr ssh-keyscan 1 ,
1375+.Xr ssh-vulnkey 1 ,
1376 .Xr chroot 2 ,
1377 .Xr hosts_access 5 ,
1378 .Xr login.conf 5 ,
1379diff --git a/sshd.c b/sshd.c
1380index e5c9835..fbe3284 100644
1381--- a/sshd.c
1382+++ b/sshd.c
1383@@ -1688,6 +1688,11 @@ main(int ac, char **av)
1384 sensitive_data.host_pubkeys[i] = NULL;
1385 continue;
1386 }
1387+ if (auth_key_is_revoked(key != NULL ? key : pubkey, 1)) {
1388+ sensitive_data.host_keys[i] = NULL;
1389+ sensitive_data.host_pubkeys[i] = NULL;
1390+ continue;
1391+ }
1392
1393 switch (keytype) {
1394 case KEY_RSA1:
1395diff --git a/sshd_config.5 b/sshd_config.5
1396index 525d9c8..18ec81f 100644
1397--- a/sshd_config.5
1398+++ b/sshd_config.5
1399@@ -885,6 +885,20 @@ are refused if the number of unauthenticated connections reaches
1400 Specifies whether password authentication is allowed.
1401 The default is
1402 .Dq yes .
1403+.It Cm PermitBlacklistedKeys
1404+Specifies whether
1405+.Xr sshd 8
1406+should allow keys recorded in its blacklist of known-compromised keys (see
1407+.Xr ssh-vulnkey 1 ) .
1408+If
1409+.Dq yes ,
1410+then attempts to authenticate with compromised keys will be logged but
1411+accepted.
1412+If
1413+.Dq no ,
1414+then attempts to authenticate with compromised keys will be rejected.
1415+The default is
1416+.Dq no .
1417 .It Cm PermitEmptyPasswords
1418 When password authentication is allowed, it specifies whether the
1419 server allows login to accounts with empty password strings.