Skip to content

Commit 3124608

Browse files
committed
Move pyopenssl to cryptography
1 parent 01ea720 commit 3124608

File tree

2 files changed

+88
-52
lines changed

2 files changed

+88
-52
lines changed

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
pyopenssl
1+
cryptography
22
json2html
3+
requests

ssl_checker.py

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
import socket
33
import sys
44
import json
5+
import ssl
6+
from datetime import datetime, timezone
57

68
from argparse import ArgumentParser, SUPPRESS
7-
from datetime import datetime
89
from time import sleep
910
from csv import DictWriter
1011

1112
try:
12-
from OpenSSL import SSL
13+
from cryptography import x509
14+
from cryptography.hazmat.backends import default_backend
15+
from cryptography.hazmat.primitives import hashes
1316
from json2html import *
1417
except ImportError:
1518
print('Please install required modules: pip install -r requirements.txt')
@@ -46,32 +49,40 @@ def get_cert(self, host, port, socks_host=None, socks_port=None):
4649
sock.settimeout(None)
4750

4851
# Try different TLS versions in order of preference (newest to oldest)
49-
tls_methods = [
50-
(SSL.TLSv1_2_METHOD, "TLS 1.2"), # TLS 1.2
51-
(SSL.TLSv1_1_METHOD, "TLS 1.1"), # TLS 1.1
52-
(SSL.TLSv1_METHOD, "TLS 1.0"), # TLS 1.0
52+
tls_versions = [
53+
(ssl.PROTOCOL_TLSv1_2, "TLS 1.2"),
54+
(ssl.PROTOCOL_TLSv1_1, "TLS 1.1"),
55+
(ssl.PROTOCOL_TLSv1, "TLS 1.0"),
5356
]
5457

55-
for tls_method, tls_version in tls_methods:
58+
for tls_protocol, tls_version in tls_versions:
5659
try:
57-
osobj = SSL.Context(tls_method)
58-
oscon = SSL.Connection(osobj, sock)
59-
oscon.set_tlsext_host_name(host.encode())
60-
oscon.set_connect_state()
61-
oscon.do_handshake()
62-
cert = oscon.get_peer_certificate()
60+
# Create SSL context
61+
context = ssl.create_default_context()
62+
context.check_hostname = False
63+
context.verify_mode = ssl.CERT_NONE
64+
65+
# Wrap socket with SSL
66+
ssl_sock = context.wrap_socket(sock, server_hostname=host)
67+
ssl_sock.do_handshake()
68+
69+
# Get certificate in DER format and convert to X509 object
70+
cert_der = ssl_sock.getpeercert(binary_form=True)
71+
cert = x509.load_der_x509_certificate(cert_der, default_backend())
72+
6373
resolved_ip = socket.gethostbyname(host)
74+
ssl_sock.close()
6475
sock.close()
6576
return cert, resolved_ip, tls_version
66-
except SSL.SysCallError as e:
77+
except (ssl.SSLError, ssl.CertificateError, OSError) as e:
6778
# If this TLS version fails, try the next one
6879
continue
6980
except Exception as e:
7081
# For other exceptions, try the next TLS version
7182
continue
7283

7384
# If all TLS versions fail, raise the last exception
74-
raise SSL.SysCallError("Failed to establish SSL connection with any supported TLS version")
85+
raise ssl.SSLError("Failed to establish SSL connection with any supported TLS version")
7586

7687
def border_msg(self, message):
7788
"""Print the message in the box."""
@@ -127,15 +138,26 @@ def analyze_ssl(self, host, context, user_args):
127138

128139
def get_cert_sans(self, x509cert):
129140
"""
130-
Get Subject Alt Names from Certificate. Shameless taken from stack overflow:
131-
https://stackoverflow.com/users/4547691/anatolii-chmykhalo
141+
Get Subject Alt Names from Certificate using cryptography library.
132142
"""
133143
san = ''
134-
ext_count = x509cert.get_extension_count()
135-
for i in range(0, ext_count):
136-
ext = x509cert.get_extension(i)
137-
if 'subjectAltName' in str(ext.get_short_name()):
138-
san = ext.__str__()
144+
try:
145+
# Get the Subject Alternative Name extension
146+
san_extension = x509cert.extensions.get_extension_for_oid(x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
147+
if san_extension:
148+
san_names = san_extension.value
149+
# Convert all SAN types to strings
150+
san_list = []
151+
for name in san_names:
152+
if hasattr(name, 'value'):
153+
san_list.append(str(name.value))
154+
else:
155+
san_list.append(str(name))
156+
san = '; '.join(san_list)
157+
except x509.extensions.ExtensionNotFound:
158+
# No SAN extension found
159+
pass
160+
139161
# replace commas to not break csv output
140162
san = san.replace(',', ';')
141163
return san
@@ -144,47 +166,60 @@ def get_cert_info(self, host, cert, resolved_ip, tls_version=None):
144166
"""Get all the information about cert and create a JSON file."""
145167
context = {}
146168

147-
cert_subject = cert.get_subject()
169+
# Get subject information
170+
subject = cert.subject
171+
issuer = cert.issuer
148172

149173
context['host'] = host
150174
context['resolved_ip'] = resolved_ip
151175
context['tls_version'] = tls_version
152-
context['issued_to'] = cert_subject.CN
153-
context['issued_o'] = cert_subject.O
154-
context['issuer_c'] = cert.get_issuer().countryName
155-
context['issuer_o'] = cert.get_issuer().organizationName
156-
context['issuer_ou'] = cert.get_issuer().organizationalUnitName
157-
context['issuer_cn'] = cert.get_issuer().commonName
158-
context['cert_sn'] = str(cert.get_serial_number())
159-
context['cert_sha1'] = cert.digest('sha1').decode()
160-
context['cert_alg'] = cert.get_signature_algorithm().decode()
161-
context['cert_ver'] = cert.get_version()
176+
177+
# Get common name from subject
178+
cn_attr = subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
179+
context['issued_to'] = cn_attr[0].value if cn_attr else 'N/A'
180+
181+
# Get organization from subject
182+
o_attr = subject.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME)
183+
context['issued_o'] = o_attr[0].value if o_attr else 'N/A'
184+
185+
# Get issuer information
186+
issuer_c_attr = issuer.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME)
187+
context['issuer_c'] = issuer_c_attr[0].value if issuer_c_attr else 'N/A'
188+
189+
issuer_o_attr = issuer.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME)
190+
context['issuer_o'] = issuer_o_attr[0].value if issuer_o_attr else 'N/A'
191+
192+
issuer_ou_attr = issuer.get_attributes_for_oid(x509.NameOID.ORGANIZATIONAL_UNIT_NAME)
193+
context['issuer_ou'] = issuer_ou_attr[0].value if issuer_ou_attr else 'N/A'
194+
195+
issuer_cn_attr = issuer.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
196+
context['issuer_cn'] = issuer_cn_attr[0].value if issuer_cn_attr else 'N/A'
197+
198+
context['cert_sn'] = str(cert.serial_number)
199+
context['cert_sha1'] = cert.fingerprint(hashes.SHA1()).hex()
200+
context['cert_alg'] = cert.signature_algorithm_oid._name
201+
context['cert_ver'] = cert.version.value
162202
context['cert_sans'] = self.get_cert_sans(cert)
163-
context['cert_exp'] = cert.has_expired()
164-
context['cert_valid'] = False if cert.has_expired() else True
203+
context['cert_exp'] = cert.not_valid_after_utc < datetime.now(timezone.utc)
204+
context['cert_valid'] = not context['cert_exp']
165205

166206
# Valid from
167-
valid_from = datetime.strptime(cert.get_notBefore().decode('ascii'),
168-
'%Y%m%d%H%M%SZ')
169-
context['valid_from'] = valid_from.strftime('%Y-%m-%d')
207+
context['valid_from'] = cert.not_valid_before_utc.strftime('%Y-%m-%d')
170208

171209
# Valid till
172-
valid_till = datetime.strptime(cert.get_notAfter().decode('ascii'),
173-
'%Y%m%d%H%M%SZ')
174-
context['valid_till'] = valid_till.strftime('%Y-%m-%d')
210+
context['valid_till'] = cert.not_valid_after_utc.strftime('%Y-%m-%d')
175211

176212
# Validity days
177-
context['validity_days'] = (valid_till - valid_from).days
213+
context['validity_days'] = (cert.not_valid_after_utc - cert.not_valid_before_utc).days
178214

179215
# Validity in days from now
180-
now = datetime.now()
181-
context['days_left'] = (valid_till - now).days
216+
now = datetime.now(timezone.utc)
217+
context['days_left'] = (cert.not_valid_after_utc - now).days
182218

183219
# Valid days left
184-
context['valid_days_to_expire'] = (datetime.strptime(context['valid_till'],
185-
'%Y-%m-%d') - datetime.now()).days
220+
context['valid_days_to_expire'] = (cert.not_valid_after_utc - datetime.now(timezone.utc)).days
186221

187-
if cert.has_expired():
222+
if context['cert_exp']:
188223
self.total_expired += 1
189224
else:
190225
self.total_valid += 1
@@ -232,7 +267,7 @@ def print_status(self, host, context, analyze=False):
232267
def show_result(self, user_args):
233268
"""Get the context."""
234269
context = {}
235-
start_time = datetime.now()
270+
start_time = datetime.now(timezone.utc)
236271
hosts = user_args.hosts
237272

238273
if not user_args.json_true and not user_args.summary_true:
@@ -271,7 +306,7 @@ def show_result(self, user_args):
271306

272307
if not user_args.json_true and not user_args.summary_true:
273308
self.print_status(host, context, user_args.analyze)
274-
except SSL.SysCallError:
309+
except ssl.SSLError:
275310
context[host] = 'failed'
276311
if not user_args.json_true:
277312
print('\t{}[\u2717]{} {:<20s} Failed: Misconfigured SSL/TLS\n'.format(Clr.RED, Clr.RST, host))
@@ -288,7 +323,7 @@ def show_result(self, user_args):
288323
if not user_args.json_true:
289324
self.border_msg(' Successful: {} | Failed: {} | Valid: {} | Warning: {} | Expired: {} | Duration: {} '.format(
290325
len(hosts) - self.total_failed, self.total_failed, self.total_valid,
291-
self.total_warning, self.total_expired, datetime.now() - start_time))
326+
self.total_warning, self.total_expired, datetime.now(timezone.utc) - start_time))
292327
if user_args.summary_true:
293328
# Exit the script just
294329
return
@@ -329,7 +364,7 @@ def export_csv(self, context, filename, user_args):
329364
def export_html(self, context):
330365
"""Export JSON to HTML."""
331366
html = json2html.convert(json=context)
332-
file_name = datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S')
367+
file_name = datetime.strftime(datetime.now(timezone.utc), '%Y_%m_%d_%H_%M_%S')
333368
with open('{}.html'.format(file_name), 'w') as html_file:
334369
html_file.write(html)
335370

0 commit comments

Comments
 (0)