From f90ab77c60cfc775e4b0173d28f7f951c6da578d Mon Sep 17 00:00:00 2001 From: Howard Johnson Date: Wed, 28 Jun 2017 21:39:38 +0100 Subject: [PATCH] Ticket 49303 - Add option to disable TLS client-initiated renegotiation Bug Description: TLS renegotiation is a CPU-intensive process, which a malicious client could use to consume server resources and perform a denial of service attack. NSS defaults to allowing client-initiated renegotiation, but has an option to disable it. It would be useful to expose this as a DS configuration option. Fix Description: Added a new 'nsSSLRenegotiate' attribute to the cn=encryption,cn=config object. This takes two values 'on', and 'off'. If the value is 'off', renegotiation is disabled. If the value is 'on', is not set, or is set to an invalid value, renegotiation is enabled. https://pagure.io/389-ds-base/issue/49303 Author: Howard Johnson Review by: ??? --- dirsrvtests/tests/tickets/ticket49303_test.py | 121 ++++++++++++++++++++++++++ ldap/schema/01core389.ldif | 3 +- ldap/servers/slapd/ssl.c | 25 ++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 dirsrvtests/tests/tickets/ticket49303_test.py diff --git a/dirsrvtests/tests/tickets/ticket49303_test.py b/dirsrvtests/tests/tickets/ticket49303_test.py new file mode 100644 index 0000000..a8d62a4 --- /dev/null +++ b/dirsrvtests/tests/tickets/ticket49303_test.py @@ -0,0 +1,121 @@ +import time +import logging +import os +import subprocess +import ldap +import pytest +from lib389 import Entry +from lib389.topologies import topology_st as topo + +DEBUGGING = os.getenv("DEBUGGING", default=False) +if DEBUGGING: + logging.getLogger(__name__).setLevel(logging.DEBUG) +else: + logging.getLogger(__name__).setLevel(logging.INFO) +log = logging.getLogger(__name__) + +CONFIG_DN = 'cn=config' +ENCRYPTION_DN = 'cn=encryption,%s' % CONFIG_DN +RSA = 'RSA' +RSA_DN = 'cn=%s,%s' % (RSA, ENCRYPTION_DN) +SERVERCERT = 'Server-Cert' + + +def try_reneg(host, port): + """ + Connect to the specified host and port with openssl, and attempt to + initiate a renegotiation. Returns true if successful, false if not. + """ + + cmd = [ + '/usr/bin/openssl', + 's_client', + '-connect', + '%s:%s' % (host, port), + ] + + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE) + except ValueError, e: + log.info("openssl failed: %s" % e) + proc.kill() + + # This 'R' command is intercepted by openssl and triggers a renegotiation + proc.communicate('R\n') + + # We rely on openssl returning 0 if no errors occured, and 1 if any did + # (for example, the server rejecting renegotiation and terminating the + # connection) + return proc.returncode == 0 + + +def enable_ssl(server, ldapsport): + server.stop() + server.nss_ssl.reinit() + server.nss_ssl.create_rsa_ca() + server.nss_ssl.create_rsa_key_and_cert() + server.start() + + server.modify_s(ENCRYPTION_DN, [(ldap.MOD_REPLACE, 'nsSSL3', 'off'), + (ldap.MOD_REPLACE, 'nsTLS1', 'on'), + (ldap.MOD_REPLACE, 'nsSSLClientAuth', 'allowed'), + (ldap.MOD_REPLACE, 'allowWeakCipher', 'on'), + (ldap.MOD_REPLACE, 'nsSSL3Ciphers', '+all')]) + + time.sleep(1) + server.modify_s(CONFIG_DN, [(ldap.MOD_REPLACE, 'nsslapd-security', 'on'), + (ldap.MOD_REPLACE, 'nsslapd-ssl-check-hostname', 'off'), + (ldap.MOD_REPLACE, 'nsslapd-secureport', ldapsport)]) + + time.sleep(1) + server.add_s(Entry((RSA_DN, {'objectclass': "top nsEncryptionModule".split(), + 'cn': RSA, + 'nsSSLPersonalitySSL': SERVERCERT, + 'nsSSLToken': 'internal (software)', + 'nsSSLActivation': 'on'}))) + + time.sleep(1) + server.restart() + + +def set_reneg(server, state): + server.modify_s(ENCRYPTION_DN, [(ldap.MOD_REPLACE, 'nsSSLRenegotiate', state)]) + time.sleep(1) + server.restart() + + +def test_ticket49303(topo): + """ + Test the nsSSLRenegotiate setting. + """ + sslport = '63601' + + log.info("Ticket 49303 - Allow disabling of SSL renegotiation") + + # No value set, defaults to reneg allowed + enable_ssl(topo.standalone, sslport) + assert (try_reneg('127.0.0.1', sslport) == True) + + # Turn reneg off + set_reneg(topo.standalone, 'off') + assert (try_reneg('127.0.0.1', sslport) == False) + + # Explicitly enable + set_reneg(topo.standalone, 'on') + assert (try_reneg('127.0.0.1', sslport) == True) + + # Set to an invalid value, defaults to allowed + set_reneg(topo.standalone, 'invalid') + assert (try_reneg('127.0.0.1', sslport) == True) + + log.info("Ticket 49303 - PASSED") + + +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 5e5f69f..148a191 100644 --- a/ldap/schema/01core389.ldif +++ b/ldap/schema/01core389.ldif @@ -107,6 +107,7 @@ attributeTypes: ( nsSSLToken-oid NAME 'nsSSLToken' DESC 'Netscape defined attrib attributeTypes: ( nsSSLPersonalitySSL-oid NAME 'nsSSLPersonalitySSL' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) attributeTypes: ( nsSSLActivation-oid NAME 'nsSSLActivation' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) attributeTypes: ( CACertExtractFile-oid NAME 'CACertExtractFile' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) +attributeTypes: ( nsSSLRenegotiate-oid NAME 'nsSSLRenegotiate' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) attributeTypes: ( ServerKeyExtractFile-oid NAME 'ServerKeyExtractFile' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) attributeTypes: ( ServerCertExtractFile-oid NAME 'ServerCertExtractFile' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'Netscape' ) attributeTypes: ( 2.16.840.1.113730.3.1.2091 NAME 'nsslapd-suffix' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'Netscape' ) @@ -317,7 +318,7 @@ 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 ) MAY ( nsSaslMapPriority ) 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 $ nsTLS10 $ nsTLS11 $ nsTLS12 $ sslVersionMin $ sslVersionMax $ nsSSLSessionTimeout $ nsSSL3SessionTimeout $ nsSSLClientAuth $ nsSSL2Ciphers $ nsSSL3Ciphers $ nsSSLSupportedCiphers $ allowWeakCipher $ CACertExtractFile $ allowWeakDHParam ) X-ORIGIN 'Netscape' ) +objectClasses: ( nsEncryptionConfig-oid NAME 'nsEncryptionConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsCertfile $ nsKeyfile $ nsSSL2 $ nsSSL3 $ nsTLS1 $ nsTLS10 $ nsTLS11 $ nsTLS12 $ sslVersionMin $ sslVersionMax $ nsSSLSessionTimeout $ nsSSL3SessionTimeout $ nsSSLClientAuth $ nsSSL2Ciphers $ nsSSL3Ciphers $ nsSSLSupportedCiphers $ allowWeakCipher $ CACertExtractFile $ allowWeakDHParam $ nsSSLRenegotiate ) X-ORIGIN 'Netscape' ) objectClasses: ( nsEncryptionModule-oid NAME 'nsEncryptionModule' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsSSLToken $ nsSSLPersonalityssl $ nsSSLActivation $ ServerKeyExtractFile $ ServerCertExtractFile ) X-ORIGIN 'Netscape' ) objectClasses: ( 2.16.840.1.113730.3.2.327 NAME 'rootDNPluginConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( rootdn-open-time $ rootdn-close-time $ rootdn-days-allowed $ rootdn-allow-host $ rootdn-deny-host $ rootdn-allow-ip $ rootdn-deny-ip ) X-ORIGIN 'Netscape' ) objectClasses: ( 2.16.840.1.113730.3.2.328 NAME 'nsSchemaPolicy' DESC 'Netscape defined objectclass' SUP top MAY ( cn $ schemaUpdateObjectclassAccept $ schemaUpdateObjectclassReject $ schemaUpdateAttributeAccept $ schemaUpdateAttributeReject) X-ORIGIN 'Netscape Directory Server' ) diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c index 01b708d..c075ad8 100644 --- a/ldap/servers/slapd/ssl.c +++ b/ldap/servers/slapd/ssl.c @@ -1753,6 +1753,7 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS) #endif char cipher_string[1024]; int allowweakcipher = CIPHER_SET_DEFAULTWEAKCIPHER; + PRBool renegotiation = SSL_RENEGOTIATE_REQUIRES_XTN; /* turn off the PKCS11 pin interactive mode */ /* wibrown 2016 */ @@ -2218,6 +2219,30 @@ slapd_ssl_init2(PRFileDesc **fd, int startTLS) #if !defined(NSS_TLS10) /* NSS_TLS11 or newer */ } #endif + + val = NULL; + if (e != NULL) { + val = slapi_entry_attr_get_charptr(e, "nssslrenegotiate"); + } + if( val ) { + if (!PL_strcasecmp(val, "off")) { + renegotiation = SSL_RENEGOTIATE_NEVER; + } else if (PL_strcasecmp(val, "on")) { + slapd_SSL_warn("The value of nsSSLRenegotiate (\"%s\") is invalid." + " Ignoring it and setting it to default.", val); + } + } + slapi_ch_free_string(&val); + + sslStatus = SSL_OptionSet(pr_sock, SSL_ENABLE_RENEGOTIATION, renegotiation); + if (sslStatus != SECSuccess) { + errorCode = PR_GetError(); + slapd_SSL_error("Failed to set SSL renegotiation on the imported " + "socket (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)", + errorCode, slapd_pr_strerror(errorCode)); + return -1; + } + freeConfigEntry( &e ); if(( slapd_SSLclientAuth = config_get_SSLclientAuth()) != SLAPD_SSLCLIENTAUTH_OFF ) { -- 2.9.4