diff options
| -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..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")  | 
