+++ /dev/null
-This patch is against OpenSSH 4.7p1, although should apply to other versions
-without too much difficulty. It makes the following changes:
-
- * Add key blacklisting support. Keys listed in
- /etc/ssh/blacklist.TYPE-LENGTH will be rejected for authentication by
- sshd, unless "PermitBlacklistedKeys yes" is set in /etc/ssh/sshd_config.
-
- * Add a new program, ssh-vulnkey, which can be used to check keys against
- these blacklists.
-
-This patch is up to date with respect to Debian openssh 1:4.7p1-10.
-
---- openssh-4.7p1.orig/sshd_config.5
-+++ openssh-4.7p1/sshd_config.5
-@@ -615,6 +615,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.
---- openssh-4.7p1.orig/sshd.c
-+++ openssh-4.7p1/sshd.c
-@@ -1466,6 +1466,21 @@
-
- for (i = 0; i < options.num_host_key_files; i++) {
- key = key_load_private(options.host_key_files[i], "", NULL);
-+ if (key && blacklisted_key(key)) {
-+ char *fp;
-+ fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
-+ if (options.permit_blacklisted_keys)
-+ error("Host key %s blacklisted (see "
-+ "ssh-vulnkey(1)); continuing anyway", fp);
-+ else
-+ error("Host key %s blacklisted (see "
-+ "ssh-vulnkey(1))", fp);
-+ free(fp);
-+ if (!options.permit_blacklisted_keys) {
-+ sensitive_data.host_keys[i] = NULL;
-+ continue;
-+ }
-+ }
- pubkey = key_load_public(options.host_key_files[i], NULL);
- sensitive_data.host_keys[i] = key;
- sensitive_data.host_pubkeys[i] = pubkey;
---- openssh-4.7p1.orig/servconf.c
-+++ openssh-4.7p1/servconf.c
-@@ -96,6 +96,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;
-@@ -218,6 +219,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)
-@@ -287,7 +290,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,
-@@ -387,6 +390,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 },
-@@ -943,6 +947,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;
---- openssh-4.7p1.orig/servconf.h
-+++ openssh-4.7p1/servconf.h
-@@ -94,6 +94,7 @@
- * authentication. /
- int kbd_interactive_authentication; /* If true, permit */
- int challenge_response_authentication;
-+ int permit_blacklisted_keys; /* If true, permit */
- int zero_knowledge_password_authentication;
- /* If true, permit jpake auth */
- int permit_empty_passwd; /* If false, do not permit empty
---- openssh-4.7p1.orig/Makefile.in
-+++ openssh-4.7p1/Makefile.in
-@@ -62,7 +62,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) sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-ldap-helper$(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-ldap-helper$(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 +93,8 @@
- audit.o audit-bsm.o platform.o sftp-server.o sftp-common.o \
- roaming_common.o roaming_serv.o ldapauth.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 ssh-ldap-helper.8.out sshd_config.5.out ssh_config.5.out ssh-ldap.conf.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-ldap-helper.8 sshd_config.5 ssh_config.5 ssh-ldap.conf.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-ldap-helper.8.out sshd_config.5.out ssh_config.5.out ssh-ldap.conf.5.out ssh-vulnkey.1.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-ldap-helper.8 sshd_config.5 ssh_config.5 ssh-ldap.conf.5 ssh-vulnkey.1
- MANTYPE = @MANTYPE@
-
- CONFIGFILES=sshd_config.out ssh_config.out moduli.out
-@@ -165,6 +165,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)
-@@ -271,6 +274,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
-@@ -289,6 +293,7 @@
- $(INSTALL) -m 644 sftp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1
- $(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-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
- $(INSTALL) -m 644 ssh-pkcs11-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-pkcs11-helper.8
- -rm -f $(DESTDIR)$(bindir)/slogin
- ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin
-@@ -361,6 +366,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)
-@@ -373,6 +379,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
---- openssh-4.7p1.orig/auth-rh-rsa.c
-+++ openssh-4.7p1/auth-rh-rsa.c
-@@ -20,6 +20,7 @@
- #include <pwd.h>
- #include <stdarg.h>
-
-+#include "xmalloc.h"
- #include "packet.h"
- #include "uidswap.h"
- #include "log.h"
-@@ -27,6 +28,7 @@
- #include "servconf.h"
- #include "key.h"
- #include "hostfile.h"
-+#include "authfile.h"
- #include "pathnames.h"
- #include "auth.h"
- #include "canohost.h"
-@@ -42,8 +44,22 @@
- auth_rhosts_rsa_key_allowed(struct passwd *pw, char *cuser, char *chost,
- Key *client_host_key)
- {
-+ char *fp;
- HostStatus host_status;
-
-+ if (blacklisted_key(client_host_key)) {
-+ fp = key_fingerprint(client_host_key, SSH_FP_MD5, SSH_FP_HEX);
-+ if (options.permit_blacklisted_keys)
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1)); continuing anyway", fp);
-+ else
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1))", fp);
-+ free(fp);
-+ if (!options.permit_blacklisted_keys)
-+ return 0;
-+ }
-+
- if (auth_key_is_revoked(client_host_key))
- return 0;
-
---- openssh-4.7p1.orig/authfile.h
-+++ openssh-4.7p1/authfile.h
-@@ -23,4 +23,7 @@
- Key *key_load_private_pem(int, int, const char *, char **);
- int key_perm_ok(int, const char *);
-
-+char *blacklist_filename(const Key *key);
-+int blacklisted_key(const Key *key);
-+
- #endif
---- openssh-4.7p1.orig/ssh-vulnkey.1
-+++ openssh-4.7p1/ssh-vulnkey.1
-@@ -0,0 +1,187 @@
-+.\" 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
-+.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
-+.Nm
-+will exit zero if any of the given keys were in the compromised list,
-+otherwise non-zero.
-+.Pp
-+Unless the
-+.Cm PermitBlacklistedKeys
-+option is used,
-+.Xr sshd 8
-+will reject attempts to authenticate with keys in the compromised list.
-+.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.
-+.El
-+.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 /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
-+If present, lists the blacklisted keys of type
-+.Ar TYPE
-+.Pf ( Dq RSA1 ,
-+.Dq RSA ,
-+or
-+.Dq DSA )
-+and bit length
-+.Ar LENGTH .
-+The format of this file is described above.
-+.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.
---- openssh-5.6p1/auth2-hostbased.c~ 2010-08-24 14:10:03.000000000 +0300
-+++ openssh-5.6p1/auth2-hostbased.c 2010-08-24 14:12:10.632553591 +0300
-@@ -40,6 +40,7 @@
- #include "compat.h"
- #include "key.h"
- #include "hostfile.h"
-+#include "authfile.h"
- #include "auth.h"
- #include "canohost.h"
- #ifdef GSSAPI
-@@ -147,6 +148,19 @@
- int len;
- char *fp;
-
-+ if (blacklisted_key(key)) {
-+ fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
-+ if (options.permit_blacklisted_keys)
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1)); continuing anyway", fp);
-+ else
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1))", fp);
-+ free(fp);
-+ if (!options.permit_blacklisted_keys)
-+ return 0;
-+ }
-+
- if (auth_key_is_revoked(key))
- return 0;
-
---- openssh-4.7p1.orig/authfile.c
-+++ openssh-4.7p1/authfile.c
-@@ -65,6 +65,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[] =
-@@ -677,3 +678,113 @@
- return ret;
- }
-
-+
-+char *
-+blacklist_filename(const Key *key)
-+{
-+ char *name;
-+
-+ xasprintf(&name, "%s.%s-%u",
-+ _PATH_BLACKLIST, key_type(key), key_size(key));
-+ return name;
-+}
-+
-+/* Scan a blacklist of known-vulnerable keys. */
-+int
-+blacklisted_key(const Key *key)
-+{
-+ char *blacklist_file;
-+ 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;
-+
-+ blacklist_file = blacklist_filename(key);
-+ debug("Checking blacklist file %s", blacklist_file);
-+ fd = open(blacklist_file, O_RDONLY);
-+ if (fd < 0)
-+ 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, 256);
-+ if (r <= 0)
-+ goto out;
-+ if (buf[0] != '#')
-+ break;
-+
-+ newline = memchr(buf, '\n', 256);
-+ 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;
-+ char buf[32];
-+ 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)
-+ free(dgst_packed);
-+ if (dgst_hex)
-+ free(dgst_hex);
-+ if (fd >= 0)
-+ close(fd);
-+ free(blacklist_file);
-+ return ret;
-+}
---- openssh-4.7p1.orig/ssh-vulnkey.c
-+++ openssh-4.7p1/ssh-vulnkey.c
-@@ -0,0 +1,325 @@
-+/*
-+ * 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 <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 "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 quiet = 0;
-+
-+static void
-+usage(void)
-+{
-+ fprintf(stderr, "usage: %s [-aq] [file ...]\n", __progname);
-+ fprintf(stderr, "Options:\n");
-+ fprintf(stderr, " -a Check keys of all users.\n");
-+ fprintf(stderr, " -q Quiet mode.\n");
-+ exit(1);
-+}
-+
-+void
-+describe_key(const char *msg, const Key *key, const char *comment)
-+{
-+ char *fp;
-+
-+ fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
-+ if (!quiet)
-+ printf("%s: %u %s %s\n", msg, key_size(key), fp, comment);
-+ free(fp);
-+}
-+
-+int
-+do_key(const Key *key, const char *comment)
-+{
-+ char *blacklist_file;
-+ struct stat st;
-+ int ret = 1;
-+
-+ blacklist_file = blacklist_filename(key);
-+ if (stat(blacklist_file, &st) < 0)
-+ describe_key("Unknown (no blacklist information)",
-+ key, comment);
-+ else if (blacklisted_key(key)) {
-+ describe_key("COMPROMISED", key, comment);
-+ ret = 0;
-+ } else
-+ describe_key("Not blacklisted", key, comment);
-+ free(blacklist_file);
-+
-+ 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) {
-+ f = fopen(filename, "r");
-+ if (!f) {
-+ char pubfile[MAXPATHLEN];
-+ if (strlcpy(pubfile, filename, sizeof pubfile) <
-+ sizeof(pubfile) &&
-+ strlcat(pubfile, ".pub", sizeof pubfile) <
-+ sizeof(pubfile))
-+ f = fopen(pubfile, "r");
-+ }
-+ if (!f) {
-+ if (!quiet_open)
-+ perror(filename);
-+ return -1;
-+ }
-+ } else
-+ f = stdin;
-+ while (read_keyfile_line(f, filename, line, sizeof(line),
-+ &linenum) != -1) {
-+ int i;
-+ char *space;
-+ int type;
-+
-+ /* 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 (atoi(cp) == 0 && 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(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(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(key, comment))
-+ ret = 0;
-+ found = 1;
-+ }
-+ if (comment)
-+ free(comment);
-+ }
-+
-+ return ret;
-+}
-+
-+int
-+do_host(void)
-+{
-+ int i;
-+ struct stat st;
-+ int ret = 1;
-+
-+ for (i = 0; default_host_files[i]; i++) {
-+ if (stat(default_host_files[i], &st) < 0)
-+ continue;
-+ if (!do_filename(default_host_files[i], 1))
-+ ret = 0;
-+ }
-+
-+ return ret;
-+}
-+
-+int
-+do_user(const char *dir)
-+{
-+ int i;
-+ char buf[MAXPATHLEN];
-+ struct stat st;
-+ int ret = 1;
-+
-+ for (i = 0; default_files[i]; i++) {
-+ snprintf(buf, sizeof(buf), "%s/%s", dir, default_files[i]);
-+ if (stat(buf, &st) < 0)
-+ continue;
-+ if (!do_filename(buf, 0))
-+ ret = 0;
-+ }
-+
-+ 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, "ahq")) != -1) {
-+ switch (opt) {
-+ case 'a':
-+ all_users = 1;
-+ break;
-+ case 'q':
-+ quiet = 1;
-+ break;
-+ case 'h':
-+ default:
-+ usage();
-+ }
-+ }
-+
-+ if (all_users) {
-+ struct passwd *pw;
-+
-+ if (!do_host())
-+ ret = 0;
-+
-+ while ((pw = getpwent()) != NULL) {
-+ if (pw->pw_dir) {
-+ if (!do_user(pw->pw_dir))
-+ ret = 0;
-+ }
-+ }
-+ } else if (optind == argc) {
-+ struct passwd *pw;
-+
-+ if (!do_host())
-+ ret = 0;
-+
-+ if ((pw = getpwuid(getuid())) == NULL)
-+ fprintf(stderr, "No user found with uid %u\n",
-+ (u_int)getuid());
-+ else {
-+ if (!do_user(pw->pw_dir))
-+ ret = 0;
-+ }
-+ } else {
-+ while (optind < argc)
-+ if (!do_filename(argv[optind++], 0))
-+ ret = 0;
-+ }
-+
-+ return ret;
-+}
---- openssh-4.7p1.orig/auth-rsa.c
-+++ openssh-4.7p1/auth-rsa.c
-@@ -40,6 +40,7 @@
- #include "servconf.h"
- #include "key.h"
- #include "hostfile.h"
-+#include "authfile.h"
- #include "auth.h"
- #ifdef GSSAPI
- #include "ssh-gss.h"
-@@ -221,6 +222,7 @@
- char *cp;
- char *key_options;
- int keybits;
-+ char *fp;
-
- /* Skip leading whitespace, empty and comment lines. */
- for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
-@@ -265,6 +267,19 @@
- "actual %d vs. announced %d.",
- file, linenum, BN_num_bits(key->rsa->n), bits);
-
-+ if (blacklisted_key(key)) {
-+ fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
-+ if (options.permit_blacklisted_keys)
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1)); continuing anyway", fp);
-+ else
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1))", fp);
-+ free(fp);
-+ if (!options.permit_blacklisted_keys)
-+ continue;
-+ }
-+
- /* We have found the desired key. */
- /*
- * If our options do not allow this key to be used,
---- openssh-4.7p1.orig/pathnames.h
-+++ openssh-4.7p1/pathnames.h
-@@ -43,6 +43,8 @@
- /* Backwards compatibility */
- #define _PATH_DH_PRIMES SSHDIR "/primes"
-
-+#define _PATH_BLACKLIST SSHDIR "/blacklist"
-+
- #ifndef _PATH_SSH_PROGRAM
- #define _PATH_SSH_PROGRAM "/usr/bin/ssh"
- #endif
---- openssh-5.9p1/auth2-pubkey.c~ 2011-09-29 00:36:17.000000000 +0300
-+++ openssh-5.9p1/auth2-pubkey.c 2011-09-29 00:37:17.847762648 +0300
-@@ -42,6 +42,7 @@
- #include "compat.h"
- #include "key.h"
- #include "hostfile.h"
-+#include "authfile.h"
- #include "auth.h"
- #include "pathnames.h"
- #include "uidswap.h"
-@@ -608,6 +609,20 @@
- {
- u_int success, i;
- char *file;
-+ char *fp;
-+
-+ if (blacklisted_key(key)) {
-+ fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
-+ if (options.permit_blacklisted_keys)
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1)); continuing anyway", fp);
-+ else
-+ logit("Public key %s blacklisted (see "
-+ "ssh-vulnkey(1))", fp);
-+ free(fp);
-+ if (!options.permit_blacklisted_keys)
-+ return 0;
-+ }
-
- if (auth_key_is_revoked(key))
- return 0;
-
--- /dev/null
+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: 2013-09-14
+
+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@
+@@ -37,7 +38,8 @@
+ -D_PATH_SSH_KEY_SIGN=\"$(SSH_KEYSIGN)\" \
+ -D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \
+ -D_PATH_SSH_PIDDIR=\"$(piddir)\" \
+- -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\"
++ -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \
++ -D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\"
+
+ CC=@CC@
+ LD=@LD@
+@@ -61,7 +63,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=authfd.o authfile.o bufaux.o bufbn.o buffer.o \
+ canohost.o channels.o cipher.o cipher-aes.o \
+@@ -96,8 +98,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
+@@ -176,6 +178,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)
+@@ -272,6 +277,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
+@@ -286,6 +292,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
+@@ -367,6 +374,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/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
+@@ -237,7 +237,7 @@
+ free(fp);
+
+ /* 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"
+@@ -657,10 +658,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());
++ free(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());
++ free(key_fp);
++ return 1;
++ }
++ }
++
+ if (options.revoked_keys_file == NULL)
+ return 0;
+ switch (ssh_krl_file_contains_key(options.revoked_keys_file, key)) {
+Index: b/auth.h
+===================================================================
+--- a/auth.h
++++ b/auth.h
+@@ -191,7 +191,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
+@@ -150,7 +150,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
+@@ -647,9 +647,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,139 @@
+ 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:
++ free(dgst_packed);
++ if (ret != 1 && dgst_hex) {
++ free(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);
++ free(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);
++ free(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
+@@ -128,6 +128,7 @@
+ oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
+ oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
+ oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
++ oUseBlacklistedKeys,
+ oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
+ oClearAllForwardings, oNoHostAuthenticationForLocalhost,
+ oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
+@@ -161,6 +162,7 @@
+ { "passwordauthentication", oPasswordAuthentication },
+ { "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
+ { "kbdinteractivedevices", oKbdInteractiveDevices },
++ { "useblacklistedkeys", oUseBlacklistedKeys },
+ { "rsaauthentication", oRSAAuthentication },
+ { "pubkeyauthentication", oPubkeyAuthentication },
+ { "dsaauthentication", oPubkeyAuthentication }, /* alias */
+@@ -523,6 +525,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;
+@@ -1210,6 +1216,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;
+@@ -1320,6 +1327,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
+@@ -114,6 +114,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;
+@@ -257,6 +258,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)
+@@ -338,7 +341,7 @@
+ sListenAddress, sAddressFamily,
+ sPrintMotd, sPrintLastLog, sIgnoreRhosts,
+ sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
+- sStrictModes, sEmptyPasswd, sTCPKeepAlive,
++ sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive,
+ sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
+ sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
+ sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
+@@ -451,6 +454,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 },
+@@ -1158,6 +1162,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;
+@@ -2036,6 +2044,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
+@@ -121,6 +121,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
+@@ -167,7 +167,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;
+@@ -243,6 +243,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);
++ free(fp);
++ key_free(private);
++ free(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
+@@ -809,6 +809,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,386 @@
++/*
++ * 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);
++ }
++ free(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;
++ }
++ free(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) {
++ free(file);
++ continue;
++ }
++ if (!do_filename(file, 0))
++ ret = 0;
++ free(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
+@@ -1447,6 +1447,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
+@@ -1525,7 +1525,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;
+@@ -1583,6 +1583,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);
++ free(fp);
++ if (!options.use_blacklisted_keys) {
++ key_free(public);
++ free(filename);
++ filename = NULL;
++ public = NULL;
++ }
++ }
+ free(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
+@@ -1229,6 +1229,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
+@@ -1491,6 +1491,8 @@
+
+ /* list of keys stored in the filesystem and PKCS#11 */
+ 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;
+@@ -1608,7 +1610,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,
+ id->userprovided);
+Index: b/sshd.8
+===================================================================
+--- a/sshd.8
++++ b/sshd.8
+@@ -954,6 +954,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
+@@ -1688,6 +1688,11 @@
+ sensitive_data.host_pubkeys[i] = NULL;
+ continue;
+ }
++ if (auth_key_is_revoked(key != NULL ? key : pubkey, 1)) {
++ sensitive_data.host_keys[i] = NULL;
++ sensitive_data.host_pubkeys[i] = NULL;
++ continue;
++ }
+
+ switch (keytype) {
+ case KEY_RSA1:
+Index: b/sshd_config.5
+===================================================================
+--- a/sshd_config.5
++++ b/sshd_config.5
+@@ -885,6 +885,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.