import sys import typing from cryptography import x509 from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.openssl import rsa from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey class WrongExtensionType(Exception): def __init__(self, oid: x509.ObjectIdentifier): super(f"wrong object id: {oid.dotted_string}") def parse_certificate(cert: str) -> typing.Union[x509.Certificate, None]: with open(cert, "rb") as f: cert_bytes = f.read() try: parsed = x509.load_pem_x509_certificate(cert_bytes) except ValueError: try: parsed = x509.load_der_x509_certificate(cert_bytes) except ValueError: print(f"skip {cert}", file=sys.stderr) return None return parsed def format_key_usage(value: x509.KeyUsage): usages = [] if value.digital_signature: usages.append("digital signature") if value.content_commitment: usages.append("non repudiation") if value.key_encipherment: usages.append("key encipherment") if value.data_encipherment: usages.append("data encipherment") if value.key_agreement: usages.append("key agreement") if value.encipher_only: usages.append("encipher only") if value.decipher_only: usages.append("decipher only") if value.key_cert_sign: usages.append("key cert sign") if value.crl_sign: usages.append("crl sign") return ", ".join(usages) def format_oid_link(oid: x509.ObjectIdentifier) -> str: return f"[{oid.dotted_string}](https://oidref.com/{oid.dotted_string})" def get_key_type(public_key): if isinstance(public_key, rsa.RSAPublicKey): return f"RSA {public_key.key_size}" if isinstance(public_key, EllipticCurvePublicKey): return f"EC {public_key.curve.name}" if isinstance(public_key, Ed25519PublicKey): return "ED25519" return type(public_key) def format_key_type(cert: x509.Certificate) -> str: try: key_type = get_key_type(cert.public_key()) except UnsupportedAlgorithm: key_type = "unsupported key type" return key_type def format_ext_key_usage(value: x509.ExtendedKeyUsage): usages = [] labels = { x509.ExtendedKeyUsageOID.ANY_EXTENDED_KEY_USAGE: "any", x509.ExtendedKeyUsageOID.SERVER_AUTH: "server auth", x509.ExtendedKeyUsageOID.CLIENT_AUTH: "client auth", x509.ExtendedKeyUsageOID.CODE_SIGNING: "code signing", x509.ExtendedKeyUsageOID.EMAIL_PROTECTION: "email protection", x509.ExtendedKeyUsageOID.IPSEC_IKE: "IPSEC IKE", x509.ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC: "Kerberos PKINIT KDC", x509.ExtendedKeyUsageOID.OCSP_SIGNING: "OCSP signing", x509.ExtendedKeyUsageOID.SMARTCARD_LOGON: "SmartCard logon", x509.ExtendedKeyUsageOID.TIME_STAMPING: "timestamping", } for eku in value: if eku in labels: usages.append(labels[eku]) else: usages.append(format_oid_link(eku)) return ", ".join(usages) def format_crl_distribution_points(value: x509.CRLDistributionPoints): dps = [] for dp in value: if dp.full_name is not None: for item in dp.full_name: if isinstance(item, x509.UniformResourceIdentifier): dps.append(item.value) else: dps.append(item) else: dps.append(f"unsupported {dp}") return ", ".join(dps) def format_access_description(ai: x509.AccessDescription): if ai.access_method == x509.AuthorityInformationAccessOID.OCSP: prefix = "OCSP:" elif ai.access_method == x509.AuthorityInformationAccessOID.CA_ISSUERS: prefix = "CA Issuers:" else: prefix = format_oid_link(ai.access_method) if isinstance(ai.access_location, x509.UniformResourceIdentifier): return f"{prefix} {ai.access_location.value}" return f"{prefix} {ai.access_location}" def format_authority_information_access(value: x509.AuthorityInformationAccess): access_infos = [] for ai in value: access_infos.append(format_access_description(ai)) return ", ".join(access_infos) def format_policy_information(pi: x509.PolicyInformation): if pi.policy_identifier == x509.ObjectIdentifier("2.5.29.32.0"): prefix = "anyPolicy" else: prefix = format_oid_link(pi.policy_identifier) qualifiers = [] if pi.policy_qualifiers is None: return prefix for pq in pi.policy_qualifiers: if isinstance(pq, str): qualifiers.append(pq) elif isinstance(pq, x509.UserNotice): qualifiers.append(pq) else: qualifiers.append(f"unknown policy information {pq}") value = ", ".join(qualifiers) return f"{prefix}: {value}" def format_certificate_policies(value: x509.CertificatePolicies): policies = [] for pi in value: policies.append(format_policy_information(pi)) return ", ".join(policies) def format_subject_alternative_names(value: x509.SubjectAlternativeName) -> str: alt_names = [] for name_type in ( x509.DNSName, x509.RFC822Name, x509.DirectoryName, x509.OtherName, x509.UniformResourceIdentifier, ): alt_names.extend(value.get_values_for_type(name_type)) if not alt_names: raise Exception(f"unhandled {value}") return ", ".join(alt_names) def format_hash_algorithm(cert: x509.Certificate) -> str: try: return cert.signature_hash_algorithm.name except UnsupportedAlgorithm: return f"unsupported hash algorithm {format_oid_link(cert.signature_algorithm_oid)}" def extension_label(oid: x509.ObjectIdentifier) -> str: labels = { x509.OID_BASIC_CONSTRAINTS: "Basic constraints", x509.OID_KEY_USAGE: "Key usage", x509.OID_EXTENDED_KEY_USAGE: "Extended key usage", x509.OID_AUTHORITY_KEY_IDENTIFIER: "Authority key identifier", x509.OID_SUBJECT_KEY_IDENTIFIER: "Subject key identifier", x509.OID_AUTHORITY_INFORMATION_ACCESS: "Authority information access", x509.OID_CRL_DISTRIBUTION_POINTS: "CRL distribution points", x509.OID_CERTIFICATE_POLICIES: "Certificate policies", } if oid in labels: return labels[oid] return format_oid_link(oid) def format_basic_constraints(bc: x509.ExtensionType) -> str: if isinstance(bc, x509.BasicConstraints): parts = [] if bc.path_length is not None: parts.append(f"path length: {bc.path_length}") parts.append(f"CA: {bc.ca}") return ", ".join(parts) return f"unexpected extension {bc.oid}" def format_subject_key_identifier(value: x509.ExtensionType) -> str: if not isinstance(value, x509.SubjectKeyIdentifier): raise WrongExtensionType(value.oid) parts = [] if value.key_identifier: parts.append(f"key identifier: {value.key_identifier.hex(':')}") if value.digest: parts.append(f"digest: {value.digest.hex(':')}") return ", ".join(parts) def format_authority_key_identifier(value: x509.ExtensionType) -> str: if not isinstance(value, x509.AuthorityKeyIdentifier): raise WrongExtensionType(value.oid) if value.key_identifier is not None: return f"identifier: {value.key_identifier.hex(':')}" return ( f" issuer {value.authority_cert_issuer[0].value.rfc4514_string()}," f" serial {value.authority_cert_serial_number}" ) def format_extension(extensions: x509.Extensions, oid: x509.ObjectIdentifier) -> str: try: ext = extensions.get_extension_for_oid(oid) except x509.ExtensionNotFound: return "-" formatters = { x509.OID_BASIC_CONSTRAINTS: format_basic_constraints, x509.OID_KEY_USAGE: format_key_usage, x509.OID_EXTENDED_KEY_USAGE: format_ext_key_usage, x509.OID_AUTHORITY_KEY_IDENTIFIER: format_authority_key_identifier, x509.OID_SUBJECT_KEY_IDENTIFIER: format_subject_key_identifier, x509.OID_AUTHORITY_INFORMATION_ACCESS: format_authority_information_access, x509.OID_CRL_DISTRIBUTION_POINTS: format_crl_distribution_points, x509.OID_CERTIFICATE_POLICIES: format_certificate_policies, x509.OID_SUBJECT_ALTERNATIVE_NAME: format_subject_alternative_names, } if oid in formatters: return formatters[oid](ext.value) + (" (critical)" if ext.critical else "") return str(ext.value) def table_line(key, values): print(f"| {key} |", " | ".join(values), "|")