diff options
author | Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> | 2012-01-04 12:17:26 +0100 |
---|---|---|
committer | Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> | 2012-01-04 12:17:26 +0100 |
commit | 84b58bf324226ac321cee8726bb46d864318cb9e (patch) | |
tree | 5d8248f1cbb7c45de71180298ca24d6ac18ea137 | |
parent | 9cf635005cd404c095d59f742fd44c7f9cf989e0 (diff) | |
download | pykolab-84b58bf324226ac321cee8726bb46d864318cb9e.tar.gz |
Add the framework for entitlements (there's no enforcement yet, but initialization fails if data does not check out)
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | configure.ac | 10 | ||||
-rw-r--r-- | pykolab/Makefile.am | 6 | ||||
-rw-r--r-- | pykolab/conf/__init__.py | 11 | ||||
-rw-r--r-- | pykolab/conf/entitlement.py | 265 |
5 files changed, 297 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index 1e3db21..0fbe791 100644 --- a/Makefile.am +++ b/Makefile.am @@ -157,10 +157,15 @@ clean: execdir = $(sbindir) install-exec-local: - mkdir -p $(DESTDIR)/$(sbindir) $(DESTDIR)/$(bindir) \ + mkdir -p $(DESTDIR)/$(sbindir) \ + $(DESTDIR)/$(bindir) \ + $(DESTDIR)/$(sysconfdir)/kolab \ $(DESTDIR)/$(libexecdir)/postfix \ $(DESTDIR)/$(localstatedir)/lib/kolab \ $(DESTDIR)/$(localstatedir)/log/kolab +if ENTERPRISE + mkdir -p $(DESTDIR)/$(sysconfdir)/kolab/entitlement.d +endif $(INSTALL) -p -m 755 conf.py $(DESTDIR)/$(sbindir)/kolab-conf $(INSTALL) -p -m 755 kolab.py $(DESTDIR)/$(sbindir)/kolab $(INSTALL) -p -m 755 kolabd.py $(DESTDIR)/$(sbindir)/kolabd diff --git a/configure.ac b/configure.ac index 8d219fe..209f138 100644 --- a/configure.ac +++ b/configure.ac @@ -15,6 +15,16 @@ AM_GLIB_GNU_GETTEXT AC_PROG_INTLTOOL AC_PROG_LN_S +AC_ARG_ENABLE([enterprise], + [ --enable-enterprise Turn on entitlements, compile binary blob], + [case "${enableval}" in + yes) enterprise=true ;; + no) enterprise=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enterprise]) ;; + esac], [enterprise=false]) + +AM_CONDITIONAL([ENTERPRISE], [test "${enterprise}" = "true"]) + AC_SUBST(DATESTAMP,`date +"%a %b %d %Y"`) AC_CONFIG_FILES([ diff --git a/pykolab/Makefile.am b/pykolab/Makefile.am index 97fdec7..83a4ec3 100644 --- a/pykolab/Makefile.am +++ b/pykolab/Makefile.am @@ -22,6 +22,10 @@ pykolab_conf_PYTHON = \ conf/defaults.py \ conf/__init__.py +if ENTERPRISE +pykolab_conf_PYTHON += conf/entitlement.py +endif + pykolab_imapdir = $(pythondir)/$(PACKAGE)/imap pykolab_imap_PYTHON = \ imap/__init__.py \ @@ -50,7 +54,7 @@ pykolab_setup_PYTHON = \ setup/ldap_setup.py pykolab_testsdir = $(pythondir)/$(PACKAGE)/tests -pykolab_setup_PYTHON = \ +pykolab_tests_PYTHON = \ tests/calendar.py \ tests/constants.py \ tests/contacts.py \ diff --git a/pykolab/conf/__init__.py b/pykolab/conf/__init__.py index 1e4c82a..b078162 100644 --- a/pykolab/conf/__init__.py +++ b/pykolab/conf/__init__.py @@ -46,6 +46,17 @@ class Conf(object): self.cli_args = None self.cli_keywords = None + self.entitlement = None + + from pykolab.conf.entitlement import Entitlement + self.entitlement = Entitlement().get() + + #try: + #from pykolab.conf.entitlement import Entitlement + #self.entitlement = Entitlement().get() + #except: + #pass + self.plugins = None # The location where our configuration parser is going to end up diff --git a/pykolab/conf/entitlement.py b/pykolab/conf/entitlement.py new file mode 100644 index 0000000..34c6353 --- /dev/null +++ b/pykolab/conf/entitlement.py @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +# Copyright 2010-2011 Kolab Systems AG (http://www.kolabsys.com) +# +# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 3 or, at your option, any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +from ConfigParser import ConfigParser +import hashlib +import OpenSSL +import os +import StringIO +import subprocess +import sys + +from pykolab.translate import _ + +import pykolab +log = pykolab.getLogger('pykolab.conf') + +class Entitlement(object): + def __init__(self, *args, **kw): + self.entitlement = {} + + self.entitlement_files = [] + + ca_cert_file = '/etc/pki/tls/certs/mirror.kolabsys.com.ca.cert' + customer_cert_file = '/etc/pki/tls/private/mirror.kolabsys.com.client.pem' + customer_key_file = '/etc/pki/tls/private/mirror.kolabsys.com.client.pem' + + # Licence lock and key verification. + self.entitlement_verification = [ + 'f700660f456a60c92ab2f00d0f1968230920d89829d42aa27d30f678', + '95783ba5521ea54aa3a32b7949f145aa5015a4c9e92d12b9e4c95c14' + ] + + if os.access(ca_cert_file, os.R_OK): + # Verify /etc/kolab/mirror_ca.crt + ca_cert = OpenSSL.crypto.load_certificate( + OpenSSL.SSL.FILETYPE_PEM, + open(ca_cert_file).read() + ) + + if (bool)(ca_cert.has_expired()): + raise Exception, _("Invalid entitlement verification " + \ + "certificate at %s" %(ca_cert_file)) + + # TODO: Check validity and warn ~1-2 months in advance. + + ca_cert_issuer = ca_cert.get_issuer() + ca_cert_subject = ca_cert.get_subject() + + ca_cert_issuer_hash = subprocess.Popen( + [ + 'openssl', + 'x509', + '-in', + ca_cert_file, + '-noout', + '-issuer_hash' + ], + stdout=subprocess.PIPE + ).communicate()[0].strip() + + ca_cert_issuer_hash_digest = hashlib.sha224(ca_cert_issuer_hash).hexdigest() + + if not ca_cert_issuer_hash_digest in self.entitlement_verification: + raise Exception, _("Invalid entitlement verification " + \ + "certificate at %s") %(ca_cert_file) + + ca_cert_subject_hash = subprocess.Popen( + [ + 'openssl', + 'x509', + '-in', + ca_cert_file, + '-noout', + '-subject_hash' + ], + stdout=subprocess.PIPE + ).communicate()[0].strip() + + ca_cert_subject_hash_digest = hashlib.sha224(ca_cert_subject_hash).hexdigest() + + if not ca_cert_subject_hash_digest in self.entitlement_verification: + raise Exception, _("Invalid entitlement verification " + \ + "certificate at %s") %(ca_cert_file) + + customer_cert_issuer_hash = subprocess.Popen( + [ + 'openssl', + 'x509', + '-in', + customer_cert_file, + '-noout', + '-issuer_hash' + ], + stdout=subprocess.PIPE + ).communicate()[0].strip() + + customer_cert_issuer_hash_digest = hashlib.sha224(customer_cert_issuer_hash).hexdigest() + + if not customer_cert_issuer_hash_digest in self.entitlement_verification: + raise Exception, _("Invalid entitlement verification " + \ + "certificate at %s") %(customer_cert_file) + + if not ca_cert_issuer.countryName == ca_cert_subject.countryName: + raise Exception, _("Invalid entitlement certificate") + + if not ca_cert_issuer.organizationName == ca_cert_subject.organizationName: + raise Exception, _("Invalid entitlement certificate") + + if os.path.isdir('/etc/kolab/entitlement.d/') and \ + os.access('/etc/kolab/entitlement.d/', os.R_OK): + + for root, dirs, files in os.walk('/etc/kolab/entitlement.d/'): + if not root == '/etc/kolab/entitlement.d/': + continue + for entitlement_file in files: + log.debug(_("Parsing entitlement file %s") %(entitlement_file), level=8) + + if os.access(os.path.join(root, entitlement_file), os.R_OK): + self.entitlement_files.append( + os.path.join(root, entitlement_file) + ) + + else: + print >> sys.stderr, \ + _("License file %s not readable!") %( + os.path.join(root, entitlement_file) + ) + + else: + print >> sys.stderr, _("No entitlement directory found") + + for entitlement_file in self.entitlement_files: + + decrypt_command = [ + 'openssl', + 'smime', + '-decrypt', + '-recip', + customer_cert_file, + '-in', + entitlement_file + ] + + decrypt_process = subprocess.Popen( + decrypt_command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + verify_command = [ + 'openssl', + 'smime', + '-verify', + '-certfile', + ca_cert_file, + '-CAfile', + ca_cert_file, + '-inform', + 'DER' + ] + + verify_process = subprocess.Popen( + verify_command, + stdin=decrypt_process.stdout, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + (stdout, stderr) = verify_process.communicate() + license = License(stdout, self.entitlement) + license.verify_certificate(customer_cert_file) + self.entitlement = license.get() + + else: + print "Error reading entitlement certificate authority file" + + def get(self): + if len(self.entitlement.keys()) == 0: + return None + else: + return self.entitlement + +class License(object): + entitlement = {} + + def __init__(self, new_entitlement, existing_entitlement): + self.parser = ConfigParser() + fp = StringIO.StringIO(new_entitlement) + self.parser.readfp(fp) + + self.entitlement['users'] = self.parser.get('kolab_entitlements', 'users') + self.entitlement['margin'] = self.parser.get('kolab_entitlements', 'margin') + + def verify_certificate(self, customer_cert_file): + # Verify the certificate section as well. + cert_serial = self.parser.get('mirror_ca', 'serial_number') + cert_issuer_hash = self.parser.get('mirror_ca', 'issuer_hash') + cert_subject_hash = self.parser.get('mirror_ca', 'subject_hash') + + customer_cert_serial = subprocess.Popen( + [ + 'openssl', + 'x509', + '-in', + customer_cert_file, + '-noout', + '-serial' + ], + stdout=subprocess.PIPE + ).communicate()[0].strip().split('=')[1] + + if not customer_cert_serial == cert_serial: + raise Exception, _("Invalid entitlement verification " + \ + "certificate at %s") %(customer_cert_file) + + customer_cert_issuer_hash = subprocess.Popen( + [ + 'openssl', + 'x509', + '-in', + customer_cert_file, + '-noout', + '-issuer_hash' + ], + stdout=subprocess.PIPE + ).communicate()[0].strip() + + if not customer_cert_issuer_hash == cert_issuer_hash: + raise Exception, _("Invalid entitlement verification " + \ + "certificate at %s") %(customer_cert_file) + + customer_cert_subject_hash = subprocess.Popen( + [ + 'openssl', + 'x509', + '-in', + customer_cert_file, + '-noout', + '-subject_hash' + ], + stdout=subprocess.PIPE + ).communicate()[0].strip() + + if not customer_cert_subject_hash == cert_subject_hash: + raise Exception, _("Invalid entitlement verification " + \ + "certificate at %s") %(customer_cert_file) + + def get(self): + return self.entitlement |