diff options
-rw-r--r-- | cryptography/hazmat/bindings/openssl/binding.py | 77 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/utils.py | 88 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/ciphers/base.py | 3 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/ciphers/modes.py | 33 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/interfaces.py | 7 | ||||
-rw-r--r-- | docs/hazmat/primitives/interfaces.rst | 12 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 3 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_block.py | 37 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_hash_vectors.py | 16 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_hashes.py | 16 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_hmac.py | 2 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_hmac_vectors.py | 14 |
12 files changed, 211 insertions, 97 deletions
diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index ccda368e..4f6b99ae 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,9 +13,7 @@ from __future__ import absolute_import, division, print_function -import sys - -import cffi +from cryptography.hazmat.bindings.utils import build_ffi _OSX_PRE_INCLUDE = """ #ifdef __APPLE__ @@ -39,20 +37,6 @@ _OSX_POST_INCLUDE = """ class Binding(object): """ OpenSSL API wrapper. - - Modules listed in the ``_modules`` listed should have the following - attributes: - - * ``INCLUDES``: A string containg C includes. - * ``TYPES``: A string containing C declarations for types. - * ``FUNCTIONS``: A string containing C declarations for functions. - * ``MACROS``: A string containing C declarations for any macros. - * ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this - can be used to do things like test for a define and provide an - alternate implementation based on that. - * ``CONDITIONAL_NAMES``: A dict mapping strings of condition names from the - library to a list of names which will not be present without the - condition. """ _module_prefix = "cryptography.hazmat.bindings.openssl." _modules = [ @@ -92,59 +76,6 @@ class Binding(object): if cls.ffi is not None and cls.lib is not None: return - ffi = cffi.FFI() - includes = [] - functions = [] - macros = [] - customizations = [] - for name in cls._modules: - module_name = cls._module_prefix + name - __import__(module_name) - module = sys.modules[module_name] - - ffi.cdef(module.TYPES) - - macros.append(module.MACROS) - functions.append(module.FUNCTIONS) - includes.append(module.INCLUDES) - customizations.append(module.CUSTOMIZATIONS) - - # loop over the functions & macros after declaring all the types - # so we can set interdependent types in different files and still - # have them all defined before we parse the funcs & macros - for func in functions: - ffi.cdef(func) - for macro in macros: - ffi.cdef(macro) - - # We include functions here so that if we got any of their definitions - # wrong, the underlying C compiler will explode. In C you are allowed - # to re-declare a function if it has the same signature. That is: - # int foo(int); - # int foo(int); - # is legal, but the following will fail to compile: - # int foo(int); - # int foo(short); - - lib = ffi.verify( - source="\n".join( - [_OSX_PRE_INCLUDE] + - includes + - [_OSX_POST_INCLUDE] + - functions + - customizations - ), - libraries=["crypto", "ssl"], - ext_package="cryptography", - ) - - for name in cls._modules: - module_name = cls._module_prefix + name - module = sys.modules[module_name] - for condition, names in module.CONDITIONAL_NAMES.items(): - if not getattr(lib, condition): - for name in names: - delattr(lib, name) - - cls.ffi = ffi - cls.lib = lib + cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, + _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, + ["crypto", "ssl"]) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py new file mode 100644 index 00000000..9e1d3937 --- /dev/null +++ b/cryptography/hazmat/bindings/utils.py @@ -0,0 +1,88 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import sys + +import cffi + + +def build_ffi(module_prefix, modules, pre_include, post_include, libraries): + """ + Modules listed in ``modules`` should have the following attributes: + + * ``INCLUDES``: A string containing C includes. + * ``TYPES``: A string containing C declarations for types. + * ``FUNCTIONS``: A string containing C declarations for functions. + * ``MACROS``: A string containing C declarations for any macros. + * ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this + can be used to do things like test for a define and provide an + alternate implementation based on that. + * ``CONDITIONAL_NAMES``: A dict mapping strings of condition names from the + library to a list of names which will not be present without the + condition. + """ + ffi = cffi.FFI() + includes = [] + functions = [] + macros = [] + customizations = [] + for name in modules: + module_name = module_prefix + name + __import__(module_name) + module = sys.modules[module_name] + + ffi.cdef(module.TYPES) + + macros.append(module.MACROS) + functions.append(module.FUNCTIONS) + includes.append(module.INCLUDES) + customizations.append(module.CUSTOMIZATIONS) + + # loop over the functions & macros after declaring all the types + # so we can set interdependent types in different files and still + # have them all defined before we parse the funcs & macros + for func in functions: + ffi.cdef(func) + for macro in macros: + ffi.cdef(macro) + + # We include functions here so that if we got any of their definitions + # wrong, the underlying C compiler will explode. In C you are allowed + # to re-declare a function if it has the same signature. That is: + # int foo(int); + # int foo(int); + # is legal, but the following will fail to compile: + # int foo(int); + # int foo(short); + lib = ffi.verify( + source="\n".join( + [pre_include] + + includes + + [post_include] + + functions + + customizations + ), + libraries=libraries + ) + + for name in modules: + module_name = module_prefix + name + module = sys.modules[module_name] + for condition, names in module.CONDITIONAL_NAMES.items(): + if not getattr(lib, condition): + for name in names: + delattr(lib, name) + + return ffi, lib diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 1da0802c..d366e4cf 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -25,6 +25,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 ab8501c6..739f23dd 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) @@ -57,6 +78,12 @@ 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 + )) + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithInitializationVector) @@ -65,6 +92,9 @@ 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 if tag is not None and len(tag) < 4: raise ValueError( "Authentication tag must be 4 bytes or longer" @@ -72,3 +102,6 @@ class GCM(object): self.initialization_vector = initialization_vector self.tag = tag + + def validate_for_algorithm(self, algorithm): + pass diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index e87c9ca9..7a6bf3e2 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -47,6 +47,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 361b723e..edb24cd9 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -67,6 +67,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/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 22cfbe71..ad399594 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -27,6 +27,9 @@ from cryptography.hazmat.primitives.ciphers.modes import CBC class DummyMode(object): name = "dummy-mode" + def validate_for_algorithm(self, algorithm): + pass + @utils.register_interface(interfaces.CipherAlgorithm) class DummyCipher(object): diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 30cf1d60..f758ffaa 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -35,6 +35,9 @@ from .utils import ( class DummyMode(object): name = "dummy-mode" + def validate_for_algorithm(self, algorithm): + pass + @utils.register_interface(interfaces.CipherAlgorithm) class DummyCipher(object): @@ -152,3 +155,37 @@ class TestAEADCipherContext(object): algorithms.AES, modes.GCM, ) + + +class TestModeValidation(object): + def test_cbc(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + 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, + ) + + def test_ctr(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CTR(b"abc"), + backend, + ) diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py index 13ffc3fd..ca97fc11 100644 --- a/tests/hazmat/primitives/test_hash_vectors.py +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -24,7 +24,7 @@ from ...utils import load_hash_vectors @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA1), + only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) @pytest.mark.hash @@ -41,7 +41,7 @@ class TestSHA1(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA224), + only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) @pytest.mark.hash @@ -58,7 +58,7 @@ class TestSHA224(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA256), + only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) @pytest.mark.hash @@ -75,7 +75,7 @@ class TestSHA256(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA384), + only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) @pytest.mark.hash @@ -92,7 +92,7 @@ class TestSHA384(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA512), + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) @pytest.mark.hash @@ -109,7 +109,7 @@ class TestSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), skip_message="Does not support RIPEMD160", ) @pytest.mark.hash @@ -130,7 +130,7 @@ class TestRIPEMD160(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool), + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), skip_message="Does not support Whirlpool", ) @pytest.mark.hash @@ -153,7 +153,7 @@ class TestWhirlpool(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hash diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index c907ef61..9ca2feee 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -70,7 +70,7 @@ class TestHashContext(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA1), + only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) @pytest.mark.hash @@ -83,7 +83,7 @@ class TestSHA1(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA224), + only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) @pytest.mark.hash @@ -96,7 +96,7 @@ class TestSHA224(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA256), + only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) @pytest.mark.hash @@ -109,7 +109,7 @@ class TestSHA256(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA384), + only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) @pytest.mark.hash @@ -122,7 +122,7 @@ class TestSHA384(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA512), + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) @pytest.mark.hash @@ -135,7 +135,7 @@ class TestSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), skip_message="Does not support RIPEMD160", ) @pytest.mark.hash @@ -148,7 +148,7 @@ class TestRIPEMD160(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool), + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), skip_message="Does not support Whirlpool", ) @pytest.mark.hash @@ -161,7 +161,7 @@ class TestWhirlpool(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hash diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 04913af6..dd9cdaab 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -34,7 +34,7 @@ class UnsupportedDummyHash(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.MD5), + only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hmac diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py index c5644459..0792080b 100644 --- a/tests/hazmat/primitives/test_hmac_vectors.py +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -22,7 +22,7 @@ from ...utils import load_hash_vectors @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.MD5), + only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hmac @@ -38,7 +38,7 @@ class TestHMAC_MD5(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA1), + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) @pytest.mark.hmac @@ -54,7 +54,7 @@ class TestHMAC_SHA1(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA224), + only_if=lambda backend: backend.hmac_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) @pytest.mark.hmac @@ -70,7 +70,7 @@ class TestHMAC_SHA224(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA256), + only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) @pytest.mark.hmac @@ -86,7 +86,7 @@ class TestHMAC_SHA256(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA384), + only_if=lambda backend: backend.hmac_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) @pytest.mark.hmac @@ -102,7 +102,7 @@ class TestHMAC_SHA384(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA512), + only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) @pytest.mark.hmac @@ -118,7 +118,7 @@ class TestHMAC_SHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hmac_supported(hashes.RIPEMD160()), skip_message="Does not support RIPEMD160", ) @pytest.mark.hmac |