Skip to content

Commit 351ca80

Browse files
committed
[nabla-c0d3#627][nabla-c0d3#638] Swith to cryptography's cert validation function
1 parent 0c88af2 commit 351ca80

File tree

12 files changed

+17630
-106
lines changed

12 files changed

+17630
-106
lines changed

json_output_schema.json

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@
12001200
"type": "object"
12011201
},
12021202
"_CertificateDeploymentAnalysisResultAsJson": {
1203-
"description": "The result of analyzing a server's certificate to verify its validity.\n\nAny certificate available within the fields that follow is parsed as a ``Certificate`` object using the cryptography\nmodule; documentation is available at\nhttps://cryptography.io/en/latest/x509/reference.html?highlight=Certificate#cryptography.x509.Certificate\n\nAttributes:\n received_certificate_chain: The certificate chain sent by the server; index 0 is the leaf certificate.\n verified_certificate_chain: The verified certificate chain returned by OpenSSL for one of the trust stores\n packaged within SSLyze. Will be ``None`` if the validation failed with all of the available trust stores\n (Apple, Mozilla, etc.). This is essentially a shortcut to\n ``path_validation_result_list[0].verified_certificate_chain``.\n path_validation_results: The result of validating the server's\n certificate chain using each trust store that is packaged with SSLyze (Mozilla, Apple, etc.).\n If for a given trust store, the validation was successful, the verified certificate chain built by OpenSSL\n can be retrieved from the ``PathValidationResult``.\n leaf_certificate_subject_matches_hostname: ``True`` if the leaf certificate's Common Name or Subject Alternative\n Names match the server's hostname.\n leaf_certificate_is_ev: ``True`` if the leaf certificate is Extended Validation, according to Mozilla.\n leaf_certificate_has_must_staple_extension: ``True`` if the OCSP must-staple extension is present in the leaf\n certificate.\n leaf_certificate_signed_certificate_timestamps_count: The number of Signed Certificate\n Timestamps (SCTs) for Certificate Transparency embedded in the leaf certificate. ``None`` if the version of\n OpenSSL installed on the system is too old to be able to parse the SCT extension.\n received_chain_has_valid_order: ``True`` if the certificate chain returned by the server was sent in the right\n order. `None`` if any of the certificates in the chain could not be parsed.\n received_chain_contains_anchor_certificate: ``True`` if the server included the anchor/root\n certificate in the chain it sends back to clients. ``None`` if the verified chain could not be built.\n verified_chain_has_sha1_signature: ``True`` if any of the leaf or intermediate certificates are\n signed using the SHA-1 algorithm. ``None`` if the verified chain could not be built.\n verified_chain_has_legacy_symantec_anchor: ``True`` if the certificate chain contains a distrusted Symantec\n anchor\n (https://blog.qualys.com/ssllabs/2017/09/26/google-and-mozilla-deprecating-existing-symantec-certificates).\n ``None`` if the verified chain could not be built.\n ocsp_response: The OCSP response returned by the server. ``None`` if no response was sent by the server or if\n the scan was run through an HTTP proxy (the proxy will not forward the server's OCSP response). If present,\n the OCSP response is an ``OCSPResponse`` object parsed using the cryptography module; documentation is\n available at\n https://cryptography.io/en/latest/x509/ocsp.html?highlight=OCSPResponse#cryptography.x509.ocsp.OCSPResponse\n ocsp_response_is_trusted: ``True`` if the OCSP response is trusted using the Mozilla trust store.\n ``None`` if no OCSP response was sent by the server.",
1203+
"description": "The result of analyzing a server's certificate to verify its validity.\n\nAny certificate available within the fields that follow is parsed as a ``Certificate`` object using the cryptography\nmodule; documentation is available at\nhttps://cryptography.io/en/latest/x509/reference.html?highlight=Certificate#cryptography.x509.Certificate\n\nAttributes:\n received_certificate_chain: The certificate chain sent by the server; index 0 is the leaf certificate.\n verified_certificate_chain: The verified certificate chain returned by OpenSSL for one of the trust stores\n packaged within SSLyze. Will be ``None`` if the validation failed with all of the available trust stores\n (Apple, Mozilla, etc.). This is essentially a shortcut to\n ``path_validation_result_list[0].verified_certificate_chain``.\n path_validation_results: The result of validating the server's\n certificate chain using each trust store that is packaged with SSLyze (Mozilla, Apple, etc.).\n If for a given trust store, the validation was successful, the verified certificate chain can be\n retrieved from the ``PathValidationResult``.\n leaf_certificate_is_ev: ``True`` if the leaf certificate is Extended Validation, according to Mozilla.\n leaf_certificate_has_must_staple_extension: ``True`` if the OCSP must-staple extension is present in the leaf\n certificate.\n leaf_certificate_signed_certificate_timestamps_count: The number of Signed Certificate\n Timestamps (SCTs) for Certificate Transparency embedded in the leaf certificate. ``None`` if the version of\n OpenSSL installed on the system is too old to be able to parse the SCT extension.\n received_chain_has_valid_order: ``True`` if the certificate chain returned by the server was sent in the right\n order. `None`` if any of the certificates in the chain could not be parsed.\n received_chain_contains_anchor_certificate: ``True`` if the server included the anchor/root\n certificate in the chain it sends back to clients. ``None`` if the verified chain could not be built.\n verified_chain_has_sha1_signature: ``True`` if any of the leaf or intermediate certificates are\n signed using the SHA-1 algorithm. ``None`` if the verified chain could not be built.\n verified_chain_has_legacy_symantec_anchor: ``True`` if the certificate chain contains a distrusted Symantec\n anchor\n (https://blog.qualys.com/ssllabs/2017/09/26/google-and-mozilla-deprecating-existing-symantec-certificates).\n ``None`` if the verified chain could not be built.\n ocsp_response: The OCSP response returned by the server. ``None`` if no response was sent by the server or if\n the scan was run through an HTTP proxy (the proxy will not forward the server's OCSP response). If present,\n the OCSP response is an ``OCSPResponse`` object parsed using the cryptography module; documentation is\n available at\n https://cryptography.io/en/latest/x509/ocsp.html?highlight=OCSPResponse#cryptography.x509.ocsp.OCSPResponse\n ocsp_response_is_trusted: ``True`` if the OCSP response is trusted using the Mozilla trust store.\n ``None`` if no OCSP response was sent by the server.",
12041204
"properties": {
12051205
"received_certificate_chain": {
12061206
"items": {
@@ -1209,10 +1209,6 @@
12091209
"title": "Received Certificate Chain",
12101210
"type": "array"
12111211
},
1212-
"leaf_certificate_subject_matches_hostname": {
1213-
"title": "Leaf Certificate Subject Matches Hostname",
1214-
"type": "boolean"
1215-
},
12161212
"leaf_certificate_has_must_staple_extension": {
12171213
"title": "Leaf Certificate Has Must Staple Extension",
12181214
"type": "boolean"
@@ -1321,7 +1317,6 @@
13211317
},
13221318
"required": [
13231319
"received_certificate_chain",
1324-
"leaf_certificate_subject_matches_hostname",
13251320
"leaf_certificate_has_must_staple_extension",
13261321
"leaf_certificate_is_ev",
13271322
"leaf_certificate_signed_certificate_timestamps_count",
@@ -1724,7 +1719,7 @@
17241719
"type": "object"
17251720
},
17261721
"_PathValidationResultAsJson": {
1727-
"description": "The result of trying to validate a server's certificate chain using a specific trust store.\n\nAttributes:\n trust_store: The trust store used for validation.\n verified_certificate_chain: The verified certificate chain returned by OpenSSL.\n Index 0 is the leaf certificate and the last element is the anchor/CA certificate from the trust store.\n Will be None if the validation failed or the verified chain could not be built.\n Each certificate is parsed using the cryptography module; documentation is available at\n https://cryptography.io/en/latest/x509/reference/#x-509-certificate-object.\n openssl_error_string: The result string returned by OpenSSL's validation function; None if validation was\n successful.\n was_validation_successful: Whether the certificate chain is trusted when using supplied the trust_stores.",
1722+
"description": "The result of trying to validate a server's certificate chain using a specific trust store.\n\nAttributes:\n trust_store: The trust store used for validation.\n verified_certificate_chain: The verified certificate chain returned by OpenSSL.\n Index 0 is the leaf certificate and the last element is the anchor/CA certificate from the trust store.\n Will be None if the validation failed or the verified chain could not be built.\n Each certificate is parsed using the cryptography module; documentation is available at\n https://cryptography.io/en/latest/x509/reference/#x-509-certificate-object.\n validation_error: The error returned by the cryptography module's validation function; None if validation was\n successful.\n was_validation_successful: Whether the certificate chain is trusted when using supplied the trust_stores.",
17281723
"properties": {
17291724
"trust_store": {
17301725
"$ref": "#/$defs/_TrustStoreAsJson"
@@ -1743,7 +1738,7 @@
17431738
],
17441739
"title": "Verified Certificate Chain"
17451740
},
1746-
"openssl_error_string": {
1741+
"validation_error": {
17471742
"anyOf": [
17481743
{
17491744
"type": "string"
@@ -1752,7 +1747,7 @@
17521747
"type": "null"
17531748
}
17541749
],
1755-
"title": "Openssl Error String"
1750+
"title": "Validation Error"
17561751
},
17571752
"was_validation_successful": {
17581753
"title": "Was Validation Successful",
@@ -1762,7 +1757,7 @@
17621757
"required": [
17631758
"trust_store",
17641759
"verified_certificate_chain",
1765-
"openssl_error_string",
1760+
"validation_error",
17661761
"was_validation_successful"
17671762
],
17681763
"title": "_PathValidationResultAsJson",

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,9 @@ def get_include_files() -> List[Tuple[str, str]]:
9898
# Dependencies
9999
install_requires=[
100100
"nassl>=5.1,<6",
101-
"cryptography>=2.6,<42",
101+
"cryptography>42,<43",
102102
"tls-parser>=2,<3",
103103
"pydantic>=2.2,<2.7",
104-
"pyOpenSSL>=23,<24",
105104
],
106105
# cx_freeze info for Windows builds with Python embedded
107106
options={"build_exe": {"packages": ["cffi", "cryptography"], "include_files": get_include_files()}},

sslyze/mozilla_tls_profile/mozilla_config_checker.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,6 @@ def _check_certificates(
293293
for cert_deployment in cert_info_result.certificate_deployments:
294294
# Validate certificate trust
295295
leaf_cert = cert_deployment.received_certificate_chain[0]
296-
if not cert_deployment.leaf_certificate_subject_matches_hostname:
297-
issues_with_certificates[
298-
"certificate_hostname_validation"
299-
] = f"Certificate hostname validation failed for {leaf_cert.subject.rfc4514_string()}."
300296
if not cert_deployment.verified_certificate_chain:
301297
issues_with_certificates[
302298
"certificate_path_validation"

sslyze/plugins/certificate_info/_cert_chain_analyzer.py

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from dataclasses import dataclass
22

3-
from ssl import CertificateError, match_hostname
43
from typing import Optional, List, cast
54

65
import cryptography
@@ -11,10 +10,6 @@
1110
from cryptography.x509.ocsp import load_der_ocsp_response, OCSPResponseStatus, OCSPResponse
1211
import nassl.ocsp_response
1312

14-
from sslyze.plugins.certificate_info._certificate_utils import (
15-
parse_subject_alternative_name_extension,
16-
get_common_names,
17-
)
1813
from sslyze.plugins.certificate_info._symantec import SymantecDistructTester
1914
from sslyze.plugins.certificate_info.trust_stores.trust_store import TrustStore, PathValidationResult
2015

@@ -35,10 +30,8 @@ class CertificateDeploymentAnalysisResult:
3530
``path_validation_result_list[0].verified_certificate_chain``.
3631
path_validation_results: The result of validating the server's
3732
certificate chain using each trust store that is packaged with SSLyze (Mozilla, Apple, etc.).
38-
If for a given trust store, the validation was successful, the verified certificate chain built by OpenSSL
39-
can be retrieved from the ``PathValidationResult``.
40-
leaf_certificate_subject_matches_hostname: ``True`` if the leaf certificate's Common Name or Subject Alternative
41-
Names match the server's hostname.
33+
If for a given trust store, the validation was successful, the verified certificate chain can be
34+
retrieved from the ``PathValidationResult``.
4235
leaf_certificate_is_ev: ``True`` if the leaf certificate is Extended Validation, according to Mozilla.
4336
leaf_certificate_has_must_staple_extension: ``True`` if the OCSP must-staple extension is present in the leaf
4437
certificate.
@@ -66,7 +59,6 @@ class CertificateDeploymentAnalysisResult:
6659
"""
6760

6861
received_certificate_chain: List[Certificate]
69-
leaf_certificate_subject_matches_hostname: bool
7062
leaf_certificate_has_must_staple_extension: bool
7163
leaf_certificate_is_ev: bool
7264
leaf_certificate_signed_certificate_timestamps_count: Optional[int]
@@ -196,14 +188,16 @@ def perform(self) -> CertificateDeploymentAnalysisResult:
196188
# Try to generate the verified certificate chain using each trust store
197189
all_path_validation_results = []
198190
for trust_store in self.trust_stores_for_validation:
199-
path_validation_result = trust_store.verify_certificate_chain(self.server_certificate_chain_as_pem)
191+
path_validation_result = trust_store.verify_certificate_chain(
192+
self.server_certificate_chain_as_pem, self.server_hostname
193+
)
200194
all_path_validation_results.append(path_validation_result)
201195

202196
# Keep one trust store that was able to build the verified chain to then run additional checks
203197
trust_store_that_can_build_verified_chain = None
204198
verified_certificate_chain = None
205199

206-
# But first tort the path validation results so the same trust_store always get picked for a given server
200+
# But first sort the path validation results so the same trust_store always get picked for a given server
207201
def sort_function(path_validation: PathValidationResult) -> str:
208202
return path_validation.trust_store.name.lower()
209203

@@ -260,7 +254,6 @@ def sort_function(path_validation: PathValidationResult) -> str:
260254
# All done
261255
return CertificateDeploymentAnalysisResult(
262256
received_certificate_chain=received_certificate_chain,
263-
leaf_certificate_subject_matches_hostname=_certificate_matches_hostname(leaf_cert, self.server_hostname),
264257
leaf_certificate_has_must_staple_extension=has_ocsp_must_staple,
265258
leaf_certificate_is_ev=is_leaf_certificate_ev,
266259
leaf_certificate_signed_certificate_timestamps_count=number_of_scts,
@@ -272,28 +265,3 @@ def sort_function(path_validation: PathValidationResult) -> str:
272265
ocsp_response=final_ocsp_response,
273266
ocsp_response_is_trusted=is_ocsp_response_trusted,
274267
)
275-
276-
277-
def _certificate_matches_hostname(certificate: Certificate, server_hostname: str) -> bool:
278-
"""Verify that the certificate was issued for the given hostname."""
279-
# Extract the names from the certificate to create the properly-formatted dictionary
280-
try:
281-
cert_subject = certificate.subject
282-
except ValueError:
283-
# Cryptography could not parse the certificate https://github.com/nabla-c0d3/sslyze/issues/495
284-
return False
285-
286-
subj_alt_name_ext = parse_subject_alternative_name_extension(certificate)
287-
subj_alt_name_as_list = [("DNS", name) for name in subj_alt_name_ext.dns_names]
288-
subj_alt_name_as_list.extend([("IP Address", ip) for ip in subj_alt_name_ext.ip_addresses])
289-
290-
certificate_names = {
291-
"subject": (tuple([("commonName", name) for name in get_common_names(cert_subject)]),),
292-
"subjectAltName": tuple(subj_alt_name_as_list),
293-
}
294-
# CertificateError is raised on failure
295-
try:
296-
match_hostname(certificate_names, server_hostname) # type: ignore
297-
return True
298-
except CertificateError:
299-
return False

sslyze/plugins/certificate_info/_cli_connector.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,6 @@ def _cert_deployment_to_console_output(
101101
deployment_as_txt.append("")
102102
deployment_as_txt.append(cls._format_subtitle(f"Certificate #{index} - Trust"))
103103

104-
hostname_validation_text = (
105-
"OK - Certificate matches server hostname"
106-
if cert_deployment.leaf_certificate_subject_matches_hostname
107-
else "FAILED - Certificate does NOT match server hostname"
108-
)
109-
deployment_as_txt.append(cls._format_field("Hostname Validation:", hostname_validation_text))
110-
111104
# Path validation that was successfully tested
112105
for path_result in cert_deployment.path_validation_results:
113106
if path_result.was_validation_successful:
@@ -118,7 +111,7 @@ def _cert_deployment_to_console_output(
118111
path_txt = f"OK - Certificate is trusted{ev_txt}"
119112

120113
else:
121-
path_txt = f"FAILED - Certificate is NOT Trusted: {path_result.openssl_error_string}"
114+
path_txt = f"FAILED - Certificate is NOT Trusted: {path_result.validation_error}"
122115

123116
deployment_as_txt.append(
124117
cls._format_field(
@@ -277,8 +270,8 @@ def _get_basic_certificate_text(cls, certificate: Certificate) -> List[str]:
277270
cls._format_field("Common Name:", _get_subject_as_short_text(certificate)),
278271
cls._format_field("Issuer:", _get_issuer_as_short_text(certificate)),
279272
cls._format_field("Serial Number:", str(certificate.serial_number)),
280-
cls._format_field("Not Before:", certificate.not_valid_before.date().isoformat()),
281-
cls._format_field("Not After:", certificate.not_valid_after.date().isoformat()),
273+
cls._format_field("Not Before:", certificate.not_valid_before_utc.date().isoformat()),
274+
cls._format_field("Not After:", certificate.not_valid_after_utc.date().isoformat()),
282275
cls._format_field("Public Key Algorithm:", certificate.public_key().__class__.__name__),
283276
]
284277

sslyze/plugins/certificate_info/json_output.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ class _TrustStoreAsJson(BaseModelWithOrmMode):
229229
class _PathValidationResultAsJson(BaseModelWithOrmMode):
230230
trust_store: _TrustStoreAsJson
231231
verified_certificate_chain: Optional[List[_CertificateAsJson]]
232-
openssl_error_string: Optional[str]
232+
validation_error: Optional[str]
233233
was_validation_successful: bool
234234

235235

@@ -239,7 +239,6 @@ class _PathValidationResultAsJson(BaseModelWithOrmMode):
239239

240240
class _CertificateDeploymentAnalysisResultAsJson(BaseModelWithOrmMode):
241241
received_certificate_chain: List[_CertificateAsJson]
242-
leaf_certificate_subject_matches_hostname: bool
243242
leaf_certificate_has_must_staple_extension: bool
244243
leaf_certificate_is_ev: bool
245244
leaf_certificate_signed_certificate_timestamps_count: Optional[int]

0 commit comments

Comments
 (0)