Annotation of src/usr.bin/ssh/hostfile.c, Revision 1.45
1.45 ! deraadt 1: /* $OpenBSD: hostfile.c,v 1.44 2006/08/01 23:22:47 stevesk Exp $ */
1.1 deraadt 2: /*
1.8 deraadt 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.
1.16 markus 7: *
1.20 deraadt 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: *
1.28 markus 15: * Copyright (c) 1999, 2000 Markus Friedl. All rights reserved.
1.20 deraadt 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.
1.8 deraadt 37: */
1.1 deraadt 38:
1.41 stevesk 39: #include <sys/types.h>
40:
41: #include <netinet/in.h>
1.33 djm 42:
1.43 stevesk 43: #include <openssl/hmac.h>
44: #include <openssl/sha.h>
45:
1.33 djm 46: #include <resolv.h>
1.44 stevesk 47: #include <stdio.h>
1.43 stevesk 48: #include <stdlib.h>
1.42 stevesk 49: #include <string.h>
1.1 deraadt 50:
1.45 ! deraadt 51: #include "xmalloc.h"
1.14 markus 52: #include "match.h"
53: #include "key.h"
54: #include "hostfile.h"
1.24 markus 55: #include "log.h"
1.33 djm 56:
57: static int
58: extract_salt(const char *s, u_int l, char *salt, size_t salt_len)
59: {
60: char *p, *b64salt;
61: u_int b64len;
62: int ret;
63:
64: if (l < sizeof(HASH_MAGIC) - 1) {
65: debug2("extract_salt: string too short");
66: return (-1);
67: }
68: if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) {
69: debug2("extract_salt: invalid magic identifier");
70: return (-1);
71: }
72: s += sizeof(HASH_MAGIC) - 1;
73: l -= sizeof(HASH_MAGIC) - 1;
74: if ((p = memchr(s, HASH_DELIM, l)) == NULL) {
75: debug2("extract_salt: missing salt termination character");
76: return (-1);
77: }
78:
79: b64len = p - s;
80: /* Sanity check */
81: if (b64len == 0 || b64len > 1024) {
82: debug2("extract_salt: bad encoded salt length %u", b64len);
83: return (-1);
84: }
85: b64salt = xmalloc(1 + b64len);
86: memcpy(b64salt, s, b64len);
87: b64salt[b64len] = '\0';
88:
89: ret = __b64_pton(b64salt, salt, salt_len);
90: xfree(b64salt);
91: if (ret == -1) {
92: debug2("extract_salt: salt decode error");
93: return (-1);
94: }
95: if (ret != SHA_DIGEST_LENGTH) {
1.36 dtucker 96: debug2("extract_salt: expected salt len %d, got %d",
97: SHA_DIGEST_LENGTH, ret);
1.33 djm 98: return (-1);
99: }
1.34 deraadt 100:
1.33 djm 101: return (0);
102: }
103:
104: char *
105: host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
106: {
107: const EVP_MD *md = EVP_sha1();
108: HMAC_CTX mac_ctx;
109: char salt[256], result[256], uu_salt[512], uu_result[512];
110: static char encoded[1024];
111: u_int i, len;
112:
113: len = EVP_MD_size(md);
114:
115: if (name_from_hostfile == NULL) {
116: /* Create new salt */
117: for (i = 0; i < len; i++)
118: salt[i] = arc4random();
119: } else {
120: /* Extract salt from known host entry */
121: if (extract_salt(name_from_hostfile, src_len, salt,
122: sizeof(salt)) == -1)
123: return (NULL);
124: }
125:
126: HMAC_Init(&mac_ctx, salt, len, md);
127: HMAC_Update(&mac_ctx, host, strlen(host));
128: HMAC_Final(&mac_ctx, result, NULL);
129: HMAC_cleanup(&mac_ctx);
130:
1.34 deraadt 131: if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
1.33 djm 132: __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
133: fatal("host_hash: __b64_ntop failed");
134:
135: snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt,
136: HASH_DELIM, uu_result);
137:
138: return (encoded);
139: }
1.1 deraadt 140:
1.9 markus 141: /*
1.14 markus 142: * Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the
143: * pointer over the key. Skips any whitespace at the beginning and at end.
1.9 markus 144: */
1.1 deraadt 145:
1.29 jakob 146: int
1.22 markus 147: hostfile_read_key(char **cpp, u_int *bitsp, Key *ret)
1.1 deraadt 148: {
1.7 markus 149: char *cp;
150:
151: /* Skip leading whitespace. */
1.9 markus 152: for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
153: ;
1.1 deraadt 154:
1.21 markus 155: if (key_read(ret, &cp) != 1)
1.7 markus 156: return 0;
157:
158: /* Skip trailing whitespace. */
1.9 markus 159: for (; *cp == ' ' || *cp == '\t'; cp++)
160: ;
1.7 markus 161:
162: /* Return results. */
163: *cpp = cp;
1.21 markus 164: *bitsp = key_size(ret);
1.7 markus 165: return 1;
1.14 markus 166: }
1.1 deraadt 167:
1.27 itojun 168: static int
1.32 jakob 169: hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum)
1.1 deraadt 170: {
1.21 markus 171: if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL)
1.14 markus 172: return 1;
173: if (bits != BN_num_bits(key->rsa->n)) {
1.31 itojun 174: logit("Warning: %s, line %d: keysize mismatch for host %s: "
1.14 markus 175: "actual %d vs. announced %d.",
176: filename, linenum, host, BN_num_bits(key->rsa->n), bits);
1.31 itojun 177: logit("Warning: replace %d with %d in %s, line %d.",
1.14 markus 178: bits, BN_num_bits(key->rsa->n), filename, linenum);
1.1 deraadt 179: }
1.14 markus 180: return 1;
1.1 deraadt 181: }
182:
1.9 markus 183: /*
184: * Checks whether the given host (which must be in all lowercase) is already
185: * in the list of our known hosts. Returns HOST_OK if the host is known and
186: * has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED
187: * if the host is known but used to have a different host key.
1.30 markus 188: *
189: * If no 'key' has been specified and a key of type 'keytype' is known
190: * for the specified host, then HOST_FOUND is returned.
1.9 markus 191: */
1.1 deraadt 192:
1.30 markus 193: static HostStatus
194: check_host_in_hostfile_by_key_or_type(const char *filename,
1.32 jakob 195: const char *host, const Key *key, int keytype, Key *found, int *numret)
1.1 deraadt 196: {
1.7 markus 197: FILE *f;
198: char line[8192];
199: int linenum = 0;
1.25 stevesk 200: u_int kbits;
1.33 djm 201: char *cp, *cp2, *hashed_host;
1.7 markus 202: HostStatus end_return;
203:
1.26 markus 204: debug3("check_host_in_hostfile: filename %s", filename);
1.30 markus 205:
1.7 markus 206: /* Open the file containing the list of known hosts. */
207: f = fopen(filename, "r");
208: if (!f)
209: return HOST_NEW;
210:
1.9 markus 211: /*
212: * Return value when the loop terminates. This is set to
213: * HOST_CHANGED if we have seen a different key for the host and have
214: * not found the proper one.
215: */
1.7 markus 216: end_return = HOST_NEW;
217:
1.25 stevesk 218: /* Go through the file. */
1.7 markus 219: while (fgets(line, sizeof(line), f)) {
220: cp = line;
221: linenum++;
222:
1.9 markus 223: /* Skip any leading whitespace, comments and empty lines. */
224: for (; *cp == ' ' || *cp == '\t'; cp++)
225: ;
1.7 markus 226: if (!*cp || *cp == '#' || *cp == '\n')
227: continue;
228:
229: /* Find the end of the host name portion. */
1.9 markus 230: for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
231: ;
1.7 markus 232:
233: /* Check if the host name matches. */
1.33 djm 234: if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) {
235: if (*cp != HASH_DELIM)
236: continue;
237: hashed_host = host_hash(host, cp, (u_int) (cp2 - cp));
238: if (hashed_host == NULL) {
239: debug("Invalid hashed host line %d of %s",
240: linenum, filename);
241: continue;
242: }
243: if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0)
244: continue;
245: }
1.7 markus 246:
247: /* Got a match. Skip host name. */
248: cp = cp2;
249:
1.9 markus 250: /*
251: * Extract the key from the line. This will skip any leading
252: * whitespace. Ignore badly formatted lines.
253: */
1.14 markus 254: if (!hostfile_read_key(&cp, &kbits, found))
255: continue;
1.23 markus 256:
257: if (numret != NULL)
258: *numret = linenum;
1.7 markus 259:
1.30 markus 260: if (key == NULL) {
261: /* we found a key of the requested type */
1.38 djm 262: if (found->type == keytype) {
263: fclose(f);
1.30 markus 264: return HOST_FOUND;
1.38 djm 265: }
1.30 markus 266: continue;
267: }
268:
269: if (!hostfile_check_key(kbits, found, host, filename, linenum))
270: continue;
271:
1.7 markus 272: /* Check if the current key is the same as the given key. */
1.14 markus 273: if (key_equal(key, found)) {
1.7 markus 274: /* Ok, they match. */
1.26 markus 275: debug3("check_host_in_hostfile: match line %d", linenum);
1.7 markus 276: fclose(f);
277: return HOST_OK;
278: }
1.9 markus 279: /*
280: * They do not match. We will continue to go through the
281: * file; however, we note that we will not return that it is
282: * new.
283: */
1.7 markus 284: end_return = HOST_CHANGED;
1.1 deraadt 285: }
1.7 markus 286: /* Clear variables and close the file. */
287: fclose(f);
288:
1.9 markus 289: /*
290: * Return either HOST_NEW or HOST_CHANGED, depending on whether we
291: * saw a different key for the host.
292: */
1.7 markus 293: return end_return;
1.30 markus 294: }
295:
296: HostStatus
1.32 jakob 297: check_host_in_hostfile(const char *filename, const char *host, const Key *key,
1.30 markus 298: Key *found, int *numret)
299: {
300: if (key == NULL)
301: fatal("no key to look up");
302: return (check_host_in_hostfile_by_key_or_type(filename, host, key, 0,
303: found, numret));
304: }
305:
306: int
307: lookup_key_in_hostfile_by_type(const char *filename, const char *host,
308: int keytype, Key *found, int *numret)
309: {
310: return (check_host_in_hostfile_by_key_or_type(filename, host, NULL,
311: keytype, found, numret) == HOST_FOUND);
1.1 deraadt 312: }
313:
1.9 markus 314: /*
315: * Appends an entry to the host file. Returns false if the entry could not
316: * be appended.
317: */
1.1 deraadt 318:
1.2 provos 319: int
1.34 deraadt 320: add_host_to_hostfile(const char *filename, const char *host, const Key *key,
1.33 djm 321: int store_hash)
1.1 deraadt 322: {
1.7 markus 323: FILE *f;
1.14 markus 324: int success = 0;
1.35 dtucker 325: char *hashed_host = NULL;
1.33 djm 326:
1.14 markus 327: if (key == NULL)
1.17 markus 328: return 1; /* XXX ? */
1.7 markus 329: f = fopen(filename, "a");
330: if (!f)
331: return 0;
1.33 djm 332:
333: if (store_hash) {
334: if ((hashed_host = host_hash(host, NULL, 0)) == NULL) {
335: error("add_host_to_hostfile: host_hash failed");
336: fclose(f);
337: return 0;
338: }
339: }
340: fprintf(f, "%s ", store_hash ? hashed_host : host);
341:
1.14 markus 342: if (key_write(key, f)) {
343: success = 1;
344: } else {
1.17 markus 345: error("add_host_to_hostfile: saving key in %s failed", filename);
1.7 markus 346: }
1.17 markus 347: fprintf(f, "\n");
1.7 markus 348: fclose(f);
1.14 markus 349: return success;
1.1 deraadt 350: }