Step 2: Running Scan Commands Against a Server

Every type of scan that SSLyze can run against a server (supported cipher suites, Heartbleed, etc.) is represented by a ScanCommand. Once a ScanCommand is run against a server, it returns a “result” object with attributes containing the results of the scan command.

All the available ScanCommands and corresponding results are described in Appendix: Scan Commands.

Basic Example

The main class for running these commands is the Scanner class, which uses a pool of workers to run ScanCommand concurrently. It is very fast when scanning a large number of servers, and it has a rate-limiting mechanism to avoid DOS-ing a single server against which multiple ScanCommand are run at the same time.

The commands can be queued by passing a ServerScanRequest to the Scanner.queue_scan() method.

The results can later be retrieved using the Scanner.get_results() method, which returns an iterable of ServerScanResult. Each result is returned as soon as the server scan was completed.

A simple example on how to run some scan commands follows:

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]}")

Advanced Usage

The following script provides an example of running scan commands against multiple servers, and processing the results:

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}")

Exporting to JSON

A ServerScanResult can be serialized to JSON using SSLyze’s special JsonEncoder.

class sslyze.JsonEncoder(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)

Special JSON encoder that can serialize any ServerScanResult returned by SSLyze.

A ServerScanResult can be serialized to JSON using the following code:

>>> from dataclasses import asdict
>>> import json
>>> import sslyze
>>>
>>> scanner = sslyze.Scanner()
>>> # Queue some ServerScanRequest... and then retrieve the results...
>>> for server_scan_result in scanner.get_results():
>>>     server_scan_result_as_json = json.dumps(asdict(server_scan_result), cls=sslyze.JsonEncoder)