diff options
-rw-r--r-- | cryptography/bindings/openssl/api.py | 58 | ||||
-rw-r--r-- | cryptography/bindings/openssl/hmac.py | 32 | ||||
-rw-r--r-- | cryptography/primitives/block/base.py | 4 | ||||
-rw-r--r-- | tests/bindings/test_openssl.py | 10 | ||||
-rw-r--r-- | tests/primitives/test_cryptrec.py | 4 | ||||
-rw-r--r-- | tests/primitives/test_openssl_vectors.py | 16 |
6 files changed, 103 insertions, 21 deletions
diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py index a3198c1a..67d73afb 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 +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,9 +110,31 @@ 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}") + ) def create_block_cipher_encrypt_context(self, cipher, mode): ctx, evp, iv_nonce = self._create_block_cipher_context(cipher, mode) @@ -119,15 +157,11 @@ class API(object): return ctx def _create_block_cipher_context(self, cipher, mode): - ctx = self.ffi.new("EVP_CIPHER_CTX *") - res = self.lib.EVP_CIPHER_CTX_init(ctx) - assert res != 0 - ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_cleanup) - # 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")) + ctx = self.lib.EVP_CIPHER_CTX_new() + ctx = self.ffi.gc(ctx, self.lib.EVP_CIPHER_CTX_free) + 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 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 14704ffe..e625dc7c 100644 --- a/cryptography/primitives/block/base.py +++ b/cryptography/primitives/block/base.py @@ -13,15 +13,13 @@ from __future__ import absolute_import, division, print_function -from cryptography.bindings import _default_api - class BlockCipher(object): def __init__(self, cipher, mode, api=None): 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 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_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_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", ) |