From 9626b5a50460d2f90baa1f1b8c6a09ccc900c178 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:49:26 -0800 Subject: Validate the IV/nonce length for a given algorithm. Fixes #159 --- cryptography/hazmat/primitives/ciphers/base.py | 3 +++ cryptography/hazmat/primitives/ciphers/modes.py | 21 +++++++++++++++++++++ cryptography/hazmat/primitives/interfaces.py | 7 +++++++ docs/hazmat/primitives/interfaces.rst | 12 ++++++++++++ tests/hazmat/bindings/test_openssl.py | 3 ++- tests/hazmat/primitives/test_block.py | 17 ++++++++++++++++- 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 3d733afc..d046a012 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -28,6 +28,9 @@ class Cipher(object): if not isinstance(algorithm, interfaces.CipherAlgorithm): raise TypeError("Expected interface of interfaces.CipherAlgorithm") + if mode is not None: + mode.validate_for_algorithm(algorithm) + self.algorithm = algorithm self.mode = mode self._backend = backend diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 1d0de689..597b4e3e 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -25,11 +25,20 @@ class CBC(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector + def validate_for_algorithm(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid iv size ({0}) for {1}".format( + len(self.initialization_vector), self.name + )) + @utils.register_interface(interfaces.Mode) class ECB(object): name = "ECB" + def validate_for_algorithm(self, algorithm): + pass + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithInitializationVector) @@ -39,6 +48,12 @@ class OFB(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector + def validate_for_algorithm(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid iv size ({0}) for {1}".format( + len(self.initialization_vector), self.name + )) + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithInitializationVector) @@ -48,6 +63,12 @@ class CFB(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector + def validate_for_algorithm(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid iv size ({0}) for {1}".format( + len(self.initialization_vector), self.name + )) + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithNonce) diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 8cc9d42c..672ac96a 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -39,6 +39,13 @@ class Mode(six.with_metaclass(abc.ABCMeta)): A string naming this mode. (e.g. ECB, CBC) """ + @abc.abstractmethod + def validate_for_algorithm(self, algorithm): + """ + Checks that all the necessary invariants of this (mode, algorithm) + combination are met. + """ + class ModeWithInitializationVector(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 11cff51a..e798c0e6 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -56,6 +56,18 @@ Interfaces used by the symmetric cipher modes described in The name may be used by a backend to influence the operation of a cipher in conjunction with the algorithm's name. + .. method:: validate_for_algorithm(algorithm) + + :param CipherAlgorithm algorithm: + + Checks that the combination of this mode with the provided algorithm + meets any necessary invariants. This should raise an exception if they + are not met. + + For example, the :class:`~cryptography.hazmat.primitives.modes.CBC` + mode uses this method to check that the provided initialization + vector's length matches the block size of the algorithm. + .. class:: ModeWithInitializationVector diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index 9f27aab7..1cadc75c 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -23,7 +23,8 @@ from cryptography.hazmat.primitives.ciphers.modes import CBC class DummyMode(object): - pass + def validate_for_algorithm(self, algorithm): + pass @utils.register_interface(interfaces.CipherAlgorithm) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 9460c53d..b41f8922 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -30,6 +30,11 @@ class DummyCipher(object): pass +class DummyMode(object): + def validate_for_algorithm(self, algorithm): + pass + + class TestCipher(object): def test_instantiate_without_backend(self): Cipher( @@ -101,10 +106,20 @@ class TestCipherContext(object): def test_nonexistent_cipher(self, backend): cipher = Cipher( - DummyCipher(), object(), backend + DummyCipher(), DummyMode(), backend ) with pytest.raises(UnsupportedAlgorithm): cipher.encryptor() with pytest.raises(UnsupportedAlgorithm): cipher.decryptor() + + +class TestModeValidation(object): + def test_cbc(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CBC(b"abc"), + backend, + ) -- cgit v1.2.3 From 707253283ec2921bf76e2c8049eb47388d9da580 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:52:32 -0800 Subject: Implement this for CTR --- cryptography/hazmat/primitives/ciphers/modes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 597b4e3e..f357dcf7 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -77,3 +77,10 @@ class CTR(object): def __init__(self, nonce): self.nonce = nonce + + + def validate_for_algorithm(self, algorithm): + if len(self.nonce) * 8 != algorithm.block_size: + raise ValueError("Invalid nonce size ({0}) for {1}".format( + len(self.nonce), self.name + )) -- cgit v1.2.3 From 26ebea2c5bde18aaecee5f03291606cc5799d0cc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:53:36 -0800 Subject: Tests for OFB and CFB --- tests/hazmat/primitives/test_block.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index b41f8922..ad56f77e 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -123,3 +123,19 @@ class TestModeValidation(object): modes.CBC(b"abc"), backend, ) + + def test_ofb(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.OFB(b"abc"), + backend, + ) + + def test_cfb(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CFB(b"abc"), + backend, + ) -- cgit v1.2.3 From 18f2c8f5da97e430387a78d6e7fe20de1c1e6ada Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:57:08 -0800 Subject: test for ctr --- tests/hazmat/primitives/test_block.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index ad56f77e..52221cb6 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -139,3 +139,11 @@ class TestModeValidation(object): modes.CFB(b"abc"), backend, ) + + def test_ctr(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CFB(b"abc"), + backend, + ) -- cgit v1.2.3 From 51cd5a1534fe4d9f84563f09cf95fb4a547cf143 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 17:03:35 -0800 Subject: flake8 --- cryptography/hazmat/primitives/ciphers/modes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index f357dcf7..63fa163c 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -78,7 +78,6 @@ class CTR(object): def __init__(self, nonce): self.nonce = nonce - def validate_for_algorithm(self, algorithm): if len(self.nonce) * 8 != algorithm.block_size: raise ValueError("Invalid nonce size ({0}) for {1}".format( -- cgit v1.2.3 From 3c25f61c18c6f8f9a2210fb2124654023bcec775 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 17:10:14 -0800 Subject: fixed typo --- tests/hazmat/primitives/test_block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 52221cb6..e0deb36b 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -144,6 +144,6 @@ class TestModeValidation(object): with pytest.raises(ValueError): Cipher( algorithms.AES(b"\x00" * 16), - modes.CFB(b"abc"), + modes.CTR(b"abc"), backend, ) -- cgit v1.2.3 From 62aefffb1396190930074bf04c91459d1536bd0e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 16 Dec 2013 09:15:13 -0800 Subject: So the tests don't all explode --- cryptography/hazmat/primitives/ciphers/modes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 63a69ac4..51a1047c 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -94,3 +94,7 @@ class GCM(object): def __init__(self, initialization_vector, tag=None): self.initialization_vector = initialization_vector self.tag = tag + + def validate_for_algorithm(self, algorithm): + # TODO: figure out what this should do + pass -- cgit v1.2.3 From 71e8ca0d79d8599f1f00d6ec18cb19a2ffabfc8d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 1 Jan 2014 12:19:47 -0800 Subject: Explanatory comment --- cryptography/hazmat/primitives/ciphers/modes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 51a1047c..31af5d6e 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -92,9 +92,11 @@ class GCM(object): name = "GCM" def __init__(self, initialization_vector, tag=None): + # len(initialization_vector) must in [1, 2 ** 64), but it's impossible + # to actually construct a bytes object that large, so we don't check + # for it self.initialization_vector = initialization_vector self.tag = tag def validate_for_algorithm(self, algorithm): - # TODO: figure out what this should do pass -- cgit v1.2.3