[openssh-commits] [openssh] 05/10: upstream commit

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Feb 17 09:39:17 AEDT 2015


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

djm pushed a commit to branch master
in repository openssh.

commit 523463a3a2a9bfc6cfc5afa01bae9147f76a37cc
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Mon Feb 16 22:13:32 2015 +0000

    upstream commit
    
    Revise hostkeys at openssh.com hostkey learning extension.
    
    The client will not ask the server to prove ownership of the private
    halves of any hitherto-unseen hostkeys it offers to the client.
    
    Allow UpdateHostKeys option to take an 'ask' argument to let the
    user manually review keys offered.
    
    ok markus@
---
 PROTOCOL       |  55 ++++++---
 auth.h         |   7 +-
 clientloop.c   | 353 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
 kex.h          |   6 +-
 monitor.c      |  45 +++++++-
 monitor_wrap.c |   7 +-
 monitor_wrap.h |   4 +-
 readconf.c     |   6 +-
 readconf.h     |   8 +-
 serverloop.c   |  88 +++++++++++++-
 ssh_api.c      |   7 +-
 ssh_config.5   |  15 ++-
 sshd.c         |  35 ++++--
 ssherr.c       |   4 +-
 14 files changed, 538 insertions(+), 102 deletions(-)

diff --git a/PROTOCOL b/PROTOCOL
index 8150c57..f956083 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -40,8 +40,8 @@ http://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt
      "ecdsa-sha2-nistp521-cert-v01 at openssh.com"
 
 OpenSSH introduces new public key algorithms to support certificate
-authentication for users and hostkeys. These methods are documented in
-the file PROTOCOL.certkeys
+authentication for users and host keys. These methods are documented
+in the file PROTOCOL.certkeys
 
 1.4. transport: Elliptic Curve cryptography
 
@@ -283,26 +283,51 @@ by the client cancel the forwarding of a Unix domain socket.
 	string		socket path
 
 2.5. connection: hostkey update and rotation "hostkeys at openssh.com"
+and "hostkeys-prove 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
+a client of all its protocol v.2 host keys 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.
+Upon receiving this message, a client should check which of the
+supplied host keys are present in known_hosts. For keys that are
+not present, it should send a "hostkeys-prove at openssh.com" message
+to request the server prove ownership of the private half of the
+key.
+
+	byte		SSH_MSG_GLOBAL_REQUEST
+	string		"hostkeys-prove at openssh.com"
+	char		1 /* want-reply */
+	string[]	hostkeys
+
+When a server receives this message, it should generate a signature
+using each requested key over the following:
+
+	string		session identifier
+	string		"hostkeys-prove at openssh.com"
+	string		hostkey
+
+These signatures should be included in the reply, in the order matching
+the hostkeys in the request:
+
+	byte		SSH_MSG_REQUEST_SUCCESS
+	string[]	signatures
+
+When the client receives this reply (and not a failure), it should
+validate the signatures and 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.
+
+These extensions let a client 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
 
@@ -428,4 +453,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.25 2015/01/26 03:04:45 djm Exp $
+$OpenBSD: PROTOCOL,v 1.26 2015/02/16 22:13:32 djm Exp $
diff --git a/auth.h b/auth.h
index d282619..db86037 100644
--- a/auth.h
+++ b/auth.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.h,v 1.81 2015/01/26 06:10:03 djm Exp $ */
+/* $OpenBSD: auth.h,v 1.82 2015/02/16 22:13:32 djm Exp $ */
 
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
@@ -206,9 +206,10 @@ Key	*get_hostkey_by_index(int);
 Key	*get_hostkey_public_by_index(int, struct ssh *);
 Key	*get_hostkey_public_by_type(int, int, struct ssh *);
 Key	*get_hostkey_private_by_type(int, int, struct ssh *);
-int	 get_hostkey_index(Key *, struct ssh *);
+int	 get_hostkey_index(Key *, int, struct ssh *);
 int	 ssh1_session_key(BIGNUM *);
-int	 sshd_hostkey_sign(Key *, Key *, u_char **, size_t *, u_char *, size_t, u_int);
+int	 sshd_hostkey_sign(Key *, Key *, u_char **, size_t *,
+	     const u_char *, size_t, u_int);
 
 /* debug messages during authentication */
 void	 auth_debug_add(const char *fmt,...) __attribute__((format(printf, 1, 2)));
diff --git a/clientloop.c b/clientloop.c
index c6f8e9d..a19d9d0 100644
--- a/clientloop.c
+++ b/clientloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: clientloop.c,v 1.268 2015/02/16 22:08:57 djm Exp $ */
+/* $OpenBSD: clientloop.c,v 1.269 2015/02/16 22:13:32 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -2089,6 +2089,216 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
 	return 0;
 }
 
+struct hostkeys_update_ctx {
+	/* The hostname and (optionally) IP address string for the server */
+	char *host_str, *ip_str;
+
+	/*
+	 * Keys received from the server and a flag for each indicating
+	 * whether they already exist in known_hosts.
+	 * keys_seen is filled in by hostkeys_find() and later (for new
+	 * keys) by client_global_hostkeys_private_confirm().
+	 */
+	struct sshkey **keys;
+	int *keys_seen;
+	size_t nkeys;
+
+	size_t nnew;
+
+	/*
+	 * Keys that are in known_hosts, but were not present in the update
+	 * from the server (i.e. scheduled to be deleted).
+	 * Filled in by hostkeys_find().
+	 */
+	struct sshkey **old_keys;
+	size_t nold;
+};
+
+static void
+hostkeys_update_ctx_free(struct hostkeys_update_ctx *ctx)
+{
+	size_t i;
+
+	if (ctx == NULL)
+		return;
+	for (i = 0; i < ctx->nkeys; i++)
+		sshkey_free(ctx->keys[i]);
+	free(ctx->keys);
+	free(ctx->keys_seen);
+	for (i = 0; i < ctx->nold; i++)
+		sshkey_free(ctx->old_keys[i]);
+	free(ctx->old_keys);
+	free(ctx->host_str);
+	free(ctx->ip_str);
+	free(ctx);
+}
+
+static int
+hostkeys_find(struct hostkey_foreach_line *l, void *_ctx)
+{
+	struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx;
+	size_t i;
+	struct sshkey **tmp;
+
+	if (l->status != HKF_STATUS_MATCHED || l->key == NULL ||
+	    l->key->type == KEY_RSA1)
+		return 0;
+
+	/* Mark off keys we've already seen for this host */
+	for (i = 0; i < ctx->nkeys; i++) {
+		if (sshkey_equal(l->key, ctx->keys[i])) {
+			debug3("%s: found %s key at %s:%ld", __func__,
+			    sshkey_ssh_name(ctx->keys[i]), l->path, l->linenum);
+			ctx->keys_seen[i] = 1;
+			return 0;
+		}
+	}
+	/* This line contained a key that not offered by the server */
+	debug3("%s: deprecated %s key at %s:%ld", __func__,
+	    sshkey_ssh_name(l->key), l->path, l->linenum);
+	if ((tmp = reallocarray(ctx->old_keys, ctx->nold + 1,
+	    sizeof(*ctx->old_keys))) == NULL)
+		fatal("%s: reallocarray failed nold = %zu",
+		    __func__, ctx->nold);
+	ctx->old_keys = tmp;
+	ctx->old_keys[ctx->nold++] = l->key;
+	l->key = NULL;
+
+	return 0;
+}
+
+static void
+update_known_hosts(struct hostkeys_update_ctx *ctx)
+{
+	int r, loglevel = options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK ?
+	    SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE;
+	char *fp, *response;
+	size_t i;
+
+	for (i = 0; i < ctx->nkeys; i++) {
+		if (ctx->keys_seen[i] != 2)
+			continue;
+		if ((fp = sshkey_fingerprint(ctx->keys[i],
+		    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+			fatal("%s: sshkey_fingerprint failed", __func__);
+		do_log2(loglevel, "Learned new hostkey: %s %s",
+		    sshkey_type(ctx->keys[i]), fp);
+		free(fp);
+	}
+	for (i = 0; i < ctx->nold; i++) {
+		if ((fp = sshkey_fingerprint(ctx->old_keys[i],
+		    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+			fatal("%s: sshkey_fingerprint failed", __func__);
+		do_log2(loglevel, "Deprecating obsolete hostkey: %s %s",
+		    sshkey_type(ctx->old_keys[i]), fp);
+		free(fp);
+	}
+	if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK) {
+		leave_raw_mode(options.request_tty == REQUEST_TTY_FORCE);
+		response = NULL;
+		for (i = 0; !quit_pending && i < 3; i++) {
+			free(response);
+			response = read_passphrase("Accept updated hostkeys? "
+			    "(yes/no): ", RP_ECHO);
+			if (strcasecmp(response, "yes") == 0)
+				break;
+			else if (quit_pending || response == NULL ||
+			    strcasecmp(response, "no") == 0) {
+				options.update_hostkeys = 0;
+				break;
+			} else {
+				do_log2(loglevel, "Please enter "
+				    "\"yes\" or \"no\"");
+			}
+		}
+		if (quit_pending || i >= 3 || response == NULL)
+			options.update_hostkeys = 0;
+		free(response);
+		enter_raw_mode(options.request_tty == REQUEST_TTY_FORCE);
+	}
+
+	/*
+	 * Now that all the keys are verified, we can go ahead and replace
+	 * them in known_hosts (assuming SSH_UPDATE_HOSTKEYS_ASK didn't
+	 * cancel the operation).
+	 */
+	if (options.update_hostkeys != 0 &&
+	    (r = hostfile_replace_entries(options.user_hostfiles[0],
+	    ctx->host_str, ctx->ip_str, ctx->keys, ctx->nkeys,
+	    options.hash_known_hosts, 0,
+	    options.fingerprint_hash)) != 0)
+		error("%s: hostfile_replace_entries failed: %s",
+		    __func__, ssh_err(r));
+}
+
+static void
+client_global_hostkeys_private_confirm(int type, u_int32_t seq, void *_ctx)
+{
+	struct ssh *ssh = active_state; /* XXX */
+	struct hostkeys_update_ctx *ctx = (struct hostkeys_update_ctx *)_ctx;
+	size_t i, ndone;
+	struct sshbuf *signdata;
+	int r;
+	const u_char *sig;
+	size_t siglen;
+
+	if (ctx->nnew == 0)
+		fatal("%s: ctx->nnew == 0", __func__); /* sanity */
+	if (type != SSH2_MSG_REQUEST_SUCCESS) {
+		error("Server failed to confirm ownership of "
+		    "private host keys");
+		hostkeys_update_ctx_free(ctx);
+		return;
+	}
+	if ((signdata = sshbuf_new()) == NULL)
+		fatal("%s: sshbuf_new failed", __func__);
+	/* Don't want to accidentally accept an unbound signature */
+	if (ssh->kex->session_id_len == 0)
+		fatal("%s: ssh->kex->session_id_len == 0", __func__);
+	/*
+	 * Expect a signature for each of the ctx->nnew private keys we
+	 * haven't seen before. They will be in the same order as the
+	 * ctx->keys where the corresponding ctx->keys_seen[i] == 0.
+	 */
+	for (ndone = i = 0; i < ctx->nkeys; i++) {
+		if (ctx->keys_seen[i])
+			continue;
+		/* Prepare data to be signed: session ID, unique string, key */
+		sshbuf_reset(signdata);
+		if ((r = sshbuf_put_string(signdata, ssh->kex->session_id,
+		    ssh->kex->session_id_len)) != 0 ||
+		    (r = sshbuf_put_cstring(signdata,
+		    "hostkeys-prove at openssh.com")) != 0 ||
+		    (r = sshkey_puts(ctx->keys[i], signdata)) != 0)
+			fatal("%s: failed to prepare signature: %s",
+			    __func__, ssh_err(r));
+		/* Extract and verify signature */
+		if ((r = sshpkt_get_string_direct(ssh, &sig, &siglen)) != 0) {
+			error("%s: couldn't parse message: %s",
+			    __func__, ssh_err(r));
+			goto out;
+		}
+		if ((r = sshkey_verify(ctx->keys[i], sig, siglen,
+		    sshbuf_ptr(signdata), sshbuf_len(signdata), 0)) != 0) {
+			error("%s: server gave bad signature for %s key %zu",
+			    __func__, sshkey_type(ctx->keys[i]), i);
+			goto out;
+		}
+		/* Key is good. Mark it as 'seen' */
+		ctx->keys_seen[i] = 2;
+		ndone++;
+	}
+	if (ndone != ctx->nnew)
+		fatal("%s: ndone != ctx->nnew (%zu / %zu)", __func__,
+		    ndone, ctx->nnew);  /* Shouldn't happen */
+	ssh_packet_check_eom(ssh);
+
+	/* Make the edits to known_hosts */
+	update_known_hosts(ctx);
+ out:
+	hostkeys_update_ctx_free(ctx);
+}
+
 /*
  * 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
@@ -2097,34 +2307,35 @@ client_input_channel_req(int type, u_int32_t seq, void *ctxt)
 static int
 client_input_hostkeys(void)
 {
+	struct ssh *ssh = active_state; /* XXX */
 	const u_char *blob = NULL;
-	u_int i, len = 0, nkeys = 0;
+	size_t i, len = 0;
 	struct sshbuf *buf = NULL;
-	struct sshkey *key = NULL, **tmp, **keys = NULL;
-	int r, success = 1;
-	char *fp, *host_str = NULL, *ip_str = NULL;
+	struct sshkey *key = NULL, **tmp;
+	int r;
+	char *fp;
 	static int hostkeys_seen = 0; /* XXX use struct ssh */
 	extern struct sockaddr_storage hostaddr; /* XXX from ssh.c */
+	struct hostkeys_update_ctx *ctx;
 
-	/*
-	 * 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();
+	ctx = xcalloc(1, sizeof(*ctx));
 
 	if (hostkeys_seen)
 		fatal("%s: server already sent hostkeys", __func__);
+	if (options.update_hostkeys == SSH_UPDATE_HOSTKEYS_ASK &&
+	    options.batch_mode)
+		return 1; /* won't ask in batchmode, so don't even try */
 	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) {
+	while (ssh_packet_remaining(ssh) > 0) {
 		sshkey_free(key);
 		key = NULL;
-		if ((r = sshkey_froms(buf, &key)) != 0)
+		if ((r = sshpkt_get_string_direct(ssh, &blob, &len)) != 0) {
+			error("%s: couldn't parse message: %s",
+			    __func__, ssh_err(r));
+			goto out;
+		}
+		if ((r = sshkey_from_blob(blob, len, &key)) != 0)
 			fatal("%s: parse key: %s", __func__, ssh_err(r));
 		fp = sshkey_fingerprint(key, options.fingerprint_hash,
 		    SSH_FP_DEFAULT);
@@ -2140,47 +2351,107 @@ client_input_hostkeys(void)
 			    __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;
+		/* Skip certs */
+		if (sshkey_is_cert(key)) {
+			debug3("%s: %s key is a certificate; skipping",
+			    __func__, sshkey_ssh_name(key));
+			continue;
+		}
+		/* Ensure keys are unique */
+		for (i = 0; i < ctx->nkeys; i++) {
+			if (sshkey_equal(key, ctx->keys[i])) {
+				error("%s: received duplicated %s host key",
+				    __func__, sshkey_ssh_name(key));
+				goto out;
+			}
+		}
+		/* Key is good, record it */
+		if ((tmp = reallocarray(ctx->keys, ctx->nkeys + 1,
+		    sizeof(*ctx->keys))) == NULL)
+			fatal("%s: reallocarray failed nkeys = %zu",
+			    __func__, ctx->nkeys);
+		ctx->keys = tmp;
+		ctx->keys[ctx->nkeys++] = key;
 		key = NULL;
 	}
 
-	if (nkeys == 0) {
+	if (ctx->nkeys == 0) {
 		error("%s: server sent no hostkeys", __func__);
 		goto out;
 	}
+	if ((ctx->keys_seen = calloc(ctx->nkeys,
+	    sizeof(*ctx->keys_seen))) == NULL)
+		fatal("%s: calloc failed", __func__);
 
 	get_hostfile_hostname_ipaddr(host,
 	    options.check_host_ip ? (struct sockaddr *)&hostaddr : NULL,
-	    options.port, &host_str, options.check_host_ip ? &ip_str : NULL);
+	    options.port, &ctx->host_str,
+	    options.check_host_ip ? &ctx->ip_str : NULL);
 
-	debug3("%s: update known hosts for %s%s%s with %u keys from server",
-	    __func__, host_str,
-	    options.check_host_ip ? " " : "",
-	    options.check_host_ip ? ip_str : "", nkeys);
-
-	if ((r = hostfile_replace_entries(options.user_hostfiles[0],
-	    host_str, options.check_host_ip ? ip_str : NULL,
-	    keys, nkeys, options.hash_known_hosts, 0,
-	    options.fingerprint_hash)) != 0) {
-		error("%s: hostfile_replace_entries failed: %s",
-		    __func__, ssh_err(r));
+	/* Find which keys we already know about. */
+	if ((r = hostkeys_foreach(options.user_hostfiles[0], hostkeys_find,
+	    ctx, ctx->host_str, ctx->ip_str,
+	    HKF_WANT_PARSE_KEY|HKF_WANT_MATCH)) != 0) {
+		error("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r));
 		goto out;
 	}
 
+	/* Figure out if we have any new keys to add */
+	ctx->nnew = 0;
+	for (i = 0; i < ctx->nkeys; i++) {
+		if (!ctx->keys_seen[i])
+			ctx->nnew++;
+	}
+
+	debug3("%s: %zu keys from server: %zu new, %zu retained. %zu to remove",
+	    __func__, ctx->nkeys, ctx->nnew, ctx->nkeys - ctx->nnew, ctx->nold);
+
+	if (ctx->nnew == 0 && ctx->nold != 0) {
+		/* We have some keys to remove. Just do it. */
+		update_known_hosts(ctx);
+	} else if (ctx->nnew != 0) {
+		/*
+		 * We have received hitherto-unseen keys from the server.
+		 * Ask the server to confirm ownership of the private halves.
+		 */
+		debug3("%s: asking server to prove ownership for %zu keys",
+		    __func__, ctx->nnew);
+		if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 ||
+		    (r = sshpkt_put_cstring(ssh,
+		    "hostkeys-prove at openssh.com")) != 0 ||
+		    (r = sshpkt_put_u8(ssh, 1)) != 0) /* bool: want reply */
+			fatal("%s: cannot prepare packet: %s",
+			    __func__, ssh_err(r));
+		if ((buf = sshbuf_new()) == NULL)
+			fatal("%s: sshbuf_new", __func__);
+		for (i = 0; i < ctx->nkeys; i++) {
+			if (ctx->keys_seen[i])
+				continue;
+			sshbuf_reset(buf);
+			if ((r = sshkey_putb(ctx->keys[i], buf)) != 0)
+				fatal("%s: sshkey_putb: %s",
+				    __func__, ssh_err(r));
+			if ((r = sshpkt_put_stringb(ssh, buf)) != 0)
+				fatal("%s: sshpkt_put_string: %s",
+				    __func__, ssh_err(r));
+		}
+		if ((r = sshpkt_send(ssh)) != 0)
+			fatal("%s: sshpkt_send: %s", __func__, ssh_err(r));
+		client_register_global_confirm(
+		    client_global_hostkeys_private_confirm, ctx);
+		ctx = NULL;  /* will be freed in callback */
+	}
+
 	/* Success */
  out:
-	free(host_str);
-	free(ip_str);
+	hostkeys_update_ctx_free(ctx);
 	sshkey_free(key);
-	for (i = 0; i < nkeys; i++)
-		sshkey_free(keys[i]);
 	sshbuf_free(buf);
-	return success;
+	/*
+	 * NB. Return success for all cases. The server doesn't need to know
+	 * what the client does with its hosts file.
+	 */
+	return 1;
 }
 
 static int
diff --git a/kex.h b/kex.h
index 45d3577..99a7d55 100644
--- a/kex.h
+++ b/kex.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.h,v 1.70 2015/01/26 06:10:03 djm Exp $ */
+/* $OpenBSD: kex.h,v 1.71 2015/02/16 22:13:32 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -130,9 +130,9 @@ struct kex {
 	int	(*verify_host_key)(struct sshkey *, struct ssh *);
 	struct sshkey *(*load_host_public_key)(int, int, struct ssh *);
 	struct sshkey *(*load_host_private_key)(int, int, struct ssh *);
-	int	(*host_key_index)(struct sshkey *, struct ssh *);
+	int	(*host_key_index)(struct sshkey *, int, struct ssh *);
 	int	(*sign)(struct sshkey *, struct sshkey *,
-	    u_char **, size_t *, u_char *, size_t, u_int);
+	    u_char **, size_t *, const u_char *, size_t, u_int);
 	int	(*kex[KEX_MAX])(struct ssh *);
 	/* kex specific state */
 	DH	*dh;			/* DH */
diff --git a/monitor.c b/monitor.c
index e97b20e..6e97def 100644
--- a/monitor.c
+++ b/monitor.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor.c,v 1.143 2015/02/13 18:57:00 markus Exp $ */
+/* $OpenBSD: monitor.c,v 1.144 2015/02/16 22:13:32 djm Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos at citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus at openbsd.org>
@@ -685,12 +685,15 @@ mm_answer_moduli(int sock, Buffer *m)
 int
 mm_answer_sign(int sock, Buffer *m)
 {
+	struct ssh *ssh = active_state; 	/* XXX */
 	extern int auth_sock;			/* XXX move to state struct? */
 	struct sshkey *key;
+	struct sshbuf *sigbuf;
 	u_char *p;
 	u_char *signature;
 	size_t datlen, siglen;
-	int r, keyid;
+	int r, keyid, is_proof = 0;
+	const char proof_req[] = "hostkeys-prove at openssh.com";
 
 	debug3("%s", __func__);
 
@@ -701,9 +704,38 @@ mm_answer_sign(int sock, Buffer *m)
 	/*
 	 * Supported KEX types use SHA1 (20 bytes), SHA256 (32 bytes),
 	 * SHA384 (48 bytes) and SHA512 (64 bytes).
+	 *
+	 * Otherwise, verify the signature request is for a hostkey
+	 * proof.
+	 *
+	 * XXX perform similar check for KEX signature requests too?
+	 * it's not trivial, since what is signed is the hash, rather
+	 * than the full kex structure...
 	 */
-	if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64)
-		fatal("%s: data length incorrect: %zu", __func__, datlen);
+	if (datlen != 20 && datlen != 32 && datlen != 48 && datlen != 64) {
+		/*
+		 * Construct expected hostkey proof and compare it to what
+		 * the client sent us.
+		 */
+		if (session_id2_len == 0) /* hostkeys is never first */
+			fatal("%s: bad data length: %zu", __func__, datlen);
+		if ((key = get_hostkey_public_by_index(keyid, ssh)) == NULL)
+			fatal("%s: no hostkey for index %d", __func__, keyid);
+		if ((sigbuf = sshbuf_new()) == NULL)
+			fatal("%s: sshbuf_new", __func__);
+		if ((r = sshbuf_put_string(sigbuf, session_id2,
+		    session_id2_len) != 0) ||
+		    (r = sshbuf_put_cstring(sigbuf, proof_req)) != 0 ||
+		    (r = sshkey_puts(key, sigbuf)) != 0)
+			fatal("%s: couldn't prepare private key "
+			    "proof buffer: %s", __func__, ssh_err(r));
+		if (datlen != sshbuf_len(sigbuf) ||
+		    memcmp(p, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf)) != 0)
+			fatal("%s: bad data length: %zu, hostkey proof len %zu",
+			    __func__, datlen, sshbuf_len(sigbuf));
+		sshbuf_free(sigbuf);
+		is_proof = 1;
+	}
 
 	/* save session id, it will be passed on the first call */
 	if (session_id2_len == 0) {
@@ -717,7 +749,7 @@ mm_answer_sign(int sock, Buffer *m)
 		    datafellows)) != 0)
 			fatal("%s: sshkey_sign failed: %s",
 			    __func__, ssh_err(r));
-	} else if ((key = get_hostkey_public_by_index(keyid, active_state)) != NULL &&
+	} else if ((key = get_hostkey_public_by_index(keyid, ssh)) != NULL &&
 	    auth_sock > 0) {
 		if ((r = ssh_agent_sign(auth_sock, key, &signature, &siglen,
 		    p, datlen, datafellows)) != 0) {
@@ -727,7 +759,8 @@ mm_answer_sign(int sock, Buffer *m)
 	} else
 		fatal("%s: no hostkey from index %d", __func__, keyid);
 
-	debug3("%s: signature %p(%zu)", __func__, signature, siglen);
+	debug3("%s: %s signature %p(%zu)", __func__,
+	    is_proof ? "KEX" : "hostkey proof", signature, siglen);
 
 	sshbuf_reset(m);
 	if ((r = sshbuf_put_string(m, signature, siglen)) != 0)
diff --git a/monitor_wrap.c b/monitor_wrap.c
index c0935dc..b379f05 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor_wrap.c,v 1.83 2015/01/19 20:16:15 markus Exp $ */
+/* $OpenBSD: monitor_wrap.c,v 1.84 2015/02/16 22:13:32 djm Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos at citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus at openbsd.org>
@@ -219,7 +219,8 @@ mm_choose_dh(int min, int nbits, int max)
 #endif
 
 int
-mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen)
+mm_key_sign(Key *key, u_char **sigp, u_int *lenp,
+    const u_char *data, u_int datalen)
 {
 	struct kex *kex = *pmonitor->m_pkex;
 	Buffer m;
@@ -227,7 +228,7 @@ mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen)
 	debug3("%s entering", __func__);
 
 	buffer_init(&m);
-	buffer_put_int(&m, kex->host_key_index(key, active_state));
+	buffer_put_int(&m, kex->host_key_index(key, 0, active_state));
 	buffer_put_string(&m, data, datalen);
 
 	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m);
diff --git a/monitor_wrap.h b/monitor_wrap.h
index d97e8db..e18784a 100644
--- a/monitor_wrap.h
+++ b/monitor_wrap.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor_wrap.h,v 1.25 2015/01/19 19:52:16 markus Exp $ */
+/* $OpenBSD: monitor_wrap.h,v 1.26 2015/02/16 22:13:32 djm Exp $ */
 
 /*
  * Copyright 2002 Niels Provos <provos at citi.umich.edu>
@@ -40,7 +40,7 @@ struct Authctxt;
 void mm_log_handler(LogLevel, const char *, void *);
 int mm_is_monitor(void);
 DH *mm_choose_dh(int, int, int);
-int mm_key_sign(Key *, u_char **, u_int *, u_char *, u_int);
+int mm_key_sign(Key *, u_char **, u_int *, const u_char *, u_int);
 void mm_inform_authserv(char *, char *);
 struct passwd *mm_getpwnamallow(const char *);
 char *mm_auth2_read_banner(void);
diff --git a/readconf.c b/readconf.c
index a5bb4a2..42a2961 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.231 2015/02/02 07:41:40 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.232 2015/02/16 22:13:32 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -1480,7 +1480,8 @@ parse_int:
 
 	case oUpdateHostkeys:
 		intptr = &options->update_hostkeys;
-		goto parse_flag;
+		multistate_ptr = multistate_yesnoask;
+		goto parse_multistate;
 
 	case oHostbasedKeyTypes:
 		charptr = &options->hostbased_key_types;
@@ -2107,6 +2108,7 @@ fmt_intarg(OpCodes code, int val)
 		return fmt_multistate_int(val, multistate_addressfamily);
 	case oVerifyHostKeyDNS:
 	case oStrictHostKeyChecking:
+	case oUpdateHostkeys:
 		return fmt_multistate_int(val, multistate_yesnoask);
 	case oControlMaster:
 		return fmt_multistate_int(val, multistate_controlmaster);
diff --git a/readconf.h b/readconf.h
index 701b9c6..576b9e3 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.108 2015/01/30 11:43:14 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.109 2015/02/16 22:13:32 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
@@ -148,7 +148,7 @@ typedef struct {
 
 	int	 fingerprint_hash;
 
-	int	 update_hostkeys;
+	int	 update_hostkeys; /* one of SSH_UPDATE_HOSTKEYS_* */
 
 	char	*hostbased_key_types;
 
@@ -174,6 +174,10 @@ typedef struct {
 #define SSHCONF_USERCONF	2  /* user provided config file not system */
 #define SSHCONF_POSTCANON	4  /* After hostname canonicalisation */
 
+#define SSH_UPDATE_HOSTKEYS_NO	0
+#define SSH_UPDATE_HOSTKEYS_YES	1
+#define SSH_UPDATE_HOSTKEYS_ASK	2
+
 void     initialize_options(Options *);
 void     fill_default_options(Options *);
 void	 fill_default_options_for_canonicalization(Options *);
diff --git a/serverloop.c b/serverloop.c
index 48bb3f6..5633ceb 100644
--- a/serverloop.c
+++ b/serverloop.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: serverloop.c,v 1.176 2015/01/20 23:14:00 deraadt Exp $ */
+/* $OpenBSD: serverloop.c,v 1.177 2015/02/16 22:13:32 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -79,6 +79,7 @@
 #include "auth-options.h"
 #include "serverloop.h"
 #include "roaming.h"
+#include "ssherr.h"
 
 extern ServerOptions options;
 
@@ -1150,11 +1151,82 @@ server_input_channel_open(int type, u_int32_t seq, void *ctxt)
 }
 
 static int
+server_input_hostkeys_prove(struct sshbuf **respp)
+{
+	struct ssh *ssh = active_state; /* XXX */
+	struct sshbuf *resp = NULL;
+	struct sshbuf *sigbuf = NULL;
+	struct sshkey *key = NULL, *key_pub = NULL, *key_prv = NULL;
+	int r, ndx, success = 0;
+	const u_char *blob;
+	u_char *sig = 0;
+	size_t blen, slen;
+
+	if ((resp = sshbuf_new()) == NULL || (sigbuf = sshbuf_new()) == NULL)
+		fatal("%s: sshbuf_new", __func__);
+
+	while (ssh_packet_remaining(ssh) > 0) {
+		sshkey_free(key);
+		key = NULL;
+		if ((r = sshpkt_get_string_direct(ssh, &blob, &blen)) != 0 ||
+		    (r = sshkey_from_blob(blob, blen, &key)) != 0) {
+			error("%s: couldn't parse key: %s",
+			    __func__, ssh_err(r));
+			goto out;
+		}
+		/*
+		 * Better check that this is actually one of our hostkeys
+		 * before attempting to sign anything with it.
+		 */
+		if ((ndx = ssh->kex->host_key_index(key, 1, ssh)) == -1) {
+			error("%s: unknown host %s key",
+			    __func__, sshkey_type(key));
+			goto out;
+		}
+		/*
+		 * XXX refactor: make kex->sign just use an index rather
+		 * than passing in public and private keys
+		 */
+		if ((key_prv = get_hostkey_by_index(ndx)) == NULL &&
+		    (key_pub = get_hostkey_public_by_index(ndx, ssh)) == NULL) {
+			error("%s: can't retrieve hostkey %d", __func__, ndx);
+			goto out;
+		}
+		sshbuf_reset(sigbuf);
+		free(sig);
+		sig = NULL;
+		if ((r = sshbuf_put_string(sigbuf,
+		    ssh->kex->session_id, ssh->kex->session_id_len)) != 0 ||
+		    (r = sshbuf_put_cstring(sigbuf,
+		    "hostkeys-prove at openssh.com")) != 0 ||
+		    (r = sshkey_puts(key, sigbuf)) != 0 ||
+		    (r = ssh->kex->sign(key_prv, key_pub, &sig, &slen,
+		    sshbuf_ptr(sigbuf), sshbuf_len(sigbuf), 0)) != 0 ||
+		    (r = sshbuf_put_string(resp, sig, slen)) != 0) {
+			error("%s: couldn't prepare signature: %s",
+			    __func__, ssh_err(r));
+			goto out;
+		}
+	}
+	/* Success */
+	*respp = resp;
+	resp = NULL; /* don't free it */
+	success = 1;
+ out:
+	free(sig);
+	sshbuf_free(resp);
+	sshbuf_free(sigbuf);
+	sshkey_free(key);
+	return success;
+}
+
+static int
 server_input_global_request(int type, u_int32_t seq, void *ctxt)
 {
 	char *rtype;
 	int want_reply;
-	int success = 0, allocated_listen_port = 0;
+	int r, success = 0, allocated_listen_port = 0;
+	struct sshbuf *resp = NULL;
 
 	rtype = packet_get_string(NULL);
 	want_reply = packet_get_char();
@@ -1191,6 +1263,10 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
 			    &allocated_listen_port, &options.fwd_opts);
 		}
 		free(fwd.listen_host);
+		if ((resp = sshbuf_new()) == NULL)
+			fatal("%s: sshbuf_new", __func__);
+		if ((r = sshbuf_put_u32(resp, allocated_listen_port)) != 0)
+			fatal("%s: sshbuf_put_u32: %s", __func__, ssh_err(r));
 	} else if (strcmp(rtype, "cancel-tcpip-forward") == 0) {
 		struct Forward fwd;
 
@@ -1234,16 +1310,20 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt)
 	} else if (strcmp(rtype, "no-more-sessions at openssh.com") == 0) {
 		no_more_sessions = 1;
 		success = 1;
+	} else if (strcmp(rtype, "hostkeys-prove at openssh.com") == 0) {
+		success = server_input_hostkeys_prove(&resp);
 	}
 	if (want_reply) {
 		packet_start(success ?
 		    SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE);
-		if (success && allocated_listen_port > 0)
-			packet_put_int(allocated_listen_port);
+		if (success && resp != NULL)
+			ssh_packet_put_raw(active_state, sshbuf_ptr(resp),
+			    sshbuf_len(resp));
 		packet_send();
 		packet_write_wait();
 	}
 	free(rtype);
+	sshbuf_free(resp);
 	return 0;
 }
 
diff --git a/ssh_api.c b/ssh_api.c
index 7097c06..265a3e6 100644
--- a/ssh_api.c
+++ b/ssh_api.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh_api.c,v 1.3 2015/01/30 01:13:33 djm Exp $ */
+/* $OpenBSD: ssh_api.c,v 1.4 2015/02/16 22:13:32 djm Exp $ */
 /*
  * Copyright (c) 2012 Markus Friedl.  All rights reserved.
  *
@@ -41,7 +41,7 @@ int	_ssh_verify_host_key(struct sshkey *, struct ssh *);
 struct sshkey *_ssh_host_public_key(int, int, struct ssh *);
 struct sshkey *_ssh_host_private_key(int, int, struct ssh *);
 int	_ssh_host_key_sign(struct sshkey *, struct sshkey *, u_char **,
-    size_t *, u_char *, size_t, u_int);
+    size_t *, const u_char *, size_t, u_int);
 
 /*
  * stubs for the server side implementation of kex.
@@ -524,7 +524,8 @@ _ssh_order_hostkeyalgs(struct ssh *ssh)
 
 int
 _ssh_host_key_sign(struct sshkey *privkey, struct sshkey *pubkey,
-    u_char **signature, size_t *slen, u_char *data, size_t dlen, u_int compat)
+    u_char **signature, size_t *slen,
+    const u_char *data, size_t dlen, u_int compat)
 {
 	return sshkey_sign(privkey, signature, slen, data, dlen, compat);
 }
diff --git a/ssh_config.5 b/ssh_config.5
index ce79fe0..fa59c51 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.203 2015/02/02 07:41:40 djm Exp $
-.Dd $Mdocdate: February 2 2015 $
+.\" $OpenBSD: ssh_config.5,v 1.204 2015/02/16 22:13:32 djm Exp $
+.Dd $Mdocdate: February 16 2015 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -1510,15 +1510,20 @@ 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
-or
+.Dq yes ,
 .Dq no
-(the default).
+(the default) or
+.Dq ask .
 Enabling this option allows learning alternate hostkeys for a server
 and supports graceful key rotation by allowing a server to send replacement
 public 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.
+If
+.Cm UpdateHostKeys
+is set to
+.Dq ask ,
+then the user is asked to confirm the modifications to the known_hosts file.
 .Pp
 Presently, only
 .Xr sshd 8
diff --git a/sshd.c b/sshd.c
index 4282bdc..aaa63d4 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshd.c,v 1.441 2015/01/31 20:30:05 djm Exp $ */
+/* $OpenBSD: sshd.c,v 1.442 2015/02/16 22:13:32 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -894,18 +894,25 @@ get_hostkey_public_by_index(int ind, struct ssh *ssh)
 }
 
 int
-get_hostkey_index(Key *key, struct ssh *ssh)
+get_hostkey_index(Key *key, int compare, struct ssh *ssh)
 {
 	int i;
 
 	for (i = 0; i < options.num_host_key_files; i++) {
 		if (key_is_cert(key)) {
-			if (key == sensitive_data.host_certificates[i])
+			if (key == sensitive_data.host_certificates[i] ||
+			    (compare && sensitive_data.host_certificates[i] &&
+			    sshkey_equal(key,
+			    sensitive_data.host_certificates[i])))
 				return (i);
 		} else {
-			if (key == sensitive_data.host_keys[i])
+			if (key == sensitive_data.host_keys[i] ||
+			    (compare && sensitive_data.host_keys[i] &&
+			    sshkey_equal(key, sensitive_data.host_keys[i])))
 				return (i);
-			if (key == sensitive_data.host_pubkeys[i])
+			if (key == sensitive_data.host_pubkeys[i] ||
+			    (compare && sensitive_data.host_pubkeys[i] &&
+			    sshkey_equal(key, sensitive_data.host_pubkeys[i])))
 				return (i);
 		}
 	}
@@ -933,19 +940,23 @@ notify_hostkeys(struct ssh *ssh)
 		debug3("%s: key %d: %s %s", __func__, i,
 		    sshkey_ssh_name(key), fp);
 		free(fp);
-		if ((r = sshkey_puts(key, buf)) != 0)
+		if (nkeys == 0) {
+			packet_start(SSH2_MSG_GLOBAL_REQUEST);
+			packet_put_cstring("hostkeys at openssh.com");
+			packet_put_char(0); /* want-reply */
+		}
+		sshbuf_reset(buf);
+		if ((r = sshkey_putb(key, buf)) != 0)
 			fatal("%s: couldn't put hostkey %d: %s",
 			    __func__, i, ssh_err(r));
+		packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf));
 		nkeys++;
 	}
+	debug3("%s: sent %d hostkeys", __func__, 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();
+	sshbuf_free(buf);
 }
 
 /*
@@ -2484,7 +2495,7 @@ do_ssh1_kex(void)
 
 int
 sshd_hostkey_sign(Key *privkey, Key *pubkey, u_char **signature, size_t *slen,
-    u_char *data, size_t dlen, u_int flag)
+    const u_char *data, size_t dlen, u_int flag)
 {
 	int r;
 	u_int xxx_slen, xxx_dlen = dlen;
diff --git a/ssherr.c b/ssherr.c
index 5c29c46..4ca7939 100644
--- a/ssherr.c
+++ b/ssherr.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: ssherr.c,v 1.3 2015/01/30 01:13:33 djm Exp $	*/
+/*	$OpenBSD: ssherr.c,v 1.4 2015/02/16 22:13:32 djm Exp $	*/
 /*
  * Copyright (c) 2011 Damien Miller
  *
@@ -121,6 +121,8 @@ ssh_err(int n)
 		return "agent not present";
 	case SSH_ERR_AGENT_NO_IDENTITIES:
 		return "agent contains no identities";
+	case SSH_ERR_BUFFER_READ_ONLY:
+		return "internal error: buffer is read-only";
 	case SSH_ERR_KRL_BAD_MAGIC:
 		return "KRL file has invalid magic number";
 	case SSH_ERR_KEY_REVOKED:

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


More information about the openssh-commits mailing list