enforce password must be bytes when loading PEM/DER asymmetric keys (#3383)

* enforce password must be bytes when loading PEM/DER asymmetric keys

Previously we were using an ffi.buffer on the Python string, which was
allowing text implicitly, but our documentation explicitly requires
bytes.

* add changelog entry
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ee0a345..055651a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -15,11 +15,15 @@
   :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization.private_bytes`
   to
   :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization`.
-
 * Added
   :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKeyWithSerialization.public_bytes`
   to
   :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKeyWithSerialization`.
+* :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`
+  and
+  :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key`
+  now require that ``password`` must be bytes if provided. Previously this
+  was documented but not enforced.
 
 1.7.2 - 2017-01-27
 ~~~~~~~~~~~~~~~~~~
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index e460ab5..e514495 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -66,6 +66,8 @@
 
 class _PasswordUserdata(object):
     def __init__(self, password):
+        if password is not None and not isinstance(password, bytes):
+            raise TypeError("Password must be bytes")
         self.password = password
         self.called = 0
         self.exception = None
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 8fa64bc..a819831 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -523,7 +523,7 @@
             backend._evp_pkey_to_public_key(key)
 
     def test_very_long_pem_serialization_password(self):
-        password = "x" * 1024
+        password = b"x" * 1024
 
         with pytest.raises(ValueError):
             load_vectors_from_file(
diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py
index 1ba8a3b..dad056c 100644
--- a/tests/hazmat/primitives/test_serialization.py
+++ b/tests/hazmat/primitives/test_serialization.py
@@ -78,6 +78,26 @@
         _check_dsa_private_numbers(key.private_numbers())
 
     @pytest.mark.parametrize(
+        "key_path",
+        [
+            ["DER_Serialization", "enc-rsa-pkcs8.der"],
+        ]
+    )
+    @pytest.mark.requires_backend_interface(interface=RSABackend)
+    def test_password_not_bytes(self, key_path, backend):
+        key_file = os.path.join("asymmetric", *key_path)
+        password = u"this password is not bytes"
+
+        with pytest.raises(TypeError):
+            load_vectors_from_file(
+                key_file,
+                lambda derfile: load_der_private_key(
+                    derfile.read(), password, backend
+                ),
+                mode="rb"
+            )
+
+    @pytest.mark.parametrize(
         ("key_path", "password"),
         [
             (["DER_Serialization", "ec_private_key.der"], None),
@@ -499,6 +519,25 @@
             ["PKCS8", "enc-rsa-pkcs8.pem"]
         ]
     )
+    def test_password_not_bytes(self, key_path, backend):
+        key_file = os.path.join("asymmetric", *key_path)
+        password = u"this password is not bytes"
+
+        with pytest.raises(TypeError):
+            load_vectors_from_file(
+                key_file,
+                lambda pemfile: load_pem_private_key(
+                    pemfile.read().encode(), password, backend
+                )
+            )
+
+    @pytest.mark.parametrize(
+        "key_path",
+        [
+            ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"],
+            ["PKCS8", "enc-rsa-pkcs8.pem"]
+        ]
+    )
     def test_wrong_password(self, key_path, backend):
         key_file = os.path.join("asymmetric", *key_path)
         password = b"this password is wrong"