def process_task( self, server_info: ServerConnectivityInfo, scan_command: PluginScanCommand ) -> "EarlyDataScanResult": if not isinstance(scan_command, EarlyDataScanCommand): raise ValueError("Unexpected scan command") session = None is_early_data_supported = False ssl_connection = server_info.get_preconfigured_ssl_connection(override_ssl_version=OpenSslVersionEnum.TLSV1_3) try: # Perform an SSL handshake and keep the session ssl_connection.connect() # Send and receive data for the TLS session to be created ssl_connection.ssl_client.write(HttpRequestGenerator.get_request(host=server_info.hostname)) ssl_connection.ssl_client.read(2048) session = ssl_connection.ssl_client.get_session() except SslHandshakeRejected: # TLS 1.3 not supported is_early_data_supported = False finally: ssl_connection.close() # Then try to re-use the session and send early data if session is not None: ssl_connection2 = server_info.get_preconfigured_ssl_connection( override_ssl_version=OpenSslVersionEnum.TLSV1_3 ) ssl_connection2.ssl_client.set_session(session) try: # Open a socket to the server but don't do the handshake ssl_connection2.do_pre_handshake(None) # Send one byte of early data ssl_connection2.ssl_client.write_early_data(b"E") ssl_connection2.ssl_client.do_handshake() if ssl_connection2.ssl_client.get_early_data_status() == OpenSslEarlyDataStatusEnum.ACCEPTED: is_early_data_supported = True else: is_early_data_supported = False except OpenSSLError as e: if "function you should not call" in e.args[0]: # This is what OpenSSL returns when the server did not enable early data is_early_data_supported = False else: raise finally: ssl_connection2.close() return EarlyDataScanResult(server_info, scan_command, is_early_data_supported)
def _get_rsa_parameters( server_info: ServerConnectivityInfo, openssl_cipher_string: str) -> Optional[Tuple[int, int]]: ssl_connection = server_info.get_preconfigured_ssl_connection() ssl_connection.ssl_client.set_cipher_list(openssl_cipher_string) parsed_cert = None try: # Perform the SSL handshake ssl_connection.connect() cert_as_pem = ssl_connection.ssl_client.get_received_chain()[0] parsed_cert = load_pem_x509_certificate( cert_as_pem.encode("ascii"), backend=default_backend()) except SslHandshakeRejected: # Server does not support RSA cipher suites? pass except ClientCertificateRequested: # AD: The server asked for a client cert. We could still retrieve the server certificate, but it is unclear # to me if the ROBOT check is supposed to work even if we do not provide a client cert. My guess is that # it should not work since it requires completing a full handshake, which we can't without a client cert. # Hence, propagate the error to make the check fail. raise finally: ssl_connection.close() if parsed_cert: try: return parsed_cert.public_key().public_numbers( ).n, parsed_cert.public_key().public_numbers().e except AttributeError: return None else: return None
def process_task( self, server_info: ServerConnectivityInfo, scan_command: PluginScanCommand) -> "HeartbleedScanResult": if not isinstance(scan_command, HeartbleedScanCommand): raise ValueError("Unexpected scan command") if server_info.highest_ssl_version_supported >= OpenSslVersionEnum.TLSV1_3: # The server uses a recent version of OpenSSL and it cannot be vulnerable to Heartbleed return HeartbleedScanResult(server_info, scan_command, False) ssl_connection = server_info.get_preconfigured_ssl_connection() # Replace nassl.sslClient.do_handshake() with a heartbleed checking SSL handshake so that all the SSLyze options # (startTLS, proxy, etc.) still work ssl_connection.ssl_client.do_handshake = types.MethodType( do_handshake_with_heartbleed, ssl_connection.ssl_client) is_vulnerable_to_heartbleed = False try: # Start the SSL handshake ssl_connection.connect() except VulnerableToHeartbleed: # The test was completed and the server is vulnerable is_vulnerable_to_heartbleed = True except NotVulnerableToHeartbleed: # The test was completed and the server is NOT vulnerable pass finally: ssl_connection.close() return HeartbleedScanResult(server_info, scan_command, is_vulnerable_to_heartbleed)
def _resume_ssl_session( server_info: ServerConnectivityInfo, ssl_version_to_use: OpenSslVersionEnum, ssl_session: Optional[nassl._nassl.SSL_SESSION] = None, should_enable_tls_ticket: bool = False, ) -> nassl._nassl.SSL_SESSION: """Connect to the server and returns the session object that was assigned for that connection. If ssl_session is given, tries to resume that session. """ ssl_connection = server_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version_to_use) if not should_enable_tls_ticket: # Need to disable TLS tickets to test session IDs, according to rfc5077: # If a ticket is presented by the client, the server MUST NOT attempt # to use the Session ID in the ClientHello for stateful session resumption ssl_connection.ssl_client.disable_stateless_session_resumption( ) # Turning off TLS tickets. if ssl_session: ssl_connection.ssl_client.set_session(ssl_session) try: # Perform the SSL handshake ssl_connection.connect() new_session = ssl_connection.ssl_client.get_session( ) # Get session data finally: ssl_connection.close() return new_session
def _get_selected_cipher_suite( server_connectivity: ServerConnectivityInfo, ssl_version: OpenSslVersionEnum, openssl_cipher_str: str, should_use_legacy_openssl: Optional[bool], ) -> "AcceptedCipherSuite": """Given an OpenSSL cipher string (which may specify multiple cipher suites), return the cipher suite that was selected by the server during the SSL handshake. """ ssl_connection = server_connectivity.get_preconfigured_ssl_connection( override_ssl_version=ssl_version, should_use_legacy_openssl=should_use_legacy_openssl) ssl_connection.ssl_client.set_cipher_list(openssl_cipher_str) # Perform the SSL handshake try: ssl_connection.connect() selected_cipher = AcceptedCipherSuite.from_ongoing_ssl_connection( ssl_connection, ssl_version) except ClientCertificateRequested: selected_cipher = AcceptedCipherSuite.from_ongoing_ssl_connection( ssl_connection, ssl_version) finally: ssl_connection.close() return selected_cipher
def process_task( self, server_info: ServerConnectivityInfo, scan_command: PluginScanCommand ) -> "HttpHeadersScanResult": if not isinstance(scan_command, HttpHeadersScanCommand): raise ValueError("Unexpected scan command") if server_info.tls_wrapped_protocol not in [TlsWrappedProtocolEnum.PLAIN_TLS, TlsWrappedProtocolEnum.HTTPS]: raise ValueError("Cannot test for HTTP headers on a StartTLS connection.") # Perform the SSL handshake mozilla_store = TrustStoresRepository.get_default().get_main_store() ssl_connection = server_info.get_preconfigured_ssl_connection(ssl_verify_locations=mozilla_store.path) try: ssl_connection.connect() try: verified_chain_as_pem = ssl_connection.ssl_client.get_verified_chain() except CouldNotBuildVerifiedChain: verified_chain_as_pem = None # Send an HTTP GET request to the server ssl_connection.ssl_client.write(HttpRequestGenerator.get_request(host=server_info.hostname)) # We do not follow redirections because the security headers must be set on the first page according to # https://hstspreload.appspot.com/: # "If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS # header (rather than the page it redirects to)." http_response = HttpResponseParser.parse_from_ssl_connection(ssl_connection.ssl_client) finally: ssl_connection.close() if http_response.version == 9: # HTTP 0.9 => Probably not an HTTP response raise ValueError("Server did not return an HTTP response") # Parse the certificate chain verified_chain = ( [ load_pem_x509_certificate(cert_as_pem.encode("ascii"), backend=default_backend()) for cert_as_pem in verified_chain_as_pem ] if verified_chain_as_pem else None ) # Parse each header hsts_header = StrictTransportSecurityHeader.from_http_response(http_response) expect_ct_header = ExpectCtHeader.from_http_response(http_response) hpkp_header = PublicKeyPinsHeader.from_http_response(http_response) hpkp_report_only_header = PublicKeyPinsReportOnlyHeader.from_http_response(http_response) return HttpHeadersScanResult( server_info, scan_command, hsts_header, hpkp_header, hpkp_report_only_header, expect_ct_header, verified_chain, )
def _test_client_renegotiation( server_info: ServerConnectivityInfo, ssl_version_to_use: OpenSslVersionEnum) -> bool: """Check whether the server honors session renegotiation requests. """ ssl_connection = server_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version_to_use, should_use_legacy_openssl=True) try: # Perform the SSL handshake ssl_connection.connect() try: # Let's try to renegotiate ssl_connection.ssl_client.do_renegotiate() accepts_client_renegotiation = True # Errors caused by a server rejecting the renegotiation except socket.timeout: # This is how Netty rejects a renegotiation - https://github.com/nabla-c0d3/sslyzeslow/issues/114 accepts_client_renegotiation = False except socket.error as e: if "connection was forcibly closed" in str(e.args): accepts_client_renegotiation = False elif "reset by peer" in str(e.args): accepts_client_renegotiation = False elif "Nassl SSL handshake failed" in str(e.args): accepts_client_renegotiation = False else: raise except OpenSSLError as e: if "handshake failure" in str(e.args): accepts_client_renegotiation = False elif "no renegotiation" in str(e.args): accepts_client_renegotiation = False elif "tlsv1 unrecognized name" in str(e.args): # Yahoo's very own way of rejecting a renegotiation accepts_client_renegotiation = False elif "tlsv1 alert internal error" in str(e.args): # Jetty server: https://github.com/nabla-c0d3/sslyzeslow/issues/290 accepts_client_renegotiation = False else: raise except ConnectionError: accepts_client_renegotiation = False finally: ssl_connection.close() return accepts_client_renegotiation
def process_task( self, server_info: ServerConnectivityInfo, scan_command: PluginScanCommand) -> "FallbackScsvScanResult": if not isinstance(scan_command, FallbackScsvScanCommand): raise ValueError("Unexpected scan command") if server_info.highest_ssl_version_supported.value <= OpenSslVersionEnum.SSLV3.value: raise ValueError( "Server only supports SSLv3; no downgrade attacks are possible" ) # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no downgrade possible with TLS 1.3 if server_info.highest_ssl_version_supported >= OpenSslVersionEnum.TLSV1_3: ssl_version_to_use = OpenSslVersionEnum.TLSV1_2 else: ssl_version_to_use = server_info.highest_ssl_version_supported # Try to connect using a lower TLS version with the fallback cipher suite enabled ssl_version_downgrade = OpenSslVersionEnum(ssl_version_to_use.value - 1) # type: ignore ssl_connection = server_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version_downgrade) ssl_connection.ssl_client.enable_fallback_scsv() supports_fallback_scsv = False try: # Perform the SSL handshake ssl_connection.connect() except _nassl.OpenSSLError as e: # This is the right, specific alert the server should return if "tlsv1 alert inappropriate fallback" in str(e.args): supports_fallback_scsv = True else: raise except SslHandshakeRejected: # If the handshake is rejected, we assume downgrade attacks are prevented (this is how F5 balancers do it) # although it could also be because the server does not support this version of TLS # https://github.com/nabla-c0d3/sslyzeslow/issues/119 supports_fallback_scsv = True finally: ssl_connection.close() return FallbackScsvScanResult(server_info, scan_command, supports_fallback_scsv)
def _test_secure_renegotiation( server_info: ServerConnectivityInfo, ssl_version_to_use: OpenSslVersionEnum) -> bool: """Check whether the server supports secure renegotiation. """ ssl_connection = server_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version_to_use, should_use_legacy_openssl=True) try: # Perform the SSL handshake ssl_connection.connect() supports_secure_renegotiation = ssl_connection.ssl_client.get_secure_renegotiation_support( ) finally: ssl_connection.close() return supports_secure_renegotiation
def _send_robot_payload( server_info: ServerConnectivityInfo, ssl_version_to_use: OpenSslVersionEnum, rsa_cipher_string: str, robot_payload_enum: RobotPmsPaddingPayloadEnum, robot_should_finish_handshake: bool, rsa_modulus: int, rsa_exponent: int, ) -> Tuple[RobotPmsPaddingPayloadEnum, str]: # Do a handshake which each record and keep track of what the server returned ssl_connection = server_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version_to_use) # Replace nassl.sslClient.do_handshake() with a ROBOT checking SSL handshake so that all the SSLyze # options (startTLS, proxy, etc.) still work ssl_connection.ssl_client.do_handshake = types.MethodType( do_handshake_with_robot, ssl_connection.ssl_client) ssl_connection.ssl_client.set_cipher_list(rsa_cipher_string) # Compute the payload cke_payload = RobotTlsRecordPayloads.get_client_key_exchange_record( robot_payload_enum, TlsVersionEnum[ssl_version_to_use.name], rsa_modulus, rsa_exponent) # H4ck: we need to pass some arguments to the handshake but there is no simple way to do it; we use an attribute ssl_connection.ssl_client._robot_cke_record = cke_payload ssl_connection.ssl_client._robot_should_finish_handshake = robot_should_finish_handshake server_response = "" try: # Start the SSL handshake ssl_connection.connect() except ServerResponseToRobot as e: # Should always be thrown server_response = e.server_response except socket.timeout: # https://github.com/nabla-c0d3/sslyzeslow/issues/361 server_response = "Connection timed out" finally: ssl_connection.close() return robot_payload_enum, server_response
def process_task( self, server_info: ServerConnectivityInfo, scan_command: PluginScanCommand ) -> "CompressionScanResult": if not isinstance(scan_command, CompressionScanCommand): raise ValueError("Unexpected scan command") # Try with TLS 1.2 even if the server supports TLS 1.3 or higher as there is no compression with TLS 1.3 if server_info.highest_ssl_version_supported >= OpenSslVersionEnum.TLSV1_3: ssl_version_to_use = OpenSslVersionEnum.TLSV1_2 else: ssl_version_to_use = server_info.highest_ssl_version_supported ssl_connection = server_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version_to_use, should_use_legacy_openssl=True ) # Make sure OpenSSL was built with support for compression to avoid false negatives if "zlib compression" not in ssl_connection.ssl_client.get_available_compression_methods(): raise RuntimeError( "OpenSSL was not built with support for zlib / compression. Did you build nassl yourself ?" ) try: # Perform the SSL handshake ssl_connection.connect() compression_name = ssl_connection.ssl_client.get_current_compression_method() except ClientCertificateRequested: # The server asked for a client cert compression_name = ssl_connection.ssl_client.get_current_compression_method() except SslHandshakeRejected: # Should only happen when the server only supports TLS 1.3, which does not support compression compression_name = "" finally: ssl_connection.close() return CompressionScanResult(server_info, scan_command, compression_name)
def perform( self, network_timeout: Optional[int] = 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: network_timeout: Network timeout value in seconds passed to the underlying socket. Returns: An object encapsulating all the information needed to connect to the server, to be passed to a `SynchronousScanner` or `ConcurrentScanner` in order to run scan commands on the server. Raises: ServerConnectivityError: If the server was not reachable or an SSL/TLS handshake could not be completed. """ # First do a DNS lookup if we don't already have an IP address and we are not using a proxy if not self.ip_address and not self.http_tunneling_settings: try: self.ip_address = self._do_dns_lookup(self.hostname, self.port) except (socket.gaierror, IndexError, ConnectionError): raise ServerHostnameCouldNotBeResolved(self) # Then try to connect client_auth_requirement = ClientAuthenticationServerConfigurationEnum.DISABLED ssl_connection = SslConnectionConfigurator.get_connection( ssl_version=OpenSslVersionEnum.SSLV23, server_info=self, should_ignore_client_auth=True) # First only try a socket connection try: ssl_connection.do_pre_handshake(network_timeout=network_timeout) # Socket errors except socket.timeout: # Host is down raise ConnectionToServerTimedOut(self) except ConnectionError: raise ServerRejectedConnection(self) # StartTLS errors except StartTlsError as e: raise ServerTlsConfigurationNotSuportedError(self, e.args[0]) # Proxy errors except ProxyError as e: raise ProxyConnectivityError(self, e.args[0]) # Other errors except Exception as e: raise ServerConnectivityError( self, "{0}: {1}".format(str(type(e).__name__), e.args[0])) finally: ssl_connection.close() # Then try to complete an SSL handshake to figure out the SSL version and cipher supported by the server ssl_version_supported = None ssl_cipher_supported = None # TODO(AD): Switch to using the protocol discovery logic available in OpenSSL 1.1.0 with TLS_client_method() for ssl_version in [ OpenSslVersionEnum.TLSV1_3, OpenSslVersionEnum.TLSV1_2, OpenSslVersionEnum.TLSV1_1, OpenSslVersionEnum.TLSV1, OpenSslVersionEnum.SSLV3, OpenSslVersionEnum.SSLV23, ]: # First try the default cipher list, and then all ciphers for cipher_list in [ SslConnectionConfigurator.DEFAULT_SSL_CIPHER_LIST, "ALL:COMPLEMENTOFALL:-PSK:-SRP" ]: ssl_connection = SslConnectionConfigurator.get_connection( ssl_version=ssl_version, server_info=self, openssl_cipher_string=cipher_list, should_ignore_client_auth=False, ) try: # Only do one attempt when testing connectivity ssl_connection.connect(network_timeout=network_timeout, network_max_retries=0) ssl_version_supported = ssl_version ssl_cipher_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 sslyzeslow # Store the SSL version and cipher list that is supported ssl_version_supported = ssl_version ssl_cipher_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 = SslConnectionConfigurator.get_connection( ssl_version=ssl_version, server_info=self, openssl_cipher_string=cipher_list, should_ignore_client_auth=True, ) try: ssl_connection_auth.connect( network_timeout=network_timeout, network_max_retries=0) ssl_cipher_supported = ssl_connection_auth.ssl_client.get_current_cipher_name( ) client_auth_requirement = ClientAuthenticationServerConfigurationEnum.OPTIONAL # If client authentication is required, we either get a ClientCertificateRequested except ClientCertificateRequested: client_auth_requirement = ClientAuthenticationServerConfigurationEnum.REQUIRED # Or a SSLHandshakeRejected except SslHandshakeRejected: client_auth_requirement = ClientAuthenticationServerConfigurationEnum.REQUIRED except Exception: # Could not complete a handshake with this server pass finally: ssl_connection_auth.close() except Exception: # Could not complete a handshake with this server pass finally: ssl_connection.close() if ssl_cipher_supported: # A handshake was successful break if ssl_version_supported is None or ssl_cipher_supported is None: raise ServerTlsConfigurationNotSuportedError( self, "Could not complete an SSL/TLS handshake with the server") return ServerConnectivityInfo( hostname=self.hostname, port=self.port, ip_address=self.ip_address, tls_wrapped_protocol=self.tls_wrapped_protocol, tls_server_name_indication=self.tls_server_name_indication, highest_ssl_version_supported=ssl_version_supported, openssl_cipher_string_supported=ssl_cipher_supported, client_auth_requirement=client_auth_requirement, xmpp_to_hostname=self.xmpp_to_hostname, client_auth_credentials=self.client_auth_credentials, http_tunneling_settings=self.http_tunneling_settings, )
def _test_cipher_suite(server_connectivity_info: ServerConnectivityInfo, ssl_version: OpenSslVersionEnum, openssl_cipher_name: str) -> "CipherSuite": """Initiates a SSL handshake with the server using the SSL version and the cipher suite specified. """ requires_legacy_openssl = True if ssl_version == OpenSslVersionEnum.TLSV1_2: # For TLS 1.2, we need to pick the right version of OpenSSL depending on which cipher suite requires_legacy_openssl = WorkaroundForTls12ForCipherSuites.requires_legacy_openssl( openssl_cipher_name) elif ssl_version == OpenSslVersionEnum.TLSV1_3: requires_legacy_openssl = False ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version, should_use_legacy_openssl=requires_legacy_openssl) # Only enable the cipher suite to test; not trivial anymore since OpenSSL 1.1.1 and TLS 1.3 if ssl_version == OpenSslVersionEnum.TLSV1_3: # The function to control cipher suites is different for TLS 1.3 # Disable the default, non-TLS 1.3 cipher suites ssl_connection.ssl_client.set_cipher_list("") # Enable the one TLS 1.3 cipher suite we want to test ssl_connection.ssl_client.set_ciphersuites(openssl_cipher_name) else: if not requires_legacy_openssl: # Disable the TLS 1.3 cipher suites if we are using the modern client ssl_connection.ssl_client.set_ciphersuites("") ssl_connection.ssl_client.set_cipher_list(openssl_cipher_name) if len(ssl_connection.ssl_client.get_cipher_list()) != 1: raise ValueError( f'Passed an OpenSSL string for multiple cipher suites: "{openssl_cipher_name}": ' f"{str(ssl_connection.ssl_client.get_cipher_list())}") try: # Perform the SSL handshake ssl_connection.connect() cipher_result: CipherSuite = AcceptedCipherSuite.from_ongoing_ssl_connection( ssl_connection, ssl_version) except SslHandshakeRejected as e: cipher_result = RejectedCipherSuite(openssl_cipher_name, ssl_version, str(e)) except ClientCertificateRequested: # TODO(AD): Sometimes get_current_cipher_name() called in from_ongoing_ssl_connection() will return None # When the handshake failed due to ClientCertificateRequested # We need to rewrite this logic to not use OpenSSL for looking up key size and RFC names as it is # too complicated # cipher_result = AcceptedCipherSuite.from_ongoing_ssl_connection(ssl_connection, ssl_version) # The ClientCertificateRequested exception already proves that the cipher suite was accepted # Workaround here: cipher_result = AcceptedCipherSuite(openssl_cipher_name, ssl_version, None, None) except Exception as e: cipher_result = ErroredCipherSuite(openssl_cipher_name, ssl_version, e) finally: ssl_connection.close() return cipher_result
def process_task( self, server_connectivity_info: ServerConnectivityInfo, scan_command: PluginScanCommand) -> "CipherSuiteScanResult": if not isinstance(scan_command, CipherSuiteScanCommand): raise ValueError("Unexpected scan command") ssl_version = self.SSL_VERSIONS_MAPPING[scan_command.__class__] # Get the list of available cipher suites for the given ssl version cipher_list: List[str] = [] if ssl_version == OpenSslVersionEnum.TLSV1_2: # For TLS 1.2, we have to use both the legacy and modern OpenSSL to cover all cipher suites ssl_connection_legacy = server_connectivity_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version, should_use_legacy_openssl=True) ssl_connection_legacy.ssl_client.set_cipher_list( "ALL:COMPLEMENTOFALL:-PSK:-SRP") cipher_list.extend( ssl_connection_legacy.ssl_client.get_cipher_list()) ssl_connection_modern = server_connectivity_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version, should_use_legacy_openssl=False) # Disable the TLS 1.3 cipher suites with the new OpenSSL API ssl_connection_modern.ssl_client.set_ciphersuites("") # Enable all other cipher suites ssl_connection_modern.ssl_client.set_cipher_list( "ALL:COMPLEMENTOFALL:-PSK:-SRP") cipher_list.extend( ssl_connection_modern.ssl_client.get_cipher_list()) # And remove duplicates (ie. supported by both legacy and modern OpenSSL) cipher_list = list(set(cipher_list)) elif ssl_version == OpenSslVersionEnum.TLSV1_3: # TLS 1.3 only has 5 cipher suites so we can hardcode them cipher_list = [ "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_128_GCM_SHA256", "TLS_AES_128_CCM_8_SHA256", "TLS_AES_128_CCM_SHA256", ] else: ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version) # Disable SRP and PSK cipher suites as they need a special setup in the client and are never used ssl_connection.ssl_client.set_cipher_list( "ALL:COMPLEMENTOFALL:-PSK:-SRP") # And remove TLS 1.3 cipher suites cipher_list = [ cipher for cipher in ssl_connection.ssl_client.get_cipher_list() if "TLS13" not in cipher ] # Scan for every available cipher suite thread_pool = ThreadPool() for cipher in cipher_list: thread_pool.add_job( (self._test_cipher_suite, [server_connectivity_info, ssl_version, cipher])) # Start processing the jobs; One thread per cipher thread_pool.start(nb_threads=min(len(cipher_list), self.MAX_THREADS)) accepted_cipher_list = [] rejected_cipher_list = [] errored_cipher_list = [] # Store the results as they come for completed_job in thread_pool.get_result(): (job, cipher_result) = completed_job if isinstance(cipher_result, AcceptedCipherSuite): accepted_cipher_list.append(cipher_result) elif isinstance(cipher_result, RejectedCipherSuite): rejected_cipher_list.append(cipher_result) elif isinstance(cipher_result, ErroredCipherSuite): errored_cipher_list.append(cipher_result) else: raise ValueError("Unexpected result") # Store thread pool errors; only something completely unexpected would trigger an error for failed_job in thread_pool.get_error(): (_, exception) = failed_job raise exception thread_pool.join() # Test for the cipher suite preference preferred_cipher = self._get_preferred_cipher_suite( server_connectivity_info, ssl_version, accepted_cipher_list) # Generate the results plugin_result = CipherSuiteScanResult( server_connectivity_info, scan_command, preferred_cipher, accepted_cipher_list, rejected_cipher_list, errored_cipher_list, ) return plugin_result