[openssh-commits] [openssh] 06/13: upstream commit

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Jan 27 00:34:02 EST 2015


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 8d4f87258f31cb6def9b3b55b6a7321d84728ff2
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Mon Jan 26 03:04:45 2015 +0000

    upstream commit
    
    Host key rotation support.
    
    Add a hostkeys at 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   | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++------
 hostfile.h   |   5 +-
 readconf.c   |  13 +++-
 readconf.h   |   6 +-
 ssh_config.5 |  26 +++++++-
 sshconnect.c |  11 +++-
 sshd.c       |  44 ++++++++++++-
 9 files changed, 404 insertions(+), 31 deletions(-)

diff --git a/PROTOCOL b/PROTOCOL
index aa59f58..8150c57 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 at 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 at 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 4522a63..7b54b6e 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 at 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 at 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 ccb2af9..9de1b38 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -39,6 +39,7 @@
 #include "includes.h"
 
 #include <sys/types.h>
+#include <sys/stat.h>
 
 #include <netinet/in.h>
 
@@ -49,6 +50,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdarg.h>
+#include <unistd.h>
 
 #include "xmalloc.h"
 #include "match.h"
@@ -430,43 +432,215 @@ lookup_key_in_hostkeys_by_type(struct hostkeys *hostkeys, int keytype,
 	    found) == HOST_FOUND);
 }
 
-/*
- * Appends an entry to the host file.  Returns false if the entry could not
- * be appended.
- */
-int
-add_host_to_hostfile(const char *filename, const char *host,
+static int
+write_host_entry(FILE *f, const char *host,
     const struct sshkey *key, int store_hash)
 {
-	FILE *f;
 	int r, success = 0;
 	char *hashed_host = NULL;
 
-	if (key == NULL)
-		return 1;	/* XXX ? */
-	f = fopen(filename, "a");
-	if (!f)
-		return 0;
-
 	if (store_hash) {
 		if ((hashed_host = host_hash(host, NULL, 0)) == NULL) {
 			error("%s: host_hash failed", __func__);
-			fclose(f);
 			return 0;
 		}
 	}
 	fprintf(f, "%s ", store_hash ? hashed_host : host);
 
-	if ((r = sshkey_write(key, f)) != 0) {
-		error("%s: saving key in %s failed: %s",
-		    __func__, filename, ssh_err(r));
-	} else
+	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.
+ */
+int
+add_host_to_hostfile(const char *filename, const char *host,
+    const struct sshkey *key, int store_hash)
+{
+	FILE *f;
+	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;
 }
 
+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;
+	}
+	/* 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;
+}
+
+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
 match_maybe_hashed(const char *host, const char *names, int *was_hashed)
 {
diff --git a/hostfile.h b/hostfile.h
index 24c3813..9080b5e 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 <ylo at cs.hut.fi>
@@ -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 cb61a5a..401f343 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 a23da11..7a8ae17 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 <ylo at cs.hut.fi>
@@ -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 361c322..0d4cdf4 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 at 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 6fc3fa5..ae3b642 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 ef63bd1..f2ee10d 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 at 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);
 

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list