288 lines
11 KiB
Python
288 lines
11 KiB
Python
#!/usr/bin/env python
|
|
|
|
from os import system, popen, makedirs, chmod
|
|
from os.path import exists, dirname, isfile, realpath
|
|
from sys import exit
|
|
from errno import EEXIST
|
|
from string import digits, ascii_uppercase as upper
|
|
from random import SystemRandom
|
|
from argparse import ArgumentParser
|
|
from ConfigParser import ConfigParser
|
|
from getpass import getpass
|
|
|
|
class SSLCA:
|
|
_path = "%s/etc" % dirname(dirname(realpath(__file__)))
|
|
ver = "0.1.0"
|
|
|
|
def __init__(self):
|
|
self.config = ConfigParser()
|
|
|
|
if not exists(self._path):
|
|
try:
|
|
makedirs(self._path)
|
|
except OSError as e:
|
|
if e.errno != EEXIST:
|
|
raise
|
|
|
|
config_file = "%s/sslca.conf" % self._path
|
|
if not exists(config_file):
|
|
print("Config file missing, answer prompts to generate:\n")
|
|
country = raw_input("Country Name (2 letter code): ")
|
|
state = raw_input("State or Province Name (full name): ")
|
|
city = raw_input("Locality Name (eg, city): ")
|
|
org_name = raw_input("Organization Name (eg, company): ")
|
|
org_unit = raw_input("Organization Unit Name (eg, section): ")
|
|
common_name = raw_input(
|
|
"Common Name (eg, your name of your server's hostname): ")
|
|
email = raw_input("Email Address: ")
|
|
path = self._is_path(raw_input("Absolute path for certificates: "))
|
|
bits = self._is_number(raw_input("Bits used by certificates: "))
|
|
days = self._is_number(raw_input(
|
|
"Days certifcates will be valid: "))
|
|
self.config.add_section("ssl")
|
|
self.config.add_section("defaults")
|
|
self.config.set("ssl", "country", country)
|
|
self.config.set("ssl", "state", state)
|
|
self.config.set("ssl", "city", city)
|
|
self.config.set("ssl", "org_name", org_name)
|
|
self.config.set("ssl", "org_unit", org_unit)
|
|
self.config.set("ssl", "common_name", common_name)
|
|
self.config.set("ssl", "email", email)
|
|
self.config.set("defaults", "path", path)
|
|
self.config.set("defaults", "bits", bits)
|
|
self.config.set("defaults", "days", days)
|
|
with open(config_file, "w+b") as cfgfile:
|
|
self.config.write(cfgfile)
|
|
chmod(config_file, 0700)
|
|
print("\nConfig file generated at: %s" % config_file)
|
|
|
|
self.config.read(config_file)
|
|
|
|
country = self.config.get("ssl", "country")
|
|
state = self.config.get("ssl", "state")
|
|
city = self.config.get("ssl", "city")
|
|
org_name = self.config.get("ssl", "org_name")
|
|
org_unit = self.config.get("ssl", "org_unit")
|
|
common_name = self.config.get("ssl", "common_name")
|
|
email = self.config.get("ssl", "email")
|
|
self.subj = "/C=%s/ST=%s/L=%s/O=%s/OU=%s/emailAddress=%s" % (
|
|
country, state, city, org_name, org_unit, email)
|
|
self.subj_ca = "%s/CN=%s" % (self.subj, common_name)
|
|
|
|
self.bits = self.config.get("defaults", "bits")
|
|
self.path = self.config.get("defaults", "path")
|
|
self.days = self.config.get("defaults", "days")
|
|
|
|
def _is_number(self, value):
|
|
if not value.isdigit() or "." in value:
|
|
return None
|
|
return int(value)
|
|
|
|
def _is_path(self, value):
|
|
if not exists(value):
|
|
return None
|
|
return value
|
|
|
|
def _exec_cmd(self, command):
|
|
system(command)
|
|
|
|
def _exec_cmd_get(self, command):
|
|
return popen(command).read()
|
|
|
|
def _offer_randpass(self, n=13):
|
|
chars = (SystemRandom().choice(upper + digits) for _ in range(n))
|
|
return "".join(chars)
|
|
|
|
def _make_cert(self, *args, **kwargs):
|
|
name = kwargs.get("name", None)
|
|
path = kwargs.get("path", self.path)
|
|
bits = kwargs.get("bits", self.bits)
|
|
subj = kwargs.get("subj", self.subj)
|
|
aesb = kwargs.get("aesb", "")
|
|
pswd = kwargs.get("pswd", False)
|
|
path = path[:-1] if path[-1] == "/" else path
|
|
if name is None:
|
|
print("Error: You must specify the name of the certificate")
|
|
print("For help with commands, use --help")
|
|
sys.exit(1)
|
|
|
|
loc = "%s/%s" % (path, name)
|
|
pout = "-passout pass:%s" % pswd if pswd else ""
|
|
pin = "-passin pass:%s" % pswd if pswd else ""
|
|
|
|
cmd1 = "openssl genrsa %s %s -out %s.key %s" % (aesb, pout, loc, bits)
|
|
cmd2 = ("openssl req -new -key %s.key -out %s.csr -subj '%s' %s" %
|
|
(loc, loc, subj, pin))
|
|
|
|
self._exec_cmd(cmd1)
|
|
self._exec_cmd(cmd2)
|
|
|
|
def usage(self):
|
|
description = "Automates CA certificate generation and signing"
|
|
parser = ArgumentParser(prog="sslca", description=description)
|
|
parser.add_argument("-v", "--version", dest="version",
|
|
action="version", version="%(prog)s " + self.ver)
|
|
subparsers = parser.add_subparsers(title="sub-commands", dest="subs")
|
|
subparsers.required = False
|
|
self.usage_ca(subparsers)
|
|
self.usage_cert(subparsers)
|
|
self.usage_sign(subparsers)
|
|
return parser
|
|
|
|
def usage_ca(self, subparsers):
|
|
ca_parser = subparsers.add_parser("ca", help="create CA certificates")
|
|
ca_parser.add_argument("-n", "--name", dest="ca_name", required=True,
|
|
help="specify name of CA files")
|
|
ca_parser.add_argument("-p", "--path", dest="ca_path",
|
|
default=self.path, help="specify path for CA files")
|
|
ca_parser.add_argument("-b", "--bits", dest="ca_bits",
|
|
default=self.bits, help="specify the number of bits used")
|
|
ca_parser.add_argument("-d", "--days", dest="ca_days",
|
|
default=self.days, help="specify the number of days valid")
|
|
ca_parser.add_argument("-P", "--pswd", dest="ca_pswd", default=None,
|
|
help="non-interactively specify passphrase for signing key")
|
|
ca_parser.add_argument("-a", "--auto", dest="ca_auto",
|
|
action="store_true",
|
|
help="automate key passphrase using random password")
|
|
|
|
def usage_cert(self, subparsers):
|
|
cert_parser = subparsers.add_parser("cert",
|
|
help="generate normal SSL certificates")
|
|
cert_parser.add_argument("-n", "--name", dest="name", required=True,
|
|
help="specify name of certificate files")
|
|
cert_parser.add_argument("-p", "--path", dest="path",
|
|
default=self.path, help="specify name of certicicate files")
|
|
cert_parser.add_argument("-b", "--bits", dest="bits",
|
|
default=self.bits, help="specify the number of bits used")
|
|
cert_parser.add_argument("-H", "--host", dest="host", required=True,
|
|
help="specify the host for certificate files")
|
|
|
|
def usage_sign(self, subparsers):
|
|
sign_parser = subparsers.add_parser("sign",
|
|
help="sign certificate with CA")
|
|
sign_parser.add_argument("-a", "--ca", dest="ca", required=True,
|
|
help="specify name of CA used for signing")
|
|
sign_parser.add_argument("-c", "--cert", dest="cert", required=True,
|
|
help="specify name of certificate to be signed")
|
|
sign_parser.add_argument("-p", "--path", dest="path", default=self.path,
|
|
help="specify path for signed certificate files")
|
|
sign_parser.add_argument("-b", "--bits", dest="bits", default=self.bits,
|
|
help="specify the number of bits used")
|
|
sign_parser.add_argument("-d", "--days", dest="days", default=self.days,
|
|
help="specify the number of days valid")
|
|
sign_parser.add_argument("-P", "--pswd", dest="pswd", default=None,
|
|
help="non-interactively specify passphrase for signing key")
|
|
|
|
def get_arguments(self, parser):
|
|
return parser.parse_args()
|
|
|
|
def get_help(self, parser):
|
|
return parser.print_help
|
|
|
|
def inject(self, arguments, helper):
|
|
command = arguments.subs
|
|
if command == "ca":
|
|
return self.gen_ca_cert(arguments)
|
|
elif command == "cert":
|
|
return self.gen_cert(arguments)
|
|
elif command == "sign":
|
|
return self.sign_cert(arguments)
|
|
helper()
|
|
|
|
def gen_cert(self, arguments):
|
|
name = arguments.name
|
|
host = arguments.host
|
|
bits = arguments.bits
|
|
path = arguments.path
|
|
path = path[:-1] if path[-1] == "/" else path
|
|
loc = "%s/%s" % (path, name)
|
|
subj = "%s/CN=%s" % (self.subj, host)
|
|
kwargs = dict()
|
|
kwargs["name"] = name
|
|
kwargs["path"] = path
|
|
kwargs["bits"] = bits
|
|
kwargs["subj"] = subj
|
|
self._make_cert(**kwargs)
|
|
print("\n\nCreated certificate pair: %s.key, %s.csr" % (loc, loc))
|
|
|
|
def gen_ca_cert(self, arguments):
|
|
name = arguments.ca_name
|
|
path = arguments.ca_path
|
|
days = arguments.ca_days
|
|
bits = arguments.ca_bits
|
|
auto = arguments.ca_auto
|
|
pswd = arguments.ca_pswd
|
|
subj = self.subj_ca
|
|
path = path[:-1] if path[-1] == "/" else path
|
|
|
|
rand = self._offer_randpass()
|
|
pswd = None
|
|
kwargs = dict()
|
|
if not (auto or pswd):
|
|
print("Suggested Random Password: %s\n" % rand)
|
|
|
|
def request_passphrase():
|
|
passphrase1 = getpass("Enter passphrase: ")
|
|
passphrase2 = getpass("Repeat passphrase: ")
|
|
|
|
if passphrase1 == passphrase2:
|
|
return passphrase1
|
|
else:
|
|
return request_passphrase()
|
|
|
|
pswd = request_passphrase()
|
|
else:
|
|
pswd = pswd if pswd else rand
|
|
|
|
kwargs["name"] = name
|
|
kwargs["path"] = path
|
|
kwargs["bits"] = bits
|
|
kwargs["subj"] = subj
|
|
kwargs["pswd"] = pswd
|
|
kwargs["aesb"] = "-aes256"
|
|
|
|
self._make_cert(**kwargs)
|
|
|
|
loc = "%s/%s" % (path, name)
|
|
pin = "-passin pass:%s" % pswd if auto or pswd else ""
|
|
cmd = "openssl x509 -req -days %s -in %s.csr" % (days, loc)
|
|
cmd = "%s -signkey %s.key -out %s.crt %s" % (cmd, loc, loc, pin)
|
|
self._exec_cmd(cmd)
|
|
|
|
print("\n\nCreated CA certificate: %s.crt" % loc)
|
|
if auto:
|
|
print("Random Pasword Used (SAVE THIS!!!): %s" % pswd)
|
|
|
|
def sign_cert(self, arguments):
|
|
ca = arguments.ca
|
|
cert = arguments.cert
|
|
path = arguments.path
|
|
bits = arguments.bits
|
|
days = arguments.days
|
|
pswd = arguments.pswd
|
|
path = path[:-1] if path[-1] == "/" else path
|
|
|
|
loc = "%s/%s" % (path, cert)
|
|
ca_loc = "%s/%s" % (path, ca)
|
|
pin = "-passin pass:%s" % pswd if pswd else ""
|
|
cmd = "openssl x509 -req -days %s -in %s.csr" % (days, loc)
|
|
cmd = "%s -CA %s.crt -CAkey %s.key" % (cmd, ca_loc, ca_loc)
|
|
cmd = "%s -CAcreateserial -out %s.crt %s" % (cmd, loc, pin)
|
|
self._exec_cmd(cmd)
|
|
|
|
if isfile("%s/%s.crt" % (path, cert)):
|
|
print("\n\nSigned certificate: %s.crt" % loc)
|
|
else:
|
|
print("Error: Something went wrong, check that files exist")
|
|
exit(1)
|
|
|
|
## Entry point
|
|
def main():
|
|
sslca = SSLCA()
|
|
parser = sslca.usage()
|
|
arguments = sslca.get_arguments(parser)
|
|
helper = sslca.get_help(parser)
|
|
sslca.inject(arguments, helper)
|
|
|
|
if __name__ == "__main__":
|
|
main() |