summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-05-01 15:39:09 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-05-01 15:39:09 -0400
commitcb2217e45f43cbcc53dcb90683a20a5ce761bc14 (patch)
tree0ff2eccecf2af6dc3112c374ba0e72743caff7aa
parent5528b806944f86e28bb6a6d3c87fed56a92eddb2 (diff)
downloadpasslib-cb2217e45f43cbcc53dcb90683a20a5ce761bc14.tar.gz
misc testing fixes
* bcrypt fuzz fix: detect $2$ hashes w/ passwords like 'abc' 'abcabc' and regenerate, since $2$ would hash them the same. one in a million chance, but I hit it. * django_bcrypt: skip multi-ident testing entirely, only one prefix is used. * get_fuzz_settings() now handles passwords too, so handlers can alter them * get_fuzz_ident() now checks os_supports_ident() directly, so bcrypt test doesn't have to * test_14_salt_chars now cleans up salt, so bcrypt test doesn't have to * glitch in fuzz skip code * add tox.ini to sdist
-rw-r--r--MANIFEST.in3
-rw-r--r--passlib/tests/test_handlers.py71
-rw-r--r--passlib/tests/tox_support.py4
-rw-r--r--passlib/tests/utils.py48
-rw-r--r--tox.ini7
5 files changed, 66 insertions, 67 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index f29430e..902f79d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,3 @@
recursive-include docs *
-include LICENSE README CHANGES passlib/*.cfg passlib/tests/*.cfg
-prune docs/_build
+include LICENSE README CHANGES passlib/*.cfg passlib/tests/*.cfg tox.ini setup.cfg
prune *.komodoproject
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index dac8849..f568b49 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -207,14 +207,8 @@ class _bcrypt_test(HandlerCase):
# builtin is still just way too slow.
if self.backend == "builtin":
kwds.setdefault("rounds", 4)
-
super(_bcrypt_test, self).populate_settings(kwds)
- # correct unused bits in provided salts, to silence some warnings.
- if 'salt' in kwds and len(kwds['salt']) == 22:
- from passlib.utils import bcrypt64
- kwds['salt'] = bcrypt64.repair_unused(kwds['salt'])
-
#===============================================================
# fuzz testing
#===============================================================
@@ -224,12 +218,14 @@ class _bcrypt_test(HandlerCase):
return True
# 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$"):
+ from passlib.handlers.bcrypt import IDENT_2X, IDENT_2Y
+ if hash.startswith(IDENT_2X) or hash.startswith(IDENT_2Y):
return False
return True
def fuzz_verifier_pybcrypt(self):
# test against py-bcrypt if available
+ from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2X, IDENT_2Y
from passlib.utils import to_native_str
try:
from bcrypt import hashpw
@@ -238,8 +234,8 @@ class _bcrypt_test(HandlerCase):
def check_pybcrypt(secret, hash):
"pybcrypt"
secret = to_native_str(secret, self.fuzz_password_encoding)
- if hash.startswith("$2y$"):
- hash = "$2a$" + hash[4:]
+ if hash.startswith(IDENT_2Y):
+ hash = IDENT_2A + hash[4:]
try:
return hashpw(secret, hash) == hash
except ValueError:
@@ -248,6 +244,7 @@ class _bcrypt_test(HandlerCase):
def fuzz_verifier_bcryptor(self):
# test against bcryptor if available
+ from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2Y
from passlib.utils import to_native_str
try:
from bcryptor.engine import Engine
@@ -256,31 +253,36 @@ class _bcrypt_test(HandlerCase):
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$"):
+ if hash.startswith(IDENT_2Y):
+ hash = IDENT_2A + hash[4:]
+ elif hash.startswith(IDENT_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:]
+ hash = IDENT_2A + hash[3:]
if secret:
secret = repeat_string(secret, 72)
return Engine(False).hash_key(secret, hash) == hash
return check_bcryptor
+ def get_fuzz_settings(self):
+ secret, other, kwds = super(_bcrypt_test,self).get_fuzz_settings()
+ from passlib.handlers.bcrypt import IDENT_2, IDENT_2X
+ from passlib.utils import to_bytes
+ ident = kwds.get('ident')
+ if ident == IDENT_2X:
+ # 2x is just recognized, not supported. don't test with it.
+ del kwds['ident']
+ elif ident == IDENT_2 and repeat_string(to_bytes(other), len(to_bytes(secret))) == to_bytes(secret):
+ # avoid false failure due to flaw in 0-revision bcrypt:
+ # repeated strings like 'abc' and 'abcabc' hash identically.
+ other = self.get_fuzz_password()
+ return secret, other, kwds
+
def get_fuzz_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return randintgauss(5, 8, 6, 1)
- def get_fuzz_ident(self):
- ident = super(_bcrypt_test,self).get_fuzz_ident()
- 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
-
#===============================================================
# custom tests
#===============================================================
@@ -934,31 +936,18 @@ class django_bcrypt_test(HandlerCase, _DjangoHelper):
# NOTE: the following have been cloned from _bcrypt_test()
- def do_genconfig(self, **kwds):
- # override default to speed up tests
- kwds.setdefault("rounds", 5)
-
- # correct unused bits in provided salts, to silence some warnings.
- if 'salt' in kwds:
- from passlib.utils import bcrypt64
- kwds['salt'] = bcrypt64.repair_unused(kwds['salt'])
- return self.handler.genconfig(**kwds)
-
- def do_encrypt(self, secret, **kwds):
- # override default to speed up tests
- kwds.setdefault("rounds", 5)
- return self.handler.encrypt(secret, **kwds)
+ def populate_settings(self, kwds):
+ # speed up test w/ lower rounds
+ kwds.setdefault("rounds", 4)
+ super(django_bcrypt_test, self).populate_settings(kwds)
def get_fuzz_rounds(self):
# decrease default rounds for fuzz testing to speed up volume.
return randintgauss(5, 8, 6, 1)
def get_fuzz_ident(self):
- ident = super(django_bcrypt_test,self).get_fuzz_ident()
- if ident == u("$2x$"):
- # just recognized, not currently supported.
- return None
- return ident
+ # omit multi-ident tests, only $2a$ counts for this class
+ return None
django_bcrypt_test = skipUnless(hash.bcrypt.has_backend(),
"no bcrypt backends available")(django_bcrypt_test)
diff --git a/passlib/tests/tox_support.py b/passlib/tests/tox_support.py
index ac375e5..b5c80b7 100644
--- a/passlib/tests/tox_support.py
+++ b/passlib/tests/tox_support.py
@@ -22,7 +22,7 @@ __all__ = [
#=============================================================================
# main
#=============================================================================
-TH_PATH = "passlib/tests/test_handlers.py"
+TH_PATH = "passlib.tests.test_handlers"
def do_hash_tests(*args):
"return list of hash algorithm tests that match regexes"
@@ -48,7 +48,7 @@ def do_preset_tests(name):
if name == "django" or name == "django-hashes":
do_hash_tests("django_.*_test", "hex_md5_test")
if name == "django":
- print_("passlib/tests/test_ext_django.py")
+ print_("passlib.tests.test_ext_django")
else:
raise ValueError("unknown name: %r" % name)
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index 4c30b20..5c62dce 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -1128,6 +1128,13 @@ class HandlerCase(TestCase):
c3 = self.do_genconfig(salt=s1[:-1])
self.assertNotEqual(c3, c1)
+ def prepare_salt(self, salt):
+ "prepare generated salt"
+ if self.handler.name in ["bcrypt", "django_bcrypt"]:
+ from passlib.utils import bcrypt64
+ salt = bcrypt64.repair_unused(salt)
+ return salt
+
def test_14_salt_chars(self):
"test genconfig() honors salt_chars"
self.require_salt_info()
@@ -1144,6 +1151,7 @@ class HandlerCase(TestCase):
salt = cs[i:i+chunk]
if len(salt) < mn:
salt = (salt*(mn//len(salt)+1))[:chunk]
+ salt = self.prepare_salt(salt)
self.do_genconfig(salt=salt)
# check some invalid salt chars, make sure they're rejected
@@ -1680,7 +1688,7 @@ class HandlerCase(TestCase):
disabled = self.is_disabled_handler
max_time = self.max_fuzz_time
if max_time <= 0:
- raise self.skipTest("disabled for this test mode")
+ raise self.skipTest("disabled by test mode")
verifiers = self.get_fuzz_verifiers()
def vname(v):
return (v.__doc__ or v.__name__).splitlines()[0]
@@ -1690,8 +1698,7 @@ class HandlerCase(TestCase):
count = 0
while tick() <= stop:
# generate random password & options
- secret, other = self.get_fuzz_password_pair()
- kwds = self.get_fuzz_settings()
+ secret, other, kwds = self.get_fuzz_settings()
ctx = dict((k,kwds[k]) for k in handler.context_kwds if k in kwds)
# create new hash
@@ -1712,10 +1719,12 @@ class HandlerCase(TestCase):
(name, secret, kwds, hash))
# 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 "
- "password using %s: wrong_secret=%r real_secret=%r "
- "config=%r hash=%r" % (name, other, secret, kwds, hash))
+ if rng.random() < .1:
+ result = verify(other, hash, **ctx)
+ if result and result != "skip":
+ raise self.failureException("was able to verify wrong "
+ "password using %s: wrong_secret=%r real_secret=%r "
+ "config=%r hash=%r" % (name, other, secret, kwds, hash))
count +=1
log.debug("fuzz test: %r checked %d passwords against %d verifiers (%s)",
@@ -1818,28 +1827,26 @@ class HandlerCase(TestCase):
return secret, other
def get_fuzz_settings(self):
- "generate random settings (for fuzz testing)"
+ "generate random password and options for fuzz testing"
kwds = {}
for name in self.fuzz_settings:
func = getattr(self, "get_fuzz_" + name)
value = func()
if value is not None:
kwds[name] = value
- return kwds
+ secret, other = self.get_fuzz_password_pair()
+ return secret, other, kwds
def get_fuzz_rounds(self):
handler = self.handler
if not has_rounds_info(handler):
return None
default = handler.default_rounds or handler.min_rounds
+ lower = handler.min_rounds
if handler.rounds_cost == "log2":
- lower = max(default-1, handler.min_rounds)
upper = default
else:
- lower = handler.min_rounds #max(default*.5, handler.min_rounds)
upper = min(default*2, handler.max_rounds)
- if TEST_MODE(max="quick"):
- upper = min(2, lower)
return randintgauss(lower, upper, default, default*.5)
def get_fuzz_salt_size(self):
@@ -1853,11 +1860,16 @@ class HandlerCase(TestCase):
def get_fuzz_ident(self):
handler = self.handler
- if 'ident' in handler.setting_kwds and hasattr(handler, "ident_values"):
- if rng.random() < .5:
- # resolve wrappers before reading values
- handler = getattr(handler, "wrapped", handler)
- return rng.choice(handler.ident_values)
+ if 'ident' not in handler.setting_kwds or not hasattr(handler, "ident_values"):
+ return None
+ if rng.random() < .5:
+ return None
+ # resolve wrappers before reading values
+ handler = getattr(handler, "wrapped", handler)
+ ident = rng.choice(handler.ident_values)
+ if self.backend == "os_crypt" and not self.using_patched_crypt and not self.os_supports_ident(ident):
+ return None
+ return ident
#=========================================================
# eoc
diff --git a/tox.ini b/tox.ini
index 7ec8464..e51cf73 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
#===========================================================================
-# NOTES
-# =====
+# Passlib configuration for TOX
+# =============================
#
# PASSLIB_TEST_MODE:
#
@@ -36,7 +36,7 @@
# global config
#===========================================================================
[tox]
-minversion=1.0
+minversion=1.3
envlist = py27,py32,py25,py26,py31,pypy15,pypy16,pypy17,pypy18,jython,gae25,gae27
#===========================================================================
@@ -45,7 +45,6 @@ envlist = py27,py32,py25,py26,py31,pypy15,pypy16,pypy17,pypy18,jython,gae25,gae2
[testenv]
setenv =
PASSLIB_TEST_MODE = full
- PASSLIB_TEST_FUZZ_TIME = 10
changedir = {envdir}
commands =
nosetests {posargs:passlib.tests}