From dbcbffa06c9930a687010ca816596ca3f5cc78e9 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 12 Jan 2019 21:18:21 -0800 Subject: support x448 public/private serialization both raw and pkcs8 (#4653) * support x448 public/private serialization both raw and pkcs8 * add tests for all other asym key types to prevent Raw * more tests * better tests * fix a test * funny story, I'm actually illiterate. * pep8 * require PrivateFormat.Raw or PublicFormat.Raw with Encoding.Raw * missing docs * parametrize * docs fixes * remove dupe line * assert something --- .../hazmat/primitives/asymmetric/serialization.rst | 33 ++++++- docs/hazmat/primitives/asymmetric/x448.rst | 82 +++++++++++++++- .../hazmat/backends/openssl/backend.py | 28 +++++- src/cryptography/hazmat/backends/openssl/x448.py | 73 +++++++++++++- .../hazmat/primitives/asymmetric/x448.py | 10 +- .../hazmat/primitives/serialization/base.py | 3 + tests/hazmat/primitives/test_dh.py | 31 ++++++ tests/hazmat/primitives/test_dsa.py | 29 ++++++ tests/hazmat/primitives/test_ec.py | 28 ++++++ tests/hazmat/primitives/test_rsa.py | 26 +++++ tests/hazmat/primitives/test_serialization.py | 68 ++++++++++++- tests/hazmat/primitives/test_x448.py | 107 +++++++++++++++++++-- 12 files changed, 497 insertions(+), 21 deletions(-) diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index 7b3fb1d6..04bc705a 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -473,6 +473,13 @@ Serialization Formats ... -----END PRIVATE KEY----- + .. attribute:: Raw + + .. versionadded:: 2.5 + + A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a + binary format and is invalid for other key types. + .. class:: PublicFormat .. versionadded:: 0.8 @@ -516,6 +523,13 @@ Serialization Formats The public key format used by OpenSSH (e.g. as found in ``~/.ssh/id_rsa.pub`` or ``~/.ssh/authorized_keys``). + .. attribute:: Raw + + .. versionadded:: 2.5 + + A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a + binary format and is invalid for other key types. + .. class:: ParameterFormat .. versionadded:: 2.0 @@ -538,14 +552,16 @@ Serialization Encodings :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` , :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` - , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization` + , :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`, and - :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey` as well as ``public_bytes`` on - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`, - :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKeyWithSerialization` + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, and - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. .. attribute:: PEM @@ -565,6 +581,13 @@ Serialization Encodings The format used by OpenSSH public keys. This is a text format. + .. attribute:: Raw + + .. versionadded:: 2.5 + + A raw format used by :doc:`/hazmat/primitives/asymmetric/x448`. It is a + binary format and is invalid for other key types. + Serialization Encryption Types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/hazmat/primitives/asymmetric/x448.rst b/docs/hazmat/primitives/asymmetric/x448.rst index 057b7b50..9b00c6af 100644 --- a/docs/hazmat/primitives/asymmetric/x448.rst +++ b/docs/hazmat/primitives/asymmetric/x448.rst @@ -66,6 +66,24 @@ Key interfaces :returns: :class:`X448PrivateKey` + .. classmethod:: from_private_bytes(data) + + :param bytes data: 56 byte private key. + + :returns: :class:`X448PrivateKey` + + .. doctest:: + + >>> from cryptography.hazmat.primitives import serialization + >>> from cryptography.hazmat.primitives.asymmetric import x448 + >>> private_key = x448.X448PrivateKey.generate() + >>> private_bytes = private_key.private_bytes( + ... encoding=serialization.Encoding.Raw, + ... format=serialization.PrivateFormat.Raw, + ... encryption_algorithm=serialization.NoEncryption() + ... ) + >>> loaded_private_key = x448.X448PrivateKey.from_private_bytes(private_bytes) + .. method:: public_key() :returns: :class:`X448PublicKey` @@ -77,6 +95,36 @@ Key interfaces :returns bytes: A shared key. + .. method:: private_bytes(encoding, format, encryption_algorithm) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8` + or + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` + ) 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.PrivateFormat` + enum. If the ``encoding`` is + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + then ``format`` must be + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.Raw` + , otherwise it must be + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`. + + :param encryption_algorithm: An instance of an object conforming to the + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` + interface. + + :return bytes: Serialized key. + .. class:: X448PublicKey .. versionadded:: 2.5 @@ -89,15 +137,41 @@ Key interfaces .. doctest:: + >>> from cryptography.hazmat.primitives import serialization >>> from cryptography.hazmat.primitives.asymmetric import x448 >>> private_key = x448.X448PrivateKey.generate() >>> public_key = private_key.public_key() - >>> public_bytes = public_key.public_bytes() + >>> public_bytes = public_key.public_bytes( + ... encoding=serialization.Encoding.Raw, + ... format=serialization.PublicFormat.Raw + ... ) >>> loaded_public_key = x448.X448PublicKey.from_public_bytes(public_bytes) - .. method:: public_bytes() - - :returns bytes: The raw bytes of the public key. + .. method:: public_bytes(encoding, format) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM`, + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`, or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw`) and + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo` + or + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` + ) 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. If the ``encoding`` is + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.Raw` + then ``format`` must be + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.Raw` + , otherwise it must be + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`. + + :returns bytes: The public key bytes. .. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index cfe146f2..ecebe7b8 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -508,6 +508,9 @@ class Backend(object): self.openssl_assert(dh_cdata != self._ffi.NULL) dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) return _DHPrivateKey(self, dh_cdata, evp_pkey) + elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): + # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 + return _X448PrivateKey(self, evp_pkey) else: raise UnsupportedAlgorithm("Unsupported key type.") @@ -539,6 +542,9 @@ class Backend(object): self.openssl_assert(dh_cdata != self._ffi.NULL) dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) return _DHPublicKey(self, dh_cdata, evp_pkey) + elif key_type == getattr(self._lib, "EVP_PKEY_X448", None): + # EVP_PKEY_X448 is not present in OpenSSL < 1.1.1 + return _X448PublicKey(self, evp_pkey) else: raise UnsupportedAlgorithm("Unsupported key type.") @@ -1678,6 +1684,16 @@ class Backend(object): "format must be an item from the PrivateFormat enum" ) + # Raw format and encoding are only valid for X25519, Ed25519, X448, and + # Ed448 keys. We capture those cases before this method is called so if + # we see those enum values here it means the caller has passed them to + # a key that doesn't support raw type + if format is serialization.PrivateFormat.Raw: + raise ValueError("raw format is invalid with this key or encoding") + + if encoding is serialization.Encoding.Raw: + raise ValueError("raw encoding is invalid with this key or format") + if not isinstance(encryption_algorithm, serialization.KeySerializationEncryption): raise TypeError( @@ -1737,7 +1753,7 @@ class Backend(object): write_bio = self._lib.i2d_PKCS8PrivateKey_bio key = evp_pkey else: - raise TypeError("encoding must be an item from the Encoding enum") + raise TypeError("encoding must be Encoding.PEM or Encoding.DER") bio = self._create_mem_bio_gc() res = write_bio( @@ -1770,6 +1786,16 @@ class Backend(object): if not isinstance(encoding, serialization.Encoding): raise TypeError("encoding must be an item from the Encoding enum") + # Raw format and encoding are only valid for X25519, Ed25519, X448, and + # Ed448 keys. We capture those cases before this method is called so if + # we see those enum values here it means the caller has passed them to + # a key that doesn't support raw type + if format is serialization.PublicFormat.Raw: + raise ValueError("raw format is invalid with this key or encoding") + + if encoding is serialization.Encoding.Raw: + raise ValueError("raw encoding is invalid with this key or format") + if ( format is serialization.PublicFormat.OpenSSH or encoding is serialization.Encoding.OpenSSH diff --git a/src/cryptography/hazmat/backends/openssl/x448.py b/src/cryptography/hazmat/backends/openssl/x448.py index a10aa821..3792fd79 100644 --- a/src/cryptography/hazmat/backends/openssl/x448.py +++ b/src/cryptography/hazmat/backends/openssl/x448.py @@ -6,11 +6,15 @@ from __future__ import absolute_import, division, print_function from cryptography import utils from cryptography.hazmat.backends.openssl.utils import _evp_pkey_derive +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.x448 import ( X448PrivateKey, X448PublicKey ) _X448_KEY_SIZE = 56 +_PEM_DER = ( + serialization.Encoding.PEM, serialization.Encoding.DER +) @utils.register_interface(X448PublicKey) @@ -19,7 +23,35 @@ class _X448PublicKey(object): self._backend = backend self._evp_pkey = evp_pkey - def public_bytes(self): + def public_bytes(self, encoding, format): + if ( + encoding is serialization.Encoding.Raw or + format is serialization.PublicFormat.Raw + ): + if ( + encoding is not serialization.Encoding.Raw or + format is not serialization.PublicFormat.Raw + ): + raise ValueError( + "When using Raw both encoding and format must be Raw" + ) + + return self._raw_public_bytes() + + if ( + encoding in _PEM_DER and + format is not serialization.PublicFormat.SubjectPublicKeyInfo + ): + raise ValueError( + "format must be SubjectPublicKeyInfo when encoding is PEM or " + "DER" + ) + + return self._backend._public_key_bytes( + encoding, format, self, self._evp_pkey, None + ) + + def _raw_public_bytes(self): buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) res = self._backend._lib.EVP_PKEY_get_raw_public_key( @@ -53,3 +85,42 @@ class _X448PrivateKey(object): return _evp_pkey_derive( self._backend, self._evp_pkey, peer_public_key ) + + def private_bytes(self, encoding, format, encryption_algorithm): + if ( + encoding is serialization.Encoding.Raw or + format is serialization.PublicFormat.Raw + ): + if ( + format is not serialization.PrivateFormat.Raw or + encoding is not serialization.Encoding.Raw or not + isinstance(encryption_algorithm, serialization.NoEncryption) + ): + raise ValueError( + "When using Raw both encoding and format must be Raw " + "and encryption_algorithm must be NoEncryption" + ) + + return self._raw_private_bytes() + + if ( + encoding in _PEM_DER and + format is not serialization.PrivateFormat.PKCS8 + ): + raise ValueError( + "format must be PKCS8 when encoding is PEM or DER" + ) + + return self._backend._private_key_bytes( + encoding, format, encryption_algorithm, self._evp_pkey, None + ) + + def _raw_private_bytes(self): + buf = self._backend._ffi.new("unsigned char []", _X448_KEY_SIZE) + buflen = self._backend._ffi.new("size_t *", _X448_KEY_SIZE) + res = self._backend._lib.EVP_PKEY_get_raw_private_key( + self._evp_pkey, buf, buflen + ) + self._backend.openssl_assert(res == 1) + self._backend.openssl_assert(buflen[0] == _X448_KEY_SIZE) + return self._backend._ffi.buffer(buf, _X448_KEY_SIZE)[:] diff --git a/src/cryptography/hazmat/primitives/asymmetric/x448.py b/src/cryptography/hazmat/primitives/asymmetric/x448.py index 69bfa408..992ec0fd 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/x448.py +++ b/src/cryptography/hazmat/primitives/asymmetric/x448.py @@ -25,7 +25,7 @@ class X448PublicKey(object): return backend.x448_load_public_bytes(data) @abc.abstractmethod - def public_bytes(self): + def public_bytes(self, encoding, format): """ The serialized bytes of the public key. """ @@ -44,7 +44,7 @@ class X448PrivateKey(object): return backend.x448_generate_key() @classmethod - def _from_private_bytes(cls, data): + def from_private_bytes(cls, data): from cryptography.hazmat.backends.openssl.backend import backend return backend.x448_load_private_bytes(data) @@ -54,6 +54,12 @@ class X448PrivateKey(object): The serialized bytes of the public key. """ + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + The serialized bytes of the private key. + """ + @abc.abstractmethod def exchange(self, peer_public_key): """ diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py index 5dd0c639..1670fd18 100644 --- a/src/cryptography/hazmat/primitives/serialization/base.py +++ b/src/cryptography/hazmat/primitives/serialization/base.py @@ -40,17 +40,20 @@ class Encoding(Enum): PEM = "PEM" DER = "DER" OpenSSH = "OpenSSH" + Raw = "Raw" class PrivateFormat(Enum): PKCS8 = "PKCS8" TraditionalOpenSSL = "TraditionalOpenSSL" + Raw = "Raw" class PublicFormat(Enum): SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1" PKCS1 = "Raw PKCS#1" OpenSSH = "OpenSSH" + Raw = "Raw" class ParameterFormat(Enum): diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py index a70ae745..c63e520f 100644 --- a/tests/hazmat/primitives/test_dh.py +++ b/tests/hazmat/primitives/test_dh.py @@ -424,6 +424,20 @@ class TestDHPrivateKeySerialization(object): priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + ] + ) + def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key() + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + @pytest.mark.parametrize( ("key_path", "loader_func", "encoding", "is_dhx"), [ @@ -806,6 +820,23 @@ class TestDHParameterSerialization(object): else: assert parameter_numbers.q is None + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PublicFormat.Raw), + (serialization.Encoding.PEM, serialization.PublicFormat.Raw), + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + ] + ) + def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + parameters = dh.generate_parameters(2, 512, backend) + key = parameters.generate_private_key().public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) + def test_parameter_bytes_invalid_encoding(self, backend): parameters = dh.generate_parameters(2, 512, backend) with pytest.raises(TypeError): diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py index fb415732..5d2f1bd8 100644 --- a/tests/hazmat/primitives/test_dsa.py +++ b/tests/hazmat/primitives/test_dsa.py @@ -713,6 +713,19 @@ class TestDSASerialization(object): priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + ] + ) + def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + key = DSA_KEY_1024.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + @pytest.mark.parametrize( ("fmt", "password"), [ @@ -951,3 +964,19 @@ class TestDSAPEMPublicKeySerialization(object): key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 ) + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PublicFormat.Raw), + (serialization.Encoding.PEM, serialization.PublicFormat.Raw), + ( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ), + ] + ) + def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + key = DSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index f883d065..830d89a0 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -705,6 +705,20 @@ class TestECSerialization(object): priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + ] + ) + def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + @pytest.mark.parametrize( ("fmt", "password"), [ @@ -985,6 +999,20 @@ class TestEllipticCurvePEMPublicKeySerialization(object): serialization.PublicFormat.SubjectPublicKeyInfo ) + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PublicFormat.Raw), + (serialization.Encoding.PEM, serialization.PublicFormat.Raw), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + ) + def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) + def test_public_bytes_invalid_format(self, backend): _skip_curve_unsupported(backend, ec.SECP256R1()) key = load_vectors_from_file( diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py index 268ee9d9..0c25bdbb 100644 --- a/tests/hazmat/primitives/test_rsa.py +++ b/tests/hazmat/primitives/test_rsa.py @@ -2061,6 +2061,19 @@ class TestRSAPrivateKeySerialization(object): priv_num = key.private_numbers() assert loaded_priv_num == priv_num + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PrivateFormat.PKCS8), + (serialization.Encoding.DER, serialization.PrivateFormat.Raw), + (serialization.Encoding.Raw, serialization.PrivateFormat.Raw), + ] + ) + def test_private_bytes_rejects_raw(self, encoding, fmt, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes(encoding, fmt, serialization.NoEncryption()) + @pytest.mark.parametrize( ("fmt", "password"), [ @@ -2286,3 +2299,16 @@ class TestRSAPEMPublicKeySerialization(object): key = RSA_KEY_2048.private_key(backend).public_key() with pytest.raises(TypeError): key.public_bytes(serialization.Encoding.PEM, "invalidformat") + + @pytest.mark.parametrize( + ("encoding", "fmt"), + [ + (serialization.Encoding.Raw, serialization.PublicFormat.Raw), + (serialization.Encoding.PEM, serialization.PublicFormat.Raw), + (serialization.Encoding.Raw, serialization.PublicFormat.PKCS1), + ] + ) + def test_public_bytes_rejects_raw(self, encoding, fmt, backend): + key = RSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes(encoding, fmt) diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py index a7355221..81d372fc 100644 --- a/tests/hazmat/primitives/test_serialization.py +++ b/tests/hazmat/primitives/test_serialization.py @@ -18,7 +18,9 @@ from cryptography.hazmat.backends.interfaces import ( ) from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from cryptography.hazmat.primitives.serialization import ( - BestAvailableEncryption, load_der_parameters, load_der_private_key, + BestAvailableEncryption, Encoding, NoEncryption, + PrivateFormat, PublicFormat, + load_der_parameters, load_der_private_key, load_der_public_key, load_pem_parameters, load_pem_private_key, load_pem_public_key, load_ssh_public_key ) @@ -1231,3 +1233,67 @@ class TestKeySerializationEncryptionTypes(object): def test_encryption_with_zero_length_password(self): with pytest.raises(ValueError): BestAvailableEncryption(b"") + + +@pytest.mark.supported( + only_if=lambda backend: backend.x448_supported(), + skip_message="Requires OpenSSL with X448 support" +) +class TestX448Serialization(object): + def test_load_der_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8-enc.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8.der"), + lambda derfile: derfile.read(), + mode="rb" + ) + key = load_der_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.DER, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + def test_load_pem_private_key(self, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8-enc.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + unencrypted = load_vectors_from_file( + os.path.join("asymmetric", "X448", "x448-pkcs8.pem"), + lambda pemfile: pemfile.read(), + mode="rb" + ) + key = load_pem_private_key(data, b"password", backend) + assert key.private_bytes( + Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() + ) == unencrypted + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader"), + [ + ( + ["X448", "x448-pub.pem"], + Encoding.PEM, + load_pem_public_key + ), + ( + ["X448", "x448-pub.der"], + Encoding.DER, + load_der_public_key + ), + ] + ) + def test_load_public_key(self, key_path, encoding, loader, backend): + data = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: pemfile.read(), + mode="rb" + ) + public_key = loader(data, backend) + assert public_key.public_bytes( + encoding, PublicFormat.SubjectPublicKeyInfo + ) == data diff --git a/tests/hazmat/primitives/test_x448.py b/tests/hazmat/primitives/test_x448.py index 71b25341..1833b03d 100644 --- a/tests/hazmat/primitives/test_x448.py +++ b/tests/hazmat/primitives/test_x448.py @@ -11,6 +11,7 @@ import pytest from cryptography.exceptions import _Reasons from cryptography.hazmat.backends.interfaces import DHBackend +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.x448 import ( X448PrivateKey, X448PublicKey ) @@ -50,7 +51,7 @@ class TestX448Exchange(object): private = binascii.unhexlify(vector["input_scalar"]) public = binascii.unhexlify(vector["input_u"]) shared_key = binascii.unhexlify(vector["output_u"]) - private_key = X448PrivateKey._from_private_bytes(private) + private_key = X448PrivateKey.from_private_bytes(private) public_key = X448PublicKey.from_public_bytes(public) computed_shared_key = private_key.exchange(public_key) assert computed_shared_key == shared_key @@ -64,11 +65,11 @@ class TestX448Exchange(object): b"aa3b4749d55b9daf1e5b00288826c467274ce3ebbdd5c17b975e09d4" b"af6c67cf10d087202db88286e2b79fceea3ec353ef54faa26e219f38" ) - private_key = X448PrivateKey._from_private_bytes(private) + private_key = X448PrivateKey.from_private_bytes(private) public_key = X448PublicKey.from_public_bytes(public) for _ in range(1000): computed_shared_key = private_key.exchange(public_key) - private_key = X448PrivateKey._from_private_bytes( + private_key = X448PrivateKey.from_private_bytes( computed_shared_key ) public_key = X448PublicKey.from_public_bytes(old_private) @@ -103,11 +104,60 @@ class TestX448Exchange(object): ) ] ) - def test_public_bytes(self, private_bytes, public_bytes, backend): - private_key = X448PrivateKey._from_private_bytes(private_bytes) - assert private_key.public_key().public_bytes() == public_bytes + def test_pub_priv_bytes_raw(self, private_bytes, public_bytes, backend): + private_key = X448PrivateKey.from_private_bytes(private_bytes) + assert private_key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) == private_bytes + assert private_key.public_key().public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes public_key = X448PublicKey.from_public_bytes(public_bytes) - assert public_key.public_bytes() == public_bytes + assert public_key.public_bytes( + serialization.Encoding.Raw, serialization.PublicFormat.Raw + ) == public_bytes + + @pytest.mark.parametrize( + ("encoding", "fmt", "encryption", "passwd", "load_func"), + [ + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(b"password"), + b"password", + serialization.load_der_private_key + ), + ( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_pem_private_key + ), + ( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + None, + serialization.load_der_private_key + ), + ] + ) + def test_round_trip_private_serialization(self, encoding, fmt, encryption, + passwd, load_func, backend): + key = X448PrivateKey.generate() + serialized = key.private_bytes(encoding, fmt, encryption) + loaded_key = load_func(serialized, passwd, backend) + assert isinstance(loaded_key, X448PrivateKey) def test_generate(self, backend): key = X448PrivateKey.generate() @@ -125,3 +175,46 @@ class TestX448Exchange(object): with pytest.raises(ValueError): X448PublicKey.from_public_bytes(b"a" * 57) + + def test_invalid_private_bytes(self, backend): + key = X448PrivateKey.generate() + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.Raw, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.Raw, + serialization.PrivateFormat.PKCS8, + None + ) + + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.Raw, + serialization.NoEncryption() + ) + + def test_invalid_public_bytes(self, backend): + key = X448PrivateKey.generate().public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.Raw, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1 + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.Raw + ) -- cgit v1.2.3