diff options
-rw-r--r-- | CHANGELOG.rst | 4 | ||||
-rw-r--r-- | docs/hazmat/primitives/asymmetric/ec.rst | 21 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/backend.py | 20 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/asymmetric/ec.py | 12 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/serialization/ssh.py | 3 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_ec.py | 76 |
6 files changed, 133 insertions, 3 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 54004b48..7780c6ba 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 5936cf44..728c5159 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 99f6ccf6..cfe146f2 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 1d709d33..6b1de7c5 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 f58ff074..cb838927 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 6d493661..9a8ddf60 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): |