Загрузить файлы в «venv/Lib/site-packages/passlib/tests»

This commit is contained in:
2026-07-02 18:52:07 +00:00
parent 717a5d6b7c
commit 63d5ee4be8
5 changed files with 1367 additions and 0 deletions

View File

@@ -0,0 +1,870 @@
"""tests for passlib.hash -- (c) Assurance Technologies 2003-2009"""
#=============================================================================
# imports
#=============================================================================
from __future__ import with_statement
# core
import re
import hashlib
from logging import getLogger
import warnings
# site
# pkg
from passlib.hash import ldap_md5, sha256_crypt
from passlib.exc import MissingBackendError, PasslibHashWarning
from passlib.utils.compat import str_to_uascii, \
uascii_to_str, unicode
import passlib.utils.handlers as uh
from passlib.tests.utils import HandlerCase, TestCase
from passlib.utils.compat import u
# module
log = getLogger(__name__)
#=============================================================================
# utils
#=============================================================================
def _makelang(alphabet, size):
"""generate all strings of given size using alphabet"""
def helper(size):
if size < 2:
for char in alphabet:
yield char
else:
for char in alphabet:
for tail in helper(size-1):
yield char+tail
return set(helper(size))
#=============================================================================
# test GenericHandler & associates mixin classes
#=============================================================================
class SkeletonTest(TestCase):
"""test hash support classes"""
#===================================================================
# StaticHandler
#===================================================================
def test_00_static_handler(self):
"""test StaticHandler class"""
class d1(uh.StaticHandler):
name = "d1"
context_kwds = ("flag",)
_hash_prefix = u("_")
checksum_chars = u("ab")
checksum_size = 1
def __init__(self, flag=False, **kwds):
super(d1, self).__init__(**kwds)
self.flag = flag
def _calc_checksum(self, secret):
return u('b') if self.flag else u('a')
# check default identify method
self.assertTrue(d1.identify(u('_a')))
self.assertTrue(d1.identify(b'_a'))
self.assertTrue(d1.identify(u('_b')))
self.assertFalse(d1.identify(u('_c')))
self.assertFalse(d1.identify(b'_c'))
self.assertFalse(d1.identify(u('a')))
self.assertFalse(d1.identify(u('b')))
self.assertFalse(d1.identify(u('c')))
self.assertRaises(TypeError, d1.identify, None)
self.assertRaises(TypeError, d1.identify, 1)
# check default genconfig method
self.assertEqual(d1.genconfig(), d1.hash(""))
# check default verify method
self.assertTrue(d1.verify('s', b'_a'))
self.assertTrue(d1.verify('s',u('_a')))
self.assertFalse(d1.verify('s', b'_b'))
self.assertFalse(d1.verify('s',u('_b')))
self.assertTrue(d1.verify('s', b'_b', flag=True))
self.assertRaises(ValueError, d1.verify, 's', b'_c')
self.assertRaises(ValueError, d1.verify, 's', u('_c'))
# check default hash method
self.assertEqual(d1.hash('s'), '_a')
self.assertEqual(d1.hash('s', flag=True), '_b')
def test_01_calc_checksum_hack(self):
"""test StaticHandler legacy attr"""
# release 1.5 StaticHandler required genhash(),
# not _calc_checksum, be implemented. we have backward compat wrapper,
# this tests that it works.
class d1(uh.StaticHandler):
name = "d1"
@classmethod
def identify(cls, hash):
if not hash or len(hash) != 40:
return False
try:
int(hash, 16)
except ValueError:
return False
return True
@classmethod
def genhash(cls, secret, hash):
if secret is None:
raise TypeError("no secret provided")
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
# NOTE: have to support hash=None since this is test of legacy 1.5 api
if hash is not None and not cls.identify(hash):
raise ValueError("invalid hash")
return hashlib.sha1(b"xyz" + secret).hexdigest()
@classmethod
def verify(cls, secret, hash):
if hash is None:
raise ValueError("no hash specified")
return cls.genhash(secret, hash) == hash.lower()
# hash should issue api warnings, but everything else should be fine.
with self.assertWarningList("d1.*should be updated.*_calc_checksum"):
hash = d1.hash("test")
self.assertEqual(hash, '7c622762588a0e5cc786ad0a143156f9fd38eea3')
self.assertTrue(d1.verify("test", hash))
self.assertFalse(d1.verify("xtest", hash))
# not defining genhash either, however, should cause NotImplementedError
del d1.genhash
self.assertRaises(NotImplementedError, d1.hash, 'test')
#===================================================================
# GenericHandler & mixins
#===================================================================
def test_10_identify(self):
"""test GenericHandler.identify()"""
class d1(uh.GenericHandler):
@classmethod
def from_string(cls, hash):
if isinstance(hash, bytes):
hash = hash.decode("ascii")
if hash == u('a'):
return cls(checksum=hash)
else:
raise ValueError
# check fallback
self.assertRaises(TypeError, d1.identify, None)
self.assertRaises(TypeError, d1.identify, 1)
self.assertFalse(d1.identify(''))
self.assertTrue(d1.identify('a'))
self.assertFalse(d1.identify('b'))
# check regexp
d1._hash_regex = re.compile(u('@.'))
self.assertRaises(TypeError, d1.identify, None)
self.assertRaises(TypeError, d1.identify, 1)
self.assertTrue(d1.identify('@a'))
self.assertFalse(d1.identify('a'))
del d1._hash_regex
# check ident-based
d1.ident = u('!')
self.assertRaises(TypeError, d1.identify, None)
self.assertRaises(TypeError, d1.identify, 1)
self.assertTrue(d1.identify('!a'))
self.assertFalse(d1.identify('a'))
del d1.ident
def test_11_norm_checksum(self):
"""test GenericHandler checksum handling"""
# setup helpers
class d1(uh.GenericHandler):
name = 'd1'
checksum_size = 4
checksum_chars = u('xz')
def norm_checksum(checksum=None, **k):
return d1(checksum=checksum, **k).checksum
# too small
self.assertRaises(ValueError, norm_checksum, u('xxx'))
# right size
self.assertEqual(norm_checksum(u('xxxx')), u('xxxx'))
self.assertEqual(norm_checksum(u('xzxz')), u('xzxz'))
# too large
self.assertRaises(ValueError, norm_checksum, u('xxxxx'))
# wrong chars
self.assertRaises(ValueError, norm_checksum, u('xxyx'))
# wrong type
self.assertRaises(TypeError, norm_checksum, b'xxyx')
# relaxed
# NOTE: this could be turned back on if we test _norm_checksum() directly...
#with self.assertWarningList("checksum should be unicode"):
# self.assertEqual(norm_checksum(b'xxzx', relaxed=True), u('xxzx'))
#self.assertRaises(TypeError, norm_checksum, 1, relaxed=True)
# test _stub_checksum behavior
self.assertEqual(d1()._stub_checksum, u('xxxx'))
def test_12_norm_checksum_raw(self):
"""test GenericHandler + HasRawChecksum mixin"""
class d1(uh.HasRawChecksum, uh.GenericHandler):
name = 'd1'
checksum_size = 4
def norm_checksum(*a, **k):
return d1(*a, **k).checksum
# test bytes
self.assertEqual(norm_checksum(b'1234'), b'1234')
# test unicode
self.assertRaises(TypeError, norm_checksum, u('xxyx'))
# NOTE: this could be turned back on if we test _norm_checksum() directly...
# self.assertRaises(TypeError, norm_checksum, u('xxyx'), relaxed=True)
# test _stub_checksum behavior
self.assertEqual(d1()._stub_checksum, b'\x00'*4)
def test_20_norm_salt(self):
"""test GenericHandler + HasSalt mixin"""
# setup helpers
class d1(uh.HasSalt, uh.GenericHandler):
name = 'd1'
setting_kwds = ('salt',)
min_salt_size = 2
max_salt_size = 4
default_salt_size = 3
salt_chars = 'ab'
def norm_salt(**k):
return d1(**k).salt
def gen_salt(sz, **k):
return d1.using(salt_size=sz, **k)(use_defaults=True).salt
salts2 = _makelang('ab', 2)
salts3 = _makelang('ab', 3)
salts4 = _makelang('ab', 4)
# check salt=None
self.assertRaises(TypeError, norm_salt)
self.assertRaises(TypeError, norm_salt, salt=None)
self.assertIn(norm_salt(use_defaults=True), salts3)
# check explicit salts
with warnings.catch_warnings(record=True) as wlog:
# check too-small salts
self.assertRaises(ValueError, norm_salt, salt='')
self.assertRaises(ValueError, norm_salt, salt='a')
self.consumeWarningList(wlog)
# check correct salts
self.assertEqual(norm_salt(salt='ab'), 'ab')
self.assertEqual(norm_salt(salt='aba'), 'aba')
self.assertEqual(norm_salt(salt='abba'), 'abba')
self.consumeWarningList(wlog)
# check too-large salts
self.assertRaises(ValueError, norm_salt, salt='aaaabb')
self.consumeWarningList(wlog)
# check generated salts
with warnings.catch_warnings(record=True) as wlog:
# check too-small salt size
self.assertRaises(ValueError, gen_salt, 0)
self.assertRaises(ValueError, gen_salt, 1)
self.consumeWarningList(wlog)
# check correct salt size
self.assertIn(gen_salt(2), salts2)
self.assertIn(gen_salt(3), salts3)
self.assertIn(gen_salt(4), salts4)
self.consumeWarningList(wlog)
# check too-large salt size
self.assertRaises(ValueError, gen_salt, 5)
self.consumeWarningList(wlog)
self.assertIn(gen_salt(5, relaxed=True), salts4)
self.consumeWarningList(wlog, ["salt_size.*above max_salt_size"])
# test with max_salt_size=None
del d1.max_salt_size
with self.assertWarningList([]):
self.assertEqual(len(gen_salt(None)), 3)
self.assertEqual(len(gen_salt(5)), 5)
# TODO: test HasRawSalt mixin
def test_30_init_rounds(self):
"""test GenericHandler + HasRounds mixin"""
# setup helpers
class d1(uh.HasRounds, uh.GenericHandler):
name = 'd1'
setting_kwds = ('rounds',)
min_rounds = 1
max_rounds = 3
default_rounds = 2
# NOTE: really is testing _init_rounds(), could dup to test _norm_rounds() via .replace
def norm_rounds(**k):
return d1(**k).rounds
# check rounds=None
self.assertRaises(TypeError, norm_rounds)
self.assertRaises(TypeError, norm_rounds, rounds=None)
self.assertEqual(norm_rounds(use_defaults=True), 2)
# check rounds=non int
self.assertRaises(TypeError, norm_rounds, rounds=1.5)
# check explicit rounds
with warnings.catch_warnings(record=True) as wlog:
# too small
self.assertRaises(ValueError, norm_rounds, rounds=0)
self.consumeWarningList(wlog)
# just right
self.assertEqual(norm_rounds(rounds=1), 1)
self.assertEqual(norm_rounds(rounds=2), 2)
self.assertEqual(norm_rounds(rounds=3), 3)
self.consumeWarningList(wlog)
# too large
self.assertRaises(ValueError, norm_rounds, rounds=4)
self.consumeWarningList(wlog)
# check no default rounds
d1.default_rounds = None
self.assertRaises(TypeError, norm_rounds, use_defaults=True)
def test_40_backends(self):
"""test GenericHandler + HasManyBackends mixin"""
class d1(uh.HasManyBackends, uh.GenericHandler):
name = 'd1'
setting_kwds = ()
backends = ("a", "b")
_enable_a = False
_enable_b = False
@classmethod
def _load_backend_a(cls):
if cls._enable_a:
cls._set_calc_checksum_backend(cls._calc_checksum_a)
return True
else:
return False
@classmethod
def _load_backend_b(cls):
if cls._enable_b:
cls._set_calc_checksum_backend(cls._calc_checksum_b)
return True
else:
return False
def _calc_checksum_a(self, secret):
return 'a'
def _calc_checksum_b(self, secret):
return 'b'
# test no backends
self.assertRaises(MissingBackendError, d1.get_backend)
self.assertRaises(MissingBackendError, d1.set_backend)
self.assertRaises(MissingBackendError, d1.set_backend, 'any')
self.assertRaises(MissingBackendError, d1.set_backend, 'default')
self.assertFalse(d1.has_backend())
# enable 'b' backend
d1._enable_b = True
# test lazy load
obj = d1()
self.assertEqual(obj._calc_checksum('s'), 'b')
# test repeat load
d1.set_backend('b')
d1.set_backend('any')
self.assertEqual(obj._calc_checksum('s'), 'b')
# test unavailable
self.assertRaises(MissingBackendError, d1.set_backend, 'a')
self.assertTrue(d1.has_backend('b'))
self.assertFalse(d1.has_backend('a'))
# enable 'a' backend also
d1._enable_a = True
# test explicit
self.assertTrue(d1.has_backend())
d1.set_backend('a')
self.assertEqual(obj._calc_checksum('s'), 'a')
# test unknown backend
self.assertRaises(ValueError, d1.set_backend, 'c')
self.assertRaises(ValueError, d1.has_backend, 'c')
# test error thrown if _has & _load are mixed
d1.set_backend("b") # switch away from 'a' so next call actually checks loader
class d2(d1):
_has_backend_a = True
self.assertRaises(AssertionError, d2.has_backend, "a")
def test_41_backends(self):
"""test GenericHandler + HasManyBackends mixin (deprecated api)"""
warnings.filterwarnings("ignore",
category=DeprecationWarning,
message=r".* support for \._has_backend_.* is deprecated.*",
)
class d1(uh.HasManyBackends, uh.GenericHandler):
name = 'd1'
setting_kwds = ()
backends = ("a", "b")
_has_backend_a = False
_has_backend_b = False
def _calc_checksum_a(self, secret):
return 'a'
def _calc_checksum_b(self, secret):
return 'b'
# test no backends
self.assertRaises(MissingBackendError, d1.get_backend)
self.assertRaises(MissingBackendError, d1.set_backend)
self.assertRaises(MissingBackendError, d1.set_backend, 'any')
self.assertRaises(MissingBackendError, d1.set_backend, 'default')
self.assertFalse(d1.has_backend())
# enable 'b' backend
d1._has_backend_b = True
# test lazy load
obj = d1()
self.assertEqual(obj._calc_checksum('s'), 'b')
# test repeat load
d1.set_backend('b')
d1.set_backend('any')
self.assertEqual(obj._calc_checksum('s'), 'b')
# test unavailable
self.assertRaises(MissingBackendError, d1.set_backend, 'a')
self.assertTrue(d1.has_backend('b'))
self.assertFalse(d1.has_backend('a'))
# enable 'a' backend also
d1._has_backend_a = True
# test explicit
self.assertTrue(d1.has_backend())
d1.set_backend('a')
self.assertEqual(obj._calc_checksum('s'), 'a')
# test unknown backend
self.assertRaises(ValueError, d1.set_backend, 'c')
self.assertRaises(ValueError, d1.has_backend, 'c')
def test_50_norm_ident(self):
"""test GenericHandler + HasManyIdents"""
# setup helpers
class d1(uh.HasManyIdents, uh.GenericHandler):
name = 'd1'
setting_kwds = ('ident',)
default_ident = u("!A")
ident_values = (u("!A"), u("!B"))
ident_aliases = { u("A"): u("!A")}
def norm_ident(**k):
return d1(**k).ident
# check ident=None
self.assertRaises(TypeError, norm_ident)
self.assertRaises(TypeError, norm_ident, ident=None)
self.assertEqual(norm_ident(use_defaults=True), u('!A'))
# check valid idents
self.assertEqual(norm_ident(ident=u('!A')), u('!A'))
self.assertEqual(norm_ident(ident=u('!B')), u('!B'))
self.assertRaises(ValueError, norm_ident, ident=u('!C'))
# check aliases
self.assertEqual(norm_ident(ident=u('A')), u('!A'))
# check invalid idents
self.assertRaises(ValueError, norm_ident, ident=u('B'))
# check identify is honoring ident system
self.assertTrue(d1.identify(u("!Axxx")))
self.assertTrue(d1.identify(u("!Bxxx")))
self.assertFalse(d1.identify(u("!Cxxx")))
self.assertFalse(d1.identify(u("A")))
self.assertFalse(d1.identify(u("")))
self.assertRaises(TypeError, d1.identify, None)
self.assertRaises(TypeError, d1.identify, 1)
# check default_ident missing is detected.
d1.default_ident = None
self.assertRaises(AssertionError, norm_ident, use_defaults=True)
#===================================================================
# experimental - the following methods are not finished or tested,
# but way work correctly for some hashes
#===================================================================
def test_91_parsehash(self):
"""test parsehash()"""
# NOTE: this just tests some existing GenericHandler classes
from passlib import hash
#
# parsehash()
#
# simple hash w/ salt
result = hash.des_crypt.parsehash("OgAwTx2l6NADI")
self.assertEqual(result, {'checksum': u('AwTx2l6NADI'), 'salt': u('Og')})
# parse rounds and extra implicit_rounds flag
h = '$5$LKO/Ute40T3FNF95$U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9'
s = u('LKO/Ute40T3FNF95')
c = u('U0prpBQd4PloSGU0pnpM4z9wKn4vZ1.jsrzQfPqxph9')
result = hash.sha256_crypt.parsehash(h)
self.assertEqual(result, dict(salt=s, rounds=5000,
implicit_rounds=True, checksum=c))
# omit checksum
result = hash.sha256_crypt.parsehash(h, checksum=False)
self.assertEqual(result, dict(salt=s, rounds=5000, implicit_rounds=True))
# sanitize
result = hash.sha256_crypt.parsehash(h, sanitize=True)
self.assertEqual(result, dict(rounds=5000, implicit_rounds=True,
salt=u('LK**************'),
checksum=u('U0pr***************************************')))
# parse w/o implicit rounds flag
result = hash.sha256_crypt.parsehash('$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3')
self.assertEqual(result, dict(
checksum=u('YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'),
salt=u('uy/jIAhCetNCTtb0'),
rounds=10428,
))
# parsing of raw checksums & salts
h1 = '$pbkdf2$60000$DoEwpvQeA8B4T.k951yLUQ$O26Y3/NJEiLCVaOVPxGXshyjW8k'
result = hash.pbkdf2_sha1.parsehash(h1)
self.assertEqual(result, dict(
checksum=b';n\x98\xdf\xf3I\x12"\xc2U\xa3\x95?\x11\x97\xb2\x1c\xa3[\xc9',
rounds=60000,
salt=b'\x0e\x810\xa6\xf4\x1e\x03\xc0xO\xe9=\xe7\\\x8bQ',
))
# sanitizing of raw checksums & salts
result = hash.pbkdf2_sha1.parsehash(h1, sanitize=True)
self.assertEqual(result, dict(
checksum=u('O26************************'),
rounds=60000,
salt=u('Do********************'),
))
def test_92_bitsize(self):
"""test bitsize()"""
# NOTE: this just tests some existing GenericHandler classes
from passlib import hash
# no rounds
self.assertEqual(hash.des_crypt.bitsize(),
{'checksum': 66, 'salt': 12})
# log2 rounds
self.assertEqual(hash.bcrypt.bitsize(),
{'checksum': 186, 'salt': 132})
# linear rounds
# NOTE: +3 comes from int(math.log(.1,2)),
# where 0.1 = 10% = default allowed variation in rounds
self.patchAttr(hash.sha256_crypt, "default_rounds", 1 << (14 + 3))
self.assertEqual(hash.sha256_crypt.bitsize(),
{'checksum': 258, 'rounds': 14, 'salt': 96})
# raw checksum
self.patchAttr(hash.pbkdf2_sha1, "default_rounds", 1 << (13 + 3))
self.assertEqual(hash.pbkdf2_sha1.bitsize(),
{'checksum': 160, 'rounds': 13, 'salt': 128})
# TODO: handle fshp correctly, and other glitches noted in code.
##self.assertEqual(hash.fshp.bitsize(variant=1),
## {'checksum': 256, 'rounds': 13, 'salt': 128})
#===================================================================
# eoc
#===================================================================
#=============================================================================
# PrefixWrapper
#=============================================================================
class dummy_handler_in_registry(object):
"""context manager that inserts dummy handler in registry"""
def __init__(self, name):
self.name = name
self.dummy = type('dummy_' + name, (uh.GenericHandler,), dict(
name=name,
setting_kwds=(),
))
def __enter__(self):
from passlib import registry
registry._unload_handler_name(self.name, locations=False)
registry.register_crypt_handler(self.dummy)
assert registry.get_crypt_handler(self.name) is self.dummy
return self.dummy
def __exit__(self, *exc_info):
from passlib import registry
registry._unload_handler_name(self.name, locations=False)
class PrefixWrapperTest(TestCase):
"""test PrefixWrapper class"""
def test_00_lazy_loading(self):
"""test PrefixWrapper lazy loading of handler"""
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}", lazy=True)
# check base state
self.assertEqual(d1._wrapped_name, "ldap_md5")
self.assertIs(d1._wrapped_handler, None)
# check loading works
self.assertIs(d1.wrapped, ldap_md5)
self.assertIs(d1._wrapped_handler, ldap_md5)
# replace w/ wrong handler, make sure doesn't reload w/ dummy
with dummy_handler_in_registry("ldap_md5") as dummy:
self.assertIs(d1.wrapped, ldap_md5)
def test_01_active_loading(self):
"""test PrefixWrapper active loading of handler"""
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}")
# check base state
self.assertEqual(d1._wrapped_name, "ldap_md5")
self.assertIs(d1._wrapped_handler, ldap_md5)
self.assertIs(d1.wrapped, ldap_md5)
# replace w/ wrong handler, make sure doesn't reload w/ dummy
with dummy_handler_in_registry("ldap_md5") as dummy:
self.assertIs(d1.wrapped, ldap_md5)
def test_02_explicit(self):
"""test PrefixWrapper with explicitly specified handler"""
d1 = uh.PrefixWrapper("d1", ldap_md5, "{XXX}", "{MD5}")
# check base state
self.assertEqual(d1._wrapped_name, None)
self.assertIs(d1._wrapped_handler, ldap_md5)
self.assertIs(d1.wrapped, ldap_md5)
# replace w/ wrong handler, make sure doesn't reload w/ dummy
with dummy_handler_in_registry("ldap_md5") as dummy:
self.assertIs(d1.wrapped, ldap_md5)
def test_10_wrapped_attributes(self):
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}")
self.assertEqual(d1.name, "d1")
self.assertIs(d1.setting_kwds, ldap_md5.setting_kwds)
self.assertFalse('max_rounds' in dir(d1))
d2 = uh.PrefixWrapper("d2", "sha256_crypt", "{XXX}")
self.assertIs(d2.setting_kwds, sha256_crypt.setting_kwds)
self.assertTrue('max_rounds' in dir(d2))
def test_11_wrapped_methods(self):
d1 = uh.PrefixWrapper("d1", "ldap_md5", "{XXX}", "{MD5}")
dph = "{XXX}X03MO1qnZdYdgyfeuILPmQ=="
lph = "{MD5}X03MO1qnZdYdgyfeuILPmQ=="
# genconfig
self.assertEqual(d1.genconfig(), '{XXX}1B2M2Y8AsgTpgAmY7PhCfg==')
# genhash
self.assertRaises(TypeError, d1.genhash, "password", None)
self.assertEqual(d1.genhash("password", dph), dph)
self.assertRaises(ValueError, d1.genhash, "password", lph)
# hash
self.assertEqual(d1.hash("password"), dph)
# identify
self.assertTrue(d1.identify(dph))
self.assertFalse(d1.identify(lph))
# verify
self.assertRaises(ValueError, d1.verify, "password", lph)
self.assertTrue(d1.verify("password", dph))
def test_12_ident(self):
# test ident is proxied
h = uh.PrefixWrapper("h2", "ldap_md5", "{XXX}")
self.assertEqual(h.ident, u("{XXX}{MD5}"))
self.assertIs(h.ident_values, None)
# test lack of ident means no proxy
h = uh.PrefixWrapper("h2", "des_crypt", "{XXX}")
self.assertIs(h.ident, None)
self.assertIs(h.ident_values, None)
# test orig_prefix disabled ident proxy
h = uh.PrefixWrapper("h1", "ldap_md5", "{XXX}", "{MD5}")
self.assertIs(h.ident, None)
self.assertIs(h.ident_values, None)
# test custom ident overrides default
h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{X")
self.assertEqual(h.ident, u("{X"))
self.assertIs(h.ident_values, None)
# test custom ident must match
h = uh.PrefixWrapper("h3", "ldap_md5", "{XXX}", ident="{XXX}A")
self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5",
"{XXX}", ident="{XY")
self.assertRaises(ValueError, uh.PrefixWrapper, "h3", "ldap_md5",
"{XXX}", ident="{XXXX")
# test ident_values is proxied
h = uh.PrefixWrapper("h4", "phpass", "{XXX}")
self.assertIs(h.ident, None)
self.assertEqual(h.ident_values, (u("{XXX}$P$"), u("{XXX}$H$")))
# test ident=True means use prefix even if hash has no ident.
h = uh.PrefixWrapper("h5", "des_crypt", "{XXX}", ident=True)
self.assertEqual(h.ident, u("{XXX}"))
self.assertIs(h.ident_values, None)
# ... but requires prefix
self.assertRaises(ValueError, uh.PrefixWrapper, "h6", "des_crypt", ident=True)
# orig_prefix + HasManyIdent - warning
with self.assertWarningList("orig_prefix.*may not work correctly"):
h = uh.PrefixWrapper("h7", "phpass", orig_prefix="$", prefix="?")
self.assertEqual(h.ident_values, None) # TODO: should output (u("?P$"), u("?H$")))
self.assertEqual(h.ident, None)
def test_13_repr(self):
"""test repr()"""
h = uh.PrefixWrapper("h2", "md5_crypt", "{XXX}", orig_prefix="$1$")
self.assertRegex(repr(h),
r"""(?x)^PrefixWrapper\(
['"]h2['"],\s+
['"]md5_crypt['"],\s+
prefix=u?["']{XXX}['"],\s+
orig_prefix=u?["']\$1\$['"]
\)$""")
def test_14_bad_hash(self):
"""test orig_prefix sanity check"""
# shoudl throw InvalidHashError if wrapped hash doesn't begin
# with orig_prefix.
h = uh.PrefixWrapper("h2", "md5_crypt", orig_prefix="$6$")
self.assertRaises(ValueError, h.hash, 'test')
#=============================================================================
# sample algorithms - these serve as known quantities
# to test the unittests themselves, as well as other
# parts of passlib. they shouldn't be used as actual password schemes.
#=============================================================================
class UnsaltedHash(uh.StaticHandler):
"""test algorithm which lacks a salt"""
name = "unsalted_test_hash"
checksum_chars = uh.LOWER_HEX_CHARS
checksum_size = 40
def _calc_checksum(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
data = b"boblious" + secret
return str_to_uascii(hashlib.sha1(data).hexdigest())
class SaltedHash(uh.HasSalt, uh.GenericHandler):
"""test algorithm with a salt"""
name = "salted_test_hash"
setting_kwds = ("salt",)
min_salt_size = 2
max_salt_size = 4
checksum_size = 40
salt_chars = checksum_chars = uh.LOWER_HEX_CHARS
_hash_regex = re.compile(u("^@salt[0-9a-f]{42,44}$"))
@classmethod
def from_string(cls, hash):
if not cls.identify(hash):
raise uh.exc.InvalidHashError(cls)
if isinstance(hash, bytes):
hash = hash.decode("ascii")
return cls(salt=hash[5:-40], checksum=hash[-40:])
def to_string(self):
hash = u("@salt%s%s") % (self.salt, self.checksum)
return uascii_to_str(hash)
def _calc_checksum(self, secret):
if isinstance(secret, unicode):
secret = secret.encode("utf-8")
data = self.salt.encode("ascii") + secret + self.salt.encode("ascii")
return str_to_uascii(hashlib.sha1(data).hexdigest())
#=============================================================================
# test sample algorithms - really a self-test of HandlerCase
#=============================================================================
# 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):
self.assertRaises(TypeError, UnsaltedHash, salt='x')
self.assertRaises(TypeError, UnsaltedHash.genconfig, rounds=1)
class SaltedHashTest(HandlerCase):
handler = SaltedHash
known_correct_hashes = [
("password", '@salt77d71f8fe74f314dac946766c1ac4a2a58365482c0'),
(UPASS_TEMP, '@salt9f978a9bfe360d069b0c13f2afecd570447407fa7e48'),
]
def test_bad_kwds(self):
stub = SaltedHash(use_defaults=True)._stub_checksum
self.assertRaises(TypeError, SaltedHash, checksum=stub, salt=None)
self.assertRaises(ValueError, SaltedHash, checksum=stub, salt='xxx')
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,41 @@
"""
passlib.tests -- tests for passlib.utils.md4
.. warning::
This module & it's functions have been deprecated, and superceded
by the functions in passlib.crypto. This file is being maintained
until the deprecated functions are removed, and is only present prevent
historical regressions up to that point. New and more thorough testing
is being done by the replacement tests in ``test_utils_crypto_builtin_md4``.
"""
#=============================================================================
# imports
#=============================================================================
# core
import warnings
# site
# pkg
# module
from passlib.tests.test_crypto_builtin_md4 import _Common_MD4_Test
# local
__all__ = [
"Legacy_MD4_Test",
]
#=============================================================================
# test pure-python MD4 implementation
#=============================================================================
class Legacy_MD4_Test(_Common_MD4_Test):
descriptionPrefix = "passlib.utils.md4.md4()"
def setUp(self):
super(Legacy_MD4_Test, self).setUp()
warnings.filterwarnings("ignore", ".*passlib.utils.md4.*deprecated", DeprecationWarning)
def get_md4_const(self):
from passlib.utils.md4 import md4
return md4
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,323 @@
"""
passlib.tests -- tests for passlib.utils.pbkdf2
.. warning::
This module & it's functions have been deprecated, and superceded
by the functions in passlib.crypto. This file is being maintained
until the deprecated functions are removed, and is only present prevent
historical regressions up to that point. New and more thorough testing
is being done by the replacement tests in ``test_utils_crypto.py``.
"""
#=============================================================================
# imports
#=============================================================================
from __future__ import with_statement
# core
import hashlib
import warnings
# site
# pkg
# module
from passlib.utils.compat import u, JYTHON
from passlib.tests.utils import TestCase, hb
#=============================================================================
# test assorted crypto helpers
#=============================================================================
class UtilsTest(TestCase):
"""test various utils functions"""
descriptionPrefix = "passlib.utils.pbkdf2"
ndn_formats = ["hashlib", "iana"]
ndn_values = [
# (iana name, hashlib name, ... other unnormalized names)
("md5", "md5", "SCRAM-MD5-PLUS", "MD-5"),
("sha1", "sha-1", "SCRAM-SHA-1", "SHA1"),
("sha256", "sha-256", "SHA_256", "sha2-256"),
("ripemd160", "ripemd-160", "SCRAM-RIPEMD-160", "RIPEmd160",
# NOTE: there was an older "RIPEMD" & "RIPEMD-128", but python treates "RIPEMD"
# as alias for "RIPEMD-160"
"ripemd", "SCRAM-RIPEMD"),
("test128", "test-128", "TEST128"),
("test2", "test2", "TEST-2"),
("test3_128", "test3-128", "TEST-3-128"),
]
def setUp(self):
super(UtilsTest, self).setUp()
warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning)
def test_norm_hash_name(self):
"""norm_hash_name()"""
from itertools import chain
from passlib.utils.pbkdf2 import norm_hash_name
from passlib.crypto.digest import _known_hash_names
# test formats
for format in self.ndn_formats:
norm_hash_name("md4", format)
self.assertRaises(ValueError, norm_hash_name, "md4", None)
self.assertRaises(ValueError, norm_hash_name, "md4", "fake")
# test types
self.assertEqual(norm_hash_name(u("MD4")), "md4")
self.assertEqual(norm_hash_name(b"MD4"), "md4")
self.assertRaises(TypeError, norm_hash_name, None)
# test selected results
with warnings.catch_warnings():
warnings.filterwarnings("ignore", '.*unknown hash')
for row in chain(_known_hash_names, self.ndn_values):
for idx, format in enumerate(self.ndn_formats):
correct = row[idx]
for value in row:
result = norm_hash_name(value, format)
self.assertEqual(result, correct,
"name=%r, format=%r:" % (value,
format))
#=============================================================================
# test PBKDF1 support
#=============================================================================
class Pbkdf1_Test(TestCase):
"""test kdf helpers"""
descriptionPrefix = "passlib.utils.pbkdf2.pbkdf1()"
pbkdf1_tests = [
# (password, salt, rounds, keylen, hash, result)
#
# from http://www.di-mgt.com.au/cryptoKDFs.html
#
(b'password', hb('78578E5A5D63CB06'), 1000, 16, 'sha1', hb('dc19847e05c64d2faf10ebfb4a3d2a20')),
#
# custom
#
(b'password', b'salt', 1000, 0, 'md5', b''),
(b'password', b'salt', 1000, 1, 'md5', hb('84')),
(b'password', b'salt', 1000, 8, 'md5', hb('8475c6a8531a5d27')),
(b'password', b'salt', 1000, 16, 'md5', hb('8475c6a8531a5d27e386cd496457812c')),
(b'password', b'salt', 1000, None, 'md5', hb('8475c6a8531a5d27e386cd496457812c')),
(b'password', b'salt', 1000, None, 'sha1', hb('4a8fd48e426ed081b535be5769892fa396293efb')),
]
if not JYTHON:
pbkdf1_tests.append(
(b'password', b'salt', 1000, None, 'md4', hb('f7f2e91100a8f96190f2dd177cb26453'))
)
def setUp(self):
super(Pbkdf1_Test, self).setUp()
warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning)
def test_known(self):
"""test reference vectors"""
from passlib.utils.pbkdf2 import pbkdf1
for secret, salt, rounds, keylen, digest, correct in self.pbkdf1_tests:
result = pbkdf1(secret, salt, rounds, keylen, digest)
self.assertEqual(result, correct)
def test_border(self):
"""test border cases"""
from passlib.utils.pbkdf2 import pbkdf1
def helper(secret=b'secret', salt=b'salt', rounds=1, keylen=1, hash='md5'):
return pbkdf1(secret, salt, rounds, keylen, hash)
helper()
# salt/secret wrong type
self.assertRaises(TypeError, helper, secret=1)
self.assertRaises(TypeError, helper, salt=1)
# non-existent hashes
self.assertRaises(ValueError, helper, hash='missing')
# rounds < 1 and wrong type
self.assertRaises(ValueError, helper, rounds=0)
self.assertRaises(TypeError, helper, rounds='1')
# keylen < 0, keylen > block_size, and wrong type
self.assertRaises(ValueError, helper, keylen=-1)
self.assertRaises(ValueError, helper, keylen=17, hash='md5')
self.assertRaises(TypeError, helper, keylen='1')
#=============================================================================
# test PBKDF2 support
#=============================================================================
class Pbkdf2_Test(TestCase):
"""test pbkdf2() support"""
descriptionPrefix = "passlib.utils.pbkdf2.pbkdf2()"
pbkdf2_test_vectors = [
# (result, secret, salt, rounds, keylen, prf="sha1")
#
# from rfc 3962
#
# test case 1 / 128 bit
(
hb("cdedb5281bb2f801565a1122b2563515"),
b"password", b"ATHENA.MIT.EDUraeburn", 1, 16
),
# test case 2 / 128 bit
(
hb("01dbee7f4a9e243e988b62c73cda935d"),
b"password", b"ATHENA.MIT.EDUraeburn", 2, 16
),
# test case 2 / 256 bit
(
hb("01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"),
b"password", b"ATHENA.MIT.EDUraeburn", 2, 32
),
# test case 3 / 256 bit
(
hb("5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"),
b"password", b"ATHENA.MIT.EDUraeburn", 1200, 32
),
# test case 4 / 256 bit
(
hb("d1daa78615f287e6a1c8b120d7062a493f98d203e6be49a6adf4fa574b6e64ee"),
b"password", b'\x12\x34\x56\x78\x78\x56\x34\x12', 5, 32
),
# test case 5 / 256 bit
(
hb("139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"),
b"X"*64, b"pass phrase equals block size", 1200, 32
),
# test case 6 / 256 bit
(
hb("9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"),
b"X"*65, b"pass phrase exceeds block size", 1200, 32
),
#
# from rfc 6070
#
(
hb("0c60c80f961f0e71f3a9b524af6012062fe037a6"),
b"password", b"salt", 1, 20,
),
(
hb("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
b"password", b"salt", 2, 20,
),
(
hb("4b007901b765489abead49d926f721d065a429c1"),
b"password", b"salt", 4096, 20,
),
# just runs too long - could enable if ALL option is set
##(
##
## unhexlify("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"),
## "password", "salt", 16777216, 20,
##),
(
hb("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"),
b"passwordPASSWORDpassword",
b"saltSALTsaltSALTsaltSALTsaltSALTsalt",
4096, 25,
),
(
hb("56fa6aa75548099dcc37d7f03425e0c3"),
b"pass\00word", b"sa\00lt", 4096, 16,
),
#
# from example in http://grub.enbug.org/Authentication
#
(
hb("887CFF169EA8335235D8004242AA7D6187A41E3187DF0CE14E256D85ED"
"97A97357AAA8FF0A3871AB9EEFF458392F462F495487387F685B7472FC"
"6C29E293F0A0"),
b"hello",
hb("9290F727ED06C38BA4549EF7DE25CF5642659211B7FC076F2D28FEFD71"
"784BB8D8F6FB244A8CC5C06240631B97008565A120764C0EE9C2CB0073"
"994D79080136"),
10000, 64, "hmac-sha512"
),
#
# custom
#
(
hb('e248fb6b13365146f8ac6307cc222812'),
b"secret", b"salt", 10, 16, "hmac-sha1",
),
(
hb('e248fb6b13365146f8ac6307cc2228127872da6d'),
b"secret", b"salt", 10, None, "hmac-sha1",
),
]
def setUp(self):
super(Pbkdf2_Test, self).setUp()
warnings.filterwarnings("ignore", ".*passlib.utils.pbkdf2.*deprecated", DeprecationWarning)
def test_known(self):
"""test reference vectors"""
from passlib.utils.pbkdf2 import pbkdf2
for row in self.pbkdf2_test_vectors:
correct, secret, salt, rounds, keylen = row[:5]
prf = row[5] if len(row) == 6 else "hmac-sha1"
result = pbkdf2(secret, salt, rounds, keylen, prf)
self.assertEqual(result, correct)
def test_border(self):
"""test border cases"""
from passlib.utils.pbkdf2 import pbkdf2
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, prf="hmac-sha1"):
return pbkdf2(secret, salt, rounds, keylen, prf)
helper()
# invalid rounds
self.assertRaises(ValueError, helper, rounds=-1)
self.assertRaises(ValueError, helper, rounds=0)
self.assertRaises(TypeError, helper, rounds='x')
# invalid keylen
self.assertRaises(ValueError, helper, keylen=-1)
self.assertRaises(ValueError, helper, keylen=0)
helper(keylen=1)
self.assertRaises(OverflowError, helper, keylen=20*(2**32-1)+1)
self.assertRaises(TypeError, helper, keylen='x')
# invalid secret/salt type
self.assertRaises(TypeError, helper, salt=5)
self.assertRaises(TypeError, helper, secret=5)
# invalid hash
self.assertRaises(ValueError, helper, prf='hmac-foo')
self.assertRaises(NotImplementedError, helper, prf='foo')
self.assertRaises(TypeError, helper, prf=5)
def test_default_keylen(self):
"""test keylen==None"""
from passlib.utils.pbkdf2 import pbkdf2
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, prf="hmac-sha1"):
return pbkdf2(secret, salt, rounds, keylen, prf)
self.assertEqual(len(helper(prf='hmac-sha1')), 20)
self.assertEqual(len(helper(prf='hmac-sha256')), 32)
def test_custom_prf(self):
"""test custom prf function"""
from passlib.utils.pbkdf2 import pbkdf2
def prf(key, msg):
return hashlib.md5(key+msg+b'fooey').digest()
self.assertRaises(NotImplementedError, pbkdf2, b'secret', b'salt', 1000, 20, prf)
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,50 @@
"""tests for passlib.win32 -- (c) Assurance Technologies 2003-2009"""
#=============================================================================
# imports
#=============================================================================
# core
import warnings
# site
# pkg
from passlib.tests.utils import TestCase
# module
from passlib.utils.compat import u
#=============================================================================
#
#=============================================================================
class UtilTest(TestCase):
"""test util funcs in passlib.win32"""
##test hashes from http://msdn.microsoft.com/en-us/library/cc245828(v=prot.10).aspx
## among other places
def setUp(self):
super(UtilTest, self).setUp()
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 = 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 = raw_nthash(secret, hex=True)
self.assertEqual(result, hash)
#=============================================================================
# eof
#=============================================================================

View File

@@ -0,0 +1,83 @@
"""passlib.tests.tox_support - helper script for tox tests"""
#=============================================================================
# init script env
#=============================================================================
import os, sys
root_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
sys.path.insert(0, root_dir)
#=============================================================================
# imports
#=============================================================================
# core
import re
import logging; log = logging.getLogger(__name__)
# site
# pkg
from passlib.utils.compat import print_
# local
__all__ = [
]
#=============================================================================
# main
#=============================================================================
TH_PATH = "passlib.tests.test_handlers"
def do_hash_tests(*args):
"""return list of hash algorithm tests that match regexes"""
if not args:
print(TH_PATH)
return
suffix = ''
args = list(args)
while True:
if args[0] == "--method":
suffix = '.' + args[1]
del args[:2]
else:
break
from passlib.tests import test_handlers
names = [TH_PATH + ":" + name + suffix for name in dir(test_handlers)
if not name.startswith("_") and any(re.match(arg,name) for arg in args)]
print_("\n".join(names))
return not names
def do_preset_tests(name):
"""return list of preset test names"""
if name == "django" or name == "django-hashes":
do_hash_tests("django_.*_test", "hex_md5_test")
if name == "django":
print_("passlib.tests.test_ext_django")
else:
raise ValueError("unknown name: %r" % name)
def do_setup_gae(path, runtime):
"""write fake GAE ``app.yaml`` to current directory so nosegae will work"""
from passlib.tests.utils import set_file
set_file(os.path.join(path, "app.yaml"), """\
application: fake-app
version: 2
runtime: %s
api_version: 1
threadsafe: no
handlers:
- url: /.*
script: dummy.py
libraries:
- name: django
version: "latest"
""" % runtime)
def main(cmd, *args):
return globals()["do_" + cmd](*args)
if __name__ == "__main__":
import sys
sys.exit(main(*sys.argv[1:]) or 0)
#=============================================================================
# eof
#=============================================================================