rewrote utilizing a sock-based github gist I found
This commit is contained in:
		
					parent
					
						
							
								d2a3a505fb
							
						
					
				
			
			
				commit
				
					
						8ddf5f1b49
					
				
			
		
					 3 changed files with 144 additions and 29 deletions
				
			
		| 
						 | 
					@ -1,10 +1,97 @@
 | 
				
			||||||
import ssl
 | 
					# heavily modified version of https://gist.githubusercontent.com/gdamjan/55a8b9eec6cf7b771f92021d93b87b2c/raw/d8dc194ec4d0187f985a57138019d04e3a59b51f/ssl-check.py
 | 
				
			||||||
import sys
 | 
					# to give a CLI for passing the hosts to check and other optional output
 | 
				
			||||||
import click
 | 
					import click
 | 
				
			||||||
import M2Crypto
 | 
					from OpenSSL import SSL
 | 
				
			||||||
 | 
					from OpenSSL import crypto
 | 
				
			||||||
 | 
					from cryptography import x509
 | 
				
			||||||
 | 
					from cryptography.x509.oid import NameOID
 | 
				
			||||||
 | 
					import idna
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from socket import socket
 | 
				
			||||||
 | 
					from collections import namedtuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__version__ = "0.1.0"
 | 
					__version__ = "0.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HostInfo = namedtuple(field_names="cert hostname peername", typename="HostInfo")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        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()
 | 
				
			||||||
 | 
					    except ConnectionRefusedError:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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_x509_text(cert):
 | 
				
			||||||
 | 
					    return crypto.dump_certificate(crypto.FILETYPE_TEXT, cert)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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):
 | 
				
			||||||
 | 
					    print(
 | 
				
			||||||
 | 
					        f"""
 | 
				
			||||||
 | 
					{hostinfo.hostname} ({hostinfo.peername[0]}:{hostinfo.peername[1]})
 | 
				
			||||||
 | 
					\tcommonName: {get_common_name(hostinfo.cert)}
 | 
				
			||||||
 | 
					\tSAN: {get_alt_names(hostinfo.cert)}
 | 
				
			||||||
 | 
					\tissuer: {get_issuer(hostinfo.cert)}
 | 
				
			||||||
 | 
					\tnotBefore: {hostinfo.cert.not_valid_before}
 | 
				
			||||||
 | 
					\tnotAfter:  {hostinfo.cert.not_valid_after}
 | 
				
			||||||
 | 
					          """
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_it_out(hostname, port):
 | 
				
			||||||
 | 
					    hostinfo = get_certificate(hostname, port)
 | 
				
			||||||
 | 
					    print_basic_info(hostinfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@click.command()
 | 
					@click.command()
 | 
				
			||||||
@click.version_option(__version__, prog_name="checkcert")
 | 
					@click.version_option(__version__, prog_name="checkcert")
 | 
				
			||||||
| 
						 | 
					@ -12,29 +99,36 @@ __version__ = "0.1.0"
 | 
				
			||||||
@click.option(
 | 
					@click.option(
 | 
				
			||||||
    "--dump", is_flag=True, help="Dump the full text version of the x509 certificate"
 | 
					    "--dump", is_flag=True, help="Dump the full text version of the x509 certificate"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@click.option(
 | 
					 | 
				
			||||||
    "--port", default=443, type=int, help="TCP port to connect to (default 443)"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@click.option("--expires", is_flag=True, help="Display the expiration date")
 | 
					@click.option("--expires", is_flag=True, help="Display the expiration date")
 | 
				
			||||||
@click.argument("domain")
 | 
					@click.argument("hosts", nargs=-1)
 | 
				
			||||||
def main(san, dump, port, expires, domain):
 | 
					def main(san, dump, expires, hosts):
 | 
				
			||||||
 | 
					    # setup the list of tuples
 | 
				
			||||||
 | 
					    HOSTS = []
 | 
				
			||||||
    # handle a domain given with a : in it to specify the port
 | 
					    # handle a domain given with a : in it to specify the port
 | 
				
			||||||
    if ":" in domain:
 | 
					    for host in hosts:
 | 
				
			||||||
        uri = domain.split(":")
 | 
					        # if  a host has a : in it, split on the :, first field will be host
 | 
				
			||||||
        domain = uri[0]
 | 
					        # second field will be the port
 | 
				
			||||||
        port = uri[1]
 | 
					        if ":" in host:
 | 
				
			||||||
    cert = ssl.get_server_certificate((domain, port))
 | 
					            host_info = host.split(":")
 | 
				
			||||||
    x509 = M2Crypto.X509.load_cert_string(cert)
 | 
					            HOSTS.append((host_info[0], int(host_info[1])))
 | 
				
			||||||
    if dump:
 | 
					        else:
 | 
				
			||||||
        print(x509.as_text())
 | 
					            HOSTS.append((host, 443))
 | 
				
			||||||
        sys.exit()
 | 
					    output_string = ""
 | 
				
			||||||
    if san:
 | 
					    for hostinfo in map(lambda x: get_certificate(x[0], x[1]), HOSTS):
 | 
				
			||||||
        all_sans = x509.get_ext("subjectAltName").get_value()
 | 
					        if dump:
 | 
				
			||||||
        sans = all_sans.split(",")
 | 
					            print(get_x509_text(hostinfo.cert).decode())
 | 
				
			||||||
        for san in sans:
 | 
					        else:
 | 
				
			||||||
            print(str(san).strip().removeprefix("DNS:"))
 | 
					            output_string += (
 | 
				
			||||||
        print(x509.get_subject().as_text())
 | 
					                f"{hostinfo.hostname} ({hostinfo.peername[0]}:{hostinfo.peername[1]})\n"
 | 
				
			||||||
    print(f"Certificate for {domain}\nexpires after: {x509.get_not_after()}")
 | 
					            )
 | 
				
			||||||
 | 
					            output_string += f"\tcommonName: {get_common_name(hostinfo.cert)}\n"
 | 
				
			||||||
 | 
					            if san:
 | 
				
			||||||
 | 
					                output_string += f"\tSAN: {get_alt_names(hostinfo.cert)}\n"
 | 
				
			||||||
 | 
					            output_string += f"\tissuer: {get_issuer(hostinfo.cert)}\n"
 | 
				
			||||||
 | 
					            output_string += f"\tnotBefore: {hostinfo.cert.not_valid_before}\n"
 | 
				
			||||||
 | 
					            output_string += f"\tnotAfter:  {hostinfo.cert.not_valid_after}\n\n"
 | 
				
			||||||
 | 
					            print(output_string)
 | 
				
			||||||
 | 
					    # print(f"Certificate for {domain}\nexpires after: {x509.get_not_after()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										30
									
								
								poetry.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								poetry.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -58,7 +58,7 @@ python-versions = "*"
 | 
				
			||||||
name = "cffi"
 | 
					name = "cffi"
 | 
				
			||||||
version = "1.14.6"
 | 
					version = "1.14.6"
 | 
				
			||||||
description = "Foreign Function Interface for Python calling C code."
 | 
					description = "Foreign Function Interface for Python calling C code."
 | 
				
			||||||
category = "dev"
 | 
					category = "main"
 | 
				
			||||||
optional = false
 | 
					optional = false
 | 
				
			||||||
python-versions = "*"
 | 
					python-versions = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -121,7 +121,7 @@ toml = ["toml"]
 | 
				
			||||||
name = "cryptography"
 | 
					name = "cryptography"
 | 
				
			||||||
version = "3.4.8"
 | 
					version = "3.4.8"
 | 
				
			||||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
 | 
					description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
 | 
				
			||||||
category = "dev"
 | 
					category = "main"
 | 
				
			||||||
optional = false
 | 
					optional = false
 | 
				
			||||||
python-versions = ">=3.6"
 | 
					python-versions = ">=3.6"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -336,7 +336,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 | 
				
			||||||
name = "pycparser"
 | 
					name = "pycparser"
 | 
				
			||||||
version = "2.20"
 | 
					version = "2.20"
 | 
				
			||||||
description = "C parser in Python"
 | 
					description = "C parser in Python"
 | 
				
			||||||
category = "dev"
 | 
					category = "main"
 | 
				
			||||||
optional = false
 | 
					optional = false
 | 
				
			||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 | 
					python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -373,6 +373,22 @@ platformdirs = ">=2.2.0"
 | 
				
			||||||
toml = ">=0.7.1"
 | 
					toml = ">=0.7.1"
 | 
				
			||||||
typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
 | 
					typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pyopenssl"
 | 
				
			||||||
 | 
					version = "21.0.0"
 | 
				
			||||||
 | 
					description = "Python wrapper module around the OpenSSL library"
 | 
				
			||||||
 | 
					category = "main"
 | 
				
			||||||
 | 
					optional = false
 | 
				
			||||||
 | 
					python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[package.dependencies]
 | 
				
			||||||
 | 
					cryptography = ">=3.3"
 | 
				
			||||||
 | 
					six = ">=1.5.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[package.extras]
 | 
				
			||||||
 | 
					docs = ["sphinx", "sphinx-rtd-theme"]
 | 
				
			||||||
 | 
					test = ["flaky", "pretend", "pytest (>=3.0.1)"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "pyparsing"
 | 
					name = "pyparsing"
 | 
				
			||||||
version = "2.4.7"
 | 
					version = "2.4.7"
 | 
				
			||||||
| 
						 | 
					@ -548,7 +564,7 @@ toml = ["setuptools (>=42)", "tomli (>=1.0.0)"]
 | 
				
			||||||
name = "six"
 | 
					name = "six"
 | 
				
			||||||
version = "1.16.0"
 | 
					version = "1.16.0"
 | 
				
			||||||
description = "Python 2 and 3 compatibility utilities"
 | 
					description = "Python 2 and 3 compatibility utilities"
 | 
				
			||||||
category = "dev"
 | 
					category = "main"
 | 
				
			||||||
optional = false
 | 
					optional = false
 | 
				
			||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 | 
					python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -671,7 +687,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
 | 
				
			||||||
[metadata]
 | 
					[metadata]
 | 
				
			||||||
lock-version = "1.1"
 | 
					lock-version = "1.1"
 | 
				
			||||||
python-versions = "^3.9"
 | 
					python-versions = "^3.9"
 | 
				
			||||||
content-hash = "c1ffdd5000aa0956b993deef3eefa5f636b041a5c5a4a35b56c638d397f82bbf"
 | 
					content-hash = "f9e3c5502f1aa3c91b8c2a76f7f4c14d39cb84281143d987cf9e8ed043efa175"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[metadata.files]
 | 
					[metadata.files]
 | 
				
			||||||
astroid = [
 | 
					astroid = [
 | 
				
			||||||
| 
						 | 
					@ -938,6 +954,10 @@ pylint = [
 | 
				
			||||||
    {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"},
 | 
					    {file = "pylint-2.11.1-py3-none-any.whl", hash = "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126"},
 | 
				
			||||||
    {file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"},
 | 
					    {file = "pylint-2.11.1.tar.gz", hash = "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"},
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					pyopenssl = [
 | 
				
			||||||
 | 
					    {file = "pyOpenSSL-21.0.0-py2.py3-none-any.whl", hash = "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"},
 | 
				
			||||||
 | 
					    {file = "pyOpenSSL-21.0.0.tar.gz", hash = "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3"},
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
pyparsing = [
 | 
					pyparsing = [
 | 
				
			||||||
    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
 | 
					    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
 | 
				
			||||||
    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
 | 
					    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ authors = ["Alex Kelly <alex.kelly@franklin.edu>"]
 | 
				
			||||||
[tool.poetry.dependencies]
 | 
					[tool.poetry.dependencies]
 | 
				
			||||||
python = "^3.9"
 | 
					python = "^3.9"
 | 
				
			||||||
click = "^8.0.1"
 | 
					click = "^8.0.1"
 | 
				
			||||||
 | 
					pyOpenSSL = "^21.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.dev-dependencies]
 | 
					[tool.poetry.dev-dependencies]
 | 
				
			||||||
python-semantic-release = "^7.19.2"
 | 
					python-semantic-release = "^7.19.2"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue