Convert between pyOpenSSL and cryptography objects (#439)

* convert pkey to cryptography keys and vice versa

* pep8 and such

* Add documentation and changelog

* add a type check and verify that it rejects ECDSA keys from cryptography
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fc81dec..9f867c4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -27,6 +27,8 @@
   `#496 <https://github.com/pyca/pyopenssl/pull/496>`_
 - Enable use of CRL (and more) in verify context.
   `#483 <https://github.com/pyca/pyopenssl/pull/483>`_
+- ``OpenSSL.crypto.PKey`` can now be constructed from ``cryptography`` objects and also exported as such.
+  `#439 <https://github.com/pyca/pyopenssl/pull/439>`_
 
 
 ----
diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py
index 4a379e8..c37f20b 100644
--- a/src/OpenSSL/crypto.py
+++ b/src/OpenSSL/crypto.py
@@ -10,6 +10,9 @@
     text_type as _text_type,
     PY3 as _PY3)
 
+from cryptography.hazmat.backends.openssl.backend import backend
+from cryptography.hazmat.primitives.asymmetric import dsa, rsa
+
 from OpenSSL._util import (
     ffi as _ffi,
     lib as _lib,
@@ -167,6 +170,45 @@
         self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free)
         self._initialized = False
 
+    def to_cryptography_key(self):
+        """
+        Export as a ``cryptography`` key.
+
+        :rtype: One of ``cryptography``'s `key interfaces`_.
+
+        .. _key interfaces: https://cryptography.io/en/latest/hazmat/\
+            primitives/asymmetric/rsa/#key-interfaces
+
+        .. versionadded:: 16.1.0
+        """
+        if self._only_public:
+            return backend._evp_pkey_to_public_key(self._pkey)
+        else:
+            return backend._evp_pkey_to_private_key(self._pkey)
+
+    @classmethod
+    def from_cryptography_key(cls, crypto_key):
+        """
+        Construct based on a ``cryptography`` *crypto_key*.
+
+        :param crypto_key: A ``cryptography`` key.
+        :type crypto_key: One of ``cryptography``'s `key interfaces`_.
+
+        :rtype: PKey
+
+        .. versionadded:: 16.1.0
+        """
+        pkey = cls()
+        if not isinstance(crypto_key, (rsa.RSAPublicKey, rsa.RSAPrivateKey,
+                                       dsa.DSAPublicKey, dsa.DSAPrivateKey)):
+            raise TypeError("Unsupported key type")
+
+        pkey._pkey = crypto_key._evp_pkey
+        if isinstance(crypto_key, (rsa.RSAPublicKey, dsa.DSAPublicKey)):
+            pkey._only_public = True
+        pkey._initialized = True
+        return pkey
+
     def generate_key(self, type, bits):
         """
         Generate a key pair of the given type, with the given number of bits.
diff --git a/tests/test_crypto.py b/tests/test_crypto.py
index 81e0ae3..0f1c867 100644
--- a/tests/test_crypto.py
+++ b/tests/test_crypto.py
@@ -18,6 +18,10 @@
 
 from six import u, b, binary_type
 
+from cryptography.hazmat.backends.openssl.backend import backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import rsa
+
 from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType
 from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType
 from OpenSSL.crypto import (
@@ -517,6 +521,13 @@
 vYeU7Ab/
 -----END RSA PRIVATE KEY-----""")
 
+ec_private_key_pem = b"""-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgYirTZSx+5O8Y6tlG
+cka6W6btJiocdrdolfcukSoTEk+hRANCAAQkvPNu7Pa1GcsWU4v7ptNfqCJVq8Cx
+zo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7fJDiFqz3
+-----END PRIVATE KEY-----
+"""
+
 
 class X509ExtTests(TestCase):
     """
@@ -749,6 +760,70 @@
                 issuer=badObj)
 
 
+class TestPKey(object):
+    """
+    py.test-based tests for :class:`OpenSSL.crypto.PKey`.
+
+    If possible, add new tests here.
+    """
+
+    def test_convert_from_cryptography_private_key(self):
+        """
+        PKey.from_cryptography_key creates a proper private PKey.
+        """
+        key = serialization.load_pem_private_key(
+            intermediate_key_pem, None, backend
+        )
+        pkey = PKey.from_cryptography_key(key)
+
+        assert isinstance(pkey, PKey)
+        assert pkey.bits() == key.key_size
+        assert pkey._only_public is False
+        assert pkey._initialized is True
+
+    def test_convert_from_cryptography_public_key(self):
+        """
+        PKey.from_cryptography_key creates a proper public PKey.
+        """
+        key = serialization.load_pem_public_key(cleartextPublicKeyPEM, backend)
+        pkey = PKey.from_cryptography_key(key)
+
+        assert isinstance(pkey, PKey)
+        assert pkey.bits() == key.key_size
+        assert pkey._only_public is True
+        assert pkey._initialized is True
+
+    def test_convert_from_cryptography_unsupported_type(self):
+        """
+        PKey.from_cryptography_key raises TypeError with an unsupported type.
+        """
+        key = serialization.load_pem_private_key(
+            ec_private_key_pem, None, backend
+        )
+        with pytest.raises(TypeError):
+            PKey.from_cryptography_key(key)
+
+    def test_convert_public_pkey_to_cryptography_key(self):
+        """
+        PKey.to_cryptography_key creates a proper cryptography public key.
+        """
+        pkey = load_publickey(FILETYPE_PEM, cleartextPublicKeyPEM)
+        key = pkey.to_cryptography_key()
+
+        assert isinstance(key, rsa.RSAPublicKey)
+        assert pkey.bits() == key.key_size
+
+    def test_convert_private_pkey_to_cryptography_key(self):
+        """
+        PKey.to_cryptography_key creates a proper cryptography private key.
+        """
+        pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+        key = pkey.to_cryptography_key()
+
+        assert isinstance(key, rsa.RSAPrivateKey)
+        assert pkey.bits() == key.key_size
+
+
 class PKeyTests(TestCase):
     """
     Unit tests for :py:class:`OpenSSL.crypto.PKey`.