Skip to content

Instantly share code, notes, and snippets.

@gdamjan
Last active May 11, 2023 08:07
Embed
What would you like to do?
Python script to check on SSL certificates
# -*- encoding: utf-8 -*-
# requires a recent enough python with idna support in socket
# pyopenssl, cryptography and idna
from OpenSSL import SSL
from cryptography import x509
from cryptography.x509.oid import NameOID
import idna
from socket import socket
from collections import namedtuple
HostInfo = namedtuple(field_names='cert hostname peername', typename='HostInfo')
HOSTS = [
('damjan.softver.org.mk', 443),
('expired.badssl.com', 443),
('wrong.host.badssl.com', 443),
('ca.ocsr.nl', 443),
('faß.de', 443),
('самодеј.мкд', 443),
]
def verify_cert(cert, hostname):
# verify notAfter/notBefore, CA trusted, servername/sni/hostname
cert.has_expired()
# service_identity.pyopenssl.verify_hostname(client_ssl, hostname)
# issuer
def get_certificate(hostname, port):
hostname_idna = idna.encode(hostname)
sock = socket()
sock.connect((hostname, port))
peername = sock.getpeername()
ctx = SSL.Context(SSL.SSLv23_METHOD) # most compatible
ctx.check_hostname = False
ctx.verify_mode = SSL.VERIFY_NONE
sock_ssl = SSL.Connection(ctx, sock)
sock_ssl.set_connect_state()
sock_ssl.set_tlsext_host_name(hostname_idna)
sock_ssl.do_handshake()
cert = sock_ssl.get_peer_certificate()
crypto_cert = cert.to_cryptography()
sock_ssl.close()
sock.close()
return HostInfo(cert=crypto_cert, peername=peername, hostname=hostname)
def get_alt_names(cert):
try:
ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
return ext.value.get_values_for_type(x509.DNSName)
except x509.ExtensionNotFound:
return None
def get_common_name(cert):
try:
names = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
return names[0].value
except x509.ExtensionNotFound:
return None
def get_issuer(cert):
try:
names = cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)
return names[0].value
except x509.ExtensionNotFound:
return None
def print_basic_info(hostinfo):
s = '''» {hostname} « … {peername}
\tcommonName: {commonname}
\tSAN: {SAN}
\tissuer: {issuer}
\tnotBefore: {notbefore}
\tnotAfter: {notafter}
'''.format(
hostname=hostinfo.hostname,
peername=hostinfo.peername,
commonname=get_common_name(hostinfo.cert),
SAN=get_alt_names(hostinfo.cert),
issuer=get_issuer(hostinfo.cert),
notbefore=hostinfo.cert.not_valid_before,
notafter=hostinfo.cert.not_valid_after
)
print(s)
def check_it_out(hostname, port):
hostinfo = get_certificate(hostname, port)
print_basic_info(hostinfo)
import concurrent.futures
if __name__ == '__main__':
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as e:
for hostinfo in e.map(lambda x: get_certificate(x[0], x[1]), HOSTS):
print_basic_info(hostinfo)
@gdamjan
Copy link
Author

gdamjan commented Sep 22, 2020

How do we get the results in json format?

json.dumps(host._as_dict())

@altafparkar
Copy link

How do we get the results in json format?

json.dumps(host._as_dict())

Thanks - but this is what I get

Code:
host = get_certificate('google.co.nz', 443)
print(host)
json.dumps(host._as_dict())

Error:
HostInfo(cert=<Certificate(subject=<Name(C=US,ST=California,L=Mountain View,O=Google LLC,CN=*.google.co.nz)>, ...)>, hostname='google.co.nz', peername=('142.250.66.195', 443))
Traceback (most recent call last):
File ".\ssl-check.py", line 110, in
print_basic_info(hostinfo)
File ".\ssl-check.py", line 91, in print_basic_info
json.dumps(host._as_dict())
AttributeError: 'HostInfo' object has no attribute '_as_dict'

I am able to build the json manually but am thinking of a cooler option. :)

@gdamjan
Copy link
Author

gdamjan commented Sep 23, 2020

._asdict()

@daviddemers
Copy link

I'm running into a suspected firewall issue when I attempt to access few hosts, and the script is failing and erroring out with:

Traceback (most recent call last):
  File "/usr/local/adm/checkcerts.py", line 53, in get_certificate
    sock.connect((hostname, port))
TimeoutError: [Errno 110] Connection timed out

For the specific hosts in my issue, I intend to address the issue with my networking team, but does anyone have a good suggestion for handling that error in the script?

@alfonsrv
Copy link

alfonsrv commented Mar 13, 2021

Insanely dope script. namedtuple just blew my mind.
Any idea on how to verify the chain?

@tomtrkd
Copy link

tomtrkd commented Jun 23, 2021

I am having problems with the verify_cert option is this working correctly and is there a way to check a self signed certificate?

@suharevA
Copy link

suharevA commented Dec 4, 2021

How to add the output of the organization that issued the certificate to the script. For example
(O) Let's Encrypt

@suharevA
Copy link

suharevA commented Dec 4, 2021

Everything. Figured it out
names_o = cert.issuer.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)

@guilhermembc
Copy link

when I get a list of sites and one of them does not have a TLS certificate configured, it generates an error:
sock.connect((hostname, port))
BlockingIOError: [Errno 11] Resource temporarily unavailable

@Damien455
Copy link

Would it be possible to convert the cert to a byte string or an instance of asn1crypto.x509.Certificate please?

@dimazarno
Copy link

Hi, the script is useful, but it's possible put these values: ('damjan.softver.org.mk', 443), ('expired.badssl.com', 443), ('wrong.host.badssl.com', 443), ('ca.ocsr.nl', 443), ('faß.de', 443), ('самодеј.мкд', 443), inside a text file? I mean exist an elegant way to read lines of file into list?

you can change HOSTS into this:

HOSTS = []

for line in hosts_file:
host, port = line.strip().split(':')
HOSTS.append((host, int(port)))

@panofish
Copy link

I really appreciate that you took the time to share this with the world... super useful !!! Thank you.

@leejhn
Copy link

leejhn commented May 11, 2023

Thank you so much for being so helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment