diff options
author | Alex Gaynor <alex.gaynor@gmail.com> | 2015-03-08 16:34:32 -0400 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2015-03-08 16:34:32 -0400 |
commit | 88e7ed6415ccf7fb2432b90876deefa8ab88cc98 (patch) | |
tree | f0d9d75696b461b6628d13b2e61c94ceff81e180 | |
parent | 7d5483b7cd0065b1f21b068ac2278ba74c21dc67 (diff) | |
parent | efc7f3d23836b7fd6633e95af0e2626eb1f594da (diff) | |
download | cryptography-88e7ed6415ccf7fb2432b90876deefa8ab88cc98.tar.gz cryptography-88e7ed6415ccf7fb2432b90876deefa8ab88cc98.tar.bz2 cryptography-88e7ed6415ccf7fb2432b90876deefa8ab88cc98.zip |
Merge pull request #1706 from reaperhulk/serialize-rsa-public-key
Serialize RSA public keys
-rw-r--r-- | CHANGELOG.rst | 8 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/rsa.rst | 55 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/serialization.rst | 19 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 26 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/rsa.py | 13 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/asymmetric/rsa.py | 19 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/serialization.py | 5 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 10 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_rsa.py | 49 |
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..fb8c93a4 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 RSA only, but is used by some older 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") |