Skip to content

Sslyze version incompatibility #209

@Ethanljf

Description

@Ethanljf

🐛 Bug Report

The latest changes to SSLYZE cause a few import issues within pshtt.py.

To Reproduce

Run pshtt in conjunction with the latest version of sslyze available via PIP

Expected behavior

Pshtt succesfully implements https_check() to figure out the reason an endpoint wouldn't verify.

Any helpful log output

Import errors stemming from pshtt.py

To resolve these issues, I recommend either pinning the sslyze requirement to a previous version or implementing the following changes:

Sslyze-related imports within pshtt.py should be as follows (to maintain existing functionality):

import sslyze
from sslyze.server_connectivity import ServerConnectivityTester
from sslyze.errors import ConnectionToServerFailed
from sslyze.scanner import Scanner, ServerScanRequest, ScanCommandExtraArgumentsDict
from sslyze.plugins.scan_commands import ScanCommand
from sslyze.server_setting import ServerNetworkLocation, ServerNetworkLocationViaDirectConnection

It is worth noting that the latest changes to sslyze support asynchronous scanning. However, to simply maintain existing asynchronous scanning, "https_check()" could be amended as follows:

def https_check(endpoint):
    """
    Uses sslyze to figure out the reason the endpoint wouldn't verify.
    """

    # remove the https:// from prefix for sslyze
    try:
        hostname = endpoint.url[8:]
        server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(hostname, 443)
        server_tester = ServerConnectivityTester()
        server_info = server_tester.perform(server_location)
        endpoint.live = True
        ip = server_location.ip_address
        if endpoint.ip is None:
            endpoint.ip = ip
        else:
            if endpoint.ip != ip:
                logging.info("{}: Endpoint IP is already {}, but requests IP is {}.".format(endpoint.url, endpoint.ip, ip))
        if server_info.tls_probing_result.client_auth_requirement.name == 'REQUIRED':
            endpoint.https_client_auth_required = True
            logging.warning("{}: Client Authentication REQUIRED".format(endpoint.url))
    except ConnectionToServerFailed as err:
        endpoint.live = False
        endpoint.https_valid = False
        logging.exception("{}: Error in sslyze server connectivity check when connecting to {}".format(endpoint.url, err.server_info.hostname))
        return
    except Exception as err:
        endpoint.unknown_error = True
        logging.exception("{}: Unknown exception in sslyze server connectivity check.".format(endpoint.url))
        return

    try:
        cert_plugin_result = None
        command = ScanCommand.CERTIFICATE_INFO
        scanner = Scanner()
        scan_request = ServerScanRequest(server_info=server_info, scan_commands=[command])
        scanner.queue_scan(scan_request)
        # Retrieve results from generator object
        scan_result = [x for x in scanner.get_results()][0]
        cert_plugin_result = scan_result.scan_commands_results['certificate_info']
    except Exception as err:
        try:
            if "timed out" in str(err):
                logging.exception("{}: Retrying sslyze scanner certificate plugin.".format(endpoint.url))
                scanner.queue_scan(scan_request)
                # Retrieve results from generator object
                scan_result = [x for x in scanner.get_results()][0]
                cert_plugin_result = scan_result.scan_commands_results['certificate_info']
            else:
                logging.exception("{}: Unknown exception in sslyze scanner certificate plugin.".format(endpoint.url))
                endpoint.unknown_error = True
                # We could make this False, but there was an error so
                # we don't know
                endpoint.https_valid = None
                return
        except Exception:
            logging.exception("{}: Unknown exception in sslyze scanner certificate plugin.".format(endpoint.url))
            endpoint.unknown_error = True
            # We could make this False, but there was an error so we
            # don't know
            endpoint.https_valid = None
            return

    try:
        public_trust = True
        custom_trust = True
        public_not_trusted_string = ""
        validation_results = cert_plugin_result.certificate_deployments[0].path_validation_results
        for result in validation_results:
            if result.was_validation_successful:
                # We're assuming that it is trusted to start with
                pass
            else:
                if 'Custom' in result.trust_store.name:
                    custom_trust = False
                else:
                    public_trust = False
                    if len(public_not_trusted_string) > 0:
                        public_not_trusted_string += ", "
                    public_not_trusted_string += result.trust_store.name
        if public_trust:
            logging.warning("{}: Publicly trusted by common trust stores.".format(endpoint.url))
        else:
            logging.warning("{}: Not publicly trusted - not trusted by {}.".format(endpoint.url, public_not_trusted_string))
        custom_trust = None
        endpoint.https_public_trusted = public_trust
        endpoint.https_custom_trusted = custom_trust
    except Exception as err:
        # Ignore exception
        logging.exception("{}: Unknown exception examining trust.".format(endpoint.url))

    # Default endpoint assessments to False until proven True.
    endpoint.https_expired_cert = False
    endpoint.https_self_signed_cert = False
    endpoint.https_bad_chain = False
    endpoint.https_bad_hostname = False

    cert_chain = cert_plugin_result.certificate_deployments[0].received_certificate_chain

    # Check for missing SAN (Leaf certificate)
    leaf_cert = cert_chain[0]
    # Extract Subject Alternative Names
    san_list = extract_dns_subject_alternative_names(leaf_cert)
    # If an empty list was return, SAN(s) are missing. Bad hostname
    if isinstance(san_list, list) and len(san_list) == 0:
        endpoint.https_bad_hostname = True

    # If leaf certificate subject does NOT match hostname, bad hostname
    if not cert_plugin_result.certificate_deployments[0].leaf_certificate_subject_matches_hostname:
        endpoint.https_bad_hostname = True

    # Check for leaf certificate expiration/self-signature.
    if leaf_cert.not_valid_after < datetime.datetime.now():
        endpoint.https_expired_cert = True

    # Check to see if the cert is self-signed
    if leaf_cert.issuer is leaf_cert.subject:
        endpoint.https_self_signed_cert = True

    # Check certificate chain
    for cert in cert_chain[1:]:
        # Check for certificate expiration
        if cert.not_valid_after < datetime.datetime.now():
            endpoint.https_bad_chain = True

        # Check to see if the cert is self-signed
        if cert.issuer is (cert.subject or None):
            endpoint.https_bad_chain = True

    try:
        endpoint.https_cert_chain_len = len(cert_plugin_result.certificate_deployments[0].received_certificate_chain)
        if (
                endpoint.https_self_signed_cert is False and (
                    len(cert_plugin_result.certificate_deployments[0].received_certificate_chain) < 2
                )
        ):
            # *** TODO check that it is not a bad hostname and that the root cert is trusted before suggesting that it is an intermediate cert issue.
            endpoint.https_missing_intermediate_cert = True
            if(cert_plugin_result.verified_certificate_chain is None):
                logging.warning("{}: Untrusted certificate chain, probably due to missing intermediate certificate.".format(endpoint.url))
                logging.info("{}: Only {} certificates in certificate chain received.".format(endpoint.url, cert_plugin_result.received_certificate_chain.__len__()))
        else:
            endpoint.https_missing_intermediate_cert = False
    except Exception:
        logging.exception("Error while determining length of certificate chain")

    # If anything is wrong then https is not valid
    if (
        endpoint.https_expired_cert or
        endpoint.https_self_signed_cert or
        endpoint.https_bad_chain or
        endpoint.https_bad_hostname
    ):
        endpoint.https_valid = False

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions