-
-
Save gdamjan/55a8b9eec6cf7b771f92021d93b87b2c to your computer and use it in GitHub Desktop.
# -*- 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) |
._asdict()
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?
Insanely dope script. namedtuple
just blew my mind.
Any idea on how to verify the chain?
I am having problems with the verify_cert option is this working correctly and is there a way to check a self signed certificate?
How to add the output of the organization that issued the certificate to the script. For example
(O) Let's Encrypt
Everything. Figured it out
names_o = cert.issuer.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)
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
Would it be possible to convert the cert to a byte string or an instance of asn1crypto.x509.Certificate please?
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)))
I really appreciate that you took the time to share this with the world... super useful !!! Thank you.
Thank you so much for being so helpful.
cert.has_expired() doesn't work for this version. Is there a workaround?
Error
'cryptography.hazmat.bindings._rust.x509.Certificat' object has no attribute 'has_expired'
cert.has_expired() doesn't work for this version.
and what is "this" version !?!
Is there a workaround?
install "another" version?!
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. :)