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( '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 = [] 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 last_exception # 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_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 _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 create_thread_pool_for_protocol_tls(self, server_connectivity_info): """ Creates and returns instance of ThreadPool class. Adds into ThreadPool new jobs for each cipher suite, which is available for protocol TLS 1.1 and TLS 1.2 Args: server_connectivity_info (ServerConnectivityInfo): contains information for connection on server. """ thread_pool = ThreadPool() protocols = ['TLSv1.1', 'TLSv1.2'] cipher_list = [] for protocol in protocols: if self.test_protocol_support(server_connectivity_info, protocol): cipher_list = self.get_cipher_list(protocol) for cipher in cipher_list: thread_pool.add_job( (self._test_ciphersuite, (server_connectivity_info, protocol, cipher))) return (thread_pool, min(self.MAX_THREADS, len(cipher)))
def create_thread_pool_for_protocol_dtls(self, server_connectivity_info, port): """ Creates and returns instance of ThreadPool class. Adds into ThreadPool new jobs for each cipher suite, which is available for protocol DTLS 1.0 and/or DTLS 1.2 Args: server_connectivity_info (ServerConnectivityInfo): contains information for connection on server port (int): contains port number for connecting comunication. """ dtls_protocols = self.get_support_dtls_protocols_by_client() thread_pool = ThreadPool() cipher_list = [] for protocol in dtls_protocols: if self.test_dtls_protocol_support(server_connectivity_info, protocol, port): cipher_list = self.get_dtls_cipher_list(protocol) for cipher in cipher_list: thread_pool.add_job( (self._test_dtls_ciphersuite, (server_connectivity_info, protocol, cipher, port))) return (thread_pool, 1)
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 _test_session_resumption_rate(self, server_info, resumption_attempts_nb): """Attempts several session ID resumption with the server.""" thread_pool = ThreadPool() for _ in xrange(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
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 __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 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 __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 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