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
@@ -26,6 +26,7 @@
 SFTP_SERVER=$(libexecdir)/sftp-server
 SSH_KEYSIGN=$(libexecdir)/ssh-keysign
 SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
+SSH_DATADIR=$(datadir)/ssh
 PRIVSEP_PATH=@PRIVSEP_PATH@
 SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@
 STRIP_OPT=@STRIP_OPT@
@@ -38,6 +39,7 @@
 	-D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \
 	-D_PATH_SSH_PIDDIR=\"$(piddir)\" \
 	-D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \
+	-D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\" \
 
 CC=@CC@
 LD=@LD@
@@ -59,7 +61,7 @@
 EXEEXT=@EXEEXT@
 MANFMT=@MANFMT@
 
-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)
+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)
 
 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 \
@@ -94,8 +96,8 @@
 	sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
 	sandbox-seccomp-filter.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-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-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-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-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
@@ -172,6 +174,9 @@
 sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o
 	$(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT)
 
+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)
@@ -260,6 +265,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
@@ -274,6 +280,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
@@ -355,6 +362,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)
@@ -367,6 +375,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/sftp-server.8
 	-rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.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
@@ -233,7 +233,7 @@
 			    file, linenum, BN_num_bits(key->rsa->n), bits);
 
 		/* Never accept a revoked key */
-		if (auth_key_is_revoked(key))
+		if (auth_key_is_revoked(key, 0))
 			break;
 
 		/* We have found the desired key. */
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"
@@ -606,10 +607,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
@@ -174,7 +174,7 @@
 
 FILE	*auth_openkeyfile(const char *, struct passwd *, int);
 FILE	*auth_openprincipals(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
@@ -146,7 +146,7 @@
 	int len;
 	char *fp;
 
-	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
@@ -440,9 +440,10 @@
 	u_int success, i;
 	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"
 
 #define MAX_KEY_FILE_SIZE	(1024 * 1024)
 
@@ -944,3 +945,140 @@
 	return ret;
 }
 
+/* Scan a blacklist of known-vulnerable keys in blacklist_file. */
+static int
+blacklisted_key_in_file(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(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
@@ -28,4 +28,6 @@
 int	 key_perm_ok(int, const char *);
 int	 key_in_file(Key *, const char *, int);
 
+int	 blacklisted_key(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
@@ -44,6 +48,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
@@ -125,6 +125,7 @@
 	oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
 	oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
 	oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
+	oUseBlacklistedKeys,
 	oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
 	oClearAllForwardings, oNoHostAuthenticationForLocalhost,
 	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
@@ -158,6 +159,7 @@
 	{ "passwordauthentication", oPasswordAuthentication },
 	{ "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
 	{ "kbdinteractivedevices", oKbdInteractiveDevices },
+	{ "useblacklistedkeys", oUseBlacklistedKeys },
 	{ "rsaauthentication", oRSAAuthentication },
 	{ "pubkeyauthentication", oPubkeyAuthentication },
 	{ "dsaauthentication", oPubkeyAuthentication },		    /* alias */
@@ -490,6 +492,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;
@@ -1181,6 +1187,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;
@@ -1291,6 +1298,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
@@ -59,6 +59,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
@@ -104,6 +104,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;
@@ -242,6 +243,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)
@@ -318,7 +321,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,
@@ -428,6 +431,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 },
@@ -1047,6 +1051,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;
@@ -1773,6 +1781,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
@@ -113,6 +113,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
@@ -81,6 +81,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
@@ -186,6 +190,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
@@ -142,7 +142,7 @@
 add_file(AuthenticationConnection *ac, const char *filename, int key_only)
 {
 	Key *private, *cert;
-	char *comment = NULL;
+	char *comment = NULL, *fp;
 	char msg[1024], *certpath = NULL;
 	int fd, perms_ok, ret = -1;
 	Buffer keyblob;
@@ -218,6 +218,14 @@
 	} else {
 		fprintf(stderr, "Could not add identity: %s\n", filename);
 	}
+	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;
+	}
 
 	/* Skip trying to load the cert if requested */
 	if (key_only)
Index: b/ssh-keygen.1
===================================================================
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -679,6 +679,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,387 @@
+/*
+ * 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);
+}
+
+static void
+describe_key(const char *filename, u_long linenum, const char *msg,
+    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);
+}
+
+static int
+do_key(const char *filename, u_long linenum,
+    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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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.
+	 */
+	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
@@ -1421,6 +1421,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
@@ -1492,7 +1492,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;
@@ -1549,6 +1549,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
@@ -1188,6 +1188,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
@@ -1489,6 +1489,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;
@@ -1582,7 +1584,7 @@
 			debug("Offering %s public key: %s", key_type(id->key),
 			    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
@@ -951,6 +951,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
@@ -1602,6 +1602,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
@@ -795,6 +795,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.
