summaryrefslogtreecommitdiff
path: root/hostfile.c
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2010-12-01 12:21:51 +1100
committerDamien Miller <djm@mindrot.org>2010-12-01 12:21:51 +1100
commitd925dcd8a5d1a3070061006788352bed93260582 (patch)
tree12f78195086ff506d0f4e4c39098d675cdae0ee9 /hostfile.c
parent03c0e533de56a1fc55ec1885d35c3197fdefbf94 (diff)
- djm@cvs.openbsd.org 2010/11/29 23:45:51
[auth.c hostfile.c hostfile.h ssh.c ssh_config.5 sshconnect.c] [sshconnect.h sshconnect2.c] automatically order the hostkeys requested by the client based on which hostkeys are already recorded in known_hosts. This avoids hostkey warnings when connecting to servers with new ECDSA keys that are preferred by default; with markus@
Diffstat (limited to 'hostfile.c')
-rw-r--r--hostfile.c301
1 files changed, 176 insertions, 125 deletions
diff --git a/hostfile.c b/hostfile.c
index afab6dad1..9145529cb 100644
--- a/hostfile.c
+++ b/hostfile.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: hostfile.c,v 1.48 2010/03/04 10:36:03 djm Exp $ */ 1/* $OpenBSD: hostfile.c,v 1.49 2010/11/29 23:45:51 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -56,6 +56,12 @@
56#include "key.h" 56#include "key.h"
57#include "hostfile.h" 57#include "hostfile.h"
58#include "log.h" 58#include "log.h"
59#include "misc.h"
60
61struct hostkeys {
62 struct hostkey_entry *entries;
63 u_int num_entries;
64};
59 65
60static int 66static int
61extract_salt(const char *s, u_int l, char *salt, size_t salt_len) 67extract_salt(const char *s, u_int l, char *salt, size_t salt_len)
@@ -164,26 +170,28 @@ hostfile_read_key(char **cpp, u_int *bitsp, Key *ret)
164 170
165 /* Return results. */ 171 /* Return results. */
166 *cpp = cp; 172 *cpp = cp;
167 *bitsp = key_size(ret); 173 if (bitsp != NULL)
174 *bitsp = key_size(ret);
168 return 1; 175 return 1;
169} 176}
170 177
171static int 178static int
172hostfile_check_key(int bits, const Key *key, const char *host, const char *filename, int linenum) 179hostfile_check_key(int bits, const Key *key, const char *host,
180 const char *filename, u_long linenum)
173{ 181{
174 if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL) 182 if (key == NULL || key->type != KEY_RSA1 || key->rsa == NULL)
175 return 1; 183 return 1;
176 if (bits != BN_num_bits(key->rsa->n)) { 184 if (bits != BN_num_bits(key->rsa->n)) {
177 logit("Warning: %s, line %d: keysize mismatch for host %s: " 185 logit("Warning: %s, line %lu: keysize mismatch for host %s: "
178 "actual %d vs. announced %d.", 186 "actual %d vs. announced %d.",
179 filename, linenum, host, BN_num_bits(key->rsa->n), bits); 187 filename, linenum, host, BN_num_bits(key->rsa->n), bits);
180 logit("Warning: replace %d with %d in %s, line %d.", 188 logit("Warning: replace %d with %d in %s, line %lu.",
181 bits, BN_num_bits(key->rsa->n), filename, linenum); 189 bits, BN_num_bits(key->rsa->n), filename, linenum);
182 } 190 }
183 return 1; 191 return 1;
184} 192}
185 193
186static enum { MRK_ERROR, MRK_NONE, MRK_REVOKE, MRK_CA } 194static HostkeyMarker
187check_markers(char **cpp) 195check_markers(char **cpp)
188{ 196{
189 char marker[32], *sp, *cp = *cpp; 197 char marker[32], *sp, *cp = *cpp;
@@ -218,49 +226,32 @@ check_markers(char **cpp)
218 return ret; 226 return ret;
219} 227}
220 228
221/* 229struct hostkeys *
222 * Checks whether the given host (which must be in all lowercase) is already 230init_hostkeys(void)
223 * in the list of our known hosts. Returns HOST_OK if the host is known and 231{
224 * has the specified key, HOST_NEW if the host is not known, and HOST_CHANGED 232 struct hostkeys *ret = xcalloc(1, sizeof(*ret));
225 * if the host is known but used to have a different host key.
226 *
227 * If no 'key' has been specified and a key of type 'keytype' is known
228 * for the specified host, then HOST_FOUND is returned.
229 */
230 233
231static HostStatus 234 ret->entries = NULL;
232check_host_in_hostfile_by_key_or_type(const char *filename, 235 return ret;
233 const char *host, const Key *key, int keytype, Key *found, 236}
234 int want_revocation, int *numret) 237
238void
239load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path)
235{ 240{
236 FILE *f; 241 FILE *f;
237 char line[8192]; 242 char line[8192];
238 int want, have, linenum = 0, want_cert = key_is_cert(key); 243 u_long linenum = 0, num_loaded = 0;
239 u_int kbits;
240 char *cp, *cp2, *hashed_host; 244 char *cp, *cp2, *hashed_host;
241 HostStatus end_return; 245 HostkeyMarker marker;
242 246 Key *key;
243 debug3("check_host_in_hostfile: host %s filename %s", host, filename); 247 int kbits;
244 248
245 if (want_revocation && (key == NULL || keytype != 0 || found != NULL)) 249 if ((f = fopen(path, "r")) == NULL)
246 fatal("%s: invalid arguments", __func__); 250 return;
247 251 debug3("%s: loading entries for host \"%.100s\" from file \"%s\"",
248 /* Open the file containing the list of known hosts. */ 252 __func__, host, path);
249 f = fopen(filename, "r"); 253 while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) {
250 if (!f)
251 return HOST_NEW;
252
253 /*
254 * Return value when the loop terminates. This is set to
255 * HOST_CHANGED if we have seen a different key for the host and have
256 * not found the proper one.
257 */
258 end_return = HOST_NEW;
259
260 /* Go through the file. */
261 while (fgets(line, sizeof(line), f)) {
262 cp = line; 254 cp = line;
263 linenum++;
264 255
265 /* Skip any leading whitespace, comments and empty lines. */ 256 /* Skip any leading whitespace, comments and empty lines. */
266 for (; *cp == ' ' || *cp == '\t'; cp++) 257 for (; *cp == ' ' || *cp == '\t'; cp++)
@@ -268,19 +259,11 @@ check_host_in_hostfile_by_key_or_type(const char *filename,
268 if (!*cp || *cp == '#' || *cp == '\n') 259 if (!*cp || *cp == '#' || *cp == '\n')
269 continue; 260 continue;
270 261
271 if (want_revocation) 262 if ((marker = check_markers(&cp)) == MRK_ERROR) {
272 want = MRK_REVOKE; 263 verbose("%s: invalid marker at %s:%lu",
273 else if (want_cert) 264 __func__, path, linenum);
274 want = MRK_CA;
275 else
276 want = MRK_NONE;
277
278 if ((have = check_markers(&cp)) == MRK_ERROR) {
279 verbose("%s: invalid marker at %s:%d",
280 __func__, filename, linenum);
281 continue;
282 } else if (want != have)
283 continue; 265 continue;
266 }
284 267
285 /* Find the end of the host name portion. */ 268 /* Find the end of the host name portion. */
286 for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) 269 for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++)
@@ -292,8 +275,8 @@ check_host_in_hostfile_by_key_or_type(const char *filename,
292 continue; 275 continue;
293 hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); 276 hashed_host = host_hash(host, cp, (u_int) (cp2 - cp));
294 if (hashed_host == NULL) { 277 if (hashed_host == NULL) {
295 debug("Invalid hashed host line %d of %s", 278 debug("Invalid hashed host line %lu of %s",
296 linenum, filename); 279 linenum, path);
297 continue; 280 continue;
298 } 281 }
299 if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) 282 if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0)
@@ -303,98 +286,166 @@ check_host_in_hostfile_by_key_or_type(const char *filename,
303 /* Got a match. Skip host name. */ 286 /* Got a match. Skip host name. */
304 cp = cp2; 287 cp = cp2;
305 288
306 if (want_revocation)
307 found = key_new(KEY_UNSPEC);
308
309 /* 289 /*
310 * Extract the key from the line. This will skip any leading 290 * Extract the key from the line. This will skip any leading
311 * whitespace. Ignore badly formatted lines. 291 * whitespace. Ignore badly formatted lines.
312 */ 292 */
313 if (!hostfile_read_key(&cp, &kbits, found)) 293 key = key_new(KEY_UNSPEC);
294 if (!hostfile_read_key(&cp, &kbits, key)) {
295 key_free(key);
296 key = key_new(KEY_RSA1);
297 if (!hostfile_read_key(&cp, &kbits, key)) {
298 key_free(key);
299 continue;
300 }
301 }
302 if (!hostfile_check_key(kbits, key, host, path, linenum))
314 continue; 303 continue;
315 304
316 if (numret != NULL) 305 debug3("%s: found %skey type %s in file %s:%lu", __func__,
317 *numret = linenum; 306 marker == MRK_NONE ? "" :
307 (marker == MRK_CA ? "ca " : "revoked "),
308 key_type(key), path, linenum);
309 hostkeys->entries = xrealloc(hostkeys->entries,
310 hostkeys->num_entries + 1, sizeof(*hostkeys->entries));
311 hostkeys->entries[hostkeys->num_entries].host = xstrdup(host);
312 hostkeys->entries[hostkeys->num_entries].file = xstrdup(path);
313 hostkeys->entries[hostkeys->num_entries].line = linenum;
314 hostkeys->entries[hostkeys->num_entries].key = key;
315 hostkeys->entries[hostkeys->num_entries].marker = marker;
316 hostkeys->num_entries++;
317 num_loaded++;
318 }
319 debug3("%s: loaded %lu keys", __func__, num_loaded);
320 return;
321}
318 322
319 if (key == NULL) { 323void
320 /* we found a key of the requested type */ 324free_hostkeys(struct hostkeys *hostkeys)
321 if (found->type == keytype) { 325{
322 fclose(f); 326 u_int i;
323 return HOST_FOUND; 327
324 } 328 for (i = 0; i < hostkeys->num_entries; i++) {
325 continue; 329 xfree(hostkeys->entries[i].host);
326 } 330 xfree(hostkeys->entries[i].file);
331 key_free(hostkeys->entries[i].key);
332 bzero(hostkeys->entries + i, sizeof(*hostkeys->entries));
333 }
334 if (hostkeys->entries != NULL)
335 xfree(hostkeys->entries);
336 hostkeys->entries = NULL;
337 hostkeys->num_entries = 0;
338 xfree(hostkeys);
339}
327 340
328 if (!hostfile_check_key(kbits, found, host, filename, linenum)) 341static int
342check_key_not_revoked(struct hostkeys *hostkeys, Key *k)
343{
344 int is_cert = key_is_cert(k);
345 u_int i;
346
347 for (i = 0; i < hostkeys->num_entries; i++) {
348 if (hostkeys->entries[i].marker != MRK_REVOKE)
329 continue; 349 continue;
350 if (key_equal_public(k, hostkeys->entries[i].key))
351 return -1;
352 if (is_cert &&
353 key_equal_public(k->cert->signature_key,
354 hostkeys->entries[i].key))
355 return -1;
356 }
357 return 0;
358}
330 359
331 if (want_revocation) { 360/*
332 if (key_is_cert(key) && 361 * Match keys against a specified key, or look one up by key type.
333 key_equal_public(key->cert->signature_key, found)) { 362 *
334 verbose("check_host_in_hostfile: revoked CA " 363 * If looking for a keytype (key == NULL) and one is found then return
335 "line %d", linenum); 364 * HOST_FOUND, otherwise HOST_NEW.
336 key_free(found); 365 *
337 return HOST_REVOKED; 366 * If looking for a key (key != NULL):
338 } 367 * 1. If the key is a cert and a matching CA is found, return HOST_OK
339 if (key_equal_public(key, found)) { 368 * 2. If the key is not a cert and a matching key is found, return HOST_OK
340 verbose("check_host_in_hostfile: revoked key " 369 * 3. If no key matches but a key with a different type is found, then
341 "line %d", linenum); 370 * return HOST_CHANGED
342 key_free(found); 371 * 4. If no matching keys are found, then return HOST_NEW.
343 return HOST_REVOKED; 372 *
344 } 373 * Finally, check any found key is not revoked.
345 key_free(found); 374 */
375static HostStatus
376check_hostkeys_by_key_or_type(struct hostkeys *hostkeys,
377 Key *k, int keytype, const struct hostkey_entry **found)
378{
379 u_int i;
380 HostStatus end_return = HOST_NEW;
381 int want_cert = key_is_cert(k);
382 HostkeyMarker want_marker = want_cert ? MRK_CA : MRK_NONE;
383 int proto = (k ? k->type : keytype) == KEY_RSA1 ? 1 : 2;
384
385 if (found != NULL)
386 *found = NULL;
387
388 for (i = 0; i < hostkeys->num_entries; i++) {
389 if (proto == 1 && hostkeys->entries[i].key->type != KEY_RSA1)
390 continue;
391 if (proto == 2 && hostkeys->entries[i].key->type == KEY_RSA1)
346 continue; 392 continue;
393 if (hostkeys->entries[i].marker != want_marker)
394 continue;
395 if (k == NULL) {
396 if (hostkeys->entries[i].key->type != keytype)
397 continue;
398 end_return = HOST_FOUND;
399 if (found != NULL)
400 *found = hostkeys->entries + i;
401 k = hostkeys->entries[i].key;
402 break;
347 } 403 }
348 404 if (want_cert) {
349 /* Check if the current key is the same as the given key. */ 405 if (key_equal_public(k->cert->signature_key,
350 if (want_cert && key_equal(key->cert->signature_key, found)) { 406 hostkeys->entries[i].key)) {
351 /* Found CA cert for key */ 407 /* A matching CA exists */
352 debug3("check_host_in_hostfile: CA match line %d", 408 end_return = HOST_OK;
353 linenum); 409 if (found != NULL)
354 fclose(f); 410 *found = hostkeys->entries + i;
355 return HOST_OK; 411 break;
356 } else if (!want_cert && key_equal(key, found)) { 412 }
357 /* Found identical key */ 413 } else {
358 debug3("check_host_in_hostfile: match line %d", linenum); 414 if (key_equal(k, hostkeys->entries[i].key)) {
359 fclose(f); 415 end_return = HOST_OK;
360 return HOST_OK; 416 if (found != NULL)
417 *found = hostkeys->entries + i;
418 break;
419 }
420 /* A non-maching key exists */
421 end_return = HOST_CHANGED;
422 if (found != NULL)
423 *found = hostkeys->entries + i;
361 } 424 }
362 /*
363 * They do not match. We will continue to go through the
364 * file; however, we note that we will not return that it is
365 * new.
366 */
367 end_return = HOST_CHANGED;
368 } 425 }
369 /* Clear variables and close the file. */ 426 if (check_key_not_revoked(hostkeys, k) != 0) {
370 fclose(f); 427 end_return = HOST_REVOKED;
371 428 if (found != NULL)
372 /* 429 *found = NULL;
373 * Return either HOST_NEW or HOST_CHANGED, depending on whether we 430 }
374 * saw a different key for the host.
375 */
376 return end_return; 431 return end_return;
377} 432}
378 433
379HostStatus 434HostStatus
380check_host_in_hostfile(const char *filename, const char *host, const Key *key, 435check_key_in_hostkeys(struct hostkeys *hostkeys, Key *key,
381 Key *found, int *numret) 436 const struct hostkey_entry **found)
382{ 437{
383 if (key == NULL) 438 if (key == NULL)
384 fatal("no key to look up"); 439 fatal("no key to look up");
385 if (check_host_in_hostfile_by_key_or_type(filename, host, 440 return check_hostkeys_by_key_or_type(hostkeys, key, 0, found);
386 key, 0, NULL, 1, NULL) == HOST_REVOKED)
387 return HOST_REVOKED;
388 return check_host_in_hostfile_by_key_or_type(filename, host, key, 0,
389 found, 0, numret);
390} 441}
391 442
392int 443int
393lookup_key_in_hostfile_by_type(const char *filename, const char *host, 444lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype,
394 int keytype, Key *found, int *numret) 445 const struct hostkey_entry **found)
395{ 446{
396 return (check_host_in_hostfile_by_key_or_type(filename, host, NULL, 447 return (check_hostkeys_by_key_or_type(hostkeys, NULL, keytype,
397 keytype, found, 0, numret) == HOST_FOUND); 448 found) == HOST_FOUND);
398} 449}
399 450
400/* 451/*