diff options
-rw-r--r-- | PROTOCOL | 24 | ||||
-rw-r--r-- | clientloop.c | 94 | ||||
-rw-r--r-- | hostfile.c | 206 | ||||
-rw-r--r-- | hostfile.h | 5 | ||||
-rw-r--r-- | readconf.c | 13 | ||||
-rw-r--r-- | readconf.h | 6 | ||||
-rw-r--r-- | ssh_config.5 | 26 | ||||
-rw-r--r-- | sshconnect.c | 11 | ||||
-rw-r--r-- | sshd.c | 44 |
9 files changed, 401 insertions, 28 deletions
@@ -282,6 +282,28 @@ by the client cancel the forwarding of a Unix domain socket. | |||
282 | boolean FALSE | 282 | boolean FALSE |
283 | string socket path | 283 | string socket path |
284 | 284 | ||
285 | 2.5. connection: hostkey update and rotation "hostkeys@openssh.com" | ||
286 | |||
287 | OpenSSH supports a protocol extension allowing a server to inform | ||
288 | a client of all its protocol v.2 hostkeys after user-authentication | ||
289 | has completed. | ||
290 | |||
291 | byte SSH_MSG_GLOBAL_REQUEST | ||
292 | string "hostkeys@openssh.com" | ||
293 | string[] hostkeys | ||
294 | |||
295 | Upon receiving this message, a client may update its known_hosts | ||
296 | file, adding keys that it has not seen before and deleting keys | ||
297 | for the server host that are no longer offered. | ||
298 | |||
299 | This extension allows a client to learn key types that it had | ||
300 | not previously encountered, thereby allowing it to potentially | ||
301 | upgrade from weaker key algorithms to better ones. It also | ||
302 | supports graceful key rotation: a server may offer multiple keys | ||
303 | of the same type for a period (to give clients an opportunity to | ||
304 | learn them using this extension) before removing the deprecated | ||
305 | key from those offered. | ||
306 | |||
285 | 3. SFTP protocol changes | 307 | 3. SFTP protocol changes |
286 | 308 | ||
287 | 3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK | 309 | 3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK |
@@ -406,4 +428,4 @@ respond with a SSH_FXP_STATUS message. | |||
406 | This extension is advertised in the SSH_FXP_VERSION hello with version | 428 | This extension is advertised in the SSH_FXP_VERSION hello with version |
407 | "1". | 429 | "1". |
408 | 430 | ||
409 | $OpenBSD: PROTOCOL,v 1.24 2014/07/15 15:54:14 millert Exp $ | 431 | $OpenBSD: PROTOCOL,v 1.25 2015/01/26 03:04:45 djm Exp $ |
diff --git a/clientloop.c b/clientloop.c index 4522a6332..7b54b6eb0 100644 --- a/clientloop.c +++ b/clientloop.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: clientloop.c,v 1.266 2015/01/20 23:14:00 deraadt Exp $ */ | 1 | /* $OpenBSD: clientloop.c,v 1.267 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 |
@@ -112,6 +112,7 @@ | |||
112 | #include "msg.h" | 112 | #include "msg.h" |
113 | #include "roaming.h" | 113 | #include "roaming.h" |
114 | #include "ssherr.h" | 114 | #include "ssherr.h" |
115 | #include "hostfile.h" | ||
115 | 116 | ||
116 | /* import options */ | 117 | /* import options */ |
117 | extern Options options; | 118 | extern Options options; |
@@ -1781,6 +1782,7 @@ client_input_exit_status(int type, u_int32_t seq, void *ctxt) | |||
1781 | quit_pending = 1; | 1782 | quit_pending = 1; |
1782 | return 0; | 1783 | return 0; |
1783 | } | 1784 | } |
1785 | |||
1784 | static int | 1786 | static int |
1785 | client_input_agent_open(int type, u_int32_t seq, void *ctxt) | 1787 | client_input_agent_open(int type, u_int32_t seq, void *ctxt) |
1786 | { | 1788 | { |
@@ -2038,6 +2040,7 @@ client_input_channel_open(int type, u_int32_t seq, void *ctxt) | |||
2038 | free(ctype); | 2040 | free(ctype); |
2039 | return 0; | 2041 | return 0; |
2040 | } | 2042 | } |
2043 | |||
2041 | static int | 2044 | static int |
2042 | client_input_channel_req(int type, u_int32_t seq, void *ctxt) | 2045 | client_input_channel_req(int type, u_int32_t seq, void *ctxt) |
2043 | { | 2046 | { |
@@ -2085,6 +2088,91 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) | |||
2085 | free(rtype); | 2088 | free(rtype); |
2086 | return 0; | 2089 | return 0; |
2087 | } | 2090 | } |
2091 | |||
2092 | /* | ||
2093 | * Handle hostkeys@openssh.com global request to inform the client of all | ||
2094 | * the server's hostkeys. The keys are checked against the user's | ||
2095 | * HostkeyAlgorithms preference before they are accepted. | ||
2096 | */ | ||
2097 | static int | ||
2098 | client_input_hostkeys(void) | ||
2099 | { | ||
2100 | const u_char *blob = NULL; | ||
2101 | u_int i, len = 0, nkeys = 0; | ||
2102 | struct sshbuf *buf = NULL; | ||
2103 | struct sshkey *key = NULL, **tmp, **keys = NULL; | ||
2104 | int r, success = 1; | ||
2105 | char *fp, *host_str = NULL; | ||
2106 | static int hostkeys_seen = 0; /* XXX use struct ssh */ | ||
2107 | |||
2108 | /* | ||
2109 | * NB. Return success for all cases other than protocol error. The | ||
2110 | * server doesn't need to know what the client does with its hosts | ||
2111 | * file. | ||
2112 | */ | ||
2113 | |||
2114 | blob = packet_get_string_ptr(&len); | ||
2115 | packet_check_eom(); | ||
2116 | |||
2117 | if (hostkeys_seen) | ||
2118 | fatal("%s: server already sent hostkeys", __func__); | ||
2119 | if (!options.update_hostkeys || options.num_user_hostfiles <= 0) | ||
2120 | return 1; | ||
2121 | if ((buf = sshbuf_from(blob, len)) == NULL) | ||
2122 | fatal("%s: sshbuf_from failed", __func__); | ||
2123 | while (sshbuf_len(buf) > 0) { | ||
2124 | sshkey_free(key); | ||
2125 | key = NULL; | ||
2126 | if ((r = sshkey_froms(buf, &key)) != 0) | ||
2127 | fatal("%s: parse key: %s", __func__, ssh_err(r)); | ||
2128 | fp = sshkey_fingerprint(key, options.fingerprint_hash, | ||
2129 | SSH_FP_DEFAULT); | ||
2130 | debug3("%s: received %s key %s", __func__, | ||
2131 | sshkey_type(key), fp); | ||
2132 | free(fp); | ||
2133 | /* Check that the key is accepted in HostkeyAlgorithms */ | ||
2134 | if (options.hostkeyalgorithms != NULL && | ||
2135 | match_pattern_list(sshkey_ssh_name(key), | ||
2136 | options.hostkeyalgorithms, | ||
2137 | strlen(options.hostkeyalgorithms), 0) != 1) { | ||
2138 | debug3("%s: %s key not permitted by HostkeyAlgorithms", | ||
2139 | __func__, sshkey_ssh_name(key)); | ||
2140 | continue; | ||
2141 | } | ||
2142 | if ((tmp = reallocarray(keys, nkeys + 1, | ||
2143 | sizeof(*keys))) == NULL) | ||
2144 | fatal("%s: reallocarray failed nkeys = %u", | ||
2145 | __func__, nkeys); | ||
2146 | keys = tmp; | ||
2147 | keys[nkeys++] = key; | ||
2148 | key = NULL; | ||
2149 | } | ||
2150 | |||
2151 | debug3("%s: received %u keys from server", __func__, nkeys); | ||
2152 | if (nkeys == 0) { | ||
2153 | error("%s: server sent no hostkeys", __func__); | ||
2154 | goto out; | ||
2155 | } | ||
2156 | |||
2157 | get_hostfile_hostname_ipaddr(host, NULL, options.port, &host_str, NULL); | ||
2158 | |||
2159 | if ((r = hostfile_replace_entries(options.user_hostfiles[0], host_str, | ||
2160 | keys, nkeys, options.hash_known_hosts, 1)) != 0) { | ||
2161 | error("%s: hostfile_replace_entries failed: %s", | ||
2162 | __func__, ssh_err(r)); | ||
2163 | goto out; | ||
2164 | } | ||
2165 | |||
2166 | /* Success */ | ||
2167 | out: | ||
2168 | free(host_str); | ||
2169 | sshkey_free(key); | ||
2170 | for (i = 0; i < nkeys; i++) | ||
2171 | sshkey_free(keys[i]); | ||
2172 | sshbuf_free(buf); | ||
2173 | return success; | ||
2174 | } | ||
2175 | |||
2088 | static int | 2176 | static int |
2089 | client_input_global_request(int type, u_int32_t seq, void *ctxt) | 2177 | client_input_global_request(int type, u_int32_t seq, void *ctxt) |
2090 | { | 2178 | { |
@@ -2092,10 +2180,12 @@ client_input_global_request(int type, u_int32_t seq, void *ctxt) | |||
2092 | int want_reply; | 2180 | int want_reply; |
2093 | int success = 0; | 2181 | int success = 0; |
2094 | 2182 | ||
2095 | rtype = packet_get_string(NULL); | 2183 | rtype = packet_get_cstring(NULL); |
2096 | want_reply = packet_get_char(); | 2184 | want_reply = packet_get_char(); |
2097 | debug("client_input_global_request: rtype %s want_reply %d", | 2185 | debug("client_input_global_request: rtype %s want_reply %d", |
2098 | rtype, want_reply); | 2186 | rtype, want_reply); |
2187 | if (strcmp(rtype, "hostkeys@openssh.com") == 0) | ||
2188 | success = client_input_hostkeys(); | ||
2099 | if (want_reply) { | 2189 | if (want_reply) { |
2100 | packet_start(success ? | 2190 | packet_start(success ? |
2101 | SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); | 2191 | SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); |
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 |
diff --git a/hostfile.h b/hostfile.h index 24c3813aa..9080b5edb 100644 --- a/hostfile.h +++ b/hostfile.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: hostfile.h,v 1.22 2015/01/18 21:40:24 djm Exp $ */ | 1 | /* $OpenBSD: hostfile.h,v 1.23 2015/01/26 03:04:45 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -44,6 +44,9 @@ int hostfile_read_key(char **, u_int *, struct sshkey *); | |||
44 | int add_host_to_hostfile(const char *, const char *, | 44 | int add_host_to_hostfile(const char *, const char *, |
45 | const struct sshkey *, int); | 45 | const struct sshkey *, int); |
46 | 46 | ||
47 | int hostfile_replace_entries(const char *filename, const char *host, | ||
48 | struct sshkey **keys, size_t nkeys, int store_hash, int quiet); | ||
49 | |||
47 | #define HASH_MAGIC "|1|" | 50 | #define HASH_MAGIC "|1|" |
48 | #define HASH_DELIM '|' | 51 | #define HASH_DELIM '|' |
49 | 52 | ||
diff --git a/readconf.c b/readconf.c index cb61a5af0..401f3430d 100644 --- a/readconf.c +++ b/readconf.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: readconf.c,v 1.228 2015/01/16 06:40:12 deraadt Exp $ */ | 1 | /* $OpenBSD: readconf.c,v 1.229 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 |
@@ -156,7 +156,7 @@ typedef enum { | |||
156 | oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, | 156 | oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, |
157 | oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, | 157 | oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, |
158 | oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, | 158 | oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, |
159 | oFingerprintHash, | 159 | oFingerprintHash, oUpdateHostkeys, |
160 | oIgnoredUnknownOption, oDeprecated, oUnsupported | 160 | oIgnoredUnknownOption, oDeprecated, oUnsupported |
161 | } OpCodes; | 161 | } OpCodes; |
162 | 162 | ||
@@ -273,6 +273,7 @@ static struct { | |||
273 | { "streamlocalbindunlink", oStreamLocalBindUnlink }, | 273 | { "streamlocalbindunlink", oStreamLocalBindUnlink }, |
274 | { "revokedhostkeys", oRevokedHostKeys }, | 274 | { "revokedhostkeys", oRevokedHostKeys }, |
275 | { "fingerprinthash", oFingerprintHash }, | 275 | { "fingerprinthash", oFingerprintHash }, |
276 | { "updatehostkeys", oUpdateHostkeys }, | ||
276 | { "ignoreunknown", oIgnoreUnknown }, | 277 | { "ignoreunknown", oIgnoreUnknown }, |
277 | 278 | ||
278 | { NULL, oBadOption } | 279 | { NULL, oBadOption } |
@@ -1476,6 +1477,10 @@ parse_int: | |||
1476 | *intptr = value; | 1477 | *intptr = value; |
1477 | break; | 1478 | break; |
1478 | 1479 | ||
1480 | case oUpdateHostkeys: | ||
1481 | intptr = &options->update_hostkeys; | ||
1482 | goto parse_flag; | ||
1483 | |||
1479 | case oDeprecated: | 1484 | case oDeprecated: |
1480 | debug("%s line %d: Deprecated option \"%s\"", | 1485 | debug("%s line %d: Deprecated option \"%s\"", |
1481 | filename, linenum, keyword); | 1486 | filename, linenum, keyword); |
@@ -1654,6 +1659,7 @@ initialize_options(Options * options) | |||
1654 | options->canonicalize_hostname = -1; | 1659 | options->canonicalize_hostname = -1; |
1655 | options->revoked_host_keys = NULL; | 1660 | options->revoked_host_keys = NULL; |
1656 | options->fingerprint_hash = -1; | 1661 | options->fingerprint_hash = -1; |
1662 | options->update_hostkeys = -1; | ||
1657 | } | 1663 | } |
1658 | 1664 | ||
1659 | /* | 1665 | /* |
@@ -1833,6 +1839,8 @@ fill_default_options(Options * options) | |||
1833 | options->canonicalize_hostname = SSH_CANONICALISE_NO; | 1839 | options->canonicalize_hostname = SSH_CANONICALISE_NO; |
1834 | if (options->fingerprint_hash == -1) | 1840 | if (options->fingerprint_hash == -1) |
1835 | options->fingerprint_hash = SSH_FP_HASH_DEFAULT; | 1841 | options->fingerprint_hash = SSH_FP_HASH_DEFAULT; |
1842 | if (options->update_hostkeys == -1) | ||
1843 | options->update_hostkeys = 1; | ||
1836 | 1844 | ||
1837 | #define CLEAR_ON_NONE(v) \ | 1845 | #define CLEAR_ON_NONE(v) \ |
1838 | do { \ | 1846 | do { \ |
@@ -2256,6 +2264,7 @@ dump_client_config(Options *o, const char *host) | |||
2256 | dump_cfg_fmtint(oUsePrivilegedPort, o->use_privileged_port); | 2264 | dump_cfg_fmtint(oUsePrivilegedPort, o->use_privileged_port); |
2257 | dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns); | 2265 | dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns); |
2258 | dump_cfg_fmtint(oVisualHostKey, o->visual_host_key); | 2266 | dump_cfg_fmtint(oVisualHostKey, o->visual_host_key); |
2267 | dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys); | ||
2259 | 2268 | ||
2260 | /* Integer options */ | 2269 | /* Integer options */ |
2261 | dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots); | 2270 | dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots); |
diff --git a/readconf.h b/readconf.h index a23da1107..7a8ae17c0 100644 --- a/readconf.h +++ b/readconf.h | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: readconf.h,v 1.106 2015/01/15 09:40:00 djm Exp $ */ | 1 | /* $OpenBSD: readconf.h,v 1.107 2015/01/26 03:04:45 djm Exp $ */ |
2 | 2 | ||
3 | /* | 3 | /* |
4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 4 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
@@ -146,7 +146,9 @@ typedef struct { | |||
146 | 146 | ||
147 | char *revoked_host_keys; | 147 | char *revoked_host_keys; |
148 | 148 | ||
149 | int fingerprint_hash; | 149 | int fingerprint_hash; |
150 | |||
151 | int update_hostkeys; | ||
150 | 152 | ||
151 | char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ | 153 | char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ |
152 | } Options; | 154 | } Options; |
diff --git a/ssh_config.5 b/ssh_config.5 index 361c32288..0d4cdf4c6 100644 --- a/ssh_config.5 +++ b/ssh_config.5 | |||
@@ -33,8 +33,8 @@ | |||
33 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 33 | .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
34 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 34 | .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
35 | .\" | 35 | .\" |
36 | .\" $OpenBSD: ssh_config.5,v 1.199 2014/12/22 09:24:59 jmc Exp $ | 36 | .\" $OpenBSD: ssh_config.5,v 1.200 2015/01/26 03:04:45 djm Exp $ |
37 | .Dd $Mdocdate: December 22 2014 $ | 37 | .Dd $Mdocdate: January 26 2015 $ |
38 | .Dt SSH_CONFIG 5 | 38 | .Dt SSH_CONFIG 5 |
39 | .Os | 39 | .Os |
40 | .Sh NAME | 40 | .Sh NAME |
@@ -1492,6 +1492,28 @@ is not specified, it defaults to | |||
1492 | .Dq any . | 1492 | .Dq any . |
1493 | The default is | 1493 | The default is |
1494 | .Dq any:any . | 1494 | .Dq any:any . |
1495 | .It Cm UpdateHostkeys | ||
1496 | Specifies whether | ||
1497 | .Xr ssh 1 | ||
1498 | should accept notifications of additional hostkeys from the server sent | ||
1499 | after authentication has completed and add them to | ||
1500 | .Cm UserKnownHostsFile . | ||
1501 | The argument must be | ||
1502 | .Dq yes | ||
1503 | (the default) | ||
1504 | or | ||
1505 | .Dq no . | ||
1506 | Enabling this option allows learning alternate hostkeys for a server | ||
1507 | and supports graceful key rotation by allowing a server to public replacement | ||
1508 | keys before old ones are removed. | ||
1509 | Additional hostkeys are only accepted if the key used to authenticate the | ||
1510 | host was already trusted or explicity accepted by the user. | ||
1511 | .Pp | ||
1512 | Presently, only | ||
1513 | .Xr sshd 8 | ||
1514 | from OpenSSH 6.8 and greater support the | ||
1515 | .Dq hostkeys@openssh.com | ||
1516 | protocol extension used to inform the client of all the server's hostkeys. | ||
1495 | .It Cm UsePrivilegedPort | 1517 | .It Cm UsePrivilegedPort |
1496 | Specifies whether to use a privileged port for outgoing connections. | 1518 | Specifies whether to use a privileged port for outgoing connections. |
1497 | The argument must be | 1519 | The argument must be |
diff --git a/sshconnect.c b/sshconnect.c index 6fc3fa520..ae3b642cb 100644 --- a/sshconnect.c +++ b/sshconnect.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sshconnect.c,v 1.256 2015/01/20 23:14:00 deraadt Exp $ */ | 1 | /* $OpenBSD: sshconnect.c,v 1.257 2015/01/26 03:04:46 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 |
@@ -818,6 +818,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, | |||
818 | int len, cancelled_forwarding = 0; | 818 | int len, cancelled_forwarding = 0; |
819 | int local = sockaddr_is_local(hostaddr); | 819 | int local = sockaddr_is_local(hostaddr); |
820 | int r, want_cert = key_is_cert(host_key), host_ip_differ = 0; | 820 | int r, want_cert = key_is_cert(host_key), host_ip_differ = 0; |
821 | int hostkey_trusted = 0; /* Known or explicitly accepted by user */ | ||
821 | struct hostkeys *host_hostkeys, *ip_hostkeys; | 822 | struct hostkeys *host_hostkeys, *ip_hostkeys; |
822 | u_int i; | 823 | u_int i; |
823 | 824 | ||
@@ -926,6 +927,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, | |||
926 | free(ra); | 927 | free(ra); |
927 | free(fp); | 928 | free(fp); |
928 | } | 929 | } |
930 | hostkey_trusted = 1; | ||
929 | break; | 931 | break; |
930 | case HOST_NEW: | 932 | case HOST_NEW: |
931 | if (options.host_key_alias == NULL && port != 0 && | 933 | if (options.host_key_alias == NULL && port != 0 && |
@@ -989,6 +991,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, | |||
989 | free(fp); | 991 | free(fp); |
990 | if (!confirm(msg)) | 992 | if (!confirm(msg)) |
991 | goto fail; | 993 | goto fail; |
994 | hostkey_trusted = 1; /* user explicitly confirmed */ | ||
992 | } | 995 | } |
993 | /* | 996 | /* |
994 | * If not in strict mode, add the key automatically to the | 997 | * If not in strict mode, add the key automatically to the |
@@ -1187,6 +1190,12 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, | |||
1187 | } | 1190 | } |
1188 | } | 1191 | } |
1189 | 1192 | ||
1193 | if (!hostkey_trusted && options.update_hostkeys) { | ||
1194 | debug("%s: hostkey not known or explicitly trusted: " | ||
1195 | "disabling UpdateHostkeys", __func__); | ||
1196 | options.update_hostkeys = 0; | ||
1197 | } | ||
1198 | |||
1190 | free(ip); | 1199 | free(ip); |
1191 | free(host); | 1200 | free(host); |
1192 | if (host_hostkeys != NULL) | 1201 | if (host_hostkeys != NULL) |
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: sshd.c,v 1.438 2015/01/20 23:14:00 deraadt Exp $ */ | 1 | /* $OpenBSD: sshd.c,v 1.439 2015/01/26 03:04:46 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 |
@@ -911,6 +911,42 @@ get_hostkey_index(Key *key, struct ssh *ssh) | |||
911 | return (-1); | 911 | return (-1); |
912 | } | 912 | } |
913 | 913 | ||
914 | /* Inform the client of all hostkeys */ | ||
915 | static void | ||
916 | notify_hostkeys(struct ssh *ssh) | ||
917 | { | ||
918 | struct sshbuf *buf; | ||
919 | struct sshkey *key; | ||
920 | int i, nkeys, r; | ||
921 | char *fp; | ||
922 | |||
923 | if ((buf = sshbuf_new()) == NULL) | ||
924 | fatal("%s: sshbuf_new", __func__); | ||
925 | for (i = nkeys = 0; i < options.num_host_key_files; i++) { | ||
926 | key = get_hostkey_public_by_index(i, ssh); | ||
927 | if (key == NULL || key->type == KEY_UNSPEC || | ||
928 | key->type == KEY_RSA1 || sshkey_is_cert(key)) | ||
929 | continue; | ||
930 | fp = sshkey_fingerprint(key, options.fingerprint_hash, | ||
931 | SSH_FP_DEFAULT); | ||
932 | debug3("%s: key %d: %s %s", __func__, i, | ||
933 | sshkey_ssh_name(key), fp); | ||
934 | free(fp); | ||
935 | if ((r = sshkey_puts(key, buf)) != 0) | ||
936 | fatal("%s: couldn't put hostkey %d: %s", | ||
937 | __func__, i, ssh_err(r)); | ||
938 | nkeys++; | ||
939 | } | ||
940 | if (nkeys == 0) | ||
941 | fatal("%s: no hostkeys", __func__); | ||
942 | debug3("%s: send %d hostkeys", __func__, nkeys); | ||
943 | packet_start(SSH2_MSG_GLOBAL_REQUEST); | ||
944 | packet_put_cstring("hostkeys@openssh.com"); | ||
945 | packet_put_char(0); /* want-reply */ | ||
946 | packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf)); | ||
947 | packet_send(); | ||
948 | } | ||
949 | |||
914 | /* | 950 | /* |
915 | * returns 1 if connection should be dropped, 0 otherwise. | 951 | * returns 1 if connection should be dropped, 0 otherwise. |
916 | * dropping starts at connection #max_startups_begin with a probability | 952 | * dropping starts at connection #max_startups_begin with a probability |
@@ -1722,6 +1758,8 @@ main(int ac, char **av) | |||
1722 | continue; | 1758 | continue; |
1723 | key = key_load_private(options.host_key_files[i], "", NULL); | 1759 | key = key_load_private(options.host_key_files[i], "", NULL); |
1724 | pubkey = key_load_public(options.host_key_files[i], NULL); | 1760 | pubkey = key_load_public(options.host_key_files[i], NULL); |
1761 | if (pubkey == NULL && key != NULL) | ||
1762 | pubkey = key_demote(key); | ||
1725 | sensitive_data.host_keys[i] = key; | 1763 | sensitive_data.host_keys[i] = key; |
1726 | sensitive_data.host_pubkeys[i] = pubkey; | 1764 | sensitive_data.host_pubkeys[i] = pubkey; |
1727 | 1765 | ||
@@ -2185,6 +2223,10 @@ main(int ac, char **av) | |||
2185 | packet_set_timeout(options.client_alive_interval, | 2223 | packet_set_timeout(options.client_alive_interval, |
2186 | options.client_alive_count_max); | 2224 | options.client_alive_count_max); |
2187 | 2225 | ||
2226 | /* Try to send all our hostkeys to the client */ | ||
2227 | if (compat20) | ||
2228 | notify_hostkeys(active_state); | ||
2229 | |||
2188 | /* Start session. */ | 2230 | /* Start session. */ |
2189 | do_authenticated(authctxt); | 2231 | do_authenticated(authctxt); |
2190 | 2232 | ||