Skip to content

Commit

Permalink
Merge 73a245f into 8deb089
Browse files Browse the repository at this point in the history
  • Loading branch information
tomato42 committed Dec 2, 2019
2 parents 8deb089 + 73a245f commit 30ab359
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 22 deletions.
40 changes: 29 additions & 11 deletions src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,6 @@ def verify(self, signature, data, hashfunc=None,
:type sigdecode: callable
:raises BadSignatureError: if the signature is invalid or malformed
:raises BadDigestError: if the provided hash is too big for the curve
associated with this VerifyingKey
:return: True if the verification was successful
:rtype: bool
Expand All @@ -601,9 +599,10 @@ def verify(self, signature, data, hashfunc=None,

hashfunc = hashfunc or self.default_hashfunc
digest = hashfunc(data).digest()
return self.verify_digest(signature, digest, sigdecode)
return self.verify_digest(signature, digest, sigdecode, True)

def verify_digest(self, signature, digest, sigdecode=sigdecode_string):
def verify_digest(self, signature, digest, sigdecode=sigdecode_string,
allow_truncate=False):
"""
Verify a signature made over provided hash value.
Expand All @@ -623,17 +622,23 @@ def verify_digest(self, signature, digest, sigdecode=sigdecode_string):
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.
:raises BadSignatureError: if the signature is invalid or malformed
:raises BadDigestError: if the provided hash is too big for the curve
associated with this VerifyingKey
:raises BadDigestError: if the provided digest is too big for the curve
associated with this VerifyingKey and allow_truncate was not set
: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 allow_truncate:
digest = digest[:self.curve.baselen]
if len(digest) > self.curve.baselen:
raise BadDigestError("this curve (%s) is too short "
"for your digest (%d)" % (self.curve.name,
Expand Down Expand Up @@ -1017,11 +1022,11 @@ def sign_deterministic(self, data, hashfunc=None,

return self.sign_digest_deterministic(
digest, hashfunc=hashfunc, sigencode=sigencode,
extra_entropy=extra_entropy)
extra_entropy=extra_entropy, allow_truncate=True)

def sign_digest_deterministic(self, digest, hashfunc=None,
sigencode=sigencode_string,
extra_entropy=b''):
extra_entropy=b'', allow_truncate=False):
"""
Create signature for digest using the deterministic RFC6679 algorithm.
Expand Down Expand Up @@ -1050,6 +1055,10 @@ def sign_digest_deterministic(self, digest, hashfunc=None,
:param extra_entropy: additional data that will be fed into the random
number generator used in the RFC6979 process. Entirely optional.
:type extra_entropy: bytes like object
: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.
:return: encoded signature for the `digest` hash
:rtype: bytes or sigencode function dependant type
Expand All @@ -1068,7 +1077,10 @@ def simple_r_s(r, s, order):
self.curve.generator.order(), secexp, hashfunc, digest,
retry_gen=retry_gen, extra_entropy=extra_entropy)
try:
r, s, order = self.sign_digest(digest, sigencode=simple_r_s, k=k)
r, s, order = self.sign_digest(digest,
sigencode=simple_r_s,
k=k,
allow_truncate=allow_truncate)
break
except RSZeroError:
retry_gen += 1
Expand Down Expand Up @@ -1123,10 +1135,10 @@ def sign(self, data, entropy=None, hashfunc=None,
hashfunc = hashfunc or self.default_hashfunc
data = normalise_bytes(data)
h = hashfunc(data).digest()
return self.sign_digest(h, entropy, sigencode, k)
return self.sign_digest(h, entropy, sigencode, k, allow_truncate=True)

def sign_digest(self, digest, entropy=None, sigencode=sigencode_string,
k=None):
k=None, allow_truncate=False):
"""
Create signature over digest using the probabilistic ECDSA algorithm.
Expand All @@ -1152,6 +1164,10 @@ def sign_digest(self, digest, entropy=None, sigencode=sigencode_string,
: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.
:raises RSZeroError: in the unlikely event when "r" parameter or
"s" parameter is equal 0 as that would leak the key. Calee should
Expand All @@ -1161,6 +1177,8 @@ def sign_digest(self, digest, entropy=None, sigencode=sigencode_string,
:rtype: bytes or sigencode function dependant type
"""
digest = normalise_bytes(digest)
if allow_truncate:
digest = digest[:self.curve.baselen]
if len(digest) > self.curve.baselen:
raise BadDigestError("this curve (%s) is too short "
"for your digest (%d)" % (self.curve.name,
Expand Down
61 changes: 50 additions & 11 deletions src/ecdsa/test_pyecdsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import pytest
from binascii import hexlify, unhexlify
from hashlib import sha1, sha256, sha512
import hashlib
from functools import partial

from hypothesis import given
import hypothesis.strategies as st
Expand Down Expand Up @@ -708,15 +710,15 @@ class OpenSSL(unittest.TestCase):
run_openssl("ecparam -list_curves")
.split('\n'))

def get_openssl_messagedigest_arg(self):
def get_openssl_messagedigest_arg(self, hash_name):
v = run_openssl("version")
# e.g. "OpenSSL 1.0.0 29 Mar 2010", or "OpenSSL 1.0.0a 1 Jun 2010",
# or "OpenSSL 0.9.8o 01 Jun 2010"
vs = v.split()[1].split(".")
if vs >= ["1", "0", "0"]: # pragma: no cover
return "-SHA1"
return "-{0}".format(hash_name)
else: # pragma: no cover
return "-ecdsa-with-SHA1"
return "-ecdsa-with-{0}".format(hash_name)

# sk: 1:OpenSSL->python 2:python->OpenSSL
# vk: 3:OpenSSL->python 4:python->OpenSSL
Expand All @@ -727,6 +729,11 @@ def get_openssl_messagedigest_arg(self):
def test_from_openssl_nist192p(self):
return self.do_test_from_openssl(NIST192p)

@pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support prime192v1")
def test_from_openssl_nist192p_sha256(self):
return self.do_test_from_openssl(NIST192p, "SHA256")

@pytest.mark.skipif("secp224r1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support secp224r1")
def test_from_openssl_nist224p(self):
Expand All @@ -737,6 +744,16 @@ def test_from_openssl_nist224p(self):
def test_from_openssl_nist256p(self):
return self.do_test_from_openssl(NIST256p)

@pytest.mark.skipif("prime256v1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support prime256v1")
def test_from_openssl_nist256p_sha384(self):
return self.do_test_from_openssl(NIST256p, "SHA384")

@pytest.mark.skipif("prime256v1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support prime256v1")
def test_from_openssl_nist256p_sha512(self):
return self.do_test_from_openssl(NIST256p, "SHA512")

@pytest.mark.skipif("secp384r1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support secp384r1")
def test_from_openssl_nist384p(self):
Expand Down Expand Up @@ -787,12 +804,12 @@ def test_from_openssl_brainpoolp384r1(self):
def test_from_openssl_brainpoolp512r1(self):
return self.do_test_from_openssl(BRAINPOOLP512r1)

def do_test_from_openssl(self, curve):
def do_test_from_openssl(self, curve, hash_name="SHA1"):
curvename = curve.openssl_name
assert curvename
# OpenSSL: create sk, vk, sign.
# Python: read vk(3), checksig(5), read sk(1), sign, check
mdarg = self.get_openssl_messagedigest_arg()
mdarg = self.get_openssl_messagedigest_arg(hash_name)
if os.path.isdir("t"): # pragma: no cover
shutil.rmtree("t")
os.mkdir("t")
Expand All @@ -809,19 +826,30 @@ def do_test_from_openssl(self, curve):
with open("t/data.sig", "rb") as e:
sig_der = e.read()
self.assertTrue(vk.verify(sig_der, data, # 5
hashfunc=sha1, sigdecode=sigdecode_der))
hashfunc=partial(hashlib.new, hash_name),
sigdecode=sigdecode_der))

with open("t/privkey.pem") as e:
fp = e.read()
sk = SigningKey.from_pem(fp) # 1
sig = sk.sign(data)
self.assertTrue(vk.verify(sig, data))
sig = sk.sign(
data,
hashfunc=partial(hashlib.new, hash_name),
)
self.assertTrue(vk.verify(sig,
data,
hashfunc=partial(hashlib.new, hash_name)))

@pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support prime192v1")
def test_to_openssl_nist192p(self):
self.do_test_to_openssl(NIST192p)

@pytest.mark.skipif("prime192v1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support prime192v1")
def test_to_openssl_nist192p_sha256(self):
self.do_test_to_openssl(NIST192p, "SHA256")

@pytest.mark.skipif("secp224r1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support secp224r1")
def test_to_openssl_nist224p(self):
Expand All @@ -832,6 +860,16 @@ def test_to_openssl_nist224p(self):
def test_to_openssl_nist256p(self):
self.do_test_to_openssl(NIST256p)

@pytest.mark.skipif("prime256v1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support prime256v1")
def test_to_openssl_nist256p_sha384(self):
self.do_test_to_openssl(NIST256p, "SHA384")

@pytest.mark.skipif("prime256v1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support prime256v1")
def test_to_openssl_nist256p_sha512(self):
self.do_test_to_openssl(NIST256p, "SHA512")

@pytest.mark.skipif("secp384r1" not in OPENSSL_SUPPORTED_CURVES,
reason="system openssl does not support secp384r1")
def test_to_openssl_nist384p(self):
Expand Down Expand Up @@ -882,12 +920,12 @@ def test_to_openssl_brainpoolp384r1(self):
def test_to_openssl_brainpoolp512r1(self):
self.do_test_to_openssl(BRAINPOOLP512r1)

def do_test_to_openssl(self, curve):
def do_test_to_openssl(self, curve, hash_name="SHA1"):
curvename = curve.openssl_name
assert curvename
# Python: create sk, vk, sign.
# OpenSSL: read vk(4), checksig(6), read sk(2), sign, check
mdarg = self.get_openssl_messagedigest_arg()
mdarg = self.get_openssl_messagedigest_arg(hash_name)
if os.path.isdir("t"): # pragma: no cover
shutil.rmtree("t")
os.mkdir("t")
Expand All @@ -898,7 +936,8 @@ def do_test_to_openssl(self, curve):
e.write(vk.to_der()) # 4
with open("t/pubkey.pem", "wb") as e:
e.write(vk.to_pem()) # 4
sig_der = sk.sign(data, hashfunc=sha1, sigencode=sigencode_der)
sig_der = sk.sign(data, hashfunc=partial(hashlib.new, hash_name),
sigencode=sigencode_der)

with open("t/data.sig", "wb") as e:
e.write(sig_der) # 6
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ basepython=python3.8

[testenv:coverage]
sitepackages=True
whitelist_externals=coverage
commands = coverage run --branch -m pytest --hypothesis-show-statistics {posargs:src/ecdsa}

[testenv:speed]
Expand Down

0 comments on commit 30ab359

Please sign in to comment.