aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2015-10-25 15:44:29 -0500
committerPaul Kehrer <paul.l.kehrer@gmail.com>2015-10-26 08:27:22 -0500
commit467072f7d50778f064f192b4e318c19c6cf98293 (patch)
tree9ef70c8cf76f86795f05fc00d22b9db785b9e659
parent9bbf778b7dde2fab6d957f3b5b4422d5bb3ce5a0 (diff)
downloadcryptography-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.rst41
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/utils.py34
-rw-r--r--src/cryptography/utils.py7
-rw-r--r--tests/hazmat/primitives/test_asym_utils.py73
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")