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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 203 additions & 3 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy