summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst4
-rw-r--r--docs/hazmat/primitives/asymmetric/ec.rst21
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py20
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/ec.py12
-rw-r--r--src/cryptography/hazmat/primitives/serialization/ssh.py3
-rw-r--r--tests/hazmat/primitives/test_ec.py76
6 files changed, 133 insertions, 3 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 54004b488..7780c6ba5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -28,6 +28,10 @@ Changelog
: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 5936cf443..728c5159a 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -704,6 +704,27 @@ Key Interfaces
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 99f6ccf69..cfe146f26 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -1383,6 +1383,26 @@ class Backend(object):
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 1d709d338..6b1de7c5b 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/ec.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py
@@ -151,6 +151,18 @@ class EllipticCurvePublicKey(object):
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 f58ff0743..cb838927d 100644
--- a/src/cryptography/hazmat/primitives/serialization/ssh.py
+++ b/src/cryptography/hazmat/primitives/serialization/ssh.py
@@ -99,8 +99,7 @@ def _load_ssh_ecdsa_public_key(expected_key_type, decoded_data, backend):
"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 6d4936619..9a8ddf600 100644
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -212,7 +212,7 @@ def test_from_encoded_point_invalid_length():
)
-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 @@ class TestEllipticCurvePEMPublicKeySerialization(object):
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):