diff options
Diffstat (limited to 'freezed_deps/ecdsa/test_malformed_sigs.py')
-rw-r--r-- | freezed_deps/ecdsa/test_malformed_sigs.py | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/freezed_deps/ecdsa/test_malformed_sigs.py b/freezed_deps/ecdsa/test_malformed_sigs.py new file mode 100644 index 0000000..c1dca44 --- /dev/null +++ b/freezed_deps/ecdsa/test_malformed_sigs.py @@ -0,0 +1,306 @@ +from __future__ import with_statement, division + +import hashlib +try: + from hashlib import algorithms_available +except ImportError: # pragma: no cover + 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, example + +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 +from .der import encode_integer, encode_bitstring, encode_octet_string, \ + encode_oid, encode_sequence, encode_constructed + + +example_data = b"some data to sign" +"""Since the data is hashed for processing, really any string will do.""" + + +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)) + + keys_and_sigs.append( + ("{0} {1}".format(curve, hash_alg), + sk.verifying_key, + sk.sign(example_data, sigencode=sigencode_der))) + + +# first make sure that the signatures can be verified + "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) + + +def st_fuzzed_sig(draw, keys_and_sigs): + """ + 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 + if sig: # pragma: no branch + 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))) + # NIST521p signature is about 140 bytes long, test slightly longer + insert_data = draw(st.binary(max_size=256)) + + 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 + + +params = {} +# not supported in hypothesis 2.0.0 +if sys.version_info >= (2, 7): # pragma: no branch + from hypothesis import HealthCheck + # deadline=5s because NIST521p are slow to verify + params["deadline"] = 5000 + params["suppress_health_check"] = [HealthCheck.data_too_large, + HealthCheck.filter_too_much, + HealthCheck.too_slow] + +slow_params = dict(params) +slow_params["max_examples"] = 10 + + +@settings(**params) +@given(st_fuzzed_sig(keys_and_sigs)) +def test_fuzzed_der_signatures(args): + verifying_key, sig = args + + with pytest.raises(BadSignatureError): + verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) + + +def st_random_der_ecdsa_sig_value(draw): + """ + Hypothesis strategy for selecting random values and encoding them + to ECDSA-Sig-Value object:: + + ECDSA-Sig-Value ::= SEQUENCE { + r INTEGER, + s INTEGER + } + """ + name, verifying_key, _ = draw(st.sampled_from(keys_and_sigs)) + note("Configuration: {0}".format(name)) + order = int(verifying_key.curve.order) + + # the encode_integer doesn't suport negative numbers, would be nice + # to generate them too, but we have coverage for remove_integer() + # verifying that it doesn't accept them, so meh. + # Test all numbers around the ones that can show up (around order) + # way smaller and slightly bigger + r = draw(st.integers(min_value=0, max_value=order << 4) | + st.integers(min_value=order >> 2, max_value=order+1)) + s = draw(st.integers(min_value=0, max_value=order << 4) | + st.integers(min_value=order >> 2, max_value=order+1)) + + sig = encode_sequence(encode_integer(r), encode_integer(s)) + + return verifying_key, sig + + +@settings(**slow_params) +@given(st_random_der_ecdsa_sig_value()) +def test_random_der_ecdsa_sig_value(params): + """ + Check if random values encoded in ECDSA-Sig-Value structure are rejected + as signature. + """ + verifying_key, sig = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) + + +def st_der_integer(*args, **kwargs): + """ + Hypothesis strategy that returns a random positive integer as DER + INTEGER. + Parameters are passed to hypothesis.strategy.integer. + """ + if "min_value" not in kwargs: # pragma: no branch + kwargs["min_value"] = 0 + return st.builds(encode_integer, st.integers(*args, **kwargs)) + + +def st_der_bit_string(draw, *args, **kwargs): + """ + Hypothesis strategy that returns a random DER BIT STRING. + Parameters are passed to hypothesis.strategy.binary. + """ + data = draw(st.binary(*args, **kwargs)) + if data: + unused = draw(st.integers(min_value=0, max_value=7)) + data = bytearray(data) + data[-1] &= - (2**unused) + data = bytes(data) + else: + unused = 0 + return encode_bitstring(data, unused) + + +def st_der_octet_string(*args, **kwargs): + """ + Hypothesis strategy that returns a random DER OCTET STRING object. + Parameters are passed to hypothesis.strategy.binary + """ + return st.builds(encode_octet_string, st.binary(*args, **kwargs)) + + +def st_der_null(): + """ + Hypothesis strategy that returns DER NULL object. + """ + return st.just(b'\x05\x00') + + +def st_der_oid(draw): + """ + Hypothesis strategy that returns DER OBJECT IDENTIFIER objects. + """ + first = draw(st.integers(min_value=0, max_value=2)) + if first < 2: + second = draw(st.integers(min_value=0, max_value=39)) + else: + second = draw(st.integers(min_value=0, max_value=2**512)) + rest = draw(st.lists(st.integers(min_value=0, max_value=2**512), + max_size=50)) + return encode_oid(first, second, *rest) + + +def st_der(): + """ + Hypothesis strategy that returns random DER structures. + + A valid DER structure is any primitive object, an octet encoding + of a valid DER structure, sequence of valid DER objects or a constructed + encoding of any of the above. + """ + return st.recursive( + st.just(b'') | st_der_integer(max_value=2**4096) | + st_der_bit_string(max_size=1024**2) | + st_der_octet_string(max_size=1024**2) | st_der_null() | st_der_oid(), + lambda children: + st.builds(lambda x: encode_octet_string(x), st.one_of(children)) | + st.builds(lambda x: encode_bitstring(x, 0), st.one_of(children)) | + st.builds(lambda x: encode_sequence(*x), + st.lists(children, max_size=200)) | + st.builds(lambda tag, x: + encode_constructed(tag, x), + st.integers(min_value=0, max_value=0x3f), + st.one_of(children)), + max_leaves=40 + ) + + +@settings(**params) +@given(st.sampled_from(keys_and_sigs), st_der()) +def test_random_der_as_signature(params, der): + """Check if random DER structures are rejected as signature""" + name, verifying_key, _ = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(der, example_data, sigdecode=sigdecode_der) + + +@settings(**params) +@given(st.sampled_from(keys_and_sigs), st.binary(max_size=1024**2)) +@example( + keys_and_sigs[0], + encode_sequence(encode_integer(0), encode_integer(0))) +@example( + keys_and_sigs[0], + encode_sequence(encode_integer(1), encode_integer(1)) + b'\x00') +@example( + keys_and_sigs[0], + encode_sequence(*[encode_integer(1)] * 3)) +def test_random_bytes_as_signature(params, der): + """Check if random bytes are rejected as signature""" + name, verifying_key, _ = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(der, example_data, sigdecode=sigdecode_der) + + +keys_and_string_sigs = [ + (name, verifying_key, + sigencode_string(*sigdecode_der(sig, verifying_key.curve.order), + order=verifying_key.curve.order)) + for name, verifying_key, sig in keys_and_sigs] +""" +Name of the curve+hash combination, VerifyingKey and signature as a +byte string. +""" + + +@settings(**params) +@given(st_fuzzed_sig(keys_and_string_sigs)) +def test_fuzzed_string_signatures(params): + verifying_key, sig = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(sig, example_data, sigdecode=sigdecode_string) |