Step 2: Running Scan Commands Against a Server

Every type of scan that SSLyze can run against a server (supported cipher suites, session renegotiation, etc.) is represented by a ScanCommand.

Once a ScanCommand is run against a server, it returns a ScanResult which is an object with attributes containing the results of the scan. The list of attributes and what they mean depends on what kind of scan was run (ie. which ScanCommand).

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

As explained in Step 1: Testing Connectivity to a Server, a properly initialized ServerConnectivityInfo is needed before the corresponding server can be scanned. Then, SSLyze can run ScanCommands against this server either:

  • Sequentially using the SynchronousScanner class.
  • Concurrently using the ConcurrentScanner class; this class is slightly more complex to use, but is also a lot faster when running a several ScanCommand and/or scanning multiple servers.

Running Commands Sequentially

Basic example

The SynchronousScanner class can be used to run ScanCommands against a server:

def demo_synchronous_scanner():
    # Run one scan command to list the server's TLS 1.0 cipher suites
    try:
        server_tester = ServerConnectivityTester(
            hostname='smtp.gmail.com',
            port=587,
            tls_wrapped_protocol=TlsWrappedProtocolEnum.STARTTLS_SMTP
        )
        print(f'\nTesting connectivity with {server_tester.hostname}:{server_tester.port}...')
        server_info = server_tester.perform()
    except ServerConnectivityError as e:
        # Could not establish an SSL connection to the server
        raise RuntimeError(f'Could not connect to {e.server_info.hostname}: {e.error_message}')

    command = Tlsv10ScanCommand()

    synchronous_scanner = SynchronousScanner()

    scan_result = synchronous_scanner.run_scan_command(server_info, command)
    for cipher in scan_result.accepted_cipher_list:
        print(f'    {cipher.name}')

The SynchronousScanner class

Running Commands Concurrently

Basic example

The ConcurrentScanner uses a pool of processes to run ScanCommands concurrently. It is very fast when scanning a large number of servers, and it has a dispatching mechanism to avoid DOS-ing a single server against which multiple ScanCommand are run at the same time.

The commands can be queued using the queue_scan_command() method, and the results can later be retrieved using the get_results() method:

def demo_concurrent_scanner():
    # Setup the server to scan and ensure it is online/reachable
    server_info = demo_server_connectivity_tester()

    # Run multiple scan commands concurrently. It is much faster than the SynchronousScanner
    concurrent_scanner = ConcurrentScanner()

    # Queue some scan commands
    print('\nQueuing some commands...')
    concurrent_scanner.queue_scan_command(server_info, Tlsv12ScanCommand())
    concurrent_scanner.queue_scan_command(server_info, CertificateInfoScanCommand())

    # Process the results
    print('\nProcessing results...')
    for scan_result in concurrent_scanner.get_results():
        # All scan results have the corresponding scan_command and server_info as an attribute
        print(f'\nReceived result for "{scan_result.scan_command.get_title()}" '
              f'on {scan_result.server_info.hostname}')

        # A scan command can fail (as a bug); it is returned as a PluginRaisedExceptionResult
        if isinstance(scan_result, PluginRaisedExceptionScanResult):
            raise RuntimeError(f'Scan command failed: {scan_result.scan_command.get_title()}')

        # Each scan result has attributes with the information yo're looking for
        # All these attributes are documented within each scan command's module
        if isinstance(scan_result.scan_command, Tlsv12ScanCommand):
            for cipher in scan_result.accepted_cipher_list:
                print(f'    {cipher.name}')

        elif isinstance(scan_result.scan_command, CertificateInfoScanCommand):
            # Print the Common Names within the verified certificate chain
            if not scan_result.verified_certificate_chain:
                print('Error: certificate chain is not trusted!')
            else:
                print('Certificate chain common names:')
                for cert in scan_result.verified_certificate_chain:
                    cert_common_names = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
                    print(f'   {cert_common_names[0].value}')

The ConcurrentScanner class