10Sstevel@tonic-gate /* 20Sstevel@tonic-gate * Author: Tatu Ylonen <ylo@cs.hut.fi> 30Sstevel@tonic-gate * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 40Sstevel@tonic-gate * All rights reserved 50Sstevel@tonic-gate * Functions for manipulating the known hosts files. 60Sstevel@tonic-gate * 70Sstevel@tonic-gate * As far as I am concerned, the code I have written for this software 80Sstevel@tonic-gate * can be used freely for any purpose. Any derived versions of this 90Sstevel@tonic-gate * software must be clearly marked as such, and if the derived work is 100Sstevel@tonic-gate * incompatible with the protocol description in the RFC file, it must be 110Sstevel@tonic-gate * called by a name other than "ssh" or "Secure Shell". 120Sstevel@tonic-gate * 130Sstevel@tonic-gate * 140Sstevel@tonic-gate * Copyright (c) 1999, 2000 Markus Friedl. All rights reserved. 150Sstevel@tonic-gate * Copyright (c) 1999 Niels Provos. All rights reserved. 160Sstevel@tonic-gate * 170Sstevel@tonic-gate * Redistribution and use in source and binary forms, with or without 180Sstevel@tonic-gate * modification, are permitted provided that the following conditions 190Sstevel@tonic-gate * are met: 200Sstevel@tonic-gate * 1. Redistributions of source code must retain the above copyright 210Sstevel@tonic-gate * notice, this list of conditions and the following disclaimer. 220Sstevel@tonic-gate * 2. Redistributions in binary form must reproduce the above copyright 230Sstevel@tonic-gate * notice, this list of conditions and the following disclaimer in the 240Sstevel@tonic-gate * documentation and/or other materials provided with the distribution. 250Sstevel@tonic-gate * 260Sstevel@tonic-gate * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 270Sstevel@tonic-gate * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 280Sstevel@tonic-gate * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 290Sstevel@tonic-gate * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 300Sstevel@tonic-gate * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 310Sstevel@tonic-gate * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 320Sstevel@tonic-gate * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 330Sstevel@tonic-gate * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 340Sstevel@tonic-gate * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 350Sstevel@tonic-gate * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 360Sstevel@tonic-gate */ 370Sstevel@tonic-gate 38*5243Sjp161948 /* $OpenBSD: hostfile.c,v 1.45 2006/08/03 03:34:42 deraadt Exp $ */ 390Sstevel@tonic-gate 400Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 410Sstevel@tonic-gate 42*5243Sjp161948 #include "includes.h" 43*5243Sjp161948 44*5243Sjp161948 #include <openssl/hmac.h> 45*5243Sjp161948 #include <openssl/sha.h> 46*5243Sjp161948 470Sstevel@tonic-gate #include "packet.h" 48*5243Sjp161948 #include "xmalloc.h" 490Sstevel@tonic-gate #include "match.h" 500Sstevel@tonic-gate #include "key.h" 510Sstevel@tonic-gate #include "hostfile.h" 520Sstevel@tonic-gate #include "log.h" 530Sstevel@tonic-gate 540Sstevel@tonic-gate /* 55*5243Sjp161948 * Format of a hashed hostname is <MAGIC><SALT>|<HASHED_HOSTNAME>. <MAGIC> is 56*5243Sjp161948 * "|1|". As in non-hashed hostnames this whole string is then followed by a 57*5243Sjp161948 * space, a key type and the key (which is out of scope of this function). 58*5243Sjp161948 * 59*5243Sjp161948 * Example what can be in 's': 60*5243Sjp161948 * 61*5243Sjp161948 * |1|t17NtsuXSLwP0H0eYdd8vJeNakM=|9XFVPh3jZUrfY6YCWn8Ua5eGZtA= 62*5243Sjp161948 */ 63*5243Sjp161948 static int 64*5243Sjp161948 extract_salt(const char *s, u_int l, char *salt, size_t salt_len) 65*5243Sjp161948 { 66*5243Sjp161948 char *p; 67*5243Sjp161948 u_char *b64salt; 68*5243Sjp161948 u_int b64len; 69*5243Sjp161948 int ret; 70*5243Sjp161948 71*5243Sjp161948 if (l < sizeof(HASH_MAGIC) - 1) { 72*5243Sjp161948 debug2("extract_salt: string too short"); 73*5243Sjp161948 return (-1); 74*5243Sjp161948 } 75*5243Sjp161948 if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) { 76*5243Sjp161948 debug2("extract_salt: invalid magic identifier"); 77*5243Sjp161948 return (-1); 78*5243Sjp161948 } 79*5243Sjp161948 s += sizeof(HASH_MAGIC) - 1; 80*5243Sjp161948 l -= sizeof(HASH_MAGIC) - 1; 81*5243Sjp161948 if ((p = memchr(s, HASH_DELIM, l)) == NULL) { 82*5243Sjp161948 debug2("extract_salt: missing salt termination character"); 83*5243Sjp161948 return (-1); 84*5243Sjp161948 } 85*5243Sjp161948 86*5243Sjp161948 b64len = p - s; 87*5243Sjp161948 /* Sanity check */ 88*5243Sjp161948 if (b64len == 0 || b64len > 1024) { 89*5243Sjp161948 debug2("extract_salt: bad encoded salt length %u", b64len); 90*5243Sjp161948 return (-1); 91*5243Sjp161948 } 92*5243Sjp161948 b64salt = xmalloc(1 + b64len); 93*5243Sjp161948 memcpy(b64salt, s, b64len); 94*5243Sjp161948 b64salt[b64len] = '\0'; 95*5243Sjp161948 96*5243Sjp161948 ret = __b64_pton(b64salt, (u_char *) salt, salt_len); 97*5243Sjp161948 xfree(b64salt); 98*5243Sjp161948 if (ret == -1) { 99*5243Sjp161948 debug2("extract_salt: salt decode error"); 100*5243Sjp161948 return (-1); 101*5243Sjp161948 } 102*5243Sjp161948 if (ret != SHA_DIGEST_LENGTH) { 103*5243Sjp161948 debug2("extract_salt: expected salt len %d, got %d", 104*5243Sjp161948 SHA_DIGEST_LENGTH, ret); 105*5243Sjp161948 return (-1); 106*5243Sjp161948 } 107*5243Sjp161948 108*5243Sjp161948 return (0); 109*5243Sjp161948 } 110*5243Sjp161948 111*5243Sjp161948 char * 112*5243Sjp161948 host_hash(const char *host, const char *name_from_hostfile, u_int src_len) 113*5243Sjp161948 { 114*5243Sjp161948 const EVP_MD *md = EVP_sha1(); 115*5243Sjp161948 HMAC_CTX mac_ctx; 116*5243Sjp161948 char salt[256], result[256], uu_salt[512], uu_result[512]; 117*5243Sjp161948 static char encoded[1024]; 118*5243Sjp161948 u_int i, len; 119*5243Sjp161948 120*5243Sjp161948 len = EVP_MD_size(md); 121*5243Sjp161948 122*5243Sjp161948 if (name_from_hostfile == NULL) { 123*5243Sjp161948 /* Create new salt */ 124*5243Sjp161948 for (i = 0; i < len; i++) 125*5243Sjp161948 salt[i] = arc4random(); 126*5243Sjp161948 } else { 127*5243Sjp161948 /* Extract salt from known host entry */ 128*5243Sjp161948 if (extract_salt(name_from_hostfile, src_len, salt, 129*5243Sjp161948 sizeof(salt)) == -1) 130*5243Sjp161948 return (NULL); 131*5243Sjp161948 } 132*5243Sjp161948 133*5243Sjp161948 HMAC_Init(&mac_ctx, salt, len, md); 134*5243Sjp161948 HMAC_Update(&mac_ctx, (u_char *) host, strlen(host)); 135*5243Sjp161948 HMAC_Final(&mac_ctx, (u_char *) result, NULL); 136*5243Sjp161948 HMAC_cleanup(&mac_ctx); 137*5243Sjp161948 138*5243Sjp161948 if (__b64_ntop((u_char *) salt, len, uu_salt, sizeof(uu_salt)) == -1 || 139*5243Sjp161948 __b64_ntop((u_char *) result, len, uu_result, sizeof(uu_result)) == -1) 140*5243Sjp161948 fatal("host_hash: __b64_ntop failed"); 141*5243Sjp161948 142*5243Sjp161948 snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt, 143*5243Sjp161948 HASH_DELIM, uu_result); 144*5243Sjp161948 145*5243Sjp161948 return (encoded); 146*5243Sjp161948 } 147*5243Sjp161948 148*5243Sjp161948 /* 1490Sstevel@tonic-gate * Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the 1500Sstevel@tonic-gate * pointer over the key. Skips any whitespace at the beginning and at end. 1510Sstevel@tonic-gate */ 1520Sstevel@tonic-gate 1530Sstevel@tonic-gate int 1540Sstevel@tonic-gate hostfile_read_key(char **cpp, u_int *bitsp, Key *ret) 1550Sstevel@tonic-gate { 1560Sstevel@tonic-gate char *cp; 1570Sstevel@tonic-gate 1580Sstevel@tonic-gate /* Skip leading whitespace. */ 1590Sstevel@tonic-gate for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) 1600Sstevel@tonic-gate ; 1610Sstevel@tonic-gate 1620Sstevel@tonic-gate if (key_read(ret, &cp) != 1) 1630Sstevel@tonic-gate return 0; 1640Sstevel@tonic-gate 1650Sstevel@tonic-gate /* Skip trailing whitespace. */ 1660Sstevel@tonic-gate for (; *cp == ' ' || *cp == '\t'; cp++) 1670Sstevel@tonic-gate ; 1680Sstevel@tonic-gate 1690Sstevel@tonic-gate /* Return results. */ 1700Sstevel@tonic-gate *cpp = cp; 1710Sstevel@tonic-gate *bitsp = key_size(ret); 1720Sstevel@tonic-gate return 1; 1730Sstevel@tonic-gate } 1740Sstevel@tonic-gate 1750Sstevel@tonic-gate static int 176*5243Sjp161948 hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum) 1770Sstevel@tonic-gate { 1780Sstevel@tonic-gate if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) 1790Sstevel@tonic-gate return 1; 1800Sstevel@tonic-gate if (bits != BN_num_bits(key->rsa->n)) { 1810Sstevel@tonic-gate log("Warning: %s, line %d: keysize mismatch for host %s: " 1820Sstevel@tonic-gate "actual %d vs. announced %d.", 1830Sstevel@tonic-gate filename, linenum, host, BN_num_bits(key->rsa->n), bits); 1840Sstevel@tonic-gate log("Warning: replace %d with %d in %s, line %d.", 1850Sstevel@tonic-gate bits, BN_num_bits(key->rsa->n), filename, linenum); 1860Sstevel@tonic-gate } 1870Sstevel@tonic-gate return 1; 1880Sstevel@tonic-gate } 1890Sstevel@tonic-gate 1900Sstevel@tonic-gate /* 1910Sstevel@tonic-gate * Checks whether the given host (which must be in all lowercase) is already 1920Sstevel@tonic-gate * in the list of our known hosts. Returns HOST_OK if the host is known and 1930Sstevel@tonic-gate * has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED 1940Sstevel@tonic-gate * if the host is known but used to have a different host key. 1950Sstevel@tonic-gate * 1960Sstevel@tonic-gate * If no 'key' has been specified and a key of type 'keytype' is known 1970Sstevel@tonic-gate * for the specified host, then HOST_FOUND is returned. 1980Sstevel@tonic-gate */ 1990Sstevel@tonic-gate 2000Sstevel@tonic-gate static HostStatus 2010Sstevel@tonic-gate check_host_in_hostfile_by_key_or_type(const char *filename, 202*5243Sjp161948 const char *host, const Key *key, int keytype, Key *found, int *numret) 2030Sstevel@tonic-gate { 2040Sstevel@tonic-gate FILE *f; 2050Sstevel@tonic-gate char line[8192]; 2060Sstevel@tonic-gate int linenum = 0; 2070Sstevel@tonic-gate u_int kbits; 208*5243Sjp161948 char *cp, *cp2, *hashed_host; 2090Sstevel@tonic-gate HostStatus end_return; 2100Sstevel@tonic-gate 2110Sstevel@tonic-gate debug3("check_host_in_hostfile: filename %s", filename); 2120Sstevel@tonic-gate 2130Sstevel@tonic-gate /* Open the file containing the list of known hosts. */ 2140Sstevel@tonic-gate f = fopen(filename, "r"); 2150Sstevel@tonic-gate if (!f) 2160Sstevel@tonic-gate return HOST_NEW; 2170Sstevel@tonic-gate 2180Sstevel@tonic-gate /* 2190Sstevel@tonic-gate * Return value when the loop terminates. This is set to 2200Sstevel@tonic-gate * HOST_CHANGED if we have seen a different key for the host and have 2210Sstevel@tonic-gate * not found the proper one. 2220Sstevel@tonic-gate */ 2230Sstevel@tonic-gate end_return = HOST_NEW; 2240Sstevel@tonic-gate 2250Sstevel@tonic-gate /* Go through the file. */ 2260Sstevel@tonic-gate while (fgets(line, sizeof(line), f)) { 2270Sstevel@tonic-gate cp = line; 2280Sstevel@tonic-gate linenum++; 2290Sstevel@tonic-gate 2300Sstevel@tonic-gate /* Skip any leading whitespace, comments and empty lines. */ 2310Sstevel@tonic-gate for (; *cp == ' ' || *cp == '\t'; cp++) 2320Sstevel@tonic-gate ; 2330Sstevel@tonic-gate if (!*cp || *cp == '#' || *cp == '\n') 2340Sstevel@tonic-gate continue; 2350Sstevel@tonic-gate 2360Sstevel@tonic-gate /* Find the end of the host name portion. */ 2370Sstevel@tonic-gate for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) 2380Sstevel@tonic-gate ; 2390Sstevel@tonic-gate 2400Sstevel@tonic-gate /* Check if the host name matches. */ 241*5243Sjp161948 if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) { 242*5243Sjp161948 if (*cp != HASH_DELIM) 243*5243Sjp161948 continue; 244*5243Sjp161948 hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); 245*5243Sjp161948 if (hashed_host == NULL) { 246*5243Sjp161948 debug("Invalid hashed host line %d of %s", 247*5243Sjp161948 linenum, filename); 248*5243Sjp161948 continue; 249*5243Sjp161948 } 250*5243Sjp161948 if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) 251*5243Sjp161948 continue; 252*5243Sjp161948 } 2530Sstevel@tonic-gate 2540Sstevel@tonic-gate /* Got a match. Skip host name. */ 2550Sstevel@tonic-gate cp = cp2; 2560Sstevel@tonic-gate 2570Sstevel@tonic-gate /* 2580Sstevel@tonic-gate * Extract the key from the line. This will skip any leading 2590Sstevel@tonic-gate * whitespace. Ignore badly formatted lines. 2600Sstevel@tonic-gate */ 2610Sstevel@tonic-gate if (!hostfile_read_key(&cp, &kbits, found)) 2620Sstevel@tonic-gate continue; 2630Sstevel@tonic-gate 2640Sstevel@tonic-gate if (numret != NULL) 2650Sstevel@tonic-gate *numret = linenum; 2660Sstevel@tonic-gate 2670Sstevel@tonic-gate if (key == NULL) { 2680Sstevel@tonic-gate /* we found a key of the requested type */ 269*5243Sjp161948 if (found->type == keytype) { 270*5243Sjp161948 fclose(f); 2710Sstevel@tonic-gate return HOST_FOUND; 272*5243Sjp161948 } 2730Sstevel@tonic-gate continue; 2740Sstevel@tonic-gate } 2750Sstevel@tonic-gate 2760Sstevel@tonic-gate if (!hostfile_check_key(kbits, found, host, filename, linenum)) 2770Sstevel@tonic-gate continue; 2780Sstevel@tonic-gate 2790Sstevel@tonic-gate /* Check if the current key is the same as the given key. */ 2800Sstevel@tonic-gate if (key_equal(key, found)) { 2810Sstevel@tonic-gate /* Ok, they match. */ 2820Sstevel@tonic-gate debug3("check_host_in_hostfile: match line %d", linenum); 2830Sstevel@tonic-gate fclose(f); 2840Sstevel@tonic-gate return HOST_OK; 2850Sstevel@tonic-gate } 2860Sstevel@tonic-gate /* 2870Sstevel@tonic-gate * They do not match. We will continue to go through the 2880Sstevel@tonic-gate * file; however, we note that we will not return that it is 2890Sstevel@tonic-gate * new. 2900Sstevel@tonic-gate */ 2910Sstevel@tonic-gate end_return = HOST_CHANGED; 2920Sstevel@tonic-gate } 2930Sstevel@tonic-gate /* Clear variables and close the file. */ 2940Sstevel@tonic-gate fclose(f); 2950Sstevel@tonic-gate 2960Sstevel@tonic-gate /* 2970Sstevel@tonic-gate * Return either HOST_NEW or HOST_CHANGED, depending on whether we 2980Sstevel@tonic-gate * saw a different key for the host. 2990Sstevel@tonic-gate */ 3000Sstevel@tonic-gate return end_return; 3010Sstevel@tonic-gate } 3020Sstevel@tonic-gate 3030Sstevel@tonic-gate HostStatus 304*5243Sjp161948 check_host_in_hostfile(const char *filename, const char *host, const Key *key, 3050Sstevel@tonic-gate Key *found, int *numret) 3060Sstevel@tonic-gate { 3070Sstevel@tonic-gate if (key == NULL) 3080Sstevel@tonic-gate fatal("no key to look up"); 3090Sstevel@tonic-gate return (check_host_in_hostfile_by_key_or_type(filename, host, key, 0, 3100Sstevel@tonic-gate found, numret)); 3110Sstevel@tonic-gate } 3120Sstevel@tonic-gate 3130Sstevel@tonic-gate int 3140Sstevel@tonic-gate lookup_key_in_hostfile_by_type(const char *filename, const char *host, 3150Sstevel@tonic-gate int keytype, Key *found, int *numret) 3160Sstevel@tonic-gate { 3170Sstevel@tonic-gate return (check_host_in_hostfile_by_key_or_type(filename, host, NULL, 3180Sstevel@tonic-gate keytype, found, numret) == HOST_FOUND); 3190Sstevel@tonic-gate } 3200Sstevel@tonic-gate 3210Sstevel@tonic-gate /* 3220Sstevel@tonic-gate * Appends an entry to the host file. Returns false if the entry could not 3230Sstevel@tonic-gate * be appended. 3240Sstevel@tonic-gate */ 3250Sstevel@tonic-gate 3260Sstevel@tonic-gate int 327*5243Sjp161948 add_host_to_hostfile(const char *filename, const char *host, const Key *key, 328*5243Sjp161948 int store_hash) 3290Sstevel@tonic-gate { 3300Sstevel@tonic-gate FILE *f; 3310Sstevel@tonic-gate int success = 0; 332*5243Sjp161948 char *hashed_host = NULL; 333*5243Sjp161948 3340Sstevel@tonic-gate if (key == NULL) 3350Sstevel@tonic-gate return 1; /* XXX ? */ 3360Sstevel@tonic-gate f = fopen(filename, "a"); 3370Sstevel@tonic-gate if (!f) 3380Sstevel@tonic-gate return 0; 339*5243Sjp161948 340*5243Sjp161948 if (store_hash) { 341*5243Sjp161948 if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { 342*5243Sjp161948 error("add_host_to_hostfile: host_hash failed"); 343*5243Sjp161948 fclose(f); 344*5243Sjp161948 return 0; 345*5243Sjp161948 } 346*5243Sjp161948 } 347*5243Sjp161948 fprintf(f, "%s ", store_hash ? hashed_host : host); 348*5243Sjp161948 3490Sstevel@tonic-gate if (key_write(key, f)) { 3500Sstevel@tonic-gate success = 1; 3510Sstevel@tonic-gate } else { 3520Sstevel@tonic-gate error("add_host_to_hostfile: saving key in %s failed", filename); 3530Sstevel@tonic-gate } 3540Sstevel@tonic-gate fprintf(f, "\n"); 3550Sstevel@tonic-gate fclose(f); 3560Sstevel@tonic-gate return success; 3570Sstevel@tonic-gate } 358