From a11f3ac2ba65ed48bef487c005b1367d006a370c Mon Sep 17 00:00:00 2001 From: Jonas Bardino Date: Mon, 16 Mar 2026 17:59:54 +0100 Subject: [PATCH 1/3] Initial unit test coverage of shared tlsserver module. --- mig/shared/tlsserver.py | 2 +- tests/test_mig_shared_tlsserver.py | 359 +++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+), 1 deletion(-) create mode 100644 tests/test_mig_shared_tlsserver.py diff --git a/mig/shared/tlsserver.py b/mig/shared/tlsserver.py index 1a730f9e9..0c2936989 100644 --- a/mig/shared/tlsserver.py +++ b/mig/shared/tlsserver.py @@ -4,7 +4,7 @@ # --- BEGIN_HEADER --- # # tlsserver - Shared functions for all SSL/TLS-secured servers -# Copyright (C) 2003-2021 The MiG Project lead by Brian Vinter +# Copyright (C) 2003-2026 The MiG Project by the Science HPC Center at UCPH # # This file is part of MiG. # diff --git a/tests/test_mig_shared_tlsserver.py b/tests/test_mig_shared_tlsserver.py new file mode 100644 index 000000000..ba5b89dab --- /dev/null +++ b/tests/test_mig_shared_tlsserver.py @@ -0,0 +1,359 @@ +# -*- coding: utf-8 -*- +# +# --- BEGIN_HEADER --- +# +# test_mig_shared_tlsserver - unit test of the corresponding mig shared module +# Copyright (C) 2003-2026 The MiG Project by the Science HPC Center at UCPH +# +# This file is part of MiG. +# +# MiG 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; either version 2 of the License, or +# (at your option) any later version. +# +# MiG 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. +# +# --- END_HEADER --- +# + +"""Unit tests for the migrid module pointed to in the filename""" + +import os +import unittest +from unittest.mock import patch, MagicMock + +# PyOpenSSL is required for hardened_openssl_context tests +try: + import OpenSSL +except ImportError: + OpenSSL = None + +# Imports of the code under test +from mig.shared.tlsserver import hardened_ssl_context, hardened_openssl_context, ssl +# Imports required for the unit test wrapping +from mig.shared.defaults import STRONG_TLS_CIPHERS, STRONG_TLS_LEGACY_CIPHERS, \ + STRONG_TLS_CURVES +# Imports required for the unit tests themselves +from tests.support import MigTestCase + + +class MigSharedTlsServer(MigTestCase): + """Unit tests for tlsserver related helper functions""" + + def _provide_configuration(self): + """Prepare isolated test config""" + return 'testconfig' + + def test_hardened_ssl_context_basic(self): + """Test basic SSL context creation with default parameters""" + with patch('mig.shared.tlsserver.ssl') as mock_ssl: + mock_ssl.PROTOCOL_SSLv23 = 1 + mock_ssl.SSLContext = MagicMock() + mock_ssl.SSLContext.return_value = MagicMock() + mock_ssl.SSLContext.return_value.options = 0 + + config = self.configuration + config.logger = self.logger + + with self.assertLogs(level="INFO") as log_capture: + context = hardened_ssl_context( + config, + 'keyfile', + 'certfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + self.assertIsNotNone(context) + mock_ssl.SSLContext.assert_called_once_with(1) + mock_ssl.SSLContext.return_value.load_cert_chain.assert_called_once_with( + 'certfile', 'keyfile' + ) + self.assertTrue( + any("enforcing strong SSL/TLS connections" in msg + for msg in log_capture.output) + ) + + @unittest.skip("Fix this test and enable it") + def test_hardened_ssl_context_options(self): + """Test SSL context options are set correctly""" + with patch('mig.shared.tlsserver.ssl') as mock_ssl: + mock_ssl.PROTOCOL_SSLv23 = 1 + mock_ssl.SSLContext = MagicMock() + mock_ssl.SSLContext.return_value = MagicMock() + mock_ssl.SSLContext.return_value.options = 0 + + config = self.configuration + config.logger = self.logger + + context = hardened_ssl_context( + config, + 'keyfile', + 'certfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + # Verify options are set + expected_options = ( + getattr(ssl, 'OP_NO_SSLv2', 0x1000000) | + getattr(ssl, 'OP_NO_SSLv3', 0x2000000) | + getattr(ssl, 'OP_NO_TLSv1', 0x4000000) | + getattr(ssl, 'OP_NO_TLSv1_1', 0x10000000) | + getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | + getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) + ) + # Verify the options were OR'd into the context + mock_ssl.SSLContext.return_value.options |= expected_options + self.assertEqual( + mock_ssl.SSLContext.return_value.options, expected_options) + + def test_hardened_ssl_context_ciphers(self): + """Test SSL context ciphers are set correctly""" + with patch('mig.shared.tlsserver.ssl') as mock_ssl: + mock_ssl.PROTOCOL_SSLv23 = 1 + mock_ssl.SSLContext = MagicMock() + mock_ssl.SSLContext.return_value = MagicMock() + mock_ssl.SSLContext.return_value.options = 0 + + config = self.configuration + config.logger = self.logger + + context = hardened_ssl_context( + config, + 'keyfile', + 'certfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + mock_ssl.SSLContext.return_value.set_ciphers.assert_called_once_with( + STRONG_TLS_CIPHERS) + + def test_hardened_ssl_context_legacy_ciphers(self): + """Test SSL context ciphers are set correctly""" + with patch('mig.shared.tlsserver.ssl') as mock_ssl: + mock_ssl.PROTOCOL_SSLv23 = 1 + mock_ssl.SSLContext = MagicMock() + mock_ssl.SSLContext.return_value = MagicMock() + mock_ssl.SSLContext.return_value.options = 0 + + config = self.configuration + config.logger = self.logger + + context = hardened_ssl_context( + config, + 'keyfile', + 'certfile', + 'dhparamsfile', + STRONG_TLS_LEGACY_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + mock_ssl.SSLContext.return_value.set_ciphers.assert_called_once_with( + STRONG_TLS_LEGACY_CIPHERS) + + @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + def test_hardened_openssl_context_basic(self): + """Test basic OpenSSL context creation with default parameters""" + with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: + mock_openssl.SSL = MagicMock() + mock_openssl.SSL.SSLv23_METHOD = 1 + mock_openssl.SSL.Context = MagicMock() + mock_openssl.SSL.Context.return_value = MagicMock() + mock_openssl.SSL.Context.return_value.set_options = MagicMock() + mock_openssl.crypto = MagicMock() + + config = self.configuration + config.logger = self.logger + + context = hardened_openssl_context( + config, + mock_openssl, + 'keyfile', + 'certfile', + 'cacertfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + self.assertIsNotNone(context) + mock_openssl.SSL.Context.assert_called_once_with(1) + mock_openssl.SSL.Context.return_value.use_certificate_chain_file.assert_called_once_with( + 'certfile' + ) + mock_openssl.SSL.Context.return_value.use_privatekey_file.assert_called_once_with( + 'keyfile' + ) + self.logger.info.assert_called_with( + "enforcing strong SSL/TLS connections") + self.logger.debug.assert_called_with( + "using SSL/TLS ciphers: %s" % STRONG_TLS_CIPHERS) + + @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + def test_hardened_openssl_context_options(self): + """Test OpenSSL context options are set correctly""" + with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: + mock_openssl.SSL = MagicMock() + mock_openssl.SSL.SSLv23_METHOD = 1 + mock_openssl.SSL.Context = MagicMock() + mock_openssl.SSL.Context.return_value = MagicMock() + mock_openssl.SSL.Context.return_value.set_options = MagicMock() + mock_openssl.crypto = MagicMock() + + config = self.configuration + config.logger = self.logger + + context = hardened_openssl_context( + config, + mock_openssl, + 'keyfile', + 'certfile', + 'cacertfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + # Verify options are set + expected_options = ( + getattr(mock_openssl.SSL, 'OP_NO_SSLv2', 0x1000000) | + getattr(mock_openssl.SSL, 'OP_NO_SSLv3', 0x2000000) | + getattr(mock_openssl.SSL, 'OP_NO_TLSv1', 0x4000000) | + getattr(mock_openssl.SSL, 'OP_NO_TLSv1_1', 0x10000000) | + getattr(mock_openssl.SSL, 'OP_NO_COMPRESSION', 0x20000) | + getattr(mock_openssl.SSL, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(mock_openssl.SSL, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(mock_openssl.SSL, 'OP_SINGLE_DH_USE', 0x100000) + ) + mock_openssl.SSL.Context.return_value.set_options.assert_called_once_with( + expected_options) + + @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + def test_hardened_openssl_context_ciphers(self): + """Test OpenSSL context ciphers are set correctly""" + with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: + mock_openssl.SSL = MagicMock() + mock_openssl.SSL.SSLv23_METHOD = 1 + mock_openssl.SSL.Context = MagicMock() + mock_openssl.SSL.Context.return_value = MagicMock() + mock_openssl.SSL.Context.return_value.set_options = MagicMock() + mock_openssl.crypto = MagicMock() + + config = self.configuration + config.logger = self.logger + + context = hardened_openssl_context( + config, + mock_openssl, + 'keyfile', + 'certfile', + 'cacertfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + mock_openssl.SSL.Context.return_value.set_cipher_list.assert_called_once_with( + STRONG_TLS_CIPHERS + ) + + @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + def test_hardened_openssl_context_cacertfile(self): + """Test OpenSSL context handles cacertfile parameter""" + with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: + mock_openssl.SSL = MagicMock() + mock_openssl.SSL.SSLv23_METHOD = 1 + mock_openssl.SSL.Context = MagicMock() + mock_openssl.SSL.Context.return_value = MagicMock() + mock_openssl.SSL.Context.return_value.set_options = MagicMock() + mock_openssl.crypto = MagicMock() + + config = self.configuration + config.logger = self.logger + + context = hardened_openssl_context( + config, + mock_openssl, + 'keyfile', + 'certfile', + 'cacertfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + mock_openssl.SSL.Context.return_value.load_verify_locations.assert_called_once_with( + 'cacertfile' + ) + + @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + def test_hardened_openssl_context_dhparams(self): + """Test OpenSSL context handles dhparamsfile parameter""" + with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: + mock_openssl.SSL = MagicMock() + mock_openssl.SSL.SSLv23_METHOD = 1 + mock_openssl.SSL.Context = MagicMock() + mock_openssl.SSL.Context.return_value = MagicMock() + mock_openssl.SSL.Context.return_value.set_options = MagicMock() + mock_openssl.crypto = MagicMock() + + config = self.configuration + config.logger = self.logger + + context = hardened_openssl_context( + config, + mock_openssl, + 'keyfile', + 'certfile', + 'cacertfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + mock_openssl.SSL.Context.return_value.load_tmp_dh.assert_called_once_with( + 'dhparamsfile' + ) From a77790a4b908e25a4e0edf55b0de04c0e05f35e9 Mon Sep 17 00:00:00 2001 From: Jonas Bardino Date: Mon, 16 Mar 2026 18:32:18 +0100 Subject: [PATCH 2/3] Sync log validation with built-in ssl version. --- tests/test_mig_shared_tlsserver.py | 51 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/tests/test_mig_shared_tlsserver.py b/tests/test_mig_shared_tlsserver.py index ba5b89dab..36cf0dee3 100644 --- a/tests/test_mig_shared_tlsserver.py +++ b/tests/test_mig_shared_tlsserver.py @@ -193,32 +193,33 @@ def test_hardened_openssl_context_basic(self): config = self.configuration config.logger = self.logger - context = hardened_openssl_context( - config, - mock_openssl, - 'keyfile', - 'certfile', - 'cacertfile', - 'dhparamsfile', - STRONG_TLS_CIPHERS, - STRONG_TLS_CURVES, - False, - True, - False - ) + with self.assertLogs(level="INFO") as log_capture: + context = hardened_openssl_context( + config, + mock_openssl, + 'keyfile', + 'certfile', + 'cacertfile', + 'dhparamsfile', + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) - self.assertIsNotNone(context) - mock_openssl.SSL.Context.assert_called_once_with(1) - mock_openssl.SSL.Context.return_value.use_certificate_chain_file.assert_called_once_with( - 'certfile' - ) - mock_openssl.SSL.Context.return_value.use_privatekey_file.assert_called_once_with( - 'keyfile' - ) - self.logger.info.assert_called_with( - "enforcing strong SSL/TLS connections") - self.logger.debug.assert_called_with( - "using SSL/TLS ciphers: %s" % STRONG_TLS_CIPHERS) + self.assertIsNotNone(context) + mock_openssl.SSL.Context.assert_called_once_with(1) + mock_openssl.SSL.Context.return_value.use_certificate_chain_file.assert_called_once_with( + 'certfile' + ) + mock_openssl.SSL.Context.return_value.use_privatekey_file.assert_called_once_with( + 'keyfile' + ) + self.assertTrue( + any("enforcing strong SSL/TLS connections" in msg + for msg in log_capture.output) + ) @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") def test_hardened_openssl_context_options(self): From 2b9ef680f31efb19e2ca57b1e1a29623b30e2291 Mon Sep 17 00:00:00 2001 From: Jonas Bardino Date: Mon, 16 Mar 2026 20:45:37 +0100 Subject: [PATCH 3/3] Rework `tlsserver` unit tests to use the proper TLS module with a bogus key and self-signed certificate for most tests. Implement variations to test the effect of toggling function hardening argument values. Move the original tests using Mock into their own class and disbale the tests that won't work in the current form. Main coverage is the built-in `ssl` module now that our PyOpenSSL dependency is likely to dwindle. --- tests/test_mig_shared_tlsserver.py | 473 +++++++++++++++++++++++++---- 1 file changed, 420 insertions(+), 53 deletions(-) diff --git a/tests/test_mig_shared_tlsserver.py b/tests/test_mig_shared_tlsserver.py index 36cf0dee3..278977e49 100644 --- a/tests/test_mig_shared_tlsserver.py +++ b/tests/test_mig_shared_tlsserver.py @@ -45,9 +45,377 @@ # Imports required for the unit tests themselves from tests.support import MigTestCase +TEST_KEY_FILE = "testkey.pem" +TEST_CERT_FILE = "testcert.pem" +TEST_CACERT_FILE = "testcacert.pem" +TEST_DHPARAMS_FILE = "testdhparams.pem" +# IMPORTANT: this is a bogus key and self-signed cert ONLY ever for testing +TEST_HOST_KEY = """-----BEGIN PRIVATE KEY----- +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 +Q+9V137S6RHzVn2s58Ta5ECTyD2oQw== +-----END PRIVATE KEY-----""" +TEST_HOST_CERT = """-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIUPoT2V+/Y/5RnBQtwYkA9VLSyfYYwDQYJKoZIhvcNAQEL +BQAwgYYxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcM +CENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFu +eVNlY3Rpb25OYW1lMR0wGwYDVQQDDBRDb21tb25OYW1lT3JIb3N0bmFtZTAeFw0y +NjAzMTYxODI2MzlaFw0zNjAzMTMxODI2MzlaMIGGMQswCQYDVQQGEwJYWDESMBAG +A1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIGA1UECgwLQ29t +cGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTEdMBsGA1UEAwwU +Q29tbW9uTmFtZU9ySG9zdG5hbWUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCWcP0X6VRv06tOKgD+gjvj0Cq8OQDJFHaQlOiXz90bjhc9ECtA/sj/3RUW +imy3kppRkO40Adrdz2AZUa6h/WDVcCvMDYpLSlERcZFRLnWWMFRj2xoRvOhJr/EF +TTuEgA4KFhQX48Kv6LzN2SEdLIFced90PL7oRBmyM2REwav4FvvT7Wv7dF5rW5w0 +mev8qAOkQj8ky/DjMi2kxKPbH4q+Jlj8SC6MB4hFvbhhHOMtyLhIUnl+b+OqfoNi +qU6omKN05VbPnp2zKSmxPSSGvZdoc2dvq+6dXqSPCoznO3sXhofTrbKaMs/qQ7Fr +7KVWdM9D7uQRt1Thhwf3H/UUOf+mmfPFuSX1KRogkHU5U1SxLaXQF/07sEqebiN/ +vQ9GuRjpfkwq4Kif5Q0l2wq59K+GzcK4mhz/ky1cGNuhxI05VqIbKDyRYcIzh+uC +Dup2Lgvw6eFvBiglccte1fFKZChbp1QS41REha0+6z0ZtujachzkBr+42Ew65ngt +e3OAN50bHtt682z2pSvy4KrEfWxEym6iN6ygKPDWHLPP0T0bFN7Lu9AOVnnZJFVa +fz4XoB2IBukpHZdFIQ8K9MlGWwF7iZZQjatiHJzsIOAr28TkjY3P3PpdnZtCeBIf +9ywjvXr8TOC0jHKxlOxouU3DN9jDqbfXFdcIYDfsmz3z9SO4WQIDAQABo1MwUTAd +BgNVHQ4EFgQU0gUTveqi67vp3Folx6KDoZtWum4wHwYDVR0jBBgwFoAU0gUTveqi +67vp3Folx6KDoZtWum4wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEAIlZ1ONQcruIU7JZXryc3/0SwsiXsxqiS+Eg7Qx5Am6JdPL2tLeQ9DHDRJ1F1 +/5JY7BU+fI+Hhe9XO3TArEmkSuJH2eEkMQcLQTP30nttvIO1haJn+6wf5nt4KNdy +qCywN/Om6n2kewNCEbDaSNm4S/dN+oCbdANobZmofawiDamb/MOG/leSNv8wN1GC +zCoFeH0rDf+ZAVN8xhqPe8+RPavT6BrWxBLjIR/52VwCrRiLLsCSt/WcdjYmOvSI ++s8qvaT4lrzS413+i7zDcNN+xPm7c1UP48myRTwT8a9hkn+0jwmL1CzYiW5kNuZx +HFuZ0gIelKc8IdFcOh5LWXS8b1Z4r+Vtx7WrcWmhJPoNTTtIm+IIF6ZyBtcrzVx5 +ctTvs1KcIFeS4ROnXC3zfSvOeXtiIeuVbiDXtcESmsXiCe9qS/j7KPbyVfel2Bwh +kVmbvoxMYZQYxQB5kROEem5zMGjPSDvCxUGljlwhzFvGkXocnC9Q5Mi61sMkAlyg +UPrFPCy8BqR4ZvZTvjtxMF81ahq2t+fmj8QeG1o0mSnH/Q3lP7vn7xSjZTu5+ldc +haiaED1TUyXIyHyn/L+NJmbdcbfPebvrv7KYdtxTC9j47kwWdkSuWMEW1pMTPM9W +EthFXgjAdlUayA5VqbcWIdm/P4d5Wc5lgj2XlbcwREcktT4= +-----END CERTIFICATE-----""" + + +class MigSharedTlsServerProper(MigTestCase): + """Unit tests for tlsserver related helper functions using proper TLS""" -class MigSharedTlsServer(MigTestCase): - """Unit tests for tlsserver related helper functions""" + def _provide_configuration(self): + """Prepare isolated test config""" + return 'testconfig' + + def _prepare_key_cert(self, key_path, cert_path): + """Save key and cert file for use in real ssl tests""" + with open(key_path, 'w') as key_fd: + key_fd.write(TEST_HOST_KEY) + with open(cert_path, 'w') as cert_fd: + cert_fd.write(TEST_HOST_CERT) + + def test_hardened_ssl_context_options_default(self): + """Test SSL context options are set correctly""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + + # Verify options are set + expected_options = ( + getattr(ssl, 'OP_NO_SSLv2', 0x1000000) | + getattr(ssl, 'OP_NO_SSLv3', 0x2000000) | + getattr(ssl, 'OP_NO_TLSv1', 0x4000000) | + getattr(ssl, 'OP_NO_TLSv1_1', 0x10000000) | + getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | + getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) | + getattr(ssl, 'OP_NO_RENEGOTIATION', 0x40000000) | + getattr(ssl, 'OP_RENEGOTIATION', 0x40000000) + ) + + # Verify the options were OR'd into the context + self.assertEqual(context.options & expected_options, expected_options) + + def test_hardened_ssl_context_options_tls1_1_only(self): + """Test SSL context options are set correctly with TLS 1.1 only""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + True, + False, + False + ) + + # Verify options are set + expected_options = ( + getattr(ssl, 'OP_NO_SSLv2', 0x1000000) | + getattr(ssl, 'OP_NO_SSLv3', 0x2000000) | + getattr(ssl, 'OP_NO_TLSv1_2', 0x8000000) | + getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | + getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) | + getattr(ssl, 'OP_NO_RENEGOTIATION', 0x40000000) | + getattr(ssl, 'OP_RENEGOTIATION', 0x40000000) + ) + + # Verify the options were OR'd into the context + self.assertEqual(context.options & expected_options, expected_options) + + def test_hardened_ssl_context_options_tls1_3_only(self): + """Test SSL context options are set correctly with TLS 1.3 only""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + False, + False + ) + + # Verify options are set + expected_options = ( + getattr(ssl, 'OP_NO_SSLv2', 0x1000000) | + getattr(ssl, 'OP_NO_SSLv3', 0x2000000) | + getattr(ssl, 'OP_NO_TLSv1', 0x4000000) | + getattr(ssl, 'OP_NO_TLSv1_1', 0x10000000) | + getattr(ssl, 'OP_NO_TLSv1_2', 0x8000000) | + getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | + getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) | + getattr(ssl, 'OP_NO_RENEGOTIATION', 0x40000000) | + getattr(ssl, 'OP_RENEGOTIATION', 0x40000000) + ) + + # Verify the options were OR'd into the context + self.assertEqual(context.options & expected_options, expected_options) + + def test_hardened_ssl_context_options_fail_reneg(self): + """Test SSL context options fail when different""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + True + ) + + # Verify options are set + expected_options = ( + getattr(ssl, 'OP_NO_SSLv2', 0x1000000) | + getattr(ssl, 'OP_NO_SSLv3', 0x2000000) | + getattr(ssl, 'OP_NO_TLSv1', 0x4000000) | + getattr(ssl, 'OP_NO_TLSv1_1', 0x10000000) | + getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | + getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) | + getattr(ssl, 'OP_NO_RENEGOTIATION', 0x40000000) | + getattr(ssl, 'OP_RENEGOTIATION', 0x40000000) + ) + + # Verify the options were OR'd into the context + self.assertNotEqual( + context.options & expected_options, expected_options) + + def test_hardened_ssl_context_options_fail_tls1_1(self): + """Test SSL context options fail when different""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + True, + True, + False + ) + + # Verify options are set + expected_options = ( + getattr(ssl, 'OP_NO_SSLv2', 0x1000000) | + getattr(ssl, 'OP_NO_SSLv3', 0x2000000) | + getattr(ssl, 'OP_NO_TLSv1', 0x4000000) | + getattr(ssl, 'OP_NO_TLSv1_1', 0x10000000) | + getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | + getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) | + getattr(ssl, 'OP_NO_RENEGOTIATION', 0x40000000) | + getattr(ssl, 'OP_RENEGOTIATION', 0x40000000) + ) + + # Verify the options were OR'd into the context + self.assertNotEqual( + context.options & expected_options, expected_options) + + def test_hardened_ssl_context_options_fail_tls1_2(self): + """Test SSL context options fail when different""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + True, + False, + False + ) + + # Verify options are set + expected_options = ( + getattr(ssl, 'OP_NO_SSLv2', 0x1000000) | + getattr(ssl, 'OP_NO_SSLv3', 0x2000000) | + getattr(ssl, 'OP_NO_TLSv1', 0x4000000) | + getattr(ssl, 'OP_NO_TLSv1_1', 0x10000000) | + getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | + getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | + getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) | + getattr(ssl, 'OP_NO_RENEGOTIATION', 0x40000000) | + getattr(ssl, 'OP_RENEGOTIATION', 0x40000000) + ) + + # Verify the options were OR'd into the context + self.assertNotEqual( + context.options & expected_options, expected_options) + + def test_hardened_ssl_context_ciphers(self): + """Test SSL context ciphers are set correctly""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + # NOTE: this may be too platform specific + expected_start = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:" + expected_end = ":DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" + result = ':'.join([spec['name'] for spec in context.get_ciphers()]) + self.assertTrue(result.startswith(expected_start)) + self.assertTrue(result.endswith(expected_end)) + + def test_hardened_ssl_context_legacy_ciphers(self): + """Test SSL context legacy ciphers are set correctly""" + config = self.configuration + config.logger = self.logger + + self._prepare_key_cert(TEST_KEY_FILE, TEST_CERT_FILE) + context = hardened_ssl_context( + config, + TEST_KEY_FILE, + TEST_CERT_FILE, + None, + STRONG_TLS_LEGACY_CIPHERS, + STRONG_TLS_CURVES, + False, + True, + False + ) + # NOTE: this may be too platform specific + expected_start = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:" + expected_end = ":CAMELLIA256-SHA256:CAMELLIA128-SHA256" + result = ':'.join([spec['name'] for spec in context.get_ciphers()]) + self.assertTrue(result.startswith(expected_start)) + self.assertTrue(result.endswith(expected_end)) + + +class MigSharedTlsServerMock(MigTestCase): + """Unit tests for tlsserver related helper functions using Mock TLS""" def _provide_configuration(self): """Prepare isolated test config""" @@ -59,7 +427,6 @@ def test_hardened_ssl_context_basic(self): mock_ssl.PROTOCOL_SSLv23 = 1 mock_ssl.SSLContext = MagicMock() mock_ssl.SSLContext.return_value = MagicMock() - mock_ssl.SSLContext.return_value.options = 0 config = self.configuration config.logger = self.logger @@ -67,9 +434,9 @@ def test_hardened_ssl_context_basic(self): with self.assertLogs(level="INFO") as log_capture: context = hardened_ssl_context( config, - 'keyfile', - 'certfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -80,18 +447,17 @@ def test_hardened_ssl_context_basic(self): self.assertIsNotNone(context) mock_ssl.SSLContext.assert_called_once_with(1) mock_ssl.SSLContext.return_value.load_cert_chain.assert_called_once_with( - 'certfile', 'keyfile' + TEST_CERT_FILE, TEST_KEY_FILE ) self.assertTrue( any("enforcing strong SSL/TLS connections" in msg for msg in log_capture.output) ) - @unittest.skip("Fix this test and enable it") - def test_hardened_ssl_context_options(self): - """Test SSL context options are set correctly""" + @unittest.skip("Fix this test and enable it?") + def test_hardened_ssl_context_mock_options(self): + """Test SSL context options are set correctly with mock""" with patch('mig.shared.tlsserver.ssl') as mock_ssl: - mock_ssl.PROTOCOL_SSLv23 = 1 mock_ssl.SSLContext = MagicMock() mock_ssl.SSLContext.return_value = MagicMock() mock_ssl.SSLContext.return_value.options = 0 @@ -101,9 +467,9 @@ def test_hardened_ssl_context_options(self): context = hardened_ssl_context( config, - 'keyfile', - 'certfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -120,12 +486,15 @@ def test_hardened_ssl_context_options(self): getattr(ssl, 'OP_NO_COMPRESSION', 0x20000) | getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0x400000) | getattr(ssl, 'OP_SINGLE_ECDH_USE', 0x80000) | - getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) + getattr(ssl, 'OP_SINGLE_DH_USE', 0x100000) | + getattr(ssl, 'OP_NO_RENEGOTIATION', 0x40000000) | + getattr(ssl, 'OP_NO_TLSv1_2', 0x8000000) ) + # Verify the options were OR'd into the context mock_ssl.SSLContext.return_value.options |= expected_options self.assertEqual( - mock_ssl.SSLContext.return_value.options, expected_options) + mock_ssl.SSLContext.return_value.options, context.options) def test_hardened_ssl_context_ciphers(self): """Test SSL context ciphers are set correctly""" @@ -133,16 +502,15 @@ def test_hardened_ssl_context_ciphers(self): mock_ssl.PROTOCOL_SSLv23 = 1 mock_ssl.SSLContext = MagicMock() mock_ssl.SSLContext.return_value = MagicMock() - mock_ssl.SSLContext.return_value.options = 0 config = self.configuration config.logger = self.logger context = hardened_ssl_context( config, - 'keyfile', - 'certfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -159,16 +527,15 @@ def test_hardened_ssl_context_legacy_ciphers(self): mock_ssl.PROTOCOL_SSLv23 = 1 mock_ssl.SSLContext = MagicMock() mock_ssl.SSLContext.return_value = MagicMock() - mock_ssl.SSLContext.return_value.options = 0 config = self.configuration config.logger = self.logger context = hardened_ssl_context( config, - 'keyfile', - 'certfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_LEGACY_CIPHERS, STRONG_TLS_CURVES, False, @@ -179,7 +546,7 @@ def test_hardened_ssl_context_legacy_ciphers(self): mock_ssl.SSLContext.return_value.set_ciphers.assert_called_once_with( STRONG_TLS_LEGACY_CIPHERS) - @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + @unittest.skip("Fix this test and enable it?") def test_hardened_openssl_context_basic(self): """Test basic OpenSSL context creation with default parameters""" with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: @@ -197,10 +564,10 @@ def test_hardened_openssl_context_basic(self): context = hardened_openssl_context( config, mock_openssl, - 'keyfile', - 'certfile', - 'cacertfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_CACERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -211,17 +578,17 @@ def test_hardened_openssl_context_basic(self): self.assertIsNotNone(context) mock_openssl.SSL.Context.assert_called_once_with(1) mock_openssl.SSL.Context.return_value.use_certificate_chain_file.assert_called_once_with( - 'certfile' + TEST_CERT_FILE ) mock_openssl.SSL.Context.return_value.use_privatekey_file.assert_called_once_with( - 'keyfile' + TEST_KEY_FILE ) self.assertTrue( any("enforcing strong SSL/TLS connections" in msg for msg in log_capture.output) ) - @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + @unittest.skip("Fix this test and enable it?") def test_hardened_openssl_context_options(self): """Test OpenSSL context options are set correctly""" with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: @@ -238,10 +605,10 @@ def test_hardened_openssl_context_options(self): context = hardened_openssl_context( config, mock_openssl, - 'keyfile', - 'certfile', - 'cacertfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_CACERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -263,7 +630,7 @@ def test_hardened_openssl_context_options(self): mock_openssl.SSL.Context.return_value.set_options.assert_called_once_with( expected_options) - @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + @unittest.skip("Fix this test and enable it?") def test_hardened_openssl_context_ciphers(self): """Test OpenSSL context ciphers are set correctly""" with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: @@ -280,10 +647,10 @@ def test_hardened_openssl_context_ciphers(self): context = hardened_openssl_context( config, mock_openssl, - 'keyfile', - 'certfile', - 'cacertfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_CACERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -295,7 +662,7 @@ def test_hardened_openssl_context_ciphers(self): STRONG_TLS_CIPHERS ) - @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + @unittest.skip("Fix this test and enable it?") def test_hardened_openssl_context_cacertfile(self): """Test OpenSSL context handles cacertfile parameter""" with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: @@ -312,10 +679,10 @@ def test_hardened_openssl_context_cacertfile(self): context = hardened_openssl_context( config, mock_openssl, - 'keyfile', - 'certfile', - 'cacertfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_CACERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -324,10 +691,10 @@ def test_hardened_openssl_context_cacertfile(self): ) mock_openssl.SSL.Context.return_value.load_verify_locations.assert_called_once_with( - 'cacertfile' + TEST_CACERT_FILE ) - @unittest.skipIf(OpenSSL is None, "requires PyOpenSSL") + @unittest.skip("Fix this test and enable it?") def test_hardened_openssl_context_dhparams(self): """Test OpenSSL context handles dhparamsfile parameter""" with patch('mig.shared.tlsserver.OpenSSL') as mock_openssl: @@ -344,10 +711,10 @@ def test_hardened_openssl_context_dhparams(self): context = hardened_openssl_context( config, mock_openssl, - 'keyfile', - 'certfile', - 'cacertfile', - 'dhparamsfile', + TEST_KEY_FILE, + TEST_CERT_FILE, + TEST_CACERT_FILE, + TEST_DHPARAMS_FILE, STRONG_TLS_CIPHERS, STRONG_TLS_CURVES, False, @@ -356,5 +723,5 @@ def test_hardened_openssl_context_dhparams(self): ) mock_openssl.SSL.Context.return_value.load_tmp_dh.assert_called_once_with( - 'dhparamsfile' + TEST_DHPARAMS_FILE )