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 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 ,
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,94 @@ 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_versions : List [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+
1635+ if (
1636+ not cipher_evaluation .ciphers_bad
1637+ and not cipher_evaluation .ciphers_phase_out
1638+ and not cipher_evaluation .ciphers_sufficient
1639+ ) or tls_versions == [TlsVersionEnum .TLS_1_3 ]:
1640+ cipher_order_status = CipherOrderStatus .na
1641+ return cipher_order_violation , cipher_order_status , cipher_order_score
1642+
1643+ tls_version = sorted ([t for t in tls_versions if t != TlsVersionEnum .TLS_1_3 ], key = lambda t : t .value )[- 1 ]
1644+
1645+ order_tuples = [
1646+ (
1647+ cipher_evaluation .ciphers_bad + cipher_evaluation .ciphers_phase_out + cipher_evaluation .ciphers_sufficient ,
1648+ cipher_evaluation .ciphers_good ,
1649+ ),
1650+ (cipher_evaluation .ciphers_bad + cipher_evaluation .ciphers_phase_out , cipher_evaluation .ciphers_sufficient ),
1651+ (cipher_evaluation .ciphers_bad , cipher_evaluation .ciphers_phase_out ),
1652+ ]
1653+ for expected_less_preferred , expected_more_preferred_list in order_tuples :
1654+ if cipher_order_status == CipherOrderStatus .bad :
1655+ break
1656+ for expected_more_preferred in expected_more_preferred_list :
1657+ print (
1658+ f"evaluating less { [s .name for s in expected_less_preferred ]} vs "
1659+ f"more { expected_more_preferred .name } TLS { tls_version } "
1660+ )
1661+ if not expected_less_preferred or not expected_more_preferred :
1662+ continue
1663+ preferred_suite = find_most_preferred_cipher_suite (
1664+ server_connectivity_info , tls_version , expected_less_preferred + [expected_more_preferred ]
1665+ )
1666+ if preferred_suite != expected_more_preferred :
1667+ # TODO: check which name to report
1668+ cipher_order_violation = [preferred_suite .name , expected_more_preferred .name ]
1669+ cipher_order_status = CipherOrderStatus .bad
1670+ cipher_order_score = scoring .WEB_TLS_CIPHER_ORDER_BAD
1671+ break
1672+
16111673 return cipher_order_violation , cipher_order_status , cipher_order_score
16121674
16131675
1676+ # TODO: maybe move to a utils module?
1677+ # adapted from sslyze.plugins.openssl_cipher_suites._test_cipher_suite.connect_with_cipher_suite
1678+ def find_most_preferred_cipher_suite (
1679+ server_connectivity_info : ServerConnectivityInfo , tls_version : TlsVersionEnum , cipher_suites : List [CipherSuite ]
1680+ ) -> CipherSuite :
1681+ suite_names = [suite .openssl_name for suite in cipher_suites ]
1682+ requires_legacy_openssl = True
1683+ if tls_version == TlsVersionEnum .TLS_1_2 :
1684+ # For TLS 1.2, we need to pick the right version of OpenSSL depending on which cipher suite
1685+ requires_legacy_openssl = any (
1686+ [WorkaroundForTls12ForCipherSuites .requires_legacy_openssl (name ) for name in suite_names ]
1687+ )
1688+ elif tls_version == TlsVersionEnum .TLS_1_3 :
1689+ requires_legacy_openssl = False
1690+
1691+ ssl_connection = server_connectivity_info .get_preconfigured_tls_connection (
1692+ override_tls_version = tls_version , should_use_legacy_openssl = requires_legacy_openssl
1693+ )
1694+ _set_cipher_suite_string (tls_version , ":" .join (suite_names ), ssl_connection .ssl_client )
1695+
1696+ try :
1697+ ssl_connection .connect ()
1698+ except ClientCertificateRequested :
1699+ pass
1700+ except (ServerRejectedTlsHandshake , TlsHandshakeTimedOut ) as exc :
1701+ raise TLSException (
1702+ f"Unable to connect with (previously accepted) cipher suites { suite_names } to determine cipher order: { exc } "
1703+ )
1704+ finally :
1705+ ssl_connection .close ()
1706+
1707+ selected_cipher = CipherSuitesRepository .get_cipher_suite_with_openssl_name (
1708+ tls_version , ssl_connection .ssl_client .get_current_cipher_name ()
1709+ )
1710+ print (f"from CS { suite_names } selected { selected_cipher } " )
1711+ return selected_cipher
1712+
1713+
16141714def do_web_http (af_ip_pairs , url , task , * args , ** kwargs ):
16151715 """
16161716 Start all the HTTP related checks for the web test.
0 commit comments