#!/usr/bin/env python3 """ CipherScanner - NS Configuration Cipher Suite Compliance Checker Reads cipher suites from scan output and checks against NS configuration file """ import re import json import argparse import sys from datetime import datetime from collections import defaultdict class CipherScanner: """Main CipherScanner class for analyzing cipher suite compliance""" def __init__(self, ns_conf_file, cipher_file): """Initialize CipherScanner with configuration files""" self.ns_conf_file = ns_conf_file self.cipher_file = cipher_file self.iana_ciphers = [] self.ns_config = {} self.analysis_results = {} def run_scan(self): """Run the complete cipher scan and analysis""" print(f""" ╔{'═'*78}╗ ║{'CIPHERSCANNER':^78}║ ╠{'═'*78}╣ ║ NS Configuration: {self.ns_conf_file:<56}║ ║ Cipher File: {self.cipher_file:<58}║ ╚{'═'*78}╝ """) # Parse cipher suites self.iana_ciphers, _ = self.parse_ciphers_from_text(self.cipher_file) if not self.iana_ciphers: print("❌ No cipher suites parsed. Scan aborted.") return False # Parse NS configuration self.ns_config = self.parse_ns_config(self.ns_conf_file) if not self.ns_config: print("❌ No NS configuration parsed. Scan aborted.") return False # Analyze compliance self.analysis_results = self.analyze_cipher_compliance() # Generate report self.generate_report() return True def parse_ciphers_from_text(self, text_file): """ Parse cipher suites from text file with format: Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035) """ ciphers = [] hex_to_name = {} try: with open(text_file, 'r') as f: lines = f.readlines() cipher_pattern = re.compile( r'Cipher Suite:\s*([\w_]+)\s+\(([^)]+)\)', re.IGNORECASE ) for line in lines: match = cipher_pattern.search(line.strip()) if match: cipher_name = match.group(1).strip() hex_value = match.group(2).strip() ciphers.append(cipher_name) hex_to_name[hex_value] = cipher_name print(f"✅ CipherScanner parsed {len(ciphers)} cipher suites from {text_file}") return ciphers, hex_to_name except FileNotFoundError: print(f"❌ CipherScanner: File not found: {text_file}") return [], {} except Exception as e: print(f"❌ CipherScanner error parsing cipher file: {e}") return [], {} def parse_ns_config(self, ns_conf_file): """ Parse NS configuration file for SSL/TLS related configurations """ ssl_configs = { 'cipher_groups': [], 'ssl_profiles': [], 'ssl_vservers': [], 'ssl_parameters': {} } try: with open(ns_conf_file, 'r') as f: content = f.read() # Remove comments and blank lines for cleaner parsing lines = [] for line in content.split('\n'): line = line.strip() # Remove comments (starting with # or //) line = re.sub(r'(#|//).*$', '', line).strip() if line: lines.append(line) # Join lines for multi-line command parsing content_clean = '\n'.join(lines) print(f"\n🔍 CipherScanner parsing NS configuration from {ns_conf_file}") # Find cipher groups - improved regex pattern cipher_group_pattern = re.compile( r'add\s+ssl\s+cipher\s+([^\s]+)\s+(.*?)(?=\n\s*(?:add|set|bind|\Z))', re.IGNORECASE | re.DOTALL | re.MULTILINE ) matches = cipher_group_pattern.findall(content_clean) for match in matches: group_name = match[0] config = match[1].strip() # Extract ciphers from group configuration cipher_list = [] # Try different patterns to find cipher names cipher_matches = re.findall(r'["\']?([A-Z0-9_\-]+)["\']?', config) for cipher_candidate in cipher_matches: # Filter out common non-cipher patterns if (len(cipher_candidate) > 5 and '-' in cipher_candidate and not cipher_candidate.startswith(('PRIORITY', 'DEFAULT'))): cipher_list.append(cipher_candidate) # Alternative: Look for cipherName parameter cipher_name_match = re.search(r'cipherName\s*[:=]\s*["\']([^"\']+)["\']', config, re.IGNORECASE) if cipher_name_match: cipher_string = cipher_name_match.group(1) cipher_list = [c.strip() for c in cipher_string.split('-') if c.strip()] ssl_configs['cipher_groups'].append({ 'name': group_name, 'ciphers': cipher_list, 'raw_config': config[:500] + "..." if len(config) > 500 else config }) # Find SSL vservers vserver_pattern = re.compile( r'add\s+lb\s+vserver\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*?(?=\n\s*(?:add|set|bind|\Z))', re.IGNORECASE | re.DOTALL | re.MULTILINE ) vserver_matches = vserver_pattern.findall(content_clean) for match in vserver_matches: vserver_name = match[0] protocol = match[1] ip = match[2] port = match[3] # Look for SSL bindings for this vserver binding_pattern = re.compile( r'bind\s+ssl\s+vserver\s+' + re.escape(vserver_name) + r'\s+(.*?)(?=\n\s*(?:bind|add|set|\Z))', re.IGNORECASE | re.DOTALL | re.MULTILINE ) binding_match = binding_pattern.search(content_clean) ssl_config = { 'name': vserver_name, 'protocol': protocol, 'ip': ip, 'port': port, 'ssl_profile': None, 'certificate': None, 'cipher_group': None } if binding_match: binding_config = binding_match.group(1) # Extract cipher group cipher_group_match = re.search(r'-cipherName\s+(\S+)', binding_config, re.IGNORECASE) if cipher_group_match: ssl_config['cipher_group'] = cipher_group_match.group(1) # Extract SSL profile profile_match = re.search(r'-sslProfile\s+(\S+)', binding_config, re.IGNORECASE) if profile_match: ssl_config['ssl_profile'] = profile_match.group(1) # Extract certificate cert_match = re.search(r'-certkeyName\s+(\S+)', binding_config, re.IGNORECASE) if cert_match: ssl_config['certificate'] = cert_match.group(1) # Only include SSL/TLS vservers if (protocol.upper() in ['SSL', 'SSL_TCP', 'SSL_BRIDGE', 'TCP'] and port in ['443', '8443', '9443', '10443']): ssl_configs['ssl_vservers'].append(ssl_config) # Find SSL profiles profile_pattern = re.compile( r'add\s+ssl\s+profile\s+(\S+)\s+(.*?)(?=\n\s*(?:add|set|bind|\Z))', re.IGNORECASE | re.DOTALL | re.MULTILINE ) profile_matches = profile_pattern.findall(content_clean) for match in profile_matches: profile_name = match[0] config = match[1] # Extract cipher settings from profile cipher_match = re.search(r'-cipherName\s+(\S+)', config, re.IGNORECASE) cipher_group = cipher_match.group(1) if cipher_match else None # Extract other SSL settings tls_settings = { 'tls11_enabled': 'NOT_FOUND', 'tls12_enabled': 'NOT_FOUND', 'tls13_enabled': 'NOT_FOUND' } for tls_ver in ['tls11', 'tls12', 'tls13']: tls_match = re.search(rf'-{tls_ver}\s+(\S+)', config, re.IGNORECASE) if tls_match: tls_settings[f'{tls_ver}_enabled'] = tls_match.group(1) ssl_configs['ssl_profiles'].append({ 'name': profile_name, 'cipher_group': cipher_group, **tls_settings, 'raw_config': config[:300] + "..." if len(config) > 300 else config }) # Find SSL parameter settings param_patterns = [ (r'set\s+ssl\s+parameter\s+-ssl3\s+(\S+)', 'ssl3_enabled'), (r'set\s+ssl\s+parameter\s+-tls1\s+(\S+)', 'tls1_enabled'), (r'set\s+ssl\s+parameter\s+-tls11\s+(\S+)', 'tls11_enabled'), (r'set\s+ssl\s+parameter\s+-tls12\s+(\S+)', 'tls12_enabled'), (r'set\s+ssl\s+parameter\s+-tls13\s+(\S+)', 'tls13_enabled'), (r'set\s+ssl\s+parameter\s+-denySSLReneg\s+(\S+)', 'deny_ssl_reneg'), ] for pattern, key in param_patterns: match = re.search(pattern, content_clean, re.IGNORECASE) if match: ssl_configs['ssl_parameters'][key] = match.group(1) print(f"✅ CipherScanner found {len(ssl_configs['cipher_groups'])} cipher groups") print(f"✅ CipherScanner found {len(ssl_configs['ssl_vservers'])} SSL vservers") print(f"✅ CipherScanner found {len(ssl_configs['ssl_profiles'])} SSL profiles") return ssl_configs except FileNotFoundError: print(f"❌ CipherScanner: File not found: {ns_conf_file}") return {} except Exception as e: print(f"❌ CipherScanner error parsing NS configuration: {e}") import traceback traceback.print_exc() return {} def map_ns_cipher_to_iana(self, ns_cipher): """ Map NetScaler cipher names to IANA/RFC names """ # Clean the cipher name ns_cipher = ns_cipher.strip().upper() cipher_mappings = { # AES CBC ciphers 'SSL3-RSA-AES-256-CBC-SHA': 'TLS_RSA_WITH_AES_256_CBC_SHA', 'SSL3-RSA-AES-128-CBC-SHA': 'TLS_RSA_WITH_AES_128_CBC_SHA', 'TLS1-RSA-AES-256-CBC-SHA': 'TLS_RSA_WITH_AES_256_CBC_SHA', 'TLS1-RSA-AES-128-CBC-SHA': 'TLS_RSA_WITH_AES_128_CBC_SHA', 'TLS1.2-RSA-AES-256-CBC-SHA': 'TLS_RSA_WITH_AES_256_CBC_SHA', 'TLS1.2-RSA-AES-128-CBC-SHA': 'TLS_RSA_WITH_AES_128_CBC_SHA', 'TLS1.2-RSA-AES-256-CBC-SHA256': 'TLS_RSA_WITH_AES_256_CBC_SHA256', 'TLS1.2-RSA-AES-128-CBC-SHA256': 'TLS_RSA_WITH_AES_128_CBC_SHA256', # AES GCM ciphers 'TLS1.2-RSA-AES-256-GCM-SHA384': 'TLS_RSA_WITH_AES_256_GCM_SHA384', 'TLS1.2-RSA-AES-128-GCM-SHA256': 'TLS_RSA_WITH_AES_128_GCM_SHA256', # ECDHE RSA ciphers 'TLS1.2-ECDHE-RSA-AES-256-CBC-SHA': 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA', 'TLS1.2-ECDHE-RSA-AES-128-CBC-SHA': 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA', 'TLS1.2-ECDHE-RSA-AES-256-CBC-SHA384': 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384', 'TLS1.2-ECDHE-RSA-AES-128-CBC-SHA256': 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', 'TLS1.2-ECDHE-RSA-AES-256-GCM-SHA384': 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', 'TLS1.2-ECDHE-RSA-AES-128-GCM-SHA256': 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256', # ECDHE ECDSA ciphers 'TLS1.2-ECDHE-ECDSA-AES-256-CBC-SHA': 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA', 'TLS1.2-ECDHE-ECDSA-AES-128-CBC-SHA': 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA', 'TLS1.2-ECDHE-ECDSA-AES-256-CBC-SHA384': 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384', 'TLS1.2-ECDHE-ECDSA-AES-128-CBC-SHA256': 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'TLS1.2-ECDHE-ECDSA-AES-256-GCM-SHA384': 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384', 'TLS1.2-ECDHE-ECDSA-AES-128-GCM-SHA256': 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256', # DHE RSA ciphers 'TLS1.2-DHE-RSA-AES-256-CBC-SHA': 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA', 'TLS1.2-DHE-RSA-AES-128-CBC-SHA': 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA', 'TLS1.2-DHE-RSA-AES-256-CBC-SHA256': 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256', 'TLS1.2-DHE-RSA-AES-128-CBC-SHA256': 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256', 'TLS1.2-DHE-RSA-AES-256-GCM-SHA384': 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384', 'TLS1.2-DHE-RSA-AES-128-GCM-SHA256': 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256', # TLS 1.3 ciphers (NetScaler format) 'TLS1.3-AES256-GCM-SHA384': 'TLS_AES_256_GCM_SHA384', 'TLS1.3-AES128-GCM-SHA256': 'TLS_AES_128_GCM_SHA256', 'TLS1.3-CHACHA20-POLY1305-SHA256': 'TLS_CHACHA20_POLY1305_SHA256', # Weak/Deprecated ciphers 'SSL3-RSA-RC4-MD5': 'TLS_RSA_WITH_RC4_128_MD5', 'SSL3-RSA-RC4-SHA': 'TLS_RSA_WITH_RC4_128_SHA', 'SSL3-RSA-DES-CBC3-SHA': 'TLS_RSA_WITH_3DES_EDE_CBC_SHA', 'TLS1-RSA-RC4-MD5': 'TLS_RSA_WITH_RC4_128_MD5', 'TLS1-RSA-RC4-SHA': 'TLS_RSA_WITH_RC4_128_SHA', # Common NetScaler cipher patterns (simplified) 'TLS1.2-ECDHE-ECDSA-AES256-SHA384': 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384', 'TLS1.2-ECDHE-ECDSA-AES128-SHA256': 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', 'TLS1.2-ECDHE-RSA-AES256-SHA384': 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384', 'TLS1.2-ECDHE-RSA-AES128-SHA256': 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', } # Direct match if ns_cipher in cipher_mappings: return cipher_mappings[ns_cipher] # Try to match patterns # Remove version prefixes for pattern matching cipher_without_version = ns_cipher version_prefixes = ['SSL3-', 'TLS1-', 'TLS1.1-', 'TLS1.2-', 'TLS1.3-'] for prefix in version_prefixes: if ns_cipher.startswith(prefix): cipher_without_version = ns_cipher[len(prefix):] break # Pattern-based mapping if 'AES256-GCM-SHA384' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version: return 'TLS_RSA_WITH_AES_256_GCM_SHA384' elif 'AES128-GCM-SHA256' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version: return 'TLS_RSA_WITH_AES_128_GCM_SHA256' elif 'AES256-CBC-SHA256' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version: return 'TLS_RSA_WITH_AES_256_CBC_SHA256' elif 'AES128-CBC-SHA256' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version: return 'TLS_RSA_WITH_AES_128_CBC_SHA256' elif 'AES256-CBC-SHA' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version: return 'TLS_RSA_WITH_AES_256_CBC_SHA' elif 'AES128-CBC-SHA' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version: return 'TLS_RSA_WITH_AES_128_CBC_SHA' elif 'AES256-GCM-SHA384' in cipher_without_version and 'ECDHE-RSA' in cipher_without_version: return 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' elif 'AES128-GCM-SHA256' in cipher_without_version and 'ECDHE-RSA' in cipher_without_version: return 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' elif 'AES256-GCM-SHA384' in cipher_without_version and 'ECDHE-ECDSA' in cipher_without_version: return 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384' elif 'AES128-GCM-SHA256' in cipher_without_version and 'ECDHE-ECDSA' in cipher_without_version: return 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256' return ns_cipher # Return as-is if no mapping found def analyze_cipher_compliance(self): """ Analyze cipher compliance between IANA ciphers and NS configuration """ results = { 'configured_ciphers': set(), 'configured_in_ns': [], 'not_configured': [], 'weak_ciphers_found': [], 'tls_versions': {}, 'cipher_group_analysis': [], 'ns_cipher_mappings': {} } # Extract all ciphers configured in NS for group in self.ns_config.get('cipher_groups', []): group_ciphers = [] for ns_cipher in group['ciphers']: iana_cipher = self.map_ns_cipher_to_iana(ns_cipher) results['configured_ciphers'].add(iana_cipher) results['ns_cipher_mappings'][ns_cipher] = iana_cipher group_ciphers.append({ 'ns_name': ns_cipher, 'iana_name': iana_cipher }) results['cipher_group_analysis'].append({ 'group_name': group['name'], 'ciphers': group_ciphers, 'count': len(group_ciphers) }) # Check which IANA ciphers are configured for iana_cipher in self.iana_ciphers: if iana_cipher in results['configured_ciphers']: results['configured_in_ns'].append(iana_cipher) else: results['not_configured'].append(iana_cipher) # Check for weak ciphers weak_patterns = [ 'RC4', 'DES', '_3DES_', 'MD5', 'NULL', 'EXPORT', 'ANON', 'ADH', 'IDEA', 'SEED', 'CAMELLIA' ] for cipher in results['configured_ciphers']: if any(pattern in cipher.upper() for pattern in weak_patterns): results['weak_ciphers_found'].append(cipher) # Check TLS version settings params = self.ns_config.get('ssl_parameters', {}) results['tls_versions'] = { 'SSL3': params.get('ssl3_enabled', 'NOT_CONFIGURED'), 'TLS1.0': params.get('tls1_enabled', 'NOT_CONFIGURED'), 'TLS1.1': params.get('tls11_enabled', 'NOT_CONFIGURED'), 'TLS1.2': params.get('tls12_enabled', 'NOT_CONFIGURED'), 'TLS1.3': params.get('tls13_enabled', 'NOT_CONFIGURED') } return results def generate_report(self): """ Generate detailed compliance report """ print(f"\n{'='*80}") print(f"CIPHERSCANNER REPORT") print(f"{'='*80}") print(f"\n📊 CipherScanner Summary:") print(f" IANA Ciphers Found: {len(self.iana_ciphers)}") print(f" Ciphers Configured in NS: {len(self.analysis_results['configured_ciphers'])}") print(f" Ciphers Matched: {len(self.analysis_results['configured_in_ns'])}") print(f" Ciphers Not Configured: {len(self.analysis_results['not_configured'])}") print(f" Weak Ciphers Found: {len(self.analysis_results['weak_ciphers_found'])}") print(f"\n🔒 TLS Version Configuration:") for version, status in self.analysis_results['tls_versions'].items(): status_icon = '✅' if status == 'DISABLED' and version in ['SSL3', 'TLS1.0', 'TLS1.1'] else \ '✅' if status == 'ENABLED' and version in ['TLS1.2', 'TLS1.3'] else \ '⚠️ ' if status == 'ENABLED' and version in ['SSL3', 'TLS1.0', 'TLS1.1'] else \ '❓' print(f" {status_icon} {version}: {status}") if self.analysis_results['cipher_group_analysis']: print(f"\n📁 Cipher Group Analysis:") for group in self.analysis_results['cipher_group_analysis']: print(f" • {group['group_name']}: {group['count']} ciphers") if self.analysis_results['configured_in_ns']: print(f"\n✅ Ciphers Configured in NS ({len(self.analysis_results['configured_in_ns'])}):") for cipher in sorted(self.analysis_results['configured_in_ns'])[:20]: print(f" ✓ {cipher}") if len(self.analysis_results['configured_in_ns']) > 20: print(f" ... and {len(self.analysis_results['configured_in_ns']) - 20} more") if self.analysis_results['not_configured']: print(f"\n❌ Ciphers NOT Configured in NS ({len(self.analysis_results['not_configured'])}):") for cipher in sorted(self.analysis_results['not_configured'])[:20]: print(f" ✗ {cipher}") if len(self.analysis_results['not_configured']) > 20: print(f" ... and {len(self.analysis_results['not_configured']) - 20} more") if self.analysis_results['weak_ciphers_found']: print(f"\n⚠️ WEAK Ciphers Found in NS Configuration ({len(self.analysis_results['weak_ciphers_found'])}):") for cipher in sorted(self.analysis_results['weak_ciphers_found']): print(f" ⚠️ {cipher}") print("\n CipherScanner RECOMMENDATION: Remove weak ciphers from configuration") # SSL VServer Summary if self.ns_config.get('ssl_vservers'): print(f"\n🌐 SSL Virtual Servers ({len(self.ns_config['ssl_vservers'])}):") for vs in self.ns_config['ssl_vservers']: cipher_status = "✅" if vs.get('cipher_group') else "❌" print(f" {cipher_status} {vs['name']} ({vs['ip']}:{vs['port']})") if vs.get('cipher_group'): print(f" Cipher Group: {vs['cipher_group']}") if vs.get('ssl_profile'): print(f" SSL Profile: {vs['ssl_profile']}") # Security Recommendations print(f"\n📋 CIPHERSCANNER SECURITY RECOMMENDATIONS:") recommendations = [] # Check for weak protocols if self.analysis_results['tls_versions'].get('SSL3') == 'ENABLED': recommendations.append("❌ DISABLE SSL 3.0 (Vulnerable to POODLE attack)") if self.analysis_results['tls_versions'].get('TLS1.0') == 'ENABLED': recommendations.append("⚠️ DISABLE TLS 1.0 (Considered weak)") if self.analysis_results['tls_versions'].get('TLS1.1') == 'ENABLED': recommendations.append("⚠️ CONSIDER DISABLING TLS 1.1") if self.analysis_results['weak_ciphers_found']: recommendations.append("❌ REMOVE weak ciphers (RC4, DES, 3DES, MD5)") if len(self.analysis_results['not_configured']) > 0: recommendations.append(f"⚠️ Configure missing {len(self.analysis_results['not_configured'])} cipher(s)") if not recommendations: recommendations.append("✅ CipherScanner: Configuration looks secure!") for i, rec in enumerate(recommendations, 1): print(f" {i}. {rec}") print(f"\n{'='*80}") print(f"CipherScanner report generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'='*80}") def save_json_report(self, output_file): """Save comprehensive report as JSON""" report = { 'metadata': { 'generated_at': datetime.now().isoformat(), 'tool': 'CipherScanner', 'version': '1.0.0', 'iana_cipher_count': len(self.iana_ciphers), 'ns_config_file': self.ns_conf_file, 'cipher_file': self.cipher_file }, 'iana_ciphers': self.iana_ciphers, 'ns_configuration_summary': { 'cipher_groups_count': len(self.ns_config.get('cipher_groups', [])), 'ssl_vservers_count': len(self.ns_config.get('ssl_vservers', [])), 'ssl_profiles_count': len(self.ns_config.get('ssl_profiles', [])), 'tls_versions': self.ns_config.get('ssl_parameters', {}) }, 'compliance_analysis': { 'configured_ciphers': list(self.analysis_results['configured_ciphers']), 'configured_in_ns': self.analysis_results['configured_in_ns'], 'not_configured': self.analysis_results['not_configured'], 'weak_ciphers_found': self.analysis_results['weak_ciphers_found'], 'tls_versions': self.analysis_results['tls_versions'], 'ns_cipher_mappings': self.analysis_results['ns_cipher_mappings'] }, 'security_assessment': { 'has_weak_ciphers': len(self.analysis_results['weak_ciphers_found']) > 0, 'weak_cipher_count': len(self.analysis_results['weak_ciphers_found']), 'tls1_2_enabled': self.analysis_results['tls_versions'].get('TLS1.2') == 'ENABLED', 'tls1_3_enabled': self.analysis_results['tls_versions'].get('TLS1.3') == 'ENABLED', 'ssl3_disabled': self.analysis_results['tls_versions'].get('SSL3') == 'DISABLED', 'overall_grade': 'A' if len(self.analysis_results['weak_ciphers_found']) == 0 else 'C' }, 'cipher_scanner_info': { 'scan_completed': True, 'scan_timestamp': datetime.now().isoformat(), 'exit_code': self.get_exit_code() } } with open(output_file, 'w') as f: json.dump(report, f, indent=2) print(f"\n💾 CipherScanner JSON report saved to: {output_file}") return report def get_exit_code(self): """Determine exit code based on scan results""" if self.analysis_results['weak_ciphers_found']: return 2 # Failure: weak ciphers detected elif len(self.analysis_results['not_configured']) > len(self.iana_ciphers) * 0.5: return 1 # Warning: less than 50% of ciphers configured else: return 0 # Success def main(): parser = argparse.ArgumentParser( description='CipherScanner - NS Configuration Cipher Suite Compliance Checker', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: %(prog)s --ns-conf ns.conf --ciphers ciphers.txt %(prog)s --ns-conf /path/to/ns.conf --ciphers scan_results.txt --output report.json %(prog)s --ns-conf ns.conf --ciphers ciphers.txt --verbose CipherScanner helps security teams audit NS load balancer cipher configurations against discovered cipher suites from security scans. """ ) parser.add_argument('--nsconf', required=True, help='NS configuration file (ns.conf)') parser.add_argument('--cipher', required=True, help='Cipher suites text file') parser.add_argument('--output', help='Output JSON report file') parser.add_argument('--verbose', action='store_true', help='Show detailed parsing information') args = parser.parse_args() # Initialize and run CipherScanner scanner = CipherScanner(args.nsconf, args.cipher) if scanner.run_scan(): # Save JSON report if requested if args.output: scanner.save_json_report(args.output) # Exit with appropriate code exit_code = scanner.get_exit_code() if exit_code == 0: print(f"\n✅ CipherScanner: Compliance PASS - Configuration looks good!") elif exit_code == 1: print(f"\n⚠️ CipherScanner: Warning - Less than 50% of scanned ciphers are configured") else: print(f"\n❌ CipherScanner: Compliance FAILURE - Weak ciphers detected!") sys.exit(exit_code) else: print(f"\n❌ CipherScanner: Scan failed due to errors") sys.exit(3) if __name__ == "__main__": main()