Skip to content

Commit 33c2f26

Browse files
committed
test signature decoding with random DER objects
fix encoding of large integers, fixes #148
1 parent d45f13f commit 33c2f26

File tree

2 files changed

+97
-3
lines changed

2 files changed

+97
-3
lines changed

src/ecdsa/der.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ def encode_integer(r):
2323
s = binascii.unhexlify(h)
2424
num = str_idx_as_int(s, 0)
2525
if num <= 0x7f:
26-
return b("\x02") + int2byte(len(s)) + s
26+
return b("\x02") + encode_length(len(s)) + s
2727
else:
2828
# DER integers are two's complement, so if the first byte is
2929
# 0x80-0xff then we need an extra 0x00 byte to prevent it from
3030
# looking negative.
31-
return b("\x02") + int2byte(len(s)+1) + b("\x00") + s
31+
return b("\x02") + encode_length(len(s)+1) + b("\x00") + s
3232

3333

3434
# sentry object to check if an argument was specified (used to detect

src/ecdsa/test_malformed_sigs.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from .util import sigencode_der, sigencode_string
1919
from .util import sigdecode_der, sigdecode_string
2020
from .curves import curves, NIST256p
21-
from .der import encode_integer, encode_sequence
21+
from .der import encode_integer, encode_bitstring, encode_octet_string, \
22+
encode_oid, encode_sequence, encode_constructed
2223

2324

2425
example_data = b"some data to sign"
@@ -109,8 +110,12 @@ def st_fuzzed_sig(draw, keys_and_sigs):
109110
params = {}
110111
# not supported in hypothesis 2.0.0
111112
if sys.version_info >= (2, 7):
113+
from hypothesis import HealthCheck
112114
# deadline=5s because NIST521p are slow to verify
113115
params["deadline"] = 5000
116+
params["suppress_health_check"] = [HealthCheck.data_too_large,
117+
HealthCheck.filter_too_much,
118+
HealthCheck.too_slow]
114119

115120

116121
@settings(**params)
@@ -164,6 +169,95 @@ def test_random_der_ecdsa_sig_value(params):
164169
verifying_key.verify(sig, example_data, sigdecode=sigdecode_der)
165170

166171

172+
def st_der_integer(*args, **kwargs):
173+
"""
174+
Hypothesis strategy that returns a random positive integer as DER
175+
INTEGER.
176+
Parameters are passed to hypothesis.strategy.integer.
177+
"""
178+
if "min_value" not in kwargs:
179+
kwargs["min_value"] = 0
180+
return st.builds(encode_integer, st.integers(*args, **kwargs))
181+
182+
183+
@st.composite
184+
def st_der_bit_string(draw, *args, **kwargs):
185+
"""
186+
Hypothesis strategy that returns a random DER BIT STRING.
187+
Parameters are passed to hypothesis.strategy.binary.
188+
"""
189+
data = draw(st.binary(*args, **kwargs))
190+
if data:
191+
unused = draw(st.integers(min_value=0, max_value=7))
192+
data = bytearray(data)
193+
data[-1] &= - (2**unused)
194+
data = bytes(data)
195+
else:
196+
unused = 0
197+
return encode_bitstring(data, unused)
198+
199+
200+
def st_der_octet_string(*args, **kwargs):
201+
"""
202+
Hypothesis strategy that returns a random DER OCTET STRING object.
203+
Parameters are passed to hypothesis.strategy.binary
204+
"""
205+
return st.builds(encode_octet_string, st.binary(*args, **kwargs))
206+
207+
208+
def st_der_null():
209+
"""
210+
Hypothesis strategy that returns DER NULL object.
211+
"""
212+
return st.just(b'\x05\x00')
213+
214+
215+
@st.composite
216+
def st_der_oid(draw):
217+
"""
218+
Hypothesis strategy that returns DER OBJECT IDENTIFIER objects.
219+
"""
220+
first = draw(st.integers(min_value=0, max_value=2))
221+
second = draw(st.integers(min_value=0, max_value=39))
222+
rest = draw(st.lists(st.integers(min_value=0, max_value=2**512),
223+
max_size=50))
224+
return encode_oid(first, second, *rest)
225+
226+
227+
def st_der():
228+
"""
229+
Hypothesis strategy that returns random DER structures.
230+
231+
A valid DER structure is any primitive object, an octet encoding
232+
of a valid DER structure, sequence of valid DER objects or a constructed
233+
encoding of any of the above.
234+
"""
235+
return st.recursive(
236+
st.just(b'') | st_der_integer(max_value=2**4096) |
237+
st_der_bit_string(max_size=1024**2) |
238+
st_der_octet_string(max_size=1024**2) | st_der_null() | st_der_oid(),
239+
lambda children:
240+
st.builds(lambda x: encode_octet_string(x), st.one_of(children)) |
241+
st.builds(lambda x: encode_bitstring(x, 0), st.one_of(children)) |
242+
st.builds(lambda x: encode_sequence(*x),
243+
st.lists(children, max_size=200)) |
244+
st.builds(lambda tag, x:
245+
encode_constructed(tag, x),
246+
st.integers(min_value=0, max_value=0x3f),
247+
st.one_of(children))
248+
)
249+
250+
251+
@settings(**params)
252+
@given(st.sampled_from(keys_and_sigs), st_der())
253+
def test_random_der_as_signature(params, der):
254+
"""Check if random DER structures are rejected as signature"""
255+
name, verifying_key, _ = params
256+
257+
with pytest.raises(BadSignatureError):
258+
verifying_key.verify(der, example_data, sigdecode=sigdecode_der)
259+
260+
167261
keys_and_string_sigs = [
168262
(name, verifying_key,
169263
sigencode_string(*sigdecode_der(sig, verifying_key.curve.order),

0 commit comments

Comments
 (0)