From b86e381e20b3fc84839a5c9952717564219efa00 Mon Sep 17 00:00:00 2001 From: Javier Ramos Date: Tue, 26 Mar 2019 15:09:08 +0100 Subject: [PATCH 1/3] Parse SubjectAlternativeNames from CSR into Lemur Certificate --- lemur/certificates/schemas.py | 18 +++++++++++++++--- lemur/certificates/utils.py | 24 +++++++++++------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index 78217de0..5528e168 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -112,10 +112,22 @@ class CertificateInputSchema(CertificateCreationSchema): if data.get('replacements'): data['replaces'] = data['replacements'] # TODO remove when field is deprecated if data.get('csr'): - dns_names = cert_utils.get_dns_names_from_csr(data['csr']) - if not data['extensions']['subAltNames']['names']: + csr_sans = cert_utils.get_sans_from_csr(data['csr']) + if not data.get('extensions'): + data['extensions'] = { + 'subAltNames': { + 'names': [] + } + } + elif not data['extensions'].get('subAltNames'): + data['extensions']['subAltNames'] = { + 'subAltNames': { + 'names': [] + } + } + elif not data['extensions']['subAltNames'].get('names'): data['extensions']['subAltNames']['names'] = [] - data['extensions']['subAltNames']['names'] += dns_names + data['extensions']['subAltNames']['names'] += csr_sans return missing.convert_validity_years(data) diff --git a/lemur/certificates/utils.py b/lemur/certificates/utils.py index 933fe45e..800e1201 100644 --- a/lemur/certificates/utils.py +++ b/lemur/certificates/utils.py @@ -14,14 +14,14 @@ from cryptography.hazmat.backends import default_backend from marshmallow.exceptions import ValidationError -def get_dns_names_from_csr(data): +def get_sans_from_csr(data): """ - Fetches DNSNames from CSR. - Potentially extendable to any kind of SubjectAlternativeName + Fetches SubjectAlternativeNames from CSR. + Works with any kind of SubjectAlternativeName :param data: PEM-encoded string with CSR - :return: + :return: List of LemurAPI-compatible subAltNames """ - dns_names = [] + sub_alt_names = [] try: request = x509.load_pem_x509_csr(data.encode('utf-8'), default_backend()) except Exception: @@ -29,14 +29,12 @@ def get_dns_names_from_csr(data): try: alt_names = request.extensions.get_extension_for_class(x509.SubjectAlternativeName) - - for name in alt_names.value.get_values_for_type(x509.DNSName): - dns_name = { - 'nameType': 'DNSName', - 'value': name - } - dns_names.append(dns_name) + for alt_name in alt_names.value: + sub_alt_names.append({ + 'nameType': type(alt_name).__name__, + 'value': alt_name.value + }) except x509.ExtensionNotFound: pass - return dns_names + return sub_alt_names From d80a6bb405be98b6cfffbbd04077d4fe82b597ef Mon Sep 17 00:00:00 2001 From: Javier Ramos Date: Wed, 27 Mar 2019 13:47:05 +0100 Subject: [PATCH 2/3] Added tests for CSR parsing into CertificateInputSchema --- lemur/certificates/schemas.py | 6 ++---- lemur/tests/test_certificates.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index 5528e168..1352f796 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -121,11 +121,9 @@ class CertificateInputSchema(CertificateCreationSchema): } elif not data['extensions'].get('subAltNames'): data['extensions']['subAltNames'] = { - 'subAltNames': { - 'names': [] - } + 'names': [] } - elif not data['extensions']['subAltNames'].get('names'): + elif not data['extensions']['subAltNames']['names']: data['extensions']['subAltNames']['names'] = [] data['extensions']['subAltNames']['names'] += csr_sans return missing.convert_validity_years(data) diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 4013d367..1d7bf65d 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -284,6 +284,31 @@ def test_certificate_input_with_extensions(client, authority): assert not errors +def test_certificate_input_schema_parse_csr(authority): + from lemur.certificates.schemas import CertificateInputSchema + + test_san_dns = 'foobar.com' + extensions = {'sub_alt_names': {'names': x509.SubjectAlternativeName([x509.DNSName(test_san_dns)])}} + csr, private_key = create_csr(owner='joe@example.com', common_name='ACommonName', organization='test', + organizational_unit='Meters', country='NL', state='Noord-Holland', location='Amsterdam', + key_type='RSA2048', extensions=extensions) + + input_data = { + 'commonName': 'test.example.com', + 'owner': 'jim@example.com', + 'authority': {'id': authority.id}, + 'description': 'testtestest', + 'csr': csr, + 'dnsProvider': None, + } + + data, errors = CertificateInputSchema().load(input_data) + + for san in data['extensions']['sub_alt_names']['names']: + assert san.value == test_san_dns + assert not errors + + def test_certificate_out_of_range_date(client, authority): from lemur.certificates.schemas import CertificateInputSchema input_data = { From 58dd424de8781e852fbb4fac102273746120e5e2 Mon Sep 17 00:00:00 2001 From: Javier Ramos Date: Wed, 17 Apr 2019 18:33:52 +0200 Subject: [PATCH 3/3] Prevent potential NoneType not subscriptable Fix when data['extensions']['subAltNames']['names'] is none --- lemur/certificates/schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index 1352f796..b807c760 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -123,7 +123,7 @@ class CertificateInputSchema(CertificateCreationSchema): data['extensions']['subAltNames'] = { 'names': [] } - elif not data['extensions']['subAltNames']['names']: + elif not data['extensions']['subAltNames'].get('names'): data['extensions']['subAltNames']['names'] = [] data['extensions']['subAltNames']['names'] += csr_sans return missing.convert_validity_years(data)