summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PROTOCOL24
-rw-r--r--clientloop.c94
-rw-r--r--hostfile.c206
-rw-r--r--hostfile.h5
-rw-r--r--readconf.c13
-rw-r--r--readconf.h6
-rw-r--r--ssh_config.526
-rw-r--r--sshconnect.c11
-rw-r--r--sshd.c44
9 files changed, 401 insertions, 28 deletions
diff --git a/PROTOCOL b/PROTOCOL
index aa59f584e..8150c577b 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -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
2852.5. connection: hostkey update and rotation "hostkeys@openssh.com"
286
287OpenSSH supports a protocol extension allowing a server to inform
288a client of all its protocol v.2 hostkeys after user-authentication
289has completed.
290
291 byte SSH_MSG_GLOBAL_REQUEST
292 string "hostkeys@openssh.com"
293 string[] hostkeys
294
295Upon receiving this message, a client may update its known_hosts
296file, adding keys that it has not seen before and deleting keys
297for the server host that are no longer offered.
298
299This extension allows a client to learn key types that it had
300not previously encountered, thereby allowing it to potentially
301upgrade from weaker key algorithms to better ones. It also
302supports graceful key rotation: a server may offer multiple keys
303of the same type for a period (to give clients an opportunity to
304learn them using this extension) before removing the deprecated
305key from those offered.
306
2853. SFTP protocol changes 3073. SFTP protocol changes
286 308
2873.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK 3093.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK
@@ -406,4 +428,4 @@ respond with a SSH_FXP_STATUS message.
406This extension is advertised in the SSH_FXP_VERSION hello with version 428This 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 */
117extern Options options; 118extern 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
1784static int 1786static int
1785client_input_agent_open(int type, u_int32_t seq, void *ctxt) 1787client_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
2041static int 2044static int
2042client_input_channel_req(int type, u_int32_t seq, void *ctxt) 2045client_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 */
2097static int
2098client_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
2088static int 2176static int
2089client_input_global_request(int type, u_int32_t seq, void *ctxt) 2177client_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
435static int
436write_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) { 479struct 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
488static int
489host_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) { 543int
461 error("%s: saving key in %s failed: %s", 544hostfile_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
470static int 644static 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 *);
44int add_host_to_hostfile(const char *, const char *, 44int add_host_to_hostfile(const char *, const char *,
45 const struct sshkey *, int); 45 const struct sshkey *, int);
46 46
47int 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 .
1493The default is 1493The default is
1494.Dq any:any . 1494.Dq any:any .
1495.It Cm UpdateHostkeys
1496Specifies whether
1497.Xr ssh 1
1498should accept notifications of additional hostkeys from the server sent
1499after authentication has completed and add them to
1500.Cm UserKnownHostsFile .
1501The argument must be
1502.Dq yes
1503(the default)
1504or
1505.Dq no .
1506Enabling this option allows learning alternate hostkeys for a server
1507and supports graceful key rotation by allowing a server to public replacement
1508keys before old ones are removed.
1509Additional hostkeys are only accepted if the key used to authenticate the
1510host was already trusted or explicity accepted by the user.
1511.Pp
1512Presently, only
1513.Xr sshd 8
1514from OpenSSH 6.8 and greater support the
1515.Dq hostkeys@openssh.com
1516protocol extension used to inform the client of all the server's hostkeys.
1495.It Cm UsePrivilegedPort 1517.It Cm UsePrivilegedPort
1496Specifies whether to use a privileged port for outgoing connections. 1518Specifies whether to use a privileged port for outgoing connections.
1497The argument must be 1519The 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)
diff --git a/sshd.c b/sshd.c
index ef63bd1e8..f2ee10d2c 100644
--- a/sshd.c
+++ b/sshd.c
@@ -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 */
915static void
916notify_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