1 Description: Reject vulnerable keys to mitigate Debian OpenSSL flaw
2 In 2008, Debian (and derived distributions such as Ubuntu) shipped an
3 OpenSSL package with a flawed random number generator, causing OpenSSH to
4 generate only a very limited set of keys which were subject to private half
5 precomputation. To mitigate this, this patch checks key authentications
6 against a blacklist of known-vulnerable keys, and adds a new ssh-vulnkey
7 program which can be used to explicitly check keys against that blacklist.
9 Author: Colin Watson <cjwatson@ubuntu.com>
10 Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1469
11 Last-Update: 2010-02-27
14 ===================================================================
18 SSH_KEYSIGN=$(libexecdir)/ssh-keysign
19 SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
20 RAND_HELPER=$(libexecdir)/ssh-rand-helper
21 +SSH_DATADIR=$(datadir)/ssh
22 PRIVSEP_PATH=@PRIVSEP_PATH@
23 SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@
26 -D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \
27 -D_PATH_SSH_PIDDIR=\"$(piddir)\" \
28 -D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \
29 - -DSSH_RAND_HELPER=\"$(RAND_HELPER)\"
30 + -DSSH_RAND_HELPER=\"$(RAND_HELPER)\" \
31 + -D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\"
36 INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@
37 INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@
39 -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) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT)
40 +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) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-vulnkey$(EXEEXT)
42 LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \
43 canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \
45 sftp-server.o sftp-common.o \
46 roaming_common.o roaming_serv.o
48 -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-rand-helper.8.out ssh-keysign.8.out ssh-pkcs11-helper.8.out sshd_config.5.out ssh_config.5.out
49 -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-rand-helper.8 ssh-keysign.8 ssh-pkcs11-helper.8 sshd_config.5 ssh_config.5
50 +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-rand-helper.8.out ssh-keysign.8.out ssh-pkcs11-helper.8.out ssh-vulnkey.1.out sshd_config.5.out ssh_config.5.out
51 +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-rand-helper.8 ssh-keysign.8 ssh-pkcs11-helper.8 ssh-vulnkey.1 sshd_config.5 ssh_config.5
54 CONFIGFILES=sshd_config.out ssh_config.out moduli.out
56 ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o
57 $(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
59 +ssh-vulnkey$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-vulnkey.o
60 + $(LD) -o $@ ssh-vulnkey.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
62 # test driver for the loginrec code - not built by default
63 logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o
64 $(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS)
66 $(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
67 $(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
68 $(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
69 + $(INSTALL) -m 0755 $(STRIP_OPT) ssh-vulnkey$(EXEEXT) $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
70 $(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
71 $(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
72 $(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
74 $(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
75 $(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
76 $(INSTALL) -m 644 ssh-pkcs11-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-pkcs11-helper.8
77 + $(INSTALL) -m 644 ssh-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
78 -rm -f $(DESTDIR)$(bindir)/slogin
79 ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin
80 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1
82 -rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT)
83 -rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT)
84 -rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
85 + -rm -f $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
86 -rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT)
87 -rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
88 -rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
90 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1
91 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1
92 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1
93 + -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
94 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8
95 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8
96 -rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
97 Index: b/auth-rh-rsa.c
98 ===================================================================
103 HostStatus host_status;
105 - if (auth_key_is_revoked(client_host_key))
106 + if (auth_key_is_revoked(client_host_key, 0))
109 /* Check if we would accept it using rhosts authentication. */
111 ===================================================================
115 file, linenum, BN_num_bits(key->rsa->n), bits);
117 /* Never accept a revoked key */
118 - if (auth_key_is_revoked(key))
119 + if (auth_key_is_revoked(key, 0))
122 /* We have found the desired key. */
124 ===================================================================
128 #include "servconf.h"
130 #include "hostfile.h"
131 +#include "authfile.h"
133 #include "auth-options.h"
134 #include "canohost.h"
135 @@ -621,10 +622,34 @@
137 /* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */
139 -auth_key_is_revoked(Key *key)
140 +auth_key_is_revoked(Key *key, int hostkey)
144 + if (blacklisted_key(key, &key_fp) == 1) {
145 + if (options.permit_blacklisted_keys) {
147 + error("Host key %s blacklisted (see "
148 + "ssh-vulnkey(1)); continuing anyway",
151 + logit("Public key %s from %s blacklisted (see "
152 + "ssh-vulnkey(1)); continuing anyway",
153 + key_fp, get_remote_ipaddr());
157 + error("Host key %s blacklisted (see "
158 + "ssh-vulnkey(1))", key_fp);
160 + logit("Public key %s from %s blacklisted (see "
162 + key_fp, get_remote_ipaddr());
168 if (options.revoked_keys_file == NULL)
172 ===================================================================
177 FILE *auth_openkeyfile(const char *, struct passwd *, int);
178 FILE *auth_openprincipals(const char *, struct passwd *, int);
179 -int auth_key_is_revoked(Key *);
180 +int auth_key_is_revoked(Key *, int);
183 check_key_in_hostfiles(struct passwd *, Key *, const char *,
184 Index: b/auth2-hostbased.c
185 ===================================================================
186 --- a/auth2-hostbased.c
187 +++ b/auth2-hostbased.c
192 - if (auth_key_is_revoked(key))
193 + if (auth_key_is_revoked(key, 0))
196 resolvedname = get_canonical_hostname(options.use_dns);
197 Index: b/auth2-pubkey.c
198 ===================================================================
205 - if (auth_key_is_revoked(key))
206 + if (auth_key_is_revoked(key, 0))
208 - if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key))
209 + if (key_is_cert(key) &&
210 + auth_key_is_revoked(key->cert->signature_key, 0))
213 success = user_cert_trusted_ca(pw, key);
215 ===================================================================
221 #include "atomicio.h"
222 +#include "pathnames.h"
224 /* Version identification string for SSH v1 identity files. */
225 static const char authfile_id_string[] =
226 @@ -906,3 +907,140 @@
230 +/* Scan a blacklist of known-vulnerable keys in blacklist_file. */
232 +blacklisted_key_in_file(Key *key, const char *blacklist_file, char **fp)
235 + char *dgst_hex = NULL;
236 + char *dgst_packed = NULL, *p;
241 + off_t start, lower, upper;
244 + debug("Checking blacklist file %s", blacklist_file);
245 + fd = open(blacklist_file, O_RDONLY);
251 + dgst_hex = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
252 + /* Remove all colons */
253 + dgst_packed = xcalloc(1, strlen(dgst_hex) + 1);
254 + for (i = 0, p = dgst_packed; dgst_hex[i]; i++)
255 + if (dgst_hex[i] != ':')
256 + *p++ = dgst_hex[i];
257 + /* Only compare least-significant 80 bits (to keep the blacklist
260 + line_len = strlen(dgst_packed + 12);
264 + /* Skip leading comments */
270 + r = atomicio(read, fd, buf, sizeof(buf));
276 + newline = memchr(buf, '\n', sizeof(buf));
279 + start += newline + 1 - buf;
280 + if (lseek(fd, start, SEEK_SET) < 0)
284 + /* Initialise binary search record numbers */
285 + if (fstat(fd, &st) < 0)
288 + upper = (st.st_size - start) / (line_len + 1);
290 + while (lower != upper) {
294 + cur = lower + (upper - lower) / 2;
296 + /* Read this line and compare to digest; this is
297 + * overflow-safe since cur < max(off_t) / (line_len + 1) */
298 + if (lseek(fd, start + cur * (line_len + 1), SEEK_SET) < 0)
300 + if (atomicio(read, fd, buf, line_len) != line_len)
302 + cmp = memcmp(buf, dgst_packed + 12, line_len);
307 + } else if (cmp > 0) {
312 + debug("Found %s in blacklist", dgst_hex);
320 + xfree(dgst_packed);
321 + if (ret != 1 && dgst_hex) {
333 + * Scan blacklists of known-vulnerable keys. If a vulnerable key is found,
334 + * its fingerprint is returned in *fp, unless fp is NULL.
337 +blacklisted_key(Key *key, char **fp)
340 + char *blacklist_file;
343 + public = key_demote(key);
344 + if (public->type == KEY_RSA1)
345 + public->type = KEY_RSA;
347 + xasprintf(&blacklist_file, "%s.%s-%u",
348 + _PATH_BLACKLIST, key_type(public), key_size(public));
349 + ret = blacklisted_key_in_file(public, blacklist_file, fp);
350 + xfree(blacklist_file);
356 + xasprintf(&blacklist_file, "%s.%s-%u",
357 + _PATH_BLACKLIST_CONFIG, key_type(public), key_size(public));
358 + ret2 = blacklisted_key_in_file(public, blacklist_file, fp);
359 + xfree(blacklist_file);
368 ===================================================================
372 int key_perm_ok(int, const char *);
373 int key_in_file(Key *, const char *, int);
375 +int blacklisted_key(Key *key, char **fp);
379 ===================================================================
383 #define SSHDIR ETCDIR "/ssh"
386 +#ifndef _PATH_SSH_DATADIR
387 +#define _PATH_SSH_DATADIR "/usr/share/ssh"
390 #ifndef _PATH_SSH_PIDDIR
391 #define _PATH_SSH_PIDDIR "/var/run"
394 /* Backwards compatibility */
395 #define _PATH_DH_PRIMES SSHDIR "/primes"
397 +#define _PATH_BLACKLIST _PATH_SSH_DATADIR "/blacklist"
398 +#define _PATH_BLACKLIST_CONFIG SSHDIR "/blacklist"
400 #ifndef _PATH_SSH_PROGRAM
401 #define _PATH_SSH_PROGRAM "/usr/bin/ssh"
404 ===================================================================
408 oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
409 oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
410 oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
411 + oUseBlacklistedKeys,
412 oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
413 oClearAllForwardings, oNoHostAuthenticationForLocalhost,
414 oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
416 { "passwordauthentication", oPasswordAuthentication },
417 { "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
418 { "kbdinteractivedevices", oKbdInteractiveDevices },
419 + { "useblacklistedkeys", oUseBlacklistedKeys },
420 { "rsaauthentication", oRSAAuthentication },
421 { "pubkeyauthentication", oPubkeyAuthentication },
422 { "dsaauthentication", oPubkeyAuthentication }, /* alias */
424 intptr = &options->challenge_response_authentication;
427 + case oUseBlacklistedKeys:
428 + intptr = &options->use_blacklisted_keys;
431 case oGssAuthentication:
432 intptr = &options->gss_authentication;
434 @@ -1134,6 +1140,7 @@
435 options->kbd_interactive_devices = NULL;
436 options->rhosts_rsa_authentication = -1;
437 options->hostbased_authentication = -1;
438 + options->use_blacklisted_keys = -1;
439 options->batch_mode = -1;
440 options->check_host_ip = -1;
441 options->strict_host_key_checking = -1;
442 @@ -1245,6 +1252,8 @@
443 options->rhosts_rsa_authentication = 0;
444 if (options->hostbased_authentication == -1)
445 options->hostbased_authentication = 0;
446 + if (options->use_blacklisted_keys == -1)
447 + options->use_blacklisted_keys = 0;
448 if (options->batch_mode == -1)
449 options->batch_mode = 0;
450 if (options->check_host_ip == -1)
452 ===================================================================
456 int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
457 char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */
458 int zero_knowledge_password_authentication; /* Try jpake */
459 + int use_blacklisted_keys; /* If true, send */
460 int batch_mode; /* Batch mode: do not ask for passwords. */
461 int check_host_ip; /* Also keep track of keys for IP address */
462 int strict_host_key_checking; /* Strict host key checking. */
464 ===================================================================
468 options->password_authentication = -1;
469 options->kbd_interactive_authentication = -1;
470 options->challenge_response_authentication = -1;
471 + options->permit_blacklisted_keys = -1;
472 options->permit_empty_passwd = -1;
473 options->permit_user_env = -1;
474 options->use_login = -1;
476 options->kbd_interactive_authentication = 0;
477 if (options->challenge_response_authentication == -1)
478 options->challenge_response_authentication = 1;
479 + if (options->permit_blacklisted_keys == -1)
480 + options->permit_blacklisted_keys = 0;
481 if (options->permit_empty_passwd == -1)
482 options->permit_empty_passwd = 0;
483 if (options->permit_user_env == -1)
485 sListenAddress, sAddressFamily,
486 sPrintMotd, sPrintLastLog, sIgnoreRhosts,
487 sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
488 - sStrictModes, sEmptyPasswd, sTCPKeepAlive,
489 + sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive,
490 sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
491 sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
492 sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
494 { "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
495 { "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
496 { "strictmodes", sStrictModes, SSHCFG_GLOBAL },
497 + { "permitblacklistedkeys", sPermitBlacklistedKeys, SSHCFG_GLOBAL },
498 { "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL },
499 { "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL },
500 { "uselogin", sUseLogin, SSHCFG_GLOBAL },
501 @@ -1029,6 +1033,10 @@
502 intptr = &options->tcp_keep_alive;
505 + case sPermitBlacklistedKeys:
506 + intptr = &options->permit_blacklisted_keys;
510 intptr = &options->permit_empty_passwd;
512 @@ -1757,6 +1765,7 @@
513 dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost);
514 dump_cfg_fmtint(sStrictModes, o->strict_modes);
515 dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive);
516 + dump_cfg_fmtint(sPermitBlacklistedKeys, o->permit_blacklisted_keys);
517 dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd);
518 dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env);
519 dump_cfg_fmtint(sUseLogin, o->use_login);
521 ===================================================================
525 int challenge_response_authentication;
526 int zero_knowledge_password_authentication;
527 /* If true, permit jpake auth */
528 + int permit_blacklisted_keys; /* If true, permit */
529 int permit_empty_passwd; /* If false, do not permit empty
531 int permit_user_env; /* If true, read ~/.ssh/environment */
533 ===================================================================
540 +Any keys recorded in the blacklist of known-compromised keys (see
544 The options are as follows:
554 OpenSSH is a derivative of the original and free
556 ===================================================================
560 add_file(AuthenticationConnection *ac, const char *filename)
563 - char *comment = NULL;
564 + char *comment = NULL, *fp;
565 char msg[1024], *certpath;
566 int fd, perms_ok, ret = -1;
569 "Bad passphrase, try again for %.200s: ", comment);
572 + if (blacklisted_key(private, &fp) == 1) {
573 + fprintf(stderr, "Public key %s blacklisted (see "
574 + "ssh-vulnkey(1)); refusing to add it\n", fp);
581 if (ssh_add_identity_constrained(ac, private, comment, lifetime,
583 Index: b/ssh-keygen.1
584 ===================================================================
595 Index: b/ssh-vulnkey.1
596 ===================================================================
600 +.\" Copyright (c) 2008 Canonical Ltd. All rights reserved.
602 +.\" Redistribution and use in source and binary forms, with or without
603 +.\" modification, are permitted provided that the following conditions
605 +.\" 1. Redistributions of source code must retain the above copyright
606 +.\" notice, this list of conditions and the following disclaimer.
607 +.\" 2. Redistributions in binary form must reproduce the above copyright
608 +.\" notice, this list of conditions and the following disclaimer in the
609 +.\" documentation and/or other materials provided with the distribution.
611 +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
612 +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
613 +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
614 +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
615 +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
616 +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
617 +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
618 +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
619 +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
620 +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
622 +.Dd $Mdocdate: May 12 2008 $
627 +.Nd check blacklist of compromised keys
636 +checks a key against a blacklist of compromised keys.
638 +A substantial number of keys are known to have been generated using a broken
639 +version of OpenSSL distributed by Debian which failed to seed its random
640 +number generator correctly.
641 +Keys generated using these OpenSSL versions should be assumed to be
643 +This tool may be useful in checking for such keys.
645 +Keys that are compromised cannot be repaired; replacements must be generated
650 +files on all systems where compromised keys were permitted to authenticate.
652 +The argument list will be interpreted as a list of paths to public key files
656 +If no suitable file is found at a given path,
660 +and retry, in case it was given a private key file.
661 +If no files are given as arguments,
666 +.Pa ~/.ssh/identity ,
667 +.Pa ~/.ssh/authorized_keys
669 +.Pa ~/.ssh/authorized_keys2 ,
670 +as well as the system's host keys if readable.
674 +is given as an argument,
676 +will read from standard input.
677 +This can be used to process output from
681 +.Dl $ ssh-keyscan -t rsa remote.example.org | ssh-vulnkey -
684 +.Cm PermitBlacklistedKeys
687 +will reject attempts to authenticate with keys in the compromised list.
693 +.Bd -literal -offset indent
694 +/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
695 +/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
696 +/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
699 +Each line is of the following format (any lines beginning with
701 +should be ignored by scripts):
703 +.Dl Ar filename : Ns Ar line : Ar status : Ar type Ar size Ar fingerprint Ar comment
705 +It is important to distinguish between the possible values of
710 +These keys are listed in a blacklist file, normally because their
711 +corresponding private keys are well-known.
712 +Replacements must be generated using
715 +A blacklist file exists for this key type and size, but this key is not
717 +Unless there is some particular reason to believe otherwise, this key
719 +(Note that DSA keys used with the broken version of OpenSSL distributed
720 +by Debian may be compromised in the event that anyone captured a network
721 +trace, even if they were generated with a secure version of OpenSSL.)
722 +.It Unknown (blacklist file not installed)
723 +No blacklist file exists for this key type and size.
724 +You should find a suitable published blacklist and install it before
725 +deciding whether this key is safe to use.
728 +The options are as follows:
731 +Check keys of all users on the system.
732 +You will typically need to run
734 +as root to use this option.
740 +.Pa ~/.ssh/identity ,
741 +.Pa ~/.ssh/authorized_keys
743 +.Pa ~/.ssh/authorized_keys2 .
744 +It will also check the system's host keys.
749 +outputs the fingerprint of each key scanned, with a description of its
751 +This option suppresses that output.
756 +does not output anything for keys that are not listed in their corresponding
757 +blacklist file (although it still produces output for keys for which there
758 +is no blacklist file, since their status is unknown).
761 +to produce output for all keys.
765 +will exit zero if any of the given keys were in the compromised list,
767 +.Sh BLACKLIST FILE FORMAT
768 +The blacklist file may start with comments, on lines starting with
770 +After these initial comments, it must follow a strict format:
772 +.Bl -bullet -offset indent -compact
774 +All the lines must be exactly the same length (20 characters followed by a
775 +newline) and must be in sorted order.
777 +Each line must consist of the lower-case hexadecimal MD5 key fingerprint,
778 +without colons, and with the first 12 characters removed (that is, the least
779 +significant 80 bits of the fingerprint).
782 +The key fingerprint may be generated using
785 +.Dl $ ssh-keygen -l -f /path/to/key
787 +This strict format is necessary to allow the blacklist file to be checked
788 +quickly, using a binary-search algorithm.
791 +.It Pa ~/.ssh/id_rsa
792 +If present, contains the protocol version 2 RSA authentication identity of
794 +.It Pa ~/.ssh/id_dsa
795 +If present, contains the protocol version 2 DSA authentication identity of
797 +.It Pa ~/.ssh/identity
798 +If present, contains the protocol version 1 RSA authentication identity of
800 +.It Pa ~/.ssh/authorized_keys
801 +If present, lists the public keys (RSA/DSA) that can be used for logging in
803 +.It Pa ~/.ssh/authorized_keys2
805 +.Pa ~/.ssh/authorized_keys .
806 +This file may still be present on some old systems, but should not be
807 +created if it is missing.
808 +.It Pa /etc/ssh/ssh_host_rsa_key
809 +If present, contains the protocol version 2 RSA identity of the system.
810 +.It Pa /etc/ssh/ssh_host_dsa_key
811 +If present, contains the protocol version 2 DSA identity of the system.
812 +.It Pa /etc/ssh/ssh_host_key
813 +If present, contains the protocol version 1 RSA identity of the system.
814 +.It Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
815 +If present, lists the blacklisted keys of type
822 +The format of this file is described above.
823 +RSA1 keys are converted to RSA before being checked in the blacklist.
824 +Note that the fingerprints of RSA1 keys are computed differently, so you
825 +will not be able to find them in the blacklist by hand.
826 +.It Pa /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
828 +.Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH ,
829 +but may be edited by the system administrator to add new blacklist entries.
836 +.An Colin Watson Aq cjwatson@ubuntu.com
838 +Florian Weimer suggested the option to check keys of all users, and the idea
842 Index: b/ssh-vulnkey.c
843 ===================================================================
848 + * Copyright (c) 2008 Canonical Ltd. All rights reserved.
850 + * Redistribution and use in source and binary forms, with or without
851 + * modification, are permitted provided that the following conditions
853 + * 1. Redistributions of source code must retain the above copyright
854 + * notice, this list of conditions and the following disclaimer.
855 + * 2. Redistributions in binary form must reproduce the above copyright
856 + * notice, this list of conditions and the following disclaimer in the
857 + * documentation and/or other materials provided with the distribution.
859 + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
860 + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
861 + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
862 + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
863 + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
864 + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
865 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
866 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
867 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
868 + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
871 +#include "includes.h"
873 +#include <sys/types.h>
874 +#include <sys/stat.h>
882 +#include <openssl/evp.h>
884 +#include "xmalloc.h"
888 +#include "authfile.h"
889 +#include "pathnames.h"
890 +#include "uidswap.h"
893 +extern char *__progname;
895 +/* Default files to check */
896 +static char *default_host_files[] = {
897 + _PATH_HOST_RSA_KEY_FILE,
898 + _PATH_HOST_DSA_KEY_FILE,
899 + _PATH_HOST_KEY_FILE,
902 +static char *default_files[] = {
903 + _PATH_SSH_CLIENT_ID_RSA,
904 + _PATH_SSH_CLIENT_ID_DSA,
905 + _PATH_SSH_CLIENT_IDENTITY,
906 + _PATH_SSH_USER_PERMITTED_KEYS,
907 + _PATH_SSH_USER_PERMITTED_KEYS2,
911 +static int verbosity = 0;
913 +static int some_keys = 0;
914 +static int some_unknown = 0;
915 +static int some_compromised = 0;
920 + fprintf(stderr, "usage: %s [-aqv] [file ...]\n", __progname);
921 + fprintf(stderr, "Options:\n");
922 + fprintf(stderr, " -a Check keys of all users.\n");
923 + fprintf(stderr, " -q Quiet mode.\n");
924 + fprintf(stderr, " -v Verbose mode.\n");
929 +describe_key(const char *filename, u_long linenum, const char *msg,
930 + Key *key, const char *comment, int min_verbosity)
934 + fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
935 + if (verbosity >= min_verbosity) {
936 + if (strchr(filename, ':'))
937 + printf("\"%s\"", filename);
939 + printf("%s", filename);
940 + printf(":%lu: %s: %s %u %s %s\n", linenum, msg,
941 + key_type(key), key_size(key), fp, comment);
947 +do_key(const char *filename, u_long linenum,
948 + Key *key, const char *comment)
951 + int blacklist_status;
956 + public = key_demote(key);
957 + if (public->type == KEY_RSA1)
958 + public->type = KEY_RSA;
960 + blacklist_status = blacklisted_key(public, NULL);
961 + if (blacklist_status == -1) {
962 + describe_key(filename, linenum,
963 + "Unknown (blacklist file not installed)", key, comment, 0);
965 + } else if (blacklist_status == 1) {
966 + describe_key(filename, linenum,
967 + "COMPROMISED", key, comment, 0);
968 + some_compromised = 1;
971 + describe_key(filename, linenum,
972 + "Not blacklisted", key, comment, 1);
980 +do_filename(const char *filename, int quiet_open)
983 + char line[SSH_MAX_PUBKEY_BYTES];
985 + u_long linenum = 0;
987 + char *comment = NULL;
988 + int found = 0, ret = 1;
990 + /* Copy much of key_load_public's logic here so that we can read
991 + * several keys from a single file (e.g. authorized_keys).
994 + if (strcmp(filename, "-") != 0) {
996 + f = fopen(filename, "r");
997 + save_errno = errno;
999 + char pubfile[MAXPATHLEN];
1000 + if (strlcpy(pubfile, filename, sizeof pubfile) <
1001 + sizeof(pubfile) &&
1002 + strlcat(pubfile, ".pub", sizeof pubfile) <
1004 + f = fopen(pubfile, "r");
1006 + errno = save_errno; /* earlier errno is more useful */
1012 + if (verbosity > 0)
1013 + printf("# %s\n", filename);
1016 + while (read_keyfile_line(f, filename, line, sizeof(line),
1017 + &linenum) != -1) {
1023 + /* Chop trailing newline. */
1024 + i = strlen(line) - 1;
1025 + if (line[i] == '\n')
1028 + /* Skip leading whitespace, empty and comment lines. */
1029 + for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
1031 + if (!*cp || *cp == '\n' || *cp == '#')
1034 + /* Cope with ssh-keyscan output and options in
1035 + * authorized_keys files.
1037 + space = strchr(cp, ' ');
1041 + type = key_type_from_name(cp);
1043 + /* Leading number (RSA1) or valid type (RSA/DSA) indicates
1044 + * that we have no host name or options to skip.
1046 + if ((strtol(cp, &end, 10) == 0 || *end != ' ') &&
1047 + type == KEY_UNSPEC) {
1050 + for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
1051 + if (*cp == '\\' && cp[1] == '"')
1052 + cp++; /* Skip both */
1053 + else if (*cp == '"')
1056 + /* Skip remaining whitespace. */
1057 + for (; *cp == ' ' || *cp == '\t'; cp++)
1063 + /* Read and process the key itself. */
1064 + key = key_new(KEY_RSA1);
1065 + if (key_read(key, &cp) == 1) {
1066 + while (*cp == ' ' || *cp == '\t')
1068 + if (!do_key(filename, linenum,
1069 + key, *cp ? cp : filename))
1074 + key = key_new(KEY_UNSPEC);
1075 + if (key_read(key, &cp) == 1) {
1076 + while (*cp == ' ' || *cp == '\t')
1078 + if (!do_key(filename, linenum,
1079 + key, *cp ? cp : filename))
1089 + if (!found && filename) {
1090 + key = key_load_public(filename, &comment);
1092 + if (!do_key(filename, 1, key, comment))
1104 +do_host(int quiet_open)
1110 + for (i = 0; default_host_files[i]; i++) {
1111 + if (stat(default_host_files[i], &st) < 0 && errno == ENOENT)
1113 + if (!do_filename(default_host_files[i], quiet_open))
1121 +do_user(const char *dir)
1128 + for (i = 0; default_files[i]; i++) {
1129 + xasprintf(&file, "%s/%s", dir, default_files[i]);
1130 + if (stat(file, &st) < 0 && errno == ENOENT) {
1134 + if (!do_filename(file, 0))
1143 +main(int argc, char **argv)
1145 + int opt, all_users = 0;
1147 + extern int optind;
1149 + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
1152 + __progname = ssh_get_progname(argv[0]);
1154 + SSLeay_add_all_algorithms();
1155 + log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1);
1157 + /* We don't need the RNG ourselves, but symbol references here allow
1158 + * ld to link us properly.
1163 + while ((opt = getopt(argc, argv, "ahqv")) != -1) {
1181 + struct passwd *pw;
1186 + while ((pw = getpwent()) != NULL) {
1188 + temporarily_use_uid(pw);
1189 + if (!do_user(pw->pw_dir))
1194 + } else if (optind == argc) {
1195 + struct passwd *pw;
1200 + if ((pw = getpwuid(geteuid())) == NULL)
1201 + fprintf(stderr, "No user found with uid %u\n",
1202 + (u_int)geteuid());
1204 + if (!do_user(pw->pw_dir))
1208 + while (optind < argc)
1209 + if (!do_filename(argv[optind++], 0))
1213 + if (verbosity >= 0) {
1214 + if (some_unknown) {
1216 + printf("# The status of some keys on your system is unknown.\n");
1217 + printf("# You may need to install additional blacklist files.\n");
1219 + if (some_compromised) {
1221 + printf("# Some keys on your system have been compromised!\n");
1222 + printf("# You must replace them using ssh-keygen(1).\n");
1224 + if (some_unknown || some_compromised) {
1226 + printf("# See the ssh-vulnkey(1) manual page for further advice.\n");
1227 + } else if (some_keys && verbosity > 0) {
1229 + printf("# No blacklisted keys!\n");
1236 ===================================================================
1239 @@ -1402,6 +1402,7 @@
1243 +.Xr ssh-vulnkey 1 ,
1248 ===================================================================
1251 @@ -1445,7 +1445,7 @@
1253 load_public_identity_files(void)
1255 - char *filename, *cp, thishost[NI_MAXHOST];
1256 + char *filename, *cp, thishost[NI_MAXHOST], *fp;
1257 char *pwdir = NULL, *pwname = NULL;
1260 @@ -1502,6 +1502,22 @@
1261 public = key_load_public(filename, NULL);
1262 debug("identity file %s type %d", filename,
1263 public ? public->type : -1);
1264 + if (public && blacklisted_key(public, &fp) == 1) {
1265 + if (options.use_blacklisted_keys)
1266 + logit("Public key %s blacklisted (see "
1267 + "ssh-vulnkey(1)); continuing anyway", fp);
1269 + logit("Public key %s blacklisted (see "
1270 + "ssh-vulnkey(1)); refusing to send it",
1273 + if (!options.use_blacklisted_keys) {
1280 xfree(options.identity_files[i]);
1281 identity_files[n_ids] = filename;
1282 identity_keys[n_ids] = public;
1283 Index: b/ssh_config.5
1284 ===================================================================
1287 @@ -1146,6 +1146,23 @@
1291 +.It Cm UseBlacklistedKeys
1294 +should use keys recorded in its blacklist of known-compromised keys (see
1295 +.Xr ssh-vulnkey 1 )
1296 +for authentication.
1299 +then attempts to use compromised keys for authentication will be logged but
1301 +It is strongly recommended that this be used only to install new authorized
1302 +keys on the remote system, and even then only with the utmost care.
1305 +then attempts to use compromised keys for authentication will be prevented.
1308 .It Cm UsePrivilegedPort
1309 Specifies whether to use a privileged port for outgoing connections.
1310 The argument must be
1311 Index: b/sshconnect2.c
1312 ===================================================================
1315 @@ -1488,6 +1488,8 @@
1317 /* list of keys stored in the filesystem */
1318 for (i = 0; i < options.num_identity_files; i++) {
1319 + if (options.identity_files[i] == NULL)
1321 key = options.identity_keys[i];
1322 if (key && key->type == KEY_RSA1)
1324 @@ -1581,7 +1583,7 @@
1325 debug("Offering %s public key: %s", key_type(id->key),
1327 sent = send_pubkey_test(authctxt, id);
1328 - } else if (id->key == NULL) {
1329 + } else if (id->key == NULL && id->filename) {
1330 debug("Trying private key: %s", id->filename);
1331 id->key = load_identity_file(id->filename);
1332 if (id->key != NULL) {
1334 ===================================================================
1341 +.Xr ssh-vulnkey 1 ,
1343 .Xr hosts_access 5 ,
1346 ===================================================================
1349 @@ -1576,6 +1576,11 @@
1350 sensitive_data.host_keys[i] = NULL;
1353 + if (auth_key_is_revoked(key, 1)) {
1355 + sensitive_data.host_keys[i] = NULL;
1358 switch (key->type) {
1360 sensitive_data.ssh1_host_key = key;
1361 Index: b/sshd_config.5
1362 ===================================================================
1365 @@ -792,6 +792,20 @@
1366 Specifies whether password authentication is allowed.
1369 +.It Cm PermitBlacklistedKeys
1372 +should allow keys recorded in its blacklist of known-compromised keys (see
1373 +.Xr ssh-vulnkey 1 ) .
1376 +then attempts to authenticate with compromised keys will be logged but
1380 +then attempts to authenticate with compromised keys will be rejected.
1383 .It Cm PermitEmptyPasswords
1384 When password authentication is allowed, it specifies whether the
1385 server allows login to accounts with empty password strings.