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:
@ -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):
|
||||
"""
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user