From: Marcin Krol Date: Wed, 5 Feb 2014 13:25:32 +0000 (+0000) Subject: - ssh-vulnkey.patch from Debian X-Git-Url: https://git.tld-linux.org/?a=commitdiff_plain;h=d8891d25042c9bfdbb72230aa4726cfbd2420edf;p=packages%2Fopenssh.git - ssh-vulnkey.patch from Debian --- diff --git a/openssh-blacklist.diff b/openssh-blacklist.diff deleted file mode 100644 index 5de3dca..0000000 --- a/openssh-blacklist.diff +++ /dev/null @@ -1,984 +0,0 @@ -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 - #include - -+#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 -+#include -+ -+#include -+#include -+#include -+#include -+ -+#include -+ -+#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; - diff --git a/openssh-vulnkey.patch b/openssh-vulnkey.patch new file mode 100644 index 0000000..a569112 --- /dev/null +++ b/openssh-vulnkey.patch @@ -0,0 +1,1382 @@ +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 +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 ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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. diff --git a/openssh.spec b/openssh.spec index fcf1931..21b38b1 100644 --- a/openssh.spec +++ b/openssh.spec @@ -64,8 +64,8 @@ Patch7: ldap-helper-sigpipe.patch Patch9: %{name}-5.2p1-hpn13v6.diff Patch10: %{name}-include.patch Patch11: %{name}-chroot.patch -# http://people.debian.org/~cjwatson/%{name}-blacklist.diff -Patch12: %{name}-blacklist.diff +# ssh-vulnkey.patch from debian sources +Patch12: %{name}-vulnkey.patch Patch13: %{name}-kuserok.patch Patch14: %{name}-bind.patch Patch15: %{name}-disable_ldap.patch @@ -533,9 +533,9 @@ openldap-a. %patch10 -p1 %patch11 -p1 # do we really need to drag this old/obsolete patch? -#%patch12 -p1 +%patch12 -p1 # code changed in upstream, needs baggins verification -#%patch13 -p1 +%patch13 -p1 %patch14 -p1 %{!?with_ldap:%patch15 -p1}