diff --git a/tests/integrationv2/conftest.py b/tests/integrationv2/conftest.py index b3b075e3a5a..2e5af2a3026 100644 --- a/tests/integrationv2/conftest.py +++ b/tests/integrationv2/conftest.py @@ -2,8 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 import os import pytest +import subprocess from global_flags import set_flag, S2N_PROVIDER_VERSION, S2N_FIPS_MODE -from providers import S2N, JavaSSL +from providers import S2N, JavaSSL, OpenSSL PATH_CONFIGURATION_KEY = pytest.StashKey() @@ -29,6 +30,13 @@ def available_providers(): if os.path.exists("./bin/SSLSocketClient.class"): providers.add(JavaSSL) + + openssl_available = subprocess.run( + ["openssl", "version"] + ) + + if openssl_available.returncode == 0: + providers.add(OpenSSL) return providers diff --git a/tests/integrationv2/processes.py b/tests/integrationv2/processes.py index 110ef4272af..ea32166c291 100644 --- a/tests/integrationv2/processes.py +++ b/tests/integrationv2/processes.py @@ -1,5 +1,6 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 +import time import os import select import selectors @@ -96,7 +97,7 @@ def _communicate(self, input_data=None, send_marker_list=None, close_marker=None This method will read and write data to a subprocess in a non-blocking manner. The code is heavily based on Popen.communicate. There are a couple differences: - * STDIN is not registered for events until the read_to_send marker is found + * STDIN is not registered for events until the ready_to_send marker is found * STDIN is only closed after all registered events have been processed (including pending stdout/stderr events, allowing more data to be stored). """ @@ -144,6 +145,7 @@ def _communicate(self, input_data=None, send_marker_list=None, close_marker=None if self.proc.stderr and not self.proc.stderr.closed: selector.register(self.proc.stderr, selectors.EVENT_READ) + # Keep iterating until we unregister stdin, stdout and stderr while selector.get_map(): timeout = self._remaining_time(endtime) if timeout is not None and timeout < 0: @@ -157,20 +159,33 @@ def _communicate(self, input_data=None, send_marker_list=None, close_marker=None ready = selector.select(timeout) self._check_timeout(endtime, orig_timeout, stdout, stderr) + # (Key, events) tuple represents a single I/O operation for key, events in ready: # STDIN is only registered to receive events after the send_marker is found. if key.fileobj is self.proc.stdin: print(f'{self.name}: stdin available') chunk = input_view[input_data_offset: input_data_offset + _PIPE_BUF] + + # will run try block and then else (if no exception was found) try: input_data_offset += os.write(key.fd, chunk) print(f'{self.name}: sent') except BrokenPipeError: - selector.unregister(key.fileobj) + print( + f"########################################################################################\n" + f"############# {self.name}: Unregistering (stdin) BrokenPipeError #############\n" + f"########################################################################################" + ) + selector.unregister(self.proc.stdin) else: if input_data_offset >= input_data_len: - selector.unregister(key.fileobj) + print( + f"#############################################################################################\n" + f"############# {self.name}: Unregistering (stdin) Input_data_offset >= input_data_len #############\n" + f"#############################################################################################" + ) + selector.unregister(self.proc.stdin) input_data_sent = True input_data_offset = 0 if send_marker_list: @@ -178,9 +193,16 @@ def _communicate(self, input_data=None, send_marker_list=None, close_marker=None print(f'{self.name}: next send_marker is {send_marker}') elif key.fileobj in (self.proc.stdout, self.proc.stderr): print(f'{self.name}: stdout available') + # 32 KB (32 × 1024 = 32,768 bytes), read 32KB from the file descriptor data = os.read(key.fd, 32768) if not data: + print( + f"##################################################################################\n" + f"################ {self.name}: Unregistering (stdout or stderr) No Data ################\n" + f"##################################################################################" + ) selector.unregister(key.fileobj) + data_str = str(data) # Prepends n - 1 bytes of previously-seen stdout to the chunk we'll be searching @@ -226,6 +248,11 @@ def _communicate(self, input_data=None, send_marker_list=None, close_marker=None if self.wait_for_marker: print(f'{self.name}: looking for wait_for_marker {self.wait_for_marker} in {data_debug}') if self.wait_for_marker is not None and self.wait_for_marker in data_str: + print( + f"################################################################################################\n" + f"############## {self.name}: Unregistering (stdout + stderr), found wait_for_marker ##############\n" + f"################################################################################################\n" + ) selector.unregister(self.proc.stdout) selector.unregister(self.proc.stderr) return None, None @@ -233,10 +260,21 @@ def _communicate(self, input_data=None, send_marker_list=None, close_marker=None if kill_marker: print(f'{self.name}: looking for kill_marker {kill_marker} in {data}') if kill_marker is not None and kill_marker in data: + print( + f"###########################################################################################\n" + f"############# {self.name}: Unregistering (stdout + stderr), found kill_marker #############\n" + f"###########################################################################################\n" + ) selector.unregister(self.proc.stdout) selector.unregister(self.proc.stderr) self.proc.kill() + + # if close_marker and close_marker in data_str: + # print(f'{self.name} Found close marker: closing stdin') + # input_data_sent = None + # selector.unregister(self.proc.stdin) + # If we have finished sending all our input, and have received the # ready-to-send marker, we can close out stdin. if self.proc.stdin and input_data_sent and not input_data: @@ -244,7 +282,7 @@ def _communicate(self, input_data=None, send_marker_list=None, close_marker=None if close_marker: print(f'{self.name}: looking for close_marker {close_marker} in {data_debug}') if close_marker is None or (close_marker and close_marker in data_str): - print(f'{self.name}: closing stdin') + print(f'{self.name} Found close marker: closing stdin') input_data_sent = None self.proc.stdin.close() @@ -389,12 +427,22 @@ def run(self): finally: # This data is dumped to stdout so we capture this # information no matter where a test fails. - print("Command line: {}".format(" ".join(self.cmd_line))) - print("Exit code: {}".format(proc.returncode)) - print("Stdout: {}".format( - proc_results[0].decode("utf-8", "backslashreplace"))) - print("Stderr: {}".format( - proc_results[1].decode("utf-8", "backslashreplace"))) + print("###############################################################") + print(f"####################### {self.cmd_line[0]} #######################") + print("###############################################################") + + print(f"Command line:\n\t{' '.join(self.cmd_line)}") + print(f"Exit code:\n\t {proc.returncode}") + + print("##########################################################") + print("########################### Stdout #######################") + print("##########################################################") + print(proc_results[0].decode("utf-8", "backslashreplace")) + + print("#########################################################") + print("########################### Stderr #######################") + print("#########################################################") + print(proc_results[1].decode("utf-8", "backslashreplace")) def kill(self): self.proc.kill() diff --git a/tests/integrationv2/providers.py b/tests/integrationv2/providers.py index d93a441f3b5..5abc0f48326 100644 --- a/tests/integrationv2/providers.py +++ b/tests/integrationv2/providers.py @@ -46,9 +46,11 @@ def __init__(self, options: ProviderOptions): self.options = options if self.options.mode == Provider.ServerMode: - self.cmd_line = self.setup_server() # lgtm [py/init-calls-subclass] + # lgtm [py/init-calls-subclass] + self.cmd_line = self.setup_server() elif self.options.mode == Provider.ClientMode: - self.cmd_line = self.setup_client() # lgtm [py/init-calls-subclass] + # lgtm [py/init-calls-subclass] + self.cmd_line = self.setup_client() def setup_client(self): """ @@ -140,7 +142,8 @@ class S2N(Provider): def __init__(self, options: ProviderOptions): Provider.__init__(self, options) - self.send_with_newline = True # lgtm [py/overwritten-inherited-attribute] + # lgtm [py/overwritten-inherited-attribute] + self.send_with_newline = True @classmethod def get_send_marker(cls): @@ -334,12 +337,18 @@ def setup_server(self): class OpenSSL(Provider): + result = subprocess.run( + ["openssl", "version"], shell=False, capture_output=True, text=True + ) + version_str = result.stdout.split(" ") + # This will return just the version number + version_openssl = version_str[1] + 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] - # Current provider needs 1.1.x https://github.com/aws/s2n-tls/issues/3963 - self._is_openssl_11() + self.at_least_openssl_1_1() @classmethod def get_send_marker(cls): @@ -388,11 +397,17 @@ def _cipher_to_cmdline(self, cipher): @classmethod def get_version(cls): - return get_flag(S2N_PROVIDER_VERSION) + return cls.version_openssl @classmethod def supports_protocol(cls, protocol, with_cert=None): - if protocol is Protocols.SSLv3: + if cls.get_version()[0:3] == "1.1" and protocol is Protocols.SSLv3: + return False + if cls.get_version()[0:3] == "3.0" and ( + protocol is Protocols.SSLv3 + or protocol is Protocols.TLS10 + or protocol is Protocols.TLS11 + ): return False return True @@ -401,14 +416,18 @@ def supports_protocol(cls, protocol, with_cert=None): def supports_cipher(cls, cipher, with_curve=None): return True - def _is_openssl_11(self) -> None: - result = subprocess.run(["openssl", "version"], shell=False, capture_output=True, text=True) + def at_least_openssl_1_1(self) -> None: + 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:3] != "1.1"): - raise FileNotFoundError(f"Openssl version returned {version}, expected 1.1.x.") + if project != "OpenSSL" or version[0:3] < "1.1": + raise FileNotFoundError( + f"Openssl version returned {version}, expected at least 1.1.x." + ) def setup_client(self): cmd_line = ['openssl', 's_client'] @@ -701,7 +720,8 @@ def __init__(self, options: ProviderOptions): Provider.__init__(self, options) self.expect_stderr = True # lgtm [py/overwritten-inherited-attribute] - self.send_with_newline = True # lgtm [py/overwritten-inherited-attribute] + # lgtm [py/overwritten-inherited-attribute] + self.send_with_newline = True @staticmethod def cipher_to_priority_str(cipher): @@ -773,13 +793,15 @@ def get_send_marker(cls): def create_priority_str(self): priority_str = "NONE" - protocol_to_priority_str = self.protocol_to_priority_str(self.options.protocol) + protocol_to_priority_str = self.protocol_to_priority_str( + self.options.protocol) if protocol_to_priority_str: priority_str += ":+" + protocol_to_priority_str else: priority_str += ":+VERS-ALL" - cipher_to_priority_str = self.cipher_to_priority_str(self.options.cipher) + cipher_to_priority_str = self.cipher_to_priority_str( + self.options.cipher) if cipher_to_priority_str: priority_str += ":+" + cipher_to_priority_str else: @@ -791,7 +813,8 @@ def create_priority_str(self): else: priority_str += ":+GROUP-ALL" - sigalg_to_priority_str = self.sigalg_to_priority_str(self.options.signature_algorithm) + sigalg_to_priority_str = self.sigalg_to_priority_str( + self.options.signature_algorithm) if sigalg_to_priority_str: priority_str += ":+" + sigalg_to_priority_str else: diff --git a/tests/integrationv2/test_client_authentication.py b/tests/integrationv2/test_client_authentication.py index 87563c997da..a043c003bf5 100644 --- a/tests/integrationv2/test_client_authentication.py +++ b/tests/integrationv2/test_client_authentication.py @@ -196,7 +196,6 @@ def test_client_auth_with_s2n_client_no_cert(managed_process, provider, other_pr def test_client_auth_with_s2n_client_with_cert(managed_process, provider, other_provider, protocol, cipher, certificate, client_certificate): port = next(available_ports) - random_bytes = data_bytes(64) client_options = ProviderOptions( mode=Provider.ClientMode, diff --git a/tests/integrationv2/utils.py b/tests/integrationv2/utils.py index 47842fd3bdb..be3f3e520fe 100644 --- a/tests/integrationv2/utils.py +++ b/tests/integrationv2/utils.py @@ -1,7 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -from common import Protocols -from providers import S2N +from common import Certificates, Protocols +from providers import OpenSSL, S2N from global_flags import get_flag, S2N_FIPS_MODE @@ -37,6 +37,8 @@ def get_expected_openssl_version(protocol): Protocols.TLS13.value: "TLSv1.3" }.get(protocol.value) +def get_openssl_version(): + return OpenSSL.get_version() def get_expected_gnutls_version(protocol): return { @@ -83,6 +85,23 @@ def invalid_test_parameters(*args, **kwargs): if not provider_.supports_protocol(protocol): return True + # If openSSL is 3.0, we should skip all testcases with certificates/client certificates of 1024 + if certificate is not None: + if get_openssl_version()[0:3] == "3.0" and ( + certificate is Certificates.RSA_1024_SHA256 + or certificate is Certificates.RSA_1024_SHA384 + or certificate is Certificates.RSA_1024_SHA384 + ): + return True + + if client_certificate is not None: + if get_openssl_version()[0:3] == "3.0" and ( + client_certificate is Certificates.RSA_1024_SHA256 + or client_certificate is Certificates.RSA_1024_SHA384 + or client_certificate is Certificates.RSA_1024_SHA384 + ): + return True + if cipher is not None: # If the selected protocol doesn't allow the cipher, don't test if protocol is not None: