andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone
Blob Blame History Raw
From 6deda4f7c872efb052919b82320b3514fe8621ad Mon Sep 17 00:00:00 2001
From: William Brown <firstyear@redhat.com>
Date: Thu, 21 Apr 2016 13:36:28 +1000
Subject: [PATCH 398/404] Ticket 48798 - Enable DS to offer weaker DH params in
 NSS

Bug Description:  Java is unable to handle DH param's greater than 1024 bit.
As of NSS 2.20 and higher, nss defaults to params of 2048 bit. This breaks
all java clients.

Fix Description:  This adds a new option, allowWeakDHParams that allows
nss to generate and use insecure DH params that Java would be capable of
using.

This test case shows the ability to allow weak params, and
that they are indeed 1024 bits

https://fedorahosted.org/389/ticket/48798

Author: wibrown

Review by: nhosoi

(cherry picked from commit 50910ac7101e2ede6bf8211383dea8d5f00539bd)
---
 dirsrvtests/tests/tickets/ticket48798_test.py | 146 ++++++++++++++++++++++++++
 ldap/schema/01core389.ldif                    |   2 +-
 ldap/servers/slapd/ssl.c                      |  73 +++++++++++++
 lib/ldaputil/cert.c                           |   8 +-
 4 files changed, 227 insertions(+), 2 deletions(-)
 create mode 100644 dirsrvtests/tests/tickets/ticket48798_test.py

diff --git a/dirsrvtests/tests/tickets/ticket48798_test.py b/dirsrvtests/tests/tickets/ticket48798_test.py
new file mode 100644
index 0000000..6872552
--- /dev/null
+++ b/dirsrvtests/tests/tickets/ticket48798_test.py
@@ -0,0 +1,146 @@
+import os
+import sys
+import time
+import ldap
+import logging
+import pytest
+
+import nss
+
+from lib389 import DirSrv, Entry, tools, tasks
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389.tasks import *
+from lib389.utils import *
+
+# Only works in py2.7
+# from subprocess import check_output
+from subprocess import Popen
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+
+class TopologyStandalone(object):
+    def __init__(self, standalone):
+        standalone.open()
+        self.standalone = standalone
+
+
+@pytest.fixture(scope="module")
+def topology(request):
+    # Creating standalone instance ...
+    standalone = DirSrv(verbose=False)
+    args_instance[SER_HOST] = HOST_STANDALONE
+    args_instance[SER_PORT] = PORT_STANDALONE
+    args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+    args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX
+    args_standalone = args_instance.copy()
+    standalone.allocate(args_standalone)
+    instance_standalone = standalone.exists()
+    if instance_standalone:
+        standalone.delete()
+    standalone.create()
+    standalone.open()
+
+    # Delete each instance in the end
+    def fin():
+        pass
+        #standalone.delete()
+    request.addfinalizer(fin)
+
+    # Clear out the tmp dir
+    #standalone.clearTmpDir(__file__)
+
+    return TopologyStandalone(standalone)
+
+def check_socket_dh_param_size(hostname, port):
+    ### You know why we have to do this? 
+    # Because TLS and SSL suck. Hard. They are impossible. It's all terrible, burn it all down.
+    cmd = "echo quit | openssl s_client -connect {HOSTNAME}:{PORT} -msg -cipher DH | grep -A 1 ServerKeyExchange".format(
+        HOSTNAME=hostname,
+        PORT=port)
+    #output = check_output(cmd, shell=True)
+    p = Popen(cmd, shell=True, stdout=PIPE)
+    (output, _) = p.communicate()
+    
+    dhheader = output.split('\n')[1]
+    # Get rid of all the other whitespace.
+    dhheader = dhheader.replace(' ', '')
+    # Example is 0c00040b0100ffffffffffffffffadf8
+    # We need the bits 0100 here. Which means 256 bytes aka 256 * 8, for 2048 bit.
+    dhheader = dhheader[8:12]
+    # make it an int, and times 8
+    i = int(dhheader, 16) * 8
+    return i
+
+
+def test_ticket48798(topology):
+    """
+    Test DH param sizes offered by DS.
+
+    """
+
+    # Create a CA
+    # This is a trick. The nss db that ships with DS is broken fundamentally.
+    ## THIS ASSUMES old nss format. SQLite will bite us!
+    for f in ('key3.db', 'cert8.db', 'key4.db', 'cert9.db', 'secmod.db', 'pkcs11.txt'):
+        try:
+            os.remove("%s/%s" % (topology.standalone.confdir, f ))
+        except:
+            pass
+
+    # Check if the db exists. Should be false.
+    assert(topology.standalone.nss_ssl._db_exists() is False)
+    # Create it. Should work.
+    assert(topology.standalone.nss_ssl.reinit() is True)
+    # Check if the db exists. Should be true
+    assert(topology.standalone.nss_ssl._db_exists() is True)
+
+    # Check if ca exists. Should be false.
+    assert(topology.standalone.nss_ssl._rsa_ca_exists() is False)
+    # Create it. Should work.
+    assert(topology.standalone.nss_ssl.create_rsa_ca() is True)
+    # Check if ca exists. Should be true
+    assert(topology.standalone.nss_ssl._rsa_ca_exists() is True)
+
+    # Check if we have a server cert / key. Should be false.
+    assert(topology.standalone.nss_ssl._rsa_key_and_cert_exists() is False)
+    # Create it. Should work.
+    assert(topology.standalone.nss_ssl.create_rsa_key_and_cert() is True)
+    # Check if server cert and key exist. Should be true.
+    assert(topology.standalone.nss_ssl._rsa_key_and_cert_exists() is True)
+
+    topology.standalone.config.enable_ssl(secport=DEFAULT_SECURE_PORT, secargs={'nsSSL3Ciphers': '+all'} )
+
+    topology.standalone.restart(30)
+
+    # Confirm that we have a connection, and that it has DH
+
+    # Open a socket to the port.
+    # Check the security settings.
+    size = check_socket_dh_param_size(topology.standalone.host, DEFAULT_SECURE_PORT)
+
+    assert(size == 2048)
+
+    # Now toggle the settings.
+    mod = [(ldap.MOD_REPLACE, 'allowWeakDHParam', 'on')]
+    dn_enc = 'cn=encryption,cn=config'
+    topology.standalone.modify_s(dn_enc, mod)
+
+    topology.standalone.restart(30)
+
+    # Check the DH params are less than 1024.
+    size = check_socket_dh_param_size(topology.standalone.host, DEFAULT_SECURE_PORT)
+
+    assert(size == 1024)
+
+    log.info('Test complete')
+
+
+if __name__ == '__main__':
+    # Run isolated
+    # -s for DEBUG mode
+    CURRENT_FILE = os.path.realpath(__file__)
+    pytest.main("-s %s" % CURRENT_FILE)
diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif
index 79cefea..c962dc0 100644
--- a/ldap/schema/01core389.ldif
+++ b/ldap/schema/01core389.ldif
@@ -172,5 +172,5 @@ objectClasses: ( 2.16.840.1.113730.3.2.103 NAME 'nsDS5ReplicationAgreement' DESC
 objectClasses: ( 2.16.840.1.113730.3.2.39 NAME 'nsslapdConfig' DESC 'Netscape defined objectclass' SUP top MAY ( cn ) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.317 NAME 'nsSaslMapping' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsSaslMapRegexString $ nsSaslMapBaseDNTemplate $ nsSaslMapFilterTemplate ) X-ORIGIN 'Netscape Directory Server' )
 objectClasses: ( 2.16.840.1.113730.3.2.43 NAME 'nsSNMP' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsSNMPEnabled ) MAY ( nsSNMPOrganization $ nsSNMPLocation $ nsSNMPContact $ nsSNMPDescription $ nsSNMPName $ nsSNMPMasterHost $ nsSNMPMasterPort ) X-ORIGIN 'Netscape Directory Server' )
-objectClasses: ( nsEncryptionConfig-oid NAME 'nsEncryptionConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsCertfile $ nsKeyfile $ nsSSL2 $ nsSSL3 $ nsTLS1 $ nsSSLSessionTimeout $ nsSSL3SessionTimeout $ nsSSLClientAuth $ nsSSL2Ciphers $ nsSSL3Ciphers $ nsSSLSupportedCiphers) X-ORIGIN 'Netscape' )
+objectClasses: ( nsEncryptionConfig-oid NAME 'nsEncryptionConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsCertfile $ nsKeyfile $ nsSSL2 $ nsSSL3 $ nsTLS1 $ nsSSLSessionTimeout $ nsSSL3SessionTimeout $ nsSSLClientAuth $ nsSSL2Ciphers $ nsSSL3Ciphers $ nsSSLSupportedCiphers $ allowWeakDHParam ) X-ORIGIN 'Netscape' )
 objectClasses: ( nsEncryptionModule-oid NAME 'nsEncryptionModule' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsSSLToken $ nsSSLPersonalityssl $ nsSSLActivation ) X-ORIGIN 'Netscape' )
diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c
index 1f64be0..090c328 100644
--- a/ldap/servers/slapd/ssl.c
+++ b/ldap/servers/slapd/ssl.c
@@ -89,6 +89,10 @@
 #define NSS_TLS10 1
 #endif
 
+#if NSS_VMAJOR * 100 + NSS_VMINOR >= 320
+#define HAVE_NSS_DHE 1
+#endif
+
 #if !defined(NSS_TLS10) /* NSS_TLS11 or newer */
 static SSLVersionRange enabledNSSVersions;
 static SSLVersionRange slapdNSSVersions;
@@ -117,6 +121,7 @@ static int stimeout;
 static char *ciphers = NULL;
 static char * configDN = "cn=encryption,cn=config";
 
+
 /* Copied from libadmin/libadmin.h public/nsapi.h */
 #define SERVER_KEY_NAME "Server-Key"
 #define MAGNUS_ERROR_LEN 1024
@@ -125,6 +130,15 @@ static char * configDN = "cn=encryption,cn=config";
 #define FILE_PATHSEP '/'
 
 /* ----------------------- Multiple cipher support ------------------------ */
+#ifdef HAVE_NSS_DHE
+#define CIPHER_SET_DEFAULTWEAKDHPARAM 0x100 /* allowWeakDhParam is not set in cn=encryption */
+#define CIPHER_SET_ALLOWWEAKDHPARAM   0x200 /* allowWeakDhParam is on */
+#define CIPHER_SET_DISALLOWWEAKDHPARAM   0x400 /* allowWeakDhParam is off */
+#endif
+
+#ifdef HAVE_NSS_DHE
+static int allowweakdhparam = CIPHER_SET_DEFAULTWEAKDHPARAM;
+#endif
 
 
 static char **cipher_names = NULL;
@@ -244,6 +258,33 @@ getSupportedCiphers()
 	return cipher_names;
 }
 
+#ifdef HAVE_NSS_DHE
+int
+get_allow_weak_dh_param(Slapi_Entry *e)
+{
+    /* Check if the user wants weak params */
+    int allow = CIPHER_SET_DEFAULTWEAKDHPARAM;
+    char *val;
+    val = slapi_entry_attr_get_charptr(e, "allowWeakDHParam");
+    if (val) {
+        if (!PL_strcasecmp(val, "off") || !PL_strcasecmp(val, "false") || 
+                !PL_strcmp(val, "0") || !PL_strcasecmp(val, "no")) {
+            allow = CIPHER_SET_DISALLOWWEAKDHPARAM;
+        } else if (!PL_strcasecmp(val, "on") || !PL_strcasecmp(val, "true") || 
+                !PL_strcmp(val, "1") || !PL_strcasecmp(val, "yes")) {
+            allow = CIPHER_SET_ALLOWWEAKDHPARAM;
+            slapd_SSL_warn("The value of allowWeakDHParam is set to %s. THIS EXPOSES YOU TO CVE-2015-4000.", val);
+        } else {
+            slapd_SSL_warn("The value of allowWeakDHParam \"%s\" is invalid.",
+                           "Ignoring it and set it to default.", val);
+        }
+    }
+    slapi_ch_free((void **) &val);
+    return allow;
+}
+#endif
+
+
 char **
 getEnabledCiphers()
 {
@@ -841,6 +882,9 @@ slapd_ssl_init() {
     int rv = 0;
     PK11SlotInfo *slot;
     Slapi_Entry *entry = NULL;
+#ifdef HAVE_NSS_DHE
+    SECStatus  nss_rv = SECFailure;
+#endif
 
     /* Get general information */
 
@@ -849,6 +893,17 @@ slapd_ssl_init() {
     val = slapi_entry_attr_get_charptr( entry, "nssslSessionTimeout" );
     ciphers = slapi_entry_attr_get_charptr( entry, "nsssl3ciphers" );
 
+#ifdef HAVE_NSS_DHE
+    allowweakdhparam = get_allow_weak_dh_param(entry);
+    if (allowweakdhparam & CIPHER_SET_ALLOWWEAKDHPARAM) {
+        slapd_SSL_warn("notice, generating new WEAK DH param");
+        nss_rv = SSL_EnableWeakDHEPrimeGroup(NULL, PR_TRUE);
+        if (nss_rv != SECSuccess) {
+            slapd_SSL_warn("Warning, unable to generate weak dh parameters");
+        }
+    }
+#endif
+
     /* We are currently using the value of sslSessionTimeout
 	   for ssl3SessionTimeout, see SSL_ConfigServerSessionIDCache() */
     /* Note from Tom Weinstein on the meaning of the timeout:
@@ -1192,6 +1247,24 @@ int slapd_ssl_init2(PRFileDesc **fd, int startTLS)
                 }
 
                 if (SECSuccess == rv) {
+
+#ifdef HAVE_NSS_DHE
+                    /* Step If we want weak dh params, flag it on the socket now! */
+
+                    rv = SSL_OptionSet(*fd, SSL_ENABLE_SERVER_DHE, PR_TRUE);
+                    if (rv != SECSuccess) {
+                        slapd_SSL_warn("Warning, unable to start DHE");
+                    }
+
+                    if (allowweakdhparam & CIPHER_SET_ALLOWWEAKDHPARAM) {
+                        slapd_SSL_warn("notice, allowing weak parameters on socket.");
+                        rv = SSL_EnableWeakDHEPrimeGroup(*fd, PR_TRUE);
+                        if (rv != SECSuccess) {
+                            slapd_SSL_warn("Warning, unable to allow weak DH params on socket.");
+                        }
+                    }
+#endif
+
                     if( slapd_pk11_fortezzaHasKEA(cert) == PR_TRUE ) {
                         rv = SSL_ConfigSecureServer(*fd, cert, key, kt_fortezza);
                     }
diff --git a/lib/ldaputil/cert.c b/lib/ldaputil/cert.c
index c26ff41..d617741 100644
--- a/lib/ldaputil/cert.c
+++ b/lib/ldaputil/cert.c
@@ -50,6 +50,7 @@
 #include "prmem.h"
 #include "key.h"
 #include "cert.h"
+#include <nss.h>
 #include <ldaputil/certmap.h>
 #include <ldaputil/errors.h>
 #include <ldaputil/cert.h>
@@ -285,7 +286,12 @@ _replaceAVA (char* attr, char** avas)
 }
 
 struct _attr_getter_pair {
-    char* (*getter) (CERTName* dn);
+#if NSS_VMAJOR < 3 || (NSS_VMAJOR == 3 && NSS_VMINOR < 15)
+    char* (*getter) ( CERTName* dn);
+#else
+    /* in 3.15.x "const" was added to the declarations */
+    char* (*getter) (const CERTName* dn);
+#endif
     const char* name1;
     const char* name2;
 } _attr_getter_table[] =
-- 
2.4.11