From 6a07ab11881c1221edc32f9890cc339342f2d6ee Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Fri, 19 Apr 2019 15:16:06 +0200 Subject: [PATCH] tools/configuration: Add support for EC/Ed25519/Ed448 generation --- README.md | 5 +++- acertmgr/__init__.py | 5 ++-- acertmgr/authority/__init__.py | 2 +- acertmgr/configuration.py | 16 ++++++++--- acertmgr/tools.py | 49 +++++++++++++++++++++++++--------- 5 files changed, 56 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e702799..62fd649 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,12 @@ By default the directory (work_dir) containing the working data (csr,certificate | authority_tos_agreement | d,**g**,c | Indicates agreement to the ToS of the certificate authority (--authority-tos-agreement on command line) | | | authority_contact_email | d,**g** | (v2 API only) Contact e-mail to be registered with your account key | | | account_key | d,**g** | Path to the account key | {work_dir}/account.key | +| account_key_algorithm | d,**g** | Key-algorithm for newly generated account keys (RSA, EC, ED25519, ED448) | RSA | +| account_key_length | d,**g** | Key-length for newly generated RSA account keys (in bits) or EC curve (256=P-256, 384=P-384, 521=P-521) | depends on account_key_algorithm | | ttl_days | d,**g** | Renew certificate if it has less than this value validity left | 30 | | cert_dir | d,**g** | Directory containing all certificate related data (crt,key,csr) | {work_dir} | -| key_length | d,**g** | Key-length for newly generated private keys | 4096 | +| key_algorithm | d,**g** | Key-algorithm for newly generated private keys (RSA, EC, ED25519, ED448) | RSA | +| key_length | d,**g** | Key-length for newly generated RSA private keys (in bits) or EC curve (256=P-256, 384=P-384, 521=P-521) | depends on key_algorithm | | csr_static | **d**,g | Whether to re-use a static CSR or generate a new dynamic CSR | false | | csr_file | **d**,g | Path to store (and load) the certificate CSR file | {cert_dir}/{cert_id}.csr | | ca_static | **d**,g | Whether to re-use a static CA or download a CA file | false | diff --git a/acertmgr/__init__.py b/acertmgr/__init__.py index 73ad21c..b690138 100755 --- a/acertmgr/__init__.py +++ b/acertmgr/__init__.py @@ -35,12 +35,11 @@ def cert_get(settings): # create ssl key key_file = settings['key_file'] - key_length = settings['key_length'] if os.path.isfile(key_file): key = tools.read_pem_file(key_file, key=True) else: - log("SSL key not found at '{0}'. Creating {1} bit key.".format(key_file, key_length)) - key = tools.new_ssl_key(key_file, key_length) + log("SSL key not found at '{0}'. Creating key.".format(key_file)) + key = tools.new_ssl_key(key_file, settings['key_algorithm'], settings['key_length']) # create ssl csr csr_file = settings['csr_file'] diff --git a/acertmgr/authority/__init__.py b/acertmgr/authority/__init__.py index 60c4b5d..46c0324 100644 --- a/acertmgr/authority/__init__.py +++ b/acertmgr/authority/__init__.py @@ -28,7 +28,7 @@ def authority(settings): acc_key = tools.read_pem_file(acc_file, key=True) else: log("Account key not found at '{0}'. Creating key.".format(acc_file)) - acc_key = tools.new_account_key(acc_file) + acc_key = tools.new_account_key(acc_file, settings['account_key_algorithm'], settings['account_key_size']) authority_module = importlib.import_module("acertmgr.authority.{0}".format(settings["api"])) authority_class = getattr(authority_module, "ACMEAuthority") diff --git a/acertmgr/configuration.py b/acertmgr/configuration.py index 04897e0..f50d0e3 100644 --- a/acertmgr/configuration.py +++ b/acertmgr/configuration.py @@ -32,7 +32,6 @@ LEGACY_AUTHORITY_TOS_AGREEMENT = "true" # Configuration defaults to use if not specified otherwise DEFAULT_CONF_FILE = "/etc/acertmgr/acertmgr.conf" DEFAULT_CONF_DIR = "/etc/acertmgr" -DEFAULT_KEY_LENGTH = 4096 # bits DEFAULT_TTL = 30 # days DEFAULT_API = "v2" DEFAULT_AUTHORITY = "https://acme-v02.api.letsencrypt.org" @@ -105,6 +104,14 @@ def parse_authority(localconfig, globalconfig, runtimeconfig): # - Account key path update_config_value(authority, 'account_key', localconfig, globalconfig, os.path.join(runtimeconfig['work_dir'], "account.key")) + + # - Account key algorithm (if key has to be (re-)generated) + update_config_value(authority, 'account_key_algorithm', localconfig, globalconfig, None) + + # - Account key length (if key has to be (re-)generated, converted to int) + update_config_value(authority, 'account_key_length', localconfig, globalconfig, None) + authority['account_key_length'] = int(authority['account_key_length']) if authority['account_key_length'] else None + return authority @@ -162,9 +169,12 @@ def parse_config_entry(entry, globalconfig, runtimeconfig): globalconfig.get('server_key', os.path.join(config['cert_dir'], "{}.key".format(config['id'])))) + # SSL key algorithm (if key has to be (re-)generated) + update_config_value(config, 'key_algorithm', localconfig, globalconfig, None) + # SSL key length (if key has to be (re-)generated, converted to int) - update_config_value(config, 'key_length', localconfig, globalconfig, DEFAULT_KEY_LENGTH) - config['key_length'] = int(config['key_length']) + update_config_value(config, 'key_length', localconfig, globalconfig, None) + config['key_length'] = int(config['key_length']) if config['key_length'] else None # SSL CA location / use static update_config_value(config, 'ca_file', localconfig, globalconfig, diff --git a/acertmgr/tools.py b/acertmgr/tools.py index d495c4b..97ee272 100644 --- a/acertmgr/tools.py +++ b/acertmgr/tools.py @@ -116,24 +116,47 @@ def new_cert_request(names, key, must_staple=False): # @brief generate a new account key # @param path path where the new key file should be written in PEM format (optional) -def new_account_key(path=None, key_size=4096): - return new_ssl_key(path, key_size) +def new_account_key(path=None, key_algo=None, key_size=None): + return new_ssl_key(path, key_algo, key_size) # @brief generate a new ssl key # @param path path where the new key file should be written in PEM format (optional) -def new_ssl_key(path=None, key_size=4096): - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=key_size, - backend=default_backend() - ) - pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() - ) +def new_ssl_key(path=None, key_algo=None, key_size=None): + if not key_algo or key_algo.lower() == 'rsa': + if not key_size: + key_size = 4096 + key_format = serialization.PrivateFormat.TraditionalOpenSSL + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=key_size, + backend=default_backend() + ) + elif key_algo.lower() == 'ec': + if not key_size or key_size == 256: + key_curve = ec.SECP256R1 + elif key_size == 384: + key_curve = ec.SECP384R1 + elif key_size == 521: + key_curve = ec.SECP521R1 + else: + raise ValueError("Unsupported EC curve size parameter: {}".format(key_size)) + key_format = serialization.PrivateFormat.PKCS8 + private_key = ec.generate_private_key(curve=key_curve, backend=default_backend()) + elif key_algo.lower() == 'ed25519' and "cryptography.hazmat.primitives.asymmetric.ed25519": + key_format = serialization.PrivateFormat.PKCS8 + private_key = ed25519.Ed25519PrivateKey.generate() + elif key_algo.lower() == 'ed448' and "cryptography.hazmat.primitives.asymmetric.ed448": + key_format = serialization.PrivateFormat.PKCS8 + private_key = ed448.Ed448PrivateKey.generate() + else: + raise ValueError("Unsupported key algorithm: {}".format(key_algo)) if path is not None: + pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=key_format, + encryption_algorithm=serialization.NoEncryption(), + ) with io.open(path, 'wb') as pem_out: pem_out.write(pem) try: