diff --git a/.gitignore b/.gitignore index 49bbeffb..05fbfd4a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ coverage-html .tox nosetests.xml t/ +.hypothesis/ # Translations *.mo diff --git a/.travis.yml b/.travis.yml index f2cbba52..8d1f6a89 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,23 +85,21 @@ 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 -t ecdsa -i 'test.*|.*_version' `which pytest` $files + instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental + git checkout $BRANCH + instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` src/ecdsa instrumental -f .instrumental.cov -sr fi - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST == "false" ]]; then - # exclude the super slow test_malformed_sigs.py, until #127 is merged - files="$(ls src/ecdsa/test*.py | grep -v test_malformed_sigs.py)" - instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files + instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` src/ecdsa 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 diff --git a/build-requirements-2.6.txt b/build-requirements-2.6.txt index 980e00df..9f39acd0 100644 --- a/build-requirements-2.6.txt +++ b/build-requirements-2.6.txt @@ -2,3 +2,4 @@ tox coveralls<1.3.0 idna<2.8 unittest2 +hypothesis<3 diff --git a/build-requirements-3.3.txt b/build-requirements-3.3.txt index 68f8c292..28dd401b 100644 --- a/build-requirements-3.3.txt +++ b/build-requirements-3.3.txt @@ -3,3 +3,5 @@ pluggy<0.6 tox<3 wheel<0.30 virtualenv==15.2.0 +enum34 +hypothesis<3.44 diff --git a/build-requirements.txt b/build-requirements.txt index 6918d2e8..072f7a9d 100644 --- a/build-requirements.txt +++ b/build-requirements.txt @@ -1,2 +1,4 @@ tox python-coveralls +hypothesis +pytest>=4.6.0 diff --git a/src/ecdsa/curves.py b/src/ecdsa/curves.py index b61f4b45..2555d938 100644 --- a/src/ecdsa/curves.py +++ b/src/ecdsa/curves.py @@ -8,14 +8,13 @@ # will need to mark it as deprecated later __all__ = ["UnknownCurveError", "orderlen", "Curve", "NIST192p", "NIST224p", "NIST256p", "NIST384p", "NIST521p", "curves", - "find_curve"] + "find_curve", "SECP256k1"] class UnknownCurveError(Exception): pass -# the NIST curves class Curve: def __init__(self, name, curve, generator, oid, openssl_name=None): self.name = name @@ -29,25 +28,41 @@ def __init__(self, name, curve, generator, oid, openssl_name=None): self.oid = oid self.encoded_oid = der.encode_oid(*oid) + def __repr__(self): + return self.name + + +# the NIST curves NIST192p = Curve("NIST192p", ecdsa.curve_192, ecdsa.generator_192, (1, 2, 840, 10045, 3, 1, 1), "prime192v1") + + NIST224p = Curve("NIST224p", ecdsa.curve_224, ecdsa.generator_224, (1, 3, 132, 0, 33), "secp224r1") + + NIST256p = Curve("NIST256p", ecdsa.curve_256, ecdsa.generator_256, (1, 2, 840, 10045, 3, 1, 7), "prime256v1") + + NIST384p = Curve("NIST384p", ecdsa.curve_384, ecdsa.generator_384, (1, 3, 132, 0, 34), "secp384r1") + + NIST521p = Curve("NIST521p", ecdsa.curve_521, ecdsa.generator_521, (1, 3, 132, 0, 35), "secp521r1") + + SECP256k1 = Curve("SECP256k1", ecdsa.curve_secp256k1, ecdsa.generator_secp256k1, (1, 3, 132, 0, 10), "secp256k1") + curves = [NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1] diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 4f926429..b0898411 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -125,6 +125,11 @@ def __init__(self, _error__please_use_generate=None): self.default_hashfunc = None self.pubkey = None + def __repr__(self): + pub_key = self.to_string("compressed") + return "VerifyingKey.from_string({0!r}, {1!r}, {2})".format( + pub_key, self.curve, self.default_hashfunc().name) + @classmethod def from_public_point(cls, point, curve=NIST192p, hashfunc=sha1): """ diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index fffb1b3b..0a181a59 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -1,51 +1,123 @@ from __future__ import with_statement, division -import pytest import hashlib +try: + from hashlib import algorithms_available +except ImportError: + algorithms_available = [ + "md5", "sha1", "sha224", "sha256", "sha384", "sha512"] +from functools import partial +import pytest +import sys +from six import binary_type +import hypothesis.strategies as st +from hypothesis import note, assume, given, settings -from six import b, binary_type -from .keys import SigningKey, VerifyingKey +from .keys import SigningKey from .keys import BadSignatureError from .util import sigencode_der, sigencode_string from .util import sigdecode_der, sigdecode_string -from .curves import curves, NIST256p, NIST521p +from .curves import curves, NIST256p -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 +example_data = b"some data to sign" +"""Since the data is hashed for processing, really any string will do.""" -#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<= (2, 7): + # deadline=2s because NIST521p are slow to verify + params["deadline"] = 2000 + + +@settings(**params) +@given(st_fuzzed_sig()) +def test_hyp_fuzzed_der_signatures(params): + verifying_key, sig = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) #### diff --git a/tox.ini b/tox.ini index a10f3965..4f31ae61 100644 --- a/tox.ini +++ b/tox.ini @@ -6,9 +6,14 @@ envlist = py26, py27, py33, py34, py35, py36, py37, py38, py, pypy, pypy3 deps = py{33}: py<1.5 py{33}: pytest<3.3 + py{33}: enum34 + py{33}: hypothesis<3.44 py{26}: unittest2 + py{26}: hypothesis<3 py{26,27,34,35,36,37,38,py,py3}: pytest + py{27,34,35,36,37,38,py,py3}: hypothesis py: pytest + py: hypothesis py{33}: wheel<0.30 coverage commands = coverage run --branch -m pytest {posargs:src/ecdsa}