ssl-checker/ssl_checker.py

251 lines
9.1 KiB
Python
Raw Normal View History

2018-04-17 18:10:44 +04:30
#!/usr/bin/env python
import socket
import sys
2018-04-21 09:37:21 +04:30
2018-04-19 14:35:50 +04:30
from argparse import ArgumentParser, SUPPRESS
2018-04-17 18:10:44 +04:30
from datetime import datetime
2018-04-18 16:32:41 +04:30
from ssl import PROTOCOL_TLSv1
try:
from OpenSSL import SSL
except ImportError:
print('Required module does not exist. Install: pip install pyopenssl')
sys.exit(1)
2018-04-17 18:10:44 +04:30
2018-04-18 17:03:02 +04:30
class Clr:
2018-04-17 19:43:46 +04:30
"""Text colors."""
2018-04-18 17:03:02 +04:30
RST = '\033[39m'
2018-04-17 19:43:46 +04:30
RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
2018-04-21 16:05:41 +04:30
def get_cert(host, port, user_args):
2018-04-17 18:10:44 +04:30
"""Connection to the host."""
2018-04-21 16:05:41 +04:30
if user_args.socks:
2018-04-21 17:10:09 +04:30
import socks
2018-04-21 16:05:41 +04:30
socks_host, socks_port = filter_hostname(user_args.socks)
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, socks_host, int(socks_port), True)
socket.socket = socks.socksocket
2018-04-18 16:32:41 +04:30
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
2018-04-21 16:05:41 +04:30
osobj = SSL.Context(PROTOCOL_TLSv1)
2018-04-19 11:20:32 +04:30
sock.connect((host, int(port)))
2018-04-18 16:32:41 +04:30
oscon = SSL.Connection(osobj, sock)
oscon.set_tlsext_host_name(host.encode())
oscon.set_connect_state()
2018-04-19 11:20:32 +04:30
oscon.do_handshake()
2018-04-18 16:32:41 +04:30
cert = oscon.get_peer_certificate()
2018-04-17 18:10:44 +04:30
sock.close()
2018-04-18 16:32:41 +04:30
2018-04-17 18:10:44 +04:30
return cert
2018-04-22 14:44:04 +04:30
def analyze_ssl(host, context):
"""Analyze the security of the SSL certificate."""
from json import loads
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
api_url = 'https://api.ssllabs.com/api/v3/'
while True:
main_request = loads(urlopen(api_url + 'analyze?host={}'.format(host)).read().decode('utf-8'))
2018-04-22 17:36:40 +04:30
if main_request['status'] == 'DNS':
print('Analyzing the security of {}. Please wait...'.format(host))
2018-04-22 14:44:04 +04:30
continue
2018-04-22 17:36:40 +04:30
if main_request['status'] == 'IN_PROGRESS':
# We can find a way to show the progress
pass
2018-04-22 14:44:04 +04:30
elif main_request['status'] == 'READY':
break
context[host]['grade'] = main_request['endpoints'][0]['grade']
endpoint_data = loads(urlopen(api_url + 'getEndpointData?host={}&s={}'.format(host, main_request['endpoints'][0]['ipAddress'])).read().decode('utf-8'))
context[host]['poodle_vuln'] = endpoint_data['details']['poodle']
context[host]['heartbleed_vuln'] = endpoint_data['details']['heartbleed']
context[host]['heartbeat_vuln'] = endpoint_data['details']['heartbeat']
context[host]['freak_vuln'] = endpoint_data['details']['freak']
context[host]['logjam_vuln'] = endpoint_data['details']['logjam']
context[host]['drownVulnerable'] = endpoint_data['details']['drownVulnerable']
return context
2018-04-21 10:43:32 +04:30
def get_cert_info(host, cert):
2018-04-17 18:10:44 +04:30
"""Get all the information about cert and create a JSON file."""
context = {}
2018-04-21 10:43:32 +04:30
cert_subject = cert.get_subject()
context['issued_to'] = cert_subject.CN
2018-04-21 22:40:17 +04:30
context['issued_o'] = cert_subject.O
2018-04-18 16:32:41 +04:30
context['issuer_c'] = cert.get_issuer().countryName
context['issuer_o'] = cert.get_issuer().organizationName
context['issuer_ou'] = cert.get_issuer().organizationalUnitName
context['issuer_cn'] = cert.get_issuer().commonName
context['cert_sn'] = cert.get_serial_number()
context['cert_alg'] = cert.get_signature_algorithm().decode()
context['cert_ver'] = cert.get_version()
context['cert_exp'] = cert.has_expired()
2018-04-17 18:10:44 +04:30
# Valid from
2018-04-18 16:32:41 +04:30
valid_from = datetime.strptime(cert.get_notBefore().decode('ascii'),
'%Y%m%d%H%M%SZ')
2018-04-17 18:10:44 +04:30
context['valid_from'] = valid_from.strftime('%Y-%m-%d')
2018-04-18 16:32:41 +04:30
# Valid till
valid_till = datetime.strptime(cert.get_notAfter().decode('ascii'),
'%Y%m%d%H%M%SZ')
2018-04-17 18:10:44 +04:30
context['valid_till'] = valid_till.strftime('%Y-%m-%d')
2018-04-18 14:11:46 +04:30
# Validity days
context['validity_days'] = (valid_till - valid_from).days
2018-04-17 18:10:44 +04:30
return context
2018-04-22 14:44:04 +04:30
def print_status(host, context, analyze=False):
2018-04-21 14:11:41 +04:30
"""Print all the usefull info about host."""
days_left = (datetime.strptime(context[host]['valid_till'], '%Y-%m-%d') - datetime.now()).days
print('\t{}[+]{} {}\n'.format(Clr.GREEN, Clr.RST, host))
print('\t\tIssued domain: {}'.format(context[host]['issued_to']))
2018-04-21 22:40:17 +04:30
print('\t\tIssued to: {}'.format(context[host]['issued_o']))
2018-04-22 11:30:26 +04:30
print('\t\tIssued by: {} ({})'.format(context[host]['issuer_o'], context[host]['issuer_c']))
2018-04-21 14:11:41 +04:30
print('\t\tValid from: {}'.format(context[host]['valid_from']))
print('\t\tValid to: {} ({} days left)'.format(context[host]['valid_till'], days_left))
print('\t\tValidity days: {}'.format(context[host]['validity_days']))
print('\t\tCertificate S/N: {}'.format(context[host]['cert_sn']))
print('\t\tCertificate version: {}'.format(context[host]['cert_ver']))
print('\t\tCertificate algorithm: {}'.format(context[host]['cert_alg']))
2018-04-22 14:44:04 +04:30
if analyze:
print('\t\tCertificate grade: {}'.format(context[host]['grade']))
print('\t\tPoodle vulnerability: {}'.format(context[host]['poodle_vuln']))
print('\t\tHeartbleed vulnerability: {}'.format(context[host]['heartbleed_vuln']))
print('\t\tHearbeat vulnerability: {}'.format(context[host]['heartbeat_vuln']))
print('\t\tFreak vulnerability: {}'.format(context[host]['freak_vuln']))
print('\t\tLogjam vulnerability: {}'.format(context[host]['logjam_vuln']))
print('\t\tDrown vulnerability: {}'.format(context[host]['drownVulnerable']))
print('\t\tExpired: {}\n'.format(context[host]['cert_exp']))
2018-04-21 14:11:41 +04:30
2018-04-19 14:35:50 +04:30
def show_result(user_args):
2018-04-17 18:10:44 +04:30
"""Get the context."""
2018-04-19 11:20:32 +04:30
context = {}
failed_cnt = 0
2018-04-19 14:35:50 +04:30
hosts = user_args.hosts
if not user_args.json_true:
2018-04-21 17:29:52 +04:30
print('Analyzing {} host(s):\n{}\n'.format(len(hosts), '-' * 21))
2018-04-19 14:35:50 +04:30
2018-04-22 14:44:04 +04:30
if not user_args.json_true and user_args.analyze:
print('{}Warning: -a/--analyze is enabled. It takes more time...{}\n'.format(Clr.YELLOW, Clr.RST))
2018-04-17 18:10:44 +04:30
for host in hosts:
2018-04-17 20:43:39 +04:30
host, port = filter_hostname(host)
2018-04-18 16:39:40 +04:30
# Check duplication
if host in context.keys():
continue
2018-04-19 11:20:32 +04:30
try:
2018-04-21 16:05:41 +04:30
cert = get_cert(host, port, user_args)
2018-04-21 10:43:32 +04:30
context[host] = get_cert_info(host, cert)
2018-04-22 14:44:04 +04:30
# Analyze the certificate if enabled
if user_args.analyze:
context = analyze_ssl(host, context)
2018-04-19 14:35:50 +04:30
if not user_args.json_true:
2018-04-22 14:44:04 +04:30
print_status(host, context, user_args.analyze)
2018-04-19 11:20:32 +04:30
except Exception as error:
2018-04-19 14:35:50 +04:30
if not user_args.json_true:
2018-04-22 14:44:04 +04:30
print('\t{}[-]{} {:<20s} Failed: {}\n'.format(Clr.RED, Clr.RST, host, error))
failed_cnt += 1
except KeyboardInterrupt:
print('{}Canceling script...{}\n'.format(Clr.YELLOW, Clr.RST))
sys.exit(1)
2018-04-19 14:35:50 +04:30
2018-04-17 19:59:43 +04:30
2018-04-19 14:35:50 +04:30
if not user_args.json_true:
print('\n{} successful and {} failed\n'.format(len(hosts) - failed_cnt, failed_cnt))
2018-04-17 18:10:44 +04:30
2018-04-21 17:29:52 +04:30
# CSV export if -c/--csv is specified
if user_args.csv_enabled:
export_csv(context, user_args.csv_enabled)
# Enable JSON output if -j/--json argument specified
2018-04-19 14:35:50 +04:30
if user_args.json_true:
2018-04-19 21:02:07 +04:30
if user_args.pretty_output:
2018-04-21 17:10:09 +04:30
from pprint import pprint
2018-04-19 21:02:07 +04:30
pprint(context)
else:
print(context)
2018-04-17 18:10:44 +04:30
2018-04-21 11:23:55 +04:30
2018-04-21 17:29:52 +04:30
def export_csv(context, filename):
"""Export all context results to CSV file."""
with open(filename, 'w') as csv_file:
for host in context.keys():
csv_file.write('{}\n'.format(host))
for key, value in context[host].items():
csv_file.write('{},{}\n'.format(key, value))
2018-04-17 20:43:39 +04:30
def filter_hostname(host):
"""Remove unused characters and split by address and port."""
host = host.replace('http://', '').replace('https://', '').replace('/', '')
port = 443
if ':' in host:
host, port = host.split(':')
2018-04-18 14:11:46 +04:30
2018-04-17 20:43:39 +04:30
return host, port
2018-04-17 18:10:44 +04:30
2018-04-19 14:35:50 +04:30
def get_args():
"""Set argparse options."""
2018-04-22 11:30:26 +04:30
parser = ArgumentParser(prog='ssl_checker.py', add_help=False,
2018-04-22 14:44:04 +04:30
description="""Collects useful information about given host's SSL certificates.""")
2018-04-21 16:05:41 +04:30
parser.add_argument('-H', '--host', dest='hosts', nargs='*', required=True,
help='Hosts as input separated by space')
parser.add_argument('-s', '--socks', dest='socks',
default=False, metavar='HOST:PORT',
help='Enable SOCKS proxy for connection')
2018-04-21 17:29:52 +04:30
parser.add_argument('-c', '--csv', dest='csv_enabled',
default=False, metavar='FILENAME.CSV',
help='Enable CSV file export')
2018-04-22 14:44:04 +04:30
parser.add_argument('-j', '--json', dest='json_true',
action='store_true', default=False,
help='Enable JSON in the output')
parser.add_argument('-a', '--analyze', dest='analyze',
default=False, action='store_true',
help='Enable SSL security analysis on the host')
2018-04-21 16:05:41 +04:30
parser.add_argument('-p', '--pretty', dest='pretty_output',
action='store_true', default=False,
help='Print pretty and more human readable Json')
parser.add_argument('-h', '--help', default=SUPPRESS,
2018-04-19 14:35:50 +04:30
action='help',
help='Show this help message and exit')
args = parser.parse_args()
2018-04-17 18:10:44 +04:30
2018-04-19 14:35:50 +04:30
# Checks hosts list
if isinstance(args.hosts, list):
if len(args.hosts) == 0:
parser.print_help()
sys.exit(0)
return args
if __name__ == '__main__':
show_result(get_args())