def _init_htpasswd_context(): # start with schemes built into apache schemes = [ # builtin support added in apache 2.4 # (https://bz.apache.org/bugzilla/show_bug.cgi?id=49288) "bcrypt", # support not "builtin" to apache, instead it requires support through host's crypt(). # adding them here to allow editing htpasswd under windows and then deploying under unix. "sha256_crypt", "sha512_crypt", "des_crypt", # apache default as of 2.2.18, and still default in 2.4 "apr_md5_crypt", # NOTE: apache says ONLY intended for transitioning htpasswd <-> ldap "ldap_sha1", # NOTE: apache says ONLY supported on Windows, Netware, TPF "plaintext" ] # apache can verify anything supported by the native crypt(), # though htpasswd tool can only generate a limited set of hashes. # (this list may overlap w/ builtin apache schemes) schemes.extend(registry.get_supported_os_crypt_schemes()) # hack to remove dups and sort into preferred order preferred = schemes[:3] + ["apr_md5_crypt"] + schemes schemes = sorted(set(schemes), key=preferred.index) # NOTE: default will change to "portable" in passlib 2.0 return CryptContext(schemes, default=htpasswd_defaults['portable_apache_22'])
def _init_htpasswd_context(): # start with schemes built into apache schemes = [ # builtin support added in apache 2.4 # (https://bz.apache.org/bugzilla/show_bug.cgi?id=49288) "bcrypt", # support not "builtin" to apache, instead it requires support through host's crypt(). # adding them here to allow editing htpasswd under windows and then deploying under unix. "sha256_crypt", "sha512_crypt", "des_crypt", # apache default as of 2.2.18, and still default in 2.4 "apr_md5_crypt", # NOTE: apache says ONLY intended for transitioning htpasswd <-> ldap "ldap_sha1", # NOTE: apache says ONLY supported on Windows, Netware, TPF "plaintext", ] # apache can verify anything supported by the native crypt(), # though htpasswd tool can only generate a limited set of hashes. # (this list may overlap w/ builtin apache schemes) schemes.extend(registry.get_supported_os_crypt_schemes()) # hack to remove dups and sort into preferred order preferred = schemes[:3] + ["apr_md5_crypt"] + schemes schemes = sorted(set(schemes), key=preferred.index) # NOTE: default will change to "portable" in passlib 2.0 return CryptContext(schemes, default=htpasswd_defaults["portable_apache_22"])
def _iter_os_crypt_schemes(): """helper which iterates over supported os_crypt schemes""" out = registry.get_supported_os_crypt_schemes() if out: # only offer disabled handler if there's another scheme in front, # as this can't actually hash any passwords out += ("unix_disabled", ) return out
def test_crypt(self): """test crypt.crypt() wrappers""" from passlib.utils import has_crypt, safe_crypt, test_crypt from passlib.registry import get_supported_os_crypt_schemes, get_crypt_handler # test everything is disabled supported = get_supported_os_crypt_schemes() if not has_crypt: self.assertEqual(supported, ()) self.assertEqual(safe_crypt("test", "aa"), None) self.assertFalse(test_crypt("test", "aaqPiZY5xR5l.")) # des_crypt() hash of "test" raise self.skipTest("crypt.crypt() not available") # expect there to be something supported, if crypt() is present if not supported: # NOTE: failures here should be investigated. usually means one of: # 1) at least one of passlib's os_crypt detection routines is giving false negative # 2) crypt() ONLY supports some hash alg which passlib doesn't know about # 3) crypt() is present but completely disabled (never encountered this yet) raise self.fail("crypt() present, but no supported schemes found!") # pick cheap alg if possible, with minimum rounds, to speed up this test. # NOTE: trusting hasher class works properly (should have been verified using it's own UTs) for scheme in ("md5_crypt", "sha256_crypt"): if scheme in supported: break else: scheme = supported[-1] hasher = get_crypt_handler(scheme) if getattr(hasher, "min_rounds", None): hasher = hasher.using(rounds=hasher.min_rounds) # helpers to generate hashes & config strings to work with def get_hash(secret): assert isinstance(secret, unicode) hash = hasher.hash(secret) if isinstance(hash, bytes): # py2 hash = hash.decode("utf-8") assert isinstance(hash, unicode) return hash # test ascii password & return type s1 = u("test") h1 = get_hash(s1) result = safe_crypt(s1, h1) self.assertIsInstance(result, unicode) self.assertEqual(result, h1) self.assertEqual(safe_crypt(to_bytes(s1), to_bytes(h1)), h1) # make sure crypt doesn't just blindly return h1 for whatever we pass in h1x = h1[:-2] + 'xx' self.assertEqual(safe_crypt(s1, h1x), h1) # test utf-8 / unicode password s2 = u('test\u1234') h2 = get_hash(s2) self.assertEqual(safe_crypt(s2, h2), h2) self.assertEqual(safe_crypt(to_bytes(s2), to_bytes(h2)), h2) # test rejects null chars in password self.assertRaises(ValueError, safe_crypt, '\x00', h1) # check test_crypt() self.assertTrue(test_crypt("test", h1)) self.assertFalse(test_crypt("test", h1x)) # check crypt returning variant error indicators # some platforms return None on errors, others empty string, # The BSDs in some cases return ":" import passlib.utils as mod orig = mod._crypt try: retval = None mod._crypt = lambda secret, hash: retval for retval in [None, "", ":", ":0", "*0"]: self.assertEqual(safe_crypt("test", h1), None) self.assertFalse(test_crypt("test", h1)) retval = 'xxx' self.assertEqual(safe_crypt("test", h1), "xxx") self.assertFalse(test_crypt("test", h1)) finally: mod._crypt = orig