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
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index 7b3fb1d..04bc705 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -473,6 +473,13 @@
             ...
             -----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 @@
         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 @@
     :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 @@
 
         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 057b7b5..9b00c6a 100644
--- a/docs/hazmat/primitives/asymmetric/x448.rst
+++ b/docs/hazmat/primitives/asymmetric/x448.rst
@@ -66,6 +66,24 @@
 
         :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 @@
 
         :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 @@
 
         .. 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()
+    .. method:: public_bytes(encoding, format)
 
-        :returns bytes: The raw bytes of the public key.
+        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 cfe146f..ecebe7b 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -508,6 +508,9 @@
             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 @@
             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 @@
                 "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 @@
                 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 @@
         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 a10aa82..3792fd7 100644
--- a/src/cryptography/hazmat/backends/openssl/x448.py
+++ b/src/cryptography/hazmat/backends/openssl/x448.py
@@ -6,11 +6,15 @@
 
 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 @@
         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 @@
         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 69bfa40..992ec0f 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/x448.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/x448.py
@@ -25,7 +25,7 @@
         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 @@
         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)
 
@@ -55,6 +55,12 @@
         """
 
     @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):
         """
         Performs a key exchange operation using the provided peer's public key.
diff --git a/src/cryptography/hazmat/primitives/serialization/base.py b/src/cryptography/hazmat/primitives/serialization/base.py
index 5dd0c63..1670fd1 100644
--- a/src/cryptography/hazmat/primitives/serialization/base.py
+++ b/src/cryptography/hazmat/primitives/serialization/base.py
@@ -40,17 +40,20 @@
     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 a70ae74..c63e520 100644
--- a/tests/hazmat/primitives/test_dh.py
+++ b/tests/hazmat/primitives/test_dh.py
@@ -425,6 +425,20 @@
         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 @@
         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 fb41573..5d2f1bd 100644
--- a/tests/hazmat/primitives/test_dsa.py
+++ b/tests/hazmat/primitives/test_dsa.py
@@ -714,6 +714,19 @@
         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"),
         [
             [serialization.PrivateFormat.PKCS8, b"s"],
@@ -951,3 +964,19 @@
             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 f883d06..830d89a 100644
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -706,6 +706,20 @@
         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"),
         [
             [serialization.PrivateFormat.PKCS8, b"s"],
@@ -985,6 +999,20 @@
                 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 268ee9d..0c25bdb 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -2062,6 +2062,19 @@
         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"),
         [
             [serialization.PrivateFormat.PKCS8, b"s"],
@@ -2286,3 +2299,16 @@
         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 a735522..81d372f 100644
--- a/tests/hazmat/primitives/test_serialization.py
+++ b/tests/hazmat/primitives/test_serialization.py
@@ -18,7 +18,9 @@
 )
 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 @@
     def test_encryption_with_zero_length_password(self):
         with pytest.raises(ValueError):
             BestAvailableEncryption(b"")
+
+
[email protected](
+    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 71b2534..1833b03 100644
--- a/tests/hazmat/primitives/test_x448.py
+++ b/tests/hazmat/primitives/test_x448.py
@@ -11,6 +11,7 @@
 
 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 @@
         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 @@
             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 @@
             )
         ]
     )
-    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 @@
 
         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
+            )