|
Line 0
Link Here
|
|
|
1 |
/* |
| 2 |
* Support for RSA/DSA key blacklisting based on partial fingerprints, |
| 3 |
* developed under Openwall Project for Owl - http://www.openwall.com/Owl/ |
| 4 |
* |
| 5 |
* Copyright (c) 2008 Dmitry V. Levin <ldv at cvs.openwall.com> |
| 6 |
* |
| 7 |
* Permission to use, copy, modify, and distribute this software for any |
| 8 |
* purpose with or without fee is hereby granted, provided that the above |
| 9 |
* copyright notice and this permission notice appear in all copies. |
| 10 |
* |
| 11 |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 12 |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 13 |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 14 |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 15 |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 16 |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 17 |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 18 |
* |
| 19 |
* The blacklist encoding was designed by Solar Designer and Dmitry V. Levin. |
| 20 |
* No intellectual property rights to the encoding scheme are claimed. |
| 21 |
* |
| 22 |
* This effort was supported by CivicActions - http://www.civicactions.com |
| 23 |
* |
| 24 |
* The file size to encode 294,903 of 48-bit fingerprints is just 1.3 MB, |
| 25 |
* which corresponds to less than 4.5 bytes per fingerprint. |
| 26 |
*/ |
| 27 |
|
| 28 |
#include "includes.h" |
| 29 |
#include <string.h> |
| 30 |
#include <unistd.h> |
| 31 |
#include <errno.h> |
| 32 |
#include <fcntl.h> |
| 33 |
|
| 34 |
#include "atomicio.h" |
| 35 |
#include "blacklist.h" |
| 36 |
#include "canohost.h" |
| 37 |
#include "log.h" |
| 38 |
#include "pathnames.h" |
| 39 |
#include "servconf.h" |
| 40 |
#include "xmalloc.h" |
| 41 |
|
| 42 |
extern ServerOptions options; |
| 43 |
|
| 44 |
typedef struct |
| 45 |
{ |
| 46 |
/* format version identifier */ |
| 47 |
char version[8]; |
| 48 |
/* index size, in bits */ |
| 49 |
uint8_t index_size; |
| 50 |
/* offset size, in bits */ |
| 51 |
uint8_t offset_size; |
| 52 |
/* record size, in bits */ |
| 53 |
uint8_t record_bits; |
| 54 |
/* number of records */ |
| 55 |
uint8_t records[3]; |
| 56 |
/* offset shift */ |
| 57 |
uint8_t shift[2]; |
| 58 |
|
| 59 |
} __attribute__((packed)) blacklist_header; |
| 60 |
|
| 61 |
static unsigned |
| 62 |
c2u(uint8_t c) |
| 63 |
{ |
| 64 |
return (c >= 'a') ? (c - 'a' + 10) : (c - '0'); |
| 65 |
} |
| 66 |
|
| 67 |
static blacklist_error_t |
| 68 |
validate_blacklist(const char *fname, int fd, unsigned *bytes, |
| 69 |
unsigned *records, unsigned *shift) |
| 70 |
{ |
| 71 |
unsigned expected; |
| 72 |
struct stat st; |
| 73 |
blacklist_header header; |
| 74 |
|
| 75 |
if (fstat(fd, &st)) { |
| 76 |
error("fstat for blacklist file %s failed: %m", fname); |
| 77 |
return BLACKLIST_ERROR_ACCESS; |
| 78 |
} |
| 79 |
|
| 80 |
if (atomicio(read, fd, &header, sizeof(header)) != sizeof(header)) { |
| 81 |
error("read blacklist file %s header failed: %m", fname); |
| 82 |
return BLACKLIST_ERROR_ACCESS; |
| 83 |
} |
| 84 |
|
| 85 |
if (memcmp(header.version, "SSH-FP", 6)) { |
| 86 |
error("blacklist file %s has unrecognized format", fname); |
| 87 |
return BLACKLIST_ERROR_FORMAT; |
| 88 |
} |
| 89 |
|
| 90 |
if (header.index_size != 16 || header.offset_size != 16 || |
| 91 |
memcmp(header.version, "SSH-FP00", 8)) { |
| 92 |
error("blacklist file %s has unsupported format", fname); |
| 93 |
return BLACKLIST_ERROR_VERSION; |
| 94 |
} |
| 95 |
|
| 96 |
*bytes = (header.record_bits >> 3) - 2; |
| 97 |
*records = |
| 98 |
(((header.records[0] << 8) + |
| 99 |
header.records[1]) << 8) + header.records[2]; |
| 100 |
*shift = (header.shift[0] << 8) + header.shift[1]; |
| 101 |
|
| 102 |
expected = sizeof(header) + 0x20000 + (*records) * (*bytes); |
| 103 |
if (st.st_size != expected) { |
| 104 |
error("blacklist file %s size mismatch: " |
| 105 |
"expected size %u, found size %lu", |
| 106 |
fname, expected, (unsigned long) st.st_size); |
| 107 |
return BLACKLIST_ERROR_ACCESS; |
| 108 |
} |
| 109 |
|
| 110 |
return BLACKLIST_ERROR_NONE; |
| 111 |
} |
| 112 |
|
| 113 |
static int |
| 114 |
expected_offset(uint16_t index, uint16_t shift, unsigned records) |
| 115 |
{ |
| 116 |
return ((index * (long long) records) >> 16) - shift; |
| 117 |
} |
| 118 |
|
| 119 |
static int |
| 120 |
xlseek(const char *fname, int fd, unsigned seek) |
| 121 |
{ |
| 122 |
if (lseek(fd, seek, SEEK_SET) != seek) { |
| 123 |
error("lseek for blacklist file %s failed: %m", fname); |
| 124 |
return BLACKLIST_ERROR_ACCESS; |
| 125 |
} |
| 126 |
return BLACKLIST_ERROR_NONE; |
| 127 |
} |
| 128 |
|
| 129 |
static blacklist_error_t |
| 130 |
check(const char *fname, int fd, const char *s) |
| 131 |
{ |
| 132 |
unsigned bytes, records, shift; |
| 133 |
unsigned num, i, j; |
| 134 |
int off_start, off_end; |
| 135 |
blacklist_error_t rc; |
| 136 |
uint16_t index; |
| 137 |
/* max number of bytes stored in record_bits, minus two bytes used for index */ |
| 138 |
uint8_t buf[(0xff >> 3) - 2]; |
| 139 |
|
| 140 |
if ((rc = validate_blacklist(fname, fd, &bytes, &records, &shift))) |
| 141 |
return rc; |
| 142 |
|
| 143 |
index = (((((c2u(s[0]) << 4) | c2u(s[1])) << 4) | |
| 144 |
c2u(s[2])) << 4) | c2u(s[3]); |
| 145 |
if (xlseek(fname, fd, sizeof(blacklist_header) + index * 2)) |
| 146 |
return BLACKLIST_ERROR_ACCESS; |
| 147 |
|
| 148 |
if (atomicio(read, fd, buf, 4) != 4) { |
| 149 |
error("read blacklist file %s offsets failed: %m", fname); |
| 150 |
return BLACKLIST_ERROR_ACCESS; |
| 151 |
} |
| 152 |
|
| 153 |
off_start = (buf[0] << 8) + buf[1] + |
| 154 |
expected_offset(index, shift, records); |
| 155 |
if (off_start < 0 || (unsigned) off_start > records) { |
| 156 |
error("blacklist file %s off_start overflow [%d] for index %#x", |
| 157 |
fname, off_start, index); |
| 158 |
return BLACKLIST_ERROR_ACCESS; |
| 159 |
} |
| 160 |
if (index < 0xffff) { |
| 161 |
off_end = (buf[2] << 8) + buf[3] + |
| 162 |
expected_offset(index + 1, shift, records); |
| 163 |
if (off_end < off_start || (unsigned) off_end > records) { |
| 164 |
error("blacklist file %s off_end overflow [%d] for index %#x", |
| 165 |
fname, off_end, index); |
| 166 |
return BLACKLIST_ERROR_ACCESS; |
| 167 |
} |
| 168 |
} else |
| 169 |
off_end = records; |
| 170 |
|
| 171 |
if (xlseek(fname, fd, |
| 172 |
sizeof(blacklist_header) + 0x20000 + off_start * bytes)) |
| 173 |
return BLACKLIST_ERROR_ACCESS; |
| 174 |
|
| 175 |
num = off_end - off_start; |
| 176 |
for (i = 0; i < num; ++i) { |
| 177 |
if (atomicio(read, fd, buf, bytes) != bytes) { |
| 178 |
error("read blacklist file %s fingerprints failed: %m", |
| 179 |
fname); |
| 180 |
return BLACKLIST_ERROR_ACCESS; |
| 181 |
} |
| 182 |
|
| 183 |
for (j = 0; j < bytes; ++j) |
| 184 |
if (((c2u(s[4 + j * 2]) << 4) | c2u(s[5 + j * 2])) != |
| 185 |
buf[j]) |
| 186 |
break; |
| 187 |
if (j >= bytes) { |
| 188 |
debug("blacklisted fingerprint: %s offset=%u, number=%u", |
| 189 |
s, off_start, i); |
| 190 |
return BLACKLIST_ERROR_ALL; |
| 191 |
} |
| 192 |
} |
| 193 |
|
| 194 |
debug("non-blacklisted fingerprint: %s offset=%u, number=%u", |
| 195 |
s, off_start, num); |
| 196 |
return BLACKLIST_ERROR_NONE; |
| 197 |
} |
| 198 |
|
| 199 |
static blacklist_error_t |
| 200 |
blacklisted_fingerprint(const char *hex) |
| 201 |
{ |
| 202 |
int fd = -1; |
| 203 |
blacklist_error_t rc = BLACKLIST_ERROR_ACCESS; |
| 204 |
const char *fname = _PATH_BLACKLIST; |
| 205 |
char *s, *p; |
| 206 |
|
| 207 |
debug("Checking fingerprint %s using blacklist file %s", hex, fname); |
| 208 |
|
| 209 |
s = xstrdup(hex); |
| 210 |
for (p = s; *hex; ++hex) |
| 211 |
if (*hex != ':') |
| 212 |
*p++ = *hex; |
| 213 |
*p = '\0'; |
| 214 |
|
| 215 |
if (strlen(s) != 32 || strlen(s) != strspn(s, "0123456789abcdef")) { |
| 216 |
error("%s: invalid fingerprint", s); |
| 217 |
goto out; |
| 218 |
} |
| 219 |
|
| 220 |
if ((fd = open(fname, O_RDONLY)) < 0) { |
| 221 |
if (ENOENT == errno) { |
| 222 |
rc = BLACKLIST_ERROR_MISSING; |
| 223 |
verbose("open blacklist file %s failed: %m", fname); |
| 224 |
} else |
| 225 |
log("open blacklist file %s failed: %m", fname); |
| 226 |
goto out; |
| 227 |
} |
| 228 |
|
| 229 |
rc = check(fname, fd, s); |
| 230 |
|
| 231 |
out: |
| 232 |
close(fd); |
| 233 |
xfree(s); |
| 234 |
return rc; |
| 235 |
} |
| 236 |
|
| 237 |
int |
| 238 |
blacklisted_key(Key *key, int hostkey) |
| 239 |
{ |
| 240 |
int rc; |
| 241 |
const char *text; |
| 242 |
char *fp = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX); |
| 243 |
|
| 244 |
switch ((rc = blacklisted_fingerprint(fp))) { |
| 245 |
case BLACKLIST_ERROR_NONE: |
| 246 |
break; |
| 247 |
case BLACKLIST_ERROR_ALL: |
| 248 |
text = (options.ignore_blacklist_errors == rc) ? |
| 249 |
"Permitted" : "Rejected"; |
| 250 |
if (hostkey) |
| 251 |
log("%s blacklisted host key %s", text, fp); |
| 252 |
else |
| 253 |
log("%s blacklisted public key %s from %.100s", |
| 254 |
text, fp, get_remote_ipaddr()); |
| 255 |
break; |
| 256 |
default: |
| 257 |
if (hostkey) |
| 258 |
log("Unable to check blacklist for host key %s", |
| 259 |
fp); |
| 260 |
else |
| 261 |
log("Unable to check blacklist for public key %s from %.100s", |
| 262 |
fp, get_remote_ipaddr()); |
| 263 |
} |
| 264 |
|
| 265 |
xfree(fp); |
| 266 |
return (rc > options.ignore_blacklist_errors); |
| 267 |
} |