558 lines
26 KiB
Python
Executable file
558 lines
26 KiB
Python
Executable file
#!/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 <https://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
|
|
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 = Khanat\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='khanat',
|
|
help='organizationName for certicate')
|
|
parser.add_argument('--common_name', default='khanat',
|
|
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()
|