Description: Reject vulnerable keys to mitigate Debian OpenSSL flaw
In 2008, Debian (and derived distributions such as Ubuntu) shipped an
OpenSSL package with a flawed random number generator, causing OpenSSH to
generate only a very limited set of keys which were subject to private half
precomputation. To mitigate this, this patch checks key authentications
against a blacklist of known-vulnerable keys, and adds a new ssh-vulnkey
program which can be used to explicitly check keys against that blacklist.
See CVE-2008-0166.
Author: Colin Watson <cjwatson@ubuntu.com>
Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1469
Last-Update: 2010-02-27
Index: b/Makefile.in
===================================================================
--- a/Makefile.in
+++ b/Makefile.in
@@ -27,6 +27,7 @@
SSH_KEYSIGN=$(libexecdir)/ssh-keysign
SSH_PKCS11_HELPER=$(libexecdir)/ssh-pkcs11-helper
RAND_HELPER=$(libexecdir)/ssh-rand-helper
+SSH_DATADIR=$(datadir)/ssh
PRIVSEP_PATH=@PRIVSEP_PATH@
SSH_PRIVSEP_USER=@SSH_PRIVSEP_USER@
STRIP_OPT=@STRIP_OPT@
@@ -39,7 +40,8 @@
-D_PATH_SSH_PKCS11_HELPER=\"$(SSH_PKCS11_HELPER)\" \
-D_PATH_SSH_PIDDIR=\"$(piddir)\" \
-D_PATH_PRIVSEP_CHROOT_DIR=\"$(PRIVSEP_PATH)\" \
- -DSSH_RAND_HELPER=\"$(RAND_HELPER)\"
+ -DSSH_RAND_HELPER=\"$(RAND_HELPER)\" \
+ -D_PATH_SSH_DATADIR=\"$(SSH_DATADIR)\"
CC=@CC@
LD=@LD@
@@ -62,7 +64,7 @@
INSTALL_SSH_PRNG_CMDS=@INSTALL_SSH_PRNG_CMDS@
INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAND_HELPER@
-TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT)
+TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-pkcs11-helper$(EXEEXT) ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) ssh-vulnkey$(EXEEXT)
LIBSSH_OBJS=acss.o authfd.o authfile.o bufaux.o bufbn.o buffer.o \
canohost.o channels.o cipher.o cipher-acss.o cipher-aes.o \
@@ -93,8 +95,8 @@
audit.o audit-bsm.o platform.o sftp-server.o sftp-common.o \
roaming_common.o roaming_serv.o
-MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-rand-helper.8.out ssh-keysign.8.out ssh-pkcs11-helper.8.out sshd_config.5.out ssh_config.5.out
-MANPAGES_IN = moduli.5 scp.1 ssh-add.1 ssh-agent.1 ssh-keygen.1 ssh-keyscan.1 ssh.1 sshd.8 sftp-server.8 sftp.1 ssh-rand-helper.8 ssh-keysign.8 ssh-pkcs11-helper.8 sshd_config.5 ssh_config.5
+MANPAGES = moduli.5.out scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-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
+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
MANTYPE = @MANTYPE@
CONFIGFILES=sshd_config.out ssh_config.out moduli.out
@@ -174,6 +176,9 @@
ssh-rand-helper${EXEEXT}: $(LIBCOMPAT) libssh.a ssh-rand-helper.o
$(LD) -o $@ ssh-rand-helper.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
+ssh-vulnkey$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-vulnkey.o
+ $(LD) -o $@ ssh-vulnkey.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
+
# test driver for the loginrec code - not built by default
logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o
$(LD) -o $@ logintest.o $(LDFLAGS) loginrec.o -lopenbsd-compat -lssh $(LIBS)
@@ -268,6 +273,7 @@
$(INSTALL) -m 0755 $(STRIP_OPT) ssh-pkcs11-helper$(EXEEXT) $(DESTDIR)$(SSH_PKCS11_HELPER)$(EXEEXT)
$(INSTALL) -m 0755 $(STRIP_OPT) sftp$(EXEEXT) $(DESTDIR)$(bindir)/sftp$(EXEEXT)
$(INSTALL) -m 0755 $(STRIP_OPT) sftp-server$(EXEEXT) $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
+ $(INSTALL) -m 0755 $(STRIP_OPT) ssh-vulnkey$(EXEEXT) $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
$(INSTALL) -m 644 ssh.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh.1
$(INSTALL) -m 644 scp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/scp.1
$(INSTALL) -m 644 ssh-add.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-add.1
@@ -285,6 +291,7 @@
$(INSTALL) -m 644 sftp-server.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
$(INSTALL) -m 644 ssh-keysign.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-keysign.8
$(INSTALL) -m 644 ssh-pkcs11-helper.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-pkcs11-helper.8
+ $(INSTALL) -m 644 ssh-vulnkey.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
-rm -f $(DESTDIR)$(bindir)/slogin
ln -s ./ssh$(EXEEXT) $(DESTDIR)$(bindir)/slogin
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/slogin.1
@@ -366,6 +373,7 @@
-rm -f $(DESTDIR)$(bindir)/ssh-agent$(EXEEXT)
-rm -f $(DESTDIR)$(bindir)/ssh-keygen$(EXEEXT)
-rm -f $(DESTDIR)$(bindir)/ssh-keyscan$(EXEEXT)
+ -rm -f $(DESTDIR)$(bindir)/ssh-vulnkey$(EXEEXT)
-rm -f $(DESTDIR)$(bindir)/sftp$(EXEEXT)
-rm -f $(DESTDIR)$(sbindir)/sshd$(EXEEXT)
-rm -r $(DESTDIR)$(SFTP_SERVER)$(EXEEXT)
@@ -379,6 +387,7 @@
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keygen.1
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/sftp.1
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-keyscan.1
+ -rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/ssh-vulnkey.1
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sshd.8
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/ssh-rand-helper.8
-rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sftp-server.8
Index: b/auth-rh-rsa.c
===================================================================
--- a/auth-rh-rsa.c
+++ b/auth-rh-rsa.c
@@ -44,7 +44,7 @@
{
HostStatus host_status;
- if (auth_key_is_revoked(client_host_key))
+ if (auth_key_is_revoked(client_host_key, 0))
return 0;
/* Check if we would accept it using rhosts authentication. */
Index: b/auth-rsa.c
===================================================================
--- a/auth-rsa.c
+++ b/auth-rsa.c
@@ -94,7 +94,7 @@
MD5_CTX md;
int len;
- if (auth_key_is_revoked(key))
+ if (auth_key_is_revoked(key, 0))
return 0;
/* don't allow short keys */
Index: b/auth.c
===================================================================
--- a/auth.c
+++ b/auth.c
@@ -59,6 +59,7 @@
#include "servconf.h"
#include "key.h"
#include "hostfile.h"
+#include "authfile.h"
#include "auth.h"
#include "auth-options.h"
#include "canohost.h"
@@ -593,10 +594,34 @@
/* Returns 1 if key is revoked by revoked_keys_file, 0 otherwise */
int
-auth_key_is_revoked(Key *key)
+auth_key_is_revoked(Key *key, int hostkey)
{
char *key_fp;
+ if (blacklisted_key(key, &key_fp) == 1) {
+ if (options.permit_blacklisted_keys) {
+ if (hostkey)
+ error("Host key %s blacklisted (see "
+ "ssh-vulnkey(1)); continuing anyway",
+ key_fp);
+ else
+ logit("Public key %s from %s blacklisted (see "
+ "ssh-vulnkey(1)); continuing anyway",
+ key_fp, get_remote_ipaddr());
+ xfree(key_fp);
+ } else {
+ if (hostkey)
+ error("Host key %s blacklisted (see "
+ "ssh-vulnkey(1))", key_fp);
+ else
+ logit("Public key %s from %s blacklisted (see "
+ "ssh-vulnkey(1))",
+ key_fp, get_remote_ipaddr());
+ xfree(key_fp);
+ return 1;
+ }
+ }
+
if (options.revoked_keys_file == NULL)
return 0;
Index: b/auth.h
===================================================================
--- a/auth.h
+++ b/auth.h
@@ -173,7 +173,7 @@
char *authorized_keys_file2(struct passwd *);
FILE *auth_openkeyfile(const char *, struct passwd *, int);
-int auth_key_is_revoked(Key *);
+int auth_key_is_revoked(Key *, int);
HostStatus
check_key_in_hostfiles(struct passwd *, Key *, const char *,
Index: b/auth2-hostbased.c
===================================================================
--- a/auth2-hostbased.c
+++ b/auth2-hostbased.c
@@ -145,7 +145,7 @@
HostStatus host_status;
int len;
- if (auth_key_is_revoked(key))
+ if (auth_key_is_revoked(key, 0))
return 0;
resolvedname = get_canonical_hostname(options.use_dns);
Index: b/auth2-pubkey.c
===================================================================
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -328,9 +328,10 @@
int success;
char *file;
- if (auth_key_is_revoked(key))
+ if (auth_key_is_revoked(key, 0))
return 0;
- if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key))
+ if (key_is_cert(key) &&
+ auth_key_is_revoked(key->cert->signature_key, 0))
return 0;
success = user_cert_trusted_ca(pw, key);
Index: b/authfile.c
===================================================================
--- a/authfile.c
+++ b/authfile.c
@@ -68,6 +68,7 @@
#include "rsa.h"
#include "misc.h"
#include "atomicio.h"
+#include "pathnames.h"
/* Version identification string for SSH v1 identity files. */
static const char authfile_id_string[] =
@@ -754,3 +755,140 @@
return ret;
}
+/* Scan a blacklist of known-vulnerable keys in blacklist_file. */
+static int
+blacklisted_key_in_file(const Key *key, const char *blacklist_file, char **fp)
+{
+ int fd = -1;
+ char *dgst_hex = NULL;
+ char *dgst_packed = NULL, *p;
+ int i;
+ size_t line_len;
+ struct stat st;
+ char buf[256];
+ off_t start, lower, upper;
+ int ret = 0;
+
+ debug("Checking blacklist file %s", blacklist_file);
+ fd = open(blacklist_file, O_RDONLY);
+ if (fd < 0) {
+ ret = -1;
+ goto out;
+ }
+
+ dgst_hex = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+ /* Remove all colons */
+ dgst_packed = xcalloc(1, strlen(dgst_hex) + 1);
+ for (i = 0, p = dgst_packed; dgst_hex[i]; i++)
+ if (dgst_hex[i] != ':')
+ *p++ = dgst_hex[i];
+ /* Only compare least-significant 80 bits (to keep the blacklist
+ * size down)
+ */
+ line_len = strlen(dgst_packed + 12);
+ if (line_len > 32)
+ goto out;
+
+ /* Skip leading comments */
+ start = 0;
+ for (;;) {
+ ssize_t r;
+ char *newline;
+
+ r = atomicio(read, fd, buf, sizeof(buf));
+ if (r <= 0)
+ goto out;
+ if (buf[0] != '#')
+ break;
+
+ newline = memchr(buf, '\n', sizeof(buf));
+ if (!newline)
+ goto out;
+ start += newline + 1 - buf;
+ if (lseek(fd, start, SEEK_SET) < 0)
+ goto out;
+ }
+
+ /* Initialise binary search record numbers */
+ if (fstat(fd, &st) < 0)
+ goto out;
+ lower = 0;
+ upper = (st.st_size - start) / (line_len + 1);
+
+ while (lower != upper) {
+ off_t cur;
+ int cmp;
+
+ cur = lower + (upper - lower) / 2;
+
+ /* Read this line and compare to digest; this is
+ * overflow-safe since cur < max(off_t) / (line_len + 1) */
+ if (lseek(fd, start + cur * (line_len + 1), SEEK_SET) < 0)
+ break;
+ if (atomicio(read, fd, buf, line_len) != line_len)
+ break;
+ cmp = memcmp(buf, dgst_packed + 12, line_len);
+ if (cmp < 0) {
+ if (cur == lower)
+ break;
+ lower = cur;
+ } else if (cmp > 0) {
+ if (cur == upper)
+ break;
+ upper = cur;
+ } else {
+ debug("Found %s in blacklist", dgst_hex);
+ ret = 1;
+ break;
+ }
+ }
+
+out:
+ if (dgst_packed)
+ xfree(dgst_packed);
+ if (ret != 1 && dgst_hex) {
+ xfree(dgst_hex);
+ dgst_hex = NULL;
+ }
+ if (fp)
+ *fp = dgst_hex;
+ if (fd >= 0)
+ close(fd);
+ return ret;
+}
+
+/*
+ * Scan blacklists of known-vulnerable keys. If a vulnerable key is found,
+ * its fingerprint is returned in *fp, unless fp is NULL.
+ */
+int
+blacklisted_key(const Key *key, char **fp)
+{
+ Key *public;
+ char *blacklist_file;
+ int ret, ret2;
+
+ public = key_demote(key);
+ if (public->type == KEY_RSA1)
+ public->type = KEY_RSA;
+
+ xasprintf(&blacklist_file, "%s.%s-%u",
+ _PATH_BLACKLIST, key_type(public), key_size(public));
+ ret = blacklisted_key_in_file(public, blacklist_file, fp);
+ xfree(blacklist_file);
+ if (ret > 0) {
+ key_free(public);
+ return ret;
+ }
+
+ xasprintf(&blacklist_file, "%s.%s-%u",
+ _PATH_BLACKLIST_CONFIG, key_type(public), key_size(public));
+ ret2 = blacklisted_key_in_file(public, blacklist_file, fp);
+ xfree(blacklist_file);
+ if (ret2 > ret)
+ ret = ret2;
+
+ key_free(public);
+ return ret;
+}
+
Index: b/authfile.h
===================================================================
--- a/authfile.h
+++ b/authfile.h
@@ -24,4 +24,6 @@
int key_perm_ok(int, const char *);
int key_in_file(Key *, const char *, int);
+int blacklisted_key(const Key *key, char **fp);
+
#endif
Index: b/pathnames.h
===================================================================
--- a/pathnames.h
+++ b/pathnames.h
@@ -18,6 +18,10 @@
#define SSHDIR ETCDIR "/ssh"
#endif
+#ifndef _PATH_SSH_DATADIR
+#define _PATH_SSH_DATADIR "/usr/share/ssh"
+#endif
+
#ifndef _PATH_SSH_PIDDIR
#define _PATH_SSH_PIDDIR "/var/run"
#endif
@@ -43,6 +47,9 @@
/* Backwards compatibility */
#define _PATH_DH_PRIMES SSHDIR "/primes"
+#define _PATH_BLACKLIST _PATH_SSH_DATADIR "/blacklist"
+#define _PATH_BLACKLIST_CONFIG SSHDIR "/blacklist"
+
#ifndef _PATH_SSH_PROGRAM
#define _PATH_SSH_PROGRAM "/usr/bin/ssh"
#endif
Index: b/readconf.c
===================================================================
--- a/readconf.c
+++ b/readconf.c
@@ -123,6 +123,7 @@
oGlobalKnownHostsFile2, oUserKnownHostsFile2, oPubkeyAuthentication,
oKbdInteractiveAuthentication, oKbdInteractiveDevices, oHostKeyAlias,
oDynamicForward, oPreferredAuthentications, oHostbasedAuthentication,
+ oUseBlacklistedKeys,
oHostKeyAlgorithms, oBindAddress, oPKCS11Provider,
oClearAllForwardings, oNoHostAuthenticationForLocalhost,
oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
@@ -152,6 +153,7 @@
{ "passwordauthentication", oPasswordAuthentication },
{ "kbdinteractiveauthentication", oKbdInteractiveAuthentication },
{ "kbdinteractivedevices", oKbdInteractiveDevices },
+ { "useblacklistedkeys", oUseBlacklistedKeys },
{ "rsaauthentication", oRSAAuthentication },
{ "pubkeyauthentication", oPubkeyAuthentication },
{ "dsaauthentication", oPubkeyAuthentication }, /* alias */
@@ -461,6 +463,10 @@
intptr = &options->challenge_response_authentication;
goto parse_flag;
+ case oUseBlacklistedKeys:
+ intptr = &options->use_blacklisted_keys;
+ goto parse_flag;
+
case oGssAuthentication:
intptr = &options->gss_authentication;
goto parse_flag;
@@ -1050,6 +1056,7 @@
options->kbd_interactive_devices = NULL;
options->rhosts_rsa_authentication = -1;
options->hostbased_authentication = -1;
+ options->use_blacklisted_keys = -1;
options->batch_mode = -1;
options->check_host_ip = -1;
options->strict_host_key_checking = -1;
@@ -1152,6 +1159,8 @@
options->rhosts_rsa_authentication = 0;
if (options->hostbased_authentication == -1)
options->hostbased_authentication = 0;
+ if (options->use_blacklisted_keys == -1)
+ options->use_blacklisted_keys = 0;
if (options->batch_mode == -1)
options->batch_mode = 0;
if (options->check_host_ip == -1)
Index: b/readconf.h
===================================================================
--- a/readconf.h
+++ b/readconf.h
@@ -54,6 +54,7 @@
int kbd_interactive_authentication; /* Try keyboard-interactive auth. */
char *kbd_interactive_devices; /* Keyboard-interactive auth devices. */
int zero_knowledge_password_authentication; /* Try jpake */
+ int use_blacklisted_keys; /* If true, send */
int batch_mode; /* Batch mode: do not ask for passwords. */
int check_host_ip; /* Also keep track of keys for IP address */
int strict_host_key_checking; /* Strict host key checking. */
Index: b/servconf.c
===================================================================
--- a/servconf.c
+++ b/servconf.c
@@ -100,6 +100,7 @@
options->password_authentication = -1;
options->kbd_interactive_authentication = -1;
options->challenge_response_authentication = -1;
+ options->permit_blacklisted_keys = -1;
options->permit_empty_passwd = -1;
options->permit_user_env = -1;
options->use_login = -1;
@@ -231,6 +232,8 @@
options->kbd_interactive_authentication = 0;
if (options->challenge_response_authentication == -1)
options->challenge_response_authentication = 1;
+ if (options->permit_blacklisted_keys == -1)
+ options->permit_blacklisted_keys = 0;
if (options->permit_empty_passwd == -1)
options->permit_empty_passwd = 0;
if (options->permit_user_env == -1)
@@ -306,7 +309,7 @@
sListenAddress, sAddressFamily,
sPrintMotd, sPrintLastLog, sIgnoreRhosts,
sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost,
- sStrictModes, sEmptyPasswd, sTCPKeepAlive,
+ sStrictModes, sPermitBlacklistedKeys, sEmptyPasswd, sTCPKeepAlive,
sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression,
sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
@@ -415,6 +418,7 @@
{ "x11uselocalhost", sX11UseLocalhost, SSHCFG_ALL },
{ "xauthlocation", sXAuthLocation, SSHCFG_GLOBAL },
{ "strictmodes", sStrictModes, SSHCFG_GLOBAL },
+ { "permitblacklistedkeys", sPermitBlacklistedKeys, SSHCFG_GLOBAL },
{ "permitemptypasswords", sEmptyPasswd, SSHCFG_ALL },
{ "permituserenvironment", sPermitUserEnvironment, SSHCFG_GLOBAL },
{ "uselogin", sUseLogin, SSHCFG_GLOBAL },
@@ -1009,6 +1013,10 @@
intptr = &options->tcp_keep_alive;
goto parse_flag;
+ case sPermitBlacklistedKeys:
+ intptr = &options->permit_blacklisted_keys;
+ goto parse_flag;
+
case sEmptyPasswd:
intptr = &options->permit_empty_passwd;
goto parse_flag;
@@ -1697,6 +1705,7 @@
dump_cfg_fmtint(sX11UseLocalhost, o->x11_use_localhost);
dump_cfg_fmtint(sStrictModes, o->strict_modes);
dump_cfg_fmtint(sTCPKeepAlive, o->tcp_keep_alive);
+ dump_cfg_fmtint(sPermitBlacklistedKeys, o->permit_blacklisted_keys);
dump_cfg_fmtint(sEmptyPasswd, o->permit_empty_passwd);
dump_cfg_fmtint(sPermitUserEnvironment, o->permit_user_env);
dump_cfg_fmtint(sUseLogin, o->use_login);
Index: b/servconf.h
===================================================================
--- a/servconf.h
+++ b/servconf.h
@@ -104,6 +104,7 @@
int challenge_response_authentication;
int zero_knowledge_password_authentication;
/* If true, permit jpake auth */
+ int permit_blacklisted_keys; /* If true, permit */
int permit_empty_passwd; /* If false, do not permit empty
* passwords. */
int permit_user_env; /* If true, read ~/.ssh/environment */
Index: b/ssh-add.1
===================================================================
--- a/ssh-add.1
+++ b/ssh-add.1
@@ -82,6 +82,10 @@
.Nm
to work.
.Pp
+Any keys recorded in the blacklist of known-compromised keys (see
+.Xr ssh-vulnkey 1 )
+will be refused.
+.Pp
The options are as follows:
.Bl -tag -width Ds
.It Fl c
@@ -182,6 +186,7 @@
.Xr ssh 1 ,
.Xr ssh-agent 1 ,
.Xr ssh-keygen 1 ,
+.Xr ssh-vulnkey 1 ,
.Xr sshd 8
.Sh AUTHORS
OpenSSH is a derivative of the original and free
Index: b/ssh-add.c
===================================================================
--- a/ssh-add.c
+++ b/ssh-add.c
@@ -139,7 +139,7 @@
add_file(AuthenticationConnection *ac, const char *filename)
{
Key *private, *cert;
- char *comment = NULL;
+ char *comment = NULL, *fp;
char msg[1024], *certpath;
int fd, perms_ok, ret = -1;
@@ -184,6 +184,14 @@
"Bad passphrase, try again for %.200s: ", comment);
}
}
+ if (blacklisted_key(private, &fp) == 1) {
+ fprintf(stderr, "Public key %s blacklisted (see "
+ "ssh-vulnkey(1)); refusing to add it\n", fp);
+ xfree(fp);
+ key_free(private);
+ xfree(comment);
+ return -1;
+ }
if (ssh_add_identity_constrained(ac, private, comment, lifetime,
confirm)) {
Index: b/ssh-keygen.1
===================================================================
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -628,6 +628,7 @@
.Xr ssh 1 ,
.Xr ssh-add 1 ,
.Xr ssh-agent 1 ,
+.Xr ssh-vulnkey 1 ,
.Xr moduli 5 ,
.Xr sshd 8
.Rs
Index: b/ssh-vulnkey.1
===================================================================
--- /dev/null
+++ b/ssh-vulnkey.1
@@ -0,0 +1,242 @@
+.\" Copyright (c) 2008 Canonical Ltd. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd $Mdocdate: May 12 2008 $
+.Dt SSH-VULNKEY 1
+.Os
+.Sh NAME
+.Nm ssh-vulnkey
+.Nd check blacklist of compromised keys
+.Sh SYNOPSIS
+.Nm
+.Op Fl q | Fl v
+.Ar file ...
+.Nm
+.Fl a
+.Sh DESCRIPTION
+.Nm
+checks a key against a blacklist of compromised keys.
+.Pp
+A substantial number of keys are known to have been generated using a broken
+version of OpenSSL distributed by Debian which failed to seed its random
+number generator correctly.
+Keys generated using these OpenSSL versions should be assumed to be
+compromised.
+This tool may be useful in checking for such keys.
+.Pp
+Keys that are compromised cannot be repaired; replacements must be generated
+using
+.Xr ssh-keygen 1 .
+Make sure to update
+.Pa authorized_keys
+files on all systems where compromised keys were permitted to authenticate.
+.Pp
+The argument list will be interpreted as a list of paths to public key files
+or
+.Pa authorized_keys
+files.
+If no suitable file is found at a given path,
+.Nm
+will append
+.Pa .pub
+and retry, in case it was given a private key file.
+If no files are given as arguments,
+.Nm
+will check
+.Pa ~/.ssh/id_rsa ,
+.Pa ~/.ssh/id_dsa ,
+.Pa ~/.ssh/identity ,
+.Pa ~/.ssh/authorized_keys
+and
+.Pa ~/.ssh/authorized_keys2 ,
+as well as the system's host keys if readable.
+.Pp
+If
+.Dq -
+is given as an argument,
+.Nm
+will read from standard input.
+This can be used to process output from
+.Xr ssh-keyscan 1 ,
+for example:
+.Pp
+.Dl $ ssh-keyscan -t rsa remote.example.org | ssh-vulnkey -
+.Pp
+Unless the
+.Cm PermitBlacklistedKeys
+option is used,
+.Xr sshd 8
+will reject attempts to authenticate with keys in the compromised list.
+.Pp
+The output from
+.Nm
+looks like this:
+.Pp
+.Bd -literal -offset indent
+/etc/ssh/ssh_host_key:1: COMPROMISED: RSA1 2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx root@host
+/home/user/.ssh/id_dsa:1: Not blacklisted: DSA 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx /home/user/.ssh/id_dsa.pub
+/home/user/.ssh/authorized_keys:3: Unknown (blacklist file not installed): RSA 1024 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx user@host
+.Ed
+.Pp
+Each line is of the following format (any lines beginning with
+.Dq #
+should be ignored by scripts):
+.Pp
+.Dl Ar filename : Ns Ar line : Ar status : Ar type Ar size Ar fingerprint Ar comment
+.Pp
+It is important to distinguish between the possible values of
+.Ar status :
+.Pp
+.Bl -tag -width Ds
+.It COMPROMISED
+These keys are listed in a blacklist file, normally because their
+corresponding private keys are well-known.
+Replacements must be generated using
+.Xr ssh-keygen 1 .
+.It Not blacklisted
+A blacklist file exists for this key type and size, but this key is not
+listed in it.
+Unless there is some particular reason to believe otherwise, this key
+may be used safely.
+(Note that DSA keys used with the broken version of OpenSSL distributed
+by Debian may be compromised in the event that anyone captured a network
+trace, even if they were generated with a secure version of OpenSSL.)
+.It Unknown (blacklist file not installed)
+No blacklist file exists for this key type and size.
+You should find a suitable published blacklist and install it before
+deciding whether this key is safe to use.
+.El
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Check keys of all users on the system.
+You will typically need to run
+.Nm
+as root to use this option.
+For each user,
+.Nm
+will check
+.Pa ~/.ssh/id_rsa ,
+.Pa ~/.ssh/id_dsa ,
+.Pa ~/.ssh/identity ,
+.Pa ~/.ssh/authorized_keys
+and
+.Pa ~/.ssh/authorized_keys2 .
+It will also check the system's host keys.
+.It Fl q
+Quiet mode.
+Normally,
+.Nm
+outputs the fingerprint of each key scanned, with a description of its
+status.
+This option suppresses that output.
+.It Fl v
+Verbose mode.
+Normally,
+.Nm
+does not output anything for keys that are not listed in their corresponding
+blacklist file (although it still produces output for keys for which there
+is no blacklist file, since their status is unknown).
+This option causes
+.Nm
+to produce output for all keys.
+.El
+.Sh EXIT STATUS
+.Nm
+will exit zero if any of the given keys were in the compromised list,
+otherwise non-zero.
+.Sh BLACKLIST FILE FORMAT
+The blacklist file may start with comments, on lines starting with
+.Dq # .
+After these initial comments, it must follow a strict format:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+All the lines must be exactly the same length (20 characters followed by a
+newline) and must be in sorted order.
+.It
+Each line must consist of the lower-case hexadecimal MD5 key fingerprint,
+without colons, and with the first 12 characters removed (that is, the least
+significant 80 bits of the fingerprint).
+.El
+.Pp
+The key fingerprint may be generated using
+.Xr ssh-keygen 1 :
+.Pp
+.Dl $ ssh-keygen -l -f /path/to/key
+.Pp
+This strict format is necessary to allow the blacklist file to be checked
+quickly, using a binary-search algorithm.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa ~/.ssh/id_rsa
+If present, contains the protocol version 2 RSA authentication identity of
+the user.
+.It Pa ~/.ssh/id_dsa
+If present, contains the protocol version 2 DSA authentication identity of
+the user.
+.It Pa ~/.ssh/identity
+If present, contains the protocol version 1 RSA authentication identity of
+the user.
+.It Pa ~/.ssh/authorized_keys
+If present, lists the public keys (RSA/DSA) that can be used for logging in
+as this user.
+.It Pa ~/.ssh/authorized_keys2
+Obsolete name for
+.Pa ~/.ssh/authorized_keys .
+This file may still be present on some old systems, but should not be
+created if it is missing.
+.It Pa /etc/ssh/ssh_host_rsa_key
+If present, contains the protocol version 2 RSA identity of the system.
+.It Pa /etc/ssh/ssh_host_dsa_key
+If present, contains the protocol version 2 DSA identity of the system.
+.It Pa /etc/ssh/ssh_host_key
+If present, contains the protocol version 1 RSA identity of the system.
+.It Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
+If present, lists the blacklisted keys of type
+.Ar TYPE
+.Pf ( Dq RSA
+or
+.Dq DSA )
+and bit length
+.Ar LENGTH .
+The format of this file is described above.
+RSA1 keys are converted to RSA before being checked in the blacklist.
+Note that the fingerprints of RSA1 keys are computed differently, so you
+will not be able to find them in the blacklist by hand.
+.It Pa /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
+Same as
+.Pa /usr/share/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH ,
+but may be edited by the system administrator to add new blacklist entries.
+.El
+.Sh SEE ALSO
+.Xr ssh-keygen 1 ,
+.Xr sshd 8
+.Sh AUTHORS
+.An -nosplit
+.An Colin Watson Aq cjwatson@ubuntu.com
+.Pp
+Florian Weimer suggested the option to check keys of all users, and the idea
+of processing
+.Xr ssh-keyscan 1
+output.
Index: b/ssh-vulnkey.c
===================================================================
--- /dev/null
+++ b/ssh-vulnkey.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2008 Canonical Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <openssl/evp.h>
+
+#include "xmalloc.h"
+#include "ssh.h"
+#include "log.h"
+#include "key.h"
+#include "authfile.h"
+#include "pathnames.h"
+#include "uidswap.h"
+#include "misc.h"
+
+extern char *__progname;
+
+/* Default files to check */
+static char *default_host_files[] = {
+ _PATH_HOST_RSA_KEY_FILE,
+ _PATH_HOST_DSA_KEY_FILE,
+ _PATH_HOST_KEY_FILE,
+ NULL
+};
+static char *default_files[] = {
+ _PATH_SSH_CLIENT_ID_RSA,
+ _PATH_SSH_CLIENT_ID_DSA,
+ _PATH_SSH_CLIENT_IDENTITY,
+ _PATH_SSH_USER_PERMITTED_KEYS,
+ _PATH_SSH_USER_PERMITTED_KEYS2,
+ NULL
+};
+
+static int verbosity = 0;
+
+static int some_keys = 0;
+static int some_unknown = 0;
+static int some_compromised = 0;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-aqv] [file ...]\n", __progname);
+ fprintf(stderr, "Options:\n");
+ fprintf(stderr, " -a Check keys of all users.\n");
+ fprintf(stderr, " -q Quiet mode.\n");
+ fprintf(stderr, " -v Verbose mode.\n");
+ exit(1);
+}
+
+void
+describe_key(const char *filename, u_long linenum, const char *msg,
+ const Key *key, const char *comment, int min_verbosity)
+{
+ char *fp;
+
+ fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+ if (verbosity >= min_verbosity) {
+ if (strchr(filename, ':'))
+ printf("\"%s\"", filename);
+ else
+ printf("%s", filename);
+ printf(":%lu: %s: %s %u %s %s\n", linenum, msg,
+ key_type(key), key_size(key), fp, comment);
+ }
+ xfree(fp);
+}
+
+int
+do_key(const char *filename, u_long linenum,
+ const Key *key, const char *comment)
+{
+ Key *public;
+ int blacklist_status;
+ int ret = 1;
+
+ some_keys = 1;
+
+ public = key_demote(key);
+ if (public->type == KEY_RSA1)
+ public->type = KEY_RSA;
+
+ blacklist_status = blacklisted_key(public, NULL);
+ if (blacklist_status == -1) {
+ describe_key(filename, linenum,
+ "Unknown (blacklist file not installed)", key, comment, 0);
+ some_unknown = 1;
+ } else if (blacklist_status == 1) {
+ describe_key(filename, linenum,
+ "COMPROMISED", key, comment, 0);
+ some_compromised = 1;
+ ret = 0;
+ } else
+ describe_key(filename, linenum,
+ "Not blacklisted", key, comment, 1);
+
+ key_free(public);
+
+ return ret;
+}
+
+int
+do_filename(const char *filename, int quiet_open)
+{
+ FILE *f;
+ char line[SSH_MAX_PUBKEY_BYTES];
+ char *cp;
+ u_long linenum = 0;
+ Key *key;
+ char *comment = NULL;
+ int found = 0, ret = 1;
+
+ /* Copy much of key_load_public's logic here so that we can read
+ * several keys from a single file (e.g. authorized_keys).
+ */
+
+ if (strcmp(filename, "-") != 0) {
+ int save_errno;
+ f = fopen(filename, "r");
+ save_errno = errno;
+ if (!f) {
+ char pubfile[MAXPATHLEN];
+ if (strlcpy(pubfile, filename, sizeof pubfile) <
+ sizeof(pubfile) &&
+ strlcat(pubfile, ".pub", sizeof pubfile) <
+ sizeof(pubfile))
+ f = fopen(pubfile, "r");
+ }
+ errno = save_errno; /* earlier errno is more useful */
+ if (!f) {
+ if (!quiet_open)
+ perror(filename);
+ return -1;
+ }
+ if (verbosity > 0)
+ printf("# %s\n", filename);
+ } else
+ f = stdin;
+ while (read_keyfile_line(f, filename, line, sizeof(line),
+ &linenum) != -1) {
+ int i;
+ char *space;
+ int type;
+ char *end;
+
+ /* Chop trailing newline. */
+ i = strlen(line) - 1;
+ if (line[i] == '\n')
+ line[i] = '\0';
+
+ /* Skip leading whitespace, empty and comment lines. */
+ for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
+ ;
+ if (!*cp || *cp == '\n' || *cp == '#')
+ continue;
+
+ /* Cope with ssh-keyscan output and options in
+ * authorized_keys files.
+ */
+ space = strchr(cp, ' ');
+ if (!space)
+ continue;
+ *space = '\0';
+ type = key_type_from_name(cp);
+ *space = ' ';
+ /* Leading number (RSA1) or valid type (RSA/DSA) indicates
+ * that we have no host name or options to skip.
+ */
+ if ((strtol(cp, &end, 10) == 0 || *end != ' ') &&
+ type == KEY_UNSPEC) {
+ int quoted = 0;
+
+ for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
+ if (*cp == '\\' && cp[1] == '"')
+ cp++; /* Skip both */
+ else if (*cp == '"')
+ quoted = !quoted;
+ }
+ /* Skip remaining whitespace. */
+ for (; *cp == ' ' || *cp == '\t'; cp++)
+ ;
+ if (!*cp)
+ continue;
+ }
+
+ /* Read and process the key itself. */
+ key = key_new(KEY_RSA1);
+ if (key_read(key, &cp) == 1) {
+ while (*cp == ' ' || *cp == '\t')
+ cp++;
+ if (!do_key(filename, linenum,
+ key, *cp ? cp : filename))
+ ret = 0;
+ found = 1;
+ } else {
+ key_free(key);
+ key = key_new(KEY_UNSPEC);
+ if (key_read(key, &cp) == 1) {
+ while (*cp == ' ' || *cp == '\t')
+ cp++;
+ if (!do_key(filename, linenum,
+ key, *cp ? cp : filename))
+ ret = 0;
+ found = 1;
+ }
+ }
+ key_free(key);
+ }
+ if (f != stdin)
+ fclose(f);
+
+ if (!found && filename) {
+ key = key_load_public(filename, &comment);
+ if (key) {
+ if (!do_key(filename, 1, key, comment))
+ ret = 0;
+ found = 1;
+ }
+ if (comment)
+ xfree(comment);
+ }
+
+ return ret;
+}
+
+int
+do_host(int quiet_open)
+{
+ int i;
+ struct stat st;
+ int ret = 1;
+
+ for (i = 0; default_host_files[i]; i++) {
+ if (stat(default_host_files[i], &st) < 0 && errno == ENOENT)
+ continue;
+ if (!do_filename(default_host_files[i], quiet_open))
+ ret = 0;
+ }
+
+ return ret;
+}
+
+int
+do_user(const char *dir)
+{
+ int i;
+ char *file;
+ struct stat st;
+ int ret = 1;
+
+ for (i = 0; default_files[i]; i++) {
+ xasprintf(&file, "%s/%s", dir, default_files[i]);
+ if (stat(file, &st) < 0 && errno == ENOENT) {
+ xfree(file);
+ continue;
+ }
+ if (!do_filename(file, 0))
+ ret = 0;
+ xfree(file);
+ }
+
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ int opt, all_users = 0;
+ int ret = 1;
+ extern int optind;
+
+ /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
+ sanitise_stdfd();
+
+ __progname = ssh_get_progname(argv[0]);
+
+ SSLeay_add_all_algorithms();
+ log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1);
+
+ /* We don't need the RNG ourselves, but symbol references here allow
+ * ld to link us properly.
+ */
+ init_rng();
+ seed_rng();
+
+ while ((opt = getopt(argc, argv, "ahqv")) != -1) {
+ switch (opt) {
+ case 'a':
+ all_users = 1;
+ break;
+ case 'q':
+ verbosity--;
+ break;
+ case 'v':
+ verbosity++;
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+
+ if (all_users) {
+ struct passwd *pw;
+
+ if (!do_host(0))
+ ret = 0;
+
+ while ((pw = getpwent()) != NULL) {
+ if (pw->pw_dir) {
+ temporarily_use_uid(pw);
+ if (!do_user(pw->pw_dir))
+ ret = 0;
+ restore_uid();
+ }
+ }
+ } else if (optind == argc) {
+ struct passwd *pw;
+
+ if (!do_host(1))
+ ret = 0;
+
+ if ((pw = getpwuid(geteuid())) == NULL)
+ fprintf(stderr, "No user found with uid %u\n",
+ (u_int)geteuid());
+ else {
+ if (!do_user(pw->pw_dir))
+ ret = 0;
+ }
+ } else {
+ while (optind < argc)
+ if (!do_filename(argv[optind++], 0))
+ ret = 0;
+ }
+
+ if (verbosity >= 0) {
+ if (some_unknown) {
+ printf("#\n");
+ printf("# The status of some keys on your system is unknown.\n");
+ printf("# You may need to install additional blacklist files.\n");
+ }
+ if (some_compromised) {
+ printf("#\n");
+ printf("# Some keys on your system have been compromised!\n");
+ printf("# You must replace them using ssh-keygen(1).\n");
+ }
+ if (some_unknown || some_compromised) {
+ printf("#\n");
+ printf("# See the ssh-vulnkey(1) manual page for further advice.\n");
+ } else if (some_keys && verbosity > 0) {
+ printf("#\n");
+ printf("# No blacklisted keys!\n");
+ }
+ }
+
+ return ret;
+}
Index: b/ssh.1
===================================================================
--- a/ssh.1
+++ b/ssh.1
@@ -1426,6 +1426,7 @@
.Xr ssh-agent 1 ,
.Xr ssh-keygen 1 ,
.Xr ssh-keyscan 1 ,
+.Xr ssh-vulnkey 1 ,
.Xr tun 4 ,
.Xr hosts.equiv 5 ,
.Xr ssh_config 5 ,
Index: b/ssh.c
===================================================================
--- a/ssh.c
+++ b/ssh.c
@@ -1301,7 +1301,7 @@
static void
load_public_identity_files(void)
{
- char *filename, *cp, thishost[NI_MAXHOST];
+ char *filename, *cp, thishost[NI_MAXHOST], *fp;
char *pwdir = NULL, *pwname = NULL;
int i = 0;
Key *public;
@@ -1358,6 +1358,22 @@
public = key_load_public(filename, NULL);
debug("identity file %s type %d", filename,
public ? public->type : -1);
+ if (public && blacklisted_key(public, &fp) == 1) {
+ if (options.use_blacklisted_keys)
+ logit("Public key %s blacklisted (see "
+ "ssh-vulnkey(1)); continuing anyway", fp);
+ else
+ logit("Public key %s blacklisted (see "
+ "ssh-vulnkey(1)); refusing to send it",
+ fp);
+ xfree(fp);
+ if (!options.use_blacklisted_keys) {
+ key_free(public);
+ xfree(filename);
+ filename = NULL;
+ public = NULL;
+ }
+ }
xfree(options.identity_files[i]);
identity_files[n_ids] = filename;
identity_keys[n_ids] = public;
Index: b/ssh_config.5
===================================================================
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -1051,6 +1051,23 @@
.Dq any .
The default is
.Dq any:any .
+.It Cm UseBlacklistedKeys
+Specifies whether
+.Xr ssh 1
+should use keys recorded in its blacklist of known-compromised keys (see
+.Xr ssh-vulnkey 1 )
+for authentication.
+If
+.Dq yes ,
+then attempts to use compromised keys for authentication will be logged but
+accepted.
+It is strongly recommended that this be used only to install new authorized
+keys on the remote system, and even then only with the utmost care.
+If
+.Dq no ,
+then attempts to use compromised keys for authentication will be prevented.
+The default is
+.Dq no .
.It Cm UsePrivilegedPort
Specifies whether to use a privileged port for outgoing connections.
The argument must be
Index: b/sshconnect2.c
===================================================================
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1418,6 +1418,8 @@
/* list of keys stored in the filesystem */
for (i = 0; i < options.num_identity_files; i++) {
+ if (options.identity_files[i] == NULL)
+ continue;
key = options.identity_keys[i];
if (key && key->type == KEY_RSA1)
continue;
@@ -1510,7 +1512,7 @@
if (id->key && id->key->type != KEY_RSA1) {
debug("Offering public key: %s", id->filename);
sent = send_pubkey_test(authctxt, id);
- } else if (id->key == NULL) {
+ } else if (id->key == NULL && id->filename) {
debug("Trying private key: %s", id->filename);
id->key = load_identity_file(id->filename);
if (id->key != NULL) {
Index: b/sshd.8
===================================================================
--- a/sshd.8
+++ b/sshd.8
@@ -928,6 +928,7 @@
.Xr ssh-agent 1 ,
.Xr ssh-keygen 1 ,
.Xr ssh-keyscan 1 ,
+.Xr ssh-vulnkey 1 ,
.Xr chroot 2 ,
.Xr hosts_access 5 ,
.Xr login.conf 5 ,
Index: b/sshd.c
===================================================================
--- a/sshd.c
+++ b/sshd.c
@@ -1564,6 +1564,11 @@
sensitive_data.host_keys[i] = NULL;
continue;
}
+ if (auth_key_is_revoked(key, 1)) {
+ key_free(key);
+ sensitive_data.host_keys[i] = NULL;
+ continue;
+ }
switch (key->type) {
case KEY_RSA1:
sensitive_data.ssh1_host_key = key;
Index: b/sshd_config.5
===================================================================
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -694,6 +694,20 @@
Specifies whether password authentication is allowed.
The default is
.Dq yes .
+.It Cm PermitBlacklistedKeys
+Specifies whether
+.Xr sshd 8
+should allow keys recorded in its blacklist of known-compromised keys (see
+.Xr ssh-vulnkey 1 ) .
+If
+.Dq yes ,
+then attempts to authenticate with compromised keys will be logged but
+accepted.
+If
+.Dq no ,
+then attempts to authenticate with compromised keys will be rejected.
+The default is
+.Dq no .
.It Cm PermitEmptyPasswords
When password authentication is allowed, it specifies whether the
server allows login to accounts with empty password strings.