diff options
-rw-r--r-- | CHANGELOG.rst | 2 | ||||
-rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 61 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/openssl/aes.py | 6 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 7 |
4 files changed, 73 insertions, 3 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1801f33f..f5702812 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,8 @@ Changelog for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` and :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on :doc:`/hazmat/backends/commoncrypto` and :doc:`/hazmat/backends/openssl`. +* Added ``AES`` :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR` + support to the OpenSSL backend when linked against 0.9.8. 0.4 - 2014-05-03 ~~~~~~~~~~~~~~~~ diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 8853646a..bd5808ed 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -132,6 +132,14 @@ class Backend(object): return _HashContext(self, algorithm) def cipher_supported(self, cipher, mode): + if self._evp_cipher_supported(cipher, mode): + return True + elif isinstance(mode, CTR) and isinstance(cipher, AES): + return True + else: + return False + + def _evp_cipher_supported(self, cipher, mode): try: adapter = self._cipher_registry[type(cipher), type(mode)] except KeyError: @@ -198,10 +206,24 @@ class Backend(object): ) def create_symmetric_encryption_ctx(self, cipher, mode): - return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) + if (isinstance(mode, CTR) and isinstance(cipher, AES) + and not self._evp_cipher_supported(cipher, mode)): + # This is needed to provide support for AES CTR mode in OpenSSL + # 0.9.8. It can be removed when we drop 0.9.8 support (RHEL 5 + # extended life ends 2020). + return _AESCTRCipherContext(self, cipher, mode) + else: + return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) def create_symmetric_decryption_ctx(self, cipher, mode): - return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + if (isinstance(mode, CTR) and isinstance(cipher, AES) + and not self._evp_cipher_supported(cipher, mode)): + # This is needed to provide support for AES CTR mode in OpenSSL + # 0.9.8. It can be removed when we drop 0.9.8 support (RHEL 5 + # extended life ends 2020). + return _AESCTRCipherContext(self, cipher, mode) + else: + return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) def pbkdf2_hmac_supported(self, algorithm): if self._lib.Cryptography_HAS_PBKDF2_HMAC: @@ -834,6 +856,41 @@ class _CipherContext(object): return self._tag +@utils.register_interface(interfaces.CipherContext) +class _AESCTRCipherContext(object): + """ + This is needed to provide support for AES CTR mode in OpenSSL 0.9.8. It can + be removed when we drop 0.9.8 support (RHEL5 extended life ends 2020). + """ + def __init__(self, backend, cipher, mode): + self._backend = backend + + self._key = self._backend._ffi.new("AES_KEY *") + assert self._key != self._backend._ffi.NULL + res = self._backend._lib.AES_set_encrypt_key( + cipher.key, len(cipher.key) * 8, self._key + ) + assert res == 0 + self._ecount = self._backend._ffi.new("char[]", 16) + self._nonce = self._backend._ffi.new("char[16]", mode.nonce) + self._num = self._backend._ffi.new("unsigned int *", 0) + + def update(self, data): + buf = self._backend._ffi.new("unsigned char[]", len(data)) + self._backend._lib.AES_ctr128_encrypt( + data, buf, len(data), self._key, self._nonce, + self._ecount, self._num + ) + return self._backend._ffi.buffer(buf)[:] + + def finalize(self): + self._key = None + self._ecount = None + self._nonce = None + self._num = None + return b"" + + @utils.register_interface(interfaces.HashContext) class _HashContext(object): def __init__(self, backend, algorithm, ctx=None): diff --git a/cryptography/hazmat/bindings/openssl/aes.py b/cryptography/hazmat/bindings/openssl/aes.py index 17c154cf..b0e00721 100644 --- a/cryptography/hazmat/bindings/openssl/aes.py +++ b/cryptography/hazmat/bindings/openssl/aes.py @@ -29,6 +29,12 @@ typedef struct aes_key_st AES_KEY; FUNCTIONS = """ int AES_set_encrypt_key(const unsigned char *, const int, AES_KEY *); int AES_set_decrypt_key(const unsigned char *, const int, AES_KEY *); +/* The ctr128_encrypt function is only useful in 0.9.8. You should use EVP for + this in 1.0.0+. */ +void AES_ctr128_encrypt(const unsigned char *, unsigned char *, + const unsigned long, const AES_KEY *, + unsigned char[], unsigned char[], unsigned int *); + """ MACROS = """ diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 154af00f..fb2ca19f 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives import hashes, interfaces from cryptography.hazmat.primitives.asymmetric import dsa, padding, rsa from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES -from cryptography.hazmat.primitives.ciphers.modes import CBC +from cryptography.hazmat.primitives.ciphers.modes import CBC, CTR from cryptography.hazmat.primitives.interfaces import BlockCipherAlgorithm from ...utils import raises_unsupported_algorithm @@ -64,6 +64,11 @@ class TestOpenSSL(object): def test_supports_cipher(self): assert backend.cipher_supported(None, None) is False + def test_aes_ctr_always_available(self): + # AES CTR should always be available in both 0.9.8 and 1.0.0+ + assert backend.cipher_supported(AES(b"\x00" * 16), + CTR(b"\x00" * 16)) is True + def test_register_duplicate_cipher_adapter(self): with pytest.raises(ValueError): backend.register_cipher_adapter(AES, CBC, None) |