22import socket
33import sys
44import json
5+ import ssl
6+ from datetime import datetime , timezone
57
68from argparse import ArgumentParser , SUPPRESS
7- from datetime import datetime
89from time import sleep
910from csv import DictWriter
1011
1112try :
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 *
1417except 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