diff options
Diffstat (limited to 'passlib/tests/test_handlers.py')
-rw-r--r-- | passlib/tests/test_handlers.py | 311 |
1 files changed, 1 insertions, 310 deletions
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 293a5da..1a772ca 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -42,7 +42,7 @@ def get_handler_case(scheme): return globals()[name] except KeyError: pass - for suffix in ("handlers_django",): + for suffix in ("handlers_django", "handlers_bcrypt"): modname = "passlib.tests.test_" + suffix __import__(modname) mod = sys.modules[modname] @@ -78,315 +78,6 @@ class apr_md5_crypt_test(HandlerCase): ] #============================================================================= -# bcrypt -#============================================================================= -class _bcrypt_test(HandlerCase): - "base for BCrypt test cases" - handler = hash.bcrypt - secret_size = 72 - reduce_default_rounds = True - - known_correct_hashes = [ - # - # from JTR 1.7.9 - # - ('U*U*U*U*', '$2a$05$c92SVSfjeiCD6F2nAD6y0uBpJDjdRkt0EgeC4/31Rf2LUZbDRDE.O'), - ('U*U***U', '$2a$05$WY62Xk2TXZ7EvVDQ5fmjNu7b0GEzSzUXUh2cllxJwhtOeMtWV3Ujq'), - ('U*U***U*', '$2a$05$Fa0iKV3E2SYVUlMknirWU.CFYGvJ67UwVKI1E2FP6XeLiZGcH3MJi'), - ('*U*U*U*U', '$2a$05$.WRrXibc1zPgIdRXYfv.4uu6TD1KWf0VnHzq/0imhUhuxSxCyeBs2'), - ('', '$2a$05$Otz9agnajgrAe0.kFVF9V.tzaStZ2s1s4ZWi/LY4sw2k/MTVFj/IO'), - - # - # test vectors from http://www.openwall.com/crypt v1.2 - # note that this omits any hashes that depend on crypt_blowfish's - # various CVE-2011-2483 workarounds (hash 2a and \xff\xff in password, - # and any 2x hashes); and only contain hashes which are correct - # under both crypt_blowfish 1.2 AND OpenBSD. - # - ('U*U', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW'), - ('U*U*', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK'), - ('U*U*U', '$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a'), - ('', '$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy'), - ('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' - '0123456789chars after 72 are ignored', - '$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui'), - (b('\xa3'), - '$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'), - (b('\xff\xa3345'), - '$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e'), - (b('\xa3ab'), - '$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS'), - (b('\xaa')*72 + b('chars after 72 are ignored as usual'), - '$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6'), - (b('\xaa\x55'*36), - '$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy'), - (b('\x55\xaa\xff'*24), - '$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe'), - - # keeping one of their 2y tests, because we are supporting that. - (b('\xa3'), - '$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq'), - - # - # from py-bcrypt tests - # - ('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'), - ('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'), - ('abc', '$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi'), - ('abcdefghijklmnopqrstuvwxyz', - '$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'), - ('~!@#$%^&*() ~!@#$%^&*()PNBFRD', - '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'), - - # - # custom test vectors - # - - # ensures utf-8 used for unicode - (UPASS_TABLE, - '$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'), - ] - - if TEST_MODE("full"): - # - # add some extra tests related to 2/2a - # - CONFIG_2 = '$2$05$' + '.'*22 - CONFIG_A = '$2a$05$' + '.'*22 - known_correct_hashes.extend([ - ("", CONFIG_2 + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'), - ("", CONFIG_A + 'J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq'), - ("abc", CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), - ("abc", CONFIG_A + 'ev6gDwpVye3oMCUpLY85aTpfBNHD0Ga'), - ("abc"*23, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), - ("abc"*23, CONFIG_A + '2kIdfSj/4/R/Q6n847VTvc68BXiRYZC'), - ("abc"*24, CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), - ("abc"*24, CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), - ("abc"*24+'x', CONFIG_2 + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), - ("abc"*24+'x', CONFIG_A + 'XuQjdH.wPVNUZ/bOfstdW/FqB8QSjte'), - ]) - - known_correct_configs = [ - ('$2a$04$uM6csdM8R9SXTex/gbTaye', UPASS_TABLE, - '$2a$04$uM6csdM8R9SXTex/gbTayezuvzFEufYGd2uB6of7qScLjQ4GwcD4G'), - ] - - known_unidentified_hashes = [ - # invalid minor version - "$2b$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", - "$2`$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", - ] - - known_malformed_hashes = [ - # bad char in otherwise correct hash - # \/ - "$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", - - # unsupported (but recognized) minor version - "$2x$12$EXRkfkdmXnagzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q", - - # rounds not zero-padded (py-bcrypt rejects this, therefore so do we) - '$2a$6$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.' - - # NOTE: salts with padding bits set are technically malformed, - # but we can reliably correct & issue a warning for that. - ] - - platform_crypt_support = [ - ("freedbsd|openbsd|netbsd", True), - ("darwin", False), - # linux - may be present via addon, e.g. debian's libpam-unix2 - # solaris - depends on policy - ] - - #=================================================================== - # override some methods - #=================================================================== - def setUp(self): - # ensure builtin is enabled for duration of test. - if TEST_MODE("full") and self.backend == "builtin": - key = "PASSLIB_BUILTIN_BCRYPT" - orig = os.environ.get(key) - if orig: - self.addCleanup(os.environ.__setitem__, key, orig) - else: - self.addCleanup(os.environ.__delitem__, key) - os.environ[key] = "enabled" - super(_bcrypt_test, self).setUp() - - def populate_settings(self, kwds): - # builtin is still just way too slow. - if self.backend == "builtin": - kwds.setdefault("rounds", 4) - super(_bcrypt_test, self).populate_settings(kwds) - - #=================================================================== - # fuzz testing - #=================================================================== - 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? - 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 - except ImportError: - return - def check_pybcrypt(secret, hash): - "pybcrypt" - secret = to_native_str(secret, self.fuzz_password_encoding) - if hash.startswith(IDENT_2Y): - hash = IDENT_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.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2Y - from passlib.utils import to_native_str - try: - from bcryptor.engine import Engine - except ImportError: - return - def check_bcryptor(secret, hash): - "bcryptor" - secret = to_native_str(secret, self.fuzz_password_encoding) - 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 = 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 fuzz_setting_rounds(self): - # decrease default rounds for fuzz testing to speed up volume. - return randintgauss(5, 8, 6, 1) - - #=================================================================== - # custom tests - #=================================================================== - known_incorrect_padding = [ - # password, bad hash, good hash - - # 2 bits of salt padding set -# ("loppux", # \/ -# "$2a$12$oaQbBqq8JnSM1NHRPQGXORm4GCUMqp7meTnkft4zgSnrbhoKdDV0C", -# "$2a$12$oaQbBqq8JnSM1NHRPQGXOOm4GCUMqp7meTnkft4zgSnrbhoKdDV0C"), - ("test", # \/ - '$2a$04$oaQbBqq8JnSM1NHRPQGXORY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO', - '$2a$04$oaQbBqq8JnSM1NHRPQGXOOY4Vw3bdHKLIXTecPDRAcJ98cz1ilveO'), - - # all 4 bits of salt padding set -# ("Passlib11", # \/ -# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVcUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK", -# "$2a$12$M8mKpW9a2vZ7PYhq/8eJVOUtKxpo6j0zAezu0G/HAMYgMkhPu4fLK"), - ("test", # \/ - "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS", - "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"), - - # bad checksum padding - ("test", # \/ - "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIV", - "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"), - ] - - def test_90_bcrypt_padding(self): - "test passlib correctly handles bcrypt padding bits" - self.require_TEST_MODE("full") - # - # prevents reccurrence of issue 25 (https://code.google.com/p/passlib/issues/detail?id=25) - # were some unused bits were incorrectly set in bcrypt salt strings. - # (fixed since 1.5.3) - # - bcrypt = self.handler - corr_desc = ".*incorrectly set padding bits" - - # - # test encrypt() / genconfig() don't generate invalid salts anymore - # - def check_padding(hash): - assert hash.startswith("$2a$") and len(hash) >= 28 - self.assertTrue(hash[28] in '.Oeu', - "unused bits incorrectly set in hash: %r" % (hash,)) - for i in irange(6): - check_padding(bcrypt.genconfig()) - for i in irange(3): - check_padding(bcrypt.encrypt("bob", rounds=bcrypt.min_rounds)) - - # - # test genconfig() corrects invalid salts & issues warning. - # - with self.assertWarningList(["salt too large", corr_desc]): - hash = bcrypt.genconfig(salt="."*21 + "A.", rounds=5, relaxed=True) - self.assertEqual(hash, "$2a$05$" + "." * 22) - - # - # make sure genhash() corrects input - # - samples = self.known_incorrect_padding - for pwd, bad, good in samples: - with self.assertWarningList([corr_desc]): - self.assertEqual(bcrypt.genhash(pwd, bad), good) - with self.assertWarningList([]): - self.assertEqual(bcrypt.genhash(pwd, good), good) - - # - # and that verify() works good & bad - # - with self.assertWarningList([corr_desc]): - self.assertTrue(bcrypt.verify(pwd, bad)) - with self.assertWarningList([]): - self.assertTrue(bcrypt.verify(pwd, good)) - - # - # test normhash cleans things up correctly - # - for pwd, bad, good in samples: - with self.assertWarningList([corr_desc]): - self.assertEqual(bcrypt.normhash(bad), good) - with self.assertWarningList([]): - self.assertEqual(bcrypt.normhash(good), good) - self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc") - -hash.bcrypt._no_backends_msg() # call this for coverage purposes - -# create test cases for specific backends -bcrypt_pybcrypt_test, bcrypt_bcryptor_test, bcrypt_os_crypt_test, bcrypt_builtin_test = \ - _bcrypt_test.create_backend_cases(["pybcrypt", "bcryptor", "os_crypt", "builtin"]) - -#============================================================================= # bigcrypt #============================================================================= class bigcrypt_test(HandlerCase): |