#!/usr/bin/python3 # -*- coding: utf-8 -*- # # create certificate (use for test) # Copyright (C) 2017 AleaJactaEst # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . """ Generate all certificates : .. graphviz:: digraph Certificate { "Root Certificate" -> "Application Certificate"; "Application Certificate" -> "Server Certificate"; "Application Certificate" -> " Client Certificate"; } Detail: * Root certificate : our global certificate * Application certification : our application certificate (use to sign child certificat) * Server certificate : certificate for server side * Client certificate : certificate for client side """ import argparse import logging import logging.config import os import stat import subprocess import sys class Certificate: """ class to generate all certificate :param str openssl: localization openssl program (absolut path) :param str workdir_cert_root: root directory :param str workdir_cert_appli: application directory :param str passroot: root password :param str passappli: application password :param str country_name: your contry name (2 characters) :param str state_or_province_name: The name of a user's state or province. :param str locality_name: Represents the name of a locality, such as a town or city. :param str organization_name: The name of the company or organization. :param str common_name: The name that represents an object. Used to perform searches. :param int size_root: root key size :param int size_appli: application key size :param int size_child: child key size (server & client) """ def __init__(self, openssl, workdir_cert_root, workdir_cert_appli, passroot, passappli, country_name, state_or_province_name, locality_name, organization_name, common_name, size_root, size_appli, size_child): """ Constructor :param str openssl: localization openssl program (absolut path) :param str workdir_cert_root: root directory :param str workdir_cert_appli: application directory :param str passroot: root password :param str passappli: application password :param str country_name: your contry name (2 characters) :param str state_or_province_name: The name of a user's state or province. :param str locality_name: Represents the name of a locality, such as a town or city. :param str organization_name: The name of the company or organization. :param str common_name: The name that represents an object. Used to perform searches. :param int size_root: root key size :param int size_appli: application key size :param int size_child: child key size (server & client) """ self.workdir_cert_root = os.path.abspath(workdir_cert_root) self.workdir_cert_appli = os.path.abspath(workdir_cert_appli) self.openssl = openssl self.passroot = passroot self.passappli = passappli self.country_name = country_name self.state_or_province_name = state_or_province_name self.locality_name = locality_name self.organization_name = organization_name self.common_name = common_name self.configroot = os.path.join(self.workdir_cert_root, 'openssl.cnf') self.configappli = os.path.join(self.workdir_cert_appli, 'openssl.cnf') self.size_root = size_root self.size_appli = size_appli self.size_child = size_child def directory_create(self, dirpath): """ Create directory if not exist :param str dirpath: absolut path :raises FileNotFoundError: if dirpath is bad path :raises TypeError: if dirpath is None """ if not os.path.exists(dirpath): os.makedirs(dirpath) def send_command_openssl(self, args): """ Execute Openssl command :param str args: argument send to openssl :raises error: if openssl return error """ command = '%s %s' % (self.openssl, args) logging.debug("command:%s" % command) code = subprocess.call(command.split(), stdout=sys.stdout, stderr=sys.stderr) if code != 0: logging.error("Command '%s' return code:%d" % (command, code)) raise RuntimeError def write_config_openssl(self, config, dirpath, certfile, keyfile): """ Create openssl configuration :param str config: configuration file (output) :param str dirpath: confiuration path :param str certfile: certificate filename :param str keyfile: key filename """ with open(config, 'w') as f: f.write('[ ca ]\n' 'default_ca = Cert_default\n' '\n[ Cert_default ]\n' 'dir = %s\n' 'certs = $dir/certs\n' 'crl_dir = $dir/crl\n' 'database = $dir/index.txt\n' 'new_certs_dir = $dir/newcerts\n' 'certificate = $dir/certs/%s\n' 'serial = $dir/serial\n' 'crlnumber = $dir/crlnumber\n' 'crl = $dir/crl/%s\n' 'private_key = $dir/private/%s\n' 'RANDFILE = $dir/private/.rand\n' 'name_opt = ca_default\n' 'cert_opt = ca_default\n' 'default_days = 390\n' 'default_crl_days = 30\n' 'default_md = sha256\n' 'preserve = no\n' 'policy = policy_match\n' 'crl_extensions = crl_ext\n' 'unique_subject = no\n' '\n[ policy_match ]\n' 'countryName = match\n' 'stateOrProvinceName = match\n' 'organizationName = match\n' 'organizationalUnitName = optional\n' 'commonName = supplied\n' 'emailAddress = optional\n' '\n[ req ]\n' 'default_bits = 2048\n' 'distinguished_name = req_distinguished_name\n' 'x509_extensions = v3_ca\n' 'string_mask = utf8only\n' 'unique_subject = no\n' '\n[ server_cert ]\n' 'basicConstraints=CA:false\n' 'nsComment = "OpenSSL Generated Certificate"\n' 'subjectKeyIdentifier=hash\n' 'authorityKeyIdentifier=keyid,issuer:always\n' 'keyUsage = critical, digitalSignature, keyEncipherment\n' 'nsCertType = server\n' '\n[ client_cert ]\n' 'basicConstraints=CA:false\n' 'nsComment = "OpenSSL Generated Certificate"\n' 'subjectKeyIdentifier=hash\n' 'authorityKeyIdentifier=keyid:always,issuer\n' 'keyUsage = critical, nonRepudiation, ' 'digitalSignature, keyEncipherment\n' 'nsCertType = client\n' '\n[ v3_ca ]\n' 'basicConstraints=critical, CA:true\n' 'nsComment = "OpenSSL Generated Certificate"\n' 'subjectKeyIdentifier=hash\n' 'authorityKeyIdentifier=keyid:always,issuer\n' 'keyUsage = critical, digitalSignature, ' 'cRLSign, keyCertSign\n' '\n[ v3_application_ca ]\n' 'basicConstraints=critical, CA:true, pathlen:0\n' 'nsComment = "OpenSSL Generated Certificate"\n' 'subjectKeyIdentifier=hash\n' 'authorityKeyIdentifier=keyid:always,issuer\n' 'keyUsage = critical, digitalSignature, ' 'cRLSign, keyCertSign\n' '\n[ req_distinguished_name ]\n' 'countryName = Country Name (2 letter code)\n' 'countryName_default = FR\n' 'countryName_min = 2\n' 'countryName_max = 2\n' 'stateOrProvinceName = State or Province Name (full name)\n' 'stateOrProvinceName_default = France\n' 'localityName = Locality Name (eg, city)\n' '0.organizationName = Organization Name (eg, company)\n' '0.organizationName_default = OpenNeL\n' 'organizationalUnitName = Organizational ' 'Unit Name (eg, section)\n' 'commonName = Common Name (e.g. server FQDN or YOUR name)\n' 'commonName_max = 64\n' 'emailAddress = Email Address\n' 'emailAddress_max = 64\n' '\n[ crl_ext ]\n' 'authorityKeyIdentifier=keyid:always\n' % (dirpath, certfile, 'cacrl.pem', keyfile)) def create_root_certificate(self): """ Create Root certificate """ logging.info("Create Root Certificate") # Create directory certfilename = 'cacert.pem' keyfilename = 'cakey.pem' self.directory_create(self.workdir_cert_root) self.directory_create(os.path.join(self.workdir_cert_root, 'certs')) private = os.path.join(self.workdir_cert_root, 'private') self.directory_create(private) os.chmod(private, stat.S_IEXEC | stat.S_IWUSR | stat.S_IRUSR) self.directory_create(os.path.join(self.workdir_cert_root, 'crl')) self.directory_create(os.path.join(self.workdir_cert_root, 'newcerts')) # Create files use in CA index = os.path.join(self.workdir_cert_root, 'index.txt') with open(index, 'w') as f: f.write('') serial = os.path.join(self.workdir_cert_root, 'serial') with open(serial, 'w') as f: f.write('10') # Create configuration self.write_config_openssl(self.configroot, self.workdir_cert_root, certfilename, keyfilename) # Create private key for our CA keyfile = os.path.join(self.workdir_cert_root, 'private', keyfilename) self.send_command_openssl('genrsa -aes256 -out %s -passout pass:%s %d' % (keyfile, self.passroot, self.size_root)) os.chmod(keyfile, stat.S_IEXEC | stat.S_IWUSR | stat.S_IRUSR) # Create certificate for our CA certfile = os.path.join(self.workdir_cert_root, 'certs', certfilename) self.send_command_openssl('req ' '-config %s ' '-key %s ' '-passin pass:%s ' '-new ' '-x509 ' '-days 390 ' '-sha256 ' '-extensions v3_ca ' '-out %s ' '-subj /C=%s/ST=%s/L=%s/O=%s/CN=%s' % (self.configroot, keyfile, self.passroot, certfile, self.country_name, self.state_or_province_name, self.locality_name, self.organization_name, self.common_name)) os.chmod(certfile, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) # Check certificate self.send_command_openssl('x509 -noout -text -in ' + certfile) def create_application_certificate(self): """ create application certificate """ logging.info("Create Application Certificate") certfilename = 'applicert.pem' csrfilename = 'applicsr.pem' keyfilename = 'applikey.pem' # Create directory self.directory_create(self.workdir_cert_appli) self.directory_create(os.path.join(self.workdir_cert_appli, 'certs')) private = os.path.join(self.workdir_cert_appli, 'private') self.directory_create(private) os.chmod(private, stat.S_IEXEC | stat.S_IWUSR | stat.S_IRUSR) self.directory_create(os.path.join(self.workdir_cert_appli, 'crl')) self.directory_create(os.path.join(self.workdir_cert_appli, 'newcerts')) self.directory_create(os.path.join(self.workdir_cert_appli, 'csr')) # Create files use in CA index = os.path.join(self.workdir_cert_appli, 'index.txt') with open(index, 'w') as f: f.write('') serial = os.path.join(self.workdir_cert_appli, 'serial') with open(serial, 'w') as f: f.write('10') serial = os.path.join(self.workdir_cert_appli, 'crlnumber') with open(serial, 'w') as f: f.write('10') # Create configuration self.write_config_openssl(self.configappli, self.workdir_cert_appli, certfilename, keyfilename) # Create private key for our Application keyfile = os.path.join(self.workdir_cert_appli, 'private', keyfilename) self.send_command_openssl('genrsa -aes256 -out %s -passout pass:%s %d' % (keyfile, self.passappli, self.size_appli)) os.chmod(keyfile, stat.S_IEXEC | stat.S_IWUSR | stat.S_IRUSR) # Create certificate for our CA csrfile = os.path.join(self.workdir_cert_appli, 'csr', csrfilename) self.send_command_openssl('req ' '-config %s ' '-new ' '-sha256 ' '-passin pass:%s ' '-key %s ' '-out %s ' '-subj /C=%s/ST=%s/L=%s/O=%s/CN=%s' % (self.configappli, self.passappli, keyfile, csrfile, self.country_name, self.state_or_province_name, self.locality_name, self.organization_name, self.common_name)) os.chmod(csrfile, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) certfile = os.path.join(self.workdir_cert_appli, 'certs', certfilename) # Sign certificate self.send_command_openssl('ca ' '-config %s ' '-extensions v3_application_ca ' '-days 390 ' '-notext ' '-md sha256 ' '-passin pass:%s ' '-in %s ' '-batch ' '-out %s ' % (self.configroot, self.passroot, csrfile, certfile)) os.chmod(csrfile, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) self.send_command_openssl('x509 -noout -text -in ' + certfile) certcafilename = os.path.join(self.workdir_cert_root, 'certs', 'cacert.pem') self.send_command_openssl('verify -CAfile %s %s' % (certcafilename, certfile)) # concat applicert & cacert cachainfile = os.path.join(self.workdir_cert_appli, 'certs', 'cachaincert.pem') with open(cachainfile, 'w') as outfp: with open(certfile, 'r') as infp: outfp.write(infp.read()) with open(certcafilename, 'r') as infp: outfp.write(infp.read()) def create_child_certificate(self, childname, extension): """ create child certificate :param str childname: prefix key/certificate :param str extension: Extension section (override value in config file) """ keyfilename = childname + "key.pem" csrfilename = childname + "csr.pem" certfilename = childname + "cert.pem" keyfile = os.path.join(self.workdir_cert_appli, 'private', keyfilename) self.send_command_openssl('genrsa -out %s %d' % (keyfile, self.size_child)) csrfile = os.path.join(self.workdir_cert_appli, 'csr', csrfilename) self.send_command_openssl('req ' '-config %s ' '-new ' '-sha256 ' '-key %s ' '-out %s ' '-subj /C=%s/ST=%s/L=%s/O=%s/CN=%s' % (self.configappli, keyfile, csrfile, self.country_name, self.state_or_province_name, self.locality_name, self.organization_name, self.common_name)) certfile = os.path.join(self.workdir_cert_appli, 'certs', certfilename) # Sign certificate self.send_command_openssl('ca ' '-config %s ' '-extensions %s ' '-days 390 ' '-notext ' '-md sha256 ' '-passin pass:%s ' '-in %s ' '-batch ' '-out %s ' % (self.configappli, extension, self.passappli, csrfile, certfile)) self.send_command_openssl('x509 -noout -text -in ' + certfile) certcafilename = os.path.join(self.workdir_cert_appli, 'certs', 'cachaincert.pem') self.send_command_openssl('verify -CAfile %s %s' % (certcafilename, certfile)) def root(filelog, loglevel, show_log_console, workdir_cert_root, workdir_cert_appli, openssl, size_root, size_appli, size_child, passroot, passappli, country_name, state_or_province_name, locality_name, organization_name, common_name): """ Generate all certificate :param str filelog: file log :param loglevel: level message :type loglevel: DEBUG, INFO, WARNING or ERROR :param str show_log_console: show message in console (stdout) :param str workdir_cert_root: root directory :param str workdir_cert_appli: application directory :param str openssl: localization openssl program (absolut path) :param str size_root: root key size :param str size_appli: application key size :param str size_child: child key size (server & client) :param str passroot: root password :param str passappli: application password :param str country_name: your contry name (2 characters) :param str state_or_province_name: The name of a user's state or province. :param str locality_name: Represents the name of a locality, such as a town or city. :param str organization_name: The name of the company or organization. :param str common_name: The name that represents an object. Used to perform searches. """ # Manage log logging.getLogger('logging') numeric_level = getattr(logging, loglevel.upper(), None) if not isinstance(numeric_level, int): raise ValueError('Invalid log level: ' + loglevel) handlers = [] if show_log_console: handlers.append(logging.StreamHandler()) if filelog: handlers.append(logging.FileHandler(filelog.name)) logging.basicConfig(handlers=handlers, level=numeric_level, format='%(asctime)s %(levelname)s [pid:%(process)d] \ [%(funcName)s:%(lineno)d] %(message)s') certicate = Certificate(openssl, workdir_cert_root, workdir_cert_appli, passroot, passappli, country_name, state_or_province_name, locality_name, organization_name, common_name, size_root, size_appli, size_child) logging.info("Generate CA certificate") certicate.create_root_certificate() logging.info("Generate Application certificate") certicate.create_application_certificate() logging.info("Generate Server certificate") certicate.create_child_certificate('server', 'server_cert') logging.info("Generate Client certificate") certicate.create_child_certificate('client', 'client_cert') logging.info("Certifcate generated") def main(arguments=sys.argv[1:]): """ Main function :param list args: root password """ parser = argparse.ArgumentParser(description='Create certificate ' '(root, application, server & client)') parser.add_argument('--version', action='version', version='%(prog)s 1.0') parser.add_argument('--openssl', default='openssl', help='binary openssl') parser.add_argument('-c', '--workdir-cert-root', default='ca', help='workdir Root certificate') parser.add_argument('-a', '--workdir-cert-appli', default='ca/appli', help='workdir Application certificate') parser.add_argument('--show-log-console', action='store_true', help='show message in console', default=False) parser.add_argument('--filelog', type=argparse.FileType('wt'), default=None, help='log file') parser.add_argument('--log', default='INFO', help='log level [DEBUG, INFO, WARNING, ERROR]') parser.add_argument('--size_root', type=int, default=4096, help='Define size key for CA certificate') parser.add_argument('--size_appli', type=int, default=4096, help='Define size key for Application certificate') parser.add_argument('--size_child', type=int, default=4096, help='Define size key for Child certificate') parser.add_argument('--passroot', default='OpenNelCA9439', help='define password for Root certificate') parser.add_argument('--passappli', default='OpenNelAPPLI1097', help='define password for Application certificate') parser.add_argument('--country_name', default='FR', help='countryName for certicate') parser.add_argument('--state_or_province_name', default='France', help='stateOrProvinceName for certicate') parser.add_argument('--locality_name', default='Paris', help='localityName for certicate') parser.add_argument('--organization_name', default='OpenNeL', help='organizationName for certicate') parser.add_argument('--common_name', default='OpenNeL', help='commonName for certicate') print("--") args = parser.parse_args(arguments) print("--") root(filelog=args.filelog, loglevel=args.log, show_log_console=args.show_log_console, workdir_cert_root=args.workdir_cert_root, workdir_cert_appli=args.workdir_cert_appli, openssl=args.openssl, size_root=args.size_root, size_appli=args.size_appli, size_child=args.size_child, passroot=args.passroot, passappli=args.passappli, country_name=args.country_name, state_or_province_name=args.state_or_province_name, locality_name=args.locality_name, organization_name=args.organization_name, common_name=args.common_name) if __name__ == '__main__': main()