# -*- coding: utf-8 -*-
# Copyright 2016 Open Permissions Platform Coalition
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software distributed under the License is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
#
import os
import subprocess
import argparse
import tempfile
DEFAULT_CERTS_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), '../certs'))
CA_CRT = os.path.join(DEFAULT_CERTS_DIR, 'CA.crt')
CA_KEY = os.path.join(DEFAULT_CERTS_DIR, 'CA.key')
MOZILLA_PEM = os.path.join(DEFAULT_CERTS_DIR, 'ca-bundle.crt')
LOCALHOST_CRT = os.path.join(DEFAULT_CERTS_DIR, 'localhost.crt')
LOCALHOST_KEY = os.path.join(DEFAULT_CERTS_DIR, 'localhost.key')
CLIENT_CRT = os.path.join(DEFAULT_CERTS_DIR, 'client.crt')
CLIENT_KEY = os.path.join(DEFAULT_CERTS_DIR, 'client.key')
SUBJECT = ('/C=GB/ST=London/L=London'
'/O=Connected Digital Economy Catapult'
'/OU=Dev Team'
'/CN=Open Permissions Platform Coalition')
SUBJECT_ALT_NAME = '''\
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[req_distinguished_name]
# we get this from the command line through -subj
[alt_names]
'''
[docs]def call_openssl(cmd, message, silent=False):
"""
call openssl
:param cmd: a string of command send to openssl
:param message: a string to print out if not silent
:param silent: a boolean for whether to suppress output from openssl
"""
if silent:
with open(os.devnull, 'w') as devnull:
return subprocess.check_call(cmd, shell=True, stdout=devnull,
stderr=subprocess.STDOUT)
else:
print message
return subprocess.check_call(cmd, shell=True)
[docs]def gen_private_key(filepath, silent=False):
"""
generate ssl private key
:param filepath: file path to the key file
:param silent: whether to suppress output
"""
cmd = 'openssl genrsa -out {} 2048'.format(filepath)
message = 'generate private key {}'.format(filepath)
call_openssl(cmd, message, silent)
[docs]def gen_self_signed_cert(filepath, keyfile, days, silent=False):
"""
generate self signed ssl certificate, i.e. a private CA certificate
:param filepath: file path to the key file
:param keyfile: file path to the private key
:param days: valid duration for the certificate
:param silent: whether to suppress output
"""
cmd = (
'openssl req -x509 -new -nodes -key {} -days {} -out {} -subj "{}"'
).format(keyfile, days, filepath, SUBJECT)
message = 'generate self signed certificate {} for {} days'.format(
filepath, days)
call_openssl(cmd, message, silent)
[docs]def gen_cert_request(filepath, keyfile, config, silent=False):
"""
generate certificate request
:param filepath: file path to the certificate request
:param keyfile: file path to the private key
:param silent: whether to suppress output
"""
message = 'generate ssl certificate request'
cmd = (
'openssl req -new -key {} -out {} -subj "{}"'
' -extensions v3_req -config {}').format(
keyfile, filepath, SUBJECT, config)
call_openssl(cmd, message, silent)
[docs]def sign_cert_request(filepath, cert_req, ca_crt, ca_key, days, extfile,
silent=False):
"""
generate self signed ssl certificate, i.e. a private CA certificate
:param filepath: file path to the key file
:param keyfile: file path to the private key
:param days: valid duration for the certificate
:param silent: whether to suppress output
"""
message = 'sign certificate request'
cmd = ('openssl x509 -req -in {} -CA {} -CAkey {} -CAcreateserial'
' -out {} -days {} -extfile {} -extensions v3_req').format(
cert_req, ca_crt, ca_key, filepath, days, extfile)
call_openssl(cmd, message, silent)
[docs]def gen_ca_cert(filename, dirname, days, silent=False):
"""
generate a CA key and certificate key pair.
:param filename: prefix for the key and cert file
:param dirname: name of the directory
:param days: days of the certificate being valid
:param silent: whether to suppress output
"""
keyfile = os.path.join(dirname, '{}.key'.format(filename))
ca_crt = os.path.join(dirname, '{}.crt'.format(filename))
gen_private_key(keyfile, silent)
gen_self_signed_cert(ca_crt, keyfile, days, silent)
[docs]def gen_non_ca_cert(filename, dirname, days, ip_list, dns_list,
ca_crt, ca_key, silent=False):
"""
generate a non CA key and certificate key pair signed by the private
CA key and crt.
:param filename: prefix for the key and cert file
:param dirname: name of the directory
:param days: days of the certificate being valid
:ip_list: a list of ip address to be included in the certificate
:dns_list: a list of dns names to be included in the certificate
:ca_key: file path to the CA key
:ca_crt: file path to the CA crt
:param silent: whether to suppress output
"""
key_file = os.path.join(dirname, '{}.key'.format(filename))
req = os.path.join(dirname, '{}.csr'.format(filename))
crt = os.path.join(dirname, '{}.crt'.format(filename))
gen_private_key(key_file, silent)
alt_names = []
for ind, ip in enumerate(ip_list):
alt_names.append('IP.{} = {}'.format(ind + 1, ip))
for ind, dns in enumerate(dns_list):
alt_names.append('DNS.{} = {}'.format(ind + 1, dns))
conf = tempfile.mktemp()
open(conf, 'w').write(SUBJECT_ALT_NAME + '\n'.join(alt_names))
gen_cert_request(req, key_file, conf, silent)
sign_cert_request(crt, req, ca_crt, ca_key, days, conf, silent)
[docs]def check_cert(certfile):
"""
output the text format of the certificate
:param filepath: file path to the ssl certificate
:returns: string
"""
cmd = 'openssl x509 -in {} -text -noout'.format(certfile)
out = subprocess.check_output(cmd, shell=True)
return out
[docs]def check_key_cert_match(keyfile, certfile):
"""
check if the ssl key matches the certificate
:param keyfile: file path to the ssl key
:param certfile: file path to the ssl certificate
:returns: true or false
"""
key_modulus = subprocess.check_output(
'openssl rsa -noout -modulus -in {}'.format(keyfile),
shell=True)
cert_modulus = subprocess.check_output(
'openssl x509 -noout -modulus -in {}'.format(certfile),
shell=True)
return key_modulus == cert_modulus
[docs]def argument_parser():
"""
process the command line arguments.
:returns: an ArgumentParser object.
"""
parser = argparse.ArgumentParser(
description='openssl wrapper')
parser.add_argument('--ca', action='store_true', default=False,
dest='is_ca',
help='Is it a Certificate Authority certificate?')
parser.add_argument('-f', '--filename', type=str, default='localhost',
help='File name prefix of the key and certificate.')
parser.add_argument('-d', '--dirname', type=str, default=DEFAULT_CERTS_DIR,
help='Destination directory name.')
parser.add_argument('--days', type=int, default=500,
help='Duration of certificate.')
parser.add_argument('--ip', nargs='*', default=['127.0.0.1'],
help='IP address.')
parser.add_argument('--dns', nargs='*', default=['localhost'],
help='DNS')
parser.add_argument('--silent', action='store_true',
help='should the output from the shell be silenced?')
return parser
[docs]def main(argv=None):
args = argument_parser().parse_args(argv)
if args.is_ca:
gen_ca_cert(args.filename, args.dirname, args.days, args.silent)
else:
ca_crt = os.path.join(DEFAULT_CERTS_DIR, 'CA.crt')
ca_key = os.path.join(DEFAULT_CERTS_DIR, 'CA.key')
gen_non_ca_cert(args.filename, args.dirname, args.days,
args.ip, args.dns, ca_crt, ca_key, args.silent)
if __name__ == '__main__':
main()