Skip to content

Commit

Permalink
Merge 493d762 into 4c92d31
Browse files Browse the repository at this point in the history
  • Loading branch information
tomato42 committed Oct 26, 2019
2 parents 4c92d31 + 493d762 commit 7e8c5bd
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ coverage-html
.tox
nosetests.xml
t/
.hypothesis/

# Translations
*.mo
Expand Down
1 change: 1 addition & 0 deletions build-requirements-2.6.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ tox
coveralls<1.3.0
idna<2.8
unittest2
hypothesis<3
2 changes: 2 additions & 0 deletions build-requirements-3.3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ pluggy<0.6
tox<3
wheel<0.30
virtualenv==15.2.0
enum34
hypothesis<3.44
2 changes: 2 additions & 0 deletions build-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
tox
python-coveralls
hypothesis
pytest>=4.6.0
19 changes: 17 additions & 2 deletions src/ecdsa/curves.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]


Expand Down
5 changes: 5 additions & 0 deletions src/ecdsa/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
139 changes: 102 additions & 37 deletions src/ecdsa/test_malformed_sigs.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,116 @@
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
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<<i for i in range(8)):
der_sigs.append(pytest.param(
key.verifying_key, hash_alg,
signature, pos, xor,
id="{0}-{1}-pos-{2}-xor-{3}".format(
curve.name, hash_alg, pos, xor)))


@pytest.mark.parametrize("verifying_key,hash_alg,signature,pos,xor", der_sigs)
def test_fuzzed_der_signatures(verifying_key, hash_alg, signature, pos, xor):
# check if a malformed DER encoded signature causes the same exception
# to be raised irrespective of the type of error
sig = bytearray(signature)
sig[pos] ^= xor
sig = binary_type(sig)

try:
verifying_key.verify(sig, example_data, getattr(hashlib, hash_alg),
sigdecode_der)
assert False
except BadSignatureError:
assert True
hash_and_size = [(name, hashlib.new(name).digest_size)
for name in algorithms_available]
"""Pairs of hash names and their output sizes.
Needed for pairing with curves as we don't support hashes
bigger than order sizes of curves."""


keys_and_sigs = []
"""Name of the curve+hash combination, VerifyingKey and DER signature."""


# for hypothesis strategy shrinking we want smallest curves and hashes first
for curve in sorted(curves, key=lambda x: x.baselen):
for hash_alg in [name for name, size in
sorted(hash_and_size, key=lambda x: x[1])
if 0 < size <= curve.baselen]:
sk = SigningKey.generate(
curve,
hashfunc=partial(hashlib.new, hash_alg))

sig = sk.sign(example_data, sigencode=sigencode_der)
keys_and_sigs.append(
("{0} {1}".format(curve, hash_alg),
sk.verifying_key, sig))


# first make sure that the signatures can be verified
@pytest.mark.parametrize(
"verifying_key,signature",
[pytest.param(vk, sig, id=name) for name, vk, sig in keys_and_sigs])
def test_signatures(verifying_key, signature):
assert verifying_key.verify(signature, example_data,
sigdecode=sigdecode_der)


@st.composite
def st_fuzzed_sig(draw):
"""
Hypothesis strategy that generates pairs of VerifyingKey and malformed
signatures created by fuzzing of a valid signature.
"""
name, verifying_key, old_sig = draw(st.sampled_from(keys_and_sigs))
note("Configuration: {0}".format(name))

sig = bytearray(old_sig)

# decide which bytes should be removed
to_remove = draw(st.lists(
st.integers(min_value=0, max_value=len(sig)-1),
unique=True))
to_remove.sort()
for i in reversed(to_remove):
del sig[i]
note("Remove bytes: {0}".format(to_remove))

# decide which bytes of the original signature should be changed
xors = draw(st.dictionaries(
st.integers(min_value=0, max_value=len(sig)-1),
st.integers(min_value=1, max_value=255)))
for i, val in xors.items():
sig[i] ^= val
note("xors: {0}".format(xors))

# decide where new data should be inserted
insert_pos = draw(st.integers(min_value=0, max_value=len(sig)))
insert_data = draw(st.binary())

sig = sig[:insert_pos] + insert_data + sig[insert_pos:]
note("Inserted at position {0} bytes: {1!r}"
.format(insert_pos, insert_data))

sig = bytes(sig)
# make sure that there was performed at least one mutation on the data
assume(to_remove or xors or insert_data)
# and that the mutations didn't cancel each-other out
assume(sig != old_sig)

return verifying_key, sig


# deadline=2s because NIST521p are slow to verify
@settings(deadline=2000)
@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)


####
Expand Down
5 changes: 5 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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{36}: hypothesis<3
py{26,27,34,35,36,37,38,py,py3}: pytest
py{26,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}
Expand Down

0 comments on commit 7e8c5bd

Please sign in to comment.