xref: /openssh-portable/hostfile.c (revision dc1b4584)
1*dc1b4584Sdjm@openbsd.org /* $OpenBSD: hostfile.c,v 1.90 2021/04/03 06:58:30 djm Exp $ */
2d4a8b7e3SDamien Miller /*
395def098SDamien Miller  * Author: Tatu Ylonen <ylo@cs.hut.fi>
495def098SDamien Miller  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
595def098SDamien Miller  *                    All rights reserved
695def098SDamien Miller  * Functions for manipulating the known hosts files.
795def098SDamien Miller  *
8e4340be5SDamien Miller  * As far as I am concerned, the code I have written for this software
9e4340be5SDamien Miller  * can be used freely for any purpose.  Any derived versions of this
10e4340be5SDamien Miller  * software must be clearly marked as such, and if the derived work is
11e4340be5SDamien Miller  * incompatible with the protocol description in the RFC file, it must be
12e4340be5SDamien Miller  * called by a name other than "ssh" or "Secure Shell".
13e4340be5SDamien Miller  *
14e4340be5SDamien Miller  *
15e4340be5SDamien Miller  * Copyright (c) 1999, 2000 Markus Friedl.  All rights reserved.
16e4340be5SDamien Miller  * Copyright (c) 1999 Niels Provos.  All rights reserved.
17e4340be5SDamien Miller  *
18e4340be5SDamien Miller  * Redistribution and use in source and binary forms, with or without
19e4340be5SDamien Miller  * modification, are permitted provided that the following conditions
20e4340be5SDamien Miller  * are met:
21e4340be5SDamien Miller  * 1. Redistributions of source code must retain the above copyright
22e4340be5SDamien Miller  *    notice, this list of conditions and the following disclaimer.
23e4340be5SDamien Miller  * 2. Redistributions in binary form must reproduce the above copyright
24e4340be5SDamien Miller  *    notice, this list of conditions and the following disclaimer in the
25e4340be5SDamien Miller  *    documentation and/or other materials provided with the distribution.
26e4340be5SDamien Miller  *
27e4340be5SDamien Miller  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
28e4340be5SDamien Miller  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29e4340be5SDamien Miller  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
30e4340be5SDamien Miller  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
31e4340be5SDamien Miller  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32e4340be5SDamien Miller  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33e4340be5SDamien Miller  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34e4340be5SDamien Miller  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35e4340be5SDamien Miller  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
36e4340be5SDamien Miller  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37d4a8b7e3SDamien Miller  */
38d4a8b7e3SDamien Miller 
39d4a8b7e3SDamien Miller #include "includes.h"
40e1776155SDamien Miller 
418ec8c3e9SDamien Miller #include <sys/types.h>
428d4f8725Sdjm@openbsd.org #include <sys/stat.h>
438ec8c3e9SDamien Miller 
448ec8c3e9SDamien Miller #include <netinet/in.h>
458ec8c3e9SDamien Miller 
46c29811ccSdjm@openbsd.org #include <errno.h>
47e7a1e5cfSDamien Miller #include <resolv.h>
48ded319ccSDamien Miller #include <stdarg.h>
49a7a73ee3SDamien Miller #include <stdio.h>
50e7a1e5cfSDamien Miller #include <stdlib.h>
51e7a1e5cfSDamien Miller #include <string.h>
528d4f8725Sdjm@openbsd.org #include <unistd.h>
53e7a1e5cfSDamien Miller 
54d7834353SDamien Miller #include "xmalloc.h"
55450a7a1fSDamien Miller #include "match.h"
561129dcfcSdjm@openbsd.org #include "sshkey.h"
57450a7a1fSDamien Miller #include "hostfile.h"
58226cfa03SBen Lindstrom #include "log.h"
59d925dcd8SDamien Miller #include "misc.h"
6074344c3cSdtucker@openbsd.org #include "pathnames.h"
611129dcfcSdjm@openbsd.org #include "ssherr.h"
62b3051d01SDamien Miller #include "digest.h"
634e8d937aSDamien Miller #include "hmac.h"
643b44f251Sdjm@openbsd.org #include "sshbuf.h"
65d925dcd8SDamien Miller 
66c29811ccSdjm@openbsd.org /* XXX hmac is too easy to dictionary attack; use bcrypt? */
67c29811ccSdjm@openbsd.org 
68e1776155SDamien Miller static int
extract_salt(const char * s,u_int l,u_char * salt,size_t salt_len)69ce986546SDamien Miller extract_salt(const char *s, u_int l, u_char *salt, size_t salt_len)
70e1776155SDamien Miller {
71e1776155SDamien Miller 	char *p, *b64salt;
72e1776155SDamien Miller 	u_int b64len;
73e1776155SDamien Miller 	int ret;
74e1776155SDamien Miller 
75e1776155SDamien Miller 	if (l < sizeof(HASH_MAGIC) - 1) {
76e1776155SDamien Miller 		debug2("extract_salt: string too short");
77e1776155SDamien Miller 		return (-1);
78e1776155SDamien Miller 	}
79e1776155SDamien Miller 	if (strncmp(s, HASH_MAGIC, sizeof(HASH_MAGIC) - 1) != 0) {
80e1776155SDamien Miller 		debug2("extract_salt: invalid magic identifier");
81e1776155SDamien Miller 		return (-1);
82e1776155SDamien Miller 	}
83e1776155SDamien Miller 	s += sizeof(HASH_MAGIC) - 1;
84e1776155SDamien Miller 	l -= sizeof(HASH_MAGIC) - 1;
85e1776155SDamien Miller 	if ((p = memchr(s, HASH_DELIM, l)) == NULL) {
86e1776155SDamien Miller 		debug2("extract_salt: missing salt termination character");
87e1776155SDamien Miller 		return (-1);
88e1776155SDamien Miller 	}
89e1776155SDamien Miller 
90e1776155SDamien Miller 	b64len = p - s;
91e1776155SDamien Miller 	/* Sanity check */
92e1776155SDamien Miller 	if (b64len == 0 || b64len > 1024) {
93e1776155SDamien Miller 		debug2("extract_salt: bad encoded salt length %u", b64len);
94e1776155SDamien Miller 		return (-1);
95e1776155SDamien Miller 	}
96e1776155SDamien Miller 	b64salt = xmalloc(1 + b64len);
97e1776155SDamien Miller 	memcpy(b64salt, s, b64len);
98e1776155SDamien Miller 	b64salt[b64len] = '\0';
99e1776155SDamien Miller 
100e1776155SDamien Miller 	ret = __b64_pton(b64salt, salt, salt_len);
101a627d42eSDarren Tucker 	free(b64salt);
102e1776155SDamien Miller 	if (ret == -1) {
103e1776155SDamien Miller 		debug2("extract_salt: salt decode error");
104e1776155SDamien Miller 		return (-1);
105e1776155SDamien Miller 	}
1064e8d937aSDamien Miller 	if (ret != (int)ssh_hmac_bytes(SSH_DIGEST_SHA1)) {
1074e8d937aSDamien Miller 		debug2("extract_salt: expected salt len %zd, got %d",
1084e8d937aSDamien Miller 		    ssh_hmac_bytes(SSH_DIGEST_SHA1), ret);
109e1776155SDamien Miller 		return (-1);
110e1776155SDamien Miller 	}
111e1776155SDamien Miller 
112e1776155SDamien Miller 	return (0);
113e1776155SDamien Miller }
114e1776155SDamien Miller 
115e1776155SDamien Miller char *
host_hash(const char * host,const char * name_from_hostfile,u_int src_len)116e1776155SDamien Miller host_hash(const char *host, const char *name_from_hostfile, u_int src_len)
117e1776155SDamien Miller {
1184e8d937aSDamien Miller 	struct ssh_hmac_ctx *ctx;
119ce986546SDamien Miller 	u_char salt[256], result[256];
120ce986546SDamien Miller 	char uu_salt[512], uu_result[512];
121e1776155SDamien Miller 	static char encoded[1024];
12210363563Stedu@openbsd.org 	u_int len;
123e1776155SDamien Miller 
1244e8d937aSDamien Miller 	len = ssh_digest_bytes(SSH_DIGEST_SHA1);
125e1776155SDamien Miller 
126e1776155SDamien Miller 	if (name_from_hostfile == NULL) {
127e1776155SDamien Miller 		/* Create new salt */
12810363563Stedu@openbsd.org 		arc4random_buf(salt, len);
129e1776155SDamien Miller 	} else {
130e1776155SDamien Miller 		/* Extract salt from known host entry */
131e1776155SDamien Miller 		if (extract_salt(name_from_hostfile, src_len, salt,
132e1776155SDamien Miller 		    sizeof(salt)) == -1)
133e1776155SDamien Miller 			return (NULL);
134e1776155SDamien Miller 	}
135e1776155SDamien Miller 
1364e8d937aSDamien Miller 	if ((ctx = ssh_hmac_start(SSH_DIGEST_SHA1)) == NULL ||
1374e8d937aSDamien Miller 	    ssh_hmac_init(ctx, salt, len) < 0 ||
1384e8d937aSDamien Miller 	    ssh_hmac_update(ctx, host, strlen(host)) < 0 ||
1394e8d937aSDamien Miller 	    ssh_hmac_final(ctx, result, sizeof(result)))
140816036f1Sdjm@openbsd.org 		fatal_f("ssh_hmac failed");
1414e8d937aSDamien Miller 	ssh_hmac_free(ctx);
142e1776155SDamien Miller 
143e1776155SDamien Miller 	if (__b64_ntop(salt, len, uu_salt, sizeof(uu_salt)) == -1 ||
144e1776155SDamien Miller 	    __b64_ntop(result, len, uu_result, sizeof(uu_result)) == -1)
145816036f1Sdjm@openbsd.org 		fatal_f("__b64_ntop failed");
146e1776155SDamien Miller 
147e1776155SDamien Miller 	snprintf(encoded, sizeof(encoded), "%s%s%c%s", HASH_MAGIC, uu_salt,
148e1776155SDamien Miller 	    HASH_DELIM, uu_result);
149e1776155SDamien Miller 
150e1776155SDamien Miller 	return (encoded);
151e1776155SDamien Miller }
152d4a8b7e3SDamien Miller 
1535428f646SDamien Miller /*
154450a7a1fSDamien Miller  * Parses an RSA (number of bits, e, n) or DSA key from a string.  Moves the
155450a7a1fSDamien Miller  * pointer over the key.  Skips any whitespace at the beginning and at end.
1565428f646SDamien Miller  */
157d4a8b7e3SDamien Miller 
1585b2aea94SDamien Miller int
hostfile_read_key(char ** cpp,u_int * bitsp,struct sshkey * ret)1591129dcfcSdjm@openbsd.org hostfile_read_key(char **cpp, u_int *bitsp, struct sshkey *ret)
160d4a8b7e3SDamien Miller {
161d4a8b7e3SDamien Miller 	char *cp;
162d4a8b7e3SDamien Miller 
163d4a8b7e3SDamien Miller 	/* Skip leading whitespace. */
1645428f646SDamien Miller 	for (cp = *cpp; *cp == ' ' || *cp == '\t'; cp++)
1655428f646SDamien Miller 		;
166d4a8b7e3SDamien Miller 
167696fb429Sdtucker@openbsd.org 	if (sshkey_read(ret, &cp) != 0)
168d4a8b7e3SDamien Miller 		return 0;
169d4a8b7e3SDamien Miller 
170d4a8b7e3SDamien Miller 	/* Skip trailing whitespace. */
1715428f646SDamien Miller 	for (; *cp == ' ' || *cp == '\t'; cp++)
1725428f646SDamien Miller 		;
173d4a8b7e3SDamien Miller 
174d4a8b7e3SDamien Miller 	/* Return results. */
175d4a8b7e3SDamien Miller 	*cpp = cp;
1761129dcfcSdjm@openbsd.org 	if (bitsp != NULL)
1771129dcfcSdjm@openbsd.org 		*bitsp = sshkey_size(ret);
178d4a8b7e3SDamien Miller 	return 1;
179d4a8b7e3SDamien Miller }
180d4a8b7e3SDamien Miller 
181d925dcd8SDamien Miller static HostkeyMarker
check_markers(char ** cpp)1821aed65ebSDamien Miller check_markers(char **cpp)
1831aed65ebSDamien Miller {
1841aed65ebSDamien Miller 	char marker[32], *sp, *cp = *cpp;
1851aed65ebSDamien Miller 	int ret = MRK_NONE;
1861aed65ebSDamien Miller 
1871aed65ebSDamien Miller 	while (*cp == '@') {
1881aed65ebSDamien Miller 		/* Only one marker is allowed */
1891aed65ebSDamien Miller 		if (ret != MRK_NONE)
1901aed65ebSDamien Miller 			return MRK_ERROR;
1911aed65ebSDamien Miller 		/* Markers are terminated by whitespace */
1921aed65ebSDamien Miller 		if ((sp = strchr(cp, ' ')) == NULL &&
1931aed65ebSDamien Miller 		    (sp = strchr(cp, '\t')) == NULL)
1941aed65ebSDamien Miller 			return MRK_ERROR;
1951aed65ebSDamien Miller 		/* Extract marker for comparison */
1961aed65ebSDamien Miller 		if (sp <= cp + 1 || sp >= cp + sizeof(marker))
1971aed65ebSDamien Miller 			return MRK_ERROR;
1981aed65ebSDamien Miller 		memcpy(marker, cp, sp - cp);
1991aed65ebSDamien Miller 		marker[sp - cp] = '\0';
2001aed65ebSDamien Miller 		if (strcmp(marker, CA_MARKER) == 0)
2011aed65ebSDamien Miller 			ret = MRK_CA;
2021aed65ebSDamien Miller 		else if (strcmp(marker, REVOKE_MARKER) == 0)
2031aed65ebSDamien Miller 			ret = MRK_REVOKE;
2041aed65ebSDamien Miller 		else
2051aed65ebSDamien Miller 			return MRK_ERROR;
2061aed65ebSDamien Miller 
2071aed65ebSDamien Miller 		/* Skip past marker and any whitespace that follows it */
2081aed65ebSDamien Miller 		cp = sp;
2091aed65ebSDamien Miller 		for (; *cp == ' ' || *cp == '\t'; cp++)
2101aed65ebSDamien Miller 			;
2111aed65ebSDamien Miller 	}
2121aed65ebSDamien Miller 	*cpp = cp;
2131aed65ebSDamien Miller 	return ret;
2141aed65ebSDamien Miller }
2151aed65ebSDamien Miller 
216d925dcd8SDamien Miller struct hostkeys *
init_hostkeys(void)217d925dcd8SDamien Miller init_hostkeys(void)
218d925dcd8SDamien Miller {
219d925dcd8SDamien Miller 	struct hostkeys *ret = xcalloc(1, sizeof(*ret));
220d4a8b7e3SDamien Miller 
221d925dcd8SDamien Miller 	ret->entries = NULL;
222d925dcd8SDamien Miller 	return ret;
223d925dcd8SDamien Miller }
224d925dcd8SDamien Miller 
225ec3d065dSdjm@openbsd.org struct load_callback_ctx {
226ec3d065dSdjm@openbsd.org 	const char *host;
227ec3d065dSdjm@openbsd.org 	u_long num_loaded;
228ec3d065dSdjm@openbsd.org 	struct hostkeys *hostkeys;
229ec3d065dSdjm@openbsd.org };
230ec3d065dSdjm@openbsd.org 
231ec3d065dSdjm@openbsd.org static int
record_hostkey(struct hostkey_foreach_line * l,void * _ctx)232ec3d065dSdjm@openbsd.org record_hostkey(struct hostkey_foreach_line *l, void *_ctx)
233ec3d065dSdjm@openbsd.org {
234ec3d065dSdjm@openbsd.org 	struct load_callback_ctx *ctx = (struct load_callback_ctx *)_ctx;
235ec3d065dSdjm@openbsd.org 	struct hostkeys *hostkeys = ctx->hostkeys;
236ec3d065dSdjm@openbsd.org 	struct hostkey_entry *tmp;
237ec3d065dSdjm@openbsd.org 
238ec3d065dSdjm@openbsd.org 	if (l->status == HKF_STATUS_INVALID) {
239398f9ef1Sdjm@openbsd.org 		/* XXX make this verbose() in the future */
240398f9ef1Sdjm@openbsd.org 		debug("%s:%ld: parse error in hostkeys file",
241ec3d065dSdjm@openbsd.org 		    l->path, l->linenum);
242ec3d065dSdjm@openbsd.org 		return 0;
243ec3d065dSdjm@openbsd.org 	}
244ec3d065dSdjm@openbsd.org 
245816036f1Sdjm@openbsd.org 	debug3_f("found %skey type %s in file %s:%lu",
246ec3d065dSdjm@openbsd.org 	    l->marker == MRK_NONE ? "" :
247ec3d065dSdjm@openbsd.org 	    (l->marker == MRK_CA ? "ca " : "revoked "),
248ec3d065dSdjm@openbsd.org 	    sshkey_type(l->key), l->path, l->linenum);
2499e509d4eSderaadt@openbsd.org 	if ((tmp = recallocarray(hostkeys->entries, hostkeys->num_entries,
250ec3d065dSdjm@openbsd.org 	    hostkeys->num_entries + 1, sizeof(*hostkeys->entries))) == NULL)
251ec3d065dSdjm@openbsd.org 		return SSH_ERR_ALLOC_FAIL;
252ec3d065dSdjm@openbsd.org 	hostkeys->entries = tmp;
253ec3d065dSdjm@openbsd.org 	hostkeys->entries[hostkeys->num_entries].host = xstrdup(ctx->host);
254ec3d065dSdjm@openbsd.org 	hostkeys->entries[hostkeys->num_entries].file = xstrdup(l->path);
255ec3d065dSdjm@openbsd.org 	hostkeys->entries[hostkeys->num_entries].line = l->linenum;
256ec3d065dSdjm@openbsd.org 	hostkeys->entries[hostkeys->num_entries].key = l->key;
257ec3d065dSdjm@openbsd.org 	l->key = NULL; /* steal it */
258ec3d065dSdjm@openbsd.org 	hostkeys->entries[hostkeys->num_entries].marker = l->marker;
259b4c7cd11Sdjm@openbsd.org 	hostkeys->entries[hostkeys->num_entries].note = l->note;
260ec3d065dSdjm@openbsd.org 	hostkeys->num_entries++;
261ec3d065dSdjm@openbsd.org 	ctx->num_loaded++;
262ec3d065dSdjm@openbsd.org 
263ec3d065dSdjm@openbsd.org 	return 0;
264ec3d065dSdjm@openbsd.org }
265ec3d065dSdjm@openbsd.org 
266d925dcd8SDamien Miller void
load_hostkeys_file(struct hostkeys * hostkeys,const char * host,const char * path,FILE * f,u_int note)267b4c7cd11Sdjm@openbsd.org load_hostkeys_file(struct hostkeys *hostkeys, const char *host,
268b4c7cd11Sdjm@openbsd.org     const char *path, FILE *f, u_int note)
269d4a8b7e3SDamien Miller {
270ec3d065dSdjm@openbsd.org 	int r;
271ec3d065dSdjm@openbsd.org 	struct load_callback_ctx ctx;
272d4a8b7e3SDamien Miller 
273ec3d065dSdjm@openbsd.org 	ctx.host = host;
274ec3d065dSdjm@openbsd.org 	ctx.num_loaded = 0;
275ec3d065dSdjm@openbsd.org 	ctx.hostkeys = hostkeys;
276d4a8b7e3SDamien Miller 
277b4c7cd11Sdjm@openbsd.org 	if ((r = hostkeys_foreach_file(path, f, record_hostkey, &ctx, host,
278b4c7cd11Sdjm@openbsd.org 	    NULL, HKF_WANT_MATCH|HKF_WANT_PARSE_KEY, note)) != 0) {
279ec3d065dSdjm@openbsd.org 		if (r != SSH_ERR_SYSTEM_ERROR && errno != ENOENT)
280816036f1Sdjm@openbsd.org 			debug_fr(r, "hostkeys_foreach failed for %s", path);
281d925dcd8SDamien Miller 	}
282ec3d065dSdjm@openbsd.org 	if (ctx.num_loaded != 0)
283816036f1Sdjm@openbsd.org 		debug3_f("loaded %lu keys from %s", ctx.num_loaded, host);
2841aed65ebSDamien Miller }
2851aed65ebSDamien Miller 
286d925dcd8SDamien Miller void
load_hostkeys(struct hostkeys * hostkeys,const char * host,const char * path,u_int note)287b4c7cd11Sdjm@openbsd.org load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path,
288b4c7cd11Sdjm@openbsd.org     u_int note)
289b4c7cd11Sdjm@openbsd.org {
290b4c7cd11Sdjm@openbsd.org 	FILE *f;
291b4c7cd11Sdjm@openbsd.org 
292b4c7cd11Sdjm@openbsd.org 	if ((f = fopen(path, "r")) == NULL) {
293b4c7cd11Sdjm@openbsd.org 		debug_f("fopen %s: %s", path, strerror(errno));
294b4c7cd11Sdjm@openbsd.org 		return;
295b4c7cd11Sdjm@openbsd.org 	}
296b4c7cd11Sdjm@openbsd.org 
297b4c7cd11Sdjm@openbsd.org 	load_hostkeys_file(hostkeys, host, path, f, note);
298b4c7cd11Sdjm@openbsd.org 	fclose(f);
299b4c7cd11Sdjm@openbsd.org }
300b4c7cd11Sdjm@openbsd.org 
301b4c7cd11Sdjm@openbsd.org void
free_hostkeys(struct hostkeys * hostkeys)302d925dcd8SDamien Miller free_hostkeys(struct hostkeys *hostkeys)
303d925dcd8SDamien Miller {
304d925dcd8SDamien Miller 	u_int i;
305d925dcd8SDamien Miller 
306d925dcd8SDamien Miller 	for (i = 0; i < hostkeys->num_entries; i++) {
307a627d42eSDarren Tucker 		free(hostkeys->entries[i].host);
308a627d42eSDarren Tucker 		free(hostkeys->entries[i].file);
3091129dcfcSdjm@openbsd.org 		sshkey_free(hostkeys->entries[i].key);
3101d2c4564SDamien Miller 		explicit_bzero(hostkeys->entries + i, sizeof(*hostkeys->entries));
311d4a8b7e3SDamien Miller 	}
312a627d42eSDarren Tucker 	free(hostkeys->entries);
313d5ba1c03Sjsg@openbsd.org 	freezero(hostkeys, sizeof(*hostkeys));
314d925dcd8SDamien Miller }
315d925dcd8SDamien Miller 
316d925dcd8SDamien Miller static int
check_key_not_revoked(struct hostkeys * hostkeys,struct sshkey * k)3171129dcfcSdjm@openbsd.org check_key_not_revoked(struct hostkeys *hostkeys, struct sshkey *k)
318d925dcd8SDamien Miller {
3191129dcfcSdjm@openbsd.org 	int is_cert = sshkey_is_cert(k);
320d925dcd8SDamien Miller 	u_int i;
321d925dcd8SDamien Miller 
322d925dcd8SDamien Miller 	for (i = 0; i < hostkeys->num_entries; i++) {
323d925dcd8SDamien Miller 		if (hostkeys->entries[i].marker != MRK_REVOKE)
324d925dcd8SDamien Miller 			continue;
3251129dcfcSdjm@openbsd.org 		if (sshkey_equal_public(k, hostkeys->entries[i].key))
326d925dcd8SDamien Miller 			return -1;
327fbff605eSmarkus@openbsd.org 		if (is_cert && k != NULL &&
3281129dcfcSdjm@openbsd.org 		    sshkey_equal_public(k->cert->signature_key,
329d925dcd8SDamien Miller 		    hostkeys->entries[i].key))
330d925dcd8SDamien Miller 			return -1;
331d925dcd8SDamien Miller 	}
332d925dcd8SDamien Miller 	return 0;
333d925dcd8SDamien Miller }
334d925dcd8SDamien Miller 
3355428f646SDamien Miller /*
336d925dcd8SDamien Miller  * Match keys against a specified key, or look one up by key type.
337d925dcd8SDamien Miller  *
338d925dcd8SDamien Miller  * If looking for a keytype (key == NULL) and one is found then return
339d925dcd8SDamien Miller  * HOST_FOUND, otherwise HOST_NEW.
340d925dcd8SDamien Miller  *
341d925dcd8SDamien Miller  * If looking for a key (key != NULL):
342d925dcd8SDamien Miller  *  1. If the key is a cert and a matching CA is found, return HOST_OK
343d925dcd8SDamien Miller  *  2. If the key is not a cert and a matching key is found, return HOST_OK
344d925dcd8SDamien Miller  *  3. If no key matches but a key with a different type is found, then
345d925dcd8SDamien Miller  *     return HOST_CHANGED
346d925dcd8SDamien Miller  *  4. If no matching keys are found, then return HOST_NEW.
347d925dcd8SDamien Miller  *
348d925dcd8SDamien Miller  * Finally, check any found key is not revoked.
3495428f646SDamien Miller  */
350d925dcd8SDamien Miller static HostStatus
check_hostkeys_by_key_or_type(struct hostkeys * hostkeys,struct sshkey * k,int keytype,int nid,const struct hostkey_entry ** found)351d925dcd8SDamien Miller check_hostkeys_by_key_or_type(struct hostkeys *hostkeys,
352af889a40Sdjm@openbsd.org     struct sshkey *k, int keytype, int nid, const struct hostkey_entry **found)
353d925dcd8SDamien Miller {
354d925dcd8SDamien Miller 	u_int i;
355d925dcd8SDamien Miller 	HostStatus end_return = HOST_NEW;
3561129dcfcSdjm@openbsd.org 	int want_cert = sshkey_is_cert(k);
357d925dcd8SDamien Miller 	HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE;
358d925dcd8SDamien Miller 
359d925dcd8SDamien Miller 	if (found != NULL)
360d925dcd8SDamien Miller 		*found = NULL;
361d925dcd8SDamien Miller 
362d925dcd8SDamien Miller 	for (i = 0; i < hostkeys->num_entries; i++) {
363d925dcd8SDamien Miller 		if (hostkeys->entries[i].marker != want_marker)
364d925dcd8SDamien Miller 			continue;
365d925dcd8SDamien Miller 		if (k == NULL) {
366d925dcd8SDamien Miller 			if (hostkeys->entries[i].key->type != keytype)
367d925dcd8SDamien Miller 				continue;
368af889a40Sdjm@openbsd.org 			if (nid != -1 &&
369af889a40Sdjm@openbsd.org 			    sshkey_type_plain(keytype) == KEY_ECDSA &&
370af889a40Sdjm@openbsd.org 			    hostkeys->entries[i].key->ecdsa_nid != nid)
371af889a40Sdjm@openbsd.org 				continue;
372d925dcd8SDamien Miller 			end_return = HOST_FOUND;
373d925dcd8SDamien Miller 			if (found != NULL)
374d925dcd8SDamien Miller 				*found = hostkeys->entries + i;
375d925dcd8SDamien Miller 			k = hostkeys->entries[i].key;
376d925dcd8SDamien Miller 			break;
377d925dcd8SDamien Miller 		}
378d925dcd8SDamien Miller 		if (want_cert) {
3791129dcfcSdjm@openbsd.org 			if (sshkey_equal_public(k->cert->signature_key,
380d925dcd8SDamien Miller 			    hostkeys->entries[i].key)) {
381d925dcd8SDamien Miller 				/* A matching CA exists */
382d925dcd8SDamien Miller 				end_return = HOST_OK;
383d925dcd8SDamien Miller 				if (found != NULL)
384d925dcd8SDamien Miller 					*found = hostkeys->entries + i;
385d925dcd8SDamien Miller 				break;
386d925dcd8SDamien Miller 			}
387d925dcd8SDamien Miller 		} else {
3881129dcfcSdjm@openbsd.org 			if (sshkey_equal(k, hostkeys->entries[i].key)) {
389d925dcd8SDamien Miller 				end_return = HOST_OK;
390d925dcd8SDamien Miller 				if (found != NULL)
391d925dcd8SDamien Miller 					*found = hostkeys->entries + i;
392d925dcd8SDamien Miller 				break;
393d925dcd8SDamien Miller 			}
394*dc1b4584Sdjm@openbsd.org 			/* A non-matching key exists */
395d4a8b7e3SDamien Miller 			end_return = HOST_CHANGED;
396d925dcd8SDamien Miller 			if (found != NULL)
397d925dcd8SDamien Miller 				*found = hostkeys->entries + i;
398d4a8b7e3SDamien Miller 		}
399d925dcd8SDamien Miller 	}
400d925dcd8SDamien Miller 	if (check_key_not_revoked(hostkeys, k) != 0) {
401d925dcd8SDamien Miller 		end_return = HOST_REVOKED;
402d925dcd8SDamien Miller 		if (found != NULL)
403d925dcd8SDamien Miller 			*found = NULL;
404d925dcd8SDamien Miller 	}
405d4a8b7e3SDamien Miller 	return end_return;
406d4a8b7e3SDamien Miller }
407d4a8b7e3SDamien Miller 
4083ed66405SBen Lindstrom HostStatus
check_key_in_hostkeys(struct hostkeys * hostkeys,struct sshkey * key,const struct hostkey_entry ** found)4091129dcfcSdjm@openbsd.org check_key_in_hostkeys(struct hostkeys *hostkeys, struct sshkey *key,
410d925dcd8SDamien Miller     const struct hostkey_entry **found)
4113ed66405SBen Lindstrom {
4123ed66405SBen Lindstrom 	if (key == NULL)
4133ed66405SBen Lindstrom 		fatal("no key to look up");
414af889a40Sdjm@openbsd.org 	return check_hostkeys_by_key_or_type(hostkeys, key, 0, -1, found);
4153ed66405SBen Lindstrom }
4163ed66405SBen Lindstrom 
4173ed66405SBen Lindstrom int
lookup_key_in_hostkeys_by_type(struct hostkeys * hostkeys,int keytype,int nid,const struct hostkey_entry ** found)418af889a40Sdjm@openbsd.org lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, int nid,
419d925dcd8SDamien Miller     const struct hostkey_entry **found)
4203ed66405SBen Lindstrom {
421af889a40Sdjm@openbsd.org 	return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype, nid,
422d925dcd8SDamien Miller 	    found) == HOST_FOUND);
4233ed66405SBen Lindstrom }
4243ed66405SBen Lindstrom 
42505a65140Sdjm@openbsd.org int
lookup_marker_in_hostkeys(struct hostkeys * hostkeys,int want_marker)42605a65140Sdjm@openbsd.org lookup_marker_in_hostkeys(struct hostkeys *hostkeys, int want_marker)
42705a65140Sdjm@openbsd.org {
42805a65140Sdjm@openbsd.org 	u_int i;
42905a65140Sdjm@openbsd.org 
43005a65140Sdjm@openbsd.org 	for (i = 0; i < hostkeys->num_entries; i++) {
43105a65140Sdjm@openbsd.org 		if (hostkeys->entries[i].marker == (HostkeyMarker)want_marker)
43205a65140Sdjm@openbsd.org 			return 1;
43305a65140Sdjm@openbsd.org 	}
43405a65140Sdjm@openbsd.org 	return 0;
43505a65140Sdjm@openbsd.org }
43605a65140Sdjm@openbsd.org 
4378d4f8725Sdjm@openbsd.org static int
write_host_entry(FILE * f,const char * host,const char * ip,const struct sshkey * key,int store_hash)4386c5c9497Sdjm@openbsd.org write_host_entry(FILE *f, const char *host, const char *ip,
4398d4f8725Sdjm@openbsd.org     const struct sshkey *key, int store_hash)
4408d4f8725Sdjm@openbsd.org {
4418d4f8725Sdjm@openbsd.org 	int r, success = 0;
442db259720Sdjm@openbsd.org 	char *hashed_host = NULL, *lhost;
443db259720Sdjm@openbsd.org 
444db259720Sdjm@openbsd.org 	lhost = xstrdup(host);
445db259720Sdjm@openbsd.org 	lowercase(lhost);
4468d4f8725Sdjm@openbsd.org 
4478d4f8725Sdjm@openbsd.org 	if (store_hash) {
448db259720Sdjm@openbsd.org 		if ((hashed_host = host_hash(lhost, NULL, 0)) == NULL) {
449816036f1Sdjm@openbsd.org 			error_f("host_hash failed");
450db259720Sdjm@openbsd.org 			free(lhost);
4518d4f8725Sdjm@openbsd.org 			return 0;
4528d4f8725Sdjm@openbsd.org 		}
4536c5c9497Sdjm@openbsd.org 		fprintf(f, "%s ", hashed_host);
4546c5c9497Sdjm@openbsd.org 	} else if (ip != NULL)
455db259720Sdjm@openbsd.org 		fprintf(f, "%s,%s ", lhost, ip);
456db259720Sdjm@openbsd.org 	else {
457db259720Sdjm@openbsd.org 		fprintf(f, "%s ", lhost);
458db259720Sdjm@openbsd.org 	}
459db259720Sdjm@openbsd.org 	free(lhost);
4608d4f8725Sdjm@openbsd.org 	if ((r = sshkey_write(key, f)) == 0)
4618d4f8725Sdjm@openbsd.org 		success = 1;
4628d4f8725Sdjm@openbsd.org 	else
463816036f1Sdjm@openbsd.org 		error_fr(r, "sshkey_write");
4648d4f8725Sdjm@openbsd.org 	fputc('\n', f);
46504c06d04Sdjm@openbsd.org 	/* If hashing is enabled, the IP address needs to go on its own line */
46604c06d04Sdjm@openbsd.org 	if (success && store_hash && ip != NULL)
46704c06d04Sdjm@openbsd.org 		success = write_host_entry(f, ip, NULL, key, 1);
4688d4f8725Sdjm@openbsd.org 	return success;
4698d4f8725Sdjm@openbsd.org }
4708d4f8725Sdjm@openbsd.org 
4715428f646SDamien Miller /*
47274344c3cSdtucker@openbsd.org  * Create user ~/.ssh directory if it doesn't exist and we want to write to it.
47374344c3cSdtucker@openbsd.org  * If notify is set, a message will be emitted if the directory is created.
47474344c3cSdtucker@openbsd.org  */
47574344c3cSdtucker@openbsd.org void
hostfile_create_user_ssh_dir(const char * filename,int notify)47674344c3cSdtucker@openbsd.org hostfile_create_user_ssh_dir(const char *filename, int notify)
47774344c3cSdtucker@openbsd.org {
47874344c3cSdtucker@openbsd.org 	char *dotsshdir = NULL, *p;
47974344c3cSdtucker@openbsd.org 	size_t len;
48074344c3cSdtucker@openbsd.org 	struct stat st;
48174344c3cSdtucker@openbsd.org 
48274344c3cSdtucker@openbsd.org 	if ((p = strrchr(filename, '/')) == NULL)
48374344c3cSdtucker@openbsd.org 		return;
48474344c3cSdtucker@openbsd.org 	len = p - filename;
48574344c3cSdtucker@openbsd.org 	dotsshdir = tilde_expand_filename("~/" _PATH_SSH_USER_DIR, getuid());
486976c4f86Sdjm@openbsd.org 	if (strlen(dotsshdir) > len || strncmp(filename, dotsshdir, len) != 0)
487976c4f86Sdjm@openbsd.org 		goto out; /* not ~/.ssh prefixed */
488976c4f86Sdjm@openbsd.org 	if (stat(dotsshdir, &st) == 0)
489976c4f86Sdjm@openbsd.org 		goto out; /* dir already exists */
49074344c3cSdtucker@openbsd.org 	else if (errno != ENOENT)
49174344c3cSdtucker@openbsd.org 		error("Could not stat %s: %s", dotsshdir, strerror(errno));
49274344c3cSdtucker@openbsd.org 	else {
49332b2502aSDamien Miller #ifdef WITH_SELINUX
49474344c3cSdtucker@openbsd.org 		ssh_selinux_setfscreatecon(dotsshdir);
49532b2502aSDamien Miller #endif
49674344c3cSdtucker@openbsd.org 		if (mkdir(dotsshdir, 0700) == -1)
49774344c3cSdtucker@openbsd.org 			error("Could not create directory '%.200s' (%s).",
49874344c3cSdtucker@openbsd.org 			    dotsshdir, strerror(errno));
49974344c3cSdtucker@openbsd.org 		else if (notify)
50074344c3cSdtucker@openbsd.org 			logit("Created directory '%s'.", dotsshdir);
50132b2502aSDamien Miller #ifdef WITH_SELINUX
50274344c3cSdtucker@openbsd.org 		ssh_selinux_setfscreatecon(NULL);
50332b2502aSDamien Miller #endif
50474344c3cSdtucker@openbsd.org 	}
505976c4f86Sdjm@openbsd.org  out:
50674344c3cSdtucker@openbsd.org 	free(dotsshdir);
50774344c3cSdtucker@openbsd.org }
50874344c3cSdtucker@openbsd.org 
50974344c3cSdtucker@openbsd.org /*
5105428f646SDamien Miller  * Appends an entry to the host file.  Returns false if the entry could not
5115428f646SDamien Miller  * be appended.
5125428f646SDamien Miller  */
513d4a8b7e3SDamien Miller int
add_host_to_hostfile(const char * filename,const char * host,const struct sshkey * key,int store_hash)5141129dcfcSdjm@openbsd.org add_host_to_hostfile(const char *filename, const char *host,
5151129dcfcSdjm@openbsd.org     const struct sshkey *key, int store_hash)
516d4a8b7e3SDamien Miller {
517d4a8b7e3SDamien Miller 	FILE *f;
5188d4f8725Sdjm@openbsd.org 	int success;
519e1776155SDamien Miller 
520450a7a1fSDamien Miller 	if (key == NULL)
521eba71babSDamien Miller 		return 1;	/* XXX ? */
52274344c3cSdtucker@openbsd.org 	hostfile_create_user_ssh_dir(filename, 0);
523d4a8b7e3SDamien Miller 	f = fopen(filename, "a");
524d4a8b7e3SDamien Miller 	if (!f)
525d4a8b7e3SDamien Miller 		return 0;
5266c5c9497Sdjm@openbsd.org 	success = write_host_entry(f, host, NULL, key, store_hash);
527e1776155SDamien Miller 	fclose(f);
5288d4f8725Sdjm@openbsd.org 	return success;
5298d4f8725Sdjm@openbsd.org }
5308d4f8725Sdjm@openbsd.org 
5318d4f8725Sdjm@openbsd.org struct host_delete_ctx {
5328d4f8725Sdjm@openbsd.org 	FILE *out;
5338d4f8725Sdjm@openbsd.org 	int quiet;
534d98f14b5Sdjm@openbsd.org 	const char *host, *ip;
535d98f14b5Sdjm@openbsd.org 	u_int *match_keys;	/* mask of HKF_MATCH_* for this key */
5368d4f8725Sdjm@openbsd.org 	struct sshkey * const *keys;
5378d4f8725Sdjm@openbsd.org 	size_t nkeys;
5386c5c9497Sdjm@openbsd.org 	int modified;
5398d4f8725Sdjm@openbsd.org };
5408d4f8725Sdjm@openbsd.org 
5418d4f8725Sdjm@openbsd.org static int
host_delete(struct hostkey_foreach_line * l,void * _ctx)5428d4f8725Sdjm@openbsd.org host_delete(struct hostkey_foreach_line *l, void *_ctx)
5438d4f8725Sdjm@openbsd.org {
5448d4f8725Sdjm@openbsd.org 	struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx;
5456c5c9497Sdjm@openbsd.org 	int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE;
5468d4f8725Sdjm@openbsd.org 	size_t i;
5478d4f8725Sdjm@openbsd.org 
5488d4f8725Sdjm@openbsd.org 	/* Don't remove CA and revocation lines */
549d98f14b5Sdjm@openbsd.org 	if (l->status == HKF_STATUS_MATCHED && l->marker == MRK_NONE) {
5508d4f8725Sdjm@openbsd.org 		/*
5518d4f8725Sdjm@openbsd.org 		 * If this line contains one of the keys that we will be
5528d4f8725Sdjm@openbsd.org 		 * adding later, then don't change it and mark the key for
5538d4f8725Sdjm@openbsd.org 		 * skipping.
5548d4f8725Sdjm@openbsd.org 		 */
5558d4f8725Sdjm@openbsd.org 		for (i = 0; i < ctx->nkeys; i++) {
556d98f14b5Sdjm@openbsd.org 			if (!sshkey_equal(ctx->keys[i], l->key))
557d98f14b5Sdjm@openbsd.org 				continue;
558d98f14b5Sdjm@openbsd.org 			ctx->match_keys[i] |= l->match;
5598d4f8725Sdjm@openbsd.org 			fprintf(ctx->out, "%s\n", l->line);
560816036f1Sdjm@openbsd.org 			debug3_f("%s key already at %s:%ld",
5618d4f8725Sdjm@openbsd.org 			    sshkey_type(l->key), l->path, l->linenum);
562e1776155SDamien Miller 			return 0;
563e1776155SDamien Miller 		}
564e1776155SDamien Miller 
5658d4f8725Sdjm@openbsd.org 		/*
5668d4f8725Sdjm@openbsd.org 		 * Hostname matches and has no CA/revoke marker, delete it
5678d4f8725Sdjm@openbsd.org 		 * by *not* writing the line to ctx->out.
5688d4f8725Sdjm@openbsd.org 		 */
5696c5c9497Sdjm@openbsd.org 		do_log2(loglevel, "%s%s%s:%ld: Removed %s key for host %s",
5708d4f8725Sdjm@openbsd.org 		    ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
5716c5c9497Sdjm@openbsd.org 		    l->path, l->linenum, sshkey_type(l->key), ctx->host);
5726c5c9497Sdjm@openbsd.org 		ctx->modified = 1;
5738d4f8725Sdjm@openbsd.org 		return 0;
5748d4f8725Sdjm@openbsd.org 	}
5758d4f8725Sdjm@openbsd.org 	/* Retain non-matching hosts and invalid lines when deleting */
5768d4f8725Sdjm@openbsd.org 	if (l->status == HKF_STATUS_INVALID) {
5778d4f8725Sdjm@openbsd.org 		do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry",
5788d4f8725Sdjm@openbsd.org 		    ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "",
5798d4f8725Sdjm@openbsd.org 		    l->path, l->linenum);
580