Merge pull request #147 from reaperhulk/hash-improvements

Provide data to hash constructor + reject unicode ala hashlib
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 9575e84..9f63250 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -12,4 +12,4 @@
 Extensive contribution guidelines are available in the repository at
 ``docs/contributing.rst``, or online at:
 
-https://cryptography.readthedocs.org/en/latest/contributing/
+https://cryptography.io/en/latest/contributing/
diff --git a/README.rst b/README.rst
index 01f3ead..61c4b80 100644
--- a/README.rst
+++ b/README.rst
@@ -15,7 +15,10 @@
 
 You can find more documentation at `Read The Docs`_.
 
-.. _`Read The Docs`: https://cryptography.readthedocs.org/
+You can find more information in the `documentation`_.
+
+.. _`documentation`: https://cryptography.io/
+
 
 Discussion
 ~~~~~~~~~~
diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py
index 3c2cf2e..7d189d6 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 @@
         "engine",
         "err",
         "evp",
+        "hmac",
         "nid",
         "opensslv",
         "pem",
@@ -86,6 +99,9 @@
         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,20 +110,38 @@
         """
         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_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
@@ -144,7 +178,7 @@
         res = self.lib.EVP_EncryptFinal_ex(ctx, buf, outlen)
         assert res != 0
         res = self.lib.EVP_CIPHER_CTX_cleanup(ctx)
-        assert res != 0
+        assert res == 1
         return self.ffi.buffer(buf)[:outlen[0]]
 
     def supports_hash(self, hash_cls):
@@ -168,6 +202,8 @@
         buf = self.ffi.new("unsigned char[]", digest_size)
         res = self.lib.EVP_DigestFinal_ex(ctx, buf, self.ffi.NULL)
         assert res != 0
+        res = self.lib.EVP_MD_CTX_cleanup(ctx)
+        assert res == 1
         return self.ffi.buffer(buf)[:digest_size]
 
     def copy_hash_context(self, ctx):
diff --git a/cryptography/bindings/openssl/evp.py b/cryptography/bindings/openssl/evp.py
index 2015990..2bb5b0f 100644
--- a/cryptography/bindings/openssl/evp.py
+++ b/cryptography/bindings/openssl/evp.py
@@ -45,6 +45,8 @@
 const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *);
 int EVP_CIPHER_block_size(const EVP_CIPHER *);
 void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *);
+EVP_CIPHER_CTX *EVP_CIPHER_CTX_new();
+void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *);
 
 EVP_MD_CTX *EVP_MD_CTX_create();
 int EVP_MD_CTX_copy_ex(EVP_MD_CTX *, const EVP_MD_CTX *);
diff --git a/cryptography/bindings/openssl/hmac.py b/cryptography/bindings/openssl/hmac.py
new file mode 100644
index 0000000..e97ac35
--- /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 50e9e9e..42c1f79 100644
--- a/cryptography/primitives/block/base.py
+++ b/cryptography/primitives/block/base.py
@@ -15,8 +15,6 @@
 
 from enum import Enum
 
-from cryptography.bindings import _default_api
-
 
 class _Operation(Enum):
     encrypt = 0
@@ -28,7 +26,7 @@
         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/docs/community.rst b/docs/community.rst
index 86ba505..552318d 100644
--- a/docs/community.rst
+++ b/docs/community.rst
@@ -12,4 +12,4 @@
 .. _`Mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev
 .. _`Source code`: https://github.com/pyca/cryptography
 .. _`Issue tracker`: https://github.com/pyca/cryptography/issues
-.. _`Documentation`: https://cryptography.readthedocs.org/
+.. _`Documentation`: https://cryptography.io/
diff --git a/docs/primitives/cryptographic-hashes.rst b/docs/primitives/cryptographic-hashes.rst
index 397e50d..d4dde04 100644
--- a/docs/primitives/cryptographic-hashes.rst
+++ b/docs/primitives/cryptographic-hashes.rst
@@ -1,5 +1,5 @@
 Message Digests
-====================
+===============
 
 .. class:: cryptography.primitives.hashes.BaseHash
 
@@ -8,7 +8,7 @@
 
     .. method:: update(data)
 
-        :param bytes data The bytes you wish to hash.
+        :param bytes data: The bytes you wish to hash.
 
     .. method:: copy()
 
diff --git a/tests/bindings/test_openssl.py b/tests/bindings/test_openssl.py
index e5b78d1..bf201e4 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 @@
         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 edf9765..02a0447 100644
--- a/tests/primitives/test_cryptrec.py
+++ b/tests/primitives/test_cryptrec.py
@@ -37,6 +37,8 @@
         ],
         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 5b2be78..86ff7ca 100644
--- a/tests/primitives/test_openssl_vectors.py
+++ b/tests/primitives/test_openssl_vectors.py
@@ -32,7 +32,9 @@
         ["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 @@
         ["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 @@
         ["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 @@
         ["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 9888309..6e19792 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/test_utils.py b/tests/test_utils.py
index 3fe9e57..f96cf00 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -15,10 +15,12 @@
 
 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():
diff --git a/tox.ini b/tox.ini
index e72eb58..b437a7a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,5 +19,7 @@
 
 [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