8000 Merge pull request #124 from tomato42/backport-sig-decode · tlsfuzzer/python-ecdsa@5c4c74a · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c4c74a

Browse files
authored
Merge pull request #124 from tomato42/backport-sig-decode
Backport signature decode
2 parents bb359d3 + 1eb2c04 commit 5c4c74a

15 files changed

+490
-29
lines changed

.coveragerc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ omit =
77
ecdsa/six.py
88
ecdsa/_version.py
99
ecdsa/test_ecdsa.py
10+
ecdsa/test_der.py
11+
ecdsa/test_malformed_sigs.py

.travis.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1+
# workaround for 3.7 not available in default configuration
2+
# travis-ci/travis-ci#9815
3+
dist: trusty
4+
sudo: false
15
language: python
6+
cache: pip
7+
before_cache:
8+
- rm -f $HOME/.cache/pip/log/debug.log
29
python:
310
- "2.6"
411
- "2.7"
@@ -11,6 +18,8 @@ install:
1118
- if [[ -e build-requirements-${TRAVIS_PYTHON_VERSION}.txt ]]; then travis_retry pip install -r build-requirements-${TRAVIS_PYTHON_VERSION}.txt; else travis_retry pip install -r build-requirements.txt; fi
1219
script:
1320
- coverage run setup.py test
21+
- if [[ $TRAVIS_PYTHON_VERSION != 3.2 && ! $TRAVIS_PYTHON_VERSION =~ py ]]; then tox -e py${TRAVIS_PYTHON_VERSION/./}; fi
22+
- if [[ $TRAVIS_PYTHON_VERSION =~ py ]]; then tox -e $TRAVIS_PYTHON_VERSION; fi
1423
- python setup.py speed
1524
after_success:
1625
- coveralls

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ There are four test suites, three for the original Pearson module, and one
7373
more for the wrapper. To run them all, do this:
7474

7575
python setup.py test
76+
tox -e coverage
7677

7778
On my 2014 Mac Mini, the combined tests take about 20 seconds to run. On a
7879
2.4GHz P4 Linux box, they take 81 seconds.
@@ -118,7 +119,8 @@ is to call `s=sk.to_string()`, and then re-create it with
118119
`SigningKey.from_string(s, curve)` . This short form does not record the
119120
curve, so you must be sure to tell from_string() the same curve you used for
120121
the original key. The short form of a NIST192p-based signing key is just 24
121-
bytes long.
122+
bytes long. If the point encoding is invalid or it does not lie on the
123+
specified curve, `from_string()` will raise MalformedPointError.
122124

123125
from ecdsa import SigningKey, NIST384p
124126
sk = SigningKey.generate(curve=NIST384p)
@@ -132,7 +134,8 @@ formats that OpenSSL uses. The PEM file looks like the familiar ASCII-armored
132134
is a shorter binary form of the same data.
133135
`SigningKey.from_pem()/.from_der()` will undo this serialization. These
134136
formats include the curve name, so you do not need to pass in a curve
135-
identifier to the deserializer.
137+
identifier to the deserializer. In case the file is malformed `from_der()`
138+
and `from_pem()` will raise UnexpectedDER or MalformedPointError.
136139

137140
from ecdsa import SigningKey, NIST384p
138141
sk = SigningKey.generate(curve=NIST384p)

build-requirements-2.6.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
tox
22
coveralls<1.3.0
33
idna<2.8
4+
unittest2

build-requirements-3.2.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ PyYAML<3.13
77
tox<3.0
88
wheel<0.30
99
virtualenv==15.2.0
10+
pytest>2.7.3

build-requirements-3.3.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ python-coveralls
22
tox<3.0
33
wheel<0.30
44
virtualenv==15.2.0
5+
pluggy<0.6

build-requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
python-coveralls
2+
pytest>3.0.7
3+
pytest-cov<2.7.0
4+
tox

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/der.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,15 @@ def remove_constructed(string):
6060
return tag, body, rest
6161

6262
def remove_sequence(string):
63+
if not string:
64+
raise UnexpectedDER("Empty string does not encode a sequence")
6365
if not string.startswith(b("\x30")):
64-
n = string[0] if isinstance(string[0], integer_types) else ord(string[0])
65-
raise UnexpectedDER("wanted sequence (0x30), got 0x%02x" % n)
66+
n = string[0] if isinstance(string[0], integer_types) else \
67+
ord(string[0])
68+
raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n)
6669
length, lengthlength = read_length(string[1:])
70+
if length > len(string) - 1 - lengthlength:
71+
raise UnexpectedDER("Length longer than the provided buffer")
6772
endseq = 1+lengthlength+length
6873
return string[1+lengthlength:endseq], string[endseq:]
6974

@@ -96,14 +101,33 @@ def remove_object(string):
96101
return tuple(numbers), rest
97102

98103
def remove_integer(string):
104+
if not string:
105+
raise UnexpectedDER("Empty string is an invalid encoding of an "
106+
"integer")
99107
if not string.startswith(b("\x02")):
100-
n = string[0] if isinstance(string[0], integer_types) else ord(string[0])
101-
raise UnexpectedDER("wanted integer (0x02), got 0x%02x" % n)
108+
n = string[0] if isinstance(string[0], integer_types) \
109+
else ord(string[0])
110+
raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n)
102111
length, llen = read_length(string[1:])
112+
if length > len(string) - 1 - llen:
113+
raise UnexpectedDER("Length longer than provided buffer")
114+
if length == 0:
115+
raise UnexpectedDER("0-byte long encoding of integer")
103116
numberbytes = string[1+llen:1+llen+length]
104117
rest = string[1+llen+length:]
105-
nbytes = numberbytes[0] if isinstance(numberbytes[0], integer_types) else ord(numberbytes[0])
106-
assert nbytes < 0x80 # can't support negative numbers yet
118+
msb = numberbytes[0] if isinstance(numberbytes[0], integer_types) \
119+
else ord(numberbytes[0])
120+
if not msb < 0x80:
121+
raise UnexpectedDER("Negative integers are not supported")
122+
# check if the encoding is the minimal one (DER requirement)
123+
if length > 1 and not msb:
124+
# leading zero byte is allowed if the integer would have been
125+
# considered a negative number otherwise
126+
smsb = numberbytes[1] if isinstance(numberbytes[1], integer_types) \
127+
else ord(numberbytes[1])
128+
if smsb < 0x80:
129+
raise UnexpectedDER("Invalid encoding of integer, unnecessary "
130+
"zero padding bytes")
107131
return int(binascii.hexlify(numberbytes), 16), rest
108132

109133
def read_number(string):
@@ -133,15 +157,23 @@ def encode_length(l):
133157
return int2byte(0x80|llen) + s
134158

135159
def read_length(string):
160+
if not string:
161+
raise UnexpectedDER("Empty string can't encode valid length value")
136162
num = string[0] if isinstance(string[0], integer_types) else ord(string[0])
137163
if not (num & 0x80):
138164
# short form
139165
return (num & 0x7f), 1
140166
# else long-form: b0&0x7f is number of additional base256 length bytes,
141167
# big-endian
142168
llen = num & 0x7f
169+
if not llen:
170+
raise UnexpectedDER("Invalid length encoding, length of length is 0")
143171
if llen > len(string)-1:
144-
raise UnexpectedDER("ran out of length bytes")
172+
raise UnexpectedDER("Length of length longer than provided buffer")
173+
# verify that the encoding is minimal possible (DER requirement)
174+
msb = string[1] if isinstance(string[1], integer_types) else ord(string[1])
175+
if not msb or llen == 1 and msb < 0x80:
176+
raise UnexpectedDER("Not minimal encoding of length")
145177
return int(binascii.hexlify(string[1:1+llen]), 16), 1+llen
146178

147179
def remove_bitstring(string):

ecdsa/keys.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
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
9-
from .util import oid_ecPublicKey, encoded_oid_ecPublicKey
10+
from .util import oid_ecPublicKey, encoded_oid_ecPublicKey, MalformedSignature
1011
from .six import PY3, b
1112
from hashlib import sha1
1213

@@ -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):
@@ -106,11 +121,14 @@ def verify_digest(self, signature, digest, sigdecode=sigdecode_string):
106121
"for your digest (%d)" % (self.curve.name,
107122
8*len(digest)))
108123
number = string_to_number(digest)
109-
r, s = sigdecode(signature, self.pubkey.order)
124+
try:
125+
r, s = sigdecode(signature, self.pubkey.order)
126+
except (der.UnexpectedDER, MalformedSignature) as e:
127+
raise BadSignatureError("Malformed formatting of signature", e)
110128
sig = ecdsa.Signature(r, s)
111129
if self.pubkey.verifies(number, sig):
112130
return True
113-
raise BadSignatureError
131+
raise BadSignatureError("Signature verification failed")
114132

115133
class SigningKey:
116134
def __init__(self, _error__please_use_generate=None):
@@ -134,7 +152,10 @@ def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1):
134152
self.default_hashfunc = hashfunc
135153
self.baselen = curve.baselen
136154
n = curve.order
137-
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))
138159
pubkey_point = curve.generator*secexp
139160
pubkey = ecdsa.Public_key(curve.generator, pubkey_point)
140161
pubkey.order = n
@@ -146,7 +167,10 @@ def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1):
146167

147168
@classmethod
148169
def from_string(klass, string, curve=NIST192p, hashfunc=sha1):
149-
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))
150174
secexp = string_to_number(string)
151175
return klass.from_secret_exponent(secexp, curve, hashfunc)
152176

0 commit comments

Comments
 (0)
0