Merge branch 'master' into simple-symmetric-encryption
diff --git a/cryptography/bindings/openssl/__init__.py b/cryptography/bindings/openssl/__init__.py
index 6c803fb..103b1db 100644
--- a/cryptography/bindings/openssl/__init__.py
+++ b/cryptography/bindings/openssl/__init__.py
@@ -11,7 +11,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from cryptography.bindings.openssl import api
+from cryptography.bindings.openssl.api import api
 
 
 __all__ = ["api"]
diff --git a/cryptography/bindings/openssl/api.py b/cryptography/bindings/openssl/api.py
index 3cc6a0e..720a6ad 100644
--- a/cryptography/bindings/openssl/api.py
+++ b/cryptography/bindings/openssl/api.py
@@ -11,7 +11,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from __future__ import absolute_import, division, print_function
+import cffi
+
+
+class OpenSSLError(Exception):
+    def __init__(self, api):
+        e = api._lib.ERR_get_error()
+        if e == 0:
+            raise SystemError("Tried to create an OpenSSLError when there was "
+                "None")
+        msg = api._ffi.new("char[]", 120)
+        api._lib.ERR_error_string(e, msg)
+        super(OpenSSLError, self).__init__(api._ffi.string(msg))
 
 
 class API(object):
@@ -19,5 +30,82 @@
     OpenSSL API wrapper.
     """
 
+    def __init__(self):
+        ffi = cffi.FFI()
+        self._populate_ffi(ffi)
+        self._ffi = ffi
+        self._lib = ffi.verify("""
+        #include <openssl/evp.h>
+        """)
+        self._lib.OpenSSL_add_all_algorithms()
+        self._lib.ERR_load_crypto_strings()
+
+    def _populate_ffi(self, ffi):
+        ffi.cdef("""
+        typedef struct {
+            ...;
+        } EVP_CIPHER_CTX;
+        typedef ... EVP_CIPHER;
+        typedef ... ENGINE;
+
+        void OpenSSL_add_all_algorithms();
+        void ERR_load_crypto_strings();
+
+        const EVP_CIPHER *EVP_get_cipherbyname(const char *);
+        int EVP_EncryptInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *,
+                               ENGINE *, unsigned char *, unsigned char *);
+        int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int);
+        int EVP_EncryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *,
+                              unsigned char *, int);
+        int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *);
+        int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *);
+
+        unsigned long ERR_get_error();
+        """)
+
+    def create_block_cipher_context(self, cipher, mode):
+        ctx = self._ffi.new("EVP_CIPHER_CTX *")
+        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
+        )
+        evp_cipher = self._lib.EVP_get_cipherbyname(ciphername.encode("ascii"))
+        if evp_cipher == self._ffi.NULL:
+            raise OpenSSLError(self)
+        # TODO: only use the key and initialization_vector as needed. Sometimes
+        # this needs to be a DecryptInit, when?
+        res = self._lib.EVP_EncryptInit_ex(
+            ctx, evp_cipher, self._ffi.NULL, cipher.key,
+            mode.initialization_vector
+        )
+        if res == 0:
+            raise OpenSSLError(self)
+        # TODO: this should depend on mode.padding
+        self._lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
+        return ctx
+
+    def update_encrypt_context(self, ctx, plaintext):
+        buf = self._ffi.new("unsigned char[]", len(plaintext))
+        outlen = self._ffi.new("int *")
+        res = self._lib.EVP_EncryptUpdate(
+            ctx, buf, outlen, plaintext, len(plaintext)
+        )
+        if res == 0:
+            raise OpenSSLError(self)
+        return self._ffi.buffer(buf)[:outlen[0]]
+
+    def finalize_encrypt_context(self, ctx):
+        # TODO: use real block size
+        buf = self._ffi.new("unsigned char[]", 16)
+        outlen = self._ffi.new("int *")
+        res = self._lib.EVP_EncryptFinal_ex(ctx, buf, outlen)
+        if res == 0:
+            raise OpenSSLError(self)
+        res = self._lib.EVP_CIPHER_CTX_cleanup(ctx)
+        if res == 0:
+            raise OpenSSLError(self)
+        return self._ffi.buffer(buf)[:outlen[0]]
+
 
 api = API()
diff --git a/cryptography/primitives/__init__.py b/cryptography/primitives/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cryptography/primitives/__init__.py
diff --git a/cryptography/primitives/block/__init__.py b/cryptography/primitives/block/__init__.py
new file mode 100644
index 0000000..e49fcf3
--- /dev/null
+++ b/cryptography/primitives/block/__init__.py
@@ -0,0 +1,6 @@
+from cryptography.primitives.block.base import BlockCipher
+
+
+__all__ = [
+    "BlockCipher",
+]
diff --git a/cryptography/primitives/block/base.py b/cryptography/primitives/block/base.py
new file mode 100644
index 0000000..207c83d
--- /dev/null
+++ b/cryptography/primitives/block/base.py
@@ -0,0 +1,55 @@
+# 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.
+
+# TODO: which binding is used should be an option somewhere
+from cryptography.bindings.openssl import api
+
+
+class BlockCipher(object):
+    def __init__(self, cipher, mode):
+        super(BlockCipher, self).__init__()
+        self.cipher = cipher
+        self.mode = mode
+        self._ctx = api.create_block_cipher_context(cipher, mode)
+        self._operation = None
+
+    @property
+    def name(self):
+        return "{0}-{1}-{2}".format(
+            self.cipher.name, self.cipher.key_size, self.mode.name,
+        )
+
+    def encrypt(self, plaintext):
+        if self._ctx is None:
+            raise ValueError("BlockCipher was already finalized")
+
+        if self._operation is None:
+            self._operation = "encrypt"
+        elif self._operation != "encrypt":
+            raise ValueError("BlockCipher cannot encrypt when the operation is"
+                             " set to %s" % self._operation)
+
+        return api.update_encrypt_context(self._ctx, plaintext)
+
+    def finalize(self):
+        if self._ctx is None:
+            raise ValueError("BlockCipher was already finalized")
+
+        if self._operation == "encrypt":
+            result = api.finalize_encrypt_context(self._ctx)
+        else:
+            raise ValueError("BlockCipher cannot finalize the unknown "
+                             "operation %s" % self._operation)
+
+        self._ctx = None
+        return result
diff --git a/cryptography/primitives/block/ciphers.py b/cryptography/primitives/block/ciphers.py
new file mode 100644
index 0000000..f40fc2a
--- /dev/null
+++ b/cryptography/primitives/block/ciphers.py
@@ -0,0 +1,24 @@
+# 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.
+
+
+class AES(object):
+    name = "AES"
+
+    def __init__(self, key):
+        super(AES, self).__init__()
+        self.key = key
+
+    @property
+    def key_size(self):
+        return len(self.key) * 8
diff --git a/cryptography/primitives/block/modes.py b/cryptography/primitives/block/modes.py
new file mode 100644
index 0000000..02f0219
--- /dev/null
+++ b/cryptography/primitives/block/modes.py
@@ -0,0 +1,20 @@
+# 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.
+
+
+class CBC(object):
+    name = "CBC"
+
+    def __init__(self, initialization_vector):
+        super(CBC, self).__init__()
+        self.initialization_vector = initialization_vector
diff --git a/docs/primitives/symmetric-encryption.rst b/docs/primitives/symmetric-encryption.rst
index 821e8e0..090e8e2 100644
--- a/docs/primitives/symmetric-encryption.rst
+++ b/docs/primitives/symmetric-encryption.rst
@@ -12,7 +12,7 @@
 
     .. code-block:: pycon
 
-        >>> from cryptography.primitives.block import BlockCipher, cipher, mode, padding
+        >>> from cryptography.primitives.block import BlockCipher, ciphers, modes, padding
         >>> cipher = BlockCipher(cipher.AES(key), mode.CBC(iv, padding.PKCS7()))
         >>> cipher.encrypt("my secret message") + cipher.finalize()
         # The ciphertext
@@ -36,7 +36,7 @@
 Ciphers
 ~~~~~~~
 
-.. class:: cryptography.primitives.block.cipher.AES(key)
+.. class:: cryptography.primitives.block.ciphers.AES(key)
 
     AES (Advanced Encryption Standard) is a block cipher standardized by NIST.
     AES is both fast, and cryptographically strong. It is a good default
@@ -49,7 +49,7 @@
 Modes
 ~~~~~
 
-.. class:: cryptography.primitives.block.mode.CBC(initialization_vector, padding)
+.. class:: cryptography.primitives.block.modes.CBC(initialization_vector, padding)
 
     CBC (Cipher block chaining) is a mode of operation for block ciphers. It is
     considered cryptographically strong.
diff --git a/tests/primitives/__init__.py b/tests/primitives/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/primitives/__init__.py
diff --git a/tests/primitives/test_block.py b/tests/primitives/test_block.py
new file mode 100644
index 0000000..799e5f0
--- /dev/null
+++ b/tests/primitives/test_block.py
@@ -0,0 +1,59 @@
+# 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.
+
+import binascii
+
+import pytest
+
+from cryptography.primitives.block import BlockCipher, ciphers, modes
+
+
+class TestBlockCipher(object):
+    def test_cipher_name(self):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(b"0" * 32)),
+            modes.CBC(binascii.unhexlify(b"0" * 32))
+        )
+        assert cipher.name == "AES-128-CBC"
+
+    def test_use_after_finalize(self):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(b"0" * 32)),
+            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()
+
+    def test_encrypt_with_invalid_operation(self):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(b"0" * 32)),
+            modes.CBC(binascii.unhexlify(b"0" * 32))
+        )
+        cipher._operation = "decrypt"
+
+        with pytest.raises(ValueError):
+            cipher.encrypt(b"b" * 16)
+
+    def test_finalize_with_invalid_operation(self):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(b"0" * 32)),
+            modes.CBC(binascii.unhexlify(b"0" * 32))
+        )
+        cipher._operation = "wat"
+
+        with pytest.raises(ValueError):
+            cipher.encrypt(b"b" * 16)
diff --git a/tests/primitives/test_ciphers.py b/tests/primitives/test_ciphers.py
new file mode 100644
index 0000000..31b4275
--- /dev/null
+++ b/tests/primitives/test_ciphers.py
@@ -0,0 +1,29 @@
+# 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.
+
+import binascii
+
+import pytest
+
+from cryptography.primitives.block.ciphers import AES
+
+
+class TestAES(object):
+    @pytest.mark.parametrize(("key", "keysize"), [
+        (b"0" * 32, 128),
+        (b"0" * 48, 192),
+        (b"0" * 64, 256),
+    ])
+    def test_key_size(self, key, keysize):
+        cipher = AES(binascii.unhexlify(key))
+        assert cipher.key_size == keysize
diff --git a/tests/primitives/test_nist.py b/tests/primitives/test_nist.py
new file mode 100644
index 0000000..8b63665
--- /dev/null
+++ b/tests/primitives/test_nist.py
@@ -0,0 +1,196 @@
+# 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.
+
+"""
+Test using the NIST Test Vectors
+"""
+import binascii
+import os
+
+import pytest
+
+from cryptography.primitives.block import BlockCipher, ciphers, modes
+
+from ..utils import load_nist_vectors_from_file
+
+
+def parameterize_kat_encrypt(fname):
+    return pytest.mark.parametrize(("key", "iv", "plaintext", "ciphertext"),
+        load_nist_vectors_from_file(
+            os.path.join("AES/KAT/", fname),
+            "ENCRYPT",
+            ["key", "iv", "plaintext", "ciphertext"],
+        ),
+    )
+
+
+def paramterize_mmt_encrypt(fname):
+    return pytest.mark.parametrize(("key", "iv", "plaintext", "ciphertext"),
+        load_nist_vectors_from_file(
+            os.path.join("AES/MMT", fname),
+            "ENCRYPT",
+            ["key", "iv", "plaintext", "ciphertext"],
+        )
+    )
+
+
+class TestAES_CBC(object):
+    @parameterize_kat_encrypt("CBCGFSbox128.rsp")
+    def test_KAT_GFSbox_128_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCGFSbox192.rsp")
+    def test_KAT_GFSbox_192_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCGFSbox256.rsp")
+    def test_KAT_GFSbox_256_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCKeySbox128.rsp")
+    def test_KAT_KeySbox_128_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCKeySbox192.rsp")
+    def test_KAT_KeySbox_192_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCKeySbox256.rsp")
+    def test_KAT_KeySbox_256_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCVarKey128.rsp")
+    def test_KAT_VarKey_128_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCVarKey192.rsp")
+    def test_KAT_VarKey_192_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCVarKey256.rsp")
+    def test_KAT_VarKey_256_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCVarTxt128.rsp")
+    def test_KAT_VarTxt_128_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCVarTxt192.rsp")
+    def test_KAT_VarTxt_192_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @parameterize_kat_encrypt("CBCVarTxt256.rsp")
+    def test_KAT_VarTxt_256_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @paramterize_mmt_encrypt("CBCMMT128.rsp")
+    def test_MMT_128_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @paramterize_mmt_encrypt("CBCMMT192.rsp")
+    def test_MMT_192_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext
+
+    @paramterize_mmt_encrypt("CBCMMT256.rsp")
+    def test_MMT_256_encrypt(self, key, iv, plaintext, ciphertext):
+        cipher = BlockCipher(
+            ciphers.AES(binascii.unhexlify(key)),
+            modes.CBC(binascii.unhexlify(iv)),
+        )
+        actual_ciphertext = cipher.encrypt(binascii.unhexlify(plaintext))
+        actual_ciphertext += cipher.finalize()
+        assert binascii.hexlify(actual_ciphertext) == ciphertext