diff options
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/errors.py | 18 | ||||
-rw-r--r-- | ipalib/plugins/service.py | 50 | ||||
-rw-r--r-- | ipalib/util.py | 22 |
3 files changed, 78 insertions, 12 deletions
diff --git a/ipalib/errors.py b/ipalib/errors.py index 0d1304e0..42d43ce6 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -1204,7 +1204,7 @@ class CertificateError(ExecutionError): errno = 4300 -class CertificateOperationError(ExecutionError): +class CertificateOperationError(CertificateError): """ **4301** Raised when a certificate operation cannot be completed @@ -1220,6 +1220,22 @@ class CertificateOperationError(ExecutionError): errno = 4301 format = _('Certificate operation cannot be completed: %(error)s') +class CertificateFormatError(CertificateError): + """ + **4302** Raised when a certificate is badly formatted + + For example: + + >>> raise CertificateFormatError(error=u'improperly formated DER-encoded certificate') + Traceback (most recent call last): + ... + CertificateFormatError: improperly formated DER-encoded certificate + + """ + + errno = 4302 + format = _('Certificate format error: %(error)s') + class MutuallyExclusiveError(ExecutionError): """ diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py index 50e8d54f..d226f95a 100644 --- a/ipalib/plugins/service.py +++ b/ipalib/plugins/service.py @@ -75,6 +75,7 @@ from ipalib import Str, Flag, Bytes from ipalib.plugins.baseldap import * from ipalib import x509 from ipalib import _, ngettext +from ipalib import util from nss.error import NSPRError @@ -130,10 +131,41 @@ def validate_certificate(ugettext, cert): """ For now just verify that it is properly base64-encoded. """ + if util.isvalid_base64(cert): + try: + base64.b64decode(cert) + except Exception, e: + raise errors.Base64DecodeError(reason=str(e)) + else: + # We'll assume this is DER data + pass + +def normalize_certificate(cert): + """ + Incoming certificates should be DER-encoded. + + Note that this can't be a normalizer on the Param because only unicode + variables are normalized. + """ + if util.isvalid_base64(cert): + try: + cert = base64.b64decode(cert) + except Exception, e: + raise errors.Base64DecodeError(reason=str(e)) + + # At this point we should have a certificate, either because the data + # was base64-encoded and now its not or it came in as DER format. + # Let's decode it and see. Fetching the serial number will pass the + # certificate through the NSS DER parser. try: - base64.b64decode(cert) - except Exception, e: - raise errors.Base64DecodeError(reason=str(e)) + serial = unicode(x509.get_serial_number(cert, x509.DER)) + except NSPRError, nsprerr: + if nsprerr.errno == -8183: # SEC_ERROR_BAD_DER + raise errors.CertificateFormatError(error='improperly formatted DER-encoded certificate') + else: + raise errors.CertificateFormatError(error=str(nsprerr)) + + return cert class service(LDAPObject): @@ -196,13 +228,9 @@ class service_add(LDAPCreate): except errors.NotFound: raise errors.NotFound(reason="The host '%s' does not exist to add a service to." % hostname) - cert = entry_attrs.get('usercertificate') + cert = options.get('usercertificate') if cert: - cert = cert[0] - # FIXME: should be in a normalizer: need to fix normalizers - # to work on non-unicode data - entry_attrs['usercertificate'] = base64.b64decode(cert) - # FIXME: shouldn't we request signing at this point? + entry_attrs['usercertificate'] = normalize_certificate(cert) if not options.get('force', False): # We know the host exists if we've gotten this far but we @@ -273,6 +301,7 @@ class service_mod(LDAPUpdate): def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): if 'usercertificate' in options: cert = options.get('usercertificate') + cert = normalize_certificate(cert) if cert: (dn, entry_attrs_old) = ldap.get_entry(dn, ['usercertificate']) if 'usercertificate' in entry_attrs_old: @@ -281,8 +310,7 @@ class service_mod(LDAPUpdate): x509.get_serial_number(entry_attrs_old['usercertificate'][0], x509.DER) ) raise errors.GenericError(format=fmt) - # FIXME: should be in normalizer; see service_add - entry_attrs['usercertificate'] = base64.b64decode(cert) + entry_attrs['usercertificate'] = cert else: entry_attrs['usercertificate'] = None return dn diff --git a/ipalib/util.py b/ipalib/util.py index 6bd1da54..1803e65a 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -26,6 +26,7 @@ import imp import logging import time import socket +import re from types import NoneType from ipalib import errors @@ -148,3 +149,24 @@ def validate_host_dns(log, fqdn): log.debug( 'IPA: found %d records for %s' % (len(rs), fqdn) ) + +def isvalid_base64(data): + """ + Validate the incoming data as valid base64 data or not. + + The character set must only include of a-z, A-Z, 0-9, + or / and + be padded with = to be a length divisible by 4 (so only 0-2 =s are + allowed). Its length must be divisible by 4. White space is + not significant so it is removed. + + This doesn't guarantee we have a base64-encoded value, just that it + fits the base64 requirements. + """ + + data = ''.join(data.split()) + + if len(data) % 4 > 0 or \ + re.match('^[a-zA-Z0-9\+\/]+\={0,2}$', data) is None: + return False + else: + return True |