1 /* $OpenBSD: hostfile.c,v 1.55 2014/01/31 16:39:19 tedu Exp $ */ 2 /* 3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 5 * All rights reserved 6 * Functions for manipulating the known hosts files. 7 * 8 * As far as I am concerned, the code I have written for this software 9 * can be used freely for any purpose. Any derived versions of this 10 * software must be clearly marked as such, and if the derived work is 11 * incompatible with the protocol description in the RFC file, it must be 12 * called by a name other than "ssh" or "Secure Shell". 13 * 14 * 15 * Copyright (c) 1999, 2000 Markus Friedl. All rights reserved. 16 * Copyright (c) 1999 Niels Provos. All rights reserved. 17 * 18 * Redistribution and use in source and binary forms, with or without 19 * modification, are permitted provided that the following conditions 20 * are met: 21 * 1. Redistributions of source code must retain the above copyright 22 * notice, this list of conditions and the following disclaimer. 23 * 2. Redistributions in binary form must reproduce the above copyright 24 * notice, this list of conditions and the following disclaimer in the 25 * documentation and/or other materials provided with the distribution. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 28 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 29 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 30 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 31 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 32 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 36 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 #include <sys/types.h> 40 41 #include <netinet/in.h> 42 43 #include <resolv.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 48 #include "xmalloc.h" 49 #include "match.h" 50 #include "key.h" 51 #include "hostfile.h" 52 #include "log.h" 53 #include "misc.h" 54 #include "digest.h" 55 #include "hmac.h" 56 57 struct hostkeys { 58 struct hostkey_entry *entries; 59 u_int num_entries; 60 }; 61 62 static int 63 extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len) 64 { 65 char *p, *b64salt; 66 u_int b64len; 67 int ret; 68 69 if (l < sizeof(HASH_MAGIC) - 1) { 70 debug2("extract_salt: string too short"); 71 return (-1); 72 } 73 if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) { 74 debug2("extract_salt: invalid magic identifier"); 75 return (-1); 76 } 77 s += sizeof(HASH_MAGIC) - 1; 78 l -= sizeof(HASH_MAGIC) - 1; 79 if ((p = memchr(s, HASH_DELIM, l)) == NULL) { 80 debug2("extract_salt: missing salt termination character"); 81 return (-1); 82 } 83 84 b64len = p - s; 85 /* Sanity check */ 86 if (b64len == 0 || b64len > 1024) { 87 debug2("extract_salt: bad encoded salt length %u", b64len); 88 return (-1); 89 } 90 b64salt = xmalloc(1 + b64len); 91 memcpy(b64salt, s, b64len); 92 b64salt[b64len] = '\0'; 93 94 ret = __b64_pton(b64salt, salt, salt_len); 95 free(b64salt); 96 if (ret == -1) { 97 debug2("extract_salt: salt decode error"); 98 return (-1); 99 } 100 if (ret != (int)ssh_hmac_bytes(SSH_DIGEST_SHA1)) { 101 debug2("extract_salt: expected salt len %zd, got %d", 102 ssh_hmac_bytes(SSH_DIGEST_SHA1), ret); 103 return (-1); 104 } 105 106 return (0); 107 } 108 109 char * 110 host_hash(const char *host, const char *name_from_hostfile, u_int src_len) 111 { 112 struct ssh_hmac_ctx *ctx; 113 u_char salt[256], result[256]; 114 char uu_salt[512], uu_result[512]; 115 static char encoded[1024]; 116 u_int i, len; 117 118 len = ssh_digest_bytes(SSH_DIGEST_SHA1); 119 120 if (name_from_hostfile == NULL) { 121 /* Create new salt */ 122 for (i = 0; i < len; i++) 123 salt[i] = arc4random(); 124 } else { 125 /* Extract salt from known host entry */ 126 if (extract_salt(name_from_hostfile, src_len, salt, 127 sizeof(salt)) == -1) 128 return (NULL); 129 } 130 131 if ((ctx = ssh_hmac_start(SSH_DIGEST_SHA1)) == NULL || 132 ssh_hmac_init(ctx, salt, len) < 0 || 133 ssh_hmac_update(ctx, host, strlen(host)) < 0 || 134 ssh_hmac_final(ctx, result, sizeof(result))) 135 fatal("%s: ssh_hmac failed", __func__); 136 ssh_hmac_free(ctx); 137 138 if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 || 139 __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1) 140 fatal("%s: __b64_ntop failed", __func__); 141 142 snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt, 143 HASH_DELIM, uu_result); 144 145 return (encoded); 146 } 147 148 /* 149 * Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the 150 * pointer over the key. Skips any whitespace at the beginning and at end. 151 */ 152 153 int 154 hostfile_read_key(char **cpp, int *bitsp, Key *ret) 155 { 156 char *cp; 157 158 /* Skip leading whitespace. */ 159 for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) 160 ; 161 162 if (key_read(ret, &cp) != 1) 163 return 0; 164 165 /* Skip trailing whitespace. */ 166 for (; *cp == ' ' || *cp == '\t'; cp++) 167 ; 168 169 /* Return results. */ 170 *cpp = cp; 171 if (bitsp != NULL) { 172 if ((*bitsp = key_size(ret)) <= 0) 173 return 0; 174 } 175 return 1; 176 } 177 178 static int 179 hostfile_check_key(int bits, const Key *key, const char *host, 180 const char *filename, u_long linenum) 181 { 182 if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) 183 return 1; 184 if (bits != BN_num_bits(key->rsa->n)) { 185 logit("Warning: %s, line %lu: keysize mismatch for host %s: " 186 "actual %d vs. announced %d.", 187 filename, linenum, host, BN_num_bits(key->rsa->n), bits); 188 logit("Warning: replace %d with %d in %s, line %lu.", 189 bits, BN_num_bits(key->rsa->n), filename, linenum); 190 } 191 return 1; 192 } 193 194 static HostkeyMarker 195 check_markers(char **cpp) 196 { 197 char marker[32], *sp, *cp = *cpp; 198 int ret = MRK_NONE; 199 200 while (*cp == '@') { 201 /* Only one marker is allowed */ 202 if (ret != MRK_NONE) 203 return MRK_ERROR; 204 /* Markers are terminated by whitespace */ 205 if ((sp = strchr(cp, ' ')) == NULL && 206 (sp = strchr(cp, '\t')) == NULL) 207 return MRK_ERROR; 208 /* Extract marker for comparison */ 209 if (sp <= cp + 1 || sp >= cp + sizeof(marker)) 210 return MRK_ERROR; 211 memcpy(marker, cp, sp - cp); 212 marker[sp - cp] = '\0'; 213 if (strcmp(marker, CA_MARKER) == 0) 214 ret = MRK_CA; 215 else if (strcmp(marker, REVOKE_MARKER) == 0) 216 ret = MRK_REVOKE; 217 else 218 return MRK_ERROR; 219 220 /* Skip past marker and any whitespace that follows it */ 221 cp = sp; 222 for (; *cp == ' ' || *cp == '\t'; cp++) 223 ; 224 } 225 *cpp = cp; 226 return ret; 227 } 228 229 struct hostkeys * 230 init_hostkeys(void) 231 { 232 struct hostkeys *ret = xcalloc(1, sizeof(*ret)); 233 234 ret->entries = NULL; 235 return ret; 236 } 237 238 void 239 load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) 240 { 241 FILE *f; 242 char line[8192]; 243 u_long linenum = 0, num_loaded = 0; 244 char *cp, *cp2, *hashed_host; 245 HostkeyMarker marker; 246 Key *key; 247 int kbits; 248 249 if ((f = fopen(path, "r")) == NULL) 250 return; 251 debug3("%s: loading entries for host \"%.100s\" from file \"%s\"", 252 __func__, host, path); 253 while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) { 254 cp = line; 255 256 /* Skip any leading whitespace, comments and empty lines. */ 257 for (; *cp == ' ' || *cp == '\t'; cp++) 258 ; 259 if (!*cp || *cp == '#' || *cp == '\n') 260 continue; 261 262 if ((marker = check_markers(&cp)) == MRK_ERROR) { 263 verbose("%s: invalid marker at %s:%lu", 264 __func__, path, linenum); 265 continue; 266 } 267 268 /* Find the end of the host name portion. */ 269 for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) 270 ; 271 272 /* Check if the host name matches. */ 273 if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) { 274 if (*cp != HASH_DELIM) 275 continue; 276 hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); 277 if (hashed_host == NULL) { 278 debug("Invalid hashed host line %lu of %s", 279 linenum, path); 280 continue; 281 } 282 if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) 283 continue; 284 } 285 286 /* Got a match. Skip host name. */ 287 cp = cp2; 288 289 /* 290 * Extract the key from the line. This will skip any leading 291 * whitespace. Ignore badly formatted lines. 292 */ 293 key = key_new(KEY_UNSPEC); 294 if (!hostfile_read_key(&cp, &kbits, key)) { 295 key_free(key); 296 key = key_new(KEY_RSA1); 297 if (!hostfile_read_key(&cp, &kbits, key)) { 298 key_free(key); 299 continue; 300 } 301 } 302 if (!hostfile_check_key(kbits, key, host, path, linenum)) 303 continue; 304 305 debug3("%s: found %skey type %s in file %s:%lu", __func__, 306 marker == MRK_NONE ? "" : 307 (marker == MRK_CA ? "ca " : "revoked "), 308 key_type(key), path, linenum); 309 hostkeys->entries = xrealloc(hostkeys->entries, 310 hostkeys->num_entries + 1, sizeof(*hostkeys->entries)); 311 hostkeys->entries[hostkeys->num_entries].host = xstrdup(host); 312 hostkeys->entries[hostkeys->num_entries].file = xstrdup(path); 313 hostkeys->entries[hostkeys->num_entries].line = linenum; 314 hostkeys->entries[hostkeys->num_entries].key = key; 315 hostkeys->entries[hostkeys->num_entries].marker = marker; 316 hostkeys->num_entries++; 317 num_loaded++; 318 } 319 debug3("%s: loaded %lu keys", __func__, num_loaded); 320 fclose(f); 321 return; 322 } 323 324 void 325 free_hostkeys(struct hostkeys *hostkeys) 326 { 327 u_int i; 328 329 for (i = 0; i < hostkeys->num_entries; i++) { 330 free(hostkeys->entries[i].host); 331 free(hostkeys->entries[i].file); 332 key_free(hostkeys->entries[i].key); 333 explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries)); 334 } 335 free(hostkeys->entries); 336 explicit_bzero(hostkeys, sizeof(*hostkeys)); 337 free(hostkeys); 338 } 339 340 static int 341 check_key_not_revoked(struct hostkeys *hostkeys, Key *k) 342 { 343 int is_cert = key_is_cert(k); 344 u_int i; 345 346 for (i = 0; i < hostkeys->num_entries; i++) { 347 if (hostkeys->entries[i].marker != MRK_REVOKE) 348 continue; 349 if (key_equal_public(k, hostkeys->entries[i].key)) 350 return -1; 351 if (is_cert && 352 key_equal_public(k->cert->signature_key, 353 hostkeys->entries[i].key)) 354 return -1; 355 } 356 return 0; 357 } 358 359 /* 360 * Match keys against a specified key, or look one up by key type. 361 * 362 * If looking for a keytype (key == NULL) and one is found then return 363 * HOST_FOUND, otherwise HOST_NEW. 364 * 365 * If looking for a key (key != NULL): 366 * 1. If the key is a cert and a matching CA is found, return HOST_OK 367 * 2. If the key is not a cert and a matching key is found, return HOST_OK 368 * 3. If no key matches but a key with a different type is found, then 369 * return HOST_CHANGED 370 * 4. If no matching keys are found, then return HOST_NEW. 371 * 372 * Finally, check any found key is not revoked. 373 */ 374 static HostStatus 375 check_hostkeys_by_key_or_type(struct hostkeys *hostkeys, 376 Key *k, int keytype, const struct hostkey_entry **found) 377 { 378 u_int i; 379 HostStatus end_return = HOST_NEW; 380 int want_cert = key_is_cert(k); 381 HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE; 382 int proto = (k ? k->type : keytype) == KEY_RSA1 ? 1 : 2; 383 384 if (found != NULL) 385 *found = NULL; 386 387 for (i = 0; i < hostkeys->num_entries; i++) { 388 if (proto == 1 && hostkeys->entries[i].key->type != KEY_RSA1) 389 continue; 390 if (proto == 2 && hostkeys->entries[i].key->type == KEY_RSA1) 391 continue; 392 if (hostkeys->entries[i].marker != want_marker) 393 continue; 394 if (k == NULL) { 395 if (hostkeys->entries[i].key->type != keytype) 396 continue; 397 end_return = HOST_FOUND; 398 if (found != NULL) 399 *found = hostkeys->entries + i; 400 k = hostkeys->entries[i].key; 401 break; 402 } 403 if (want_cert) { 404 if (key_equal_public(k->cert->signature_key, 405 hostkeys->entries[i].key)) { 406 /* A matching CA exists */ 407 end_return = HOST_OK; 408 if (found != NULL) 409 *found = hostkeys->entries + i; 410 break; 411 } 412 } else { 413 if (key_equal(k, hostkeys->entries[i].key)) { 414 end_return = HOST_OK; 415 if (found != NULL) 416 *found = hostkeys->entries + i; 417 break; 418 } 419 /* A non-maching key exists */ 420 end_return = HOST_CHANGED; 421 if (found != NULL) 422 *found = hostkeys->entries + i; 423 } 424 } 425 if (check_key_not_revoked(hostkeys, k) != 0) { 426 end_return = HOST_REVOKED; 427 if (found != NULL) 428 *found = NULL; 429 } 430 return end_return; 431 } 432 433 HostStatus 434 check_key_in_hostkeys(struct hostkeys *hostkeys, Key *key, 435 const struct hostkey_entry **found) 436 { 437 if (key == NULL) 438 fatal("no key to look up"); 439 return check_hostkeys_by_key_or_type(hostkeys, key, 0, found); 440 } 441 442 int 443 lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, 444 const struct hostkey_entry **found) 445 { 446 return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, 447 found) == HOST_FOUND); 448 } 449 450 /* 451 * Appends an entry to the host file. Returns false if the entry could not 452 * be appended. 453 */ 454 455 int 456 add_host_to_hostfile(const char *filename, const char *host, const Key *key, 457 int store_hash) 458 { 459 FILE *f; 460 int success = 0; 461 char *hashed_host = NULL; 462 463 if (key == NULL) 464 return 1; /* XXX ? */ 465 f = fopen(filename, "a"); 466 if (!f) 467 return 0; 468 469 if (store_hash) { 470 if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { 471 error("add_host_to_hostfile: host_hash failed"); 472 fclose(f); 473 return 0; 474 } 475 } 476 fprintf(f, "%s ", store_hash ? hashed_host : host); 477 478 if (key_write(key, f)) { 479 success = 1; 480 } else { 481 error("add_host_to_hostfile: saving key in %s failed", filename); 482 } 483 fprintf(f, "\n"); 484 fclose(f); 485 return success; 486 } 487