Merge pull request #448 from reaperhulk/travis-add-brew-osx

Add Homebrew OpenSSL to Travis OS X Matrix
diff --git a/.travis/install.sh b/.travis/install.sh
index fd210d4..1e3b170 100755
--- a/.travis/install.sh
+++ b/.travis/install.sh
@@ -16,6 +16,11 @@
     fi
 fi
 
+if [[ "${TOX_ENV}" == "docs" && "$(name -s)" != "Darwin" ]]; then
+    sudo apt-get -y update
+    sudo apt-get install libenchant-dev
+fi
+
 if [[ "$(uname -s)" == "Darwin" ]]; then
     brew update
     brew install pyenv
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 284fa98..07ee58c 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -43,7 +43,11 @@
         self._ffi = self._binding.ffi
         self._lib = self._binding.lib
 
+        # adds all ciphers/digests for EVP
         self._lib.OpenSSL_add_all_algorithms()
+        # registers available SSL/TLS ciphers and digests
+        self._lib.SSL_library_init()
+        # loads error strings for libcrypto and libssl functions
         self._lib.SSL_load_error_strings()
 
         self._cipher_registry = {}
diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py
index e0cd61f..9c1af40 100644
--- a/cryptography/hazmat/bindings/commoncrypto/binding.py
+++ b/cryptography/hazmat/bindings/commoncrypto/binding.py
@@ -25,6 +25,7 @@
     _module_prefix = "cryptography.hazmat.bindings.commoncrypto."
     _modules = [
         "common_digest",
+        "common_hmac",
     ]
 
     ffi = None
diff --git a/cryptography/hazmat/bindings/commoncrypto/common_hmac.py b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py
new file mode 100644
index 0000000..a4bf900
--- /dev/null
+++ b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py
@@ -0,0 +1,46 @@
+# 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 <CommonCrypto/CommonHMAC.h>
+"""
+
+TYPES = """
+typedef struct {
+    ...;
+} CCHmacContext;
+enum {
+    kCCHmacAlgSHA1,
+    kCCHmacAlgMD5,
+    kCCHmacAlgSHA256,
+    kCCHmacAlgSHA384,
+    kCCHmacAlgSHA512,
+    kCCHmacAlgSHA224
+};
+typedef uint32_t CCHmacAlgorithm;
+"""
+
+FUNCTIONS = """
+void CCHmacInit(CCHmacContext *, CCHmacAlgorithm, const void *, size_t);
+void CCHmacUpdate(CCHmacContext *, const void *, size_t);
+void CCHmacFinal(CCHmacContext *, void *);
+
+"""
+
+MACROS = """
+"""
+
+CUSTOMIZATIONS = """
+"""
+
+CONDITIONAL_NAMES = {}
diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py
index 6b2a77b..1b66bd2 100644
--- a/cryptography/hazmat/bindings/openssl/err.py
+++ b/cryptography/hazmat/bindings/openssl/err.py
@@ -22,23 +22,67 @@
 };
 typedef struct ERR_string_data_st ERR_STRING_DATA;
 
+static const int ASN1_R_BAD_PASSWORD_READ;
+
 static const int ERR_LIB_EVP;
 static const int ERR_LIB_PEM;
 
-static const int EVP_F_EVP_ENCRYPTFINAL_EX;
 static const int EVP_F_EVP_DECRYPTFINAL_EX;
+static const int EVP_F_EVP_ENCRYPTFINAL_EX;
 
 static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH;
 
-static const int PEM_F_PEM_READ_BIO_PRIVATEKEY;
 static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO;
+static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO;
+static const int PEM_F_D2I_PKCS8PRIVATEKEY_FP;
+static const int PEM_F_DO_PK8PKEY;
+static const int PEM_F_DO_PK8PKEY_FP;
+static const int PEM_F_LOAD_IV;
+static const int PEM_F_PEM_ASN1_READ;
+static const int PEM_F_PEM_ASN1_READ_BIO;
+static const int PEM_F_PEM_ASN1_WRITE;
+static const int PEM_F_PEM_ASN1_WRITE_BIO;
+static const int PEM_F_PEM_DEF_CALLBACK;
+static const int PEM_F_PEM_DO_HEADER;
+static const int PEM_F_PEM_F_PEM_WRITE_PKCS8PRIVATEKEY;
+static const int PEM_F_PEM_GET_EVP_CIPHER_INFO;
+static const int PEM_F_PEM_PK8PKEY;
+static const int PEM_F_PEM_READ;
+static const int PEM_F_PEM_READ_BIO;
+static const int PEM_F_PEM_READ_BIO_PRIVATEKEY;
+static const int PEM_F_PEM_READ_BIO_PRIVATEKEY;
+static const int PEM_F_PEM_READ_PRIVATEKEY;
+static const int PEM_F_PEM_SEALFINAL;
+static const int PEM_F_PEM_SEALINIT;
+static const int PEM_F_PEM_SIGNFINAL;
+static const int PEM_F_PEM_WRITE;
+static const int PEM_F_PEM_WRITE_BIO;
+static const int PEM_F_PEM_X509_INFO_READ;
+static const int PEM_F_PEM_X509_INFO_READ_BIO;
+static const int PEM_F_PEM_X509_INFO_WRITE_BIO;
 
+static const int PEM_R_BAD_BASE64_DECODE;
+static const int PEM_R_BAD_DECRYPT;
+static const int PEM_R_BAD_END_LINE;
+static const int PEM_R_BAD_IV_CHARS;
 static const int PEM_R_BAD_PASSWORD_READ;
-static const int ASN1_R_BAD_PASSWORD_READ;
+static const int PEM_R_BAD_PASSWORD_READ;
+static const int PEM_R_ERROR_CONVERTING_PRIVATE_KEY;
+static const int PEM_R_NOT_DEK_INFO;
+static const int PEM_R_NOT_ENCRYPTED;
+static const int PEM_R_NOT_PROC_TYPE;
+static const int PEM_R_NO_START_LINE;
+static const int PEM_R_PROBLEMS_GETTING_PASSWORD;
+static const int PEM_R_PUBLIC_KEY_NO_RSA;
+static const int PEM_R_READ_KEY;
+static const int PEM_R_SHORT_HEADER;
+static const int PEM_R_UNSUPPORTED_CIPHER;
+static const int PEM_R_UNSUPPORTED_ENCRYPTION;
 """
 
 FUNCTIONS = """
 void ERR_load_crypto_strings(void);
+void ERR_load_SSL_strings(void);
 void ERR_free_strings(void);
 char* ERR_error_string(unsigned long, char *);
 void ERR_error_string_n(unsigned long, char *, size_t);
diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py
index 69290eb..b825348 100644
--- a/cryptography/hazmat/bindings/utils.py
+++ b/cryptography/hazmat/bindings/utils.py
@@ -34,6 +34,7 @@
         condition.
     """
     ffi = cffi.FFI()
+    types = []
     includes = []
     functions = []
     macros = []
@@ -43,20 +44,13 @@
         __import__(module_name)
         module = sys.modules[module_name]
 
-        ffi.cdef(module.TYPES)
-
+        types.append(module.TYPES)
         macros.append(module.MACROS)
         functions.append(module.FUNCTIONS)
         includes.append(module.INCLUDES)
         customizations.append(module.CUSTOMIZATIONS)
 
-    # loop over the functions & macros after declaring all the types
-    # so we can set interdependent types in different files and still
-    # have them all defined before we parse the funcs & macros
-    for func in functions:
-        ffi.cdef(func)
-    for macro in macros:
-        ffi.cdef(macro)
+    ffi.cdef("\n".join(types + functions + macros))
 
     # We include functions here so that if we got any of their definitions
     # wrong, the underlying C compiler will explode. In C you are allowed
diff --git a/docs/conf.py b/docs/conf.py
index 0066031..a42dcb2 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -38,6 +38,7 @@
     'sphinx.ext.intersphinx',
     'sphinx.ext.viewcode',
     'cryptography-docs',
+    'sphinxcontrib.spelling',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index 8316569..7d95404 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -324,6 +324,11 @@
             return (iv, ciphertext, encryptor.tag)
 
         def decrypt(key, associated_data, iv, ciphertext, tag):
+            if len(tag) != 16:
+                raise ValueError(
+                    "tag must be 16 bytes -- truncation not supported"
+                )
+
             # Construct a Cipher object, with the key, iv, and additionally the
             # GCM tag used for authenticating the message.
             decryptor = Cipher(
diff --git a/docs/security.rst b/docs/security.rst
index 8895970..4dadc84 100644
--- a/docs/security.rst
+++ b/docs/security.rst
@@ -7,6 +7,6 @@
 fingerprint ``E27D 4AA0 1651 72CB C5D2  AF2B 125F 5C67 DFE9 4084`` (this public
 key is available from most commonly-used key servers).
 
-Once you’ve submitted an issue via email, you should receive an acknowledgment
+Once you've submitted an issue via email, you should receive an acknowledgment
 within 48 hours, and depending on the action to be taken, you may receive
-further followup emails.
+further follow-up emails.
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
new file mode 100644
index 0000000..97356c2
--- /dev/null
+++ b/docs/spelling_wordlist.txt
@@ -0,0 +1,28 @@
+backend
+backends
+boolean
+ciphertext
+committer
+crypto
+cryptographic
+cryptographically
+decrypt
+decrypted
+decrypting
+fernet
+hazmat
+indistinguishability
+introspectability
+invariants
+pickleable
+plaintext
+testability
+unencrypted
+unpadded
+unpadding
+Backends
+Blowfish
+Changelog
+Docstrings
+Fernet
+Schneier
diff --git a/setup.py b/setup.py
index feed0cd..e8bcc11 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@
     exec(f.read(), about)
 
 
-CFFI_DEPENDENCY = "cffi>=0.6"
+CFFI_DEPENDENCY = "cffi>=0.8"
 SIX_DEPENDENCY = "six>=1.4.1"
 
 requirements = [
diff --git a/tests/conftest.py b/tests/conftest.py
index 0ddc333..1d9f96e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,5 +1,6 @@
 import pytest
 
+from cryptography.hazmat.backends import _ALL_BACKENDS
 from cryptography.hazmat.backends.interfaces import (
     HMACBackend, CipherBackend, HashBackend
 )
@@ -7,11 +8,9 @@
 from .utils import check_for_iface, check_backend_support
 
 
-def pytest_generate_tests(metafunc):
-    from cryptography.hazmat.backends import _ALL_BACKENDS
-
-    if "backend" in metafunc.fixturenames:
-        metafunc.parametrize("backend", _ALL_BACKENDS)
+@pytest.fixture(params=_ALL_BACKENDS)
+def backend(request):
+    return request.param
 
 
 @pytest.mark.trylast
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index ad39959..2a32992 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -95,3 +95,21 @@
                 backend._lib.EVP_F_EVP_DECRYPTFINAL_EX,
                 0
             )
+
+    def test_ssl_ciphers_registered(self):
+        meth = backend._lib.TLSv1_method()
+        ctx = backend._lib.SSL_CTX_new(meth)
+        assert ctx != backend._ffi.NULL
+        backend._lib.SSL_CTX_free(ctx)
+
+    def test_evp_ciphers_registered(self):
+        cipher = backend._lib.EVP_get_cipherbyname(b"aes-256-cbc")
+        assert cipher != backend._ffi.NULL
+
+    def test_error_strings_loaded(self):
+        # returns a value in a static buffer
+        err = backend._lib.ERR_error_string(101183626, backend._ffi.NULL)
+        assert backend._ffi.string(err) == (
+            b"error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:"
+            b"data not multiple of block length"
+        )
diff --git a/tests/test_fernet.py b/tests/test_fernet.py
index 45188c4..bd4d90a 100644
--- a/tests/test_fernet.py
+++ b/tests/test_fernet.py
@@ -25,6 +25,7 @@
 
 from cryptography.fernet import Fernet, InvalidToken
 from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.ciphers import algorithms, modes
 
 
 def json_parametrize(keys, fname):
@@ -37,7 +38,14 @@
     ])
 
 
+@pytest.mark.cipher
 class TestFernet(object):
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     @json_parametrize(
         ("secret", "now", "iv", "src", "token"), "generate.json",
     )
@@ -50,6 +58,12 @@
         )
         assert actual_token == token.encode("ascii")
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     @json_parametrize(
         ("secret", "now", "src", "ttl_sec", "token"), "verify.json",
     )
@@ -61,6 +75,12 @@
         payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec)
         assert payload == src.encode("ascii")
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json")
     def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch):
         f = Fernet(secret.encode("ascii"), backend=backend)
@@ -69,16 +89,34 @@
         with pytest.raises(InvalidToken):
             f.decrypt(token.encode("ascii"), ttl=ttl_sec)
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     def test_invalid_start_byte(self, backend):
         f = Fernet(Fernet.generate_key(), backend=backend)
         with pytest.raises(InvalidToken):
             f.decrypt(base64.urlsafe_b64encode(b"\x81"))
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     def test_timestamp_too_short(self, backend):
         f = Fernet(Fernet.generate_key(), backend=backend)
         with pytest.raises(InvalidToken):
             f.decrypt(base64.urlsafe_b64encode(b"\x80abc"))
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     def test_unicode(self, backend):
         f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend)
         with pytest.raises(TypeError):
@@ -86,6 +124,12 @@
         with pytest.raises(TypeError):
             f.decrypt(six.u(""))
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"])
     def test_roundtrips(self, message, backend):
         f = Fernet(Fernet.generate_key(), backend=backend)
@@ -95,6 +139,12 @@
         f = Fernet(Fernet.generate_key())
         assert f._backend is default_backend()
 
+    @pytest.mark.supported(
+        only_if=lambda backend: backend.cipher_supported(
+            algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16)
+        ),
+        skip_message="Does not support AES CBC",
+    )
     def test_bad_key(self, backend):
         with pytest.raises(ValueError):
             Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend)
diff --git a/tox.ini b/tox.ini
index ce2f539..ff5df36 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,7 +13,9 @@
 
 [testenv:docs]
 deps =
+    pyenchant
     sphinx
+    sphinxcontrib-spelling
     sphinx_rtd_theme
 basepython = python2.7
 commands =
@@ -21,6 +23,7 @@
     sphinx-build -W -b latex -d {envtmpdir}/doctrees docs docs/_build/latex
     sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
     sphinx-build -W -b linkcheck docs docs/_build/html
+    sphinx-build -W -b spelling docs docs/_build/html
 
 # Temporarily disable coverage on pypy because of performance problems with
 # coverage.py on pypy.