Repository: boto/boto

Issue:
KMS Doesn't Work in Python 3
Specifically, encrypting and decrypting keys doesn't handle binary/string types correctly. The unit tests in tests/unit/kms fail under Python 3.3 and 3.4. I'll be submitting a pull request shortly that fixes this.

```
~/ext/boto (develop)$ nosetests-3.4 tests/unit/kms
EE.
======================================================================
ERROR: This test ensures that binary is base64 encoded when it is sent to
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/doug/ext/boto/tests/unit/kms/test_kms.py", line 41, in test_binary_input
    self.service_connection.encrypt(key_id='foo', plaintext=data)
  File "/Users/doug/ext/boto/boto/kms/layer1.py", line 412, in encrypt
    body=json.dumps(params))
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 192, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 250, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 173, in default
    raise TypeError(repr(o) + " is not JSON serializable")
nose.proxy.TypeError: b'AAECAwQF' is not JSON serializable
-------------------- >> begin captured logging << --------------------
boto: DEBUG: Using access key provided by client.
boto: DEBUG: Using secret key provided by client.
--------------------- >> end captured logging << ---------------------

======================================================================
ERROR: This test ensures that the output is base64 decoded before
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/doug/ext/boto/tests/unit/kms/test_kms.py", line 62, in test_binary_ouput
    response = self.service_connection.decrypt(b'some arbitrary value')
  File "/Users/doug/ext/boto/boto/kms/layer1.py", line 288, in decrypt
    body=json.dumps(params))
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/__init__.py", line 230, in dumps
    return _default_encoder.encode(obj)
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 192, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 250, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/Cellar/python3/3.4.2_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/json/encoder.py", line 173, in default
    raise TypeError(repr(o) + " is not JSON serializable")
nose.proxy.TypeError: b'c29tZSBhcmJpdHJhcnkgdmFsdWU=' is not JSON serializable
-------------------- >> begin captured logging << --------------------
boto: DEBUG: Using access key provided by client.
boto: DEBUG: Using secret key provided by client.
--------------------- >> end captured logging << ---------------------

----------------------------------------------------------------------
Ran 3 tests in 0.007s

FAILED (errors=2)
```



Discussion:

Resolving patch:
diff --git a/boto/kms/layer1.py b/boto/kms/layer1.py
--- a/boto/kms/layer1.py
+++ b/boto/kms/layer1.py
@@ -279,7 +279,7 @@ def decrypt(self, ciphertext_blob, encryption_context=None,
                 "Value of argument ``ciphertext_blob`` "
                 "must be of type %s." % six.binary_type)
         ciphertext_blob = base64.b64encode(ciphertext_blob)
-        params = {'CiphertextBlob': ciphertext_blob, }
+        params = {'CiphertextBlob': ciphertext_blob.decode('utf-8'), }
         if encryption_context is not None:
             params['EncryptionContext'] = encryption_context
         if grant_tokens is not None:
@@ -403,7 +403,7 @@ def encrypt(self, key_id, plaintext, encryption_context=None,
                 "Value of argument ``plaintext`` "
                 "must be of type %s." % six.binary_type)
         plaintext = base64.b64encode(plaintext)
-        params = {'KeyId': key_id, 'Plaintext': plaintext, }
+        params = {'KeyId': key_id, 'Plaintext': plaintext.decode('utf-8'), }
         if encryption_context is not None:
             params['EncryptionContext'] = encryption_context
         if grant_tokens is not None:


Test patch:
diff --git a/tests/test.py b/tests/test.py
--- a/tests/test.py
+++ b/tests/test.py
@@ -52,6 +52,7 @@
     'tests/unit/glacier',
     'tests/unit/iam',
     'tests/unit/ec2',
+    'tests/unit/kms',
     'tests/unit/logs',
     'tests/unit/manage',
     'tests/unit/mws',
diff --git a/tests/unit/kms/test_kms.py b/tests/unit/kms/test_kms.py
--- a/tests/unit/kms/test_kms.py
+++ b/tests/unit/kms/test_kms.py
@@ -39,7 +39,7 @@ def test_binary_input(self):
         self.set_http_response(status_code=200)
         data = b'\x00\x01\x02\x03\x04\x05'
         self.service_connection.encrypt(key_id='foo', plaintext=data)
-        body = json.loads(self.actual_request.body)
+        body = json.loads(self.actual_request.body.decode('utf-8'))
         self.assertEqual(body['Plaintext'], 'AAECAwQF')
 
     def test_non_binary_input_for_blobs_fails(self):