Example #1
0
def basic_example() -> None:
    # Define the server that you want to scan
    server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup("www.google.com", 443)

    # Do connectivity testing to ensure SSLyze is able to connect
    try:
        server_info = ServerConnectivityTester().perform(server_location)
    except ConnectionToServerFailed as e:
        # Could not connect to the server; abort
        print(f"Error connecting to {server_location}: {e.error_message}")
        return

    # Then queue some scan commands for the server
    scanner = Scanner()
    server_scan_req = ServerScanRequest(
        server_info=server_info, scan_commands={ScanCommand.CERTIFICATE_INFO, ScanCommand.SSL_2_0_CIPHER_SUITES},
    )
    scanner.start_scans([server_scan_req])

    # Then retrieve the results
    for server_scan_result in scanner.get_results():
        print(f"\nResults for {server_scan_result.server_info.server_location.hostname}:")

        # SSL 2.0 results
        ssl2_result = server_scan_result.scan_commands_results[ScanCommand.SSL_2_0_CIPHER_SUITES]
        print("\nAccepted cipher suites for SSL 2.0:")
        for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
            print(f"* {accepted_cipher_suite.cipher_suite.name}")

        # Certificate info results
        certinfo_result = server_scan_result.scan_commands_results[ScanCommand.CERTIFICATE_INFO]
        print("\nCertificate info:")
        for cert_deployment in certinfo_result.certificate_deployments:
            print(f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}")
Example #2
0
def main() -> None:
    # First validate that we can connect to the servers we want to scan
    servers_to_scan = []
    for hostname in ["cloudflare.com", "google.com"]:
        server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(hostname, 443)
        try:
            server_info = ServerConnectivityTester().perform(server_location)
            servers_to_scan.append(server_info)
        except ConnectionToServerFailed as e:
            print(f"Error connecting to {server_location.hostname}:{server_location.port}: {e.error_message}")
            return

    scanner = Scanner()

    # Then queue some scan commands for each server
    all_server_scans = [
        ServerScanRequest(
            server_info=server_info, scan_commands={ScanCommand.CERTIFICATE_INFO, ScanCommand.SSL_2_0_CIPHER_SUITES}
        )
        for server_info in servers_to_scan
    ]
    scanner.start_scans(all_server_scans)

    # Then retrieve the result of the scan commands for each server
    for server_scan_result in scanner.get_results():
        print(f"\nResults for {server_scan_result.server_info.server_location.hostname}:")

        # Scan commands that were run with no errors
        try:
            ssl2_result = server_scan_result.scan_commands_results[ScanCommand.SSL_2_0_CIPHER_SUITES]
            print("\nAccepted cipher suites for SSL 2.0:")
            for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
                print(f"* {accepted_cipher_suite.cipher_suite.name}")
        except KeyError:
            pass

        try:
            certinfo_result = server_scan_result.scan_commands_results[ScanCommand.CERTIFICATE_INFO]
            print("\nCertificate info:")
            for cert_deployment in certinfo_result.certificate_deployments:
                print(f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}")
        except KeyError:
            pass

        # Scan commands that were run with errors
        for scan_command, error in server_scan_result.scan_commands_errors.items():
            print(f"\nError when running {scan_command}:\n{error.exception_trace}")
Example #3
0
def main(server_software_running_on_localhost: WebServerSoftwareEnum) -> None:
    # Ensure the server is accessible on localhost
    server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
        "localhost", 443)
    server_info = ServerConnectivityTester().perform(server_location)

    if server_software_running_on_localhost == WebServerSoftwareEnum.APACHE2:
        # Apache2 is configured to require a client cert, and returns an error at the TLS layer if it is missing
        if server_info.tls_probing_result.client_auth_requirement != ClientAuthRequirementEnum.REQUIRED:
            raise RuntimeError(
                f"SSLyze did not detect that client authentication was required by Apache2:"
                f" {server_info.tls_probing_result.client_auth_requirement}.")
    elif server_software_running_on_localhost == WebServerSoftwareEnum.NGINX:
        # Nginx is configured to require a client cert but implements this by returning an error at the HTTP layer,
        # if the client cert is missing. This gets translated in SSLyze as "optionally" requiring a client cert
        if server_info.tls_probing_result.client_auth_requirement != ClientAuthRequirementEnum.OPTIONAL:
            raise RuntimeError(
                f"SSLyze did not detect that client authentication was required by Nginx:"
                f" {server_info.tls_probing_result.client_auth_requirement}.")
    elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
        # IIS is not configured to require a client cert for now because I don't know how to enable this
        if server_info.tls_probing_result.client_auth_requirement != ClientAuthRequirementEnum.DISABLED:
            raise RuntimeError(
                f"SSLyze detected that client authentication was enabled by IIS:"
                f" {server_info.tls_probing_result.client_auth_requirement}.")
    else:
        raise ValueError(
            f"Unexpected value: {server_software_running_on_localhost}")

    # Queue all scan commands
    print("Starting scan.")
    scanner = Scanner()
    server_scan_req = ServerScanRequest(
        server_info=server_info,
        scan_commands=ScanCommandsRepository.get_all_scan_commands(),
    )
    scanner.start_scans([server_scan_req])

    # Retrieve the result
    for server_scan_result in scanner.get_results():
        successful_cmds_count = len(server_scan_result.scan_commands_results)
        errored_cmds_count = len(server_scan_result.scan_commands_errors)
        print(
            f"Finished scan with {successful_cmds_count} results and {errored_cmds_count} errors."
        )

        # Crash if any scan commands triggered an error that's not due to client authentication being required
        triggered_unexpected_error = False
        for scan_command, error in server_scan_result.scan_commands_errors.items(
        ):
            if error.reason != ScanCommandErrorReasonEnum.CLIENT_CERTIFICATE_NEEDED:
                triggered_unexpected_error = True
                print(
                    f"\nError when running {scan_command}: {error.reason.name}."
                )
                if error.exception_trace:
                    exc_trace = ""
                    for line in error.exception_trace.format(chain=False):
                        exc_trace += f"       {line}"
                    print(exc_trace)

                print("\n")

        if triggered_unexpected_error:
            raise RuntimeError("The scan triggered unexpected errors")
        else:
            # The CLIENT_CERTIFICATE_NEEDED errors are expected, because of how Apache2 is configured
            print("OK: Triggered CLIENT_CERTIFICATE_NEEDED errors only.")

        # Crash if SSLyze didn't complete the scan commands that are supposed to work even when we don't provide a
        # client certificate
        if server_software_running_on_localhost == WebServerSoftwareEnum.APACHE2:
            expected_scan_command_results = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
                ScanCommand.SSL_3_0_CIPHER_SUITES,
                ScanCommand.SSL_2_0_CIPHER_SUITES,
                ScanCommand.OPENSSL_CCS_INJECTION,
                ScanCommand.HEARTBLEED,
                ScanCommand.ELLIPTIC_CURVES,
                ScanCommand.TLS_FALLBACK_SCSV,
                ScanCommand.CERTIFICATE_INFO,
                ScanCommand.TLS_COMPRESSION,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.NGINX:
            # With nginx, when configured to require client authentication, more scan commands work because unlike
            # Apache2, it does complete a full TLS handshake even when a client cert was not provided. It then returns
            # an error page at the HTTP layer.
            expected_scan_command_results = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
                ScanCommand.SSL_3_0_CIPHER_SUITES,
                ScanCommand.SSL_2_0_CIPHER_SUITES,
                ScanCommand.OPENSSL_CCS_INJECTION,
                ScanCommand.HEARTBLEED,
                ScanCommand.ELLIPTIC_CURVES,
                ScanCommand.TLS_FALLBACK_SCSV,
                ScanCommand.CERTIFICATE_INFO,
                ScanCommand.TLS_COMPRESSION,
                ScanCommand.SESSION_RESUMPTION,
                ScanCommand.TLS_1_3_EARLY_DATA,
                ScanCommand.HTTP_HEADERS,
                ScanCommand.SESSION_RESUMPTION_RATE,
                ScanCommand.SESSION_RENEGOTIATION,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
            # With IIS, client authentication is not enabled so all scan commands should succeed
            expected_scan_command_results = ScanCommandsRepository.get_all_scan_commands(
            )  # type: ignore
        else:
            raise ValueError(
                f"Unexpected value: {server_software_running_on_localhost}")

        completed_scan_command_results = server_scan_result.scan_commands_results.keys(
        )
        if completed_scan_command_results != expected_scan_command_results:
            raise RuntimeError(
                f"SSLyze did not complete all the expected scan commands: {completed_scan_command_results}"
            )
        else:
            print("OK: Completed all the expected scan commands.")

        # Ensure the right TLS versions were detected by SSLyze as enabled
        # https://github.com/nabla-c0d3/sslyze/issues/472
        if server_software_running_on_localhost in [
                WebServerSoftwareEnum.APACHE2, WebServerSoftwareEnum.NGINX
        ]:
            # Apache and nginx are configured to only enable TLS 1.2 and TLS 1.3
            expected_enabled_tls_scan_commands = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
            # TLS 1.3 is not supported by IIS
            expected_enabled_tls_scan_commands = {
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
            }
        else:
            raise ValueError(
                f"Unexpected value: {server_software_running_on_localhost}")

        for ciphers_scan_cmd in expected_enabled_tls_scan_commands:
            scan_cmd_result = server_scan_result.scan_commands_results[
                ciphers_scan_cmd]  # type: ignore
            if not scan_cmd_result.accepted_cipher_suites:
                raise RuntimeError(
                    f"SSLyze did not detect {scan_cmd_result.tls_version_used.name} to be enabled on the server."
                )
            else:
                print(
                    f"OK: Scan command {ciphers_scan_cmd} detected cipher suites."
                )

        # Ensure a JSON output can be generated from the results
        json_output = _SslyzeOutputAsJson(
            server_scan_results=[server_scan_result],
            server_connectivity_errors=[],
            total_scan_time=3,
        )
        json_output_as_dict = asdict(json_output)
        json.dumps(json_output_as_dict,
                   cls=JsonEncoder,
                   sort_keys=True,
                   indent=4,
                   ensure_ascii=True)
        print("OK: Was able to generate JSON output.")