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
This commit is contained in:
Neil Schelly
2017-01-27 15:31:29 -05:00
committed by kevgliss
parent 4af871f408
commit f13a3505f3
8 changed files with 326 additions and 217 deletions

View File

@ -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):
"""

View File

@ -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,