diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2012-03-12 22:48:23 -0400 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2012-03-12 22:48:23 -0400 |
| commit | ba4550d9dc9d12bf3ee714fb05040ebbf4adb8e8 (patch) | |
| tree | e264eb2edf72efbeaf40d1616e4ffb2b528bde2e | |
| parent | e89ebdf93b92dc018bd3ee1542cc4416b5024ab4 (diff) | |
| download | passlib-ba4550d9dc9d12bf3ee714fb05040ebbf4adb8e8.tar.gz | |
misc bugfixes
* removed cisco_type7 config string, conflicted w/ empty password
* fixed unicode type issue in cisco_type7, win32.nthash
* bsdi_crypt.min_rounds now 1 (0 results in identical hashes)
* fixed unicode type issue in UPASS_TABLE tests for plaintext, ldap_plaintext
* relocated test vectors from test_win32 to lmhash/nthash
* 8bit test for UnsaltedHash
* fuzz testing expanded to use 5-99 char passwords, and 1/10000 are empty
*
| -rw-r--r-- | passlib/handlers/cisco.py | 14 | ||||
| -rw-r--r-- | passlib/handlers/des_crypt.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/windows.py | 4 | ||||
| -rw-r--r-- | passlib/tests/test_handlers.py | 42 | ||||
| -rw-r--r-- | passlib/tests/test_utils_handlers.py | 17 | ||||
| -rw-r--r-- | passlib/tests/test_win32.py | 15 | ||||
| -rw-r--r-- | passlib/tests/utils.py | 23 |
7 files changed, 85 insertions, 32 deletions
diff --git a/passlib/handlers/cisco.py b/passlib/handlers/cisco.py index 102d049..37d7233 100644 --- a/passlib/handlers/cisco.py +++ b/passlib/handlers/cisco.py @@ -11,7 +11,7 @@ from warnings import warn #libs #pkg from passlib.utils import h64, to_bytes -from passlib.utils.compat import b, bascii_to_str, unicode, u, join_byte_values, \ +from passlib.utils.compat import b, bascii_to_str, bytes, unicode, u, join_byte_values, \ join_byte_elems, byte_elem_value, iter_byte_values, uascii_to_str, str_to_uascii import passlib.utils.handlers as uh #local @@ -109,7 +109,6 @@ class cisco_type7(uh.GenericHandler): name = "cisco_type7" setting_kwds = ("salt",) checksum_chars = uh.UPPER_HEX_CHARS - _stub_checksum = u("00") max_salt_value = 52 @@ -117,8 +116,14 @@ class cisco_type7(uh.GenericHandler): # methods #========================================================= @classmethod + def genconfig(cls): + return None + + @classmethod def from_string(cls, hash): if not hash: + if hash is None: + return cls(use_defaults=True) raise ValueError("no hash provided") if len(hash) < 2: raise ValueError("invalid cisco_type7 hash") @@ -155,14 +160,13 @@ class cisco_type7(uh.GenericHandler): return uh.rng.randint(0, 15) def to_string(self): - return "%02d%s" % (self.salt, uascii_to_str(self.checksum or - self._stub_checksum)) + return "%02d%s" % (self.salt, uascii_to_str(self.checksum)) def _calc_checksum(self, secret): # XXX: no idea what unicode policy is, but all examples are # 7-bit ascii compatible, so using UTF-8 secret = to_bytes(secret, "utf-8", errname="secret") - return str_to_uascii(hexlify(self._cipher(secret, self.salt))).upper() + return hexlify(self._cipher(secret, self.salt)).decode("ascii").upper() @classmethod def decode(cls, hash, encoding="utf-8"): diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py index f663a39..a65d147 100644 --- a/passlib/handlers/des_crypt.py +++ b/passlib/handlers/des_crypt.py @@ -275,7 +275,7 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler #--HasRounds-- default_rounds = 5001 - min_rounds = 0 + min_rounds = 1 max_rounds = 16777215 # (1<<24)-1 rounds_cost = "linear" diff --git a/passlib/handlers/windows.py b/passlib/handlers/windows.py index 9805037..a60e911 100644 --- a/passlib/handlers/windows.py +++ b/passlib/handlers/windows.py @@ -10,7 +10,7 @@ from warnings import warn #site #libs from passlib.utils import to_unicode, to_bytes -from passlib.utils.compat import b, bytes, str_to_uascii, u, uascii_to_str +from passlib.utils.compat import b, bytes, str_to_uascii, u, unicode, uascii_to_str from passlib.utils.md4 import md4 import passlib.utils.handlers as uh #pkg @@ -161,7 +161,7 @@ class nthash(uh.StaticHandler): "in Passlib 1.8, please use nthash.raw() instead", DeprecationWarning) ret = nthash.raw(secret) - return hexlify(ret) if hex else ret + return hexlify(ret).decode("ascii") if hex else ret #========================================================= # eoc diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 29c77ef..5b32a60 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -10,7 +10,7 @@ import warnings #site #pkg from passlib import hash -from passlib.utils.compat import irange +from passlib.utils.compat import irange, PY3 from passlib.tests.utils import TestCase, HandlerCase, create_backend_case, \ enable_option, b, catch_warnings, UserHandlerMixin, randintgauss from passlib.utils.compat import u @@ -25,6 +25,8 @@ UPASS_WAV = u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2') UPASS_USD = u("\u20AC\u00A5$") UPASS_TABLE = u("t\u00e1\u0411\u2113\u0259") +PASS_TABLE_UTF8 = b('t\xc3\xa1\xd0\x91\xe2\x84\x93\xc9\x99') # utf-8 + #========================================================= #apr md5 crypt #========================================================= @@ -597,6 +599,8 @@ builtin_des_crypt_test = create_backend_case(_des_crypt_test, "builtin") #django #========================================================= 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() @@ -617,7 +621,7 @@ class _DjangoHelper(object): return self.skipTest("no known correct hashes specified") from passlib.tests.test_ext_django import has_django1 if not has_django1: - return self.skipTest("Django not installed") + return self.skipTest("Django >= 1.0 not installed") from django.contrib.auth.models import check_password for secret, hash in self.iter_known_hashes(): self.assertTrue(check_password(secret, hash)) @@ -850,16 +854,27 @@ class ldap_plaintext_test(HandlerCase): handler = hash.ldap_plaintext known_correct_hashes = [ ("password", 'password'), - (UPASS_TABLE, 't\xc3\xa1\xd0\x91\xe2\x84\x93\xc9\x99'), + (UPASS_TABLE, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), + (PASS_TABLE_UTF8, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), ] known_unidentified_hashes = [ - "{FOO}bar" + "{FOO}bar", + + # XXX: currently we reject empty string as valid for this format. + "", ] known_other_hashes = [ ("ldap_md5", "{MD5}/F4DjTilcDIIVEHn/nAQsA==") ] + def get_fuzz_password(self): + # XXX: currently we reject empty string as valid for this format. + pwd = None + while not pwd: + pwd = super(ldap_plaintext_test, self).get_fuzz_password() + return pwd + #NOTE: since the ldap_{crypt} handlers are all wrappers, # don't need separate test. have just one for end-to-end testing purposes. @@ -933,6 +948,13 @@ class lmhash_test(HandlerCase): known_correct_hashes = [ # + # http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx + # + ("OLDPASSWORD", "c9b81d939d6fd80cd408e6b105741864"), + ("NEWPASSWORD", '09eeab5aa415d6e4d408e6b105741864'), + ("welcome", "c23413a8a1e7665faad3b435b51404ee"), + + # # custom # ('', 'aad3b435b51404eeaad3b435b51404ee'), @@ -945,7 +967,7 @@ class lmhash_test(HandlerCase): (u('encyclop\xE6dia'), 'fed6416bffc9750d48462b9d7aaac065'), ] - # TODO: test encoding keyword. + # TODO: test encoding keyword. known_unidentified_hashes = [ # bad char in otherwise correct hash @@ -1343,6 +1365,12 @@ class nthash_test(HandlerCase): known_correct_hashes = [ # + # http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx + # + ("OLDPASSWORD", u("6677b2c394311355b54f25eec5bfacf5")), + ("NEWPASSWORD", u("256781a62031289d3c2c98c14f1efc8c")), + + # # from JTR 1.7.9 # @@ -1612,6 +1640,10 @@ class plaintext_test(HandlerCase): known_correct_hashes = [ ('',''), ('password', 'password'), + + # ensure unicode uses utf-8 + (UPASS_TABLE, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), + (PASS_TABLE_UTF8, UPASS_TABLE if PY3 else PASS_TABLE_UTF8), ] #========================================================= diff --git a/passlib/tests/test_utils_handlers.py b/passlib/tests/test_utils_handlers.py index 4788798..cffac95 100644 --- a/passlib/tests/test_utils_handlers.py +++ b/passlib/tests/test_utils_handlers.py @@ -572,21 +572,21 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler): #TODO: provide data samples for algorithms # (positive knowns, negative knowns, invalid identify) +UPASS_TEMP = u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2') + class UnsaltedHashTest(HandlerCase): handler = UnsaltedHash known_correct_hashes = [ ("password", "61cfd32684c47de231f1f982c214e884133762c0"), + (UPASS_TEMP, '96b329d120b97ff81ada770042e44ba87343ad2b'), ] def test_bad_kwds(self): - if not JYTHON: - #FIXME: annoyingly, the object() constructor of Jython (as of 2.5.2) - # silently drops any extra kwds (old 2.4 behavior) - # instead of raising TypeError (new 2.5 behavior). - # we *could* use a custom base object to restore correct - # behavior, but that's a lot of effort for a non-critical - # border case. so just skipping this test instead... + if not PY_MAX_25: + # annoyingly, py25's ``super().__init__()`` doesn't throw TypeError + # when passing unknown keywords to object. just ignoring + # this issue for now, since it's a minor border case. self.assertRaises(TypeError, UnsaltedHash, salt='x') self.assertRaises(TypeError, UnsaltedHash.genconfig, rounds=1) @@ -595,8 +595,7 @@ class SaltedHashTest(HandlerCase): known_correct_hashes = [ ("password", '@salt77d71f8fe74f314dac946766c1ac4a2a58365482c0'), - (u('\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2'), - '@salt9f978a9bfe360d069b0c13f2afecd570447407fa7e48'), + (UPASS_TEMP, '@salt9f978a9bfe360d069b0c13f2afecd570447407fa7e48'), ] def test_bad_kwds(self): diff --git a/passlib/tests/test_win32.py b/passlib/tests/test_win32.py index 917cbf6..09f5023 100644 --- a/passlib/tests/test_win32.py +++ b/passlib/tests/test_win32.py @@ -4,11 +4,11 @@ #========================================================= #core from binascii import hexlify +import warnings #site #pkg from passlib.tests.utils import TestCase #module -import passlib.win32 as mod from passlib.utils.compat import u #========================================================= @@ -20,21 +20,30 @@ class UtilTest(TestCase): ##test hashes from http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx ## among other places + def setUp(self): + TestCase.setUp(self) + warnings.filterwarnings("ignore", + "the 'passlib.win32' module is deprecated") + def test_lmhash(self): + from passlib.win32 import raw_lmhash for secret, hash in [ ("OLDPASSWORD", u("c9b81d939d6fd80cd408e6b105741864")), ("NEWPASSWORD", u('09eeab5aa415d6e4d408e6b105741864')), ("welcome", u("c23413a8a1e7665faad3b435b51404ee")), ]: - result = mod.raw_lmhash(secret, hex=True) + result = raw_lmhash(secret, hex=True) self.assertEqual(result, hash) def test_nthash(self): + warnings.filterwarnings("ignore", + r"nthash\.raw_nthash\(\) is deprecated") + from passlib.win32 import raw_nthash for secret, hash in [ ("OLDPASSWORD", u("6677b2c394311355b54f25eec5bfacf5")), ("NEWPASSWORD", u("256781a62031289d3c2c98c14f1efc8c")), ]: - result = mod.raw_nthash(secret, hex=True) + result = raw_nthash(secret, hex=True) self.assertEqual(result, hash) #========================================================= diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py index 8e2883b..b3ce550 100644 --- a/passlib/tests/utils.py +++ b/passlib/tests/utils.py @@ -710,7 +710,7 @@ class HandlerCase(TestCase): as possible. """ handler = self.handler - alt_backend = _has_other_backends(handler, "os_crypt") + alt_backend = _find_alternate_backend(handler, "os_crypt") if not alt_backend: raise AssertionError("handler has no available backends!") import passlib.utils as mod @@ -921,7 +921,8 @@ class HandlerCase(TestCase): return raise self.failureException("failed to find different salt after " "%d samples" % (samples,)) - sampler(self.do_genconfig) + if self.do_genconfig() is not None: # cisco_type7 has salt & no config + sampler(self.do_genconfig) sampler(lambda : self.do_encrypt("stub")) def test_12_min_salt_size(self): @@ -1487,7 +1488,7 @@ class HandlerCase(TestCase): while tick() <= stop: # generate random password & options secret = self.get_fuzz_password() - other = secret.strip()[1:] + other = self.mangle_fuzz_password(secret) if rng.randint(0,1): secret = secret.encode(self.fuzz_password_encoding) other = other.encode(self.fuzz_password_encoding) @@ -1496,7 +1497,8 @@ class HandlerCase(TestCase): # create new hash hash = self.do_encrypt(secret, **kwds) - ##log.debug("fuzz test: hash=%r secret=%r", hash, secret) + ##log.debug("fuzz test: hash=%r secret=%r other=%r", + ## hash, secret, other) # run through all verifiers we found. for verify in verifiers: @@ -1564,7 +1566,14 @@ class HandlerCase(TestCase): def get_fuzz_password(self): "generate random passwords (for fuzz testing)" - return getrandstr(rng, self.fuzz_password_alphabet, rng.randint(5,15)) + if rng.random() < .0001: + return u('') + return getrandstr(rng, self.fuzz_password_alphabet, rng.randint(5,99)) + + def mangle_fuzz_password(self, secret): + "mangle fuzz-testing password so it doesn't match" + secret = secret.strip()[1:] + return secret or self.get_fuzz_password() def get_fuzz_settings(self): "generate random settings (for fuzz testing)" @@ -1711,7 +1720,7 @@ def _enable_backend_case(handler, backend): return True, None from passlib.utils import has_crypt if backend == "os_crypt" and has_crypt: - if enable_option("cover") and _has_other_backends(handler, "os_crypt"): + if enable_option("cover") and _find_alternate_backend(handler, "os_crypt"): #in this case, HandlerCase will monkeypatch os_crypt #to use another backend, just so we can test os_crypt fully. return True, None @@ -1733,7 +1742,7 @@ def _is_default_backend(handler, name): finally: handler.set_backend(orig) -def _has_other_backends(handler, ignore): +def _find_alternate_backend(handler, ignore): "helper to check if alternate backend is available" for name in handler.backends: if name != ignore and handler.has_backend(name): |
