diff --git a/product/Vifib/Tool/CertificateAuthorityTool.py b/product/Vifib/Tool/CertificateAuthorityTool.py new file mode 100644 index 0000000000000000000000000000000000000000..f3fa3f0a82e5b1806ce7a0220757d1c698c65169 --- /dev/null +++ b/product/Vifib/Tool/CertificateAuthorityTool.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved. +# 艁ukasz Nowak <luke@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly advised to contract a Free Software +# Service Company +# +# 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; either version 2 +# of the License, 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 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 AccessControl import ClassSecurityInfo +from Products.ERP5Type.Globals import InitializeClass +from Products.ERP5Type.Tool.BaseTool import BaseTool +from Products.ERP5Type import Permissions +from Products.PageTemplates.PageTemplateFile import PageTemplateFile +from zLOG import LOG, INFO, ERROR + +import os +import subprocess +import base64 + +class CertificateGenerationError(Exception): + """Exception raised when certificate authority failed to work""" + pass + +class CertificateAuthorityBusy(Exception): + """Exception raised when certificate authority is busy""" + pass + +class CertificateAuthorityDamaged(Exception): + """Exception raised when certificate authority is damaged""" + pass + +class CertificateAuthorityTool(BaseTool): + """CertificateAuthorityTool + + This tool assumes that in certificate_authority_path openssl configuration is ready. + """ + + id = 'portal_certificate_authority' + meta_type = 'ERP5 Certificate Authority Tool' + portal_type = 'Certificate Authority Tool' + security = ClassSecurityInfo() + allowed_types = () + + certificate_authority_path = '' + + manage_options = (({'label': 'Edit', + 'action': 'manage_editCertificateAuthorityToolForm',}, + ) + ) + BaseTool.manage_options + + _properties = (({'id':'certificate_authority_path', + 'type':'string', + 'mode':'w', + 'label':'Path to certificate authority' + }, + ) + ) + + def _lockCertificateAuthority(self): + """Checks lock and locks Certificate Authority tool, raises CertificateAuthorityBusy""" + if os.path.exists(self.lock): + raise CertificateAuthorityBusy + open(self.lock, 'w').write('locked') + + def _unlockCertificateAuthority(self): + """Checks lock and locks Certificate Authority tool""" + if os.path.exists(self.lock): + os.unlink(self.lock) + else: + LOG('CertificateAuthorityTool', INFO, 'Lock file %r did not existed ' + 'during unlocking' % self.lock) + + def _checkCertificateAuthority(self): + """Checks Certificate Authority configuration, raises CertificateAuthorityDamaged""" + if not self.certificate_authority_path: + raise CertificateAuthorityDamaged('Certificate authority path is not ' + 'configured' % self.certificate_authority_path) + if not os.path.isdir(self.certificate_authority_path): + raise CertificateAuthorityDamaged('Path to Certificate Authority %r is ' + 'wrong' % self.certificate_authority_path) + self.serial = os.path.join(self.certificate_authority_path, 'serial') + self.crl = os.path.join(self.certificate_authority_path, 'crlnumber') + self.index = os.path.join(self.certificate_authority_path, 'index.txt') + self.openssl = os.path.join(self.certificate_authority_path, 'openssl') + self.openssl_config = os.path.join(self.certificate_authority_path, + 'openssl.cnf') + self.lock = os.path.join(self.certificate_authority_path, 'lock') + for f in [self.serial, self.crl, self.index]: + if not os.path.isfile(f): + raise CertificateAuthorityDamaged('File %r does not exists.' % f) + if not os.path.isfile(self.openssl): + raise CertificateAuthorityDamaged('Openssl wrapper %r does not exists' % + self.openssl) + + security.declarePrivate('manage_afterAdd') + def manage_afterAdd(self, item, container) : + """Init permissions right after creation. + + Permissions in slap tool are simple: + o Each member can access the tool. + o Only manager can view and create. + o Anonymous can not access + """ + item.manage_permission(Permissions.AddPortalContent, + ['Manager']) + item.manage_permission(Permissions.AccessContentsInformation, + ['Member', 'Manager']) + item.manage_permission(Permissions.View, + ['Manager',]) + BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container) + + #'Edit' option form + manage_editCertificateAuthorityToolForm = PageTemplateFile( + '../www/Vifib_editCertificateAuthorityTool', + globals(), + __name__='manage_editCertificateAuthorityToolForm') + + security.declareProtected(Permissions.ManageProperties, 'manage_editCertificateAuthorityTool') + def manage_editCertificateAuthorityTool(self, certificate_authority_path, RESPONSE=None): + """Edit the object""" + error_message = '' + + #Save certificate_authority_path + if certificate_authority_path == '' or certificate_authority_path is None: + error_message += 'Invalid path ' + else: + self.certificate_authority_path = certificate_authority_path + + #Redirect + if RESPONSE is not None: + if error_message != '': + self.REQUEST.form['manage_tabs_message'] = error_message + return self.manage_editCertificateAuthorityToolForm(RESPONSE) + else: + message = "Updated" + RESPONSE.redirect('%s/manage_editCertificateAuthorityToolForm' + '?manage_tabs_message=%s' + % (self.absolute_url(), message) + ) + + security.declareProtected(Permissions.AccessContentsInformation, 'getNewCertificate') + def getNewCertificate(self): + """Returns dictionary {key, certificate, id} where id is certificate id to be used""" + self._checkCertificateAuthority() + self._lockCertificateAuthority() + try: + new_id = open(self.serial, 'r').read().strip() + cn = base64.encodestring(str(new_id) + ':') + key = os.path.join(self.certificate_authority_path, 'private', new_id+'.key') + csr = os.path.join(self.certificate_authority_path, new_id + '.csr') + cert = os.path.join(self.certificate_authority_path, 'certs', new_id + '.crt') + try: + keygen = subprocess.Popen([self.openssl, 'req', '-nodes', '-config', + self.openssl_config, '-new', '-keyout', key, '-out', csr, '-days', + '3650'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + stdin=subprocess.PIPE) + result = keygen.communicate('%s\n' % cn)[0] + if keygen.returncode is None or keygen.returncode != 0: + LOG('CertificateAuthorityTool', ERROR, 'Issue during key generation, result was:%r' % result) + keygen.kill() + raise CertificateGenerationError + keysign = subprocess.Popen([self.openssl, 'ca', '-batch', '-config', + self.openssl_config, '-out', cert, '-infiles', csr], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + result = keysign.communicate()[0] + if keysign.returncode is None or keysign.returncode != 0: + LOG('CertificateAuthorityTool', ERROR, 'Issue during key signing, result was:%r' % result) + keygen.kill() + raise CertificateGenerationError + os.unlink(csr) + return dict( + key=open(key).read(), + certificate=open(cert).read(), + id=new_id) + except: + try: + for p in [key, csr, cert]: + if os.path.exists(p): + os.unlink(p) + except: + # do not raise during cleanup + pass + raise + finally: + self._unlockCertificateAuthority() + + security.declareProtected(Permissions.AccessContentsInformation, 'revokeCertificate') + def revokeCertificate(self, serial): + """Revokes certificate with serial, returns dictionary {crl}""" + self._checkCertificateAuthority() + self._lockCertificateAuthority() + try: + new_id = open(self.crl, 'r').read().strip() + crl = os.path.join(self.certificate_authority_path, 'crl', new_id + '.crl') + cert = os.path.join(self.certificate_authority_path, 'certs', serial + '.crt') + if not os.path.exists(cert): + raise ValueError('Certificate with serial %r does not exists' % serial) + try: + crl_update = subprocess.Popen([self.openssl, 'ca', '-config', + self.openssl_config, '-revoke', cert], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + result = crl_update.communicate()[0] + if crl_update.returncode is None or crl_update.returncode != 0: + LOG('CertificateAuthorityTool', ERROR, 'Issue during CRL update, result was:%r' % result) + crl_update.kill() + raise CertificateGenerationError + crl_gen = subprocess.Popen([self.openssl, 'ca', '-config', + self.openssl_config, '-gencrl', '-out', crl], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + result = crl_gen.communicate()[0] + if crl_gen.returncode is None or crl_gen.returncode != 0: + LOG('CertificateAuthorityTool', ERROR, 'Issue during CRL generation, result was:%r' % result) + crl_gen.kill() + raise CertificateGenerationError + return dict(crl=open(crl).read()) + except: + try: + for p in [crl]: + if os.path.exists(p): + os.unlink(p) + except: + # do not raise during cleanup + pass + raise + finally: + self._unlockCertificateAuthority() + +InitializeClass(CertificateAuthorityTool) diff --git a/product/Vifib/__init__.py b/product/Vifib/__init__.py index ef20ddb189998aa7cbebd723d55de2ad979d4334..9d879fe217f70af390576e91d06b8fe0f4fd7901 100644 --- a/product/Vifib/__init__.py +++ b/product/Vifib/__init__.py @@ -37,8 +37,8 @@ document_classes = updateGlobals(this_module, globals(), object_classes = () content_classes = () content_constructors = () -from Tool import SlapTool -portal_tools = ( SlapTool.SlapTool, ) +from Tool import SlapTool, CertificateAuthorityTool +portal_tools = ( SlapTool.SlapTool, CertificateAuthorityTool.CertificateAuthorityTool ) from Products.PluggableAuthService.PluggableAuthService import registerMultiPlugin import VifibMachineAuthenticationPlugin diff --git a/product/Vifib/www/Vifib_editCertificateAuthorityTool.zpt b/product/Vifib/www/Vifib_editCertificateAuthorityTool.zpt new file mode 100644 index 0000000000000000000000000000000000000000..cde29746db1716a5a302b725d8d4bd660aa9b580 --- /dev/null +++ b/product/Vifib/www/Vifib_editCertificateAuthorityTool.zpt @@ -0,0 +1,29 @@ +<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1> +<h2 tal:replace="structure here/manage_tabs"> TABS </h2> +<h2 tal:define="form_title string:Edit ERP5 Certificate Authority Tool" + tal:replace="structure context/manage_form_title">FORM TITLE</h2> + +<p class="form-help">Please input the Certificate Authority path</p> + +<form action="manage_editCertificateAuthorityTool" method="POST"> + +<table tal:define="certificate_authority_path request/certificate_authority_path|context/certificate_authority_path|string:;"> + +<tr> + <td>Path to configured Certificate Authority</td> + <td> + <input type="text" name="certificate_authority_path" value="" + tal:attributes="value certificate_authority_path;" /> + </td> +</tr> +<tr> + <td colspan="2"> + <input type="submit" value="save"/> + </td> +</tr> + +</table> + +</form> + +<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>