- djm@cvs.openbsd.org 2010/09/22 05:01:30
     [kex.c kex.h kexecdh.c kexecdhc.c kexecdhs.c readconf.c readconf.h]
     [servconf.c servconf.h ssh_config.5 sshconnect2.c sshd.c sshd_config.5]
     add a KexAlgorithms knob to the client and server configuration to allow
     selection of which key exchange methods are used by ssh(1) and sshd(8)
     and their order of preference.
     ok markus@
diff --git a/ChangeLog b/ChangeLog
index 7d9e994..5cb4c88 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -31,6 +31,13 @@
      either ready or stale without races. stale server sockets are now
      automatically removed
      ok deraadt
+   - djm@cvs.openbsd.org 2010/09/22 05:01:30
+     [kex.c kex.h kexecdh.c kexecdhc.c kexecdhs.c readconf.c readconf.h]
+     [servconf.c servconf.h ssh_config.5 sshconnect2.c sshd.c sshd_config.5]
+     add a KexAlgorithms knob to the client and server configuration to allow
+     selection of which key exchange methods are used by ssh(1) and sshd(8)
+     and their order of preference.
+     ok markus@
 
 20100910
  - (dtucker) [openbsd-compat/port-linux.c] Check is_selinux_enabled for exact
diff --git a/kex.c b/kex.c
index 7c87631..c65e28f 100644
--- a/kex.c
+++ b/kex.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.c,v 1.85 2010/09/09 10:45:45 djm Exp $ */
+/* $OpenBSD: kex.c,v 1.86 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  *
@@ -62,6 +62,34 @@
 static void kex_kexinit_finish(Kex *);
 static void kex_choose_conf(Kex *);
 
+/* Validate KEX method name list */
+int
+kex_names_valid(const char *names)
+{
+	char *s, *cp, *p;
+
+	if (names == NULL || strcmp(names, "") == 0)
+		return 0;
+	s = cp = xstrdup(names);
+	for ((p = strsep(&cp, ",")); p && *p != '\0';
+	    (p = strsep(&cp, ","))) {
+	    	if (strcmp(p, KEX_DHGEX_SHA256) != 0 &&
+		    strcmp(p, KEX_DHGEX_SHA1) != 0 &&
+		    strcmp(p, KEX_DH14) != 0 &&
+		    strcmp(p, KEX_DH1) != 0 &&
+		    (strncmp(p, KEX_ECDH_SHA2_STEM,
+		    sizeof(KEX_ECDH_SHA2_STEM) - 1) != 0 ||
+		    kex_ecdh_name_to_nid(p) == -1)) {
+			error("Unsupported KEX algorithm \"%.100s\"", p);
+			xfree(s);
+			return 0;
+		}
+	}
+	debug3("kex names ok: [%s]", names);
+	xfree(s);
+	return 1;
+}
+
 /* put algorithm proposal into buffer */
 static void
 kex_prop2buf(Buffer *b, char *proposal[PROPOSAL_MAX])
diff --git a/kex.h b/kex.h
index 3e312fb..7373d3c 100644
--- a/kex.h
+++ b/kex.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.h,v 1.51 2010/09/09 10:45:45 djm Exp $ */
+/* $OpenBSD: kex.h,v 1.52 2010/09/22 05:01:29 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -138,6 +138,8 @@
 	void	(*kex[KEX_MAX])(Kex *);
 };
 
+int	 kex_names_valid(const char *);
+
 Kex	*kex_setup(char *[PROPOSAL_MAX]);
 void	 kex_finish(Kex *);
 
@@ -169,7 +171,8 @@
 int	kex_ecdh_name_to_nid(const char *);
 const EVP_MD *kex_ecdh_name_to_evpmd(const char *);
 #else
-# define kex_ecdh_name_to_evpmd(x) NULL
+# define kex_ecdh_name_to_nid(x) (-1)
+# define kex_ecdh_name_to_evpmd(x) (NULL)
 #endif
 
 void
diff --git a/kexecdh.c b/kexecdh.c
index 4c58a51..f13f69d 100644
--- a/kexecdh.c
+++ b/kexecdh.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kexecdh.c,v 1.2 2010/09/09 10:45:45 djm Exp $ */
+/* $OpenBSD: kexecdh.c,v 1.3 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2010 Damien Miller.  All rights reserved.
@@ -48,15 +48,9 @@
 int
 kex_ecdh_name_to_nid(const char *kexname)
 {
-	int ret;
-
 	if (strlen(kexname) < sizeof(KEX_ECDH_SHA2_STEM) - 1)
 		fatal("%s: kexname too short \"%s\"", __func__, kexname);
-	ret = key_curve_name_to_nid(kexname + sizeof(KEX_ECDH_SHA2_STEM) - 1);
-	if (ret == -1)
-		fatal("%s: unsupported curve negotiated \"%s\"", __func__,
-		    kexname);
-	return ret;
+	return key_curve_name_to_nid(kexname + sizeof(KEX_ECDH_SHA2_STEM) - 1);
 }
 
 const EVP_MD *
@@ -64,6 +58,8 @@
 {
 	int nid = kex_ecdh_name_to_nid(kexname);
 
+	if (nid == -1)
+		fatal("%s: unsupported ECDH curve \"%s\"", __func__, kexname);
 	return key_ec_nid_to_evpmd(nid);
 }
 
diff --git a/kexecdhc.c b/kexecdhc.c
index 297a0e5..115d4bf 100644
--- a/kexecdhc.c
+++ b/kexecdhc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kexecdhc.c,v 1.1 2010/08/31 11:54:45 djm Exp $ */
+/* $OpenBSD: kexecdhc.c,v 1.2 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2010 Damien Miller.  All rights reserved.
@@ -59,7 +59,8 @@
 	u_int klen, slen, sbloblen, hashlen;
 	int curve_nid;
 
-	curve_nid = kex_ecdh_name_to_nid(kex->name);
+	if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1)
+		fatal("%s: unsupported ECDH curve \"%s\"", __func__, kex->name);
 	if ((client_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL)
 		fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
 	if (EC_KEY_generate_key(client_key) != 1)
diff --git a/kexecdhs.c b/kexecdhs.c
index d2c3feb..8c515df 100644
--- a/kexecdhs.c
+++ b/kexecdhs.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kexecdhs.c,v 1.1 2010/08/31 11:54:45 djm Exp $ */
+/* $OpenBSD: kexecdhs.c,v 1.2 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2010 Damien Miller.  All rights reserved.
@@ -61,7 +61,8 @@
 	u_int klen, slen, sbloblen, hashlen;
 	int curve_nid;
 
-	curve_nid = kex_ecdh_name_to_nid(kex->name);
+	if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1)
+		fatal("%s: unsupported ECDH curve \"%s\"", __func__, kex->name);
 	if ((server_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL)
 		fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
 	if (EC_KEY_generate_key(server_key) != 1)
diff --git a/readconf.c b/readconf.c
index 5864229..da7efd1 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.188 2010/08/31 11:54:45 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.189 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -132,6 +132,7 @@
 	oHashKnownHosts,
 	oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
 	oVisualHostKey, oUseRoaming, oZeroKnowledgePasswordAuthentication,
+	oKexAlgorithms,
 	oDeprecated, oUnsupported
 } OpCodes;
 
@@ -240,6 +241,7 @@
 #else
 	{ "zeroknowledgepasswordauthentication", oUnsupported },
 #endif
+	{ "kexalgorithms", oKexAlgorithms },
 
 	{ NULL, oBadOption }
 };
@@ -699,6 +701,18 @@
 			options->macs = xstrdup(arg);
 		break;
 
+	case oKexAlgorithms:
+		arg = strdelim(&s);
+		if (!arg || *arg == '\0')
+			fatal("%.200s line %d: Missing argument.",
+			    filename, linenum);
+		if (!kex_names_valid(arg))
+			fatal("%.200s line %d: Bad SSH2 KexAlgorithms '%s'.",
+			    filename, linenum, arg ? arg : "<NONE>");
+		if (*activep && options->kex_algorithms == NULL)
+			options->kex_algorithms = xstrdup(arg);
+		break;
+
 	case oHostKeyAlgorithms:
 		arg = strdelim(&s);
 		if (!arg || *arg == '\0')
@@ -1078,6 +1092,7 @@
 	options->cipher = -1;
 	options->ciphers = NULL;
 	options->macs = NULL;
+	options->kex_algorithms = NULL;
 	options->hostkeyalgorithms = NULL;
 	options->protocol = SSH_PROTO_UNKNOWN;
 	options->num_identity_files = 0;
@@ -1191,6 +1206,7 @@
 		options->cipher = SSH_CIPHER_NOT_SET;
 	/* options->ciphers, default set in myproposals.h */
 	/* options->macs, default set in myproposals.h */
+	/* options->kex_algorithms, default set in myproposals.h */
 	/* options->hostkeyalgorithms, default set in myproposals.h */
 	if (options->protocol == SSH_PROTO_UNKNOWN)
 		options->protocol = SSH_PROTO_2;
diff --git a/readconf.h b/readconf.h
index 95d1046..ae61466 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.86 2010/07/19 09:15:12 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.87 2010/09/22 05:01:29 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -73,6 +73,7 @@
 	char   *ciphers;	/* SSH2 ciphers in order of preference. */
 	char   *macs;		/* SSH2 macs in order of preference. */
 	char   *hostkeyalgorithms;	/* SSH2 server key types in order of preference. */
+	char   *kex_algorithms;	/* SSH2 kex methods in order of preference. */
 	int	protocol;	/* Protocol in order of preference. */
 	char   *hostname;	/* Real host to connect. */
 	char   *host_key_alias;	/* hostname alias for .ssh/known_hosts */
diff --git a/servconf.c b/servconf.c
index def6b71..d26a7db 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.210 2010/09/01 15:21:35 naddy Exp $ */
+/* $OpenBSD: servconf.c,v 1.211 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -109,6 +109,7 @@
 	options->num_deny_groups = 0;
 	options->ciphers = NULL;
 	options->macs = NULL;
+	options->kex_algorithms = NULL;
 	options->protocol = SSH_PROTO_UNKNOWN;
 	options->gateway_ports = -1;
 	options->num_subsystems = 0;
@@ -314,6 +315,7 @@
 	sUsePrivilegeSeparation, sAllowAgentForwarding,
 	sZeroKnowledgePasswordAuthentication, sHostCertificate,
 	sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
+	sKexAlgorithms,
 	sDeprecated, sUnsupported
 } ServerOpCodes;
 
@@ -436,6 +438,7 @@
 	{ "revokedkeys", sRevokedKeys, SSHCFG_ALL },
 	{ "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL },
 	{ "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL },
+	{ "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL },
 	{ NULL, sBadOption, 0 }
 };
 
@@ -1131,6 +1134,18 @@
 			options->macs = xstrdup(arg);
 		break;
 
+	case sKexAlgorithms:
+		arg = strdelim(&cp);
+		if (!arg || *arg == '\0')
+			fatal("%s line %d: Missing argument.",
+			    filename, linenum);
+		if (!kex_names_valid(arg))
+			fatal("%s line %d: Bad SSH2 KexAlgorithms '%s'.",
+			    filename, linenum, arg ? arg : "<NONE>");
+		if (options->kex_algorithms == NULL)
+			options->kex_algorithms = xstrdup(arg);
+		break;
+
 	case sProtocol:
 		intptr = &options->protocol;
 		arg = strdelim(&cp);
diff --git a/servconf.h b/servconf.h
index 45d2a2a..ad13f2e 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.93 2010/05/07 11:30:30 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.94 2010/09/22 05:01:29 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -72,6 +72,7 @@
 	int     tcp_keep_alive;	/* If true, set SO_KEEPALIVE. */
 	char   *ciphers;	/* Supported SSH2 ciphers. */
 	char   *macs;		/* Supported SSH2 macs. */
+	char   *kex_algorithms;	/* SSH2 kex methods in order of preference. */
 	int	protocol;	/* Supported protocol versions. */
 	int     gateway_ports;	/* If true, allow remote connects to forwarded ports. */
 	SyslogFacility log_facility;	/* Facility for system logging. */
diff --git a/ssh_config.5 b/ssh_config.5
index 33038ff..6e49842 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -34,8 +34,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.139 2010/08/31 11:54:45 djm Exp $
-.Dd $Mdocdate: August 31 2010 $
+.\" $OpenBSD: ssh_config.5,v 1.140 2010/09/22 05:01:29 djm Exp $
+.Dd $Mdocdate: September 22 2010 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -646,6 +646,17 @@
 .Dq pam ,
 and
 .Dq skey .
+.It Cm KexAlgorithms
+Specifies the available KEX (Key Exchange) algorithms.
+Multiple algorithms must be comma-separated.
+The default is
+.Dq ecdh-sha2-nistp256 ,
+.Dq ecdh-sha2-nistp384 ,
+.Dq ecdh-sha2-nistp521 ,
+.Dq diffie-hellman-group-exchange-sha256 , 
+.Dq diffie-hellman-group-exchange-sha1 ,
+.Dq diffie-hellman-group14-sha1 ,
+.Dq diffie-hellman-group1-sha1 .
 .It Cm LocalCommand
 Specifies a command to execute on the local machine after successfully
 connecting to the server.
diff --git a/sshconnect2.c b/sshconnect2.c
index a31a663..6fe356c 100644
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect2.c,v 1.184 2010/08/31 11:54:45 djm Exp $ */
+/* $OpenBSD: sshconnect2.c,v 1.185 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2008 Damien Miller.  All rights reserved.
@@ -135,6 +135,8 @@
 	if (options.hostkeyalgorithms != NULL)
 		myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] =
 		    options.hostkeyalgorithms;
+	if (options.kex_algorithms != NULL)
+		myproposal[PROPOSAL_KEX_ALGS] = options.kex_algorithms;
 
 	if (options.rekey_limit)
 		packet_set_rekey_limit((u_int32_t)options.rekey_limit);
diff --git a/sshd.c b/sshd.c
index 7995f5a..5d4d14a 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshd.c,v 1.379 2010/08/31 12:33:38 djm Exp $ */
+/* $OpenBSD: sshd.c,v 1.380 2010/09/22 05:01:29 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -2297,6 +2297,8 @@
 		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_SERVER_HOST_KEY_ALGS] = list_hostkey_types();
 
diff --git a/sshd_config.5 b/sshd_config.5
index af3d89b..d87f602 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -34,8 +34,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: sshd_config.5,v 1.126 2010/08/31 11:54:45 djm Exp $
-.Dd $Mdocdate: August 31 2010 $
+.\" $OpenBSD: sshd_config.5,v 1.127 2010/09/22 05:01:30 djm Exp $
+.Dd $Mdocdate: September 22 2010 $
 .Dt SSHD_CONFIG 5
 .Os
 .Sh NAME
@@ -538,6 +538,17 @@
 file on logout.
 The default is
 .Dq yes .
+.It Cm KexAlgorithms
+Specifies the available KEX (Key Exchange) algorithms.
+Multiple algorithms must be comma-separated.
+The default is
+.Dq ecdh-sha2-nistp256 ,
+.Dq ecdh-sha2-nistp384 ,
+.Dq ecdh-sha2-nistp521 ,
+.Dq diffie-hellman-group-exchange-sha256 , 
+.Dq diffie-hellman-group-exchange-sha1 ,
+.Dq diffie-hellman-group14-sha1 ,
+.Dq diffie-hellman-group1-sha1 .
 .It Cm KeyRegenerationInterval
 In protocol version 1, the ephemeral server key is automatically regenerated
 after this many seconds (if it has been used).