summaryrefslogtreecommitdiff
path: root/passlib/tests
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-17 22:00:18 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-17 22:00:18 -0400
commit0cade3487dea5d8117b9f5e045c9fd9425778aec (patch)
tree54e7d30de4df5f89db7d316838176d126edec0ed /passlib/tests
parent2ad8463a456796300df5340a2bc511e290e62072 (diff)
downloadpasslib-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.py97
-rw-r--r--passlib/tests/utils.py62
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)"