mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 01:19:11 +00:00 
			
		
		
		
	cli: Cleaned up certificate listing
This commit is contained in:
		
							
								
								
									
										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") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user