def _test_session_resumption_rate( self, server_info: ServerConnectivityInfo, ssl_version_to_use: OpenSslVersionEnum, resumption_attempts_nb: int ) -> Tuple[int, List[str]]: """Attempt several session ID resumption with the server. """ thread_pool = ThreadPool() for _ in range(resumption_attempts_nb): thread_pool.add_job((self._resume_with_session_id, [server_info, ssl_version_to_use])) thread_pool.start(nb_threads=min(resumption_attempts_nb, self.MAX_THREADS_NB)) # Count successful/failed resumptions successful_resumptions_nb = 0 for completed_job in thread_pool.get_result(): (job, was_resumption_successful) = completed_job if was_resumption_successful: successful_resumptions_nb += 1 # Count errors and store error messages errored_resumptions_list = [] for failed_job in thread_pool.get_error(): (job, exception) = failed_job error_msg = f'{str(exception.__class__.__name__)} - {str(exception)}' errored_resumptions_list.append(error_msg) thread_pool.join() return successful_resumptions_nb, errored_resumptions_list
class ConcurrentServerConnectivityTester: """Utility class to run servers connectivity testing using a thread pool. """ _DEFAULT_MAX_THREADS = 20 def __init__( self, server_connectivity_testers: List[ServerConnectivityTester] ) -> None: # Use a thread pool to connect to each server self._thread_pool = ThreadPool() self._server_connectivity_testers = server_connectivity_testers def start_connectivity_testing( self, max_threads: int = _DEFAULT_MAX_THREADS, network_timeout: Optional[int] = None) -> None: for server_tester in self._server_connectivity_testers: self._thread_pool.add_job( (server_tester.perform, [network_timeout])) nb_threads = min(len(self._server_connectivity_testers), max_threads) self._thread_pool.start(nb_threads) def get_reachable_servers(self) -> Iterable[ServerConnectivityInfo]: for (_, server_info) in self._thread_pool.get_result(): yield server_info def get_invalid_servers(self) -> Iterable[ServerConnectivityError]: for (_, exception) in self._thread_pool.get_error(): yield cast(ServerConnectivityError, exception)
def process_task(self, server_connectivity_info, plugin_command, options_dict=None): if options_dict and "verbose" in options_dict.keys(): verbose_mode = options_dict["verbose"] else: verbose_mode = False test_protocols = {"SSLv3", "TLSv1"} thread_pool = ThreadPool() ciphers_list = [] support_protocol_list = [] for proto in test_protocols: if self.test_protocol_support(PROTOCOL_VERSION[proto], server_connectivity_info): ssl_client = SslClient(ssl_version=PROTOCOL_VERSION[proto]) ssl_client.set_cipher_list(proto) ciphers_list = ssl_client.get_cipher_list() for cipher in ciphers_list: thread_pool.add_job( (self._test_ciphersuite, (server_connectivity_info, PROTOCOL_VERSION[proto], cipher)) ) support_protocol_list.append(proto) thread_pool.start(nb_threads=min(len(ciphers_list), self.MAX_THREADS)) accept_ciphers = [] reject_ciphers = [] if verbose_mode: print " VERBOSE MODE PRINT" print " ------------------" for completed_job in thread_pool.get_result(): (job, cipher_result) = completed_job if isinstance(cipher_result, AcceptCipher): accept_ciphers.append(cipher_result) elif isinstance(cipher_result, RejectCipher): reject_ciphers.append(cipher_result) else: raise ValueError("Unexpected result") if verbose_mode: cipher_result.print_cipher() if verbose_mode: print " ----------------------" print " END VERBOSE MODE PRINT" print " ----------------------" for error_job in thread_pool.get_error(): (_, exception) = error_job raise exception thread_pool.join() support_vulnerable_ciphers_set = self.get_vulnerable_ciphers(accept_ciphers) is_vulnerable = True if len(support_vulnerable_ciphers_set) > 0 else False return BEASTVulnerabilityTesterResult( server_connectivity_info, plugin_command, options_dict, support_vulnerable_ciphers_set, is_vulnerable, support_protocol_list, )
def _test_session_resumption_rate( self, server_info: ServerConnectivityInfo, ssl_version_to_use: OpenSslVersionEnum, resumption_attempts_nb: int) -> Tuple[int, List[str]]: """Attempt several session ID resumption with the server. """ thread_pool = ThreadPool() for _ in range(resumption_attempts_nb): thread_pool.add_job((self._resume_with_session_id, [server_info, ssl_version_to_use])) thread_pool.start( nb_threads=min(resumption_attempts_nb, self.MAX_THREADS_NB)) # Count successful/failed resumptions successful_resumptions_nb = 0 for completed_job in thread_pool.get_result(): (job, was_resumption_successful) = completed_job if was_resumption_successful: successful_resumptions_nb += 1 # Count errors and store error messages errored_resumptions_list = [] for failed_job in thread_pool.get_error(): (job, exception) = failed_job error_msg = f'{str(exception.__class__.__name__)} - {str(exception)}' errored_resumptions_list.append(error_msg) thread_pool.join() return successful_resumptions_nb, errored_resumptions_list
def _test_session_resumption_rate(self, server_info, resumption_attempts_nb): # type: (ServerConnectivityInfo, int) -> Tuple[int, List[Text]] """Attempt several session ID resumption with the server. """ thread_pool = ThreadPool() for _ in range(resumption_attempts_nb): thread_pool.add_job((self._resume_with_session_id, (server_info, ))) thread_pool.start(nb_threads=min(resumption_attempts_nb, self.MAX_THREADS_NB)) # Count successful/failed resumptions successful_resumptions_nb = 0 for completed_job in thread_pool.get_result(): (job, was_resumption_successful) = completed_job if was_resumption_successful: successful_resumptions_nb += 1 # Count errors and store error messages errored_resumptions_list = [] for failed_job in thread_pool.get_error(): (job, exception) = failed_job error_msg = '{} - {}'.format(str(exception.__class__.__name__), str(exception)) errored_resumptions_list.append(error_msg) thread_pool.join() return successful_resumptions_nb, errored_resumptions_list
class ServersConnectivityTester(object): """Utility class to run servers connectivity testing on a list of ServerConnectivityInfo using a thread pool. """ _DEFAULT_MAX_THREADS = 50 def __init__(self, tentative_server_info_list): # type: (List[ServerConnectivityInfo]) -> None # Use a thread pool to connect to each server self._thread_pool = ThreadPool() self._server_info_list = tentative_server_info_list def start_connectivity_testing(self, max_threads=_DEFAULT_MAX_THREADS, network_timeout=None): # type: (Optional[int], Optional[int]) -> None for tentative_server_info in self._server_info_list: self._thread_pool.add_job((tentative_server_info.test_connectivity_to_server, [network_timeout])) nb_threads = min(len(self._server_info_list), max_threads) self._thread_pool.start(nb_threads) def get_reachable_servers(self): # type: () -> Iterable[ServerConnectivityInfo] for (job, _) in self._thread_pool.get_result(): test_connectivity_to_server_method, _ = job server_info = test_connectivity_to_server_method.__self__ yield server_info def get_invalid_servers(self): # type: () -> Iterable[Tuple[ServerConnectivityInfo, Exception]] for (job, exception) in self._thread_pool.get_error(): test_connectivity_to_server_method, _ = job server_info = test_connectivity_to_server_method.__self__ yield (server_info, exception)
class ConcurrentServerConnectivityTester: """Utility class to run servers connectivity testing using a thread pool. """ _DEFAULT_MAX_THREADS = 20 def __init__(self, server_connectivity_testers: List[ServerConnectivityTester]) -> None: # Use a thread pool to connect to each server self._thread_pool = ThreadPool() self._server_connectivity_testers = server_connectivity_testers def start_connectivity_testing( self, max_threads: int = _DEFAULT_MAX_THREADS, network_timeout: Optional[int] = None ) -> None: for server_tester in self._server_connectivity_testers: self._thread_pool.add_job((server_tester.perform, [network_timeout])) nb_threads = min(len(self._server_connectivity_testers), max_threads) self._thread_pool.start(nb_threads) def get_reachable_servers(self) -> Iterable[ServerConnectivityInfo]: for (_, server_info) in self._thread_pool.get_result(): yield server_info def get_invalid_servers(self) -> Iterable[ServerConnectivityError]: for (_, exception) in self._thread_pool.get_error(): yield cast(ServerConnectivityError, exception)
def _test_session_resumption_rate(self, server_info, resumption_attempts_nb): # type: (ServerConnectivityInfo, int) -> Tuple[int, List[Text]] """Attempt several session ID resumption with the server. """ thread_pool = ThreadPool() for _ in range(resumption_attempts_nb): thread_pool.add_job( (self._resume_with_session_id, (server_info, ))) thread_pool.start( nb_threads=min(resumption_attempts_nb, self.MAX_THREADS_NB)) # Count successful/failed resumptions successful_resumptions_nb = 0 for completed_job in thread_pool.get_result(): (job, was_resumption_successful) = completed_job if was_resumption_successful: successful_resumptions_nb += 1 # Count errors and store error messages errored_resumptions_list = [] for failed_job in thread_pool.get_error(): (job, exception) = failed_job error_msg = u'{} - {}'.format(str(exception.__class__.__name__), str(exception)) errored_resumptions_list.append(error_msg) thread_pool.join() return successful_resumptions_nb, errored_resumptions_list
def _run_oracle_over_threads(cls, server_info, cipher_string, rsa_modulus, rsa_exponent, should_complete_handshake): # type: (ServerConnectivityInfo, Text, int, int, bool) -> RobotScanResultEnum # Use threads to speed things up thread_pool = ThreadPool() for payload_enum in RobotPmsPaddingPayloadEnum: # Run each payload twice to ensure the results are consistent thread_pool.add_job((cls._send_robot_payload, (server_info, cipher_string, payload_enum, should_complete_handshake, rsa_modulus, rsa_exponent))) thread_pool.add_job((cls._send_robot_payload, (server_info, cipher_string, payload_enum, should_complete_handshake, rsa_modulus, rsa_exponent))) # Use one thread per check thread_pool.start(nb_threads=len(RobotPmsPaddingPayloadEnum)*2) # Store the results - two attempts per ROBOT payload payload_responses = { RobotPmsPaddingPayloadEnum.VALID: [], RobotPmsPaddingPayloadEnum.WRONG_FIRST_TWO_BYTES: [], RobotPmsPaddingPayloadEnum.WRONG_POSITION_00: [], RobotPmsPaddingPayloadEnum.NO_00_IN_THE_MIDDLE: [], RobotPmsPaddingPayloadEnum.WRONG_VERSION_NUMBER: [], } for completed_job in thread_pool.get_result(): (job, (payload_enum, server_response)) = completed_job payload_responses[payload_enum].append(server_response) for failed_job in thread_pool.get_error(): # Should never happen when running the Robot check as we catch all exceptions in the handshake (_, exception) = failed_job raise exception thread_pool.join() return RobotServerResponsesAnalyzer(payload_responses).compute_result_enum()
def process_task( self, server_info: ServerConnectivityInfo, scan_command: PluginScanCommand ) -> 'CertificateInfoScanResult': if not isinstance(scan_command, CertificateInfoScanCommand): raise ValueError('Unexpected scan command') final_trust_store_list = TrustStoresRepository.get_default().get_all_stores() if scan_command.custom_ca_file: if not os.path.isfile(scan_command.custom_ca_file): raise ValueError('Could not open supplied CA file at "{}"'.format(scan_command.custom_ca_file)) final_trust_store_list.append(TrustStore(scan_command.custom_ca_file, 'Custom --ca_file', 'N/A')) # Workaround for https://github.com/pyca/cryptography/issues/3495 default_backend() thread_pool = ThreadPool() for trust_store in final_trust_store_list: # Try to connect with each trust store thread_pool.add_job((self._get_and_verify_certificate_chain, [server_info, trust_store])) # Start processing the jobs; one thread per trust thread_pool.start(len(final_trust_store_list)) # Store the results as they come certificate_chain: List[Certificate] = [] path_validation_result_list = [] path_validation_error_list = [] ocsp_response = None for (job, result) in thread_pool.get_result(): (_, (_, trust_store)) = job certificate_chain, validation_result, _ocsp_response = result # Keep the OCSP response if the validation was succesful and a response was returned if _ocsp_response: ocsp_response = _ocsp_response # Store the returned verify string for each trust store path_validation_result_list.append(PathValidationResult(trust_store, validation_result)) # Store thread pool errors last_exception = None for (job, exception) in thread_pool.get_error(): (_, (_, trust_store)) = job path_validation_error_list.append(PathValidationError(trust_store, exception)) last_exception = exception thread_pool.join() if len(path_validation_error_list) == len(final_trust_store_list): # All connections failed unexpectedly; raise an exception instead of returning a result raise last_exception # type: ignore # All done return CertificateInfoScanResult(server_info, scan_command, certificate_chain, path_validation_result_list, path_validation_error_list, ocsp_response)
def process_task(self, server_connectivity_info, plugin_command, option_dict=None): if option_dict and 'verbose' in option_dict.keys(): verbose_mode = option_dict['verbose'] else: verbose_mode = False ssl3_support = self.test_SSLv3_support(server_connectivity_info) support_vulnerable_ciphers = None if ssl3_support: cipher_list = self.get_ssl3_cipher_list() thread_pool = ThreadPool() for cipher in cipher_list: thread_pool.add_job((self._test_ciphersuite, (server_connectivity_info, cipher))) thread_pool.start( nb_threads=min(len(cipher_list), self.MAX_THREADS)) accept_ciphers = [] reject_ciphers = [] if verbose_mode: print ' VERBOSE MODE PRINT' print ' ------------------' for completed_job in thread_pool.get_result(): (job, cipher_result) = completed_job if isinstance(cipher_result, AcceptCipher): accept_ciphers.append(cipher_result) elif isinstance(cipher_result, RejectCipher): reject_ciphers.append(cipher_result) else: raise ValueError("Unexpected result") if verbose_mode: cipher_result.print_cipher() if verbose_mode: print ' ----------------------' print ' END VERBOSE MODE PRINT' print ' ----------------------' for error_job in thread_pool.get_error(): (_, exception) = error_job raise exception thread_pool.join() support_vulnerable_ciphers = self.get_vulnerable_ciphers( accept_ciphers) is_vulnerable = ssl3_support and ( support_vulnerable_ciphers is not None or len(support_vulnerable_ciphers) > 0) return POODLEVulnerabilityTesterResult(server_connectivity_info, plugin_command, option_dict, ssl3_support, is_vulnerable, support_vulnerable_ciphers)
def process_task(self, server_info, command, options_dict=None): if command == 'certinfo_basic': result_class = CertInfoBasicResult elif command == 'certinfo_full': result_class = CertInfoFullResult else: raise ValueError("PluginCertInfo: Unknown command.") final_trust_store_list = list(DEFAULT_TRUST_STORE_LIST) if options_dict and 'ca_file' in options_dict.keys(): final_trust_store_list.append( TrustStore(options_dict['ca_file'], 'Custom --ca_file', 'N/A')) thread_pool = ThreadPool() for trust_store in final_trust_store_list: # Try to connect with each trust store thread_pool.add_job( (self._get_certificate_chain, (server_info, trust_store))) # Start processing the jobs; one thread per trust thread_pool.start(len(final_trust_store_list)) # Store the results as they come certificate_chain = [] path_validation_result_list = [] path_validation_error_list = [] ocsp_response = None for (job, result) in thread_pool.get_result(): (_, (_, trust_store)) = job certificate_chain, validation_result, ocsp_response = result # Store the returned verify string for each trust store path_validation_result_list.append( PathValidationResult(trust_store, validation_result)) # Store thread pool errors last_exception = None for (job, exception) in thread_pool.get_error(): (_, (_, trust_store)) = job path_validation_error_list.append( PathValidationError(trust_store, exception)) last_exception = exception thread_pool.join() if len(path_validation_error_list) == len(final_trust_store_list): # All connections failed unexpectedly; raise an exception instead of returning a result raise RuntimeError( 'Could not connect to the server; last error: {}'.format( last_exception)) # All done return result_class(server_info, command, options_dict, certificate_chain, path_validation_result_list, path_validation_error_list, ocsp_response)
def process_task(self, server_connectivity_info, plugin_command, options_dict=None): ssl_version = self.SSL_VERSIONS_MAPPING[plugin_command] # Get the list of available cipher suites for the given ssl version ssl_client = SslClient(ssl_version=ssl_version) ssl_client.set_cipher_list('ALL:COMPLEMENTOFALL') cipher_list = ssl_client.get_cipher_list() # 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 = OpenSSLCipherSuitesResult(server_connectivity_info, plugin_command, options_dict, preferred_cipher, accepted_cipher_list, rejected_cipher_list, errored_cipher_list) return plugin_result
def _run_oracle_over_threads( cls, server_info: ServerConnectivityInfo, ssl_version_to_use: OpenSslVersionEnum, cipher_string: str, rsa_modulus: int, rsa_exponent: int, should_complete_handshake: bool, ) -> RobotScanResultEnum: # Use threads to speed things up thread_pool = ThreadPool() for payload_enum in RobotPmsPaddingPayloadEnum: # Run each payload three times to ensure the results are consistent for _ in range(3): thread_pool.add_job(( cls._send_robot_payload, [ server_info, ssl_version_to_use, cipher_string, payload_enum, should_complete_handshake, rsa_modulus, rsa_exponent, ], )) # Use one thread per check thread_pool.start(nb_threads=len(RobotPmsPaddingPayloadEnum)) # Store the results - two attempts per ROBOT payload payload_responses: Dict[RobotPmsPaddingPayloadEnum, List[str]] = { RobotPmsPaddingPayloadEnum.VALID: [], RobotPmsPaddingPayloadEnum.WRONG_FIRST_TWO_BYTES: [], RobotPmsPaddingPayloadEnum.WRONG_POSITION_00: [], RobotPmsPaddingPayloadEnum.NO_00_IN_THE_MIDDLE: [], RobotPmsPaddingPayloadEnum.WRONG_VERSION_NUMBER: [], } for completed_job in thread_pool.get_result(): (job, (payload_enum, server_response)) = completed_job payload_responses[payload_enum].append(server_response) for failed_job in thread_pool.get_error(): # Should never happen when running the Robot check as we catch all exceptions in the handshake (_, exception) = failed_job raise exception thread_pool.join() return RobotServerResponsesAnalyzer( payload_responses).compute_result_enum()
def process_task(self, server_info, command, options_dict=None): if command == 'certinfo_basic': result_class = CertInfoBasicResult elif command == 'certinfo_full': result_class = CertInfoFullResult else: raise ValueError("PluginCertInfo: Unknown command.") final_trust_store_list = list(DEFAULT_TRUST_STORE_LIST) if options_dict and 'ca_file' in options_dict.keys(): final_trust_store_list.append(TrustStore(options_dict['ca_file'], 'Custom --ca_file', 'N/A')) thread_pool = ThreadPool() for trust_store in final_trust_store_list: # Try to connect with each trust store thread_pool.add_job((self._get_certificate_chain, (server_info, trust_store))) # Start processing the jobs; one thread per trust thread_pool.start(len(final_trust_store_list)) # Store the results as they come certificate_chain = [] path_validation_result_list = [] path_validation_error_list = [] ocsp_response = None for (job, result) in thread_pool.get_result(): (_, (_, trust_store)) = job certificate_chain, validation_result, ocsp_response = result # Store the returned verify string for each trust store path_validation_result_list.append(PathValidationResult(trust_store, validation_result)) # Store thread pool errors last_exception = None for (job, exception) in thread_pool.get_error(): (_, (_, trust_store)) = job path_validation_error_list.append(PathValidationError(trust_store, exception)) last_exception = exception thread_pool.join() if len(path_validation_error_list) == len(final_trust_store_list): # All connections failed unexpectedly; raise an exception instead of returning a result raise RuntimeError('Could not connect to the server; last error: {}'.format(last_exception)) # All done return result_class(server_info, command, options_dict, certificate_chain, path_validation_result_list, path_validation_error_list, ocsp_response)
def process_task(self, server_connectivity_info, plugin_command, options_dict=None): ssl_version = self.SSL_VERSIONS_MAPPING[plugin_command] # Get the list of available cipher suites for the given ssl version ssl_client = SslClient(ssl_version=ssl_version) ssl_client.set_cipher_list('ALL:COMPLEMENTOFALL') cipher_list = ssl_client.get_cipher_list() # 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 = OpenSSLCipherSuitesResult(server_connectivity_info, plugin_command, options_dict, preferred_cipher, accepted_cipher_list, rejected_cipher_list, errored_cipher_list) return plugin_result
def process_task(self, server_connectivity_info, plugin_command, option_dict=None): if option_dict and 'verbose' in option_dict.keys(): verbose_mode = option_dict['verbose'] else: verbose_mode = False ssl3_support = self.test_SSLv3_support(server_connectivity_info) support_vulnerable_ciphers = None if ssl3_support: cipher_list = self.get_ssl3_cipher_list() thread_pool = ThreadPool() for cipher in cipher_list: thread_pool.add_job((self._test_ciphersuite, (server_connectivity_info,cipher))) thread_pool.start(nb_threads=min(len(cipher_list),self.MAX_THREADS)) accept_ciphers = [] reject_ciphers = [] if verbose_mode: print ' VERBOSE MODE PRINT' print ' ------------------' for completed_job in thread_pool.get_result(): (job, cipher_result) = completed_job if isinstance(cipher_result, AcceptCipher): accept_ciphers.append(cipher_result) elif isinstance(cipher_result,RejectCipher): reject_ciphers.append(cipher_result) else: raise ValueError("Unexpected result") if verbose_mode: cipher_result.print_cipher() if verbose_mode: print ' ----------------------' print ' END VERBOSE MODE PRINT' print ' ----------------------' for error_job in thread_pool.get_error(): (_, exception) = error_job raise exception thread_pool.join() support_vulnerable_ciphers = self.get_vulnerable_ciphers(accept_ciphers) is_vulnerable = ssl3_support and (support_vulnerable_ciphers is not None or len(support_vulnerable_ciphers) > 0) return POODLEVulnerabilityTesterResult(server_connectivity_info, plugin_command,option_dict, ssl3_support, is_vulnerable, support_vulnerable_ciphers)
def process_task(self, server_info, scan_command): # type: (ServerConnectivityInfo, CertificateInfoScanCommand) -> CertificateInfoScanResult final_trust_store_list = list(TrustStoresRepository.get_all()) if scan_command.custom_ca_file: if not os.path.isfile(scan_command.custom_ca_file): raise ValueError(u'Could not open supplied CA file at "{}"'.format(scan_command.custom_ca_file)) final_trust_store_list.append(TrustStore(scan_command.custom_ca_file, u'Custom --ca_file', u'N/A')) thread_pool = ThreadPool() for trust_store in final_trust_store_list: # Try to connect with each trust store thread_pool.add_job((self._get_and_verify_certificate_chain, (server_info, trust_store))) # Start processing the jobs; one thread per trust thread_pool.start(len(final_trust_store_list)) # Store the results as they come certificate_chain = [] path_validation_result_list = [] path_validation_error_list = [] ocsp_response = None for (job, result) in thread_pool.get_result(): (_, (_, trust_store)) = job certificate_chain, validation_result, ocsp_response = result # Store the returned verify string for each trust store path_validation_result_list.append(PathValidationResult(trust_store, validation_result)) # Store thread pool errors last_exception = None for (job, exception) in thread_pool.get_error(): (_, (_, trust_store)) = job path_validation_error_list.append(PathValidationError(trust_store, exception)) last_exception = exception thread_pool.join() if len(path_validation_error_list) == len(final_trust_store_list): # All connections failed unexpectedly; raise an exception instead of returning a result raise RuntimeError(u'Could not connect to the server; last error: {}'.format(last_exception)) # All done return CertificateInfoScanResult(server_info, scan_command, certificate_chain, path_validation_result_list, path_validation_error_list, ocsp_response)
class ServersConnectivityTester(object): """Utility class to run servers connectivity testing on a list of ServerConnectivityInfo using a thread pool. """ _DEFAULT_MAX_THREADS = 50 def __init__(self, tentative_server_info_list): # type: (List[ServerConnectivityInfo]) -> None # Use a thread pool to connect to each server self._thread_pool = ThreadPool() self._server_info_list = tentative_server_info_list def start_connectivity_testing(self, max_threads=_DEFAULT_MAX_THREADS, network_timeout=None): # type: (int, Optional[int]) -> None for tentative_server_info in self._server_info_list: self._thread_pool.add_job( (tentative_server_info.test_connectivity_to_server, [network_timeout])) nb_threads = min(len(self._server_info_list), max_threads) self._thread_pool.start(nb_threads) def get_reachable_servers(self): # type: () -> Iterable[ServerConnectivityInfo] for (job, _) in self._thread_pool.get_result(): test_connectivity_to_server_method, _ = job # TODO(AD): Using __self__ here is really ugly server_info = test_connectivity_to_server_method.__self__ # type: ignore yield server_info def get_invalid_servers(self): # type: () -> Iterable[Tuple[ServerConnectivityInfo, Exception]] for (job, exception) in self._thread_pool.get_error(): test_connectivity_to_server_method, _ = job # TODO(AD): Using __self__ here is really ugly server_info = test_connectivity_to_server_method.__self__ # type: ignore yield (server_info, exception)
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
def process_task(self, server_connectivity_info, scan_command): # type: (ServerConnectivityInfo, CipherSuiteScanCommand) -> CipherSuiteScanResult ssl_version = self.SSL_VERSIONS_MAPPING[scan_command.__class__] # Get the list of available cipher suites for the given ssl version 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 cipher_list = [] ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version, should_use_legacy_openssl=True) ssl_connection.ssl_client.set_cipher_list( 'ALL:COMPLEMENTOFALL:-PSK:-SRP') cipher_list.extend(ssl_connection.ssl_client.get_cipher_list()) ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version, should_use_legacy_openssl=False) ssl_connection.ssl_client.set_cipher_list( 'ALL:COMPLEMENTOFALL:-PSK:-SRP') cipher_list.extend(ssl_connection.ssl_client.get_cipher_list()) # Lastly we have to remove TLS 1.3 cipher suites cipher_list = [ cipher for cipher in ssl_connection.ssl_client.get_cipher_list() if 'TLS13' not in cipher ] # And remove duplicates (ie. supported by both legacy and modern OpenSSL) cipher_list = set(cipher_list) elif ssl_version == OpenSslVersionEnum.TLSV1_3: # For TLS 1.3 we need to manually pick the cipher suites as there is no OpenSSL cipher string to select them ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection( override_ssl_version=ssl_version) cipher_list = [ cipher for cipher in ssl_connection.ssl_client.get_cipher_list() if 'TLS13' in cipher ] 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
def process_task(self, server_connectivity_info, scan_command): # type: (ServerConnectivityInfo, CipherSuiteScanCommand) -> CipherSuiteScanResult ssl_version = self.SSL_VERSIONS_MAPPING[scan_command.__class__] # Get the list of available cipher suites for the given ssl version 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 cipher_list = [] ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(override_ssl_version=ssl_version, should_use_legacy_openssl=True) ssl_connection.ssl_client.set_cipher_list('ALL:COMPLEMENTOFALL:-PSK:-SRP') cipher_list.extend(ssl_connection.ssl_client.get_cipher_list()) ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(override_ssl_version=ssl_version, should_use_legacy_openssl=False) ssl_connection.ssl_client.set_cipher_list('ALL:COMPLEMENTOFALL:-PSK:-SRP') cipher_list.extend(ssl_connection.ssl_client.get_cipher_list()) # Lastly we have to remove TLS 1.3 cipher suites cipher_list = [cipher for cipher in ssl_connection.ssl_client.get_cipher_list() if 'TLS13' not in cipher] # And remove duplicates (ie. supported by both legacy and modern OpenSSL) cipher_list = set(cipher_list) elif ssl_version == OpenSslVersionEnum.TLSV1_3: # For TLS 1.3 we need to manually pick the cipher suites as there is no OpenSSL cipher string to select them ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(override_ssl_version=ssl_version) cipher_list = [cipher for cipher in ssl_connection.ssl_client.get_cipher_list() if 'TLS13' in cipher] 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_list = self._get_preferred_cipher_suite_list(server_connectivity_info, ssl_version, accepted_cipher_list) # Generate the results plugin_result = CipherSuiteScanResult(server_connectivity_info, scan_command, preferred_cipher_list, accepted_cipher_list, rejected_cipher_list, errored_cipher_list) return plugin_result
def process_task( self, server_info: ServerConnectivityInfo, scan_command: PluginScanCommand ) -> 'CertificateInfoScanResult': if not isinstance(scan_command, CertificateInfoScanCommand): raise ValueError('Unexpected scan command') final_trust_store_list = TrustStoresRepository.get_default().get_all_stores() if scan_command.custom_ca_file: custom_ca_file_path = Path(scan_command.custom_ca_file) if not custom_ca_file_path.is_file(): raise ValueError(f'Could not open supplied CA file at "{custom_ca_file_path}"') final_trust_store_list.append(TrustStore(custom_ca_file_path, 'Custom --ca_file', 'N/A')) # Workaround for https://github.com/pyca/cryptography/issues/3495 default_backend() thread_pool = ThreadPool() for trust_store in final_trust_store_list: # Try to connect with each trust store thread_pool.add_job((self._get_and_verify_certificate_chain, [server_info, trust_store])) # Start processing the jobs; one thread per trust thread_pool.start(len(final_trust_store_list)) # Store the results as they come path_validation_result_list = [] path_validation_error_list = [] ocsp_response = None received_chain = None for (job, result) in thread_pool.get_result(): (_, (_, trust_store)) = job received_chain_as_pem, verified_chain_as_pem, validation_result, _ocsp_response = result # Parse the certificates using the cryptography module if not received_chain: received_chain = [ load_pem_x509_certificate(pem_cert.encode('ascii'), backend=default_backend()) for pem_cert in received_chain_as_pem ] 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 # Keep the OCSP response if the validation was successful and a response was returned if _ocsp_response: ocsp_response = _ocsp_response # Store the returned verify string for each trust store path_validation_result_list.append(PathValidationResult(trust_store, verified_chain, validation_result)) # Store thread pool errors last_exception = None for (job, exception) in thread_pool.get_error(): (_, (_, trust_store)) = job path_validation_error_list.append(PathValidationError(trust_store, exception)) last_exception = exception thread_pool.join() if len(path_validation_error_list) == len(final_trust_store_list): # All connections failed unexpectedly; raise an exception instead of returning a result raise last_exception # type: ignore if not received_chain: raise ValueError('Error: Could not retrieve the server certificate chain') # All done return CertificateInfoScanResult( server_info, scan_command, received_chain, path_validation_result_list, path_validation_error_list, ocsp_response )
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
def process_task(self, server_connectivity_info, plugin_command, options_dict=None): if options_dict and 'verbose' in options_dict.keys(): verbose_mode = options_dict['verbose'] else: verbose_mode = False test_protocols = {'SSLv3', 'TLSv1'} thread_pool = ThreadPool() ciphers_list = [] support_protocol_list = [] for proto in test_protocols: if self.test_protocol_support(PROTOCOL_VERSION[proto], server_connectivity_info): ssl_client = SslClient(ssl_version=PROTOCOL_VERSION[proto]) ssl_client.set_cipher_list(proto) ciphers_list = ssl_client.get_cipher_list() for cipher in ciphers_list: thread_pool.add_job((self._test_ciphersuite, (server_connectivity_info, PROTOCOL_VERSION[proto], cipher))) support_protocol_list.append(proto) thread_pool.start(nb_threads=min(len(ciphers_list), self.MAX_THREADS)) accept_ciphers = [] reject_ciphers = [] if verbose_mode: print ' VERBOSE MODE PRINT' print ' ------------------' for completed_job in thread_pool.get_result(): (job, cipher_result) = completed_job if isinstance(cipher_result, AcceptCipher): accept_ciphers.append(cipher_result) elif isinstance(cipher_result, RejectCipher): reject_ciphers.append(cipher_result) else: raise ValueError("Unexpected result") if verbose_mode: cipher_result.print_cipher() if verbose_mode: print ' ----------------------' print ' END VERBOSE MODE PRINT' print ' ----------------------' for error_job in thread_pool.get_error(): (_, exception) = error_job raise exception thread_pool.join() support_vulnerable_ciphers_set = self.get_vulnerable_ciphers( accept_ciphers) is_vulnerable = True \ if len(support_vulnerable_ciphers_set) > 0 \ else False return BEASTVulnerabilityTesterResult(server_connectivity_info, plugin_command, options_dict, support_vulnerable_ciphers_set, is_vulnerable, support_protocol_list)