mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-30 17:09:19 +00:00 
			
		
		
		
	cli: Cleaned up certificate listing
This commit is contained in:
		
							
								
								
									
										131
									
								
								certidude/cli.py
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								certidude/cli.py
									
									
									
									
									
								
							| @@ -683,20 +683,29 @@ def certidude_setup_authority(parent, country, state, locality, organization, or | |||||||
|  |  | ||||||
| @click.command("list", help="List certificates") | @click.command("list", help="List certificates") | ||||||
| @click.argument("ca", nargs=-1) | @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-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-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") | @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 |     from pycountry import countries | ||||||
|     def dump_common(j): |     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] |         person = [j for j in (j.given_name, j.surname) if j] | ||||||
|         if person: |         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: |         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 ( |         bits = [j for j in ( | ||||||
|             countries.get(alpha2=j.country_code.upper()).name if |             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.organization, | ||||||
|             j.organizational_unit) if j] |             j.organizational_unit) if j] | ||||||
|         if bits: |         if bits: | ||||||
|             click.echo(" |    |   Organization: %s" % ", ".join(bits)) |             click.echo("Organization: %s" % ", ".join(bits)) | ||||||
|  |  | ||||||
|         if show_key_type: |         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: |         if show_extensions: | ||||||
|             for key, value, data in j.extensions: |             for key, value, data in j.extensions: | ||||||
|                 click.echo((" |    |   Extension " + key + ":").ljust(50) + " " + value) |                 click.echo(("Extension " + key + ":").ljust(50) + " " + value) | ||||||
|         elif j.key_usage: |         else: | ||||||
|             click.echo(" |    |   Key usage: " + j.key_usage) |             if j.key_usage: | ||||||
|         click.echo(" |    |") |                 click.echo("Key usage: " + j.key_usage) | ||||||
|  |             if j.fqdn: | ||||||
|  |                 click.echo("Associated hostname: " + j.fqdn) | ||||||
|  |  | ||||||
|     config = load_config() |     config = load_config() | ||||||
|  |  | ||||||
| @@ -728,61 +739,75 @@ def certidude_list(ca, show_key_type, show_extensions, show_path): | |||||||
|         wanted_list = ca |         wanted_list = ca | ||||||
|  |  | ||||||
|     for ca in config.all_authorities(wanted_list): |     for ca in config.all_authorities(wanted_list): | ||||||
|         click.echo("Certificate authority " + click.style(ca.slug, fg="blue")) |         if not hide_requests: | ||||||
| #        if ca.certificate.email_address: |  | ||||||
| #            click.echo("  \u2709 %s" % ca.certificate.email_address) |  | ||||||
|  |  | ||||||
|         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")) |  | ||||||
|  |  | ||||||
|         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")) |  | ||||||
|  |  | ||||||
|         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 ca.revoked_dir: |  | ||||||
|             click.echo(" |   Revoked certificates directory: %s" % ca.revoked_dir) |  | ||||||
|  |  | ||||||
|         click.echo(" +-- Pending requests") |  | ||||||
|  |  | ||||||
|             for j in ca.get_requests(): |             for j in ca.get_requests(): | ||||||
|             click.echo(" |    +-- Request " + click.style(j.common_name, fg="blue")) |                 if not verbose: | ||||||
|             click.echo(" |    |   Submitted: %s, %s" % (naturaltime(j.created), j.created)) |                     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")) | ||||||
|  |  | ||||||
|                 dump_common(j) |                 dump_common(j) | ||||||
|  |  | ||||||
|         click.echo(" +-- Signed certificates") |                 # 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 show_path: | ||||||
|  |                     click.echo("Details: openssl req -in %s -text -noout" % j.path) | ||||||
|  |                     click.echo("Sign: certidude sign %s" % j.path) | ||||||
|  |                 click.echo() | ||||||
|  |  | ||||||
|  |         if show_signed: | ||||||
|             for j in ca.get_signed(): |             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 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(click.style(j.common_name, fg="blue") + " " + click.style(j.serial_number_hex, fg="white")) | ||||||
|  |                 click.echo("="*(len(j.common_name)+60)) | ||||||
|  |  | ||||||
|                 if j.signed < NOW and j.expires > NOW: |                 if j.signed < NOW and j.expires > NOW: | ||||||
|                 click.echo(" |    | \u2713 Certificate " + click.style("valid", fg="green") + " " + naturaltime(j.expires)) |                     click.echo("Status: \u2713 " + click.style("valid", fg="green") + " " + naturaltime(j.expires) + click.style(", %s" %j.expires,  fg="white")) | ||||||
|                 elif NOW > j.expires: |                 elif NOW > j.expires: | ||||||
|                 click.echo(" |    | \u2717 Certificate " + click.style("expired", fg="red") + " " + naturaltime(j.expires)) |                     click.echo("Status: \u2717 " + click.style("expired", fg="red") + " " + naturaltime(j.expires) + click.style(", %s" %j.expires,  fg="white")) | ||||||
|                 else: |                 else: | ||||||
|                 click.echo(" |    | \u2717 Certificate " + click.style("not valid yet", fg="red")) |                     click.echo("Status: \u2717 " + click.style("not valid yet", fg="red") + click.style(", %s" %j.expires,  fg="white")) | ||||||
|                 dump_common(j) |                 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: |                 if show_path: | ||||||
|                 click.echo(" |    |   Path: %s" % j.path) |                     click.echo("Details: openssl x509 -in %s -text -noout" % j.path) | ||||||
|             click.echo(" |    |   Revoked: %s%s" % (naturaltime(NOW-j.changed), click.style(", %s" % j.changed, fg="white"))) |                     click.echo("Revoke: certidude revoke %s" % j.path) | ||||||
|  |                 click.echo() | ||||||
|  |  | ||||||
|  |         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) |                 dump_common(j) | ||||||
|  |                 if show_path: | ||||||
|  |                     click.echo("Details: openssl x509 -in %s -text -noout" % j.path) | ||||||
|  |                 click.echo() | ||||||
|  |  | ||||||
|         click.echo() |         click.echo() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -265,6 +265,13 @@ class CertificateBase: | |||||||
|                 return bit[6:] |                 return bit[6:] | ||||||
|         return "" |         return "" | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def fqdn(self): | ||||||
|  |         for bit in self.subject_alt_name.split(", "): | ||||||
|  |             if bit.startswith("DNS:"): | ||||||
|  |                 return bit[4:] | ||||||
|  |         return "" | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def subject_alt_name(self): |     def subject_alt_name(self): | ||||||
|         for key, value, data in self.extensions: |         for key, value, data in self.extensions: | ||||||
| @@ -296,7 +303,6 @@ class CertificateBase: | |||||||
|         m, _ = self.pubkey |         m, _ = self.pubkey | ||||||
|         return ":".join(re.findall("..", hashlib.sha1(binascii.unhexlify("%x" % m)).hexdigest())) |         return ":".join(re.findall("..", hashlib.sha1(binascii.unhexlify("%x" % m)).hexdigest())) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Request(CertificateBase): | class Request(CertificateBase): | ||||||
|     def __init__(self, mixed=None): |     def __init__(self, mixed=None): | ||||||
|         self.buf = None |         self.buf = None | ||||||
| @@ -356,8 +362,8 @@ class Certificate(CertificateBase): | |||||||
|  |  | ||||||
|         if isinstance(mixed, io.TextIOWrapper): |         if isinstance(mixed, io.TextIOWrapper): | ||||||
|             self.path = mixed.name |             self.path = mixed.name | ||||||
|             _, _, _, _, _, _, _, _, _, ctime = os.stat(self.path) |             _, _, _, _, _, _, _, _, mtime, _ = os.stat(self.path) | ||||||
|             self.changed = datetime.fromtimestamp(ctime) |             self.changed = datetime.fromtimestamp(mtime) | ||||||
|             mixed = mixed.read() |             mixed = mixed.read() | ||||||
|  |  | ||||||
|         if isinstance(mixed, str): |         if isinstance(mixed, str): | ||||||
| @@ -378,7 +384,7 @@ class Certificate(CertificateBase): | |||||||
|     @property |     @property | ||||||
|     def extensions(self): |     def extensions(self): | ||||||
|         # WTF?! |         # 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) |             e = self._obj.get_extension(j) | ||||||
|             yield e.get_short_name().decode("ascii"), str(e), e.get_data() |             yield e.get_short_name().decode("ascii"), str(e), e.get_data() | ||||||
|  |  | ||||||
| @@ -386,6 +392,10 @@ class Certificate(CertificateBase): | |||||||
|     def serial_number(self): |     def serial_number(self): | ||||||
|         return "%040x" % self._obj.get_serial_number() |         return "%040x" % self._obj.get_serial_number() | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def serial_number_hex(self): | ||||||
|  |         return ":".join(re.findall("[0123456789abcdef]{2}", self.serial_number)) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def signed(self): |     def signed(self): | ||||||
|         return datetime.strptime(self._obj.get_notBefore().decode("ascii"), "%Y%m%d%H%M%SZ") |         return datetime.strptime(self._obj.get_notBefore().decode("ascii"), "%Y%m%d%H%M%SZ") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user