--- openssh-3.6.1p2.orig/Makefile.in 2003-04-29 09:12:08 +0000 +++ openssh-3.6.1p2/Makefile.in 2008-05-24 23:10:19 +0000 @@ -60,7 +60,7 @@ INSTALL_SSH_RAND_HELPER=@INSTALL_SSH_RAN TARGETS=ssh$(EXEEXT) sshd$(EXEEXT) ssh-add$(EXEEXT) ssh-keygen$(EXEEXT) ssh-keyscan${EXEEXT} ssh-keysign${EXEEXT} ssh-agent$(EXEEXT) scp$(EXEEXT) ssh-rand-helper${EXEEXT} sftp-server$(EXEEXT) sftp$(EXEEXT) -LIBSSH_OBJS=authfd.o authfile.o bufaux.o buffer.o canohost.o channels.o \ +LIBSSH_OBJS=authfd.o authfile.o blacklist.o bufaux.o buffer.o canohost.o channels.o \ cipher.o compat.o compress.o crc32.o deattack.o fatal.o \ hostfile.o log.o match.o mpaux.o nchan.o packet.o radix.o readpass.o \ rsa.o tildexpand.o ttymodes.o xmalloc.o atomicio.o \ --- openssh-3.6.1p2.orig/auth-rh-rsa.c 2008-05-24 23:08:04 +0000 +++ openssh-3.6.1p2/auth-rh-rsa.c 2008-05-24 23:09:46 +0000 @@ -26,6 +26,7 @@ RCSID("$OpenBSD: auth-rh-rsa.c,v 1.36 20 #include "canohost.h" #include "monitor_wrap.h" +#include "blacklist.h" /* import */ extern ServerOptions options; @@ -40,6 +41,9 @@ auth_rhosts_rsa_key_allowed(struct passw if (!auth_rhosts(pw, cuser)) return 0; + if (blacklisted_key(client_host_key)) + return 0; + host_status = check_key_in_hostfiles(pw, client_host_key, chost, _PATH_SSH_SYSTEM_HOSTFILE, options.ignore_user_known_hosts ? NULL : _PATH_SSH_USER_HOSTFILE); --- openssh-3.6.1p2.orig/auth-rsa.c 2002-06-11 15:47:42 +0000 +++ openssh-3.6.1p2/auth-rsa.c 2008-05-24 23:10:48 +0000 @@ -34,6 +34,7 @@ RCSID("$OpenBSD: auth-rsa.c,v 1.56 2002/ #include "hostfile.h" #include "monitor_wrap.h" #include "ssh.h" +#include "blacklist.h" /* import */ extern ServerOptions options; @@ -250,6 +251,9 @@ auth_rsa_key_allowed(struct passwd *pw, "actual %d vs. announced %d.", file, linenum, BN_num_bits(key->rsa->n), bits); + if (blacklisted_key(key)) + continue; + /* We have found the desired key. */ /* * If our options do not allow this key to be used, --- openssh-3.6.1p2.orig/auth2-hostbased.c 2008-05-24 23:08:04 +0000 +++ openssh-3.6.1p2/auth2-hostbased.c 2008-05-24 23:09:46 +0000 @@ -38,6 +38,7 @@ RCSID("$OpenBSD: auth2-hostbased.c,v 1.2 #include "canohost.h" #include "monitor_wrap.h" #include "pathnames.h" +#include "blacklist.h" /* import */ extern ServerOptions options; @@ -136,6 +137,9 @@ hostbased_key_allowed(struct passwd *pw, HostStatus host_status; int len; + if (blacklisted_key(key)) + return 0; + resolvedname = get_canonical_hostname(options.use_dns); ipaddr = get_remote_ipaddr(); --- openssh-3.6.1p2.orig/auth2-pubkey.c 2002-06-06 20:27:56 +0000 +++ openssh-3.6.1p2/auth2-pubkey.c 2008-05-24 23:11:52 +0000 @@ -40,6 +40,7 @@ RCSID("$OpenBSD: auth2-pubkey.c,v 1.2 20 #include "auth-options.h" #include "canohost.h" #include "monitor_wrap.h" +#include "blacklist.h" /* import */ extern ServerOptions options; @@ -263,6 +264,9 @@ user_key_allowed(struct passwd *pw, Key int success; char *file; + if (blacklisted_key(key)) + return 0; + file = authorized_keys_file(pw); success = user_key_allowed2(pw, key, file); xfree(file); --- openssh-3.6.1p2.orig/blacklist.c 1970-01-01 00:00:00 +0000 +++ openssh-3.6.1p2/blacklist.c 2008-05-26 12:52:39 +0000 @@ -0,0 +1,257 @@ +/* + * Support for RSA/DSA key blacklisting based on partial fingerprints, + * developed under Openwall Project for Owl - http://www.openwall.com/Owl/ + * + * Copyright (c) 2008 Dmitry V. Levin + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The blacklist encoding was designed by Solar Designer and Dmitry V. Levin. + * No intellectual property rights to the encoding scheme are claimed. + * + * This effort was supported by CivicActions - http://www.civicactions.com + * + * The file size to encode 294,903 of 48-bit fingerprints is just 1.3 MB, + * which corresponds to less than 4.5 bytes per fingerprint. + */ + +#include "includes.h" +#include +#include +#include +#include + +#include "atomicio.h" +#include "blacklist.h" +#include "log.h" +#include "pathnames.h" +#include "servconf.h" +#include "xmalloc.h" + +extern ServerOptions options; + +typedef struct +{ + /* format version identifier */ + char version[8]; + /* index size, in bits */ + uint8_t index_size; + /* offset size, in bits */ + uint8_t offset_size; + /* record size, in bits */ + uint8_t record_bits; + /* number of records */ + uint8_t records[3]; + /* offset shift */ + uint8_t shift[2]; + +} __attribute__((packed)) blacklist_header; + +static unsigned +c2u(uint8_t c) +{ + return (c >= 'a') ? (c - 'a' + 10) : (c - '0'); +} + +static blacklist_error_t +validate_blacklist(const char *fname, int fd, unsigned *bytes, + unsigned *records, unsigned *shift) +{ + unsigned expected; + struct stat st; + blacklist_header header; + + if (fstat(fd, &st)) { + error("fstat for blacklist file %s failed: %m", fname); + return BLACKLIST_ERROR_ACCESS; + } + + if (atomicio(read, fd, &header, sizeof(header)) != sizeof(header)) { + error("read blacklist file %s header failed: %m", fname); + return BLACKLIST_ERROR_ACCESS; + } + + if (memcmp(header.version, "SSH-FP", 6)) { + error("blacklist file %s has unrecognized format", fname); + return BLACKLIST_ERROR_FORMAT; + } + + if (header.index_size != 16 || header.offset_size != 16 || + memcmp(header.version, "SSH-FP00", 8)) { + error("blacklist file %s has unsupported format", fname); + return BLACKLIST_ERROR_VERSION; + } + + *bytes = (header.record_bits >> 3) - 2; + *records = + (((header.records[0] << 8) + + header.records[1]) << 8) + header.records[2]; + *shift = (header.shift[0] << 8) + header.shift[1]; + + expected = sizeof(header) + 0x20000 + (*records) * (*bytes); + if (st.st_size != expected) { + error("blacklist file %s size mismatch: " + "expected size %u, found size %lu", + fname, expected, (unsigned long) st.st_size); + return BLACKLIST_ERROR_ACCESS; + } + + return BLACKLIST_ERROR_NONE; +} + +static int +expected_offset(uint16_t index, uint16_t shift, unsigned records) +{ + return ((index * (long long) records) >> 16) - shift; +} + +static int +xlseek(const char *fname, int fd, unsigned seek) +{ + if (lseek(fd, seek, SEEK_SET) != seek) { + error("lseek for blacklist file %s failed: %m", fname); + return BLACKLIST_ERROR_ACCESS; + } + return BLACKLIST_ERROR_NONE; +} + +static blacklist_error_t +check(const char *fname, int fd, const char *s) +{ + unsigned bytes, records, shift; + unsigned num, i, j; + int off_start, off_end; + blacklist_error_t rc; + uint16_t index; + /* max number of bytes stored in record_bits, minus two bytes used for index */ + uint8_t buf[(0xff >> 3) - 2]; + + if ((rc = validate_blacklist(fname, fd, &bytes, &records, &shift))) + return rc; + + index = (((((c2u(s[0]) << 4) | c2u(s[1])) << 4) | + c2u(s[2])) << 4) | c2u(s[3]); + if (xlseek(fname, fd, sizeof(blacklist_header) + index * 2)) + return BLACKLIST_ERROR_ACCESS; + + if (atomicio(read, fd, buf, 4) != 4) { + error("read blacklist file %s offsets failed: %m", fname); + return BLACKLIST_ERROR_ACCESS; + } + + off_start = (buf[0] << 8) + buf[1] + + expected_offset(index, shift, records); + if (off_start < 0 || (unsigned) off_start > records) { + error("blacklist file %s off_start overflow [%d] for index %#x", + fname, off_start, index); + return BLACKLIST_ERROR_ACCESS; + } + if (index < 0xffff) { + off_end = (buf[2] << 8) + buf[3] + + expected_offset(index + 1, shift, records); + if (off_end < off_start || (unsigned) off_end > records) { + error("blacklist file %s off_end overflow [%d] for index %#x", + fname, off_end, index); + return BLACKLIST_ERROR_ACCESS; + } + } else + off_end = records; + + if (xlseek(fname, fd, + sizeof(blacklist_header) + 0x20000 + off_start * bytes)) + return BLACKLIST_ERROR_ACCESS; + + num = off_end - off_start; + for (i = 0; i < num; ++i) { + if (atomicio(read, fd, buf, bytes) != bytes) { + error("read blacklist file %s fingerprints failed: %m", + fname); + return BLACKLIST_ERROR_ACCESS; + } + + for (j = 0; j < bytes; ++j) + if (((c2u(s[4 + j * 2]) << 4) | c2u(s[5 + j * 2])) != + buf[j]) + break; + if (j >= bytes) { + debug("blacklisted fingerprint: %s offset=%u, number=%u", + s, off_start, i); + return BLACKLIST_ERROR_ALL; + } + } + + debug("non-blacklisted fingerprint: %s offset=%u, number=%u", + s, off_start, num); + return BLACKLIST_ERROR_NONE; +} + +static blacklist_error_t +blacklisted_fingerprint(const char *hex) +{ + int fd = -1; + blacklist_error_t rc = BLACKLIST_ERROR_ACCESS; + const char *fname = _PATH_BLACKLIST; + char *s, *p; + + debug("Checking fingerprint %s using blacklist file %s", hex, fname); + + s = xstrdup(hex); + for (p = s; *hex; ++hex) + if (*hex != ':') + *p++ = *hex; + *p = '\0'; + + if (strlen(s) != 32 || strlen(s) != strspn(s, "0123456789abcdef")) { + error("%s: invalid fingerprint", s); + goto out; + } + + if ((fd = open(fname, O_RDONLY)) < 0) { + if (ENOENT == errno) { + rc = BLACKLIST_ERROR_MISSING; + verbose("open blacklist file %s failed: %m", fname); + } else + log("open blacklist file %s failed: %m", fname); + goto out; + } + + rc = check(fname, fd, s); + +out: + close(fd); + xfree(s); + return rc; +} + +int +blacklisted_key(Key *key) +{ + int rc; + char *fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); + + switch ((rc = blacklisted_fingerprint(fp))) { + case BLACKLIST_ERROR_NONE: + break; + case BLACKLIST_ERROR_ALL: + log("%s blacklisted public key %s", + ((options.ignore_blacklist_errors == rc) ? + "Permitted" : "Rejected"), fp); + break; + default: + log("Unable to check blacklist for public key %s", + fp); + } + + xfree(fp); + return (rc > options.ignore_blacklist_errors); +} --- openssh-3.6.1p2.orig/blacklist.h 1970-01-01 00:00:00 +0000 +++ openssh-3.6.1p2/blacklist.h 2008-05-26 12:38:02 +0000 @@ -0,0 +1,37 @@ +/* + * Support for RSA/DSA key blacklisting based on partial fingerprints, + * developed under Openwall Project for Owl - http://www.openwall.com/Owl/ + * + * Copyright (c) 2008 Dmitry V. Levin + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef BLACKLIST_H_ +#define BLACKLIST_H_ + +#include "key.h" + +int blacklisted_key(Key *key); + +typedef enum +{ + BLACKLIST_ERROR_NONE = 0, + BLACKLIST_ERROR_MISSING, + BLACKLIST_ERROR_VERSION, + BLACKLIST_ERROR_FORMAT, + BLACKLIST_ERROR_ACCESS, + BLACKLIST_ERROR_ALL +} blacklist_error_t; + +#endif /* BLACKLIST_H_ */ --- openssh-3.6.1p2.orig/pathnames.h 2002-06-06 19:57:34 +0000 +++ openssh-3.6.1p2/pathnames.h 2008-05-24 23:09:46 +0000 @@ -43,6 +43,8 @@ /* Backwards compatibility */ #define _PATH_DH_PRIMES SSHDIR "/primes" +#define _PATH_BLACKLIST SSHDIR "/blacklist" + #ifndef _PATH_SSH_PROGRAM #define _PATH_SSH_PROGRAM "/usr/bin/ssh" #endif --- openssh-3.6.1p2.orig/servconf.c 2008-05-24 23:08:04 +0000 +++ openssh-3.6.1p2/servconf.c 2008-05-26 12:54:04 +0000 @@ -39,6 +39,7 @@ RCSID("$OpenBSD: servconf.c,v 1.116 2003 #include "cipher.h" #include "kex.h" #include "mac.h" +#include "blacklist.h" static void add_listen_addr(ServerOptions *, char *, u_short); static void add_one_listen_addr(ServerOptions *, char *, u_short); @@ -100,6 +101,7 @@ initialize_server_options(ServerOptions options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->challenge_response_authentication = -1; + options->ignore_blacklist_errors = -1; options->permit_empty_passwd = -1; options->permit_user_env = -1; options->use_login = -1; @@ -222,6 +224,8 @@ fill_default_server_options(ServerOption options->kbd_interactive_authentication = 0; if (options->challenge_response_authentication == -1) options->challenge_response_authentication = 1; + if (options->ignore_blacklist_errors == -1) + options->ignore_blacklist_errors = BLACKLIST_ERROR_VERSION; if (options->permit_empty_passwd == -1) options->permit_empty_passwd = 0; if (options->permit_user_env == -1) @@ -293,7 +297,7 @@ typedef enum { sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, sPrintMotd, sPrintLastLog, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, - sStrictModes, sEmptyPasswd, sKeepAlives, + sStrictModes, sIgnoreBlacklistErrors, sEmptyPasswd, sKeepAlives, sPermitUserEnvironment, sUseLogin, sAllowTcpForwarding, sCompression, sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups, sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile, @@ -356,6 +360,7 @@ static struct { { "x11uselocalhost", sX11UseLocalhost }, { "xauthlocation", sXAuthLocation }, { "strictmodes", sStrictModes }, + { "ignoreblacklisterrors", sIgnoreBlacklistErrors }, { "permitemptypasswords", sEmptyPasswd }, { "permituserenvironment", sPermitUserEnvironment }, { "uselogin", sUseLogin }, @@ -714,6 +719,31 @@ parse_flag: intptr = &options->keepalives; goto parse_flag; + case sIgnoreBlacklistErrors: + arg = strdelim(&cp); + if (!arg || *arg == '\0') + fatal("%s line %d: missing none/missing/version/format/access/all argument.", + filename, linenum); + value = 0; /* silence compiler */ + if (strcmp(arg, "none") == 0) + value = BLACKLIST_ERROR_NONE; + else if (strcmp(arg, "missing") == 0) + value = BLACKLIST_ERROR_MISSING; + else if (strcmp(arg, "version") == 0) + value = BLACKLIST_ERROR_VERSION; + else if (strcmp(arg, "format") == 0) + value = BLACKLIST_ERROR_FORMAT; + else if (strcmp(arg, "access") == 0) + value = BLACKLIST_ERROR_ACCESS; + else if (strcmp(arg, "all") == 0) + value = BLACKLIST_ERROR_ALL; + else + fatal("%s line %d: Bad none/missing/version/format/access/all argument: %s", + filename, linenum, arg); + if (options->ignore_blacklist_errors == -1) + options->ignore_blacklist_errors = value; + break; + case sEmptyPasswd: intptr = &options->permit_empty_passwd; goto parse_flag; --- openssh-3.6.1p2.orig/servconf.h 2008-05-24 23:08:04 +0000 +++ openssh-3.6.1p2/servconf.h 2008-05-26 12:53:09 +0000 @@ -95,6 +95,7 @@ typedef struct { * authentication. */ int kbd_interactive_authentication; /* If true, permit */ int challenge_response_authentication; + int ignore_blacklist_errors; /* none/missing/version/format/access/all */ int permit_empty_passwd; /* If false, do not permit empty * passwords. */ int permit_user_env; /* If true, read ~/.ssh/environment */ --- openssh-3.6.1p2.orig/sshd.c 2008-05-24 23:08:04 +0000 +++ openssh-3.6.1p2/sshd.c 2008-05-24 23:13:03 +0000 @@ -84,6 +84,7 @@ RCSID("$OpenBSD: sshd.c,v 1.263 2003/02/ #include "monitor.h" #include "monitor_wrap.h" #include "monitor_fdpass.h" +#include "blacklist.h" #ifdef LIBWRAP #include @@ -1006,6 +1007,11 @@ main(int ac, char **av) sensitive_data.host_keys[i] = NULL; continue; } + if (blacklisted_key(key)) { + sensitive_data.host_keys[i] = NULL; + key_free(key); + continue; + } switch (key->type) { case KEY_RSA1: sensitive_data.ssh1_host_key = key; --- openssh-3.6.1p2.orig/sshd_config.5 2008-05-24 23:08:04 +0000 +++ openssh-3.6.1p2/sshd_config.5 2008-05-26 12:55:33 +0000 @@ -434,6 +434,39 @@ is enabled. Specifies whether password authentication is allowed. The default is .Dq yes . +.It Cm IgnoreBlacklistErrors +Specifies whether +.Xr sshd 8 +should allow keys recorded in its blacklist of known-compromised keys. +If +.Dq all , +then attempts to authenticate with compromised keys will be logged +but accepted. +If +.Dq access , +then attempts to authenticate with compromised keys will be rejected, +but blacklist file access errors will be ignored. +If +.Dq format , +then attempts to authenticate with compromised keys will be rejected, but +blacklist file access errors due to missing blacklist file or blacklist +file unrecognized format will be ignored. +If +.Dq version , +then attempts to authenticate with compromised keys will be rejected, but +blacklist file access errors due to missing blacklist file or blacklist +file format version mismatch will be ignored. +If +.Dq missing , +then attempts to authenticate with compromised keys will be rejected, +but blacklist file access errors due to missing blacklist file will +be ignored. +If +.Dq none , +then attempts to authenticate with compromised keys, or in case of +any blacklist file access error, will be rejected. +The default is +.Dq version . .It Cm PermitEmptyPasswords When password authentication is allowed, it specifies whether the server allows login to accounts with empty password strings.