from typing import List, Optional from cryptography.hazmat.backends import default_backend from cryptography.x509 import load_der_x509_certificate from OpenSSL.crypto import X509, X509Store, X509StoreContext, X509StoreContextError from .exceptions import InvalidCertificateChain from .pem_cert_bytes_to_open_ssl_x509 import pem_cert_bytes_to_open_ssl_x509 def validate_certificate_chain( *, x5c: List[bytes], pem_root_certs_bytes: Optional[List[bytes]] = None, ) -> bool: """Validate that the certificates in x5c chain back to a known root certificate Args: `x5c`: X5C certificates from a registration response's attestation statement (optional) `pem_root_certs_bytes`: Any additional (PEM-formatted) root certificates that may complete the certificate chain Raises: `helpers.exceptions.InvalidCertificateChain` if chain cannot be validated """ if pem_root_certs_bytes is None or len(pem_root_certs_bytes) < 1: # We have no root certs to chain back to, so just pass on validation return True # Make sure we have at least one certificate to try and link back to a root cert if len(x5c) < 1: raise InvalidCertificateChain("x5c was empty") # Prepare leaf cert try: leaf_cert_bytes = x5c[0] leaf_cert_crypto = load_der_x509_certificate(leaf_cert_bytes, default_backend()) leaf_cert = X509().from_cryptography(leaf_cert_crypto) except Exception as err: raise InvalidCertificateChain(f"Could not prepare leaf cert: {err}") # Prepare any intermediate certs try: # May be an empty array, that's fine intermediate_certs_bytes = x5c[1:] intermediate_certs_crypto = [ load_der_x509_certificate(cert, default_backend()) for cert in intermediate_certs_bytes ] intermediate_certs = [X509().from_cryptography(cert) for cert in intermediate_certs_crypto] except Exception as err: raise InvalidCertificateChain(f"Could not prepare intermediate certs: {err}") # Prepare a collection of possible root certificates root_certs_store = X509Store() try: for cert in pem_root_certs_bytes: root_certs_store.add_cert(pem_cert_bytes_to_open_ssl_x509(cert)) except Exception as err: raise InvalidCertificateChain(f"Could not prepare root certs: {err}") # Load certs into a "context" for validation context = X509StoreContext( store=root_certs_store, certificate=leaf_cert, chain=intermediate_certs, ) # Validate the chain (will raise if it can't) try: context.verify_certificate() except X509StoreContextError: raise InvalidCertificateChain("Certificate chain could not be validated") return True