Source code for pki.util.crl
"""Utilities for Certificate Revocation List (CRL) generation and management."""
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, get_args
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from django.utils import timezone
from trustpoint_core.crypto_types import AllowedCertSignHashAlgos
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.x509 import CertificateRevocationList
from pki.models.ca import CaModel
[docs]
def generate_empty_crl(
ca_cert: x509.Certificate,
private_key: rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey,
hash_algorithm: hashes.HashAlgorithm | None = None,
crl_validity_hours: int = 2400,
crl_number: int = 1,
) -> str:
"""Generate an empty CRL for a CA.
Args:
ca_cert: The CA certificate.
private_key: The private key of the CA.
hash_algorithm: The hash algorithm to use. Defaults to SHA256.
crl_validity_hours: Validity period in hours.
crl_number: The CRL number to use.
Returns:
str: The CRL in PEM format.
"""
if hash_algorithm is None:
hash_algorithm = hashes.SHA256()
crl_issued_at = datetime.datetime.now(datetime.UTC)
ca_subject = ca_cert.subject
crl_builder = x509.CertificateRevocationListBuilder(
issuer_name=ca_subject,
last_update=crl_issued_at,
next_update=crl_issued_at + datetime.timedelta(hours=crl_validity_hours),
)
crl_builder = crl_builder.add_extension(x509.CRLNumber(crl_number), critical=False)
crl = crl_builder.sign(private_key=private_key, algorithm=hash_algorithm) # type: ignore[arg-type]
return crl.public_bytes(encoding=serialization.Encoding.PEM).decode()
[docs]
def generate_crl_with_revoked_certs(
issuing_ca: CaModel,
crl_validity_hours: int = 24,
) -> CertificateRevocationList:
"""Generate a CRL with revoked certificates for an issuing CA.
Args:
issuing_ca: The issuing CA model instance.
crl_validity_hours: Hours until the next CRL update (nextUpdate field).
Returns:
CertificateRevocationList: The generated CRL.
Raises:
AttributeError: If called on a keyless CA.
ValueError: If credential is None for issuing CA.
TypeError: If hash algorithm is not allowed.
"""
if issuing_ca.is_keyless_ca:
msg = 'Keyless CAs cannot issue CRLs (no private key available).'
raise AttributeError(msg)
if issuing_ca.credential is None:
msg = 'Credential is None for issuing CA'
raise ValueError(msg)
crl_issued_at = timezone.now()
ca_subject = issuing_ca.credential.certificate_or_error.get_certificate_serializer().as_crypto().subject
latest_crl = issuing_ca.get_latest_crl()
next_crl_number = (latest_crl.crl_number + 1) if latest_crl and latest_crl.crl_number else 1
crl_builder = x509.CertificateRevocationListBuilder(
issuer_name=ca_subject,
last_update=crl_issued_at,
next_update=crl_issued_at + datetime.timedelta(hours=crl_validity_hours),
)
crl_builder = crl_builder.add_extension(x509.CRLNumber(next_crl_number), critical=False)
crl_certificates = issuing_ca.revoked_certificates.all()
for cert in crl_certificates:
revoked_cert = (
x509.RevokedCertificateBuilder()
.serial_number(int(cert.certificate.serial_number, 16))
.revocation_date(cert.revoked_at)
.add_extension(x509.CRLReason(x509.ReasonFlags(cert.revocation_reason)), critical=False)
.build()
)
crl_builder = crl_builder.add_revoked_certificate(revoked_cert)
hash_algorithm = issuing_ca.credential.hash_algorithm
if hash_algorithm is not None and not isinstance(hash_algorithm, get_args(AllowedCertSignHashAlgos)):
err_msg = f'CRL: Hash algo must be one of {AllowedCertSignHashAlgos}, but found {type(hash_algorithm)}'
raise TypeError(err_msg)
priv_k = issuing_ca.credential.get_private_key_serializer().as_crypto()
return crl_builder.sign(private_key=priv_k, algorithm=hash_algorithm)