openssh (1:5.5p1-6+squeeze1) ssh-vulnkey.patch

Summary

 Makefile.in       |   17 +-
 auth-rh-rsa.c     |    2 
 auth-rsa.c        |    2 
 auth.c            |   27 +++
 auth.h            |    2 
 auth2-hostbased.c |    2 
 auth2-pubkey.c    |    5 
 authfile.c        |  138 +++++++++++++++++++
 authfile.h        |    2 
 pathnames.h       |    7 
 readconf.c        |    9 +
 readconf.h        |    1 
 servconf.c        |   11 +
 servconf.h        |    1 
 ssh-add.1         |    5 
 ssh-add.c         |   10 +
 ssh-keygen.1      |    1 
 ssh-vulnkey.1     |  242 +++++++++++++++++++++++++++++++++
 ssh-vulnkey.c     |  388 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ssh.1             |    1 
 ssh.c             |   18 ++
 ssh_config.5      |   17 ++
 sshconnect2.c     |    4 
 sshd.8            |    1 
 sshd.c            |    5 
 sshd_config.5     |   14 +
 26 files changed, 917 insertions(+), 15 deletions(-)

    
download this patch

Patch contents

Description: Reject vulnerable keys to mitigate Debian OpenSSL flaw
 In 2008, Debian (and derived distributions such as Ubuntu) shipped an
 OpenSSL package with a flawed random number generator, causing OpenSSH to
 generate only a very limited set of keys which were subject to private half
 precomputation.  To mitigate this, this patch checks key authentications
 against a blacklist of known-vulnerable keys, and adds a new ssh-vulnkey
 program which can be used to explicitly check keys against that blacklist.
 See CVE-2008-0166.
Author: Colin Watson <cjwatson@ubuntu.com>
Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1469
Last-Update: 2010-02-27

Index: b/Makefile.in
===================================================================
--- a/Makefile.in
+++ b/Makefile.in
@@ -27,6 +27,7 @@
 SSH_KEYSIGN=$(libexecdir)/ssh-keysign
 SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
 RAND_HELPER=$(libexecdir)/ssh-rand-helper
+SSH_DATADIR=$(datadir)/ssh
 PRIVSEP_PATH=@PRIVSEP_PATH@
 SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@
 STRIP_OPT=@STRIP_OPT@
@@ -39,7 +40,8 @@
 	-D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \
 	-D_PATH_SSH_PIDDIR=\"$(piddir)\" \
 	-D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \
-	-DSSH_RAND_HELPER=\"$(RAND_HELPER)\"
+	-DSSH_RAND_HELPER=\"$(RAND_HELPER)\" \
+	-D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\"
 
 CC=@CC@
 LD=@LD@
@@ -62,7 +64,7 @@
 INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@
 INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@
 
-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) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT)
+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) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-vulnkey$(EXEEXT)
 
 LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \
 	canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \
@@ -93,8 +95,8 @@
 	audit.o audit-bsm.o platform.o sftp-server.o sftp-common.o \
 	roaming_common.o roaming_serv.o
 
-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-rand-helper.8.out ssh-keysign.8.out ssh-pkcs11-helper.8.out sshd_config.5.out ssh_config.5.out
-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-rand-helper.8 ssh-keysign.8 ssh-pkcs11-helper.8 sshd_config.5 ssh_config.5
+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-rand-helper.8.out ssh-keysign.8.out ssh-pkcs11-helper.8.out ssh-vulnkey.1.out sshd_config.5.out ssh_config.5.out
+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-rand-helper.8 ssh-keysign.8 ssh-pkcs11-helper.8 ssh-vulnkey.1 sshd_config.5 ssh_config.5
 MANTYPE		= @MANTYPE@
 
 CONFIGFILES=sshd_config.out ssh_config.out moduli.out
@@ -174,6 +176,9 @@
 ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o
 	$(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
 
+ssh-vulnkey$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-vulnkey.o
+	$(LD) -o $@ ssh-vulnkey.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
+
 # test driver for the loginrec code - not built by default
 logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o
 	$(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS)
@@ -268,6 +273,7 @@
 	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
 	$(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
 	$(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
+	$(INSTALL) -m 0755 $(STRIP_OPT) ssh-vulnkey$(EXEEXT) $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
 	$(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
 	$(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
 	$(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
@@ -285,6 +291,7 @@
 	$(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
 	$(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
 	$(INSTALL) -m 644 ssh-pkcs11-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-pkcs11-helper.8
+	$(INSTALL) -m 644 ssh-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
 	-rm -f $(DESTDIR)$(bindir)/slogin
 	ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1
@@ -366,6 +373,7 @@
 	-rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT)
 	-rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT)
 	-rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
+	-rm -f $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
 	-rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT)
 	-rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
 	-rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
@@ -379,6 +387,7 @@
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1
+	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
Index: b/auth-rh-rsa.c
===================================================================
--- a/auth-rh-rsa.c
+++ b/auth-rh-rsa.c
@@ -44,7 +44,7 @@
 {
 	HostStatus host_status;
 
-	if (auth_key_is_revoked(client_host_key))
+	if (auth_key_is_revoked(client_host_key, 0))
 		return 0;
 
 	/* Check if we would accept it using rhosts authentication. */
Index: b/auth-rsa.c
===================================================================
--- a/auth-rsa.c
+++ b/auth-rsa.c
@@ -94,7 +94,7 @@
 	MD5_CTX md;
 	int len;
 
-	if (auth_key_is_revoked(key))
+	if (auth_key_is_revoked(key, 0))
 		return 0;
 
 	/* don't allow short keys */
Index: b/auth.c
===================================================================
--- a/auth.c
+++ b/auth.c
@@ -59,6 +59,7 @@
 #include "servconf.h"
 #include "key.h"
 #include "hostfile.h"
+#include "authfile.h"
 #include "auth.h"
 #include "auth-options.h"
 #include "canohost.h"
@@ -593,10 +594,34 @@
 
 /* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */
 int
-auth_key_is_revoked(Key *key)
+auth_key_is_revoked(Key *key, int hostkey)
 {
 	char *key_fp;
 
+	if (blacklisted_key(key, &key_fp) == 1) {
+		if (options.permit_blacklisted_keys) {
+			if (hostkey)
+				error("Host key %s blacklisted (see "
+				    "ssh-vulnkey(1)); continuing anyway",
+				    key_fp);
+			else
+				logit("Public key %s from %s blacklisted (see "
+				    "ssh-vulnkey(1)); continuing anyway",
+				    key_fp, get_remote_ipaddr());
+			xfree(key_fp);
+		} else {
+			if (hostkey)
+				error("Host key %s blacklisted (see "
+				    "ssh-vulnkey(1))", key_fp);
+			else
+				logit("Public key %s from %s blacklisted (see "
+				    "ssh-vulnkey(1))",
+				    key_fp, get_remote_ipaddr());
+			xfree(key_fp);
+			return 1;
+		}
+	}
+
 	if (options.revoked_keys_file == NULL)
 		return 0;
 
Index: b/auth.h
===================================================================
--- a/auth.h
+++ b/auth.h
@@ -173,7 +173,7 @@
 char	*authorized_keys_file2(struct passwd *);
 
 FILE	*auth_openkeyfile(const char *, struct passwd *, int);
-int	 auth_key_is_revoked(Key *);
+int	 auth_key_is_revoked(Key *, int);
 
 HostStatus
 check_key_in_hostfiles(struct passwd *, Key *, const char *,
Index: b/auth2-hostbased.c
===================================================================
--- a/auth2-hostbased.c
+++ b/auth2-hostbased.c
@@ -145,7 +145,7 @@
 	HostStatus host_status;
 	int len;
 
-	if (auth_key_is_revoked(key))
+	if (auth_key_is_revoked(key, 0))
 		return 0;
 
 	resolvedname = get_canonical_hostname(options.use_dns);
Index: b/auth2-pubkey.c
===================================================================
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -328,9 +328,10 @@
 	int success;
 	char *file;
 
-	if (auth_key_is_revoked(key))
+	if (auth_key_is_revoked(key, 0))
 		return 0;
-	if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key))
+	if (key_is_cert(key) &&
+	    auth_key_is_revoked(key->cert->signature_key, 0))
 		return 0;
 
 	success = user_cert_trusted_ca(pw, key);
Index: b/authfile.c
===================================================================
--- a/authfile.c
+++ b/authfile.c
@@ -68,6 +68,7 @@
 #include "rsa.h"
 #include "misc.h"
 #include "atomicio.h"
+#include "pathnames.h"
 
 /* Version identification string for SSH v1 identity files. */
 static const char authfile_id_string[] =
@@ -754,3 +755,140 @@
 	return ret;
 }
 
+/* Scan a blacklist of known-vulnerable keys in blacklist_file. */
+static int
+blacklisted_key_in_file(const Key *key, const char *blacklist_file, char **fp)
+{
+	int fd = -1;
+	char *dgst_hex = NULL;
+	char *dgst_packed = NULL, *p;
+	int i;
+	size_t line_len;
+	struct stat st;
+	char buf[256];
+	off_t start, lower, upper;
+	int ret = 0;
+
+	debug("Checking blacklist file %s", blacklist_file);
+	fd = open(blacklist_file, O_RDONLY);
+	if (fd < 0) {
+		ret = -1;
+		goto out;
+	}
+
+	dgst_hex = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+	/* Remove all colons */
+	dgst_packed = xcalloc(1, strlen(dgst_hex) + 1);
+	for (i = 0, p = dgst_packed; dgst_hex[i]; i++)
+		if (dgst_hex[i] != ':')
+			*p++ = dgst_hex[i];
+	/* Only compare least-significant 80 bits (to keep the blacklist
+	 * size down)
+	 */
+	line_len = strlen(dgst_packed + 12);
+	if (line_len > 32)
+		goto out;
+
+	/* Skip leading comments */
+	start = 0;
+	for (;;) {
+		ssize_t r;
+		char *newline;
+
+		r = atomicio(read, fd, buf, sizeof(buf));
+		if (r <= 0)
+			goto out;
+		if (buf[0] != '#')
+			break;
+
+		newline = memchr(buf, '\n', sizeof(buf));
+		if (!newline)
+			goto out;
+		start += newline + 1 - buf;
+		if (lseek(fd, start, SEEK_SET) < 0)
+			goto out;
+	}
+
+	/* Initialise binary search record numbers */
+	if (fstat(fd, &st) < 0)
+		goto out;
+	lower = 0;
+	upper = (st.st_size - start) / (line_len + 1);
+
+	while (lower != upper) {
+		off_t cur;
+		int cmp;
+
+		cur = lower + (upper - lower) / 2;
+
+		/* Read this line and compare to digest; this is
+		 * overflow-safe since cur < max(off_t) / (line_len + 1) */
+		if (lseek(fd, start + cur * (line_len + 1), SEEK_SET) < 0)
+			break;
+		if (atomicio(read, fd, buf, line_len) != line_len)
+			break;
+		cmp = memcmp(buf, dgst_packed + 12, line_len);
+		if (cmp < 0) {
+			if (cur == lower)
+				break;
+			lower = cur;
+		} else if (cmp > 0) {
+			if (cur == upper)
+				break;
+			upper = cur;
+		} else {
+			debug("Found %s in blacklist", dgst_hex);
+			ret = 1;
+			break;
+		}
+	}
+
+out:
+	if (dgst_packed)
+		xfree(dgst_packed);
+	if (ret != 1 && dgst_hex) {
+		xfree(dgst_hex);
+		dgst_hex = NULL;
+	}
+	if (fp)
+		*fp = dgst_hex;
+	if (fd >= 0)
+		close(fd);
+	return ret;
+}
+
+/*
+ * Scan blacklists of known-vulnerable keys. If a vulnerable key is found,
+ * its fingerprint is returned in *fp, unless fp is NULL.
+ */
+int
+blacklisted_key(const Key *key, char **fp)
+{
+	Key *public;
+	char *blacklist_file;
+	int ret, ret2;
+
+	public = key_demote(key);
+	if (public->type == KEY_RSA1)
+		public->type = KEY_RSA;
+
+	xasprintf(&blacklist_file, "%s.%s-%u",
+	    _PATH_BLACKLIST, key_type(public), key_size(public));
+	ret = blacklisted_key_in_file(public, blacklist_file, fp);
+	xfree(blacklist_file);
+	if (ret > 0) {
+		key_free(public);
+		return ret;
+	}
+
+	xasprintf(&blacklist_file, "%s.%s-%u",
+	    _PATH_BLACKLIST_CONFIG, key_type(public), key_size(public));
+	ret2 = blacklisted_key_in_file(public, blacklist_file, fp);
+	xfree(blacklist_file);
+	if (ret2 > ret)
+		ret = ret2;
+
+	key_free(public);
+	return ret;
+}
+
Index: b/authfile.h
===================================================================
--- a/authfile.h
+++ b/authfile.h
@@ -24,4 +24,6 @@
 int	 key_perm_ok(int, const char *);
 int	 key_in_file(Key *, const char *, int);
 
+int	 blacklisted_key(const Key *key, char **fp);
+
 #endif
Index: b/pathnames.h
===================================================================
--- a/pathnames.h
+++ b/pathnames.h
@@ -18,6 +18,10 @@
 #define SSHDIR				ETCDIR "/ssh"
 #endif
 
+#ifndef _PATH_SSH_DATADIR
+#define _PATH_SSH_DATADIR		"/usr/share/ssh"
+#endif
+
 #ifndef _PATH_SSH_PIDDIR
 #define _PATH_SSH_PIDDIR		"/var/run"
 #endif
@@ -43,6 +47,9 @@
 /* Backwards compatibility */
 #define _PATH_DH_PRIMES			SSHDIR "/primes"
 
+#define _PATH_BLACKLIST			_PATH_SSH_DATADIR "/blacklist"
+#define _PATH_BLACKLIST_CONFIG		SSHDIR "/blacklist"
+
 #ifndef _PATH_SSH_PROGRAM
 #define _PATH_SSH_PROGRAM		"/usr/bin/ssh"
 #endif
Index: b/readconf.c
===================================================================
--- a/readconf.c
+++ b/readconf.c
@@ -123,6 +123,7 @@
 	oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
 	oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
 	oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
+	oUseBlacklistedKeys,
 	oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
 	oClearAllForwardings, oNoHostAuthenticationForLocalhost,
 	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
@@ -152,6 +153,7 @@
 	{ "passwordauthentication", oPasswordAuthentication },
 	{ "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
 	{ "kbdinteractivedevices", oKbdInteractiveDevices },
+	{ "useblacklistedkeys", oUseBlacklistedKeys },
 	{ "rsaauthentication", oRSAAuthentication },
 	{ "pubkeyauthentication", oPubkeyAuthentication },
 	{ "dsaauthentication", oPubkeyAuthentication },		    /* alias */
@@ -461,6 +463,10 @@
 		intptr = &options->challenge_response_authentication;
 		goto parse_flag;
 
+	case oUseBlacklistedKeys:
+		intptr = &options->use_blacklisted_keys;
+		goto parse_flag;
+
 	case oGssAuthentication:
 		intptr = &options->gss_authentication;
 		goto parse_flag;
@@ -1050,6 +1056,7 @@
 	options->kbd_interactive_devices = NULL;
 	options->rhosts_rsa_authentication = -1;
 	options->hostbased_authentication = -1;
+	options->use_blacklisted_keys = -1;
 	options->batch_mode = -1;
 	options->check_host_ip = -1;
 	options->strict_host_key_checking = -1;
@@ -1152,6 +1159,8 @@
 		options->rhosts_rsa_authentication = 0;
 	if (options->hostbased_authentication == -1)
 		options->hostbased_authentication = 0;
+	if (options->use_blacklisted_keys == -1)
+		options->use_blacklisted_keys = 0;
 	if (options->batch_mode == -1)
 		options->batch_mode = 0;
 	if (options->check_host_ip == -1)
Index: b/readconf.h
===================================================================
--- a/readconf.h
+++ b/readconf.h
@@ -54,6 +54,7 @@
 	int     kbd_interactive_authentication; /* Try keyboard-interactive auth. */
 	char	*kbd_interactive_devices; /* Keyboard-interactive auth devices. */
 	int     zero_knowledge_password_authentication;	/* Try jpake */
+	int	use_blacklisted_keys;	/* If true, send */
 	int     batch_mode;	/* Batch mode: do not ask for passwords. */
 	int     check_host_ip;	/* Also keep track of keys for IP address */
 	int     strict_host_key_checking;	/* Strict host key checking. */
Index: b/servconf.c
===================================================================
--- a/servconf.c
+++ b/servconf.c
@@ -100,6 +100,7 @@
 	options->password_authentication = -1;
 	options->kbd_interactive_authentication = -1;
 	options->challenge_response_authentication = -1;
+	options->permit_blacklisted_keys = -1;
 	options->permit_empty_passwd = -1;
 	options->permit_user_env = -1;
 	options->use_login = -1;
@@ -231,6 +232,8 @@
 		options->kbd_interactive_authentication = 0;
 	if (options->challenge_response_authentication == -1)
 		options->challenge_response_authentication = 1;
+	if (options->permit_blacklisted_keys == -1)
+		options->permit_blacklisted_keys = 0;
 	if (options->permit_empty_passwd == -1)
 		options->permit_empty_passwd = 0;
 	if (options->permit_user_env == -1)
@@ -306,7 +309,7 @@
 	sListenAddress, sAddressFamily,
 	sPrintMotd, sPrintLastLog, sIgnoreRhosts,
 	sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
-	sStrictModes, sEmptyPasswd, sTCPKeepAlive,
+	sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive,
 	sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
 	sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
 	sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
@@ -415,6 +418,7 @@
 	{ "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
 	{ "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
 	{ "strictmodes", sStrictModes, SSHCFG_GLOBAL },
+	{ "permitblacklistedkeys", sPermitBlacklistedKeys, SSHCFG_GLOBAL },
 	{ "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL },
 	{ "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL },
 	{ "uselogin", sUseLogin, SSHCFG_GLOBAL },
@@ -1009,6 +1013,10 @@
 		intptr = &options->tcp_keep_alive;
 		goto parse_flag;
 
+	case sPermitBlacklistedKeys:
+		intptr = &options->permit_blacklisted_keys;
+		goto parse_flag;
+
 	case sEmptyPasswd:
 		intptr = &options->permit_empty_passwd;
 		goto parse_flag;
@@ -1697,6 +1705,7 @@
 	dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost);
 	dump_cfg_fmtint(sStrictModes, o->strict_modes);
 	dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive);
+	dump_cfg_fmtint(sPermitBlacklistedKeys, o->permit_blacklisted_keys);
 	dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd);
 	dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env);
 	dump_cfg_fmtint(sUseLogin, o->use_login);
Index: b/servconf.h
===================================================================
--- a/servconf.h
+++ b/servconf.h
@@ -104,6 +104,7 @@
 	int     challenge_response_authentication;
 	int     zero_knowledge_password_authentication;
 					/* If true, permit jpake auth */
+	int     permit_blacklisted_keys;	/* If true, permit */
 	int     permit_empty_passwd;	/* If false, do not permit empty
 					 * passwords. */
 	int     permit_user_env;	/* If true, read ~/.ssh/environment */
Index: b/ssh-add.1
===================================================================
--- a/ssh-add.1
+++ b/ssh-add.1
@@ -82,6 +82,10 @@
 .Nm
 to work.
 .Pp
+Any keys recorded in the blacklist of known-compromised keys (see
+.Xr ssh-vulnkey 1 )
+will be refused.
+.Pp
 The options are as follows:
 .Bl -tag -width Ds
 .It Fl c
@@ -182,6 +186,7 @@
 .Xr ssh 1 ,
 .Xr ssh-agent 1 ,
 .Xr ssh-keygen 1 ,
+.Xr ssh-vulnkey 1 ,
 .Xr sshd 8
 .Sh AUTHORS
 OpenSSH is a derivative of the original and free
Index: b/ssh-add.c
===================================================================
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -139,7 +139,7 @@
 add_file(AuthenticationConnection *ac, const char *filename)
 {
 	Key *private, *cert;
-	char *comment = NULL;
+	char *comment = NULL, *fp;
 	char msg[1024], *certpath;
 	int fd, perms_ok, ret = -1;
 
@@ -184,6 +184,14 @@
 			    "Bad passphrase, try again for %.200s: ", comment);
 		}
 	}
+	if (blacklisted_key(private, &fp) == 1) {
+		fprintf(stderr, "Public key %s blacklisted (see "
+		    "ssh-vulnkey(1)); refusing to add it\n", fp);
+		xfree(fp);
+		key_free(private);
+		xfree(comment);
+		return -1;
+	}
 
 	if (ssh_add_identity_constrained(ac, private, comment, lifetime,
 	    confirm)) {
Index: b/ssh-keygen.1
===================================================================
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -628,6 +628,7 @@
 .Xr ssh 1 ,
 .Xr ssh-add 1 ,
 .Xr ssh-agent 1 ,
+.Xr ssh-vulnkey 1 ,
 .Xr moduli 5 ,
 .Xr sshd 8
 .Rs
Index: b/ssh-vulnkey.1
===================================================================
--- /dev/null
+++ b/ssh-vulnkey.1
@@ -0,0 +1,242 @@
+.\" Copyright (c) 2008 Canonical Ltd.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd $Mdocdate: May 12 2008 $
+.Dt SSH-VULNKEY 1
+.Os
+.Sh NAME
+.Nm ssh-vulnkey
+.Nd check blacklist of compromised keys
+.Sh SYNOPSIS
+.Nm
+.Op Fl q | Fl v
+.Ar file ...
+.Nm
+.Fl a
+.Sh DESCRIPTION
+.Nm
+checks a key against a blacklist of compromised keys.
+.Pp
+A substantial number of keys are known to have been generated using a broken
+version of OpenSSL distributed by Debian which failed to seed its random
+number generator correctly.
+Keys generated using these OpenSSL versions should be assumed to be
+compromised.
+This tool may be useful in checking for such keys.
+.Pp
+Keys that are compromised cannot be repaired; replacements must be generated
+using
+.Xr ssh-keygen 1 .
+Make sure to update
+.Pa authorized_keys
+files on all systems where compromised keys were permitted to authenticate.
+.Pp
+The argument list will be interpreted as a list of paths to public key files
+or
+.Pa authorized_keys
+files.
+If no suitable file is found at a given path,
+.Nm
+will append
+.Pa .pub
+and retry, in case it was given a private key file.
+If no files are given as arguments,
+.Nm
+will check
+.Pa ~/.ssh/id_rsa ,
+.Pa ~/.ssh/id_dsa ,
+.Pa ~/.ssh/identity ,
+.Pa ~/.ssh/authorized_keys
+and
+.Pa ~/.ssh/authorized_keys2 ,
+as well as the system's host keys if readable.
+.Pp
+If
+.Dq -
+is given as an argument,
+.Nm
+will read from standard input.
+This can be used to process output from
+.Xr ssh-keyscan 1 ,
+for example:
+.Pp
+.Dl $ ssh-keyscan -t rsa remote.example.org | ssh-vulnkey -
+.Pp
+Unless the
+.Cm PermitBlacklistedKeys
+option is used,
+.Xr sshd 8
+will reject attempts to authenticate with keys in the compromised list.
+.Pp
+The output from
+.Nm
+looks like this:
+.Pp
+.Bd -literal -offset indent
+/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
+/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
+/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
+.Ed
+.Pp
+Each line is of the following format (any lines beginning with
+.Dq #
+should be ignored by scripts):
+.Pp
+.Dl Ar filename : Ns Ar line : Ar status : Ar type Ar size Ar fingerprint Ar comment
+.Pp
+It is important to distinguish between the possible values of
+.Ar status :
+.Pp
+.Bl -tag -width Ds
+.It COMPROMISED
+These keys are listed in a blacklist file, normally because their
+corresponding private keys are well-known.
+Replacements must be generated using
+.Xr ssh-keygen 1 .
+.It Not blacklisted
+A blacklist file exists for this key type and size, but this key is not
+listed in it.
+Unless there is some particular reason to believe otherwise, this key
+may be used safely.
+(Note that DSA keys used with the broken version of OpenSSL distributed
+by Debian may be compromised in the event that anyone captured a network
+trace, even if they were generated with a secure version of OpenSSL.)
+.It Unknown (blacklist file not installed)
+No blacklist file exists for this key type and size.
+You should find a suitable published blacklist and install it before
+deciding whether this key is safe to use.
+.El
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Check keys of all users on the system.
+You will typically need to run
+.Nm
+as root to use this option.
+For each user,
+.Nm
+will check
+.Pa ~/.ssh/id_rsa ,
+.Pa ~/.ssh/id_dsa ,
+.Pa ~/.ssh/identity ,
+.Pa ~/.ssh/authorized_keys
+and
+.Pa ~/.ssh/authorized_keys2 .
+It will also check the system's host keys.
+.It Fl q
+Quiet mode.
+Normally,
+.Nm
+outputs the fingerprint of each key scanned, with a description of its
+status.
+This option suppresses that output.
+.It Fl v
+Verbose mode.
+Normally,
+.Nm
+does not output anything for keys that are not listed in their corresponding
+blacklist file (although it still produces output for keys for which there
+is no blacklist file, since their status is unknown).
+This option causes
+.Nm
+to produce output for all keys.
+.El
+.Sh EXIT STATUS
+.Nm
+will exit zero if any of the given keys were in the compromised list,
+otherwise non-zero.
+.Sh BLACKLIST FILE FORMAT
+The blacklist file may start with comments, on lines starting with
+.Dq # .
+After these initial comments, it must follow a strict format:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+All the lines must be exactly the same length (20 characters followed by a
+newline) and must be in sorted order.
+.It
+Each line must consist of the lower-case hexadecimal MD5 key fingerprint,
+without colons, and with the first 12 characters removed (that is, the least
+significant 80 bits of the fingerprint).
+.El
+.Pp
+The key fingerprint may be generated using
+.Xr ssh-keygen 1 :
+.Pp
+.Dl $ ssh-keygen -l -f /path/to/key
+.Pp
+This strict format is necessary to allow the blacklist file to be checked
+quickly, using a binary-search algorithm.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa ~/.ssh/id_rsa
+If present, contains the protocol version 2 RSA authentication identity of
+the user.
+.It Pa ~/.ssh/id_dsa
+If present, contains the protocol version 2 DSA authentication identity of
+the user.
+.It Pa ~/.ssh/identity
+If present, contains the protocol version 1 RSA authentication identity of
+the user.
+.It Pa ~/.ssh/authorized_keys
+If present, lists the public keys (RSA/DSA) that can be used for logging in
+as this user.
+.It Pa ~/.ssh/authorized_keys2
+Obsolete name for
+.Pa ~/.ssh/authorized_keys .
+This file may still be present on some old systems, but should not be
+created if it is missing.
+.It Pa /etc/ssh/ssh_host_rsa_key
+If present, contains the protocol version 2 RSA identity of the system.
+.It Pa /etc/ssh/ssh_host_dsa_key
+If present, contains the protocol version 2 DSA identity of the system.
+.It Pa /etc/ssh/ssh_host_key
+If present, contains the protocol version 1 RSA identity of the system.
+.It Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
+If present, lists the blacklisted keys of type
+.Ar TYPE
+.Pf ( Dq RSA
+or
+.Dq DSA )
+and bit length
+.Ar LENGTH .
+The format of this file is described above.
+RSA1 keys are converted to RSA before being checked in the blacklist.
+Note that the fingerprints of RSA1 keys are computed differently, so you
+will not be able to find them in the blacklist by hand.
+.It Pa /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
+Same as
+.Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH ,
+but may be edited by the system administrator to add new blacklist entries.
+.El
+.Sh SEE ALSO
+.Xr ssh-keygen 1 ,
+.Xr sshd 8
+.Sh AUTHORS
+.An -nosplit
+.An Colin Watson Aq cjwatson@ubuntu.com
+.Pp
+Florian Weimer suggested the option to check keys of all users, and the idea
+of processing
+.Xr ssh-keyscan 1
+output.
Index: b/ssh-vulnkey.c
===================================================================
--- /dev/null
+++ b/ssh-vulnkey.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2008 Canonical Ltd.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <openssl/evp.h>
+
+#include "xmalloc.h"
+#include "ssh.h"
+#include "log.h"
+#include "key.h"
+#include "authfile.h"
+#include "pathnames.h"
+#include "uidswap.h"
+#include "misc.h"
+
+extern char *__progname;
+
+/* Default files to check */
+static char *default_host_files[] = {
+	_PATH_HOST_RSA_KEY_FILE,
+	_PATH_HOST_DSA_KEY_FILE,
+	_PATH_HOST_KEY_FILE,
+	NULL
+};
+static char *default_files[] = {
+	_PATH_SSH_CLIENT_ID_RSA,
+	_PATH_SSH_CLIENT_ID_DSA,
+	_PATH_SSH_CLIENT_IDENTITY,
+	_PATH_SSH_USER_PERMITTED_KEYS,
+	_PATH_SSH_USER_PERMITTED_KEYS2,
+	NULL
+};
+
+static int verbosity = 0;
+
+static int some_keys = 0;
+static int some_unknown = 0;
+static int some_compromised = 0;
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: %s [-aqv] [file ...]\n", __progname);
+	fprintf(stderr, "Options:\n");
+	fprintf(stderr, "  -a          Check keys of all users.\n");
+	fprintf(stderr, "  -q          Quiet mode.\n");
+	fprintf(stderr, "  -v          Verbose mode.\n");
+	exit(1);
+}
+
+void
+describe_key(const char *filename, u_long linenum, const char *msg,
+    const Key *key, const char *comment, int min_verbosity)
+{
+	char *fp;
+
+	fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+	if (verbosity >= min_verbosity) {
+		if (strchr(filename, ':'))
+			printf("\"%s\"", filename);
+		else
+			printf("%s", filename);
+		printf(":%lu: %s: %s %u %s %s\n", linenum, msg,
+		    key_type(key), key_size(key), fp, comment);
+	}
+	xfree(fp);
+}
+
+int
+do_key(const char *filename, u_long linenum,
+    const Key *key, const char *comment)
+{
+	Key *public;
+	int blacklist_status;
+	int ret = 1;
+
+	some_keys = 1;
+
+	public = key_demote(key);
+	if (public->type == KEY_RSA1)
+		public->type = KEY_RSA;
+
+	blacklist_status = blacklisted_key(public, NULL);
+	if (blacklist_status == -1) {
+		describe_key(filename, linenum,
+		    "Unknown (blacklist file not installed)", key, comment, 0);
+		some_unknown = 1;
+	} else if (blacklist_status == 1) {
+		describe_key(filename, linenum,
+		    "COMPROMISED", key, comment, 0);
+		some_compromised = 1;
+		ret = 0;
+	} else
+		describe_key(filename, linenum,
+		    "Not blacklisted", key, comment, 1);
+
+	key_free(public);
+
+	return ret;
+}
+
+int
+do_filename(const char *filename, int quiet_open)
+{
+	FILE *f;
+	char line[SSH_MAX_PUBKEY_BYTES];
+	char *cp;
+	u_long linenum = 0;
+	Key *key;
+	char *comment = NULL;
+	int found = 0, ret = 1;
+
+	/* Copy much of key_load_public's logic here so that we can read
+	 * several keys from a single file (e.g. authorized_keys).
+	 */
+
+	if (strcmp(filename, "-") != 0) {
+		int save_errno;
+		f = fopen(filename, "r");
+		save_errno = errno;
+		if (!f) {
+			char pubfile[MAXPATHLEN];
+			if (strlcpy(pubfile, filename, sizeof pubfile) <
+			    sizeof(pubfile) &&
+			    strlcat(pubfile, ".pub", sizeof pubfile) <
+			    sizeof(pubfile))
+				f = fopen(pubfile, "r");
+		}
+		errno = save_errno; /* earlier errno is more useful */
+		if (!f) {
+			if (!quiet_open)
+				perror(filename);
+			return -1;
+		}
+		if (verbosity > 0)
+			printf("# %s\n", filename);
+	} else
+		f = stdin;
+	while (read_keyfile_line(f, filename, line, sizeof(line),
+		    &linenum) != -1) {
+		int i;
+		char *space;
+		int type;
+		char *end;
+
+		/* Chop trailing newline. */
+		i = strlen(line) - 1;
+		if (line[i] == '\n')
+			line[i] = '\0';
+
+		/* Skip leading whitespace, empty and comment lines. */
+		for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
+			;
+		if (!*cp || *cp == '\n' || *cp == '#')
+			continue;
+
+		/* Cope with ssh-keyscan output and options in
+		 * authorized_keys files.
+		 */
+		space = strchr(cp, ' ');
+		if (!space)
+			continue;
+		*space = '\0';
+		type = key_type_from_name(cp);
+		*space = ' ';
+		/* Leading number (RSA1) or valid type (RSA/DSA) indicates
+		 * that we have no host name or options to skip.
+		 */
+		if ((strtol(cp, &end, 10) == 0 || *end != ' ') &&
+		    type == KEY_UNSPEC) {
+			int quoted = 0;
+
+			for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
+				if (*cp == '\\' && cp[1] == '"')
+					cp++;	/* Skip both */
+				else if (*cp == '"')
+					quoted = !quoted;
+			}
+			/* Skip remaining whitespace. */
+			for (; *cp == ' ' || *cp == '\t'; cp++)
+				;
+			if (!*cp)
+				continue;
+		}
+
+		/* Read and process the key itself. */
+		key = key_new(KEY_RSA1);
+		if (key_read(key, &cp) == 1) {
+			while (*cp == ' ' || *cp == '\t')
+				cp++;
+			if (!do_key(filename, linenum,
+			    key, *cp ? cp : filename))
+				ret = 0;
+			found = 1;
+		} else {
+			key_free(key);
+			key = key_new(KEY_UNSPEC);
+			if (key_read(key, &cp) == 1) {
+				while (*cp == ' ' || *cp == '\t')
+					cp++;
+				if (!do_key(filename, linenum,
+				    key, *cp ? cp : filename))
+					ret = 0;
+				found = 1;
+			}
+		}
+		key_free(key);
+	}
+	if (f != stdin)
+		fclose(f);
+
+	if (!found && filename) {
+		key = key_load_public(filename, &comment);
+		if (key) {
+			if (!do_key(filename, 1, key, comment))
+				ret = 0;
+			found = 1;
+		}
+		if (comment)
+			xfree(comment);
+	}
+
+	return ret;
+}
+
+int
+do_host(int quiet_open)
+{
+	int i;
+	struct stat st;
+	int ret = 1;
+
+	for (i = 0; default_host_files[i]; i++) {
+		if (stat(default_host_files[i], &st) < 0 && errno == ENOENT)
+			continue;
+		if (!do_filename(default_host_files[i], quiet_open))
+			ret = 0;
+	}
+
+	return ret;
+}
+
+int
+do_user(const char *dir)
+{
+	int i;
+	char *file;
+	struct stat st;
+	int ret = 1;
+
+	for (i = 0; default_files[i]; i++) {
+		xasprintf(&file, "%s/%s", dir, default_files[i]);
+		if (stat(file, &st) < 0 && errno == ENOENT) {
+			xfree(file);
+			continue;
+		}
+		if (!do_filename(file, 0))
+			ret = 0;
+		xfree(file);
+	}
+
+	return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+	int opt, all_users = 0;
+	int ret = 1;
+	extern int optind;
+
+	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
+	sanitise_stdfd();
+
+	__progname = ssh_get_progname(argv[0]);
+
+	SSLeay_add_all_algorithms();
+	log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1);
+
+	/* We don't need the RNG ourselves, but symbol references here allow
+	 * ld to link us properly.
+	 */
+	init_rng();
+	seed_rng();
+
+	while ((opt = getopt(argc, argv, "ahqv")) != -1) {
+		switch (opt) {
+		case 'a':
+			all_users = 1;
+			break;
+		case 'q':
+			verbosity--;
+			break;
+		case 'v':
+			verbosity++;
+			break;
+		case 'h':
+		default:
+			usage();
+		}
+	}
+
+	if (all_users) {
+		struct passwd *pw;
+
+		if (!do_host(0))
+			ret = 0;
+
+		while ((pw = getpwent()) != NULL) {
+			if (pw->pw_dir) {
+				temporarily_use_uid(pw);
+				if (!do_user(pw->pw_dir))
+					ret = 0;
+				restore_uid();
+			}
+		}
+	} else if (optind == argc) {
+		struct passwd *pw;
+
+		if (!do_host(1))
+			ret = 0;
+
+		if ((pw = getpwuid(geteuid())) == NULL)
+			fprintf(stderr, "No user found with uid %u\n",
+			    (u_int)geteuid());
+		else {
+			if (!do_user(pw->pw_dir))
+				ret = 0;
+		}
+	} else {
+		while (optind < argc)
+			if (!do_filename(argv[optind++], 0))
+				ret = 0;
+	}
+
+	if (verbosity >= 0) {
+		if (some_unknown) {
+			printf("#\n");
+			printf("# The status of some keys on your system is unknown.\n");
+			printf("# You may need to install additional blacklist files.\n");
+		}
+		if (some_compromised) {
+			printf("#\n");
+			printf("# Some keys on your system have been compromised!\n");
+			printf("# You must replace them using ssh-keygen(1).\n");
+		}
+		if (some_unknown || some_compromised) {
+			printf("#\n");
+			printf("# See the ssh-vulnkey(1) manual page for further advice.\n");
+		} else if (some_keys && verbosity > 0) {
+			printf("#\n");
+			printf("# No blacklisted keys!\n");
+		}
+	}
+
+	return ret;
+}
Index: b/ssh.1
===================================================================
--- a/ssh.1
+++ b/ssh.1
@@ -1426,6 +1426,7 @@
 .Xr ssh-agent 1 ,
 .Xr ssh-keygen 1 ,
 .Xr ssh-keyscan 1 ,
+.Xr ssh-vulnkey 1 ,
 .Xr tun 4 ,
 .Xr hosts.equiv 5 ,
 .Xr ssh_config 5 ,
Index: b/ssh.c
===================================================================
--- a/ssh.c
+++ b/ssh.c
@@ -1301,7 +1301,7 @@
 static void
 load_public_identity_files(void)
 {
-	char *filename, *cp, thishost[NI_MAXHOST];
+	char *filename, *cp, thishost[NI_MAXHOST], *fp;
 	char *pwdir = NULL, *pwname = NULL;
 	int i = 0;
 	Key *public;
@@ -1358,6 +1358,22 @@
 		public = key_load_public(filename, NULL);
 		debug("identity file %s type %d", filename,
 		    public ? public->type : -1);
+		if (public && blacklisted_key(public, &fp) == 1) {
+			if (options.use_blacklisted_keys)
+				logit("Public key %s blacklisted (see "
+				    "ssh-vulnkey(1)); continuing anyway", fp);
+			else
+				logit("Public key %s blacklisted (see "
+				    "ssh-vulnkey(1)); refusing to send it",
+				    fp);
+			xfree(fp);
+			if (!options.use_blacklisted_keys) {
+				key_free(public);
+				xfree(filename);
+				filename = NULL;
+				public = NULL;
+			}
+		}
 		xfree(options.identity_files[i]);
 		identity_files[n_ids] = filename;
 		identity_keys[n_ids] = public;
Index: b/ssh_config.5
===================================================================
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -1051,6 +1051,23 @@
 .Dq any .
 The default is
 .Dq any:any .
+.It Cm UseBlacklistedKeys
+Specifies whether
+.Xr ssh 1
+should use keys recorded in its blacklist of known-compromised keys (see
+.Xr ssh-vulnkey 1 )
+for authentication.
+If
+.Dq yes ,
+then attempts to use compromised keys for authentication will be logged but
+accepted.
+It is strongly recommended that this be used only to install new authorized
+keys on the remote system, and even then only with the utmost care.
+If
+.Dq no ,
+then attempts to use compromised keys for authentication will be prevented.
+The default is
+.Dq no .
 .It Cm UsePrivilegedPort
 Specifies whether to use a privileged port for outgoing connections.
 The argument must be
Index: b/sshconnect2.c
===================================================================
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1418,6 +1418,8 @@
 
 	/* list of keys stored in the filesystem */
 	for (i = 0; i < options.num_identity_files; i++) {
+		if (options.identity_files[i] == NULL)
+			continue;
 		key = options.identity_keys[i];
 		if (key && key->type == KEY_RSA1)
 			continue;
@@ -1510,7 +1512,7 @@
 		if (id->key && id->key->type != KEY_RSA1) {
 			debug("Offering public key: %s", id->filename);
 			sent = send_pubkey_test(authctxt, id);
-		} else if (id->key == NULL) {
+		} else if (id->key == NULL && id->filename) {
 			debug("Trying private key: %s", id->filename);
 			id->key = load_identity_file(id->filename);
 			if (id->key != NULL) {
Index: b/sshd.8
===================================================================
--- a/sshd.8
+++ b/sshd.8
@@ -928,6 +928,7 @@
 .Xr ssh-agent 1 ,
 .Xr ssh-keygen 1 ,
 .Xr ssh-keyscan 1 ,
+.Xr ssh-vulnkey 1 ,
 .Xr chroot 2 ,
 .Xr hosts_access 5 ,
 .Xr login.conf 5 ,
Index: b/sshd.c
===================================================================
--- a/sshd.c
+++ b/sshd.c
@@ -1564,6 +1564,11 @@
 			sensitive_data.host_keys[i] = NULL;
 			continue;
 		}
+		if (auth_key_is_revoked(key, 1)) {
+			key_free(key);
+			sensitive_data.host_keys[i] = NULL;
+			continue;
+		}
 		switch (key->type) {
 		case KEY_RSA1:
 			sensitive_data.ssh1_host_key = key;
Index: b/sshd_config.5
===================================================================
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -694,6 +694,20 @@
 Specifies whether password authentication is allowed.
 The default is
 .Dq yes .
+.It Cm PermitBlacklistedKeys
+Specifies whether
+.Xr sshd 8
+should allow keys recorded in its blacklist of known-compromised keys (see
+.Xr ssh-vulnkey 1 ) .
+If
+.Dq yes ,
+then attempts to authenticate with compromised keys will be logged but
+accepted.
+If
+.Dq no ,
+then attempts to authenticate with compromised keys will be rejected.
+The default is
+.Dq no .
 .It Cm PermitEmptyPasswords
 When password authentication is allowed, it specifies whether the
 server allows login to accounts with empty password strings.