2727from django .core .cache import cache
2828from django .db import transaction
2929from nassl .ephemeral_key_info import DhEphemeralKeyInfo , EcDhEphemeralKeyInfo , OpenSslEvpPkeyEnum
30+ from nassl .ssl_client import ClientCertificateRequested
3031from sslyze import (
3132 Scanner ,
3233 ServerScanRequest ,
3839 ServerNetworkConfiguration ,
3940 ProtocolWithOpportunisticTlsEnum ,
4041 ScanCommandsExtraArguments ,
41- CertificateInfoExtraArgument , CipherSuite ,
42+ CertificateInfoExtraArgument ,
43+ CipherSuite ,
4244)
45+ from sslyze .errors import ServerTlsConfigurationNotSupported , ServerRejectedTlsHandshake , TlsHandshakeTimedOut
4346
4447from sslyze .plugins .certificate_info ._certificate_utils import (
4548 parse_subject_alternative_name_extension ,
4649 get_common_names ,
4750)
51+ from sslyze .plugins .openssl_cipher_suites ._test_cipher_suite import _set_cipher_suite_string
52+ from sslyze .plugins .openssl_cipher_suites ._tls12_workaround import WorkaroundForTls12ForCipherSuites
53+ from sslyze .plugins .openssl_cipher_suites .cipher_suites import CipherSuitesRepository
54+ from sslyze .server_connectivity import ServerConnectivityInfo
4855
4956from checks import categories , scoring
5057from checks .http_client import http_get_ip
6067 WebTestTls ,
6168 ZeroRttStatus ,
6269)
63- from checks .scoring import Score
6470from checks .tasks import SetupUnboundContext
6571from checks .tasks .dispatcher import check_registry , post_callback_hook
6672from checks .tasks .http_headers import (
@@ -1374,6 +1380,7 @@ def has_daneTA(tlsa_records):
13741380 return True
13751381 return False
13761382
1383+
13771384def check_web_tls (url , af_ip_pair = None , * args , ** kwargs ):
13781385 """
13791386 Check the webserver's TLS configuration.
@@ -1398,7 +1405,16 @@ def check_web_tls(url, af_ip_pair=None, *args, **kwargs):
13981405 prots_bad , prots_phase_out , prots_good , prots_sufficient , prots_score = evaluate_tls_protocols (prots_accepted )
13991406 dh_param , ec_param , fs_bad , fs_phase_out , fs_score = evaluate_tls_fs_params (ciphers_accepted )
14001407 cipher_evaluation = TLSCipherEvaluation .from_ciphers_accepted (ciphers_accepted )
1401- cipher_order_violation , cipher_order_status , cipher_order_score = test_cipher_order (ciphers_accepted )
1408+ # TODO: pick best TLS version
1409+ cipher_order_violation , cipher_order_status , cipher_order_score = test_cipher_order (
1410+ ServerConnectivityInfo (
1411+ server_location = result .server_location ,
1412+ network_configuration = result .network_configuration ,
1413+ tls_probing_result = result .connectivity_result ,
1414+ ),
1415+ prots_accepted [0 ],
1416+ cipher_evaluation ,
1417+ )
14021418
14031419 ocsp_status = OcspStatus .ok
14041420 if any (
@@ -1585,15 +1601,18 @@ def from_ciphers_accepted(cls, ciphers_accepted: List[CipherSuiteAcceptedByServe
15851601 elif suite .cipher_suite .name in CIPHERS_PHASE_OUT :
15861602 ciphers_phase_out .append (suite .cipher_suite )
15871603 else :
1588- ciphers_bad .append (f" { suite .cipher_suite . openssl_name } ( { suite . cipher_suite . name } )" )
1604+ ciphers_bad .append (suite .cipher_suite )
15891605 return cls (
1590- ciphers_good = ciphers_good , ciphers_sufficient = ciphers_sufficient , ciphers_phase_out = ciphers_phase_out ,
1606+ ciphers_good = ciphers_good ,
1607+ ciphers_sufficient = ciphers_sufficient ,
1608+ ciphers_phase_out = ciphers_phase_out ,
15911609 ciphers_bad = ciphers_bad ,
15921610 ciphers_good_str = cls ._format_str (ciphers_good ),
15931611 ciphers_sufficient_str = cls ._format_str (ciphers_sufficient ),
15941612 ciphers_phase_out_str = cls ._format_str (ciphers_phase_out ),
15951613 ciphers_bad_str = cls ._format_str (ciphers_bad ),
15961614 )
1615+
15971616 @staticmethod
15981617 def _format_str (suites : List [CipherSuite ]) -> List [str ]:
15991618 # TODO: remove IANA name, just here for debugging now
@@ -1604,13 +1623,99 @@ def score(self) -> scoring.Score:
16041623 return scoring .WEB_TLS_SUITES_BAD if self .ciphers_bad else scoring .WEB_TLS_SUITES_GOOD
16051624
16061625
1607- def test_cipher_order (cipher_evaluation : TLSCipherEvaluation ) -> Tuple [List [str ], CipherOrderStatus , scoring .Score ]:
1626+ def test_cipher_order (
1627+ server_connectivity_info : ServerConnectivityInfo ,
1628+ tls_version : TlsVersionEnum ,
1629+ cipher_evaluation : TLSCipherEvaluation ,
1630+ ) -> Tuple [List [str ], CipherOrderStatus , scoring .Score ]:
16081631 cipher_order_violation = []
1609- cipher_order_status = CipherOrderStatus .na
1632+ cipher_order_status = CipherOrderStatus .good
16101633 cipher_order_score = scoring .WEB_TLS_CIPHER_ORDER_OK
1634+ if (
1635+ not cipher_evaluation .ciphers_bad
1636+ and not cipher_evaluation .ciphers_phase_out_str
1637+ and not cipher_evaluation .ciphers_sufficient
1638+ ):
1639+ cipher_order_status = CipherOrderStatus .na
1640+ return cipher_order_violation , cipher_order_status , cipher_order_score
1641+
1642+ order_tuples = [
1643+ (
1644+ cipher_evaluation .ciphers_bad + cipher_evaluation .ciphers_phase_out + cipher_evaluation .ciphers_sufficient ,
1645+ cipher_evaluation .ciphers_good ,
1646+ ),
1647+ (cipher_evaluation .ciphers_bad + cipher_evaluation .ciphers_phase_out , cipher_evaluation .ciphers_sufficient ),
1648+ (cipher_evaluation .ciphers_bad , cipher_evaluation .ciphers_phase_out ),
1649+ ]
1650+ for expected_less_preferred , expected_more_preferred in order_tuples :
1651+ print (
1652+ f"evaluating less { [s .openssl_name for s in expected_less_preferred ]} vs "
1653+ f"more { [s .openssl_name for s in expected_more_preferred ]} TLS { tls_version } "
1654+ )
1655+ if not expected_less_preferred or not expected_more_preferred :
1656+ continue
1657+ preferred_suite = find_most_preferred_cipher_suite (
1658+ server_connectivity_info , tls_version , expected_less_preferred + expected_more_preferred
1659+ )
1660+ if preferred_suite not in expected_more_preferred :
1661+ cipher_order_violation = [preferred_suite .openssl_name ] # TODO: check which name to report
1662+ cipher_order_status = CipherOrderStatus .bad
1663+ cipher_order_score = scoring .WEB_TLS_CIPHER_ORDER_BAD
1664+
16111665 return cipher_order_violation , cipher_order_status , cipher_order_score
16121666
16131667
1668+ # TODO: maybe move to a utils module?
1669+ # adapted from sslyze.plugins.openssl_cipher_suites._test_cipher_suite.connect_with_cipher_suite
1670+ def find_most_preferred_cipher_suite (
1671+ server_connectivity_info : ServerConnectivityInfo , tls_version : TlsVersionEnum , cipher_suites : List [CipherSuite ]
1672+ ) -> CipherSuite :
1673+ suite_names = [suite .openssl_name for suite in cipher_suites ]
1674+ requires_legacy_openssl = True
1675+ if tls_version == TlsVersionEnum .TLS_1_2 :
1676+ # For TLS 1.2, we need to pick the right version of OpenSSL depending on which cipher suite
1677+ requires_legacy_openssl = any (
1678+ [WorkaroundForTls12ForCipherSuites .requires_legacy_openssl (name ) for name in suite_names ]
1679+ )
1680+ elif tls_version == TlsVersionEnum .TLS_1_3 :
1681+ requires_legacy_openssl = False
1682+
1683+ ssl_connection = server_connectivity_info .get_preconfigured_tls_connection (
1684+ override_tls_version = tls_version , should_use_legacy_openssl = requires_legacy_openssl
1685+ )
1686+ _set_cipher_suite_string (tls_version , ":" .join (suite_names ), ssl_connection .ssl_client )
1687+
1688+ try :
1689+ # Perform the SSL handshake
1690+ ssl_connection .connect ()
1691+
1692+ except ServerTlsConfigurationNotSupported :
1693+ # SSLyze rejected the handshake because the server's DH config was too insecure; this means the
1694+ # cipher suite is actually supported
1695+ pass
1696+
1697+ except ClientCertificateRequested :
1698+ # When the handshake failed due to ClientCertificateRequested
1699+ pass
1700+ except ServerRejectedTlsHandshake :
1701+ return False
1702+
1703+ except TlsHandshakeTimedOut :
1704+ # Sometimes triggered by servers that don't support (at all) a specific version of TLS
1705+ # Amazon Cloudfront does that with TLS 1.3
1706+ # There's no easy way to differentiate this error from a network glitch/timeout
1707+ return False
1708+
1709+ finally :
1710+ ssl_connection .close ()
1711+
1712+ selected_cipher = CipherSuitesRepository .get_cipher_suite_with_openssl_name (
1713+ tls_version , ssl_connection .ssl_client .get_current_cipher_name ()
1714+ )
1715+ print (f"from CS { suite_names } selected { selected_cipher } " )
1716+ return selected_cipher
1717+
1718+
16141719def do_web_http (af_ip_pairs , url , task , * args , ** kwargs ):
16151720 """
16161721 Start all the HTTP related checks for the web test.
0 commit comments