From f13a3505f3c6af94deda77e0c8a54e5d98d1f067 Mon Sep 17 00:00:00 2001 From: Neil Schelly Date: Fri, 27 Jan 2017 15:31:29 -0500 Subject: [PATCH] X509 extensions issue#646 (#666) * Allowing that create_csr can be called with an additional flag in the csr_config to adjust the BasicConstraints for a CA. * If there are no SANs, skip adding a blank list of SANs. * Adding handling for all the extended key usage, key usage, and subject key identifier extensions. * Fixing lint checks. I was overly verbose. * This implements marshalling of the certificate extensions into x509 ExtensionType objects in the schema validation code. * Will create x509 ExtensionType objects in the schema validation stage * Allows errors parsing incoming options to bubble up to the requestor as ValidationErrors. * Cleans up create_csr a lot in the certificates/service.py * Makes BasicConstraints _just another extension_, rather than a hard-coded one * Adds BasicConstraints option for path_length to the UI for creating an authority * Removes SAN types which cannot be handled from the UI for authorities and certificates. * Fixes Certificate() object model so that it doesn't just hard-code only SAN records in the extensions property and actually returns the extensions how you expect to see them. Since Lemur is focused on using these data in the "CSR" phase of things, extensions that don't get populated until signing will be in dict() form.* Trying out schema validation of extensions --- lemur/certificates/models.py | 42 ++- lemur/certificates/service.py | 84 ++---- lemur/common/fields.py | 240 ++++++++++++++++++ lemur/schemas.py | 55 +--- .../authorities/authority/extensions.tpl.html | 12 +- .../app/angular/authorities/services.js | 9 + .../certificates/certificate/options.tpl.html | 2 +- lemur/tests/test_certificates.py | 99 +------- 8 files changed, 326 insertions(+), 217 deletions(-) diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 94c841f9..e802f343 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -9,6 +9,7 @@ import arrow from flask import current_app +from cryptography import x509 from cryptography.hazmat.primitives.asymmetric import rsa from sqlalchemy.orm import relationship @@ -229,16 +230,39 @@ class Certificate(db.Model): @property def extensions(self): - # TODO pull the OU, O, CN, etc + other extensions. - names = [{'name_type': 'DNSName', 'value': x.name} for x in self.domains] + return_extensions = {} + cert = lemur.common.utils.parse_certificate(self.body) + for extension in cert.extensions: + if isinstance(extension, x509.BasicConstraints): + return_extensions['basic_constraints'] = extension + elif isinstance(extension, x509.SubjectAlternativeName): + return_extensions['sub_alt_names'] = extension + elif isinstance(extension, x509.ExtendedKeyUsage): + return_extensions['extended_key_usage'] = extension + elif isinstance(extension, x509.KeyUsage): + return_extensions['key_usage'] = extension + elif isinstance(extension, x509.SubjectKeyIdentifier): + return_extensions['subject_key_identifier'] = {'include_ski': True} + elif isinstance(extension, x509.AuthorityInformationAccess): + return_extensions['certificate_info_access'] = {'include_aia': True} + elif isinstance(extension, x509.AuthorityKeyIdentifier): + aki = { + 'use_key_identifier': False, + 'use_authority_cert': False + } + if extension.key_identifier: + aki['use_key_identifier'] = True + if extension.authority_cert_issuer: + aki['use_authority_cert'] = True + return_extensions['authority_key_identifier'] = aki + elif isinstance(extension, x509.CRLDistributionPoints): + # FIXME: Don't support CRLDistributionPoints yet https://github.com/Netflix/lemur/issues/662 + pass + else: + # FIXME: Not supporting custom OIDs yet. https://github.com/Netflix/lemur/issues/665 + pass - extensions = { - 'sub_alt_names': { - 'names': names - } - } - - return extensions + return return_extensions def get_arn(self, account_number): """ diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index 2e4b5ddc..e03d67a0 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -345,65 +345,23 @@ def create_csr(**csr_config): x509.NameAttribute(x509.OID_EMAIL_ADDRESS, csr_config['owner']) ])) - builder = builder.add_extension( - x509.BasicConstraints(ca=False, path_length=None), critical=True, - ) + extensions = csr_config.get('extensions', {}) + critical_extensions = ['basic_constraints', 'sub_alt_names', 'key_usage'] + noncritical_extensions = ['extended_key_usage'] + for k, v in extensions.items(): + if k in critical_extensions and v: + current_app.logger.debug("Add CExt: {0} {1}".format(k, v)) + builder = builder.add_extension(v, critical=True) + if k in noncritical_extensions and v: + current_app.logger.debug("Add Ext: {0} {1}".format(k, v)) + builder = builder.add_extension(v, critical=False) - if csr_config.get('extensions'): - for k, v in csr_config.get('extensions', {}).items(): - if k == 'sub_alt_names': - # map types to their x509 objects - general_names = [] - for name in v['names']: - if name['name_type'] == 'DNSName': - general_names.append(x509.DNSName(name['value'])) - - builder = builder.add_extension( - x509.SubjectAlternativeName(general_names), critical=True - ) - - # TODO support more CSR options, none of the authority plugins currently support these options - # builder.add_extension( - # x509.KeyUsage( - # digital_signature=digital_signature, - # content_commitment=content_commitment, - # key_encipherment=key_enipherment, - # data_encipherment=data_encipherment, - # key_agreement=key_agreement, - # key_cert_sign=key_cert_sign, - # crl_sign=crl_sign, - # encipher_only=enchipher_only, - # decipher_only=decipher_only - # ), critical=True - # ) - # - # # we must maintain our own list of OIDs here - # builder.add_extension( - # x509.ExtendedKeyUsage( - # server_authentication=server_authentication, - # email= - # ) - # ) - # - # builder.add_extension( - # x509.AuthorityInformationAccess() - # ) - # - # builder.add_extension( - # x509.AuthorityKeyIdentifier() - # ) - # - # builder.add_extension( - # x509.SubjectKeyIdentifier() - # ) - # - # builder.add_extension( - # x509.CRLDistributionPoints() - # ) - # - # builder.add_extension( - # x509.ObjectIdentifier(oid) - # ) + ski = extensions.get('subject_key_identifier', {}) + if ski.get('include_ski', False): + builder = builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()), + critical=False + ) request = builder.sign( private_key, hashes.SHA256(), default_backend() @@ -500,14 +458,6 @@ def get_certificate_primitives(certificate): certificate via `create`. """ start, end = calculate_reissue_range(certificate.not_before, certificate.not_after) - names = [{'name_type': 'DNSName', 'value': x.name} for x in certificate.domains] - - # TODO pull additional extensions - extensions = { - 'sub_alt_names': { - 'names': names - } - } return dict( authority=certificate.authority, @@ -517,7 +467,7 @@ def get_certificate_primitives(certificate): validity_end=end, destinations=certificate.destinations, roles=certificate.roles, - extensions=extensions, + extensions=certificate.extensions, owner=certificate.owner, organization=certificate.organization, organizational_unit=certificate.organizational_unit, diff --git a/lemur/common/fields.py b/lemur/common/fields.py index 3030cb0b..c15c160c 100644 --- a/lemur/common/fields.py +++ b/lemur/common/fields.py @@ -3,6 +3,9 @@ import warnings from datetime import datetime as dt from marshmallow.fields import Field from marshmallow import utils +from cryptography import x509 +from marshmallow.exceptions import ValidationError +import ipaddress class ArrowDateTime(Field): @@ -91,3 +94,240 @@ class ArrowDateTime(Field): warnings.warn('It is recommended that you install python-dateutil ' 'for improved datetime deserialization.') raise self.fail('invalid') + + +class KeyUsageExtension(Field): + """An x509.KeyUsage ExtensionType object + + Dict of KeyUsage names/values are deserialized into an x509.KeyUsage object + and back. + + :param kwargs: The same keyword arguments that :class:`Field` receives. + + """ + + def _serialize(self, value, attr, obj): + return { + 'useDigitalSignature': value.digital_signature, + 'useNonRepudiation': value.content_commitment, + 'useKeyEncipherment': value.key_encipherment, + 'useDataEncipherment': value.data_encipherment, + 'useKeyAgreement': value.key_agreement, + 'useKeyCertSign': value.key_cert_sign, + 'useCrlSign': value.crl_sign, + 'useEncipherOnly': value._encipher_only, + 'useDecipherOnly': value._decipher_only + } + + def _deserialize(self, value, attr, data): + keyusages = { + 'digital_signature': False, + 'content_commitment': False, + 'key_encipherment': False, + 'data_encipherment': False, + 'key_agreement': False, + 'key_cert_sign': False, + 'crl_sign': False, + 'encipher_only': False, + 'decipher_only': False + } + for k, v in value.items(): + if k == 'useDigitalSignature': + keyusages['digital_signature'] = v + if k == 'useNonRepudiation': + keyusages['content_commitment'] = v + if k == 'useKeyEncipherment': + keyusages['key_encipherment'] = v + if k == 'useDataEncipherment': + keyusages['data_encipherment'] = v + if k == 'useKeyCertSign': + keyusages['key_cert_sign'] = v + if k == 'useCrlSign': + keyusages['crl_sign'] = v + if k == 'useEncipherOnly' and v: + keyusages['encipher_only'] = True + keyusages['key_agreement'] = True + if k == 'useDecipherOnly' and v: + keyusages['decipher_only'] = True + keyusages['key_agreement'] = True + + if keyusages['encipher_only'] and keyusages['decipher_only']: + raise ValidationError('A certificate cannot have both Encipher Only and Decipher Only Extended Key Usages.') + + return x509.KeyUsage( + digital_signature=keyusages['digital_signature'], + content_commitment=keyusages['content_commitment'], + key_encipherment=keyusages['key_encipherment'], + data_encipherment=keyusages['data_encipherment'], + key_agreement=keyusages['key_agreement'], + key_cert_sign=keyusages['key_cert_sign'], + crl_sign=keyusages['crl_sign'], + encipher_only=keyusages['encipher_only'], + decipher_only=keyusages['decipher_only'] + ) + + +class ExtendedKeyUsageExtension(Field): + """An x509.ExtendedKeyUsage ExtensionType object + + Dict of ExtendedKeyUsage names/values are deserialized into an x509.ExtendedKeyUsage object + and back. + + :param kwargs: The same keyword arguments that :class:`Field` receives. + + """ + + def _serialize(self, value, attr, obj): + usages = value._usages + usage_list = {} + for usage in usages: + if usage.dotted_string == x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH: + usage_list["useClientAuthentication"] = True + if usage.dotted_string == x509.oid.ExtendedKeyUsageOID.SERVER_AUTH: + usage_list["useServerAuthentication"] = True + if usage.dotted_string == x509.oid.ExtendedKeyUsageOID.CODE_SIGNING: + usage_list["useCodeSigning"] = True + if usage.dotted_string == x509.oid.ExtendedKeyUsageOID.EMAIL_PROTECTION: + usage_list["useEmailProtection"] = True + if usage.dotted_string == x509.oid.ExtendedKeyUsageOID.TIME_STAMPING: + usage_list["useTimestamping"] = True + if usage.dotted_string == x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING: + usage_list["useOCSPSigning"] = True + if usage.dotted_string == "1.3.6.1.5.5.7.3.14": + usage_list["useEapOverLAN"] = True + if usage.dotted_string == "1.3.6.1.5.5.7.3.13": + usage_list["useEapOverPPP"] = True + if usage.dotted_string == "1.3.6.1.4.1.311.20.2.2": + usage_list["useSmartCardLogon"] = True + + return usage_list + + def _deserialize(self, value, attr, data): + usage_oids = [] + for k, v in value.items(): + if k == 'useClientAuthentication' and v: + usage_oids.append(x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH) + if k == 'useServerAuthentication' and v: + usage_oids.append(x509.oid.ExtendedKeyUsageOID.SERVER_AUTH) + if k == 'useCodeSigning' and v: + usage_oids.append(x509.oid.ExtendedKeyUsageOID.CODE_SIGNING) + if k == 'useEmailProtection' and v: + usage_oids.append(x509.oid.ExtendedKeyUsageOID.EMAIL_PROTECTION) + if k == 'useTimestamping' and v: + usage_oids.append(x509.oid.ExtendedKeyUsageOID.TIME_STAMPING) + if k == 'useOCSPSigning' and v: + usage_oids.append(x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING) + if k == 'useEapOverLAN' and v: + usage_oids.append(x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.3.14")) + if k == 'useEapOverPPP' and v: + usage_oids.append(x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.3.13")) + if k == 'useSmartCardLogon' and v: + usage_oids.append(x509.oid.ObjectIdentifier("1.3.6.1.4.1.311.20.2.2")) + + return x509.ExtendedKeyUsage(usage_oids) + + +class BasicConstraintsExtension(Field): + """An x509.BasicConstraints ExtensionType object + + Dict of CA boolean and a path_length integer names/values are deserialized into an x509.BasicConstraints object + and back. + + :param kwargs: The same keyword arguments that :class:`Field` receives. + + """ + + def _serialize(self, value, attr, obj): + return {'ca': value.ca(), 'path_length': value.path_length()} + + def _deserialize(self, value, attr, data): + ca = value.get('ca', False) + path_length = value.get('path_length', None) + + if ca: + if not isinstance(path_length, (type(None), int)): + raise ValidationError('A CA certificate path_length (for BasicConstraints) must be None or an integer.') + return x509.BasicConstraints(ca=True, path_length=path_length) + else: + return x509.BasicConstraints(ca=False, path_length=None) + + +class SubjectAlternativeNameExtension(Field): + """An x509.SubjectAlternativeName ExtensionType object + + Dict of CA boolean and a path_length integer names/values are deserialized into an x509.BasicConstraints object + and back. + + :param kwargs: The same keyword arguments that :class:`Field` receives. + + """ + + def _serialize(self, value, attr, obj): + general_names = [] + for name in value._general_names: + value = name.value() + if isinstance(name, x509.DNSName): + name_type = 'DNSName' + if isinstance(name, x509.IPAddress): + name_type = 'IPAddress' + value = str(value) + if isinstance(name, x509.UniformResourceIdentifier): + name_type = 'uniformResourceIdentifier' + if isinstance(name, x509.DirectoryName): + name_type = 'directoryName' + if isinstance(name, x509.RFC822Name): + name_type = 'rfc822Name' + if isinstance(name, x509.RegisteredID): + name_type = 'registeredID' + value = value.dotted_string + general_names.append({'nameType': name_type, 'value': value}) + + return general_names + + def _deserialize(self, value, attr, data): + general_names = [] + for name in value.get('names', []): + if name['nameType'] == 'DNSName': + general_names.append(x509.DNSName(name['value'])) + if name['nameType'] == 'IPAddress': + general_names.append(x509.IPAddress(ipaddress.ip_address(name['value']))) + if name['nameType'] == 'IPNetwork': + general_names.append(x509.IPAddress(ipaddress.ip_network(name['value']))) + if name['nameType'] == 'uniformResourceIdentifier': + general_names.append(x509.UniformResourceIdentifier(name['value'])) + if name['nameType'] == 'directoryName': + # FIXME: Need to parse a string in name['value'] like: + # 'CN=Common Name, O=Org Name, OU=OrgUnit Name, C=US, ST=ST, L=City/emailAddress=person@example.com' + # or + # 'CN=Common Name/O=Org Name/OU=OrgUnit Name/C=US/ST=NH/L=City/emailAddress=person@example.com' + # and turn it into something like: + # x509.Name([ + # x509.NameAttribute(x509.OID_COMMON_NAME, "Common Name"), + # x509.NameAttribute(x509.OID_ORGANIZATION_NAME, "Org Name"), + # x509.NameAttribute(x509.OID_ORGANIZATIONAL_UNIT_NAME, "OrgUnit Name"), + # x509.NameAttribute(x509.OID_COUNTRY_NAME, "US"), + # x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, "NH"), + # x509.NameAttribute(x509.OID_LOCALITY_NAME, "City"), + # x509.NameAttribute(x509.OID_EMAIL_ADDRESS, "person@example.com") + # ] + # general_names.append(x509.DirectoryName(x509.Name(BLAH)))) + pass + if name['nameType'] == 'rfc822Name': + general_names.append(x509.RFC822Name(name['value'])) + if name['nameType'] == 'registeredID': + general_names.append(x509.RegisteredID(x509.ObjectIdentifier(name['value']))) + if name['nameType'] == 'otherName': + # This has two inputs (type and value), so it doesn't fit the mold of the rest of these GeneralName entities. + # general_names.append(x509.OtherName(name['type'], bytes(name['value']), 'utf-8')) + pass + if name['nameType'] == 'x400Address': + # The Python Cryptography library doesn't support x400Address types (yet?) + pass + if name['nameType'] == 'EDIPartyName': + # The Python Cryptography library doesn't support EDIPartyName types (yet?) + pass + + if general_names: + return x509.SubjectAlternativeName(general_names) + else: + return None diff --git a/lemur/schemas.py b/lemur/schemas.py index d6ece2e2..9eff45e9 100644 --- a/lemur/schemas.py +++ b/lemur/schemas.py @@ -9,11 +9,12 @@ """ from sqlalchemy.orm.exc import NoResultFound -from marshmallow import fields, post_load, pre_load, post_dump, validates_schema +from marshmallow import fields, post_load, pre_load, post_dump from marshmallow.exceptions import ValidationError from lemur.common import validators from lemur.common.schema import LemurSchema, LemurInputSchema, LemurOutputSchema +from lemur.common.fields import KeyUsageExtension, ExtendedKeyUsageExtension, BasicConstraintsExtension, SubjectAlternativeNameExtension from lemur.plugins import plugins from lemur.roles.models import Role @@ -166,10 +167,6 @@ class BaseExtensionSchema(LemurSchema): return data -class BasicConstraintsSchema(BaseExtensionSchema): - pass - - class AuthorityKeyIdentifierSchema(BaseExtensionSchema): use_key_identifier = fields.Boolean() use_authority_cert = fields.Boolean() @@ -183,30 +180,6 @@ class CertificateInfoAccessSchema(BaseExtensionSchema): return {'includeAIA': data['include_aia']} -class KeyUsageSchema(BaseExtensionSchema): - use_crl_sign = fields.Boolean() - use_data_encipherment = fields.Boolean() - use_decipher_only = fields.Boolean() - use_encipher_only = fields.Boolean() - use_key_encipherment = fields.Boolean() - use_digital_signature = fields.Boolean() - use_non_repudiation = fields.Boolean() - use_key_agreement = fields.Boolean() - use_key_cert_sign = fields.Boolean() - - -class ExtendedKeyUsageSchema(BaseExtensionSchema): - use_server_authentication = fields.Boolean() - use_client_authentication = fields.Boolean() - use_eap_over_lan = fields.Boolean() - use_eap_over_ppp = fields.Boolean() - use_ocsp_signing = fields.Boolean() - use_smart_card_logon = fields.Boolean() - use_timestamping = fields.Boolean() - use_code_signing = fields.Boolean() - use_email_protection = fields.Boolean() - - class SubjectKeyIdentifierSchema(BaseExtensionSchema): include_ski = fields.Boolean() @@ -215,20 +188,6 @@ class SubjectKeyIdentifierSchema(BaseExtensionSchema): return {'includeSKI': data['include_ski']} -class SubAltNameSchema(BaseExtensionSchema): - name_type = fields.String(validate=validators.sub_alt_type) - value = fields.String() - - @validates_schema - def check_sensitive(self, data): - if data.get('name_type') == 'DNSName': - validators.sensitive_domain(data['value']) - - -class SubAltNamesSchema(BaseExtensionSchema): - names = fields.Nested(SubAltNameSchema, many=True) - - class CustomOIDSchema(BaseExtensionSchema): oid = fields.String() encoding = fields.String(validate=validators.encoding) @@ -237,13 +196,15 @@ class CustomOIDSchema(BaseExtensionSchema): class ExtensionSchema(BaseExtensionSchema): - basic_constraints = fields.Nested(BasicConstraintsSchema) - key_usage = fields.Nested(KeyUsageSchema) - extended_key_usage = fields.Nested(ExtendedKeyUsageSchema) + basic_constraints = BasicConstraintsExtension() + key_usage = KeyUsageExtension() + extended_key_usage = ExtendedKeyUsageExtension() subject_key_identifier = fields.Nested(SubjectKeyIdentifierSchema) - sub_alt_names = fields.Nested(SubAltNamesSchema) + sub_alt_names = SubjectAlternativeNameExtension() authority_key_identifier = fields.Nested(AuthorityKeyIdentifierSchema) certificate_info_access = fields.Nested(CertificateInfoAccessSchema) + # FIXME: Convert custom OIDs to a custom field in fields.py like other Extensions + # FIXME: Remove support in UI for Critical custom extensions https://github.com/Netflix/lemur/issues/665 custom = fields.List(fields.Nested(CustomOIDSchema)) diff --git a/lemur/static/app/angular/authorities/authority/extensions.tpl.html b/lemur/static/app/angular/authorities/authority/extensions.tpl.html index 140eb33e..d5f65ee4 100644 --- a/lemur/static/app/angular/authorities/authority/extensions.tpl.html +++ b/lemur/static/app/angular/authorities/authority/extensions.tpl.html @@ -4,7 +4,7 @@ Subject Alternate Names
- +
@@ -185,6 +185,16 @@
+
+ +
+ +
+
+ ng-options="item for item in ['DNSName', 'IPAddress', 'IPNetwork', 'uniformResourceIdentifier', 'directoryName','rfc822Name', 'registeredID']">
diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index c1c23608..5e35ba69 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -6,6 +6,7 @@ import datetime import arrow from freezegun import freeze_time +from cryptography import x509 from lemur.certificates.views import * # noqa @@ -29,7 +30,7 @@ def test_get_or_increase_name(session, certificate): def test_get_certificate_primitives(certificate): from lemur.certificates.service import get_certificate_primitives - names = [{'name_type': 'DNSName', 'value': x.name} for x in certificate.domains] + names = [x509.DNSName(x.name) for x in certificate.domains] data = { 'common_name': certificate.cn, @@ -37,9 +38,7 @@ def test_get_certificate_primitives(certificate): 'authority': certificate.authority, 'description': certificate.description, 'extensions': { - 'sub_alt_names': { - 'names': names - } + 'sub_alt_names': x509.SubjectAlternativeName(names) }, 'destinations': [], 'roles': [], @@ -171,10 +170,10 @@ def test_certificate_input_with_extensions(client, authority): 'description': 'testtestest', 'extensions': { 'keyUsage': { - 'useKeyEncipherment': True, - 'useDigitalSignature': True + 'digital_signature': True }, 'extendedKeyUsage': { + 'useClientAuthentication': True, 'useServerAuthentication': True }, 'subjectKeyIdentifier': { @@ -245,90 +244,6 @@ def test_certificate_valid_dates(client, authority): assert not errors -def test_sub_alt_name_schema(session): - from lemur.schemas import SubAltNameSchema # SubAltNamesSchema - input_data = {'nameType': 'DNSName', 'value': 'test.example.com'} - - data, errors = SubAltNameSchema().load(input_data) - assert not errors - assert data == {'name_type': 'DNSName', 'value': 'test.example.com'} - - data, errors = SubAltNameSchema().dumps(data) - assert not errors - - input_datas = {'names': [input_data]} - - # data, errors = SubAltNamesSchema().load(input_datas) - # assert not errors - # assert data == {'names': [{'name_type': 'DNSName', 'value': 'test.example.com'}]} - - # data, errors = SubAltNamesSchema().dumps(data) - # assert data == json.dumps(input_datas) - # assert not errors - - input_data = {'nameType': 'CNAME', 'value': 'test.example.com'} - data, errors = SubAltNameSchema().load(input_data) - assert errors - - -def test_key_usage_schema(): - from lemur.schemas import KeyUsageSchema - - input_data = { - 'useCRLSign': True, - 'useDataEncipherment': True, - 'useDecipherOnly': True, - 'useEncipherOnly': True, - 'useKeyEncipherment': True, - 'useDigitalSignature': True, - 'useNonRepudiation': True - } - - data, errors = KeyUsageSchema().load(input_data) - - assert not errors - assert data == { - 'use_crl_sign': True, - 'use_data_encipherment': True, - 'use_decipher_only': True, - 'use_encipher_only': True, - 'use_key_encipherment': True, - 'use_digital_signature': True, - 'use_non_repudiation': True - } - - -def test_extended_key_usage_schema(): - from lemur.schemas import ExtendedKeyUsageSchema - - input_data = { - 'useServerAuthentication': True, - 'useClientAuthentication': True, - 'useEapOverLAN': True, - 'useEapOverPPP': True, - 'useOCSPSigning': True, - 'useSmartCardLogon': True, - 'useTimestamping': True, - 'useCodeSigning': True, - 'useEmailProtection': True - } - - data, errors = ExtendedKeyUsageSchema().load(input_data) - - assert not errors - assert data == { - 'use_server_authentication': True, - 'use_client_authentication': True, - 'use_eap_over_lan': True, - 'use_eap_over_ppp': True, - 'use_ocsp_signing': True, - 'use_smart_card_logon': True, - 'use_timestamping': True, - 'use_code_signing': True, - 'use_email_protection': True - } - - def test_create_basic_csr(client): from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -342,7 +257,7 @@ def test_create_basic_csr(client): location='A place', owner='joe@example.com', key_type='RSA2048', - extensions=dict(names=dict(sub_alt_names=['test.example.com', 'test2.example.com'])) + extensions=dict(names=dict(sub_alt_names=x509.SubjectAlternativeName([x509.DNSName('test.example.com'), x509.DNSName('test2.example.com')]))) ) csr, pem = create_csr(**csr_config) @@ -395,7 +310,7 @@ def test_create_csr(): assert csr assert private_key - extensions = {'sub_alt_names': {'names': [{'name_type': 'DNSName', 'value': 'AnotherCommonName'}]}} + extensions = {'sub_alt_names': x509.SubjectAlternativeName([x509.DNSName('AnotherCommonName')])} csr, private_key = create_csr(owner='joe@example.com', common_name='ACommonName', organization='test', organizational_unit='Meters', country='US', state='CA', location='Here', extensions=extensions, key_type='RSA2048') assert csr