def test_error_server_connectivity_issue_handshake_timeout(self, mock_scan_commands): # Given a server to scan with some commands server_scan = ServerScanRequest( server_info=ServerConnectivityInfoFactory.create(), scan_commands={ScanCommandForTests.MOCK_COMMAND_1, ScanCommandForTests.MOCK_COMMAND_2}, ) # And the first scan command will trigger a handshake timeout with the server with mock.patch.object( MockPlugin1Implementation, "_scan_job_work_function", side_effect=TlsHandshakeTimedOut( server_location=server_scan.server_info.server_location, network_configuration=server_scan.server_info.network_configuration, error_message="error", ), ): # When queuing the scan scanner = Scanner() scanner.queue_scan(server_scan) # It succeeds for result in scanner.get_results(): # And the error was properly caught and returned assert len(result.scan_commands_errors) == 1 error = result.scan_commands_errors[ScanCommandForTests.MOCK_COMMAND_1] assert ScanCommandErrorReasonEnum.CONNECTIVITY_ISSUE == error.reason assert error.exception_trace
def _scan_job_work_function(arg1: str) -> str: raise TlsHandshakeTimedOut( server_location=server_scan_request.server_location, network_configuration=server_scan_request. network_configuration, error_message="error", )
def connect(self, should_retry_connection: bool = True) -> None: max_attempts_nb = self._network_configuration.network_max_retries if should_retry_connection else 1 connection_attempts_nb = 0 delay_for_next_attempt = 0 # First try to connect to the server, and do retries if there are timeouts while True: # Sleep if it's a retry attempt time.sleep(delay_for_next_attempt) try: self._do_pre_handshake() except socket.timeout: # Attempt to retry connection if a network error occurred during connection or the handshake connection_attempts_nb += 1 if connection_attempts_nb >= max_attempts_nb: # Exhausted the number of retry attempts, give up raise ConnectionToServerTimedOut( server_location=self._server_location, network_configuration=self._network_configuration, error_message="Connection to the server timed out", ) elif connection_attempts_nb == 1: # Start with a 1 second delay delay_for_next_attempt = 1 else: # Exponential back off; cap maximum delay at 6 seconds delay_for_next_attempt = min(6, 2 * delay_for_next_attempt) except ConnectionError: raise ServerRejectedConnection( server_location=self._server_location, network_configuration=self._network_configuration, error_message="Server rejected the connection", ) except OSError: # OSError is the parent class of all socket (ie. non-TLS) connection errors such as socket.timeout or # ConnectionError; hence this is the most generic error handler and should always be defined last raise ConnectionToServerFailed( server_location=self._server_location, network_configuration=self._network_configuration, error_message="Connection to the server failed", ) else: # No network error occurred break # After successfully connecting to the server, perform the TLS handshake try: self.ssl_client.do_handshake() except ClientCertificateRequested: # Server expected a client certificate and we didn't provide one raise except socket.timeout: # Network timeout, propagate the error raise TlsHandshakeTimedOut( server_location=self._server_location, network_configuration=self._network_configuration, error_message= "Connection to server timed out during the TLS handshake", ) except ConnectionError: raise ServerRejectedTlsHandshake( server_location=self._server_location, network_configuration=self._network_configuration, error_message="Server rejected the connection", ) except OSError as e: # OSError is the parent of all (non-TLS) socket/connection errors so it should be last if "Nassl SSL handshake failed" in e.args[0]: # Special error returned by nassl raise ServerRejectedTlsHandshake( server_location=self._server_location, network_configuration=self._network_configuration, error_message="Server rejected the connection", ) # Unknown connection error raise except _nassl.OpenSSLError as e: openssl_error_message = e.args[0] if "dh key too small" in openssl_error_message: # This is when SSLyze's OpenSSL rejects DH parameters (to protect against Logjam); this actually # means the server supports whatever cipher suite was used raise ServerTlsConfigurationNotSupported( server_location=self._server_location, network_configuration=self._network_configuration, error_message="DH key too small", ) if "no ciphers available" in openssl_error_message: # This one is returned by OpenSSL when a cipher set via set_cipher_list() is not actually supported # Should never happen (SSLyze bugs) raise NoCiphersAvailableBugInSSlyze( f"Set a cipher that is not supported by nassl: {self.ssl_client.get_cipher_list()}" ) for error_msg in _HANDSHAKE_REJECTED_TLS_ERRORS.keys(): if error_msg in openssl_error_message: raise ServerRejectedTlsHandshake( server_location=self._server_location, network_configuration=self._network_configuration, error_message=_HANDSHAKE_REJECTED_TLS_ERRORS[ error_msg], ) # Unknown SSL error if we get there raise