pshtt
pshtt copied to clipboard
Sslyze version incompatibility
🐛 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
Any plans to update to sslyze v3? cryptography seems to have a CVE which is solved in Cryptography 3.2, but sslyze v2.x pins the dependency to 2.5 and hence not updating to sslyze v3 technically means using cryptography package with vulnerability.
Checking in again if there are any plans to upgrade to a newer sslyze.
This is becoming something of an issue for us at Freedom of the Press Foundation as the version of cryptography
the existing dependency tree requires has been accumulating more known security issues. So far, fortunately, none of the issues have been problems for us, but someday one might be and we'd really like to be able to upgrade.
If it would speed things up, we'd be happy to volunteer some of our own engineering time to resolving this if you'd accept a pull request and, ideally, we could get some guidance from someone on your team :)