Skip to content

Commit

Permalink
support RSA public key serialization (PKCS8)
Browse files Browse the repository at this point in the history
  • Loading branch information
reaperhulk committed Mar 1, 2015
1 parent d208417 commit fbd7334
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 7 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ Changelog
:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`
to
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`.
* Added
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`
and deprecated
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithNumbers`.
* Added
:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization.public_bytes`
to
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`.

0.7.2 - 2015-01-16
~~~~~~~~~~~~~~~~~~
Expand Down
50 changes: 49 additions & 1 deletion docs/hazmat/primitives/asymmetric/rsa.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ There is also support for :func:`loading public keys in the SSH format
Key serialization
~~~~~~~~~~~~~~~~~

If you have a key that you've loaded or generated which implements the
If you have a private key that you've loaded or generated which implements the
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`
interface you can use
:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`
Expand Down Expand Up @@ -113,6 +113,23 @@ It is also possible to serialize without encryption using
>>> pem.splitlines()[0]
'-----BEGIN RSA PRIVATE KEY-----'

Similarly, if your public key implements
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`
interface you can use
:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization.public_bytes`
to serialize the key.

.. doctest::

>>> from cryptography.hazmat.primitives import serialization
>>> public_key = private_key.public_key()
>>> pem = public_key.public_bytes(
... encoding=serialization.Encoding.PEM,
... format=serialization.Format.PKCS8
... )
>>> pem.splitlines()[0]
'-----BEGIN PUBLIC KEY-----'

Signing
~~~~~~~

Expand Down Expand Up @@ -625,6 +642,37 @@ Key interfaces
instance.


.. class:: RSAPublicKeyWithSerialization

.. versionadded:: 0.8

Extends :class:`RSAPublicKey`.

.. method:: public_numbers()

Create a
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers`
object.

:returns: An
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers`
instance.

.. method:: public_bytes(encoding, format)

Allows serialization of the key to bytes. Encoding (PEM or DER) and
format (TraditionalOpenSSL or PKCS8) are chosen to define the exact
serialization.

:param encoding: A value from the
:class:`~cryptography.hazmat.primitives.serialization.Encoding` enum.

:param format: A value from the
:class:`~cryptography.hazmat.primitives.serialization.Format` enum.

:return bytes: Serialized key.


.. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem)
.. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography
.. _`specific mathematical properties`: https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation
Expand Down
25 changes: 23 additions & 2 deletions src/cryptography/hazmat/backends/openssl/rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
)
from cryptography.hazmat.primitives.asymmetric.rsa import (
RSAPrivateKeyWithNumbers, RSAPrivateKeyWithSerialization,
RSAPublicKeyWithNumbers
RSAPublicKeyWithSerialization
)
from cryptography.hazmat.primitives.serialization import (
BestAvailableEncryption, Encoding, Format, KeySerializationEncryption,
Expand Down Expand Up @@ -622,7 +622,7 @@ def private_bytes(self, encoding, format, encryption_algorithm):
return self._backend._read_mem_bio(bio)


@utils.register_interface(RSAPublicKeyWithNumbers)
@utils.register_interface(RSAPublicKeyWithSerialization)
class _RSAPublicKey(object):
def __init__(self, backend, rsa_cdata):
self._backend = backend
Expand Down Expand Up @@ -654,3 +654,24 @@ def public_numbers(self):
e=self._backend._bn_to_int(self._rsa_cdata.e),
n=self._backend._bn_to_int(self._rsa_cdata.n),
)

def public_bytes(self, encoding, format):
if not isinstance(encoding, Encoding):
raise TypeError("encoding must be an item from the Encoding enum")

if not isinstance(format, Format):
raise TypeError("format must be an item from the Format enum")

# This is a temporary check until we land DER serialization.
if encoding is not Encoding.PEM:
raise ValueError("Only PEM encoding is supported by this backend")

if format is not Format.PKCS8:
raise ValueError(
"Only PKCS8 formatting is supported by this backend"
)

bio = self._backend._create_mem_bio()
res = self._backend._lib.PEM_write_bio_PUBKEY(bio, self._evp_pkey)
assert res == 1
return self._backend._read_mem_bio(bio)
19 changes: 18 additions & 1 deletion src/cryptography/hazmat/primitives/asymmetric/rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,30 @@ def key_size(self):


@six.add_metaclass(abc.ABCMeta)
class RSAPublicKeyWithNumbers(RSAPublicKey):
class RSAPublicKeyWithSerialization(RSAPublicKey):
@abc.abstractmethod
def public_numbers(self):
"""
Returns an RSAPublicNumbers
"""

@abc.abstractmethod
def public_bytes(self, encoding, format):
"""
Returns the key serialized as bytes.
"""


RSAPublicKeyWithNumbers = utils.deprecated(
RSAPublicKeyWithSerialization,
__name__,
(
"The RSAPublicKeyWithNumbers interface has been renamed to "
"RSAPublicKeyWithSerialization"
),
utils.DeprecatedIn08
)


def generate_private_key(public_exponent, key_size, backend):
if not isinstance(backend, RSABackend):
Expand Down
17 changes: 16 additions & 1 deletion tests/hazmat/backends/test_openssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,11 +508,26 @@ def test_password_length_limit(self):
serialization.BestAvailableEncryption(password)
)

def test_unsupported_key_encoding(self):
def test_unsupported_private_key_encoding(self):
key = RSA_KEY_2048.private_key(backend)
with pytest.raises(ValueError):
key.private_bytes(
serialization.Encoding.DER,
serialization.Format.PKCS8,
serialization.NoEncryption()
)

def test_unsupported_public_key_encoding(self):
key = RSA_KEY_2048.private_key(backend).public_key()
with pytest.raises(ValueError):
key.public_bytes(
serialization.Encoding.DER, serialization.Format.PKCS8
)

def test_unsupported_public_key_format(self):
key = RSA_KEY_2048.private_key(backend).public_key()
with pytest.raises(ValueError):
key.public_bytes(
serialization.Encoding.PEM,
serialization.Format.TraditionalOpenSSL
)
36 changes: 34 additions & 2 deletions tests/hazmat/primitives/test_rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ def test_modular_inverse():


def _skip_if_no_serialization(key, backend):
if not isinstance(key, rsa.RSAPrivateKeyWithSerialization):
if not isinstance(
key,
(rsa.RSAPrivateKeyWithSerialization, rsa.RSAPublicKeyWithSerialization)
):
pytest.skip(
"{0} does not support RSA key serialization".format(backend)
)
Expand Down Expand Up @@ -1748,7 +1751,7 @@ def test_invalid_recover_prime_factors(self):

@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
class TestRSAPEMWriter(object):
class TestRSAPEMPrivateKeySerialization(object):
@pytest.mark.parametrize(
("fmt", "password"),
itertools.product(
Expand Down Expand Up @@ -1854,3 +1857,32 @@ def test_private_bytes_unsupported_encryption_type(self, backend):
serialization.Format.TraditionalOpenSSL,
DummyKeyEncryption()
)


@pytest.mark.requires_backend_interface(interface=RSABackend)
@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend)
class TestRSAPEMPublicKeySerialization(object):
def test_public_bytes_unencrypted_pem(self, backend):
key_bytes = load_vectors_from_file(
os.path.join("asymmetric", "PKCS8", "unenc-rsa-pkcs8.pub.pem"),
lambda pemfile: pemfile.read().encode()
)
key = serialization.load_pem_public_key(key_bytes, backend)
_skip_if_no_serialization(key, backend)
serialized = key.public_bytes(
serialization.Encoding.PEM,
serialization.Format.PKCS8,
)
assert serialized == key_bytes

def test_public_bytes_invalid_encoding(self, backend):
key = RSA_KEY_2048.private_key(backend).public_key()
_skip_if_no_serialization(key, backend)
with pytest.raises(TypeError):
key.public_bytes("notencoding", serialization.Format.PKCS8)

def test_public_bytes_invalid_format(self, backend):
key = RSA_KEY_2048.private_key(backend).public_key()
_skip_if_no_serialization(key, backend)
with pytest.raises(TypeError):
key.public_bytes(serialization.Encoding.PEM, "invalidformat")

0 comments on commit fbd7334

Please sign in to comment.