diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-10-25 15:44:29 -0500 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2015-10-26 08:27:22 -0500 |
commit | 467072f7d50778f064f192b4e318c19c6cf98293 (patch) | |
tree | 9ef70c8cf76f86795f05fc00d22b9db785b9e659 | |
parent | 9bbf778b7dde2fab6d957f3b5b4422d5bb3ce5a0 (diff) | |
download | cryptography-467072f7d50778f064f192b4e318c19c6cf98293.tar.gz cryptography-467072f7d50778f064f192b4e318c19c6cf98293.tar.bz2 cryptography-467072f7d50778f064f192b4e318c19c6cf98293.zip |
add support for encoding/decoding elliptic curve points
Based on the work of @ronf in #2346.
-rw-r--r-- | docs/hazmat/primitives/asymmetric/utils.rst | 41 | ||||
-rw-r--r-- | src/cryptography/hazmat/primitives/asymmetric/utils.py | 34 | ||||
-rw-r--r-- | src/cryptography/utils.py | 7 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_asym_utils.py | 73 |
4 files changed, 151 insertions, 4 deletions
diff --git a/docs/hazmat/primitives/asymmetric/utils.rst b/docs/hazmat/primitives/asymmetric/utils.rst index 07883598..825fe3c1 100644 --- a/docs/hazmat/primitives/asymmetric/utils.rst +++ b/docs/hazmat/primitives/asymmetric/utils.rst @@ -28,3 +28,44 @@ Asymmetric Utilities :param int s: The raw signature value ``s``. :return bytes: The encoded signature. + +.. function:: encode_ec_point(curve, x, y) + + .. versionadded:: 1.1 + + Encodes an elliptic curve point to a byte string as described in + _`SEC 1 v2.0` section 2.3.3. This function only supports uncompressed + points. + + :param curve: A :class:`EllipticCurve` provider. + + :param x: The x value of the point. + + :type: int or None + + :param int y: The y value of the point. + + :return bytes: The encoded point. + + :raises TypeError: Raised when curve is not an :class:`EllipticCurve`. + +.. function:: decode_ec_point(key_length, data) + + .. versionadded:: 1.1 + + Decodes a byte string as described in _`SEC 1 v2.0` section 2.3.3 to the + ``x`` and ``y`` integer values. This function only supports uncompressed + points. + + :param curve: A :class:`EllipticCurve` provider. + + :param bytes data: The serialized point byte string. + + :returns: The decoded tuple ``(x, y)``. + + :raises ValueError: Raised on invalid point type or data length. + + :raises TypeError: Raised when curve is not an :class:`EllipticCurve`. + + +.. _`SEC 1 v2.0`: http://www.secg.org/sec1-v2.pdf diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py index bad9ab73..b62eadf0 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/utils.py +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -13,6 +13,7 @@ from pyasn1.type import namedtype, univ import six from cryptography import utils +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve class _DSSSigValue(univ.Sequence): @@ -71,3 +72,36 @@ def encode_dss_signature(r, s): sig.setComponentByName('r', r) sig.setComponentByName('s', s) return encoder.encode(sig) + + +def encode_ec_point(curve, x, y): + if not isinstance(curve, EllipticCurve): + raise TypeError("curve must be an EllipticCurve instance") + + if x is None: + return b'\x00' + else: + # Get the ceiling of curve.key_size / 8 + byte_length = (curve.key_size + 7) // 8 + return ( + b'\x04' + utils.int_to_bytes(x, byte_length) + + utils.int_to_bytes(y, byte_length) + ) + + +def decode_ec_point(curve, data): + if not isinstance(curve, EllipticCurve): + raise TypeError("curve must be an EllipticCurve instance") + + if data == b'\x00': + return None, None + elif data.startswith(b'\x04'): + # Get the ceiling of curve.key_size / 8 + byte_length = (curve.key_size + 7) // 8 + if len(data) == 2 * byte_length + 1: + return (utils.int_from_bytes(data[1:byte_length + 1], 'big'), + utils.int_from_bytes(data[byte_length + 1:], 'big')) + else: + raise ValueError('Invalid elliptic curve point data length') + else: + raise ValueError('Unsupported elliptic curve point type') diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py index dac4046d..dbd961f7 100644 --- a/src/cryptography/utils.py +++ b/src/cryptography/utils.py @@ -48,9 +48,12 @@ else: return result -def int_to_bytes(integer): +def int_to_bytes(integer, length=None): hex_string = '%x' % integer - n = len(hex_string) + if length is None: + n = len(hex_string) + else: + n = length * 2 return binascii.unhexlify(hex_string.zfill(n + (n & 1))) diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py index b9971137..c713e9c6 100644 --- a/tests/hazmat/primitives/test_asym_utils.py +++ b/tests/hazmat/primitives/test_asym_utils.py @@ -4,11 +4,14 @@ from __future__ import absolute_import, division, print_function +import binascii + import pytest +from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( - decode_dss_signature, decode_rfc6979_signature, - encode_dss_signature, encode_rfc6979_signature + decode_dss_signature, decode_ec_point, decode_rfc6979_signature, + encode_dss_signature, encode_ec_point, encode_rfc6979_signature ) @@ -76,3 +79,69 @@ def test_decode_dss_invalid_asn1(): # This is the BER "end-of-contents octets," which older versions of # pyasn1 are wrongly willing to return from top-level DER decoding. decode_dss_signature(b"\x00\x00") + + +def test_encode_ec_point_none(): + assert encode_ec_point(ec.SECP384R1(), None, 100) == b"\x00" + + +def test_encode_wrong_curve_type(): + with pytest.raises(TypeError): + encode_ec_point("notacurve", 3, 4) + + +def test_encode_ec_point(): + # secp256r1 point + x = int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', 16 + ) + y = int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', 16 + ) + data = encode_ec_point(ec.SECP256R1(), x, y) + assert data == binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec3ea" + "2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + + +def test_decode_ec_point_none(): + assert decode_ec_point(ec.SECP384R1(), b"\x00") == (None, None) + + +def test_decode_ec_point(): + # secp256r1 point + data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec3ea" + "2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + x, y = decode_ec_point(ec.SECP256R1(), data) + assert x == int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', 16 + ) + assert y == int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', 16 + ) + + +def test_decode_ec_point_invalid_length(): + bad_data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec3ea" + "2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460" + ) + with pytest.raises(ValueError): + decode_ec_point(ec.SECP384R1(), bad_data) + + +def test_decode_ec_point_unsupported_point_type(): + # set to point type 2. + unsupported_type = binascii.unhexlify( + "02233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec3e" + ) + with pytest.raises(ValueError): + decode_ec_point(ec.SECP256R1(), unsupported_type) + + +def test_decode_wrong_curve_type(): + with pytest.raises(TypeError): + decode_ec_point("notacurve", b"\x02data") |