# HG changeset patch
# Parent  673e8ad8abd7c88c3c248fc375224ea6325c26b0

Raise minimal size of DH group parameters to 2048 bits like upstream did in
7.2. 1024b values are believed to be in breaking range for state adversaries
and the default moduli shipped with openssh have been around long enough to
make it more likely for them to be broken.

Also provide an option that allows the client to accept shorter (RFC4419
compliant) parameters.

CVE-2015-4000 (LOGJAM)
bsc#932483

diff --git a/openssh-6.6p1/dh.c b/openssh-6.6p1/dh.c
--- a/openssh-6.6p1/dh.c
+++ b/openssh-6.6p1/dh.c
@@ -35,16 +35,18 @@
 #include <stdlib.h>
 #include <string.h>
 
 #include "dh.h"
 #include "pathnames.h"
 #include "log.h"
 #include "misc.h"
 
+int dh_grp_min = DH_GRP_MIN;
+
 static int
 parse_prime(int linenum, char *line, struct dhgroup *dhg)
 {
 	char *cp, *arg;
 	char *strsize, *gen, *prime;
 	const char *errstr = NULL;
 	long long n;
 
diff --git a/openssh-6.6p1/dh.h b/openssh-6.6p1/dh.h
--- a/openssh-6.6p1/dh.h
+++ b/openssh-6.6p1/dh.h
@@ -39,17 +39,18 @@ DH	*dh_new_group1(void);
 DH	*dh_new_group14(void);
 
 void	 dh_gen_key(DH *, int);
 int	 dh_pub_is_valid(DH *, BIGNUM *);
 
 int	 dh_estimate(int);
 
 /* Min and max values from RFC4419. */
-#define DH_GRP_MIN	1024
+#define DH_GRP_MIN_RFC	1024
+#define DH_GRP_MIN	2048
 #define DH_GRP_MAX	8192
 
 /*
  * Values for "type" field of moduli(5)
  * Specifies the internal structure of the prime modulus.
  */
 #define MODULI_TYPE_UNKNOWN		(0)
 #define MODULI_TYPE_UNSTRUCTURED	(1)
diff --git a/openssh-6.6p1/kexgexc.c b/openssh-6.6p1/kexgexc.c
--- a/openssh-6.6p1/kexgexc.c
+++ b/openssh-6.6p1/kexgexc.c
@@ -41,41 +41,45 @@
 #include "cipher.h"
 #include "kex.h"
 #include "log.h"
 #include "packet.h"
 #include "dh.h"
 #include "ssh2.h"
 #include "compat.h"
 
+/* import from dh.c */
+extern int dh_grp_min;
+
 void
 kexgex_client(Kex *kex)
 {
 	BIGNUM *dh_server_pub = NULL, *shared_secret = NULL;
 	BIGNUM *p = NULL, *g = NULL;
 	Key *server_host_key;
 	u_char *kbuf, *hash, *signature = NULL, *server_host_key_blob = NULL;
 	u_int klen, slen, sbloblen, hashlen;
 	int kout;
 	int min, max, nbits;
+	int p_bitlen;
 	DH *dh;
 
 	nbits = dh_estimate(kex->dh_need * 8);
 
 	if (datafellows & SSH_OLD_DHGEX) {
 		/* Old GEX request */
 		packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
 		packet_put_int(nbits);
-		min = DH_GRP_MIN;
+		min = dh_grp_min;
 		max = DH_GRP_MAX;
 
 		debug("SSH2_MSG_KEX_DH_GEX_REQUEST_OLD(%u) sent", nbits);
 	} else {
 		/* New GEX request */
-		min = DH_GRP_MIN;
+		min = dh_grp_min;
 		max = DH_GRP_MAX;
 		packet_start(SSH2_MSG_KEX_DH_GEX_REQUEST);
 		packet_put_int(min);
 		packet_put_int(nbits);
 		packet_put_int(max);
 
 		debug("SSH2_MSG_KEX_DH_GEX_REQUEST(%u<%u<%u) sent",
 		    min, nbits, max);
@@ -87,24 +91,32 @@ kexgex_client(Kex *kex)
 	packet_send();
 
 	debug("expecting SSH2_MSG_KEX_DH_GEX_GROUP");
 	packet_read_expect(SSH2_MSG_KEX_DH_GEX_GROUP);
 
 	if ((p = BN_new()) == NULL)
 		fatal("BN_new");
 	packet_get_bignum2(p);
+	p_bitlen = BN_num_bits(p);
 	if ((g = BN_new()) == NULL)
 		fatal("BN_new");
 	packet_get_bignum2(g);
 	packet_check_eom();
 
-	if (BN_num_bits(p) < min || BN_num_bits(p) > max)
+	if (p_bitlen < min || p_bitlen > max) {
+		if (p_bitlen < min && p_bitlen >= DH_GRP_MIN_RFC)
+			logit("DH parameter offered by the server (%d bits) "
+			    "is considered insecure. "
+			    "You can lower the accepted the minimum "
+			    "via the KexDHMin option.",
+			    p_bitlen);
 		fatal("DH_GEX group out of range: %d !< %d !< %d",
-		    min, BN_num_bits(p), max);
+		    min, p_bitlen, max);
+	}
 
 	dh = dh_new_group(g, p);
 	dh_gen_key(dh, kex->we_need * 8);
 
 #ifdef DEBUG_KEXDH
 	DHparams_print_fp(stderr, dh);
 	fprintf(stderr, "pub= ");
 	BN_print_fp(stderr, dh->pub_key);
diff --git a/openssh-6.6p1/readconf.c b/openssh-6.6p1/readconf.c
--- a/openssh-6.6p1/readconf.c
+++ b/openssh-6.6p1/readconf.c
@@ -50,16 +50,18 @@
 #include "key.h"
 #include "readconf.h"
 #include "match.h"
 #include "misc.h"
 #include "buffer.h"
 #include "kex.h"
 #include "mac.h"
 #include "uidswap.h"
+#include "dh.h"
+
 
 /* Format of the configuration file:
 
    # Configuration data is parsed as follows:
    #  1. command line options
    #  2. user-specific file
    #  3. system-wide file
    # Any configuration value is only changed the first time it is set.
@@ -141,17 +143,18 @@ typedef enum {
 	oClearAllForwardings, oNoHostAuthenticationForLocalhost,
 	oEnableSSHKeysign, oRekeyLimit, oVerifyHostKeyDNS, oConnectTimeout,
 	oAddressFamily, oGssAuthentication, oGssDelegateCreds, oGssEnableMITM,
 	oServerAliveInterval, oServerAliveCountMax, oIdentitiesOnly,
 	oSendEnv, oControlPath, oControlMaster, oControlPersist,
 	oHashKnownHosts,
 	oTunnel, oTunnelDevice, oLocalCommand, oPermitLocalCommand,
 	oVisualHostKey, oUseRoaming,
-	oKexAlgorithms, oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass,
+	oKexAlgorithms, oKexDHMin,
+	oIPQoS, oRequestTTY, oIgnoreUnknown, oProxyUseFdpass,
 	oCanonicalDomains, oCanonicalizeHostname, oCanonicalizeMaxDots,
 	oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
 	oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
 /* Textual representations of the tokens. */
 
 static struct {
@@ -250,29 +253,33 @@ static struct {
 	{ "hashknownhosts", oHashKnownHosts },
 	{ "tunnel", oTunnel },
 	{ "tunneldevice", oTunnelDevice },
 	{ "localcommand", oLocalCommand },
 	{ "permitlocalcommand", oPermitLocalCommand },
 	{ "visualhostkey", oVisualHostKey },
 	{ "useroaming", oUseRoaming },
 	{ "kexalgorithms", oKexAlgorithms },
+	{ "kexdhmin", oKexDHMin },
 	{ "ipqos", oIPQoS },
 	{ "requesttty", oRequestTTY },
 	{ "proxyusefdpass", oProxyUseFdpass },
 	{ "canonicaldomains", oCanonicalDomains },
 	{ "canonicalizefallbacklocal", oCanonicalizeFallbackLocal },
 	{ "canonicalizehostname", oCanonicalizeHostname },
 	{ "canonicalizemaxdots", oCanonicalizeMaxDots },
 	{ "canonicalizepermittedcnames", oCanonicalizePermittedCNAMEs },
 	{ "ignoreunknown", oIgnoreUnknown },
 
 	{ NULL, oBadOption }
 };
 
+/* import from dh.c */
+extern int dh_grp_min;
+
 /*
  * Adds a local TCP/IP port forward to options.  Never returns if there is an
  * error.
  */
 
 void
 add_local_forward(Options *options, const Forward *newfwd)
 {
@@ -1063,16 +1070,20 @@ parse_int:
 			    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 oKexDHMin:
+		intptr = &options->kex_dhmin;
+		goto parse_int;
+
 	case oHostKeyAlgorithms:
 		arg = strdelim(&s);
 		if (!arg || *arg == '\0')
 			fatal("%.200s line %d: Missing argument.", filename, linenum);
 		if (!key_names_valid2(arg))
 			fatal("%.200s line %d: Bad protocol 2 host key algorithms '%s'.",
 			    filename, linenum, arg ? arg : "<NONE>");
 		if (*activep && options->hostkeyalgorithms == NULL)
@@ -1520,16 +1531,17 @@ initialize_options(Options * options)
 	options->address_family = -1;
 	options->connection_attempts = -1;
 	options->connection_timeout = -1;
 	options->number_of_password_prompts = -1;
 	options->cipher = -1;
 	options->ciphers = NULL;
 	options->macs = NULL;
 	options->kex_algorithms = NULL;
+	options->kex_dhmin = -1;
 	options->hostkeyalgorithms = NULL;
 	options->protocol = SSH_PROTO_UNKNOWN;
 	options->num_identity_files = 0;
 	options->hostname = NULL;
 	options->host_key_alias = NULL;
 	options->proxy_command = NULL;
 	options->user = NULL;
 	options->escape_char = -1;
@@ -1656,16 +1668,23 @@ fill_default_options(Options * options)
 	if (options->number_of_password_prompts == -1)
 		options->number_of_password_prompts = 3;
 	/* Selected in ssh_login(). */
 	if (options->cipher == -1)
 		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 */
+	if (options->kex_dhmin == -1)
+		options->kex_dhmin = DH_GRP_MIN;
+	else {
+		options->kex_dhmin = MAX(options->kex_dhmin, DH_GRP_MIN_RFC);
+		options->kex_dhmin = MIN(options->kex_dhmin, DH_GRP_MAX);
+	}
+	dh_grp_min = options->kex_dhmin;
 	/* options->hostkeyalgorithms, default set in myproposals.h */
 	if (options->protocol == SSH_PROTO_UNKNOWN)
 		options->protocol = SSH_PROTO_2;
 	if (options->num_identity_files == 0) {
 		if (options->protocol & SSH_PROTO_1) {
 			add_identity_file(options, "~/",
 			    _PATH_SSH_CLIENT_IDENTITY, 0);
 		}
diff --git a/openssh-6.6p1/readconf.h b/openssh-6.6p1/readconf.h
--- a/openssh-6.6p1/readconf.h
+++ b/openssh-6.6p1/readconf.h
@@ -79,16 +79,17 @@ typedef struct {
 					 * aborting connection attempt */
 	int     number_of_password_prompts;	/* Max number of password
 						 * prompts. */
 	int     cipher;		/* Cipher to use. */
 	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     kex_dhmin;	/* minimum bit length of the DH group parameter */
 	int	protocol;	/* Protocol in order of preference. */
 	char   *hostname;	/* Real host to connect. */
 	char   *host_key_alias;	/* hostname alias for .ssh/known_hosts */
 	char   *proxy_command;	/* Proxy command for connecting the host. */
 	char   *user;		/* User to log in as. */
 	int     escape_char;	/* Escape character; -2 = none */
 
 	u_int	num_system_hostfiles;	/* Paths for /etc/ssh/ssh_known_hosts */
diff --git a/openssh-6.6p1/ssh_config.0 b/openssh-6.6p1/ssh_config.0
--- a/openssh-6.6p1/ssh_config.0
+++ b/openssh-6.6p1/ssh_config.0
@@ -516,16 +516,29 @@ DESCRIPTION
 
                    curve25519-sha256@libssh.org,
                    ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,
                    diffie-hellman-group-exchange-sha256,
                    diffie-hellman-group-exchange-sha1,
                    diffie-hellman-group14-sha1,
                    diffie-hellman-group1-sha1
 
+     KexDHMin
+             Specifies the minimum accepted bit length of the DH group parameter p.
+             As per RFC4419, this is 1024 bits however, this has increasingly
+             been seen as insecure, which prompted the change to 2048 bits.
+             Setting this option allows the client to accept parameters shorter
+             than the current minimum, down to the RFC specified 1024 bits.
+             Using this option may be needed when connecting to servers that
+             only know short DH group parameters.
+             
+             Note that using this option can severly impact security and thus
+             should be viewed as a temporary fix of last resort and all efforts
+             should be made to fix the server.
+
      LocalCommand
              Specifies a command to execute on the local machine after
              successfully connecting to the server.  The command string
              extends to the end of the line, and is executed with the user's
              shell.  The following escape character substitutions will be
              performed: `%d' (local user's home directory), `%h' (remote host
              name), `%l' (local host name), `%n' (host name as provided on the
              command line), `%p' (remote port), `%r' (remote user name) or
diff --git a/openssh-6.6p1/ssh_config.5 b/openssh-6.6p1/ssh_config.5
--- a/openssh-6.6p1/ssh_config.5
+++ b/openssh-6.6p1/ssh_config.5
@@ -892,16 +892,28 @@ The default is:
 .Bd -literal -offset indent
 curve25519-sha256@libssh.org,
 ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,
 diffie-hellman-group-exchange-sha256,
 diffie-hellman-group-exchange-sha1,
 diffie-hellman-group14-sha1,
 diffie-hellman-group1-sha1
 .Ed
+.It Cm KexDHMin
+Specifies the minimum accepted bit length of the DH group parameter p.
+As per RFC4419, this is 1024 bits however, this has increasingly
+been seen as insecure, which prompted the change to 2048 bits.
+Setting this option allows the client to accept parameters shorter
+than the current minimum, down to the RFC specified 1024 bits.
+Using this option may be needed when connecting to servers that
+only know short DH group parameters.
+
+Note that using this option can severly impact security and thus
+should be viewed as a temporary fix of last resort and all efforts
+should be made to fix the server.
 .It Cm LocalCommand
 Specifies a command to execute on the local machine after successfully
 connecting to the server.
 The command string extends to the end of the line, and is executed with
 the user's shell.
 The following escape character substitutions will be performed:
 .Ql %d
 (local user's home directory),
