Annotation of src/usr.bin/ssh/hostfile.c, Revision 1.38
1.1 deraadt 1: /*
1.8 deraadt 2: * Author: Tatu Ylonen <ylo@cs.hut.fi>
3: * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
4: * All rights reserved
5: * Functions for manipulating the known hosts files.
1.16 markus 6: *
1.20 deraadt 7: * As far as I am concerned, the code I have written for this software
8: * can be used freely for any purpose. Any derived versions of this
9: * software must be clearly marked as such, and if the derived work is
10: * incompatible with the protocol description in the RFC file, it must be
11: * called by a name other than "ssh" or "Secure Shell".
12: *
13: *
1.28 markus 14: * Copyright (c) 1999, 2000 Markus Friedl. All rights reserved.
1.20 deraadt 15: * Copyright (c) 1999 Niels Provos. All rights reserved.
16: *
17: * Redistribution and use in source and binary forms, with or without
18: * modification, are permitted provided that the following conditions
19: * are met:
20: * 1. Redistributions of source code must retain the above copyright
21: * notice, this list of conditions and the following disclaimer.
22: * 2. Redistributions in binary form must reproduce the above copyright
23: * notice, this list of conditions and the following disclaimer in the
24: * documentation and/or other materials provided with the distribution.
25: *
26: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.8 deraadt 36: */
1.1 deraadt 37:
38: #include "includes.h"
1.38 ! djm 39: RCSID("$OpenBSD: hostfile.c,v 1.37 2006/02/07 03:47:05 stevesk Exp $");
1.33 djm 40:
41: #include <resolv.h>
1.37 stevesk 42:
1.33 djm 43: #include <openssl/hmac.h>
44: #include <openssl/sha.h>
1.1 deraadt 45:
1.14 markus 46: #include "match.h"
47: #include "key.h"
48: #include "hostfile.h"
1.24 markus 49: #include "log.h"
1.33 djm 50: #include "xmalloc.h"
51:
52: static int
53: extract_salt(const char *s, u_int l, char *salt, size_t salt_len)
54: {
55: char *p, *b64salt;
56: u_int b64len;
57: int ret;
58:
59: if (l < sizeof(HASH_MAGIC) - 1) {
60: debug2("extract_salt: string too short");
61: return (-1);
62: }
63: if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) {
64: debug2("extract_salt: invalid magic identifier");
65: return (-1);
66: }
67: s += sizeof(HASH_MAGIC) - 1;
68: l -= sizeof(HASH_MAGIC) - 1;
69: if ((p = memchr(s, HASH_DELIM, l)) == NULL) {
70: debug2("extract_salt: missing salt termination character");
71: return (-1);
72: }
73:
74: b64len = p - s;
75: /* Sanity check */
76: if (b64len == 0 || b64len > 1024) {
77: debug2("extract_salt: bad encoded salt length %u", b64len);
78: return (-1);
79: }
80: b64salt = xmalloc(1 + b64len);
81: memcpy(b64salt, s, b64len);
82: b64salt[b64len] = '\0';
83:
84: ret = __b64_pton(b64salt, salt, salt_len);
85: xfree(b64salt);
86: if (ret == -1) {
87: debug2("extract_salt: salt decode error");
88: return (-1);
89: }
90: if (ret != SHA_DIGEST_LENGTH) {
1.36 dtucker 91: debug2("extract_salt: expected salt len %d, got %d",
92: SHA_DIGEST_LENGTH, ret);
1.33 djm 93: return (-1);
94: }
1.34 deraadt 95:
1.33 djm 96: return (0);
97: }
98:
99: char *
100: host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
101: {
102: const EVP_MD *md = EVP_sha1();
103: HMAC_CTX mac_ctx;
104: char salt[256], result[256], uu_salt[512], uu_result[512];
105: static char encoded[1024];
106: u_int i, len;
107:
108: len = EVP_MD_size(md);
109:
110: if (name_from_hostfile == NULL) {
111: /* Create new salt */
112: for (i = 0; i < len; i++)
113: salt[i] = arc4random();
114: } else {
115: /* Extract salt from known host entry */
116: if (extract_salt(name_from_hostfile, src_len, salt,
117: sizeof(salt)) == -1)
118: return (NULL);
119: }
120:
121: HMAC_Init(&mac_ctx, salt, len, md);
122: HMAC_Update(&mac_ctx, host, strlen(host));
123: HMAC_Final(&mac_ctx, result, NULL);
124: HMAC_cleanup(&mac_ctx);
125:
1.34 deraadt 126: if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
1.33 djm 127: __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
128: fatal("host_hash: __b64_ntop failed");
129:
130: snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt,
131: HASH_DELIM, uu_result);
132:
133: return (encoded);
134: }
1.1 deraadt 135:
1.9 markus 136: /*
1.14 markus 137: * Parses an RSA (number of bits, e, n) or DSA key from a string. Moves the
138: * pointer over the key. Skips any whitespace at the beginning and at end.
1.9 markus 139: */
1.1 deraadt 140:
1.29 jakob 141: int
1.22 markus 142: hostfile_read_key(char **cpp, u_int *bitsp, Key *ret)
1.1 deraadt 143: {
1.7 markus 144: char *cp;
145:
146: /* Skip leading whitespace. */
1.9 markus 147: for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
148: ;
1.1 deraadt 149:
1.21 markus 150: if (key_read(ret, &cp) != 1)
1.7 markus 151: return 0;
152:
153: /* Skip trailing whitespace. */
1.9 markus 154: for (; *cp == ' ' || *cp == '\t'; cp++)
155: ;
1.7 markus 156:
157: /* Return results. */
158: *cpp = cp;
1.21 markus 159: *bitsp = key_size(ret);
1.7 markus 160: return 1;
1.14 markus 161: }
1.1 deraadt 162:
1.27 itojun 163: static int
1.32 jakob 164: hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum)
1.1 deraadt 165: {
1.21 markus 166: if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL)
1.14 markus 167: return 1;
168: if (bits != BN_num_bits(key->rsa->n)) {
1.31 itojun 169: logit("Warning: %s, line %d: keysize mismatch for host %s: "
1.14 markus 170: "actual %d vs. announced %d.",
171: filename, linenum, host, BN_num_bits(key->rsa->n), bits);
1.31 itojun 172: logit("Warning: replace %d with %d in %s, line %d.",
1.14 markus 173: bits, BN_num_bits(key->rsa->n), filename, linenum);
1.1 deraadt 174: }
1.14 markus 175: return 1;
1.1 deraadt 176: }
177:
1.9 markus 178: /*
179: * Checks whether the given host (which must be in all lowercase) is already
180: * in the list of our known hosts. Returns HOST_OK if the host is known and
181: * has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED
182: * if the host is known but used to have a different host key.
1.30 markus 183: *
184: * If no 'key' has been specified and a key of type 'keytype' is known
185: * for the specified host, then HOST_FOUND is returned.
1.9 markus 186: */
1.1 deraadt 187:
1.30 markus 188: static HostStatus
189: check_host_in_hostfile_by_key_or_type(const char *filename,
1.32 jakob 190: const char *host, const Key *key, int keytype, Key *found, int *numret)
1.1 deraadt 191: {
1.7 markus 192: FILE *f;
193: char line[8192];
194: int linenum = 0;
1.25 stevesk 195: u_int kbits;
1.33 djm 196: char *cp, *cp2, *hashed_host;
1.7 markus 197: HostStatus end_return;
198:
1.26 markus 199: debug3("check_host_in_hostfile: filename %s", filename);
1.30 markus 200:
1.7 markus 201: /* Open the file containing the list of known hosts. */
202: f = fopen(filename, "r");
203: if (!f)
204: return HOST_NEW;
205:
1.9 markus 206: /*
207: * Return value when the loop terminates. This is set to
208: * HOST_CHANGED if we have seen a different key for the host and have
209: * not found the proper one.
210: */
1.7 markus 211: end_return = HOST_NEW;
212:
1.25 stevesk 213: /* Go through the file. */
1.7 markus 214: while (fgets(line, sizeof(line), f)) {
215: cp = line;
216: linenum++;
217:
1.9 markus 218: /* Skip any leading whitespace, comments and empty lines. */
219: for (; *cp == ' ' || *cp == '\t'; cp++)
220: ;
1.7 markus 221: if (!*cp || *cp == '#' || *cp == '\n')
222: continue;
223:
224: /* Find the end of the host name portion. */
1.9 markus 225: for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
226: ;
1.7 markus 227:
228: /* Check if the host name matches. */
1.33 djm 229: if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) {
230: if (*cp != HASH_DELIM)
231: continue;
232: hashed_host = host_hash(host, cp, (u_int) (cp2 - cp));
233: if (hashed_host == NULL) {
234: debug("Invalid hashed host line %d of %s",
235: linenum, filename);
236: continue;
237: }
238: if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0)
239: continue;
240: }
1.7 markus 241:
242: /* Got a match. Skip host name. */
243: cp = cp2;
244:
1.9 markus 245: /*
246: * Extract the key from the line. This will skip any leading
247: * whitespace. Ignore badly formatted lines.
248: */
1.14 markus 249: if (!hostfile_read_key(&cp, &kbits, found))
250: continue;
1.23 markus 251:
252: if (numret != NULL)
253: *numret = linenum;
1.7 markus 254:
1.30 markus 255: if (key == NULL) {
256: /* we found a key of the requested type */
1.38 ! djm 257: if (found->type == keytype) {
! 258: fclose(f);
1.30 markus 259: return HOST_FOUND;
1.38 ! djm 260: }
1.30 markus 261: continue;
262: }
263:
264: if (!hostfile_check_key(kbits, found, host, filename, linenum))
265: continue;
266:
1.7 markus 267: /* Check if the current key is the same as the given key. */
1.14 markus 268: if (key_equal(key, found)) {
1.7 markus 269: /* Ok, they match. */
1.26 markus 270: debug3("check_host_in_hostfile: match line %d", linenum);
1.7 markus 271: fclose(f);
272: return HOST_OK;
273: }
1.9 markus 274: /*
275: * They do not match. We will continue to go through the
276: * file; however, we note that we will not return that it is
277: * new.
278: */
1.7 markus 279: end_return = HOST_CHANGED;
1.1 deraadt 280: }
1.7 markus 281: /* Clear variables and close the file. */
282: fclose(f);
283:
1.9 markus 284: /*
285: * Return either HOST_NEW or HOST_CHANGED, depending on whether we
286: * saw a different key for the host.
287: */
1.7 markus 288: return end_return;
1.30 markus 289: }
290:
291: HostStatus
1.32 jakob 292: check_host_in_hostfile(const char *filename, const char *host, const Key *key,
1.30 markus 293: Key *found, int *numret)
294: {
295: if (key == NULL)
296: fatal("no key to look up");
297: return (check_host_in_hostfile_by_key_or_type(filename, host, key, 0,
298: found, numret));
299: }
300:
301: int
302: lookup_key_in_hostfile_by_type(const char *filename, const char *host,
303: int keytype, Key *found, int *numret)
304: {
305: return (check_host_in_hostfile_by_key_or_type(filename, host, NULL,
306: keytype, found, numret) == HOST_FOUND);
1.1 deraadt 307: }
308:
1.9 markus 309: /*
310: * Appends an entry to the host file. Returns false if the entry could not
311: * be appended.
312: */
1.1 deraadt 313:
1.2 provos 314: int
1.34 deraadt 315: add_host_to_hostfile(const char *filename, const char *host, const Key *key,
1.33 djm 316: int store_hash)
1.1 deraadt 317: {
1.7 markus 318: FILE *f;
1.14 markus 319: int success = 0;
1.35 dtucker 320: char *hashed_host = NULL;
1.33 djm 321:
1.14 markus 322: if (key == NULL)
1.17 markus 323: return 1; /* XXX ? */
1.7 markus 324: f = fopen(filename, "a");
325: if (!f)
326: return 0;
1.33 djm 327:
328: if (store_hash) {
329: if ((hashed_host = host_hash(host, NULL, 0)) == NULL) {
330: error("add_host_to_hostfile: host_hash failed");
331: fclose(f);
332: return 0;
333: }
334: }
335: fprintf(f, "%s ", store_hash ? hashed_host : host);
336:
1.14 markus 337: if (key_write(key, f)) {
338: success = 1;
339: } else {
1.17 markus 340: error("add_host_to_hostfile: saving key in %s failed", filename);
1.7 markus 341: }
1.17 markus 342: fprintf(f, "\n");
1.7 markus 343: fclose(f);
1.14 markus 344: return success;
1.1 deraadt 345: }