mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-22 16:25:17 +00:00
cli: Cleaned up certificate listing
This commit is contained in:
parent
42916a7ccc
commit
e292e01aff
141
certidude/cli.py
141
certidude/cli.py
@ -683,20 +683,29 @@ def certidude_setup_authority(parent, country, state, locality, organization, or
|
||||
|
||||
@click.command("list", help="List certificates")
|
||||
@click.argument("ca", nargs=-1)
|
||||
@click.option("--verbose", "-v", default=False, is_flag=True, help="Verbose output")
|
||||
@click.option("--show-key-type", "-k", default=False, is_flag=True, help="Show key type and length")
|
||||
@click.option("--show-path", "-p", default=False, is_flag=True, help="Show filesystem paths")
|
||||
@click.option("--show-extensions", "-e", default=False, is_flag=True, help="Show X.509 Certificate Extensions")
|
||||
def certidude_list(ca, show_key_type, show_extensions, show_path):
|
||||
@click.option("--hide-requests", "-h", default=False, is_flag=True, help="Hide signing requests")
|
||||
@click.option("--show-signed", "-s", default=False, is_flag=True, help="Show signed certificates")
|
||||
@click.option("--show-revoked", "-r", default=False, is_flag=True, help="Show revoked certificates")
|
||||
def certidude_list(ca, verbose, show_key_type, show_extensions, show_path, show_signed, show_revoked, hide_requests):
|
||||
# Statuses:
|
||||
# s - submitted
|
||||
# v - valid
|
||||
# e - expired
|
||||
# y - not valid yet
|
||||
# r - revoked
|
||||
|
||||
from pycountry import countries
|
||||
def dump_common(j):
|
||||
if show_path:
|
||||
click.echo(" | | Path: %s" % j.path)
|
||||
|
||||
person = [j for j in (j.given_name, j.surname) if j]
|
||||
if person:
|
||||
click.echo(" | | Associated person: %s" % " ".join(person) + (" <%s>" % j.email_address if j.email_address else ""))
|
||||
click.echo("Associated person: %s" % " ".join(person) + (" <%s>" % j.email_address if j.email_address else ""))
|
||||
elif j.email_address:
|
||||
click.echo(" | | Associated e-mail: " + j.email_address)
|
||||
click.echo("Associated e-mail: " + j.email_address)
|
||||
|
||||
bits = [j for j in (
|
||||
countries.get(alpha2=j.country_code.upper()).name if
|
||||
@ -706,17 +715,19 @@ def certidude_list(ca, show_key_type, show_extensions, show_path):
|
||||
j.organization,
|
||||
j.organizational_unit) if j]
|
||||
if bits:
|
||||
click.echo(" | | Organization: %s" % ", ".join(bits))
|
||||
click.echo("Organization: %s" % ", ".join(bits))
|
||||
|
||||
if show_key_type:
|
||||
click.echo(" | | Key type: %s-bit %s" % (j.key_length, j.key_type))
|
||||
click.echo("Key type: %s-bit %s" % (j.key_length, j.key_type))
|
||||
|
||||
if show_extensions:
|
||||
for key, value, data in j.extensions:
|
||||
click.echo((" | | Extension " + key + ":").ljust(50) + " " + value)
|
||||
elif j.key_usage:
|
||||
click.echo(" | | Key usage: " + j.key_usage)
|
||||
click.echo(" | |")
|
||||
click.echo(("Extension " + key + ":").ljust(50) + " " + value)
|
||||
else:
|
||||
if j.key_usage:
|
||||
click.echo("Key usage: " + j.key_usage)
|
||||
if j.fqdn:
|
||||
click.echo("Associated hostname: " + j.fqdn)
|
||||
|
||||
config = load_config()
|
||||
|
||||
@ -728,61 +739,75 @@ def certidude_list(ca, show_key_type, show_extensions, show_path):
|
||||
wanted_list = ca
|
||||
|
||||
for ca in config.all_authorities(wanted_list):
|
||||
click.echo("Certificate authority " + click.style(ca.slug, fg="blue"))
|
||||
# if ca.certificate.email_address:
|
||||
# click.echo(" \u2709 %s" % ca.certificate.email_address)
|
||||
if not hide_requests:
|
||||
for j in ca.get_requests():
|
||||
if not verbose:
|
||||
click.echo("s " + j.path + " " + j.distinguished_name)
|
||||
continue
|
||||
click.echo(click.style(j.common_name, fg="blue"))
|
||||
click.echo("=" * len(j.common_name))
|
||||
click.echo("State: ? " + click.style("submitted", fg="yellow") + " " + naturaltime(j.created) + click.style(", %s" %j.created, fg="white"))
|
||||
|
||||
if ca.certificate.signed < NOW and ca.certificate.expires > NOW:
|
||||
print(ca.certificate.expires)
|
||||
click.echo(" | \u2713 Certificate: " + click.style("valid", fg="green") + ", %s" % ca.certificate.expires)
|
||||
elif NOW > ca.certificate.expires:
|
||||
click.echo(" | \u2717 Certificate: " + click.style("expired", fg="red"))
|
||||
else:
|
||||
click.echo(" | \u2717 Certificate: " + click.style("not valid yet", fg="red"))
|
||||
dump_common(j)
|
||||
|
||||
if os.path.exists(ca.private_key):
|
||||
click.echo(" | \u2713 Private key " + ca.private_key + ": " + click.style("okay", fg="green"))
|
||||
# TODO: Check permissions
|
||||
else:
|
||||
click.echo(" | \u2717 Private key " + ca.private_key + ": " + click.style("does not exist", fg="red"))
|
||||
# Calculate checksums for cross-checking
|
||||
import hashlib
|
||||
md5sum = hashlib.md5()
|
||||
sha1sum = hashlib.sha1()
|
||||
sha256sum = hashlib.sha256()
|
||||
with open(j.path, "rb") as fh:
|
||||
buf = fh.read()
|
||||
md5sum.update(buf)
|
||||
sha1sum.update(buf)
|
||||
sha256sum.update(buf)
|
||||
click.echo("MD5 checksum: %s" % md5sum.hexdigest())
|
||||
click.echo("SHA-1 checksum: %s" % sha1sum.hexdigest())
|
||||
click.echo("SHA-256 checksum: %s" % sha256sum.hexdigest())
|
||||
|
||||
if os.path.isdir(ca.signed_dir):
|
||||
click.echo(" | \u2713 Signed certificates directory " + ca.signed_dir + ": " + click.style("okay", fg="green"))
|
||||
else:
|
||||
click.echo(" | \u2717 Signed certificates directory " + ca.signed_dir + ": " + click.style("does not exist", fg="red"))
|
||||
if show_path:
|
||||
click.echo("Details: openssl req -in %s -text -noout" % j.path)
|
||||
click.echo("Sign: certidude sign %s" % j.path)
|
||||
click.echo()
|
||||
|
||||
if ca.revoked_dir:
|
||||
click.echo(" | Revoked certificates directory: %s" % ca.revoked_dir)
|
||||
if show_signed:
|
||||
for j in ca.get_signed():
|
||||
if not verbose:
|
||||
if j.signed < NOW and j.expires > NOW:
|
||||
click.echo("v " + j.path + " " + j.distinguished_name)
|
||||
elif NOW > j.expires:
|
||||
click.echo("e " + j.path + " " + j.distinguished_name)
|
||||
else:
|
||||
click.echo("y " + j.path + " " + j.distinguished_name)
|
||||
continue
|
||||
|
||||
click.echo(" +-- Pending requests")
|
||||
click.echo(click.style(j.common_name, fg="blue") + " " + click.style(j.serial_number_hex, fg="white"))
|
||||
click.echo("="*(len(j.common_name)+60))
|
||||
|
||||
for j in ca.get_requests():
|
||||
click.echo(" | +-- Request " + click.style(j.common_name, fg="blue"))
|
||||
click.echo(" | | Submitted: %s, %s" % (naturaltime(j.created), j.created))
|
||||
dump_common(j)
|
||||
if j.signed < NOW and j.expires > NOW:
|
||||
click.echo("Status: \u2713 " + click.style("valid", fg="green") + " " + naturaltime(j.expires) + click.style(", %s" %j.expires, fg="white"))
|
||||
elif NOW > j.expires:
|
||||
click.echo("Status: \u2717 " + click.style("expired", fg="red") + " " + naturaltime(j.expires) + click.style(", %s" %j.expires, fg="white"))
|
||||
else:
|
||||
click.echo("Status: \u2717 " + click.style("not valid yet", fg="red") + click.style(", %s" %j.expires, fg="white"))
|
||||
dump_common(j)
|
||||
|
||||
click.echo(" +-- Signed certificates")
|
||||
if show_path:
|
||||
click.echo("Details: openssl x509 -in %s -text -noout" % j.path)
|
||||
click.echo("Revoke: certidude revoke %s" % j.path)
|
||||
click.echo()
|
||||
|
||||
for j in ca.get_signed():
|
||||
click.echo(" | +-- Certificate " + click.style(j.common_name, fg="blue") + " " + click.style(":".join(re.findall("\d\d", j.serial_number)), fg="white"))
|
||||
|
||||
if j.signed < NOW and j.expires > NOW:
|
||||
click.echo(" | | \u2713 Certificate " + click.style("valid", fg="green") + " " + naturaltime(j.expires))
|
||||
elif NOW > j.expires:
|
||||
click.echo(" | | \u2717 Certificate " + click.style("expired", fg="red") + " " + naturaltime(j.expires))
|
||||
else:
|
||||
click.echo(" | | \u2717 Certificate " + click.style("not valid yet", fg="red"))
|
||||
dump_common(j)
|
||||
|
||||
click.echo(" +-- Revocations")
|
||||
|
||||
for j in ca.get_revoked():
|
||||
click.echo(" | +-- Revocation " + click.style(j.common_name, fg="blue") + " " + click.style(":".join(re.findall("\d\d", j.serial_number)), fg="white"))
|
||||
# click.echo(" | | Serial: %s" % ":".join(re.findall("\d\d", j.serial_number)))
|
||||
if show_path:
|
||||
click.echo(" | | Path: %s" % j.path)
|
||||
click.echo(" | | Revoked: %s%s" % (naturaltime(NOW-j.changed), click.style(", %s" % j.changed, fg="white")))
|
||||
dump_common(j)
|
||||
if show_revoked:
|
||||
for j in ca.get_revoked():
|
||||
if not verbose:
|
||||
click.echo("r " + j.path + " " + j.distinguished_name)
|
||||
continue
|
||||
click.echo(click.style(j.common_name, fg="blue") + " " + click.style(j.serial_number_hex, fg="white"))
|
||||
click.echo("="*(len(j.common_name)+60))
|
||||
click.echo("Status: \u2717 " + click.style("revoked", fg="red") + " %s%s" % (naturaltime(NOW-j.changed), click.style(", %s" % j.changed, fg="white")))
|
||||
dump_common(j)
|
||||
if show_path:
|
||||
click.echo("Details: openssl x509 -in %s -text -noout" % j.path)
|
||||
click.echo()
|
||||
|
||||
click.echo()
|
||||
|
||||
|
@ -265,6 +265,13 @@ class CertificateBase:
|
||||
return bit[6:]
|
||||
return ""
|
||||
|
||||
@property
|
||||
def fqdn(self):
|
||||
for bit in self.subject_alt_name.split(", "):
|
||||
if bit.startswith("DNS:"):
|
||||
return bit[4:]
|
||||
return ""
|
||||
|
||||
@property
|
||||
def subject_alt_name(self):
|
||||
for key, value, data in self.extensions:
|
||||
@ -296,7 +303,6 @@ class CertificateBase:
|
||||
m, _ = self.pubkey
|
||||
return ":".join(re.findall("..", hashlib.sha1(binascii.unhexlify("%x" % m)).hexdigest()))
|
||||
|
||||
|
||||
class Request(CertificateBase):
|
||||
def __init__(self, mixed=None):
|
||||
self.buf = None
|
||||
@ -356,8 +362,8 @@ class Certificate(CertificateBase):
|
||||
|
||||
if isinstance(mixed, io.TextIOWrapper):
|
||||
self.path = mixed.name
|
||||
_, _, _, _, _, _, _, _, _, ctime = os.stat(self.path)
|
||||
self.changed = datetime.fromtimestamp(ctime)
|
||||
_, _, _, _, _, _, _, _, mtime, _ = os.stat(self.path)
|
||||
self.changed = datetime.fromtimestamp(mtime)
|
||||
mixed = mixed.read()
|
||||
|
||||
if isinstance(mixed, str):
|
||||
@ -378,7 +384,7 @@ class Certificate(CertificateBase):
|
||||
@property
|
||||
def extensions(self):
|
||||
# WTF?!
|
||||
for j in range(1, self._obj.get_extension_count()):
|
||||
for j in range(0, self._obj.get_extension_count()):
|
||||
e = self._obj.get_extension(j)
|
||||
yield e.get_short_name().decode("ascii"), str(e), e.get_data()
|
||||
|
||||
@ -386,6 +392,10 @@ class Certificate(CertificateBase):
|
||||
def serial_number(self):
|
||||
return "%040x" % self._obj.get_serial_number()
|
||||
|
||||
@property
|
||||
def serial_number_hex(self):
|
||||
return ":".join(re.findall("[0123456789abcdef]{2}", self.serial_number))
|
||||
|
||||
@property
|
||||
def signed(self):
|
||||
return datetime.strptime(self._obj.get_notBefore().decode("ascii"), "%Y%m%d%H%M%SZ")
|
||||
|
Loading…
Reference in New Issue
Block a user