1333ee039SDag-Erling Smørgrav /* $OpenBSD: hostfile.c,v 1.45 2006/08/03 03:34:42 deraadt Exp $ */ 2511b41d2SMark Murray /* 3511b41d2SMark Murray * Author: Tatu Ylonen <ylo@cs.hut.fi> 4511b41d2SMark Murray * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 5511b41d2SMark Murray * All rights reserved 6511b41d2SMark Murray * Functions for manipulating the known hosts files. 7511b41d2SMark Murray * 8c2d3a559SKris Kennaway * As far as I am concerned, the code I have written for this software 9c2d3a559SKris Kennaway * can be used freely for any purpose. Any derived versions of this 10c2d3a559SKris Kennaway * software must be clearly marked as such, and if the derived work is 11c2d3a559SKris Kennaway * incompatible with the protocol description in the RFC file, it must be 12c2d3a559SKris Kennaway * called by a name other than "ssh" or "Secure Shell". 13c2d3a559SKris Kennaway * 14c2d3a559SKris Kennaway * 15c2d3a559SKris Kennaway * Copyright (c) 1999, 2000 Markus Friedl. All rights reserved. 16c2d3a559SKris Kennaway * Copyright (c) 1999 Niels Provos. All rights reserved. 17c2d3a559SKris Kennaway * 18c2d3a559SKris Kennaway * Redistribution and use in source and binary forms, with or without 19c2d3a559SKris Kennaway * modification, are permitted provided that the following conditions 20c2d3a559SKris Kennaway * are met: 21c2d3a559SKris Kennaway * 1. Redistributions of source code must retain the above copyright 22c2d3a559SKris Kennaway * notice, this list of conditions and the following disclaimer. 23c2d3a559SKris Kennaway * 2. Redistributions in binary form must reproduce the above copyright 24c2d3a559SKris Kennaway * notice, this list of conditions and the following disclaimer in the 25c2d3a559SKris Kennaway * documentation and/or other materials provided with the distribution. 26c2d3a559SKris Kennaway * 27c2d3a559SKris Kennaway * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 28c2d3a559SKris Kennaway * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 29c2d3a559SKris Kennaway * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 30c2d3a559SKris Kennaway * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 31c2d3a559SKris Kennaway * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 32c2d3a559SKris Kennaway * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33c2d3a559SKris Kennaway * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34c2d3a559SKris Kennaway * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35c2d3a559SKris Kennaway * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 36c2d3a559SKris Kennaway * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37511b41d2SMark Murray */ 38511b41d2SMark Murray 39511b41d2SMark Murray #include "includes.h" 40aa49c926SDag-Erling Smørgrav 41333ee039SDag-Erling Smørgrav #include <sys/types.h> 42333ee039SDag-Erling Smørgrav 43333ee039SDag-Erling Smørgrav #include <netinet/in.h> 44333ee039SDag-Erling Smørgrav 45aa49c926SDag-Erling Smørgrav #include <openssl/hmac.h> 46aa49c926SDag-Erling Smørgrav #include <openssl/sha.h> 47511b41d2SMark Murray 48333ee039SDag-Erling Smørgrav #include <resolv.h> 49333ee039SDag-Erling Smørgrav #include <stdarg.h> 50333ee039SDag-Erling Smørgrav #include <stdio.h> 51333ee039SDag-Erling Smørgrav #include <stdlib.h> 52333ee039SDag-Erling Smørgrav #include <string.h> 53333ee039SDag-Erling Smørgrav 54333ee039SDag-Erling Smørgrav #include "xmalloc.h" 55a8f6863aSKris Kennaway #include "match.h" 56a8f6863aSKris Kennaway #include "key.h" 57a8f6863aSKris Kennaway #include "hostfile.h" 58ca3176e7SBrian Feldman #include "log.h" 59aa49c926SDag-Erling Smørgrav 60aa49c926SDag-Erling Smørgrav static int 61aa49c926SDag-Erling Smørgrav extract_salt(const char *s, u_int l, char *salt, size_t salt_len) 62aa49c926SDag-Erling Smørgrav { 63aa49c926SDag-Erling Smørgrav char *p, *b64salt; 64aa49c926SDag-Erling Smørgrav u_int b64len; 65aa49c926SDag-Erling Smørgrav int ret; 66aa49c926SDag-Erling Smørgrav 67aa49c926SDag-Erling Smørgrav if (l < sizeof(HASH_MAGIC) - 1) { 68aa49c926SDag-Erling Smørgrav debug2("extract_salt: string too short"); 69aa49c926SDag-Erling Smørgrav return (-1); 70aa49c926SDag-Erling Smørgrav } 71aa49c926SDag-Erling Smørgrav if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) { 72aa49c926SDag-Erling Smørgrav debug2("extract_salt: invalid magic identifier"); 73aa49c926SDag-Erling Smørgrav return (-1); 74aa49c926SDag-Erling Smørgrav } 75aa49c926SDag-Erling Smørgrav s += sizeof(HASH_MAGIC) - 1; 76aa49c926SDag-Erling Smørgrav l -= sizeof(HASH_MAGIC) - 1; 77aa49c926SDag-Erling Smørgrav if ((p = memchr(s, HASH_DELIM, l)) == NULL) { 78aa49c926SDag-Erling Smørgrav debug2("extract_salt: missing salt termination character"); 79aa49c926SDag-Erling Smørgrav return (-1); 80aa49c926SDag-Erling Smørgrav } 81aa49c926SDag-Erling Smørgrav 82aa49c926SDag-Erling Smørgrav b64len = p - s; 83aa49c926SDag-Erling Smørgrav /* Sanity check */ 84aa49c926SDag-Erling Smørgrav if (b64len == 0 || b64len > 1024) { 85aa49c926SDag-Erling Smørgrav debug2("extract_salt: bad encoded salt length %u", b64len); 86aa49c926SDag-Erling Smørgrav return (-1); 87aa49c926SDag-Erling Smørgrav } 88aa49c926SDag-Erling Smørgrav b64salt = xmalloc(1 + b64len); 89aa49c926SDag-Erling Smørgrav memcpy(b64salt, s, b64len); 90aa49c926SDag-Erling Smørgrav b64salt[b64len] = '\0'; 91aa49c926SDag-Erling Smørgrav 92aa49c926SDag-Erling Smørgrav ret = __b64_pton(b64salt, salt, salt_len); 93aa49c926SDag-Erling Smørgrav xfree(b64salt); 94aa49c926SDag-Erling Smørgrav if (ret == -1) { 95aa49c926SDag-Erling Smørgrav debug2("extract_salt: salt decode error"); 96aa49c926SDag-Erling Smørgrav return (-1); 97aa49c926SDag-Erling Smørgrav } 98aa49c926SDag-Erling Smørgrav if (ret != SHA_DIGEST_LENGTH) { 99b74df5b2SDag-Erling Smørgrav debug2("extract_salt: expected salt len %d, got %d", 100b74df5b2SDag-Erling Smørgrav SHA_DIGEST_LENGTH, ret); 101aa49c926SDag-Erling Smørgrav return (-1); 102aa49c926SDag-Erling Smørgrav } 103aa49c926SDag-Erling Smørgrav 104aa49c926SDag-Erling Smørgrav return (0); 105aa49c926SDag-Erling Smørgrav } 106aa49c926SDag-Erling Smørgrav 107aa49c926SDag-Erling Smørgrav char * 108aa49c926SDag-Erling Smørgrav host_hash(const char *host, const char *name_from_hostfile, u_int src_len) 109aa49c926SDag-Erling Smørgrav { 110aa49c926SDag-Erling Smørgrav const EVP_MD *md = EVP_sha1(); 111aa49c926SDag-Erling Smørgrav HMAC_CTX mac_ctx; 112aa49c926SDag-Erling Smørgrav char salt[256], result[256], uu_salt[512], uu_result[512]; 113aa49c926SDag-Erling Smørgrav static char encoded[1024]; 114aa49c926SDag-Erling Smørgrav u_int i, len; 115aa49c926SDag-Erling Smørgrav 116aa49c926SDag-Erling Smørgrav len = EVP_MD_size(md); 117aa49c926SDag-Erling Smørgrav 118aa49c926SDag-Erling Smørgrav if (name_from_hostfile == NULL) { 119aa49c926SDag-Erling Smørgrav /* Create new salt */ 120aa49c926SDag-Erling Smørgrav for (i = 0; i < len; i++) 121aa49c926SDag-Erling Smørgrav salt[i] = arc4random(); 122aa49c926SDag-Erling Smørgrav } else { 123aa49c926SDag-Erling Smørgrav /* Extract salt from known host entry */ 124aa49c926SDag-Erling Smørgrav if (extract_salt(name_from_hostfile, src_len, salt, 125aa49c926SDag-Erling Smørgrav sizeof(salt)) == -1) 126aa49c926SDag-Erling Smørgrav return (NULL); 127aa49c926SDag-Erling Smørgrav } 128aa49c926SDag-Erling Smørgrav 129aa49c926SDag-Erling Smørgrav HMAC_Init(&mac_ctx, salt, len, md); 130aa49c926SDag-Erling Smørgrav HMAC_Update(&mac_ctx, host, strlen(host)); 131aa49c926SDag-Erling Smørgrav HMAC_Final(&mac_ctx, result, NULL); 132aa49c926SDag-Erling Smørgrav HMAC_cleanup(&mac_ctx); 133aa49c926SDag-Erling Smørgrav 134aa49c926SDag-Erling Smørgrav if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 || 135aa49c926SDag-Erling Smørgrav __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1) 136aa49c926SDag-Erling Smørgrav fatal("host_hash: __b64_ntop failed"); 137aa49c926SDag-Erling Smørgrav 138aa49c926SDag-Erling Smørgrav snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt, 139aa49c926SDag-Erling Smørgrav HASH_DELIM, uu_result); 140aa49c926SDag-Erling Smørgrav 141aa49c926SDag-Erling Smørgrav return (encoded); 142aa49c926SDag-Erling Smørgrav } 143511b41d2SMark Murray 144511b41d2SMark Murray /* 145a8f6863aSKris Kennaway * Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the 146a8f6863aSKris Kennaway * pointer over the key. Skips any whitespace at the beginning and at end. 147511b41d2SMark Murray */ 148511b41d2SMark Murray 149511b41d2SMark Murray int 150ca3176e7SBrian Feldman hostfile_read_key(char **cpp, u_int *bitsp, Key *ret) 151511b41d2SMark Murray { 152511b41d2SMark Murray char *cp; 153511b41d2SMark Murray 154511b41d2SMark Murray /* Skip leading whitespace. */ 155511b41d2SMark Murray for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++) 156511b41d2SMark Murray ; 157511b41d2SMark Murray 158ca3176e7SBrian Feldman if (key_read(ret, &cp) != 1) 159511b41d2SMark Murray return 0; 160511b41d2SMark Murray 161511b41d2SMark Murray /* Skip trailing whitespace. */ 162511b41d2SMark Murray for (; *cp == ' ' || *cp == '\t'; cp++) 163511b41d2SMark Murray ; 164511b41d2SMark Murray 165511b41d2SMark Murray /* Return results. */ 166511b41d2SMark Murray *cpp = cp; 167ca3176e7SBrian Feldman *bitsp = key_size(ret); 168511b41d2SMark Murray return 1; 169511b41d2SMark Murray } 170511b41d2SMark Murray 171af12a3e7SDag-Erling Smørgrav static int 1721ec0d754SDag-Erling Smørgrav hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum) 173511b41d2SMark Murray { 174ca3176e7SBrian Feldman if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) 175a8f6863aSKris Kennaway return 1; 176a8f6863aSKris Kennaway if (bits != BN_num_bits(key->rsa->n)) { 177cf2b5f3bSDag-Erling Smørgrav logit("Warning: %s, line %d: keysize mismatch for host %s: " 178a8f6863aSKris Kennaway "actual %d vs. announced %d.", 179a8f6863aSKris Kennaway filename, linenum, host, BN_num_bits(key->rsa->n), bits); 180cf2b5f3bSDag-Erling Smørgrav logit("Warning: replace %d with %d in %s, line %d.", 181a8f6863aSKris Kennaway bits, BN_num_bits(key->rsa->n), filename, linenum); 182511b41d2SMark Murray } 183a8f6863aSKris Kennaway return 1; 184511b41d2SMark Murray } 185511b41d2SMark Murray 186511b41d2SMark Murray /* 187511b41d2SMark Murray * Checks whether the given host (which must be in all lowercase) is already 188511b41d2SMark Murray * in the list of our known hosts. Returns HOST_OK if the host is known and 189511b41d2SMark Murray * has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED 190511b41d2SMark Murray * if the host is known but used to have a different host key. 191f388f5efSDag-Erling Smørgrav * 192f388f5efSDag-Erling Smørgrav * If no 'key' has been specified and a key of type 'keytype' is known 193f388f5efSDag-Erling Smørgrav * for the specified host, then HOST_FOUND is returned. 194511b41d2SMark Murray */ 195511b41d2SMark Murray 196f388f5efSDag-Erling Smørgrav static HostStatus 197f388f5efSDag-Erling Smørgrav check_host_in_hostfile_by_key_or_type(const char *filename, 1981ec0d754SDag-Erling Smørgrav const char *host, const Key *key, int keytype, Key *found, int *numret) 199511b41d2SMark Murray { 200511b41d2SMark Murray FILE *f; 201511b41d2SMark Murray char line[8192]; 202511b41d2SMark Murray int linenum = 0; 203ca3176e7SBrian Feldman u_int kbits; 204aa49c926SDag-Erling Smørgrav char *cp, *cp2, *hashed_host; 205511b41d2SMark Murray HostStatus end_return; 206511b41d2SMark Murray 207ca3176e7SBrian Feldman debug3("check_host_in_hostfile: filename %s", filename); 208f388f5efSDag-Erling Smørgrav 209511b41d2SMark Murray /* Open the file containing the list of known hosts. */ 210511b41d2SMark Murray f = fopen(filename, "r"); 211511b41d2SMark Murray if (!f) 212511b41d2SMark Murray return HOST_NEW; 213511b41d2SMark Murray 214511b41d2SMark Murray /* 215511b41d2SMark Murray * Return value when the loop terminates. This is set to 216511b41d2SMark Murray * HOST_CHANGED if we have seen a different key for the host and have 217511b41d2SMark Murray * not found the proper one. 218511b41d2SMark Murray */ 219511b41d2SMark Murray end_return = HOST_NEW; 220511b41d2SMark Murray 221ca3176e7SBrian Feldman /* Go through the file. */ 222511b41d2SMark Murray while (fgets(line, sizeof(line), f)) { 223511b41d2SMark Murray cp = line; 224511b41d2SMark Murray linenum++; 225511b41d2SMark Murray 226511b41d2SMark Murray /* Skip any leading whitespace, comments and empty lines. */ 227511b41d2SMark Murray for (; *cp == ' ' || *cp == '\t'; cp++) 228511b41d2SMark Murray ; 229511b41d2SMark Murray if (!*cp || *cp == '#' || *cp == '\n') 230511b41d2SMark Murray continue; 231511b41d2SMark Murray 232511b41d2SMark Murray /* Find the end of the host name portion. */ 233511b41d2SMark Murray for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) 234511b41d2SMark Murray ; 235511b41d2SMark Murray 236511b41d2SMark Murray /* Check if the host name matches. */ 237aa49c926SDag-Erling Smørgrav if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) { 238aa49c926SDag-Erling Smørgrav if (*cp != HASH_DELIM) 239511b41d2SMark Murray continue; 240aa49c926SDag-Erling Smørgrav hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); 241aa49c926SDag-Erling Smørgrav if (hashed_host == NULL) { 242aa49c926SDag-Erling Smørgrav debug("Invalid hashed host line %d of %s", 243aa49c926SDag-Erling Smørgrav linenum, filename); 244aa49c926SDag-Erling Smørgrav continue; 245aa49c926SDag-Erling Smørgrav } 246aa49c926SDag-Erling Smørgrav if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) 247aa49c926SDag-Erling Smørgrav continue; 248aa49c926SDag-Erling Smørgrav } 249511b41d2SMark Murray 250511b41d2SMark Murray /* Got a match. Skip host name. */ 251511b41d2SMark Murray cp = cp2; 252511b41d2SMark Murray 253511b41d2SMark Murray /* 254511b41d2SMark Murray * Extract the key from the line. This will skip any leading 255511b41d2SMark Murray * whitespace. Ignore badly formatted lines. 256511b41d2SMark Murray */ 257a8f6863aSKris Kennaway if (!hostfile_read_key(&cp, &kbits, found)) 258a8f6863aSKris Kennaway continue; 259511b41d2SMark Murray 260ca3176e7SBrian Feldman if (numret != NULL) 261ca3176e7SBrian Feldman *numret = linenum; 262ca3176e7SBrian Feldman 263f388f5efSDag-Erling Smørgrav if (key == NULL) { 264f388f5efSDag-Erling Smørgrav /* we found a key of the requested type */ 265333ee039SDag-Erling Smørgrav if (found->type == keytype) { 266333ee039SDag-Erling Smørgrav fclose(f); 267f388f5efSDag-Erling Smørgrav return HOST_FOUND; 268333ee039SDag-Erling Smørgrav } 269f388f5efSDag-Erling Smørgrav continue; 270f388f5efSDag-Erling Smørgrav } 271f388f5efSDag-Erling Smørgrav 272f388f5efSDag-Erling Smørgrav if (!hostfile_check_key(kbits, found, host, filename, linenum)) 273f388f5efSDag-Erling Smørgrav continue; 274f388f5efSDag-Erling Smørgrav 275511b41d2SMark Murray /* Check if the current key is the same as the given key. */ 276a8f6863aSKris Kennaway if (key_equal(key, found)) { 277511b41d2SMark Murray /* Ok, they match. */ 278ca3176e7SBrian Feldman debug3("check_host_in_hostfile: match line %d", linenum); 279511b41d2SMark Murray fclose(f); 280511b41d2SMark Murray return HOST_OK; 281511b41d2SMark Murray } 282511b41d2SMark Murray /* 283511b41d2SMark Murray * They do not match. We will continue to go through the 284511b41d2SMark Murray * file; however, we note that we will not return that it is 285511b41d2SMark Murray * new. 286511b41d2SMark Murray */ 287511b41d2SMark Murray end_return = HOST_CHANGED; 288511b41d2SMark Murray } 289511b41d2SMark Murray /* Clear variables and close the file. */ 290511b41d2SMark Murray fclose(f); 291511b41d2SMark Murray 292511b41d2SMark Murray /* 293511b41d2SMark Murray * Return either HOST_NEW or HOST_CHANGED, depending on whether we 294511b41d2SMark Murray * saw a different key for the host. 295511b41d2SMark Murray */ 296511b41d2SMark Murray return end_return; 297511b41d2SMark Murray } 298511b41d2SMark Murray 299f388f5efSDag-Erling Smørgrav HostStatus 3001ec0d754SDag-Erling Smørgrav check_host_in_hostfile(const char *filename, const char *host, const Key *key, 301f388f5efSDag-Erling Smørgrav Key *found, int *numret) 302f388f5efSDag-Erling Smørgrav { 303f388f5efSDag-Erling Smørgrav if (key == NULL) 304f388f5efSDag-Erling Smørgrav fatal("no key to look up"); 305f388f5efSDag-Erling Smørgrav return (check_host_in_hostfile_by_key_or_type(filename, host, key, 0, 306f388f5efSDag-Erling Smørgrav found, numret)); 307f388f5efSDag-Erling Smørgrav } 308f388f5efSDag-Erling Smørgrav 309f388f5efSDag-Erling Smørgrav int 310f388f5efSDag-Erling Smørgrav lookup_key_in_hostfile_by_type(const char *filename, const char *host, 311f388f5efSDag-Erling Smørgrav int keytype, Key *found, int *numret) 312f388f5efSDag-Erling Smørgrav { 313f388f5efSDag-Erling Smørgrav return (check_host_in_hostfile_by_key_or_type(filename, host, NULL, 314f388f5efSDag-Erling Smørgrav keytype, found, numret) == HOST_FOUND); 315f388f5efSDag-Erling Smørgrav } 316f388f5efSDag-Erling Smørgrav 317511b41d2SMark Murray /* 318511b41d2SMark Murray * Appends an entry to the host file. Returns false if the entry could not 319511b41d2SMark Murray * be appended. 320511b41d2SMark Murray */ 321511b41d2SMark Murray 322511b41d2SMark Murray int 323aa49c926SDag-Erling Smørgrav add_host_to_hostfile(const char *filename, const char *host, const Key *key, 324aa49c926SDag-Erling Smørgrav int store_hash) 325511b41d2SMark Murray { 326511b41d2SMark Murray FILE *f; 327a8f6863aSKris Kennaway int success = 0; 328d4ecd108SDag-Erling Smørgrav char *hashed_host = NULL; 329aa49c926SDag-Erling Smørgrav 330a8f6863aSKris Kennaway if (key == NULL) 331e8aafc91SKris Kennaway return 1; /* XXX ? */ 332511b41d2SMark Murray f = fopen(filename, "a"); 333511b41d2SMark Murray if (!f) 334511b41d2SMark Murray return 0; 335aa49c926SDag-Erling Smørgrav 336aa49c926SDag-Erling Smørgrav if (store_hash) { 337aa49c926SDag-Erling Smørgrav if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { 338aa49c926SDag-Erling Smørgrav error("add_host_to_hostfile: host_hash failed"); 339aa49c926SDag-Erling Smørgrav fclose(f); 340aa49c926SDag-Erling Smørgrav return 0; 341aa49c926SDag-Erling Smørgrav } 342aa49c926SDag-Erling Smørgrav } 343aa49c926SDag-Erling Smørgrav fprintf(f, "%s ", store_hash ? hashed_host : host); 344aa49c926SDag-Erling Smørgrav 345a8f6863aSKris Kennaway if (key_write(key, f)) { 346a8f6863aSKris Kennaway success = 1; 347a8f6863aSKris Kennaway } else { 348e8aafc91SKris Kennaway error("add_host_to_hostfile: saving key in %s failed", filename); 349511b41d2SMark Murray } 350e8aafc91SKris Kennaway fprintf(f, "\n"); 351511b41d2SMark Murray fclose(f); 352a8f6863aSKris Kennaway return success; 353511b41d2SMark Murray } 354