diff options
author | djm@openbsd.org <djm@openbsd.org> | 2015-01-26 03:04:45 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2015-01-27 00:00:57 +1100 |
commit | 8d4f87258f31cb6def9b3b55b6a7321d84728ff2 (patch) | |
tree | c98e66c1c0824f0b0e312d7b44d8eeac46265362 /hostfile.c | |
parent | 60b1825262b1f1e24fc72050b907189c92daf18e (diff) |
upstream commit
Host key rotation support.
Add a hostkeys@openssh.com protocol extension (global request) for
a server to inform a client of all its available host key after
authentication has completed. The client may record the keys in
known_hosts, allowing it to upgrade to better host key algorithms
and a server to gracefully rotate its keys.
The client side of this is controlled by a UpdateHostkeys config
option (default on).
ok markus@
Diffstat (limited to 'hostfile.c')
-rw-r--r-- | hostfile.c | 206 |
1 files changed, 190 insertions, 16 deletions
diff --git a/hostfile.c b/hostfile.c index ccb2af920..9de1b383b 100644 --- a/hostfile.c +++ b/hostfile.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.c,v 1.61 2015/01/18 21:48:09 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.c,v 1.62 2015/01/26 03:04:45 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 |
@@ -39,6 +39,7 @@ | |||
39 | #include "includes.h" | 39 | #include "includes.h" |
40 | 40 | ||
41 | #include <sys/types.h> | 41 | #include <sys/types.h> |
42 | #include <sys/stat.h> | ||
42 | 43 | ||
43 | #include <netinet/in.h> | 44 | #include <netinet/in.h> |
44 | 45 | ||
@@ -49,6 +50,7 @@ | |||
49 | #include <stdlib.h> | 50 | #include <stdlib.h> |
50 | #include <string.h> | 51 | #include <string.h> |
51 | #include <stdarg.h> | 52 | #include <stdarg.h> |
53 | #include <unistd.h> | ||
52 | 54 | ||
53 | #include "xmalloc.h" | 55 | #include "xmalloc.h" |
54 | #include "match.h" | 56 | #include "match.h" |
@@ -430,6 +432,29 @@ lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, | |||
430 | found) == HOST_FOUND); | 432 | found) == HOST_FOUND); |
431 | } | 433 | } |
432 | 434 | ||
435 | static int | ||
436 | write_host_entry(FILE *f, const char *host, | ||
437 | const struct sshkey *key, int store_hash) | ||
438 | { | ||
439 | int r, success = 0; | ||
440 | char *hashed_host = NULL; | ||
441 | |||
442 | if (store_hash) { | ||
443 | if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { | ||
444 | error("%s: host_hash failed", __func__); | ||
445 | return 0; | ||
446 | } | ||
447 | } | ||
448 | fprintf(f, "%s ", store_hash ? hashed_host : host); | ||
449 | |||
450 | if ((r = sshkey_write(key, f)) == 0) | ||
451 | success = 1; | ||
452 | else | ||
453 | error("%s: sshkey_write failed: %s", __func__, ssh_err(r)); | ||
454 | fputc('\n', f); | ||
455 | return success; | ||
456 | } | ||
457 | |||
433 | /* | 458 | /* |
434 | * Appends an entry to the host file. Returns false if the entry could not | 459 | * Appends an entry to the host file. Returns false if the entry could not |
435 | * be appended. | 460 | * be appended. |
@@ -439,32 +464,181 @@ add_host_to_hostfile(const char *filename, const char *host, | |||
439 | const struct sshkey *key, int store_hash) | 464 | const struct sshkey *key, int store_hash) |
440 | { | 465 | { |
441 | FILE *f; | 466 | FILE *f; |
442 | int r, success = 0; | 467 | int success; |
443 | char *hashed_host = NULL; | ||
444 | 468 | ||
445 | if (key == NULL) | 469 | if (key == NULL) |
446 | return 1; /* XXX ? */ | 470 | return 1; /* XXX ? */ |
447 | f = fopen(filename, "a"); | 471 | f = fopen(filename, "a"); |
448 | if (!f) | 472 | if (!f) |
449 | return 0; | 473 | return 0; |
474 | success = write_host_entry(f, host, key, store_hash); | ||
475 | fclose(f); | ||
476 | return success; | ||
477 | } | ||
450 | 478 | ||
451 | if (store_hash) { | 479 | struct host_delete_ctx { |
452 | if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { | 480 | FILE *out; |
453 | error("%s: host_hash failed", __func__); | 481 | int quiet; |
454 | fclose(f); | 482 | const char *host; |
483 | int *skip_keys; | ||
484 | struct sshkey * const *keys; | ||
485 | size_t nkeys; | ||
486 | }; | ||
487 | |||
488 | static int | ||
489 | host_delete(struct hostkey_foreach_line *l, void *_ctx) | ||
490 | { | ||
491 | struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx; | ||
492 | int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; | ||
493 | size_t i; | ||
494 | |||
495 | if (l->status == HKF_STATUS_HOST_MATCHED) { | ||
496 | if (l->marker != MRK_NONE) { | ||
497 | /* Don't remove CA and revocation lines */ | ||
498 | fprintf(ctx->out, "%s\n", l->line); | ||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | /* XXX might need a knob for this later */ | ||
503 | /* Don't remove RSA1 keys */ | ||
504 | if (l->key->type == KEY_RSA1) { | ||
505 | fprintf(ctx->out, "%s\n", l->line); | ||
455 | return 0; | 506 | return 0; |
456 | } | 507 | } |
508 | |||
509 | /* | ||
510 | * If this line contains one of the keys that we will be | ||
511 | * adding later, then don't change it and mark the key for | ||
512 | * skipping. | ||
513 | */ | ||
514 | for (i = 0; i < ctx->nkeys; i++) { | ||
515 | if (sshkey_equal(ctx->keys[i], l->key)) { | ||
516 | ctx->skip_keys[i] = 1; | ||
517 | fprintf(ctx->out, "%s\n", l->line); | ||
518 | debug3("%s: %s key already at %s:%ld", __func__, | ||
519 | sshkey_type(l->key), l->path, l->linenum); | ||
520 | return 0; | ||
521 | } | ||
522 | } | ||
523 | |||
524 | /* | ||
525 | * Hostname matches and has no CA/revoke marker, delete it | ||
526 | * by *not* writing the line to ctx->out. | ||
527 | */ | ||
528 | do_log2(loglevel, "%s%s%s:%ld: Host %s removed", | ||
529 | ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", | ||
530 | l->path, l->linenum, ctx->host); | ||
531 | return 0; | ||
457 | } | 532 | } |
458 | fprintf(f, "%s ", store_hash ? hashed_host : host); | 533 | /* Retain non-matching hosts and invalid lines when deleting */ |
534 | if (l->status == HKF_STATUS_INVALID) { | ||
535 | do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry", | ||
536 | ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", | ||
537 | l->path, l->linenum); | ||
538 | } | ||
539 | fprintf(ctx->out, "%s\n", l->line); | ||
540 | return 0; | ||
541 | } | ||
459 | 542 | ||
460 | if ((r = sshkey_write(key, f)) != 0) { | 543 | int |
461 | error("%s: saving key in %s failed: %s", | 544 | hostfile_replace_entries(const char *filename, const char *host, |
462 | __func__, filename, ssh_err(r)); | 545 | struct sshkey **keys, size_t nkeys, int store_hash, int quiet) |
463 | } else | 546 | { |
464 | success = 1; | 547 | int r, fd, oerrno = 0; |
465 | fputc('\n', f); | 548 | int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; |
466 | fclose(f); | 549 | struct host_delete_ctx ctx; |
467 | return success; | 550 | char *temp = NULL, *back = NULL; |
551 | mode_t omask; | ||
552 | size_t i; | ||
553 | |||
554 | memset(&ctx, 0, sizeof(ctx)); | ||
555 | ctx.host = host; | ||
556 | ctx.quiet = quiet; | ||
557 | if ((ctx.skip_keys = calloc(nkeys, sizeof(*ctx.skip_keys))) == NULL) | ||
558 | return SSH_ERR_ALLOC_FAIL; | ||
559 | ctx.keys = keys; | ||
560 | ctx.nkeys = nkeys; | ||
561 | |||
562 | /* | ||
563 | * Prepare temporary file for in-place deletion. | ||
564 | */ | ||
565 | if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) < 0 || | ||
566 | (r = asprintf(&back, "%s.old", filename)) < 0) { | ||
567 | r = SSH_ERR_ALLOC_FAIL; | ||
568 | goto fail; | ||
569 | } | ||
570 | |||
571 | omask = umask(077); | ||
572 | if ((fd = mkstemp(temp)) == -1) { | ||
573 | oerrno = errno; | ||
574 | error("%s: mkstemp: %s", __func__, strerror(oerrno)); | ||
575 | r = SSH_ERR_SYSTEM_ERROR; | ||
576 | goto fail; | ||
577 | } | ||
578 | if ((ctx.out = fdopen(fd, "w")) == NULL) { | ||
579 | oerrno = errno; | ||
580 | close(fd); | ||
581 | error("%s: fdopen: %s", __func__, strerror(oerrno)); | ||
582 | r = SSH_ERR_SYSTEM_ERROR; | ||
583 | goto fail; | ||
584 | } | ||
585 | |||
586 | /* Remove all entries for the specified host from the file */ | ||
587 | if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, | ||
588 | HKF_WANT_PARSE_KEY)) != 0) { | ||
589 | error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); | ||
590 | goto fail; | ||
591 | } | ||
592 | |||
593 | /* Add the requested keys */ | ||
594 | for (i = 0; i < nkeys; i++) { | ||
595 | if (ctx.skip_keys[i]) | ||
596 | continue; | ||
597 | do_log2(loglevel, "%s%sadd %s key to %s", | ||
598 | quiet ? __func__ : "", quiet ? ": " : NULL, | ||
599 | sshkey_type(keys[i]), filename); | ||
600 | if (!write_host_entry(ctx.out, host, keys[i], store_hash)) { | ||
601 | r = SSH_ERR_INTERNAL_ERROR; | ||
602 | goto fail; | ||
603 | } | ||
604 | } | ||
605 | fclose(ctx.out); | ||
606 | ctx.out = NULL; | ||
607 | |||
608 | /* Backup the original file and replace it with the temporary */ | ||
609 | if (unlink(back) == -1 && errno != ENOENT) { | ||
610 | oerrno = errno; | ||
611 | error("%s: unlink %.100s: %s", __func__, back, strerror(errno)); | ||
612 | r = SSH_ERR_SYSTEM_ERROR; | ||
613 | goto fail; | ||
614 | } | ||
615 | if (link(filename, back) == -1) { | ||
616 | oerrno = errno; | ||
617 | error("%s: link %.100s to %.100s: %s", __func__, filename, back, | ||
618 | strerror(errno)); | ||
619 | r = SSH_ERR_SYSTEM_ERROR; | ||
620 | goto fail; | ||
621 | } | ||
622 | if (rename(temp, filename) == -1) { | ||
623 | oerrno = errno; | ||
624 | error("%s: rename \"%s\" to \"%s\": %s", __func__, | ||
625 | temp, filename, strerror(errno)); | ||
626 | r = SSH_ERR_SYSTEM_ERROR; | ||
627 | goto fail; | ||
628 | } | ||
629 | /* success */ | ||
630 | r = 0; | ||
631 | fail: | ||
632 | if (temp != NULL && r != 0) | ||
633 | unlink(temp); | ||
634 | free(temp); | ||
635 | free(back); | ||
636 | if (ctx.out != NULL) | ||
637 | fclose(ctx.out); | ||
638 | free(ctx.skip_keys); | ||
639 | if (r == SSH_ERR_SYSTEM_ERROR) | ||
640 | errno = oerrno; | ||
641 | return r; | ||
468 | } | 642 | } |
469 | 643 | ||
470 | static int | 644 | static int |