diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 9e78e870..2ff1d3c2 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -13,12 +13,12 @@ from flask import current_app from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import rsa from lemur import database from lemur.extensions import metrics from lemur.plugins.base import plugins from lemur.certificates.models import Certificate +from lemur.common.utils import generate_private_key from lemur.destinations.models import Destination from lemur.notifications.models import Notification @@ -317,13 +317,7 @@ def create_csr(**csr_config): :param csr_config: """ - if 'RSA' in csr_config.get('key_type'): - key_size = int(csr_config.get('key_type')[3:]) - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=key_size, - backend=default_backend() - ) + private_key = generate_private_key(csr_config.get('key_type')) # TODO When we figure out a better way to validate these options they should be parsed as str builder = x509.CertificateSigningRequestBuilder() diff --git a/lemur/common/utils.py b/lemur/common/utils.py index 7cfca4f4..e69d71a8 100644 --- a/lemur/common/utils.py +++ b/lemur/common/utils.py @@ -11,6 +11,7 @@ import random from cryptography import x509 from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import rsa from flask_restful.reqparse import RequestParser @@ -37,12 +38,44 @@ def get_psuedo_random_string(): def parse_certificate(body): + """ + Helper function that parses a PEM certificate. + + :param body: + :return: + """ if isinstance(body, str): body = body.encode('utf-8') return x509.load_pem_x509_certificate(body, default_backend()) +def generate_private_key(key_type): + """ + Generates a new private key based on key_type. + + Valid key types: RSA2048, RSA4096 + + :param key_type: + :return: + """ + valid_key_types = ['RSA2048', 'RSA4096'] + + if key_type not in valid_key_types: + raise Exception("Invalid key type: {key_type}. Supported key types: {choices}".format( + key_type=key_type, + choices=",".join(valid_key_types) + )) + + if 'RSA' in key_type: + key_size = int(key_type[3:]) + return rsa.generate_private_key( + public_exponent=65537, + key_size=key_size, + backend=default_backend() + ) + + def is_weekend(date): """ Determines if a given date is on a weekend. diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index 53fae150..bb45fd66 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -16,9 +16,10 @@ from acme.client import Client from acme import jose from acme import messages +from lemur.common.utils import generate_private_key + from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa import OpenSSL.crypto @@ -101,12 +102,6 @@ def request_certificate(acme_client, authorizations, csr): return pem_certificate, pem_certificate_chain -def generate_rsa_private_key(): - return rsa.generate_private_key( - public_exponent=65537, key_size=2048, backend=default_backend() - ) - - def setup_acme_client(): key = current_app.config.get('ACME_PRIVATE_KEY').strip() acme_email = current_app.config.get('ACME_EMAIL') @@ -127,7 +122,7 @@ def acme_client_for_private_key(acme_directory_url, private_key): def register(email): - private_key = generate_rsa_private_key() + private_key = generate_private_key('RSA2048') acme_client = acme_client_for_private_key(current_app.config('ACME_DIRECTORY_URL'), private_key) registration = acme_client.register( diff --git a/lemur/plugins/lemur_cryptography/plugin.py b/lemur/plugins/lemur_cryptography/plugin.py index af76db23..4ab27c77 100644 --- a/lemur/plugins/lemur_cryptography/plugin.py +++ b/lemur/plugins/lemur_cryptography/plugin.py @@ -13,19 +13,15 @@ from flask import current_app from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import rsa - from lemur.plugins.bases import IssuerPlugin from lemur.plugins import lemur_cryptography as cryptography_issuer +from lemur.common.utils import generate_private_key + def build_root_certificate(options): - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, - backend=default_backend() - ) + private_key = generate_private_key(options.get('key_type')) subject = issuer = x509.Name([ x509.NameAttribute(x509.OID_COUNTRY_NAME, options['country']), diff --git a/lemur/plugins/lemur_cryptography/tests/test_cryptography.py b/lemur/plugins/lemur_cryptography/tests/test_cryptography.py index e69de29b..ec498aae 100644 --- a/lemur/plugins/lemur_cryptography/tests/test_cryptography.py +++ b/lemur/plugins/lemur_cryptography/tests/test_cryptography.py @@ -0,0 +1,36 @@ +import arrow + + +def test_build_root_certificate(): + from lemur.plugins.lemur_cryptography.plugin import build_root_certificate + + options = { + 'key_type': 'RSA2048', + 'country': 'US', + 'state': 'CA', + 'location': 'Example place', + 'organization': 'Example, Inc.', + 'organizational_unit': 'Example Unit', + 'common_name': 'Example ROOT', + 'validity_start': arrow.get('2016-12-01').datetime, + 'validity_end': arrow.get('2016-12-02').datetime, + 'first_serial': 1 + + } + cert_pem, private_key_pem = build_root_certificate(options) + + assert cert_pem + assert private_key_pem + + +def test_issue_certificate(authority): + from lemur.tests.vectors import CSR_STR + from lemur.plugins.lemur_cryptography.plugin import issue_certificate + + options = { + 'authority': authority, + 'validity_start': arrow.get('2016-12-01').datetime, + 'validity_end': arrow.get('2016-12-02').datetime + } + cert = issue_certificate(CSR_STR, options) + assert cert diff --git a/lemur/tests/test_utils.py b/lemur/tests/test_utils.py new file mode 100644 index 00000000..d1a6d01f --- /dev/null +++ b/lemur/tests/test_utils.py @@ -0,0 +1,11 @@ +import pytest + + +def test_generate_private_key(): + from lemur.common.utils import generate_private_key + + assert generate_private_key('RSA2048') + assert generate_private_key('RSA4096') + + with pytest.raises(Exception): + generate_private_key('ECC') diff --git a/lemur/tests/vectors.py b/lemur/tests/vectors.py index e9b2be91..cc8de62b 100644 --- a/lemur/tests/vectors.py +++ b/lemur/tests/vectors.py @@ -269,34 +269,3 @@ zm3Cn4Ul8DO26w9QS4fmZjmnPOZFXYMWoOR6osHzb62PWQ8FBMqXcdToBV2Q9Iw4 PiFAxlc0tVjlLqQ= -----END CERTIFICATE REQUEST----- """ - - -CSR_PEM_STR = """ ------BEGIN RSA PRIVATE KEY----- -MIIEpgIBAAKCAQEAzZ2PgKflffhIPzwLXe26tuw+7Q72y9dAMrDVEms8u4Cch3yc -hN6Il5tvM4xTc0UN+aobAhaVoPDN+haXT/XyBRvbFV1f8nvmDQZGdmkyYkZ2xK2X -MvKnPtjEXdiShPYNKSEqfYQBHhDy08EqBqn+VajmgXoQai+TijVK+yQMVUyB9aFV -PO8P+sYP1rFIsj6OPVl8Rf9w2TT9VDk2In38SIgEYU2/RLZV6beYjpYnN8P7wV+q -rB/vlySLrNlhPgAZQFVEEwr3Om4KbuM43PJ5ZtwpapACnMZ9rmxHXq/hYYWoDGBr -G2BvvyFqymDektAYxNurTTIjpZiHm4poxHRaZwIDAQABAoIBAQCm5MwVBrKtI/ko -colbbVoPngSZkHrcC9SNEKFyON7r5sGm64t0AdjnDgAd3DnkJ1nnm54efMxo/OyD -oRCik6QlZ23VkpwNi2m4iq5o8Iw33rAKhkhizzjXN0V0UxTinYEjMEt348ywZdtj -67c7/4F0cArhb32hYwqjtQwuex0Tofb37Aj5rNPv1n8ytbhz1vriAVZHZEcjdjdn -CSVeblufORQKRzK3wW5nRN721b9gSvEJHfHeNpXQO9X8Yl5tn7UxjoQWXLZK+khv -pawN8BFt7lVLkQR14Nq7bKwuJ6KR1ig698a1Ii8Luyh2BgfIc25ryuzs8fFCioKi -TK/nzMQ5AoGBAPxtbTrkTTNQ03hnfefRVKGwNHPqLhIQpI99FC4yLYYHsUwFmMVR -ccg4aqNUtI0zn1snKC58NICxIPP9c0NuHqBuNuwhuRPINfQfjg/aOpE2QycZcew1 -BQxXH5d3zXWKLpN15kIS2s18/MpNgTFx2Z0EGqLezDXs6JaPJkqg04glAoGBANCG -h106B9hbuTPYCAlTwvaoWnbaxLmtlWzRpqYBuiiBPGvLc545faXkJKb5/zd002kK -wblGrPtCnhTvCtHbTg/KuR/R8EsAriPhpWK+N4hCADJ8SLOxMU5S9O24FEK50ltN -Q84LS2Wo9fWXQhojiBrctn/ws5ngRCfUbhQeA3ybAoGBAOW7vWaUswIZ9GwnXDon -lGuXDxXTslw0k2AXyM8GUdIinCSBD3m9Vt2PItZFWBEOQ2DVMUelOK9LBZ+pMkbT -KMJ/rDKZunQbiacFNOiOhzDzfohOKxV7Z33EqPbUTMRFn4ALFCVcPZA4yWRgx0y1 -vgSd4JQMSzRkyYWFAKd42SuVAoGBAIG84aWQQGdNkin+Y+mhsrCSSE6giDtaE5jz -y7KHapJe7f/HQnUUIee/zUoSSsbvKcW2CpfCsEdXyFEP9PRidOwAXjO9A7s2fiIW -9zY7UQO2xLakevtJ6HppxLfOitSFFqr1pJUik9N5TyZw6JCowLqtzeJGGQhI7z60 -vZRIpDS3AoGBAPYJgNB7EcWj5U39ol+8cofG1kPauoEaildTur5ftzyzjy4DXrOW -sfU/xhUp6EKLGSXEPqeXAWR6ARf1F4U9Ozp6KA93lGSrSY561jKoqhxxOXAf5il2 -p7Fzh2CckUeiGd5el+h2WUCOcgtlPlfRyV/Mlvx1H0gFieGucXTP23Ox ------END RSA PRIVATE KEY----- -""" diff --git a/setup.cfg b/setup.cfg index f5761d54..d7e6692c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -[pytest] +[tool:pytest] python_files=test*.py addopts=--tb=native -p no:doctest norecursedirs=bin dist docs htmlcov script hooks node_modules .* {args}