From 4ea0db112ac0ab0a2288ae4eed1e1c2b928bcb2d Mon Sep 17 00:00:00 2001 From: Appelmans Date: Mon, 22 Jan 2024 14:22:10 -0800 Subject: [PATCH 1/8] Adds SSLv3 integ test --- tests/integrationv2/common.py | 6 ++++-- tests/integrationv2/providers.py | 30 +++++++++++++++++++++++++- tests/integrationv2/test_happy_path.py | 28 ++++++++++++++++++++---- tests/integrationv2/tox.ini | 2 +- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/tests/integrationv2/common.py b/tests/integrationv2/common.py index dae3a47790d..3db3e4c37dc 100644 --- a/tests/integrationv2/common.py +++ b/tests/integrationv2/common.py @@ -348,8 +348,10 @@ class Curves(object): """ X25519 = Curve("X25519", Protocols.TLS13) P256 = Curve("P-256") - P384 = Curve("P-384") - P521 = Curve("P-521") + # providers who negotiate SSLv3 may not not send the supported + # groups extension so P-384 and P-521 cannot be negotiated + P384 = Curve("P-384", Protocols.TLS10) + P521 = Curve("P-521", Protocols.TLS10) SecP256r1Kyber768Draft00 = Curve("SecP256r1Kyber768Draft00") X25519Kyber768Draft00 = Curve("X25519Kyber768Draft00") diff --git a/tests/integrationv2/providers.py b/tests/integrationv2/providers.py index 285d3b98133..79a229474a7 100644 --- a/tests/integrationv2/providers.py +++ b/tests/integrationv2/providers.py @@ -468,6 +468,9 @@ def get_version(cls): @classmethod def supports_protocol(cls, protocol, with_cert=None): + if protocol is Protocols.SSLv3: + return False + return True @classmethod @@ -507,6 +510,8 @@ def setup_client(self): cmd_line.append('-tls1_1') elif self.options.protocol == Protocols.TLS10: cmd_line.append('-tls1') + elif self.options.protocol == Protocols.SSLv3: + cmd_line.append('-ssl3') if self.options.cipher is not None: cmd_line.extend(self._cipher_to_cmdline(self.options.cipher)) @@ -582,6 +587,8 @@ def setup_server(self): cmd_line.append('-tls1_1') elif self.options.protocol == Protocols.TLS10: cmd_line.append('-tls1') + elif self.options.protocol == Protocols.SSLv3: + cmd_line.append('-ssl3') if self.options.cipher is not None: cmd_line.extend(self._cipher_to_cmdline(self.options.cipher)) @@ -606,6 +613,27 @@ def setup_server(self): return cmd_line +class SSLv3Provider(OpenSSL): + def __init__(self, options: ProviderOptions): + Provider.__init__(self, options) + # We print some OpenSSL logging that includes stderr + self.expect_stderr = True # lgtm [py/overwritten-inherited-attribute] + self._is_openssl_102() + + def _is_openssl_102(self): + result = subprocess.run(["openssl", "version"], shell=False, capture_output=True, text=True) + version_str = result.stdout.split(" ") + project = version_str[0] + version = version_str[1] + print(f"openssl version: {project} version: {version}") + if (project != "OpenSSL" or version[0:5] != "1.0.2"): + raise FileNotFoundError(f"Openssl version returned {version}, expected 1.0.2") + + @classmethod + def supports_protocol(cls, protocol, with_cert=None): + if protocol is Protocols.SSLv3: + return True + return False class JavaSSL(Provider): """ @@ -623,7 +651,7 @@ def get_send_marker(cls): @classmethod def supports_protocol(cls, protocol, with_cert=None): # https://aws.amazon.com/blogs/opensource/tls-1-0-1-1-changes-in-openjdk-and-amazon-corretto/ - if protocol is Protocols.TLS10 or protocol is Protocols.TLS11: + if protocol is Protocols.SSLv3 or protocol is Protocols.TLS10 or protocol is Protocols.TLS11: return False return True diff --git a/tests/integrationv2/test_happy_path.py b/tests/integrationv2/test_happy_path.py index 77d445e7301..b29d0722e73 100644 --- a/tests/integrationv2/test_happy_path.py +++ b/tests/integrationv2/test_happy_path.py @@ -4,13 +4,30 @@ from configuration import available_ports, ALL_TEST_CIPHERS, ALL_TEST_CURVES, ALL_TEST_CERTS, PROTOCOLS from common import ProviderOptions, data_bytes from fixtures import managed_process # lgtm [py/unused-import] -from providers import Provider, S2N, OpenSSL, JavaSSL, GnuTLS +from providers import Provider, S2N, OpenSSL, JavaSSL, GnuTLS, SSLv3Provider from utils import invalid_test_parameters, get_parameter_name, get_expected_s2n_version, to_bytes +# The default libssl used to test compatibility with s2n is +# openssl 1.1.1. However, that version has removed support for +# SSLv3. Therefore, in order to do an SSLv3 handshake, we override +# env variables to signal that the libssl used should be openssl 1.0.2 +# for this provider. +def get_sslv3_provider_override_env_vars(provider): + if provider is SSLv3Provider: + sslv3_provider_install_dir = os.environ["OPENSSL_1_0_2_INSTALL_DIR"] + + override_env_vars = dict() + override_env_vars["PATH"] = sslv3_provider_install_dir + "/bin" + override_env_vars["LD_LIBRARY_PATH"] = sslv3_provider_install_dir + "/lib" + + return override_env_vars + else: + return [] + @pytest.mark.uncollect_if(func=invalid_test_parameters) @pytest.mark.parametrize("cipher", ALL_TEST_CIPHERS, ids=get_parameter_name) -@pytest.mark.parametrize("provider", [S2N, OpenSSL, GnuTLS, JavaSSL]) +@pytest.mark.parametrize("provider", [S2N, OpenSSL, GnuTLS, JavaSSL, SSLv3Provider]) @pytest.mark.parametrize("other_provider", [S2N], ids=get_parameter_name) @pytest.mark.parametrize("curve", ALL_TEST_CURVES, ids=get_parameter_name) @pytest.mark.parametrize("protocol", PROTOCOLS, ids=get_parameter_name) @@ -32,7 +49,8 @@ def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider curve=curve, data_to_send=random_bytes, insecure=True, - protocol=protocol + protocol=protocol, + env_overrides=get_sslv3_provider_override_env_vars(provider) ) server_options = copy.copy(client_options) @@ -40,6 +58,7 @@ def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider server_options.mode = Provider.ServerMode server_options.key = certificate.key server_options.cert = certificate.cert + server_options.override_env_vars=None # Passing the type of client and server as a parameter will # allow us to use a fixture to enumerate all possibilities. @@ -69,7 +88,7 @@ def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider @pytest.mark.uncollect_if(func=invalid_test_parameters) @pytest.mark.parametrize("cipher", ALL_TEST_CIPHERS, ids=get_parameter_name) -@pytest.mark.parametrize("provider", [S2N, OpenSSL, GnuTLS]) +@pytest.mark.parametrize("provider", [S2N, OpenSSL, GnuTLS, SSLv3Provider]) @pytest.mark.parametrize("other_provider", [S2N], ids=get_parameter_name) @pytest.mark.parametrize("curve", ALL_TEST_CURVES, ids=get_parameter_name) @pytest.mark.parametrize("protocol", PROTOCOLS, ids=get_parameter_name) @@ -99,6 +118,7 @@ def test_s2n_client_happy_path(managed_process, cipher, provider, other_provider server_options.mode = Provider.ServerMode server_options.key = certificate.key server_options.cert = certificate.cert + server_options.env_overrides = get_sslv3_provider_override_env_vars(provider) kill_marker = None if provider == GnuTLS: diff --git a/tests/integrationv2/tox.ini b/tests/integrationv2/tox.ini index 7056471a76a..5d087c4e396 100644 --- a/tests/integrationv2/tox.ini +++ b/tests/integrationv2/tox.ini @@ -5,7 +5,7 @@ skipsdist = True [testenv] # install pytest in the virtualenv where commands will be executed setenv = S2N_INTEG_TEST = 1 -passenv = DYLD_LIBRARY_PATH, LD_LIBRARY_PATH, OQS_OPENSSL_1_1_1_INSTALL_DIR, HOME, TOX_TEST_NAME +passenv = DYLD_LIBRARY_PATH, LD_LIBRARY_PATH, OQS_OPENSSL_1_1_1_INSTALL_DIR, OPENSSL_1_0_2_INSTALL_DIR, HOME, TOX_TEST_NAME ignore_errors=False deps = pytest==7 From b67852e8cf0e487403a975208d48dc13f9f56d81 Mon Sep 17 00:00:00 2001 From: Appelmans Date: Mon, 22 Jan 2024 14:46:37 -0800 Subject: [PATCH 2/8] autopep8 --- tests/integrationv2/providers.py | 2 ++ tests/integrationv2/test_happy_path.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/integrationv2/providers.py b/tests/integrationv2/providers.py index 79a229474a7..7be29a92570 100644 --- a/tests/integrationv2/providers.py +++ b/tests/integrationv2/providers.py @@ -613,6 +613,7 @@ def setup_server(self): return cmd_line + class SSLv3Provider(OpenSSL): def __init__(self, options: ProviderOptions): Provider.__init__(self, options) @@ -635,6 +636,7 @@ def supports_protocol(cls, protocol, with_cert=None): return True return False + class JavaSSL(Provider): """ NOTE: Only a Java SSL client has been set up. The server has not been diff --git a/tests/integrationv2/test_happy_path.py b/tests/integrationv2/test_happy_path.py index b29d0722e73..aa521d37328 100644 --- a/tests/integrationv2/test_happy_path.py +++ b/tests/integrationv2/test_happy_path.py @@ -12,6 +12,8 @@ # SSLv3. Therefore, in order to do an SSLv3 handshake, we override # env variables to signal that the libssl used should be openssl 1.0.2 # for this provider. + + def get_sslv3_provider_override_env_vars(provider): if provider is SSLv3Provider: sslv3_provider_install_dir = os.environ["OPENSSL_1_0_2_INSTALL_DIR"] @@ -58,7 +60,7 @@ def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider server_options.mode = Provider.ServerMode server_options.key = certificate.key server_options.cert = certificate.cert - server_options.override_env_vars=None + server_options.override_env_vars = None # Passing the type of client and server as a parameter will # allow us to use a fixture to enumerate all possibilities. From a7ffcbdf36c34334a76d41590128a1f89f509514 Mon Sep 17 00:00:00 2001 From: Appelmans Date: Mon, 22 Jan 2024 17:16:37 -0800 Subject: [PATCH 3/8] Reorganizes changes --- tests/integrationv2/configuration.py | 1 + tests/integrationv2/providers.py | 21 ++++++++--------- tests/integrationv2/test_happy_path.py | 32 ++++---------------------- 3 files changed, 14 insertions(+), 40 deletions(-) diff --git a/tests/integrationv2/configuration.py b/tests/integrationv2/configuration.py index b6e00ba41b7..0ca84d668af 100644 --- a/tests/integrationv2/configuration.py +++ b/tests/integrationv2/configuration.py @@ -16,6 +16,7 @@ Protocols.TLS12, Protocols.TLS11, Protocols.TLS10, + Protocols.SSLv3, ] diff --git a/tests/integrationv2/providers.py b/tests/integrationv2/providers.py index 7be29a92570..5a6dd9e00c4 100644 --- a/tests/integrationv2/providers.py +++ b/tests/integrationv2/providers.py @@ -616,19 +616,16 @@ def setup_server(self): class SSLv3Provider(OpenSSL): def __init__(self, options: ProviderOptions): - Provider.__init__(self, options) - # We print some OpenSSL logging that includes stderr - self.expect_stderr = True # lgtm [py/overwritten-inherited-attribute] - self._is_openssl_102() + OpenSSL.__init__(self, options) + self._override_libssl(options) - def _is_openssl_102(self): - result = subprocess.run(["openssl", "version"], shell=False, capture_output=True, text=True) - version_str = result.stdout.split(" ") - project = version_str[0] - version = version_str[1] - print(f"openssl version: {project} version: {version}") - if (project != "OpenSSL" or version[0:5] != "1.0.2"): - raise FileNotFoundError(f"Openssl version returned {version}, expected 1.0.2") + def _override_libssl(self, options: ProviderOptions): + install_dir = os.environ["OPENSSL_1_0_2_INSTALL_DIR"] + + override_env_vars = dict() + override_env_vars["PATH"] = install_dir + "/bin" + override_env_vars["LD_LIBRARY_PATH"] = install_dir + "/lib" + options.env_overrides = override_env_vars @classmethod def supports_protocol(cls, protocol, with_cert=None): diff --git a/tests/integrationv2/test_happy_path.py b/tests/integrationv2/test_happy_path.py index aa521d37328..d0a686b1fe0 100644 --- a/tests/integrationv2/test_happy_path.py +++ b/tests/integrationv2/test_happy_path.py @@ -7,34 +7,14 @@ from providers import Provider, S2N, OpenSSL, JavaSSL, GnuTLS, SSLv3Provider from utils import invalid_test_parameters, get_parameter_name, get_expected_s2n_version, to_bytes -# The default libssl used to test compatibility with s2n is -# openssl 1.1.1. However, that version has removed support for -# SSLv3. Therefore, in order to do an SSLv3 handshake, we override -# env variables to signal that the libssl used should be openssl 1.0.2 -# for this provider. - - -def get_sslv3_provider_override_env_vars(provider): - if provider is SSLv3Provider: - sslv3_provider_install_dir = os.environ["OPENSSL_1_0_2_INSTALL_DIR"] - - override_env_vars = dict() - override_env_vars["PATH"] = sslv3_provider_install_dir + "/bin" - override_env_vars["LD_LIBRARY_PATH"] = sslv3_provider_install_dir + "/lib" - - return override_env_vars - else: - return [] - @pytest.mark.uncollect_if(func=invalid_test_parameters) @pytest.mark.parametrize("cipher", ALL_TEST_CIPHERS, ids=get_parameter_name) -@pytest.mark.parametrize("provider", [S2N, OpenSSL, GnuTLS, JavaSSL, SSLv3Provider]) -@pytest.mark.parametrize("other_provider", [S2N], ids=get_parameter_name) +@pytest.mark.parametrize("provider", [OpenSSL, JavaSSL, GnuTLS, SSLv3Provider]) @pytest.mark.parametrize("curve", ALL_TEST_CURVES, ids=get_parameter_name) @pytest.mark.parametrize("protocol", PROTOCOLS, ids=get_parameter_name) @pytest.mark.parametrize("certificate", ALL_TEST_CERTS, ids=get_parameter_name) -def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider, curve, protocol, certificate): +def test_s2n_server_happy_path(managed_process, cipher, provider, curve, protocol, certificate): port = next(available_ports) # s2nd can receive large amounts of data because all the data is @@ -51,8 +31,7 @@ def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider curve=curve, data_to_send=random_bytes, insecure=True, - protocol=protocol, - env_overrides=get_sslv3_provider_override_env_vars(provider) + protocol=protocol ) server_options = copy.copy(client_options) @@ -60,7 +39,6 @@ def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider server_options.mode = Provider.ServerMode server_options.key = certificate.key server_options.cert = certificate.cert - server_options.override_env_vars = None # Passing the type of client and server as a parameter will # allow us to use a fixture to enumerate all possibilities. @@ -91,11 +69,10 @@ def test_s2n_server_happy_path(managed_process, cipher, provider, other_provider @pytest.mark.uncollect_if(func=invalid_test_parameters) @pytest.mark.parametrize("cipher", ALL_TEST_CIPHERS, ids=get_parameter_name) @pytest.mark.parametrize("provider", [S2N, OpenSSL, GnuTLS, SSLv3Provider]) -@pytest.mark.parametrize("other_provider", [S2N], ids=get_parameter_name) @pytest.mark.parametrize("curve", ALL_TEST_CURVES, ids=get_parameter_name) @pytest.mark.parametrize("protocol", PROTOCOLS, ids=get_parameter_name) @pytest.mark.parametrize("certificate", ALL_TEST_CERTS, ids=get_parameter_name) -def test_s2n_client_happy_path(managed_process, cipher, provider, other_provider, curve, protocol, certificate): +def test_s2n_client_happy_path(managed_process, cipher, provider, curve, protocol, certificate): port = next(available_ports) # We can only send 4096 - 1 (\n at the end) bytes here because of the @@ -120,7 +97,6 @@ def test_s2n_client_happy_path(managed_process, cipher, provider, other_provider server_options.mode = Provider.ServerMode server_options.key = certificate.key server_options.cert = certificate.cert - server_options.env_overrides = get_sslv3_provider_override_env_vars(provider) kill_marker = None if provider == GnuTLS: From 99dc800f8b7ae674397f07bce8cc74a00e5003c0 Mon Sep 17 00:00:00 2001 From: Appelmans Date: Mon, 22 Jan 2024 17:41:15 -0800 Subject: [PATCH 4/8] Added S2N back --- tests/integrationv2/test_happy_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrationv2/test_happy_path.py b/tests/integrationv2/test_happy_path.py index d0a686b1fe0..30d6fbc94cb 100644 --- a/tests/integrationv2/test_happy_path.py +++ b/tests/integrationv2/test_happy_path.py @@ -10,7 +10,7 @@ @pytest.mark.uncollect_if(func=invalid_test_parameters) @pytest.mark.parametrize("cipher", ALL_TEST_CIPHERS, ids=get_parameter_name) -@pytest.mark.parametrize("provider", [OpenSSL, JavaSSL, GnuTLS, SSLv3Provider]) +@pytest.mark.parametrize("provider", [S2N, OpenSSL, GnuTLS, JavaSSL, SSLv3Provider]) @pytest.mark.parametrize("curve", ALL_TEST_CURVES, ids=get_parameter_name) @pytest.mark.parametrize("protocol", PROTOCOLS, ids=get_parameter_name) @pytest.mark.parametrize("certificate", ALL_TEST_CERTS, ids=get_parameter_name) From 4b5275dee7f452e2e5f8b0136d7b7d7f95d56dd5 Mon Sep 17 00:00:00 2001 From: Appelmans Date: Tue, 23 Jan 2024 10:51:27 -0800 Subject: [PATCH 5/8] Added fips exception --- tests/integrationv2/providers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/integrationv2/providers.py b/tests/integrationv2/providers.py index 5a6dd9e00c4..c8a9748cd10 100644 --- a/tests/integrationv2/providers.py +++ b/tests/integrationv2/providers.py @@ -163,6 +163,11 @@ def supports_protocol(cls, protocol, with_cert=None): # e.g. "openssl-1.0" in "openssl-1.0.2-fips" if unsupported_lc in current_libcrypto: return False + + # s2n-tls will not negotiate SSLv3 if in fips mode + if protocol == Protocols.SSLv3 and get_flag(S2N_FIPS_MODE): + return False + return True @classmethod From 1e9e0d07ad88d342eea9eafc93d556c689f78ef7 Mon Sep 17 00:00:00 2001 From: Appelmans Date: Tue, 23 Jan 2024 10:54:18 -0800 Subject: [PATCH 6/8] autopep8 --- tests/integrationv2/providers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrationv2/providers.py b/tests/integrationv2/providers.py index c8a9748cd10..c68e604a898 100644 --- a/tests/integrationv2/providers.py +++ b/tests/integrationv2/providers.py @@ -163,7 +163,7 @@ def supports_protocol(cls, protocol, with_cert=None): # e.g. "openssl-1.0" in "openssl-1.0.2-fips" if unsupported_lc in current_libcrypto: return False - + # s2n-tls will not negotiate SSLv3 if in fips mode if protocol == Protocols.SSLv3 and get_flag(S2N_FIPS_MODE): return False From ad709e68906f0dca4ed9d0056edbba1c5915a52b Mon Sep 17 00:00:00 2001 From: Appelmans Date: Thu, 25 Jan 2024 13:03:05 -0800 Subject: [PATCH 7/8] Improves comment --- tests/integrationv2/common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integrationv2/common.py b/tests/integrationv2/common.py index 3db3e4c37dc..b49784d8a35 100644 --- a/tests/integrationv2/common.py +++ b/tests/integrationv2/common.py @@ -348,8 +348,9 @@ class Curves(object): """ X25519 = Curve("X25519", Protocols.TLS13) P256 = Curve("P-256") - # providers who negotiate SSLv3 may not not send the supported - # groups extension so P-384 and P-521 cannot be negotiated + # Our only SSLv3 provider doesn't support extensions + # so there is no way to negotiate a curve other than the + # default P-256 in SSLv3. P384 = Curve("P-384", Protocols.TLS10) P521 = Curve("P-521", Protocols.TLS10) SecP256r1Kyber768Draft00 = Curve("SecP256r1Kyber768Draft00") From 2a6d708fac8795b89b193b393d6aa05426f1388e Mon Sep 17 00:00:00 2001 From: Appelmans Date: Thu, 25 Jan 2024 13:06:40 -0800 Subject: [PATCH 8/8] autopep8 --- tests/integrationv2/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrationv2/common.py b/tests/integrationv2/common.py index b49784d8a35..01ddafd7087 100644 --- a/tests/integrationv2/common.py +++ b/tests/integrationv2/common.py @@ -349,7 +349,7 @@ class Curves(object): X25519 = Curve("X25519", Protocols.TLS13) P256 = Curve("P-256") # Our only SSLv3 provider doesn't support extensions - # so there is no way to negotiate a curve other than the + # so there is no way to negotiate a curve other than the # default P-256 in SSLv3. P384 = Curve("P-384", Protocols.TLS10) P521 = Curve("P-521", Protocols.TLS10)