From 7ae99ff80fc325ed4a8d2a56c7a405b74d3de734 Mon Sep 17 00:00:00 2001 From: Jaguar0625 Date: Wed, 24 Nov 2021 14:21:01 -0400 Subject: [PATCH] feature: certtool improvements - default ca cn to address corresponding to main public key - add `package` option to copy required server files into a separate directory for easy copying --- certtool.py | 141 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 39 deletions(-) diff --git a/certtool.py b/certtool.py index 144f344..befd43b 100644 --- a/certtool.py +++ b/certtool.py @@ -8,9 +8,10 @@ from pathlib import Path from subprocess import PIPE, STDOUT, Popen -from symbolchain.core.CryptoTypes import PrivateKey +from symbolchain.core.Network import NetworkLocator from symbolchain.core.PrivateKeyStorage import PrivateKeyStorage from symbolchain.core.symbol.KeyPair import KeyPair +from symbolchain.core.symbol.Network import Network from zenlog import log @@ -87,42 +88,25 @@ def prepare_node_config(node_cn): ''') -def main(): - parser = argparse.ArgumentParser(description='Cert generation tool', formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('--output', help='certificates working directory (all files will be created inside)', default='certificates') - parser.add_argument('--ca', help='path to key PEM file that will be used as a CA key', default='ca.key.pem') - parser.add_argument('--name-ca', help='use provided name as CA CN (common name) - suggested: account name') - parser.add_argument('--name-node', help='use provided name as node CN (common name) - suggested: node name, host or ip') - parser.add_argument('--force', help='overwrite output directory if it already exists', action='store_true') - args = parser.parse_args() - - filepath = Path(args.output) - if filepath.exists(): - if not args.force: - raise FileExistsError(f'output directory ({filepath}) already exists, use --force to overwrite') - shutil.rmtree(filepath) - - check_openssl_version() - - # obtain full path prior to switching directory - ca_path = Path(args.ca).absolute() - - os.makedirs(filepath) - os.chdir(filepath) - +def openssl_prepare_keys(ca_path): log.info('creating ca.pubkey.pem') - run_openssl(['pkey', '-in', ca_path, '-pubout', '-out', 'ca.pubkey.pem']) + run_openssl([ + 'pkey', + '-in', ca_path, + '-out', 'ca.pubkey.pem', + '-pubout' + ]) log.info('creating random node.key.pem') - node_key_pair = KeyPair(PrivateKey.random()) - PrivateKeyStorage('.', None).save('node.key', node_key_pair.private_key) + run_openssl([ + 'genpkey', + '-out', 'node.key.pem', + '-outform', 'PEM', + '-algorithm', 'ed25519' + ]) - log.info('preparing configuration files') - ca_cn = get_common_name(args.name_ca, 'CA common name') - node_cn = get_common_name(args.name_node, 'node common name') - prepare_ca_config(ca_path, ca_cn) - prepare_node_config(node_cn) +def openssl_prepare_certs(ca_path): log.info('creating CA certificate') run_openssl([ 'req', @@ -131,19 +115,25 @@ def main(): '-key', ca_path, '-new', '-x509', '-days', '7300', - '-out', 'ca.crt.pem']) + '-out', 'ca.crt.pem' + ]) - # prepare node CSR + log.info('preparing node CSR') run_openssl([ 'req', '-config', 'node.cnf', '-key', 'node.key.pem', '-new', - '-out', 'node.csr.pem']) + '-out', 'node.csr.pem' + ]) log.info('signing node certificate') - run_openssl(['rand', '-out', './serial.dat', '-hex', '19']) - + run_openssl([ + 'rand', + '-out', './serial.dat', + '-hex', + '19' + ]) run_openssl([ 'ca', '-config', 'ca.cnf', @@ -151,9 +141,82 @@ def main(): '-notext', '-batch', '-in', 'node.csr.pem', - '-out', 'node.crt.pem']) + '-out', 'node.crt.pem' + ]) + + +def prepare_directory(directory_path, force): + filepath = Path(directory_path) + if filepath.exists(): + if not force: + raise FileExistsError(f'output directory ({filepath}) already exists, use --force to overwrite') + + shutil.rmtree(filepath) + + os.makedirs(filepath) + return filepath + + +def main(): + parser = argparse.ArgumentParser(description='Cert generation tool', formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--working', help='certificates working directory (all files will be created inside)', default='cert') + parser.add_argument('--package', help='certificates package directory (all required server files will be copied inside)') + parser.add_argument('--package-mode', help='contents of the package', choices=('all', 'node'), default='all') + parser.add_argument('--ca', help='path to key PEM file that will be used as a CA key', default='ca.key.pem') + parser.add_argument('--name-ca', help='use provided name as CA CN (common name) - suggested: account name') + parser.add_argument('--name-node', help='use provided name as node CN (common name) - suggested: node name, host or ip') + parser.add_argument('--network', help='network to use when using autogenerated ca names', default='mainnet') + parser.add_argument('--force', help='overwrite output directory if it already exists', action='store_true') + args = parser.parse_args() + + check_openssl_version() + + # obtain full paths prior to switching directory + ca_path = Path(args.ca).absolute() + + if args.package: + package_path = Path(args.package).absolute() + prepare_directory(args.package, args.force) + + os.chdir(prepare_directory(args.working, args.force)) + + log.info('preparing configuration files') + if args.name_ca: + ca_cn = args.name_ca + elif '.pem' == ca_path.suffix: + main_private_key = PrivateKeyStorage(ca_path.parent).load(ca_path.stem) + main_public_key = KeyPair(main_private_key).public_key + + network = NetworkLocator.find_by_name(Network.NETWORKS, args.network) + main_address = network.public_key_to_address(main_public_key) + ca_cn = str(main_address) + else: + ca_cn = get_common_name(args.name_ca, 'CA common name') + + node_cn = get_common_name(args.name_node, 'node common name') + + log.info(f' * CA common name: {ca_cn}') + log.info(f' * Node common name: {node_cn}') + + prepare_ca_config(ca_path, ca_cn) + prepare_node_config(node_cn) + + openssl_prepare_keys(ca_path) + openssl_prepare_certs(ca_path) + + log.info(f'certificates generated in {args.working} directory') + + if args.package: + package_filenames = ['node.crt.pem', 'node.key.pem'] + if 'all' == args.package_mode: + package_filenames += ['ca.pubkey.pem', 'ca.crt.pem'] + + for filename in package_filenames: + destination_path = package_path / filename + shutil.copyfile(filename, destination_path) + os.chmod(destination_path, 0o400) - log.info(f'certificates generated in {args.output} directory') + log.info(f'certificates packaged in {args.package} directory') if __name__ == '__main__':