from cryptography import x509
from cryptography.x509.oid import ExtensionOID, AuthorityInformationAccessOID, NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import Encoding
import binascii
try:
unicode
except NameError:
unicode = str
[docs]class Cert(object):
"""The :class:`Cert <Cert>` object, which is a convenience
wrapper for interacting with the underlying :py:class:`cryptography.x509.Certificate` object
Args:
x509_obj (:py:class:`cryptography.x509.Certificate`): An instance of :py:class:`cryptography.x509.Certificate`
Raises:
ValueError: given type is not an instance of :py:class:`cryptography.x509.Certificate`
"""
_x509 = None
def __init__(self, x509_obj):
if not isinstance(x509_obj, x509.Certificate):
raise TypeError("Argument must be a x509 Certificate object")
self._x509 = x509_obj
def __repr__(self):
return '<Cert common_name="{0}" subject="{1}" issuer="{2}">'.format(
self.common_name, self.subject, self.issuer
)
@property
def issuer(self):
""" str: RFC4515 formatted string of the issuer field from the underlying :py:class:`cryptography.x509.Certificate` object"""
return self._x509.issuer.rfc4514_string()
@property
def subject(self):
""" str: RFC4515 formatted string of the subject field from the underlying :py:class:`cryptography.x509.Certificate` object"""
return self._x509.subject.rfc4514_string()
@property
def common_name(self):
""" str: Extracted common name from the underlying :py:class:`cryptography.x509.Certificate` object"""
for attr in self._x509.subject.get_attributes_for_oid(NameOID.COMMON_NAME):
return attr.value
@property
def subject_alternative_names(self):
""" list(str): Extracted x509 Extensions from the :py:class:`cryptography.x509.Certificate` object"""
names = []
try:
ext = self._x509.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
)
names = ext.value.get_values_for_type(x509.DNSName)
except x509.extensions.ExtensionNotFound:
pass
return names
@property
def is_ca(self):
""" bool: Checks whether the Certificate Authority bit has been set"""
is_ca = False
try:
ext = self._x509.extensions.get_extension_for_oid(
ExtensionOID.BASIC_CONSTRAINTS
)
is_ca = True if ext.value.ca else False
except x509.extensions.ExtensionNotFound:
pass
return is_ca
@property
def is_root(self):
""" bool: Checks whether the certificate is a root"""
return self.subject == self.issuer
@property
def serial(self):
""" str: gets the serial from the underlying :py:class:`cryptography.x509.Certificate` object"""
return self._x509.serial_number
@property
def signature_hash_algorithm(self):
""" str: gets the signature hashing algorithm name from the underlying :py:class:`cryptography.x509.Certificate` object"""
return self._x509.signature_hash_algorithm.name
@property
def not_valid_before(self):
""" :py:class:`datetime.datetime`: from the underlying :py:class:`cryptography.x509.Certificate` object"""
return self._x509.not_valid_before
@property
def not_valid_after(self):
""" :py:class:`datetime.datetime`: from the underlying :py:class:`cryptography.x509.Certificate` object"""
return self._x509.not_valid_after
@property
def fingerprint(self):
""" str: ascii encoded sha256 fingerprint by calling :py:func:`get_fingerprint`"""
return self.get_fingerprint(hashes.SHA256)
@property
def ca_issuer_access_location(self):
""" str: a URL that contains the CA issuer certificate"""
access_location = None
try:
aias = self._x509.extensions.get_extension_for_oid(
ExtensionOID.AUTHORITY_INFORMATION_ACCESS
)
for aia in aias.value:
if AuthorityInformationAccessOID.CA_ISSUERS == aia.access_method:
access_location = aia.access_location.value
except x509.extensions.ExtensionNotFound:
pass
return access_location
[docs] def get_fingerprint(self, _hash=hashes.SHA256):
""" Get fingerprint of the certificate
Args:
_hash (:py:class:`cryptography.hazmat.primitives.hashes`, optional): Hasher to use. Defaults to hashes.SHA256.
Returns:
str: ascii formatted fingerprint
"""
binary = self._x509.fingerprint(_hash())
txt = binascii.hexlify(binary).decode("ascii")
return txt
[docs] def export(self, encoding=Encoding.PEM):
"""Export the :py:class:`cryptography.x509.Certificate` object"
Args:
encoding (:py:class:`cryptography.hazmat.primitives.serialization.Encoding`, optional): The output format. Defaults to Encoding.PEM.
Returns:
str: ascii formatted
"""
encoded = unicode(self._x509.public_bytes(encoding), "ascii")
return encoded
[docs]class CertificateChain(object):
""" Creates an iterable that contains a list of :class:`Cert <Cert>` objects.
Args:
chain (:py:class:`CertificateChain <CertificateChain>`, optional): Create a new CertificateChain based on this chain. Defaults to None.
"""
_chain = None
def __init__(self, chain=None):
self._chain = [] if not chain else chain
def __iter__(self):
for cert in self._chain:
yield cert
def __iadd__(self, x509_obj):
self._chain.append(x509_obj)
return self
def __len__(self):
return self._chain.__len__()
@property
def leaf(self):
""" First :class:`Cert <Cert>`: in the chain. Also known as the 'leaf'"""
return self._chain[0]
@property
def intermediates(self):
""" A new :class:`CertificateChain <CertificateChain>` object with only intermediate certificates """
new_chain = [x for x in self._chain if (x.is_ca and not x.is_root)]
return self.__class__(chain=new_chain)