示例#1
0
    def test_connectivity_error(self, mock_scan_commands):
        # Given a server to scan
        scan_request = ServerScanRequestFactory.create()

        # And the server will NOT be reachable
        error = ConnectionToServerFailed(
            server_location=scan_request.server_location,
            network_configuration=scan_request.network_configuration,
            error_message="testt",
        )
        with mock.patch.object(_mass_connectivity_tester, "check_connectivity_to_server", side_effect=error):
            # And given an observer to monitor scans
            observer = _MockScannerObserver()

            # When running the scans with the observer
            scanner = Scanner(observers=[observer])
            scanner.queue_scans([scan_request])

            # It succeeds
            all_scan_results = []
            for result in scanner.get_results():
                all_scan_results.append(result)

        # And the right result was returned
        assert len(all_scan_results) == 1
        assert all_scan_results[0].scan_status == ServerScanStatusEnum.ERROR_NO_CONNECTIVITY

        # And the observer was called appropriately
        assert observer.server_connectivity_test_error_calls_count == 1
        assert observer.server_connectivity_test_completed_calls_count == 0
        assert observer.server_scan_completed_calls_count == 1
        assert observer.all_server_scans_completed_calls_count == 1
示例#2
0
 def create():
     return ConnectionToServerFailed(
         server_location=ServerNetworkLocationViaDirectConnectionFactory.
         create(),
         network_configuration=ServerNetworkConfiguration(
             tls_server_name_indication="a.com"),
         error_message="This is ân éè error",
     )
示例#3
0
def _detect_support_for_tls_1_2_or_below(
    server_location: ServerNetworkLocation,
    network_config: ServerNetworkConfiguration,
    tls_version: TlsVersionEnum,
) -> _TlsVersionDetectionResult:
    # First try the default cipher list, and then all ciphers; this is to work around F5 network devices
    # that time out when the client hello is too long (ie. too many cipher suites enabled)
    # https://support.f5.com/csp/article/K14758
    for cipher_list in ["DEFAULT", "ALL:COMPLEMENTOFALL:-PSK:-SRP"]:
        ssl_connection = SslConnection(
            server_location=server_location,
            network_configuration=network_config,
            tls_version=tls_version,
            should_ignore_client_auth=False,
        )
        ssl_connection.ssl_client.set_cipher_list(cipher_list)

        try:
            # Only do one attempt when testing connectivity
            ssl_connection.connect(should_retry_connection=False)
            return _TlsVersionDetectionResult(
                tls_version_supported=tls_version,
                server_requested_client_cert=False,
                cipher_suite_supported=ssl_connection.ssl_client.
                get_current_cipher_name(),
            )

        except ClientCertificateRequested:
            # Connection successful but the servers wants a client certificate which wasn't supplied to sslyze
            return _TlsVersionDetectionResult(
                tls_version_supported=tls_version,
                server_requested_client_cert=True,
                # Calling ssl_connection.ssl_client.get_current_cipher_name() will fail in this situation so we just
                # store the whole cipher_list
                cipher_suite_supported=cipher_list,
            )

        except TlsHandshakeFailed:
            # Try the next cipher list
            pass

        except (OSError, _nassl.OpenSSLError) as e:
            # If these errors get propagated here, it means they're not part of the known/normal errors that
            # can happen when trying to connect to a server and defined in tls_connection.py
            # Hence we re-raise these as "unknown" connection errors; might be caused by bad connectivity to
            # the server (random disconnects, etc.) and the scan against this server should not be performed
            raise ConnectionToServerFailed(
                server_location=server_location,
                network_configuration=network_config,
                error_message=f'Unexpected connection error: "{e.args}"',
            )

        finally:
            ssl_connection.close()

    # If we get here, none of the handshakes were successful
    raise _TlsVersionNotSupported()
示例#4
0
def _detect_support_for_tls_1_3(
    server_location: ServerNetworkLocation,
    network_config: ServerNetworkConfiguration,
) -> _TlsVersionDetectionResult:
    ssl_connection = SslConnection(
        server_location=server_location,
        network_configuration=network_config,
        tls_version=TlsVersionEnum.TLS_1_3,
        should_ignore_client_auth=False,
    )

    try:
        ssl_connection.connect(should_retry_connection=False)
        return _TlsVersionDetectionResult(
            tls_version_supported=TlsVersionEnum.TLS_1_3,
            server_requested_client_cert=False,
            cipher_suite_supported=ssl_connection.ssl_client.
            get_current_cipher_name(),
        )
    except ClientCertificateRequested:
        # Connection successful but the servers wants a client certificate which wasn't supplied to sslyze
        return _TlsVersionDetectionResult(
            tls_version_supported=TlsVersionEnum.TLS_1_3,
            server_requested_client_cert=True,
            cipher_suite_supported=ssl_connection.ssl_client.
            get_current_cipher_name(),
        )

    except TlsHandshakeFailed:
        pass

    except (OSError, _nassl.OpenSSLError) as e:
        # If these errors get propagated here, it means they're not part of the known/normal errors that
        # can happen when trying to connect to a server and defined in tls_connection.py
        # Hence we re-raise these as "unknown" connection errors; might be caused by bad connectivity to
        # the server (random disconnects, etc.) and the scan against this server should not be performed
        raise ConnectionToServerFailed(
            server_location=server_location,
            network_configuration=network_config,
            error_message=f'Unexpected connection error: "{e.args}"',
        )

    finally:
        ssl_connection.close()

    # If we get here, none of the handshakes were successful
    raise _TlsVersionNotSupported()
示例#5
0
    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
示例#6
0
    def perform(
        self,
        server_location: ServerNetworkLocation,
        network_configuration: Optional[ServerNetworkConfiguration] = None
    ) -> ServerConnectivityInfo:
        """Attempt to perform a full SSL/TLS handshake with the server.

        This method will ensure that the server can be reached, and will also identify one SSL/TLS version and one
        cipher suite that is supported by the server.

        Args:
            server_location
            network_configuration

        Returns:
            An object encapsulating all the information needed to connect to the server, to be passed to a `Scanner` in
            order to run scan commands against the server.

        Raises:
            ServerConnectivityError: If the server was not reachable or an SSL/TLS handshake could not be completed.
        """
        if network_configuration is None:
            final_network_config = ServerNetworkConfiguration.default_for_server_location(
                server_location)
        else:
            final_network_config = network_configuration

        # Try to complete an SSL handshake to figure out the SSL version and cipher supported by the server
        highest_tls_version_supported = None
        cipher_suite_supported = None
        client_auth_requirement = ClientAuthRequirementEnum.DISABLED

        # TODO(AD): Switch to using the protocol discovery logic available in OpenSSL 1.1.0 with TLS_client_method()
        for tls_version in [
                TlsVersionEnum.TLS_1_3,
                TlsVersionEnum.TLS_1_2,
                TlsVersionEnum.TLS_1_1,
                TlsVersionEnum.TLS_1_0,
                TlsVersionEnum.SSL_3_0,
        ]:
            # First try the default cipher list, and then all ciphers
            for cipher_list in [None, "ALL:COMPLEMENTOFALL:-PSK:-SRP"]:
                ssl_connection = SslConnection(
                    server_location=server_location,
                    network_configuration=final_network_config,
                    tls_version=tls_version,
                    should_ignore_client_auth=False,
                )
                if cipher_list:
                    if tls_version == TlsVersionEnum.TLS_1_3:
                        # Skip the second attempt with all ciphers enabled as these ciphers don't exist in TLS 1.3
                        continue

                    ssl_connection.ssl_client.set_cipher_list(cipher_list)

                try:
                    # Only do one attempt when testing connectivity
                    ssl_connection.connect(should_retry_connection=False)
                    highest_tls_version_supported = tls_version
                    cipher_suite_supported = ssl_connection.ssl_client.get_current_cipher_name(
                    )
                except ClientCertificateRequested:
                    # Connection successful but the servers wants a client certificate which wasn't supplied to sslyze
                    # Store the SSL version and cipher list that is supported
                    highest_tls_version_supported = tls_version
                    cipher_suite_supported = cipher_list
                    # Close the current connection and try again but ignore client authentication
                    ssl_connection.close()

                    # Try a new connection to see if client authentication is optional
                    ssl_connection_auth = SslConnection(
                        server_location=server_location,
                        network_configuration=final_network_config,
                        tls_version=tls_version,
                        should_ignore_client_auth=True,
                    )
                    if cipher_list:
                        ssl_connection_auth.ssl_client.set_cipher_list(
                            cipher_list)
                    try:
                        ssl_connection_auth.connect(
                            should_retry_connection=False)
                        cipher_suite_supported = ssl_connection_auth.ssl_client.get_current_cipher_name(
                        )
                        client_auth_requirement = ClientAuthRequirementEnum.OPTIONAL

                    # If client authentication is required, we either get a ClientCertificateRequested
                    except ClientCertificateRequested:
                        client_auth_requirement = ClientAuthRequirementEnum.REQUIRED
                    # Or a ServerRejectedTlsHandshake
                    except ServerRejectedTlsHandshake:
                        client_auth_requirement = ClientAuthRequirementEnum.REQUIRED
                    finally:
                        ssl_connection_auth.close()

                except TlsHandshakeFailed:
                    # This TLS version did not work; keep going
                    pass

                except (OSError, _nassl.OpenSSLError) as e:
                    # If these errors get propagated here, it means they're not part of the known/normal errors that
                    # can happen when trying to connect to a server and defined in tls_connection.py
                    # Hence we re-raise these as "unknown" connection errors; might be caused by bad connectivity to
                    # the server (random disconnects, etc.) and the scan against this server should not be performed
                    raise ConnectionToServerFailed(
                        server_location=server_location,
                        network_configuration=final_network_config,
                        error_message=
                        f'Unexpected connection error: "{e.args}"',
                    )

                finally:
                    ssl_connection.close()

            if cipher_suite_supported:
                # A handshake was successful
                break

        if highest_tls_version_supported is None or cipher_suite_supported is None:
            raise ServerTlsConfigurationNotSupported(
                server_location=server_location,
                network_configuration=final_network_config,
                error_message=
                "Probing failed: could not find a TLS version and cipher suite supported by the server",
            )

        # Check if ECDH key exchanges are supported
        is_ecdh_key_exchange_supported = False
        if "ECDH" in cipher_suite_supported:
            is_ecdh_key_exchange_supported = True
        else:
            if highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_2.value:
                ssl_connection = SslConnection(
                    server_location=server_location,
                    network_configuration=final_network_config,
                    tls_version=highest_tls_version_supported,
                    should_use_legacy_openssl=False,
                    should_ignore_client_auth=True,
                )
                if not isinstance(ssl_connection.ssl_client, SslClient):
                    raise RuntimeError(
                        "Should never happen: specified should_use_legacy_openssl=False but didn't get the modern"
                        " SSL client")

                # Set the right elliptic curve cipher suites
                enable_ecdh_cipher_suites(highest_tls_version_supported,
                                          ssl_connection.ssl_client)
                try:
                    ssl_connection.connect(should_retry_connection=False)
                    is_ecdh_key_exchange_supported = True
                except ClientCertificateRequested:
                    is_ecdh_key_exchange_supported = True
                except ServerRejectedTlsHandshake:
                    is_ecdh_key_exchange_supported = False
                finally:
                    ssl_connection.close()

        tls_probing_result = ServerTlsProbingResult(
            highest_tls_version_supported=highest_tls_version_supported,
            cipher_suite_supported=cipher_suite_supported,
            client_auth_requirement=client_auth_requirement,
            supports_ecdh_key_exchange=is_ecdh_key_exchange_supported,
        )

        return ServerConnectivityInfo(
            server_location=server_location,
            network_configuration=final_network_config,
            tls_probing_result=tls_probing_result,
        )