Skip to content

Commit 634e5b0

Browse files
authored
Merge pull request #252 from tomato42/explicit-curve-params
Support explicit curve params
2 parents a385c44 + 1ea4aef commit 634e5b0

9 files changed

+1049
-184
lines changed

build-requirements-3.4.txt

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ hypothesis
44
pytest>=4.6.0
55
PyYAML<5.3
66
coverage
7+
attrs<21

src/ecdsa/curves.py

+203-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from __future__ import division
22

3-
from . import der, ecdsa
4-
from .util import orderlen
3+
from six import PY2
4+
from . import der, ecdsa, ellipticcurve
5+
from .util import orderlen, number_to_string, string_to_number
6+
from ._compat import normalise_bytes
57

68

79
# orderlen was defined in this module previously, so keep it in __all__,
@@ -29,9 +31,15 @@
2931
"BRAINPOOLP320r1",
3032
"BRAINPOOLP384r1",
3133
"BRAINPOOLP512r1",
34+
"PRIME_FIELD_OID",
35+
"CHARACTERISTIC_TWO_FIELD_OID",
3236
]
3337

3438

39+
PRIME_FIELD_OID = (1, 2, 840, 10045, 1, 1)
40+
CHARACTERISTIC_TWO_FIELD_OID = (1, 2, 840, 10045, 1, 2)
41+
42+
3543
class UnknownCurveError(Exception):
3644
pass
3745

@@ -47,11 +55,203 @@ def __init__(self, name, curve, generator, oid, openssl_name=None):
4755
self.verifying_key_length = 2 * orderlen(curve.p())
4856
self.signature_length = 2 * self.baselen
4957
self.oid = oid
50-
self.encoded_oid = der.encode_oid(*oid)
58+
if oid:
59+
self.encoded_oid = der.encode_oid(*oid)
60+
61+
def __eq__(self, other):
62+
if isinstance(other, Curve):
63+
return (
64+
self.curve == other.curve and self.generator == other.generator
65+
)
66+
return NotImplemented
67+
68+
def __ne__(self, other):
69+
return not self == other
5170

5271
def __repr__(self):
5372
return self.name
5473

74+
def to_der(self, encoding=None, point_encoding="uncompressed"):
75+
"""Serialise the curve parameters to binary string.
76+
77+
:param str encoding: the format to save the curve parameters in.
78+
Default is ``named_curve``, with fallback being the ``explicit``
79+
if the OID is not set for the curve.
80+
:param str point_encoding: the point encoding of the generator when
81+
explicit curve encoding is used. Ignored for ``named_curve``
82+
format.
83+
84+
:return: DER encoded ECParameters structure
85+
:rtype: bytes
86+
"""
87+
if encoding is None:
88+
if self.oid:
89+
encoding = "named_curve"
90+
else:
91+
encoding = "explicit"
92+
93+
if encoding == "named_curve":
94+
if not self.oid:
95+
raise UnknownCurveError(
96+
"Can't encode curve using named_curve encoding without "
97+
"associated curve OID"
98+
)
99+
return der.encode_oid(*self.oid)
100+
101+
# encode the ECParameters sequence
102+
curve_p = self.curve.p()
103+
version = der.encode_integer(1)
104+
field_id = der.encode_sequence(
105+
der.encode_oid(*PRIME_FIELD_OID), der.encode_integer(curve_p)
106+
)
107+
curve = der.encode_sequence(
108+
der.encode_octet_string(
109+
number_to_string(self.curve.a() % curve_p, curve_p)
110+
),
111+
der.encode_octet_string(
112+
number_to_string(self.curve.b() % curve_p, curve_p)
113+
),
114+
)
115+
base = der.encode_octet_string(self.generator.to_bytes(point_encoding))
116+
order = der.encode_integer(self.generator.order())
117+
seq_elements = [version, field_id, curve, base, order]
118+
if self.curve.cofactor():
119+
cofactor = der.encode_integer(self.curve.cofactor())
120+
seq_elements.append(cofactor)
121+
122+
return der.encode_sequence(*seq_elements)
123+
124+
def to_pem(self, encoding=None, point_encoding="uncompressed"):
125+
"""
126+
Serialise the curve parameters to the :term:`PEM` format.
127+
128+
:param str encoding: the format to save the curve parameters in.
129+
Default is ``named_curve``, with fallback being the ``explicit``
130+
if the OID is not set for the curve.
131+
:param str point_encoding: the point encoding of the generator when
132+
explicit curve encoding is used. Ignored for ``named_curve``
133+
format.
134+
135+
:return: PEM encoded ECParameters structure
136+
:rtype: str
137+
"""
138+
return der.topem(
139+
self.to_der(encoding, point_encoding), "EC PARAMETERS"
140+
)
141+
142+
@staticmethod
143+
def from_der(data, valid_encodings=None):
144+
"""Decode the curve parameters from DER file.
145+
146+
:param data: the binary string to decode the parameters from
147+
:type data: :term:`bytes-like object`
148+
:param valid_encodings: set of names of allowed encodings, by default
149+
all (set by passing ``None``), supported ones are ``named_curve``
150+
and ``explicit``
151+
:type valid_encodings: :term:`set-like object`
152+
"""
153+
if not valid_encodings:
154+
valid_encodings = set(("named_curve", "explicit"))
155+
if not all(i in ["named_curve", "explicit"] for i in valid_encodings):
156+
raise ValueError(
157+
"Only named_curve and explicit encodings supported"
158+
)
159+
data = normalise_bytes(data)
160+
if not der.is_sequence(data):
161+
if "named_curve" not in valid_encodings:
162+
raise der.UnexpectedDER(
163+
"named_curve curve parameters not allowed"
164+
)
165+
oid, empty = der.remove_object(data)
166+
if empty:
167+
raise der.UnexpectedDER("Unexpected data after OID")
168+
return find_curve(oid)
169+
170+
if "explicit" not in valid_encodings:
171+
raise der.UnexpectedDER("explicit curve parameters not allowed")
172+
173+
seq, empty = der.remove_sequence(data)
174+
if empty:
175+
raise der.UnexpectedDER(
176+
"Unexpected data after ECParameters structure"
177+
)
178+
# decode the ECParameters sequence
179+
version, rest = der.remove_integer(seq)
180+
if version != 1:
181+
raise der.UnexpectedDER("Unknown parameter encoding format")
182+
field_id, rest = der.remove_sequence(rest)
183+
curve, rest = der.remove_sequence(rest)
184+
base_bytes, rest = der.remove_octet_string(rest)
185+
order, rest = der.remove_integer(rest)
186+
cofactor = None
187+
if rest:
188+
# the ASN.1 specification of ECParameters allows for future
189+
# extensions of the sequence, so ignore the remaining bytes
190+
cofactor, _ = der.remove_integer(rest)
191+
192+
# decode the ECParameters.fieldID sequence
193+
field_type, rest = der.remove_object(field_id)
194+
if field_type == CHARACTERISTIC_TWO_FIELD_OID:
195+
raise UnknownCurveError("Characteristic 2 curves unsupported")
196+
if field_type != PRIME_FIELD_OID:
197+
raise UnknownCurveError(
198+
"Unknown field type: {0}".format(field_type)
199+
)
200+
prime, empty = der.remove_integer(rest)
201+
if empty:
202+
raise der.UnexpectedDER(
203+
"Unexpected data after ECParameters.fieldID.Prime-p element"
204+
)
205+
206+
# decode the ECParameters.curve sequence
207+
curve_a_bytes, rest = der.remove_octet_string(curve)
208+
curve_b_bytes, rest = der.remove_octet_string(rest)
209+
# seed can be defined here, but we don't parse it, so ignore `rest`
210+
211+
curve_a = string_to_number(curve_a_bytes)
212+
curve_b = string_to_number(curve_b_bytes)
213+
214+
curve_fp = ellipticcurve.CurveFp(prime, curve_a, curve_b, cofactor)
215+
216+
# decode the ECParameters.base point
217+
218+
base = ellipticcurve.PointJacobi.from_bytes(
219+
curve_fp,
220+
base_bytes,
221+
valid_encodings=("uncompressed", "compressed", "hybrid"),
222+
order=order,
223+
generator=True,
224+
)
225+
tmp_curve = Curve("unknown", curve_fp, base, None)
226+
227+
# if the curve matches one of the well-known ones, use the well-known
228+
# one in preference, as it will have the OID and name associated
229+
for i in curves:
230+
if tmp_curve == i:
231+
return i
232+
return tmp_curve
233+
234+
@classmethod
235+
def from_pem(cls, string, valid_encodings=None):
236+
"""Decode the curve parameters from PEM file.
237+
238+
:param str string: the text string to decode the parameters from
239+
:param valid_encodings: set of names of allowed encodings, by default
240+
all (set by passing ``None``), supported ones are ``named_curve``
241+
and ``explicit``
242+
:type valid_encodings: :term:`set-like object`
243+
"""
244+
if not PY2 and isinstance(string, str): # pragma: no branch
245+
string = string.encode()
246+
247+
ec_param_index = string.find(b"-----BEGIN EC PARAMETERS-----")
248+
if ec_param_index == -1:
249+
raise der.UnexpectedDER("EC PARAMETERS PEM header not found")
250+
251+
return cls.from_der(
252+
der.unpem(string[ec_param_index:]), valid_encodings
253+
)
254+
55255

56256
# the SEC curves
57257
SECP112r1 = Curve(

0 commit comments

Comments
 (0)