mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2026-10-25 22:50:12 -05:00
x509: use python-cryptography to process certs
Update x509.load_certificate and related functions to return python-cryptography ``Certificate`` objects. Update the call sites accordingly, including removal of NSS initialisation code. Also update GeneralName parsing code to return python-cryptography GeneralName values, for consistency with other code that processes GeneralNames. The new function, `get_san_general_names`, and associated helper functions, can be removed when python-cryptography provides a way to deal with unrecognised critical extensions. Part of: https://fedorahosted.org/freeipa/ticket/6398 Reviewed-By: Jan Cholasta <jcholast@redhat.com> Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
This commit is contained in:
1 parent
c57dc890b2
commit
db116f73fe
16 files changed
+369
-397
No files matched your search
+62
-53
@@ -22,11 +22,11 @@
|
||||
import base64
|
||||
import collections
|
||||
import datetime
|
||||
from operator import attrgetter
|
||||
import os
|
||||
|
||||
import cryptography.x509
|
||||
from nss import nss
|
||||
from nss.error import NSPRError
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
import six
|
||||
|
||||
from ipalib import Command, Str, Int, Flag
|
||||
@@ -224,7 +224,7 @@ def bind_principal_can_manage_cert(cert):
|
||||
"""Check that the bind principal can manage the given cert.
|
||||
|
||||
``cert``
|
||||
An NSS certificate object.
|
||||
A python-cryptography ``Certificate`` object.
|
||||
|
||||
"""
|
||||
bind_principal = kerberos.Principal(getattr(context, 'principal'))
|
||||
@@ -233,9 +233,14 @@ def bind_principal_can_manage_cert(cert):
|
||||
|
||||
hostname = bind_principal.hostname
|
||||
|
||||
# If we have a hostname we want to verify that the subject
|
||||
# of the certificate matches it.
|
||||
return hostname == cert.subject.common_name #pylint: disable=E1101
|
||||
# Verify that hostname matches subject of cert.
|
||||
# We check the "most-specific" CN value.
|
||||
cns = cert.subject.get_attributes_for_oid(
|
||||
cryptography.x509.oid.NameOID.COMMON_NAME)
|
||||
if len(cns) == 0:
|
||||
return False # no CN in subject
|
||||
else:
|
||||
return hostname == cns[-1].value
|
||||
|
||||
|
||||
class BaseCertObject(Object):
|
||||
@@ -370,30 +375,27 @@ class BaseCertObject(Object):
|
||||
attribute.
|
||||
|
||||
"""
|
||||
cert = obj.get('certificate')
|
||||
if cert is not None:
|
||||
cert = x509.load_certificate(cert)
|
||||
obj['subject'] = DN(unicode(cert.subject))
|
||||
obj['issuer'] = DN(unicode(cert.issuer))
|
||||
obj['serial_number'] = cert.serial_number
|
||||
obj['valid_not_before'] = unicode(cert.valid_not_before_str)
|
||||
obj['valid_not_after'] = unicode(cert.valid_not_after_str)
|
||||
if 'certificate' in obj:
|
||||
cert = x509.load_certificate(obj['certificate'])
|
||||
obj['subject'] = DN(cert.subject)
|
||||
obj['issuer'] = DN(cert.issuer)
|
||||
obj['serial_number'] = cert.serial
|
||||
obj['valid_not_before'] = x509.format_datetime(
|
||||
cert.not_valid_before)
|
||||
obj['valid_not_after'] = x509.format_datetime(
|
||||
cert.not_valid_after)
|
||||
if full:
|
||||
obj['md5_fingerprint'] = x509.to_hex_with_colons(
|
||||
nss.md5_digest(cert.der_data))
|
||||
cert.fingerprint(hashes.MD5()))
|
||||
obj['sha1_fingerprint'] = x509.to_hex_with_colons(
|
||||
nss.sha1_digest(cert.der_data))
|
||||
cert.fingerprint(hashes.SHA1()))
|
||||
|
||||
try:
|
||||
ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME)
|
||||
general_names = x509.decode_generalnames(ext_san.value)
|
||||
except KeyError:
|
||||
general_names = []
|
||||
general_names = x509.process_othernames(
|
||||
x509.get_san_general_names(cert))
|
||||
|
||||
for name_type, _desc, name, der_name in general_names:
|
||||
for gn in general_names:
|
||||
try:
|
||||
self._add_san_attribute(
|
||||
obj, full, name_type, name, der_name)
|
||||
self._add_san_attribute(obj, full, gn)
|
||||
except Exception:
|
||||
# Invalid GeneralName (i.e. not a valid X.509 cert);
|
||||
# don't fail but log something about it
|
||||
@@ -404,45 +406,52 @@ class BaseCertObject(Object):
|
||||
if serial_number is not None:
|
||||
obj['serial_number_hex'] = u'0x%X' % serial_number
|
||||
|
||||
|
||||
def _add_san_attribute(
|
||||
self, obj, full, name_type, name, der_name):
|
||||
def _add_san_attribute(self, obj, full, gn):
|
||||
name_type_map = {
|
||||
nss.certRFC822Name: 'san_rfc822name',
|
||||
nss.certDNSName: 'san_dnsname',
|
||||
nss.certX400Address: 'san_x400address',
|
||||
nss.certDirectoryName: 'san_directoryname',
|
||||
nss.certEDIPartyName: 'san_edipartyname',
|
||||
nss.certURI: 'san_uri',
|
||||
nss.certIPAddress: 'san_ipaddress',
|
||||
nss.certRegisterID: 'san_oid',
|
||||
(nss.certOtherName, x509.SAN_UPN): 'san_other_upn',
|
||||
(nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME): 'san_other_kpn',
|
||||
cryptography.x509.RFC822Name:
|
||||
('san_rfc822name', attrgetter('value')),
|
||||
cryptography.x509.DNSName: ('san_dnsname', attrgetter('value')),
|
||||
# cryptography.x509.???: 'san_x400address',
|
||||
cryptography.x509.DirectoryName:
|
||||
('san_directoryname', lambda x: DN(x.value)),
|
||||
# cryptography.x509.???: 'san_edipartyname',
|
||||
cryptography.x509.UniformResourceIdentifier:
|
||||
('san_uri', attrgetter('value')),
|
||||
cryptography.x509.IPAddress:
|
||||
('san_ipaddress', attrgetter('value')),
|
||||
cryptography.x509.RegisteredID:
|
||||
('san_oid', attrgetter('value.dotted_string')),
|
||||
cryptography.x509.OtherName: ('san_other', _format_othername),
|
||||
x509.UPN: ('san_other_upn', attrgetter('name')),
|
||||
x509.KRB5PrincipalName: ('san_other_kpn', attrgetter('name')),
|
||||
}
|
||||
default_attrs = {
|
||||
'san_rfc822name', 'san_dnsname', 'san_other_upn', 'san_other_kpn',
|
||||
}
|
||||
|
||||
attr_name = name_type_map.get(name_type, 'san_other')
|
||||
if type(gn) not in name_type_map:
|
||||
return
|
||||
|
||||
attr_name, format_name = name_type_map[type(gn)]
|
||||
|
||||
if full or attr_name in default_attrs:
|
||||
if attr_name != 'san_other':
|
||||
name_formatted = name
|
||||
else:
|
||||
# display as "OID : b64(DER)"
|
||||
name_formatted = u'{}:{}'.format(
|
||||
name_type[1], base64.b64encode(der_name))
|
||||
attr_value = self.params[attr_name].type(name_formatted)
|
||||
attr_value = self.params[attr_name].type(format_name(gn))
|
||||
obj.setdefault(attr_name, []).append(attr_value)
|
||||
|
||||
if full and attr_name.startswith('san_other_'):
|
||||
# also include known otherName in generic otherName attribute
|
||||
name_formatted = u'{}:{}'.format(
|
||||
name_type[1], base64.b64encode(der_name))
|
||||
attr_value = self.params['san_other'].type(name_formatted)
|
||||
attr_value = self.params['san_other'].type(_format_othername(gn))
|
||||
obj.setdefault('san_other', []).append(attr_value)
|
||||
|
||||
|
||||
def _format_othername(on):
|
||||
"""Format a python-cryptography OtherName for display."""
|
||||
return u'{}:{}'.format(
|
||||
on.type_id.dotted_string,
|
||||
base64.b64encode(on.value)
|
||||
)
|
||||
|
||||
|
||||
class BaseCertMethod(Method):
|
||||
def get_options(self):
|
||||
yield self.obj.params['cacn'].clone(query=True)
|
||||
@@ -909,7 +918,7 @@ class cert_show(Retrieve, CertMethod, VirtualCommand):
|
||||
raise acierr # pylint: disable=E0702
|
||||
|
||||
ca_obj = api.Command.ca_show(options['cacn'])['result']
|
||||
if DN(unicode(cert.issuer)) != DN(ca_obj['ipacasubjectdn'][0]):
|
||||
if DN(cert.issuer) != DN(ca_obj['ipacasubjectdn'][0]):
|
||||
# DN of cert differs from what we requested
|
||||
raise errors.NotFound(
|
||||
reason=_("Certificate with serial number %(serial)s "
|
||||
@@ -1132,16 +1141,16 @@ class cert_find(Search, CertMethod):
|
||||
|
||||
def _get_cert_key(self, cert):
|
||||
try:
|
||||
nss_cert = x509.load_certificate(cert, x509.DER)
|
||||
except NSPRError as e:
|
||||
cert_obj = x509.load_certificate(cert, x509.DER)
|
||||
except ValueError as e:
|
||||
message = messages.SearchResultTruncated(
|
||||
reason=_("failed to load certificate: %s") % e,
|
||||
)
|
||||
self.add_message(message)
|
||||
|
||||
raise ValueError("failed to load certificate")
|
||||
raise
|
||||
|
||||
return (DN(unicode(nss_cert.issuer)), nss_cert.serial_number)
|
||||
return (DN(cert_obj.issuer), cert_obj.serial)
|
||||
|
||||
def _get_cert_obj(self, cert, all, raw, pkey_only):
|
||||
obj = {'certificate': unicode(base64.b64encode(cert))}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
import six
|
||||
|
||||
from ipalib import api, errors, messages
|
||||
@@ -49,8 +50,6 @@ from ipalib import output
|
||||
from ipapython import kerberos
|
||||
from ipapython.dn import DN
|
||||
|
||||
import nss.nss as nss
|
||||
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
@@ -268,16 +267,17 @@ def set_certificate_attrs(entry_attrs):
|
||||
cert = entry_attrs['usercertificate']
|
||||
cert = x509.normalize_certificate(cert)
|
||||
cert = x509.load_certificate(cert, datatype=x509.DER)
|
||||
entry_attrs['subject'] = unicode(cert.subject)
|
||||
entry_attrs['serial_number'] = unicode(cert.serial_number)
|
||||
entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial_number
|
||||
entry_attrs['issuer'] = unicode(cert.issuer)
|
||||
entry_attrs['valid_not_before'] = unicode(cert.valid_not_before_str)
|
||||
entry_attrs['valid_not_after'] = unicode(cert.valid_not_after_str)
|
||||
entry_attrs['subject'] = unicode(DN(cert.subject))
|
||||
entry_attrs['serial_number'] = unicode(cert.serial)
|
||||
entry_attrs['serial_number_hex'] = u'0x%X' % cert.serial
|
||||
entry_attrs['issuer'] = unicode(DN(cert.issuer))
|
||||
entry_attrs['valid_not_before'] = x509.format_datetime(
|
||||
cert.not_valid_before)
|
||||
entry_attrs['valid_not_after'] = x509.format_datetime(cert.not_valid_after)
|
||||
entry_attrs['md5_fingerprint'] = x509.to_hex_with_colons(
|
||||
nss.md5_digest(cert.der_data))
|
||||
cert.fingerprint(hashes.MD5()))
|
||||
entry_attrs['sha1_fingerprint'] = x509.to_hex_with_colons(
|
||||
nss.sha1_digest(cert.der_data))
|
||||
cert.fingerprint(hashes.SHA1()))
|
||||
|
||||
def check_required_principal(ldap, principal):
|
||||
"""
|
||||
|
||||
Reference in new issue
Block a user