diff options
author | Colin Watson <cjwatson@debian.org> | 2008-05-12 23:33:01 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2008-05-12 23:33:01 +0000 |
commit | 47608c17e64138f8d16aa2bdc49a0eb00e1c3549 (patch) | |
tree | 92572d90b9aa8f45c0d9e6dbb185065667fdcea0 | |
parent | 19ccea525446d5a3c2a176d813c505be81b91cbf (diff) |
* Mitigate OpenSSL security vulnerability:
- Add key blacklisting support. Keys listed in
/etc/ssh/blacklist.TYPE-LENGTH will be rejected for authentication by
sshd, unless "PermitBlacklistedKeys yes" is set in
/etc/ssh/sshd_config.
- Add a new program, ssh-vulnkey, which can be used to check keys
against these blacklists.
- Depend on openssh-blacklist.
- Force dependencies on libssl0.9.8 / libcrypto0.9.8-udeb to at least
0.9.8g-9.
- Automatically regenerate known-compromised host keys, with a
critical-priority debconf note. (I regret that there was no time to
gather translations.)
-rw-r--r-- | Makefile.in | 13 | ||||
-rw-r--r-- | auth-rh-rsa.c | 16 | ||||
-rw-r--r-- | auth-rsa.c | 15 | ||||
-rw-r--r-- | auth2-hostbased.c | 15 | ||||
-rw-r--r-- | auth2-pubkey.c | 15 | ||||
-rw-r--r-- | authfile.c | 111 | ||||
-rw-r--r-- | authfile.h | 3 | ||||
-rw-r--r-- | debian/changelog | 15 | ||||
-rw-r--r-- | debian/control | 8 | ||||
-rw-r--r-- | debian/openssh-server.postinst | 30 | ||||
-rw-r--r-- | debian/openssh-server.templates | 20 | ||||
-rw-r--r-- | pathnames.h | 2 | ||||
-rw-r--r-- | servconf.c | 10 | ||||
-rw-r--r-- | servconf.h | 1 | ||||
-rw-r--r-- | ssh-vulnkey.1 | 151 | ||||
-rw-r--r-- | ssh-vulnkey.c | 311 | ||||
-rw-r--r-- | sshd.c | 15 | ||||
-rw-r--r-- | sshd_config.5 | 14 |
18 files changed, 756 insertions, 9 deletions
diff --git a/Makefile.in b/Makefile.in index 3ac9aaf45..0c6f7e550 100644 --- a/Makefile.in +++ b/Makefile.in | |||
@@ -60,7 +60,7 @@ EXEEXT=@EXEEXT@ | |||
60 | INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ | 60 | INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@ |
61 | INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@ | 61 | INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@ |
62 | 62 | ||
63 | TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) | 63 | TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-vulnkey$(EXEEXT) |
64 | 64 | ||
65 | LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \ | 65 | LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \ |
66 | canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \ | 66 | canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \ |
@@ -88,8 +88,8 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \ | |||
88 | loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ | 88 | loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ |
89 | audit.o audit-bsm.o platform.o | 89 | audit.o audit-bsm.o platform.o |
90 | 90 | ||
91 | MANPAGES = 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-rand-helper.8.out ssh-keysign.8.out sshd_config.5.out ssh_config.5.out | 91 | MANPAGES = 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-rand-helper.8.out ssh-keysign.8.out ssh-vulnkey.1.out sshd_config.5.out ssh_config.5.out |
92 | MANPAGES_IN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 sshd_config.5 ssh_config.5 | 92 | MANPAGES_IN = scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 ssh-vulnkey.1 sshd_config.5 ssh_config.5 |
93 | MANTYPE = @MANTYPE@ | 93 | MANTYPE = @MANTYPE@ |
94 | 94 | ||
95 | CONFIGFILES=sshd_config.out ssh_config.out moduli.out | 95 | CONFIGFILES=sshd_config.out ssh_config.out moduli.out |
@@ -165,6 +165,9 @@ sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glo | |||
165 | ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o | 165 | ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o |
166 | $(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) | 166 | $(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) |
167 | 167 | ||
168 | ssh-vulnkey$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-vulnkey.o | ||
169 | $(LD) -o $@ ssh-vulnkey.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) | ||
170 | |||
168 | # test driver for the loginrec code - not built by default | 171 | # test driver for the loginrec code - not built by default |
169 | logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o | 172 | logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o |
170 | $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) | 173 | $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS) |
@@ -264,6 +267,7 @@ install-files: scard-install | |||
264 | $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign $(DESTDIR)$(SSH_KEYSIGN) | 267 | $(INSTALL) -m 4711 $(STRIP_OPT) ssh-keysign $(DESTDIR)$(SSH_KEYSIGN) |
265 | $(INSTALL) -m 0755 $(STRIP_OPT) sftp $(DESTDIR)$(bindir)/sftp | 268 | $(INSTALL) -m 0755 $(STRIP_OPT) sftp $(DESTDIR)$(bindir)/sftp |
266 | $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server $(DESTDIR)$(SFTP_SERVER) | 269 | $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server $(DESTDIR)$(SFTP_SERVER) |
270 | $(INSTALL) -m 0755 $(STRIP_OPT) ssh-vulnkey $(DESTDIR)$(bindir)/ssh-vulnkey | ||
267 | $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 | 271 | $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1 |
268 | $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1 | 272 | $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1 |
269 | $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1 | 273 | $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1 |
@@ -280,6 +284,7 @@ install-files: scard-install | |||
280 | $(INSTALL) -m 644 sftp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 | 284 | $(INSTALL) -m 644 sftp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 |
281 | $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 | 285 | $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 |
282 | $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 | 286 | $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8 |
287 | $(INSTALL) -m 644 ssh-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1 | ||
283 | -rm -f $(DESTDIR)$(bindir)/slogin | 288 | -rm -f $(DESTDIR)$(bindir)/slogin |
284 | ln -s ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin | 289 | ln -s ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin |
285 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 | 290 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1 |
@@ -361,6 +366,7 @@ uninstall: | |||
361 | -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT) | 366 | -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT) |
362 | -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT) | 367 | -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT) |
363 | -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT) | 368 | -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT) |
369 | -rm -f $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT) | ||
364 | -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT) | 370 | -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT) |
365 | -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT) | 371 | -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT) |
366 | -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT) | 372 | -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT) |
@@ -373,6 +379,7 @@ uninstall: | |||
373 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1 | 379 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1 |
374 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 | 380 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1 |
375 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1 | 381 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1 |
382 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1 | ||
376 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 | 383 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8 |
377 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8 | 384 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8 |
378 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 | 385 | -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8 |
diff --git a/auth-rh-rsa.c b/auth-rh-rsa.c index eca750275..093461e83 100644 --- a/auth-rh-rsa.c +++ b/auth-rh-rsa.c | |||
@@ -20,6 +20,7 @@ | |||
20 | #include <pwd.h> | 20 | #include <pwd.h> |
21 | #include <stdarg.h> | 21 | #include <stdarg.h> |
22 | 22 | ||
23 | #include "xmalloc.h" | ||
23 | #include "packet.h" | 24 | #include "packet.h" |
24 | #include "uidswap.h" | 25 | #include "uidswap.h" |
25 | #include "log.h" | 26 | #include "log.h" |
@@ -27,6 +28,7 @@ | |||
27 | #include "servconf.h" | 28 | #include "servconf.h" |
28 | #include "key.h" | 29 | #include "key.h" |
29 | #include "hostfile.h" | 30 | #include "hostfile.h" |
31 | #include "authfile.h" | ||
30 | #include "pathnames.h" | 32 | #include "pathnames.h" |
31 | #include "auth.h" | 33 | #include "auth.h" |
32 | #include "canohost.h" | 34 | #include "canohost.h" |
@@ -42,8 +44,22 @@ int | |||
42 | auth_rhosts_rsa_key_allowed(struct passwd *pw, char *cuser, char *chost, | 44 | auth_rhosts_rsa_key_allowed(struct passwd *pw, char *cuser, char *chost, |
43 | Key *client_host_key) | 45 | Key *client_host_key) |
44 | { | 46 | { |
47 | char *fp; | ||
45 | HostStatus host_status; | 48 | HostStatus host_status; |
46 | 49 | ||
50 | if (blacklisted_key(client_host_key)) { | ||
51 | fp = key_fingerprint(client_host_key, SSH_FP_MD5, SSH_FP_HEX); | ||
52 | if (options.permit_blacklisted_keys) | ||
53 | logit("Public key %s blacklisted (see " | ||
54 | "ssh-vulnkey(1)); continuing anyway", fp); | ||
55 | else | ||
56 | logit("Public key %s blacklisted (see " | ||
57 | "ssh-vulnkey(1))", fp); | ||
58 | xfree(fp); | ||
59 | if (!options.permit_blacklisted_keys) | ||
60 | return 0; | ||
61 | } | ||
62 | |||
47 | /* Check if we would accept it using rhosts authentication. */ | 63 | /* Check if we would accept it using rhosts authentication. */ |
48 | if (!auth_rhosts(pw, cuser)) | 64 | if (!auth_rhosts(pw, cuser)) |
49 | return 0; | 65 | return 0; |
diff --git a/auth-rsa.c b/auth-rsa.c index 69f9a5896..1d277f92a 100644 --- a/auth-rsa.c +++ b/auth-rsa.c | |||
@@ -40,6 +40,7 @@ | |||
40 | #include "servconf.h" | 40 | #include "servconf.h" |
41 | #include "key.h" | 41 | #include "key.h" |
42 | #include "hostfile.h" | 42 | #include "hostfile.h" |
43 | #include "authfile.h" | ||
43 | #include "auth.h" | 44 | #include "auth.h" |
44 | #ifdef GSSAPI | 45 | #ifdef GSSAPI |
45 | #include "ssh-gss.h" | 46 | #include "ssh-gss.h" |
@@ -221,6 +222,7 @@ auth_rsa_key_allowed(struct passwd *pw, BIGNUM *client_n, Key **rkey) | |||
221 | char *cp; | 222 | char *cp; |
222 | char *key_options; | 223 | char *key_options; |
223 | int keybits; | 224 | int keybits; |
225 | char *fp; | ||
224 | 226 | ||
225 | /* Skip leading whitespace, empty and comment lines. */ | 227 | /* Skip leading whitespace, empty and comment lines. */ |
226 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) | 228 | for (cp = line; *cp == ' ' || *cp == '\t'; cp++) |
@@ -265,6 +267,19 @@ auth_rsa_key_allowed(struct passwd *pw, BIGNUM *client_n, Key **rkey) | |||
265 | "actual %d vs. announced %d.", | 267 | "actual %d vs. announced %d.", |
266 | file, linenum, BN_num_bits(key->rsa->n), bits); | 268 | file, linenum, BN_num_bits(key->rsa->n), bits); |
267 | 269 | ||
270 | if (blacklisted_key(key)) { | ||
271 | fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
272 | if (options.permit_blacklisted_keys) | ||
273 | logit("Public key %s blacklisted (see " | ||
274 | "ssh-vulnkey(1)); continuing anyway", fp); | ||
275 | else | ||
276 | logit("Public key %s blacklisted (see " | ||
277 | "ssh-vulnkey(1))", fp); | ||
278 | xfree(fp); | ||
279 | if (!options.permit_blacklisted_keys) | ||
280 | continue; | ||
281 | } | ||
282 | |||
268 | /* We have found the desired key. */ | 283 | /* We have found the desired key. */ |
269 | /* | 284 | /* |
270 | * If our options do not allow this key to be used, | 285 | * If our options do not allow this key to be used, |
diff --git a/auth2-hostbased.c b/auth2-hostbased.c index 663dec5d9..a0ee3060a 100644 --- a/auth2-hostbased.c +++ b/auth2-hostbased.c | |||
@@ -40,6 +40,7 @@ | |||
40 | #include "compat.h" | 40 | #include "compat.h" |
41 | #include "key.h" | 41 | #include "key.h" |
42 | #include "hostfile.h" | 42 | #include "hostfile.h" |
43 | #include "authfile.h" | ||
43 | #include "auth.h" | 44 | #include "auth.h" |
44 | #include "canohost.h" | 45 | #include "canohost.h" |
45 | #ifdef GSSAPI | 46 | #ifdef GSSAPI |
@@ -141,10 +142,24 @@ int | |||
141 | hostbased_key_allowed(struct passwd *pw, const char *cuser, char *chost, | 142 | hostbased_key_allowed(struct passwd *pw, const char *cuser, char *chost, |
142 | Key *key) | 143 | Key *key) |
143 | { | 144 | { |
145 | char *fp; | ||
144 | const char *resolvedname, *ipaddr, *lookup; | 146 | const char *resolvedname, *ipaddr, *lookup; |
145 | HostStatus host_status; | 147 | HostStatus host_status; |
146 | int len; | 148 | int len; |
147 | 149 | ||
150 | if (blacklisted_key(key)) { | ||
151 | fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
152 | if (options.permit_blacklisted_keys) | ||
153 | logit("Public key %s blacklisted (see " | ||
154 | "ssh-vulnkey(1)); continuing anyway", fp); | ||
155 | else | ||
156 | logit("Public key %s blacklisted (see " | ||
157 | "ssh-vulnkey(1))", fp); | ||
158 | xfree(fp); | ||
159 | if (!options.permit_blacklisted_keys) | ||
160 | return 0; | ||
161 | } | ||
162 | |||
148 | resolvedname = get_canonical_hostname(options.use_dns); | 163 | resolvedname = get_canonical_hostname(options.use_dns); |
149 | ipaddr = get_remote_ipaddr(); | 164 | ipaddr = get_remote_ipaddr(); |
150 | 165 | ||
diff --git a/auth2-pubkey.c b/auth2-pubkey.c index 9863cd9e6..cf911968a 100644 --- a/auth2-pubkey.c +++ b/auth2-pubkey.c | |||
@@ -42,6 +42,7 @@ | |||
42 | #include "compat.h" | 42 | #include "compat.h" |
43 | #include "key.h" | 43 | #include "key.h" |
44 | #include "hostfile.h" | 44 | #include "hostfile.h" |
45 | #include "authfile.h" | ||
45 | #include "auth.h" | 46 | #include "auth.h" |
46 | #include "pathnames.h" | 47 | #include "pathnames.h" |
47 | #include "uidswap.h" | 48 | #include "uidswap.h" |
@@ -269,9 +270,23 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file) | |||
269 | int | 270 | int |
270 | user_key_allowed(struct passwd *pw, Key *key) | 271 | user_key_allowed(struct passwd *pw, Key *key) |
271 | { | 272 | { |
273 | char *fp; | ||
272 | int success; | 274 | int success; |
273 | char *file; | 275 | char *file; |
274 | 276 | ||
277 | if (blacklisted_key(key)) { | ||
278 | fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
279 | if (options.permit_blacklisted_keys) | ||
280 | logit("Public key %s blacklisted (see " | ||
281 | "ssh-vulnkey(1)); continuing anyway", fp); | ||
282 | else | ||
283 | logit("Public key %s blacklisted (see " | ||
284 | "ssh-vulnkey(1))", fp); | ||
285 | xfree(fp); | ||
286 | if (!options.permit_blacklisted_keys) | ||
287 | return 0; | ||
288 | } | ||
289 | |||
275 | file = authorized_keys_file(pw); | 290 | file = authorized_keys_file(pw); |
276 | success = user_key_allowed2(pw, key, file); | 291 | success = user_key_allowed2(pw, key, file); |
277 | xfree(file); | 292 | xfree(file); |
diff --git a/authfile.c b/authfile.c index 735c64780..5348a014d 100644 --- a/authfile.c +++ b/authfile.c | |||
@@ -65,6 +65,7 @@ | |||
65 | #include "rsa.h" | 65 | #include "rsa.h" |
66 | #include "misc.h" | 66 | #include "misc.h" |
67 | #include "atomicio.h" | 67 | #include "atomicio.h" |
68 | #include "pathnames.h" | ||
68 | 69 | ||
69 | /* Version identification string for SSH v1 identity files. */ | 70 | /* Version identification string for SSH v1 identity files. */ |
70 | static const char authfile_id_string[] = | 71 | static const char authfile_id_string[] = |
@@ -677,3 +678,113 @@ key_load_public(const char *filename, char **commentp) | |||
677 | key_free(pub); | 678 | key_free(pub); |
678 | return NULL; | 679 | return NULL; |
679 | } | 680 | } |
681 | |||
682 | char * | ||
683 | blacklist_filename(const Key *key) | ||
684 | { | ||
685 | char *name; | ||
686 | |||
687 | xasprintf(&name, "%s.%s-%u", | ||
688 | _PATH_BLACKLIST, key_type(key), key_size(key)); | ||
689 | return name; | ||
690 | } | ||
691 | |||
692 | /* Scan a blacklist of known-vulnerable keys. */ | ||
693 | int | ||
694 | blacklisted_key(const Key *key) | ||
695 | { | ||
696 | char *blacklist_file; | ||
697 | int fd = -1; | ||
698 | char *dgst_hex = NULL; | ||
699 | char *dgst_packed = NULL, *p; | ||
700 | int i; | ||
701 | size_t line_len; | ||
702 | struct stat st; | ||
703 | char buf[256]; | ||
704 | off_t start, lower, upper; | ||
705 | int ret = 0; | ||
706 | |||
707 | blacklist_file = blacklist_filename(key); | ||
708 | debug("Checking blacklist file %s", blacklist_file); | ||
709 | fd = open(blacklist_file, O_RDONLY); | ||
710 | if (fd < 0) | ||
711 | goto out; | ||
712 | |||
713 | dgst_hex = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
714 | /* Remove all colons */ | ||
715 | dgst_packed = xcalloc(1, strlen(dgst_hex) + 1); | ||
716 | for (i = 0, p = dgst_packed; dgst_hex[i]; i++) | ||
717 | if (dgst_hex[i] != ':') | ||
718 | *p++ = dgst_hex[i]; | ||
719 | /* Only compare least-significant 80 bits (to keep the blacklist | ||
720 | * size down) | ||
721 | */ | ||
722 | line_len = strlen(dgst_packed + 12); | ||
723 | if (line_len > 32) | ||
724 | goto out; | ||
725 | |||
726 | /* Skip leading comments */ | ||
727 | start = 0; | ||
728 | for (;;) { | ||
729 | ssize_t r; | ||
730 | char *newline; | ||
731 | |||
732 | r = atomicio(read, fd, buf, 256); | ||
733 | if (r <= 0) | ||
734 | goto out; | ||
735 | if (buf[0] != '#') | ||
736 | break; | ||
737 | |||
738 | newline = memchr(buf, '\n', 256); | ||
739 | if (!newline) | ||
740 | goto out; | ||
741 | start += newline + 1 - buf; | ||
742 | if (lseek(fd, start, SEEK_SET) < 0) | ||
743 | goto out; | ||
744 | } | ||
745 | |||
746 | /* Initialise binary search record numbers */ | ||
747 | if (fstat(fd, &st) < 0) | ||
748 | goto out; | ||
749 | lower = 0; | ||
750 | upper = (st.st_size - start) / (line_len + 1); | ||
751 | |||
752 | while (lower != upper) { | ||
753 | off_t cur; | ||
754 | char buf[32]; | ||
755 | int cmp; | ||
756 | |||
757 | cur = lower + (upper - lower) / 2; | ||
758 | |||
759 | /* Read this line and compare to digest; this is | ||
760 | * overflow-safe since cur < max(off_t) / (line_len + 1) */ | ||
761 | if (lseek(fd, start + cur * (line_len + 1), SEEK_SET) < 0) | ||
762 | break; | ||
763 | if (atomicio(read, fd, buf, line_len) != line_len) | ||
764 | break; | ||
765 | cmp = memcmp(buf, dgst_packed + 12, line_len); | ||
766 | if (cmp < 0) { | ||
767 | if (cur == lower) | ||
768 | break; | ||
769 | lower = cur; | ||
770 | } else if (cmp > 0) { | ||
771 | if (cur == upper) | ||
772 | break; | ||
773 | upper = cur; | ||
774 | } else { | ||
775 | debug("Found %s in blacklist", dgst_hex); | ||
776 | ret = 1; | ||
777 | break; | ||
778 | } | ||
779 | } | ||
780 | |||
781 | out: | ||
782 | if (dgst_packed) | ||
783 | xfree(dgst_packed); | ||
784 | if (dgst_hex) | ||
785 | xfree(dgst_hex); | ||
786 | if (fd >= 0) | ||
787 | close(fd); | ||
788 | xfree(blacklist_file); | ||
789 | return ret; | ||
790 | } | ||
diff --git a/authfile.h b/authfile.h index a6c74934d..e47710495 100644 --- a/authfile.h +++ b/authfile.h | |||
@@ -23,4 +23,7 @@ Key *key_load_private_type(int, const char *, const char *, char **, int *); | |||
23 | Key *key_load_private_pem(int, int, const char *, char **); | 23 | Key *key_load_private_pem(int, int, const char *, char **); |
24 | int key_perm_ok(int, const char *); | 24 | int key_perm_ok(int, const char *); |
25 | 25 | ||
26 | char *blacklist_filename(const Key *key); | ||
27 | int blacklisted_key(const Key *key); | ||
28 | |||
26 | #endif | 29 | #endif |
diff --git a/debian/changelog b/debian/changelog index 9a9095189..95ab72caf 100644 --- a/debian/changelog +++ b/debian/changelog | |||
@@ -1,6 +1,19 @@ | |||
1 | openssh (1:4.7p1-9) UNRELEASED; urgency=low | 1 | openssh (1:4.7p1-9) UNRELEASED; urgency=critical |
2 | 2 | ||
3 | * Fill in CVE identifier for security vulnerability fixed in 1:4.7p1-8. | 3 | * Fill in CVE identifier for security vulnerability fixed in 1:4.7p1-8. |
4 | * Mitigate OpenSSL security vulnerability: | ||
5 | - Add key blacklisting support. Keys listed in | ||
6 | /etc/ssh/blacklist.TYPE-LENGTH will be rejected for authentication by | ||
7 | sshd, unless "PermitBlacklistedKeys yes" is set in | ||
8 | /etc/ssh/sshd_config. | ||
9 | - Add a new program, ssh-vulnkey, which can be used to check keys | ||
10 | against these blacklists. | ||
11 | - Depend on openssh-blacklist. | ||
12 | - Force dependencies on libssl0.9.8 / libcrypto0.9.8-udeb to at least | ||
13 | 0.9.8g-9. | ||
14 | - Automatically regenerate known-compromised host keys, with a | ||
15 | critical-priority debconf note. (I regret that there was no time to | ||
16 | gather translations.) | ||
4 | 17 | ||
5 | -- Colin Watson <cjwatson@debian.org> Wed, 09 Apr 2008 14:57:43 +0100 | 18 | -- Colin Watson <cjwatson@debian.org> Wed, 09 Apr 2008 14:57:43 +0100 |
6 | 19 | ||
diff --git a/debian/control b/debian/control index 290799998..c2bf961eb 100644 --- a/debian/control +++ b/debian/control | |||
@@ -8,7 +8,7 @@ Uploaders: Colin Watson <cjwatson@debian.org>, Matthew Vernon <matthew@debian.or | |||
8 | 8 | ||
9 | Package: openssh-client | 9 | Package: openssh-client |
10 | Architecture: any | 10 | Architecture: any |
11 | Depends: ${shlibs:Depends}, debconf (>= 1.2.0) | debconf-2.0, adduser (>= 3.10), dpkg (>= 1.7.0), passwd | 11 | Depends: ${shlibs:Depends}, debconf (>= 1.2.0) | debconf-2.0, adduser (>= 3.10), dpkg (>= 1.7.0), passwd, libssl0.9.8 (>= 0.9.8g-9) |
12 | Recommends: xauth | 12 | Recommends: xauth |
13 | Conflicts: ssh (<< 1:3.8.1p1-9), sftp, rsh-client (<<0.16.1-1), ssh-krb5 (<< 1:4.3p2-7) | 13 | Conflicts: ssh (<< 1:3.8.1p1-9), sftp, rsh-client (<<0.16.1-1), ssh-krb5 (<< 1:4.3p2-7) |
14 | Replaces: ssh, ssh-krb5 | 14 | Replaces: ssh, ssh-krb5 |
@@ -37,7 +37,7 @@ Description: secure shell client, an rlogin/rsh/rcp replacement | |||
37 | Package: openssh-server | 37 | Package: openssh-server |
38 | Priority: optional | 38 | Priority: optional |
39 | Architecture: any | 39 | Architecture: any |
40 | Depends: ${shlibs:Depends}, debconf (>= 1.2.0) | debconf-2.0, libpam-runtime (>= 0.76-14), libpam-modules (>= 0.72-9), adduser (>= 3.9), dpkg (>= 1.9.0), openssh-client (= ${binary:Version}), lsb-base (>= 3.0-6) | 40 | Depends: ${shlibs:Depends}, debconf (>= 1.2.0) | debconf-2.0, libpam-runtime (>= 0.76-14), libpam-modules (>= 0.72-9), adduser (>= 3.9), dpkg (>= 1.9.0), openssh-client (= ${binary:Version}), lsb-base (>= 3.0-6), libssl0.9.8 (>= 0.9.8g-9), openssh-blacklist |
41 | Recommends: xauth | 41 | Recommends: xauth |
42 | Conflicts: ssh (<< 1:3.8.1p1-9), ssh-nonfree (<<2), ssh-socks, ssh2, sftp, rsh-client (<<0.16.1-1), ssh-krb5 (<< 1:4.3p2-7) | 42 | Conflicts: ssh (<< 1:3.8.1p1-9), ssh-nonfree (<<2), ssh-socks, ssh2, sftp, rsh-client (<<0.16.1-1), ssh-krb5 (<< 1:4.3p2-7) |
43 | Replaces: ssh, openssh-client (<< 1:3.8.1p1-11), ssh-krb5 | 43 | Replaces: ssh, openssh-client (<< 1:3.8.1p1-11), ssh-krb5 |
@@ -99,7 +99,7 @@ XC-Package-Type: udeb | |||
99 | Section: debian-installer | 99 | Section: debian-installer |
100 | Priority: optional | 100 | Priority: optional |
101 | Architecture: any | 101 | Architecture: any |
102 | Depends: ${shlibs:Depends}, libnss-files-udeb | 102 | Depends: ${shlibs:Depends}, libnss-files-udeb, libcrypto0.9.8-udeb (>= 0.9.8g-9) |
103 | XB-Installer-Menu-Item: 99900 | 103 | XB-Installer-Menu-Item: 99900 |
104 | Description: secure shell client for the Debian installer | 104 | Description: secure shell client for the Debian installer |
105 | This is the portable version of OpenSSH, a free implementation of | 105 | This is the portable version of OpenSSH, a free implementation of |
@@ -113,7 +113,7 @@ XC-Package-Type: udeb | |||
113 | Section: debian-installer | 113 | Section: debian-installer |
114 | Priority: optional | 114 | Priority: optional |
115 | Architecture: any | 115 | Architecture: any |
116 | Depends: ${shlibs:Depends}, libnss-files-udeb | 116 | Depends: ${shlibs:Depends}, libnss-files-udeb, libcrypto0.9.8-udeb (>= 0.9.8g-9) |
117 | Description: secure shell server for the Debian installer | 117 | Description: secure shell server for the Debian installer |
118 | This is the portable version of OpenSSH, a free implementation of | 118 | This is the portable version of OpenSSH, a free implementation of |
119 | the Secure Shell protocol as specified by the IETF secsh working | 119 | the Secure Shell protocol as specified by the IETF secsh working |
diff --git a/debian/openssh-server.postinst b/debian/openssh-server.postinst index 1d26a7b55..b7ea210c4 100644 --- a/debian/openssh-server.postinst +++ b/debian/openssh-server.postinst | |||
@@ -183,6 +183,35 @@ create_keys() { | |||
183 | } | 183 | } |
184 | 184 | ||
185 | 185 | ||
186 | vulnerable_host_keys() { | ||
187 | # If the admin has explicitly put the vulnerable keys back, we | ||
188 | # assume they can look after themselves. | ||
189 | db_fget ssh/vulnerable_host_keys seen | ||
190 | if [ "$RET" = true ]; then | ||
191 | return 0 | ||
192 | fi | ||
193 | |||
194 | hostkeys="$(host_keys_required)" | ||
195 | vulnerable= | ||
196 | for hostkey in $hostkeys; do | ||
197 | [ -f "$hostkey" ] || continue | ||
198 | if ssh-vulnkey -q "$hostkey"; then | ||
199 | vulnerable="${vulnerable:+$vulnerable }$hostkey" | ||
200 | fi | ||
201 | done | ||
202 | if [ "$vulnerable" ]; then | ||
203 | db_subst ssh/vulnerable_host_keys HOST_KEYS "$vulnerable" | ||
204 | db_input critical ssh/vulnerable_host_keys || true | ||
205 | db_go | ||
206 | for hostkey in $vulnerable; do | ||
207 | mv "$hostkey" "$hostkey.broken" || true | ||
208 | mv "$hostkey.pub" "$hostkey.pub.broken" || true | ||
209 | done | ||
210 | create_keys | ||
211 | fi | ||
212 | } | ||
213 | |||
214 | |||
186 | check_password_auth() { | 215 | check_password_auth() { |
187 | passwordauth="$(get_config_option PasswordAuthentication)" | 216 | passwordauth="$(get_config_option PasswordAuthentication)" |
188 | crauth="$(get_config_option ChallengeResponseAuthentication)" | 217 | crauth="$(get_config_option ChallengeResponseAuthentication)" |
@@ -422,6 +451,7 @@ fix_doc_symlink | |||
422 | create_sshdconfig | 451 | create_sshdconfig |
423 | check_idea_key | 452 | check_idea_key |
424 | create_keys | 453 | create_keys |
454 | vulnerable_host_keys | ||
425 | fix_statoverride | 455 | fix_statoverride |
426 | if dpkg --compare-versions "$2" lt 1:4.3p2-3; then | 456 | if dpkg --compare-versions "$2" lt 1:4.3p2-3; then |
427 | fix_sshd_shell | 457 | fix_sshd_shell |
diff --git a/debian/openssh-server.templates b/debian/openssh-server.templates index 2cc62f8f1..6c1187e7f 100644 --- a/debian/openssh-server.templates +++ b/debian/openssh-server.templates | |||
@@ -62,3 +62,23 @@ _Description: Disable challenge-response authentication? | |||
62 | able to log in using passwords. If you leave it enabled (the default | 62 | able to log in using passwords. If you leave it enabled (the default |
63 | answer), then the 'PasswordAuthentication no' option will have no useful | 63 | answer), then the 'PasswordAuthentication no' option will have no useful |
64 | effect unless you also adjust your PAM configuration in /etc/pam.d/ssh. | 64 | effect unless you also adjust your PAM configuration in /etc/pam.d/ssh. |
65 | |||
66 | Template: ssh/vulnerable_host_keys | ||
67 | Type: note | ||
68 | _Description: Vulnerable host keys will be regenerated | ||
69 | Some of the OpenSSH server host keys on this system were generated with a | ||
70 | version of OpenSSL that had a broken random number generator. As a result, | ||
71 | these host keys are from a well-known set, are subject to brute-force | ||
72 | attacks, and must be regenerated. | ||
73 | . | ||
74 | Users of this system should be informed of this change, as they will be | ||
75 | prompted about the host key change the next time they log in. Use | ||
76 | 'ssh-keygen -l -f HOST_KEY_FILE' after the upgrade has changed to print the | ||
77 | fingerprints of the new host keys. | ||
78 | . | ||
79 | The affected host keys are: | ||
80 | . | ||
81 | ${HOST_KEYS} | ||
82 | . | ||
83 | User keys may also be affected by this problem. The 'ssh-vulnkey' command | ||
84 | may be used as a partial test for this. | ||
diff --git a/pathnames.h b/pathnames.h index f2571e274..8886e8edd 100644 --- a/pathnames.h +++ b/pathnames.h | |||
@@ -43,6 +43,8 @@ | |||
43 | /* Backwards compatibility */ | 43 | /* Backwards compatibility */ |
44 | #define _PATH_DH_PRIMES SSHDIR "/primes" | 44 | #define _PATH_DH_PRIMES SSHDIR "/primes" |
45 | 45 | ||
46 | #define _PATH_BLACKLIST SSHDIR "/blacklist" | ||
47 | |||
46 | #ifndef _PATH_SSH_PROGRAM | 48 | #ifndef _PATH_SSH_PROGRAM |
47 | #define _PATH_SSH_PROGRAM "/usr/bin/ssh" | 49 | #define _PATH_SSH_PROGRAM "/usr/bin/ssh" |
48 | #endif | 50 | #endif |
diff --git a/servconf.c b/servconf.c index 14a9dde3d..c5688912d 100644 --- a/servconf.c +++ b/servconf.c | |||
@@ -96,6 +96,7 @@ initialize_server_options(ServerOptions *options) | |||
96 | options->password_authentication = -1; | 96 | options->password_authentication = -1; |
97 | options->kbd_interactive_authentication = -1; | 97 | options->kbd_interactive_authentication = -1; |
98 | options->challenge_response_authentication = -1; | 98 | options->challenge_response_authentication = -1; |
99 | options->permit_blacklisted_keys = -1; | ||
99 | options->permit_empty_passwd = -1; | 100 | options->permit_empty_passwd = -1; |
100 | options->permit_user_env = -1; | 101 | options->permit_user_env = -1; |
101 | options->use_login = -1; | 102 | options->use_login = -1; |
@@ -218,6 +219,8 @@ fill_default_server_options(ServerOptions *options) | |||
218 | options->kbd_interactive_authentication = 0; | 219 | options->kbd_interactive_authentication = 0; |
219 | if (options->challenge_response_authentication == -1) | 220 | if (options->challenge_response_authentication == -1) |
220 | options->challenge_response_authentication = 1; | 221 | options->challenge_response_authentication = 1; |
222 | if (options->permit_blacklisted_keys == -1) | ||
223 | options->permit_blacklisted_keys = 0; | ||
221 | if (options->permit_empty_passwd == -1) | 224 | if (options->permit_empty_passwd == -1) |
222 | options->permit_empty_passwd = 0; | 225 | options->permit_empty_passwd = 0; |
223 | if (options->permit_user_env == -1) | 226 | if (options->permit_user_env == -1) |
@@ -287,7 +290,7 @@ typedef enum { | |||
287 | sListenAddress, sAddressFamily, | 290 | sListenAddress, sAddressFamily, |
288 | sPrintMotd, sPrintLastLog, sIgnoreRhosts, | 291 | sPrintMotd, sPrintLastLog, sIgnoreRhosts, |
289 | sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, | 292 | sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, |
290 | sStrictModes, sEmptyPasswd, sTCPKeepAlive, | 293 | sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive, |
291 | sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression, | 294 | sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression, |
292 | sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, | 295 | sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, |
293 | sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, | 296 | sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, |
@@ -387,6 +390,7 @@ static struct { | |||
387 | { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL }, | 390 | { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL }, |
388 | { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL }, | 391 | { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL }, |
389 | { "strictmodes", sStrictModes, SSHCFG_GLOBAL }, | 392 | { "strictmodes", sStrictModes, SSHCFG_GLOBAL }, |
393 | { "permitblacklistedkeys", sPermitBlacklistedKeys, SSHCFG_GLOBAL }, | ||
390 | { "permitemptypasswords", sEmptyPasswd, SSHCFG_GLOBAL }, | 394 | { "permitemptypasswords", sEmptyPasswd, SSHCFG_GLOBAL }, |
391 | { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL }, | 395 | { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL }, |
392 | { "uselogin", sUseLogin, SSHCFG_GLOBAL }, | 396 | { "uselogin", sUseLogin, SSHCFG_GLOBAL }, |
@@ -943,6 +947,10 @@ parse_flag: | |||
943 | intptr = &options->tcp_keep_alive; | 947 | intptr = &options->tcp_keep_alive; |
944 | goto parse_flag; | 948 | goto parse_flag; |
945 | 949 | ||
950 | case sPermitBlacklistedKeys: | ||
951 | intptr = &options->permit_blacklisted_keys; | ||
952 | goto parse_flag; | ||
953 | |||
946 | case sEmptyPasswd: | 954 | case sEmptyPasswd: |
947 | intptr = &options->permit_empty_passwd; | 955 | intptr = &options->permit_empty_passwd; |
948 | goto parse_flag; | 956 | goto parse_flag; |
diff --git a/servconf.h b/servconf.h index 257de1c8b..be42e9f60 100644 --- a/servconf.h +++ b/servconf.h | |||
@@ -94,6 +94,7 @@ typedef struct { | |||
94 | * authentication. */ | 94 | * authentication. */ |
95 | int kbd_interactive_authentication; /* If true, permit */ | 95 | int kbd_interactive_authentication; /* If true, permit */ |
96 | int challenge_response_authentication; | 96 | int challenge_response_authentication; |
97 | int permit_blacklisted_keys; /* If true, permit */ | ||
97 | int permit_empty_passwd; /* If false, do not permit empty | 98 | int permit_empty_passwd; /* If false, do not permit empty |
98 | * passwords. */ | 99 | * passwords. */ |
99 | int permit_user_env; /* If true, read ~/.ssh/environment */ | 100 | int permit_user_env; /* If true, read ~/.ssh/environment */ |
diff --git a/ssh-vulnkey.1 b/ssh-vulnkey.1 new file mode 100644 index 000000000..9286e73c0 --- /dev/null +++ b/ssh-vulnkey.1 | |||
@@ -0,0 +1,151 @@ | |||
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 | ||
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 | .Nm | ||
85 | will exit zero if any of the given keys were in the compromised list, | ||
86 | otherwise non-zero. | ||
87 | .Pp | ||
88 | Unless the | ||
89 | .Cm PermitBlacklistedKeys | ||
90 | option is used, | ||
91 | .Xr sshd 8 | ||
92 | will reject attempts to authenticate with keys in the compromised list. | ||
93 | .Pp | ||
94 | The options are as follows: | ||
95 | .Bl -tag -width Ds | ||
96 | .It Fl a | ||
97 | Check keys of all users on the system. | ||
98 | You will typically need to run | ||
99 | .Nm | ||
100 | as root to use this option. | ||
101 | For each user, | ||
102 | .Nm | ||
103 | will check | ||
104 | .Pa ~/.ssh/id_rsa , | ||
105 | .Pa ~/.ssh/id_dsa , | ||
106 | .Pa ~/.ssh/identity , | ||
107 | .Pa ~/.ssh/authorized_keys | ||
108 | and | ||
109 | .Pa ~/.ssh/authorized_keys2 . | ||
110 | It will also check the system's host keys. | ||
111 | .It Fl q | ||
112 | Quiet mode. | ||
113 | Normally, | ||
114 | .Nm | ||
115 | outputs the fingerprint of each key scanned, with a description of its | ||
116 | status. | ||
117 | This option suppresses that output. | ||
118 | .El | ||
119 | .Sh BLACKLIST FILE FORMAT | ||
120 | The blacklist file may start with comments, on lines starting with | ||
121 | .Dq # . | ||
122 | After these initial comments, it must follow a strict format: | ||
123 | .Pp | ||
124 | .Bl -bullet -offset indent -compact | ||
125 | .It | ||
126 | All the lines must be exactly the same length (20 characters followed by a | ||
127 | newline) and must be in sorted order. | ||
128 | .It | ||
129 | Each line must consist of the lower-case hexadecimal MD5 key fingerprint, | ||
130 | without colons, and with the first 12 characters removed (that is, the least | ||
131 | significant 80 bits of the fingerprint). | ||
132 | .El | ||
133 | .Pp | ||
134 | The key fingerprint may be generated using | ||
135 | .Xr ssh-keygen 1 : | ||
136 | .Pp | ||
137 | .Dl $ ssh-keygen -l -f /path/to/key | ||
138 | .Pp | ||
139 | This strict format is necessary to allow the blacklist file to be checked | ||
140 | quickly, using a binary-search algorithm. | ||
141 | .Sh SEE ALSO | ||
142 | .Xr ssh-keygen 1 , | ||
143 | .Xr sshd 8 | ||
144 | .Sh AUTHORS | ||
145 | .An -nosplit | ||
146 | .An Colin Watson Aq cjwatson@ubuntu.com | ||
147 | .Pp | ||
148 | Florian Weimer suggested the option to check keys of all users, and the idea | ||
149 | of processing | ||
150 | .Xr ssh-keyscan 1 | ||
151 | output. | ||
diff --git a/ssh-vulnkey.c b/ssh-vulnkey.c new file mode 100644 index 000000000..ba87cbd28 --- /dev/null +++ b/ssh-vulnkey.c | |||
@@ -0,0 +1,311 @@ | |||
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 <string.h> | ||
31 | #include <stdio.h> | ||
32 | #include <fcntl.h> | ||
33 | #include <unistd.h> | ||
34 | |||
35 | #include <openssl/evp.h> | ||
36 | |||
37 | #include "xmalloc.h" | ||
38 | #include "ssh.h" | ||
39 | #include "log.h" | ||
40 | #include "key.h" | ||
41 | #include "authfile.h" | ||
42 | #include "pathnames.h" | ||
43 | #include "misc.h" | ||
44 | |||
45 | extern char *__progname; | ||
46 | |||
47 | /* Default files to check */ | ||
48 | static char *default_host_files[] = { | ||
49 | _PATH_HOST_RSA_KEY_FILE, | ||
50 | _PATH_HOST_DSA_KEY_FILE, | ||
51 | _PATH_HOST_KEY_FILE, | ||
52 | NULL | ||
53 | }; | ||
54 | static char *default_files[] = { | ||
55 | _PATH_SSH_CLIENT_ID_RSA, | ||
56 | _PATH_SSH_CLIENT_ID_DSA, | ||
57 | _PATH_SSH_CLIENT_IDENTITY, | ||
58 | _PATH_SSH_USER_PERMITTED_KEYS, | ||
59 | _PATH_SSH_USER_PERMITTED_KEYS2, | ||
60 | NULL | ||
61 | }; | ||
62 | |||
63 | static int quiet = 0; | ||
64 | |||
65 | static void | ||
66 | usage(void) | ||
67 | { | ||
68 | fprintf(stderr, "usage: %s [-aq] [file ...]\n", __progname); | ||
69 | fprintf(stderr, "Options:\n"); | ||
70 | fprintf(stderr, " -a Check keys of all users.\n"); | ||
71 | fprintf(stderr, " -q Quiet mode.\n"); | ||
72 | exit(1); | ||
73 | } | ||
74 | |||
75 | void | ||
76 | describe_key(const char *msg, const Key *key, const char *comment) | ||
77 | { | ||
78 | char *fp; | ||
79 | |||
80 | fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
81 | if (!quiet) | ||
82 | printf("%s: %u %s %s\n", msg, key_size(key), fp, comment); | ||
83 | xfree(fp); | ||
84 | } | ||
85 | |||
86 | int | ||
87 | do_key(const Key *key, const char *comment) | ||
88 | { | ||
89 | char *blacklist_file; | ||
90 | struct stat st; | ||
91 | int ret = 1; | ||
92 | |||
93 | blacklist_file = blacklist_filename(key); | ||
94 | if (stat(blacklist_file, &st) < 0) | ||
95 | describe_key("Unknown (no blacklist information)", | ||
96 | key, comment); | ||
97 | else if (blacklisted_key(key)) { | ||
98 | describe_key("COMPROMISED", key, comment); | ||
99 | ret = 0; | ||
100 | } else | ||
101 | describe_key("Not blacklisted", key, comment); | ||
102 | xfree(blacklist_file); | ||
103 | |||
104 | return ret; | ||
105 | } | ||
106 | |||
107 | int | ||
108 | do_filename(const char *filename, int quiet_open) | ||
109 | { | ||
110 | FILE *f; | ||
111 | char line[SSH_MAX_PUBKEY_BYTES]; | ||
112 | char *cp; | ||
113 | u_long linenum = 0; | ||
114 | Key *key; | ||
115 | char *comment = NULL; | ||
116 | int found = 0, ret = 1; | ||
117 | |||
118 | /* Copy much of key_load_public's logic here so that we can read | ||
119 | * several keys from a single file (e.g. authorized_keys). | ||
120 | */ | ||
121 | |||
122 | if (strcmp(filename, "-") != 0) { | ||
123 | f = fopen(filename, "r"); | ||
124 | if (!f) { | ||
125 | char pubfile[MAXPATHLEN]; | ||
126 | if (strlcpy(pubfile, filename, sizeof pubfile) < | ||
127 | sizeof(pubfile) && | ||
128 | strlcat(pubfile, ".pub", sizeof pubfile) < | ||
129 | sizeof(pubfile)) | ||
130 | f = fopen(pubfile, "r"); | ||
131 | } | ||
132 | if (!f) { | ||
133 | if (!quiet_open) | ||
134 | perror(filename); | ||
135 | return -1; | ||
136 | } | ||
137 | } else | ||
138 | f = stdin; | ||
139 | while (read_keyfile_line(f, filename, line, sizeof(line), | ||
140 | &linenum) != -1) { | ||
141 | cp = line; | ||
142 | switch (*cp) { | ||
143 | case '#': | ||
144 | case '\n': | ||
145 | case '\0': | ||
146 | continue; | ||
147 | } | ||
148 | /* Skip leading whitespace. */ | ||
149 | for (; *cp && (*cp == ' ' || *cp == '\t'); cp++) | ||
150 | ; | ||
151 | /* Cope with ssh-keyscan output. */ | ||
152 | comment = NULL; | ||
153 | if (*cp) { | ||
154 | char *space; | ||
155 | int type; | ||
156 | |||
157 | space = strchr(cp, ' '); | ||
158 | if (!space) | ||
159 | continue; | ||
160 | *space = '\0'; | ||
161 | type = key_type_from_name(cp); | ||
162 | if (type == KEY_UNSPEC) { | ||
163 | comment = xstrdup(cp); | ||
164 | cp = space + 1; | ||
165 | } | ||
166 | *space = ' '; | ||
167 | } | ||
168 | if (!comment) | ||
169 | comment = xstrdup(filename); | ||
170 | if (*cp) { | ||
171 | key = key_new(KEY_RSA1); | ||
172 | if (key_read(key, &cp) == 1) { | ||
173 | if (!do_key(key, comment)) | ||
174 | ret = 0; | ||
175 | key_free(key); | ||
176 | found = 1; | ||
177 | } else { | ||
178 | key_free(key); | ||
179 | key = key_new(KEY_UNSPEC); | ||
180 | if (key_read(key, &cp) == 1) { | ||
181 | if (!do_key(key, comment)) | ||
182 | ret = 0; | ||
183 | key_free(key); | ||
184 | found = 1; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | xfree(comment); | ||
189 | comment = NULL; | ||
190 | } | ||
191 | if (f != stdin) | ||
192 | fclose(f); | ||
193 | |||
194 | if (!found && filename) { | ||
195 | key = key_load_public(filename, &comment); | ||
196 | if (key) { | ||
197 | if (!do_key(key, comment)) | ||
198 | ret = 0; | ||
199 | found = 1; | ||
200 | } | ||
201 | if (comment) | ||
202 | xfree(comment); | ||
203 | } | ||
204 | |||
205 | return ret; | ||
206 | } | ||
207 | |||
208 | int | ||
209 | do_host(void) | ||
210 | { | ||
211 | int i; | ||
212 | struct stat st; | ||
213 | int ret = 1; | ||
214 | |||
215 | for (i = 0; default_host_files[i]; i++) { | ||
216 | if (stat(default_host_files[i], &st) < 0) | ||
217 | continue; | ||
218 | if (!do_filename(default_host_files[i], 1)) | ||
219 | ret = 0; | ||
220 | } | ||
221 | |||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | int | ||
226 | do_user(const char *dir) | ||
227 | { | ||
228 | int i; | ||
229 | char buf[MAXPATHLEN]; | ||
230 | struct stat st; | ||
231 | int ret = 1; | ||
232 | |||
233 | for (i = 0; default_files[i]; i++) { | ||
234 | snprintf(buf, sizeof(buf), "%s/%s", dir, default_files[i]); | ||
235 | if (stat(buf, &st) < 0) | ||
236 | continue; | ||
237 | if (!do_filename(buf, 0)) | ||
238 | ret = 0; | ||
239 | } | ||
240 | |||
241 | return ret; | ||
242 | } | ||
243 | |||
244 | int | ||
245 | main(int argc, char **argv) | ||
246 | { | ||
247 | int opt, all_users = 0; | ||
248 | int ret = 1; | ||
249 | extern int optind; | ||
250 | |||
251 | /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ | ||
252 | sanitise_stdfd(); | ||
253 | |||
254 | __progname = ssh_get_progname(argv[0]); | ||
255 | |||
256 | SSLeay_add_all_algorithms(); | ||
257 | log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1); | ||
258 | |||
259 | /* We don't need the RNG ourselves, but symbol references here allow | ||
260 | * ld to link us properly. | ||
261 | */ | ||
262 | init_rng(); | ||
263 | seed_rng(); | ||
264 | |||
265 | while ((opt = getopt(argc, argv, "ahq")) != -1) { | ||
266 | switch (opt) { | ||
267 | case 'a': | ||
268 | all_users = 1; | ||
269 | break; | ||
270 | case 'q': | ||
271 | quiet = 1; | ||
272 | break; | ||
273 | case 'h': | ||
274 | default: | ||
275 | usage(); | ||
276 | } | ||
277 | } | ||
278 | |||
279 | if (all_users) { | ||
280 | struct passwd *pw; | ||
281 | |||
282 | if (!do_host()) | ||
283 | ret = 0; | ||
284 | |||
285 | while ((pw = getpwent()) != NULL) { | ||
286 | if (pw->pw_dir) { | ||
287 | if (!do_user(pw->pw_dir)) | ||
288 | ret = 0; | ||
289 | } | ||
290 | } | ||
291 | } else if (optind == argc) { | ||
292 | struct passwd *pw; | ||
293 | |||
294 | if (!do_host()) | ||
295 | ret = 0; | ||
296 | |||
297 | if ((pw = getpwuid(getuid())) == NULL) | ||
298 | fprintf(stderr, "No user found with uid %u\n", | ||
299 | (u_int)getuid()); | ||
300 | else { | ||
301 | if (!do_user(pw->pw_dir)) | ||
302 | ret = 0; | ||
303 | } | ||
304 | } else { | ||
305 | while (optind < argc) | ||
306 | if (!do_filename(argv[optind++], 0)) | ||
307 | ret = 0; | ||
308 | } | ||
309 | |||
310 | return ret; | ||
311 | } | ||
@@ -1466,6 +1466,21 @@ main(int ac, char **av) | |||
1466 | 1466 | ||
1467 | for (i = 0; i < options.num_host_key_files; i++) { | 1467 | for (i = 0; i < options.num_host_key_files; i++) { |
1468 | key = key_load_private(options.host_key_files[i], "", NULL); | 1468 | key = key_load_private(options.host_key_files[i], "", NULL); |
1469 | if (key && blacklisted_key(key)) { | ||
1470 | char *fp; | ||
1471 | fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); | ||
1472 | if (options.permit_blacklisted_keys) | ||
1473 | error("Host key %s blacklisted (see " | ||
1474 | "ssh-vulnkey(1)); continuing anyway", fp); | ||
1475 | else | ||
1476 | error("Host key %s blacklisted (see " | ||
1477 | "ssh-vulnkey(1))", fp); | ||
1478 | xfree(fp); | ||
1479 | if (!options.permit_blacklisted_keys) { | ||
1480 | sensitive_data.host_keys[i] = NULL; | ||
1481 | continue; | ||
1482 | } | ||
1483 | } | ||
1469 | sensitive_data.host_keys[i] = key; | 1484 | sensitive_data.host_keys[i] = key; |
1470 | if (key == NULL) { | 1485 | if (key == NULL) { |
1471 | error("Could not load host key: %s", | 1486 | error("Could not load host key: %s", |
diff --git a/sshd_config.5 b/sshd_config.5 index a7a7227b2..dab26e079 100644 --- a/sshd_config.5 +++ b/sshd_config.5 | |||
@@ -615,6 +615,20 @@ are refused if the number of unauthenticated connections reaches | |||
615 | Specifies whether password authentication is allowed. | 615 | Specifies whether password authentication is allowed. |
616 | The default is | 616 | The default is |
617 | .Dq yes . | 617 | .Dq yes . |
618 | .It Cm PermitBlacklistedKeys | ||
619 | Specifies whether | ||
620 | .Xr sshd 8 | ||
621 | should allow keys recorded in its blacklist of known-compromised keys (see | ||
622 | .Xr ssh-vulnkey 1 ) . | ||
623 | If | ||
624 | .Dq yes , | ||
625 | then attempts to authenticate with compromised keys will be logged but | ||
626 | accepted. | ||
627 | If | ||
628 | .Dq no , | ||
629 | then attempts to authenticate with compromised keys will be rejected. | ||
630 | The default is | ||
631 | .Dq no . | ||
618 | .It Cm PermitEmptyPasswords | 632 | .It Cm PermitEmptyPasswords |
619 | When password authentication is allowed, it specifies whether the | 633 | When password authentication is allowed, it specifies whether the |
620 | server allows login to accounts with empty password strings. | 634 | server allows login to accounts with empty password strings. |