From 9b34ca92c3ac061aee2301728dc1280a83890814 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 16 Feb 2017 22:20:38 -0600 Subject: add support for update_into on CipherContext (#3190) * add support for update_into on CipherContext This allows you to provide your own buffer (like recv_into) to improve performance when repeatedly calling encrypt/decrypt on large payloads. * another skip_if * more skip_if complexity * maybe do this right * correct number of args * coverage for the coverage gods * add a cffi minimum test tox target and travis builder This tests against macOS so we capture some commoncrypto branches * extra arg * need to actually install py35 * fix * coverage for GCM decrypt in CC * no longer relevant * 1.8 now * pep8 * dramatically simplify * update docs * remove unneeded test * changelog entry * test improvements * coverage fix * add some comments to example * move the comments to their own line * fix and move comment --- tests/hazmat/primitives/test_block.py | 20 +++++ tests/hazmat/primitives/test_ciphers.py | 153 +++++++++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 3 deletions(-) (limited to 'tests/hazmat') diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index eb0a2c3b..11a70195 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -6,6 +6,8 @@ from __future__ import absolute_import, division, print_function import binascii +import cffi + import pytest from cryptography.exceptions import ( @@ -15,6 +17,7 @@ from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, base, modes ) +from cryptography.utils import _version_check from .utils import ( generate_aead_exception_test, generate_aead_tag_exception_test @@ -70,6 +73,23 @@ class TestCipherContext(object): with pytest.raises(AlreadyFinalized): decryptor.finalize() + @pytest.mark.skipif( + not _version_check(cffi.__version__, '1.7'), + reason="cffi version too old" + ) + def test_use_update_into_after_finalize(self, backend): + cipher = Cipher( + algorithms.AES(binascii.unhexlify(b"0" * 32)), + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + encryptor.finalize() + with pytest.raises(AlreadyFinalized): + buf = bytearray(31) + encryptor.update_into(b"b" * 16, buf) + def test_unaligned_block_encryption(self, backend): cipher = Cipher( algorithms.AES(binascii.unhexlify(b"0" * 32)), diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index d9a07ff6..83952a87 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -5,17 +5,24 @@ from __future__ import absolute_import, division, print_function import binascii +import os + +import cffi import pytest from cryptography.exceptions import _Reasons +from cryptography.hazmat.backends.interfaces import CipherBackend from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes from cryptography.hazmat.primitives.ciphers.algorithms import ( AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES ) -from cryptography.hazmat.primitives.ciphers.modes import ECB +from cryptography.utils import _version_check -from ...utils import raises_unsupported_algorithm +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) class TestAES(object): @@ -132,4 +139,144 @@ def test_invalid_backend(): pretend_backend = object() with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): - ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), ECB, pretend_backend) + ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), modes.ECB, pretend_backend) + + +@pytest.mark.skipif( + not _version_check(cffi.__version__, '1.7'), + reason="cffi version too old" +) +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCipherUpdateInto(object): + @pytest.mark.parametrize( + "params", + load_vectors_from_file( + os.path.join("ciphers", "AES", "ECB", "ECBGFSbox128.rsp"), + load_nist_vectors + ) + ) + def test_update_into(self, params, backend): + key = binascii.unhexlify(params["key"]) + pt = binascii.unhexlify(params["plaintext"]) + ct = binascii.unhexlify(params["ciphertext"]) + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt, buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"0" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_update_into_gcm(self, backend): + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") + pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") + c = ciphers.Cipher(AES(key), modes.GCM(iv), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt, buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + encryptor.finalize() + c = ciphers.Cipher(AES(key), modes.GCM(iv, encryptor.tag), backend) + decryptor = c.decryptor() + res = decryptor.update_into(ct, buf) + decryptor.finalize() + assert res == len(pt) + assert bytes(buf)[:res] == pt + + @pytest.mark.parametrize( + "params", + load_vectors_from_file( + os.path.join("ciphers", "AES", "ECB", "ECBGFSbox128.rsp"), + load_nist_vectors + ) + ) + def test_update_into_multiple_calls(self, params, backend): + key = binascii.unhexlify(params["key"]) + pt = binascii.unhexlify(params["plaintext"]) + ct = binascii.unhexlify(params["ciphertext"]) + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(len(pt) + 15) + res = encryptor.update_into(pt[:3], buf) + assert res == 0 + res = encryptor.update_into(pt[3:], buf) + assert res == len(pt) + assert bytes(buf)[:res] == ct + + def test_update_into_buffer_too_small(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.ECB(), backend) + encryptor = c.encryptor() + buf = bytearray(16) + with pytest.raises(ValueError): + encryptor.update_into(b"testing", buf) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) + ), + skip_message="Does not support AES GCM", + ) + def test_update_into_buffer_too_small_gcm(self, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), modes.GCM(b"\x00" * 12), backend) + encryptor = c.encryptor() + buf = bytearray(5) + with pytest.raises(ValueError): + encryptor.update_into(b"testing", buf) + + +@pytest.mark.skipif( + _version_check(cffi.__version__, '1.7'), + reason="cffi version too new" +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCipherUpdateIntoUnsupported(object): + def _too_old(self, mode, backend): + key = b"\x00" * 16 + c = ciphers.Cipher(AES(key), mode, backend) + encryptor = c.encryptor() + buf = bytearray(32) + with pytest.raises(NotImplementedError): + encryptor.update_into(b"\x00" * 16, buf) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES ECB", + ) + def test_cffi_too_old_ecb(self, backend): + self._too_old(modes.ECB(), backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.CTR(b"0" * 16) + ), + skip_message="Does not support AES CTR", + ) + def test_cffi_too_old_ctr(self, backend): + self._too_old(modes.CTR(b"0" * 16), backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + AES(b"\x00" * 16), modes.GCM(b"0" * 16) + ), + skip_message="Does not support AES GCM", + ) + def test_cffi_too_old_gcm(self, backend): + self._too_old(modes.GCM(b"0" * 16), backend) -- cgit v1.2.3