Skip to content

Commit 99c907d

Browse files
committed
harden also key decoding
as assert statements will be removed in optimised build, do use a custom exception that inherits from AssertionError so that the failures are caught this is reworking of d47a238 to implement the same checks but without implementing support for SEC1/X9.62 formatted keys
1 parent 3427fa2 commit 99c907d

File tree

3 files changed

+207
-14
lines changed

3 files changed

+207
-14
lines changed

ecdsa/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
__all__ = ["curves", "der", "ecdsa", "ellipticcurve", "keys", "numbertheory",
22
"test_pyecdsa", "util", "six"]
3-
from .keys import SigningKey, VerifyingKey, BadSignatureError, BadDigestError
3+
from .keys import SigningKey, VerifyingKey, BadSignatureError, BadDigestError,\
4+
MalformedPointError
45
from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1
6+
from .der import UnexpectedDER
57

68
_hush_pyflakes = [SigningKey, VerifyingKey, BadSignatureError, BadDigestError,
9+
MalformedPointError, UnexpectedDER,
710
NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1]
811
del _hush_pyflakes
912

ecdsa/keys.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from . import ecdsa
44
from . import der
55
from . import rfc6979
6+
from . import ellipticcurve
67
from .curves import NIST192p, find_curve
78
from .util import string_to_number, number_to_string, randrange
89
from .util import sigencode_string, sigdecode_string
@@ -15,6 +16,11 @@ class BadSignatureError(Exception):
1516
class BadDigestError(Exception):
1617
pass
1718

19+
20+
class MalformedPointError(AssertionError):
21+
pass
22+
23+
1824
class VerifyingKey:
1925
def __init__(self, _error__please_use_generate=None):
2026
if not _error__please_use_generate:
@@ -33,17 +39,21 @@ def from_public_point(klass, point, curve=NIST192p, hashfunc=sha1):
3339
def from_string(klass, string, curve=NIST192p, hashfunc=sha1,
3440
validate_point=True):
3541
order = curve.order
36-
assert len(string) == curve.verifying_key_length, \
37-
(len(string), curve.verifying_key_length)
42+
if len(string) != curve.verifying_key_length:
43+
raise MalformedPointError(
44+
"Malformed encoding of public point. Expected string {0} bytes"
45+
" long, received {1} bytes long string".format(
46+
curve.verifying_key_length, len(string)))
3847
xs = string[:curve.baselen]
3948
ys = string[curve.baselen:]
40-
assert len(xs) == curve.baselen, (len(xs), curve.baselen)
41-
assert len(ys) == curve.baselen, (len(ys), curve.baselen)
49+
if len(xs) != curve.baselen:
50+
raise MalformedPointError("Unexpected length of encoded x")
51+
if len(ys) != curve.baselen:
52+
raise MalformedPointError("Unexpected length of encoded y")
4253
x = string_to_number(xs)
4354
y = string_to_number(ys)
44-
if validate_point:
45-
assert ecdsa.point_is_valid(curve.generator, x, y)
46-
from . import ellipticcurve
55+
if validate_point and not ecdsa.point_is_valid(curve.generator, x, y):
56+
raise MalformedPointError("Point does not lie on the curve")
4757
point = ellipticcurve.Point(curve.curve, x, y, order)
4858
return klass.from_public_point(point, curve, hashfunc)
4959

@@ -65,13 +75,18 @@ def from_der(klass, string):
6575
if empty != b(""):
6676
raise der.UnexpectedDER("trailing junk after DER pubkey objects: %s" %
6777
binascii.hexlify(empty))
68-
assert oid_pk == oid_ecPublicKey, (oid_pk, oid_ecPublicKey)
78+
if oid_pk != oid_ecPublicKey:
79+
raise der.UnexpectedDER(
80+
"Unexpected OID in encoding, received {0}, expected {1}"
81+
.format(oid_pk, oid_ecPublicKey))
6982
curve = find_curve(oid_curve)
7083
point_str, empty = der.remove_bitstring(point_str_bitstring)
7184
if empty != b(""):
7285
raise der.UnexpectedDER("trailing junk after pubkey pointstring: %s" %
7386
binascii.hexlify(empty))
74-
assert point_str.startswith(b("\x00\x04"))
87+
if not point_str.startswith(b("\x00\x04")):
88+
raise der.UnexpectedDER(
89+
"Unsupported or invalid encoding of pubcli key")
7590
return klass.from_string(point_str[2:], curve)
7691

7792
def to_string(self):
@@ -137,7 +152,10 @@ def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1):
137152
self.default_hashfunc = hashfunc
138153
self.baselen = curve.baselen
139154
n = curve.order
140-
assert 1 <= secexp < n
155+
if not 1 <= secexp < n:
156+
raise MalformedPointError(
157+
"Invalid value for secexp, expected integer between 1 and {0}"
158+
.format(n))
141159
pubkey_point = curve.generator*secexp
142160
pubkey = ecdsa.Public_key(curve.generator, pubkey_point)
143161
pubkey.order = n
@@ -149,7 +167,10 @@ def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1):
149167

150168
@classmethod
151169
def from_string(klass, string, curve=NIST192p, hashfunc=sha1):
152-
assert len(string) == curve.baselen, (len(string), curve.baselen)
170+
if len(string) != curve.baselen:
171+
raise MalformedPointError(
172+
"Invalid length of private key, received {0}, expected {1}"
173+
.format(len(string), curve.baselen))
153174
secexp = string_to_number(string)
154175
return klass.from_secret_exponent(secexp, curve, hashfunc)
155176

ecdsa/test_pyecdsa.py

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from __future__ import with_statement, division
22

3-
import unittest
3+
try:
4+
import unittest2 as unittest
5+
except ImportError:
6+
import unittest
47
import os
58
import time
69
import shutil
@@ -10,15 +13,17 @@
1013

1114
from .six import b, print_, binary_type
1215
from .keys import SigningKey, VerifyingKey
13-
from .keys import BadSignatureError
16+
from .keys import BadSignatureError, MalformedPointError, BadDigestError
1417
from . import util
1518
from .util import sigencode_der, sigencode_strings
1619
from .util import sigdecode_der, sigdecode_strings
20+
from .util import encoded_oid_ecPublicKey, MalformedSignature
1721
from .curves import Curve, UnknownCurveError
1822
from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1
1923
from .ellipticcurve import Point
2024
from . import der
2125
from . import rfc6979
26+
from . import ecdsa
2227

2328
class SubprocessError(Exception):
2429
pass
@@ -258,6 +263,47 @@ def order(self): return 123456789
258263
pub2 = VerifyingKey.from_pem(pem)
259264
self.assertTruePubkeysEqual(pub1, pub2)
260265

266+
def test_vk_from_der_garbage_after_curve_oid(self):
267+
type_oid_der = encoded_oid_ecPublicKey
268+
curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1)) + \
269+
b('garbage')
270+
enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der)
271+
point_der = der.encode_bitstring(b'\x00\xff')
272+
to_decode = der.encode_sequence(enc_type_der, point_der)
273+
274+
with self.assertRaises(der.UnexpectedDER):
275+
VerifyingKey.from_der(to_decode)
276+
277+
def test_vk_from_der_invalid_key_type(self):
278+
type_oid_der = der.encode_oid(*(1, 2, 3))
279+
curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1))
280+
enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der)
281+
point_der = der.encode_bitstring(b'\x00\xff')
282+
to_decode = der.encode_sequence(enc_type_der, point_der)
283+
284+
with self.assertRaises(der.UnexpectedDER):
285+
VerifyingKey.from_der(to_decode)
286+
287+
def test_vk_from_der_garbage_after_point_string(self):
288+
type_oid_der = encoded_oid_ecPublicKey
289+
curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1))
290+
enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der)
291+
point_der = der.encode_bitstring(b'\x00\xff') + b('garbage')
292+
to_decode = der.encode_sequence(enc_type_der, point_der)
293+
294+
with self.assertRaises(der.UnexpectedDER):
295+
VerifyingKey.from_der(to_decode)
296+
297+
def test_vk_from_der_invalid_bitstring(self):
298+
type_oid_der = encoded_oid_ecPublicKey
299+
curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1))
300+
enc_type_der = der.encode_sequence(type_oid_der, curve_oid_der)
301+
point_der = der.encode_bitstring(b'\x08\xff')
302+
to_decode = der.encode_sequence(enc_type_der, point_der)
303+
304+
with self.assertRaises(der.UnexpectedDER):
305+
VerifyingKey.from_der(to_decode)
306+
261307
def test_signature_strings(self):
262308
priv1 = SigningKey.generate()
263309
pub1 = priv1.get_verifying_key()
@@ -281,6 +327,86 @@ def test_signature_strings(self):
281327
self.assertEqual(type(sig_der), binary_type)
282328
self.assertTrue(pub1.verify(sig_der, data, sigdecode=sigdecode_der))
283329

330+
def test_sig_decode_strings_with_invalid_count(self):
331+
with self.assertRaises(MalformedSignature):
332+
sigdecode_strings([b('one'), b('two'), b('three')], 0xff)
333+
334+
def test_sig_decode_strings_with_wrong_r_len(self):
335+
with self.assertRaises(MalformedSignature):
336+
sigdecode_strings([b('one'), b('two')], 0xff)
337+
338+
def test_sig_decode_strings_with_wrong_s_len(self):
339+
with self.assertRaises(MalformedSignature):
340+
sigdecode_strings([b('\xa0'), b('\xb0\xff')], 0xff)
341+
342+
def test_verify_with_too_long_input(self):
343+
sk = SigningKey.generate()
344+
vk = sk.verifying_key
345+
346+
with self.assertRaises(BadDigestError):
347+
vk.verify_digest(None, b('\x00') * 128)
348+
349+
def test_sk_from_secret_exponent_with_wrong_sec_exponent(self):
350+
with self.assertRaises(MalformedPointError):
351+
SigningKey.from_secret_exponent(0)
352+
353+
def test_sk_from_string_with_wrong_len_string(self):
354+
with self.assertRaises(MalformedPointError):
355+
SigningKey.from_string(b('\x01'))
356+
357+
def test_sk_from_der_with_junk_after_sequence(self):
358+
ver_der = der.encode_integer(1)
359+
to_decode = der.encode_sequence(ver_der) + b('garbage')
360+
361+
with self.assertRaises(der.UnexpectedDER):
362+
SigningKey.from_der(to_decode)
363+
364+
def test_sk_from_der_with_wrong_version(self):
365+
ver_der = der.encode_integer(0)
366+
to_decode = der.encode_sequence(ver_der)
367+
368+
with self.assertRaises(der.UnexpectedDER):
369+
SigningKey.from_der(to_decode)
370+
371+
def test_sk_from_der_invalid_const_tag(self):
372+
ver_der = der.encode_integer(1)
373+
privkey_der = der.encode_octet_string(b('\x00\xff'))
374+
curve_oid_der = der.encode_oid(*(1, 2, 3))
375+
const_der = der.encode_constructed(1, curve_oid_der)
376+
to_decode = der.encode_sequence(ver_der, privkey_der, const_der,
377+
curve_oid_der)
378+
379+
with self.assertRaises(der.UnexpectedDER):
380+
SigningKey.from_der(to_decode)
381+
382+
def test_sk_from_der_garbage_after_privkey_oid(self):
383+
ver_der = der.encode_integer(1)
384+
privkey_der = der.encode_octet_string(b('\x00\xff'))
385+
curve_oid_der = der.encode_oid(*(1, 2, 3)) + b('garbage')
386+
const_der = der.encode_constructed(0, curve_oid_der)
387+
to_decode = der.encode_sequence(ver_der, privkey_der, const_der,
388+
curve_oid_der)
389+
390+
with self.assertRaises(der.UnexpectedDER):
391+
SigningKey.from_der(to_decode)
392+
393+
def test_sk_from_der_with_short_privkey(self):
394+
ver_der = der.encode_integer(1)
395+
privkey_der = der.encode_octet_string(b('\x00\xff'))
396+
curve_oid_der = der.encode_oid(*(1, 2, 840, 10045, 3, 1, 1))
397+
const_der = der.encode_constructed(0, curve_oid_der)
398+
to_decode = der.encode_sequence(ver_der, privkey_der, const_der,
399+
curve_oid_der)
400+
401+
sk = SigningKey.from_der(to_decode)
402+
self.assertEqual(sk.privkey.secret_multiplier, 255)
403+
404+
def test_sign_with_too_long_hash(self):
405+
sk = SigningKey.from_secret_exponent(12)
406+
407+
with self.assertRaises(BadDigestError):
408+
sk.sign_digest(b('\xff') * 64)
409+
284410
def test_hashfunc(self):
285411
sk = SigningKey.generate(curve=NIST256p, hashfunc=sha256)
286412
data = b("security level is 128 bits")
@@ -299,6 +425,49 @@ def test_hashfunc(self):
299425
curve=NIST256p)
300426
self.assertTrue(vk3.verify(sig, data, hashfunc=sha256))
301427

428+
def test_decoding_with_malformed_uncompressed(self):
429+
enc = b('\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3'
430+
'\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4'
431+
'z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*')
432+
433+
with self.assertRaises(MalformedPointError):
434+
VerifyingKey.from_string(b('\x02') + enc)
435+
436+
def test_decoding_with_point_not_on_curve(self):
437+
enc = b('\x0c\xe0\x1d\xe0d\x1c\x8eS\x8a\xc0\x9eK\xa8x !\xd5\xc2\xc3'
438+
'\xfd\xc8\xa0c\xff\xfb\x02\xb9\xc4\x84)\x1a\x0f\x8b\x87\xa4'
439+
'z\x8a#\xb5\x97\xecO\xb6\xa0HQ\x89*')
440+
441+
with self.assertRaises(MalformedPointError):
442+
VerifyingKey.from_string(enc[:47] + b('\x00'))
443+
444+
def test_decoding_with_point_at_infinity(self):
445+
# decoding it is unsupported, as it's not necessary to encode it
446+
with self.assertRaises(MalformedPointError):
447+
VerifyingKey.from_string(b('\x00'))
448+
449+
def test_from_string_with_invalid_curve_too_short_ver_key_len(self):
450+
# both verifying_key_length and baselen are calculated internally
451+
# by the Curve constructor, but since we depend on them verify
452+
# that inconsistent values are detected
453+
curve = Curve("test", ecdsa.curve_192, ecdsa.generator_192, (1, 2))
454+
curve.verifying_key_length = 16
455+
curve.baselen = 32
456+
457+
with self.assertRaises(MalformedPointError):
458+
VerifyingKey.from_string(b('\x00')*16, curve)
459+
460+
def test_from_string_with_invalid_curve_too_long_ver_key_len(self):
461+
# both verifying_key_length and baselen are calculated internally
462+
# by the Curve constructor, but since we depend on them verify
463+
# that inconsistent values are detected
464+
curve = Curve("test", ecdsa.curve_192, ecdsa.generator_192, (1, 2))
465+
curve.verifying_key_length = 16
466+
curve.baselen = 16
467+
468+
with self.assertRaises(MalformedPointError):
469+
VerifyingKey.from_string(b('\x00')*16, curve)
470+
302471

303472
class OpenSSL(unittest.TestCase):
304473
# test interoperability with OpenSSL tools. Note that openssl's ECDSA

0 commit comments

Comments
 (0)