Skip to content

Commit

Permalink
fix str vs bytes on Python 3, tests pass
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentfretin committed May 8, 2017
1 parent 512f5ba commit edba88c
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 38 deletions.
43 changes: 22 additions & 21 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 @@ -79,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'
b'Stephan Richter'

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 @@ -136,7 +137,7 @@ 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'
b'Stephan Richter'

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

Expand Down Expand Up @@ -183,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'
b'Stephan Richter'

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 @@ -223,7 +224,7 @@ decryption (private) key.
>>> firstTime = localKeys._LocalKeyManagementFacility__cache[key][0]

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

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

Expand All @@ -238,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 @@ -271,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 @@ -299,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 @@ -331,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 key.decode().split('\n')[1]

>>> getKeySegment(testingKeys.generate())
'MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9'
Expand All @@ -347,13 +348,13 @@ 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...'
b'_\xc4\x04\xbe5B\x7f\xaf\xd6\x92\xbd\xa0\xcf\x156\x1d\x88=p9{\xaa...'

We can also safely en- and decrypt:

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


Key Holder
Expand Down
17 changes: 11 additions & 6 deletions src/keas/kmi/facility.py
Expand Up @@ -23,7 +23,7 @@
try:
# Python 3
from http.client import HTTPSConnection
from urllib import parse as urlparse
from urllib.parse import urlparse
except ImportError:
# Python 2
from httplib import HTTPSConnection
Expand Down Expand Up @@ -60,12 +60,15 @@ def _pkcs7Encode(self, text, k=16):
return text + binascii.unhexlify(n * ("%02x" % n))

def _pkcs7Decode(self, text, k=16):
n = int(binascii.hexlify(text[-1]), 16)
# In Python 3, text[-1] returns an int, not bytes, we need text[-1:] to
# have bytes. In Python 2, it doesn't matter, both return str.
# Actually it seems we could just do `n = text[-1]` in Python 3.
n = int(binascii.hexlify(text[-1:]), 16)
if n > k:
raise ValueError("Input is not padded or padding is corrupt")
return text[:-n]

_bytesToKeySalt = '12345678'
_bytesToKeySalt = b'12345678'
def _bytesToKey(self, data):
# Simplified version of M2Crypto.m2.bytes_to_key().
assert len(self._bytesToKeySalt) == 8, len(self._bytesToKeySalt)
Expand Down Expand Up @@ -103,7 +106,9 @@ def encrypt_file(self, key, fsrc, fdst, chunksize=24*1024):
encryptionKey = self._bytesToKey(self.getEncryptionKey(key))

# 2. Create a random initialization vector
iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
# bytes(bytearray(generator)) is needed for Python 2,
# with Python 3 bytes(generator) works
iv = bytes(bytearray((random.randint(0, 0xFF)) for i in range(16)))

# 3. Create a cipher object
cipher = self.CipherFactory.new(
Expand Down Expand Up @@ -132,7 +137,7 @@ def encrypt_file(self, key, fsrc, fdst, chunksize=24*1024):

# Apply padding.
if len(chunk) % 16 != 0:
chunk += ' ' * (16 - len(chunk) % 16)
chunk += b' ' * (16 - len(chunk) % 16)

# Write the chunk
fdst.write(cipher.encrypt(chunk))
Expand Down Expand Up @@ -318,7 +323,7 @@ def generate(self):
"""See interfaces.IKeyGenerationService"""
pieces = urlparse(self.url)
conn = self.httpConnFactory(pieces.netloc)
conn.request('POST', '/new', '', {})
conn.request('POST', '/new', b'', {})
response = conn.getresponse()
data = response.read()
response.close()
Expand Down
3 changes: 2 additions & 1 deletion src/keas/kmi/keyholder.py
Expand Up @@ -23,5 +23,6 @@ class KeyHolder(object):
"""A key holder utility that loads the key from a file and keeps it in RAM."""

def __init__(self, filename):
self.key = file(filename, 'rb').read()
with open(filename, 'rb') as f:
self.key = f.read()

10 changes: 5 additions & 5 deletions src/keas/kmi/persistent.txt
Expand Up @@ -27,11 +27,11 @@ that you're supposed to provide in your application.

None of the raw data appears in the pickle

>>> import cPickle as pickle
>>> from zodbpickle import pickle
>>> pickled_data = pickle.dumps(userdata)
>>> 'Stephan' in pickled_data
>>> b'Stephan' in pickled_data
False
>>> '123456789' in pickled_data
>>> b'123456789' in pickled_data
False

We can successfully load it
Expand All @@ -48,9 +48,9 @@ from `EncryptedPersistent` will be encrypted.
>>> users['mgedmin'] = UserPrivateData('Marius Gedminas', '987654321')

>>> pickled_data = pickle.dumps(users)
>>> 'stephan' in pickled_data
>>> b'stephan' in pickled_data
True
>>> '123456789' in pickled_data
>>> b'123456789' in pickled_data
False


Expand Down
5 changes: 4 additions & 1 deletion src/keas/kmi/rest.py
Expand Up @@ -17,19 +17,22 @@

def get_status(context, request):
return Response(
'KMS server holding %d keys' %len(context),
'KMS server holding %d keys' % len(context),
charset='utf-8',
headerlist=[('Content-Type', 'text/plain')])

def create_key(context, request):
return Response(
context.generate(),
charset='utf-8',
headerlist=[('Content-Type', 'text/plain')])

def get_key(context, request):
key = request.body
try:
return Response(
context.getEncryptionKey(key),
charset='utf-8',
headerlist=[('Content-Type', 'text/plain')])
except KeyError:
return exc.HTTPNotFound('Key not found')
Expand Down
8 changes: 4 additions & 4 deletions src/keas/kmi/testing.py
Expand Up @@ -20,7 +20,7 @@

from hashlib import md5

KeyEncyptingKey = '''-----BEGIN RSA PRIVATE KEY-----
KeyEncyptingKey = b'''-----BEGIN RSA PRIVATE KEY-----
MIIBOAIBAAJBAL+VS9lDsS9XOaeJppfK9lhxKMRFdcg50MR3aJEQK9rvDEqNwBS9
rQlU/x/NWxG0vvFCnrDn7VvQN+syb3+a0DMCAgChAkAzKw3lwPxw0VVccq1J7qeO
4DXR1iEMIoWruiCyq0aLkHnQzrZpaHnd4w+JNKIGOVDEWItf3iZNMXkoqj2hoPmp
Expand All @@ -32,9 +32,9 @@
'''

EncryptedEncryptionKey = (
'\xbc\x08\xdbo\x04\xe3\xc7G\x13\xd3\x86\x92\xfa\xe8i>,+\xda\xf8/B2]s\xd4'
'\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')
b'\xbc\x08\xdbo\x04\xe3\xc7G\x13\xd3\x86\x92\xfa\xe8i>,+\xda\xf8/B2]s\xd4'
b'\x10}[\xfd\x19\x98\xb1\xfa*V~U\xdf\t\x02\x01\xa6\xa8\xae\x8b\x8cm\xd9n'
b'\xd5\x83\xa1%k\x16lfuY\\q\x8c\x8b')

class FakeHTTPMessage(object):

Expand Down

0 comments on commit edba88c

Please sign in to comment.