Version in base suite: 0.13-3 Base version: python-ecdsa_0.13-3 Target version: python-ecdsa_0.13-3+deb10u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python-ecdsa/python-ecdsa_0.13-3.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python-ecdsa/python-ecdsa_0.13-3+deb10u1.dsc changelog | 10 control | 2 patches/00-strict-error-checking-in-der.patch | 666 ++++++++++++++++++++++++++ patches/series | 1 patches/system-six.patch | 28 - 5 files changed, 685 insertions(+), 22 deletions(-) diff -Nru python-ecdsa-0.13/debian/changelog python-ecdsa-0.13/debian/changelog --- python-ecdsa-0.13/debian/changelog 2018-04-01 19:26:44.000000000 +0000 +++ python-ecdsa-0.13/debian/changelog 2019-10-21 23:36:37.000000000 +0000 @@ -1,3 +1,13 @@ +python-ecdsa (0.13-3+deb10u1) buster-security; urgency=high + + * Add patch for strict error checking in DER decoding integers. + Fix: + - CVE-2019-14853 + - CVE-2019-14859 + * Add python{3}-pytest as Build Dependency + + -- Josue Ortega Mon, 21 Oct 2019 17:36:37 -0600 + python-ecdsa (0.13-3) unstable; urgency=medium [ Ondřej Nový ] diff -Nru python-ecdsa-0.13/debian/control python-ecdsa-0.13/debian/control --- python-ecdsa-0.13/debian/control 2018-04-01 19:26:44.000000000 +0000 +++ python-ecdsa-0.13/debian/control 2019-10-21 23:36:37.000000000 +0000 @@ -7,9 +7,11 @@ debhelper (>= 11), dh-python, python-all, + python-pytest, python3-all, python-six (>= 1.2), python3-six (>= 1.2), + python3-pytest, openssl Homepage: https://github.com/warner/python-ecdsa Vcs-Browser: https://salsa.debian.org/python-team/modules/python-ecdsa diff -Nru python-ecdsa-0.13/debian/patches/00-strict-error-checking-in-der.patch python-ecdsa-0.13/debian/patches/00-strict-error-checking-in-der.patch --- python-ecdsa-0.13/debian/patches/00-strict-error-checking-in-der.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-ecdsa-0.13/debian/patches/00-strict-error-checking-in-der.patch 2019-10-21 23:36:37.000000000 +0000 @@ -0,0 +1,666 @@ +Description: trict error checking in DER decoding of integers and sequences + This fixes CVE-2019-14853 and CVE-2019-14859. +From: Hubert Kario +Origin: https://github.com/warner/python-ecdsa/pull/124 +Last-Update: 2019-10-21 + + +--- a/ecdsa/__init__.py ++++ b/ecdsa/__init__.py +@@ -1,9 +1,12 @@ + __all__ = ["curves", "der", "ecdsa", "ellipticcurve", "keys", "numbertheory", + "test_pyecdsa", "util", "six"] +-from .keys import SigningKey, VerifyingKey, BadSignatureError, BadDigestError ++from .keys import SigningKey, VerifyingKey, BadSignatureError, BadDigestError,\ ++ MalformedPointError + from .curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1 ++from .der import UnexpectedDER + + _hush_pyflakes = [SigningKey, VerifyingKey, BadSignatureError, BadDigestError, ++ MalformedPointError, UnexpectedDER, + NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1] + del _hush_pyflakes + +--- a/ecdsa/der.py ++++ b/ecdsa/der.py +@@ -60,10 +60,15 @@ + return tag, body, rest + + def remove_sequence(string): ++ if not string: ++ raise UnexpectedDER("Empty string does not encode a sequence") + if not string.startswith(b("\x30")): +- n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) +- raise UnexpectedDER("wanted sequence (0x30), got 0x%02x" % n) ++ n = string[0] if isinstance(string[0], integer_types) else \ ++ ord(string[0]) ++ raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n) + length, lengthlength = read_length(string[1:]) ++ if length > len(string) - 1 - lengthlength: ++ raise UnexpectedDER("Length longer than the provided buffer") + endseq = 1+lengthlength+length + return string[1+lengthlength:endseq], string[endseq:] + +@@ -96,14 +101,33 @@ + return tuple(numbers), rest + + def remove_integer(string): ++ if not string: ++ raise UnexpectedDER("Empty string is an invalid encoding of an " ++ "integer") + if not string.startswith(b("\x02")): +- n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) +- raise UnexpectedDER("wanted integer (0x02), got 0x%02x" % n) ++ n = string[0] if isinstance(string[0], integer_types) \ ++ else ord(string[0]) ++ raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n) + length, llen = read_length(string[1:]) ++ if length > len(string) - 1 - llen: ++ raise UnexpectedDER("Length longer than provided buffer") ++ if length == 0: ++ raise UnexpectedDER("0-byte long encoding of integer") + numberbytes = string[1+llen:1+llen+length] + rest = string[1+llen+length:] +- nbytes = numberbytes[0] if isinstance(numberbytes[0], integer_types) else ord(numberbytes[0]) +- assert nbytes < 0x80 # can't support negative numbers yet ++ msb = numberbytes[0] if isinstance(numberbytes[0], integer_types) \ ++ else ord(numberbytes[0]) ++ if not msb < 0x80: ++ raise UnexpectedDER("Negative integers are not supported") ++ # check if the encoding is the minimal one (DER requirement) ++ if length > 1 and not msb: ++ # leading zero byte is allowed if the integer would have been ++ # considered a negative number otherwise ++ smsb = numberbytes[1] if isinstance(numberbytes[1], integer_types) \ ++ else ord(numberbytes[1]) ++ if smsb < 0x80: ++ raise UnexpectedDER("Invalid encoding of integer, unnecessary " ++ "zero padding bytes") + return int(binascii.hexlify(numberbytes), 16), rest + + def read_number(string): +@@ -133,6 +157,8 @@ + return int2byte(0x80|llen) + s + + def read_length(string): ++ if not string: ++ raise UnexpectedDER("Empty string can't encode valid length value") + num = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + if not (num & 0x80): + # short form +@@ -140,8 +166,14 @@ + # else long-form: b0&0x7f is number of additional base256 length bytes, + # big-endian + llen = num & 0x7f ++ if not llen: ++ raise UnexpectedDER("Invalid length encoding, length of length is 0") + if llen > len(string)-1: +- raise UnexpectedDER("ran out of length bytes") ++ raise UnexpectedDER("Length of length longer than provided buffer") ++ # verify that the encoding is minimal possible (DER requirement) ++ msb = string[1] if isinstance(string[1], integer_types) else ord(string[1]) ++ if not msb or llen == 1 and msb < 0x80: ++ raise UnexpectedDER("Not minimal encoding of length") + return int(binascii.hexlify(string[1:1+llen]), 16), 1+llen + + def remove_bitstring(string): +--- a/ecdsa/keys.py ++++ b/ecdsa/keys.py +@@ -3,10 +3,11 @@ + from . import ecdsa + from . import der + from . import rfc6979 ++from . import ellipticcurve + from .curves import NIST192p, find_curve + from .util import string_to_number, number_to_string, randrange + from .util import sigencode_string, sigdecode_string +-from .util import oid_ecPublicKey, encoded_oid_ecPublicKey ++from .util import oid_ecPublicKey, encoded_oid_ecPublicKey, MalformedSignature + from six import PY3, b + from hashlib import sha1 + +@@ -15,6 +16,11 @@ + class BadDigestError(Exception): + pass + ++ ++class MalformedPointError(AssertionError): ++ pass ++ ++ + class VerifyingKey: + def __init__(self, _error__please_use_generate=None): + if not _error__please_use_generate: +@@ -33,17 +39,21 @@ + def from_string(klass, string, curve=NIST192p, hashfunc=sha1, + validate_point=True): + order = curve.order +- assert len(string) == curve.verifying_key_length, \ +- (len(string), curve.verifying_key_length) ++ if len(string) != curve.verifying_key_length: ++ raise MalformedPointError( ++ "Malformed encoding of public point. Expected string {0} bytes" ++ " long, received {1} bytes long string".format( ++ curve.verifying_key_length, len(string))) + xs = string[:curve.baselen] + ys = string[curve.baselen:] +- assert len(xs) == curve.baselen, (len(xs), curve.baselen) +- assert len(ys) == curve.baselen, (len(ys), curve.baselen) ++ if len(xs) != curve.baselen: ++ raise MalformedPointError("Unexpected length of encoded x") ++ if len(ys) != curve.baselen: ++ raise MalformedPointError("Unexpected length of encoded y") + x = string_to_number(xs) + y = string_to_number(ys) +- if validate_point: +- assert ecdsa.point_is_valid(curve.generator, x, y) +- from . import ellipticcurve ++ if validate_point and not ecdsa.point_is_valid(curve.generator, x, y): ++ raise MalformedPointError("Point does not lie on the curve") + point = ellipticcurve.Point(curve.curve, x, y, order) + return klass.from_public_point(point, curve, hashfunc) + +@@ -65,13 +75,18 @@ + if empty != b(""): + raise der.UnexpectedDER("trailing junk after DER pubkey objects: %s" % + binascii.hexlify(empty)) +- assert oid_pk == oid_ecPublicKey, (oid_pk, oid_ecPublicKey) ++ if oid_pk != oid_ecPublicKey: ++ raise der.UnexpectedDER( ++ "Unexpected OID in encoding, received {0}, expected {1}" ++ .format(oid_pk, oid_ecPublicKey)) + curve = find_curve(oid_curve) + point_str, empty = der.remove_bitstring(point_str_bitstring) + if empty != b(""): + raise der.UnexpectedDER("trailing junk after pubkey pointstring: %s" % + binascii.hexlify(empty)) +- assert point_str.startswith(b("\x00\x04")) ++ if not point_str.startswith(b("\x00\x04")): ++ raise der.UnexpectedDER( ++ "Unsupported or invalid encoding of pubcli key") + return klass.from_string(point_str[2:], curve) + + def to_string(self): +@@ -106,11 +121,14 @@ + "for your digest (%d)" % (self.curve.name, + 8*len(digest))) + number = string_to_number(digest) +- r, s = sigdecode(signature, self.pubkey.order) ++ try: ++ r, s = sigdecode(signature, self.pubkey.order) ++ except (der.UnexpectedDER, MalformedSignature) as e: ++ raise BadSignatureError("Malformed formatting of signature", e) + sig = ecdsa.Signature(r, s) + if self.pubkey.verifies(number, sig): + return True +- raise BadSignatureError ++ raise BadSignatureError("Signature verification failed") + + class SigningKey: + def __init__(self, _error__please_use_generate=None): +@@ -134,7 +152,10 @@ + self.default_hashfunc = hashfunc + self.baselen = curve.baselen + n = curve.order +- assert 1 <= secexp < n ++ if not 1 <= secexp < n: ++ raise MalformedPointError( ++ "Invalid value for secexp, expected integer between 1 and {0}" ++ .format(n)) + pubkey_point = curve.generator*secexp + pubkey = ecdsa.Public_key(curve.generator, pubkey_point) + pubkey.order = n +@@ -146,7 +167,10 @@ + + @classmethod + def from_string(klass, string, curve=NIST192p, hashfunc=sha1): +- assert len(string) == curve.baselen, (len(string), curve.baselen) ++ if len(string) != curve.baselen: ++ raise MalformedPointError( ++ "Invalid length of private key, received {0}, expected {1}" ++ .format(len(string), curve.baselen)) + secexp = string_to_number(string) + return klass.from_secret_exponent(secexp, curve, hashfunc) + +--- /dev/null ++++ b/ecdsa/test_der.py +@@ -0,0 +1,88 @@ ++ ++# compatibility with Python 2.6, for that we need unittest2 package, ++# which is not available on 3.3 or 3.4 ++try: ++ import unittest2 as unittest ++except ImportError: ++ import unittest ++from .der import remove_integer, UnexpectedDER, read_length ++from six import b ++ ++class TestRemoveInteger(unittest.TestCase): ++ # DER requires the integers to be 0-padded only if they would be ++ # interpreted as negative, check if those errors are detected ++ def test_non_minimal_encoding(self): ++ with self.assertRaises(UnexpectedDER): ++ remove_integer(b('\x02\x02\x00\x01')) ++ ++ def test_negative_with_high_bit_set(self): ++ with self.assertRaises(UnexpectedDER): ++ remove_integer(b('\x02\x01\x80')) ++ ++ def test_two_zero_bytes_with_high_bit_set(self): ++ with self.assertRaises(UnexpectedDER): ++ remove_integer(b('\x02\x03\x00\x00\xff')) ++ ++ def test_zero_length_integer(self): ++ with self.assertRaises(UnexpectedDER): ++ remove_integer(b('\x02\x00')) ++ ++ def test_empty_string(self): ++ with self.assertRaises(UnexpectedDER): ++ remove_integer(b('')) ++ ++ def test_encoding_of_zero(self): ++ val, rem = remove_integer(b('\x02\x01\x00')) ++ ++ self.assertEqual(val, 0) ++ self.assertFalse(rem) ++ ++ def test_encoding_of_127(self): ++ val, rem = remove_integer(b('\x02\x01\x7f')) ++ ++ self.assertEqual(val, 127) ++ self.assertFalse(rem) ++ ++ def test_encoding_of_128(self): ++ val, rem = remove_integer(b('\x02\x02\x00\x80')) ++ ++ self.assertEqual(val, 128) ++ self.assertFalse(rem) ++ ++ ++class TestReadLength(unittest.TestCase): ++ # DER requires the lengths between 0 and 127 to be encoded using the short ++ # form and lengths above that encoded with minimal number of bytes ++ # necessary ++ def test_zero_length(self): ++ self.assertEqual((0, 1), read_length(b('\x00'))) ++ ++ def test_two_byte_zero_length(self): ++ with self.assertRaises(UnexpectedDER): ++ read_length(b('\x81\x00')) ++ ++ def test_two_byte_small_length(self): ++ with self.assertRaises(UnexpectedDER): ++ read_length(b('\x81\x7f')) ++ ++ def test_long_form_with_zero_length(self): ++ with self.assertRaises(UnexpectedDER): ++ read_length(b('\x80')) ++ ++ def test_smallest_two_byte_length(self): ++ self.assertEqual((128, 2), read_length(b('\x81\x80'))) ++ ++ def test_zero_padded_length(self): ++ with self.assertRaises(UnexpectedDER): ++ read_length(b('\x82\x00\x80')) ++ ++ def test_two_three_byte_length(self): ++ self.assertEqual((256, 3), read_length(b'\x82\x01\x00')) ++ ++ def test_empty_string(self): ++ with self.assertRaises(UnexpectedDER): ++ read_length(b('')) ++ ++ def test_length_overflow(self): ++ with self.assertRaises(UnexpectedDER): ++ read_length(b('\x83\x01\x00')) +--- /dev/null ++++ b/ecdsa/test_malformed_sigs.py +@@ -0,0 +1,87 @@ ++from __future__ import with_statement, division ++ ++import pytest ++import hashlib ++ ++from six import b, binary_type ++from .keys import SigningKey, VerifyingKey ++from .keys import BadSignatureError ++from .util import sigencode_der, sigencode_string ++from .util import sigdecode_der, sigdecode_string ++from .curves import curves, NIST256p, NIST521p ++ ++der_sigs = [] ++example_data = b("some data to sign") ++ ++# Just NIST256p with SHA256 is 560 test cases, all curves with all hashes is ++# few thousand slow test cases; execute the most interesting only ++ ++#for curve in curves: ++for curve in [NIST521p]: ++ #for hash_alg in ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"]: ++ for hash_alg in ["sha256"]: ++ key = SigningKey.generate(curve) ++ signature = key.sign(example_data, hashfunc=getattr(hashlib, hash_alg), ++ sigencode=sigencode_der) ++ for pos in range(len(signature)): ++ for xor in (1<