From 8d4f87258f31cb6def9b3b55b6a7321d84728ff2 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 26 Jan 2015 03:04:45 +0000 Subject: 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@ --- PROTOCOL | 24 ++++++- clientloop.c | 94 ++++++++++++++++++++++++++- hostfile.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- hostfile.h | 5 +- readconf.c | 13 +++- readconf.h | 6 +- ssh_config.5 | 26 +++++++- sshconnect.c | 11 +++- sshd.c | 44 ++++++++++++- 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. boolean FALSE string socket path +2.5. connection: hostkey update and rotation "hostkeys@openssh.com" + +OpenSSH supports a protocol extension allowing a server to inform +a client of all its protocol v.2 hostkeys after user-authentication +has completed. + + byte SSH_MSG_GLOBAL_REQUEST + string "hostkeys@openssh.com" + string[] hostkeys + +Upon receiving this message, a client may update its known_hosts +file, adding keys that it has not seen before and deleting keys +for the server host that are no longer offered. + +This extension allows a client to learn key types that it had +not previously encountered, thereby allowing it to potentially +upgrade from weaker key algorithms to better ones. It also +supports graceful key rotation: a server may offer multiple keys +of the same type for a period (to give clients an opportunity to +learn them using this extension) before removing the deprecated +key from those offered. + 3. SFTP protocol changes 3.1. sftp: Reversal of arguments to SSH_FXP_SYMLINK @@ -406,4 +428,4 @@ respond with a SSH_FXP_STATUS message. This extension is advertised in the SSH_FXP_VERSION hello with version "1". -$OpenBSD: PROTOCOL,v 1.24 2014/07/15 15:54:14 millert Exp $ +$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 @@ -/* $OpenBSD: clientloop.c,v 1.266 2015/01/20 23:14:00 deraadt Exp $ */ +/* $OpenBSD: clientloop.c,v 1.267 2015/01/26 03:04:45 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -112,6 +112,7 @@ #include "msg.h" #include "roaming.h" #include "ssherr.h" +#include "hostfile.h" /* import options */ extern Options options; @@ -1781,6 +1782,7 @@ client_input_exit_status(int type, u_int32_t seq, void *ctxt) quit_pending = 1; return 0; } + static int client_input_agent_open(int type, u_int32_t seq, void *ctxt) { @@ -2038,6 +2040,7 @@ client_input_channel_open(int type, u_int32_t seq, void *ctxt) free(ctype); return 0; } + static int client_input_channel_req(int type, u_int32_t seq, void *ctxt) { @@ -2085,6 +2088,91 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt) free(rtype); return 0; } + +/* + * Handle hostkeys@openssh.com global request to inform the client of all + * the server's hostkeys. The keys are checked against the user's + * HostkeyAlgorithms preference before they are accepted. + */ +static int +client_input_hostkeys(void) +{ + const u_char *blob = NULL; + u_int i, len = 0, nkeys = 0; + struct sshbuf *buf = NULL; + struct sshkey *key = NULL, **tmp, **keys = NULL; + int r, success = 1; + char *fp, *host_str = NULL; + static int hostkeys_seen = 0; /* XXX use struct ssh */ + + /* + * NB. Return success for all cases other than protocol error. The + * server doesn't need to know what the client does with its hosts + * file. + */ + + blob = packet_get_string_ptr(&len); + packet_check_eom(); + + if (hostkeys_seen) + fatal("%s: server already sent hostkeys", __func__); + if (!options.update_hostkeys || options.num_user_hostfiles <= 0) + return 1; + if ((buf = sshbuf_from(blob, len)) == NULL) + fatal("%s: sshbuf_from failed", __func__); + while (sshbuf_len(buf) > 0) { + sshkey_free(key); + key = NULL; + if ((r = sshkey_froms(buf, &key)) != 0) + fatal("%s: parse key: %s", __func__, ssh_err(r)); + fp = sshkey_fingerprint(key, options.fingerprint_hash, + SSH_FP_DEFAULT); + debug3("%s: received %s key %s", __func__, + sshkey_type(key), fp); + free(fp); + /* Check that the key is accepted in HostkeyAlgorithms */ + if (options.hostkeyalgorithms != NULL && + match_pattern_list(sshkey_ssh_name(key), + options.hostkeyalgorithms, + strlen(options.hostkeyalgorithms), 0) != 1) { + debug3("%s: %s key not permitted by HostkeyAlgorithms", + __func__, sshkey_ssh_name(key)); + continue; + } + if ((tmp = reallocarray(keys, nkeys + 1, + sizeof(*keys))) == NULL) + fatal("%s: reallocarray failed nkeys = %u", + __func__, nkeys); + keys = tmp; + keys[nkeys++] = key; + key = NULL; + } + + debug3("%s: received %u keys from server", __func__, nkeys); + if (nkeys == 0) { + error("%s: server sent no hostkeys", __func__); + goto out; + } + + get_hostfile_hostname_ipaddr(host, NULL, options.port, &host_str, NULL); + + if ((r = hostfile_replace_entries(options.user_hostfiles[0], host_str, + keys, nkeys, options.hash_known_hosts, 1)) != 0) { + error("%s: hostfile_replace_entries failed: %s", + __func__, ssh_err(r)); + goto out; + } + + /* Success */ + out: + free(host_str); + sshkey_free(key); + for (i = 0; i < nkeys; i++) + sshkey_free(keys[i]); + sshbuf_free(buf); + return success; +} + static int client_input_global_request(int type, u_int32_t seq, void *ctxt) { @@ -2092,10 +2180,12 @@ client_input_global_request(int type, u_int32_t seq, void *ctxt) int want_reply; int success = 0; - rtype = packet_get_string(NULL); + rtype = packet_get_cstring(NULL); want_reply = packet_get_char(); debug("client_input_global_request: rtype %s want_reply %d", rtype, want_reply); + if (strcmp(rtype, "hostkeys@openssh.com") == 0) + success = client_input_hostkeys(); if (want_reply) { packet_start(success ? 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 @@ -/* $OpenBSD: hostfile.c,v 1.61 2015/01/18 21:48:09 djm Exp $ */ +/* $OpenBSD: hostfile.c,v 1.62 2015/01/26 03:04:45 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -39,6 +39,7 @@ #include "includes.h" #include +#include #include @@ -49,6 +50,7 @@ #include #include #include +#include #include "xmalloc.h" #include "match.h" @@ -430,6 +432,29 @@ lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype, found) == HOST_FOUND); } +static int +write_host_entry(FILE *f, const char *host, + const struct sshkey *key, int store_hash) +{ + int r, success = 0; + char *hashed_host = NULL; + + if (store_hash) { + if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { + error("%s: host_hash failed", __func__); + return 0; + } + } + fprintf(f, "%s ", store_hash ? hashed_host : host); + + if ((r = sshkey_write(key, f)) == 0) + success = 1; + else + error("%s: sshkey_write failed: %s", __func__, ssh_err(r)); + fputc('\n', f); + return success; +} + /* * Appends an entry to the host file. Returns false if the entry could not * be appended. @@ -439,32 +464,181 @@ add_host_to_hostfile(const char *filename, const char *host, const struct sshkey *key, int store_hash) { FILE *f; - int r, success = 0; - char *hashed_host = NULL; + int success; if (key == NULL) return 1; /* XXX ? */ f = fopen(filename, "a"); if (!f) return 0; + success = write_host_entry(f, host, key, store_hash); + fclose(f); + return success; +} - if (store_hash) { - if ((hashed_host = host_hash(host, NULL, 0)) == NULL) { - error("%s: host_hash failed", __func__); - fclose(f); +struct host_delete_ctx { + FILE *out; + int quiet; + const char *host; + int *skip_keys; + struct sshkey * const *keys; + size_t nkeys; +}; + +static int +host_delete(struct hostkey_foreach_line *l, void *_ctx) +{ + struct host_delete_ctx *ctx = (struct host_delete_ctx *)_ctx; + int loglevel = ctx->quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; + size_t i; + + if (l->status == HKF_STATUS_HOST_MATCHED) { + if (l->marker != MRK_NONE) { + /* Don't remove CA and revocation lines */ + fprintf(ctx->out, "%s\n", l->line); + return 0; + } + + /* XXX might need a knob for this later */ + /* Don't remove RSA1 keys */ + if (l->key->type == KEY_RSA1) { + fprintf(ctx->out, "%s\n", l->line); return 0; } + + /* + * If this line contains one of the keys that we will be + * adding later, then don't change it and mark the key for + * skipping. + */ + for (i = 0; i < ctx->nkeys; i++) { + if (sshkey_equal(ctx->keys[i], l->key)) { + ctx->skip_keys[i] = 1; + fprintf(ctx->out, "%s\n", l->line); + debug3("%s: %s key already at %s:%ld", __func__, + sshkey_type(l->key), l->path, l->linenum); + return 0; + } + } + + /* + * Hostname matches and has no CA/revoke marker, delete it + * by *not* writing the line to ctx->out. + */ + do_log2(loglevel, "%s%s%s:%ld: Host %s removed", + ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", + l->path, l->linenum, ctx->host); + return 0; } - fprintf(f, "%s ", store_hash ? hashed_host : host); + /* Retain non-matching hosts and invalid lines when deleting */ + if (l->status == HKF_STATUS_INVALID) { + do_log2(loglevel, "%s%s%s:%ld: invalid known_hosts entry", + ctx->quiet ? __func__ : "", ctx->quiet ? ": " : "", + l->path, l->linenum); + } + fprintf(ctx->out, "%s\n", l->line); + return 0; +} - if ((r = sshkey_write(key, f)) != 0) { - error("%s: saving key in %s failed: %s", - __func__, filename, ssh_err(r)); - } else - success = 1; - fputc('\n', f); - fclose(f); - return success; +int +hostfile_replace_entries(const char *filename, const char *host, + struct sshkey **keys, size_t nkeys, int store_hash, int quiet) +{ + int r, fd, oerrno = 0; + int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO; + struct host_delete_ctx ctx; + char *temp = NULL, *back = NULL; + mode_t omask; + size_t i; + + memset(&ctx, 0, sizeof(ctx)); + ctx.host = host; + ctx.quiet = quiet; + if ((ctx.skip_keys = calloc(nkeys, sizeof(*ctx.skip_keys))) == NULL) + return SSH_ERR_ALLOC_FAIL; + ctx.keys = keys; + ctx.nkeys = nkeys; + + /* + * Prepare temporary file for in-place deletion. + */ + if ((r = asprintf(&temp, "%s.XXXXXXXXXXX", filename)) < 0 || + (r = asprintf(&back, "%s.old", filename)) < 0) { + r = SSH_ERR_ALLOC_FAIL; + goto fail; + } + + omask = umask(077); + if ((fd = mkstemp(temp)) == -1) { + oerrno = errno; + error("%s: mkstemp: %s", __func__, strerror(oerrno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + if ((ctx.out = fdopen(fd, "w")) == NULL) { + oerrno = errno; + close(fd); + error("%s: fdopen: %s", __func__, strerror(oerrno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + + /* Remove all entries for the specified host from the file */ + if ((r = hostkeys_foreach(filename, host_delete, &ctx, host, + HKF_WANT_PARSE_KEY)) != 0) { + error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); + goto fail; + } + + /* Add the requested keys */ + for (i = 0; i < nkeys; i++) { + if (ctx.skip_keys[i]) + continue; + do_log2(loglevel, "%s%sadd %s key to %s", + quiet ? __func__ : "", quiet ? ": " : NULL, + sshkey_type(keys[i]), filename); + if (!write_host_entry(ctx.out, host, keys[i], store_hash)) { + r = SSH_ERR_INTERNAL_ERROR; + goto fail; + } + } + fclose(ctx.out); + ctx.out = NULL; + + /* Backup the original file and replace it with the temporary */ + if (unlink(back) == -1 && errno != ENOENT) { + oerrno = errno; + error("%s: unlink %.100s: %s", __func__, back, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + if (link(filename, back) == -1) { + oerrno = errno; + error("%s: link %.100s to %.100s: %s", __func__, filename, back, + strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + if (rename(temp, filename) == -1) { + oerrno = errno; + error("%s: rename \"%s\" to \"%s\": %s", __func__, + temp, filename, strerror(errno)); + r = SSH_ERR_SYSTEM_ERROR; + goto fail; + } + /* success */ + r = 0; + fail: + if (temp != NULL && r != 0) + unlink(temp); + free(temp); + free(back); + if (ctx.out != NULL) + fclose(ctx.out); + free(ctx.skip_keys); + if (r == SSH_ERR_SYSTEM_ERROR) + errno = oerrno; + return r; } static int diff --git a/hostfile.h b/hostfile.h index 24c3813aa..9080b5edb 100644 --- a/hostfile.h +++ b/hostfile.h @@ -1,4 +1,4 @@ -/* $OpenBSD: hostfile.h,v 1.22 2015/01/18 21:40:24 djm Exp $ */ +/* $OpenBSD: hostfile.h,v 1.23 2015/01/26 03:04:45 djm Exp $ */ /* * Author: Tatu Ylonen @@ -44,6 +44,9 @@ int hostfile_read_key(char **, u_int *, struct sshkey *); int add_host_to_hostfile(const char *, const char *, const struct sshkey *, int); +int hostfile_replace_entries(const char *filename, const char *host, + struct sshkey **keys, size_t nkeys, int store_hash, int quiet); + #define HASH_MAGIC "|1|" #define HASH_DELIM '|' diff --git a/readconf.c b/readconf.c index cb61a5af0..401f3430d 100644 --- a/readconf.c +++ b/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.228 2015/01/16 06:40:12 deraadt Exp $ */ +/* $OpenBSD: readconf.c,v 1.229 2015/01/26 03:04:45 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -156,7 +156,7 @@ typedef enum { oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots, oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs, oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys, - oFingerprintHash, + oFingerprintHash, oUpdateHostkeys, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; @@ -273,6 +273,7 @@ static struct { { "streamlocalbindunlink", oStreamLocalBindUnlink }, { "revokedhostkeys", oRevokedHostKeys }, { "fingerprinthash", oFingerprintHash }, + { "updatehostkeys", oUpdateHostkeys }, { "ignoreunknown", oIgnoreUnknown }, { NULL, oBadOption } @@ -1476,6 +1477,10 @@ parse_int: *intptr = value; break; + case oUpdateHostkeys: + intptr = &options->update_hostkeys; + goto parse_flag; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); @@ -1654,6 +1659,7 @@ initialize_options(Options * options) options->canonicalize_hostname = -1; options->revoked_host_keys = NULL; options->fingerprint_hash = -1; + options->update_hostkeys = -1; } /* @@ -1833,6 +1839,8 @@ fill_default_options(Options * options) options->canonicalize_hostname = SSH_CANONICALISE_NO; if (options->fingerprint_hash == -1) options->fingerprint_hash = SSH_FP_HASH_DEFAULT; + if (options->update_hostkeys == -1) + options->update_hostkeys = 1; #define CLEAR_ON_NONE(v) \ do { \ @@ -2256,6 +2264,7 @@ dump_client_config(Options *o, const char *host) dump_cfg_fmtint(oUsePrivilegedPort, o->use_privileged_port); dump_cfg_fmtint(oVerifyHostKeyDNS, o->verify_host_key_dns); dump_cfg_fmtint(oVisualHostKey, o->visual_host_key); + dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys); /* Integer options */ 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 @@ -/* $OpenBSD: readconf.h,v 1.106 2015/01/15 09:40:00 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.107 2015/01/26 03:04:45 djm Exp $ */ /* * Author: Tatu Ylonen @@ -146,7 +146,9 @@ typedef struct { char *revoked_host_keys; - int fingerprint_hash; + int fingerprint_hash; + + int update_hostkeys; char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ } 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 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.199 2014/12/22 09:24:59 jmc Exp $ -.Dd $Mdocdate: December 22 2014 $ +.\" $OpenBSD: ssh_config.5,v 1.200 2015/01/26 03:04:45 djm Exp $ +.Dd $Mdocdate: January 26 2015 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1492,6 +1492,28 @@ is not specified, it defaults to .Dq any . The default is .Dq any:any . +.It Cm UpdateHostkeys +Specifies whether +.Xr ssh 1 +should accept notifications of additional hostkeys from the server sent +after authentication has completed and add them to +.Cm UserKnownHostsFile . +The argument must be +.Dq yes +(the default) +or +.Dq no . +Enabling this option allows learning alternate hostkeys for a server +and supports graceful key rotation by allowing a server to public replacement +keys before old ones are removed. +Additional hostkeys are only accepted if the key used to authenticate the +host was already trusted or explicity accepted by the user. +.Pp +Presently, only +.Xr sshd 8 +from OpenSSH 6.8 and greater support the +.Dq hostkeys@openssh.com +protocol extension used to inform the client of all the server's hostkeys. .It Cm UsePrivilegedPort Specifies whether to use a privileged port for outgoing connections. 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 @@ -/* $OpenBSD: sshconnect.c,v 1.256 2015/01/20 23:14:00 deraadt Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.257 2015/01/26 03:04:46 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -818,6 +818,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, int len, cancelled_forwarding = 0; int local = sockaddr_is_local(hostaddr); int r, want_cert = key_is_cert(host_key), host_ip_differ = 0; + int hostkey_trusted = 0; /* Known or explicitly accepted by user */ struct hostkeys *host_hostkeys, *ip_hostkeys; u_int i; @@ -926,6 +927,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, free(ra); free(fp); } + hostkey_trusted = 1; break; case HOST_NEW: if (options.host_key_alias == NULL && port != 0 && @@ -989,6 +991,7 @@ check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port, free(fp); if (!confirm(msg)) goto fail; + hostkey_trusted = 1; /* user explicitly confirmed */ } /* * 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, } } + if (!hostkey_trusted && options.update_hostkeys) { + debug("%s: hostkey not known or explicitly trusted: " + "disabling UpdateHostkeys", __func__); + options.update_hostkeys = 0; + } + free(ip); free(host); 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 @@ -/* $OpenBSD: sshd.c,v 1.438 2015/01/20 23:14:00 deraadt Exp $ */ +/* $OpenBSD: sshd.c,v 1.439 2015/01/26 03:04:46 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -911,6 +911,42 @@ get_hostkey_index(Key *key, struct ssh *ssh) return (-1); } +/* Inform the client of all hostkeys */ +static void +notify_hostkeys(struct ssh *ssh) +{ + struct sshbuf *buf; + struct sshkey *key; + int i, nkeys, r; + char *fp; + + if ((buf = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new", __func__); + for (i = nkeys = 0; i < options.num_host_key_files; i++) { + key = get_hostkey_public_by_index(i, ssh); + if (key == NULL || key->type == KEY_UNSPEC || + key->type == KEY_RSA1 || sshkey_is_cert(key)) + continue; + fp = sshkey_fingerprint(key, options.fingerprint_hash, + SSH_FP_DEFAULT); + debug3("%s: key %d: %s %s", __func__, i, + sshkey_ssh_name(key), fp); + free(fp); + if ((r = sshkey_puts(key, buf)) != 0) + fatal("%s: couldn't put hostkey %d: %s", + __func__, i, ssh_err(r)); + nkeys++; + } + if (nkeys == 0) + fatal("%s: no hostkeys", __func__); + debug3("%s: send %d hostkeys", __func__, nkeys); + packet_start(SSH2_MSG_GLOBAL_REQUEST); + packet_put_cstring("hostkeys@openssh.com"); + packet_put_char(0); /* want-reply */ + packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf)); + packet_send(); +} + /* * returns 1 if connection should be dropped, 0 otherwise. * dropping starts at connection #max_startups_begin with a probability @@ -1722,6 +1758,8 @@ main(int ac, char **av) continue; key = key_load_private(options.host_key_files[i], "", NULL); pubkey = key_load_public(options.host_key_files[i], NULL); + if (pubkey == NULL && key != NULL) + pubkey = key_demote(key); sensitive_data.host_keys[i] = key; sensitive_data.host_pubkeys[i] = pubkey; @@ -2185,6 +2223,10 @@ main(int ac, char **av) packet_set_timeout(options.client_alive_interval, options.client_alive_count_max); + /* Try to send all our hostkeys to the client */ + if (compat20) + notify_hostkeys(active_state); + /* Start session. */ do_authenticated(authctxt); -- cgit v1.2.3