[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