From edba88c75263c33c11e2c35cd33d7fc982f1757c Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Mon, 8 May 2017 13:54:17 +0200 Subject: [PATCH] fix str vs bytes on Python 3, tests pass --- src/keas/kmi/README.txt | 43 +++++++++++++++++++------------------ src/keas/kmi/facility.py | 17 +++++++++------ src/keas/kmi/keyholder.py | 3 ++- src/keas/kmi/persistent.txt | 10 ++++----- src/keas/kmi/rest.py | 5 ++++- src/keas/kmi/testing.py | 8 +++---- 6 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/keas/kmi/README.txt b/src/keas/kmi/README.txt index 3c5db18..915274d 100644 --- a/src/keas/kmi/README.txt +++ b/src/keas/kmi/README.txt @@ -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() @@ -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----- @@ -79,22 +80,22 @@ 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() @@ -102,11 +103,11 @@ We can also encrypt data given by a file descriptor 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 @@ -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] @@ -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 @@ -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] @@ -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----- @@ -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----- @@ -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 @@ -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' @@ -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 diff --git a/src/keas/kmi/facility.py b/src/keas/kmi/facility.py index e1ec895..7ffb9dc 100644 --- a/src/keas/kmi/facility.py +++ b/src/keas/kmi/facility.py @@ -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 @@ -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) @@ -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( @@ -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)) @@ -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() diff --git a/src/keas/kmi/keyholder.py b/src/keas/kmi/keyholder.py index f3bbc6b..ca1f475 100644 --- a/src/keas/kmi/keyholder.py +++ b/src/keas/kmi/keyholder.py @@ -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() diff --git a/src/keas/kmi/persistent.txt b/src/keas/kmi/persistent.txt index 6ff1254..cabb877 100644 --- a/src/keas/kmi/persistent.txt +++ b/src/keas/kmi/persistent.txt @@ -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 @@ -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 diff --git a/src/keas/kmi/rest.py b/src/keas/kmi/rest.py index 6175f43..3b4e80f 100644 --- a/src/keas/kmi/rest.py +++ b/src/keas/kmi/rest.py @@ -17,12 +17,14 @@ 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): @@ -30,6 +32,7 @@ def get_key(context, request): try: return Response( context.getEncryptionKey(key), + charset='utf-8', headerlist=[('Content-Type', 'text/plain')]) except KeyError: return exc.HTTPNotFound('Key not found') diff --git a/src/keas/kmi/testing.py b/src/keas/kmi/testing.py index 5192cc4..a9b2a13 100644 --- a/src/keas/kmi/testing.py +++ b/src/keas/kmi/testing.py @@ -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 @@ -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):