scanner.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. #!/usr/bin/env python3
  2. """
  3. CipherScanner - NS Configuration Cipher Suite Compliance Checker
  4. Reads cipher suites from scan output and checks against NS configuration file
  5. """
  6. import re
  7. import json
  8. import argparse
  9. import sys
  10. from datetime import datetime
  11. from collections import defaultdict
  12. class CipherScanner:
  13. """Main CipherScanner class for analyzing cipher suite compliance"""
  14. def __init__(self, ns_conf_file, cipher_file):
  15. """Initialize CipherScanner with configuration files"""
  16. self.ns_conf_file = ns_conf_file
  17. self.cipher_file = cipher_file
  18. self.iana_ciphers = []
  19. self.ns_config = {}
  20. self.analysis_results = {}
  21. def run_scan(self):
  22. """Run the complete cipher scan and analysis"""
  23. print(f"""
  24. ╔{'═'*78}╗
  25. ║{'CIPHERSCANNER':^78}║
  26. ╠{'═'*78}╣
  27. ║ NS Configuration: {self.ns_conf_file:<56}║
  28. ║ Cipher File: {self.cipher_file:<58}║
  29. ╚{'═'*78}╝
  30. """)
  31. # Parse cipher suites
  32. self.iana_ciphers, _ = self.parse_ciphers_from_text(self.cipher_file)
  33. if not self.iana_ciphers:
  34. print("❌ No cipher suites parsed. Scan aborted.")
  35. return False
  36. # Parse NS configuration
  37. self.ns_config = self.parse_ns_config(self.ns_conf_file)
  38. if not self.ns_config:
  39. print("❌ No NS configuration parsed. Scan aborted.")
  40. return False
  41. # Analyze compliance
  42. self.analysis_results = self.analyze_cipher_compliance()
  43. # Generate report
  44. self.generate_report()
  45. return True
  46. def parse_ciphers_from_text(self, text_file):
  47. """
  48. Parse cipher suites from text file with format:
  49. Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
  50. """
  51. ciphers = []
  52. hex_to_name = {}
  53. try:
  54. with open(text_file, 'r') as f:
  55. lines = f.readlines()
  56. cipher_pattern = re.compile(
  57. r'Cipher Suite:\s*([\w_]+)\s+\(([^)]+)\)',
  58. re.IGNORECASE
  59. )
  60. for line in lines:
  61. match = cipher_pattern.search(line.strip())
  62. if match:
  63. cipher_name = match.group(1).strip()
  64. hex_value = match.group(2).strip()
  65. ciphers.append(cipher_name)
  66. hex_to_name[hex_value] = cipher_name
  67. print(f"✅ CipherScanner parsed {len(ciphers)} cipher suites from {text_file}")
  68. return ciphers, hex_to_name
  69. except FileNotFoundError:
  70. print(f"❌ CipherScanner: File not found: {text_file}")
  71. return [], {}
  72. except Exception as e:
  73. print(f"❌ CipherScanner error parsing cipher file: {e}")
  74. return [], {}
  75. def parse_ns_config(self, ns_conf_file):
  76. """
  77. Parse NS configuration file for SSL/TLS related configurations
  78. """
  79. ssl_configs = {
  80. 'cipher_groups': [],
  81. 'ssl_profiles': [],
  82. 'ssl_vservers': [],
  83. 'ssl_parameters': {}
  84. }
  85. try:
  86. with open(ns_conf_file, 'r') as f:
  87. content = f.read()
  88. # Remove comments and blank lines for cleaner parsing
  89. lines = []
  90. for line in content.split('\n'):
  91. line = line.strip()
  92. # Remove comments (starting with # or //)
  93. line = re.sub(r'(#|//).*$', '', line).strip()
  94. if line:
  95. lines.append(line)
  96. # Join lines for multi-line command parsing
  97. content_clean = '\n'.join(lines)
  98. print(f"\n🔍 CipherScanner parsing NS configuration from {ns_conf_file}")
  99. # Find cipher groups - improved regex pattern
  100. cipher_group_pattern = re.compile(
  101. r'add\s+ssl\s+cipher\s+([^\s]+)\s+(.*?)(?=\n\s*(?:add|set|bind|\Z))',
  102. re.IGNORECASE | re.DOTALL | re.MULTILINE
  103. )
  104. matches = cipher_group_pattern.findall(content_clean)
  105. for match in matches:
  106. group_name = match[0]
  107. config = match[1].strip()
  108. # Extract ciphers from group configuration
  109. cipher_list = []
  110. # Try different patterns to find cipher names
  111. cipher_matches = re.findall(r'["\']?([A-Z0-9_\-]+)["\']?', config)
  112. for cipher_candidate in cipher_matches:
  113. # Filter out common non-cipher patterns
  114. if (len(cipher_candidate) > 5 and
  115. '-' in cipher_candidate and
  116. not cipher_candidate.startswith(('PRIORITY', 'DEFAULT'))):
  117. cipher_list.append(cipher_candidate)
  118. # Alternative: Look for cipherName parameter
  119. cipher_name_match = re.search(r'cipherName\s*[:=]\s*["\']([^"\']+)["\']', config, re.IGNORECASE)
  120. if cipher_name_match:
  121. cipher_string = cipher_name_match.group(1)
  122. cipher_list = [c.strip() for c in cipher_string.split('-') if c.strip()]
  123. ssl_configs['cipher_groups'].append({
  124. 'name': group_name,
  125. 'ciphers': cipher_list,
  126. 'raw_config': config[:500] + "..." if len(config) > 500 else config
  127. })
  128. # Find SSL vservers
  129. vserver_pattern = re.compile(
  130. r'add\s+lb\s+vserver\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*?(?=\n\s*(?:add|set|bind|\Z))',
  131. re.IGNORECASE | re.DOTALL | re.MULTILINE
  132. )
  133. vserver_matches = vserver_pattern.findall(content_clean)
  134. for match in vserver_matches:
  135. vserver_name = match[0]
  136. protocol = match[1]
  137. ip = match[2]
  138. port = match[3]
  139. # Look for SSL bindings for this vserver
  140. binding_pattern = re.compile(
  141. r'bind\s+ssl\s+vserver\s+' + re.escape(vserver_name) + r'\s+(.*?)(?=\n\s*(?:bind|add|set|\Z))',
  142. re.IGNORECASE | re.DOTALL | re.MULTILINE
  143. )
  144. binding_match = binding_pattern.search(content_clean)
  145. ssl_config = {
  146. 'name': vserver_name,
  147. 'protocol': protocol,
  148. 'ip': ip,
  149. 'port': port,
  150. 'ssl_profile': None,
  151. 'certificate': None,
  152. 'cipher_group': None
  153. }
  154. if binding_match:
  155. binding_config = binding_match.group(1)
  156. # Extract cipher group
  157. cipher_group_match = re.search(r'-cipherName\s+(\S+)', binding_config, re.IGNORECASE)
  158. if cipher_group_match:
  159. ssl_config['cipher_group'] = cipher_group_match.group(1)
  160. # Extract SSL profile
  161. profile_match = re.search(r'-sslProfile\s+(\S+)', binding_config, re.IGNORECASE)
  162. if profile_match:
  163. ssl_config['ssl_profile'] = profile_match.group(1)
  164. # Extract certificate
  165. cert_match = re.search(r'-certkeyName\s+(\S+)', binding_config, re.IGNORECASE)
  166. if cert_match:
  167. ssl_config['certificate'] = cert_match.group(1)
  168. # Only include SSL/TLS vservers
  169. if (protocol.upper() in ['SSL', 'SSL_TCP', 'SSL_BRIDGE', 'TCP'] and
  170. port in ['443', '8443', '9443', '10443']):
  171. ssl_configs['ssl_vservers'].append(ssl_config)
  172. # Find SSL profiles
  173. profile_pattern = re.compile(
  174. r'add\s+ssl\s+profile\s+(\S+)\s+(.*?)(?=\n\s*(?:add|set|bind|\Z))',
  175. re.IGNORECASE | re.DOTALL | re.MULTILINE
  176. )
  177. profile_matches = profile_pattern.findall(content_clean)
  178. for match in profile_matches:
  179. profile_name = match[0]
  180. config = match[1]
  181. # Extract cipher settings from profile
  182. cipher_match = re.search(r'-cipherName\s+(\S+)', config, re.IGNORECASE)
  183. cipher_group = cipher_match.group(1) if cipher_match else None
  184. # Extract other SSL settings
  185. tls_settings = {
  186. 'tls11_enabled': 'NOT_FOUND',
  187. 'tls12_enabled': 'NOT_FOUND',
  188. 'tls13_enabled': 'NOT_FOUND'
  189. }
  190. for tls_ver in ['tls11', 'tls12', 'tls13']:
  191. tls_match = re.search(rf'-{tls_ver}\s+(\S+)', config, re.IGNORECASE)
  192. if tls_match:
  193. tls_settings[f'{tls_ver}_enabled'] = tls_match.group(1)
  194. ssl_configs['ssl_profiles'].append({
  195. 'name': profile_name,
  196. 'cipher_group': cipher_group,
  197. **tls_settings,
  198. 'raw_config': config[:300] + "..." if len(config) > 300 else config
  199. })
  200. # Find SSL parameter settings
  201. param_patterns = [
  202. (r'set\s+ssl\s+parameter\s+-ssl3\s+(\S+)', 'ssl3_enabled'),
  203. (r'set\s+ssl\s+parameter\s+-tls1\s+(\S+)', 'tls1_enabled'),
  204. (r'set\s+ssl\s+parameter\s+-tls11\s+(\S+)', 'tls11_enabled'),
  205. (r'set\s+ssl\s+parameter\s+-tls12\s+(\S+)', 'tls12_enabled'),
  206. (r'set\s+ssl\s+parameter\s+-tls13\s+(\S+)', 'tls13_enabled'),
  207. (r'set\s+ssl\s+parameter\s+-denySSLReneg\s+(\S+)', 'deny_ssl_reneg'),
  208. ]
  209. for pattern, key in param_patterns:
  210. match = re.search(pattern, content_clean, re.IGNORECASE)
  211. if match:
  212. ssl_configs['ssl_parameters'][key] = match.group(1)
  213. print(f"✅ CipherScanner found {len(ssl_configs['cipher_groups'])} cipher groups")
  214. print(f"✅ CipherScanner found {len(ssl_configs['ssl_vservers'])} SSL vservers")
  215. print(f"✅ CipherScanner found {len(ssl_configs['ssl_profiles'])} SSL profiles")
  216. return ssl_configs
  217. except FileNotFoundError:
  218. print(f"❌ CipherScanner: File not found: {ns_conf_file}")
  219. return {}
  220. except Exception as e:
  221. print(f"❌ CipherScanner error parsing NS configuration: {e}")
  222. import traceback
  223. traceback.print_exc()
  224. return {}
  225. def map_ns_cipher_to_iana(self, ns_cipher):
  226. """
  227. Map NetScaler cipher names to IANA/RFC names
  228. """
  229. # Clean the cipher name
  230. ns_cipher = ns_cipher.strip().upper()
  231. cipher_mappings = {
  232. # AES CBC ciphers
  233. 'SSL3-RSA-AES-256-CBC-SHA': 'TLS_RSA_WITH_AES_256_CBC_SHA',
  234. 'SSL3-RSA-AES-128-CBC-SHA': 'TLS_RSA_WITH_AES_128_CBC_SHA',
  235. 'TLS1-RSA-AES-256-CBC-SHA': 'TLS_RSA_WITH_AES_256_CBC_SHA',
  236. 'TLS1-RSA-AES-128-CBC-SHA': 'TLS_RSA_WITH_AES_128_CBC_SHA',
  237. 'TLS1.2-RSA-AES-256-CBC-SHA': 'TLS_RSA_WITH_AES_256_CBC_SHA',
  238. 'TLS1.2-RSA-AES-128-CBC-SHA': 'TLS_RSA_WITH_AES_128_CBC_SHA',
  239. 'TLS1.2-RSA-AES-256-CBC-SHA256': 'TLS_RSA_WITH_AES_256_CBC_SHA256',
  240. 'TLS1.2-RSA-AES-128-CBC-SHA256': 'TLS_RSA_WITH_AES_128_CBC_SHA256',
  241. # AES GCM ciphers
  242. 'TLS1.2-RSA-AES-256-GCM-SHA384': 'TLS_RSA_WITH_AES_256_GCM_SHA384',
  243. 'TLS1.2-RSA-AES-128-GCM-SHA256': 'TLS_RSA_WITH_AES_128_GCM_SHA256',
  244. # ECDHE RSA ciphers
  245. 'TLS1.2-ECDHE-RSA-AES-256-CBC-SHA': 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA',
  246. 'TLS1.2-ECDHE-RSA-AES-128-CBC-SHA': 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA',
  247. 'TLS1.2-ECDHE-RSA-AES-256-CBC-SHA384': 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384',
  248. 'TLS1.2-ECDHE-RSA-AES-128-CBC-SHA256': 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256',
  249. 'TLS1.2-ECDHE-RSA-AES-256-GCM-SHA384': 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
  250. 'TLS1.2-ECDHE-RSA-AES-128-GCM-SHA256': 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
  251. # ECDHE ECDSA ciphers
  252. 'TLS1.2-ECDHE-ECDSA-AES-256-CBC-SHA': 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA',
  253. 'TLS1.2-ECDHE-ECDSA-AES-128-CBC-SHA': 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA',
  254. 'TLS1.2-ECDHE-ECDSA-AES-256-CBC-SHA384': 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384',
  255. 'TLS1.2-ECDHE-ECDSA-AES-128-CBC-SHA256': 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256',
  256. 'TLS1.2-ECDHE-ECDSA-AES-256-GCM-SHA384': 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
  257. 'TLS1.2-ECDHE-ECDSA-AES-128-GCM-SHA256': 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256',
  258. # DHE RSA ciphers
  259. 'TLS1.2-DHE-RSA-AES-256-CBC-SHA': 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA',
  260. 'TLS1.2-DHE-RSA-AES-128-CBC-SHA': 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA',
  261. 'TLS1.2-DHE-RSA-AES-256-CBC-SHA256': 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256',
  262. 'TLS1.2-DHE-RSA-AES-128-CBC-SHA256': 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256',
  263. 'TLS1.2-DHE-RSA-AES-256-GCM-SHA384': 'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384',
  264. 'TLS1.2-DHE-RSA-AES-128-GCM-SHA256': 'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256',
  265. # TLS 1.3 ciphers (NetScaler format)
  266. 'TLS1.3-AES256-GCM-SHA384': 'TLS_AES_256_GCM_SHA384',
  267. 'TLS1.3-AES128-GCM-SHA256': 'TLS_AES_128_GCM_SHA256',
  268. 'TLS1.3-CHACHA20-POLY1305-SHA256': 'TLS_CHACHA20_POLY1305_SHA256',
  269. # Weak/Deprecated ciphers
  270. 'SSL3-RSA-RC4-MD5': 'TLS_RSA_WITH_RC4_128_MD5',
  271. 'SSL3-RSA-RC4-SHA': 'TLS_RSA_WITH_RC4_128_SHA',
  272. 'SSL3-RSA-DES-CBC3-SHA': 'TLS_RSA_WITH_3DES_EDE_CBC_SHA',
  273. 'TLS1-RSA-RC4-MD5': 'TLS_RSA_WITH_RC4_128_MD5',
  274. 'TLS1-RSA-RC4-SHA': 'TLS_RSA_WITH_RC4_128_SHA',
  275. # Common NetScaler cipher patterns (simplified)
  276. 'TLS1.2-ECDHE-ECDSA-AES256-SHA384': 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384',
  277. 'TLS1.2-ECDHE-ECDSA-AES128-SHA256': 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256',
  278. 'TLS1.2-ECDHE-RSA-AES256-SHA384': 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384',
  279. 'TLS1.2-ECDHE-RSA-AES128-SHA256': 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256',
  280. }
  281. # Direct match
  282. if ns_cipher in cipher_mappings:
  283. return cipher_mappings[ns_cipher]
  284. # Try to match patterns
  285. # Remove version prefixes for pattern matching
  286. cipher_without_version = ns_cipher
  287. version_prefixes = ['SSL3-', 'TLS1-', 'TLS1.1-', 'TLS1.2-', 'TLS1.3-']
  288. for prefix in version_prefixes:
  289. if ns_cipher.startswith(prefix):
  290. cipher_without_version = ns_cipher[len(prefix):]
  291. break
  292. # Pattern-based mapping
  293. if 'AES256-GCM-SHA384' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version:
  294. return 'TLS_RSA_WITH_AES_256_GCM_SHA384'
  295. elif 'AES128-GCM-SHA256' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version:
  296. return 'TLS_RSA_WITH_AES_128_GCM_SHA256'
  297. elif 'AES256-CBC-SHA256' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version:
  298. return 'TLS_RSA_WITH_AES_256_CBC_SHA256'
  299. elif 'AES128-CBC-SHA256' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version:
  300. return 'TLS_RSA_WITH_AES_128_CBC_SHA256'
  301. elif 'AES256-CBC-SHA' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version:
  302. return 'TLS_RSA_WITH_AES_256_CBC_SHA'
  303. elif 'AES128-CBC-SHA' in cipher_without_version and 'ECDHE' not in cipher_without_version and 'DHE' not in cipher_without_version:
  304. return 'TLS_RSA_WITH_AES_128_CBC_SHA'
  305. elif 'AES256-GCM-SHA384' in cipher_without_version and 'ECDHE-RSA' in cipher_without_version:
  306. return 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384'
  307. elif 'AES128-GCM-SHA256' in cipher_without_version and 'ECDHE-RSA' in cipher_without_version:
  308. return 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'
  309. elif 'AES256-GCM-SHA384' in cipher_without_version and 'ECDHE-ECDSA' in cipher_without_version:
  310. return 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384'
  311. elif 'AES128-GCM-SHA256' in cipher_without_version and 'ECDHE-ECDSA' in cipher_without_version:
  312. return 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256'
  313. return ns_cipher # Return as-is if no mapping found
  314. def analyze_cipher_compliance(self):
  315. """
  316. Analyze cipher compliance between IANA ciphers and NS configuration
  317. """
  318. results = {
  319. 'configured_ciphers': set(),
  320. 'configured_in_ns': [],
  321. 'not_configured': [],
  322. 'weak_ciphers_found': [],
  323. 'tls_versions': {},
  324. 'cipher_group_analysis': [],
  325. 'ns_cipher_mappings': {}
  326. }
  327. # Extract all ciphers configured in NS
  328. for group in self.ns_config.get('cipher_groups', []):
  329. group_ciphers = []
  330. for ns_cipher in group['ciphers']:
  331. iana_cipher = self.map_ns_cipher_to_iana(ns_cipher)
  332. results['configured_ciphers'].add(iana_cipher)
  333. results['ns_cipher_mappings'][ns_cipher] = iana_cipher
  334. group_ciphers.append({
  335. 'ns_name': ns_cipher,
  336. 'iana_name': iana_cipher
  337. })
  338. results['cipher_group_analysis'].append({
  339. 'group_name': group['name'],
  340. 'ciphers': group_ciphers,
  341. 'count': len(group_ciphers)
  342. })
  343. # Check which IANA ciphers are configured
  344. for iana_cipher in self.iana_ciphers:
  345. if iana_cipher in results['configured_ciphers']:
  346. results['configured_in_ns'].append(iana_cipher)
  347. else:
  348. results['not_configured'].append(iana_cipher)
  349. # Check for weak ciphers
  350. weak_patterns = [
  351. 'RC4', 'DES', '_3DES_', 'MD5', 'NULL', 'EXPORT',
  352. 'ANON', 'ADH', 'IDEA', 'SEED', 'CAMELLIA'
  353. ]
  354. for cipher in results['configured_ciphers']:
  355. if any(pattern in cipher.upper() for pattern in weak_patterns):
  356. results['weak_ciphers_found'].append(cipher)
  357. # Check TLS version settings
  358. params = self.ns_config.get('ssl_parameters', {})
  359. results['tls_versions'] = {
  360. 'SSL3': params.get('ssl3_enabled', 'NOT_CONFIGURED'),
  361. 'TLS1.0': params.get('tls1_enabled', 'NOT_CONFIGURED'),
  362. 'TLS1.1': params.get('tls11_enabled', 'NOT_CONFIGURED'),
  363. 'TLS1.2': params.get('tls12_enabled', 'NOT_CONFIGURED'),
  364. 'TLS1.3': params.get('tls13_enabled', 'NOT_CONFIGURED')
  365. }
  366. return results
  367. def generate_report(self):
  368. """
  369. Generate detailed compliance report
  370. """
  371. print(f"\n{'='*80}")
  372. print(f"CIPHERSCANNER REPORT")
  373. print(f"{'='*80}")
  374. print(f"\n📊 CipherScanner Summary:")
  375. print(f" IANA Ciphers Found: {len(self.iana_ciphers)}")
  376. print(f" Ciphers Configured in NS: {len(self.analysis_results['configured_ciphers'])}")
  377. print(f" Ciphers Matched: {len(self.analysis_results['configured_in_ns'])}")
  378. print(f" Ciphers Not Configured: {len(self.analysis_results['not_configured'])}")
  379. print(f" Weak Ciphers Found: {len(self.analysis_results['weak_ciphers_found'])}")
  380. print(f"\n🔒 TLS Version Configuration:")
  381. for version, status in self.analysis_results['tls_versions'].items():
  382. status_icon = '✅' if status == 'DISABLED' and version in ['SSL3', 'TLS1.0', 'TLS1.1'] else \
  383. '✅' if status == 'ENABLED' and version in ['TLS1.2', 'TLS1.3'] else \
  384. '⚠️ ' if status == 'ENABLED' and version in ['SSL3', 'TLS1.0', 'TLS1.1'] else \
  385. '❓'
  386. print(f" {status_icon} {version}: {status}")
  387. if self.analysis_results['cipher_group_analysis']:
  388. print(f"\n📁 Cipher Group Analysis:")
  389. for group in self.analysis_results['cipher_group_analysis']:
  390. print(f" • {group['group_name']}: {group['count']} ciphers")
  391. if self.analysis_results['configured_in_ns']:
  392. print(f"\n✅ Ciphers Configured in NS ({len(self.analysis_results['configured_in_ns'])}):")
  393. for cipher in sorted(self.analysis_results['configured_in_ns'])[:20]:
  394. print(f" ✓ {cipher}")
  395. if len(self.analysis_results['configured_in_ns']) > 20:
  396. print(f" ... and {len(self.analysis_results['configured_in_ns']) - 20} more")
  397. if self.analysis_results['not_configured']:
  398. print(f"\n❌ Ciphers NOT Configured in NS ({len(self.analysis_results['not_configured'])}):")
  399. for cipher in sorted(self.analysis_results['not_configured'])[:20]:
  400. print(f" ✗ {cipher}")
  401. if len(self.analysis_results['not_configured']) > 20:
  402. print(f" ... and {len(self.analysis_results['not_configured']) - 20} more")
  403. if self.analysis_results['weak_ciphers_found']:
  404. print(f"\n⚠️ WEAK Ciphers Found in NS Configuration ({len(self.analysis_results['weak_ciphers_found'])}):")
  405. for cipher in sorted(self.analysis_results['weak_ciphers_found']):
  406. print(f" ⚠️ {cipher}")
  407. print("\n CipherScanner RECOMMENDATION: Remove weak ciphers from configuration")
  408. # SSL VServer Summary
  409. if self.ns_config.get('ssl_vservers'):
  410. print(f"\n🌐 SSL Virtual Servers ({len(self.ns_config['ssl_vservers'])}):")
  411. for vs in self.ns_config['ssl_vservers']:
  412. cipher_status = "✅" if vs.get('cipher_group') else "❌"
  413. print(f" {cipher_status} {vs['name']} ({vs['ip']}:{vs['port']})")
  414. if vs.get('cipher_group'):
  415. print(f" Cipher Group: {vs['cipher_group']}")
  416. if vs.get('ssl_profile'):
  417. print(f" SSL Profile: {vs['ssl_profile']}")
  418. # Security Recommendations
  419. print(f"\n📋 CIPHERSCANNER SECURITY RECOMMENDATIONS:")
  420. recommendations = []
  421. # Check for weak protocols
  422. if self.analysis_results['tls_versions'].get('SSL3') == 'ENABLED':
  423. recommendations.append("❌ DISABLE SSL 3.0 (Vulnerable to POODLE attack)")
  424. if self.analysis_results['tls_versions'].get('TLS1.0') == 'ENABLED':
  425. recommendations.append("⚠️ DISABLE TLS 1.0 (Considered weak)")
  426. if self.analysis_results['tls_versions'].get('TLS1.1') == 'ENABLED':
  427. recommendations.append("⚠️ CONSIDER DISABLING TLS 1.1")
  428. if self.analysis_results['weak_ciphers_found']:
  429. recommendations.append("❌ REMOVE weak ciphers (RC4, DES, 3DES, MD5)")
  430. if len(self.analysis_results['not_configured']) > 0:
  431. recommendations.append(f"⚠️ Configure missing {len(self.analysis_results['not_configured'])} cipher(s)")
  432. if not recommendations:
  433. recommendations.append("✅ CipherScanner: Configuration looks secure!")
  434. for i, rec in enumerate(recommendations, 1):
  435. print(f" {i}. {rec}")
  436. print(f"\n{'='*80}")
  437. print(f"CipherScanner report generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
  438. print(f"{'='*80}")
  439. def save_json_report(self, output_file):
  440. """Save comprehensive report as JSON"""
  441. report = {
  442. 'metadata': {
  443. 'generated_at': datetime.now().isoformat(),
  444. 'tool': 'CipherScanner',
  445. 'version': '1.0.0',
  446. 'iana_cipher_count': len(self.iana_ciphers),
  447. 'ns_config_file': self.ns_conf_file,
  448. 'cipher_file': self.cipher_file
  449. },
  450. 'iana_ciphers': self.iana_ciphers,
  451. 'ns_configuration_summary': {
  452. 'cipher_groups_count': len(self.ns_config.get('cipher_groups', [])),
  453. 'ssl_vservers_count': len(self.ns_config.get('ssl_vservers', [])),
  454. 'ssl_profiles_count': len(self.ns_config.get('ssl_profiles', [])),
  455. 'tls_versions': self.ns_config.get('ssl_parameters', {})
  456. },
  457. 'compliance_analysis': {
  458. 'configured_ciphers': list(self.analysis_results['configured_ciphers']),
  459. 'configured_in_ns': self.analysis_results['configured_in_ns'],
  460. 'not_configured': self.analysis_results['not_configured'],
  461. 'weak_ciphers_found': self.analysis_results['weak_ciphers_found'],
  462. 'tls_versions': self.analysis_results['tls_versions'],
  463. 'ns_cipher_mappings': self.analysis_results['ns_cipher_mappings']
  464. },
  465. 'security_assessment': {
  466. 'has_weak_ciphers': len(self.analysis_results['weak_ciphers_found']) > 0,
  467. 'weak_cipher_count': len(self.analysis_results['weak_ciphers_found']),
  468. 'tls1_2_enabled': self.analysis_results['tls_versions'].get('TLS1.2') == 'ENABLED',
  469. 'tls1_3_enabled': self.analysis_results['tls_versions'].get('TLS1.3') == 'ENABLED',
  470. 'ssl3_disabled': self.analysis_results['tls_versions'].get('SSL3') == 'DISABLED',
  471. 'overall_grade': 'A' if len(self.analysis_results['weak_ciphers_found']) == 0 else 'C'
  472. },
  473. 'cipher_scanner_info': {
  474. 'scan_completed': True,
  475. 'scan_timestamp': datetime.now().isoformat(),
  476. 'exit_code': self.get_exit_code()
  477. }
  478. }
  479. with open(output_file, 'w') as f:
  480. json.dump(report, f, indent=2)
  481. print(f"\n💾 CipherScanner JSON report saved to: {output_file}")
  482. return report
  483. def get_exit_code(self):
  484. """Determine exit code based on scan results"""
  485. if self.analysis_results['weak_ciphers_found']:
  486. return 2 # Failure: weak ciphers detected
  487. elif len(self.analysis_results['not_configured']) > len(self.iana_ciphers) * 0.5:
  488. return 1 # Warning: less than 50% of ciphers configured
  489. else:
  490. return 0 # Success
  491. def main():
  492. parser = argparse.ArgumentParser(
  493. description='CipherScanner - NS Configuration Cipher Suite Compliance Checker',
  494. formatter_class=argparse.RawDescriptionHelpFormatter,
  495. epilog="""
  496. Examples:
  497. %(prog)s --ns-conf ns.conf --ciphers ciphers.txt
  498. %(prog)s --ns-conf /path/to/ns.conf --ciphers scan_results.txt --output report.json
  499. %(prog)s --ns-conf ns.conf --ciphers ciphers.txt --verbose
  500. CipherScanner helps security teams audit NS load balancer cipher configurations
  501. against discovered cipher suites from security scans.
  502. """
  503. )
  504. parser.add_argument('--nsconf', required=True, help='NS configuration file (ns.conf)')
  505. parser.add_argument('--cipher', required=True, help='Cipher suites text file')
  506. parser.add_argument('--output', help='Output JSON report file')
  507. parser.add_argument('--verbose', action='store_true', help='Show detailed parsing information')
  508. args = parser.parse_args()
  509. # Initialize and run CipherScanner
  510. scanner = CipherScanner(args.nsconf, args.cipher)
  511. if scanner.run_scan():
  512. # Save JSON report if requested
  513. if args.output:
  514. scanner.save_json_report(args.output)
  515. # Exit with appropriate code
  516. exit_code = scanner.get_exit_code()
  517. if exit_code == 0:
  518. print(f"\n✅ CipherScanner: Compliance PASS - Configuration looks good!")
  519. elif exit_code == 1:
  520. print(f"\n⚠️ CipherScanner: Warning - Less than 50% of scanned ciphers are configured")
  521. else:
  522. print(f"\n❌ CipherScanner: Compliance FAILURE - Weak ciphers detected!")
  523. sys.exit(exit_code)
  524. else:
  525. print(f"\n❌ CipherScanner: Scan failed due to errors")
  526. sys.exit(3)
  527. if __name__ == "__main__":
  528. main()