diff options
author | Colin Watson <cjwatson@ubuntu.com> | 2014-02-09 16:09:50 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2014-02-09 16:17:31 +0000 |
commit | 8909ff0e3cd07d1b042d1be1c8b8828dbf6c9a83 (patch) | |
tree | ebee4092f1411059e34da6f66b4ebd64f4411020 | |
parent | 07f2a771c490bd68cd5c5ea9c535705e93bd94f3 (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.in | 17 | ||||
-rw-r--r-- | auth-rh-rsa.c | 2 | ||||
-rw-r--r-- | auth-rsa.c | 2 | ||||
-rw-r--r-- | auth.c | 27 | ||||
-rw-r--r-- | auth.h | 2 | ||||
-rw-r--r-- | auth2-hostbased.c | 2 | ||||
-rw-r--r-- | auth2-pubkey.c | 5 | ||||
-rw-r--r-- | authfile.c | 136 | ||||
-rw-r--r-- | authfile.h | 2 | ||||
-rw-r--r-- | pathnames.h | 7 | ||||
-rw-r--r-- | readconf.c | 9 | ||||
-rw-r--r-- | readconf.h | 1 | ||||
-rw-r--r-- | servconf.c | 11 | ||||
-rw-r--r-- | servconf.h | 1 | ||||
-rw-r--r-- | ssh-add.1 | 5 | ||||
-rw-r--r-- | ssh-add.c | 10 | ||||
-rw-r--r-- | ssh-keygen.1 | 1 | ||||
-rw-r--r-- | ssh-vulnkey.1 | 242 | ||||
-rw-r--r-- | ssh-vulnkey.c | 386 | ||||
-rw-r--r-- | ssh.1 | 1 | ||||
-rw-r--r-- | ssh.c | 18 | ||||
-rw-r--r-- | ssh_config.5 | 17 | ||||
-rw-r--r-- | sshconnect2.c | 4 | ||||
-rw-r--r-- | sshd.8 | 1 | ||||
-rw-r--r-- | sshd.c | 5 | ||||
-rw-r--r-- | sshd_config.5 | 14 |
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 | |||
26 | SFTP_SERVER=$(libexecdir)/sftp-server | 26 | SFTP_SERVER=$(libexecdir)/sftp-server |
27 | SSH_KEYSIGN=$(libexecdir)/ssh-keysign | 27 | SSH_KEYSIGN=$(libexecdir)/ssh-keysign |
28 | SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper | 28 | SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper |
29 | SSH_DATADIR=$(datadir)/ssh | ||
29 | PRIVSEP_PATH=@PRIVSEP_PATH@ | 30 | PRIVSEP_PATH=@PRIVSEP_PATH@ |
30 | SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@ | 31 | SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@ |
31 | STRIP_OPT=@STRIP_OPT@ | 32 | STRIP_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 | ||
42 | CC=@CC@ | 44 | CC=@CC@ |
43 | LD=@LD@ | 45 | LD=@LD@ |
@@ -61,7 +63,7 @@ LDFLAGS=-L. -Lopenbsd-compat/ @LDFLAGS@ | |||
61 | EXEEXT=@EXEEXT@ | 63 | EXEEXT=@EXEEXT@ |
62 | MANFMT=@MANFMT@ | 64 | MANFMT=@MANFMT@ |
63 | 65 | ||
64 | 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) | 66 | 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) |
65 | 67 | ||
66 | LIBSSH_OBJS=authfd.o authfile.o bufaux.o bufbn.o buffer.o \ | 68 | LIBSSH_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 | ||
99 | 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 | 101 | 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 |
100 | 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 | 102 | 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 |
101 | MANTYPE = @MANTYPE@ | 103 | MANTYPE = @MANTYPE@ |
102 | 104 | ||
103 | CONFIGFILES=sshd_config.out ssh_config.out moduli.out | 105 | CONFIGFILES=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 | |||
176 | sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o | 178 | sftp$(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 | ||
181 | ssh-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 |
180 | logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o | 185 | logintest: 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. */ |
@@ -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 */ |
659 | int | 660 | int |
660 | auth_key_is_revoked(Key *key) | 661 | auth_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)) { |
@@ -191,7 +191,7 @@ char *authorized_principals_file(struct passwd *); | |||
191 | 191 | ||
192 | FILE *auth_openkeyfile(const char *, struct passwd *, int); | 192 | FILE *auth_openkeyfile(const char *, struct passwd *, int); |
193 | FILE *auth_openprincipals(const char *, struct passwd *, int); | 193 | FILE *auth_openprincipals(const char *, struct passwd *, int); |
194 | int auth_key_is_revoked(Key *); | 194 | int auth_key_is_revoked(Key *, int); |
195 | 195 | ||
196 | HostStatus | 196 | HostStatus |
197 | check_key_in_hostfiles(struct passwd *, Key *, const char *, | 197 | check_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. */ | ||
949 | static int | ||
950 | blacklisted_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 | |||
1036 | out: | ||
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 | */ | ||
1053 | int | ||
1054 | blacklisted_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 **); | |||
28 | int key_perm_ok(int, const char *); | 28 | int key_perm_ok(int, const char *); |
29 | int key_in_file(Key *, const char *, int); | 29 | int key_in_file(Key *, const char *, int); |
30 | 30 | ||
31 | int 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 */ |
@@ -81,6 +81,10 @@ environment variable must contain the name of its socket for | |||
81 | .Nm | 81 | .Nm |
82 | to work. | 82 | to work. |
83 | .Pp | 83 | .Pp |
84 | Any keys recorded in the blacklist of known-compromised keys (see | ||
85 | .Xr ssh-vulnkey 1 ) | ||
86 | will be refused. | ||
87 | .Pp | ||
84 | The options are as follows: | 88 | The 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 |
191 | OpenSSH is a derivative of the original and free | 196 | OpenSSH is a derivative of the original and free |
@@ -167,7 +167,7 @@ static int | |||
167 | add_file(AuthenticationConnection *ac, const char *filename, int key_only) | 167 | add_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 | ||
37 | checks a key against a blacklist of compromised keys. | ||
38 | .Pp | ||
39 | A substantial number of keys are known to have been generated using a broken | ||
40 | version of OpenSSL distributed by Debian which failed to seed its random | ||
41 | number generator correctly. | ||
42 | Keys generated using these OpenSSL versions should be assumed to be | ||
43 | compromised. | ||
44 | This tool may be useful in checking for such keys. | ||
45 | .Pp | ||
46 | Keys that are compromised cannot be repaired; replacements must be generated | ||
47 | using | ||
48 | .Xr ssh-keygen 1 . | ||
49 | Make sure to update | ||
50 | .Pa authorized_keys | ||
51 | files on all systems where compromised keys were permitted to authenticate. | ||
52 | .Pp | ||
53 | The argument list will be interpreted as a list of paths to public key files | ||
54 | or | ||
55 | .Pa authorized_keys | ||
56 | files. | ||
57 | If no suitable file is found at a given path, | ||
58 | .Nm | ||
59 | will append | ||
60 | .Pa .pub | ||
61 | and retry, in case it was given a private key file. | ||
62 | If no files are given as arguments, | ||
63 | .Nm | ||
64 | will check | ||
65 | .Pa ~/.ssh/id_rsa , | ||
66 | .Pa ~/.ssh/id_dsa , | ||
67 | .Pa ~/.ssh/identity , | ||
68 | .Pa ~/.ssh/authorized_keys | ||
69 | and | ||
70 | .Pa ~/.ssh/authorized_keys2 , | ||
71 | as well as the system's host keys if readable. | ||
72 | .Pp | ||
73 | If | ||
74 | .Dq - | ||
75 | is given as an argument, | ||
76 | .Nm | ||
77 | will read from standard input. | ||
78 | This can be used to process output from | ||
79 | .Xr ssh-keyscan 1 , | ||
80 | for example: | ||
81 | .Pp | ||
82 | .Dl $ ssh-keyscan -t rsa remote.example.org | ssh-vulnkey - | ||
83 | .Pp | ||
84 | Unless the | ||
85 | .Cm PermitBlacklistedKeys | ||
86 | option is used, | ||
87 | .Xr sshd 8 | ||
88 | will reject attempts to authenticate with keys in the compromised list. | ||
89 | .Pp | ||
90 | The output from | ||
91 | .Nm | ||
92 | looks 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 | ||
100 | Each line is of the following format (any lines beginning with | ||
101 | .Dq # | ||
102 | should 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 | ||
106 | It is important to distinguish between the possible values of | ||
107 | .Ar status : | ||
108 | .Pp | ||
109 | .Bl -tag -width Ds | ||
110 | .It COMPROMISED | ||
111 | These keys are listed in a blacklist file, normally because their | ||
112 | corresponding private keys are well-known. | ||
113 | Replacements must be generated using | ||
114 | .Xr ssh-keygen 1 . | ||
115 | .It Not blacklisted | ||
116 | A blacklist file exists for this key type and size, but this key is not | ||
117 | listed in it. | ||
118 | Unless there is some particular reason to believe otherwise, this key | ||
119 | may be used safely. | ||
120 | (Note that DSA keys used with the broken version of OpenSSL distributed | ||
121 | by Debian may be compromised in the event that anyone captured a network | ||
122 | trace, even if they were generated with a secure version of OpenSSL.) | ||
123 | .It Unknown (blacklist file not installed) | ||
124 | No blacklist file exists for this key type and size. | ||
125 | You should find a suitable published blacklist and install it before | ||
126 | deciding whether this key is safe to use. | ||
127 | .El | ||
128 | .Pp | ||
129 | The options are as follows: | ||
130 | .Bl -tag -width Ds | ||
131 | .It Fl a | ||
132 | Check keys of all users on the system. | ||
133 | You will typically need to run | ||
134 | .Nm | ||
135 | as root to use this option. | ||
136 | For each user, | ||
137 | .Nm | ||
138 | will check | ||
139 | .Pa ~/.ssh/id_rsa , | ||
140 | .Pa ~/.ssh/id_dsa , | ||
141 | .Pa ~/.ssh/identity , | ||
142 | .Pa ~/.ssh/authorized_keys | ||
143 | and | ||
144 | .Pa ~/.ssh/authorized_keys2 . | ||
145 | It will also check the system's host keys. | ||
146 | .It Fl q | ||
147 | Quiet mode. | ||
148 | Normally, | ||
149 | .Nm | ||
150 | outputs the fingerprint of each key scanned, with a description of its | ||
151 | status. | ||
152 | This option suppresses that output. | ||
153 | .It Fl v | ||
154 | Verbose mode. | ||
155 | Normally, | ||
156 | .Nm | ||
157 | does not output anything for keys that are not listed in their corresponding | ||
158 | blacklist file (although it still produces output for keys for which there | ||
159 | is no blacklist file, since their status is unknown). | ||
160 | This option causes | ||
161 | .Nm | ||
162 | to produce output for all keys. | ||
163 | .El | ||
164 | .Sh EXIT STATUS | ||
165 | .Nm | ||
166 | will exit zero if any of the given keys were in the compromised list, | ||
167 | otherwise non-zero. | ||
168 | .Sh BLACKLIST FILE FORMAT | ||
169 | The blacklist file may start with comments, on lines starting with | ||
170 | .Dq # . | ||
171 | After these initial comments, it must follow a strict format: | ||
172 | .Pp | ||
173 | .Bl -bullet -offset indent -compact | ||
174 | .It | ||
175 | All the lines must be exactly the same length (20 characters followed by a | ||
176 | newline) and must be in sorted order. | ||
177 | .It | ||
178 | Each line must consist of the lower-case hexadecimal MD5 key fingerprint, | ||
179 | without colons, and with the first 12 characters removed (that is, the least | ||
180 | significant 80 bits of the fingerprint). | ||
181 | .El | ||
182 | .Pp | ||
183 | The key fingerprint may be generated using | ||
184 | .Xr ssh-keygen 1 : | ||
185 | .Pp | ||
186 | .Dl $ ssh-keygen -l -f /path/to/key | ||
187 | .Pp | ||
188 | This strict format is necessary to allow the blacklist file to be checked | ||
189 | quickly, using a binary-search algorithm. | ||
190 | .Sh FILES | ||
191 | .Bl -tag -width Ds | ||
192 | .It Pa ~/.ssh/id_rsa | ||
193 | If present, contains the protocol version 2 RSA authentication identity of | ||
194 | the user. | ||
195 | .It Pa ~/.ssh/id_dsa | ||
196 | If present, contains the protocol version 2 DSA authentication identity of | ||
197 | the user. | ||
198 | .It Pa ~/.ssh/identity | ||
199 | If present, contains the protocol version 1 RSA authentication identity of | ||
200 | the user. | ||
201 | .It Pa ~/.ssh/authorized_keys | ||
202 | If present, lists the public keys (RSA/DSA) that can be used for logging in | ||
203 | as this user. | ||
204 | .It Pa ~/.ssh/authorized_keys2 | ||
205 | Obsolete name for | ||
206 | .Pa ~/.ssh/authorized_keys . | ||
207 | This file may still be present on some old systems, but should not be | ||
208 | created if it is missing. | ||
209 | .It Pa /etc/ssh/ssh_host_rsa_key | ||
210 | If present, contains the protocol version 2 RSA identity of the system. | ||
211 | .It Pa /etc/ssh/ssh_host_dsa_key | ||
212 | If present, contains the protocol version 2 DSA identity of the system. | ||
213 | .It Pa /etc/ssh/ssh_host_key | ||
214 | If 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 | ||
216 | If present, lists the blacklisted keys of type | ||
217 | .Ar TYPE | ||
218 | .Pf ( Dq RSA | ||
219 | or | ||
220 | .Dq DSA ) | ||
221 | and bit length | ||
222 | .Ar LENGTH . | ||
223 | The format of this file is described above. | ||
224 | RSA1 keys are converted to RSA before being checked in the blacklist. | ||
225 | Note that the fingerprints of RSA1 keys are computed differently, so you | ||
226 | will 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 | ||
228 | Same as | ||
229 | .Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH , | ||
230 | but 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 | ||
239 | Florian Weimer suggested the option to check keys of all users, and the idea | ||
240 | of processing | ||
241 | .Xr ssh-keyscan 1 | ||
242 | output. | ||
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 | |||
47 | extern char *__progname; | ||
48 | |||
49 | /* Default files to check */ | ||
50 | static char *default_host_files[] = { | ||
51 | _PATH_HOST_RSA_KEY_FILE, | ||
52 | _PATH_HOST_DSA_KEY_FILE, | ||
53 | _PATH_HOST_KEY_FILE, | ||
54 | NULL | ||
55 | }; | ||
56 | static 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 | |||
65 | static int verbosity = 0; | ||
66 | |||
67 | static int some_keys = 0; | ||
68 | static int some_unknown = 0; | ||
69 | static int some_compromised = 0; | ||
70 | |||
71 | static void | ||
72 | usage(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 | |||
82 | static void | ||
83 | describe_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 | |||
100 | static int | ||
101 | do_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 | |||
133 | static int | ||
134 | do_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 | |||
256 | static int | ||
257 | do_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 | |||
273 | static int | ||
274 | do_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 | |||
295 | int | ||
296 | main(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 | } | ||
@@ -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 , |
@@ -1525,7 +1525,7 @@ ssh_session2(void) | |||
1525 | static void | 1525 | static void |
1526 | load_public_identity_files(void) | 1526 | load_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 . |
1230 | The default is | 1230 | The default is |
1231 | .Dq any:any . | 1231 | .Dq any:any . |
1232 | .It Cm UseBlacklistedKeys | ||
1233 | Specifies whether | ||
1234 | .Xr ssh 1 | ||
1235 | should use keys recorded in its blacklist of known-compromised keys (see | ||
1236 | .Xr ssh-vulnkey 1 ) | ||
1237 | for authentication. | ||
1238 | If | ||
1239 | .Dq yes , | ||
1240 | then attempts to use compromised keys for authentication will be logged but | ||
1241 | accepted. | ||
1242 | It is strongly recommended that this be used only to install new authorized | ||
1243 | keys on the remote system, and even then only with the utmost care. | ||
1244 | If | ||
1245 | .Dq no , | ||
1246 | then attempts to use compromised keys for authentication will be prevented. | ||
1247 | The default is | ||
1248 | .Dq no . | ||
1232 | .It Cm UsePrivilegedPort | 1249 | .It Cm UsePrivilegedPort |
1233 | Specifies whether to use a privileged port for outgoing connections. | 1250 | Specifies whether to use a privileged port for outgoing connections. |
1234 | The argument must be | 1251 | The 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); |
@@ -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 , |
@@ -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 | |||
885 | Specifies whether password authentication is allowed. | 885 | Specifies whether password authentication is allowed. |
886 | The default is | 886 | The default is |
887 | .Dq yes . | 887 | .Dq yes . |
888 | .It Cm PermitBlacklistedKeys | ||
889 | Specifies whether | ||
890 | .Xr sshd 8 | ||
891 | should allow keys recorded in its blacklist of known-compromised keys (see | ||
892 | .Xr ssh-vulnkey 1 ) . | ||
893 | If | ||
894 | .Dq yes , | ||
895 | then attempts to authenticate with compromised keys will be logged but | ||
896 | accepted. | ||
897 | If | ||
898 | .Dq no , | ||
899 | then attempts to authenticate with compromised keys will be rejected. | ||
900 | The default is | ||
901 | .Dq no . | ||
888 | .It Cm PermitEmptyPasswords | 902 | .It Cm PermitEmptyPasswords |
889 | When password authentication is allowed, it specifies whether the | 903 | When password authentication is allowed, it specifies whether the |
890 | server allows login to accounts with empty password strings. | 904 | server allows login to accounts with empty password strings. |