diff --git a/common.py b/common.py index bc70493..8e04215 100644 --- a/common.py +++ b/common.py @@ -22,19 +22,19 @@ config.read('samba-pki-tools.ini') class Printing(): def information(string): - print(Fore.CYAN + string) + print(Fore.CYAN + '[i] ' + string) return print(Style.RESET_ALL) def success(string): - print(Fore.GREEN + string) + print(Fore.GREEN + '[+] '+ string) return print(Style.RESET_ALL) def warning(string): - print(Fore.YELLOW + string) + print(Fore.YELLOW + '[!] ' + string) return print(Style.RESET_ALL) def error(string): - print(Fore.RED + string) + print(Fore.RED + '[-] ' + string) return print(Style.RESET_ALL) class TisPKI: @@ -107,6 +107,10 @@ class TisPKI: name = name.replace(' ','_') return os.path.join(TisPKI.intermediate_keyout_path(name),f'{name}_intermediate_ca.key') + def intermediate_ca_crlfile(name): + name = name.replace(' ','_') + return os.path.join(TisPKI.intermediate_crl_path(name),f'{name}.crl') + def check_directories(path,verbose=False): diff --git a/dc_module.py b/dc_module.py index 94cec46..df76596 100644 --- a/dc_module.py +++ b/dc_module.py @@ -13,6 +13,7 @@ # Maange DC Certificates from common import Printing, TisPKI, check_directories, config +from intermediate_module import generate_intermediate_crl import subprocess import jinja2 @@ -24,7 +25,7 @@ import shutil import time -def generate_dc_certificate(dc_name=None, ca_name=None, force=False, verbose=False): +def generate_dc_certificate(dc_name=None, ca_name=None, verbose=False): if ca_name != "Root": dc_certfile = os.path.join(TisPKI.intermediate_cert_path(ca_name),f'{dc_name}.crt') @@ -50,13 +51,11 @@ def generate_dc_certificate(dc_name=None, ca_name=None, force=False, verbose=Fal if not os.path.isfile(dc_certfile) and not os.path.isfile(dc_keyfile): Printing.information(f'Generate certificate for {dc_name}') - dc_guid = subprocess.run('/bin/bash get_guid.sh',shell=True, check=True, executable='/bin/bash') - if dc_guid.returncode != 0: - Printing.error('Unable to find dc_guid') + dc_name_guid = dc_name.split('.')[0] + dc_guid = str(subprocess.check_output(f'/bin/bash get_guid.sh {dc_name_guid}',shell=True).decode("utf-8")).strip() + if str(dc_guid) == '': + Printing.error('Unable to find dc_guid. Please verify the DC FQDN.') sys.exit(1) - else: - dc_name_guid = dc_name.split('.')[0] - dc_guid = str(subprocess.check_output(f'/bin/bash get_guid.sh {dc_name_guid}',shell=True).decode("utf-8")).strip() if verbose: Printing.information(f'{dc_name} GUID is : ' + str(dc_guid).strip()) @@ -70,6 +69,7 @@ def generate_dc_certificate(dc_name=None, ca_name=None, force=False, verbose=Fal 'crl_uri': crl_uri, 'pki_dir': pki_dir, 'default_cert_duration': config.get('openssl_config','default_cert_duration'), + 'default_crl_duration': config.get('openssl_config','default_crl_duration'), 'country': config.get('openssl_config','country'), 'state': config.get('openssl_config','state'), 'city': config.get('openssl_config','city'), @@ -89,7 +89,7 @@ def generate_dc_certificate(dc_name=None, ca_name=None, force=False, verbose=Fal Printing.information(f'{dc_name} OpenSSL configfile is correctly generated !') Printing.information(f'Generate private key and CSR for {dc_name}') - gen_dc_key = subprocess.run(f"openssl req -new -addext 'subjectAltName = email:copy' -newkey rsa:4096 -keyout {dc_keyfile} \ + gen_dc_key = subprocess.run(f"openssl req -new -addext 'subjectAltName = email:copy' -newkey rsa:4096 -nodes -keyout {dc_keyfile} \ -out {dc_csrfile} -config {dc_openssl_configfile}" , shell=True, check=False, executable='/bin/bash') if gen_dc_key.returncode == 0: @@ -109,10 +109,40 @@ def generate_dc_certificate(dc_name=None, ca_name=None, force=False, verbose=Fal retry = input('If you want to retry, press Y : ') if retry == "y" or retry == 'Y': os.remove(dc_keyfile) - generate_dc_certificate(dc_name, ca_name, force, verbose) + generate_dc_certificate(dc_name, ca_name, verbose) else: Printing.error('Error on generating Domain Controler private key') retry = input('If you want to retry, press Y : ') if retry == "y" or retry == 'Y': os.remove(dc_keyfile) - generate_dc_certificate(dc_name, ca_name, force, verbose) \ No newline at end of file + generate_dc_certificate(dc_name, ca_name, verbose) + + +def revoke_dc_certificate(dc_name=None, ca_name=None, verbose=False): + Printing.information(f'Revoke {dc_name} certificate') + + if ca_name != "Root": + dc_keyfile = os.path.join(TisPKI.intermediate_keyout_path(ca_name),f'{dc_name}.key') + dc_certfile = os.path.join(TisPKI.intermediate_cert_path(ca_name),f'{dc_name}.crt') + dc_openssl_configfile = os.path.join(TisPKI.intermediate_config_path(ca_name),f'openssl_{dc_name}.ini') + else: + dc_keyfile = os.path.join(TisPKI.root_keyout_path(),f'{dc_name}.key') + dc_certfile = os.path.join(TisPKI.root_cert_path(),f'{dc_name}.crt') + dc_openssl_configfile = os.path.join(TisPKI.root_config_path(),f'openssl_{dc_name}.ini') + + revoke_dc = input(f'Are you realy sure to revoke {dc_name} certificate ? [y/N]'.strip() or 'y') + if revoke_dc.lower() == "y": + Printing.information(f'OK, revoking {dc_name} certificate !') + revoke_cmd = subprocess.run(f"/usr/bin/openssl ca -config {dc_openssl_configfile} -revoke {dc_certfile}", + shell=True, check=False, executable='/bin/bash') + + if revoke_cmd.returncode == 0: + Printing.information(f'Regenerate {ca_name} CRL') + generate_intermediate_crl(dc_openssl_configfile, ca_name, verbose) + else: + Printing.error('Unable to revoke CA Intermediate certificate') + + remove_files = input('Would you like to remove private key and certificate ? [Y/n]' or "y") + if remove_files.lower() == "y": + os.remove(dc_certfile) + os.remove(dc_keyfile) diff --git a/intermediate_module.py b/intermediate_module.py index b2bc569..6faf43b 100644 --- a/intermediate_module.py +++ b/intermediate_module.py @@ -13,6 +13,7 @@ # Maange Intermediate CA from common import Printing, TisPKI, check_directories, config +from root_module import generate_root_crl import subprocess import jinja2 @@ -22,9 +23,9 @@ import sys from colorama import Fore, Style import shutil import time +import glob - -def create_openssl_intermediate(name, force=False,verbose=False): +def create_openssl_intermediate(name, force=False, verbose=False): ## if force: ## Printing.error("Do you realy want to remove ALL you PKI ? This will destroy ALL YOUR CERTIFICATES AND PRIVATE KEY") @@ -113,4 +114,74 @@ def create_openssl_intermediate(name, force=False,verbose=False): os.remove(TisPKI.intermediate_ca_keyfile(name)) create_openssl_intermediate(name, force, verbose) else: - Printing.warning('Intermediate CA private key and certificate already exist. Skip.') \ No newline at end of file + Printing.warning('Intermediate CA private key and certificate already exist. Skip.') + + +def generate_intermediate_crl(configfile=None, ca_name=None, verbose=False): + Printing.information(f'Generate CRL for {ca_name} intermediate CA') + + if configfile==None: + for file in glob.glob(os.path.join(TisPKI.intermediate_config_path(ca_name),'openssl_*.ini')): + configfile = file + break + + gen_crl = subprocess.run(f'openssl ca -config {configfile} -gencrl -out {TisPKI.intermediate_ca_crlfile(ca_name)}',shell=True) + if gen_crl.returncode == 0: + if verbose: + subprocess.run(f'openssl crl -in {TisPKI.intermediate_ca_crlfile(ca_name)} -text') + Printing.success(f'CRL successfuly generated in : {TisPKI.intermediate_ca_crlfile(ca_name)}') + else: + Printing.error('Unable to generate CRL') + + +def list_ca_certificates(ca_name=None): + Printing.information(f'List certificates issued of {ca_name}') + certs_list = [] + try: + with open(os.path.join('/opt',TisPKI.pki_intermediate_dir(ca_name),'index.txt'),'r') as index: + for line in index.readlines(): + if line.split('\t')[0] == 'R': + status = 'Revoked' + elif line.split('\t')[0] == 'V': + status = 'Valid ' + elif line.split('\t')[0] == 'E': + status = 'Expired' + else: + status = line.split('\t')[0] + + serial_number = line.split('\t')[3] + + exp_date = line.split('\t')[1] + expiration_date = '20' + exp_date[0:2] + '/' + exp_date[2:4] + '/' + exp_date[4:6] + ' ' + exp_date[6:8] + ':' + exp_date[8:10] +':'+ exp_date[10:12] + + rev_date = line.split('\t')[2] + if rev_date != '': + revocation_date = '20' + rev_date[0:2] + '/' + rev_date[2:4] + '/' + rev_date[4:6] + ' ' + rev_date[6:8] + ':' + rev_date[8:10] +':'+ rev_date[10:12] + else: + revocation_date = ' ' + + cn = line.split('\t')[5].split('CN')[1].replace('=','').replace('\n','') + cn_len = 20 + if len(cn) < cn_len: + diff = cn_len - len(cn) + space = '' + for i in range(0, diff): + space = space + ' ' + commonName = cn + space + commonName_full = '' + else: + commonName = cn[0:20] + commonName_full = cn + + certs_list.append(commonName + ' | ' + status + ' | ' + serial_number + ' | ' + expiration_date + ' | ' + revocation_date + ' | ' + commonName_full) + + print(' CommonName | Status | Serial Number | Expiration date | Revocation date |') + print('---------------------|---------|------------------------------------------|---------------------|---------------------|') + for cert in certs_list: + print(cert) + except Exception as e: + if "Errno 2" in str(e): + Printing.error('Unable to find CA') + else: + Printing.error('Error when list certificates %s' % e) + diff --git a/manage_pki.py b/manage_pki.py index ffb274b..0244fbf 100644 --- a/manage_pki.py +++ b/manage_pki.py @@ -26,29 +26,43 @@ def main(): root_group = parser.add_argument_group('Root CA options') root_group.add_argument('--create-root', dest="initialize", action="store_true", help="Create PKI") - root_group.add_argument('--full-create', dest="full_initialize", action="store_true", help="Create Root CA, intermediate CA and DC certificate. Use --name and --dc-name") - root_group.add_argument('--root-crl', dest='root_crl', action="store_true", help='Regenerate CRL for root CA') - root_group.add_argument('--root-show-certs','--root-show-certificates', dest="root_certs", help='List all certificates issues of root CA') - root_group.add_argument('--root-revoke', dest='root_revoke', help='Revoke an intermediate CA') + root_group.add_argument('--full-create', dest="full_initialize", action="store_true", + help="Create Root CA, intermediate CA and DC certificate. Use --name and --dc-name") + root_group.add_argument('--root-crl', dest='root_crl', action="store_true", + help='Regenerate CRL for root CA') + root_group.add_argument('--root-show-certs','--root-show-certificates', dest="root_certs", action="store_true", + help='List all certificates issues of root CA') + root_group.add_argument('--root-revoke', dest='root_revoke', action="store_true", + help='Revoke an intermediate CA. Use with --name') intermediate_group = parser.add_argument_group('Intermediate CA options', 'Manage intermediate CA') - intermediate_group.add_argument('--create-intermediate', dest="create_intermediate", action="store_true", help="Create an intermediate CA. Specify name with --name option.") - intermediate_group.add_argument('--name', dest='intermediate_name', help='Specify what intermediate CA to manage') - intermediate_group.add_argument('--crl', dest='intermediate_crl', help='Regenerate CRL for intermediate CA. Specify name with --name option.') - intermediate_group.add_argument('--show-certs', '--show-certificates', dest='intermediate_list', help='List all certificates issues of intermediate CA. Specify name with --name option.') - intermediate_group.add_argument('--revoke-certs', dest='intermediate_revoke', help="Revoke certificate issue of an intermediate CA. Specify intermediate CA name with --name option") + intermediate_group.add_argument('--create-intermediate', dest="create_intermediate", action="store_true", + help="Create an intermediate CA. Specify name with --name option.") + intermediate_group.add_argument('--name', dest='intermediate_name', + help='Specify what intermediate CA to manage') + intermediate_group.add_argument('--crl', dest='intermediate_crl', action="store_true", + help='Regenerate CRL for intermediate CA. Specify name with --name option.') + intermediate_group.add_argument('--show-certs', '--show-certificates', dest='intermediate_list', action="store_true", + help='List all certificates issues of intermediate CA. Specify name with --name option.') + intermediate_group.add_argument('--revoke-certs', dest='intermediate_revoke', action='store_true', + help="Revoke certificate issue of an intermediate CA. Specify intermediate CA name with --name option") dc_cert = parser.add_argument_group('Domain Controler options', 'Manage DC certificates') - dc_cert.add_argument('--dc-cert', dest="dc_cert", action="store_true", help="Create a DC certificate. Specify intermediate CA name with --name option. \ - If you want to use Root ca, set \"Root\" for name value. ") + dc_cert.add_argument('--dc-cert', dest="dc_cert", action="store_true", + help="Create a DC certificate. Specify intermediate CA name with --name option. \ + If you want to use Root ca, set \"Root\" for name value. ") dc_cert.add_argument('--dc-name', dest='dc_name', help='Specity the FQDN of DC.') + dc_cert.add_argument('--revoke-dc-cert', dest="revoke_dc_cert", action="store_true", + help="Revoke a DC certificate. Specify intermediate CA name with --name option.") dangerous_group = parser.add_argument_group('Dangerous options', "Caution: use these options at your own risk.") - dangerous_group.add_argument('-f', '--force', dest="force", action="store_true", help="Force reinitialize PKI. VERY DANGEROUS") + dangerous_group.add_argument('-f', '--force', dest="force", action="store_true", + help="Force reinitialize PKI. VERY DANGEROUS") debug_group = parser.add_argument_group('Debug options') - debug_group.add_argument('-v', '--verbose', dest="verbose", action="store_true", help="Print all command") + debug_group.add_argument('-v', '--verbose', dest="verbose", action="store_true", + help="Print all command") args = parser.parse_args() @@ -59,6 +73,15 @@ def main(): if args.root_crl: generate_root_crl(verbose=args.verbose) + if args.root_revoke: + if not args.intermediate_name: + print('Add --name to revoke intermediate CA') + else: + revoke_intermediate_cert(args.intermediate_name) + + if args.root_certs: + list_root_certificates() + # Intermediate CA if args.create_intermediate: @@ -67,13 +90,32 @@ def main(): else: create_openssl_intermediate(args.intermediate_name,args.force,args.verbose) + if args.intermediate_crl: + if not args.intermediate_name: + print('Add --name to create intermediate CA') + else: + generate_intermediate_crl(ca_name=args.intermediate_name,verbose=args.verbose) + + if args.intermediate_list: + if not args.intermediate_name: + print('Add --name to specify which intermediate CA use.') + else: + list_ca_certificates(ca_name=args.intermediate_name) + + # DC certificates if args.dc_cert: if not args.dc_name or not args.intermediate_name: print('Add --dc-name or --name with this command') else: - generate_dc_certificate(dc_name=args.dc_name, ca_name=args.intermediate_name, force=args.force, verbose=args.verbose) + generate_dc_certificate(dc_name=args.dc_name, ca_name=args.intermediate_name, verbose=args.verbose) + + if args.revoke_dc_cert: + if not args.dc_name or not args.intermediate_name: + print('Add --dc-name or --name with this command') + else: + revoke_dc_certificate(dc_name=args.dc_name, ca_name=args.intermediate_name, verbose=args.verbose) # User certificates @@ -87,7 +129,11 @@ def main(): input("Press Enter to continue...") create_openssl_intermediate(args.intermediate_name,args.force,args.verbose) input("Press Enter to continue...") - generate_dc_certificate(dc_name=args.dc_name, ca_name=args.intermediate_name, force=args.force, verbose=args.verbose) + generate_dc_certificate(dc_name=args.dc_name, ca_name=args.intermediate_name, verbose=args.verbose) + + # If no args + if len(sys.argv) == 1: + parser.print_help() if __name__ == '__main__': diff --git a/root_module.py b/root_module.py index 741f85d..2445e23 100644 --- a/root_module.py +++ b/root_module.py @@ -91,6 +91,24 @@ def create_openssl_config(force=False,verbose=False): Printing.warning('Root CA private key and certificate already exist. Skip.') +def revoke_intermediate_cert(name): + Printing.information(f'Revoke {name} CA Intermediate certificate') + + Printing.error(f'Are you realy sure to revoke {name} CA Intermediate certificate ?') + Printing.error('If you revoke the CA Intermediate, you revoke also all users and servers certificate signed by this CA') + destroy = input('If you are realy sure, please enter : "I want to revoke CA Intermediate" : ') + if destroy == 'I want to revoke CA Intermediate': + Printing.information('OK, too late ! Revoking your CA Intermediate certificate !') + revoke_cmd = subprocess.run(f"/usr/bin/openssl ca -config {os.path.join(TisPKI.root_config_path(),'openssl_root_ca_sign_intermediate.ini')} -revoke {TisPKI.intermediate_ca_certfile(name)}", + shell=True, check=False, executable='/bin/bash') + + if revoke_cmd.returncode == 0: + Printing.information('Regenerate Root CRL') + generate_root_crl() + else: + Printing.error('Unable to revoke CA Intermediate certificate') + + def generate_root_crl(verbose=False): Printing.information('Generate CRL for Root CA') @@ -101,3 +119,51 @@ def generate_root_crl(verbose=False): Printing.success(f'CRL successfuly generated in : {TisPKI.root_ca_crlfile()}') else: Printing.error('Unable to generate CRL') + + +def list_root_certificates(): + Printing.information('List certificates issued of Root CA') + certs_list = [] + with open(os.path.join('/opt','pki','index.txt'),'r') as index: + for line in index.readlines(): + if line.split('\t')[0] == 'R': + status = 'Revoked' + elif line.split('\t')[0] == 'V': + status = 'Valid ' + elif line.split('\t')[0] == 'E': + status = 'Expired' + else: + status = line.split('\t')[0] + + serial_number = line.split('\t')[3] + + exp_date = line.split('\t')[1] + expiration_date = '20' + exp_date[0:2] + '/' + exp_date[2:4] + '/' + exp_date[4:6] + ' ' + exp_date[6:8] + ':' + exp_date[8:10] +':'+ exp_date[10:12] + + rev_date = line.split('\t')[2] + if rev_date != '': + revocation_date = '20' + rev_date[0:2] + '/' + rev_date[2:4] + '/' + rev_date[4:6] + ' ' + rev_date[6:8] + ':' + rev_date[8:10] +':'+ rev_date[10:12] + else: + revocation_date = ' ' + + cn = line.split('\t')[5].split('CN')[1].replace('=','').replace('\n','') + cn_len = 20 + if len(cn) < cn_len: + diff = cn_len - len(cn) + space = '' + for i in range(0, diff): + space = space + ' ' + commonName = cn + space + commonName_full = '' + else: + commonName = cn[0:20] + commonName_full = cn + + certs_list.append(commonName + ' | ' + status + ' | ' + serial_number + ' | ' + expiration_date + ' | ' + revocation_date + ' | ' + commonName_full) + + print(' CommonName | Status | Serial Number | Expiration date | Revocation date |') + print('---------------------|---------|------------------------------------------|---------------------|---------------------|') + for cert in certs_list: + print(cert) + + diff --git a/templates/openssl_server_cert.tmpl b/templates/openssl_server_cert.tmpl index 7ef1a09..da91128 100644 --- a/templates/openssl_server_cert.tmpl +++ b/templates/openssl_server_cert.tmpl @@ -32,7 +32,7 @@ default_md = sha512 name_opt = ca_default cert_opt = ca_default -default_days = {{ default_crl_duration }} +default_days = {{ default_cert_duration }} preserve = no policy = policy_loose