From 5b9a787c8b414142c37c21f24baaeba59474cc4d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Oct 2019 10:14:45 +0200 Subject: [PATCH 01/17] decision coverage: make git checkout work reliably also stop copying diff-instrumental.py, it's now in master so it will be present when we checkout the base commit --- .travis.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2cbba52..195150c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,7 @@ before_install: TRAVIS_COMMIT_RANGE=$PR_FIRST^..$TRAVIS_COMMIT fi # sanity check current commit - - git rev-parse HEAD + - BRANCH=$(git rev-parse HEAD) - echo "TRAVIS_COMMIT_RANGE=$TRAVIS_COMMIT_RANGE" - git fetch origin master:refs/remotes/origin/master @@ -78,7 +78,6 @@ install: script: - if [[ $TOX_ENV ]]; then tox -e $TOX_ENV; fi - tox -e speed - - cp diff-instrumental.py diff-instrumental-2.py - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST != "false" ]]; then git checkout $PR_FIRST^ @@ -86,8 +85,8 @@ script: files="$(ls src/ecdsa/test*.py | grep -v test_malformed_sigs.py)" instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files instrumental -f .instrumental.cov -s - instrumental -f .instrumental.cov -s | python diff-instrumental-2.py --save .diff-instrumental - git checkout $TRAVIS_COMMIT + instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental + git checkout $BRANCH instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files instrumental -f .instrumental.cov -sr fi @@ -98,11 +97,11 @@ script: instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files instrumental -f .instrumental.cov -s # just log the values when merging - instrumental -f .instrumental.cov -s | python diff-instrumental-2.py + instrumental -f .instrumental.cov -s | python diff-instrumental.py fi - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST != "false" ]]; then - instrumental -f .instrumental.cov -s | python diff-instrumental-2.py --read .diff-instrumental --fail-under 70 --max-difference -0.1 + instrumental -f .instrumental.cov -s | python diff-instrumental.py --read .diff-instrumental --fail-under 70 --max-difference -0.1 fi after_success: - if [[ -z $INSTRUMENTAL ]]; then coveralls; fi From 7a8237eeda8bc9e048dcf15f328608b30a7d8c80 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 22 Oct 2019 01:02:53 +0200 Subject: [PATCH 02/17] test: VerifyingKey.from_string bytes-like Document what inputs don't work in which python versions --- src/ecdsa/test_keys.py | 97 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/ecdsa/test_keys.py diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py new file mode 100644 index 00000000..f3871055 --- /dev/null +++ b/src/ecdsa/test_keys.py @@ -0,0 +1,97 @@ +try: + import unittest2 as unittest +except ImportError: + import unittest + +try: + buffer +except NameError: + buffer = memoryview + +import array +import six +import sys +import pytest + +from .keys import VerifyingKey + + +class TestVerifyingKeyFromString(unittest.TestCase): + """ + Verify that ecdsa.keys.VerifyingKey.from_string() can be used with + bytes-like objects + """ + + @classmethod + def setUpClass(cls): + cls.key_bytes = (b'\x04L\xa2\x95\xdb\xc7Z\xd7\x1f\x93\nz\xcf\x97\xcf' + b'\xd7\xc2\xd9o\xfe8}X!\xae\xd4\xfah\xfa^\rpI\xba\xd1' + b'Y\xfb\x92xa\xebo+\x9cG\xfav\xca') + cls.vk = VerifyingKey.from_string(cls.key_bytes) + + def test_bytes(self): + self.assertIsNotNone(self.vk) + self.assertIsInstance(self.vk, VerifyingKey) + self.assertEqual( + self.vk.pubkey.point.x(), + 105419898848891948935835657980914000059957975659675736097) + self.assertEqual( + self.vk.pubkey.point.y(), + 4286866841217412202667522375431381222214611213481632495306) + + def test_bytes_memoryview(self): + vk = VerifyingKey.from_string(buffer(self.key_bytes)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.skipIf(sys.version_info >= (2, 7), "fails on python2.6 only") + @unittest.expectedFailure + def test_bytearray26(self): + vk = VerifyingKey.from_string(bytearray(self.key_bytes)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.skipUnless(sys.version_info >= (2, 7), "fails on python2.6 only") + def test_bytearray(self): + vk = VerifyingKey.from_string(bytearray(self.key_bytes)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_bytesarray_memoryview(self): + vk = VerifyingKey.from_string(buffer(bytearray(self.key_bytes))) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_array_array_of_bytes(self): + arr = array.array('B', self.key_bytes) + vk = VerifyingKey.from_string(arr) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_array_array_of_bytes_memoryview(self): + arr = array.array('B', self.key_bytes) + vk = VerifyingKey.from_string(buffer(arr)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.expectedFailure + def test_array_array_of_ints(self): + arr = array.array('I', self.key_bytes) + vk = VerifyingKey.from_string(arr) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.skipIf(sys.version_info >= (3, 0), "Fails on python3") + def test_array_array_of_ints_memoryview2(self): + arr = array.array('I', self.key_bytes) + vk = VerifyingKey.from_string(buffer(arr)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.expectedFailure + @unittest.skipUnless(sys.version_info >= (3, 0), "Fails on python3") + def test_array_array_of_ints_memoryview(self): + arr = array.array('I', self.key_bytes) + vk = VerifyingKey.from_string(buffer(arr)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) From ff3772273997b2750012171cede8b124cbf08e96 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 22 Oct 2019 01:24:51 +0200 Subject: [PATCH 03/17] bytes input for VerifyingKey.from_string() make the bytearray work on pythohn2.6 make multi-byte array.array objects work on all versions --- src/ecdsa/_compat.py | 16 ++++++++++++++++ src/ecdsa/keys.py | 15 +++++++++++++-- src/ecdsa/test_keys.py | 18 ------------------ 3 files changed, 29 insertions(+), 20 deletions(-) create mode 100644 src/ecdsa/_compat.py diff --git a/src/ecdsa/_compat.py b/src/ecdsa/_compat.py new file mode 100644 index 00000000..11f0b32a --- /dev/null +++ b/src/ecdsa/_compat.py @@ -0,0 +1,16 @@ +""" +Common functions for providing cross-python version compatibility. +""" +import sys + + +if sys.version_info < (3, 0): + def normalise_bytes(buffer_object): + """Cast the input into array of bytes.""" + return buffer(buffer_object) + + +else: + def normalise_bytes(buffer_object): + """Cast the input into array of bytes.""" + return memoryview(buffer_object).cast('B') diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 4f926429..d490a103 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -55,6 +55,15 @@ Abstract Syntax Notation 1 is a standard description language for specifying serialisation and deserialisation of data structures in a portable and cross-platform way. + + bytes-like object + All the types that implement the buffer protocol. That includes + ``str`` (only on python2), ``bytes``, ``bytesarray``, ``array.array` + and ``memoryview`` of those objects. + Please note that ``array.array` serialisation (converting it to byte + string) is endianess dependant! Signature computed over ``array.array`` + of integers on a big-endian system will not be verified on a + little-endian system and vice-versa. """ import binascii @@ -70,6 +79,7 @@ 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, MalformedSignature +from ._compat import normalise_bytes __all__ = ["BadSignatureError", "BadDigestError", "VerifyingKey", "SigningKey", @@ -231,8 +241,8 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1, Python 2 days when there were no binary strings. In Python 3 the input needs to be a bytes-like object. - :param string: :term:`raw encoding` of the public key - :type string: bytes-like object + :param string: single point encoding of the public key + :type string: :term:`bytes-like object` :param curve: the curve on which the public key is expected to lie :type curve: ecdsa.curves.Curve :param hashfunc: The default hash function that will be used for @@ -245,6 +255,7 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1, :return: Initialised VerifyingKey object :rtype: VerifyingKey """ + string = normalise_bytes(string) sig_len = len(string) if sig_len == curve.verifying_key_length: point = cls._from_raw_encoding(string, curve, validate_point) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index f3871055..f594a937 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -44,14 +44,6 @@ def test_bytes_memoryview(self): self.assertEqual(self.vk.to_string(), vk.to_string()) - @unittest.skipIf(sys.version_info >= (2, 7), "fails on python2.6 only") - @unittest.expectedFailure - def test_bytearray26(self): - vk = VerifyingKey.from_string(bytearray(self.key_bytes)) - - self.assertEqual(self.vk.to_string(), vk.to_string()) - - @unittest.skipUnless(sys.version_info >= (2, 7), "fails on python2.6 only") def test_bytearray(self): vk = VerifyingKey.from_string(bytearray(self.key_bytes)) @@ -74,22 +66,12 @@ def test_array_array_of_bytes_memoryview(self): self.assertEqual(self.vk.to_string(), vk.to_string()) - @unittest.expectedFailure def test_array_array_of_ints(self): arr = array.array('I', self.key_bytes) vk = VerifyingKey.from_string(arr) self.assertEqual(self.vk.to_string(), vk.to_string()) - @unittest.skipIf(sys.version_info >= (3, 0), "Fails on python3") - def test_array_array_of_ints_memoryview2(self): - arr = array.array('I', self.key_bytes) - vk = VerifyingKey.from_string(buffer(arr)) - - self.assertEqual(self.vk.to_string(), vk.to_string()) - - @unittest.expectedFailure - @unittest.skipUnless(sys.version_info >= (3, 0), "Fails on python3") def test_array_array_of_ints_memoryview(self): arr = array.array('I', self.key_bytes) vk = VerifyingKey.from_string(buffer(arr)) From 0a2d74be878a4b5886c706a7a7fe1737f565d9b8 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 22 Oct 2019 01:35:23 +0200 Subject: [PATCH 04/17] test: VerifyingKey.from_string X9.62 inputs more test coverage for VerifyingKey.from_string, to verify that the alternative inputs can handle bytearray too --- src/ecdsa/test_keys.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index f594a937..e0c3291a 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -77,3 +77,23 @@ def test_array_array_of_ints_memoryview(self): vk = VerifyingKey.from_string(buffer(arr)) self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_bytes_uncompressed(self): + vk = VerifyingKey.from_string(b'\x04' + self.key_bytes) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_bytearray_uncompressed(self): + vk = VerifyingKey.from_string(bytearray(b'\x04' + self.key_bytes)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_bytes_compressed(self): + vk = VerifyingKey.from_string(b'\x02' + self.key_bytes[:24]) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_bytearray_uncompressed(self): + vk = VerifyingKey.from_string(bytearray(b'\x02' + self.key_bytes[:24])) + + self.assertEqual(self.vk.to_string(), vk.to_string()) From e7d9b3e919ca71a3158cf5f5b26a932642874e19 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Oct 2019 01:17:28 +0200 Subject: [PATCH 05/17] test: VerifyingKey.from_der document what parameter types don't work for from_der() --- src/ecdsa/test_keys.py | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index e0c3291a..f575dd93 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -14,6 +14,7 @@ import pytest from .keys import VerifyingKey +from .der import unpem class TestVerifyingKeyFromString(unittest.TestCase): @@ -97,3 +98,55 @@ def test_bytearray_uncompressed(self): vk = VerifyingKey.from_string(bytearray(b'\x02' + self.key_bytes[:24])) self.assertEqual(self.vk.to_string(), vk.to_string()) + + +class TestVerifyingKeyFromDer(unittest.TestCase): + """ + Verify that ecdsa.keys.VerifyingKey.from_der() can be used with + bytes-like objects. + """ + @classmethod + def setUpClass(cls): + prv_key_str = ( + "-----BEGIN EC PRIVATE KEY-----\n" + "MF8CAQEEGF7IQgvW75JSqULpiQQ8op9WH6Uldw6xxaAKBggqhkjOPQMBAaE0AzIA\n" + "BLiBd9CE7xf15FY5QIAoNg+fWbSk1yZOYtoGUdzkejWkxbRc9RWTQjqLVXucIJnz\n" + "bA==\n" + "-----END EC PRIVATE KEY-----\n") + key_str = ( + "-----BEGIN PUBLIC KEY-----\n" + "MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEuIF30ITvF/XkVjlAgCg2D59ZtKTX\n" + "Jk5i2gZR3OR6NaTFtFz1FZNCOotVe5wgmfNs\n" + "-----END PUBLIC KEY-----\n") + cls.key_bytes = unpem(key_str) + assert isinstance(cls.key_bytes, bytes) + cls.vk = VerifyingKey.from_pem(key_str) + + def test_bytes(self): + vk = VerifyingKey.from_der(self.key_bytes) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.expectedFailure + def test_bytes_memoryview(self): + vk = VerifyingKey.from_der(buffer(self.key_bytes)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_bytearray(self): + vk = VerifyingKey.from_der(bytearray(self.key_bytes)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.expectedFailure + def test_bytesarray_memoryview(self): + vk = VerifyingKey.from_der(buffer(bytearray(self.key_bytes))) + + self.assertEqual(self.vk.to_string(), vk.to_string()) + + @unittest.expectedFailure + def test_array_array_of_bytes(self): + arr = array.array('B', self.key_bytes) + vk = VerifyingKey.from_der(arr) + + self.assertEqual(self.vk.to_string(), vk.to_string()) From 12567ae7408298c6f83700152223558f7cd696aa Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Oct 2019 01:30:38 +0200 Subject: [PATCH 06/17] bytes input for VerifyingKey.from_der() --- src/ecdsa/der.py | 6 +++--- src/ecdsa/keys.py | 7 ++++--- src/ecdsa/test_keys.py | 9 ++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index 516451ca..145e0e17 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -135,7 +135,7 @@ def remove_constructed(string): def remove_sequence(string): if not string: raise UnexpectedDER("Empty string does not encode a sequence") - if not string.startswith(b("\x30")): + if string[:1] != b"\x30": n = string[0] if isinstance(string[0], integer_types) else \ ord(string[0]) raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n) @@ -157,7 +157,7 @@ def remove_octet_string(string): def remove_object(string): - if not string.startswith(b("\x06")): + if string[:1] != b"\x06": n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) raise UnexpectedDER("wanted type 'object' (0x06), got 0x%02x" % n) length, lengthlength = read_length(string[1:]) @@ -302,7 +302,7 @@ def remove_bitstring(string, expect_unused=_sentry): " specified", DeprecationWarning) num = string[0] if isinstance(string[0], integer_types) else ord(string[0]) - if not string.startswith(b("\x03")): + if string[:1] != b"\x03": raise UnexpectedDER("wanted bitstring (0x03), got 0x%02x" % num) length, llen = read_length(string[1:]) if not length: diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index d490a103..59930edb 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -328,16 +328,17 @@ def from_der(cls, string): :return: Initialised VerifyingKey object :rtype: VerifyingKey """ + string = normalise_bytes(string) # [[oid_ecPublicKey,oid_curve], point_str_bitstring] s1, empty = der.remove_sequence(string) - if empty != b(""): + if empty != b"": raise der.UnexpectedDER("trailing junk after DER pubkey: %s" % binascii.hexlify(empty)) s2, point_str_bitstring = der.remove_sequence(s1) # s2 = oid_ecPublicKey,oid_curve oid_pk, rest = der.remove_object(s2) oid_curve, empty = der.remove_object(rest) - if empty != b(""): + if empty != b"": raise der.UnexpectedDER("trailing junk after DER pubkey objects: %s" % binascii.hexlify(empty)) if not oid_pk == oid_ecPublicKey: @@ -345,7 +346,7 @@ def from_der(cls, string): "encoding: {0!r}".format(oid_pk)) curve = find_curve(oid_curve) point_str, empty = der.remove_bitstring(point_str_bitstring, 0) - if empty != b(""): + if empty != b"": raise der.UnexpectedDER("trailing junk after pubkey pointstring: %s" % binascii.hexlify(empty)) # raw encoding of point is invalid in DER files diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index f575dd93..7635c0f2 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -127,7 +127,6 @@ def test_bytes(self): self.assertEqual(self.vk.to_string(), vk.to_string()) - @unittest.expectedFailure def test_bytes_memoryview(self): vk = VerifyingKey.from_der(buffer(self.key_bytes)) @@ -138,15 +137,19 @@ def test_bytearray(self): self.assertEqual(self.vk.to_string(), vk.to_string()) - @unittest.expectedFailure def test_bytesarray_memoryview(self): vk = VerifyingKey.from_der(buffer(bytearray(self.key_bytes))) self.assertEqual(self.vk.to_string(), vk.to_string()) - @unittest.expectedFailure def test_array_array_of_bytes(self): arr = array.array('B', self.key_bytes) vk = VerifyingKey.from_der(arr) self.assertEqual(self.vk.to_string(), vk.to_string()) + + def test_array_array_of_bytes_memoryview(self): + arr = array.array('B', self.key_bytes) + vk = VerifyingKey.from_der(buffer(arr)) + + self.assertEqual(self.vk.to_string(), vk.to_string()) From 6f0cbf807c6fa3db99ceece34163ff6b66819022 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Oct 2019 02:32:09 +0200 Subject: [PATCH 07/17] bytes input for VerifyingKey.verify() --- src/ecdsa/der.py | 2 +- src/ecdsa/keys.py | 11 +++++-- src/ecdsa/test_keys.py | 72 +++++++++++++++++++++++++++++++++++++++++- src/ecdsa/util.py | 9 ++++-- 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index 145e0e17..3c0da2a7 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -180,7 +180,7 @@ def remove_integer(string): if not string: raise UnexpectedDER("Empty string is an invalid encoding of an " "integer") - if not string.startswith(b("\x02")): + if string[:1] != b"\x02": n = string[0] if isinstance(string[0], integer_types) \ else ord(string[0]) raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 59930edb..0ee20812 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -543,7 +543,7 @@ def verify(self, signature, data, hashfunc=None, as the `sigdecode` parameter. :param signature: encoding of the signature - :type signature: bytes like object + :type signature: sigdecode method dependant :param data: data signed by the `signature`, will be hashed using `hashfunc`, if specified, or default hash function :type data: bytes like object @@ -565,6 +565,10 @@ def verify(self, signature, data, hashfunc=None, :return: True if the verification was successful :rtype: bool """ + # signature doesn't have to be a bytes-like-object so don't normalise + # it, the decoders will do that + data = normalise_bytes(data) + hashfunc = hashfunc or self.default_hashfunc digest = hashfunc(data).digest() return self.verify_digest(signature, digest, sigdecode) @@ -579,7 +583,7 @@ def verify_digest(self, signature, digest, sigdecode=sigdecode_string): as the `sigdecode` parameter. :param signature: encoding of the signature - :type signature: bytes like object + :type signature: sigdecode method dependant :param digest: raw hash value that the signature authenticates. :type digest: bytes like object :param sigdecode: Callable to define the way the signature needs to @@ -597,6 +601,9 @@ def verify_digest(self, signature, digest, sigdecode=sigdecode_string): :return: True if the verification was successful :rtype: bool """ + # signature doesn't have to be a bytes-like-object so don't normalise + # it, the decoders will do that + digest = normalise_bytes(digest) if len(digest) > self.curve.baselen: raise BadDigestError("this curve (%s) is too short " "for your digest (%d)" % (self.curve.name, diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 7635c0f2..8f9b588d 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -12,9 +12,12 @@ import six import sys import pytest +import hashlib -from .keys import VerifyingKey +from .keys import VerifyingKey, SigningKey from .der import unpem +from .util import sigencode_string, sigencode_der, sigencode_strings, \ + sigdecode_string, sigdecode_der, sigdecode_strings class TestVerifyingKeyFromString(unittest.TestCase): @@ -153,3 +156,70 @@ def test_array_array_of_bytes_memoryview(self): vk = VerifyingKey.from_der(buffer(arr)) self.assertEqual(self.vk.to_string(), vk.to_string()) + + +# test VerifyingKey.verify() +prv_key_str = ( + "-----BEGIN EC PRIVATE KEY-----\n" + "MF8CAQEEGF7IQgvW75JSqULpiQQ8op9WH6Uldw6xxaAKBggqhkjOPQMBAaE0AzIA\n" + "BLiBd9CE7xf15FY5QIAoNg+fWbSk1yZOYtoGUdzkejWkxbRc9RWTQjqLVXucIJnz\n" + "bA==\n" + "-----END EC PRIVATE KEY-----\n") +key_bytes = unpem(prv_key_str) +assert isinstance(key_bytes, bytes) +sk = SigningKey.from_der(key_bytes) +vk = sk.verifying_key + +data = (b"some string for signing" + b"contents don't really matter" + b"but do include also some crazy values: " + b"\x00\x01\t\r\n\x00\x00\x00\xff\xf0") +assert len(data) % 4 == 0 +sha1 = hashlib.sha1() +sha1.update(data) +data_hash = sha1.digest() + +sig_raw = sk.sign(data, sigencode=sigencode_string) +assert isinstance(sig_raw, bytes) +sig_der = sk.sign(data, sigencode=sigencode_der) +assert isinstance(sig_der, bytes) +sig_strings = sk.sign(data, sigencode=sigencode_strings) +assert isinstance(sig_strings[0], bytes) + +verifiers = [] +for modifier, fun in [ + ("bytes", lambda x: x), + ("bytes memoryview", lambda x: buffer(x)), + ("bytearray", lambda x: bytearray(x)), + ("bytearray memoryview", lambda x: buffer(bytearray(x))), + ("array.array of bytes", lambda x: array.array('B', x)), + ("array.array of bytes memoryview", lambda x: buffer(array.array('B', x))), + ("array.array of ints", lambda x: array.array('I', x)), + ("array.array of ints memoryview", lambda x: buffer(array.array('I', x))) + ]: + if "ints" in modifier: + conv = lambda x: x + else: + conv = fun + for sig_format, signature, decoder, mod_apply in [ + ("raw", sig_raw, sigdecode_string, lambda x: conv(x)), + ("der", sig_der, sigdecode_der, lambda x: conv(x)), + ("strings", sig_strings, sigdecode_strings, lambda x: + tuple(conv(i) for i in x)) + ]: + for method_name, vrf_mthd, vrf_data in [ + ("verify", vk.verify, data), + ("verify_digest", vk.verify_digest, data_hash) + ]: + verifiers.append(pytest.param( + signature, decoder, mod_apply, fun, vrf_mthd, vrf_data, + id="{2}-{0}-{1}".format(modifier, sig_format, method_name))) + +@pytest.mark.parametrize( + "signature,decoder,mod_apply,fun,vrf_mthd,vrf_data", + verifiers) +def test_VerifyingKey_verify( + signature, decoder, mod_apply, fun, vrf_mthd, vrf_data): + sig = mod_apply(signature) + + assert vrf_mthd(sig, fun(vrf_data), sigdecode=decoder) diff --git a/src/ecdsa/util.py b/src/ecdsa/util.py index d229ed25..4769e312 100644 --- a/src/ecdsa/util.py +++ b/src/ecdsa/util.py @@ -6,6 +6,7 @@ from hashlib import sha256 from six import PY3, int2byte, b, next from . import der +from ._compat import normalise_bytes # RFC5480: # The "unrestricted" algorithm identifier is: @@ -312,6 +313,7 @@ def sigdecode_string(signature, order): :return: tuple with decoded 'r' and 's' values of signature :rtype: tuple of ints """ + signature = normalise_bytes(signature) l = orderlen(order) if not len(signature) == 2 * l: raise MalformedSignature( @@ -347,6 +349,8 @@ def sigdecode_strings(rs_strings, order): "Invalid number of strings provided: {0}, expected 2" .format(len(rs_strings))) (r_str, s_str) = rs_strings + r_str = normalise_bytes(r_str) + s_str = normalise_bytes(s_str) l = orderlen(order) if not len(r_str) == l: raise MalformedSignature( @@ -388,14 +392,15 @@ def sigdecode_der(sig_der, order): :return: tuple with decoded 'r' and 's' values of signature :rtype: tuple of ints """ + sig_der = normalise_bytes(sig_der) # return der.encode_sequence(der.encode_integer(r), der.encode_integer(s)) rs_strings, empty = der.remove_sequence(sig_der) - if empty != b(""): + if empty != b"": raise der.UnexpectedDER("trailing junk after DER sig: %s" % binascii.hexlify(empty)) r, rest = der.remove_integer(rs_strings) s, empty = der.remove_integer(rest) - if empty != b(""): + if empty != b"": raise der.UnexpectedDER("trailing junk after DER numbers: %s" % binascii.hexlify(empty)) return r, s From 8e95d845b71ed8c78f14dc25cc727b865c423710 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Oct 2019 02:35:08 +0200 Subject: [PATCH 08/17] bytes input for VerifyingKey.from_public_key_recovery() and for VerifyingKey.from_public_key_recovery_with_digest() --- src/ecdsa/keys.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 0ee20812..43e513dd 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -383,6 +383,7 @@ def from_public_key_recovery(cls, signature, data, curve, hashfunc=sha1, :return: Initialised VerifyingKey objects :rtype: list of VerifyingKey """ + data = normalise_bytes(data) digest = hashfunc(data).digest() return cls.from_public_key_recovery_with_digest( signature, digest, curve, hashfunc=hashfunc, @@ -423,6 +424,7 @@ def from_public_key_recovery_with_digest( r, s = sigdecode(signature, generator.order()) sig = ecdsa.Signature(r, s) + digest = normalise_bytes(digest) digest_as_number = string_to_number(digest) pks = sig.recover_public_keys(digest_as_number, generator) From 7e5c5ecc099fba14304191728cdd08fdfc5569ca Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 24 Oct 2019 00:02:47 +0200 Subject: [PATCH 09/17] bytes input for SigningKey.from_string() --- src/ecdsa/keys.py | 1 + src/ecdsa/test_keys.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 43e513dd..a55c50f8 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -737,6 +737,7 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1): :return: Initialised SigningKey object :rtype: SigningKey """ + string = normalise_bytes(string) if len(string) != curve.baselen: raise MalformedPointError( "Invalid length of private key, received {0}, expected {1}" diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 8f9b588d..84499b6d 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -223,3 +223,32 @@ def test_VerifyingKey_verify( sig = mod_apply(signature) assert vrf_mthd(sig, fun(vrf_data), sigdecode=decoder) + + +# test SigningKey.from_string() +prv_key_bytes = (b'^\xc8B\x0b\xd6\xef\x92R\xa9B\xe9\x89\x04<\xa2' + b'\x9fV\x1f\xa5%w\x0e\xb1\xc5') +assert len(prv_key_bytes) == 24 +keys = [] +for modifier, convert in [ + ("bytes", lambda x: x), + ("bytes memoryview", buffer), + ("bytearray", bytearray), + ("bytearray memoryview", lambda x: buffer(bytearray(x))), + ("array.array of bytes", lambda x: array.array('B', x)), + ("array.array of bytes memoryview", + lambda x: buffer(array.array('B', x))), + ("array.array of ints", lambda x: array.array('I', x)), + ("array.array of ints memoryview", + lambda x: buffer(array.array('I', x))) + ]: + keys.append(pytest.param( + convert, + id=modifier)) + +@pytest.mark.parametrize("convert", keys) +def test_SigningKey_from_string(convert): + key = convert(prv_key_bytes) + sk = SigningKey.from_string(key) + + assert sk.to_string() == prv_key_bytes From 31b28fb087f613e6feb4aef1df61ae9c821ddfc5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 24 Oct 2019 00:19:08 +0200 Subject: [PATCH 10/17] bytes input for SigningKey.from_der() add support for all bytes-like objects as input for SigningKey.from_der --- src/ecdsa/der.py | 2 +- src/ecdsa/keys.py | 1 + src/ecdsa/test_keys.py | 26 +++++++++++++++++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index 3c0da2a7..4bedb4ec 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -147,7 +147,7 @@ def remove_sequence(string): def remove_octet_string(string): - if not string.startswith(b("\x04")): + if string[:1] != b"\x04": n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) raise UnexpectedDER("wanted type 'octetstring' (0x04), got 0x%02x" % n) length, llen = read_length(string[1:]) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index a55c50f8..d5f67515 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -830,6 +830,7 @@ def from_der(cls, string, hashfunc=sha1): :return: Initialised VerifyingKey object :rtype: VerifyingKey """ + string = normalise_bytes(string) s, empty = der.remove_sequence(string) if empty != b(""): raise der.UnexpectedDER("trailing junk after DER privkey: %s" % diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 84499b6d..405956d3 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -229,7 +229,7 @@ def test_VerifyingKey_verify( prv_key_bytes = (b'^\xc8B\x0b\xd6\xef\x92R\xa9B\xe9\x89\x04<\xa2' b'\x9fV\x1f\xa5%w\x0e\xb1\xc5') assert len(prv_key_bytes) == 24 -keys = [] +converters = [] for modifier, convert in [ ("bytes", lambda x: x), ("bytes memoryview", buffer), @@ -242,13 +242,33 @@ def test_VerifyingKey_verify( ("array.array of ints memoryview", lambda x: buffer(array.array('I', x))) ]: - keys.append(pytest.param( + converters.append(pytest.param( convert, id=modifier)) -@pytest.mark.parametrize("convert", keys) +@pytest.mark.parametrize("convert", converters) def test_SigningKey_from_string(convert): key = convert(prv_key_bytes) sk = SigningKey.from_string(key) assert sk.to_string() == prv_key_bytes + + +# test SigningKey.from_der() +prv_key_str = ( + "-----BEGIN EC PRIVATE KEY-----\n" + "MF8CAQEEGF7IQgvW75JSqULpiQQ8op9WH6Uldw6xxaAKBggqhkjOPQMBAaE0AzIA\n" + "BLiBd9CE7xf15FY5QIAoNg+fWbSk1yZOYtoGUdzkejWkxbRc9RWTQjqLVXucIJnz\n" + "bA==\n" + "-----END EC PRIVATE KEY-----\n") +key_bytes = unpem(prv_key_str) +assert isinstance(key_bytes, bytes) + +# last two converters are for array.array of ints, those require input +# that's multiple of 4, which no curve we support produces +@pytest.mark.parametrize("convert", converters[:-2]) +def test_SigningKey_from_der(convert): + key = convert(key_bytes) + sk = SigningKey.from_der(key) + + assert sk.to_string() == prv_key_bytes From f7c08bdf1e75d8b9aa67c8221c38d683f398dd19 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 24 Oct 2019 01:00:10 +0200 Subject: [PATCH 11/17] get rid of the s[x] if isintance(s[x]... idiom this is repeating the s[x] three times for every element extracted, use a function for that also decreases number of tests needed for condition coverage --- src/ecdsa/_compat.py | 9 +++++++++ src/ecdsa/der.py | 39 +++++++++++++++++---------------------- src/ecdsa/test_der.py | 13 +++++++++++++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/ecdsa/_compat.py b/src/ecdsa/_compat.py index 11f0b32a..13d3a2aa 100644 --- a/src/ecdsa/_compat.py +++ b/src/ecdsa/_compat.py @@ -2,6 +2,15 @@ Common functions for providing cross-python version compatibility. """ import sys +from six import integer_types + + +def str_idx_as_int(string, index): + """Take index'th byte from string, return as integer""" + val = string[index] + if isinstance(val, integer_types): + return val + return ord(val) if sys.version_info < (3, 0): diff --git a/src/ecdsa/der.py b/src/ecdsa/der.py index 4bedb4ec..fb2a7ad2 100644 --- a/src/ecdsa/der.py +++ b/src/ecdsa/der.py @@ -3,7 +3,8 @@ import binascii import base64 import warnings -from six import int2byte, b, integer_types, text_type +from six import int2byte, b, text_type +from ._compat import str_idx_as_int class UnexpectedDER(Exception): @@ -20,7 +21,7 @@ def encode_integer(r): if len(h) % 2: h = b("0") + h s = binascii.unhexlify(h) - num = s[0] if isinstance(s[0], integer_types) else ord(s[0]) + num = str_idx_as_int(s, 0) if num <= 0x7f: return b("\x02") + int2byte(len(s)) + s else: @@ -83,7 +84,7 @@ def encode_bitstring(s, unused=_sentry): if unused: if not s: raise ValueError("unused is non-zero but s is empty") - last = s[-1] if isinstance(s[-1], integer_types) else ord(s[-1]) + last = str_idx_as_int(s, -1) if last & (2 ** unused - 1): raise ValueError("unused bits must be zeros in DER") encoded_unused = int2byte(unused) @@ -121,7 +122,7 @@ def encode_number(n): def remove_constructed(string): - s0 = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + s0 = str_idx_as_int(string, 0) if (s0 & 0xe0) != 0xa0: raise UnexpectedDER("wanted type 'constructed tag' (0xa0-0xbf), " "got 0x%02x" % s0) @@ -136,8 +137,7 @@ def remove_sequence(string): if not string: raise UnexpectedDER("Empty string does not encode a sequence") if string[:1] != b"\x30": - n = string[0] if isinstance(string[0], integer_types) else \ - ord(string[0]) + n = str_idx_as_int(string, 0) raise UnexpectedDER("wanted type 'sequence' (0x30), got 0x%02x" % n) length, lengthlength = read_length(string[1:]) if length > len(string) - 1 - lengthlength: @@ -148,7 +148,7 @@ def remove_sequence(string): def remove_octet_string(string): if string[:1] != b"\x04": - n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + n = str_idx_as_int(string, 0) raise UnexpectedDER("wanted type 'octetstring' (0x04), got 0x%02x" % n) length, llen = read_length(string[1:]) body = string[1+llen:1+llen+length] @@ -158,7 +158,7 @@ def remove_octet_string(string): def remove_object(string): if string[:1] != b"\x06": - n = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + n = str_idx_as_int(string, 0) raise UnexpectedDER("wanted type 'object' (0x06), got 0x%02x" % n) length, lengthlength = read_length(string[1:]) body = string[1+lengthlength:1+lengthlength+length] @@ -181,8 +181,7 @@ def remove_integer(string): raise UnexpectedDER("Empty string is an invalid encoding of an " "integer") if string[:1] != b"\x02": - n = string[0] if isinstance(string[0], integer_types) \ - else ord(string[0]) + n = str_idx_as_int(string, 0) raise UnexpectedDER("wanted type 'integer' (0x02), got 0x%02x" % n) length, llen = read_length(string[1:]) if length > len(string) - 1 - llen: @@ -191,16 +190,14 @@ def remove_integer(string): raise UnexpectedDER("0-byte long encoding of integer") numberbytes = string[1+llen:1+llen+length] rest = string[1+llen+length:] - msb = numberbytes[0] if isinstance(numberbytes[0], integer_types) \ - else ord(numberbytes[0]) + msb = str_idx_as_int(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]) + smsb = str_idx_as_int(numberbytes, 1) if smsb < 0x80: raise UnexpectedDER("Invalid encoding of integer, unnecessary " "zero padding bytes") @@ -215,7 +212,7 @@ def read_number(string): if llen > len(string): raise UnexpectedDER("ran out of length bytes") number = number << 7 - d = string[llen] if isinstance(string[llen], integer_types) else ord(string[llen]) + d = str_idx_as_int(string, llen) number += (d & 0x7f) llen += 1 if not d & 0x80: @@ -238,7 +235,7 @@ def encode_length(l): 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]) + num = str_idx_as_int(string, 0) if not (num & 0x80): # short form return (num & 0x7f), 1 @@ -250,7 +247,7 @@ def read_length(string): if llen > len(string)-1: 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]) + msb = str_idx_as_int(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 @@ -301,7 +298,7 @@ def remove_bitstring(string, expect_unused=_sentry): warnings.warn("Legacy call convention used, expect_unused= needs to be" " specified", DeprecationWarning) - num = string[0] if isinstance(string[0], integer_types) else ord(string[0]) + num = str_idx_as_int(string, 0) if string[:1] != b"\x03": raise UnexpectedDER("wanted bitstring (0x03), got 0x%02x" % num) length, llen = read_length(string[1:]) @@ -310,8 +307,7 @@ def remove_bitstring(string, expect_unused=_sentry): body = string[1+llen:1+llen+length] rest = string[1+llen+length:] if expect_unused is not _sentry: - unused = body[0] if isinstance(body[0], integer_types) \ - else ord(body[0]) + unused = str_idx_as_int(body, 0) if not 0 <= unused <= 7: raise UnexpectedDER("Invalid encoding of unused bits") if expect_unused is not None and expect_unused != unused: @@ -320,8 +316,7 @@ def remove_bitstring(string, expect_unused=_sentry): if unused: if not body: raise UnexpectedDER("Invalid encoding of empty bit string") - last = body[-1] if isinstance(body[-1], integer_types) else \ - ord(body[-1]) + last = str_idx_as_int(body, -1) # verify that all the unused bits are set to zero (DER requirement) if last & (2 ** unused - 1): raise UnexpectedDER("Non zero padding bits in bit string") diff --git a/src/ecdsa/test_der.py b/src/ecdsa/test_der.py index c163519f..a44befb1 100644 --- a/src/ecdsa/test_der.py +++ b/src/ecdsa/test_der.py @@ -10,6 +10,8 @@ from six import b import pytest import warnings +from ._compat import str_idx_as_int + class TestRemoveInteger(unittest.TestCase): # DER requires the integers to be 0-padded only if they would be @@ -229,3 +231,14 @@ def test_invalid_encoding_of_empty_string(self): def test_invalid_padding_bits(self): with self.assertRaises(UnexpectedDER): remove_bitstring(b'\x03\x02\x01\xff', None) + + +class TestStrIdxAsInt(unittest.TestCase): + def test_str(self): + self.assertEqual(115, str_idx_as_int('str', 0)) + + def test_bytes(self): + self.assertEqual(115, str_idx_as_int(b'str', 0)) + + def test_bytearray(self): + self.assertEqual(115, str_idx_as_int(bytearray(b'str'), 0)) From f630dcd4e4087384005d9dc0966b1ea8abd6eeb2 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 25 Oct 2019 00:11:21 +0200 Subject: [PATCH 12/17] bytes input for SigningKey.sign_deterministic() as extra_entropy can be large, and different type than outputs from either bits2octets or number_to_string, do not concatenate them but rather push them to the hmac one by one don't use six.b(), not needed on py2.6 simplify the chained comparison and remove unneeded else after return in rfc5979.generate_k --- src/ecdsa/keys.py | 2 ++ src/ecdsa/rfc6979.py | 31 +++++++++++++++++++------------ src/ecdsa/test_keys.py | 14 +++++++++++++- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index d5f67515..78937f57 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -974,6 +974,8 @@ def sign_deterministic(self, data, hashfunc=None, :rtype: bytes or sigencode function dependant type """ hashfunc = hashfunc or self.default_hashfunc + data = normalise_bytes(data) + extra_entropy = normalise_bytes(extra_entropy) digest = hashfunc(data).digest() return self.sign_digest_deterministic( diff --git a/src/ecdsa/rfc6979.py b/src/ecdsa/rfc6979.py index 290dcae7..3a8ce1c1 100644 --- a/src/ecdsa/rfc6979.py +++ b/src/ecdsa/rfc6979.py @@ -12,7 +12,6 @@ import hmac from binascii import hexlify from .util import number_to_string, number_to_string_crop, bit_length -from six import b # bit_length was defined in this module previously so keep it for backwards @@ -54,24 +53,33 @@ def generate_k(order, secexp, hash_func, data, retry_gen=0, extra_entropy=b''): qlen = bit_length(order) holen = hash_func().digest_size rolen = (qlen + 7) / 8 - bx = number_to_string(secexp, order) + bits2octets(data, order) + \ - extra_entropy + bx = (number_to_string(secexp, order), + bits2octets(data, order), + extra_entropy) # Step B - v = b('\x01') * holen + v = b'\x01' * holen # Step C - k = b('\x00') * holen + k = b'\x00' * holen # Step D - k = hmac.new(k, v + b('\x00') + bx, hash_func).digest() + k = hmac.new(k, digestmod=hash_func) + k.update(v + b'\x00') + for i in bx: + k.update(i) + k = k.digest() # Step E v = hmac.new(k, v, hash_func).digest() # Step F - k = hmac.new(k, v + b('\x01') + bx, hash_func).digest() + k = hmac.new(k, digestmod=hash_func) + k.update(v + b'\x01') + for i in bx: + k.update(i) + k = k.digest() # Step G v = hmac.new(k, v, hash_func).digest() @@ -79,7 +87,7 @@ def generate_k(order, secexp, hash_func, data, retry_gen=0, extra_entropy=b''): # Step H while True: # Step H1 - t = b('') + t = b'' # Step H2 while len(t) < rolen: @@ -89,11 +97,10 @@ def generate_k(order, secexp, hash_func, data, retry_gen=0, extra_entropy=b''): # Step H3 secret = bits2int(t, qlen) - if secret >= 1 and secret < order: + if 1 <= secret < order: if retry_gen <= 0: return secret - else: - retry_gen -= 1 + retry_gen -= 1 - k = hmac.new(k, v + b('\x00'), hash_func).digest() + k = hmac.new(k, v + b'\x00', hash_func).digest() v = hmac.new(k, v, hash_func).digest() diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 405956d3..469e7760 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -178,7 +178,7 @@ def test_array_array_of_bytes_memoryview(self): sha1 = hashlib.sha1() sha1.update(data) data_hash = sha1.digest() - +assert isinstance(data_hash, bytes) sig_raw = sk.sign(data, sigencode=sigencode_string) assert isinstance(sig_raw, bytes) sig_der = sk.sign(data, sigencode=sigencode_der) @@ -272,3 +272,15 @@ def test_SigningKey_from_der(convert): sk = SigningKey.from_der(key) assert sk.to_string() == prv_key_bytes + + +# test SigningKey.sign_deterministic() +extra_entropy=b'\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11' + +@pytest.mark.parametrize("convert", converters) +def test_SigningKey_sign_deterministic(convert): + sig = sk.sign_deterministic( + convert(data), + extra_entropy=convert(extra_entropy)) + + vk.verify(sig, data) From 9726e1b0cd47ee7c9aa5475f197f5cdd76af390d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 25 Oct 2019 00:13:49 +0200 Subject: [PATCH 13/17] sign_digest_deterministric: use default hashfunc --- src/ecdsa/keys.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 78937f57..a463f51b 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -1018,6 +1018,7 @@ def sign_digest_deterministic(self, digest, hashfunc=None, :rtype: bytes or sigencode function dependant type """ secexp = self.privkey.secret_multiplier + hashfunc = hashfunc or self.default_hashfunc def simple_r_s(r, s, order): return r, s, order From 29d8751c865c9163bc44957ecea7d37777c85876 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 25 Oct 2019 00:18:34 +0200 Subject: [PATCH 14/17] bytes input for SigningKey.sign_digest_deterministic() --- src/ecdsa/keys.py | 2 ++ src/ecdsa/test_keys.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index a463f51b..5053db79 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -1019,6 +1019,8 @@ def sign_digest_deterministic(self, digest, hashfunc=None, """ secexp = self.privkey.secret_multiplier hashfunc = hashfunc or self.default_hashfunc + digest = normalise_bytes(digest) + extra_entropy = normalise_bytes(extra_entropy) def simple_r_s(r, s, order): return r, s, order diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 469e7760..54114825 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -284,3 +284,13 @@ def test_SigningKey_sign_deterministic(convert): extra_entropy=convert(extra_entropy)) vk.verify(sig, data) + + +# test SigningKey.sign_digest_deterministic() +@pytest.mark.parametrize("convert", converters) +def test_SigningKey_sign_digest_deterministic(convert): + sig = sk.sign_digest_deterministic( + convert(data_hash), + extra_entropy=convert(extra_entropy)) + + vk.verify(sig, data) From 6a64f66cdb8f60cda57d87d823c623ecf4e07522 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 25 Oct 2019 00:21:11 +0200 Subject: [PATCH 15/17] bytes input for SigningKey.sign() --- src/ecdsa/keys.py | 1 + src/ecdsa/test_keys.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 5053db79..04dec598 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -1084,6 +1084,7 @@ def sign(self, data, entropy=None, hashfunc=None, :rtype: bytes or sigencode function dependant type """ hashfunc = hashfunc or self.default_hashfunc + data = normalise_bytes(data) h = hashfunc(data).digest() return self.sign_digest(h, entropy, sigencode, k) diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index 54114825..d3f90105 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -294,3 +294,10 @@ def test_SigningKey_sign_digest_deterministic(convert): extra_entropy=convert(extra_entropy)) vk.verify(sig, data) + + +@pytest.mark.parametrize("convert", converters) +def test_SigningKey_sign(convert): + sig = sk.sign(convert(data)) + + vk.verify(sig, data) From f1f5979a3e0ce08c38939c6adce9d292e2de3eff Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 25 Oct 2019 00:24:36 +0200 Subject: [PATCH 16/17] bytes input for SigningKey.sign_digest() --- src/ecdsa/keys.py | 1 + src/ecdsa/test_keys.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 04dec598..50630152 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -1123,6 +1123,7 @@ def sign_digest(self, digest, entropy=None, sigencode=sigencode_string, :return: encoded signature for the `digest` hash :rtype: bytes or sigencode function dependant type """ + digest = normalise_bytes(digest) if len(digest) > self.curve.baselen: raise BadDigestError("this curve (%s) is too short " "for your digest (%d)" % (self.curve.name, diff --git a/src/ecdsa/test_keys.py b/src/ecdsa/test_keys.py index d3f90105..3f1dffe4 100644 --- a/src/ecdsa/test_keys.py +++ b/src/ecdsa/test_keys.py @@ -301,3 +301,10 @@ def test_SigningKey_sign(convert): sig = sk.sign(convert(data)) vk.verify(sig, data) + + +@pytest.mark.parametrize("convert", converters) +def test_SigningKey_sign_digest(convert): + sig = sk.sign_digest(convert(data_hash)) + + vk.verify(sig, data) From b06b4bdf51884fd6aeec48d767259701c26476e5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 25 Oct 2019 00:44:13 +0200 Subject: [PATCH 17/17] run tests in instrumental from newly created modules --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 195150c0..1b15378e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,6 +87,7 @@ script: instrumental -f .instrumental.cov -s instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental git checkout $BRANCH + files="$(ls src/ecdsa/test*.py | grep -v test_malformed_sigs.py)" instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files instrumental -f .instrumental.cov -sr fi