1- from OpenSSL import crypto
1+ #!/usr/bin/env python3
2+ """
3+ Module: digitalpy.core.security.crl_regen
4+ Description: Regenerate a Certificate Revocation List (CRL) and optionally append it to the CA bundle.
5+ Version: 1.1.1
6+ """
7+
28import argparse
9+ import json
10+ import sys
11+ from datetime import datetime , timedelta
12+
13+ from cryptography import x509
14+ from cryptography .hazmat .primitives import hashes , serialization
15+ from cryptography .hazmat .primitives .serialization import load_pem_private_key
316
4- class CertficateRevocationListController :
5- def __init__ (self , ca_pem_path : str , ca_key_path : str , crl_file_path : str ):
6- """CertficateRevocationListController constructor
17+
18+ class CertificateRevocationListController :
19+ def __init__ (self , ca_pem_path : str , ca_key_path : str , crl_path : str , validity_days : int ):
20+ """
21+ Controller for regenerating a Certificate Revocation List (CRL).
722
823 Args:
9- ca_pem_path (str): path to the ca pem file of the crl
10- ca_key_path (str): path to the ca key file of the crl
11- crl_file_path (str): path to the crl file
24+ ca_pem_path: Path to the CA certificate PEM file
25+ ca_key_path: Path to the CA private key PEM file
26+ crl_path: Output path for the CRL (PEM or JSON)
27+ validity_days: Number of days until the next CRL update
1228 """
1329 self .ca_pem_path = ca_pem_path
1430 self .ca_key_path = ca_key_path
15- self .ca_key = crypto .load_privatekey (crypto .FILETYPE_PEM , open (ca_key_path ).read ())
16- self .ca_pem = crypto .load_certificate (crypto .FILETYPE_PEM , open (ca_pem_path , 'rb' ).read ())
17- self .crl_file_path = crl_file_path
31+ self .crl_path = crl_path
32+ self .validity_days = validity_days
33+
34+ # Load CA certificate
35+ with open (ca_pem_path , "rb" ) as f :
36+ self .ca_cert = x509 .load_pem_x509_certificate (f .read ())
37+
38+ # Load CA private key (unencrypted PEM)
39+ with open (ca_key_path , "rb" ) as f :
40+ self .ca_key = load_pem_private_key (f .read (), password = None )
1841
1942 def regenerate_crl (self ):
20- """regenerate the configured crl"""
21-
22- # instantiate CRL object
23- crl = crypto .CRL ()
24- crl .sign (self .ca_pem , self .ca_key , b"sha256" )
25-
26- # open CRL file and write CRL contents
27- with open (self .crl_file_path , 'wb' ) as f :
28- f .write (crl .export (cert = self .ca_pem , key = self .ca_key , digest = b"sha256" ))
29-
30-
31- delete = 0
32- # read the contents of the ca pem
33- with open (self .ca_pem_path , "r" ) as f :
34- lines = f .readlines ()
35- # re-write the contents of the ca pem until x509 crl is reached
36- with open (self .ca_pem_path , "w" ) as f :
37- for line in lines :
38- if delete :
39- continue
40- elif line .strip ("\n " ) != "-----BEGIN X509 CRL-----" :
41- f .write (line )
42- else :
43- delete = 1
44-
45- # add the updated x509 crl to the end of the ca pem
46- with open (self .ca_pem_path , "ab" ) as f :
47- f .write (crl .export (cert = self .ca_pem , key = self .ca_key , digest = b"sha256" ))
43+ now = datetime .utcnow ()
44+ next_update = now + timedelta (days = self .validity_days )
4845
49- if __name__ == "__main__" :
50- parser = argparse .ArgumentParser (description = 'command line arguments' )
51- parser .add_argument ('--ca-pem-path' , dest = 'ca_pem_path' , type = str , help = 'path to the certificate authority pem' )
52- parser .add_argument ('--ca-key-path' , dest = 'ca_key_path' , type = str , help = 'path to the certificate authority key' )
53- parser .add_argument ('--crl-path' , dest = 'crl_path' , type = str , help = 'Path to the CRL' )
46+ # Build an empty CRL (add revoked certs here if needed)
47+ builder = (
48+ x509 .CertificateRevocationListBuilder ()
49+ .issuer_name (self .ca_cert .subject )
50+ .last_update (now )
51+ .next_update (next_update )
52+ )
53+ crl_obj = builder .sign (private_key = self .ca_key , algorithm = hashes .SHA256 ())
54+
55+ # If JSON output requested
56+ if self .crl_path .lower ().endswith (".json" ):
57+ out = {
58+ "issuer" : self .ca_cert .subject .rfc4514_string (),
59+ "last_update" : now .isoformat () + "Z" ,
60+ "next_update" : next_update .isoformat () + "Z" ,
61+ "revoked" : [
62+ {
63+ "serial_number" : rc .serial_number ,
64+ "revocation_date" : rc .revocation_date .isoformat () + "Z" ,
65+ }
66+ for rc in crl_obj
67+ ],
68+ }
69+ with open (self .crl_path , "w" ) as f :
70+ json .dump (out , f , indent = 2 )
71+ else :
72+ # PEM output
73+ data = crl_obj .public_bytes (serialization .Encoding .PEM )
74+ with open (self .crl_path , "wb" ) as f :
75+ f .write (data )
76+ # Append to CA bundle
77+ with open (self .ca_pem_path , "ab" ) as f :
78+ f .write (data )
79+
80+ # Print results
81+ print (f"✅ CRL regenerated: { self .crl_path } " )
82+ print (f" Last Update : { now .isoformat ()} Z" )
83+ print (f" Next Update : { next_update .isoformat ()} Z" )
84+
85+
86+ def main ():
87+ # Header output
88+ print ("digitalpy.core.security.crl_regen v1.1.0 - Regenerate a CRL and optionally append to the CA bundle." )
89+
90+ parser = argparse .ArgumentParser (
91+ description = "Regenerate a CRL and optionally append to your CA bundle."
92+ )
93+ parser .add_argument (
94+ "--ca-pem-path" , required = True ,
95+ help = "Path to the CA certificate PEM file"
96+ )
97+ parser .add_argument (
98+ "--ca-key-path" , required = True ,
99+ help = "Path to the CA private key PEM file"
100+ )
101+ parser .add_argument (
102+ "--crl-path" , required = True ,
103+ help = "Output path for the CRL (supports .pem or .json extensions)"
104+ )
105+ parser .add_argument (
106+ "--validity-days" , "--validity_days" , type = int , default = 365 ,
107+ dest = "validity_days" ,
108+ help = "Days until next_update (default: 365)"
109+ )
54110
55111 args = parser .parse_args ()
56-
57- CertficateRevocationListController (args .ca_pem_path , args .ca_key_path , args .crl_path ).regenerate_crl ()
112+
113+ try :
114+ ctl = CertificateRevocationListController (
115+ args .ca_pem_path ,
116+ args .ca_key_path ,
117+ args .crl_path ,
118+ args .validity_days ,
119+ )
120+ ctl .regenerate_crl ()
121+ except Exception as e :
122+ print (f"❌ Error generating CRL: { e } " , file = sys .stderr )
123+ sys .exit (1 )
124+
125+
126+ if __name__ == "__main__" :
127+ main ()
0 commit comments