import os
import zipfile
from collections.abc import Iterable
from datetime import datetime
from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric import padding
from pytz import utc
current_dir = os.path.dirname(__file__)
[docs]
class CertificateStore:
def __init__(self, trusted_certificates: Iterable):
self._trusted_certificates = {
cert.subject.public_bytes(): cert for cert in trusted_certificates
}
[docs]
def issuer_certificate(self, cert):
return self._trusted_certificates[cert.issuer.public_bytes()]
[docs]
def verify_certificate(self, issued_cert, at: datetime):
"""Verifies issued_cert.
Args:
issued_cert: The issued certificate.
Returns:
True if certificate was issued by a trusted certificate
:param issued_cert: issued certificate to validate
:param at: date at witch to do the validation
"""
if at.tzinfo:
at = at.astimezone(tz=utc).replace(tzinfo=None)
def validate_date(cert):
if not cert.not_valid_before <= at <= cert.not_valid_after:
raise ValueError("Date Not Valid")
try:
validate_date(issued_cert)
signing_cert = self.issuer_certificate(issued_cert)
validate_date(signing_cert)
signing_cert.public_key().verify(
signature=issued_cert.signature,
data=issued_cert.tbs_certificate_bytes,
padding=padding.PKCS1v15(),
algorithm=issued_cert.signature_hash_algorithm
)
# Assume the parent certificates have been already validated, only checking dates
while True:
parent_cert = self.issuer_certificate(signing_cert)
if parent_cert == signing_cert:
return True
validate_date(parent_cert)
signing_cert = parent_cert
except (IndexError, InvalidSignature, ValueError) as ex:
return False
[docs]
@classmethod
def create(cls, certs_zip):
"""
Creates a Certificate Store from the certificates in a zip file
:param certs_zip:
:return: CertificateStore
"""
with zipfile.ZipFile(certs_zip, "r") as zf:
return cls(
trusted_certificates=
(x509.load_der_x509_certificate(zf.read(fileinfo)) for fileinfo in zf.infolist())
)