summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--contrib/nssciphersuite/README.txt36
-rwxr-xr-xcontrib/nssciphersuite/nssciphersuite.py147
-rw-r--r--ipaserver/install/httpinstance.py19
-rw-r--r--ipaserver/install/server/upgrade.py18
4 files changed, 220 insertions, 0 deletions
diff --git a/contrib/nssciphersuite/README.txt b/contrib/nssciphersuite/README.txt
new file mode 100644
index 000000000..c36594b9b
--- /dev/null
+++ b/contrib/nssciphersuite/README.txt
@@ -0,0 +1,36 @@
+Cipher suite for mod_nss
+------------------------
+
+The nssciphersuite.py script parses mod_nss' nss_engine_cipher.c file and
+creates a list of secure cipher suites for TLS. The script filters out
+insecure, obsolete and slow ciphers according to some rules.
+
+As of January 2016 and mod_nss 1.0.12 the cipher suite list contains 14
+cipher suites for TLS 1.0, 1.1 and 1.2 for RSA and ECDSA certificates. The
+cipher suite list also supports Perfect Forward Secrecy with ephemeral ECDH
+key exchange. https://www.ssllabs.com/ gives a 'A' grade.
+
+Note:
+No suite is compatible with IE 8 and earlier on Windows XP. If you need IE 8
+support, append "+rsa_3des_sha" to enable TLS_RSA_WITH_3DES_EDE_CBC_SHA.
+
+# disabled cipher attributes: SSL_3DES, SSL_CAMELLIA, SSL_CAMELLIA128, SSL_CAMELLIA256, SSL_DES, SSL_DSS, SSL_MD5, SSL_RC2, SSL_RC4, SSL_aDSS, SSL_aNULL, SSL_eNULL, SSL_kECDHe, SSL_kECDHr, kECDH
+# weak strength: SSL_EXPORT40, SSL_EXPORT56, SSL_LOW, SSL_STRONG_NONE
+# enabled cipher suites:
+# TLS_RSA_WITH_AES_128_CBC_SHA256
+# TLS_RSA_WITH_AES_256_CBC_SHA256
+# TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+# TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
+# TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+# TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
+# TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+# TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
+# TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+# TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
+# TLS_RSA_WITH_AES_128_GCM_SHA256
+# TLS_RSA_WITH_AES_128_CBC_SHA
+# TLS_RSA_WITH_AES_256_GCM_SHA384
+# TLS_RSA_WITH_AES_256_CBC_SHA
+#
+
+NSSCipherSuite +aes_128_sha_256,+aes_256_sha_256,+ecdhe_ecdsa_aes_128_gcm_sha_256,+ecdhe_ecdsa_aes_128_sha,+ecdhe_ecdsa_aes_256_gcm_sha_384,+ecdhe_ecdsa_aes_256_sha,+ecdhe_rsa_aes_128_gcm_sha_256,+ecdhe_rsa_aes_128_sha,+ecdhe_rsa_aes_256_gcm_sha_384,+ecdhe_rsa_aes_256_sha,+rsa_aes_128_gcm_sha_256,+rsa_aes_128_sha,+rsa_aes_256_gcm_sha_384,+rsa_aes_256_sha
diff --git a/contrib/nssciphersuite/nssciphersuite.py b/contrib/nssciphersuite/nssciphersuite.py
new file mode 100755
index 000000000..dee05d470
--- /dev/null
+++ b/contrib/nssciphersuite/nssciphersuite.py
@@ -0,0 +1,147 @@
+#!/usr/bin/python3
+#
+# Authors:
+# Christian Heimes <cheimes@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+"""Generate safe NSSCipherSuite stanza for mod_nss
+"""
+from __future__ import print_function
+
+import operator
+import re
+from urllib.request import urlopen # pylint: disable=no-name-in-module
+
+SOURCE = "https://git.fedorahosted.org/cgit/mod_nss.git/plain/nss_engine_cipher.c"
+
+CIPHER_RE = re.compile(
+ r'\s*\{'
+ r'\"(?P<name>\w+)\",\s*'
+ r'(?P<num>(TLS|SSL)_\w+),\s*'
+ r'\"(?P<openssl_name>[\w-]+)\",\s*'
+ r'(?P<attr>[\w|]+),\s*'
+ r'(?P<version>\w+),\s*'
+ r'(?P<strength>\w+),\s*'
+ r'(?P<bits>\d+),\s*'
+ r'(?P<alg_bits>\d+)'
+)
+
+DISABLED_CIPHERS = {
+ # ciphers without encryption or authentication
+ 'SSL_eNULL', 'SSL_aNULL',
+ # MD5 is broken
+ # SHA-1 is still required as PRF algorithm for TLSv1.0
+ 'SSL_MD5',
+ # RC2 and RC4 stream ciphers are broken.
+ 'SSL_RC2', 'SSL_RC4',
+ # DES is broken and Triple DES is too weak.
+ 'SSL_DES', 'SSL_3DES',
+ # DSA is problematic.
+ 'SSL_DSS', 'SSL_aDSS',
+ # prefer AES over Camellia.
+ 'SSL_CAMELLIA128', 'SSL_CAMELLIA256', 'SSL_CAMELLIA',
+ # non-ephemeral EC Diffie-Hellmann with fixed parameters are not
+ # used by common browser and are therefore irrelevant for HTTPS.
+ 'kECDH', 'SSL_kECDHr', 'SSL_kECDHe'
+}
+
+WEAK_STRENGTH = {
+ 'SSL_STRONG_NONE',
+ 'SSL_EXPORT40',
+ 'SSL_EXPORT56',
+ 'SSL_LOW'
+}
+
+
+def parse_nss_engine_cipher(lines, encoding='utf-8'):
+ """Parse nss_engine_cipher.c and get list of ciphers
+
+ :param lines: iterable or list of lines
+ :param encoding: default encoding
+ :return: list of cipher dicts
+ """
+ ciphers = []
+ start = False
+ for line in lines:
+ if not isinstance(line, str):
+ line = line.decode(encoding)
+
+ if line.startswith('cipher_properties'):
+ start = True
+ elif not start:
+ continue
+ elif line.startswith('};'):
+ break
+
+ mo = CIPHER_RE.match(line)
+ if not mo:
+ continue
+
+ match = mo.groupdict()
+ match['attr'] = set(match['attr'].split('|'))
+ match['bits'] = int(match['bits'])
+ match['alg_bits'] = int(match['alg_bits'])
+
+ # some cipher elemets aren't flagged
+ for algo in ['SHA256', 'SHA384']:
+ if match['num'].endswith(algo):
+ match['attr'].add('SSL_{}'.format(algo))
+
+ # cipher block chaining isn't tracked
+ if '_CBC' in match['num']:
+ match['attr'].add('SSL_CBC')
+
+ if match['attr'].intersection(DISABLED_CIPHERS):
+ match['enabled'] = False
+ elif match['strength'] in WEAK_STRENGTH:
+ match['enabled'] = False
+ else:
+ match['enabled'] = True
+
+ # EECDH + AES-CBC and large hash functions is slow and not more secure
+ if (match['attr'].issuperset({'SSL_CBC', 'SSL_kEECDH'}) and
+ match['attr'].intersection({'SSL_SHA256', 'SSL_SHA384'})):
+ match['enabled'] = False
+
+ ciphers.append(match)
+
+ ciphers.sort(key=operator.itemgetter('name'))
+ return ciphers
+
+
+def main():
+ with urlopen(SOURCE) as r:
+ ciphers = parse_nss_engine_cipher(r)
+ # with open('nss_engine_cipher.c') as f:
+ # ciphers = parse_nss_engine_cipher(f)
+
+ print("# disabled cipher attributes: {}".format(
+ ', '.join(sorted(DISABLED_CIPHERS))))
+ print("# weak strength: {}".format(', '.join(sorted(WEAK_STRENGTH))))
+ print("# enabled cipher suites:")
+ suite = []
+ for cipher in ciphers:
+ if cipher['enabled']:
+ print("# {:36}".format(cipher['num']))
+ suite.append('+{}'.format(cipher['name']))
+ print()
+ print("NSSCipherSuite {}".format(','.join(suite)))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
index 3b46dce82..44e0a7fe0 100644
--- a/ipaserver/install/httpinstance.py
+++ b/ipaserver/install/httpinstance.py
@@ -57,6 +57,19 @@ SELINUX_BOOLEAN_SETTINGS = dict(
KDCPROXY_USER = 'kdcproxy'
HTTPD_USER = constants.HTTPD_USER
+# See contrib/nsscipersuite/nssciphersuite.py
+NSS_CIPHER_SUITE = [
+ '+aes_128_sha_256', '+aes_256_sha_256',
+ '+ecdhe_ecdsa_aes_128_gcm_sha_256', '+ecdhe_ecdsa_aes_128_sha',
+ '+ecdhe_ecdsa_aes_256_gcm_sha_384', '+ecdhe_ecdsa_aes_256_sha',
+ '+ecdhe_rsa_aes_128_gcm_sha_256', '+ecdhe_rsa_aes_128_sha',
+ '+ecdhe_rsa_aes_256_gcm_sha_384', '+ecdhe_rsa_aes_256_sha',
+ '+rsa_aes_128_gcm_sha_256', '+rsa_aes_128_sha',
+ '+rsa_aes_256_gcm_sha_384', '+rsa_aes_256_sha'
+]
+NSS_CIPHER_REVISION = '20160129'
+
+
def httpd_443_configured():
"""
We now allow mod_ssl to be installed so don't automatically disable it.
@@ -146,6 +159,8 @@ class HTTPInstance(service.Service):
self.step("setting mod_nss port to 443", self.__set_mod_nss_port)
+ self.step("setting mod_nss cipher suite",
+ self.set_mod_nss_cipher_suite)
self.step("setting mod_nss protocol list to TLSv1.0 - TLSv1.2",
self.set_mod_nss_protocol)
self.step("setting mod_nss password file", self.__set_mod_nss_passwordfile)
@@ -255,6 +270,10 @@ class HTTPInstance(service.Service):
installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRenegotiation', 'on', False)
installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSRequireSafeNegotiation', 'on', False)
+ def set_mod_nss_cipher_suite(self):
+ ciphers = ','.join(NSS_CIPHER_SUITE)
+ installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSCipherSuite', ciphers, False)
+
def __set_mod_nss_passwordfile(self):
installutils.set_directive(paths.HTTPD_NSS_CONF, 'NSSPassPhraseDialog', 'file:' + paths.HTTPD_PASSWORD_CONF)
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 48f8579a4..584a5fc13 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -1343,6 +1343,23 @@ def update_mod_nss_protocol(http):
sysupgrade.set_upgrade_state('nss.conf', 'protocol_updated_tls12', True)
+
+def update_mod_nss_cipher_suite(http):
+ root_logger.info('[Updating mod_nss cipher suite]')
+
+ revision = sysupgrade.get_upgrade_state('nss.conf', 'cipher_suite_updated')
+ if revision >= httpinstance.NSS_CIPHER_REVISION:
+ root_logger.debug("Cipher suite already updated")
+ return
+
+ http.set_mod_nss_cipher_suite()
+
+ sysupgrade.set_upgrade_state(
+ 'nss.conf',
+ 'cipher_suite_updated',
+ httpinstance.NSS_CIPHER_REVISION)
+
+
def ds_enable_sidgen_extdom_plugins(ds):
"""For AD trust agents, make sure we enable sidgen and extdom plugins
"""
@@ -1526,6 +1543,7 @@ def upgrade_configuration():
http.stop()
update_mod_nss_protocol(http)
+ update_mod_nss_cipher_suite(http)
fix_trust_flags()
export_kra_agent_pem()
http.start()