diff options
-rw-r--r-- | .coveragerc | 4 | ||||
-rw-r--r-- | cryptography/bindings/openssl/api.py | 115 | ||||
-rw-r--r-- | cryptography/bindings/openssl/evp.py | 5 | ||||
-rw-r--r-- | cryptography/bindings/openssl/hmac.py | 32 | ||||
-rw-r--r-- | cryptography/primitives/block/base.py | 66 | ||||
-rw-r--r-- | cryptography/primitives/block/ciphers.py | 24 | ||||
-rw-r--r-- | cryptography/primitives/block/modes.py | 15 | ||||
-rw-r--r-- | cryptography/primitives/hashes.py | 8 | ||||
-rw-r--r-- | cryptography/primitives/interfaces.py | 21 | ||||
-rw-r--r-- | docs/primitives/cryptographic-hashes.rst | 4 | ||||
-rw-r--r-- | docs/primitives/symmetric-encryption.rst | 53 | ||||
-rw-r--r-- | setup.py | 5 | ||||
-rw-r--r-- | tests/bindings/test_openssl.py | 10 | ||||
-rw-r--r-- | tests/primitives/test_block.py | 56 | ||||
-rw-r--r-- | tests/primitives/test_ciphers.py | 17 | ||||
-rw-r--r-- | tests/primitives/test_cryptrec.py | 4 | ||||
-rw-r--r-- | tests/primitives/test_hashes.py | 15 | ||||
-rw-r--r-- | tests/primitives/test_nist.py | 90 | ||||
-rw-r--r-- | tests/primitives/test_openssl_vectors.py | 16 | ||||
-rw-r--r-- | tests/primitives/test_utils.py | 5 | ||||
-rw-r--r-- | tests/primitives/utils.py | 13 | ||||
-rw-r--r-- | tests/test_utils.py | 10 | ||||
-rw-r--r-- | tox.ini | 6 |
23 files changed, 475 insertions, 119 deletions
diff --git a/.coveragerc b/.coveragerc index 398ff08a..b891cb7c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,6 @@ [run] branch = True + +[report] +exclude_lines = + @abc.abstractmethod diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index f5e042e7..fedaf9cc 100644 --- a/cryptography/bindings/openssl/api.py +++ b/cryptography/bindings/openssl/api.py @@ -13,11 +13,23 @@ from __future__ import absolute_import, division, print_function +import itertools import sys import cffi from cryptography.primitives import interfaces +from cryptography.primitives.block.ciphers import AES, Camellia, TripleDES +from cryptography.primitives.block.modes import CBC, CTR, ECB, OFB, CFB + + +class GetCipherByName(object): + def __init__(self, fmt): + self._fmt = fmt + + def __call__(self, api, cipher, mode): + cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower() + return api.lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) class API(object): @@ -35,6 +47,7 @@ class API(object): "engine", "err", "evp", + "hmac", "nid", "opensslv", "pem", @@ -86,6 +99,9 @@ class API(object): self.lib.OpenSSL_add_all_algorithms() self.lib.SSL_load_error_strings() + self._cipher_registry = {} + self._register_default_ciphers() + def openssl_version_text(self): """ Friendly string name of linked OpenSSL. @@ -94,18 +110,64 @@ class API(object): """ return self.ffi.string(self.lib.OPENSSL_VERSION_TEXT).decode("ascii") - def supports_cipher(self, ciphername): - return (self.ffi.NULL != - self.lib.EVP_get_cipherbyname(ciphername.encode("ascii"))) + def supports_cipher(self, cipher, mode): + try: + adapter = self._cipher_registry[type(cipher), type(mode)] + except KeyError: + return False + evp_cipher = adapter(self, cipher, mode) + return self.ffi.NULL != evp_cipher + + def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): + if (cipher_cls, mode_cls) in self._cipher_registry: + raise ValueError("Duplicate registration for: {0} {1}".format( + cipher_cls, mode_cls) + ) + self._cipher_registry[cipher_cls, mode_cls] = adapter + + def _register_default_ciphers(self): + for cipher_cls, mode_cls in itertools.product( + [AES, Camellia], + [CBC, CTR, ECB, OFB, CFB], + ): + self.register_cipher_adapter( + cipher_cls, + mode_cls, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) + for mode_cls in [CBC, CFB, OFB]: + self.register_cipher_adapter( + TripleDES, + mode_cls, + GetCipherByName("des-ede3-{mode.name}") + ) + + def create_block_cipher_encrypt_context(self, cipher, mode): + ctx, evp, iv_nonce = self._create_block_cipher_context(cipher, mode) + res = self.lib.EVP_EncryptInit_ex(ctx, evp, api.ffi.NULL, cipher.key, + iv_nonce) + assert res != 0 + # We purposely disable padding here as it's handled higher up in the + # API. + self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0) + return ctx - def create_block_cipher_context(self, cipher, mode): + def create_block_cipher_decrypt_context(self, cipher, mode): + ctx, evp, iv_nonce = self._create_block_cipher_context(cipher, mode) + res = self.lib.EVP_DecryptInit_ex(ctx, evp, api.ffi.NULL, cipher.key, + iv_nonce) + assert res != 0 + # We purposely disable padding here as it's handled higher up in the + # API. + self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0) + return ctx + + def _create_block_cipher_context(self, cipher, mode): ctx = self.lib.EVP_CIPHER_CTX_new() ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_free) - # TODO: compute name using a better algorithm - ciphername = "{0}-{1}-{2}".format( - cipher.name, cipher.key_size, mode.name - ).lower() - evp_cipher = self.lib.EVP_get_cipherbyname(ciphername.encode("ascii")) + evp_cipher = self._cipher_registry[type(cipher), type(mode)]( + self, cipher, mode + ) assert evp_cipher != self.ffi.NULL if isinstance(mode, interfaces.ModeWithInitializationVector): iv_nonce = mode.initialization_vector @@ -114,24 +176,21 @@ class API(object): else: iv_nonce = self.ffi.NULL - # TODO: Sometimes this needs to be a DecryptInit, when? - res = self.lib.EVP_EncryptInit_ex( - ctx, evp_cipher, self.ffi.NULL, cipher.key, iv_nonce - ) - assert res != 0 + return (ctx, evp_cipher, iv_nonce) - # We purposely disable padding here as it's handled higher up in the - # API. - self.lib.EVP_CIPHER_CTX_set_padding(ctx, 0) - return ctx + def update_encrypt_context(self, ctx, data): + block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) + buf = self.ffi.new("unsigned char[]", len(data) + block_size - 1) + outlen = self.ffi.new("int *") + res = self.lib.EVP_EncryptUpdate(ctx, buf, outlen, data, len(data)) + assert res != 0 + return self.ffi.buffer(buf)[:outlen[0]] - def update_encrypt_context(self, ctx, plaintext): + def update_decrypt_context(self, ctx, data): block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) - buf = self.ffi.new("unsigned char[]", len(plaintext) + block_size - 1) + buf = self.ffi.new("unsigned char[]", len(data) + block_size - 1) outlen = self.ffi.new("int *") - res = self.lib.EVP_EncryptUpdate( - ctx, buf, outlen, plaintext, len(plaintext) - ) + res = self.lib.EVP_DecryptUpdate(ctx, buf, outlen, data, len(data)) assert res != 0 return self.ffi.buffer(buf)[:outlen[0]] @@ -145,6 +204,16 @@ class API(object): assert res == 1 return self.ffi.buffer(buf)[:outlen[0]] + def finalize_decrypt_context(self, ctx): + block_size = self.lib.EVP_CIPHER_CTX_block_size(ctx) + buf = self.ffi.new("unsigned char[]", block_size) + outlen = self.ffi.new("int *") + res = self.lib.EVP_DecryptFinal_ex(ctx, buf, outlen) + assert res != 0 + res = self.lib.EVP_CIPHER_CTX_cleanup(ctx) + assert res == 1 + return self.ffi.buffer(buf)[:outlen[0]] + def supports_hash(self, hash_cls): return (self.ffi.NULL != self.lib.EVP_get_digestbyname(hash_cls.name.encode("ascii"))) diff --git a/cryptography/bindings/openssl/evp.py b/cryptography/bindings/openssl/evp.py index 2bb5b0f7..41df1056 100644 --- a/cryptography/bindings/openssl/evp.py +++ b/cryptography/bindings/openssl/evp.py @@ -41,6 +41,11 @@ int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int); int EVP_EncryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, const unsigned char *, int); int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); +int EVP_DecryptInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, + const unsigned char *, const unsigned char *); +int EVP_DecryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, + const unsigned char *, int); +int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *); const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *); int EVP_CIPHER_block_size(const EVP_CIPHER *); diff --git a/cryptography/bindings/openssl/hmac.py b/cryptography/bindings/openssl/hmac.py new file mode 100644 index 00000000..e97ac35e --- /dev/null +++ b/cryptography/bindings/openssl/hmac.py @@ -0,0 +1,32 @@ +# 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. + +INCLUDES = """ +#include <openssl/hmac.h> +""" + +TYPES = """ +typedef struct { ...; } HMAC_CTX; +""" + +FUNCTIONS = """ +void HMAC_CTX_init(HMAC_CTX *); +void HMAC_CTX_cleanup(HMAC_CTX *); +int HMAC_Init_ex(HMAC_CTX *, const void *, int, const EVP_MD *, ENGINE *); +int HMAC_Update(HMAC_CTX *, const unsigned char *, size_t); +int HMAC_Final(HMAC_CTX *, unsigned char *, unsigned int *); +int HMAC_CTX_copy(HMAC_CTX *, HMAC_CTX *); +""" + +MACROS = """ +""" diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py index 50e9e9e5..12b6f626 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -13,14 +13,7 @@ from __future__ import absolute_import, division, print_function -from enum import Enum - -from cryptography.bindings import _default_api - - -class _Operation(Enum): - encrypt = 0 - decrypt = 1 +from cryptography.primitives import interfaces class BlockCipher(object): @@ -28,35 +21,54 @@ class BlockCipher(object): super(BlockCipher, self).__init__() if api is None: - api = _default_api + from cryptography.bindings import _default_api as api self.cipher = cipher self.mode = mode self._api = api - self._ctx = api.create_block_cipher_context(cipher, mode) - self._operation = None - def encrypt(self, plaintext): - if self._ctx is None: - raise ValueError("BlockCipher was already finalized") + def encryptor(self): + return _CipherEncryptionContext(self.cipher, self.mode, self._api) - if self._operation is None: - self._operation = _Operation.encrypt - elif self._operation is not _Operation.encrypt: - raise ValueError("BlockCipher cannot encrypt when the operation is" - " set to %s" % self._operation.name) + def decryptor(self): + return _CipherDecryptionContext(self.cipher, self.mode, self._api) - return self._api.update_encrypt_context(self._ctx, plaintext) + +@interfaces.register(interfaces.CipherContext) +class _CipherEncryptionContext(object): + def __init__(self, cipher, mode, api): + super(_CipherEncryptionContext, self).__init__() + self._api = api + self._ctx = self._api.create_block_cipher_encrypt_context(cipher, mode) + + def update(self, data): + if self._ctx is None: + raise ValueError("Context was already finalized") + return self._api.update_encrypt_context(self._ctx, data) def finalize(self): if self._ctx is None: - raise ValueError("BlockCipher was already finalized") + raise ValueError("Context was already finalized") + data = self._api.finalize_encrypt_context(self._ctx) + self._ctx = None + return data + - if self._operation is _Operation.encrypt: - result = self._api.finalize_encrypt_context(self._ctx) - else: - raise ValueError("BlockCipher cannot finalize the unknown " - "operation %s" % self._operation.name) +@interfaces.register(interfaces.CipherContext) +class _CipherDecryptionContext(object): + def __init__(self, cipher, mode, api): + super(_CipherDecryptionContext, self).__init__() + self._api = api + self._ctx = self._api.create_block_cipher_decrypt_context(cipher, mode) + def update(self, data): + if self._ctx is None: + raise ValueError("Context was already finalized") + return self._api.update_decrypt_context(self._ctx, data) + + def finalize(self): + if self._ctx is None: + raise ValueError("Context was already finalized") + data = self._api.finalize_decrypt_context(self._ctx) self._ctx = None - return result + return data diff --git a/cryptography/primitives/block/ciphers.py b/cryptography/primitives/block/ciphers.py index 4ac150a4..4143b89d 100644 --- a/cryptography/primitives/block/ciphers.py +++ b/cryptography/primitives/block/ciphers.py @@ -52,3 +52,27 @@ class Camellia(object): @property def key_size(self): return len(self.key) * 8 + + +class TripleDES(object): + name = "3DES" + block_size = 64 + key_sizes = frozenset([64, 128, 192]) + + def __init__(self, key): + super(TripleDES, self).__init__() + if len(key) == 8: + key += key + key + elif len(key) == 16: + key += key[:8] + self.key = key + + # Verify that the key size matches the expected key size + if self.key_size not in self.key_sizes: + raise ValueError("Invalid key size ({0}) for {1}".format( + self.key_size, self.name + )) + + @property + def key_size(self): + return len(self.key) * 8 diff --git a/cryptography/primitives/block/modes.py b/cryptography/primitives/block/modes.py index 43631801..a933c187 100644 --- a/cryptography/primitives/block/modes.py +++ b/cryptography/primitives/block/modes.py @@ -16,14 +16,7 @@ from __future__ import absolute_import, division, print_function from cryptography.primitives import interfaces -def register(iface): - def register_decorator(klass): - iface.register(klass) - return klass - return register_decorator - - -@register(interfaces.ModeWithInitializationVector) +@interfaces.register(interfaces.ModeWithInitializationVector) class CBC(object): name = "CBC" @@ -36,7 +29,7 @@ class ECB(object): name = "ECB" -@register(interfaces.ModeWithInitializationVector) +@interfaces.register(interfaces.ModeWithInitializationVector) class OFB(object): name = "OFB" @@ -45,7 +38,7 @@ class OFB(object): self.initialization_vector = initialization_vector -@register(interfaces.ModeWithInitializationVector) +@interfaces.register(interfaces.ModeWithInitializationVector) class CFB(object): name = "CFB" @@ -54,7 +47,7 @@ class CFB(object): self.initialization_vector = initialization_vector -@register(interfaces.ModeWithNonce) +@interfaces.register(interfaces.ModeWithNonce) class CTR(object): name = "CTR" diff --git a/cryptography/primitives/hashes.py b/cryptography/primitives/hashes.py index e8c1f929..7133a916 100644 --- a/cryptography/primitives/hashes.py +++ b/cryptography/primitives/hashes.py @@ -23,13 +23,17 @@ from cryptography.bindings import _default_api class BaseHash(six.with_metaclass(abc.ABCMeta)): - def __init__(self, api=None, ctx=None): + def __init__(self, data=None, api=None, ctx=None): if api is None: api = _default_api self._api = api self._ctx = self._api.create_hash_context(self) if ctx is None else ctx + if data is not None: + self.update(data) def update(self, data): + if isinstance(data, six.text_type): + raise TypeError("Unicode-objects must be encoded before hashing") self._api.update_hash_context(self._ctx, data) def copy(self): @@ -40,7 +44,7 @@ class BaseHash(six.with_metaclass(abc.ABCMeta)): self.digest_size) def hexdigest(self): - return binascii.hexlify(self.digest()).decode("ascii") + return str(binascii.hexlify(self.digest()).decode("ascii")) def _copy_ctx(self): return self._api.copy_hash_context(self._ctx) diff --git a/cryptography/primitives/interfaces.py b/cryptography/primitives/interfaces.py index c1fc9910..49c19d0e 100644 --- a/cryptography/primitives/interfaces.py +++ b/cryptography/primitives/interfaces.py @@ -18,9 +18,30 @@ import abc import six +def register(iface): + def register_decorator(klass): + iface.register(klass) + return klass + return register_decorator + + class ModeWithInitializationVector(six.with_metaclass(abc.ABCMeta)): pass class ModeWithNonce(six.with_metaclass(abc.ABCMeta)): pass + + +class CipherContext(six.with_metaclass(abc.ABCMeta)): + @abc.abstractmethod + def update(self, data): + """ + update takes bytes and return bytes + """ + + @abc.abstractmethod + def finalize(self): + """ + finalize return bytes + """ diff --git a/docs/primitives/cryptographic-hashes.rst b/docs/primitives/cryptographic-hashes.rst index d4dde042..aeb30f40 100644 --- a/docs/primitives/cryptographic-hashes.rst +++ b/docs/primitives/cryptographic-hashes.rst @@ -1,11 +1,13 @@ Message Digests =============== -.. class:: cryptography.primitives.hashes.BaseHash +.. class:: cryptography.primitives.hashes.BaseHash(data=None) Abstract base class that implements a common interface for all hash algorithms that follow here. + If ``data`` is provided ``update(data)`` is called upon construction. + .. method:: update(data) :param bytes data: The bytes you wish to hash. diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst index 7899e67d..73d8ad37 100644 --- a/docs/primitives/symmetric-encryption.rst +++ b/docs/primitives/symmetric-encryption.rst @@ -15,29 +15,47 @@ where the encrypter and decrypter both use the same key. Block ciphers work by encrypting content in chunks, often 64- or 128-bits. They combine an underlying algorithm (such as AES), with a mode (such as - CBC, CTR, or GCM). A simple example of encrypting content with AES is: + CBC, CTR, or GCM). A simple example of encrypting (and then decrypting) + content with AES is: .. doctest:: >>> from cryptography.primitives.block import BlockCipher, ciphers, modes >>> cipher = BlockCipher(ciphers.AES(key), modes.CBC(iv)) - >>> cipher.encrypt(b"a secret message") + cipher.finalize() - '...' + >>> encryptor = cipher.encryptor() + >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() + >>> decryptor = cipher.decryptor() + >>> decryptor.update(ct) + decryptor.finalize() + 'a secret message' :param cipher: One of the ciphers described below. :param mode: One of the modes described below. - ``encrypt()`` should be called repeatedly with new plaintext, and once the - full plaintext is fed in, ``finalize()`` should be called. + .. method:: encryptor() - .. method:: encrypt(plaintext) + :return :class:`CipherContext`: encryption instance - :param bytes plaintext: The text you wish to encrypt. - :return bytes: Returns the ciphertext that was added. + .. method:: decryptor() + + :return :class:`CipherContext`: decryption instance + +.. class:: cryptography.primitives.interfaces.CipherContext() + + When calling ``encryptor()`` or ``decryptor()`` on a BlockCipher object you + will receive a return object conforming to the CipherContext interface. You + can then call ``update(data)`` with data until you have fed everything into + the context. Once that is done call ``finalize()`` to finish the operation and + obtain the remainder of the data. + + + .. method:: update(data) + + :param bytes data: The text you wish to pass into the context. + :return bytes: Returns the data that was encrypted or decrypted. .. method:: finalize() - :return bytes: Returns the remainder of the ciphertext. + :return bytes: Returns the remainder of the data. Ciphers ~~~~~~~ @@ -61,6 +79,23 @@ Ciphers This must be kept secret. +.. class:: cryptography.primitives.block.ciphers.TripleDES(key) + + Triple DES (Data Encryption Standard), sometimes refered to as 3DES, is a + block cipher standardized by NIST. Triple DES has known cryptoanalytic + flaws, however none of them currently enable a practical attack. + Nonetheless, Triples DES is not reccomended for new applications because it + is incredibly slow; old applications should consider moving away from it. + + :param bytes key: The secret key, either ``64``, ``128``, or ``192`` bits + (note that DES functionally uses ``56``, ``112``, or + ``168`` bits of the key, there is a parity byte in each + component of the key), in some materials these are + referred to as being up to three separate keys (each + ``56`` bits long), they can simply be concatenated to + produce the full key. This must be kept secret. + + Modes ~~~~~ @@ -10,8 +10,6 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -import sys - from setuptools import setup, find_packages @@ -32,9 +30,6 @@ setup_requires = [ CFFI_DEPENDENCY, ] -if sys.version_info[:2] < (3, 4): - install_requires += ["enum34"] - setup( name=about["__title__"], version=about["__version__"], diff --git a/tests/bindings/test_openssl.py b/tests/bindings/test_openssl.py index e5b78d18..bf201e4d 100644 --- a/tests/bindings/test_openssl.py +++ b/tests/bindings/test_openssl.py @@ -11,7 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from cryptography.bindings.openssl.api import api +from cryptography.primitives.block.ciphers import AES +from cryptography.primitives.block.modes import CBC class TestOpenSSL(object): @@ -30,4 +34,8 @@ class TestOpenSSL(object): assert api.openssl_version_text().startswith("OpenSSL") def test_supports_cipher(self): - assert api.supports_cipher("not-a-real-cipher") is False + assert api.supports_cipher(None, None) is False + + def test_register_duplicate_cipher_adapter(self): + with pytest.raises(ValueError): + api.register_cipher_adapter(AES, CBC, None) diff --git a/tests/primitives/test_block.py b/tests/primitives/test_block.py index 9f5905bf..5e147a79 100644 --- a/tests/primitives/test_block.py +++ b/tests/primitives/test_block.py @@ -15,11 +15,10 @@ from __future__ import absolute_import, division, print_function import binascii -import pretend import pytest +from cryptography.primitives import interfaces from cryptography.primitives.block import BlockCipher, ciphers, modes -from cryptography.primitives.block.base import _Operation class TestBlockCipher(object): @@ -29,40 +28,42 @@ class TestBlockCipher(object): modes.CBC(binascii.unhexlify(b"0" * 32)) ) - def test_use_after_finalize(self, api): + def test_creates_encryptor(self): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)), - api + modes.CBC(binascii.unhexlify(b"0" * 32)) ) - cipher.encrypt(b"a" * 16) - cipher.finalize() - with pytest.raises(ValueError): - cipher.encrypt(b"b" * 16) - with pytest.raises(ValueError): - cipher.finalize() + assert isinstance(cipher.encryptor(), interfaces.CipherContext) - def test_encrypt_with_invalid_operation(self, api): + def test_creates_decryptor(self): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), - modes.CBC(binascii.unhexlify(b"0" * 32)), - api + modes.CBC(binascii.unhexlify(b"0" * 32)) ) - cipher._operation = _Operation.decrypt + assert isinstance(cipher.decryptor(), interfaces.CipherContext) - with pytest.raises(ValueError): - cipher.encrypt(b"b" * 16) - def test_finalize_with_invalid_operation(self, api): +class TestBlockCipherContext(object): + def test_use_after_finalize(self, api): cipher = BlockCipher( ciphers.AES(binascii.unhexlify(b"0" * 32)), modes.CBC(binascii.unhexlify(b"0" * 32)), api ) - cipher._operation = pretend.stub(name="wat") - + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + encryptor.finalize() + with pytest.raises(ValueError): + encryptor.update(b"b" * 16) + with pytest.raises(ValueError): + encryptor.finalize() + decryptor = cipher.decryptor() + decryptor.update(b"a" * 16) + decryptor.finalize() + with pytest.raises(ValueError): + decryptor.update(b"b" * 16) with pytest.raises(ValueError): - cipher.finalize() + decryptor.finalize() def test_unaligned_block_encryption(self, api): cipher = BlockCipher( @@ -70,7 +71,16 @@ class TestBlockCipher(object): modes.ECB(), api ) - ct = cipher.encrypt(b"a" * 15) + encryptor = cipher.encryptor() + ct = encryptor.update(b"a" * 15) assert ct == b"" - ct += cipher.encrypt(b"a" * 65) + ct += encryptor.update(b"a" * 65) assert len(ct) == 80 + ct += encryptor.finalize() + decryptor = cipher.decryptor() + pt = decryptor.update(ct[:3]) + assert pt == b"" + pt += decryptor.update(ct[3:]) + assert len(pt) == 80 + assert pt == b"a" * 80 + decryptor.finalize() diff --git a/tests/primitives/test_ciphers.py b/tests/primitives/test_ciphers.py index 27d35850..17fcdbaf 100644 --- a/tests/primitives/test_ciphers.py +++ b/tests/primitives/test_ciphers.py @@ -17,7 +17,7 @@ import binascii import pytest -from cryptography.primitives.block.ciphers import AES, Camellia +from cryptography.primitives.block.ciphers import AES, Camellia, TripleDES class TestAES(object): @@ -48,3 +48,18 @@ class TestCamellia(object): def test_invalid_key_size(self): with pytest.raises(ValueError): Camellia(binascii.unhexlify(b"0" * 12)) + + +class TestTripleDES(object): + @pytest.mark.parametrize("key", [ + b"0" * 16, + b"0" * 32, + b"0" * 48, + ]) + def test_key_size(self, key): + cipher = TripleDES(binascii.unhexlify(key)) + assert cipher.key_size == 192 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + TripleDES(binascii.unhexlify(b"0" * 12)) diff --git a/tests/primitives/test_cryptrec.py b/tests/primitives/test_cryptrec.py index edf97652..02a04473 100644 --- a/tests/primitives/test_cryptrec.py +++ b/tests/primitives/test_cryptrec.py @@ -37,6 +37,8 @@ class TestCamelliaECB(object): ], lambda key: ciphers.Camellia(binascii.unhexlify((key))), lambda key: modes.ECB(), - only_if=lambda api: api.supports_cipher("camellia-128-ecb"), + only_if=lambda api: api.supports_cipher( + ciphers.Camellia("\x00" * 16), modes.ECB() + ), skip_message="Does not support Camellia ECB", ) diff --git a/tests/primitives/test_hashes.py b/tests/primitives/test_hashes.py index 901ddabb..03de8916 100644 --- a/tests/primitives/test_hashes.py +++ b/tests/primitives/test_hashes.py @@ -13,11 +13,26 @@ from __future__ import absolute_import, division, print_function +import pytest + +import six + from cryptography.primitives import hashes from .utils import generate_base_hash_test +class TestBaseHash(object): + def test_base_hash_reject_unicode(self, api): + m = hashes.SHA1(api=api) + with pytest.raises(TypeError): + m.update(six.u("\u00FC")) + + def test_base_hash_hexdigest_string_type(self, api): + m = hashes.SHA1(api=api, data=b"") + assert isinstance(m.hexdigest(), str) + + class TestSHA1(object): test_SHA1 = generate_base_hash_test( hashes.SHA1, diff --git a/tests/primitives/test_nist.py b/tests/primitives/test_nist.py index d97b207b..2a32d1bc 100644 --- a/tests/primitives/test_nist.py +++ b/tests/primitives/test_nist.py @@ -164,3 +164,93 @@ class TestAES_CFB(object): lambda key, iv: ciphers.AES(binascii.unhexlify(key)), lambda key, iv: modes.CFB(binascii.unhexlify(iv)), ) + + +class TestTripleDES_CBC(object): + test_KAT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("3DES", "KAT"), + [ + "TCBCinvperm.rsp", + "TCBCpermop.rsp", + "TCBCsubtab.rsp", + "TCBCvarkey.rsp", + "TCBCvartext.rsp", + ], + lambda keys, iv: ciphers.TripleDES(binascii.unhexlify(keys)), + lambda keys, iv: modes.CBC(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("3DES", "MMT"), + [ + "TCBCMMT1.rsp", + "TCBCMMT2.rsp", + "TCBCMMT3.rsp", + ], + lambda key1, key2, key3, iv: ( + ciphers.TripleDES(binascii.unhexlify(key1 + key2 + key3)) + ), + lambda key1, key2, key3, iv: modes.CBC(binascii.unhexlify(iv)), + ) + + +class TestTripleDES_OFB(object): + test_KAT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("3DES", "KAT"), + [ + "TOFBpermop.rsp", + "TOFBsubtab.rsp", + "TOFBvarkey.rsp", + "TOFBvartext.rsp", + "TOFBinvperm.rsp", + ], + lambda keys, iv: ciphers.TripleDES(binascii.unhexlify(keys)), + lambda keys, iv: modes.OFB(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("3DES", "MMT"), + [ + "TOFBMMT1.rsp", + "TOFBMMT2.rsp", + "TOFBMMT3.rsp", + ], + lambda key1, key2, key3, iv: ( + ciphers.TripleDES(binascii.unhexlify(key1 + key2 + key3)) + ), + lambda key1, key2, key3, iv: modes.OFB(binascii.unhexlify(iv)), + ) + + +class TestTripleDES_CFB(object): + test_KAT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("3DES", "KAT"), + [ + "TCFB64invperm.rsp", + "TCFB64permop.rsp", + "TCFB64subtab.rsp", + "TCFB64varkey.rsp", + "TCFB64vartext.rsp", + ], + lambda keys, iv: ciphers.TripleDES(binascii.unhexlify(keys)), + lambda keys, iv: modes.CFB(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + lambda path: load_nist_vectors_from_file(path, "ENCRYPT"), + os.path.join("3DES", "MMT"), + [ + "TCFB64MMT1.rsp", + "TCFB64MMT2.rsp", + "TCFB64MMT3.rsp", + ], + lambda key1, key2, key3, iv: ( + ciphers.TripleDES(binascii.unhexlify(key1 + key2 + key3)) + ), + lambda key1, key2, key3, iv: modes.CFB(binascii.unhexlify(iv)), + ) diff --git a/tests/primitives/test_openssl_vectors.py b/tests/primitives/test_openssl_vectors.py index 5b2be784..86ff7cad 100644 --- a/tests/primitives/test_openssl_vectors.py +++ b/tests/primitives/test_openssl_vectors.py @@ -32,7 +32,9 @@ class TestCamelliaCBC(object): ["camellia-cbc.txt"], lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)), lambda key, iv: modes.CBC(binascii.unhexlify(iv)), - only_if=lambda api: api.supports_cipher("camellia-128-cbc"), + only_if=lambda api: api.supports_cipher( + ciphers.Camellia("\x00" * 16), modes.CBC("\x00" * 16) + ), skip_message="Does not support Camellia CBC", ) @@ -44,7 +46,9 @@ class TestCamelliaOFB(object): ["camellia-ofb.txt"], lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)), lambda key, iv: modes.OFB(binascii.unhexlify(iv)), - only_if=lambda api: api.supports_cipher("camellia-128-ofb"), + only_if=lambda api: api.supports_cipher( + ciphers.Camellia("\x00" * 16), modes.OFB("\x00" * 16) + ), skip_message="Does not support Camellia OFB", ) @@ -56,7 +60,9 @@ class TestCamelliaCFB(object): ["camellia-cfb.txt"], lambda key, iv: ciphers.Camellia(binascii.unhexlify(key)), lambda key, iv: modes.CFB(binascii.unhexlify(iv)), - only_if=lambda api: api.supports_cipher("camellia-128-cfb"), + only_if=lambda api: api.supports_cipher( + ciphers.Camellia("\x00" * 16), modes.CFB("\x00" * 16) + ), skip_message="Does not support Camellia CFB", ) @@ -68,6 +74,8 @@ class TestAESCTR(object): ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"], lambda key, iv: ciphers.AES(binascii.unhexlify(key)), lambda key, iv: modes.CTR(binascii.unhexlify(iv)), - only_if=lambda api: api.supports_cipher("aes-128-ctr"), + only_if=lambda api: api.supports_cipher( + ciphers.AES("\x00" * 16), modes.CTR("\x00" * 16) + ), skip_message="Does not support AES CTR", ) diff --git a/tests/primitives/test_utils.py b/tests/primitives/test_utils.py index 9888309e..6e197923 100644 --- a/tests/primitives/test_utils.py +++ b/tests/primitives/test_utils.py @@ -1,7 +1,8 @@ import pytest -from .utils import (base_hash_test, encrypt_test, hash_test, - long_string_hash_test) +from .utils import ( + base_hash_test, encrypt_test, hash_test, long_string_hash_test +) class TestEncryptTest(object): diff --git a/tests/primitives/utils.py b/tests/primitives/utils.py index a3759b03..91ca36d8 100644 --- a/tests/primitives/utils.py +++ b/tests/primitives/utils.py @@ -37,9 +37,14 @@ def encrypt_test(api, cipher_factory, mode_factory, params, only_if, mode_factory(**params), api ) - actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext)) - actual_ciphertext += cipher.finalize() + encryptor = cipher.encryptor() + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() assert actual_ciphertext == binascii.unhexlify(ciphertext) + decryptor = cipher.decryptor() + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) def generate_hash_test(param_loader, path, file_names, hash_cls, @@ -67,6 +72,8 @@ def hash_test(api, hash_cls, params, only_if, skip_message): m = hash_cls(api=api) m.update(binascii.unhexlify(msg)) assert m.hexdigest() == md.replace(" ", "").lower() + digest = hash_cls(api=api, data=binascii.unhexlify(msg)).hexdigest() + assert digest == md.replace(" ", "").lower() def generate_base_hash_test(hash_cls, digest_size, block_size, @@ -115,6 +122,6 @@ def generate_long_string_hash_test(hash_factory, md, only_if=None, def long_string_hash_test(api, hash_factory, md, only_if, skip_message): if only_if is not None and not only_if(api): pytest.skip(skip_message) - m = hash_factory(api) + m = hash_factory(api=api) m.update(b"a" * 1000000) assert m.hexdigest() == md.lower() diff --git a/tests/test_utils.py b/tests/test_utils.py index 3fe9e570..f96cf004 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -15,10 +15,12 @@ import textwrap import pytest -from .utils import (load_nist_vectors, load_nist_vectors_from_file, - load_cryptrec_vectors, load_cryptrec_vectors_from_file, - load_openssl_vectors, load_openssl_vectors_from_file, load_hash_vectors, - load_hash_vectors_from_file) +from .utils import ( + load_nist_vectors, load_nist_vectors_from_file, load_cryptrec_vectors, + load_cryptrec_vectors_from_file, load_openssl_vectors, + load_openssl_vectors_from_file, load_hash_vectors, + load_hash_vectors_from_file +) def test_load_nist_vectors_encrypt(): @@ -19,5 +19,7 @@ commands = [testenv:pep8] deps = flake8 -# E128 continuation line under-indented for visual indent -commands = flake8 --ignore="E128" cryptography/ tests/ docs/ +commands = flake8 . + +[flake8] +exclude = .tox,*.egg |