Openwall GNU/*/Linux - a small security-enhanced Linux distro for servers
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Tue, 27 May 2008 19:44:35 +0400
From: Solar Designer <solar@...nwall.com>
To: oss-security@...ts.openwall.com
Subject: Re: OpenSSH key blacklisting

On Sat, May 17, 2008 at 04:46:30PM +0200, Robert Buchholz wrote:
> Do you have a patch to propose, implementing your idea?

Dmitry V. Levin and I have completed design of the encoding scheme, and
Dmitry implemented it.  Now we have:

blacklist-encode.c - the encoder program;
blacklist-check.c - the "checker" program, used for testing only;
openssh-3.6.1p2-owl-blacklist.diff - the patch to sshd.

The patch is against an older version that we still have in Owl (with
lots of other patches), but it is trivial to forward-port.  In fact, I
expect that Dmitry will port it to the newer version in ALT Linux's
distributions very soon (if not already).  Dmitry - please announce your
forward-port in here when you have it.

Dmitry has done fairly extensive testing, but we would not mind others
in the community doing more tests and reporting back in here.

We also have openssh-blacklist-0.3-1.bin.bz2, which is used as a
"source" in our OpenSSH package.  It was generated from
ftp://ftp.debian.org/debian/pool/main/o/openssh-blacklist/openssh-blacklist_0.3.tar.gz
with:

	cat [DR]SA-{1024,2048}.[bl]e{32,64} | ./blacklist-encode 6 > openssh-blacklist-0.3-1.bin
	bzip2 !$

That is, it contains 48-bit partial fingerprints for 1024-bit and
2048-bit RSA and 1024-bit DSA keys for PID range 1 to 32767 (a total of
almost 300k keys).  The installed file size is just 1.3 MB, which
corresponds to less than 4.5 bytes per fingerprint, and the .bz2 (and
.rpm) is just 1.2 MB.  Lookups are very quick, and only three small
portions of the file are read per lookup, for a total of under 100
bytes of data to read (as far as sshd is concerned).

Neither the code nor the file format is specific to 48-bit partial
fingerprints; it is possible to use larger ones by supplying something
other than "6" (the size in bytes) on blacklist-encode's command-line.
There is a safety check against even smaller values in
blacklist-encode.c's main(), although if you really know what you're
doing, you can go for 40-bit as well, bringing file size for the same
keys to under 1 MB.

Our latest source code may be found here:

	http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/openssh/

(along with lots of other patches to OpenSSH).

The pre-encoded blacklist file may be found here:

	ftp://ftp.ru.openwall.com/pub/Owl/pool/sources/openssh/

(and on other mirrors).

I've attached current revisions of the source files and patch mentioned
above.  This is to encourage community review and comments, and to
enable easy quoting of relevant context (please do not overquote).

Please note that this effort was/is supported by CivicActions.  It will
enable us to receive funding for and get involved in more community
activities in the future if you give due credit to both Openwall and
CivicActions (especially with website links) when you reuse this stuff.

Thanks in advance for any feedback.

Alexander

/*
 * The blacklist encoder 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 <ldv at cvs.openwall.com>
 *
 * 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.
 */

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif

#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <error.h>
#include <limits.h>

static void *
xmalloc(size_t size)
{
	void   *r = malloc(size);

	if (!r)
		error(EXIT_FAILURE, errno, "malloc: allocating %lu bytes",
		      (unsigned long) size);
	return r;
}

static void *
xcalloc(size_t nmemb, size_t size)
{
	void   *r = calloc(nmemb, size);

	if (!r)
		error(EXIT_FAILURE, errno, "calloc: allocating %lu*%lu bytes",
		      (unsigned long) nmemb, (unsigned long) size);
	return r;
}

static void *
xrealloc(void *ptr, size_t nmemb, size_t elem_size)
{
	if (nmemb && ULONG_MAX / nmemb < elem_size)
		error(EXIT_FAILURE, 0, "realloc: nmemb*size > ULONG_MAX");

	size_t  size = nmemb * elem_size;
	void   *r = realloc(ptr, size);

	if (!r)
		error(EXIT_FAILURE, errno,
		      "realloc: allocating %lu*%lu bytes",
		      (unsigned long) nmemb, (unsigned long) elem_size);
	return r;
}

static char *
xstrdup(const char *s)
{
	size_t  len = strlen(s);
	char   *r = xmalloc(len + 1);

	memcpy(r, s, len + 1);
	return r;
}

static unsigned
c2u(uint8_t c)
{
	return (c >= 'a') ? (c - 'a' + 10) : (c - '0');
}

static char **records = NULL;
static unsigned records_count = 0;

static int
comparator(const void *p1, const void *p2)
{
	return strcmp(*(char *const *) p1, *(char *const *) p2);
}

static void
read_stream(FILE *fp, unsigned bytes)
{
	char   *line = NULL;
	unsigned size = 0, allocated = 0, len = bytes * 2;
	int     n;

	while ((n = getline(&line, &size, fp)) >= 0)
	{
		if (n > 0 && line[n - 1] == '\n')
			line[--n] = '\0';
		if (n < len || strspn(line, "0123456789abcdef") < n)
			continue;	/* ignore short or invalid lines */
		line[len] = '\0';

		if (!records)
			records = xcalloc(allocated = 1024, sizeof(*records));
		if (records_count >= allocated)
			records = xrealloc(records, allocated *= 2,
					   sizeof(*records));
		records[records_count++] = xstrdup(line);
	}
	free(line);
	records = xrealloc(records, records_count, sizeof(*records));
	if (records_count >= (1U << 24))
		error(EXIT_FAILURE, 0, "too many records: %u", records_count);

	qsort(records, records_count, sizeof(*records), comparator);
}

static void
print_uint8(FILE *fp, uint8_t v)
{
	fprintf(fp, "%c", v);
}

static void
print_uint16(FILE *fp, uint16_t v)
{
	fprintf(fp, "%c%c", v >> 8, v & 0xff);
}

static void
print_uint24(FILE *fp, uint32_t v)
{
	fprintf(fp, "%c%c%c", (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff);
}

int
main(int ac, const char **av)
{
	unsigned count, i, record_bytes, first_index = 0, prev_index = 0;
	int     min_offset, max_offset;
	int    *offsets;

	if (ac < 2)
		error(EXIT_FAILURE, 0, "insufficient arguments");
	if (ac > 2)
		error(EXIT_FAILURE, 0, "too many arguments");
	record_bytes = atoi(av[1]);
	if (record_bytes < 6 || record_bytes > 16)
		error(EXIT_FAILURE, 0, "fingerprint size out of bounds");

	read_stream(stdin, record_bytes);

	/* initialize global records offset table */
	offsets = xcalloc(65536, sizeof(*offsets));
	for (count = 0; count < records_count; ++count, prev_index = i)
	{
		const char *r = records[count];

		i = (((((c2u(r[0]) << 4) + c2u(r[1])) << 4) +
		      c2u(r[2])) << 4) + c2u(r[3]);
		if (count == 0)
			first_index = i;
		else if (i == prev_index)
			continue;
		offsets[i] = count;
	}

	/* set offsets for indices without records */
	if (offsets[65536 - 1] == 0)
		offsets[65536 - 1] = records_count;
	for (i = 65536 - 2; i > first_index; --i)
		if (offsets[i] == 0)
			offsets[i] = offsets[i + 1];

	/* make global records offset table relative to
	   expected position assuming uniform distribution. */
	for (i = 0, min_offset = 0, max_offset = 0; i < 65536; ++i)
	{
		offsets[i] -= (i * (unsigned long long) records_count) >> 16;
		if (offsets[i] < min_offset)
			min_offset = offsets[i];
		if (offsets[i] > max_offset)
			max_offset = offsets[i];
	}
	min_offset = -min_offset;
	if (min_offset < 0)
		error(EXIT_FAILURE, 0,
		      "invalid offset shift: %d", min_offset);
	for (i = 0; i < 65536; ++i)
	{
		offsets[i] += min_offset;
		if (offsets[i] < 0 || offsets[i] >= 65536)
			error(EXIT_FAILURE, 0,
			      "offset overflow for index %#x: %d",
			      i, offsets[i]);
	}
	max_offset += min_offset;

	/* Header, 16 bytes */

	/* format version identifier */
	printf("SSH-FP00");
	/* index size, in bits */
	print_uint8(stdout, 16);
	/* offset size, in bits */
	print_uint8(stdout, 16);
	/* record size, in bits */
	print_uint8(stdout, record_bytes * 8);
	/* records count */
	print_uint24(stdout, records_count);
	/* offset shift */
	print_uint16(stdout, min_offset);
	fprintf(stderr, "records=%u, offset shift=%d, max offset=%d\n",
		records_count, min_offset, max_offset);

	/* Index, 65536 * 2 bytes */
	for (i = 0; i < 65536; ++i)
		print_uint16(stdout, offsets[i]);

	/* Fingerprints, records_count * (record_bytes-2) bytes */
	for (count = 0; count < records_count; ++count)
	{
		const char *r = records[count] + 4;

		for (i = 0; i < record_bytes - 2; ++i)
			print_uint8(stdout,
				    c2u(r[i * 2]) * 16 + c2u(r[i * 2 + 1]));
	}

	if (fclose(stdout))
		error(EXIT_FAILURE, errno, "stdout");
	return 0;
}

/*
 * The blacklist checker 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 <ldv at cvs.openwall.com>
 *
 * 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.
 */

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif

#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

static unsigned
c2u(uint8_t c)
{
	return (c >= 'a') ? (c - 'a' + 10) : (c - '0');
}

static ssize_t
read_retry(int fd, void *buf, size_t records)
{
	return TEMP_FAILURE_RETRY(read(fd, buf, records));
}

static ssize_t
read_loop(int fd, char *buffer, size_t records)
{
	ssize_t offset = 0;

	while (records > 0)
	{
		ssize_t block = read_retry(fd, &buffer[offset], records);

		if (block <= 0)
			return offset ? : block;
		offset += block;
		records -= block;
	}
	return offset;
}

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];

} fp_header;

static int
open_blacklist(const char *fname, unsigned *bytes, unsigned *records, unsigned *shift)
{
	int     fd;
	unsigned expected;
	struct stat st;
	fp_header header;

	if ((fd = open(fname, O_RDONLY)) < 0)
	{
		error(0, errno, "open: %s", fname);
		return -1;
	}
	if (fstat(fd, &st))
	{
		error(0, errno, "fstat: %s", fname);
		close(fd);
		return -1;
	}

	if (read_loop(fd, (char *) &header, sizeof header) != sizeof header)
	{
		error(0, errno, "read header: %s", fname);
		close(fd);
		return -1;
	}

	if (header.index_size != 16 || header.offset_size != 16)
	{
		error(0, 0, "%s: unsupported file format", fname);
		close(fd);
		return -1;
	}

	*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(0, 0, "%s: expected file size %u, found file size %lu",
		      fname, expected, (unsigned long) st.st_size);
		close(fd);
		return -1;
	}

	return fd;
}

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(0, errno, "lseek: %s", fname);
		return -1;
	}
	return 0;
}

static int
check(const char *fname, const char *s)
{
	int     fd;
	unsigned bytes, records, shift;
	unsigned i, j;
	int     offset, off_end;
	uint16_t index;
	/* max number of bytes stored in record_bits, minus two bytes used for index */
	uint8_t buf[(0xff >> 3) - 2];

	if (strlen(s) != 32 || strlen(s) != strspn(s, "0123456789abcdef"))
	{
		fprintf(stderr, "invalid fingerprint: %s\n", s);
		return 1;
	}

	fd = open_blacklist(fname, &bytes, &records, &shift);
	if (fd < 0)
		return 1;

	index = (((((c2u(s[0]) << 4) | c2u(s[1])) << 4) |
		c2u(s[2])) << 4) | c2u(s[3]);
	if (xlseek(fname, fd, sizeof(fp_header) + index * 2))
	{
		close(fd);
		return 1;
	}

	if (read_loop(fd, (char *) buf, 4) != 4)
	{
		error(0, errno, "read offsets: %s", fname);
		close(fd);
		return 1;
	}

	offset = (buf[0] << 8) + buf[1] +
		expected_offset(index, shift, records);
	if (offset < 0 || offset > records)
	{
		error(0, 0, "index=%#x, offset overflow: %d",
		      index, offset);
		close(fd);
		return 1;
	}
	if (index < 0xffff)
	{
		off_end = (buf[2] << 8) + buf[3] +
			expected_offset(index + 1, shift, records);
		if (off_end < offset || off_end > records)
		{
			error(0, 0, "index=%#x, offset overflow: %d",
			      index, off_end);
			close(fd);
			return 1;
		}
	} else
		off_end = records;

	if (xlseek(fname, fd, sizeof(fp_header) + 0x20000 + offset * bytes))
	{
		close(fd);
		return 1;
	}

	for (i = 0; i < off_end - offset; ++i)
	{
		if (read_loop(fd, (char *) buf, bytes) != bytes)
		{
			error(0, errno, "read fingerprints: %s", fname);
			close(fd);
			return 1;
		}

		for (j = 0; j < bytes; ++j)
			if (((c2u(s[4 + j * 2]) << 4) | c2u(s[5 + j * 2]))
			    != buf[j])
				break;
		if (j >= bytes)
		{
			fprintf(stderr, "BAD: %s offset=%u, offcnt=%u, i=%u\n",
				s, offset, off_end - offset, i);
			close(fd);
			return 1;
		}
	}

	fprintf(stderr, "OK: %s offset=%u, offcnt=%u\n",
		s, offset, off_end - offset);
	close(fd);
	return 0;
}

int
main(int ac, const char **av)
{
	int i, rc = 0;

	if (ac < 3)
		error(EXIT_FAILURE, 0, "insufficient arguments");
	for (i = 2; i < ac; ++i)
		rc |= check(av[1], av[i]);
	return rc;
}

--- 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=@...TALL_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 <ldv at cvs.openwall.com>
+ *
+ * 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 <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#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 <ldv at cvs.openwall.com>
+ *
+ * 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 <tcpd.h>
@@ -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.

Powered by blists - more mailing lists

Your e-mail address:

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Powered by Openwall GNU/*/Linux - Powered by OpenVZ