def test_via_http_proxy(self):
        # Given an HTTP proxy
        proxy_port = 8123
        proxy_server = ThreadingHTTPServer(("", proxy_port), ProxyHandler)
        proxy_server_thread = threading.Thread(target=proxy_server.serve_forever)
        proxy_server_thread.start()

        # And a server location
        server_location = ServerNetworkLocationViaHttpProxy(
            hostname="www.google.com",
            port=443,
            # Configured with this proxy
            http_proxy_settings=HttpProxySettings("localhost", proxy_port),
        )

        # When testing connectivity
        try:
            server_info = ServerConnectivityTester().perform(server_location)
        finally:
            proxy_server.shutdown()

        # It succeeds
        assert server_info.tls_probing_result.cipher_suite_supported
        assert server_info.tls_probing_result.highest_tls_version_supported
        assert server_info.tls_probing_result.client_auth_requirement
        assert server_info.get_preconfigured_tls_connection()
Exemple #2
0
 def create() -> ServerNetworkLocationViaHttpProxy:
     return ServerNetworkLocationViaHttpProxy(
         hostname="ûnicôdé." + fake.hostname(),
         port=123,
         http_proxy_settings=HttpProxySettings(hostname="prôxy." +
                                               fake.hostname(),
                                               port=456),
     )
    def test_via_http_proxy_but_proxy_rejected_http_connect(self):
        # Given a server location
        server_location = ServerNetworkLocationViaHttpProxy(
            hostname="www.google.com",
            port=443,
            # Configured with a proxy that is going to reject the HTTP CONNECT request
            http_proxy_settings=HttpProxySettings("www.hotmail.com", 443),
        )

        # When testing connectivity, it fails with the right error
        with pytest.raises(ConnectionToHttpProxyTimedOut):
            ServerConnectivityTester().perform(server_location)
    def test_via_http_proxy_but_proxy_rejected_connection(self):
        # Given a server location
        server_location = ServerNetworkLocationViaHttpProxy(
            hostname="www.google.com",
            port=443,
            # Configured with a proxy that's offline
            http_proxy_settings=HttpProxySettings("localhost", 1234),
        )

        # When testing connectivity, it fails with the right error
        with pytest.raises(HttpProxyRejectedConnection):
            ServerConnectivityTester().perform(server_location)
    def test_via_http_proxy_but_proxy_timed_out(self):
        # Given a server location
        server_location = ServerNetworkLocationViaHttpProxy(
            hostname="www.google.com",
            port=443,
            # Configured with a proxy that will time out
            http_proxy_settings=HttpProxySettings("www.hotmail.com", 1234),
        )

        # When testing connectivity, it fails with the right error
        with pytest.raises(ConnectionToHttpProxyTimedOut):
            ServerConnectivityTester().perform(server_location)
    def test_via_http_proxy_but_proxy_dns_error(self):
        # Given a server location
        server_location = ServerNetworkLocationViaHttpProxy(
            hostname="www.google.com",
            port=443,
            # Configured with a proxy that cannot be looked up via DNS
            http_proxy_settings=HttpProxySettings("notarealdomain.not.real.notreal.not", 443),
        )

        # When testing connectivity, it fails with the right error
        with pytest.raises(ConnectionToHttpProxyFailed):
            ServerConnectivityTester().perform(server_location)
    def parse_command_line(self) -> ParsedCommandLine:
        """Parses the command line used to launch SSLyze.
        """
        (args_command_list, args_target_list) = self._parser.parse_args()

        if args_command_list.update_trust_stores:
            # Just update the trust stores and do nothing
            TrustStoresRepository.update_default()
            raise TrustStoresUpdateCompleted()

        # Handle the --targets_in command line and fill args_target_list
        if args_command_list.targets_in:
            if args_target_list:
                raise CommandLineParsingError(
                    "Cannot use --targets_list and specify targets within the command line."
                )

            try:  # Read targets from a file
                with open(args_command_list.targets_in) as f:
                    for target in f.readlines():
                        if target.strip():  # Ignore empty lines
                            if not target.startswith(
                                    "#"):  # Ignore comment lines
                                args_target_list.append(target.strip())
            except IOError:
                raise CommandLineParsingError(
                    "Can't read targets from input file '{}.".format(
                        args_command_list.targets_in))

        if not args_target_list:
            raise CommandLineParsingError("No targets to scan.")

        # Handle the --regular command line parameter as a shortcut
        if self._parser.has_option("--regular"):
            if getattr(args_command_list, "regular"):
                setattr(args_command_list, "regular", False)
                for cmd in self.REGULAR_CMD:
                    setattr(args_command_list, cmd, True)

        # Handle JSON settings
        should_print_json_to_console = False
        json_path_out: Optional[Path] = None
        if args_command_list.json_file:
            if args_command_list.json_file == "-":
                if args_command_list.quiet:
                    raise CommandLineParsingError(
                        "Cannot use --quiet with --json_out -.")
                should_print_json_to_console = True
            else:
                json_path_out = Path(args_command_list.json_file).absolute()

        # Sanity checks on the client cert options
        client_auth_creds = None
        if bool(args_command_list.cert) ^ bool(args_command_list.key):
            raise CommandLineParsingError(
                "No private key or certificate file were given. See --cert and --key."
            )

        elif args_command_list.cert:
            # Private key formats
            if args_command_list.keyform == "DER":
                key_type = OpenSslFileTypeEnum.ASN1
            elif args_command_list.keyform == "PEM":
                key_type = OpenSslFileTypeEnum.PEM
            else:
                raise CommandLineParsingError(
                    "--keyform should be DER or PEM.")

            # Let's try to open the cert and key files
            try:
                client_auth_creds = ClientAuthenticationCredentials(
                    certificate_chain_path=Path(args_command_list.cert),
                    key_path=Path(args_command_list.key),
                    key_password=args_command_list.keypass,
                    key_type=key_type,
                )
            except ValueError as e:
                raise CommandLineParsingError(
                    "Invalid client authentication settings: {}.".format(
                        e.args[0]))

        # HTTP CONNECT proxy
        http_proxy_settings = None
        if args_command_list.https_tunnel:
            try:
                http_proxy_settings = HttpProxySettings.from_url(
                    args_command_list.https_tunnel)
            except ValueError as e:
                raise CommandLineParsingError(
                    "Invalid proxy URL for --https_tunnel: {}.".format(
                        e.args[0]))

        # Create the server location objects for each specified servers
        good_servers: List[Tuple[ServerNetworkLocation,
                                 ServerNetworkConfiguration]] = []
        invalid_server_strings: List[InvalidServerStringError] = []
        for server_string in args_target_list:
            try:
                # Parse the string supplied via the CLI for this server
                hostname, ip_address, port = CommandLineServerStringParser.parse_server_string(
                    server_string)
            except InvalidServerStringError as e:
                # The server string is malformed
                invalid_server_strings.append(e)
                continue

            # If not port number was supplied, assume 443
            final_port = port if port else 443

            # Figure out how we're going to connect to the server
            server_location: ServerNetworkLocation
            if http_proxy_settings:
                # Connect to the server via an HTTP proxy
                # A limitation when using the CLI is that only one http_proxy_settings can be specified for all servers
                server_location = ServerNetworkLocationViaHttpProxy(
                    hostname=hostname,
                    port=final_port,
                    http_proxy_settings=http_proxy_settings)
            else:
                # Connect to the server directly
                if ip_address:
                    server_location = ServerNetworkLocationViaDirectConnection(
                        hostname=hostname,
                        port=final_port,
                        ip_address=ip_address)
                else:
                    # No IP address supplied - do a DNS lookup
                    try:
                        server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
                            hostname=hostname, port=final_port)
                    except ServerHostnameCouldNotBeResolved:
                        invalid_server_strings.append(
                            InvalidServerStringError(
                                server_string=f"{hostname}:{final_port}",
                                error_message=
                                f"Could not resolve hostname {hostname}",
                            ))
                        continue

            # Figure out extra network config for this server
            # Opportunistic TLS
            opportunistic_tls: Optional[
                ProtocolWithOpportunisticTlsEnum] = None
            if args_command_list.starttls:
                if args_command_list.starttls == "auto":
                    # Special value to auto-derive the protocol from the port number
                    opportunistic_tls = ProtocolWithOpportunisticTlsEnum.from_default_port(
                        final_port)
                elif args_command_list.starttls in _STARTTLS_PROTOCOL_DICT:
                    opportunistic_tls = _STARTTLS_PROTOCOL_DICT[
                        args_command_list.starttls]
                else:
                    raise CommandLineParsingError(self.START_TLS_USAGE)

            try:
                sni_hostname = args_command_list.sni if args_command_list.sni else hostname
                network_config = ServerNetworkConfiguration(
                    tls_opportunistic_encryption=opportunistic_tls,
                    tls_server_name_indication=sni_hostname,
                    tls_client_auth_credentials=client_auth_creds,
                    xmpp_to_hostname=args_command_list.xmpp_to,
                )
                good_servers.append((server_location, network_config))
            except InvalidServerNetworkConfigurationError as e:
                raise CommandLineParsingError(e.args[0])

        # Figure out global network settings
        concurrent_server_scans_limit = None
        per_server_concurrent_connections_limit = None
        if args_command_list.https_tunnel:
            # All the connections will go through a single proxy; only scan one server at a time to not DOS the proxy
            concurrent_server_scans_limit = 1
        if args_command_list.slow_connection:
            # Go easy on the servers; only open 2 concurrent connections against each server
            per_server_concurrent_connections_limit = 2

        # Figure out the scan commands that are enabled
        scan_commands: Set[ScanCommandType] = set()
        scan_commands_extra_arguments: ScanCommandExtraArgumentsDict = {}
        for scan_command in ScanCommandsRepository.get_all_scan_commands():
            cli_connector_cls = ScanCommandsRepository.get_implementation_cls(
                scan_command).cli_connector_cls
            is_scan_cmd_enabled, extra_args = cli_connector_cls.find_cli_options_in_command_line(
                args_command_list.__dict__)
            if is_scan_cmd_enabled:
                scan_commands.add(scan_command)
                if extra_args:
                    scan_commands_extra_arguments[
                        scan_command] = extra_args  # type: ignore

        return ParsedCommandLine(
            invalid_servers=invalid_server_strings,
            servers_to_scans=good_servers,
            scan_commands=scan_commands,
            scan_commands_extra_arguments=scan_commands_extra_arguments,
            should_print_json_to_console=should_print_json_to_console,
            json_path_out=json_path_out,
            should_disable_console_output=args_command_list.quiet
            or args_command_list.json_file == "-",
            concurrent_server_scans_limit=concurrent_server_scans_limit,
            per_server_concurrent_connections_limit=
            per_server_concurrent_connections_limit,
        )