def create(): cmd_line = ParsedCommandLine( invalid_servers=[ InvalidServerStringError(server_string="www.badpãrsing.com", error_message="Pãrsing err") ], servers_to_scans=[ ( ServerNetworkLocationViaDirectConnectionFactory.create(), ServerNetworkConfiguration( tls_server_name_indication="a.com"), ), ( ServerNetworkLocationViaHttpProxyFactory.create(), ServerNetworkConfiguration( tls_server_name_indication="a.com"), ), ], scan_commands={ ScanCommand.TLS_COMPRESSION, ScanCommand.HTTP_HEADERS }, scan_commands_extra_arguments={}, json_file_out=None, should_disable_console_output=False, per_server_concurrent_connections_limit=None, concurrent_server_scans_limit=None, ) return cmd_line
def test_works_when_client_auth_succeeded(self): # Given a server that requires client authentication with LegacyOpenSslServer( client_auth_config=ClientAuthConfigEnum.REQUIRED) as server: server_location = ServerNetworkLocation( hostname=server.hostname, ip_address=server.ip_address, port=server.port) # And sslyze provides a client certificate network_config = ServerNetworkConfiguration( tls_server_name_indication=server.hostname, tls_client_auth_credentials=ClientAuthenticationCredentials( certificate_chain_path=server.get_client_certificate_path( ), key_path=server.get_client_key_path()), ) server_info = check_connectivity_to_server_and_return_info( server_location, network_config) # When scanning for HTTP headers, it succeeds result: HttpHeadersScanResult = HttpHeadersImplementation.scan_server( server_info) assert not result.strict_transport_security_header assert not result.expect_ct_header
def test_works_when_client_auth_succeeded(self): # Given a server that does NOT support SCSV and that requires client authentication with LegacyOpenSslServer( client_auth_config=ClientAuthConfigEnum.REQUIRED) as server: server_location = ServerNetworkLocationViaDirectConnection( hostname=server.hostname, ip_address=server.ip_address, port=server.port) # And sslyze provides a client certificate network_config = ServerNetworkConfiguration( tls_server_name_indication=server.hostname, tls_client_auth_credentials=ClientAuthenticationCredentials( certificate_chain_path=server.get_client_certificate_path( ), key_path=server.get_client_key_path()), ) server_info = ServerConnectivityTester().perform( server_location, network_config) # When testing for SCSV, it succeeds result: FallbackScsvScanResult = FallbackScsvImplementation.scan_server( server_info) # And the server is reported as NOT supporting SCSV assert not result.supports_fallback_scsv
def test_works_when_client_auth_succeeded(self): # Given a server that is vulnerable and that requires client authentication with LegacyOpenSslServer( client_auth_config=ClientAuthConfigEnum.REQUIRED) as server: server_location = ServerNetworkLocationViaDirectConnection( hostname=server.hostname, ip_address=server.ip_address, port=server.port) # And sslyze provides a client certificate network_config = ServerNetworkConfiguration( tls_server_name_indication=server.hostname, tls_client_auth_credentials=ClientAuthenticationCredentials( certificate_chain_path=server.get_client_certificate_path( ), key_path=server.get_client_key_path()), ) server_info = ServerConnectivityTester().perform( server_location, network_config) # When testing for insecure reneg, it succeeds result: SessionRenegotiationScanResult = SessionRenegotiationImplementation.scan_server( server_info) # And the results are correct assert result.supports_secure_renegotiation assert result.is_vulnerable_to_client_renegotiation_dos
def create( server_location: Optional[ServerNetworkLocation] = None, tls_probing_result: Optional[ServerTlsProbingResult] = None, ) -> ServerConnectivityInfo: if server_location: final_server_location = server_location else: final_server_location = ServerNetworkLocationViaDirectConnectionFactory.create( ) if tls_probing_result: final_tls_probing_result = tls_probing_result else: final_tls_probing_result = ServerTlsProbingResult( highest_tls_version_supported=TlsVersionEnum.TLS_1_2, cipher_suite_supported="AES", client_auth_requirement=ClientAuthRequirementEnum.DISABLED, ) return ServerConnectivityInfo( server_location=final_server_location, network_configuration=ServerNetworkConfiguration( tls_server_name_indication=final_server_location.hostname), tls_probing_result=final_tls_probing_result, )
def get_supported_tls(self, highest_supported): supported = [highest_supported] for version, method in { "SSL_2_0": TlsVersionEnum.SSLV2, "SSL_3_0": TlsVersionEnum.SSLV3, "TLS_1_0": TlsVersionEnum.TLSV1, "TLS_1_1": TlsVersionEnum.TLSV1_1, "TLS_1_2": TlsVersionEnum.TLSV1_2, }.items(): # Only test SSL/TLS connections with lesser versions if highest_supported == version: break try: # Attempt connection # If connection fails, exception will be raised, causing the failure to be # logged and the version to not be appended to the supported list ctx = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup( self.domain, 443) cfg = ServerNetworkConfiguration(self.domain) connx = SslConnection(ctx, cfg, method, True) connx.connect(self.domain) supported.append(version) except Exception as e: logging.info( f"Failed to connect using %{version}: ({type(e)}) - {e}") return supported
def test_works_when_client_auth_succeeded(self): # Given a server that requires client authentication with ModernOpenSslServer( client_auth_config=ClientAuthConfigEnum.REQUIRED) as server: server_location = ServerNetworkLocation( hostname=server.hostname, ip_address=server.ip_address, port=server.port) # And sslyze provides a client certificate network_config = ServerNetworkConfiguration( tls_server_name_indication=server.hostname, tls_client_auth_credentials=ClientAuthenticationCredentials( certificate_chain_path=server.get_client_certificate_path( ), key_path=server.get_client_key_path()), ) server_info = check_connectivity_to_server_and_return_info( server_location, network_config) # When testing for resumption, it succeeds result: SessionResumptionSupportScanResult = SessionResumptionSupportImplementation.scan_server( server_info) assert result.session_id_successful_resumptions_count assert result.session_id_resumption_result == TlsResumptionSupportEnum.FULLY_SUPPORTED
def create(): return ConnectionToServerFailed( server_location=ServerNetworkLocationViaDirectConnectionFactory. create(), network_configuration=ServerNetworkConfiguration( tls_server_name_indication="a.com"), error_message="This is ân éè error", )
def test_smtp(self): # Given an SMTP server to scan hostname = "smtp.gmail.com" server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(hostname, 587) network_configuration = ServerNetworkConfiguration( tls_server_name_indication=hostname, tls_opportunistic_encryption=ProtocolWithOpportunisticTlsEnum.SMTP ) server_info = ServerConnectivityTester().perform(server_location, network_configuration) # When scanning for cipher suites, it succeeds result: CipherSuitesScanResult = Tlsv12ScanImplementation.scan_server(server_info) assert result.accepted_cipher_suites
def test_xmpp_but_server_rejected_opportunistic_tls(self): # Given an XMPP server hostname = "jabber.org" server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(hostname=hostname, port=5222) network_configuration = ServerNetworkConfiguration( # But we provide a wrong XMPP setting xmpp_to_hostname="lol.lol", tls_server_name_indication=hostname, tls_opportunistic_encryption=ProtocolWithOpportunisticTlsEnum.XMPP, ) # When testing connectivity, it fails with the right error with pytest.raises(ServerRejectedOpportunisticTlsNegotiation): ServerConnectivityTester().perform(server_location, network_configuration)
def test(self, hostname, port, protocol): # Given some server using a non-HTTP protocol with Opportunistic TLS server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(hostname, port) network_configuration = ServerNetworkConfiguration( tls_server_name_indication=hostname, tls_opportunistic_encryption=protocol ) # When testing connectivity against it server_info = ServerConnectivityTester().perform(server_location, network_configuration) # It succeeds assert server_info.tls_probing_result assert server_info.tls_probing_result.client_auth_requirement assert server_info.tls_probing_result.highest_tls_version_supported assert server_info.tls_probing_result.cipher_suite_supported
def create( server_location: Optional[ServerNetworkLocation] = None, scan_status: ServerScanStatusEnum = ServerScanStatusEnum.COMPLETED, scan_result: Optional[AllScanCommandsAttempts] = None, ) -> ServerScanResult: final_server_location: ServerNetworkLocation if server_location is None: final_server_location = ServerNetworkLocationViaDirectConnectionFactory.create( ) else: final_server_location = server_location network_configuration = ServerNetworkConfiguration.default_for_server_location( final_server_location) connectivity_result: Optional[ServerTlsProbingResult] if scan_status == ServerScanStatusEnum.COMPLETED: connectivity_status = ServerConnectivityStatusEnum.COMPLETED connectivity_error_trace = None connectivity_result = ServerTlsProbingResultFactory.create() if scan_result is None: final_scan_result = AllScanCommandsAttemptsFactory.create() else: final_scan_result = scan_result elif scan_status == ServerScanStatusEnum.ERROR_NO_CONNECTIVITY: connectivity_error_trace = TracebackExceptionFactory.create() connectivity_status = ServerConnectivityStatusEnum.ERROR connectivity_result = None final_scan_result = None else: raise ValueError("Should never happen") return ServerScanResult( uuid=uuid4(), server_location=final_server_location, network_configuration=network_configuration, connectivity_status=connectivity_status, connectivity_error_trace=connectivity_error_trace, connectivity_result=connectivity_result, scan_status=scan_status, scan_result=final_scan_result, )
def test(self, hostname, port, protocol): # Given some server using a non-HTTP protocol with Opportunistic TLS server_location = ServerNetworkLocation(hostname, port) network_configuration = ServerNetworkConfiguration( tls_server_name_indication=hostname, tls_opportunistic_encryption=protocol) # When testing connectivity against it tls_probing_result = check_connectivity_to_server( server_location=server_location, network_configuration=network_configuration, ) # It succeeds assert tls_probing_result assert tls_probing_result.client_auth_requirement assert tls_probing_result.highest_tls_version_supported assert tls_probing_result.cipher_suite_supported # And the result can be converted to JSON tls_probing_result_as_json = _ServerTlsProbingResultAsJson.from_orm( tls_probing_result) assert tls_probing_result_as_json.json()
def perform( self, server_location: ServerNetworkLocation, network_configuration: Optional[ServerNetworkConfiguration] = None ) -> ServerConnectivityInfo: """Attempt to perform a full SSL/TLS handshake with the server. This method will ensure that the server can be reached, and will also identify one SSL/TLS version and one cipher suite that is supported by the server. Args: server_location network_configuration Returns: An object encapsulating all the information needed to connect to the server, to be passed to a `Scanner` in order to run scan commands against the server. Raises: ServerConnectivityError: If the server was not reachable or an SSL/TLS handshake could not be completed. """ if network_configuration is None: final_network_config = ServerNetworkConfiguration.default_for_server_location( server_location) else: final_network_config = network_configuration # Try to complete an SSL handshake to figure out the SSL version and cipher supported by the server highest_tls_version_supported = None cipher_suite_supported = None client_auth_requirement = ClientAuthRequirementEnum.DISABLED # TODO(AD): Switch to using the protocol discovery logic available in OpenSSL 1.1.0 with TLS_client_method() for tls_version in [ OpenSslVersionEnum.TLSV1_3, OpenSslVersionEnum.TLSV1_2, OpenSslVersionEnum.TLSV1_1, OpenSslVersionEnum.TLSV1, OpenSslVersionEnum.SSLV3, ]: # First try the default cipher list, and then all ciphers for cipher_list in [None, "ALL:COMPLEMENTOFALL:-PSK:-SRP"]: ssl_connection = SslConnection( server_location=server_location, network_configuration=final_network_config, tls_version=tls_version, should_ignore_client_auth=False, ) if cipher_list: if tls_version == OpenSslVersionEnum.TLSV1_3: # Skip the second attempt with all ciphers enabled as these ciphers don't exist in TLS 1.3 continue ssl_connection.ssl_client.set_cipher_list(cipher_list) try: # Only do one attempt when testing connectivity ssl_connection.connect(should_retry_connection=False) highest_tls_version_supported = tls_version cipher_suite_supported = ssl_connection.ssl_client.get_current_cipher_name( ) except ClientCertificateRequested: # Connection successful but the servers wants a client certificate which wasn't supplied to sslyze # Store the SSL version and cipher list that is supported highest_tls_version_supported = tls_version cipher_suite_supported = cipher_list # Close the current connection and try again but ignore client authentication ssl_connection.close() # Try a new connection to see if client authentication is optional ssl_connection_auth = SslConnection( server_location=server_location, network_configuration=final_network_config, tls_version=tls_version, should_ignore_client_auth=True, ) if cipher_list: ssl_connection_auth.ssl_client.set_cipher_list( cipher_list) try: ssl_connection_auth.connect( should_retry_connection=False) cipher_suite_supported = ssl_connection_auth.ssl_client.get_current_cipher_name( ) client_auth_requirement = ClientAuthRequirementEnum.OPTIONAL # If client authentication is required, we either get a ClientCertificateRequested except ClientCertificateRequested: client_auth_requirement = ClientAuthRequirementEnum.REQUIRED # Or a ServerRejectedTlsHandshake except ServerRejectedTlsHandshake: client_auth_requirement = ClientAuthRequirementEnum.REQUIRED finally: ssl_connection_auth.close() except TlsHandshakeFailed: # This TLS version did not work; keep going pass finally: ssl_connection.close() if cipher_suite_supported: # A handshake was successful break if highest_tls_version_supported is None or cipher_suite_supported is None: raise ServerTlsConfigurationNotSupported( server_location=server_location, network_configuration=final_network_config, error_message= "Probing failed: could not find a TLS version and cipher suite supported by the server", ) tls_probing_result = ServerTlsProbingResult( highest_tls_version_supported=highest_tls_version_supported, cipher_suite_supported=cipher_suite_supported, client_auth_requirement=client_auth_requirement, ) return ServerConnectivityInfo( server_location=server_location, network_configuration=final_network_config, tls_probing_result=tls_probing_result, )
def parse_command_line(self) -> ParsedCommandLine: """Parses the command line used to launch SSLyze. """ (args_command_list, args_target_list) = self._parser.parse_args() if args_command_list.update_trust_stores: # Just update the trust stores and do nothing TrustStoresRepository.update_default() raise TrustStoresUpdateCompleted() # Handle the --targets_in command line and fill args_target_list if args_command_list.targets_in: if args_target_list: raise CommandLineParsingError( "Cannot use --targets_list and specify targets within the command line." ) try: # Read targets from a file with open(args_command_list.targets_in) as f: for target in f.readlines(): if target.strip(): # Ignore empty lines if not target.startswith( "#"): # Ignore comment lines args_target_list.append(target.strip()) except IOError: raise CommandLineParsingError( "Can't read targets from input file '{}.".format( args_command_list.targets_in)) if not args_target_list: raise CommandLineParsingError("No targets to scan.") # Handle the --regular command line parameter as a shortcut if self._parser.has_option("--regular"): if getattr(args_command_list, "regular"): setattr(args_command_list, "regular", False) for cmd in self.REGULAR_CMD: setattr(args_command_list, cmd, True) # Handle JSON settings should_print_json_to_console = False json_path_out: Optional[Path] = None if args_command_list.json_file: if args_command_list.json_file == "-": if args_command_list.quiet: raise CommandLineParsingError( "Cannot use --quiet with --json_out -.") should_print_json_to_console = True else: json_path_out = Path(args_command_list.json_file).absolute() # Sanity checks on the client cert options client_auth_creds = None if bool(args_command_list.cert) ^ bool(args_command_list.key): raise CommandLineParsingError( "No private key or certificate file were given. See --cert and --key." ) elif args_command_list.cert: # Private key formats if args_command_list.keyform == "DER": key_type = OpenSslFileTypeEnum.ASN1 elif args_command_list.keyform == "PEM": key_type = OpenSslFileTypeEnum.PEM else: raise CommandLineParsingError( "--keyform should be DER or PEM.") # Let's try to open the cert and key files try: client_auth_creds = ClientAuthenticationCredentials( certificate_chain_path=Path(args_command_list.cert), key_path=Path(args_command_list.key), key_password=args_command_list.keypass, key_type=key_type, ) except ValueError as e: raise CommandLineParsingError( "Invalid client authentication settings: {}.".format( e.args[0])) # HTTP CONNECT proxy http_proxy_settings = None if args_command_list.https_tunnel: try: http_proxy_settings = HttpProxySettings.from_url( args_command_list.https_tunnel) except ValueError as e: raise CommandLineParsingError( "Invalid proxy URL for --https_tunnel: {}.".format( e.args[0])) # Create the server location objects for each specified servers good_servers: List[Tuple[ServerNetworkLocation, ServerNetworkConfiguration]] = [] invalid_server_strings: List[InvalidServerStringError] = [] for server_string in args_target_list: try: # Parse the string supplied via the CLI for this server hostname, ip_address, port = CommandLineServerStringParser.parse_server_string( server_string) except InvalidServerStringError as e: # The server string is malformed invalid_server_strings.append(e) continue # If not port number was supplied, assume 443 final_port = port if port else 443 # Figure out how we're going to connect to the server server_location: ServerNetworkLocation if http_proxy_settings: # Connect to the server via an HTTP proxy # A limitation when using the CLI is that only one http_proxy_settings can be specified for all servers server_location = ServerNetworkLocationViaHttpProxy( hostname=hostname, port=final_port, http_proxy_settings=http_proxy_settings) else: # Connect to the server directly if ip_address: server_location = ServerNetworkLocationViaDirectConnection( hostname=hostname, port=final_port, ip_address=ip_address) else: # No IP address supplied - do a DNS lookup try: server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup( hostname=hostname, port=final_port) except ServerHostnameCouldNotBeResolved: invalid_server_strings.append( InvalidServerStringError( server_string=f"{hostname}:{final_port}", error_message= f"Could not resolve hostname {hostname}", )) continue # Figure out extra network config for this server # Opportunistic TLS opportunistic_tls: Optional[ ProtocolWithOpportunisticTlsEnum] = None if args_command_list.starttls: if args_command_list.starttls == "auto": # Special value to auto-derive the protocol from the port number opportunistic_tls = ProtocolWithOpportunisticTlsEnum.from_default_port( final_port) elif args_command_list.starttls in _STARTTLS_PROTOCOL_DICT: opportunistic_tls = _STARTTLS_PROTOCOL_DICT[ args_command_list.starttls] else: raise CommandLineParsingError(self.START_TLS_USAGE) try: sni_hostname = args_command_list.sni if args_command_list.sni else hostname network_config = ServerNetworkConfiguration( tls_opportunistic_encryption=opportunistic_tls, tls_server_name_indication=sni_hostname, tls_client_auth_credentials=client_auth_creds, xmpp_to_hostname=args_command_list.xmpp_to, ) good_servers.append((server_location, network_config)) except InvalidServerNetworkConfigurationError as e: raise CommandLineParsingError(e.args[0]) # Figure out global network settings concurrent_server_scans_limit = None per_server_concurrent_connections_limit = None if args_command_list.https_tunnel: # All the connections will go through a single proxy; only scan one server at a time to not DOS the proxy concurrent_server_scans_limit = 1 if args_command_list.slow_connection: # Go easy on the servers; only open 2 concurrent connections against each server per_server_concurrent_connections_limit = 2 # Figure out the scan commands that are enabled scan_commands: Set[ScanCommandType] = set() scan_commands_extra_arguments: ScanCommandExtraArgumentsDict = {} for scan_command in ScanCommandsRepository.get_all_scan_commands(): cli_connector_cls = ScanCommandsRepository.get_implementation_cls( scan_command).cli_connector_cls is_scan_cmd_enabled, extra_args = cli_connector_cls.find_cli_options_in_command_line( args_command_list.__dict__) if is_scan_cmd_enabled: scan_commands.add(scan_command) if extra_args: scan_commands_extra_arguments[ scan_command] = extra_args # type: ignore return ParsedCommandLine( invalid_servers=invalid_server_strings, servers_to_scans=good_servers, scan_commands=scan_commands, scan_commands_extra_arguments=scan_commands_extra_arguments, should_print_json_to_console=should_print_json_to_console, json_path_out=json_path_out, should_disable_console_output=args_command_list.quiet or args_command_list.json_file == "-", concurrent_server_scans_limit=concurrent_server_scans_limit, per_server_concurrent_connections_limit= per_server_concurrent_connections_limit, )
def perform( self, server_location: ServerNetworkLocation, network_configuration: Optional[ServerNetworkConfiguration] = None ) -> ServerConnectivityInfo: """Attempt to perform a full SSL/TLS handshake with the server. This method will ensure that the server can be reached, and will also identify one SSL/TLS version and one cipher suite that is supported by the server. Args: server_location network_configuration Returns: An object encapsulating all the information needed to connect to the server, to be passed to a `Scanner` in order to run scan commands against the server. Raises: ServerConnectivityError: If the server was not reachable or an SSL/TLS handshake could not be completed. """ if network_configuration is None: final_network_config = ServerNetworkConfiguration.default_for_server_location( server_location) else: final_network_config = network_configuration # Try to complete an SSL handshake to figure out the SSL version and cipher supported by the server highest_tls_version_supported = None cipher_suite_supported = None client_auth_requirement = ClientAuthRequirementEnum.DISABLED # TODO(AD): Switch to using the protocol discovery logic available in OpenSSL 1.1.0 with TLS_client_method() for tls_version in [ TlsVersionEnum.TLS_1_3, TlsVersionEnum.TLS_1_2, TlsVersionEnum.TLS_1_1, TlsVersionEnum.TLS_1_0, TlsVersionEnum.SSL_3_0, ]: # First try the default cipher list, and then all ciphers for cipher_list in [None, "ALL:COMPLEMENTOFALL:-PSK:-SRP"]: ssl_connection = SslConnection( server_location=server_location, network_configuration=final_network_config, tls_version=tls_version, should_ignore_client_auth=False, ) if cipher_list: if tls_version == TlsVersionEnum.TLS_1_3: # Skip the second attempt with all ciphers enabled as these ciphers don't exist in TLS 1.3 continue ssl_connection.ssl_client.set_cipher_list(cipher_list) try: # Only do one attempt when testing connectivity ssl_connection.connect(should_retry_connection=False) highest_tls_version_supported = tls_version cipher_suite_supported = ssl_connection.ssl_client.get_current_cipher_name( ) except ClientCertificateRequested: # Connection successful but the servers wants a client certificate which wasn't supplied to sslyze # Store the SSL version and cipher list that is supported highest_tls_version_supported = tls_version cipher_suite_supported = cipher_list # Close the current connection and try again but ignore client authentication ssl_connection.close() # Try a new connection to see if client authentication is optional ssl_connection_auth = SslConnection( server_location=server_location, network_configuration=final_network_config, tls_version=tls_version, should_ignore_client_auth=True, ) if cipher_list: ssl_connection_auth.ssl_client.set_cipher_list( cipher_list) try: ssl_connection_auth.connect( should_retry_connection=False) cipher_suite_supported = ssl_connection_auth.ssl_client.get_current_cipher_name( ) client_auth_requirement = ClientAuthRequirementEnum.OPTIONAL # If client authentication is required, we either get a ClientCertificateRequested except ClientCertificateRequested: client_auth_requirement = ClientAuthRequirementEnum.REQUIRED # Or a ServerRejectedTlsHandshake except ServerRejectedTlsHandshake: client_auth_requirement = ClientAuthRequirementEnum.REQUIRED finally: ssl_connection_auth.close() except TlsHandshakeFailed: # This TLS version did not work; keep going pass except (OSError, _nassl.OpenSSLError) as e: # If these errors get propagated here, it means they're not part of the known/normal errors that # can happen when trying to connect to a server and defined in tls_connection.py # Hence we re-raise these as "unknown" connection errors; might be caused by bad connectivity to # the server (random disconnects, etc.) and the scan against this server should not be performed raise ConnectionToServerFailed( server_location=server_location, network_configuration=final_network_config, error_message= f'Unexpected connection error: "{e.args}"', ) finally: ssl_connection.close() if cipher_suite_supported: # A handshake was successful break if highest_tls_version_supported is None or cipher_suite_supported is None: raise ServerTlsConfigurationNotSupported( server_location=server_location, network_configuration=final_network_config, error_message= "Probing failed: could not find a TLS version and cipher suite supported by the server", ) # Check if ECDH key exchanges are supported is_ecdh_key_exchange_supported = False if "ECDH" in cipher_suite_supported: is_ecdh_key_exchange_supported = True else: if highest_tls_version_supported.value >= TlsVersionEnum.TLS_1_2.value: ssl_connection = SslConnection( server_location=server_location, network_configuration=final_network_config, tls_version=highest_tls_version_supported, should_use_legacy_openssl=False, should_ignore_client_auth=True, ) if not isinstance(ssl_connection.ssl_client, SslClient): raise RuntimeError( "Should never happen: specified should_use_legacy_openssl=False but didn't get the modern" " SSL client") # Set the right elliptic curve cipher suites enable_ecdh_cipher_suites(highest_tls_version_supported, ssl_connection.ssl_client) try: ssl_connection.connect(should_retry_connection=False) is_ecdh_key_exchange_supported = True except ClientCertificateRequested: is_ecdh_key_exchange_supported = True except ServerRejectedTlsHandshake: is_ecdh_key_exchange_supported = False finally: ssl_connection.close() tls_probing_result = ServerTlsProbingResult( highest_tls_version_supported=highest_tls_version_supported, cipher_suite_supported=cipher_suite_supported, client_auth_requirement=client_auth_requirement, supports_ecdh_key_exchange=is_ecdh_key_exchange_supported, ) return ServerConnectivityInfo( server_location=server_location, network_configuration=final_network_config, tls_probing_result=tls_probing_result, )