From e71ddce83853566311effebf68b9bbbdebf4c2ab Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Thu, 12 Apr 2012 14:13:36 -0400 Subject: scram hash: improved UTs to cover some edge cases, full-verify now throws error for inconsistent hashes. --- passlib/tests/test_handlers.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index f00e1cf..63e457c 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -1789,6 +1789,13 @@ class scram_test(HandlerCase): # bad char in digest ---\/ '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX3-', + # missing sections + '$scram$4096$QSXCR.Q6sek8bf92', + '$scram$4096$QSXCR.Q6sek8bf92$', + + # too many sections + '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30$', + # missing separator '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30' 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY', @@ -1800,6 +1807,8 @@ class scram_test(HandlerCase): # missing sha-1 alg '$scram$4096$QSXCR.Q6sek8bf92$sha-256=HZbuOlKbWl.eR8AfIposuKbhX30', + # non-iana name + '$scram$4096$QSXCR.Q6sek8bf92$sha1=HZbuOlKbWl.eR8AfIposuKbhX30', ] # silence norm_hash_name() warning @@ -1857,6 +1866,10 @@ class scram_test(HandlerCase): self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$' 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'), ["sha-1"]) + self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30', format="hashlib"), + ["sha1"]) + self.assertEqual(eda('$scram$4096$QSXCR.Q6sek8bf92$' 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' 'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,' @@ -1887,6 +1900,9 @@ class scram_test(HandlerCase): # check rounds self.assertRaises(ValueError, hash, "IX", s1, 0, 'sha-1') + # bad types + self.assertRaises(TypeError, hash, "IX", u('\x01'), 1000, 'md5') + def test_94_saslprep(self): "test encrypt/verify use saslprep" # NOTE: this just does a light test that saslprep() is being @@ -1925,6 +1941,8 @@ class scram_test(HandlerCase): def test_96_full_verify(self): "test verify(full=True) flag" + def vpart(s, h): + return self.handler.verify(s, h) def vfull(s, h): return self.handler.verify(s, h, full=True) @@ -1953,12 +1971,16 @@ class scram_test(HandlerCase): 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ') self.assertRaises(ValueError, vfull, 'pencil', h) - # catch digests belonging to diff passwords. + # catch hash containing digests belonging to diff passwords. + # proper behavior for quick-verify (the default) is undefined, + # but full-verify should throw error. h = ('$scram$4096$QSXCR.Q6sek8bf92$' - 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' - 'sha-256=R7RJDWIbeKRTFwhE9oxh04kab0CllrQ3kCcpZUcligc' # 'tape' - 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' + 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,' # 'pencil' + 'sha-256=R7RJDWIbeKRTFwhE9oxh04kab0CllrQ3kCcpZUcligc,' # 'tape' + 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/' # 'pencil' 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ') + self.assertTrue(vpart('tape', h)) + self.assertFalse(vpart('pencil', h)) self.assertRaises(ValueError, vfull, 'pencil', h) self.assertRaises(ValueError, vfull, 'tape', h) -- cgit v1.2.1 From c0f420bf7d7659ee110432f7cbb0233554dfd32a Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Thu, 12 Apr 2012 21:52:26 -0400 Subject: assorted bugfixes, tweaks, and tests added; based on coverage examination * test os_crypt backend has functional fallback * test handler methods accept all unicode/bytes combinations for secret & hash * fixed some incorrect error messages & types being caught & raised * other minor cleanups --- passlib/tests/test_handlers.py | 92 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 63e457c..d1c5cbf 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -497,6 +497,7 @@ class cisco_pix_test(UserHandlerMixin, HandlerCase): class cisco_type7_test(HandlerCase): handler = hash.cisco_type7 salt_bits = 4 + salt_type = int known_correct_hashes = [ # @@ -539,6 +540,41 @@ class cisco_type7_test(HandlerCase): (UPASS_TABLE, '0958EDC8A9F495F6F8A5FD'), ] + known_unidentified_hashes = [ + # salt with hex value + "0A480E051A33490E", + + # salt value > 52. this may in fact be valid, but we reject it for now + # (see docs for more). + '99400E4812', + ] + + def test_90_decode(self): + "test cisco_type7.decode()" + from passlib.utils import to_unicode, to_bytes + + handler = self.handler + for secret, hash in self.known_correct_hashes: + usecret = to_unicode(secret) + bsecret = to_bytes(secret) + self.assertEqual(handler.decode(hash), usecret) + self.assertEqual(handler.decode(hash, None), bsecret) + + self.assertRaises(UnicodeDecodeError, handler.decode, + '0958EDC8A9F495F6F8A5FD', 'ascii') + + def test_91_salt(self): + "test salt value border cases" + handler = self.handler + self.assertRaises(TypeError, handler, salt=None) + handler(salt=None, use_defaults=True) + self.assertRaises(TypeError, handler, salt='abc') + self.assertRaises(ValueError, handler, salt=-10) + with catch_warnings(record=True) as wlog: + h = handler(salt=100, relaxed=True) + self.consumeWarningList(wlog, ["salt/offset must be.*"]) + self.assertEqual(h.salt, 52) + #========================================================= # crypt16 #========================================================= @@ -794,6 +830,9 @@ class fshp_test(HandlerCase): ] known_malformed_hashes = [ + # bad base64 padding + '{FSHP0|0|1}qUqP5cyxm6YcTAhz05Hph5gvu9M', + # wrong salt size '{FSHP0|1|1}qUqP5cyxm6YcTAhz05Hph5gvu9M=', @@ -801,6 +840,32 @@ class fshp_test(HandlerCase): '{FSHP0|0|A}qUqP5cyxm6YcTAhz05Hph5gvu9M=', ] + def test_90_variant(self): + "test variant keyword" + handler = self.handler + kwds = dict(salt='a', rounds=1) + + # accepts ints + handler(variant=1, **kwds) + + # accepts bytes or unicode + handler(variant=u('1'), **kwds) + handler(variant=b('1'), **kwds) + + # aliases + handler(variant=u('sha256'), **kwds) + handler(variant=b('sha256'), **kwds) + + # rejects None + self.assertRaises(TypeError, handler, variant=None, **kwds) + + # rejects other types + self.assertRaises(TypeError, handler, variant=complex(1,1), **kwds) + + # invalid variant + self.assertRaises(ValueError, handler, variant='9', **kwds) + self.assertRaises(ValueError, handler, variant=9, **kwds) + #========================================================= #hex digests #========================================================= @@ -1080,6 +1145,9 @@ class _md5_crypt_test(HandlerCase): known_malformed_hashes = [ # bad char in otherwise correct hash \/ '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!', + + # too many fields + '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.$', ] platform_crypt_support = dict( @@ -2005,6 +2073,12 @@ class _sha1_crypt_test(HandlerCase): # zero padded rounds '$sha1$01773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH', + + # too many fields + '$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH$', + + # empty rounds field + '$sha1$$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH$', ] platform_crypt_support = dict( @@ -2316,6 +2390,14 @@ class sun_md5_crypt_test(HandlerCase): ] known_malformed_hashes = [ + # unexpected end of hash + "$md5,rounds=5000", + + # bad rounds + "$md5,rounds=500A$xxxx", + "$md5,rounds=0500$xxxx", + "$md5,rounds=0$xxxx", + # bad char in otherwise correct hash "$md5$RPgL!6IJ$WTvAlUJ7MqH5xak2FMEwS/", @@ -2369,6 +2451,16 @@ class unix_disabled_test(HandlerCase): # TODO: test custom marker support # TODO: test default marker selection + def test_90_preserves_existing(self): + "test preserves existing disabled hash" + handler = self.handler + + # use marker if no hash + self.assertEqual(handler.genhash("stub", None), handler.marker) + + # use hash if provided and valid + self.assertEqual(handler.genhash("stub", "!asd"), "!asd") + class unix_fallback_test(HandlerCase): handler = hash.unix_fallback accepts_all_hashes = True -- cgit v1.2.1 From 154046a05e38cb889f886853971a48a8c7d626b4 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Fri, 13 Apr 2012 14:12:14 -0400 Subject: issue warning if app requests even bsdi_crypt rounds --- passlib/tests/test_handlers.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index d1c5cbf..822bc99 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -428,6 +428,10 @@ class _bsdi_crypt_test(HandlerCase): # darwin ? ) + def setUp(self): + super(_bsdi_crypt_test, self).setUp() + warnings.filterwarnings("ignore", "bsdi_crypt rounds should be odd.*") + os_crypt_bsdi_crypt_test = create_backend_case(_bsdi_crypt_test, "os_crypt") builtin_bsdi_crypt_test = create_backend_case(_bsdi_crypt_test, "builtin") -- cgit v1.2.1 From 29e7c681bed9a2a9b96f71b56b0bf4edca8ef044 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Tue, 17 Apr 2012 15:09:21 -0400 Subject: updated passlib.apache module's api - more flexible to use, changed some ambiguous method names --- passlib/tests/test_handlers.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 822bc99..b06d100 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -912,6 +912,37 @@ class hex_sha512_test(HandlerCase): '29caf'), ] +#========================================================= +# htdigest hash +#========================================================= +class htdigest_test(UserHandlerMixin, HandlerCase): + handler = hash.htdigest + + known_correct_hashes = [ + # secret, user, realm + + # from RFC 2617 + (("Circle Of Life", "Mufasa", "testrealm@host.com"), + '939e7578ed9e3c518a452acee763bce9'), + + # custom + ((UPASS_TABLE, UPASS_USD, UPASS_WAV), + '4dabed2727d583178777fab468dd1f17'), + ] + + def test_80_user(self): + raise self.skipTest("test case doesn't support 'realm' keyword") + + def _insert_user(self, kwds, secret): + "insert username into kwds" + if isinstance(secret, tuple): + secret, user, realm = secret + else: + user, realm = "user", "realm" + kwds.setdefault("user", user) + kwds.setdefault("realm", realm) + return secret + #========================================================= #ldap hashes #========================================================= -- cgit v1.2.1 From ab5a64cea160b07477874e646bb3c11511af3967 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Tue, 17 Apr 2012 15:10:07 -0400 Subject: bugfix: set des_crypt.checksum_size, so it will now reject hashes with missing chars --- passlib/tests/test_handlers.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index b06d100..259a58f 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -643,6 +643,10 @@ class _des_crypt_test(HandlerCase): # bad char in otherwise correctly formatted hash #\/ '!gAwTx2l6NADI', + + # wrong size + 'OgAwTx2l6NAD', + 'OgAwTx2l6NADIj', ] platform_crypt_support = dict( -- cgit v1.2.1 From e56e08c56ea0a4a8ac45aff7bfcd39d351e04e53 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Tue, 17 Apr 2012 15:34:14 -0400 Subject: CryptPolicy deprecated, part2 - updated rest of library to use CryptContext directly --- passlib/tests/test_handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 259a58f..a5dd3ae 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -2040,10 +2040,10 @@ class scram_test(HandlerCase): self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"]) self.assertFalse(c1.hash_needs_update(h)) - c2 = c1.replace(scram__algs="sha1") + c2 = c1.copy(scram__algs="sha1") self.assertFalse(c2.hash_needs_update(h)) - c2 = c1.replace(scram__algs="sha1,sha256") + c2 = c1.copy(scram__algs="sha1,sha256") self.assertTrue(c2.hash_needs_update(h)) def test_96_full_verify(self): -- cgit v1.2.1 From ceb7a00ddae502624d609bc63a9048f0de9f1b23 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Tue, 17 Apr 2012 21:55:38 -0400 Subject: a bunch of bugfixes found during unittesting * bsdi_crypt apparently available on openbsd 4.9 * typo fixes * ConfigParser apparently only uses OrderedDict for >= PY27, adjusted CryptContext test accordingly * fixed test that depended on sha256_crypt.default_rounds * handle os_crypt backend w/ no fallback (bcrypt) * let _norm_rounds accept longs --- passlib/tests/test_handlers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index a5dd3ae..d045e94 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -59,6 +59,7 @@ class _bcrypt_test(HandlerCase): "base for BCrypt test cases" handler = hash.bcrypt secret_size = 72 + os_crypt_has_fallback = False known_correct_hashes = [ # @@ -421,7 +422,7 @@ class _bsdi_crypt_test(HandlerCase): platform_crypt_support = dict( freebsd=True, - openbsd=False, + openbsd=True, netbsd=True, linux=False, solaris=False, @@ -851,7 +852,7 @@ class fshp_test(HandlerCase): def test_90_variant(self): "test variant keyword" handler = self.handler - kwds = dict(salt='a', rounds=1) + kwds = dict(salt=b('a'), rounds=1) # accepts ints handler(variant=1, **kwds) -- cgit v1.2.1 From 2ad8463a456796300df5340a2bc511e290e62072 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Tue, 17 Apr 2012 21:58:54 -0400 Subject: disabling saslprep() support under Jython - it lacks the stringprep module --- passlib/tests/test_handlers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index d045e94..102a3dc 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -1919,9 +1919,13 @@ class scram_test(HandlerCase): '$scram$4096$QSXCR.Q6sek8bf92$sha1=HZbuOlKbWl.eR8AfIposuKbhX30', ] - # silence norm_hash_name() warning def setUp(self): super(scram_test, self).setUp() + + # some platforms lack stringprep (e.g. Jython, IronPython) + self.require_stringprep() + + # silence norm_hash_name() warning warnings.filterwarnings("ignore", r"norm_hash_name\(\): unknown hash") def test_90_algs(self): -- cgit v1.2.1 From 0cade3487dea5d8117b9f5e045c9fd9425778aec Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Tue, 17 Apr 2012 22:00:18 -0400 Subject: reworked fuzz verifier system, so that we can skip $2x$ hashes during bcrypt crypt() testing --- passlib/tests/test_handlers.py | 97 ++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 47 deletions(-) (limited to 'passlib/tests/test_handlers.py') 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()" -- cgit v1.2.1 From e81b32667429c6486a63f4a2c2bf446ba2c8ea90 Mon Sep 17 00:00:00 2001 From: Eli Collins Date: Tue, 17 Apr 2012 22:23:35 -0400 Subject: changed bcrypt's os_crypt backend to try alternatives before bailing. --- passlib/tests/test_handlers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'passlib/tests/test_handlers.py') diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index f566b4f..61cdc8f 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -59,7 +59,6 @@ class _bcrypt_test(HandlerCase): "base for BCrypt test cases" handler = hash.bcrypt secret_size = 72 - os_crypt_has_fallback = False known_correct_hashes = [ # @@ -204,6 +203,8 @@ class _bcrypt_test(HandlerCase): #=============================================================== def os_supports_ident(self, hash): "check if OS crypt is expected to support given ident" + if hash is None: + 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$"): -- cgit v1.2.1