# HG changeset patch
# Parent  259f94c993ed55219d49cb3de2459e88095637ee
# Date: Sun, 20 Apr 2014 17:14:08 +1000 (EST)
# From: Damien Miller <djm@mindrot.org>
# To: openssh-unix-dev@mindrot.org
# Subject: bad bignum encoding for curve25519-sha256@libssh.org
# Message-ID: <alpine.BSO.2.11.1404201713390.26134@natsu.mindrot.org>
#
# Hi,
#
# So I screwed up when writing the support for the curve25519 KEX method
# that doesn't depend on OpenSSL's BIGNUM type - a bug in my code left
# leading zero bytes where they should have been skipped. The impact of
# this is that OpenSSH 6.5 and 6.6 will fail during key exchange with a
# peer that implements curve25519-sha256@libssh.org properly about 0.2%
# of the time (one in every 512ish connections).
#
# We've fixed this for OpenSSH 6.7 by avoiding the curve25519-sha256
# key exchange for previous versions, but I'd recommend distributors
# of OpenSSH apply this patch so the affected code doesn't become
# too entrenched in LTS releases.
#
# The patch fixes the bug and makes OpenSSH identify itself as 6.6.1 so as
# to distinguish itself from the incorrect versions so the compatibility
# code to disable the affected KEX isn't activated.
#
# I've committed this on the 6.6 branch too.
#
# Apologies for the hassle.
#
# -d

diff --git a/openssh-6.6p1/bufaux.c b/openssh-6.6p1/bufaux.c
--- a/openssh-6.6p1/bufaux.c
+++ b/openssh-6.6p1/bufaux.c
@@ -1,9 +1,9 @@
-/* $OpenBSD: bufaux.c,v 1.56 2014/02/02 03:44:31 djm Exp $ */
+/* $OpenBSD: bufaux.c,v 1.57 2014/04/16 23:22:45 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
  * Auxiliary functions for storing and retrieving various data types to/from
  * Buffers.
  *
  * As far as I am concerned, the code I have written for this software
@@ -367,16 +367,19 @@ buffer_get_bignum2_as_string(Buffer *buf
 void
 buffer_put_bignum2_from_string(Buffer *buffer, const u_char *s, u_int l)
 {
 	u_char *buf, *p;
 	int pad = 0;
 
 	if (l > 8 * 1024)
 		fatal("%s: length %u too long", __func__, l);
+	/* Skip leading zero bytes */
+	for (; l > 0 && *s == 0; l--, s++)
+		;
 	p = buf = xmalloc(l + 1);
 	/*
 	 * If most significant bit is set then prepend a zero byte to
 	 * avoid interpretation as a negative number.
 	 */
 	if (l > 0 && (s[0] & 0x80) != 0) {
 		*p++ = '\0';
 		pad = 1;
diff --git a/openssh-6.6p1/compat.c b/openssh-6.6p1/compat.c
--- a/openssh-6.6p1/compat.c
+++ b/openssh-6.6p1/compat.c
@@ -90,16 +90,19 @@ compat_datafellows(const char *version)
 					SSH_OLD_FORWARD_ADDR},
 		{ "OpenSSH_2.*,"
 		  "OpenSSH_3.0*,"
 		  "OpenSSH_3.1*",	SSH_BUG_EXTEOF|SSH_OLD_FORWARD_ADDR},
 		{ "OpenSSH_3.*",	SSH_OLD_FORWARD_ADDR },
 		{ "Sun_SSH_1.0*",	SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
 		{ "OpenSSH_4*",		0 },
 		{ "OpenSSH_5*",		SSH_NEW_OPENSSH|SSH_BUG_DYNAMIC_RPORT},
+		{ "OpenSSH_6.6.1*",	SSH_NEW_OPENSSH},
+		{ "OpenSSH_6.5*,"
+		  "OpenSSH_6.6*",	SSH_NEW_OPENSSH|SSH_BUG_CURVE25519PAD},
 		{ "OpenSSH*",		SSH_NEW_OPENSSH },
 		{ "*MindTerm*",		0 },
 		{ "2.1.0*",		SSH_BUG_SIGBLOB|SSH_BUG_HMAC|
 					SSH_OLD_SESSIONID|SSH_BUG_DEBUG|
 					SSH_BUG_RSASIGMD5|SSH_BUG_HBSERVICE|
 					SSH_BUG_FIRSTKEX },
 		{ "2.1 *",		SSH_BUG_SIGBLOB|SSH_BUG_HMAC|
 					SSH_OLD_SESSIONID|SSH_BUG_DEBUG|
@@ -246,22 +249,34 @@ compat_cipher_proposal(char *cipher_prop
 	debug2("%s: original cipher proposal: %s", __func__, cipher_prop);
 	cipher_prop = filter_proposal(cipher_prop, "aes*");
 	debug2("%s: compat cipher proposal: %s", __func__, cipher_prop);
 	if (*cipher_prop == '\0')
 		fatal("No supported ciphers found");
 	return cipher_prop;
 }
 
-
 char *
 compat_pkalg_proposal(char *pkalg_prop)
 {
 	if (!(datafellows & SSH_BUG_RSASIGMD5))
 		return pkalg_prop;
 	debug2("%s: original public key proposal: %s", __func__, pkalg_prop);
 	pkalg_prop = filter_proposal(pkalg_prop, "ssh-rsa");
 	debug2("%s: compat public key proposal: %s", __func__, pkalg_prop);
 	if (*pkalg_prop == '\0')
 		fatal("No supported PK algorithms found");
 	return pkalg_prop;
 }
 
+char *
+compat_kex_proposal(char *kex_prop)
+{
+	if (!(datafellows & SSH_BUG_CURVE25519PAD))
+		return kex_prop;
+	debug2("%s: original KEX proposal: %s", __func__, kex_prop);
+	kex_prop = filter_proposal(kex_prop, "curve25519-sha256@libssh.org");
+	debug2("%s: compat KEX proposal: %s", __func__, kex_prop);
+	if (*kex_prop == '\0')
+		fatal("No supported key exchange algorithms found");
+	return kex_prop;
+}
+
diff --git a/openssh-6.6p1/compat.h b/openssh-6.6p1/compat.h
--- a/openssh-6.6p1/compat.h
+++ b/openssh-6.6p1/compat.h
@@ -54,20 +54,22 @@
 #define SSH_BUG_DUMMYCHAN	0x00100000
 #define SSH_BUG_EXTEOF		0x00200000
 #define SSH_BUG_PROBE		0x00400000
 #define SSH_BUG_FIRSTKEX	0x00800000
 #define SSH_OLD_FORWARD_ADDR	0x01000000
 #define SSH_BUG_RFWD_ADDR	0x02000000
 #define SSH_NEW_OPENSSH		0x04000000
 #define SSH_BUG_DYNAMIC_RPORT	0x08000000
+#define SSH_BUG_CURVE25519PAD	0x10000000
 
 void     enable_compat13(void);
 void     enable_compat20(void);
 void     compat_datafellows(const char *);
 int	 proto_spec(const char *);
 char	*compat_cipher_proposal(char *);
 char	*compat_pkalg_proposal(char *);
+char	*compat_kex_proposal(char *);
 
 extern int compat13;
 extern int compat20;
 extern int datafellows;
 #endif
diff --git a/openssh-6.6p1/sshconnect2.c b/openssh-6.6p1/sshconnect2.c
--- a/openssh-6.6p1/sshconnect2.c
+++ b/openssh-6.6p1/sshconnect2.c
@@ -190,16 +190,18 @@ ssh_kex2(char *host, struct sockaddr *ho
 	else {
 		/* Prefer algorithms that we already have keys for */
 		myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] =
 		    compat_pkalg_proposal(
 		    order_hostkeyalgs(host, hostaddr, port));
 	}
 	if (options.kex_algorithms != NULL)
 		myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms;
+	myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(
+	    myproposal[PROPOSAL_KEX_ALGS]);
 
 	if (options.rekey_limit || options.rekey_interval)
 		packet_set_rekey_limits((u_int32_t)options.rekey_limit,
 		    (time_t)options.rekey_interval);
 
 	/* start key exchange */
 	kex = kex_setup(myproposal);
 	kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client;
diff --git a/openssh-6.6p1/sshd.c b/openssh-6.6p1/sshd.c
--- a/openssh-6.6p1/sshd.c
+++ b/openssh-6.6p1/sshd.c
@@ -2457,16 +2457,19 @@ do_ssh2_kex(void)
 		myproposal[PROPOSAL_COMP_ALGS_STOC] = "none";
 	} else if (options.compression == COMP_DELAYED) {
 		myproposal[PROPOSAL_COMP_ALGS_CTOS] =
 		myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com";
 	}
 	if (options.kex_algorithms != NULL)
 		myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms;
 
+	myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal(
+	    myproposal[PROPOSAL_KEX_ALGS]);
+
 	if (options.rekey_limit || options.rekey_interval)
 		packet_set_rekey_limits((u_int32_t)options.rekey_limit,
 		    (time_t)options.rekey_interval);
 
 	myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = compat_pkalg_proposal(
 	    list_hostkey_types());
 
 	/* start key exchange */
diff --git a/openssh-6.6p1/version.h b/openssh-6.6p1/version.h
--- a/openssh-6.6p1/version.h
+++ b/openssh-6.6p1/version.h
@@ -1,6 +1,6 @@
 /* $OpenBSD: version.h,v 1.70 2014/02/27 22:57:40 djm Exp $ */
 
-#define SSH_VERSION	"OpenSSH_6.6"
+#define SSH_VERSION	"OpenSSH_6.6.1"
 
 #define SSH_PORTABLE	"p1"
 #define SSH_RELEASE	SSH_VERSION SSH_PORTABLE
