| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- #!/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()
|