Add blacklist feature for weak Debian-generated ssh keys.
authorSimon Schubert <corecode@dragonflybsd.org>
Fri, 16 May 2008 10:49:26 +0000 (10:49 +0000)
committerSimon Schubert <corecode@dragonflybsd.org>
Fri, 16 May 2008 10:49:26 +0000 (10:49 +0000)
Default is to disallow weak keys.

Obtained-from:  Ubuntu Linux

crypto/openssh-5/README.compromised-keys [new file with mode: 0644]
crypto/openssh-5/ssh-vulnkey.1 [new file with mode: 0644]
crypto/openssh-5/ssh-vulnkey.c [new file with mode: 0644]

diff --git a/crypto/openssh-5/README.compromised-keys b/crypto/openssh-5/README.compromised-keys
new file mode 100644 (file)
index 0000000..c3e6cbb
--- /dev/null
@@ -0,0 +1,134 @@
+The following instructions relate to CVE-2008-0166. They were prepared by
+Matt Zimmerman, assisted by Colin Watson.
+
+== What Happened ==
+
+A weakness has been discovered in the random number generator used by OpenSSL
+on Debian and Ubuntu systems.  As a result of this weakness, certain encryption
+keys are generated much more frequently than they should be, such that an
+attacker could guess the key through a brute-force attack given minimal
+knowledge of the system.
+
+We consider this an extremely serious vulnerability, and urge all users to act
+immediately to secure their systems.
+
+== Who is affected ==
+
+Systems which are running any of the following releases:
+
+ * Debian 4.0 (etch)
+ * Ubuntu 7.04 (Feisty)
+ * Ubuntu 7.10 (Gutsy)
+ * Ubuntu 8.04 LTS (Hardy)
+ * Ubuntu "Intrepid Ibex" (development): libssl <= 0.9.8g-8
+
+and have openssh-server installed or have been used to create an OpenSSH key or
+X.509 (SSL) certificate.
+
+All OpenSSH and X.509 keys generated on such systems must be considered
+untrustworthy, regardless of the system on which they are used, even after the
+update has been applied.
+
+This includes the automatically generated host keys used by OpenSSH, which are
+the basis for its server spoofing and man-in-the-middle protection.
+
+The specific package versions affected are:
+
+ * Debian 4.0: libssl <= 0.9.8c-4etch3
+ * Ubuntu 7.04: libssl <= 0.9.8c-4ubuntu0.2
+ * Ubuntu 7.10: libssl <= 0.9.8e-5ubuntu3.1
+ * Ubuntu 8.04: libssl <= 0.9.8g-4ubuntu3
+
+== What to do if you are affected ==
+
+OpenSSH:
+
+1. Install the security updates
+
+   Once the update is applied, weak user keys will be automatically rejected
+   where possible (though they cannot be detected in all cases).  If you are
+   using such keys for user authentication, they will immediately stop working
+   and will need to be replaced (see step 3).
+   
+   OpenSSH host keys can be automatically regenerated when the OpenSSH security
+   update is applied.  The update will prompt for confirmation before taking
+   this step.
+   
+2. Update OpenSSH known_hosts files
+
+   The regeneration of host keys will cause a warning to be displayed when
+   connecting to the system using SSH until the host key is updated in the
+   known_hosts file.  The warning will look like this:
+
+   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+   @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
+   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+   IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
+   Someone could be eavesdropping on you right now (man-in-the-middle attack)!
+   It is also possible that the RSA host key has just been changed.
+
+   In this case, the host key has simply been changed, and you should update
+   the relevant known_hosts file as indicated in the error message.
+
+3. Check all OpenSSH user keys
+
+   The safest course of action is to regenerate all OpenSSH user keys,
+   except where it can be established to a high degree of certainty that the
+   key was generated on an unaffected system.
+
+   Check whether your key is affected by running the ssh-vulnkey tool, included
+   in the security update.  By default, ssh-vulnkey will check the standard
+   location for user keys (~/.ssh/id_rsa, ~/.ssh/id_dsa and ~/.ssh/identity),
+   your authorized_keys file (~/.ssh/authorized_keys and
+   ~/.ssh/authorized_keys2), and the system's host keys
+   (/etc/ssh/ssh_host_dsa_key and /etc/ssh/ssh_host_rsa_key).
+
+   To check all your own keys, assuming they are in the standard
+   locations (~/.ssh/id_rsa, ~/.ssh/id_dsa, or ~/.ssh/identity):
+
+     ssh-vulnkey
+
+   To check all keys on your system:
+
+     sudo ssh-vulnkey -a
+
+   To check a key in a non-standard location:
+
+     ssh-vulnkey /path/to/key
+
+   If ssh-vulnkey says "No blacklist file", then it has no information
+   about whether that key is affected.
+
+4. Regenerate any affected user keys
+
+   OpenSSH keys used for user authentication must be manually regenerated,
+   including those which may have since been transferred to a different system
+   after being generated.
+
+   New keys can be generated using ssh-keygen, e.g.:
+
+   $ ssh-keygen 
+   Generating public/private rsa key pair.
+   Enter file in which to save the key (/home/user/.ssh/id_rsa): 
+   Enter passphrase (empty for no passphrase): 
+   Enter same passphrase again: 
+   Your identification has been saved in /home/user/.ssh/id_rsa.
+   Your public key has been saved in /home/user/.ssh/id_rsa.pub.
+   The key fingerprint is:
+   00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 user@host
+
+5. Update authorized_keys files (if necessary)
+
+   Once the user keys have been regenerated, the relevant public keys must
+   be propagated to any authorized_keys files on remote systems.  Be sure to
+   delete the affected key.
+
+OpenSSL:
+
+1. Install the security update
+
+2. Create new certificates to replace any server or client certificates in use
+   on the system
+
+3. If certificates have been generated for use on other systems, they must be
+   found and replaced as well.
diff --git a/crypto/openssh-5/ssh-vulnkey.1 b/crypto/openssh-5/ssh-vulnkey.1
new file mode 100644 (file)
index 0000000..41de104
--- /dev/null
@@ -0,0 +1,187 @@
+.\" 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
+.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
+.Nm
+will exit zero if any of the given keys were in the compromised list,
+otherwise non-zero.
+.Pp
+Unless the
+.Cm PermitBlacklistedKeys
+option is used,
+.Xr sshd 8
+will reject attempts to authenticate with keys in the compromised list.
+.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.
+.El
+.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 /etc/ssh/blacklist. Ns Ar TYPE Ns Pa - Ns Ar LENGTH
+If present, lists the blacklisted keys of type
+.Ar TYPE
+.Pf ( Dq RSA1 ,
+.Dq RSA ,
+or
+.Dq DSA )
+and bit length
+.Ar LENGTH .
+The format of this file is described above.
+.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.
diff --git a/crypto/openssh-5/ssh-vulnkey.c b/crypto/openssh-5/ssh-vulnkey.c
new file mode 100644 (file)
index 0000000..3297c43
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+ * 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 <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 "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 quiet = 0;
+
+static void
+usage(void)
+{
+       fprintf(stderr, "usage: %s [-aq] [file ...]\n", __progname);
+       fprintf(stderr, "Options:\n");
+       fprintf(stderr, "  -a          Check keys of all users.\n");
+       fprintf(stderr, "  -q          Quiet mode.\n");
+       exit(1);
+}
+
+void
+describe_key(const char *msg, const Key *key, const char *comment)
+{
+       char *fp;
+
+       fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
+       if (!quiet)
+               printf("%s: %u %s %s\n", msg, key_size(key), fp, comment);
+       xfree(fp);
+}
+
+int
+do_key(const Key *key, const char *comment)
+{
+       char *blacklist_file;
+       struct stat st;
+       int ret = 1;
+
+       blacklist_file = blacklist_filename(key);
+       if (stat(blacklist_file, &st) < 0)
+               describe_key("Unknown (no blacklist information)",
+                   key, comment);
+       else if (blacklisted_key(key)) {
+               describe_key("COMPROMISED", key, comment);
+               ret = 0;
+       } else
+               describe_key("Not blacklisted", key, comment);
+       xfree(blacklist_file);
+
+       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) {
+               f = fopen(filename, "r");
+               if (!f) {
+                       char pubfile[MAXPATHLEN];
+                       if (strlcpy(pubfile, filename, sizeof pubfile) <
+                           sizeof(pubfile) &&
+                           strlcat(pubfile, ".pub", sizeof pubfile) <
+                           sizeof(pubfile))
+                               f = fopen(pubfile, "r");
+               }
+               if (!f) {
+                       if (!quiet_open)
+                               perror(filename);
+                       return -1;
+               }
+       } else
+               f = stdin;
+       while (read_keyfile_line(f, filename, line, sizeof(line),
+                   &linenum) != -1) {
+               int i;
+               char *space;
+               int type;
+
+               /* 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 (atoi(cp) == 0 && 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(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(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(key, comment))
+                               ret = 0;
+                       found = 1;
+               }
+               if (comment)
+                       xfree(comment);
+       }
+
+       return ret;
+}
+
+int
+do_host(void)
+{
+       int i;
+       struct stat st;
+       int ret = 1;
+
+       for (i = 0; default_host_files[i]; i++) {
+               if (stat(default_host_files[i], &st) < 0)
+                       continue;
+               if (!do_filename(default_host_files[i], 1))
+                       ret = 0;
+       }
+
+       return ret;
+}
+
+int
+do_user(const char *dir)
+{
+       int i;
+       char buf[MAXPATHLEN];
+       struct stat st;
+       int ret = 1;
+
+       for (i = 0; default_files[i]; i++) {
+               snprintf(buf, sizeof(buf), "%s/%s", dir, default_files[i]);
+               if (stat(buf, &st) < 0)
+                       continue;
+               if (!do_filename(buf, 0))
+                       ret = 0;
+       }
+
+       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, "ahq")) != -1) {
+               switch (opt) {
+               case 'a':
+                       all_users = 1;
+                       break;
+               case 'q':
+                       quiet = 1;
+                       break;
+               case 'h':
+               default:
+                       usage();
+               }
+       }
+
+       if (all_users) {
+               struct passwd *pw;
+
+               if (!do_host())
+                       ret = 0;
+
+               while ((pw = getpwent()) != NULL) {
+                       if (pw->pw_dir) {
+                               if (!do_user(pw->pw_dir))
+                                       ret = 0;
+                       }
+               }
+       } else if (optind == argc) {
+               struct passwd *pw;
+
+               if (!do_host())
+                       ret = 0;
+
+               if ((pw = getpwuid(getuid())) == NULL)
+                       fprintf(stderr, "No user found with uid %u\n",
+                           (u_int)getuid());
+               else {
+                       if (!do_user(pw->pw_dir))
+                               ret = 0;
+               }
+       } else {
+               while (optind < argc)
+                       if (!do_filename(argv[optind++], 0))
+                               ret = 0;
+       }
+
+       return ret;
+}