summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Watson <cjwatson@ubuntu.com>2014-02-09 16:09:50 +0000
committerColin Watson <cjwatson@debian.org>2014-02-09 16:17:31 +0000
commit8909ff0e3cd07d1b042d1be1c8b8828dbf6c9a83 (patch)
treeebee4092f1411059e34da6f66b4ebd64f4411020
parent07f2a771c490bd68cd5c5ea9c535705e93bd94f3 (diff)
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. Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1469 Last-Update: 2013-09-14 Patch-Name: ssh-vulnkey.patch
-rw-r--r--Makefile.in17
-rw-r--r--auth-rh-rsa.c2
-rw-r--r--auth-rsa.c2
-rw-r--r--auth.c27
-rw-r--r--auth.h2
-rw-r--r--auth2-hostbased.c2
-rw-r--r--auth2-pubkey.c5
-rw-r--r--authfile.c136
-rw-r--r--authfile.h2
-rw-r--r--pathnames.h7
-rw-r--r--readconf.c9
-rw-r--r--readconf.h1
-rw-r--r--servconf.c11
-rw-r--r--servconf.h1
-rw-r--r--ssh-add.15
-rw-r--r--ssh-add.c10
-rw-r--r--ssh-keygen.11
-rw-r--r--ssh-vulnkey.1242
-rw-r--r--ssh-vulnkey.c386
-rw-r--r--ssh.11
-rw-r--r--ssh.c18
-rw-r--r--ssh_config.517
-rw-r--r--sshconnect2.c4
-rw-r--r--sshd.81
-rw-r--r--sshd.c5
-rw-r--r--sshd_config.514
26 files changed, 913 insertions, 15 deletions
diff --git a/Makefile.in b/Makefile.in
index f9799268a..b8f509941 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -26,6 +26,7 @@ ASKPASS_PROGRAM=$(libexecdir)/ssh-askpass
26SFTP_SERVER=$(libexecdir)/sftp-server 26SFTP_SERVER=$(libexecdir)/sftp-server
27SSH_KEYSIGN=$(libexecdir)/ssh-keysign 27SSH_KEYSIGN=$(libexecdir)/ssh-keysign
28SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper 28SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
29SSH_DATADIR=$(datadir)/ssh
29PRIVSEP_PATH=@PRIVSEP_PATH@ 30PRIVSEP_PATH=@PRIVSEP_PATH@
30SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@ 31SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@
31STRIP_OPT=@STRIP_OPT@ 32STRIP_OPT=@STRIP_OPT@
@@ -37,7 +38,8 @@ PATHS= -DSSHDIR=\"$(sysconfdir)\" \
37 -D_PATH_SSH_KEY_SIGN=\"$(SSH_KEYSIGN)\" \ 38 -D_PATH_SSH_KEY_SIGN=\"$(SSH_KEYSIGN)\" \
38 -D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \ 39 -D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \
39 -D_PATH_SSH_PIDDIR=\"$(piddir)\" \ 40 -D_PATH_SSH_PIDDIR=\"$(piddir)\" \
40 -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" 41 -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \
42 -D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\"
41 43
42CC=@CC@ 44CC=@CC@
43LD=@LD@ 45LD=@LD@
@@ -61,7 +63,7 @@ LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@
61EXEEXT=@EXEEXT@ 63EXEEXT=@EXEEXT@
62MANFMT=@MANFMT@ 64MANFMT=@MANFMT@
63 65
64TARGETS=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) 66TARGETS=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)
65 67
66LIBSSH_OBJS=authfd.o authfile.o bufaux.o bufbn.o buffer.o \ 68LIBSSH_OBJS=authfd.o authfile.o bufaux.o bufbn.o buffer.o \
67 canohost.o channels.o cipher.o cipher-aes.o \ 69 canohost.o channels.o cipher.o cipher-aes.o \
@@ -96,8 +98,8 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \
96 sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \ 98 sandbox-null.o sandbox-rlimit.o sandbox-systrace.o sandbox-darwin.o \
97 sandbox-seccomp-filter.o 99 sandbox-seccomp-filter.o
98 100
99MANPAGES = 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 101MANPAGES = 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
100MANPAGES_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 102MANPAGES_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
101MANTYPE = @MANTYPE@ 103MANTYPE = @MANTYPE@
102 104
103CONFIGFILES=sshd_config.out ssh_config.out moduli.out 105CONFIGFILES=sshd_config.out ssh_config.out moduli.out
@@ -176,6 +178,9 @@ sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o s
176sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o 178sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o
177 $(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT) 179 $(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT)
178 180
181ssh-vulnkey$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-vulnkey.o
182 $(LD) -o $@ ssh-vulnkey.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
183
179# test driver for the loginrec code - not built by default 184# test driver for the loginrec code - not built by default
180logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o 185logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o
181 $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) 186 $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS)
@@ -272,6 +277,7 @@ install-files:
272 $(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT) 277 $(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
273 $(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT) 278 $(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
274 $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT) 279 $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
280 $(INSTALL) -m 0755 $(STRIP_OPT) ssh-vulnkey$(EXEEXT) $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
275 $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 281 $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
276 $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1 282 $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
277 $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1 283 $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
@@ -286,6 +292,7 @@ install-files:
286 $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 292 $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
287 $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 293 $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
288 $(INSTALL) -m 644 ssh-pkcs11-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-pkcs11-helper.8 294 $(INSTALL) -m 644 ssh-pkcs11-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-pkcs11-helper.8
295 $(INSTALL) -m 644 ssh-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
289 -rm -f $(DESTDIR)$(bindir)/slogin 296 -rm -f $(DESTDIR)$(bindir)/slogin
290 ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin 297 ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin
291 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 298 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1
@@ -367,6 +374,7 @@ uninstall:
367 -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT) 374 -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT)
368 -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT) 375 -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT)
369 -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT) 376 -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
377 -rm -f $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
370 -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT) 378 -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT)
371 -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT) 379 -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
372 -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT) 380 -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
@@ -379,6 +387,7 @@ uninstall:
379 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1 387 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1
380 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 388 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1
381 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1 389 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1
390 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
382 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 391 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8
383 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 392 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
384 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 393 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
diff --git a/auth-rh-rsa.c b/auth-rh-rsa.c
index b21a0f4a2..891ec3297 100644
--- a/auth-rh-rsa.c
+++ b/auth-rh-rsa.c
@@ -44,7 +44,7 @@ auth_rhosts_rsa_key_allowed(struct passwd *pw, char *cuser, char *chost,
44{ 44{
45 HostStatus host_status; 45 HostStatus host_status;
46 46
47 if (auth_key_is_revoked(client_host_key)) 47 if (auth_key_is_revoked(client_host_key, 0))
48 return 0; 48 return 0;
49 49
50 /* Check if we would accept it using rhosts authentication. */ 50 /* Check if we would accept it using rhosts authentication. */
diff --git a/auth-rsa.c b/auth-rsa.c
index 545aa496a..6ed152c5f 100644
--- a/auth-rsa.c
+++ b/auth-rsa.c
@@ -237,7 +237,7 @@ rsa_key_allowed_in_file(struct passwd *pw, char *file,
237 free(fp); 237 free(fp);
238 238
239 /* Never accept a revoked key */ 239 /* Never accept a revoked key */
240 if (auth_key_is_revoked(key)) 240 if (auth_key_is_revoked(key, 0))
241 break; 241 break;
242 242
243 /* We have found the desired key. */ 243 /* We have found the desired key. */
diff --git a/auth.c b/auth.c
index 9a36f1dac..6662e9a75 100644
--- a/auth.c
+++ b/auth.c
@@ -59,6 +59,7 @@
59#include "servconf.h" 59#include "servconf.h"
60#include "key.h" 60#include "key.h"
61#include "hostfile.h" 61#include "hostfile.h"
62#include "authfile.h"
62#include "auth.h" 63#include "auth.h"
63#include "auth-options.h" 64#include "auth-options.h"
64#include "canohost.h" 65#include "canohost.h"
@@ -657,10 +658,34 @@ getpwnamallow(const char *user)
657 658
658/* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */ 659/* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */
659int 660int
660auth_key_is_revoked(Key *key) 661auth_key_is_revoked(Key *key, int hostkey)
661{ 662{
662 char *key_fp; 663 char *key_fp;
663 664
665 if (blacklisted_key(key, &key_fp) == 1) {
666 if (options.permit_blacklisted_keys) {
667 if (hostkey)
668 error("Host key %s blacklisted (see "
669 "ssh-vulnkey(1)); continuing anyway",
670 key_fp);
671 else
672 logit("Public key %s from %s blacklisted (see "
673 "ssh-vulnkey(1)); continuing anyway",
674 key_fp, get_remote_ipaddr());
675 free(key_fp);
676 } else {
677 if (hostkey)
678 error("Host key %s blacklisted (see "
679 "ssh-vulnkey(1))", key_fp);
680 else
681 logit("Public key %s from %s blacklisted (see "
682 "ssh-vulnkey(1))",
683 key_fp, get_remote_ipaddr());
684 free(key_fp);
685 return 1;
686 }
687 }
688
664 if (options.revoked_keys_file == NULL) 689 if (options.revoked_keys_file == NULL)
665 return 0; 690 return 0;
666 switch (ssh_krl_file_contains_key(options.revoked_keys_file, key)) { 691 switch (ssh_krl_file_contains_key(options.revoked_keys_file, key)) {
diff --git a/auth.h b/auth.h
index 5b6824f71..ec95460cf 100644
--- a/auth.h
+++ b/auth.h
@@ -191,7 +191,7 @@ char *authorized_principals_file(struct passwd *);
191 191
192FILE *auth_openkeyfile(const char *, struct passwd *, int); 192FILE *auth_openkeyfile(const char *, struct passwd *, int);
193FILE *auth_openprincipals(const char *, struct passwd *, int); 193FILE *auth_openprincipals(const char *, struct passwd *, int);
194int auth_key_is_revoked(Key *); 194int auth_key_is_revoked(Key *, int);
195 195
196HostStatus 196HostStatus
197check_key_in_hostfiles(struct passwd *, Key *, const char *, 197check_key_in_hostfiles(struct passwd *, Key *, const char *,
diff --git a/auth2-hostbased.c b/auth2-hostbased.c
index a344dcc1f..3a17f1bf2 100644
--- a/auth2-hostbased.c
+++ b/auth2-hostbased.c
@@ -150,7 +150,7 @@ hostbased_key_allowed(struct passwd *pw, const char *cuser, char *chost,
150 int len; 150 int len;
151 char *fp; 151 char *fp;
152 152
153 if (auth_key_is_revoked(key)) 153 if (auth_key_is_revoked(key, 0))
154 return 0; 154 return 0;
155 155
156 resolvedname = get_canonical_hostname(options.use_dns); 156 resolvedname = get_canonical_hostname(options.use_dns);
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index 2b3ecb104..12eb8a6b2 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -647,9 +647,10 @@ user_key_allowed(struct passwd *pw, Key *key)
647 u_int success, i; 647 u_int success, i;
648 char *file; 648 char *file;
649 649
650 if (auth_key_is_revoked(key)) 650 if (auth_key_is_revoked(key, 0))
651 return 0; 651 return 0;
652 if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key)) 652 if (key_is_cert(key) &&
653 auth_key_is_revoked(key->cert->signature_key, 0))
653 return 0; 654 return 0;
654 655
655 success = user_cert_trusted_ca(pw, key); 656 success = user_cert_trusted_ca(pw, key);
diff --git a/authfile.c b/authfile.c
index 63ae16bbd..983359157 100644
--- a/authfile.c
+++ b/authfile.c
@@ -68,6 +68,7 @@
68#include "rsa.h" 68#include "rsa.h"
69#include "misc.h" 69#include "misc.h"
70#include "atomicio.h" 70#include "atomicio.h"
71#include "pathnames.h"
71 72
72#define MAX_KEY_FILE_SIZE (1024 * 1024) 73#define MAX_KEY_FILE_SIZE (1024 * 1024)
73 74
@@ -944,3 +945,138 @@ key_in_file(Key *key, const char *filename, int strict_type)
944 return ret; 945 return ret;
945} 946}
946 947
948/* Scan a blacklist of known-vulnerable keys in blacklist_file. */
949static int
950blacklisted_key_in_file(Key *key, const char *blacklist_file, char **fp)
951{
952 int fd = -1;
953 char *dgst_hex = NULL;
954 char *dgst_packed = NULL, *p;
955 int i;
956 size_t line_len;
957 struct stat st;
958 char buf[256];
959 off_t start, lower, upper;
960 int ret = 0;
961
962 debug("Checking blacklist file %s", blacklist_file);
963 fd = open(blacklist_file, O_RDONLY);
964 if (fd < 0) {
965 ret = -1;
966 goto out;
967 }
968
969 dgst_hex = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
970 /* Remove all colons */
971 dgst_packed = xcalloc(1, strlen(dgst_hex) + 1);
972 for (i = 0, p = dgst_packed; dgst_hex[i]; i++)
973 if (dgst_hex[i] != ':')
974 *p++ = dgst_hex[i];
975 /* Only compare least-significant 80 bits (to keep the blacklist
976 * size down)
977 */
978 line_len = strlen(dgst_packed + 12);
979 if (line_len > 32)
980 goto out;
981
982 /* Skip leading comments */
983 start = 0;
984 for (;;) {
985 ssize_t r;
986 char *newline;
987
988 r = atomicio(read, fd, buf, sizeof(buf));
989 if (r <= 0)
990 goto out;
991 if (buf[0] != '#')
992 break;
993
994 newline = memchr(buf, '\n', sizeof(buf));
995 if (!newline)
996 goto out;
997 start += newline + 1 - buf;
998 if (lseek(fd, start, SEEK_SET) < 0)
999 goto out;
1000 }
1001
1002 /* Initialise binary search record numbers */
1003 if (fstat(fd, &st) < 0)
1004 goto out;
1005 lower = 0;
1006 upper = (st.st_size - start) / (line_len + 1);
1007
1008 while (lower != upper) {
1009 off_t cur;
1010 int cmp;
1011
1012 cur = lower + (upper - lower) / 2;
1013
1014 /* Read this line and compare to digest; this is
1015 * overflow-safe since cur < max(off_t) / (line_len + 1) */
1016 if (lseek(fd, start + cur * (line_len + 1), SEEK_SET) < 0)
1017 break;
1018 if (atomicio(read, fd, buf, line_len) != line_len)
1019 break;
1020 cmp = memcmp(buf, dgst_packed + 12, line_len);
1021 if (cmp < 0) {
1022 if (cur == lower)
1023 break;
1024 lower = cur;
1025 } else if (cmp > 0) {
1026 if (cur == upper)
1027 break;
1028 upper = cur;
1029 } else {
1030 debug("Found %s in blacklist", dgst_hex);
1031 ret = 1;
1032 break;
1033 }
1034 }
1035
1036out:
1037 free(dgst_packed);
1038 if (ret != 1 && dgst_hex) {
1039 free(dgst_hex);
1040 dgst_hex = NULL;
1041 }
1042 if (fp)
1043 *fp = dgst_hex;
1044 if (fd >= 0)
1045 close(fd);
1046 return ret;
1047}
1048
1049/*
1050 * Scan blacklists of known-vulnerable keys. If a vulnerable key is found,
1051 * its fingerprint is returned in *fp, unless fp is NULL.
1052 */
1053int
1054blacklisted_key(Key *key, char **fp)
1055{
1056 Key *public;
1057 char *blacklist_file;
1058 int ret, ret2;
1059
1060 public = key_demote(key);
1061 if (public->type == KEY_RSA1)
1062 public->type = KEY_RSA;
1063
1064 xasprintf(&blacklist_file, "%s.%s-%u",
1065 _PATH_BLACKLIST, key_type(public), key_size(public));
1066 ret = blacklisted_key_in_file(public, blacklist_file, fp);
1067 free(blacklist_file);
1068 if (ret > 0) {
1069 key_free(public);
1070 return ret;
1071 }
1072
1073 xasprintf(&blacklist_file, "%s.%s-%u",
1074 _PATH_BLACKLIST_CONFIG, key_type(public), key_size(public));
1075 ret2 = blacklisted_key_in_file(public, blacklist_file, fp);
1076 free(blacklist_file);
1077 if (ret2 > ret)
1078 ret = ret2;
1079
1080 key_free(public);
1081 return ret;
1082}
diff --git a/authfile.h b/authfile.h
index 78349beb5..3f2bdcb06 100644
--- a/authfile.h
+++ b/authfile.h
@@ -28,4 +28,6 @@ Key *key_load_private_pem(int, int, const char *, char **);
28int key_perm_ok(int, const char *); 28int key_perm_ok(int, const char *);
29int key_in_file(Key *, const char *, int); 29int key_in_file(Key *, const char *, int);
30 30
31int blacklisted_key(Key *key, char **fp);
32
31#endif 33#endif
diff --git a/pathnames.h b/pathnames.h
index 5027fbaed..47f7867d5 100644
--- a/pathnames.h
+++ b/pathnames.h
@@ -18,6 +18,10 @@
18#define SSHDIR ETCDIR "/ssh" 18#define SSHDIR ETCDIR "/ssh"
19#endif 19#endif
20 20
21#ifndef _PATH_SSH_DATADIR
22#define _PATH_SSH_DATADIR "/usr/share/ssh"
23#endif
24
21#ifndef _PATH_SSH_PIDDIR 25#ifndef _PATH_SSH_PIDDIR
22#define _PATH_SSH_PIDDIR "/var/run" 26#define _PATH_SSH_PIDDIR "/var/run"
23#endif 27#endif
@@ -44,6 +48,9 @@
44/* Backwards compatibility */ 48/* Backwards compatibility */
45#define _PATH_DH_PRIMES SSHDIR "/primes" 49#define _PATH_DH_PRIMES SSHDIR "/primes"
46 50
51#define _PATH_BLACKLIST _PATH_SSH_DATADIR "/blacklist"
52#define _PATH_BLACKLIST_CONFIG SSHDIR "/blacklist"
53
47#ifndef _PATH_SSH_PROGRAM 54#ifndef _PATH_SSH_PROGRAM
48#define _PATH_SSH_PROGRAM "/usr/bin/ssh" 55#define _PATH_SSH_PROGRAM "/usr/bin/ssh"
49#endif 56#endif
diff --git a/readconf.c b/readconf.c
index 2695fd6c0..22e5a3a61 100644
--- a/readconf.c
+++ b/readconf.c
@@ -128,6 +128,7 @@ typedef enum {
128 oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication, 128 oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
129 oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias, 129 oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
130 oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication, 130 oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
131 oUseBlacklistedKeys,
131 oHostKeyAlgorithms, oBindAddress, oPKCS11Provider, 132 oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
132 oClearAllForwardings, oNoHostAuthenticationForLocalhost, 133 oClearAllForwardings, oNoHostAuthenticationForLocalhost,
133 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout, 134 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
@@ -161,6 +162,7 @@ static struct {
161 { "passwordauthentication", oPasswordAuthentication }, 162 { "passwordauthentication", oPasswordAuthentication },
162 { "kbdinteractiveauthentication", oKbdInteractiveAuthentication }, 163 { "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
163 { "kbdinteractivedevices", oKbdInteractiveDevices }, 164 { "kbdinteractivedevices", oKbdInteractiveDevices },
165 { "useblacklistedkeys", oUseBlacklistedKeys },
164 { "rsaauthentication", oRSAAuthentication }, 166 { "rsaauthentication", oRSAAuthentication },
165 { "pubkeyauthentication", oPubkeyAuthentication }, 167 { "pubkeyauthentication", oPubkeyAuthentication },
166 { "dsaauthentication", oPubkeyAuthentication }, /* alias */ 168 { "dsaauthentication", oPubkeyAuthentication }, /* alias */
@@ -523,6 +525,10 @@ parse_flag:
523 intptr = &options->challenge_response_authentication; 525 intptr = &options->challenge_response_authentication;
524 goto parse_flag; 526 goto parse_flag;
525 527
528 case oUseBlacklistedKeys:
529 intptr = &options->use_blacklisted_keys;
530 goto parse_flag;
531
526 case oGssAuthentication: 532 case oGssAuthentication:
527 intptr = &options->gss_authentication; 533 intptr = &options->gss_authentication;
528 goto parse_flag; 534 goto parse_flag;
@@ -1210,6 +1216,7 @@ initialize_options(Options * options)
1210 options->kbd_interactive_devices = NULL; 1216 options->kbd_interactive_devices = NULL;
1211 options->rhosts_rsa_authentication = -1; 1217 options->rhosts_rsa_authentication = -1;
1212 options->hostbased_authentication = -1; 1218 options->hostbased_authentication = -1;
1219 options->use_blacklisted_keys = -1;
1213 options->batch_mode = -1; 1220 options->batch_mode = -1;
1214 options->check_host_ip = -1; 1221 options->check_host_ip = -1;
1215 options->strict_host_key_checking = -1; 1222 options->strict_host_key_checking = -1;
@@ -1320,6 +1327,8 @@ fill_default_options(Options * options)
1320 options->rhosts_rsa_authentication = 0; 1327 options->rhosts_rsa_authentication = 0;
1321 if (options->hostbased_authentication == -1) 1328 if (options->hostbased_authentication == -1)
1322 options->hostbased_authentication = 0; 1329 options->hostbased_authentication = 0;
1330 if (options->use_blacklisted_keys == -1)
1331 options->use_blacklisted_keys = 0;
1323 if (options->batch_mode == -1) 1332 if (options->batch_mode == -1)
1324 options->batch_mode = 0; 1333 options->batch_mode = 0;
1325 if (options->check_host_ip == -1) 1334 if (options->check_host_ip == -1)
diff --git a/readconf.h b/readconf.h
index 675b35dfe..a508151f7 100644
--- a/readconf.h
+++ b/readconf.h
@@ -59,6 +59,7 @@ typedef struct {
59 int kbd_interactive_authentication; /* Try keyboard-interactive auth. */ 59 int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
60 char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */ 60 char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */
61 int zero_knowledge_password_authentication; /* Try jpake */ 61 int zero_knowledge_password_authentication; /* Try jpake */
62 int use_blacklisted_keys; /* If true, send */
62 int batch_mode; /* Batch mode: do not ask for passwords. */ 63 int batch_mode; /* Batch mode: do not ask for passwords. */
63 int check_host_ip; /* Also keep track of keys for IP address */ 64 int check_host_ip; /* Also keep track of keys for IP address */
64 int strict_host_key_checking; /* Strict host key checking. */ 65 int strict_host_key_checking; /* Strict host key checking. */
diff --git a/servconf.c b/servconf.c
index c938ae399..9155a8b70 100644
--- a/servconf.c
+++ b/servconf.c
@@ -114,6 +114,7 @@ initialize_server_options(ServerOptions *options)
114 options->password_authentication = -1; 114 options->password_authentication = -1;
115 options->kbd_interactive_authentication = -1; 115 options->kbd_interactive_authentication = -1;
116 options->challenge_response_authentication = -1; 116 options->challenge_response_authentication = -1;
117 options->permit_blacklisted_keys = -1;
117 options->permit_empty_passwd = -1; 118 options->permit_empty_passwd = -1;
118 options->permit_user_env = -1; 119 options->permit_user_env = -1;
119 options->use_login = -1; 120 options->use_login = -1;
@@ -257,6 +258,8 @@ fill_default_server_options(ServerOptions *options)
257 options->kbd_interactive_authentication = 0; 258 options->kbd_interactive_authentication = 0;
258 if (options->challenge_response_authentication == -1) 259 if (options->challenge_response_authentication == -1)
259 options->challenge_response_authentication = 1; 260 options->challenge_response_authentication = 1;
261 if (options->permit_blacklisted_keys == -1)
262 options->permit_blacklisted_keys = 0;
260 if (options->permit_empty_passwd == -1) 263 if (options->permit_empty_passwd == -1)
261 options->permit_empty_passwd = 0; 264 options->permit_empty_passwd = 0;
262 if (options->permit_user_env == -1) 265 if (options->permit_user_env == -1)
@@ -338,7 +341,7 @@ typedef enum {
338 sListenAddress, sAddressFamily, 341 sListenAddress, sAddressFamily,
339 sPrintMotd, sPrintLastLog, sIgnoreRhosts, 342 sPrintMotd, sPrintLastLog, sIgnoreRhosts,
340 sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, 343 sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
341 sStrictModes, sEmptyPasswd, sTCPKeepAlive, 344 sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive,
342 sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression, 345 sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
343 sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, 346 sRekeyLimit, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
344 sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, 347 sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
@@ -451,6 +454,7 @@ static struct {
451 { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL }, 454 { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
452 { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL }, 455 { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
453 { "strictmodes", sStrictModes, SSHCFG_GLOBAL }, 456 { "strictmodes", sStrictModes, SSHCFG_GLOBAL },
457 { "permitblacklistedkeys", sPermitBlacklistedKeys, SSHCFG_GLOBAL },
454 { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL }, 458 { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL },
455 { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL }, 459 { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL },
456 { "uselogin", sUseLogin, SSHCFG_GLOBAL }, 460 { "uselogin", sUseLogin, SSHCFG_GLOBAL },
@@ -1158,6 +1162,10 @@ process_server_config_line(ServerOptions *options, char *line,
1158 intptr = &options->tcp_keep_alive; 1162 intptr = &options->tcp_keep_alive;
1159 goto parse_flag; 1163 goto parse_flag;
1160 1164
1165 case sPermitBlacklistedKeys:
1166 intptr = &options->permit_blacklisted_keys;
1167 goto parse_flag;
1168
1161 case sEmptyPasswd: 1169 case sEmptyPasswd:
1162 intptr = &options->permit_empty_passwd; 1170 intptr = &options->permit_empty_passwd;
1163 goto parse_flag; 1171 goto parse_flag;
@@ -2036,6 +2044,7 @@ dump_config(ServerOptions *o)
2036 dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost); 2044 dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost);
2037 dump_cfg_fmtint(sStrictModes, o->strict_modes); 2045 dump_cfg_fmtint(sStrictModes, o->strict_modes);
2038 dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive); 2046 dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive);
2047 dump_cfg_fmtint(sPermitBlacklistedKeys, o->permit_blacklisted_keys);
2039 dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd); 2048 dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd);
2040 dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env); 2049 dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env);
2041 dump_cfg_fmtint(sUseLogin, o->use_login); 2050 dump_cfg_fmtint(sUseLogin, o->use_login);
diff --git a/servconf.h b/servconf.h
index ab6e34669..f655c5bf7 100644
--- a/servconf.h
+++ b/servconf.h
@@ -121,6 +121,7 @@ typedef struct {
121 int challenge_response_authentication; 121 int challenge_response_authentication;
122 int zero_knowledge_password_authentication; 122 int zero_knowledge_password_authentication;
123 /* If true, permit jpake auth */ 123 /* If true, permit jpake auth */
124 int permit_blacklisted_keys; /* If true, permit */
124 int permit_empty_passwd; /* If false, do not permit empty 125 int permit_empty_passwd; /* If false, do not permit empty
125 * passwords. */ 126 * passwords. */
126 int permit_user_env; /* If true, read ~/.ssh/environment */ 127 int permit_user_env; /* If true, read ~/.ssh/environment */
diff --git a/ssh-add.1 b/ssh-add.1
index 44846b67e..d394b2696 100644
--- a/ssh-add.1
+++ b/ssh-add.1
@@ -81,6 +81,10 @@ environment variable must contain the name of its socket for
81.Nm 81.Nm
82to work. 82to work.
83.Pp 83.Pp
84Any keys recorded in the blacklist of known-compromised keys (see
85.Xr ssh-vulnkey 1 )
86will be refused.
87.Pp
84The options are as follows: 88The options are as follows:
85.Bl -tag -width Ds 89.Bl -tag -width Ds
86.It Fl c 90.It Fl c
@@ -186,6 +190,7 @@ is unable to contact the authentication agent.
186.Xr ssh 1 , 190.Xr ssh 1 ,
187.Xr ssh-agent 1 , 191.Xr ssh-agent 1 ,
188.Xr ssh-keygen 1 , 192.Xr ssh-keygen 1 ,
193.Xr ssh-vulnkey 1 ,
189.Xr sshd 8 194.Xr sshd 8
190.Sh AUTHORS 195.Sh AUTHORS
191OpenSSH is a derivative of the original and free 196OpenSSH is a derivative of the original and free
diff --git a/ssh-add.c b/ssh-add.c
index 5e8166f66..b309582f5 100644
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -167,7 +167,7 @@ static int
167add_file(AuthenticationConnection *ac, const char *filename, int key_only) 167add_file(AuthenticationConnection *ac, const char *filename, int key_only)
168{ 168{
169 Key *private, *cert; 169 Key *private, *cert;
170 char *comment = NULL; 170 char *comment = NULL, *fp;
171 char msg[1024], *certpath = NULL; 171 char msg[1024], *certpath = NULL;
172 int fd, perms_ok, ret = -1; 172 int fd, perms_ok, ret = -1;
173 Buffer keyblob; 173 Buffer keyblob;
@@ -243,6 +243,14 @@ add_file(AuthenticationConnection *ac, const char *filename, int key_only)
243 } else { 243 } else {
244 fprintf(stderr, "Could not add identity: %s\n", filename); 244 fprintf(stderr, "Could not add identity: %s\n", filename);
245 } 245 }
246 if (blacklisted_key(private, &fp) == 1) {
247 fprintf(stderr, "Public key %s blacklisted (see "
248 "ssh-vulnkey(1)); refusing to add it\n", fp);
249 free(fp);
250 key_free(private);
251 free(comment);
252 return -1;
253 }
246 254
247 /* Skip trying to load the cert if requested */ 255 /* Skip trying to load the cert if requested */
248 if (key_only) 256 if (key_only)
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index 0d55854e9..144be7d6b 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -809,6 +809,7 @@ The file format is described in
809.Xr ssh 1 , 809.Xr ssh 1 ,
810.Xr ssh-add 1 , 810.Xr ssh-add 1 ,
811.Xr ssh-agent 1 , 811.Xr ssh-agent 1 ,
812.Xr ssh-vulnkey 1 ,
812.Xr moduli 5 , 813.Xr moduli 5 ,
813.Xr sshd 8 814.Xr sshd 8
814.Rs 815.Rs
diff --git a/ssh-vulnkey.1 b/ssh-vulnkey.1
new file mode 100644
index 000000000..bcb9d31c6
--- /dev/null
+++ b/ssh-vulnkey.1
@@ -0,0 +1,242 @@
1.\" Copyright (c) 2008 Canonical Ltd. All rights reserved.
2.\"
3.\" Redistribution and use in source and binary forms, with or without
4.\" modification, are permitted provided that the following conditions
5.\" are met:
6.\" 1. Redistributions of source code must retain the above copyright
7.\" notice, this list of conditions and the following disclaimer.
8.\" 2. Redistributions in binary form must reproduce the above copyright
9.\" notice, this list of conditions and the following disclaimer in the
10.\" documentation and/or other materials provided with the distribution.
11.\"
12.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
13.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
14.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
15.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
16.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
17.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
18.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
19.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22.\"
23.Dd $Mdocdate: May 12 2008 $
24.Dt SSH-VULNKEY 1
25.Os
26.Sh NAME
27.Nm ssh-vulnkey
28.Nd check blacklist of compromised keys
29.Sh SYNOPSIS
30.Nm
31.Op Fl q | Fl v
32.Ar file ...
33.Nm
34.Fl a
35.Sh DESCRIPTION
36.Nm
37checks a key against a blacklist of compromised keys.
38.Pp
39A substantial number of keys are known to have been generated using a broken
40version of OpenSSL distributed by Debian which failed to seed its random
41number generator correctly.
42Keys generated using these OpenSSL versions should be assumed to be
43compromised.
44This tool may be useful in checking for such keys.
45.Pp
46Keys that are compromised cannot be repaired; replacements must be generated
47using
48.Xr ssh-keygen 1 .
49Make sure to update
50.Pa authorized_keys
51files on all systems where compromised keys were permitted to authenticate.
52.Pp
53The argument list will be interpreted as a list of paths to public key files
54or
55.Pa authorized_keys
56files.
57If no suitable file is found at a given path,
58.Nm
59will append
60.Pa .pub
61and retry, in case it was given a private key file.
62If no files are given as arguments,
63.Nm
64will check
65.Pa ~/.ssh/id_rsa ,
66.Pa ~/.ssh/id_dsa ,
67.Pa ~/.ssh/identity ,
68.Pa ~/.ssh/authorized_keys
69and
70.Pa ~/.ssh/authorized_keys2 ,
71as well as the system's host keys if readable.
72.Pp
73If
74.Dq -
75is given as an argument,
76.Nm
77will read from standard input.
78This can be used to process output from
79.Xr ssh-keyscan 1 ,
80for example:
81.Pp
82.Dl $ ssh-keyscan -t rsa remote.example.org | ssh-vulnkey -
83.Pp
84Unless the
85.Cm PermitBlacklistedKeys
86option is used,
87.Xr sshd 8
88will reject attempts to authenticate with keys in the compromised list.
89.Pp
90The output from
91.Nm
92looks like this:
93.Pp
94.Bd -literal -offset indent
95/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
96/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
97/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
98.Ed
99.Pp
100Each line is of the following format (any lines beginning with
101.Dq #
102should be ignored by scripts):
103.Pp
104.Dl Ar filename : Ns Ar line : Ar status : Ar type Ar size Ar fingerprint Ar comment
105.Pp
106It is important to distinguish between the possible values of
107.Ar status :
108.Pp
109.Bl -tag -width Ds
110.It COMPROMISED
111These keys are listed in a blacklist file, normally because their
112corresponding private keys are well-known.
113Replacements must be generated using
114.Xr ssh-keygen 1 .
115.It Not blacklisted
116A blacklist file exists for this key type and size, but this key is not
117listed in it.
118Unless there is some particular reason to believe otherwise, this key
119may be used safely.
120(Note that DSA keys used with the broken version of OpenSSL distributed
121by Debian may be compromised in the event that anyone captured a network
122trace, even if they were generated with a secure version of OpenSSL.)
123.It Unknown (blacklist file not installed)
124No blacklist file exists for this key type and size.
125You should find a suitable published blacklist and install it before
126deciding whether this key is safe to use.
127.El
128.Pp
129The options are as follows:
130.Bl -tag -width Ds
131.It Fl a
132Check keys of all users on the system.
133You will typically need to run
134.Nm
135as root to use this option.
136For each user,
137.Nm
138will check
139.Pa ~/.ssh/id_rsa ,
140.Pa ~/.ssh/id_dsa ,
141.Pa ~/.ssh/identity ,
142.Pa ~/.ssh/authorized_keys
143and
144.Pa ~/.ssh/authorized_keys2 .
145It will also check the system's host keys.
146.It Fl q
147Quiet mode.
148Normally,
149.Nm
150outputs the fingerprint of each key scanned, with a description of its
151status.
152This option suppresses that output.
153.It Fl v
154Verbose mode.
155Normally,
156.Nm
157does not output anything for keys that are not listed in their corresponding
158blacklist file (although it still produces output for keys for which there
159is no blacklist file, since their status is unknown).
160This option causes
161.Nm
162to produce output for all keys.
163.El
164.Sh EXIT STATUS
165.Nm
166will exit zero if any of the given keys were in the compromised list,
167otherwise non-zero.
168.Sh BLACKLIST FILE FORMAT
169The blacklist file may start with comments, on lines starting with
170.Dq # .
171After these initial comments, it must follow a strict format:
172.Pp
173.Bl -bullet -offset indent -compact
174.It
175All the lines must be exactly the same length (20 characters followed by a
176newline) and must be in sorted order.
177.It
178Each line must consist of the lower-case hexadecimal MD5 key fingerprint,
179without colons, and with the first 12 characters removed (that is, the least
180significant 80 bits of the fingerprint).
181.El
182.Pp
183The key fingerprint may be generated using
184.Xr ssh-keygen 1 :
185.Pp
186.Dl $ ssh-keygen -l -f /path/to/key
187.Pp
188This strict format is necessary to allow the blacklist file to be checked
189quickly, using a binary-search algorithm.
190.Sh FILES
191.Bl -tag -width Ds
192.It Pa ~/.ssh/id_rsa
193If present, contains the protocol version 2 RSA authentication identity of
194the user.
195.It Pa ~/.ssh/id_dsa
196If present, contains the protocol version 2 DSA authentication identity of
197the user.
198.It Pa ~/.ssh/identity
199If present, contains the protocol version 1 RSA authentication identity of
200the user.
201.It Pa ~/.ssh/authorized_keys
202If present, lists the public keys (RSA/DSA) that can be used for logging in
203as this user.
204.It Pa ~/.ssh/authorized_keys2
205Obsolete name for
206.Pa ~/.ssh/authorized_keys .
207This file may still be present on some old systems, but should not be
208created if it is missing.
209.It Pa /etc/ssh/ssh_host_rsa_key
210If present, contains the protocol version 2 RSA identity of the system.
211.It Pa /etc/ssh/ssh_host_dsa_key
212If present, contains the protocol version 2 DSA identity of the system.
213.It Pa /etc/ssh/ssh_host_key
214If present, contains the protocol version 1 RSA identity of the system.
215.It Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
216If present, lists the blacklisted keys of type
217.Ar TYPE
218.Pf ( Dq RSA
219or
220.Dq DSA )
221and bit length
222.Ar LENGTH .
223The format of this file is described above.
224RSA1 keys are converted to RSA before being checked in the blacklist.
225Note that the fingerprints of RSA1 keys are computed differently, so you
226will not be able to find them in the blacklist by hand.
227.It Pa /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
228Same as
229.Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH ,
230but may be edited by the system administrator to add new blacklist entries.
231.El
232.Sh SEE ALSO
233.Xr ssh-keygen 1 ,
234.Xr sshd 8
235.Sh AUTHORS
236.An -nosplit
237.An Colin Watson Aq cjwatson@ubuntu.com
238.Pp
239Florian Weimer suggested the option to check keys of all users, and the idea
240of processing
241.Xr ssh-keyscan 1
242output.
diff --git a/ssh-vulnkey.c b/ssh-vulnkey.c
new file mode 100644
index 000000000..ca1a5be74
--- /dev/null
+++ b/ssh-vulnkey.c
@@ -0,0 +1,386 @@
1/*
2 * Copyright (c) 2008 Canonical Ltd. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "includes.h"
26
27#include <sys/types.h>
28#include <sys/stat.h>
29
30#include <errno.h>
31#include <string.h>
32#include <stdio.h>
33#include <fcntl.h>
34#include <unistd.h>
35
36#include <openssl/evp.h>
37
38#include "xmalloc.h"
39#include "ssh.h"
40#include "log.h"
41#include "key.h"
42#include "authfile.h"
43#include "pathnames.h"
44#include "uidswap.h"
45#include "misc.h"
46
47extern char *__progname;
48
49/* Default files to check */
50static char *default_host_files[] = {
51 _PATH_HOST_RSA_KEY_FILE,
52 _PATH_HOST_DSA_KEY_FILE,
53 _PATH_HOST_KEY_FILE,
54 NULL
55};
56static char *default_files[] = {
57 _PATH_SSH_CLIENT_ID_RSA,
58 _PATH_SSH_CLIENT_ID_DSA,
59 _PATH_SSH_CLIENT_IDENTITY,
60 _PATH_SSH_USER_PERMITTED_KEYS,
61 _PATH_SSH_USER_PERMITTED_KEYS2,
62 NULL
63};
64
65static int verbosity = 0;
66
67static int some_keys = 0;
68static int some_unknown = 0;
69static int some_compromised = 0;
70
71static void
72usage(void)
73{
74 fprintf(stderr, "usage: %s [-aqv] [file ...]\n", __progname);
75 fprintf(stderr, "Options:\n");
76 fprintf(stderr, " -a Check keys of all users.\n");
77 fprintf(stderr, " -q Quiet mode.\n");
78 fprintf(stderr, " -v Verbose mode.\n");
79 exit(1);
80}
81
82static void
83describe_key(const char *filename, u_long linenum, const char *msg,
84 Key *key, const char *comment, int min_verbosity)
85{
86 char *fp;
87
88 fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
89 if (verbosity >= min_verbosity) {
90 if (strchr(filename, ':'))
91 printf("\"%s\"", filename);
92 else
93 printf("%s", filename);
94 printf(":%lu: %s: %s %u %s %s\n", linenum, msg,
95 key_type(key), key_size(key), fp, comment);
96 }
97 free(fp);
98}
99
100static int
101do_key(const char *filename, u_long linenum,
102 Key *key, const char *comment)
103{
104 Key *public;
105 int blacklist_status;
106 int ret = 1;
107
108 some_keys = 1;
109
110 public = key_demote(key);
111 if (public->type == KEY_RSA1)
112 public->type = KEY_RSA;
113
114 blacklist_status = blacklisted_key(public, NULL);
115 if (blacklist_status == -1) {
116 describe_key(filename, linenum,
117 "Unknown (blacklist file not installed)", key, comment, 0);
118 some_unknown = 1;
119 } else if (blacklist_status == 1) {
120 describe_key(filename, linenum,
121 "COMPROMISED", key, comment, 0);
122 some_compromised = 1;
123 ret = 0;
124 } else
125 describe_key(filename, linenum,
126 "Not blacklisted", key, comment, 1);
127
128 key_free(public);
129
130 return ret;
131}
132
133static int
134do_filename(const char *filename, int quiet_open)
135{
136 FILE *f;
137 char line[SSH_MAX_PUBKEY_BYTES];
138 char *cp;
139 u_long linenum = 0;
140 Key *key;
141 char *comment = NULL;
142 int found = 0, ret = 1;
143
144 /* Copy much of key_load_public's logic here so that we can read
145 * several keys from a single file (e.g. authorized_keys).
146 */
147
148 if (strcmp(filename, "-") != 0) {
149 int save_errno;
150 f = fopen(filename, "r");
151 save_errno = errno;
152 if (!f) {
153 char pubfile[MAXPATHLEN];
154 if (strlcpy(pubfile, filename, sizeof pubfile) <
155 sizeof(pubfile) &&
156 strlcat(pubfile, ".pub", sizeof pubfile) <
157 sizeof(pubfile))
158 f = fopen(pubfile, "r");
159 }
160 errno = save_errno; /* earlier errno is more useful */
161 if (!f) {
162 if (!quiet_open)
163 perror(filename);
164 return -1;
165 }
166 if (verbosity > 0)
167 printf("# %s\n", filename);
168 } else
169 f = stdin;
170 while (read_keyfile_line(f, filename, line, sizeof(line),
171 &linenum) != -1) {
172 int i;
173 char *space;
174 int type;
175 char *end;
176
177 /* Chop trailing newline. */
178 i = strlen(line) - 1;
179 if (line[i] == '\n')
180 line[i] = '\0';
181
182 /* Skip leading whitespace, empty and comment lines. */
183 for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
184 ;
185 if (!*cp || *cp == '\n' || *cp == '#')
186 continue;
187
188 /* Cope with ssh-keyscan output and options in
189 * authorized_keys files.
190 */
191 space = strchr(cp, ' ');
192 if (!space)
193 continue;
194 *space = '\0';
195 type = key_type_from_name(cp);
196 *space = ' ';
197 /* Leading number (RSA1) or valid type (RSA/DSA) indicates
198 * that we have no host name or options to skip.
199 */
200 if ((strtol(cp, &end, 10) == 0 || *end != ' ') &&
201 type == KEY_UNSPEC) {
202 int quoted = 0;
203
204 for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
205 if (*cp == '\\' && cp[1] == '"')
206 cp++; /* Skip both */
207 else if (*cp == '"')
208 quoted = !quoted;
209 }
210 /* Skip remaining whitespace. */
211 for (; *cp == ' ' || *cp == '\t'; cp++)
212 ;
213 if (!*cp)
214 continue;
215 }
216
217 /* Read and process the key itself. */
218 key = key_new(KEY_RSA1);
219 if (key_read(key, &cp) == 1) {
220 while (*cp == ' ' || *cp == '\t')
221 cp++;
222 if (!do_key(filename, linenum,
223 key, *cp ? cp : filename))
224 ret = 0;
225 found = 1;
226 } else {
227 key_free(key);
228 key = key_new(KEY_UNSPEC);
229 if (key_read(key, &cp) == 1) {
230 while (*cp == ' ' || *cp == '\t')
231 cp++;
232 if (!do_key(filename, linenum,
233 key, *cp ? cp : filename))
234 ret = 0;
235 found = 1;
236 }
237 }
238 key_free(key);
239 }
240 if (f != stdin)
241 fclose(f);
242
243 if (!found && filename) {
244 key = key_load_public(filename, &comment);
245 if (key) {
246 if (!do_key(filename, 1, key, comment))
247 ret = 0;
248 found = 1;
249 }
250 free(comment);
251 }
252
253 return ret;
254}
255
256static int
257do_host(int quiet_open)
258{
259 int i;
260 struct stat st;
261 int ret = 1;
262
263 for (i = 0; default_host_files[i]; i++) {
264 if (stat(default_host_files[i], &st) < 0 && errno == ENOENT)
265 continue;
266 if (!do_filename(default_host_files[i], quiet_open))
267 ret = 0;
268 }
269
270 return ret;
271}
272
273static int
274do_user(const char *dir)
275{
276 int i;
277 char *file;
278 struct stat st;
279 int ret = 1;
280
281 for (i = 0; default_files[i]; i++) {
282 xasprintf(&file, "%s/%s", dir, default_files[i]);
283 if (stat(file, &st) < 0 && errno == ENOENT) {
284 free(file);
285 continue;
286 }
287 if (!do_filename(file, 0))
288 ret = 0;
289 free(file);
290 }
291
292 return ret;
293}
294
295int
296main(int argc, char **argv)
297{
298 int opt, all_users = 0;
299 int ret = 1;
300 extern int optind;
301
302 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
303 sanitise_stdfd();
304
305 __progname = ssh_get_progname(argv[0]);
306
307 SSLeay_add_all_algorithms();
308 log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1);
309
310 /* We don't need the RNG ourselves, but symbol references here allow
311 * ld to link us properly.
312 */
313 seed_rng();
314
315 while ((opt = getopt(argc, argv, "ahqv")) != -1) {
316 switch (opt) {
317 case 'a':
318 all_users = 1;
319 break;
320 case 'q':
321 verbosity--;
322 break;
323 case 'v':
324 verbosity++;
325 break;
326 case 'h':
327 default:
328 usage();
329 }
330 }
331
332 if (all_users) {
333 struct passwd *pw;
334
335 if (!do_host(0))
336 ret = 0;
337
338 while ((pw = getpwent()) != NULL) {
339 if (pw->pw_dir) {
340 temporarily_use_uid(pw);
341 if (!do_user(pw->pw_dir))
342 ret = 0;
343 restore_uid();
344 }
345 }
346 } else if (optind == argc) {
347 struct passwd *pw;
348
349 if (!do_host(1))
350 ret = 0;
351
352 if ((pw = getpwuid(geteuid())) == NULL)
353 fprintf(stderr, "No user found with uid %u\n",
354 (u_int)geteuid());
355 else {
356 if (!do_user(pw->pw_dir))
357 ret = 0;
358 }
359 } else {
360 while (optind < argc)
361 if (!do_filename(argv[optind++], 0))
362 ret = 0;
363 }
364
365 if (verbosity >= 0) {
366 if (some_unknown) {
367 printf("#\n");
368 printf("# The status of some keys on your system is unknown.\n");
369 printf("# You may need to install additional blacklist files.\n");
370 }
371 if (some_compromised) {
372 printf("#\n");
373 printf("# Some keys on your system have been compromised!\n");
374 printf("# You must replace them using ssh-keygen(1).\n");
375 }
376 if (some_unknown || some_compromised) {
377 printf("#\n");
378 printf("# See the ssh-vulnkey(1) manual page for further advice.\n");
379 } else if (some_keys && verbosity > 0) {
380 printf("#\n");
381 printf("# No blacklisted keys!\n");
382 }
383 }
384
385 return ret;
386}
diff --git a/ssh.1 b/ssh.1
index 62292cc09..66a7007d7 100644
--- a/ssh.1
+++ b/ssh.1
@@ -1447,6 +1447,7 @@ if an error occurred.
1447.Xr ssh-agent 1 , 1447.Xr ssh-agent 1 ,
1448.Xr ssh-keygen 1 , 1448.Xr ssh-keygen 1 ,
1449.Xr ssh-keyscan 1 , 1449.Xr ssh-keyscan 1 ,
1450.Xr ssh-vulnkey 1 ,
1450.Xr tun 4 , 1451.Xr tun 4 ,
1451.Xr hosts.equiv 5 , 1452.Xr hosts.equiv 5 ,
1452.Xr ssh_config 5 , 1453.Xr ssh_config 5 ,
diff --git a/ssh.c b/ssh.c
index 87233bc91..567248d64 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1525,7 +1525,7 @@ ssh_session2(void)
1525static void 1525static void
1526load_public_identity_files(void) 1526load_public_identity_files(void)
1527{ 1527{
1528 char *filename, *cp, thishost[NI_MAXHOST]; 1528 char *filename, *cp, thishost[NI_MAXHOST], *fp;
1529 char *pwdir = NULL, *pwname = NULL; 1529 char *pwdir = NULL, *pwname = NULL;
1530 int i = 0; 1530 int i = 0;
1531 Key *public; 1531 Key *public;
@@ -1583,6 +1583,22 @@ load_public_identity_files(void)
1583 public = key_load_public(filename, NULL); 1583 public = key_load_public(filename, NULL);
1584 debug("identity file %s type %d", filename, 1584 debug("identity file %s type %d", filename,
1585 public ? public->type : -1); 1585 public ? public->type : -1);
1586 if (public && blacklisted_key(public, &fp) == 1) {
1587 if (options.use_blacklisted_keys)
1588 logit("Public key %s blacklisted (see "
1589 "ssh-vulnkey(1)); continuing anyway", fp);
1590 else
1591 logit("Public key %s blacklisted (see "
1592 "ssh-vulnkey(1)); refusing to send it",
1593 fp);
1594 free(fp);
1595 if (!options.use_blacklisted_keys) {
1596 key_free(public);
1597 free(filename);
1598 filename = NULL;
1599 public = NULL;
1600 }
1601 }
1586 free(options.identity_files[i]); 1602 free(options.identity_files[i]);
1587 identity_files[n_ids] = filename; 1603 identity_files[n_ids] = filename;
1588 identity_keys[n_ids] = public; 1604 identity_keys[n_ids] = public;
diff --git a/ssh_config.5 b/ssh_config.5
index e72919a89..8d806c701 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -1229,6 +1229,23 @@ is not specified, it defaults to
1229.Dq any . 1229.Dq any .
1230The default is 1230The default is
1231.Dq any:any . 1231.Dq any:any .
1232.It Cm UseBlacklistedKeys
1233Specifies whether
1234.Xr ssh 1
1235should use keys recorded in its blacklist of known-compromised keys (see
1236.Xr ssh-vulnkey 1 )
1237for authentication.
1238If
1239.Dq yes ,
1240then attempts to use compromised keys for authentication will be logged but
1241accepted.
1242It is strongly recommended that this be used only to install new authorized
1243keys on the remote system, and even then only with the utmost care.
1244If
1245.Dq no ,
1246then attempts to use compromised keys for authentication will be prevented.
1247The default is
1248.Dq no .
1232.It Cm UsePrivilegedPort 1249.It Cm UsePrivilegedPort
1233Specifies whether to use a privileged port for outgoing connections. 1250Specifies whether to use a privileged port for outgoing connections.
1234The argument must be 1251The argument must be
diff --git a/sshconnect2.c b/sshconnect2.c
index 0b13530ce..93818c991 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1491,6 +1491,8 @@ pubkey_prepare(Authctxt *authctxt)
1491 1491
1492 /* list of keys stored in the filesystem and PKCS#11 */ 1492 /* list of keys stored in the filesystem and PKCS#11 */
1493 for (i = 0; i < options.num_identity_files; i++) { 1493 for (i = 0; i < options.num_identity_files; i++) {
1494 if (options.identity_files[i] == NULL)
1495 continue;
1494 key = options.identity_keys[i]; 1496 key = options.identity_keys[i];
1495 if (key && key->type == KEY_RSA1) 1497 if (key && key->type == KEY_RSA1)
1496 continue; 1498 continue;
@@ -1608,7 +1610,7 @@ userauth_pubkey(Authctxt *authctxt)
1608 debug("Offering %s public key: %s", key_type(id->key), 1610 debug("Offering %s public key: %s", key_type(id->key),
1609 id->filename); 1611 id->filename);
1610 sent = send_pubkey_test(authctxt, id); 1612 sent = send_pubkey_test(authctxt, id);
1611 } else if (id->key == NULL) { 1613 } else if (id->key == NULL && id->filename) {
1612 debug("Trying private key: %s", id->filename); 1614 debug("Trying private key: %s", id->filename);
1613 id->key = load_identity_file(id->filename, 1615 id->key = load_identity_file(id->filename,
1614 id->userprovided); 1616 id->userprovided);
diff --git a/sshd.8 b/sshd.8
index b0c7ab6bd..a604429b7 100644
--- a/sshd.8
+++ b/sshd.8
@@ -954,6 +954,7 @@ The content of this file is not sensitive; it can be world-readable.
954.Xr ssh-agent 1 , 954.Xr ssh-agent 1 ,
955.Xr ssh-keygen 1 , 955.Xr ssh-keygen 1 ,
956.Xr ssh-keyscan 1 , 956.Xr ssh-keyscan 1 ,
957.Xr ssh-vulnkey 1 ,
957.Xr chroot 2 , 958.Xr chroot 2 ,
958.Xr hosts_access 5 , 959.Xr hosts_access 5 ,
959.Xr login.conf 5 , 960.Xr login.conf 5 ,
diff --git a/sshd.c b/sshd.c
index e5c983514..fbe3284a9 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1688,6 +1688,11 @@ main(int ac, char **av)
1688 sensitive_data.host_pubkeys[i] = NULL; 1688 sensitive_data.host_pubkeys[i] = NULL;
1689 continue; 1689 continue;
1690 } 1690 }
1691 if (auth_key_is_revoked(key != NULL ? key : pubkey, 1)) {
1692 sensitive_data.host_keys[i] = NULL;
1693 sensitive_data.host_pubkeys[i] = NULL;
1694 continue;
1695 }
1691 1696
1692 switch (keytype) { 1697 switch (keytype) {
1693 case KEY_RSA1: 1698 case KEY_RSA1:
diff --git a/sshd_config.5 b/sshd_config.5
index 525d9c858..18ec81fe8 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -885,6 +885,20 @@ are refused if the number of unauthenticated connections reaches
885Specifies whether password authentication is allowed. 885Specifies whether password authentication is allowed.
886The default is 886The default is
887.Dq yes . 887.Dq yes .
888.It Cm PermitBlacklistedKeys
889Specifies whether
890.Xr sshd 8
891should allow keys recorded in its blacklist of known-compromised keys (see
892.Xr ssh-vulnkey 1 ) .
893If
894.Dq yes ,
895then attempts to authenticate with compromised keys will be logged but
896accepted.
897If
898.Dq no ,
899then attempts to authenticate with compromised keys will be rejected.
900The default is
901.Dq no .
888.It Cm PermitEmptyPasswords 902.It Cm PermitEmptyPasswords
889When password authentication is allowed, it specifies whether the 903When password authentication is allowed, it specifies whether the
890server allows login to accounts with empty password strings. 904server allows login to accounts with empty password strings.