Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ Changelog
:meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization.private_bytes`
to
:class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`.
* 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
55 changes: 54 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.PublicFormat.SubjectPublicKeyInfo
... )
>>> pem.splitlines()[0]
'-----BEGIN PUBLIC KEY-----'

Signing
~~~~~~~

Expand Down Expand Up @@ -626,6 +643,42 @@ 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 (
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or
:attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and
format (
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`
or
:attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.PKCS1`)
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.PublicFormat` 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
19 changes: 19 additions & 0 deletions docs/hazmat/primitives/asymmetric/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,25 @@ Serialization Formats
encryption. Choose this unless you have explicit legacy compatibility
requirements.

.. class:: PublicFormat

.. versionadded:: 0.8

An enumeration for public key formats. Used with the ``public_bytes``
method available on
:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`.

.. attribute:: SubjectPublicKeyInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the best name for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the name used by PyCrypto and Crypto++. Go calls it a "DER-encoded PKIX structure" but if you look in the source code it says "pkixPublicKey reflects a PKIX public key structure. See SubjectPublicKeyInfo in RFC 3280."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Good enough for me.

On Sun, Mar 8, 2015 at 3:12 PM, Paul Kehrer notifications@github.com
wrote:

In docs/hazmat/primitives/asymmetric/serialization.rst
#1706 (comment):

@@ -309,6 +309,25 @@ Serialization Formats
encryption. Choose this unless you have explicit legacy compatibility
requirements.

+.. class:: PublicFormat
+

  • .. versionadded:: 0.8
  • An enumeration for public key formats. Used with the public_bytes
  • method available on
  • :class:~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization.
  • .. attribute:: SubjectPublicKeyInfo

It's the name used by PyCrypto and Crypto++
http://www.cryptopp.com/wiki/Keys_and_Formats#Public_Key_Format. Go
calls it a "DER-encoded PKIX structure" but if you look in the source code
it says "pkixPublicKey reflects a PKIX public key structure. See
SubjectPublicKeyInfo in RFC 3280."


Reply to this email directly or view it on GitHub
https://github.com/pyca/cryptography/pull/1706/files#r26007907.

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084


This is the typical public key format. It consists of an algorithm
identifier and the public key as a bit string. Choose this unless
you have specific needs.

.. attribute:: PKCS1

Just the public key elements (without the algorithm identifier). This
format is RSA only, but is used by some older systems.

Serialization Encodings
~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
26 changes: 26 additions & 0 deletions src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,32 @@ def _private_key_bytes(self, encoding, format, encryption_algorithm,
assert res == 1
return self._read_mem_bio(bio)

def _public_key_bytes(self, encoding, format, pkcs1_write_func, evp_pkey,
cdata):
if not isinstance(encoding, serialization.Encoding):
raise TypeError("encoding must be an item from the Encoding enum")

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

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

if format is serialization.PublicFormat.SubjectPublicKeyInfo:
write_bio = self._lib.PEM_write_bio_PUBKEY
key = evp_pkey
elif format is serialization.PublicFormat.PKCS1:
write_bio = pkcs1_write_func
key = cdata

bio = self._create_mem_bio()
res = write_bio(bio, key)
assert res == 1
return self._read_mem_bio(bio)


class GetCipherByName(object):
def __init__(self, fmt):
Expand Down
13 changes: 11 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
)


Expand Down Expand Up @@ -572,7 +572,7 @@ def private_bytes(self, encoding, format, encryption_algorithm):
)


@utils.register_interface(RSAPublicKeyWithNumbers)
@utils.register_interface(RSAPublicKeyWithSerialization)
class _RSAPublicKey(object):
def __init__(self, backend, rsa_cdata):
self._backend = backend
Expand Down Expand Up @@ -604,3 +604,12 @@ 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):
return self._backend._public_key_bytes(
encoding,
format,
self._backend._lib.PEM_write_bio_RSAPublicKey,
self._evp_pkey,
self._rsa_cdata
)
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
5 changes: 5 additions & 0 deletions src/cryptography/hazmat/primitives/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ class PrivateFormat(Enum):
TraditionalOpenSSL = "TraditionalOpenSSL"


class PublicFormat(Enum):
SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1"
PKCS1 = "Raw PKCS#1"


@six.add_metaclass(abc.ABCMeta)
class KeySerializationEncryption(object):
pass
Expand Down
10 changes: 9 additions & 1 deletion tests/hazmat/backends/test_openssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,11 +508,19 @@ 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.PrivateFormat.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.PublicFormat.SubjectPublicKeyInfo
)
49 changes: 47 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 @@ -1857,3 +1860,45 @@ def test_private_bytes_unsupported_encryption_type(self, backend):
serialization.PrivateFormat.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.PublicFormat.SubjectPublicKeyInfo,
)
assert serialized == key_bytes

def test_public_bytes_pkcs1_unencrypted_pem(self, backend):
key_bytes = load_vectors_from_file(
os.path.join("asymmetric", "public", "PKCS1", "rsa.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.PublicFormat.PKCS1,
)
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.PublicFormat.PKCS1)

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")