Index: openssh-6.6p1/PROTOCOL
===================================================================
--- openssh-6.6p1.orig/PROTOCOL
+++ openssh-6.6p1/PROTOCOL
@@ -102,6 +102,25 @@ OpenSSH supports the use of ECDH in Curv
 described at:
 http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.org.txt?h=curve25519
 
+1.9 transport: strict key exchange extension
+
+OpenSSH supports a number of transport-layer hardening measures under
+a "strict KEX" feature. This feature is signalled similarly to the
+RFC8305 ext-info feature: by including a additional algorithm in the
+SSH2_MSG_KEXINIT kex_algorithms field. The client may append
+"kex-strict-c-v00@openssh.com" to its kex_algorithms and the server
+may append "kex-strict-s-v00@openssh.com".
+
+When endpoint that supports this extension observes this algorithm
+name in a peer's KEXINIT packet, it MUST make the following changes to
+the the protocol:
+
+a) During initial KEX, terminate the connection if any unexpected or
+   out-of-sequence packet is received. This includes terminating the
+   connection if the first packet received is not SSH2_MSG_KEXINIT.
+b) At each SSH2_MSG_NEWKEYS message, reset the packet sequence number
+   to zero.
+
 2. Connection protocol changes
 
 2.1. connection: Channel write close extension "eow@openssh.com"
Index: openssh-6.6p1/kex.c
===================================================================
--- openssh-6.6p1.orig/kex.c
+++ openssh-6.6p1/kex.c
@@ -58,6 +58,8 @@
 #include "ssh-gss.h"
 #endif
 
+Kex *xxx_kex = NULL;
+
 #if OPENSSL_VERSION_NUMBER >= 0x00907000L
 # if defined(HAVE_EVP_SHA256)
 # define evp_ssh_sha256 EVP_sha256
@@ -67,8 +69,8 @@ extern const EVP_MD *evp_ssh_sha256(void
 #endif
 
 /* prototype */
-static void kex_kexinit_finish(Kex *);
-static void kex_choose_conf(Kex *);
+static void kex_kexinit_finish(Kex *kex, u_int32_t seq);
+static void kex_choose_conf(Kex *, uint32_t seq);
 
 struct kexalg {
 	char *name;
@@ -202,6 +204,61 @@ kex_names_valid(const char *names)
 	return 1;
 }
 
+/* returns non-zero if proposal contains any algorithm from algs */
+static int
+has_any_alg(const char *proposal, const char *algs)
+{
+	char *cp;
+
+	if ((cp = match_list(proposal, algs, NULL)) == NULL)
+		return 0;
+	free(cp);
+	return 1;
+}
+
+static int
+kexalgs_contains (char **peer, const char *ext)
+{
+    return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext);
+}
+
+/*
+ * Concatenate algorithm names, avoiding duplicates in the process.
+ * Caller must free returned string.
+ */
+char *
+kex_names_cat(const char *a, const char *b)
+{
+	char *ret = NULL, *tmp = NULL, *cp, *p;
+	size_t len;
+
+	if (a == NULL || *a == '\0')
+		return NULL;
+	if (b == NULL || *b == '\0')
+		return strdup(a);
+	if (strlen(b) > 1024*1024)
+		return NULL;
+	len = strlen(a) + strlen(b) + 2;
+	if ((tmp = cp = strdup(b)) == NULL ||
+	    (ret = calloc(1, len)) == NULL) {
+		free(tmp);
+		return NULL;
+	}
+	strlcpy(ret, a, len);
+	for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) {
+		if (has_any_alg(ret, p))
+			continue; /* Algorithm already present */
+		if (strlcat(ret, ",", len) >= len ||
+		    strlcat(ret, p, len) >= len) {
+			free(tmp);
+			free(ret);
+			return NULL; /* Shouldn't happen */
+		}
+	}
+	free(tmp);
+	return ret;
+}
+
 /* put algorithm proposal into buffer */
 static void
 kex_prop2buf(Buffer *b, char *proposal[PROPOSAL_MAX])
@@ -266,6 +323,12 @@ kex_prop_free(char **proposal)
 static void
 kex_protocol_error(int type, u_int32_t seq, void *ctxt)
 {
+	/* If in strict mode, any unexpected message is an error */
+	if (xxx_kex && (xxx_kex->flags & KEX_INITIAL) && xxx_kex->kex_strict) {
+		packet_disconnect("strict KEX violation: "
+		    "unexpected packet type %u (seqnr %u)", type, seq);
+	}
+
 	error("Hm, kex protocol error: type %d seq %u", type, seq);
 }
 
@@ -294,6 +357,7 @@ kex_finish(Kex *kex)
 
 	set_newkeys(MODE_IN);
 	kex->done = 1;
+	kex->flags &= ~KEX_INITIAL;
 	buffer_clear(&kex->peer);
 	/* buffer_clear(&kex->my); */
 	kex->flags &= ~KEX_INIT_SENT;
@@ -347,7 +411,7 @@ kex_input_kexinit(int type, u_int32_t se
 	if (kex == NULL)
 		fatal("kex_input_kexinit: no kex, cannot rekey");
 
-	dispatch_set(SSH2_MSG_KEXINIT, NULL);
+	dispatch_set(SSH2_MSG_KEXINIT, &kex_protocol_error);
 	ptr = packet_get_raw(&dlen);
 	buffer_append(&kex->peer, ptr, dlen);
 
@@ -370,7 +434,7 @@ kex_input_kexinit(int type, u_int32_t se
 	(void) packet_get_int();	/* reserved */
 	packet_check_eom();
 
-	kex_kexinit_finish(kex);
+	kex_kexinit_finish(kex, seq);
 }
 
 Kex *
@@ -383,6 +447,7 @@ kex_setup(char *proposal[PROPOSAL_MAX])
 	buffer_init(&kex->my);
 	kex_prop2buf(&kex->my, proposal);
 	kex->done = 0;
+	kex->flags |= KEX_INITIAL;
 
 	kex_send_kexinit(kex);					/* we start */
 	kex_reset_dispatch();
@@ -391,12 +456,12 @@ kex_setup(char *proposal[PROPOSAL_MAX])
 }
 
 static void
-kex_kexinit_finish(Kex *kex)
+kex_kexinit_finish(Kex *kex, u_int32_t seq)
 {
 	if (!(kex->flags & KEX_INIT_SENT))
 		kex_send_kexinit(kex);
 
-	kex_choose_conf(kex);
+	kex_choose_conf(kex, seq);
 
 	if (kex->kex_type >= 0 && kex->kex_type < KEX_MAX &&
 	    kex->kex[kex->kex_type] != NULL) {
@@ -521,7 +586,7 @@ proposals_match(char *my[PROPOSAL_MAX],
 }
 
 static void
-kex_choose_conf(Kex *kex)
+kex_choose_conf(Kex *kex, u_int32_t seq)
 {
 	Newkeys *newkeys;
 	char **my, **peer;
@@ -551,6 +616,24 @@ kex_choose_conf(Kex *kex)
 		}
 	}
 
+	/* Check whether peer supports kex_strict */
+	if ((kex->flags & KEX_INITIAL) != 0) {
+		if (kex->server) {
+			kex->kex_strict = kexalgs_contains(peer,
+			    "kex-strict-c-v00@openssh.com");
+		} else {
+			kex->kex_strict = kexalgs_contains(peer,
+			    "kex-strict-s-v00@openssh.com");
+		}
+		if (kex->kex_strict) {
+			debug3("will use strict KEX ordering");
+			if (seq != 0)
+				packet_disconnect(
+				    "strict KEX violation: "
+				    "KEXINIT was not the first packet");
+		}
+	}
+
 	/* Algorithm Negotiation */
 	for (mode = 0; mode < MODE_MAX; mode++) {
 		newkeys = xcalloc(1, sizeof(*newkeys));
Index: openssh-6.6p1/kex.h
===================================================================
--- openssh-6.6p1.orig/kex.h
+++ openssh-6.6p1/kex.h
@@ -82,6 +82,7 @@ enum kex_exchange {
 };
 
 #define KEX_INIT_SENT	0x0001
+#define KEX_INITIAL     0x0002
 
 typedef struct Kex Kex;
 typedef struct Mac Mac;
@@ -133,6 +134,7 @@ struct Kex {
 	int	roaming;
 	Buffer	my;
 	Buffer	peer;
+	int     kex_strict;
 	sig_atomic_t done;
 	int	flags;
 	int	hash_alg;
@@ -153,6 +155,9 @@ struct Kex {
 	void	(*kex[KEX_MAX])(Kex *);
 };
 
+extern Kex *xxx_kex;
+
+char    *kex_names_cat(const char *a, const char *b);
 int	 kex_names_valid(const char *);
 char	*kex_alg_list(char);
 
Index: openssh-6.6p1/packet.c
===================================================================
--- openssh-6.6p1.orig/packet.c
+++ openssh-6.6p1/packet.c
@@ -202,6 +202,7 @@ struct session_state {
 };
 
 static struct session_state *active_state, *backup_state;
+static int expecting_packet_type = 0;
 
 static struct session_state *
 alloc_session_state(void)
@@ -999,6 +1000,11 @@ packet_send2_wrapped(void)
 	active_state->p_send.bytes += len;
 	buffer_clear(&active_state->outgoing_packet);
 
+	if (type == SSH2_MSG_NEWKEYS && xxx_kex && xxx_kex->kex_strict) {
+		debug("resetting send seqnr %u", active_state->p_send.seqnr);
+		active_state->p_send.seqnr = 0;
+	}
+
 	if (type == SSH2_MSG_NEWKEYS)
 		set_newkeys(MODE_OUT);
 	else if (type == SSH2_MSG_USERAUTH_SUCCESS && active_state->server_side)
@@ -1172,10 +1178,14 @@ packet_read_expect(int expected_type)
 {
 	int type;
 
+	expecting_packet_type = expected_type;
+
 	type = packet_read();
 	if (type != expected_type)
 		packet_disconnect("Protocol error: expected packet type %d, got %d",
 		    expected_type, type);
+
+	expecting_packet_type = 0;
 }
 
 /* Checks if a full packet is available in the data received so far via
@@ -1464,6 +1474,10 @@ packet_read_poll2(u_int32_t *seqnr_p)
 #endif
 	/* reset for next packet */
 	active_state->packlen = 0;
+	if (type == SSH2_MSG_NEWKEYS && xxx_kex && xxx_kex->kex_strict) {
+		debug("resetting read seqnr %u", active_state->p_read.seqnr);
+		active_state->p_read.seqnr = 0;
+	}
 	return type;
 }
 
@@ -1477,10 +1491,43 @@ packet_read_poll_seqnr(u_int32_t *seqnr_
 	for (;;) {
 		if (compat20) {
 			type = packet_read_poll2(seqnr_p);
-			if (type) {
-				active_state->keep_alive_timeouts = 0;
-				DBG(debug("received packet type %d", type));
+			if (type == 0) {
+				/* no message ready */
+				return 0;
+			}
+
+			active_state->keep_alive_timeouts = 0;
+			DBG(debug("received packet type %d", type));
+
+			/* Always process disconnect messages */
+			if (type == SSH2_MSG_DISCONNECT) {
+				reason = packet_get_int();
+				msg = packet_get_string(NULL);
+				/* Ignore normal client exit notifications */
+				do_log2(active_state->server_side &&
+				    reason == SSH2_DISCONNECT_BY_APPLICATION ?
+				    SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR,
+				    "Received disconnect from %s: %u: %.400s",
+				    get_remote_ipaddr(), reason, msg);
+				free(msg);
+				cleanup_exit(255);
 			}
+
+			/*
+			 * Do not implicitly handle any messages here during initial
+			 * KEX when in strict mode. They will be need to be allowed
+			 * explicitly by a packet_read_expect() call.
+			 */
+			if (xxx_kex != NULL &&
+			    (xxx_kex->flags & KEX_INITIAL) &&
+                            xxx_kex->kex_strict)
+			{
+				if (type == expecting_packet_type)
+					return type;
+				return 0;
+			}
+
+			/* Implicitly handle transport-level messages */
 			switch (type) {
 			case SSH2_MSG_IGNORE:
 				debug3("Received SSH2_MSG_IGNORE");
@@ -1493,18 +1540,6 @@ packet_read_poll_seqnr(u_int32_t *seqnr_
 				msg = packet_get_string(NULL);
 				free(msg);
 				break;
-			case SSH2_MSG_DISCONNECT:
-				reason = packet_get_int();
-				msg = packet_get_string(NULL);
-				/* Ignore normal client exit notifications */
-				do_log2(active_state->server_side &&
-				    reason == SSH2_DISCONNECT_BY_APPLICATION ?
-				    SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR,
-				    "Received disconnect from %s: %u: %.400s",
-				    get_remote_ipaddr(), reason, msg);
-				free(msg);
-				cleanup_exit(255);
-				break;
 			case SSH2_MSG_UNIMPLEMENTED:
 				seqnr = packet_get_int();
 				debug("Received SSH2_MSG_UNIMPLEMENTED for %u",
@@ -1673,6 +1708,8 @@ packet_send_debug(const char *fmt,...)
 	vsnprintf(buf, sizeof(buf), fmt, args);
 	va_end(args);
 
+	debug2("sending SSH2_MSG_DISCONNECT: %s", buf);
+
 	if (compat20) {
 		packet_start(SSH2_MSG_DEBUG);
 		packet_put_char(0);	/* bool: always display */
Index: openssh-6.6p1/sshd.c
===================================================================
--- openssh-6.6p1.orig/sshd.c
+++ openssh-6.6p1/sshd.c
@@ -201,9 +201,6 @@ int num_listen_socks = 0;
 char *client_version_string = NULL;
 char *server_version_string = NULL;
 
-/* for rekeying XXX fixme */
-Kex *xxx_kex;
-
 /* Daemon's agent connection */
 AuthenticationConnection *auth_conn = NULL;
 int have_agent = 0;
@@ -2700,6 +2697,7 @@ static void
 do_ssh2_kex(void)
 {
 	Kex *kex;
+	char *s;
 
 	if (options.ciphers != NULL) {
 		myproposal[PROPOSAL_ENC_ALGS_CTOS] =
@@ -2724,13 +2722,23 @@ do_ssh2_kex(void)
 		myproposal[PROPOSAL_COMP_ALGS_CTOS] =
 		myproposal[PROPOSAL_COMP_ALGS_STOC] = "none";
 	}
+
 	if (options.kex_algorithms != NULL)
 		myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms;
 	else if (fips_mode())
 		myproposal[PROPOSAL_KEX_ALGS] = KEX_DEFAULT_KEX_FIPS140_2;
 
-	myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(
-	    myproposal[PROPOSAL_KEX_ALGS]);
+	/* Append EXT_INFO signalling to KexAlgorithms */
+	if (myproposal[PROPOSAL_KEX_ALGS] == NULL)
+		s = strdup("kex-strict-s-v00@openssh.com");
+	else
+		s = kex_names_cat(myproposal[PROPOSAL_KEX_ALGS],
+				  "kex-strict-s-v00@openssh.com");
+
+	if (s == NULL)
+		fatal("%s: kex_names_cat", __func__);
+
+	myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(s);
 
 	if (options.rekey_limit || options.rekey_interval)
 		packet_set_rekey_limits((u_int32_t)options.rekey_limit,
Index: openssh-6.6p1/ssh-keysign.c
===================================================================
--- openssh-6.6p1.orig/ssh-keysign.c
+++ openssh-6.6p1/ssh-keysign.c
@@ -52,6 +52,8 @@
 #include "pathnames.h"
 #include "readconf.h"
 #include "uidswap.h"
+#include "cipher.h"
+#include "kex.h"
 
 /* XXX readconf.c needs these */
 uid_t original_real_uid;
Index: openssh-6.6p1/sshconnect2.c
===================================================================
--- openssh-6.6p1.orig/sshconnect2.c
+++ openssh-6.6p1/sshconnect2.c
@@ -92,8 +92,6 @@ u_int session_id2_len = 0;
 char *xxx_host;
 struct sockaddr *xxx_hostaddr;
 
-Kex *xxx_kex = NULL;
-
 static int
 verify_host_key_callback(Key *hostkey)
 {
@@ -159,6 +157,7 @@ void
 ssh_kex2(char *host, struct sockaddr *hostaddr, u_short port)
 {
 	Kex *kex;
+	char *s;
 
 #ifdef GSSAPI
 	char *orig = NULL, *gss = NULL;
@@ -234,8 +233,18 @@ ssh_kex2(char *host, struct sockaddr *ho
 		myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms;
 	else if (fips_mode())
 		myproposal[PROPOSAL_KEX_ALGS] = KEX_DEFAULT_KEX_FIPS140_2;
-	myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(
-	    myproposal[PROPOSAL_KEX_ALGS]);
+
+	/* Append EXT_INFO signalling to KexAlgorithms */
+	if (myproposal[PROPOSAL_KEX_ALGS] == NULL)
+		s = strdup("kex-strict-c-v00@openssh.com");
+	else
+		s = kex_names_cat(myproposal[PROPOSAL_KEX_ALGS],
+				  "kex-strict-c-v00@openssh.com");
+
+	if (s == NULL)
+		fatal("%s: kex_names_cat", __func__);
+
+	myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(s);
 
 #ifdef GSSAPI
 	/* If we've got GSSAPI algorithms, then we also support the
Index: openssh-6.6p1/monitor.c
===================================================================
--- openssh-6.6p1.orig/monitor.c
+++ openssh-6.6p1/monitor.c
@@ -1923,6 +1923,7 @@ mm_get_kex(Buffer *m)
 	kex->server = 1;
 	kex->hostkey_type = buffer_get_int(m);
 	kex->kex_type = buffer_get_int(m);
+	kex->kex_strict = buffer_get_int(m);
 	blob = buffer_get_string(m, &bloblen);
 	buffer_init(&kex->my);
 	buffer_append(&kex->my, blob, bloblen);
Index: openssh-6.6p1/monitor_wrap.c
===================================================================
--- openssh-6.6p1.orig/monitor_wrap.c
+++ openssh-6.6p1/monitor_wrap.c
@@ -595,6 +595,7 @@ mm_send_kex(Buffer *m, Kex *kex)
 	buffer_put_int(m, kex->we_need);
 	buffer_put_int(m, kex->hostkey_type);
 	buffer_put_int(m, kex->kex_type);
+	buffer_put_int(m, kex->kex_strict);
 	buffer_put_string(m, buffer_ptr(&kex->my), buffer_len(&kex->my));
 	buffer_put_string(m, buffer_ptr(&kex->peer), buffer_len(&kex->peer));
 	buffer_put_int(m, kex->flags);
