Compressed point support (#4629)

* compressed point support

* refactor to use oct2point directly

* small docs change

* remove deprecation for the moment and a bit of review feedback

* no backend arg, implicitly import it

* missed a spot

* double oops

* remove superfluous call

* use refactored method

* use vector file

* one last item
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 54004b4..7780c6b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -28,6 +28,10 @@
   :class:`~cryptography.x509.RelativeDistinguishedName` and
   :class:`~cryptography.x509.NameAttribute` to format the name or component as
   a RFC 4514 Distinguished Name string.
+* Added
+  :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.from_encoded_point`,
+  which immediately checks if the point is on the curve and supports compressed
+  points.
 
 .. _v2-4-2:
 
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index 5936cf4..728c515 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -704,6 +704,27 @@
         Size (in :term:`bits`) of a secret scalar for the curve (as generated
         by :func:`generate_private_key`).
 
+    .. classmethod:: from_encoded_point(curve, data)
+
+        .. versionadded:: 2.5
+
+        Decodes a byte string as described in `SEC 1 v2.0`_ section 2.3.3 and
+        returns an :class:`EllipticCurvePublicKey`. This class method supports
+        compressed points.
+
+        :param curve: An
+            :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`
+            instance.
+
+        :param bytes data: The serialized point byte string.
+
+        :returns: An :class:`EllipticCurvePublicKey` instance.
+
+        :raises ValueError: Raised when an invalid point is supplied.
+
+        :raises TypeError: Raised when curve is not an
+            :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`.
+
 
 .. class:: EllipticCurvePublicKeyWithSerialization
 
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 99f6ccf..cfe146f 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -1383,6 +1383,26 @@
 
         return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey)
 
+    def load_elliptic_curve_public_bytes(self, curve, point_bytes):
+        ec_cdata = self._ec_key_new_by_curve(curve)
+        group = self._lib.EC_KEY_get0_group(ec_cdata)
+        self.openssl_assert(group != self._ffi.NULL)
+        point = self._lib.EC_POINT_new(group)
+        self.openssl_assert(point != self._ffi.NULL)
+        point = self._ffi.gc(point, self._lib.EC_POINT_free)
+        with self._tmp_bn_ctx() as bn_ctx:
+            res = self._lib.EC_POINT_oct2point(
+                group, point, point_bytes, len(point_bytes), bn_ctx
+            )
+            if res != 1:
+                self._consume_errors()
+                raise ValueError("Invalid public bytes for the given curve")
+
+        res = self._lib.EC_KEY_set_public_key(ec_cdata, point)
+        self.openssl_assert(res == 1)
+        evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata)
+        return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey)
+
     def derive_elliptic_curve_private_key(self, private_value, curve):
         ec_cdata = self._ec_key_new_by_curve(curve)
 
diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py
index 1d709d3..6b1de7c 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/ec.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py
@@ -151,6 +151,18 @@
         Verifies the signature of the data.
         """
 
+    @classmethod
+    def from_encoded_point(cls, curve, data):
+        utils._check_bytes("data", data)
+        if not isinstance(curve, EllipticCurve):
+            raise TypeError("curve must be an EllipticCurve instance")
+
+        if six.indexbytes(data, 0) not in [0x02, 0x03, 0x04]:
+            raise ValueError("Unsupported elliptic curve point type")
+
+        from cryptography.hazmat.backends.openssl.backend import backend
+        return backend.load_elliptic_curve_public_bytes(curve, data)
+
 
 EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey
 
diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py
index f58ff07..cb83892 100644
--- a/src/cryptography/hazmat/primitives/serialization/ssh.py
+++ b/src/cryptography/hazmat/primitives/serialization/ssh.py
@@ -99,8 +99,7 @@
             "Compressed elliptic curve points are not supported"
         )
 
-    numbers = ec.EllipticCurvePublicNumbers.from_encoded_point(curve, data)
-    return numbers.public_key(backend)
+    return ec.EllipticCurvePublicKey.from_encoded_point(curve, data)
 
 
 def _ssh_read_next_string(data):
diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py
index 6d49366..9a8ddf6 100644
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -212,7 +212,7 @@
         )
 
 
-def test_from_encoded_point_unsupported_point_type():
+def test_from_encoded_point_unsupported_point_no_backend():
     # set to point type 2.
     unsupported_type = binascii.unhexlify(
         "02233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22a"
@@ -1009,6 +1009,80 @@
                 serialization.Encoding.PEM, serialization.PublicFormat.PKCS1
             )
 
+    @pytest.mark.parametrize(
+        "vector",
+        load_vectors_from_file(
+            os.path.join("asymmetric", "EC", "compressed_points.txt"),
+            load_nist_vectors
+        )
+    )
+    def test_from_encoded_point_compressed(self, vector):
+        curve = {
+            b"SECP256R1": ec.SECP256R1(),
+            b"SECP256K1": ec.SECP256K1(),
+        }[vector["curve"]]
+        point = binascii.unhexlify(vector["point"])
+        pn = ec.EllipticCurvePublicKey.from_encoded_point(curve, point)
+        public_num = pn.public_numbers()
+        assert public_num.x == int(vector["x"], 16)
+        assert public_num.y == int(vector["y"], 16)
+
+    def test_from_encoded_point_notoncurve(self):
+        uncompressed_point = binascii.unhexlify(
+            "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac"
+            "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f"
+            "6e"
+        )
+        with pytest.raises(ValueError):
+            ec.EllipticCurvePublicKey.from_encoded_point(
+                ec.SECP256R1(), uncompressed_point
+            )
+
+    def test_from_encoded_point_uncompressed(self):
+        uncompressed_point = binascii.unhexlify(
+            "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac"
+            "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f"
+            "6d"
+        )
+        pn = ec.EllipticCurvePublicKey.from_encoded_point(
+            ec.SECP256R1(), uncompressed_point
+        )
+        assert pn.public_numbers().x == int(
+            '7399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac68',
+            16
+        )
+        assert pn.public_numbers().y == int(
+            '6699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f6d',
+            16
+        )
+
+    def test_from_encoded_point_invalid_length(self):
+        bad_data = binascii.unhexlify(
+            "047399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac"
+            "686699ececc4f5f0d756d3c450708a0694eb0a07a68b805070b40b058d27271f"
+            "6d"
+        )
+        with pytest.raises(ValueError):
+            ec.EllipticCurvePublicKey.from_encoded_point(
+                ec.SECP384R1(), bad_data
+            )
+
+    def test_from_encoded_point_not_a_curve(self):
+        with pytest.raises(TypeError):
+            ec.EllipticCurvePublicKey.from_encoded_point(
+                "notacurve", b"\x04data"
+            )
+
+    def test_from_encoded_point_unsupported_encoding(self):
+        unsupported_type = binascii.unhexlify(
+            "057399336a9edf2197c2f8eb3d39aed9c34a66e45d918a07dc7684c42c9b37ac6"
+            "8"
+        )
+        with pytest.raises(ValueError):
+            ec.EllipticCurvePublicKey.from_encoded_point(
+                ec.SECP256R1(), unsupported_type
+            )
+
 
 @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend)
 class TestECDSAVerification(object):