aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst8
-rw-r--r--docs/hazmat/primitives/asymmetric/rsa.rst55
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst19
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py26
-rw-r--r--src/cryptography/hazmat/backends/openssl/rsa.py13
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/rsa.py19
-rw-r--r--src/cryptography/hazmat/primitives/serialization.py5
-rw-r--r--tests/hazmat/backends/test_openssl.py10
-rw-r--r--tests/hazmat/primitives/test_rsa.py49
9 files changed, 197 insertions, 7 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 70fd7a53..86628946 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -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
~~~~~~~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst
index a8d7bfc0..e7033100 100644
--- a/docs/hazmat/primitives/asymmetric/rsa.rst
+++ b/docs/hazmat/primitives/asymmetric/rsa.rst
@@ -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`
@@ -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
~~~~~~~
@@ -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
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index 4a2aedc9..dd532b51 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -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
+
+ 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 occasionally used by various systems.
+
Serialization Encodings
~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 42dcc0fb..f33aba95 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -1184,6 +1184,32 @@ class Backend(object):
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):
diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py
index 0470c3fd..25168c2f 100644
--- a/src/cryptography/hazmat/backends/openssl/rsa.py
+++ b/src/cryptography/hazmat/backends/openssl/rsa.py
@@ -19,7 +19,7 @@ from cryptography.hazmat.primitives.asymmetric.padding import (
)
from cryptography.hazmat.primitives.asymmetric.rsa import (
RSAPrivateKeyWithNumbers, RSAPrivateKeyWithSerialization,
- RSAPublicKeyWithNumbers
+ RSAPublicKeyWithSerialization
)
@@ -572,7 +572,7 @@ class _RSAPrivateKey(object):
)
-@utils.register_interface(RSAPublicKeyWithNumbers)
+@utils.register_interface(RSAPublicKeyWithSerialization)
class _RSAPublicKey(object):
def __init__(self, backend, rsa_cdata):
self._backend = backend
@@ -604,3 +604,12 @@ class _RSAPublicKey(object):
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
+ )
diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
index 4963d85c..8adc7459 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -89,13 +89,30 @@ class RSAPublicKey(object):
@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):
diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py
index 7e363198..8699fa91 100644
--- a/src/cryptography/hazmat/primitives/serialization.py
+++ b/src/cryptography/hazmat/primitives/serialization.py
@@ -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
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 8ee9d246..ba0a2ba3 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -508,7 +508,7 @@ class TestRSAPEMSerialization(object):
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(
@@ -516,3 +516,11 @@ class TestRSAPEMSerialization(object):
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
+ )
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index 890a1d4e..e6d0ac28 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -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)
)
@@ -1748,7 +1751,7 @@ class TestRSAPrimeFactorRecovery(object):
@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(
@@ -1857,3 +1860,45 @@ class TestRSAPEMWriter(object):
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")