Repository: pyca/cryptography

Issue:
Allow bytearray in padding and unpadding
It looks like in the padding code, it requires the data passed to padding and unpadding to be bytes, and does not allow bytearray's. I saw in #4409 that bytes like objects were allowed as key material and such, does it make sense to allow them to be given to padders and unpadders? If so, I think it's just two instances of `_check_bytes` that would need to changed to `_check_byteslike` in https://github.com/pyca/cryptography/blob/master/src/cryptography/hazmat/primitives/padding.py, line 43 and 67.


Discussion:
Allowing buffers in padding seems reasonable -- though it's kind of pointless unless we change the internals of the padders, which would make tons of copies as currently written.
I don't quite follow?
The point of allowing buffers is that you can clear the memory, and thus be resillient to memory disclosure vulnerabilities.

Right now our padding implementation makes many copies of the memory, so even if the caller cleared _their_ copy, you wouldn't be secure against a disclosure vulnerability. Therefore we'e need to refactor the implementation to not make so many copies before we should support passing buffers.
Ah, I see. I admit, I was only thinking of convenience in this case, after passing a bytearray to a padder and being rejected.

Resolving patch:
diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py
--- a/src/cryptography/hazmat/primitives/padding.py
+++ b/src/cryptography/hazmat/primitives/padding.py
@@ -40,9 +40,9 @@ def _byte_padding_update(buffer_, data, block_size):
     if buffer_ is None:
         raise AlreadyFinalized("Context was already finalized.")
 
-    utils._check_bytes("data", data)
+    utils._check_byteslike("data", data)
 
-    buffer_ += data
+    buffer_ += bytes(data)
 
     finished_blocks = len(buffer_) // (block_size // 8)
 
@@ -64,9 +64,9 @@ def _byte_unpadding_update(buffer_, data, block_size):
     if buffer_ is None:
         raise AlreadyFinalized("Context was already finalized.")
 
-    utils._check_bytes("data", data)
+    utils._check_byteslike("data", data)
 
-    buffer_ += data
+    buffer_ += bytes(data)
 
     finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0)
 


Test patch:
diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py
--- a/tests/hazmat/primitives/test_padding.py
+++ b/tests/hazmat/primitives/test_padding.py
@@ -109,6 +109,18 @@ def test_large_padding(self):
 
         assert data == b""
 
+    def test_bytearray(self):
+        padder = padding.PKCS7(128).padder()
+        unpadded = bytearray(b"t" * 38)
+        padded = (
+            padder.update(unpadded)
+            + padder.update(unpadded)
+            + padder.finalize()
+        )
+        unpadder = padding.PKCS7(128).unpadder()
+        final = unpadder.update(padded) + unpadder.finalize()
+        assert final == unpadded + unpadded
+
 
 class TestANSIX923(object):
     @pytest.mark.parametrize("size", [127, 4096, -2])
@@ -193,3 +205,15 @@ def test_use_after_finalize(self):
             unpadder.update(b"")
         with pytest.raises(AlreadyFinalized):
             unpadder.finalize()
+
+    def test_bytearray(self):
+        padder = padding.ANSIX923(128).padder()
+        unpadded = bytearray(b"t" * 38)
+        padded = (
+            padder.update(unpadded)
+            + padder.update(unpadded)
+            + padder.finalize()
+        )
+        unpadder = padding.ANSIX923(128).unpadder()
+        final = unpadder.update(padded) + unpadder.finalize()
+        assert final == unpadded + unpadded