def test(self): """The final output only gets written at the end, when calling scans_completed(). Hence we need to call all the methods in the right order and validate the final output at the end. """ output_file = StringIO() generator = XmlOutputGenerator(output_file) generator.command_line_parsed(None, MockCommandLineValues()) failed_scan = FailedServerScan( server_string=u'unibadeéè.com', connection_exception=ServerConnectivityError( error_msg=u'Some érrôr')) generator.server_connectivity_test_failed(failed_scan) server_info = MockServerConnectivityInfo() generator.server_connectivity_test_succeeded(server_info) generator.scans_started() plugin_xml_out_1 = Element(u'plugin1', attrib={'test1': 'value1'}) plugin_xml_out_1.text = u'Plugin ûnicôdé output' plugin_result_1 = MockPluginResult('plugin1', None, plugin_xml_out_1) plugin_xml_out_2 = Element(u'plugin2', attrib={'test2': 'value2'}) plugin_xml_out_2.text = u'other plugin Output' plugin_result_2 = MockPluginResult('plugin2', None, plugin_xml_out_2) server_scan = CompletedServerScan(server_info, [plugin_result_1, plugin_result_2]) generator.server_scan_completed(server_scan) scan_time = 1.3 generator.scans_completed(scan_time) received_output = unicode(output_file.getvalue(), 'utf-8') output_file.close() # Ensure the output properly listed the connectivity error with unicode escaped as \u sequences self.assertIn(u'unibadeéè.com', received_output) self.assertIn(u'Some érrôr', received_output) # Ensure the output properly listed the online domain self.assertIn(server_info.hostname, received_output) self.assertIn(str(server_info.port), received_output) self.assertIn(server_info.ip_address, received_output) # Ensure the output displayed the plugin's XML output self.assertIn(plugin_result_1.plugin_command, received_output) self.assertIn(plugin_result_2.plugin_command, received_output) self.assertIn(plugin_result_1.as_xml().text, received_output) self.assertIn(plugin_result_2.as_xml().text, received_output) # Ensure the console output displayed the total scan time self.assertIn('totalScanTime="{}"'.format(scan_time), received_output) self.assertIn( 'networkTimeout="{}"'.format(MockCommandLineValues().timeout), received_output) self.assertIn( 'networkMaxRetries="{}"'.format( MockCommandLineValues().nb_retries), received_output)
def test(self): """The final output only gets written at the end, when calling scans_completed(). Hence we need to call all the methods in the right order and validate the final output at the end. """ output_file = StringIO() generator = JsonOutputGenerator(output_file) generator.command_line_parsed(None, MockCommandLineValues()) failed_scan = FailedServerScan(server_string=u'unibadeéè.com', connection_exception=ServerConnectivityError(error_msg=u'Some érrôr')) generator.server_connectivity_test_failed(failed_scan) server_info = MockServerConnectivityInfo() generator.server_connectivity_test_succeeded(server_info) generator.scans_started() # noinspection PyTypeChecker plugin_result_1 = MockPluginResult('plugin1', u'Plugin ûnicôdé output', None) # noinspection PyTypeChecker plugin_result_2 = MockPluginResult('plugin2', u'other plugin Output', None) # noinspection PyTypeChecker server_scan = CompletedServerScan(server_info, [plugin_result_1, plugin_result_2]) generator.server_scan_completed(server_scan) scan_time = 1.3 generator.scans_completed(scan_time) received_output = output_file.getvalue() output_file.close() # Ensure the output properly listed the connectivity error with unicode escaped as \u sequences self.assertIn(json.dumps(u'unibadeéè.com', ensure_ascii=True), received_output) self.assertIn(json.dumps(u'Some érrôr', ensure_ascii=True), received_output) # Ensure the output properly listed the online domain self.assertIn(json.dumps(server_info.hostname, ensure_ascii=True), received_output) self.assertIn(str(server_info.port), received_output) self.assertIn(server_info.ip_address, received_output) # Ensure the output displayed the plugin's attributes as JSON self.assertIn(plugin_result_1.plugin_command, received_output) self.assertIn(plugin_result_2.plugin_command, received_output) self.assertIn('"text_output":', received_output) self.assertIn(json.dumps(plugin_result_1.text_output, ensure_ascii=True), received_output) self.assertIn(plugin_result_2.text_output, received_output) # Ensure the console output displayed the total scan time self.assertIn(str(scan_time), received_output) self.assertIn('"network_timeout": "{}"'.format(MockCommandLineValues().timeout), received_output) self.assertIn('"network_max_retries": "{}"'.format(MockCommandLineValues().nb_retries), received_output)
def test_server_connectivity_test_failed(self): output_file = StringIO() generator = ConsoleOutputGenerator(output_file) failed_scan = FailedServerScan( server_string=u'unicödeéè.com', connection_exception=ServerConnectivityError( error_msg=u'Some érrôr')) generator.server_connectivity_test_failed(failed_scan) received_output = output_file.getvalue() output_file.close() # Ensure the console output properly listed the connectivity error with unicode self.assertIn(u'unicödeéè.com', received_output) self.assertIn(u'Some érrôr', received_output) self.assertIn(u'discarding corresponding tasks', received_output)
def parse_command_line(self): """Parses the command line used to launch SSLyze. """ (args_command_list, args_target_list) = self._parser.parse_args() # Handle the --targets_in command line and fill args_target_list if args_command_list.targets_in: if args_target_list: raise CommandLineParsingError( "Cannot use --targets_list and specify targets within the command line." ) try: # Read targets from a file with open(args_command_list.targets_in) as f: for target in f.readlines(): if target.strip(): # Ignore empty lines if not target.startswith( '#'): # Ignore comment lines args_target_list.append(target.strip()) except IOError: raise CommandLineParsingError( "Can't read targets from input file '{}.".format( args_command_list.targets_in)) if not args_target_list: raise CommandLineParsingError('No targets to scan.') # Handle the --regular command line parameter as a shortcut if self._parser.has_option('--regular'): if getattr(args_command_list, 'regular'): setattr(args_command_list, 'regular', False) for cmd in self.REGULAR_CMD: setattr(args_command_list, cmd, True) # Sanity checks on the command line options # Prevent --quiet and --xml_out - if args_command_list.xml_file and args_command_list.xml_file == '-' and args_command_list.quiet: raise CommandLineParsingError( 'Cannot use --quiet with --xml_out -.') # Prevent --quiet and --json_out - if args_command_list.json_file and args_command_list.json_file == '-' and args_command_list.quiet: raise CommandLineParsingError( 'Cannot use --quiet with --json_out -.') # Prevent --xml_out - and --json_out - if args_command_list.json_file and args_command_list.json_file == '-' \ and args_command_list.xml_file and args_command_list.xml_file == '-': raise CommandLineParsingError( 'Cannot use --xml_out - with --json_out -.') # Sanity checks on the client cert options client_auth_creds = None if bool(args_command_list.cert) ^ bool(args_command_list.key): raise CommandLineParsingError( 'No private key or certificate file were given. See --cert and --key.' ) elif args_command_list.cert: # Private key formats if args_command_list.keyform == 'DER': key_type = SSL_FILETYPE_ASN1 elif args_command_list.keyform == 'PEM': key_type = SSL_FILETYPE_PEM else: raise CommandLineParsingError( '--keyform should be DER or PEM.') # Let's try to open the cert and key files try: client_auth_creds = ClientAuthenticationCredentials( args_command_list.cert, args_command_list.key, key_type, args_command_list.keypass) except ValueError as e: raise CommandLineParsingError( 'Invalid client authentication settings: {}.'.format(e[0])) # HTTP CONNECT proxy http_tunneling_settings = None if args_command_list.https_tunnel: try: http_tunneling_settings = HttpConnectTunnelingSettings.from_url( args_command_list.https_tunnel) except ValueError as e: raise CommandLineParsingError( 'Invalid proxy URL for --https_tunnel: {}.'.format(e[0])) # STARTTLS tls_wrapped_protocol = TlsWrappedProtocolEnum.PLAIN_TLS if args_command_list.starttls: if args_command_list.starttls not in self.START_TLS_PROTOCOLS: raise CommandLineParsingError(self.START_TLS_USAGE) else: # StartTLS was specified if args_command_list.starttls in self.STARTTLS_PROTOCOL_DICT.keys( ): # Protocol was given in the command line tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[ args_command_list.starttls] # Number of connection retries if args_command_list.nb_retries < 1: raise CommandLineParsingError( 'Cannot have a number smaller than 1 for --nb_retries.') # Create the server connectivity info for each specifed servers # A limitation when using the command line is that only one client_auth_credentials and http_tunneling_settings # can be specified, for all the servers to scan good_server_list = [] bad_server_list = [] for server_string in args_target_list: # Support unicode domains server_string = unicode(server_string, 'utf-8') try: good_server_list.append( ServerConnectivityInfo.from_command_line( server_string=server_string, tls_wrapped_protocol=tls_wrapped_protocol, tls_server_name_indication=args_command_list.sni, xmpp_to_hostname=args_command_list.xmpp_to, client_auth_credentials=client_auth_creds, http_tunneling_settings=http_tunneling_settings)) except ServerConnectivityError as e: # Will happen for example if the DNS lookup failed or the server string is malformed bad_server_list.append(FailedServerScan(server_string, e)) except ValueError as e: # Will happen for example if xmpp_to is specified for a non-XMPP connection raise CommandLineParsingError(e[0]) # Command line hacks # Handle --starttls=auto now that we parsed the server strings if args_command_list.starttls == 'auto': for server_info in good_server_list: # We use the port number to deduce the protocol if server_info.port in self.STARTTLS_PROTOCOL_DICT.keys(): server_info.tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[ server_info.port] # Handle --http_get now that we parsed the server strings # Doing it here is hacky as the option is defined within PluginOpenSSLCipherSuites if args_command_list.http_get: for server_info in good_server_list: if server_info.port == 443: server_info.tls_wrapped_protocol = TlsWrappedProtocolEnum.HTTPS return good_server_list, bad_server_list, args_command_list
def main(): # For py2exe builds freeze_support() # Handle SIGINT to terminate processes signal.signal(signal.SIGINT, sigint_handler) start_time = time() # Retrieve available plugins sslyze_plugins = PluginsFinder() available_plugins = sslyze_plugins.get_plugins() available_commands = sslyze_plugins.get_commands() # Create the command line parser and the list of available options sslyze_parser = CommandLineParser(available_plugins, __version__) try: good_server_list, bad_server_list, args_command_list = sslyze_parser.parse_command_line() except CommandLineParsingError as e: print e.get_error_msg() return output_hub = OutputHub() output_hub.command_line_parsed(available_plugins, args_command_list) # Initialize the pool of processes that will run each plugin if args_command_list.https_tunnel: # Maximum one process to not kill the proxy plugins_process_pool = PluginsProcessPool(sslyze_plugins, args_command_list.nb_retries, args_command_list.timeout, max_processes_nb=1) else: plugins_process_pool = PluginsProcessPool(sslyze_plugins, args_command_list.nb_retries, args_command_list.timeout) # Figure out which hosts are up and fill the task queue with work to do connectivity_tester = ServersConnectivityTester(good_server_list) connectivity_tester.start_connectivity_testing(network_timeout=args_command_list.timeout) # Store and print server whose command line string was bad for failed_scan in bad_server_list: output_hub.server_connectivity_test_failed(failed_scan) # Store and print servers we were able to connect to online_servers_list = [] for server_connectivity_info in connectivity_tester.get_reachable_servers(): online_servers_list.append(server_connectivity_info) output_hub.server_connectivity_test_succeeded(server_connectivity_info) # Send tasks to worker processes for plugin_command in available_commands: if getattr(args_command_list, plugin_command): # Get this plugin's options if there's any plugin_options_dict = {} for option in available_commands[plugin_command].get_interface().get_options(): # Was this option set ? if getattr(args_command_list,option.dest): plugin_options_dict[option.dest] = getattr(args_command_list, option.dest) plugins_process_pool.queue_plugin_task(server_connectivity_info, plugin_command, plugin_options_dict) # Store and print servers we were NOT able to connect to for tentative_server_info, exception in connectivity_tester.get_invalid_servers(): failed_scan = FailedServerScan(tentative_server_info.server_string, exception) output_hub.server_connectivity_test_failed(failed_scan) # Keep track of how many tasks have to be performed for each target task_num = 0 output_hub.scans_started() for command in available_commands: if getattr(args_command_list, command): task_num += 1 # Each host has a list of results result_dict = {} # We cannot use the server_info object directly as its address will change due to multiprocessing RESULT_KEY_FORMAT = u'{hostname}:{ip_address}:{port}'.format for server_info in online_servers_list: result_dict[RESULT_KEY_FORMAT(hostname=server_info.hostname, ip_address=server_info.ip_address, port=server_info.port)] = [] # Process the results as they come for plugin_result in plugins_process_pool.get_results(): server_info = plugin_result.server_info result_dict[RESULT_KEY_FORMAT(hostname=server_info.hostname, ip_address=server_info.ip_address, port=server_info.port)].append(plugin_result) plugin_result_list = result_dict[RESULT_KEY_FORMAT(hostname=server_info.hostname, ip_address=server_info.ip_address, port=server_info.port)] if len(plugin_result_list) == task_num: # Done with this server; send the result to the output hub output_hub.server_scan_completed(CompletedServerScan(server_info, plugin_result_list)) # All done exec_time = time()-start_time output_hub.scans_completed(exec_time)