Skip to content

Commit

Permalink
Merge pull request #2 from ecreall/master
Browse files Browse the repository at this point in the history
Port to Python 3
  • Loading branch information
vincentfretin committed May 15, 2017
2 parents 0e5baf4 + 12e3f2b commit 85c14a0
Show file tree
Hide file tree
Showing 20 changed files with 227 additions and 144 deletions.
4 changes: 1 addition & 3 deletions .gitignore
Expand Up @@ -14,6 +14,4 @@ local/
# Test data
datakey.dat
kek.dat
sample.cert
sample.key
source-cache.log
source-cache.log
3 changes: 3 additions & 0 deletions .travis.yml
@@ -1,6 +1,9 @@
language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
install:
- python bootstrap.py
- bin/buildout
Expand Down
14 changes: 7 additions & 7 deletions README.txt
Expand Up @@ -3,29 +3,29 @@ This package provides a NIST SP 800-57 compliant Key Management Infrastructure

To get started do::

$ python bootstrap.py # Must be Python 2.5 or higher
$ ./bin/buildout # Depends on successfull compilation of M2Crypto
$ ./bin/runserver # or ./bin/paster serve server.ini
$ python bootstrap.py # Must be Python 2.7 or higher
$ ./bin/buildout # Depends on successful compilation of M2Crypto
$ ./bin/runserver # or ./bin/gunicorn --paste server.ini

The server will come up on port 8080. You can create a new key encrypting key
using::

$ wget https://localhost:8080/new -O kek.dat --ca-certificate sample.pem \
$ wget https://localhost:8080/new -O kek.dat --ca-certificate sample.crt \
--post-data=""

or, if you want a more convenient tool::

$ ./bin/testclient https://localhost:8080/new -n > kek.dat
$ ./bin/testclient https://localhost:8080 -n > kek.dat

The data encryption key can now be retrieved by posting the KEK to another
URL::

$ wget https://localhost:8080/key --header 'Content-Type: text/plain' \
--post-file kek.dat -O datakey.dat --ca-certificate sample.pem
--post-file kek.dat -O datakey.dat --ca-certificate sample.crt

or ::

$ ./bin/testclient https://localhost:8080/new -g kek.dat > datakey.dat
$ ./bin/testclient https://localhost:8080 -g kek.dat > datakey.dat

Note: To be compliant, the server must use an encrypted communication channel
of course. The ``--ca-certificate`` tells wget to trust the sample self-signed
Expand Down
8 changes: 4 additions & 4 deletions buildout.cfg
Expand Up @@ -32,17 +32,17 @@ eggs = keas.kmi
[paster]
recipe = zc.recipe.egg
eggs = keas.kmi
PasteScript
gunicorn
pyOpenSSL
scripts = paster
scripts = gunicorn

[runserver]
recipe = zc.recipe.egg
eggs = ${paster:eggs}
scripts = paster=runserver
scripts = gunicorn=runserver
initialization =
import sys
sys.argv[1:] = ['serve', 'server.ini']
sys.argv[1:] = ['--paste', 'server.ini']

[testclient]
recipe = zc.recipe.egg
Expand Down
5 changes: 1 addition & 4 deletions generate-sample-cert.sh
@@ -1,6 +1,3 @@
#!/bin/sh
openssl genrsa 1024 > sample.key
openssl req -new -x509 -nodes -sha1 -days 3650 -key sample.key > sample.cert
cat sample.cert sample.key > sample.pem
rm sample.key sample.cert

openssl req -new -x509 -nodes -sha256 -days 3650 -key sample.key > sample.crt
15 changes: 0 additions & 15 deletions sample.pem → sample.crt
Expand Up @@ -14,18 +14,3 @@ Y5m90HRgsDcXVbhn0rRfcC8o4EtGDvCjqsFYXy/ImF9tjEiuaysxbqepl+XMszPE
1kO50quWsV1FLSdcJX6t/ofJYOxiQkqPvg9t/ovTnEZ+w4NfPo+0MJgudjJoD2+w
5UTsKtU=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDK17rB/KVaK8MVjiEkvA4ZncOOIC3nStZ/erXM+qwkghPM4Tfr
2FTUiTgwwdLdu/ht74oWnppttfaTQ+sVz2rFXnPgfqKTGoJTwWFiuNuZhSRDVssG
VnL/RatZW6wns8UNf+W4hUe6/vGQP6obNTe2T4R+t2hXP51OkOy4BMcq0QIDAQAB
AoGAHcDJDx1M784NfoLrj6TZ+J3wik9kDFIo5mgMdLWsPGqsFthOSJTh1I8QI+66
THX++bkyKyE2i7MuKOnEeN2Ezo2jAThF7XoWhm6/+pSXhSqmL1jKr/1CZRaR9jv0
cCVJc3mTuAGH+yFVeGpWNvDaCmOUlD5M48xTROJXteDQ0TECQQDuDM9pmQdqkGIp
dvbIviS8donYn0kJ0TKS14pMtb/C63lcld513rHS43ru3FRY9baR/q5vV9vW5RhH
S7w4cYvVAkEA2iNLsFEAkY88oZJYbdyybeKxZdReyes1/zPe4RYzRdbDHRNAa+zk
mZIZDI820E0Y+DeoT+q3nXkXiiOS/iRNDQJBAKdAvOH2sO1AcJetjArS/cCkkIlw
sMKDB0OAyRzIfekXxPc2HU03oD0Jsy/sAh9W1GWTST/VvRIpeHtvTNljfdkCQF5T
UuBcNoW6zXoEYU6oV1Oi6hjhW1eu6PuAv4jPY754XoiNEZdZqYQqo8BFkWtDW1/C
GXrtQRbMDPzD40UYB2UCQQCmJpJp+u2lHj7zuZikHIHQBNyXyoGnzgNs6XUj1Bs6
Y4vjue8w6RkRLZ1YGP+xqsngVqb9IRygyLDpEgwEnOT4
-----END RSA PRIVATE KEY-----
15 changes: 15 additions & 0 deletions sample.key
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDK17rB/KVaK8MVjiEkvA4ZncOOIC3nStZ/erXM+qwkghPM4Tfr
2FTUiTgwwdLdu/ht74oWnppttfaTQ+sVz2rFXnPgfqKTGoJTwWFiuNuZhSRDVssG
VnL/RatZW6wns8UNf+W4hUe6/vGQP6obNTe2T4R+t2hXP51OkOy4BMcq0QIDAQAB
AoGAHcDJDx1M784NfoLrj6TZ+J3wik9kDFIo5mgMdLWsPGqsFthOSJTh1I8QI+66
THX++bkyKyE2i7MuKOnEeN2Ezo2jAThF7XoWhm6/+pSXhSqmL1jKr/1CZRaR9jv0
cCVJc3mTuAGH+yFVeGpWNvDaCmOUlD5M48xTROJXteDQ0TECQQDuDM9pmQdqkGIp
dvbIviS8donYn0kJ0TKS14pMtb/C63lcld513rHS43ru3FRY9baR/q5vV9vW5RhH
S7w4cYvVAkEA2iNLsFEAkY88oZJYbdyybeKxZdReyes1/zPe4RYzRdbDHRNAa+zk
mZIZDI820E0Y+DeoT+q3nXkXiiOS/iRNDQJBAKdAvOH2sO1AcJetjArS/cCkkIlw
sMKDB0OAyRzIfekXxPc2HU03oD0Jsy/sAh9W1GWTST/VvRIpeHtvTNljfdkCQF5T
UuBcNoW6zXoEYU6oV1Oi6hjhW1eu6PuAv4jPY754XoiNEZdZqYQqo8BFkWtDW1/C
GXrtQRbMDPzD40UYB2UCQQCmJpJp+u2lHj7zuZikHIHQBNyXyoGnzgNs6XUj1Bs6
Y4vjue8w6RkRLZ1YGP+xqsngVqb9IRygyLDpEgwEnOT4
-----END RSA PRIVATE KEY-----
6 changes: 4 additions & 2 deletions server.ini
Expand Up @@ -3,10 +3,12 @@ use = egg:keas.kmi
storage-dir=keys/

[server:main]
use = egg:Paste#http
use = egg:gunicorn#main
host = 0.0.0.0
port = 8080
ssl_pem = sample.pem
worker_class = sync
keyfile = sample.key
certfile = sample.crt

# Logging Configuration
[loggers]
Expand Down
8 changes: 8 additions & 0 deletions setup.py
Expand Up @@ -43,6 +43,13 @@ def read(*rnames):
'Intended Audience :: Developers',
'License :: OSI Approved :: Zope Public License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Natural Language :: English',
'Operating System :: OS Independent',
'Topic :: Internet :: WWW/HTTP'],
Expand All @@ -62,6 +69,7 @@ def read(*rnames):
'pyramid',
'pyramid_zcml',
'setuptools',
'six',
'transaction',
'zope.interface',
'zope.schema',
Expand Down
60 changes: 29 additions & 31 deletions src/keas/kmi/README.txt
Expand Up @@ -7,6 +7,7 @@ infrastructure. Part of this infrastructure is a key management facility that
provides several services related to keys. All keys are stored in a specified
storage directory.

>>> from __future__ import print_function
>>> import tempfile
>>> storage_dir = tempfile.mkdtemp()

Expand Down Expand Up @@ -54,7 +55,7 @@ key:
Let's now use the key generation service's API to generate a key.

>>> key = keys.generate()
>>> print key
>>> print(key.decode())
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
Expand All @@ -65,10 +66,7 @@ HIPAA and NIST key strength requirement.

You can now use this key encrypting key to extract the encryption keys:

>>> try:
... from hashlib import md5
... except ImportError:
... from md5 import md5
>>> from hashlib import md5
>>> hash_key = md5(key).hexdigest()

>>> len(keys.get(hash_key))
Expand All @@ -82,34 +80,34 @@ you to encrypt and decrypt a string given the key encrypting key.

Let's now encrypt some data:

>>> encrypted = keys.encrypt(key, 'Stephan Richter')
>>> encrypted = keys.encrypt(key, b'Stephan Richter')
>>> len(encrypted)
16

We can also decrypt the data.

>>> keys.decrypt(key, encrypted)
'Stephan Richter'
>>> keys.decrypt(key, encrypted) == b'Stephan Richter'
True

We can also encrypt data given by a file descriptor

>>> import tempfile
>>> tmp_file = tempfile.TemporaryFile()
>>> data="encryptioniscool"*24*1024
>>> tmp_file.write(data)
>>> tmp_file.seek(0)
>>> data = b"encryptioniscool"*24*1024
>>> pos = tmp_file.write(data)
>>> pos = tmp_file.seek(0)
>>> encrypted_file = tempfile.TemporaryFile()
>>> keys.encrypt_file(key, tmp_file, encrypted_file)
>>> tmp_file.close()

And decrypt the file

>>> decrypted_file = tempfile.TemporaryFile()
>>> encrypted_file.seek(0)
>>> pos = encrypted_file.seek(0)
>>> keys.decrypt_file(key, encrypted_file, decrypted_file)
>>> encrypted_file.close()

>>> decrypted_file.seek(0)
>>> pos = decrypted_file.seek(0)
>>> decrypted_data = decrypted_file.read()
>>> decrypted_file.close()
>>> decrypted_data == data
Expand Down Expand Up @@ -138,8 +136,8 @@ date/time the key has been fetched and the unencrypted DEK.

>>> firstTime = keys._KeyManagementFacility__dek_cache[hash_key][0]

>>> keys.decrypt(key, encrypted)
'Stephan Richter'
>>> keys.decrypt(key, encrypted) == b'Stephan Richter'
True

>>> secondTime = keys._KeyManagementFacility__dek_cache[hash_key][0]

Expand Down Expand Up @@ -186,12 +184,12 @@ As with the master facility, the local facility provides the

So en- and decryption is very easy to do:

>>> encrypted = localKeys.encrypt(key, 'Stephan Richter')
>>> encrypted = localKeys.encrypt(key, b'Stephan Richter')
>>> len(encrypted)
16

>>> localKeys.decrypt(key, encrypted)
'Stephan Richter'
>>> localKeys.decrypt(key, encrypted) == b'Stephan Richter'
True

Instead of forwarding the en- an decryption request to the master facility,
the local facility merely fetches the encryption key pair and executes the
Expand Down Expand Up @@ -225,8 +223,8 @@ decryption (private) key.

>>> firstTime = localKeys._LocalKeyManagementFacility__cache[key][0]

>>> localKeys.decrypt(key, encrypted)
'Stephan Richter'
>>> localKeys.decrypt(key, encrypted) == b'Stephan Richter'
True

>>> secondTime = localKeys._LocalKeyManagementFacility__cache[key][0]

Expand All @@ -241,7 +239,7 @@ The local facility also provides the ``IKeyGenerationService`` interface:
The local method call is identical to the master one:

>>> key2 = localKeys.generate()
>>> print key2
>>> print(key2.decode())
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
Expand Down Expand Up @@ -274,7 +272,7 @@ So let's have a look at the call:

>>> request = Request({})
>>> key3 = rest.create_key(keys, request).body
>>> print key3
>>> print(key3.decode())
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
Expand Down Expand Up @@ -302,13 +300,13 @@ encryption key string:
If you try to request a nonexistent key, you get a 404 error: encryption key
string:

>>> request.body = 'xxyz'
>>> print rest.get_key(keys, request)
>>> request.body = b'xxyz'
>>> print(rest.get_key(keys, request))
Key not found

A `GET` request to the root shows us a server status page

>>> print rest.get_status(keys, Request({}))
>>> print(rest.get_status(keys, Request({})))
200 OK
Content-Type: text/plain
Content-Length: 25
Expand All @@ -334,7 +332,7 @@ Of course, the key generation service is supported:
However, you will always receive the same key:

>>> def getKeySegment(key):
... return key.split('\n')[1]
... return str(key.decode().split('\n')[1])

>>> getKeySegment(testingKeys.generate())
'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'
Expand All @@ -349,14 +347,14 @@ However, you will always receive the same key:
All other methods remain the same:

>>> key = testingKeys.generate()
>>> testingKeys.getEncryptionKey(key)
'_\xc4\x04\xbe5B\x7f\xaf\xd6\x92\xbd\xa0\xcf\x156\x1d\x88=p9{\xaa...'
>>> testingKeys.getEncryptionKey(key) == b'_\xc4\x04\xbe5B\x7f\xaf\xd6\x92\xbd\xa0\xcf\x156\x1d\x88=p9{\xaal\xb4\x84M\x1d\xfd\xb2z\xae\x1a'
True

We can also safely en- and decrypt:

>>> encrypted = testingKeys.encrypt(key, 'Stephan Richter')
>>> testingKeys.decrypt(key, encrypted)
'Stephan Richter'
>>> encrypted = testingKeys.encrypt(key, b'Stephan Richter')
>>> testingKeys.decrypt(key, encrypted) == b'Stephan Richter'
True


Key Holder
Expand Down
59 changes: 59 additions & 0 deletions src/keas/kmi/_compat.py
@@ -0,0 +1,59 @@
##############################################################################
#
# Copyright (c) 2013 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
#
##############################################################################
import sys
from six import PY3

# This code was copied from ZODB/_compat.py

if not PY3:
# Python 2.x
# PyPy's cPickle doesn't have noload, and noload is broken in Python 2.7,
# so we need zodbpickle.
# Get the fastest working version we can (PyPy has no fastpickle)
try:
import zodbpickle.fastpickle as cPickle
except ImportError:
import zodbpickle.pickle as cPickle
Pickler = cPickle.Pickler
Unpickler = cPickle.Unpickler
else:
# Python 3.x: can't use stdlib's pickle because
# http://bugs.python.org/issue6784
import zodbpickle.pickle

class Pickler(zodbpickle.pickle.Pickler):
def __init__(self, f, protocol=None):
super(Pickler, self).__init__(f, protocol)

class Unpickler(zodbpickle.pickle.Unpickler):
def __init__(self, f):
super(Unpickler, self).__init__(f)

# Py3: Python 3 doesn't allow assignments to find_global,
# instead, find_class can be overridden

find_global = None

def find_class(self, modulename, name):
if self.find_global is None:
return super(Unpickler, self).find_class(modulename, name)
return self.find_global(modulename, name)


try:
# XXX: why not just import BytesIO from io?
from cStringIO import StringIO as BytesIO
except ImportError:
# Python 3.x
from io import BytesIO

0 comments on commit 85c14a0

Please sign in to comment.