refactor: clean-up for pylint

This commit is contained in:
Alex Kelly 2021-09-30 16:14:22 -04:00
parent d15740d3e9
commit 9a1a960641
2 changed files with 54 additions and 59 deletions

View file

@ -45,7 +45,7 @@ limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=pylint_django
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes

View file

@ -1,15 +1,14 @@
# heavily modified version of https://gist.githubusercontent.com/gdamjan/55a8b9eec6cf7b771f92021d93b87b2c/raw/d8dc194ec4d0187f985a57138019d04e3a59b51f/ssl-check.py
# to give a CLI for passing the hosts to check and other optional output
""" CLI to get basic information about certificates and determine validity"""
from collections import namedtuple
import concurrent.futures
from socket import socket
import click
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.2.0"
@ -19,25 +18,23 @@ HostInfo = namedtuple(
def get_certificate(hostname, port):
"""retreive certificate details and return HostInfo tuple of values"""
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
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,
@ -48,6 +45,7 @@ def get_certificate(hostname, port):
def get_alt_names(cert):
"""retreive the SAN values for given cert"""
try:
ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
return ext.value.get_values_for_type(x509.DNSName)
@ -56,10 +54,12 @@ def get_alt_names(cert):
def get_x509_text(cert):
"""return the human-readable text version of the certificate"""
return crypto.dump_certificate(crypto.FILETYPE_TEXT, cert)
def get_common_name(cert):
"""Retrun the common name from the certificate"""
try:
names = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
return names[0].value
@ -68,6 +68,7 @@ def get_common_name(cert):
def get_issuer(cert):
"""Return the name of the CA/Issuer of the certificate"""
try:
names = cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)
return names[0].value
@ -75,62 +76,56 @@ def get_issuer(cert):
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}
"""
)
@click.command()
@click.version_option(__version__, prog_name="checkcert")
@click.option("--san", is_flag=True, help="Output Subject Alternate Names")
@click.option(
"--dump", is_flag=True, help="Dump the full text version of the x509 certificate"
)
@click.option("--color/--no-color", default=True)
@click.option(
"--color/--no-color",
default=True,
help="Enable/disable ANSI color output to show cert validity",
)
@click.argument("hosts", nargs=-1)
def main(san, dump, color, hosts):
"""Return information about certificates given including their validity"""
# setup the list of tuples
HOSTS = []
all_hosts = []
# handle a domain given with a : in it to specify the port
for host in hosts:
# if a host has a : in it, split on the :, first field will be host
# second field will be the port
if ":" in host:
host_info = host.split(":")
HOSTS.append((host_info[0], int(host_info[1])))
all_hosts.append((host_info[0], int(host_info[1])))
else:
HOSTS.append((host, 443))
for hostinfo in map(lambda x: get_certificate(x[0], x[1]), HOSTS):
output_string = ""
if dump:
print(get_x509_text(hostinfo.cert).decode())
else:
output_string += (
f"{hostinfo.hostname} ({hostinfo.peername[0]}:{hostinfo.peername[1]})\n"
)
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"
if hostinfo.is_valid and color:
click.echo(click.style(output_string, fg="green"))
elif not hostinfo.is_valid and color:
click.echo(click.style(output_string, fg="red"))
all_hosts.append((host, 443))
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as epool:
for hostinfo in epool.map(lambda x: get_certificate(x[0], x[1]), all_hosts):
output_string = ""
if dump:
print(get_x509_text(hostinfo.cert).decode())
else:
click.echo(click.style(output_string))
output_string += (
f"{hostinfo.hostname} "
f"({hostinfo.peername[0]}:{hostinfo.peername[1]})\n"
)
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"
if hostinfo.is_valid and color:
click.echo(click.style(output_string, fg="green"))
elif not hostinfo.is_valid and color:
click.echo(click.style(output_string, fg="red"))
else:
click.echo(click.style(output_string))
# print(f"Certificate for {domain}\nexpires after: {x509.get_not_after()}")
if __name__ == "__main__":
main() # pragma: no cover
main() # pylint: disable=no-value-for-parameter # pragma: no cover