summaryrefslogtreecommitdiff
path: root/passlib
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-04-10 14:26:52 -0400
committerEli Collins <elic@assurancetechnologies.com>2012-04-10 14:26:52 -0400
commit75758ab6138a01ef58d75a0b4cb4c172248d9d8e (patch)
tree0b3b1db5cb2a2553dca6397231d71493665a3ce5 /passlib
parentd1cd32ca8209513eb7f00201c3555363b3675dd6 (diff)
downloadpasslib-75758ab6138a01ef58d75a0b4cb4c172248d9d8e.tar.gz
tightened OS crypt backend tests
* split os_crypt tests into separate mixin * tests now require os_crypt backends to detect some simple incorrect returns from crypt() - e.g. returning wrong ident prefix, wrong size, etc - added relevant asserts to all os_crypt backends * tests now check if platform crypt detection is functioning correctly via platform_crypt_support dict in tests.
Diffstat (limited to 'passlib')
-rw-r--r--passlib/handlers/bcrypt.py9
-rw-r--r--passlib/handlers/des_crypt.py9
-rw-r--r--passlib/handlers/md5_crypt.py4
-rw-r--r--passlib/handlers/sha1_crypt.py13
-rw-r--r--passlib/handlers/sha2_crypt.py1
-rw-r--r--passlib/tests/test_handlers.py64
-rw-r--r--passlib/tests/utils.py165
7 files changed, 229 insertions, 36 deletions
diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py
index 8690eb5..c1c4127 100644
--- a/passlib/handlers/bcrypt.py
+++ b/passlib/handlers/bcrypt.py
@@ -244,8 +244,10 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
"'bcryptor' for bcrypt support"
def _calc_checksum_os_crypt(self, secret):
- hash = safe_crypt(secret, self._get_config())
+ config = self._get_config()
+ hash = safe_crypt(secret, config)
if hash:
+ assert hash.startswith(config) and len(hash) == len(config)+31
return hash[-31:]
else:
#NOTE: not checking backends since this is lowest priority,
@@ -260,7 +262,9 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
# py3: not supported (patch submitted)
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
- hash = pybcrypt_hashpw(secret, self._get_config())
+ config = self._get_config()
+ hash = pybcrypt_hashpw(secret, config)
+ assert hash.startswith(config) and len(hash) == len(config)+31
return str_to_uascii(hash[-31:])
def _calc_checksum_bcryptor(self, secret):
@@ -281,6 +285,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh.
else:
config = self._get_config()
hash = bcryptor_engine(False).hash_key(secret, config)
+ assert hash.startswith(config) and len(hash) == len(config)+31
return str_to_uascii(hash[-31:])
def _calc_checksum_builtin(self, secret):
diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py
index 41e132a..df7683a 100644
--- a/passlib/handlers/des_crypt.py
+++ b/passlib/handlers/des_crypt.py
@@ -219,7 +219,8 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler):
# no official policy since des-crypt predates unicode
hash = safe_crypt(secret, self.salt)
if hash:
- return hash[2:]
+ assert hash.startswith(self.salt) and len(hash) == 4
+ return hash[-2:]
else:
return self._calc_checksum_builtin(secret)
@@ -331,9 +332,11 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
return raw_ext_crypt(secret, self.rounds, self.salt.encode("ascii")).decode("ascii")
def _calc_checksum_os_crypt(self, secret):
- hash = safe_crypt(secret, self.to_string())
+ config = self.to_string()
+ hash = safe_crypt(secret, config)
if hash:
- return hash[9:]
+ assert hash.startswith(config[:9]) and len(hash) == 20
+ return hash[-11:]
else:
return self._calc_checksum_builtin(secret)
diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py
index f55e7c7..ac478b6 100644
--- a/passlib/handlers/md5_crypt.py
+++ b/passlib/handlers/md5_crypt.py
@@ -266,8 +266,10 @@ class md5_crypt(uh.HasManyBackends, _MD5_Common):
return _raw_md5_crypt(secret, self.salt)
def _calc_checksum_os_crypt(self, secret):
- hash = safe_crypt(secret, self.ident + self.salt)
+ config = self.ident + self.salt
+ hash = safe_crypt(secret, config)
if hash:
+ assert hash.startswith(config) and len(hash) == len(config) + 23
return hash[-22:]
else:
return self._calc_checksum_builtin(secret)
diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py
index 2bed9e8..35dff46 100644
--- a/passlib/handlers/sha1_crypt.py
+++ b/passlib/handlers/sha1_crypt.py
@@ -83,8 +83,9 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
return cls(rounds=rounds, salt=salt, checksum=chk)
- def to_string(self):
- return uh.render_mc3(self.ident, self.rounds, self.salt, self.checksum)
+ def to_string(self, config=False):
+ chk = None if config else self.checksum
+ return uh.render_mc3(self.ident, self.rounds, self.salt, chk)
#=========================================================
#backend
@@ -95,7 +96,7 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
@classproperty
def _has_backend_os_crypt(cls):
- return test_crypt("test", '$sha1$1$Wq3GL2Vp$C8U''25GvfHS8qGHim'
+ return test_crypt("test", '$sha1$1$Wq3GL2Vp$C8U25GvfHS8qGHim'
'ExLaiSFlGkAe')
def _calc_checksum_builtin(self, secret):
@@ -122,9 +123,11 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler
]
def _calc_checksum_os_crypt(self, secret):
- hash = safe_crypt(secret, self.to_string())
+ config = self.to_string(config=True)
+ hash = safe_crypt(secret, config)
if hash:
- return hash[hash.rindex("$")+1:]
+ assert hash.startswith(config) and len(hash) == len(config) + 29
+ return hash[-28:]
else:
return self._calc_checksum_builtin(secret)
diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py
index dc68226..fccc4e1 100644
--- a/passlib/handlers/sha2_crypt.py
+++ b/passlib/handlers/sha2_crypt.py
@@ -349,6 +349,7 @@ class _SHA2_Common(uh.HasManyBackends, uh.HasRounds, uh.HasSalt,
# NOTE: avoiding full parsing routine via from_string().checksum,
# and just extracting the bit we need.
cs = self.checksum_size
+ assert hash.startswith(self.ident) and hash[-cs-1] == _UDOLLAR
return hash[-cs:]
else:
return self._calc_checksum_builtin(secret)
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index 6584f9e..8a119a2 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -166,6 +166,14 @@ class _bcrypt_test(HandlerCase):
# but we can reliably correct & issue a warning for that.
]
+ platform_crypt_support = dict(
+ freebsd=True,
+ openbsd=True,
+ netbsd=True,
+ # linux - some systems
+ # solaris - depends on policy
+ )
+
#===============================================================
# override some methods
#===============================================================
@@ -412,6 +420,15 @@ class _bsdi_crypt_test(HandlerCase):
"_K1.!crsmZxOLzfJH8iw"
]
+ platform_crypt_support = dict(
+ freebsd=True,
+ openbsd=False,
+ netbsd=True,
+ linux=False,
+ solaris=False,
+ # darwin ?
+ )
+
os_crypt_bsdi_crypt_test = create_backend_case(_bsdi_crypt_test, "os_crypt")
builtin_bsdi_crypt_test = create_backend_case(_bsdi_crypt_test, "builtin")
@@ -589,6 +606,15 @@ class _des_crypt_test(HandlerCase):
'!gAwTx2l6NADI',
]
+ platform_crypt_support = dict(
+ freebsd=True,
+ openbsd=True,
+ netbsd=True,
+ linux=True,
+ solaris=True,
+ # darwin?
+ )
+
def test_90_invalid_secret_chars(self):
self.assertRaises(ValueError, self.do_encrypt, 'sec\x00t')
@@ -1057,6 +1083,15 @@ class _md5_crypt_test(HandlerCase):
'$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!',
]
+ platform_crypt_support = dict(
+ freebsd=True,
+ openbsd=True,
+ netbsd=True,
+ linux=True,
+ solaris=True,
+ # darwin?
+ )
+
os_crypt_md5_crypt_test = create_backend_case(_md5_crypt_test, "os_crypt")
builtin_md5_crypt_test = create_backend_case(_md5_crypt_test, "builtin")
@@ -1951,6 +1986,15 @@ class _sha1_crypt_test(HandlerCase):
'$sha1$01773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH',
]
+ platform_crypt_support = dict(
+ freebsd=False,
+ openbsd=False,
+ netbsd=True,
+ linux=False,
+ solaris=False,
+ darwin=False,
+ )
+
os_crypt_sha1_crypt_test = create_backend_case(_sha1_crypt_test, "os_crypt")
builtin_sha1_crypt_test = create_backend_case(_sha1_crypt_test, "builtin")
@@ -2079,6 +2123,15 @@ class _sha256_crypt_test(HandlerCase):
filter_config_warnings = True # rounds too low, salt too small
+ platform_crypt_support = dict(
+ freebsd=False,
+ openbsd=False,
+ netbsd=False,
+ linux=True,
+ # solaris - depends on policy
+ # darwin ?,
+ )
+
os_crypt_sha256_crypt_test = create_backend_case(_sha256_crypt_test, "os_crypt")
builtin_sha256_crypt_test = create_backend_case(_sha256_crypt_test, "builtin")
@@ -2158,6 +2211,8 @@ class _sha512_crypt_test(HandlerCase):
filter_config_warnings = True # rounds too low, salt too small
+ platform_crypt_support = _sha256_crypt_test.platform_crypt_support
+
os_crypt_sha512_crypt_test = create_backend_case(_sha512_crypt_test, "os_crypt")
builtin_sha512_crypt_test = create_backend_case(_sha512_crypt_test, "builtin")
@@ -2259,6 +2314,15 @@ class sun_md5_crypt_test(HandlerCase):
]
+ platform_crypt_support = dict(
+ freebsd=False,
+ openbsd=False,
+ netbsd=False,
+ linux=False,
+ solaris=True,
+ darwin=False,
+ )
+
def do_verify(self, secret, hash):
# override to fake error for "$..." hash strings listed in known_config.
# these have to be hash strings, in order to test bare salt issue.
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index 7c79975..6dedc3d 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -698,31 +698,9 @@ class HandlerCase(TestCase):
if backend:
if not hasattr(handler, "set_backend"):
raise RuntimeError("handler doesn't support multiple backends")
- if backend == "os_crypt" and not handler.has_backend("os_crypt"):
- self._patch_safe_crypt()
self.addCleanup(handler.set_backend, handler.get_backend())
handler.set_backend(backend)
- def _patch_safe_crypt(self):
- """if crypt() doesn't support current hash alg, this patches
- safe_crypt() so that it transparently uses another one of the handler's
- backends, so that we can go ahead and test as much of code path
- as possible.
- """
- handler = self.handler
- alt_backend = _find_alternate_backend(handler, "os_crypt")
- if not alt_backend:
- raise AssertionError("handler has no available backends!")
- import passlib.utils as mod
- def crypt_stub(secret, hash):
- with temporary_backend(handler, alt_backend):
- hash = handler.genhash(secret, hash)
- assert isinstance(hash, str)
- return hash
- self.addCleanup(setattr, mod, "_crypt", mod._crypt)
- mod._crypt = crypt_stub
- self.using_patched_crypt = True
-
#=========================================================
# basic tests
#=========================================================
@@ -1635,6 +1613,115 @@ class HandlerCase(TestCase):
# eoc
#=========================================================
+class OsCryptMixin(HandlerCase):
+ """helper used by create_backend_case() which adds additional features
+ to test the os_crypt backend.
+
+ * if crypt support is missing, inserts fake crypt support to simulate
+ a working safe_crypt, to test passlib's codepath as fully as possible.
+
+ * extra tests to verify non-conformant crypt implementations are handled
+ correctly.
+
+ * check that native crypt support is detected correctly for known platforms.
+ """
+ #=========================================================
+ # option flags
+ #=========================================================
+ # platforms that are known to support / not support this hash natively.
+ # encodeds as os.platform prefixes.
+ platform_crypt_support = dict()
+
+ # TODO: test that os_crypt support is detected correct on the expected
+ # platofrms.
+
+ #=========================================================
+ # instance attrs
+ #=========================================================
+ __unittest_skip = True
+
+ # force this backend
+ backend = "os_crypt"
+
+ # flag read by HandlerCase to detect if fake os crypt is enabled.
+ using_patched_crypt = False
+
+ #=========================================================
+ # setup
+ #=========================================================
+ def setUp(self):
+ assert self.backend == "os_crypt"
+ if not self.handler.has_backend("os_crypt"):
+ self.handler.get_backend() # hack to prevent recursion issue
+ self._patch_safe_crypt()
+ HandlerCase.setUp(self)
+
+ def _patch_safe_crypt(self):
+ """if crypt() doesn't support current hash alg, this patches
+ safe_crypt() so that it transparently uses another one of the handler's
+ backends, so that we can go ahead and test as much of code path
+ as possible.
+ """
+ handler = self.handler
+ alt_backend = _find_alternate_backend(handler, "os_crypt")
+ if not alt_backend:
+ raise AssertionError("handler has no available backends!")
+ import passlib.utils as mod
+ def crypt_stub(secret, hash):
+ with temporary_backend(handler, alt_backend):
+ hash = handler.genhash(secret, hash)
+ assert isinstance(hash, str)
+ return hash
+ self.addCleanup(setattr, mod, "_crypt", mod._crypt)
+ mod._crypt = crypt_stub
+ self.using_patched_crypt = True
+
+ #=========================================================
+ # custom tests
+ #=========================================================
+ def test_80_faulty_crypt(self):
+ "test with faulty crypt()"
+ # patch safe_crypt to return mock value.
+ import passlib.utils as mod
+ self.addCleanup(setattr, mod, "_crypt", mod._crypt)
+ crypt_value = [None]
+ mod._crypt = lambda secret, config: crypt_value[0]
+
+ # prepare framework
+ config = self.do_genconfig()
+ hash = self.get_sample_hash()[1]
+ exc_types = (AssertionError,)
+
+ def test(value):
+ crypt_value[0] = value
+ self.assertRaises(exc_types, self.do_genhash, "stub", config)
+ self.assertRaises(exc_types, self.do_encrypt, "stub")
+ self.assertRaises(exc_types, self.do_verify, "stub", hash)
+
+ test('$x' + hash[2:]) # detect wrong prefix
+ test(hash[:-1]) # detect too short
+ test(hash + 'x') # detect too long
+
+ def test_81_crypt_support(self):
+ "test crypt support detection"
+ platform = sys.platform
+ for name, flag in self.platform_crypt_support.items():
+ if not platform.startswith(name):
+ continue
+ if flag != self.using_patched_crypt:
+ return
+ if flag:
+ self.fail("expected %r platform would have native support "
+ "for %r" % (platform, self.handler.name))
+ else:
+ self.fail("expected %r platform would NOT have native support "
+ "for %r" % (platform, self.handler.name))
+ raise self.skipTest("no data for %r platform" % platform)
+
+ #=========================================================
+ # eoc
+ #=========================================================
+
class UserHandlerMixin(HandlerCase):
"""helper for handlers w/ 'user' context kwd; mixin for HandlerCase
@@ -1643,11 +1730,21 @@ class UserHandlerMixin(HandlerCase):
calls. as well, passing in a pair of strings as the password
will be interpreted as (secret,user)
"""
- __unittest_skip = True
+ #=========================================================
+ # option flags
+ #=========================================================
default_user = "user"
requires_user = True
user_case_insensitive = False
+ #=========================================================
+ # instance attrs
+ #=========================================================
+ __unittest_skip = True
+
+ #=========================================================
+ # custom tests
+ #=========================================================
def test_80_user(self):
"test user context keyword"
handler = self.handler
@@ -1687,6 +1784,10 @@ class UserHandlerMixin(HandlerCase):
# TODO: user size? kinda dicey, depends on algorithm.
+ #=========================================================
+ # override test helpers
+ #=========================================================
+
def is_secret_8bit(self, secret):
secret = self._insert_user({}, secret)
return not is_ascii_safe(secret)
@@ -1715,13 +1816,22 @@ class UserHandlerMixin(HandlerCase):
secret = self._insert_user(kwds, secret)
return self.handler.genhash(secret, config, **kwds)
+ #=========================================================
+ # modify fuzz testing
+ #=========================================================
fuzz_user_alphabet = u("asdQWE123")
+
fuzz_settings = HandlerCase.fuzz_settings + ["user"]
+
def get_fuzz_user(self):
if not self.requires_user and rng.random() < .1:
return None
return getrandstr(rng, self.fuzz_user_alphabet, rng.randint(2,10))
+ #=========================================================
+ # eoc
+ #=========================================================
+
#=========================================================
#backend test helpers
#=========================================================
@@ -1805,10 +1915,15 @@ def create_backend_case(base_class, backend, module=None):
if not enable and ut_version < 2:
return None
- #create subclass of 'base' which uses correct backend
+ # pick bases
+ bases = (base_class,)
+ if backend == "os_crypt":
+ bases += (OsCryptMixin,)
+
+ # create subclass to test backend
backend_class = type(
"%s_%s" % (backend, handler.name),
- (base_class,),
+ bases,
dict(
descriptionPrefix = "%s (%s backend)" % (handler.name, backend),
backend = backend,