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@
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@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@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 @@
 	quit_pending = 1;
 	return 0;
 }
+
 static int
 client_input_agent_open(int type, u_int32_t seq, void *ctxt)
 {
@@ -2038,6 +2040,7 @@
 	free(ctype);
 	return 0;
 }
+
 static int
 client_input_channel_req(int type, u_int32_t seq, void *ctxt)
 {
@@ -2085,6 +2088,91 @@
 	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 @@
 	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);