[Checkins] SVN: keas.kmi/trunk/ - Refactored REST server to be a simple repoze.bfg application.

Stephan Richter srichter at gmail.com
Wed Sep 29 00:38:03 EDT 2010


Log message for revision 117038:
  - Refactored REST server to be a simple repoze.bfg application.
  
  - The encrypted data encrypting keys (DEKs) are now stored in a directory
    instead of the ZODB. This increases transparency in the data store and makes
    backups easier.
  

Changed:
  U   keas.kmi/trunk/CHANGES.txt
  U   keas.kmi/trunk/buildout.cfg
  U   keas.kmi/trunk/server.ini
  U   keas.kmi/trunk/setup.py
  U   keas.kmi/trunk/src/keas/kmi/README.txt
  D   keas.kmi/trunk/src/keas/kmi/apidoc.zcml
  D   keas.kmi/trunk/src/keas/kmi/application.py
  D   keas.kmi/trunk/src/keas/kmi/application.zcml
  U   keas.kmi/trunk/src/keas/kmi/configure.zcml
  D   keas.kmi/trunk/src/keas/kmi/db.py
  D   keas.kmi/trunk/src/keas/kmi/db.zcml
  U   keas.kmi/trunk/src/keas/kmi/facility.py
  A   keas.kmi/trunk/src/keas/kmi/facility.txt
  U   keas.kmi/trunk/src/keas/kmi/interfaces.py
  U   keas.kmi/trunk/src/keas/kmi/rest.py
  D   keas.kmi/trunk/src/keas/kmi/security.zcml
  U   keas.kmi/trunk/src/keas/kmi/testclient.py
  U   keas.kmi/trunk/src/keas/kmi/testing.py
  D   keas.kmi/trunk/src/keas/kmi/testing.zcml
  U   keas.kmi/trunk/src/keas/kmi/tests.py
  U   keas.kmi/trunk/src/keas/kmi/wsgi.py

-=-
Modified: keas.kmi/trunk/CHANGES.txt
===================================================================
--- keas.kmi/trunk/CHANGES.txt	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/CHANGES.txt	2010-09-29 04:38:02 UTC (rev 117038)
@@ -2,12 +2,16 @@
 CHANGES
 =======
 
-1.1.2 (unreleased)
+2.0.0 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- Refactored REST server to be a simple repoze.bfg application.
 
+- The encrypted data encrypting keys (DEKs) are now stored in a directory
+  instead of the ZODB. This increases transparency in the data store and makes
+  backups easier.
 
+
 1.1.1 (2010-08-27)
 ------------------
 

Modified: keas.kmi/trunk/buildout.cfg
===================================================================
--- keas.kmi/trunk/buildout.cfg	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/buildout.cfg	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,7 +1,8 @@
 [buildout]
 develop = .
 extends = http://download.zope.org/bluebream/bluebream-1.0b3.cfg
-parts = test coverage-test coverage-report python paster runserver testclient ctags
+parts = test coverage-test coverage-report python paster runserver testclient
+        ctags
 versions = versions
 
 [test]
@@ -11,12 +12,12 @@
 [coverage-test]
 recipe = zc.recipe.testrunner
 eggs = keas.kmi [test]
-defaults = ['--coverage', '../../coverage']
+defaults = ['--coverage', '${buildout:directory}/coverage']
 
 [coverage-report]
 recipe = zc.recipe.egg
 eggs = z3c.coverage
-scripts = coverage=coverage-report
+scripts = coveragereport=coverage-report
 arguments = ('coverage', 'coverage/report')
 
 [python]
@@ -30,19 +31,11 @@
 
 [paster]
 recipe = zc.recipe.egg
-eggs = Paste
-       PasteScript
-       PasteDeploy
+dependent-scripts = true
+eggs = keas.kmi
+       Paste
        pyOpenSSL
-       zope.app.component
-       zope.app.publication
-       zope.app.publisher
-       zope.app.security
-       zope.component
-       zope.error
-       zope.publisher
-       zope.securitypolicy
-       keas.kmi
+scripts = paster
 
 [runserver]
 recipe = zc.recipe.egg
@@ -57,3 +50,4 @@
 [versions]
 setuptools = 0.6c12dev-r84273
 zc.buildout = 1.5.0
+zc.recipe.egg = 1.3.0

Modified: keas.kmi/trunk/server.ini
===================================================================
--- keas.kmi/trunk/server.ini	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/server.ini	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,9 +1,43 @@
 [app:main]
 use = egg:keas.kmi
-conf=zope.conf
+storage-dir=keys/
 
 [server:main]
 use = egg:Paste#http
 host = 0.0.0.0
 port = 8080
 ssl_pem = sample.pem
+
+# Logging Configuration
+[loggers]
+keys = root, kmi
+
+[handlers]
+keys = console, file
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_kmi]
+level = INFO
+handlers = console
+propagate = 0
+qualname = kmi
+
+[handler_file]
+class = FileHandler
+args = ('source-cache.log',)
+formatter = generic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stdout,)
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
+datefmt= %Y-%m-%d %H:%M:%S

Modified: keas.kmi/trunk/setup.py
===================================================================
--- keas.kmi/trunk/setup.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/setup.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -22,7 +22,7 @@
 
 setup (
     name='keas.kmi',
-    version = '1.1.2dev',
+    version = '2.0.0dev',
     author = "Stephan Richter and the Zope Community",
     author_email = "zope-dev at zope.org",
     description = "A Key Management Infrastructure",
@@ -36,7 +36,7 @@
         read('CHANGES.txt')
         ),
     license = "ZPL 2.1",
-    keywords = "zope3 security key management infrastructure nist 800-57",
+    keywords = "security key management infrastructure nist 800-57",
     classifiers = [
         'Development Status :: 5 - Production/Stable',
         'Environment :: Web Environment',
@@ -46,7 +46,7 @@
         'Natural Language :: English',
         'Operating System :: OS Independent',
         'Topic :: Internet :: WWW/HTTP',
-        'Framework :: Zope3'],
+        'Framework :: Repoze'],
     url = 'http://pypi.python.org/pypi/keas.kmi',
     packages = find_packages('src'),
     include_package_data = True,
@@ -60,14 +60,9 @@
         ),
     install_requires = [
         'M2Crypto',
-        'ZODB3',
+        'repoze.bfg',
         'setuptools',
-        'z3c.rest',
-        'zope.app.wsgi',
-        'zope.annotation',
-        'zope.component',
-        'zope.container',
-        'zope.dublincore',
+        'transaction',
         'zope.interface',
         'zope.schema',
         ],

Modified: keas.kmi/trunk/src/keas/kmi/README.txt
===================================================================
--- keas.kmi/trunk/src/keas/kmi/README.txt	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/README.txt	2010-09-29 04:38:02 UTC (rev 117038)
@@ -4,10 +4,14 @@
 
 This package provides a NIST SP 800-57 compliant key management
 infrastructure. Part of this infrastructure is a key management facility that
-provides several services related to keys.
+provides several services related to keys. All keys are stored in a specified
+storage directory.
 
+  >>> import tempfile
+  >>> storage_dir = tempfile.mkdtemp()
+
   >>> from keas.kmi import facility
-  >>> keys = facility.KeyManagementFacility()
+  >>> keys = facility.KeyManagementFacility(storage_dir)
   >>> keys
   <KeyManagementFacility (0)>
 
@@ -67,8 +71,8 @@
   ...    from md5 import md5
   >>> hash = md5(key)
 
-  >>> keys.get(hash.hexdigest())
-  <Key ...>
+  >>> len(keys.get(hash.hexdigest()))
+  64
 
 Our key management facility also supports the encryption service, which allows
 you to encrypt and decrypt a string given the key encrypting key.
@@ -214,13 +218,10 @@
 So let's have a look at the call:
 
   >>> from keas.kmi import rest
-  >>> from zope.publisher.browser import TestRequest
+  >>> from webob import Request
 
-  >>> request = TestRequest()
-  >>> request.method = 'POST'
-
-  >>> newCall = rest.NewView(keys, request)
-  >>> key3 = newCall()
+  >>> request = Request({})
+  >>> key3 = rest.create_key(keys, request).body
   >>> print key3
   -----BEGIN RSA PRIVATE KEY-----
   ...
@@ -229,7 +230,6 @@
 The key is available in the facility of course:
 
   >>> hash = md5(key3)
-
   >>> hash.hexdigest() in keys
   True
 
@@ -240,39 +240,27 @@
 The request sends the key encrypting key in its body. The response is the
 encryption key string:
 
-  >>> import cStringIO
-  >>> io = cStringIO.StringIO(key3)
+  >>> request = Request({})
+  >>> request.body = key3
 
-  >>> request = TestRequest(io)
-  >>> request.method = 'POST'
-
-  >>> keyCall = rest.KeyView(keys, request)
-  >>> encKey = keyCall()
-  >>> len(encKey)
+  >>> encKey = rest.get_key(keys, request)
+  >>> len(encKey.body)
   32
 
-If you try to request a nonexistent key, you get a 404 error:
-encryption key string:
+If you try to request a nonexistent key, you get a 404 error: encryption key
+string:
 
-  >>> import cStringIO
-  >>> io = cStringIO.StringIO('xyzzy')
-
-  >>> request = TestRequest(io)
-  >>> request.method = 'POST'
-
-  >>> keyCall = rest.KeyView(keys, request)
-  >>> print keyCall()
+  >>> request.body = 'xxyz'
+  >>> print rest.get_key(keys, request)
   Key not found
-  >>> request.response.getStatus()
-  404
 
 A `GET` request to the root shows us a server status page
 
-  >>> request = TestRequest()
-  >>> request.method = 'GET'
-
-  >>> newCall = rest.StatusView(keys, request)
-  >>> print newCall()
+  >>> print rest.get_status(keys, Request({}))
+  200 OK
+  Content-Type: text/plain
+  Content-Length: 25
+  <BLANKLINE>
   KMS server holding 3 keys
 
 
@@ -283,7 +271,8 @@
 allows you to install a testing facility globally, not storing the keys in the
 database and still reuse a ZODB over multiple sessions.
 
-  >>> testingKeys = testing.TestingKeyManagementFacility()
+  >>> storage_dir = tempfile.mkdtemp()
+  >>> testingKeys = testing.TestingKeyManagementFacility(storage_dir)
 
 Of course, the key generation service is supported:
 
@@ -299,7 +288,9 @@
   'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'
   >>> getKeySegment(testingKeys.generate())
   'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'
-  >>> testingKeys = testing.TestingKeyManagementFacility()
+
+  >>> storage_dir = tempfile.mkdtemp()
+  >>> testingKeys = testing.TestingKeyManagementFacility(storage_dir)
   >>> getKeySegment(testingKeys.generate())
   'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'
 
@@ -314,3 +305,15 @@
   >>> encrypted = testingKeys.encrypt(key, 'Stephan Richter')
   >>> testingKeys.decrypt(key, encrypted)
   'Stephan Richter'
+
+
+Key Holder
+----------
+
+The key holder is a simple class designed to store a key in RAM:
+
+  >>> from keas.kmi import keyholder
+  >>> holder = keyholder.KeyHolder(__file__)
+
+  >>> verify.verifyObject(interfaces.IKeyHolder, holder)
+  True

Deleted: keas.kmi/trunk/src/keas/kmi/apidoc.zcml
===================================================================
--- keas.kmi/trunk/src/keas/kmi/apidoc.zcml	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/apidoc.zcml	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,19 +0,0 @@
-
-<configure xmlns="http://namespaces.zope.org/zope"
-           i18n_domain="keas">
-
-  <configure
-      xmlns:zcml="http://namespaces.zope.org/zcml"
-      xmlns:apidoc="http://namespaces.zope.org/apidoc"
-      zcml:condition="have apidoc">
-
-    <apidoc:bookchapter
-        id="keas.kmi"
-        title="Key Management Infrastructure (keas.kmi)"
-        doc_path="README.txt"
-        parent="keas"
-        />
-
-  </configure>
-
-</configure>

Deleted: keas.kmi/trunk/src/keas/kmi/application.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/application.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/application.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,21 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2008 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""
-$Id$
-"""
-__docformat__ = "reStructuredText"
-from zope.error.error import RootErrorReportingUtility
-
-globalErrorReportingUtility = RootErrorReportingUtility()
-globalErrorReportingUtility.setProperties(20, True, ())

Deleted: keas.kmi/trunk/src/keas/kmi/application.zcml
===================================================================
--- keas.kmi/trunk/src/keas/kmi/application.zcml	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/application.zcml	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,50 +0,0 @@
-<configure xmlns="http://namespaces.zope.org/zope">
-
-  <include package="zope.app.component" file="meta.zcml" />
-  <include package="zope.app.publication" file="meta.zcml" />
-  <include package="zope.app.publisher.browser" file="meta.zcml" />
-  <include package="zope.app.security" file="meta.zcml" />
-
-  <include package="zope.publisher" />
-
-  <include package="zope.app.publication" />
-  <include package="zope.app.publisher" />
-  <include package="zope.app.publisher.browser" />
-  <include package="zope.app.security" />
-
-  <utility
-      provides="zope.error.interfaces.IErrorReportingUtility"
-      component="keas.kmi.application.globalErrorReportingUtility"
-      />
-
-  <subscriber handler="zope.component.event.objectEventNotify" />
-
-  <!-- Setup charset negotiation -->
-  <adapter
-      factory="zope.publisher.http.HTTPCharsets"
-      for="zope.publisher.interfaces.http.IHTTPRequest"
-      provides="zope.i18n.interfaces.IUserPreferredCharsets"
-      />
-  <adapter
-      factory="zope.app.publisher.browser.ModifiableBrowserLanguages"
-      for="zope.publisher.interfaces.http.IHTTPRequest"
-      provides="zope.i18n.interfaces.IModifiableUserPreferredLanguages"
-      />
-
-  <include package="keas.kmi" />
-  <include package="keas.kmi" file="db.zcml" />
-
-  <include package="zope.securitypolicy" file="meta.zcml" />
-  <include package="zope.securitypolicy" />
-  <securityPolicy
-      component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
-
-  <!-- Grant access to anybody -->
-  <unauthenticatedPrincipal id="zope.anybody" title="Unauthenticated User" />
-  <unauthenticatedGroup id="zope.Anybody" title="Unauthenticated Users" />
-
-  <grant principal="zope.Anybody" permission="zope.View" />
-  <grant principal="zope.Anybody" permission="keas.kmi.GenerateKey" />
-  <grant principal="zope.Anybody" permission="keas.kmi.AccessKey" />
-
-</configure>

Modified: keas.kmi/trunk/src/keas/kmi/configure.zcml
===================================================================
--- keas.kmi/trunk/src/keas/kmi/configure.zcml	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/configure.zcml	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,35 +1,26 @@
-<configure
-    xmlns="http://namespaces.zope.org/zope"
-    i18n_domain="keas">
+<configure xmlns="http://namespaces.repoze.org/bfg">
 
-  <include file="rest.zcml" />
-  <include file="security.zcml" />
+  <include package="repoze.bfg.includes" />
 
-  <!-- We purposefully keep the access to the facility very limited -->
-  <class class=".facility.KeyManagementFacility">
-    <require
-        permission="keas.kmi.Encrypt"
-        attributes="encrypt decrypt"
-        />
-    <require
-        permission="keas.kmi.GenerateKey"
-        attributes="generate"
-        />
-    <require
-        permission="keas.kmi.AccessKey"
-        attributes="getEncryptionKey __len__"
-        />
-  </class>
+  <route
+    name="keas.kmi.index"
+    path="/"
+    request_method="GET"
+    view=".rest.get_status"
+    />
 
-  <class class=".facility.LocalKeyManagementFacility">
-    <require
-        permission="keas.kmi.Encrypt"
-        attributes="encrypt decrypt"
-        />
-    <require
-        permission="keas.kmi.GenerateKey"
-        attributes="generate"
-        />
-  </class>
+  <route
+    name="keas.kmi.new"
+    path="/new"
+    request_method="POST"
+    view=".rest.create_key"
+    />
 
+  <route
+    name="keas.kmi.key"
+    path="/key"
+    request_method="POST"
+    view=".rest.get_key"
+    />
+
 </configure>

Deleted: keas.kmi/trunk/src/keas/kmi/db.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/db.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/db.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,41 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2008 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Automatic ZODB installation
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-
-import transaction
-from zope.component import adapter
-from zope.app.appsetup.interfaces import IDatabaseOpenedEvent
-from zope.app.appsetup.bootstrap import getInformationFromEvent
-from zope.app.publication.zopepublication import ZopePublication
-
-from keas.kmi.facility import KeyManagementFacility
-from keas.kmi.interfaces import IKeyManagementFacility
-
-
- at adapter(IDatabaseOpenedEvent)
-def bootstrapKeyManagementFacility(event):
-    """Installs KeyManagementFacility as the root object of the DB."""
-    db, connection, root, root_object = getInformationFromEvent(event)
-    if root_object is None:
-        root[ZopePublication.root_name] = KeyManagementFacility()
-        transaction.commit()
-    elif not IKeyManagementFacility.providedBy(root_object):
-        raise RuntimeError('Your database root object is not a key management'
-                           ' facility.  Remove your Data.fs and try again.')
-    connection.close()
-

Deleted: keas.kmi/trunk/src/keas/kmi/db.zcml
===================================================================
--- keas.kmi/trunk/src/keas/kmi/db.zcml	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/db.zcml	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,7 +0,0 @@
-<configure xmlns="http://namespaces.zope.org/zope">
-
-  <subscriber
-      handler=".db.bootstrapKeyManagementFacility"
-      />
-
-</configure>

Modified: keas.kmi/trunk/src/keas/kmi/facility.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/facility.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/facility.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -12,51 +12,31 @@
 #
 ##############################################################################
 """Implementation of Key Management Facility
-
-$Id$
 """
 from __future__ import absolute_import
 __docformat__ = "reStructuredText"
 
-import datetime
+import M2Crypto
+import os
+import httplib
+import logging
 import time
+import urlparse
+import zope.interface
+from keas.kmi import interfaces
+
 try:
     from hashlib import md5
 except ImportError:
     from md5 import md5
 
-import M2Crypto
-import persistent
-import zope.interface
-import zope.location
-from z3c.rest import client
-from zope.annotation.interfaces import IAttributeAnnotatable
-from zope.container import btree
-from zope.dublincore import property
-from zope.schema.fieldproperty import FieldProperty
+logger = logging.getLogger('kmi')
 
-from keas.kmi import interfaces
-
-
-class Key(zope.location.Location, persistent.Persistent):
-    zope.interface.implements(interfaces.IKey, IAttributeAnnotatable)
-
-    created = property.DCProperty('created')
-    creator = property.DCProperty('creator')
-    key = FieldProperty(interfaces.IKey['key'])
-
-    def __init__(self, key):
-        self.key = key
-
-    def __repr__(self):
-        return '<%s %r>' %(self.__class__.__name__, self.key)
-
-
 class EncryptionService(object):
 
     cipher = 'aes_256_cbc'
 
-    # Note: decryption fails if you use an empty initialization vector; but it
+    # Note: Decryption fails if you use an empty initialization vector; but it
     # only fails when you restart the Python process.  The length of the
     # initialization vector is assumed to be 16 bytes because that's what
     #   openssl aes-256-cbc -nosalt -P -k 'a'
@@ -90,7 +70,7 @@
         return decrypted
 
 
-class KeyManagementFacility(EncryptionService, btree.BTreeContainer):
+class KeyManagementFacility(EncryptionService):
     zope.interface.implements(interfaces.IExtendedKeyManagementFacility)
 
     rsaKeyLength = 512 # The length of the key encrypting key
@@ -100,6 +80,54 @@
 
     keyLength = rsaKeyLength/16
 
+    def __init__(self, storage_dir):
+        self.storage_dir = storage_dir
+
+    def keys(self):
+        return [filename[:-4] for filename in os.listdir(self.storage_dir)
+                if filename.endswith('.dek')]
+
+    def __iter__(self):
+        return iter(self.keys())
+
+    def __getitem__(self, name):
+        if name+'.dek' not in os.listdir(self.storage_dir):
+            raise KeyError(name)
+        fn = os.path.join(self.storage_dir, name+'.dek')
+        with open(fn, 'rb') as file:
+            return file.read()
+
+    def get(self, name, default=None):
+        try:
+            return self[name]
+        except KeyError:
+            return default
+
+    def values(self):
+        return [value for name, value in self.items()]
+
+    def __len__(self):
+        return len(self.keys())
+
+    def items(self):
+        return [(name, self[name]) for name in self.keys()]
+
+    def __contains__(self, name):
+        return name in self.keys()
+
+    has_key = __contains__
+
+    def __setitem__(self, name, key):
+        fn = os.path.join(self.storage_dir, name+'.dek')
+        with open(fn, 'w') as file:
+            return file.write(key)
+        logger.info('New key added (hash): %s', name)
+
+    def __delitem__(self, name):
+        fn = os.path.join(self.storage_dir, name+'.dek')
+        os.remove(fn)
+        logger.info('Key removed (hash): %s', name)
+
     def generate(self):
         """See interfaces.IKeyGenerationService"""
         # 1. Generate the private/public RSA key encrypting key
@@ -117,7 +145,7 @@
         hash.update(privateKey)
         # 5. Save the encryption key
         encryptedKey = rsa.public_encrypt(key, self.rsaPadding)
-        self[hash.hexdigest()] = Key(encryptedKey)
+        self[hash.hexdigest()] = encryptedKey
         # 6. Return the private key encrypting key
         return privateKey
 
@@ -127,11 +155,12 @@
         hash = md5()
         hash.update(key)
         # 2. Extract the encrypted encryption key
-        encryptedKey = self[hash.hexdigest()].key
+        encryptedKey = self[hash.hexdigest()]
         # 3. Decrypt the key.
         rsa = M2Crypto.RSA.load_key_string(key)
         decryptedKey = rsa.private_decrypt(encryptedKey, self.rsaPadding)
         # 4. Return decrypted encryption key
+        logger.info('Encryption key requested: %s', hash.hexdigest())
         return decryptedKey
 
     def __repr__(self):
@@ -143,7 +172,7 @@
     zope.interface.implements(interfaces.IKeyManagementFacility)
 
     timeout = 3600
-    clientClass = client.RESTClient
+    httpConnFactory = httplib.HTTPSConnection
 
     def __init__(self, url):
         self.url = url
@@ -151,18 +180,21 @@
 
     def generate(self):
         """See interfaces.IKeyGenerationService"""
-        client = self.clientClass(self.url)
-        client.post('/new')
-        return client.contents
+        pieces = urlparse.urlparse(self.url)
+        conn = self.httpConnFactory(pieces.netloc)
+        conn.request('POST', '/new', '', {})
+        response = conn.getresponse()
+        return response.read()
 
     def getEncryptionKey(self, key):
         """Given the key encrypting key, get the encryption key."""
         if (key in self._cache and
             self._cache[key][0] + self.timeout > time.time()):
             return self._cache[key][1]
-        client = self.clientClass(self.url)
-        client.post('/key', key, headers={'content-type': 'text/plain'})
-        encryptionKey = client.contents
+        pieces = urlparse.urlparse(self.url)
+        conn = self.httpConnFactory(pieces.netloc)
+        conn.request('POST', '/key', key, {'content-type': 'text/plain'})
+        encryptionKey = conn.getresponse().read()
         self._cache[key] = (time.time(), encryptionKey)
         return encryptionKey
 

Added: keas.kmi/trunk/src/keas/kmi/facility.txt
===================================================================
--- keas.kmi/trunk/src/keas/kmi/facility.txt	                        (rev 0)
+++ keas.kmi/trunk/src/keas/kmi/facility.txt	2010-09-29 04:38:02 UTC (rev 117038)
@@ -0,0 +1,58 @@
+=========================
+Key Management Facilities
+=========================
+
+The default key managment facility implements the
+``IExtendedKeyManagementFacility``. This interface extends ``IMapping`` so
+that the entire mapping interface is supported
+
+  >>> import tempfile
+  >>> from keas.kmi import facility
+  >>> kmf = facility.KeyManagementFacility(tempfile.mkdtemp())
+
+  >>> key1 = kmf.generate()
+  >>> key2 = kmf.generate()
+  >>> key3 = kmf.generate()
+
+There should be 3 keys now:
+
+  >>> len(kmf)
+  3
+
+And their hash values are the keys of the map:
+
+  >>> kmf.keys()
+  ['...', '...', '...']
+  >>> list(iter(kmf))
+  ['...', '...', '...']
+
+Now let's get one key:
+
+  >>> hash = kmf.keys()[0]
+  >>> len(kmf[hash])
+  64
+
+  >>> len(kmf.get(hash))
+  64
+  >>> kmf.get('xyz', 'default')
+  'default'
+
+We can also check whether a hash is int he facility:
+
+  >>> hash in kmf
+  True
+  >>> 'xyz' in hash
+  False
+
+Of course you can get values and items as well:
+
+  >>> len(kmf.values())
+  3
+  >>> len(kmf.items())
+  3
+
+Finally, one can delete keys using their hash as well.
+
+  >>> del kmf[hash]
+  >>> len(kmf)
+  2


Property changes on: keas.kmi/trunk/src/keas/kmi/facility.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: keas.kmi/trunk/src/keas/kmi/interfaces.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/interfaces.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/interfaces.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -11,34 +11,14 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
+"""Key Management Interfaces
 """
-$Id$
-"""
 __docformat__ = "reStructuredText"
 import zope.interface
 import zope.schema
-from zope.container import interfaces
+from zope.interface.common import mapping
 
 
-class IKey(zope.interface.Interface):
-    """Encryption Key"""
-
-    created = zope.schema.Datetime(
-        title=u'Creation Date/Time',
-        description=u'The date/time the key pair was created.',
-        required=True)
-
-    creator = zope.schema.TextLine(
-        title=u'Creator',
-        description=u'The principal/user that requested the new key.',
-        required=True)
-
-    key = zope.schema.Bytes(
-        title=u'Key',
-        description=u'The key used to encrypt and decrypt the data.',
-        required=True)
-
-
 class IEncryptionService(zope.interface.Interface):
     """Utility providing encryption mechanism"""
 
@@ -65,11 +45,10 @@
     """
 
 class IExtendedKeyManagementFacility(IKeyManagementFacility,
-                                     interfaces.IContainer):
+                                     mapping.IMapping):
     """Extended Key Management Facility.
 
-    This facility also also the management of the keys via Python's mapping
-    API.
+    This facility also allows access of the keys via Python's mapping API.
     """
 
 

Modified: keas.kmi/trunk/src/keas/kmi/rest.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/rest.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/rest.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -12,42 +12,25 @@
 #
 ##############################################################################
 """REST-API to master key management facility
-
-$Id$
 """
-from zope.publisher.browser import BrowserPage
+from webob import Response, exc
 
-class RestView(BrowserPage):
+def get_status(context, request):
+    return Response(
+        'KMS server holding %d keys' %len(context),
+        headerlist=[('Content-Type', 'text/plain')])
 
-    def __call__(self):
-        method = self.request.method
-        if not hasattr(self, method):
-            self.request.response.setStatus(405)
-            return 'Method not allowed.'
-        return getattr(self, method)()
+def create_key(context, request):
+    return Response(
+        context.generate(),
+        headerlist=[('Content-Type', 'text/plain')])
 
-class StatusView(RestView):
+def get_key(context, request):
+    key = request.body
+    try:
+        return Response(
+            context.getEncryptionKey(key),
+            headerlist=[('Content-Type', 'text/plain')])
+    except KeyError:
+        return exc.HTTPNotFound('Key not found')
 
-    def GET(self):
-        self.request.response.setHeader('content-type', 'text/plain')
-        return 'KMS server holding %d keys' % len(self.context)
-
-class NewView(RestView):
-
-    def POST(self):
-        self.request.response.setHeader('content-type', 'text/plain')
-        return self.context.generate()
-
-class KeyView(RestView):
-
-    def POST(self):
-        stream = self.request.bodyStream.getCacheStream()
-        stream.seek(0)
-        key = stream.read()
-        self.request.response.setHeader('content-type', 'text/plain')
-        try:
-            return self.context.getEncryptionKey(key)
-        except KeyError:
-            self.request.response.setStatus(404)
-            return 'Key not found'
-

Deleted: keas.kmi/trunk/src/keas/kmi/security.zcml
===================================================================
--- keas.kmi/trunk/src/keas/kmi/security.zcml	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/security.zcml	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,19 +0,0 @@
-<configure
-    xmlns="http://namespaces.zope.org/zope"
-    i18n_domain="keas">
-
-  <!-- Key Management Permissions -->
-  <permission
-      id="keas.kmi.Encrypt"
-      title="Encrypt Data"
-      />
-  <permission
-      id="keas.kmi.GenerateKey"
-      title="Generate Key"
-      />
-  <permission
-      id="keas.kmi.AccessKey"
-      title="Access Key"
-      />
-
-</configure>

Modified: keas.kmi/trunk/src/keas/kmi/testclient.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/testclient.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/testclient.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -19,15 +19,18 @@
 import sys
 import optparse
 import textwrap
+import urlparse
 
 from keas.kmi.facility import LocalKeyManagementFacility
 
-
 def ping(kmf):
-    client = kmf.clientClass(kmf.url)
-    print client.fullStatus
+    pieces = urlparse.urlparse(kmf.url)
+    conn = kmf.httpConnFactory(pieces.netloc)
+    conn.request('GET', '/')
+    response = conn.getresponse()
+    print response.status, response.reason
     print
-    print client.contents
+    print response.read()
 
 
 def new_key(kmf):
@@ -75,50 +78,58 @@
     sys.stdout.write(decrypted)
 
 
-def main():
-    parser = optparse.OptionParser(textwrap.dedent("""\
-                usage: %prog URL
-                            see if the server is alive
+parser = optparse.OptionParser(textwrap.dedent("""\
+     %prog URL
 
-                       %prog URL -n > key.txt
-                            generate a new key and key encrypting key
+           see if the server is alive
 
-                       %prog URL -e key.txt data.txt > encrypted.txt
-                            encrypt data
+           %prog URL -n > key.txt
+                generate a new key and key encrypting key
 
-                       %prog URL -d key.txt encrytped.txt > data.txt
-                            decrypt data
+           %prog URL -e key.txt data.txt > encrypted.txt
+                encrypt data
 
-                       %prog URL -g key.txt > secretkey.bin
-                            get the secret encryption key
-                """.rstrip()),
-                description="Client for a Key Management Server.")
-    parser.add_option('-n', '--new',
-                      help='generate a new key',
-                      action='store_const', dest='action',
-                      const=new_key)
-    parser.add_option('-g', '--get-key',
-                      help='get key',
-                      action='store_const', dest='action',
-                      const=get_key)
-    parser.add_option('-e', '--encrypt',
-                      help='encrypt data',
-                      action='store_const', dest='action',
-                      const=encrypt)
-    parser.add_option('-d', '--decrypt',
-                      help='decrypt data',
-                      action='store_const', dest='action',
-                      const=decrypt)
-    opts, args = parser.parse_args()
+           %prog URL -d key.txt encrytped.txt > data.txt
+                decrypt data
+
+           %prog URL -g key.txt > secretkey.bin
+                get the secret encryption key
+    """.rstrip()),
+    description="Client for a Key Management Server.")
+parser.add_option(
+    '-n', '--new',
+    help='generate a new key',
+    action='store_const', dest='action',
+    const=new_key)
+parser.add_option(
+    '-g', '--get-key',
+    help='get key',
+    action='store_const', dest='action',
+    const=get_key)
+parser.add_option(
+    '-e', '--encrypt',
+    help='encrypt data',
+    action='store_const', dest='action',
+    const=encrypt)
+parser.add_option(
+    '-d', '--decrypt',
+    help='decrypt data',
+    action='store_const', dest='action',
+    const=decrypt)
+
+def main(argv=None):
+    if argv is None:
+        argv = sys.argv[1:]
+    opts, args = parser.parse_args(argv)
     if not opts.action:
         opts.action = ping
     if not args:
-        parser.error('please specify the KMS server URL')
+        parser.error('Please specify the KMS server URL')
 
     url = args.pop(0)
     kmf = LocalKeyManagementFacility(url)
 
     try:
         opts.action(kmf, *args)
-    except TypeError:
+    except TypeError, err:
         parser.error('incorrect number of arguments')

Modified: keas.kmi/trunk/src/keas/kmi/testing.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/testing.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/testing.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -12,20 +12,18 @@
 #
 ##############################################################################
 """
-$Id$
 """
+import StringIO
+import webob
+from zope.publisher import browser
+from zope.interface import implements
+from keas.kmi import facility, rest, interfaces
 
-import cStringIO
 try:
     from hashlib import md5
 except ImportError:
     from md5 import md5
 
-from zope.publisher import browser
-from zope.interface import implements
-
-from keas.kmi import facility, rest, interfaces
-
 KeyEncyptingKey = '''-----BEGIN RSA PRIVATE KEY-----
 MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9
 rQlU/x/NWxG0vvFCnrDn7VvQN+syb3+a0DMCAgChAkAzKw3lwPxw0VVccq1J7qeO
@@ -42,44 +40,79 @@
     '\x10}[\xfd\x19\x98\xb1\xfa*V~U\xdf\t\x02\x01\xa6\xa8\xae\x8b\x8cm\xd9n'
     '\xd5\x83\xa1%k\x16lfuY\\q\x8c\x8b')
 
-class FakeRESTClient(object):
+class FakeHTTPMessage(object):
 
-    context = None
+    def __init__(self, res):
+        self.res = res
+        self.headers = ['Server: Fake/1.0']
 
-    def __init__(self, url):
-        self.url = url
+class FakeHTTPResponse(object):
 
-    def post(self, url, data=None, headers={}):
-        io = cStringIO.StringIO(data) if data else None
-        request = browser.TestRequest(io)
-        request.method = 'POST'
+    # These attributes should be overridden by the test setup.
+    status = 200
+    reason = 'Ok'
+
+    def __init__(self, data):
+        self.fp = StringIO.StringIO(data)
+        self.msg = FakeHTTPMessage(self)
+
+    def read(self, amt=10*2**10):
+        data = self.fp.read(amt)
+        if self.fp.len == self.fp.tell():
+            self.fp = None
+        return data
+
+    def getheader(self, name):
+        if name.lower() == 'content-length':
+            return str(len(self.fp.getvalue()))
+
+    def close(self):
+        pass
+
+
+class FakeHTTPConnection(object):
+
+    def __init__(self, host, port=None, timeout=10):
+        self.host = host
+        self.port = port
+        self.request_data = None
+
+    def request(self, method, url, body=None, headers=None):
+        self.request_data = (method, url, body, headers)
+
+    def getresponse(self, buffering=False):
+        url = self.request_data[1]
         if url == '/new':
-            klass = rest.NewView
+            view = rest.create_key
         elif url == '/key':
-            if headers.get('content-type') != 'text/plain':
+            if self.request_data[3].get('content-type') != 'text/plain':
                 # ensure we don't trip on
                 # http://trac.pythonpaste.org/pythonpaste/ticket/294
                 raise ValueError('bad content type')
-            klass = rest.KeyView
+            view = rest.get_key
         else:
             raise ValueError(url)
 
-        view = klass(self.context, request)
-        self.contents = view()
+        io = StringIO.StringIO(self.request_data[2])
+        req = webob.Request({'wsgi.input': io})
+        res = view(self.context, req)
+        return FakeHTTPResponse(res.body)
 
+    def close(self):
+        pass
 
 def setupRestApi(localFacility, masterFacility):
-    MyFakeRESTClient = type(
-        'FakeRESTClient', (FakeRESTClient,), {'context': masterFacility})
-    localFacility.clientClass = MyFakeRESTClient
+    localFacility.httpConnFactory = type(
+        'MyFakeHTTPConnection', (FakeHTTPConnection,),
+        {'context': masterFacility})
 
 
 class TestingKeyManagementFacility(facility.KeyManagementFacility):
 
-    def __init__(self):
-        super(TestingKeyManagementFacility, self).__init__()
+    def __init__(self, storage_dir):
+        super(TestingKeyManagementFacility, self).__init__(storage_dir)
         md5Key = md5(KeyEncyptingKey).hexdigest()
-        self[md5Key] = facility.Key(EncryptedEncryptionKey)
+        self[md5Key] = EncryptedEncryptionKey
 
     def generate(self):
         return KeyEncyptingKey

Deleted: keas.kmi/trunk/src/keas/kmi/testing.zcml
===================================================================
--- keas.kmi/trunk/src/keas/kmi/testing.zcml	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/testing.zcml	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,28 +0,0 @@
-<configure
-    xmlns="http://namespaces.zope.org/zope"
-    i18n_domain="keas">
-
-  <include file="security.zcml" />
-
-  <!-- We purposefully keep the access to the facility very limited -->
-  <class class=".testing.TestingKeyManagementFacility">
-    <require
-	permission="keas.kmi.Encrypt"
-	attribute="encrypt decrypt"
-	/>
-    <require
-	permission="keas.kmi.GenerateKey"
-	attribute="generate"
-	/>
-    <require
-	permission="keas.kmi.AccessKey"
-	attribute="getEncryptionKey"
-	/>
-  </class>
-
-  <utility
-      factory=".testing.TestingKeyManagementFacility"
-      provides=".interfaces.IKeyManagementFacility"
-      />
-
-</configure>

Modified: keas.kmi/trunk/src/keas/kmi/tests.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/tests.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/tests.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -14,24 +14,22 @@
 """
 $Id$
 """
-import unittest
 import doctest
-
+import tempfile
 import transaction
+import unittest
+
 from zope.app.testing import setup
 from zope.component import provideUtility
-from zope.interface.verify import verifyObject
 
 from keas.kmi.testing import TestingKeyManagementFacility
-from keas.kmi.keyholder import KeyHolder
 from keas.kmi.interfaces import IKeyManagementFacility
-from keas.kmi.interfaces import IKeyHolder
 
 
 def setUpPersistent(test):
     setup.setUpTestAsModule(test, name='keas.kmi.tests.doctestfile')
     setup.placelessSetUp()
-    provideUtility(TestingKeyManagementFacility(),
+    provideUtility(TestingKeyManagementFacility(tempfile.mkdtemp()),
                    provides=IKeyManagementFacility)
 
 
@@ -41,25 +39,16 @@
     setup.tearDownTestAsModule(test)
 
 
-def doctest_KeyHolder():
-    """Smoke test for the KeyHolder class.
-
-        >>> holder = KeyHolder(__file__)
-        >>> verifyObject(IKeyHolder, holder)
-        True
-
-    """
-
-
 def test_suite():
     return unittest.TestSuite([
         doctest.DocFileSuite(
             'README.txt',
-            setUp=setup.placelessSetUp, tearDown=setup.placelessTearDown,
             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
         doctest.DocFileSuite(
+            'facility.txt',
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+        doctest.DocFileSuite(
             'persistent.txt',
             setUp=setUpPersistent, tearDown=tearDownPersistent,
             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
-        doctest.DocTestSuite(),
     ])

Modified: keas.kmi/trunk/src/keas/kmi/wsgi.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/wsgi.py	2010-09-28 20:53:06 UTC (rev 117037)
+++ keas.kmi/trunk/src/keas/kmi/wsgi.py	2010-09-29 04:38:02 UTC (rev 117038)
@@ -1,14 +1,34 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
 """
 WSGI application for the Key Management Server.
 """
-
+import keas.kmi
 import os
+from repoze.bfg.router import make_app
+from keas.kmi import facility
 
-from zope.app.wsgi import getWSGIApplication
+FACILITY = None
 
+def get_facility(environ):
+    return FACILITY
 
-def application_factory(global_conf, conf='kms.conf', **local_conf):
-    configfile = os.path.join(global_conf['here'], conf)
-    application = getWSGIApplication(configfile)
-    return application
+def application_factory(global_config, **kw):
+    storage_dir = os.path.abspath(kw['storage-dir'])
+    if not os.path.exists(storage_dir):
+        os.mkdir(storage_dir)
+    global FACILITY
+    FACILITY = facility.KeyManagementFacility(storage_dir)
+    return make_app(get_facility, keas.kmi, options=kw)
 



More information about the checkins mailing list