From 941a8dd6ee9869c7d2b6c9fc36057241f6a1993b Mon Sep 17 00:00:00 2001 From: blag Date: Wed, 29 Jul 2020 01:57:55 -0700 Subject: [PATCH 1/5] Expose allow_truncate option in SigningKey.sign() and VerifyingKey.verify() --- src/ecdsa/keys.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 89273500..e1ebd884 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -602,7 +602,8 @@ def to_der(self, point_encoding="uncompressed"): ) def verify( - self, signature, data, hashfunc=None, sigdecode=sigdecode_string + self, signature, data, hashfunc=None, sigdecode=sigdecode_string, + allow_truncate=True ): """ Verify a signature made over provided data. @@ -629,6 +630,11 @@ def verify( second one. See :func:`ecdsa.util.sigdecode_string` and :func:`ecdsa.util.sigdecode_der` for examples. :type sigdecode: callable + :param bool allow_truncate: if True, the provided digest can have + bigger bit-size than the order of the curve, the extra bits (at + the end of the digest) will be truncated. Use it when verifying + SHA-384 output using NIST256p or in similar situations. Defaults to + True. :raises BadSignatureError: if the signature is invalid or malformed @@ -641,7 +647,7 @@ def verify( hashfunc = hashfunc or self.default_hashfunc digest = hashfunc(data).digest() - return self.verify_digest(signature, digest, sigdecode, True) + return self.verify_digest(signature, digest, sigdecode, allow_truncate) def verify_digest( self, @@ -1262,6 +1268,7 @@ def sign( hashfunc=None, sigencode=sigencode_string, k=None, + allow_truncate=True, ): """ Create signature over data using the probabilistic ECDSA algorithm. @@ -1298,6 +1305,11 @@ def sign( :param int k: a pre-selected nonce for calculating the signature. In typical use cases, it should be set to None (the default) to allow its generation from an entropy source. + :param bool allow_truncate: if True, the provided digest can have + bigger bit-size than the order of the curve, the extra bits (at + the end of the digest) will be truncated. Use it when signing + SHA-384 output using NIST256p or in similar situations. True by + default. :raises RSZeroError: in the unlikely event when "r" parameter or "s" parameter is equal 0 as that would leak the key. Calee should @@ -1309,7 +1321,7 @@ def sign( hashfunc = hashfunc or self.default_hashfunc data = normalise_bytes(data) h = hashfunc(data).digest() - return self.sign_digest(h, entropy, sigencode, k, allow_truncate=True) + return self.sign_digest(h, entropy, sigencode, k, allow_truncate) def sign_digest( self, From 7888c7bdf9425b51e140c35b650e57f0ee345967 Mon Sep 17 00:00:00 2001 From: blag Date: Wed, 29 Jul 2020 02:39:43 -0700 Subject: [PATCH 2/5] Add tests for allow_truncate=False --- src/ecdsa/test_pyecdsa.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 58eff851..ae597d14 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1203,6 +1203,44 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"): ) +class TooSmallCurve(unittest.TestCase): + @pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support prime192v1") + def test_sign_too_small_curve_dont_allow_truncate_raises(self): + sk = SigningKey.generate(curve=NIST192p) + vk = sk.get_verifying_key() + data = b("data") + with self.assertRaises(ecdsa.keys.BadDigestError): + sk.sign( + data, + hashfunc=partial(hashlib.new, "SHA256"), + sigencode=sigencode_der, + allow_truncate=False, + ) + + @pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support prime192v1") + def test_verify_too_small_curve_dont_allow_truncate_raises(self): + sk = SigningKey.generate(curve=NIST192p) + vk = sk.get_verifying_key() + data = b("data") + sig_der = sk.sign( + data, + hashfunc=partial(hashlib.new, "SHA256"), + sigencode=sigencode_der, + allow_truncate=True, + ) + with self.assertRaises(BadDigestError): + vk.verify( + sig_der, + data, + hashfunc=partial(hashlib.new, "SHA256"), + sigdecode=sigdecode_der, + allow_truncate=False, + ) + + + class DER(unittest.TestCase): def test_integer(self): self.assertEqual(der.encode_integer(0), b("\x02\x01\x00")) From 52b8477e69f30f36a4821b1dc4636ab09b4ced9e Mon Sep 17 00:00:00 2001 From: blag Date: Wed, 29 Jul 2020 12:05:43 -0700 Subject: [PATCH 3/5] Fix exception path --- src/ecdsa/test_pyecdsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index ae597d14..5e470fa8 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1210,7 +1210,7 @@ def test_sign_too_small_curve_dont_allow_truncate_raises(self): sk = SigningKey.generate(curve=NIST192p) vk = sk.get_verifying_key() data = b("data") - with self.assertRaises(ecdsa.keys.BadDigestError): + with self.assertRaises(BadDigestError): sk.sign( data, hashfunc=partial(hashlib.new, "SHA256"), From 4ffeed64263167a50ba673d83763e05e92737745 Mon Sep 17 00:00:00 2001 From: blag Date: Wed, 29 Jul 2020 12:06:20 -0700 Subject: [PATCH 4/5] Add OPENSSL_SUPPORTED_CURVES --- src/ecdsa/test_pyecdsa.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 5e470fa8..6f5b5de8 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1204,6 +1204,11 @@ def do_test_to_openssl(self, curve, hash_name="SHA1"): class TooSmallCurve(unittest.TestCase): + OPENSSL_SUPPORTED_CURVES = set( + c.split(":")[0].strip() + for c in run_openssl("ecparam -list_curves").split("\n") + ) + @pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES, reason="system openssl does not support prime192v1") def test_sign_too_small_curve_dont_allow_truncate_raises(self): From 4f7d53420dc0394e15ad9730ef35bdf922d5600c Mon Sep 17 00:00:00 2001 From: blag Date: Thu, 6 Aug 2020 21:26:42 -0700 Subject: [PATCH 5/5] Reformat to make black happy --- src/ecdsa/keys.py | 8 ++++++-- src/ecdsa/test_pyecdsa.py | 13 ++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index e1ebd884..e7aa8371 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -602,8 +602,12 @@ def to_der(self, point_encoding="uncompressed"): ) def verify( - self, signature, data, hashfunc=None, sigdecode=sigdecode_string, - allow_truncate=True + self, + signature, + data, + hashfunc=None, + sigdecode=sigdecode_string, + allow_truncate=True, ): """ Verify a signature made over provided data. diff --git a/src/ecdsa/test_pyecdsa.py b/src/ecdsa/test_pyecdsa.py index 6f5b5de8..65b67160 100644 --- a/src/ecdsa/test_pyecdsa.py +++ b/src/ecdsa/test_pyecdsa.py @@ -1209,8 +1209,10 @@ class TooSmallCurve(unittest.TestCase): for c in run_openssl("ecparam -list_curves").split("\n") ) - @pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES, - reason="system openssl does not support prime192v1") + @pytest.mark.skipif( + "prime192v1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support prime192v1", + ) def test_sign_too_small_curve_dont_allow_truncate_raises(self): sk = SigningKey.generate(curve=NIST192p) vk = sk.get_verifying_key() @@ -1223,8 +1225,10 @@ def test_sign_too_small_curve_dont_allow_truncate_raises(self): allow_truncate=False, ) - @pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES, - reason="system openssl does not support prime192v1") + @pytest.mark.skipif( + "prime192v1" not in OPENSSL_SUPPORTED_CURVES, + reason="system openssl does not support prime192v1", + ) def test_verify_too_small_curve_dont_allow_truncate_raises(self): sk = SigningKey.generate(curve=NIST192p) vk = sk.get_verifying_key() @@ -1245,7 +1249,6 @@ def test_verify_too_small_curve_dont_allow_truncate_raises(self): ) - class DER(unittest.TestCase): def test_integer(self): self.assertEqual(der.encode_integer(0), b("\x02\x01\x00"))