Branch 'dev/entitlements' - configure.ac Makefile.am pykolab/conf pykolab/Makefile.am

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Wed Jan 4 12:18:05 CET 2012


 Makefile.am                 |    7 -
 configure.ac                |   10 +
 pykolab/Makefile.am         |    6 
 pykolab/conf/__init__.py    |   11 +
 pykolab/conf/entitlement.py |  265 ++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 297 insertions(+), 2 deletions(-)

New commits:
commit 84b58bf324226ac321cee8726bb46d864318cb9e
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Jan 4 12:17:26 2012 +0100

    Add the framework for entitlements (there's no enforcement yet, but initialization fails if data does not check out)

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





More information about the commits mailing list