diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2012-04-17 22:00:18 -0400 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2012-04-17 22:00:18 -0400 |
| commit | 0cade3487dea5d8117b9f5e045c9fd9425778aec (patch) | |
| tree | 54e7d30de4df5f89db7d316838176d126edec0ed /passlib/tests | |
| parent | 2ad8463a456796300df5340a2bc511e290e62072 (diff) | |
| download | passlib-0cade3487dea5d8117b9f5e045c9fd9425778aec.tar.gz | |
reworked fuzz verifier system, so that we can skip $2x$ hashes during bcrypt crypt() testing
Diffstat (limited to 'passlib/tests')
| -rw-r--r-- | passlib/tests/test_handlers.py | 97 | ||||
| -rw-r--r-- | passlib/tests/utils.py | 62 |
2 files changed, 93 insertions, 66 deletions
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 102a3dc..f566b4f 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -202,49 +202,53 @@ class _bcrypt_test(HandlerCase): #=============================================================== # fuzz testing #=============================================================== - def get_fuzz_verifiers(self): - verifiers = super(_bcrypt_test, self).get_fuzz_verifiers() - - # test other backends against py-bcrypt if available + def os_supports_ident(self, hash): + "check if OS crypt is expected to support given ident" + # most OSes won't support 2x/2y + # XXX: definitely not the BSDs, but what about the linux variants? + if hash.startswith("$2x$") or hash.startswith("$2y$"): + return False + return True + + def fuzz_verifier_pybcrypt(self): + # test against py-bcrypt if available from passlib.utils import to_native_str try: from bcrypt import hashpw except ImportError: - pass - else: - def check_pybcrypt(secret, hash): - "pybcrypt" - secret = to_native_str(secret, self.fuzz_password_encoding) - if hash.startswith("$2y$"): - hash = "$2a$" + hash[4:] - try: - return hashpw(secret, hash) == hash - except ValueError: - raise ValueError("py-bcrypt rejected hash: %r" % (hash,)) - verifiers.append(check_pybcrypt) - - # test other backends against bcryptor if available + return + def check_pybcrypt(secret, hash): + "pybcrypt" + secret = to_native_str(secret, self.fuzz_password_encoding) + if hash.startswith("$2y$"): + hash = "$2a$" + hash[4:] + try: + return hashpw(secret, hash) == hash + except ValueError: + raise ValueError("py-bcrypt rejected hash: %r" % (hash,)) + return check_pybcrypt + + def fuzz_verifier_bcryptor(self): + # test against bcryptor if available + from passlib.utils import to_native_str try: from bcryptor.engine import Engine except ImportError: - pass - else: - def check_bcryptor(secret, hash): - "bcryptor" - secret = to_native_str(secret, self.fuzz_password_encoding) - if hash.startswith("$2y$"): - hash = "$2a$" + hash[4:] - elif hash.startswith("$2$"): - # bcryptor doesn't support $2$ hashes; but we can fake it - # using the $2a$ algorithm, by repeating the password until - # it's 72 chars in length. - hash = "$2a$" + hash[3:] - if secret: - secret = repeat_string(secret, 72) - return Engine(False).hash_key(secret, hash) == hash - verifiers.append(check_bcryptor) - - return verifiers + return + def check_bcryptor(secret, hash): + "bcryptor" + secret = to_native_str(secret, self.fuzz_password_encoding) + if hash.startswith("$2y$"): + hash = "$2a$" + hash[4:] + elif hash.startswith("$2$"): + # bcryptor doesn't support $2$ hashes; but we can fake it + # using the $2a$ algorithm, by repeating the password until + # it's 72 chars in length. + hash = "$2a$" + hash[3:] + if secret: + secret = repeat_string(secret, 72) + return Engine(False).hash_key(secret, hash) == hash + return check_bcryptor def get_fuzz_rounds(self): # decrease default rounds for fuzz testing to speed up volume. @@ -255,6 +259,8 @@ class _bcrypt_test(HandlerCase): if ident == u("$2x$"): # just recognized, not currently supported. return None + if self.backend == "os_crypt" and not self.using_patched_crypt and not self.os_supports_ident(ident): + return None return ident #=============================================================== @@ -672,18 +678,15 @@ class _DjangoHelper(object): # NOTE: not testing against Django < 1.0 since it doesn't support # most of these hash formats. - def get_fuzz_verifiers(self): - verifiers = super(_DjangoHelper, self).get_fuzz_verifiers() - + def fuzz_verifier_django(self): from passlib.tests.test_ext_django import has_django1 - if has_django1: - from django.contrib.auth.models import check_password - def verify_django(secret, hash): - "django check_password()" - return check_password(secret, hash) - verifiers.append(verify_django) - - return verifiers + if not has_django1: + return None + from django.contrib.auth.models import check_password + def verify_django(secret, hash): + "django check_password()" + return check_password(secret, hash) + return verify_django def test_90_django_reference(self): "run known correct hashes through Django's check_password()" diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py index ba439b7..7d7b205 100644 --- a/passlib/tests/utils.py +++ b/passlib/tests/utils.py @@ -1636,12 +1636,15 @@ class HandlerCase(TestCase): # run through all verifiers we found. for verify in verifiers: name = vname(verify) - - if not verify(secret, hash, **ctx): + result = verify(secret, hash, **ctx) + if result == "skip": # let verifiers signal lack of support + continue + assert result is True or result is False + if not result: raise self.failureException("failed to verify against %s: " "secret=%r config=%r hash=%r" % (name, secret, kwds, hash)) - # occasionally check that some other secret WON'T verify + # occasionally check that some other secrets WON'T verify # against this hash. if rng.random() < .1 and verify(other, hash, **ctx): raise self.failureException("was able to verify wrong " @@ -1663,13 +1666,16 @@ class HandlerCase(TestCase): handler = self.handler verifiers = [] - # test against self - def check_default(secret, hash, **ctx): - "self" - return self.do_verify(secret, hash, **ctx) - verifiers.append(check_default) + # call all methods starting with prefix in order to create + # any verifiers. + prefix = "fuzz_verifier_" + for name in dir(self): + if name.startswith(prefix): + func = getattr(self, name)() + if func is not None: + verifiers.append(func) - # test against any other available backends + # create verifiers for any other available backends if hasattr(handler, "backends") and enable_option("all-backends"): def maker(backend): def func(secret, hash): @@ -1679,23 +1685,41 @@ class HandlerCase(TestCase): func.__doc__ = backend + "-backend" return func cur = handler.get_backend() - check_default.__doc__ = cur + "-backend" for backend in handler.backends: if backend != cur and handler.has_backend(backend): verifiers.append(maker(backend)) + return verifiers + + def fuzz_verifier_default(self): + # test against self + def check_default(secret, hash, **ctx): + return self.do_verify(secret, hash, **ctx) + if self.backend: + check_default.__doc__ = self.backend + "-backend" + else: + check_default.__doc__ = "self" + return check_default + + def os_supports_ident(self, ident): + "skip verifier_crypt when OS doesn't support ident" + return True + + def fuzz_verifier_crypt(self): # test againt OS crypt() # NOTE: skipping this if using_patched_crypt since _has_crypt_support() # will return false positive in that case. - if not self.using_patched_crypt and _has_crypt_support(handler): - from crypt import crypt - def check_crypt(secret, hash): - "stdlib-crypt" - secret = to_native_str(secret, self.fuzz_password_encoding) - return crypt(secret, hash) == hash - verifiers.append(check_crypt) - - return verifiers + handler = self.handler + if self.using_patched_crypt or not _has_crypt_support(handler): + return None + from crypt import crypt + def check_crypt(secret, hash): + "stdlib-crypt" + if not self.os_supports_ident(hash): + return "skip" + secret = to_native_str(secret, self.fuzz_password_encoding) + return crypt(secret, hash) == hash + return check_crypt def get_fuzz_password(self): "generate random passwords (for fuzz testing)" |
