Skip to content

Commit

Permalink
Convert between pyOpenSSL and cryptography objects (#439)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
reaperhulk authored and Lukasa committed Jul 29, 2016
1 parent db8ec13 commit 72d968b
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Changes:
`#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>`_


----
Expand Down
42 changes: 42 additions & 0 deletions src/OpenSSL/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
75 changes: 75 additions & 0 deletions tests/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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`.
Expand Down

0 comments on commit 72d968b

Please sign in to comment.