diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fc81dec1f..9f867c4a4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,8 @@ Changes: `#496 `_ - Enable use of CRL (and more) in verify context. `#483 `_ +- ``OpenSSL.crypto.PKey`` can now be constructed from ``cryptography`` objects and also exported as such. + `#439 `_ ---- diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 4a379e84e..c37f20bad 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 @@ def __init__(self): 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 81e0ae381..0f1c867f5 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 @@ def normalize_privatekey_pem(pem): 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 @@ def test_invalid_issuer(self): 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`.