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

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), "|")