hostfile.c revision 262566
1/* $OpenBSD: hostfile.c,v 1.53 2014/01/09 23:20:00 djm 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 "includes.h"
40
41#include <sys/types.h>
42
43#include <netinet/in.h>
44
45#include <openssl/hmac.h>
46#include <openssl/sha.h>
47
48#include <resolv.h>
49#include <stdarg.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53
54#include "xmalloc.h"
55#include "match.h"
56#include "key.h"
57#include "hostfile.h"
58#include "log.h"
59#include "misc.h"
60#include "digest.h"
61
62struct hostkeys {
63	struct hostkey_entry *entries;
64	u_int num_entries;
65};
66
67static int
68extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len)
69{
70	char *p, *b64salt;
71	u_int b64len;
72	int ret;
73
74	if (l < sizeof(HASH_MAGIC) - 1) {
75		debug2("extract_salt: string too short");
76		return (-1);
77	}
78	if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) {
79		debug2("extract_salt: invalid magic identifier");
80		return (-1);
81	}
82	s += sizeof(HASH_MAGIC) - 1;
83	l -= sizeof(HASH_MAGIC) - 1;
84	if ((p = memchr(s, HASH_DELIM, l)) == NULL) {
85		debug2("extract_salt: missing salt termination character");
86		return (-1);
87	}
88
89	b64len = p - s;
90	/* Sanity check */
91	if (b64len == 0 || b64len > 1024) {
92		debug2("extract_salt: bad encoded salt length %u", b64len);
93		return (-1);
94	}
95	b64salt = xmalloc(1 + b64len);
96	memcpy(b64salt, s, b64len);
97	b64salt[b64len] = '\0';
98
99	ret = __b64_pton(b64salt, salt, salt_len);
100	free(b64salt);
101	if (ret == -1) {
102		debug2("extract_salt: salt decode error");
103		return (-1);
104	}
105	if (ret != SHA_DIGEST_LENGTH) {
106		debug2("extract_salt: expected salt len %d, got %d",
107		    SHA_DIGEST_LENGTH, ret);
108		return (-1);
109	}
110
111	return (0);
112}
113
114char *
115host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
116{
117	const EVP_MD *md = EVP_sha1();
118	HMAC_CTX mac_ctx;
119	u_char salt[256], result[256];
120	char uu_salt[512], uu_result[512];
121	static char encoded[1024];
122	u_int i, len;
123
124	len = EVP_MD_size(md);
125
126	if (name_from_hostfile == NULL) {
127		/* Create new salt */
128		for (i = 0; i < len; i++)
129			salt[i] = arc4random();
130	} else {
131		/* Extract salt from known host entry */
132		if (extract_salt(name_from_hostfile, src_len, salt,
133		    sizeof(salt)) == -1)
134			return (NULL);
135	}
136
137	HMAC_Init(&mac_ctx, salt, len, md);
138	HMAC_Update(&mac_ctx, (u_char *)host, strlen(host));
139	HMAC_Final(&mac_ctx, result, NULL);
140	HMAC_cleanup(&mac_ctx);
141
142	if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
143	    __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
144		fatal("host_hash: __b64_ntop failed");
145
146	snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt,
147	    HASH_DELIM, uu_result);
148
149	return (encoded);
150}
151
152/*
153 * Parses an RSA (number of bits, e, n) or DSA key from a string.  Moves the
154 * pointer over the key.  Skips any whitespace at the beginning and at end.
155 */
156
157int
158hostfile_read_key(char **cpp, int *bitsp, Key *ret)
159{
160	char *cp;
161
162	/* Skip leading whitespace. */
163	for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
164		;
165
166	if (key_read(ret, &cp) != 1)
167		return 0;
168
169	/* Skip trailing whitespace. */
170	for (; *cp == ' ' || *cp == '\t'; cp++)
171		;
172
173	/* Return results. */
174	*cpp = cp;
175	if (bitsp != NULL) {
176		if ((*bitsp = key_size(ret)) <= 0)
177			return 0;
178	}
179	return 1;
180}
181
182static int
183hostfile_check_key(int bits, const Key *key, const char *host,
184    const char *filename, u_long linenum)
185{
186	if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL)
187		return 1;
188	if (bits != BN_num_bits(key->rsa->n)) {
189		logit("Warning: %s, line %lu: keysize mismatch for host %s: "
190		    "actual %d vs. announced %d.",
191		    filename, linenum, host, BN_num_bits(key->rsa->n), bits);
192		logit("Warning: replace %d with %d in %s, line %lu.",
193		    bits, BN_num_bits(key->rsa->n), filename, linenum);
194	}
195	return 1;
196}
197
198static HostkeyMarker
199check_markers(char **cpp)
200{
201	char marker[32], *sp, *cp = *cpp;
202	int ret = MRK_NONE;
203
204	while (*cp == '@') {
205		/* Only one marker is allowed */
206		if (ret != MRK_NONE)
207			return MRK_ERROR;
208		/* Markers are terminated by whitespace */
209		if ((sp = strchr(cp, ' ')) == NULL &&
210		    (sp = strchr(cp, '\t')) == NULL)
211			return MRK_ERROR;
212		/* Extract marker for comparison */
213		if (sp <= cp + 1 || sp >= cp + sizeof(marker))
214			return MRK_ERROR;
215		memcpy(marker, cp, sp - cp);
216		marker[sp - cp] = '\0';
217		if (strcmp(marker, CA_MARKER) == 0)
218			ret = MRK_CA;
219		else if (strcmp(marker, REVOKE_MARKER) == 0)
220			ret = MRK_REVOKE;
221		else
222			return MRK_ERROR;
223
224		/* Skip past marker and any whitespace that follows it */
225		cp = sp;
226		for (; *cp == ' ' || *cp == '\t'; cp++)
227			;
228	}
229	*cpp = cp;
230	return ret;
231}
232
233struct hostkeys *
234init_hostkeys(void)
235{
236	struct hostkeys *ret = xcalloc(1, sizeof(*ret));
237
238	ret->entries = NULL;
239	return ret;
240}
241
242void
243load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path)
244{
245	FILE *f;
246	char line[8192];
247	u_long linenum = 0, num_loaded = 0;
248	char *cp, *cp2, *hashed_host;
249	HostkeyMarker marker;
250	Key *key;
251	int kbits;
252
253	if ((f = fopen(path, "r")) == NULL)
254		return;
255	debug3("%s: loading entries for host \"%.100s\" from file \"%s\"",
256	    __func__, host, path);
257	while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) {
258		cp = line;
259
260		/* Skip any leading whitespace, comments and empty lines. */
261		for (; *cp == ' ' || *cp == '\t'; cp++)
262			;
263		if (!*cp || *cp == '#' || *cp == '\n')
264			continue;
265
266		if ((marker = check_markers(&cp)) == MRK_ERROR) {
267			verbose("%s: invalid marker at %s:%lu",
268			    __func__, path, linenum);
269			continue;
270		}
271
272		/* Find the end of the host name portion. */
273		for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
274			;
275
276		/* Check if the host name matches. */
277		if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) {
278			if (*cp != HASH_DELIM)
279				continue;
280			hashed_host = host_hash(host, cp, (u_int) (cp2 - cp));
281			if (hashed_host == NULL) {
282				debug("Invalid hashed host line %lu of %s",
283				    linenum, path);
284				continue;
285			}
286			if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0)
287				continue;
288		}
289
290		/* Got a match.  Skip host name. */
291		cp = cp2;
292
293		/*
294		 * Extract the key from the line.  This will skip any leading
295		 * whitespace.  Ignore badly formatted lines.
296		 */
297		key = key_new(KEY_UNSPEC);
298		if (!hostfile_read_key(&cp, &kbits, key)) {
299			key_free(key);
300			key = key_new(KEY_RSA1);
301			if (!hostfile_read_key(&cp, &kbits, key)) {
302				key_free(key);
303				continue;
304			}
305		}
306		if (!hostfile_check_key(kbits, key, host, path, linenum))
307			continue;
308
309		debug3("%s: found %skey type %s in file %s:%lu", __func__,
310		    marker == MRK_NONE ? "" :
311		    (marker == MRK_CA ? "ca " : "revoked "),
312		    key_type(key), path, linenum);
313		hostkeys->entries = xrealloc(hostkeys->entries,
314		    hostkeys->num_entries + 1, sizeof(*hostkeys->entries));
315		hostkeys->entries[hostkeys->num_entries].host = xstrdup(host);
316		hostkeys->entries[hostkeys->num_entries].file = xstrdup(path);
317		hostkeys->entries[hostkeys->num_entries].line = linenum;
318		hostkeys->entries[hostkeys->num_entries].key = key;
319		hostkeys->entries[hostkeys->num_entries].marker = marker;
320		hostkeys->num_entries++;
321		num_loaded++;
322	}
323	debug3("%s: loaded %lu keys", __func__, num_loaded);
324	fclose(f);
325	return;
326}
327
328void
329free_hostkeys(struct hostkeys *hostkeys)
330{
331	u_int i;
332
333	for (i = 0; i < hostkeys->num_entries; i++) {
334		free(hostkeys->entries[i].host);
335		free(hostkeys->entries[i].file);
336		key_free(hostkeys->entries[i].key);
337		bzero(hostkeys->entries + i, sizeof(*hostkeys->entries));
338	}
339	free(hostkeys->entries);
340	bzero(hostkeys, sizeof(*hostkeys));
341	free(hostkeys);
342}
343
344static int
345check_key_not_revoked(struct hostkeys *hostkeys, Key *k)
346{
347	int is_cert = key_is_cert(k);
348	u_int i;
349
350	for (i = 0; i < hostkeys->num_entries; i++) {
351		if (hostkeys->entries[i].marker != MRK_REVOKE)
352			continue;
353		if (key_equal_public(k, hostkeys->entries[i].key))
354			return -1;
355		if (is_cert &&
356		    key_equal_public(k->cert->signature_key,
357		    hostkeys->entries[i].key))
358			return -1;
359	}
360	return 0;
361}
362
363/*
364 * Match keys against a specified key, or look one up by key type.
365 *
366 * If looking for a keytype (key == NULL) and one is found then return
367 * HOST_FOUND, otherwise HOST_NEW.
368 *
369 * If looking for a key (key != NULL):
370 *  1. If the key is a cert and a matching CA is found, return HOST_OK
371 *  2. If the key is not a cert and a matching key is found, return HOST_OK
372 *  3. If no key matches but a key with a different type is found, then
373 *     return HOST_CHANGED
374 *  4. If no matching keys are found, then return HOST_NEW.
375 *
376 * Finally, check any found key is not revoked.
377 */
378static HostStatus
379check_hostkeys_by_key_or_type(struct hostkeys *hostkeys,
380    Key *k, int keytype, const struct hostkey_entry **found)
381{
382	u_int i;
383	HostStatus end_return = HOST_NEW;
384	int want_cert = key_is_cert(k);
385	HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE;
386	int proto = (k ? k->type : keytype) == KEY_RSA1 ? 1 : 2;
387
388	if (found != NULL)
389		*found = NULL;
390
391	for (i = 0; i < hostkeys->num_entries; i++) {
392		if (proto == 1 && hostkeys->entries[i].key->type != KEY_RSA1)
393			continue;
394		if (proto == 2 && hostkeys->entries[i].key->type == KEY_RSA1)
395			continue;
396		if (hostkeys->entries[i].marker != want_marker)
397			continue;
398		if (k == NULL) {
399			if (hostkeys->entries[i].key->type != keytype)
400				continue;
401			end_return = HOST_FOUND;
402			if (found != NULL)
403				*found = hostkeys->entries + i;
404			k = hostkeys->entries[i].key;
405			break;
406		}
407		if (want_cert) {
408			if (key_equal_public(k->cert->signature_key,
409			    hostkeys->entries[i].key)) {
410				/* A matching CA exists */
411				end_return = HOST_OK;
412				if (found != NULL)
413					*found = hostkeys->entries + i;
414				break;
415			}
416		} else {
417			if (key_equal(k, hostkeys->entries[i].key)) {
418				end_return = HOST_OK;
419				if (found != NULL)
420					*found = hostkeys->entries + i;
421				break;
422			}
423			/* A non-maching key exists */
424			end_return = HOST_CHANGED;
425			if (found != NULL)
426				*found = hostkeys->entries + i;
427		}
428	}
429	if (check_key_not_revoked(hostkeys, k) != 0) {
430		end_return = HOST_REVOKED;
431		if (found != NULL)
432			*found = NULL;
433	}
434	return end_return;
435}
436
437HostStatus
438check_key_in_hostkeys(struct hostkeys *hostkeys, Key *key,
439    const struct hostkey_entry **found)
440{
441	if (key == NULL)
442		fatal("no key to look up");
443	return check_hostkeys_by_key_or_type(hostkeys, key, 0, found);
444}
445
446int
447lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype,
448    const struct hostkey_entry **found)
449{
450	return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype,
451	    found) == HOST_FOUND);
452}
453
454/*
455 * Appends an entry to the host file.  Returns false if the entry could not
456 * be appended.
457 */
458
459int
460add_host_to_hostfile(const char *filename, const char *host, const Key *key,
461    int store_hash)
462{
463	FILE *f;
464	int success = 0;
465	char *hashed_host = NULL;
466
467	if (key == NULL)
468		return 1;	/* XXX ? */
469	f = fopen(filename, "a");
470	if (!f)
471		return 0;
472
473	if (store_hash) {
474		if ((hashed_host = host_hash(host, NULL, 0)) == NULL) {
475			error("add_host_to_hostfile: host_hash failed");
476			fclose(f);
477			return 0;
478		}
479	}
480	fprintf(f, "%s ", store_hash ? hashed_host : host);
481
482	if (key_write(key, f)) {
483		success = 1;
484	} else {
485		error("add_host_to_hostfile: saving key in %s failed", filename);
486	}
487	fprintf(f, "\n");
488	fclose(f);
489	return success;
490}
491