You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
8.6 KiB
Python
269 lines
8.6 KiB
Python
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), "|")
|