mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Initial commit
This commit is contained in:
1
venv/Lib/site-packages/passlib/tests/__init__.py
Normal file
1
venv/Lib/site-packages/passlib/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""passlib tests"""
|
6
venv/Lib/site-packages/passlib/tests/__main__.py
Normal file
6
venv/Lib/site-packages/passlib/tests/__main__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import os
|
||||
from nose import run
|
||||
run(
|
||||
defaultTest=os.path.dirname(__file__),
|
||||
)
|
||||
|
15
venv/Lib/site-packages/passlib/tests/_test_bad_register.py
Normal file
15
venv/Lib/site-packages/passlib/tests/_test_bad_register.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""helper for method in test_registry.py"""
|
||||
from passlib.registry import register_crypt_handler
|
||||
import passlib.utils.handlers as uh
|
||||
|
||||
class dummy_bad(uh.StaticHandler):
|
||||
name = "dummy_bad"
|
||||
|
||||
class alt_dummy_bad(uh.StaticHandler):
|
||||
name = "dummy_bad"
|
||||
|
||||
# NOTE: if passlib.tests is being run from symlink (e.g. via gaeunit),
|
||||
# this module may be imported a second time as test._test_bad_registry.
|
||||
# we don't want it to do anything in that case.
|
||||
if __name__.startswith("passlib.tests"):
|
||||
register_crypt_handler(alt_dummy_bad)
|
67
venv/Lib/site-packages/passlib/tests/backports.py
Normal file
67
venv/Lib/site-packages/passlib/tests/backports.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""backports of needed unittest2 features"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import re
|
||||
import sys
|
||||
##from warnings import warn
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.compat import PY26
|
||||
# local
|
||||
__all__ = [
|
||||
"TestCase",
|
||||
"unittest",
|
||||
# TODO: deprecate these exports in favor of "unittest.XXX"
|
||||
"skip", "skipIf", "skipUnless",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# import latest unittest module available
|
||||
#=============================================================================
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
if PY26:
|
||||
raise ImportError("Passlib's tests require 'unittest2' under Python 2.6 (as of Passlib 1.7)")
|
||||
# python 2.7 and python 3.2 both have unittest2 features (at least, the ones we use)
|
||||
import unittest
|
||||
|
||||
#=============================================================================
|
||||
# unittest aliases
|
||||
#=============================================================================
|
||||
skip = unittest.skip
|
||||
skipIf = unittest.skipIf
|
||||
skipUnless = unittest.skipUnless
|
||||
SkipTest = unittest.SkipTest
|
||||
|
||||
#=============================================================================
|
||||
# custom test harness
|
||||
#=============================================================================
|
||||
class TestCase(unittest.TestCase):
|
||||
"""backports a number of unittest2 features in TestCase"""
|
||||
|
||||
#===================================================================
|
||||
# backport some unittest2 names
|
||||
#===================================================================
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# backport assertRegex() alias from 3.2 to 2.7
|
||||
# was present in 2.7 under an alternate name
|
||||
#---------------------------------------------------------------
|
||||
if not hasattr(unittest.TestCase, "assertRegex"):
|
||||
assertRegex = unittest.TestCase.assertRegexpMatches
|
||||
|
||||
if not hasattr(unittest.TestCase, "assertRaisesRegex"):
|
||||
assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
9
venv/Lib/site-packages/passlib/tests/sample1.cfg
Normal file
9
venv/Lib/site-packages/passlib/tests/sample1.cfg
Normal file
@@ -0,0 +1,9 @@
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all__vary_rounds = 0.1
|
||||
bsdi_crypt__default_rounds = 25001
|
||||
bsdi_crypt__max_rounds = 30001
|
||||
sha512_crypt__max_rounds = 50000
|
||||
sha512_crypt__min_rounds = 40000
|
||||
|
9
venv/Lib/site-packages/passlib/tests/sample1b.cfg
Normal file
9
venv/Lib/site-packages/passlib/tests/sample1b.cfg
Normal file
@@ -0,0 +1,9 @@
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all__vary_rounds = 0.1
|
||||
bsdi_crypt__default_rounds = 25001
|
||||
bsdi_crypt__max_rounds = 30001
|
||||
sha512_crypt__max_rounds = 50000
|
||||
sha512_crypt__min_rounds = 40000
|
||||
|
BIN
venv/Lib/site-packages/passlib/tests/sample1c.cfg
Normal file
BIN
venv/Lib/site-packages/passlib/tests/sample1c.cfg
Normal file
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all.vary_rounds = 10%%
|
||||
bsdi_crypt.max_rounds = 30000
|
||||
bsdi_crypt.default_rounds = 25000
|
||||
sha512_crypt.max_rounds = 50000
|
||||
sha512_crypt.min_rounds = 40000
|
769
venv/Lib/site-packages/passlib/tests/test_apache.py
Normal file
769
venv/Lib/site-packages/passlib/tests/test_apache.py
Normal file
@@ -0,0 +1,769 @@
|
||||
"""tests for passlib.apache -- (c) Assurance Technologies 2008-2011"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
from logging import getLogger
|
||||
import os
|
||||
import subprocess
|
||||
# site
|
||||
# pkg
|
||||
from passlib import apache, registry
|
||||
from passlib.exc import MissingBackendError
|
||||
from passlib.utils.compat import irange
|
||||
from passlib.tests.backports import unittest
|
||||
from passlib.tests.utils import TestCase, get_file, set_file, ensure_mtime_changed
|
||||
from passlib.utils.compat import u
|
||||
from passlib.utils import to_bytes
|
||||
from passlib.utils.handlers import to_unicode_for_identify
|
||||
# module
|
||||
log = getLogger(__name__)
|
||||
|
||||
#=============================================================================
|
||||
# helpers
|
||||
#=============================================================================
|
||||
|
||||
def backdate_file_mtime(path, offset=10):
|
||||
"""backdate file's mtime by specified amount"""
|
||||
# NOTE: this is used so we can test code which detects mtime changes,
|
||||
# without having to actually *pause* for that long.
|
||||
atime = os.path.getatime(path)
|
||||
mtime = os.path.getmtime(path)-offset
|
||||
os.utime(path, (atime, mtime))
|
||||
|
||||
#=============================================================================
|
||||
# detect external HTPASSWD tool
|
||||
#=============================================================================
|
||||
|
||||
|
||||
htpasswd_path = os.environ.get("PASSLIB_TEST_HTPASSWD_PATH") or "htpasswd"
|
||||
|
||||
|
||||
def _call_htpasswd(args, stdin=None):
|
||||
"""
|
||||
helper to run htpasswd cmd
|
||||
"""
|
||||
if stdin is not None:
|
||||
stdin = stdin.encode("utf-8")
|
||||
proc = subprocess.Popen([htpasswd_path] + args, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, stdin=subprocess.PIPE if stdin else None)
|
||||
out, err = proc.communicate(stdin)
|
||||
rc = proc.wait()
|
||||
out = to_unicode_for_identify(out or "")
|
||||
return out, rc
|
||||
|
||||
|
||||
def _call_htpasswd_verify(path, user, password):
|
||||
"""
|
||||
wrapper for htpasswd verify
|
||||
"""
|
||||
out, rc = _call_htpasswd(["-vi", path, user], password)
|
||||
return not rc
|
||||
|
||||
|
||||
def _detect_htpasswd():
|
||||
"""
|
||||
helper to check if htpasswd is present
|
||||
"""
|
||||
try:
|
||||
out, rc = _call_htpasswd([])
|
||||
except OSError:
|
||||
# TODO: under py3, could trap the more specific FileNotFoundError
|
||||
# cmd not found
|
||||
return False, False
|
||||
# when called w/o args, it should print usage to stderr & return rc=2
|
||||
if not rc:
|
||||
log.warning("htpasswd test returned with rc=0")
|
||||
have_bcrypt = " -B " in out
|
||||
return True, have_bcrypt
|
||||
|
||||
|
||||
HAVE_HTPASSWD, HAVE_HTPASSWD_BCRYPT = _detect_htpasswd()
|
||||
|
||||
requires_htpasswd_cmd = unittest.skipUnless(HAVE_HTPASSWD, "requires `htpasswd` cmdline tool")
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# htpasswd
|
||||
#=============================================================================
|
||||
class HtpasswdFileTest(TestCase):
|
||||
"""test HtpasswdFile class"""
|
||||
descriptionPrefix = "HtpasswdFile"
|
||||
|
||||
# sample with 4 users
|
||||
sample_01 = (b'user2:2CHkkwa2AtqGs\n'
|
||||
b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\n'
|
||||
b'user4:pass4\n'
|
||||
b'user1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n')
|
||||
|
||||
# sample 1 with user 1, 2 deleted; 4 changed
|
||||
sample_02 = b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\nuser4:pass4\n'
|
||||
|
||||
# sample 1 with user2 updated, user 1 first entry removed, and user 5 added
|
||||
sample_03 = (b'user2:pass2x\n'
|
||||
b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\n'
|
||||
b'user4:pass4\n'
|
||||
b'user1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n'
|
||||
b'user5:pass5\n')
|
||||
|
||||
# standalone sample with 8-bit username
|
||||
sample_04_utf8 = b'user\xc3\xa6:2CHkkwa2AtqGs\n'
|
||||
sample_04_latin1 = b'user\xe6:2CHkkwa2AtqGs\n'
|
||||
|
||||
sample_dup = b'user1:pass1\nuser1:pass2\n'
|
||||
|
||||
# sample with bcrypt & sha256_crypt hashes
|
||||
sample_05 = (b'user2:2CHkkwa2AtqGs\n'
|
||||
b'user3:{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=\n'
|
||||
b'user4:pass4\n'
|
||||
b'user1:$apr1$t4tc7jTh$GPIWVUo8sQKJlUdV8V5vu0\n'
|
||||
b'user5:$2a$12$yktDxraxijBZ360orOyCOePFGhuis/umyPNJoL5EbsLk.s6SWdrRO\n'
|
||||
b'user6:$5$rounds=110000$cCRp/xUUGVgwR4aP$'
|
||||
b'p0.QKFS5qLNRqw1/47lXYiAcgIjJK.WjCO8nrEKuUK.\n')
|
||||
|
||||
def test_00_constructor_autoload(self):
|
||||
"""test constructor autoload"""
|
||||
# check with existing file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtpasswdFile(path)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
self.assertEqual(ht.path, path)
|
||||
self.assertTrue(ht.mtime)
|
||||
|
||||
# check changing path
|
||||
ht.path = path + "x"
|
||||
self.assertEqual(ht.path, path + "x")
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
# check new=True
|
||||
ht = apache.HtpasswdFile(path, new=True)
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
self.assertEqual(ht.path, path)
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
# check autoload=False (deprecated alias for new=True)
|
||||
with self.assertWarningList("``autoload=False`` is deprecated"):
|
||||
ht = apache.HtpasswdFile(path, autoload=False)
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
self.assertEqual(ht.path, path)
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
# check missing file
|
||||
os.remove(path)
|
||||
self.assertRaises(IOError, apache.HtpasswdFile, path)
|
||||
|
||||
# NOTE: "default_scheme" option checked via set_password() test, among others
|
||||
|
||||
def test_00_from_path(self):
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtpasswdFile.from_path(path)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
self.assertEqual(ht.path, None)
|
||||
self.assertFalse(ht.mtime)
|
||||
|
||||
def test_01_delete(self):
|
||||
"""test delete()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
self.assertTrue(ht.delete("user1")) # should delete both entries
|
||||
self.assertTrue(ht.delete("user2"))
|
||||
self.assertFalse(ht.delete("user5")) # user not present
|
||||
self.assertEqual(ht.to_string(), self.sample_02)
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.delete, "user:")
|
||||
|
||||
def test_01_delete_autosave(self):
|
||||
path = self.mktemp()
|
||||
sample = b'user1:pass1\nuser2:pass2\n'
|
||||
set_file(path, sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path)
|
||||
ht.delete("user1")
|
||||
self.assertEqual(get_file(path), sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path, autosave=True)
|
||||
ht.delete("user1")
|
||||
self.assertEqual(get_file(path), b"user2:pass2\n")
|
||||
|
||||
def test_02_set_password(self):
|
||||
"""test set_password()"""
|
||||
ht = apache.HtpasswdFile.from_string(
|
||||
self.sample_01, default_scheme="plaintext")
|
||||
self.assertTrue(ht.set_password("user2", "pass2x"))
|
||||
self.assertFalse(ht.set_password("user5", "pass5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_03)
|
||||
|
||||
# test legacy default kwd
|
||||
with self.assertWarningList("``default`` is deprecated"):
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01, default="plaintext")
|
||||
self.assertTrue(ht.set_password("user2", "pass2x"))
|
||||
self.assertFalse(ht.set_password("user5", "pass5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_03)
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.set_password, "user:", "pass")
|
||||
|
||||
# test that legacy update() still works
|
||||
with self.assertWarningList("update\(\) is deprecated"):
|
||||
ht.update("user2", "test")
|
||||
self.assertTrue(ht.check_password("user2", "test"))
|
||||
|
||||
def test_02_set_password_autosave(self):
|
||||
path = self.mktemp()
|
||||
sample = b'user1:pass1\n'
|
||||
set_file(path, sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path)
|
||||
ht.set_password("user1", "pass2")
|
||||
self.assertEqual(get_file(path), sample)
|
||||
|
||||
ht = apache.HtpasswdFile(path, default_scheme="plaintext", autosave=True)
|
||||
ht.set_password("user1", "pass2")
|
||||
self.assertEqual(get_file(path), b"user1:pass2\n")
|
||||
|
||||
def test_02_set_password_default_scheme(self):
|
||||
"""test set_password() -- default_scheme"""
|
||||
|
||||
def check(scheme):
|
||||
ht = apache.HtpasswdFile(default_scheme=scheme)
|
||||
ht.set_password("user1", "pass1")
|
||||
return ht.context.identify(ht.get_hash("user1"))
|
||||
|
||||
# explicit scheme
|
||||
self.assertEqual(check("sha256_crypt"), "sha256_crypt")
|
||||
self.assertEqual(check("des_crypt"), "des_crypt")
|
||||
|
||||
# unknown scheme
|
||||
self.assertRaises(KeyError, check, "xxx")
|
||||
|
||||
# alias resolution
|
||||
self.assertEqual(check("portable"), apache.htpasswd_defaults["portable"])
|
||||
self.assertEqual(check("portable_apache_22"), apache.htpasswd_defaults["portable_apache_22"])
|
||||
self.assertEqual(check("host_apache_22"), apache.htpasswd_defaults["host_apache_22"])
|
||||
|
||||
# default
|
||||
self.assertEqual(check(None), apache.htpasswd_defaults["portable_apache_22"])
|
||||
|
||||
def test_03_users(self):
|
||||
"""test users()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
ht.set_password("user5", "pass5")
|
||||
ht.delete("user3")
|
||||
ht.set_password("user3", "pass3")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user3", "user4", "user5"])
|
||||
|
||||
def test_04_check_password(self):
|
||||
"""test check_password()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_05)
|
||||
self.assertRaises(TypeError, ht.check_password, 1, 'pass9')
|
||||
self.assertTrue(ht.check_password("user9","pass9") is None)
|
||||
|
||||
# users 1..6 of sample_01 run through all the main hash formats,
|
||||
# to make sure they're recognized.
|
||||
for i in irange(1, 7):
|
||||
i = str(i)
|
||||
try:
|
||||
self.assertTrue(ht.check_password("user"+i, "pass"+i))
|
||||
self.assertTrue(ht.check_password("user"+i, "pass9") is False)
|
||||
except MissingBackendError:
|
||||
if i == "5":
|
||||
# user5 uses bcrypt, which is apparently not available right now
|
||||
continue
|
||||
raise
|
||||
|
||||
self.assertRaises(ValueError, ht.check_password, "user:", "pass")
|
||||
|
||||
# test that legacy verify() still works
|
||||
with self.assertWarningList(["verify\(\) is deprecated"]*2):
|
||||
self.assertTrue(ht.verify("user1", "pass1"))
|
||||
self.assertFalse(ht.verify("user1", "pass2"))
|
||||
|
||||
def test_05_load(self):
|
||||
"""test load()"""
|
||||
# setup empty file
|
||||
path = self.mktemp()
|
||||
set_file(path, "")
|
||||
backdate_file_mtime(path, 5)
|
||||
ha = apache.HtpasswdFile(path, default_scheme="plaintext")
|
||||
self.assertEqual(ha.to_string(), b"")
|
||||
|
||||
# make changes, check load_if_changed() does nothing
|
||||
ha.set_password("user1", "pass1")
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), b"user1:pass1\n")
|
||||
|
||||
# change file
|
||||
set_file(path, self.sample_01)
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# make changes, check load() overwrites them
|
||||
ha.set_password("user5", "pass5")
|
||||
ha.load()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# test load w/ no path
|
||||
hb = apache.HtpasswdFile()
|
||||
self.assertRaises(RuntimeError, hb.load)
|
||||
self.assertRaises(RuntimeError, hb.load_if_changed)
|
||||
|
||||
# test load w/ dups and explicit path
|
||||
set_file(path, self.sample_dup)
|
||||
hc = apache.HtpasswdFile()
|
||||
hc.load(path)
|
||||
self.assertTrue(hc.check_password('user1','pass1'))
|
||||
|
||||
# NOTE: load_string() tested via from_string(), which is used all over this file
|
||||
|
||||
def test_06_save(self):
|
||||
"""test save()"""
|
||||
# load from file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtpasswdFile(path)
|
||||
|
||||
# make changes, check they saved
|
||||
ht.delete("user1")
|
||||
ht.delete("user2")
|
||||
ht.save()
|
||||
self.assertEqual(get_file(path), self.sample_02)
|
||||
|
||||
# test save w/ no path
|
||||
hb = apache.HtpasswdFile(default_scheme="plaintext")
|
||||
hb.set_password("user1", "pass1")
|
||||
self.assertRaises(RuntimeError, hb.save)
|
||||
|
||||
# test save w/ explicit path
|
||||
hb.save(path)
|
||||
self.assertEqual(get_file(path), b"user1:pass1\n")
|
||||
|
||||
def test_07_encodings(self):
|
||||
"""test 'encoding' kwd"""
|
||||
# test bad encodings cause failure in constructor
|
||||
self.assertRaises(ValueError, apache.HtpasswdFile, encoding="utf-16")
|
||||
|
||||
# check sample utf-8
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_04_utf8, encoding="utf-8",
|
||||
return_unicode=True)
|
||||
self.assertEqual(ht.users(), [ u("user\u00e6") ])
|
||||
|
||||
# test deprecated encoding=None
|
||||
with self.assertWarningList("``encoding=None`` is deprecated"):
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_04_utf8, encoding=None)
|
||||
self.assertEqual(ht.users(), [ b'user\xc3\xa6' ])
|
||||
|
||||
# check sample latin-1
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_04_latin1,
|
||||
encoding="latin-1", return_unicode=True)
|
||||
self.assertEqual(ht.users(), [ u("user\u00e6") ])
|
||||
|
||||
def test_08_get_hash(self):
|
||||
"""test get_hash()"""
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.get_hash("user3"), b"{SHA}3ipNV1GrBtxPmHFC21fCbVCSXIo=")
|
||||
self.assertEqual(ht.get_hash("user4"), b"pass4")
|
||||
self.assertEqual(ht.get_hash("user5"), None)
|
||||
|
||||
with self.assertWarningList("find\(\) is deprecated"):
|
||||
self.assertEqual(ht.find("user4"), b"pass4")
|
||||
|
||||
def test_09_to_string(self):
|
||||
"""test to_string"""
|
||||
|
||||
# check with known sample
|
||||
ht = apache.HtpasswdFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
|
||||
# test blank
|
||||
ht = apache.HtpasswdFile()
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
def test_10_repr(self):
|
||||
ht = apache.HtpasswdFile("fakepath", autosave=True, new=True, encoding="latin-1")
|
||||
repr(ht)
|
||||
|
||||
def test_11_malformed(self):
|
||||
self.assertRaises(ValueError, apache.HtpasswdFile.from_string,
|
||||
b'realm:user1:pass1\n')
|
||||
self.assertRaises(ValueError, apache.HtpasswdFile.from_string,
|
||||
b'pass1\n')
|
||||
|
||||
def test_12_from_string(self):
|
||||
# forbid path kwd
|
||||
self.assertRaises(TypeError, apache.HtpasswdFile.from_string,
|
||||
b'', path=None)
|
||||
|
||||
def test_13_whitespace(self):
|
||||
"""whitespace & comment handling"""
|
||||
|
||||
# per htpasswd source (https://github.com/apache/httpd/blob/trunk/support/htpasswd.c),
|
||||
# lines that match "^\s*(#.*)?$" should be ignored
|
||||
source = to_bytes(
|
||||
'\n'
|
||||
'user2:pass2\n'
|
||||
'user4:pass4\n'
|
||||
'user7:pass7\r\n'
|
||||
' \t \n'
|
||||
'user1:pass1\n'
|
||||
' # legacy users\n'
|
||||
'#user6:pass6\n'
|
||||
'user5:pass5\n\n'
|
||||
)
|
||||
|
||||
# loading should see all users (except user6, who was commented out)
|
||||
ht = apache.HtpasswdFile.from_string(source)
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user7"])
|
||||
|
||||
# update existing user
|
||||
ht.set_hash("user4", "althash4")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user7"])
|
||||
|
||||
# add a new user
|
||||
ht.set_hash("user6", "althash6")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user6", "user7"])
|
||||
|
||||
# delete existing user
|
||||
ht.delete("user7")
|
||||
self.assertEqual(sorted(ht.users()), ["user1", "user2", "user4", "user5", "user6"])
|
||||
|
||||
# re-serialization should preserve whitespace
|
||||
target = to_bytes(
|
||||
'\n'
|
||||
'user2:pass2\n'
|
||||
'user4:althash4\n'
|
||||
' \t \n'
|
||||
'user1:pass1\n'
|
||||
' # legacy users\n'
|
||||
'#user6:pass6\n'
|
||||
'user5:pass5\n'
|
||||
'user6:althash6\n'
|
||||
)
|
||||
self.assertEqual(ht.to_string(), target)
|
||||
|
||||
@requires_htpasswd_cmd
|
||||
def test_htpasswd_cmd_verify(self):
|
||||
"""
|
||||
verify "htpasswd" command can read output
|
||||
"""
|
||||
path = self.mktemp()
|
||||
ht = apache.HtpasswdFile(path=path, new=True)
|
||||
|
||||
def hash_scheme(pwd, scheme):
|
||||
return ht.context.handler(scheme).hash(pwd)
|
||||
|
||||
# base scheme
|
||||
ht.set_hash("user1", hash_scheme("password","apr_md5_crypt"))
|
||||
|
||||
# 2.2-compat scheme
|
||||
host_no_bcrypt = apache.htpasswd_defaults["host_apache_22"]
|
||||
ht.set_hash("user2", hash_scheme("password", host_no_bcrypt))
|
||||
|
||||
# 2.4-compat scheme
|
||||
host_best = apache.htpasswd_defaults["host"]
|
||||
ht.set_hash("user3", hash_scheme("password", host_best))
|
||||
|
||||
# unsupported scheme -- should always fail to verify
|
||||
ht.set_hash("user4", "$xxx$foo$bar$baz")
|
||||
|
||||
# make sure htpasswd properly recognizes hashes
|
||||
ht.save()
|
||||
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user1", "wrong"))
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user2", "wrong"))
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user3", "wrong"))
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user4", "wrong"))
|
||||
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user1", "password"))
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user2", "password"))
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user3", "password"))
|
||||
|
||||
@requires_htpasswd_cmd
|
||||
@unittest.skipUnless(registry.has_backend("bcrypt"), "bcrypt support required")
|
||||
def test_htpasswd_cmd_verify_bcrypt(self):
|
||||
"""
|
||||
verify "htpasswd" command can read bcrypt format
|
||||
|
||||
this tests for regression of issue 95, where we output "$2b$" instead of "$2y$";
|
||||
fixed in v1.7.2.
|
||||
"""
|
||||
path = self.mktemp()
|
||||
ht = apache.HtpasswdFile(path=path, new=True)
|
||||
def hash_scheme(pwd, scheme):
|
||||
return ht.context.handler(scheme).hash(pwd)
|
||||
ht.set_hash("user1", hash_scheme("password", "bcrypt"))
|
||||
ht.save()
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user1", "wrong"))
|
||||
if HAVE_HTPASSWD_BCRYPT:
|
||||
self.assertTrue(_call_htpasswd_verify(path, "user1", "password"))
|
||||
else:
|
||||
# apache2.2 should fail, acting like it's an unknown hash format
|
||||
self.assertFalse(_call_htpasswd_verify(path, "user1", "password"))
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# htdigest
|
||||
#=============================================================================
|
||||
class HtdigestFileTest(TestCase):
|
||||
"""test HtdigestFile class"""
|
||||
descriptionPrefix = "HtdigestFile"
|
||||
|
||||
# sample with 4 users
|
||||
sample_01 = (b'user2:realm:549d2a5f4659ab39a80dac99e159ab19\n'
|
||||
b'user3:realm:a500bb8c02f6a9170ae46af10c898744\n'
|
||||
b'user4:realm:ab7b5d5f28ccc7666315f508c7358519\n'
|
||||
b'user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n')
|
||||
|
||||
# sample 1 with user 1, 2 deleted; 4 changed
|
||||
sample_02 = (b'user3:realm:a500bb8c02f6a9170ae46af10c898744\n'
|
||||
b'user4:realm:ab7b5d5f28ccc7666315f508c7358519\n')
|
||||
|
||||
# sample 1 with user2 updated, user 1 first entry removed, and user 5 added
|
||||
sample_03 = (b'user2:realm:5ba6d8328943c23c64b50f8b29566059\n'
|
||||
b'user3:realm:a500bb8c02f6a9170ae46af10c898744\n'
|
||||
b'user4:realm:ab7b5d5f28ccc7666315f508c7358519\n'
|
||||
b'user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n'
|
||||
b'user5:realm:03c55fdc6bf71552356ad401bdb9af19\n')
|
||||
|
||||
# standalone sample with 8-bit username & realm
|
||||
sample_04_utf8 = b'user\xc3\xa6:realm\xc3\xa6:549d2a5f4659ab39a80dac99e159ab19\n'
|
||||
sample_04_latin1 = b'user\xe6:realm\xe6:549d2a5f4659ab39a80dac99e159ab19\n'
|
||||
|
||||
def test_00_constructor_autoload(self):
|
||||
"""test constructor autoload"""
|
||||
# check with existing file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtdigestFile(path)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
|
||||
# check without autoload
|
||||
ht = apache.HtdigestFile(path, new=True)
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
# check missing file
|
||||
os.remove(path)
|
||||
self.assertRaises(IOError, apache.HtdigestFile, path)
|
||||
|
||||
# NOTE: default_realm option checked via other tests.
|
||||
|
||||
def test_01_delete(self):
|
||||
"""test delete()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertTrue(ht.delete("user1", "realm"))
|
||||
self.assertTrue(ht.delete("user2", "realm"))
|
||||
self.assertFalse(ht.delete("user5", "realm"))
|
||||
self.assertFalse(ht.delete("user3", "realm5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_02)
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.delete, "user:", "realm")
|
||||
|
||||
# invalid realm
|
||||
self.assertRaises(ValueError, ht.delete, "user", "realm:")
|
||||
|
||||
def test_01_delete_autosave(self):
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
|
||||
ht = apache.HtdigestFile(path)
|
||||
self.assertTrue(ht.delete("user1", "realm"))
|
||||
self.assertFalse(ht.delete("user3", "realm5"))
|
||||
self.assertFalse(ht.delete("user5", "realm"))
|
||||
self.assertEqual(get_file(path), self.sample_01)
|
||||
|
||||
ht.autosave = True
|
||||
self.assertTrue(ht.delete("user2", "realm"))
|
||||
self.assertEqual(get_file(path), self.sample_02)
|
||||
|
||||
def test_02_set_password(self):
|
||||
"""test update()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertTrue(ht.set_password("user2", "realm", "pass2x"))
|
||||
self.assertFalse(ht.set_password("user5", "realm", "pass5"))
|
||||
self.assertEqual(ht.to_string(), self.sample_03)
|
||||
|
||||
# default realm
|
||||
self.assertRaises(TypeError, ht.set_password, "user2", "pass3")
|
||||
ht.default_realm = "realm2"
|
||||
ht.set_password("user2", "pass3")
|
||||
ht.check_password("user2", "realm2", "pass3")
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.set_password, "user:", "realm", "pass")
|
||||
self.assertRaises(ValueError, ht.set_password, "u"*256, "realm", "pass")
|
||||
|
||||
# invalid realm
|
||||
self.assertRaises(ValueError, ht.set_password, "user", "realm:", "pass")
|
||||
self.assertRaises(ValueError, ht.set_password, "user", "r"*256, "pass")
|
||||
|
||||
# test that legacy update() still works
|
||||
with self.assertWarningList("update\(\) is deprecated"):
|
||||
ht.update("user2", "realm2", "test")
|
||||
self.assertTrue(ht.check_password("user2", "test"))
|
||||
|
||||
# TODO: test set_password autosave
|
||||
|
||||
def test_03_users(self):
|
||||
"""test users()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
ht.set_password("user5", "realm", "pass5")
|
||||
ht.delete("user3", "realm")
|
||||
ht.set_password("user3", "realm", "pass3")
|
||||
self.assertEqual(sorted(ht.users("realm")), ["user1", "user2", "user3", "user4", "user5"])
|
||||
|
||||
self.assertRaises(TypeError, ht.users, 1)
|
||||
|
||||
def test_04_check_password(self):
|
||||
"""test check_password()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertRaises(TypeError, ht.check_password, 1, 'realm', 'pass5')
|
||||
self.assertRaises(TypeError, ht.check_password, 'user', 1, 'pass5')
|
||||
self.assertIs(ht.check_password("user5", "realm","pass5"), None)
|
||||
for i in irange(1,5):
|
||||
i = str(i)
|
||||
self.assertTrue(ht.check_password("user"+i, "realm", "pass"+i))
|
||||
self.assertIs(ht.check_password("user"+i, "realm", "pass5"), False)
|
||||
|
||||
# default realm
|
||||
self.assertRaises(TypeError, ht.check_password, "user5", "pass5")
|
||||
ht.default_realm = "realm"
|
||||
self.assertTrue(ht.check_password("user1", "pass1"))
|
||||
self.assertIs(ht.check_password("user5", "pass5"), None)
|
||||
|
||||
# test that legacy verify() still works
|
||||
with self.assertWarningList(["verify\(\) is deprecated"]*2):
|
||||
self.assertTrue(ht.verify("user1", "realm", "pass1"))
|
||||
self.assertFalse(ht.verify("user1", "realm", "pass2"))
|
||||
|
||||
# invalid user
|
||||
self.assertRaises(ValueError, ht.check_password, "user:", "realm", "pass")
|
||||
|
||||
def test_05_load(self):
|
||||
"""test load()"""
|
||||
# setup empty file
|
||||
path = self.mktemp()
|
||||
set_file(path, "")
|
||||
backdate_file_mtime(path, 5)
|
||||
ha = apache.HtdigestFile(path)
|
||||
self.assertEqual(ha.to_string(), b"")
|
||||
|
||||
# make changes, check load_if_changed() does nothing
|
||||
ha.set_password("user1", "realm", "pass1")
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), b'user1:realm:2a6cf53e7d8f8cf39d946dc880b14128\n')
|
||||
|
||||
# change file
|
||||
set_file(path, self.sample_01)
|
||||
ha.load_if_changed()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# make changes, check load_if_changed overwrites them
|
||||
ha.set_password("user5", "realm", "pass5")
|
||||
ha.load()
|
||||
self.assertEqual(ha.to_string(), self.sample_01)
|
||||
|
||||
# test load w/ no path
|
||||
hb = apache.HtdigestFile()
|
||||
self.assertRaises(RuntimeError, hb.load)
|
||||
self.assertRaises(RuntimeError, hb.load_if_changed)
|
||||
|
||||
# test load w/ explicit path
|
||||
hc = apache.HtdigestFile()
|
||||
hc.load(path)
|
||||
self.assertEqual(hc.to_string(), self.sample_01)
|
||||
|
||||
# change file, test deprecated force=False kwd
|
||||
ensure_mtime_changed(path)
|
||||
set_file(path, "")
|
||||
with self.assertWarningList(r"load\(force=False\) is deprecated"):
|
||||
ha.load(force=False)
|
||||
self.assertEqual(ha.to_string(), b"")
|
||||
|
||||
def test_06_save(self):
|
||||
"""test save()"""
|
||||
# load from file
|
||||
path = self.mktemp()
|
||||
set_file(path, self.sample_01)
|
||||
ht = apache.HtdigestFile(path)
|
||||
|
||||
# make changes, check they saved
|
||||
ht.delete("user1", "realm")
|
||||
ht.delete("user2", "realm")
|
||||
ht.save()
|
||||
self.assertEqual(get_file(path), self.sample_02)
|
||||
|
||||
# test save w/ no path
|
||||
hb = apache.HtdigestFile()
|
||||
hb.set_password("user1", "realm", "pass1")
|
||||
self.assertRaises(RuntimeError, hb.save)
|
||||
|
||||
# test save w/ explicit path
|
||||
hb.save(path)
|
||||
self.assertEqual(get_file(path), hb.to_string())
|
||||
|
||||
def test_07_realms(self):
|
||||
"""test realms() & delete_realm()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
|
||||
self.assertEqual(ht.delete_realm("x"), 0)
|
||||
self.assertEqual(ht.realms(), ['realm'])
|
||||
|
||||
self.assertEqual(ht.delete_realm("realm"), 4)
|
||||
self.assertEqual(ht.realms(), [])
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
def test_08_get_hash(self):
|
||||
"""test get_hash()"""
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.get_hash("user3", "realm"), "a500bb8c02f6a9170ae46af10c898744")
|
||||
self.assertEqual(ht.get_hash("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519")
|
||||
self.assertEqual(ht.get_hash("user5", "realm"), None)
|
||||
|
||||
with self.assertWarningList("find\(\) is deprecated"):
|
||||
self.assertEqual(ht.find("user4", "realm"), "ab7b5d5f28ccc7666315f508c7358519")
|
||||
|
||||
def test_09_encodings(self):
|
||||
"""test encoding parameter"""
|
||||
# test bad encodings cause failure in constructor
|
||||
self.assertRaises(ValueError, apache.HtdigestFile, encoding="utf-16")
|
||||
|
||||
# check sample utf-8
|
||||
ht = apache.HtdigestFile.from_string(self.sample_04_utf8, encoding="utf-8", return_unicode=True)
|
||||
self.assertEqual(ht.realms(), [ u("realm\u00e6") ])
|
||||
self.assertEqual(ht.users(u("realm\u00e6")), [ u("user\u00e6") ])
|
||||
|
||||
# check sample latin-1
|
||||
ht = apache.HtdigestFile.from_string(self.sample_04_latin1, encoding="latin-1", return_unicode=True)
|
||||
self.assertEqual(ht.realms(), [ u("realm\u00e6") ])
|
||||
self.assertEqual(ht.users(u("realm\u00e6")), [ u("user\u00e6") ])
|
||||
|
||||
def test_10_to_string(self):
|
||||
"""test to_string()"""
|
||||
|
||||
# check sample
|
||||
ht = apache.HtdigestFile.from_string(self.sample_01)
|
||||
self.assertEqual(ht.to_string(), self.sample_01)
|
||||
|
||||
# check blank
|
||||
ht = apache.HtdigestFile()
|
||||
self.assertEqual(ht.to_string(), b"")
|
||||
|
||||
def test_11_malformed(self):
|
||||
self.assertRaises(ValueError, apache.HtdigestFile.from_string,
|
||||
b'realm:user1:pass1:other\n')
|
||||
self.assertRaises(ValueError, apache.HtdigestFile.from_string,
|
||||
b'user1:pass1\n')
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
139
venv/Lib/site-packages/passlib/tests/test_apps.py
Normal file
139
venv/Lib/site-packages/passlib/tests/test_apps.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""test passlib.apps"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib import apps, hash as hashmod
|
||||
from passlib.tests.utils import TestCase
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# test predefined app contexts
|
||||
#=============================================================================
|
||||
class AppsTest(TestCase):
|
||||
"""perform general tests to make sure contexts work"""
|
||||
# NOTE: these tests are not really comprehensive,
|
||||
# since they would do little but duplicate
|
||||
# the presets in apps.py
|
||||
#
|
||||
# they mainly try to ensure no typos
|
||||
# or dynamic behavior foul-ups.
|
||||
|
||||
def test_master_context(self):
|
||||
ctx = apps.master_context
|
||||
self.assertGreater(len(ctx.schemes()), 50)
|
||||
|
||||
def test_custom_app_context(self):
|
||||
ctx = apps.custom_app_context
|
||||
self.assertEqual(ctx.schemes(), ("sha512_crypt", "sha256_crypt"))
|
||||
for hash in [
|
||||
('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6'
|
||||
'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751'),
|
||||
('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny'
|
||||
'xDGgMlDcOsfaI17'),
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
def test_django16_context(self):
|
||||
ctx = apps.django16_context
|
||||
for hash in [
|
||||
'pbkdf2_sha256$29000$ZsgquwnCyBs2$fBxRQpfKd2PIeMxtkKPy0h7SrnrN+EU/cm67aitoZ2s=',
|
||||
'sha1$0d082$cdb462ae8b6be8784ef24b20778c4d0c82d5957f',
|
||||
'md5$b887a$37767f8a745af10612ad44c80ff52e92',
|
||||
'crypt$95a6d$95x74hLDQKXI2',
|
||||
'098f6bcd4621d373cade4e832627b4f6',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
self.assertEqual(ctx.identify("!"), "django_disabled")
|
||||
self.assertFalse(ctx.verify("test", "!"))
|
||||
|
||||
def test_django_context(self):
|
||||
ctx = apps.django_context
|
||||
for hash in [
|
||||
'pbkdf2_sha256$29000$ZsgquwnCyBs2$fBxRQpfKd2PIeMxtkKPy0h7SrnrN+EU/cm67aitoZ2s=',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
self.assertEqual(ctx.identify("!"), "django_disabled")
|
||||
self.assertFalse(ctx.verify("test", "!"))
|
||||
|
||||
def test_ldap_nocrypt_context(self):
|
||||
ctx = apps.ldap_nocrypt_context
|
||||
for hash in [
|
||||
'{SSHA}cPusOzd6d5n3OjSVK3R329ZGCNyFcC7F',
|
||||
'test',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
self.assertIs(ctx.identify('{CRYPT}$5$rounds=31817$iZGmlyBQ99JSB5'
|
||||
'n6$p4E.pdPBWx19OajgjLRiOW0itGnyxDGgMlDcOsfaI17'), None)
|
||||
|
||||
def test_ldap_context(self):
|
||||
ctx = apps.ldap_context
|
||||
for hash in [
|
||||
('{CRYPT}$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0'
|
||||
'itGnyxDGgMlDcOsfaI17'),
|
||||
'{SSHA}cPusOzd6d5n3OjSVK3R329ZGCNyFcC7F',
|
||||
'test',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
def test_ldap_mysql_context(self):
|
||||
ctx = apps.mysql_context
|
||||
for hash in [
|
||||
'*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29',
|
||||
'378b243e220ca493',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
def test_postgres_context(self):
|
||||
ctx = apps.postgres_context
|
||||
hash = 'md55d9c68c6c50ed3d02a2fcf54f63993b6'
|
||||
self.assertTrue(ctx.verify("test", hash, user='user'))
|
||||
|
||||
def test_phppass_context(self):
|
||||
ctx = apps.phpass_context
|
||||
for hash in [
|
||||
'$P$8Ja1vJsKa5qyy/b3mCJGXM7GyBnt6..',
|
||||
'$H$8b95CoYQnQ9Y6fSTsACyphNh5yoM02.',
|
||||
'_cD..aBxeRhYFJvtUvsI',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
h1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
if hashmod.bcrypt.has_backend():
|
||||
self.assertTrue(ctx.verify("test", h1))
|
||||
self.assertEqual(ctx.default_scheme(), "bcrypt")
|
||||
self.assertEqual(ctx.handler().name, "bcrypt")
|
||||
else:
|
||||
self.assertEqual(ctx.identify(h1), "bcrypt")
|
||||
self.assertEqual(ctx.default_scheme(), "phpass")
|
||||
self.assertEqual(ctx.handler().name, "phpass")
|
||||
|
||||
def test_phpbb3_context(self):
|
||||
ctx = apps.phpbb3_context
|
||||
for hash in [
|
||||
'$P$8Ja1vJsKa5qyy/b3mCJGXM7GyBnt6..',
|
||||
'$H$8b95CoYQnQ9Y6fSTsACyphNh5yoM02.',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
self.assertTrue(ctx.hash("test").startswith("$H$"))
|
||||
|
||||
def test_roundup_context(self):
|
||||
ctx = apps.roundup_context
|
||||
for hash in [
|
||||
'{PBKDF2}9849$JMTYu3eOUSoFYExprVVqbQ$N5.gV.uR1.BTgLSvi0qyPiRlGZ0',
|
||||
'{SHA}a94a8fe5ccb19ba61c4c0873d391e987982fbbd3',
|
||||
'{CRYPT}dptOmKDriOGfU',
|
||||
'{plaintext}test',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
1786
venv/Lib/site-packages/passlib/tests/test_context.py
Normal file
1786
venv/Lib/site-packages/passlib/tests/test_context.py
Normal file
File diff suppressed because it is too large
Load Diff
743
venv/Lib/site-packages/passlib/tests/test_context_deprecated.py
Normal file
743
venv/Lib/site-packages/passlib/tests/test_context_deprecated.py
Normal file
@@ -0,0 +1,743 @@
|
||||
"""tests for passlib.context
|
||||
|
||||
this file is a clone of the 1.5 test_context.py,
|
||||
containing the tests using the legacy CryptPolicy api.
|
||||
it's being preserved here to ensure the old api doesn't break
|
||||
(until Passlib 1.8, when this and the legacy api will be removed).
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
from logging import getLogger
|
||||
import os
|
||||
import warnings
|
||||
# site
|
||||
try:
|
||||
from pkg_resources import resource_filename
|
||||
except ImportError:
|
||||
resource_filename = None
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.context import CryptContext, CryptPolicy, LazyCryptContext
|
||||
from passlib.utils import to_bytes, to_unicode
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.tests.utils import TestCase, set_file
|
||||
from passlib.registry import (register_crypt_handler_path,
|
||||
_has_crypt_handler as has_crypt_handler,
|
||||
_unload_handler_name as unload_handler_name,
|
||||
)
|
||||
# module
|
||||
log = getLogger(__name__)
|
||||
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class CryptPolicyTest(TestCase):
|
||||
"""test CryptPolicy object"""
|
||||
|
||||
# TODO: need to test user categories w/in all this
|
||||
|
||||
descriptionPrefix = "CryptPolicy"
|
||||
|
||||
#===================================================================
|
||||
# sample crypt policies used for testing
|
||||
#===================================================================
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 1 - average config file
|
||||
#---------------------------------------------------------------
|
||||
# NOTE: copy of this is stored in file passlib/tests/sample_config_1s.cfg
|
||||
sample_config_1s = """\
|
||||
[passlib]
|
||||
schemes = des_crypt, md5_crypt, bsdi_crypt, sha512_crypt
|
||||
default = md5_crypt
|
||||
all.vary_rounds = 10%%
|
||||
bsdi_crypt.max_rounds = 30000
|
||||
bsdi_crypt.default_rounds = 25000
|
||||
sha512_crypt.max_rounds = 50000
|
||||
sha512_crypt.min_rounds = 40000
|
||||
"""
|
||||
sample_config_1s_path = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), "sample_config_1s.cfg"))
|
||||
if not os.path.exists(sample_config_1s_path) and resource_filename:
|
||||
# in case we're zipped up in an egg.
|
||||
sample_config_1s_path = resource_filename("passlib.tests",
|
||||
"sample_config_1s.cfg")
|
||||
|
||||
# make sure sample_config_1s uses \n linesep - tests rely on this
|
||||
assert sample_config_1s.startswith("[passlib]\nschemes")
|
||||
|
||||
sample_config_1pd = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
default = "md5_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__max_rounds = 30000,
|
||||
bsdi_crypt__default_rounds = 25000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds = 40000,
|
||||
)
|
||||
|
||||
sample_config_1pid = {
|
||||
"schemes": "des_crypt, md5_crypt, bsdi_crypt, sha512_crypt",
|
||||
"default": "md5_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
"all.vary_rounds": 0.1,
|
||||
"bsdi_crypt.max_rounds": 30000,
|
||||
"bsdi_crypt.default_rounds": 25000,
|
||||
"sha512_crypt.max_rounds": 50000,
|
||||
"sha512_crypt.min_rounds": 40000,
|
||||
}
|
||||
|
||||
sample_config_1prd = dict(
|
||||
schemes = [ hash.des_crypt, hash.md5_crypt, hash.bsdi_crypt, hash.sha512_crypt],
|
||||
default = "md5_crypt", # NOTE: passlib <= 1.5 was handler obj.
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__max_rounds = 30000,
|
||||
bsdi_crypt__default_rounds = 25000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds = 40000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 2 - partial policy & result of overlay on sample 1
|
||||
#---------------------------------------------------------------
|
||||
sample_config_2s = """\
|
||||
[passlib]
|
||||
bsdi_crypt.min_rounds = 29000
|
||||
bsdi_crypt.max_rounds = 35000
|
||||
bsdi_crypt.default_rounds = 31000
|
||||
sha512_crypt.min_rounds = 45000
|
||||
"""
|
||||
|
||||
sample_config_2pd = dict(
|
||||
# using this to test full replacement of existing options
|
||||
bsdi_crypt__min_rounds = 29000,
|
||||
bsdi_crypt__max_rounds = 35000,
|
||||
bsdi_crypt__default_rounds = 31000,
|
||||
# using this to test partial replacement of existing options
|
||||
sha512_crypt__min_rounds=45000,
|
||||
)
|
||||
|
||||
sample_config_12pd = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
default = "md5_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__min_rounds = 29000,
|
||||
bsdi_crypt__max_rounds = 35000,
|
||||
bsdi_crypt__default_rounds = 31000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds=45000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 3 - just changing default
|
||||
#---------------------------------------------------------------
|
||||
sample_config_3pd = dict(
|
||||
default="sha512_crypt",
|
||||
)
|
||||
|
||||
sample_config_123pd = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
default = "sha512_crypt",
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
bsdi_crypt__min_rounds = 29000,
|
||||
bsdi_crypt__max_rounds = 35000,
|
||||
bsdi_crypt__default_rounds = 31000,
|
||||
sha512_crypt__max_rounds = 50000,
|
||||
sha512_crypt__min_rounds=45000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 4 - category specific
|
||||
#---------------------------------------------------------------
|
||||
sample_config_4s = """
|
||||
[passlib]
|
||||
schemes = sha512_crypt
|
||||
all.vary_rounds = 10%%
|
||||
default.sha512_crypt.max_rounds = 20000
|
||||
admin.all.vary_rounds = 5%%
|
||||
admin.sha512_crypt.max_rounds = 40000
|
||||
"""
|
||||
|
||||
sample_config_4pd = dict(
|
||||
schemes = [ "sha512_crypt" ],
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
all__vary_rounds = 0.1,
|
||||
sha512_crypt__max_rounds = 20000,
|
||||
# NOTE: not maintaining backwards compat for rendering to "5%"
|
||||
admin__all__vary_rounds = 0.05,
|
||||
admin__sha512_crypt__max_rounds = 40000,
|
||||
)
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# sample 5 - to_string & deprecation testing
|
||||
#---------------------------------------------------------------
|
||||
sample_config_5s = sample_config_1s + """\
|
||||
deprecated = des_crypt
|
||||
admin__context__deprecated = des_crypt, bsdi_crypt
|
||||
"""
|
||||
|
||||
sample_config_5pd = sample_config_1pd.copy()
|
||||
sample_config_5pd.update(
|
||||
deprecated = [ "des_crypt" ],
|
||||
admin__context__deprecated = [ "des_crypt", "bsdi_crypt" ],
|
||||
)
|
||||
|
||||
sample_config_5pid = sample_config_1pid.copy()
|
||||
sample_config_5pid.update({
|
||||
"deprecated": "des_crypt",
|
||||
"admin.context.deprecated": "des_crypt, bsdi_crypt",
|
||||
})
|
||||
|
||||
sample_config_5prd = sample_config_1prd.copy()
|
||||
sample_config_5prd.update({
|
||||
# XXX: should deprecated return the actual handlers in this case?
|
||||
# would have to modify how policy stores info, for one.
|
||||
"deprecated": ["des_crypt"],
|
||||
"admin__context__deprecated": ["des_crypt", "bsdi_crypt"],
|
||||
})
|
||||
|
||||
#===================================================================
|
||||
# constructors
|
||||
#===================================================================
|
||||
def setUp(self):
|
||||
TestCase.setUp(self)
|
||||
warnings.filterwarnings("ignore",
|
||||
r"The CryptPolicy class has been deprecated")
|
||||
warnings.filterwarnings("ignore",
|
||||
r"the method.*hash_needs_update.*is deprecated")
|
||||
warnings.filterwarnings("ignore", "The 'all' scheme is deprecated.*")
|
||||
warnings.filterwarnings("ignore", "bsdi_crypt rounds should be odd")
|
||||
|
||||
def test_00_constructor(self):
|
||||
"""test CryptPolicy() constructor"""
|
||||
policy = CryptPolicy(**self.sample_config_1pd)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
policy = CryptPolicy(self.sample_config_1pd)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
self.assertRaises(TypeError, CryptPolicy, {}, {})
|
||||
self.assertRaises(TypeError, CryptPolicy, {}, dummy=1)
|
||||
|
||||
# check key with too many separators is rejected
|
||||
self.assertRaises(TypeError, CryptPolicy,
|
||||
schemes = [ "des_crypt", "md5_crypt", "bsdi_crypt", "sha512_crypt"],
|
||||
bad__key__bsdi_crypt__max_rounds = 30000,
|
||||
)
|
||||
|
||||
# check nameless handler rejected
|
||||
class nameless(uh.StaticHandler):
|
||||
name = None
|
||||
self.assertRaises(ValueError, CryptPolicy, schemes=[nameless])
|
||||
|
||||
# check scheme must be name or crypt handler
|
||||
self.assertRaises(TypeError, CryptPolicy, schemes=[uh.StaticHandler])
|
||||
|
||||
# check name conflicts are rejected
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = 'dummy_1'
|
||||
self.assertRaises(KeyError, CryptPolicy, schemes=[dummy_1, dummy_1])
|
||||
|
||||
# with unknown deprecated value
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=['des_crypt'],
|
||||
deprecated=['md5_crypt'])
|
||||
|
||||
# with unknown default value
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=['des_crypt'],
|
||||
default='md5_crypt')
|
||||
|
||||
def test_01_from_path_simple(self):
|
||||
"""test CryptPolicy.from_path() constructor"""
|
||||
# NOTE: this is separate so it can also run under GAE
|
||||
|
||||
# test preset stored in existing file
|
||||
path = self.sample_config_1s_path
|
||||
policy = CryptPolicy.from_path(path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test if path missing
|
||||
self.assertRaises(EnvironmentError, CryptPolicy.from_path, path + 'xxx')
|
||||
|
||||
def test_01_from_path(self):
|
||||
"""test CryptPolicy.from_path() constructor with encodings"""
|
||||
path = self.mktemp()
|
||||
|
||||
# test "\n" linesep
|
||||
set_file(path, self.sample_config_1s)
|
||||
policy = CryptPolicy.from_path(path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test "\r\n" linesep
|
||||
set_file(path, self.sample_config_1s.replace("\n","\r\n"))
|
||||
policy = CryptPolicy.from_path(path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test with custom encoding
|
||||
uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
|
||||
set_file(path, uc2)
|
||||
policy = CryptPolicy.from_path(path, encoding="utf-16")
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
def test_02_from_string(self):
|
||||
"""test CryptPolicy.from_string() constructor"""
|
||||
# test "\n" linesep
|
||||
policy = CryptPolicy.from_string(self.sample_config_1s)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test "\r\n" linesep
|
||||
policy = CryptPolicy.from_string(
|
||||
self.sample_config_1s.replace("\n","\r\n"))
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test with unicode
|
||||
data = to_unicode(self.sample_config_1s)
|
||||
policy = CryptPolicy.from_string(data)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test with non-ascii-compatible encoding
|
||||
uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
|
||||
policy = CryptPolicy.from_string(uc2, encoding="utf-16")
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# test category specific options
|
||||
policy = CryptPolicy.from_string(self.sample_config_4s)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_4pd)
|
||||
|
||||
def test_03_from_source(self):
|
||||
"""test CryptPolicy.from_source() constructor"""
|
||||
# pass it a path
|
||||
policy = CryptPolicy.from_source(self.sample_config_1s_path)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass it a string
|
||||
policy = CryptPolicy.from_source(self.sample_config_1s)
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass it a dict (NOTE: make a copy to detect in-place modifications)
|
||||
policy = CryptPolicy.from_source(self.sample_config_1pd.copy())
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass it existing policy
|
||||
p2 = CryptPolicy.from_source(policy)
|
||||
self.assertIs(policy, p2)
|
||||
|
||||
# pass it something wrong
|
||||
self.assertRaises(TypeError, CryptPolicy.from_source, 1)
|
||||
self.assertRaises(TypeError, CryptPolicy.from_source, [])
|
||||
|
||||
def test_04_from_sources(self):
|
||||
"""test CryptPolicy.from_sources() constructor"""
|
||||
|
||||
# pass it empty list
|
||||
self.assertRaises(ValueError, CryptPolicy.from_sources, [])
|
||||
|
||||
# pass it one-element list
|
||||
policy = CryptPolicy.from_sources([self.sample_config_1s])
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_1pd)
|
||||
|
||||
# pass multiple sources
|
||||
policy = CryptPolicy.from_sources(
|
||||
[
|
||||
self.sample_config_1s_path,
|
||||
self.sample_config_2s,
|
||||
self.sample_config_3pd,
|
||||
])
|
||||
self.assertEqual(policy.to_dict(), self.sample_config_123pd)
|
||||
|
||||
def test_05_replace(self):
|
||||
"""test CryptPolicy.replace() constructor"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
|
||||
# check overlaying sample 2
|
||||
p2 = p1.replace(**self.sample_config_2pd)
|
||||
self.assertEqual(p2.to_dict(), self.sample_config_12pd)
|
||||
|
||||
# check repeating overlay makes no change
|
||||
p2b = p2.replace(**self.sample_config_2pd)
|
||||
self.assertEqual(p2b.to_dict(), self.sample_config_12pd)
|
||||
|
||||
# check overlaying sample 3
|
||||
p3 = p2.replace(self.sample_config_3pd)
|
||||
self.assertEqual(p3.to_dict(), self.sample_config_123pd)
|
||||
|
||||
def test_06_forbidden(self):
|
||||
"""test CryptPolicy() forbidden kwds"""
|
||||
|
||||
# salt not allowed to be set
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=["des_crypt"],
|
||||
des_crypt__salt="xx",
|
||||
)
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=["des_crypt"],
|
||||
all__salt="xx",
|
||||
)
|
||||
|
||||
# schemes not allowed for category
|
||||
self.assertRaises(KeyError, CryptPolicy,
|
||||
schemes=["des_crypt"],
|
||||
user__context__schemes=["md5_crypt"],
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# reading
|
||||
#===================================================================
|
||||
def test_10_has_schemes(self):
|
||||
"""test has_schemes() method"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
self.assertTrue(p1.has_schemes())
|
||||
|
||||
p3 = CryptPolicy(**self.sample_config_3pd)
|
||||
self.assertTrue(not p3.has_schemes())
|
||||
|
||||
def test_11_iter_handlers(self):
|
||||
"""test iter_handlers() method"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
s = self.sample_config_1prd['schemes']
|
||||
self.assertEqual(list(p1.iter_handlers()), s)
|
||||
|
||||
p3 = CryptPolicy(**self.sample_config_3pd)
|
||||
self.assertEqual(list(p3.iter_handlers()), [])
|
||||
|
||||
def test_12_get_handler(self):
|
||||
"""test get_handler() method"""
|
||||
|
||||
p1 = CryptPolicy(**self.sample_config_1pd)
|
||||
|
||||
# check by name
|
||||
self.assertIs(p1.get_handler("bsdi_crypt"), hash.bsdi_crypt)
|
||||
|
||||
# check by missing name
|
||||
self.assertIs(p1.get_handler("sha256_crypt"), None)
|
||||
self.assertRaises(KeyError, p1.get_handler, "sha256_crypt", required=True)
|
||||
|
||||
# check default
|
||||
self.assertIs(p1.get_handler(), hash.md5_crypt)
|
||||
|
||||
def test_13_get_options(self):
|
||||
"""test get_options() method"""
|
||||
|
||||
p12 = CryptPolicy(**self.sample_config_12pd)
|
||||
|
||||
self.assertEqual(p12.get_options("bsdi_crypt"),dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds = 0.1,
|
||||
min_rounds = 29000,
|
||||
max_rounds = 35000,
|
||||
default_rounds = 31000,
|
||||
))
|
||||
|
||||
self.assertEqual(p12.get_options("sha512_crypt"),dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds = 0.1,
|
||||
min_rounds = 45000,
|
||||
max_rounds = 50000,
|
||||
))
|
||||
|
||||
p4 = CryptPolicy.from_string(self.sample_config_4s)
|
||||
self.assertEqual(p4.get_options("sha512_crypt"), dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds=0.1,
|
||||
max_rounds=20000,
|
||||
))
|
||||
|
||||
self.assertEqual(p4.get_options("sha512_crypt", "user"), dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "10%"
|
||||
vary_rounds=0.1,
|
||||
max_rounds=20000,
|
||||
))
|
||||
|
||||
self.assertEqual(p4.get_options("sha512_crypt", "admin"), dict(
|
||||
# NOTE: not maintaining backwards compat for rendering to "5%"
|
||||
vary_rounds=0.05,
|
||||
max_rounds=40000,
|
||||
))
|
||||
|
||||
def test_14_handler_is_deprecated(self):
|
||||
"""test handler_is_deprecated() method"""
|
||||
pa = CryptPolicy(**self.sample_config_1pd)
|
||||
pb = CryptPolicy(**self.sample_config_5pd)
|
||||
|
||||
self.assertFalse(pa.handler_is_deprecated("des_crypt"))
|
||||
self.assertFalse(pa.handler_is_deprecated(hash.bsdi_crypt))
|
||||
self.assertFalse(pa.handler_is_deprecated("sha512_crypt"))
|
||||
|
||||
self.assertTrue(pb.handler_is_deprecated("des_crypt"))
|
||||
self.assertFalse(pb.handler_is_deprecated(hash.bsdi_crypt))
|
||||
self.assertFalse(pb.handler_is_deprecated("sha512_crypt"))
|
||||
|
||||
# check categories as well
|
||||
self.assertTrue(pb.handler_is_deprecated("des_crypt", "user"))
|
||||
self.assertFalse(pb.handler_is_deprecated("bsdi_crypt", "user"))
|
||||
self.assertTrue(pb.handler_is_deprecated("des_crypt", "admin"))
|
||||
self.assertTrue(pb.handler_is_deprecated("bsdi_crypt", "admin"))
|
||||
|
||||
# check deprecation is overridden per category
|
||||
pc = CryptPolicy(
|
||||
schemes=["md5_crypt", "des_crypt"],
|
||||
deprecated=["md5_crypt"],
|
||||
user__context__deprecated=["des_crypt"],
|
||||
)
|
||||
self.assertTrue(pc.handler_is_deprecated("md5_crypt"))
|
||||
self.assertFalse(pc.handler_is_deprecated("des_crypt"))
|
||||
self.assertFalse(pc.handler_is_deprecated("md5_crypt", "user"))
|
||||
self.assertTrue(pc.handler_is_deprecated("des_crypt", "user"))
|
||||
|
||||
def test_15_min_verify_time(self):
|
||||
"""test get_min_verify_time() method"""
|
||||
# silence deprecation warnings for min verify time
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
pa = CryptPolicy()
|
||||
self.assertEqual(pa.get_min_verify_time(), 0)
|
||||
self.assertEqual(pa.get_min_verify_time('admin'), 0)
|
||||
|
||||
pb = pa.replace(min_verify_time=.1)
|
||||
self.assertEqual(pb.get_min_verify_time(), 0)
|
||||
self.assertEqual(pb.get_min_verify_time('admin'), 0)
|
||||
|
||||
#===================================================================
|
||||
# serialization
|
||||
#===================================================================
|
||||
def test_20_iter_config(self):
|
||||
"""test iter_config() method"""
|
||||
p5 = CryptPolicy(**self.sample_config_5pd)
|
||||
self.assertEqual(dict(p5.iter_config()), self.sample_config_5pd)
|
||||
self.assertEqual(dict(p5.iter_config(resolve=True)), self.sample_config_5prd)
|
||||
self.assertEqual(dict(p5.iter_config(ini=True)), self.sample_config_5pid)
|
||||
|
||||
def test_21_to_dict(self):
|
||||
"""test to_dict() method"""
|
||||
p5 = CryptPolicy(**self.sample_config_5pd)
|
||||
self.assertEqual(p5.to_dict(), self.sample_config_5pd)
|
||||
self.assertEqual(p5.to_dict(resolve=True), self.sample_config_5prd)
|
||||
|
||||
def test_22_to_string(self):
|
||||
"""test to_string() method"""
|
||||
pa = CryptPolicy(**self.sample_config_5pd)
|
||||
s = pa.to_string() # NOTE: can't compare string directly, ordering etc may not match
|
||||
pb = CryptPolicy.from_string(s)
|
||||
self.assertEqual(pb.to_dict(), self.sample_config_5pd)
|
||||
|
||||
s = pa.to_string(encoding="latin-1")
|
||||
self.assertIsInstance(s, bytes)
|
||||
|
||||
#===================================================================
|
||||
#
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# CryptContext
|
||||
#=============================================================================
|
||||
class CryptContextTest(TestCase):
|
||||
"""test CryptContext class"""
|
||||
descriptionPrefix = "CryptContext"
|
||||
|
||||
def setUp(self):
|
||||
TestCase.setUp(self)
|
||||
warnings.filterwarnings("ignore",
|
||||
r"CryptContext\(\)\.replace\(\) has been deprecated.*")
|
||||
warnings.filterwarnings("ignore",
|
||||
r"The CryptContext ``policy`` keyword has been deprecated.*")
|
||||
warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*")
|
||||
warnings.filterwarnings("ignore",
|
||||
r"the method.*hash_needs_update.*is deprecated")
|
||||
|
||||
#===================================================================
|
||||
# constructor
|
||||
#===================================================================
|
||||
def test_00_constructor(self):
|
||||
"""test constructor"""
|
||||
# create crypt context using handlers
|
||||
cc = CryptContext([hash.md5_crypt, hash.bsdi_crypt, hash.des_crypt])
|
||||
c,b,a = cc.policy.iter_handlers()
|
||||
self.assertIs(a, hash.des_crypt)
|
||||
self.assertIs(b, hash.bsdi_crypt)
|
||||
self.assertIs(c, hash.md5_crypt)
|
||||
|
||||
# create context using names
|
||||
cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"])
|
||||
c,b,a = cc.policy.iter_handlers()
|
||||
self.assertIs(a, hash.des_crypt)
|
||||
self.assertIs(b, hash.bsdi_crypt)
|
||||
self.assertIs(c, hash.md5_crypt)
|
||||
|
||||
# policy kwd
|
||||
policy = cc.policy
|
||||
cc = CryptContext(policy=policy)
|
||||
self.assertEqual(cc.to_dict(), policy.to_dict())
|
||||
|
||||
cc = CryptContext(policy=policy, default="bsdi_crypt")
|
||||
self.assertNotEqual(cc.to_dict(), policy.to_dict())
|
||||
self.assertEqual(cc.to_dict(), dict(schemes=["md5_crypt","bsdi_crypt","des_crypt"],
|
||||
default="bsdi_crypt"))
|
||||
|
||||
self.assertRaises(TypeError, setattr, cc, 'policy', None)
|
||||
self.assertRaises(TypeError, CryptContext, policy='x')
|
||||
|
||||
def test_01_replace(self):
|
||||
"""test replace()"""
|
||||
|
||||
cc = CryptContext(["md5_crypt", "bsdi_crypt", "des_crypt"])
|
||||
self.assertIs(cc.policy.get_handler(), hash.md5_crypt)
|
||||
|
||||
cc2 = cc.replace()
|
||||
self.assertIsNot(cc2, cc)
|
||||
# NOTE: was not able to maintain backward compatibility with this...
|
||||
##self.assertIs(cc2.policy, cc.policy)
|
||||
|
||||
cc3 = cc.replace(default="bsdi_crypt")
|
||||
self.assertIsNot(cc3, cc)
|
||||
# NOTE: was not able to maintain backward compatibility with this...
|
||||
##self.assertIs(cc3.policy, cc.policy)
|
||||
self.assertIs(cc3.policy.get_handler(), hash.bsdi_crypt)
|
||||
|
||||
def test_02_no_handlers(self):
|
||||
"""test no handlers"""
|
||||
|
||||
# check constructor...
|
||||
cc = CryptContext()
|
||||
self.assertRaises(KeyError, cc.identify, 'hash', required=True)
|
||||
self.assertRaises(KeyError, cc.hash, 'secret')
|
||||
self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
|
||||
|
||||
# check updating policy after the fact...
|
||||
cc = CryptContext(['md5_crypt'])
|
||||
p = CryptPolicy(schemes=[])
|
||||
cc.policy = p
|
||||
|
||||
self.assertRaises(KeyError, cc.identify, 'hash', required=True)
|
||||
self.assertRaises(KeyError, cc.hash, 'secret')
|
||||
self.assertRaises(KeyError, cc.verify, 'secret', 'hash')
|
||||
|
||||
#===================================================================
|
||||
# policy adaptation
|
||||
#===================================================================
|
||||
sample_policy_1 = dict(
|
||||
schemes = [ "des_crypt", "md5_crypt", "phpass", "bsdi_crypt",
|
||||
"sha256_crypt"],
|
||||
deprecated = [ "des_crypt", ],
|
||||
default = "sha256_crypt",
|
||||
bsdi_crypt__max_rounds = 30,
|
||||
bsdi_crypt__default_rounds = 25,
|
||||
bsdi_crypt__vary_rounds = 0,
|
||||
sha256_crypt__max_rounds = 3000,
|
||||
sha256_crypt__min_rounds = 2000,
|
||||
sha256_crypt__default_rounds = 3000,
|
||||
phpass__ident = "H",
|
||||
phpass__default_rounds = 7,
|
||||
)
|
||||
|
||||
def test_12_hash_needs_update(self):
|
||||
"""test hash_needs_update() method"""
|
||||
cc = CryptContext(**self.sample_policy_1)
|
||||
|
||||
# check deprecated scheme
|
||||
self.assertTrue(cc.hash_needs_update('9XXD4trGYeGJA'))
|
||||
self.assertFalse(cc.hash_needs_update('$1$J8HC2RCr$HcmM.7NxB2weSvlw2FgzU0'))
|
||||
|
||||
# check min rounds
|
||||
self.assertTrue(cc.hash_needs_update('$5$rounds=1999$jD81UCoo.zI.UETs$Y7qSTQ6mTiU9qZB4fRr43wRgQq4V.5AAf7F97Pzxey/'))
|
||||
self.assertFalse(cc.hash_needs_update('$5$rounds=2000$228SSRje04cnNCaQ$YGV4RYu.5sNiBvorQDlO0WWQjyJVGKBcJXz3OtyQ2u8'))
|
||||
|
||||
# check max rounds
|
||||
self.assertFalse(cc.hash_needs_update('$5$rounds=3000$fS9iazEwTKi7QPW4$VasgBC8FqlOvD7x2HhABaMXCTh9jwHclPA9j5YQdns.'))
|
||||
self.assertTrue(cc.hash_needs_update('$5$rounds=3001$QlFHHifXvpFX4PLs$/0ekt7lSs/lOikSerQ0M/1porEHxYq7W/2hdFpxA3fA'))
|
||||
|
||||
#===================================================================
|
||||
# border cases
|
||||
#===================================================================
|
||||
def test_30_nonstring_hash(self):
|
||||
"""test non-string hash values cause error"""
|
||||
warnings.filterwarnings("ignore", ".*needs_update.*'scheme' keyword is deprecated.*")
|
||||
|
||||
#
|
||||
# test hash=None or some other non-string causes TypeError
|
||||
# and that explicit-scheme code path behaves the same.
|
||||
#
|
||||
cc = CryptContext(["des_crypt"])
|
||||
for hash, kwds in [
|
||||
(None, {}),
|
||||
# NOTE: 'scheme' kwd is deprecated...
|
||||
(None, {"scheme": "des_crypt"}),
|
||||
(1, {}),
|
||||
((), {}),
|
||||
]:
|
||||
|
||||
self.assertRaises(TypeError, cc.hash_needs_update, hash, **kwds)
|
||||
|
||||
cc2 = CryptContext(["mysql323"])
|
||||
self.assertRaises(TypeError, cc2.hash_needs_update, None)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# LazyCryptContext
|
||||
#=============================================================================
|
||||
class dummy_2(uh.StaticHandler):
|
||||
name = "dummy_2"
|
||||
|
||||
class LazyCryptContextTest(TestCase):
|
||||
descriptionPrefix = "LazyCryptContext"
|
||||
|
||||
def setUp(self):
|
||||
TestCase.setUp(self)
|
||||
|
||||
# make sure this isn't registered before OR after
|
||||
unload_handler_name("dummy_2")
|
||||
self.addCleanup(unload_handler_name, "dummy_2")
|
||||
|
||||
# silence some warnings
|
||||
warnings.filterwarnings("ignore",
|
||||
r"CryptContext\(\)\.replace\(\) has been deprecated")
|
||||
warnings.filterwarnings("ignore", ".*(CryptPolicy|context\.policy).*(has|have) been deprecated.*")
|
||||
|
||||
def test_kwd_constructor(self):
|
||||
"""test plain kwds"""
|
||||
self.assertFalse(has_crypt_handler("dummy_2"))
|
||||
register_crypt_handler_path("dummy_2", "passlib.tests.test_context")
|
||||
|
||||
cc = LazyCryptContext(iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"])
|
||||
|
||||
self.assertFalse(has_crypt_handler("dummy_2", True))
|
||||
|
||||
self.assertTrue(cc.policy.handler_is_deprecated("des_crypt"))
|
||||
self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"])
|
||||
|
||||
self.assertTrue(has_crypt_handler("dummy_2", True))
|
||||
|
||||
def test_callable_constructor(self):
|
||||
"""test create_policy() hook, returning CryptPolicy"""
|
||||
self.assertFalse(has_crypt_handler("dummy_2"))
|
||||
register_crypt_handler_path("dummy_2", "passlib.tests.test_context")
|
||||
|
||||
def create_policy(flag=False):
|
||||
self.assertTrue(flag)
|
||||
return CryptPolicy(schemes=iter(["dummy_2", "des_crypt"]), deprecated=["des_crypt"])
|
||||
|
||||
cc = LazyCryptContext(create_policy=create_policy, flag=True)
|
||||
|
||||
self.assertFalse(has_crypt_handler("dummy_2", True))
|
||||
|
||||
self.assertTrue(cc.policy.handler_is_deprecated("des_crypt"))
|
||||
self.assertEqual(cc.policy.schemes(), ["dummy_2", "des_crypt"])
|
||||
|
||||
self.assertTrue(has_crypt_handler("dummy_2", True))
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
160
venv/Lib/site-packages/passlib/tests/test_crypto_builtin_md4.py
Normal file
160
venv/Lib/site-packages/passlib/tests/test_crypto_builtin_md4.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""passlib.tests -- unittests for passlib.crypto._md4"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, division
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import hashlib
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.utils.compat import bascii_to_str, PY3, u
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
from passlib.tests.utils import TestCase, skipUnless
|
||||
# local
|
||||
__all__ = [
|
||||
"_Common_MD4_Test",
|
||||
"MD4_Builtin_Test",
|
||||
"MD4_SSL_Test",
|
||||
]
|
||||
#=============================================================================
|
||||
# test pure-python MD4 implementation
|
||||
#=============================================================================
|
||||
class _Common_MD4_Test(TestCase):
|
||||
"""common code for testing md4 backends"""
|
||||
|
||||
vectors = [
|
||||
# input -> hex digest
|
||||
# test vectors from http://www.faqs.org/rfcs/rfc1320.html - A.5
|
||||
(b"", "31d6cfe0d16ae931b73c59d7e0c089c0"),
|
||||
(b"a", "bde52cb31de33e46245e05fbdbd6fb24"),
|
||||
(b"abc", "a448017aaf21d8525fc10ae87aa6729d"),
|
||||
(b"message digest", "d9130a8164549fe818874806e1c7014b"),
|
||||
(b"abcdefghijklmnopqrstuvwxyz", "d79e1c308aa5bbcdeea8ed63df412da9"),
|
||||
(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "043f8582f241db351ce627e153e7f0e4"),
|
||||
(b"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "e33b4ddc9c38f2199c3e7b164fcc0536"),
|
||||
]
|
||||
|
||||
def get_md4_const(self):
|
||||
"""
|
||||
get md4 constructor --
|
||||
overridden by subclasses to use alternate backends.
|
||||
"""
|
||||
return lookup_hash("md4").const
|
||||
|
||||
def test_attrs(self):
|
||||
"""informational attributes"""
|
||||
h = self.get_md4_const()()
|
||||
self.assertEqual(h.name, "md4")
|
||||
self.assertEqual(h.digest_size, 16)
|
||||
self.assertEqual(h.block_size, 64)
|
||||
|
||||
def test_md4_update(self):
|
||||
"""update() method"""
|
||||
md4 = self.get_md4_const()
|
||||
h = md4(b'')
|
||||
self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0")
|
||||
|
||||
h.update(b'a')
|
||||
self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24")
|
||||
|
||||
h.update(b'bcdefghijklmnopqrstuvwxyz')
|
||||
self.assertEqual(h.hexdigest(), "d79e1c308aa5bbcdeea8ed63df412da9")
|
||||
|
||||
if PY3:
|
||||
# reject unicode, hash should return digest of b''
|
||||
h = md4()
|
||||
self.assertRaises(TypeError, h.update, u('a'))
|
||||
self.assertEqual(h.hexdigest(), "31d6cfe0d16ae931b73c59d7e0c089c0")
|
||||
else:
|
||||
# coerce unicode to ascii, hash should return digest of b'a'
|
||||
h = md4()
|
||||
h.update(u('a'))
|
||||
self.assertEqual(h.hexdigest(), "bde52cb31de33e46245e05fbdbd6fb24")
|
||||
|
||||
def test_md4_hexdigest(self):
|
||||
"""hexdigest() method"""
|
||||
md4 = self.get_md4_const()
|
||||
for input, hex in self.vectors:
|
||||
out = md4(input).hexdigest()
|
||||
self.assertEqual(out, hex)
|
||||
|
||||
def test_md4_digest(self):
|
||||
"""digest() method"""
|
||||
md4 = self.get_md4_const()
|
||||
for input, hex in self.vectors:
|
||||
out = bascii_to_str(hexlify(md4(input).digest()))
|
||||
self.assertEqual(out, hex)
|
||||
|
||||
def test_md4_copy(self):
|
||||
"""copy() method"""
|
||||
md4 = self.get_md4_const()
|
||||
h = md4(b'abc')
|
||||
|
||||
h2 = h.copy()
|
||||
h2.update(b'def')
|
||||
self.assertEqual(h2.hexdigest(), '804e7f1c2586e50b49ac65db5b645131')
|
||||
|
||||
h.update(b'ghi')
|
||||
self.assertEqual(h.hexdigest(), 'c5225580bfe176f6deeee33dee98732c')
|
||||
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# create subclasses to test various backends
|
||||
#------------------------------------------------------------------------
|
||||
|
||||
def has_native_md4(): # pragma: no cover -- runtime detection
|
||||
"""
|
||||
check if hashlib natively supports md4.
|
||||
"""
|
||||
try:
|
||||
hashlib.new("md4")
|
||||
return True
|
||||
except ValueError:
|
||||
# not supported - ssl probably missing (e.g. ironpython)
|
||||
return False
|
||||
|
||||
|
||||
@skipUnless(has_native_md4(), "hashlib lacks ssl/md4 support")
|
||||
class MD4_SSL_Test(_Common_MD4_Test):
|
||||
descriptionPrefix = "hashlib.new('md4')"
|
||||
|
||||
# NOTE: we trust ssl got md4 implementation right,
|
||||
# this is more to test our test is correct :)
|
||||
|
||||
def setUp(self):
|
||||
super(MD4_SSL_Test, self).setUp()
|
||||
|
||||
# make sure we're using right constructor.
|
||||
self.assertEqual(self.get_md4_const().__module__, "hashlib")
|
||||
|
||||
|
||||
class MD4_Builtin_Test(_Common_MD4_Test):
|
||||
descriptionPrefix = "passlib.crypto._md4.md4()"
|
||||
|
||||
def setUp(self):
|
||||
super(MD4_Builtin_Test, self).setUp()
|
||||
|
||||
if has_native_md4():
|
||||
|
||||
# Temporarily make lookup_hash() use builtin pure-python implementation,
|
||||
# by monkeypatching hashlib.new() to ensure we fall back to passlib's md4 class.
|
||||
orig = hashlib.new
|
||||
def wrapper(name, *args):
|
||||
if name == "md4":
|
||||
raise ValueError("md4 disabled for testing")
|
||||
return orig(name, *args)
|
||||
self.patchAttr(hashlib, "new", wrapper)
|
||||
|
||||
# flush cache before & after test, since we're mucking with it.
|
||||
lookup_hash.clear_cache()
|
||||
self.addCleanup(lookup_hash.clear_cache)
|
||||
|
||||
# make sure we're using right constructor.
|
||||
self.assertEqual(self.get_md4_const().__module__, "passlib.crypto._md4")
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
194
venv/Lib/site-packages/passlib/tests/test_crypto_des.py
Normal file
194
venv/Lib/site-packages/passlib/tests/test_crypto_des.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""passlib.tests -- unittests for passlib.crypto.des"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, division
|
||||
# core
|
||||
from functools import partial
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.utils import getrandbytes
|
||||
from passlib.tests.utils import TestCase
|
||||
|
||||
#=============================================================================
|
||||
# test DES routines
|
||||
#=============================================================================
|
||||
class DesTest(TestCase):
|
||||
descriptionPrefix = "passlib.crypto.des"
|
||||
|
||||
# test vectors taken from http://www.skepticfiles.org/faq/testdes.htm
|
||||
des_test_vectors = [
|
||||
# key, plaintext, ciphertext
|
||||
(0x0000000000000000, 0x0000000000000000, 0x8CA64DE9C1B123A7),
|
||||
(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF, 0x7359B2163E4EDC58),
|
||||
(0x3000000000000000, 0x1000000000000001, 0x958E6E627A05557B),
|
||||
(0x1111111111111111, 0x1111111111111111, 0xF40379AB9E0EC533),
|
||||
(0x0123456789ABCDEF, 0x1111111111111111, 0x17668DFC7292532D),
|
||||
(0x1111111111111111, 0x0123456789ABCDEF, 0x8A5AE1F81AB8F2DD),
|
||||
(0x0000000000000000, 0x0000000000000000, 0x8CA64DE9C1B123A7),
|
||||
(0xFEDCBA9876543210, 0x0123456789ABCDEF, 0xED39D950FA74BCC4),
|
||||
(0x7CA110454A1A6E57, 0x01A1D6D039776742, 0x690F5B0D9A26939B),
|
||||
(0x0131D9619DC1376E, 0x5CD54CA83DEF57DA, 0x7A389D10354BD271),
|
||||
(0x07A1133E4A0B2686, 0x0248D43806F67172, 0x868EBB51CAB4599A),
|
||||
(0x3849674C2602319E, 0x51454B582DDF440A, 0x7178876E01F19B2A),
|
||||
(0x04B915BA43FEB5B6, 0x42FD443059577FA2, 0xAF37FB421F8C4095),
|
||||
(0x0113B970FD34F2CE, 0x059B5E0851CF143A, 0x86A560F10EC6D85B),
|
||||
(0x0170F175468FB5E6, 0x0756D8E0774761D2, 0x0CD3DA020021DC09),
|
||||
(0x43297FAD38E373FE, 0x762514B829BF486A, 0xEA676B2CB7DB2B7A),
|
||||
(0x07A7137045DA2A16, 0x3BDD119049372802, 0xDFD64A815CAF1A0F),
|
||||
(0x04689104C2FD3B2F, 0x26955F6835AF609A, 0x5C513C9C4886C088),
|
||||
(0x37D06BB516CB7546, 0x164D5E404F275232, 0x0A2AEEAE3FF4AB77),
|
||||
(0x1F08260D1AC2465E, 0x6B056E18759F5CCA, 0xEF1BF03E5DFA575A),
|
||||
(0x584023641ABA6176, 0x004BD6EF09176062, 0x88BF0DB6D70DEE56),
|
||||
(0x025816164629B007, 0x480D39006EE762F2, 0xA1F9915541020B56),
|
||||
(0x49793EBC79B3258F, 0x437540C8698F3CFA, 0x6FBF1CAFCFFD0556),
|
||||
(0x4FB05E1515AB73A7, 0x072D43A077075292, 0x2F22E49BAB7CA1AC),
|
||||
(0x49E95D6D4CA229BF, 0x02FE55778117F12A, 0x5A6B612CC26CCE4A),
|
||||
(0x018310DC409B26D6, 0x1D9D5C5018F728C2, 0x5F4C038ED12B2E41),
|
||||
(0x1C587F1C13924FEF, 0x305532286D6F295A, 0x63FAC0D034D9F793),
|
||||
(0x0101010101010101, 0x0123456789ABCDEF, 0x617B3A0CE8F07100),
|
||||
(0x1F1F1F1F0E0E0E0E, 0x0123456789ABCDEF, 0xDB958605F8C8C606),
|
||||
(0xE0FEE0FEF1FEF1FE, 0x0123456789ABCDEF, 0xEDBFD1C66C29CCC7),
|
||||
(0x0000000000000000, 0xFFFFFFFFFFFFFFFF, 0x355550B2150E2451),
|
||||
(0xFFFFFFFFFFFFFFFF, 0x0000000000000000, 0xCAAAAF4DEAF1DBAE),
|
||||
(0x0123456789ABCDEF, 0x0000000000000000, 0xD5D44FF720683D0D),
|
||||
(0xFEDCBA9876543210, 0xFFFFFFFFFFFFFFFF, 0x2A2BB008DF97C2F2),
|
||||
]
|
||||
|
||||
def test_01_expand(self):
|
||||
"""expand_des_key()"""
|
||||
from passlib.crypto.des import expand_des_key, shrink_des_key, \
|
||||
_KDATA_MASK, INT_56_MASK
|
||||
|
||||
# make sure test vectors are preserved (sans parity bits)
|
||||
# uses ints, bytes are tested under # 02
|
||||
for key1, _, _ in self.des_test_vectors:
|
||||
key2 = shrink_des_key(key1)
|
||||
key3 = expand_des_key(key2)
|
||||
# NOTE: this assumes expand_des_key() sets parity bits to 0
|
||||
self.assertEqual(key3, key1 & _KDATA_MASK)
|
||||
|
||||
# type checks
|
||||
self.assertRaises(TypeError, expand_des_key, 1.0)
|
||||
|
||||
# too large
|
||||
self.assertRaises(ValueError, expand_des_key, INT_56_MASK+1)
|
||||
self.assertRaises(ValueError, expand_des_key, b"\x00"*8)
|
||||
|
||||
# too small
|
||||
self.assertRaises(ValueError, expand_des_key, -1)
|
||||
self.assertRaises(ValueError, expand_des_key, b"\x00"*6)
|
||||
|
||||
def test_02_shrink(self):
|
||||
"""shrink_des_key()"""
|
||||
from passlib.crypto.des import expand_des_key, shrink_des_key, INT_64_MASK
|
||||
rng = self.getRandom()
|
||||
|
||||
# make sure reverse works for some random keys
|
||||
# uses bytes, ints are tested under # 01
|
||||
for i in range(20):
|
||||
key1 = getrandbytes(rng, 7)
|
||||
key2 = expand_des_key(key1)
|
||||
key3 = shrink_des_key(key2)
|
||||
self.assertEqual(key3, key1)
|
||||
|
||||
# type checks
|
||||
self.assertRaises(TypeError, shrink_des_key, 1.0)
|
||||
|
||||
# too large
|
||||
self.assertRaises(ValueError, shrink_des_key, INT_64_MASK+1)
|
||||
self.assertRaises(ValueError, shrink_des_key, b"\x00"*9)
|
||||
|
||||
# too small
|
||||
self.assertRaises(ValueError, shrink_des_key, -1)
|
||||
self.assertRaises(ValueError, shrink_des_key, b"\x00"*7)
|
||||
|
||||
def _random_parity(self, key):
|
||||
"""randomize parity bits"""
|
||||
from passlib.crypto.des import _KDATA_MASK, _KPARITY_MASK, INT_64_MASK
|
||||
rng = self.getRandom()
|
||||
return (key & _KDATA_MASK) | (rng.randint(0,INT_64_MASK) & _KPARITY_MASK)
|
||||
|
||||
def test_03_encrypt_bytes(self):
|
||||
"""des_encrypt_block()"""
|
||||
from passlib.crypto.des import (des_encrypt_block, shrink_des_key,
|
||||
_pack64, _unpack64)
|
||||
|
||||
# run through test vectors
|
||||
for key, plaintext, correct in self.des_test_vectors:
|
||||
# convert to bytes
|
||||
key = _pack64(key)
|
||||
plaintext = _pack64(plaintext)
|
||||
correct = _pack64(correct)
|
||||
|
||||
# test 64-bit key
|
||||
result = des_encrypt_block(key, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r plaintext=%r:" %
|
||||
(key, plaintext))
|
||||
|
||||
# test 56-bit version
|
||||
key2 = shrink_des_key(key)
|
||||
result = des_encrypt_block(key2, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r shrink(key)=%r plaintext=%r:" %
|
||||
(key, key2, plaintext))
|
||||
|
||||
# test with random parity bits
|
||||
for _ in range(20):
|
||||
key3 = _pack64(self._random_parity(_unpack64(key)))
|
||||
result = des_encrypt_block(key3, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r rndparity(key)=%r plaintext=%r:" %
|
||||
(key, key3, plaintext))
|
||||
|
||||
# check invalid keys
|
||||
stub = b'\x00' * 8
|
||||
self.assertRaises(TypeError, des_encrypt_block, 0, stub)
|
||||
self.assertRaises(ValueError, des_encrypt_block, b'\x00'*6, stub)
|
||||
|
||||
# check invalid input
|
||||
self.assertRaises(TypeError, des_encrypt_block, stub, 0)
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, b'\x00'*7)
|
||||
|
||||
# check invalid salts
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, stub, salt=-1)
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, stub, salt=1<<24)
|
||||
|
||||
# check invalid rounds
|
||||
self.assertRaises(ValueError, des_encrypt_block, stub, stub, 0, rounds=0)
|
||||
|
||||
def test_04_encrypt_ints(self):
|
||||
"""des_encrypt_int_block()"""
|
||||
from passlib.crypto.des import des_encrypt_int_block
|
||||
|
||||
# run through test vectors
|
||||
for key, plaintext, correct in self.des_test_vectors:
|
||||
# test 64-bit key
|
||||
result = des_encrypt_int_block(key, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r plaintext=%r:" %
|
||||
(key, plaintext))
|
||||
|
||||
# test with random parity bits
|
||||
for _ in range(20):
|
||||
key3 = self._random_parity(key)
|
||||
result = des_encrypt_int_block(key3, plaintext)
|
||||
self.assertEqual(result, correct, "key=%r rndparity(key)=%r plaintext=%r:" %
|
||||
(key, key3, plaintext))
|
||||
|
||||
# check invalid keys
|
||||
self.assertRaises(TypeError, des_encrypt_int_block, b'\x00', 0)
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, -1, 0)
|
||||
|
||||
# check invalid input
|
||||
self.assertRaises(TypeError, des_encrypt_int_block, 0, b'\x00')
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, -1)
|
||||
|
||||
# check invalid salts
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, salt=-1)
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, salt=1<<24)
|
||||
|
||||
# check invalid rounds
|
||||
self.assertRaises(ValueError, des_encrypt_int_block, 0, 0, 0, rounds=0)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
544
venv/Lib/site-packages/passlib/tests/test_crypto_digest.py
Normal file
544
venv/Lib/site-packages/passlib/tests/test_crypto_digest.py
Normal file
@@ -0,0 +1,544 @@
|
||||
"""tests for passlib.utils.(des|pbkdf2|md4)"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, division
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import hashlib
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
# module
|
||||
from passlib.exc import UnknownHashError
|
||||
from passlib.utils.compat import PY3, u, JYTHON
|
||||
from passlib.tests.utils import TestCase, TEST_MODE, skipUnless, hb
|
||||
|
||||
#=============================================================================
|
||||
# test assorted crypto helpers
|
||||
#=============================================================================
|
||||
class HashInfoTest(TestCase):
|
||||
"""test various crypto functions"""
|
||||
descriptionPrefix = "passlib.crypto.digest"
|
||||
|
||||
#: list of formats norm_hash_name() should support
|
||||
norm_hash_formats = ["hashlib", "iana"]
|
||||
|
||||
#: test cases for norm_hash_name()
|
||||
#: each row contains (iana name, hashlib name, ... 0+ unnormalized names)
|
||||
norm_hash_samples = [
|
||||
# real hashes
|
||||
("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"),
|
||||
|
||||
# fake hashes (to check if fallback normalization behaves sanely)
|
||||
("sha4_256", "sha4-256", "SHA4-256", "SHA-4-256"),
|
||||
("test128", "test-128", "TEST128"),
|
||||
("test2", "test2", "TEST-2"),
|
||||
("test3_128", "test3-128", "TEST-3-128"),
|
||||
]
|
||||
|
||||
def test_norm_hash_name(self):
|
||||
"""norm_hash_name()"""
|
||||
from itertools import chain
|
||||
from passlib.crypto.digest import norm_hash_name, _known_hash_names
|
||||
|
||||
# snapshot warning state, ignore unknown hash warnings
|
||||
ctx = warnings.catch_warnings()
|
||||
ctx.__enter__()
|
||||
self.addCleanup(ctx.__exit__)
|
||||
warnings.filterwarnings("ignore", '.*unknown hash')
|
||||
warnings.filterwarnings("ignore", '.*unsupported hash')
|
||||
|
||||
# test string 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
|
||||
for row in chain(_known_hash_names, self.norm_hash_samples):
|
||||
for idx, format in enumerate(self.norm_hash_formats):
|
||||
correct = row[idx]
|
||||
for value in row:
|
||||
result = norm_hash_name(value, format)
|
||||
self.assertEqual(result, correct,
|
||||
"name=%r, format=%r:" % (value,
|
||||
format))
|
||||
|
||||
def test_lookup_hash_ctor(self):
|
||||
"""lookup_hash() -- constructor"""
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
# invalid/unknown names should be rejected
|
||||
self.assertRaises(ValueError, lookup_hash, "new")
|
||||
self.assertRaises(ValueError, lookup_hash, "__name__")
|
||||
self.assertRaises(ValueError, lookup_hash, "sha4")
|
||||
|
||||
# 1. should return hashlib builtin if found
|
||||
self.assertEqual(lookup_hash("md5"), (hashlib.md5, 16, 64))
|
||||
|
||||
# 2. should return wrapper around hashlib.new() if found
|
||||
try:
|
||||
hashlib.new("sha")
|
||||
has_sha = True
|
||||
except ValueError:
|
||||
has_sha = False
|
||||
if has_sha:
|
||||
record = lookup_hash("sha")
|
||||
const = record[0]
|
||||
self.assertEqual(record, (const, 20, 64))
|
||||
self.assertEqual(hexlify(const(b"abc").digest()),
|
||||
b"0164b8a914cd2a5e74c4f7ff082c4d97f1edf880")
|
||||
|
||||
else:
|
||||
self.assertRaises(ValueError, lookup_hash, "sha")
|
||||
|
||||
# 3. should fall back to builtin md4
|
||||
try:
|
||||
hashlib.new("md4")
|
||||
has_md4 = True
|
||||
except ValueError:
|
||||
has_md4 = False
|
||||
record = lookup_hash("md4")
|
||||
const = record[0]
|
||||
if not has_md4:
|
||||
from passlib.crypto._md4 import md4
|
||||
self.assertIs(const, md4)
|
||||
self.assertEqual(record, (const, 16, 64))
|
||||
self.assertEqual(hexlify(const(b"abc").digest()),
|
||||
b"a448017aaf21d8525fc10ae87aa6729d")
|
||||
|
||||
# should memoize records
|
||||
self.assertIs(lookup_hash("md5"), lookup_hash("md5"))
|
||||
|
||||
def test_lookup_hash_w_unknown_name(self):
|
||||
"""lookup_hash() -- unknown hash name"""
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
# unknown names should be rejected by default
|
||||
self.assertRaises(UnknownHashError, lookup_hash, "xxx256")
|
||||
|
||||
# required=False should return stub record instead
|
||||
info = lookup_hash("xxx256", required=False)
|
||||
self.assertFalse(info.supported)
|
||||
self.assertRaisesRegex(UnknownHashError, "unknown hash: 'xxx256'", info.const)
|
||||
self.assertEqual(info.name, "xxx256")
|
||||
self.assertEqual(info.digest_size, None)
|
||||
self.assertEqual(info.block_size, None)
|
||||
|
||||
# should cache stub records
|
||||
info2 = lookup_hash("xxx256", required=False)
|
||||
self.assertIs(info2, info)
|
||||
|
||||
def test_mock_fips_mode(self):
|
||||
"""
|
||||
lookup_hash() -- test set_mock_fips_mode()
|
||||
"""
|
||||
from passlib.crypto.digest import lookup_hash, _set_mock_fips_mode
|
||||
|
||||
# check if md5 is available so we can test mock helper
|
||||
if not lookup_hash("md5", required=False).supported:
|
||||
raise self.skipTest("md5 not supported")
|
||||
|
||||
# enable monkeypatch to mock up fips mode
|
||||
_set_mock_fips_mode()
|
||||
self.addCleanup(_set_mock_fips_mode, False)
|
||||
|
||||
pat = "'md5' hash disabled for fips"
|
||||
self.assertRaisesRegex(UnknownHashError, pat, lookup_hash, "md5")
|
||||
|
||||
info = lookup_hash("md5", required=False)
|
||||
self.assertRegex(info.error_text, pat)
|
||||
self.assertRaisesRegex(UnknownHashError, pat, info.const)
|
||||
|
||||
# should use hardcoded fallback info
|
||||
self.assertEqual(info.digest_size, 16)
|
||||
self.assertEqual(info.block_size, 64)
|
||||
|
||||
def test_lookup_hash_metadata(self):
|
||||
"""lookup_hash() -- metadata"""
|
||||
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
# quick test of metadata using known reference - sha256
|
||||
info = lookup_hash("sha256")
|
||||
self.assertEqual(info.name, "sha256")
|
||||
self.assertEqual(info.iana_name, "sha-256")
|
||||
self.assertEqual(info.block_size, 64)
|
||||
self.assertEqual(info.digest_size, 32)
|
||||
self.assertIs(lookup_hash("SHA2-256"), info)
|
||||
|
||||
# quick test of metadata using known reference - md5
|
||||
info = lookup_hash("md5")
|
||||
self.assertEqual(info.name, "md5")
|
||||
self.assertEqual(info.iana_name, "md5")
|
||||
self.assertEqual(info.block_size, 64)
|
||||
self.assertEqual(info.digest_size, 16)
|
||||
|
||||
def test_lookup_hash_alt_types(self):
|
||||
"""lookup_hash() -- alternate types"""
|
||||
|
||||
from passlib.crypto.digest import lookup_hash
|
||||
|
||||
info = lookup_hash("sha256")
|
||||
self.assertIs(lookup_hash(info), info)
|
||||
self.assertIs(lookup_hash(info.const), info)
|
||||
|
||||
self.assertRaises(TypeError, lookup_hash, 123)
|
||||
|
||||
# TODO: write full test of compile_hmac() -- currently relying on pbkdf2_hmac() tests
|
||||
|
||||
#=============================================================================
|
||||
# test PBKDF1 support
|
||||
#=============================================================================
|
||||
class Pbkdf1_Test(TestCase):
|
||||
"""test kdf helpers"""
|
||||
descriptionPrefix = "passlib.crypto.digest.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: # FIXME: find out why not jython, or reenable this.
|
||||
pbkdf1_tests.append(
|
||||
(b'password', b'salt', 1000, None, 'md4', hb('f7f2e91100a8f96190f2dd177cb26453'))
|
||||
)
|
||||
|
||||
def test_known(self):
|
||||
"""test reference vectors"""
|
||||
from passlib.crypto.digest import pbkdf1
|
||||
for secret, salt, rounds, keylen, digest, correct in self.pbkdf1_tests:
|
||||
result = pbkdf1(digest, secret, salt, rounds, keylen)
|
||||
self.assertEqual(result, correct)
|
||||
|
||||
def test_border(self):
|
||||
"""test border cases"""
|
||||
from passlib.crypto.digest import pbkdf1
|
||||
def helper(secret=b'secret', salt=b'salt', rounds=1, keylen=1, hash='md5'):
|
||||
return pbkdf1(hash, secret, salt, rounds, keylen)
|
||||
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-HMAC support
|
||||
#=============================================================================
|
||||
|
||||
# import the test subject
|
||||
from passlib.crypto.digest import pbkdf2_hmac, PBKDF2_BACKENDS
|
||||
|
||||
# NOTE: relying on tox to verify this works under all the various backends.
|
||||
class Pbkdf2Test(TestCase):
|
||||
"""test pbkdf2() support"""
|
||||
descriptionPrefix = "passlib.crypto.digest.pbkdf2_hmac() <backends: %s>" % ", ".join(PBKDF2_BACKENDS)
|
||||
|
||||
pbkdf2_test_vectors = [
|
||||
# (result, secret, salt, rounds, keylen, digest="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
|
||||
##(
|
||||
##
|
||||
## hb("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, "sha512"
|
||||
),
|
||||
|
||||
#
|
||||
# test vectors from fastpbkdf2 <https://github.com/ctz/fastpbkdf2/blob/master/testdata.py>
|
||||
#
|
||||
(
|
||||
hb('55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc'
|
||||
'49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783'),
|
||||
b'passwd', b'salt', 1, 64, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56'
|
||||
'a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d'),
|
||||
b'Password', b'NaCl', 80000, 64, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b'),
|
||||
b'password', b'salt', 1, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43'),
|
||||
b'password', b'salt', 2, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a'),
|
||||
b'password', b'salt', 4096, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c'
|
||||
'635518c7dac47e9'),
|
||||
b'passwordPASSWORDpassword', b'saltSALTsaltSALTsaltSALTsaltSALTsalt',
|
||||
4096, 40, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('9e83f279c040f2a11aa4a02b24c418f2d3cb39560c9627fa4f47e3bcc2897c3d'),
|
||||
b'', b'salt', 1024, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('ea5808411eb0c7e830deab55096cee582761e22a9bc034e3ece925225b07bf46'),
|
||||
b'password', b'', 1024, 32, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('89b69d0516f829893c696226650a8687'),
|
||||
b'pass\x00word', b'sa\x00lt', 4096, 16, 'sha256',
|
||||
),
|
||||
|
||||
(
|
||||
hb('867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5d513554e1c8cf252'),
|
||||
b'password', b'salt', 1, 32, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('e1d9c16aa681708a45f5c7c4e215ceb66e011a2e9f0040713f18aefdb866d53c'),
|
||||
b'password', b'salt', 2, 32, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('d197b1b33db0143e018b12f3d1d1479e6cdebdcc97c5c0f87f6902e072f457b5'),
|
||||
b'password', b'salt', 4096, 32, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('6e23f27638084b0f7ea1734e0d9841f55dd29ea60a834466f3396bac801fac1eeb'
|
||||
'63802f03a0b4acd7603e3699c8b74437be83ff01ad7f55dac1ef60f4d56480c35e'
|
||||
'e68fd52c6936'),
|
||||
b'passwordPASSWORDpassword', b'saltSALTsaltSALTsaltSALTsaltSALTsalt',
|
||||
1, 72, 'sha512',
|
||||
),
|
||||
|
||||
(
|
||||
hb('0c60c80f961f0e71f3a9b524af6012062fe037a6'),
|
||||
b'password', b'salt', 1, 20, 'sha1',
|
||||
),
|
||||
|
||||
#
|
||||
# custom tests
|
||||
#
|
||||
(
|
||||
hb('e248fb6b13365146f8ac6307cc222812'),
|
||||
b"secret", b"salt", 10, 16, "sha1",
|
||||
),
|
||||
(
|
||||
hb('e248fb6b13365146f8ac6307cc2228127872da6d'),
|
||||
b"secret", b"salt", 10, None, "sha1",
|
||||
),
|
||||
(
|
||||
hb('b1d5485772e6f76d5ebdc11b38d3eff0a5b2bd50dc11f937e86ecacd0cd40d1b'
|
||||
'9113e0734e3b76a3'),
|
||||
b"secret", b"salt", 62, 40, "md5",
|
||||
),
|
||||
(
|
||||
hb('ea014cc01f78d3883cac364bb5d054e2be238fb0b6081795a9d84512126e3129'
|
||||
'062104d2183464c4'),
|
||||
b"secret", b"salt", 62, 40, "md4",
|
||||
),
|
||||
]
|
||||
|
||||
def test_known(self):
|
||||
"""test reference vectors"""
|
||||
for row in self.pbkdf2_test_vectors:
|
||||
correct, secret, salt, rounds, keylen = row[:5]
|
||||
digest = row[5] if len(row) == 6 else "sha1"
|
||||
result = pbkdf2_hmac(digest, secret, salt, rounds, keylen)
|
||||
self.assertEqual(result, correct)
|
||||
|
||||
def test_backends(self):
|
||||
"""verify expected backends are present"""
|
||||
from passlib.crypto.digest import PBKDF2_BACKENDS
|
||||
|
||||
# check for fastpbkdf2
|
||||
try:
|
||||
import fastpbkdf2
|
||||
has_fastpbkdf2 = True
|
||||
except ImportError:
|
||||
has_fastpbkdf2 = False
|
||||
self.assertEqual("fastpbkdf2" in PBKDF2_BACKENDS, has_fastpbkdf2)
|
||||
|
||||
# check for hashlib
|
||||
try:
|
||||
from hashlib import pbkdf2_hmac
|
||||
has_hashlib_ssl = pbkdf2_hmac.__module__ != "hashlib"
|
||||
except ImportError:
|
||||
has_hashlib_ssl = False
|
||||
self.assertEqual("hashlib-ssl" in PBKDF2_BACKENDS, has_hashlib_ssl)
|
||||
|
||||
# check for appropriate builtin
|
||||
from passlib.utils.compat import PY3
|
||||
if PY3:
|
||||
self.assertIn("builtin-from-bytes", PBKDF2_BACKENDS)
|
||||
else:
|
||||
# XXX: only true as long as this is preferred over hexlify
|
||||
self.assertIn("builtin-unpack", PBKDF2_BACKENDS)
|
||||
|
||||
def test_border(self):
|
||||
"""test border cases"""
|
||||
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, digest="sha1"):
|
||||
return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
|
||||
helper()
|
||||
|
||||
# invalid rounds
|
||||
self.assertRaises(ValueError, helper, rounds=-1)
|
||||
self.assertRaises(ValueError, helper, rounds=0)
|
||||
self.assertRaises(TypeError, helper, rounds='x')
|
||||
|
||||
# invalid keylen
|
||||
helper(keylen=1)
|
||||
self.assertRaises(ValueError, helper, keylen=-1)
|
||||
self.assertRaises(ValueError, helper, keylen=0)
|
||||
# NOTE: hashlib actually throws error for keylen>=MAX_SINT32,
|
||||
# but pbkdf2 forbids anything > MAX_UINT32 * digest_size
|
||||
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, digest='foo')
|
||||
self.assertRaises(TypeError, helper, digest=5)
|
||||
|
||||
def test_default_keylen(self):
|
||||
"""test keylen==None"""
|
||||
def helper(secret=b'password', salt=b'salt', rounds=1, keylen=None, digest="sha1"):
|
||||
return pbkdf2_hmac(digest, secret, salt, rounds, keylen)
|
||||
self.assertEqual(len(helper(digest='sha1')), 20)
|
||||
self.assertEqual(len(helper(digest='sha256')), 32)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
634
venv/Lib/site-packages/passlib/tests/test_crypto_scrypt.py
Normal file
634
venv/Lib/site-packages/passlib/tests/test_crypto_scrypt.py
Normal file
@@ -0,0 +1,634 @@
|
||||
"""tests for passlib.utils.scrypt"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
from binascii import hexlify
|
||||
import hashlib
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import struct
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
|
||||
# site
|
||||
# pkg
|
||||
from passlib import exc
|
||||
from passlib.utils import getrandbytes
|
||||
from passlib.utils.compat import PYPY, u, bascii_to_str
|
||||
from passlib.utils.decor import classproperty
|
||||
from passlib.tests.utils import TestCase, skipUnless, TEST_MODE, hb
|
||||
# subject
|
||||
from passlib.crypto import scrypt as scrypt_mod
|
||||
# local
|
||||
__all__ = [
|
||||
"ScryptEngineTest",
|
||||
"BuiltinScryptTest",
|
||||
"FastScryptTest",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# support functions
|
||||
#=============================================================================
|
||||
def hexstr(data):
|
||||
"""return bytes as hex str"""
|
||||
return bascii_to_str(hexlify(data))
|
||||
|
||||
def unpack_uint32_list(data, check_count=None):
|
||||
"""unpack bytes as list of uint32 values"""
|
||||
count = len(data) // 4
|
||||
assert check_count is None or check_count == count
|
||||
return struct.unpack("<%dI" % count, data)
|
||||
|
||||
def seed_bytes(seed, count):
|
||||
"""
|
||||
generate random reference bytes from specified seed.
|
||||
used to generate some predictable test vectors.
|
||||
"""
|
||||
if hasattr(seed, "encode"):
|
||||
seed = seed.encode("ascii")
|
||||
buf = b''
|
||||
i = 0
|
||||
while len(buf) < count:
|
||||
buf += hashlib.sha256(seed + struct.pack("<I", i)).digest()
|
||||
i += 1
|
||||
return buf[:count]
|
||||
|
||||
#=============================================================================
|
||||
# test builtin engine's internals
|
||||
#=============================================================================
|
||||
class ScryptEngineTest(TestCase):
|
||||
descriptionPrefix = "passlib.crypto.scrypt._builtin"
|
||||
|
||||
def test_smix(self):
|
||||
"""smix()"""
|
||||
from passlib.crypto.scrypt._builtin import ScryptEngine
|
||||
rng = self.getRandom()
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test vector from (expired) scrypt rfc draft
|
||||
# (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 9)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
input = hb("""
|
||||
f7 ce 0b 65 3d 2d 72 a4 10 8c f5 ab e9 12 ff dd
|
||||
77 76 16 db bb 27 a7 0e 82 04 f3 ae 2d 0f 6f ad
|
||||
89 f6 8f 48 11 d1 e8 7b cc 3b d7 40 0a 9f fd 29
|
||||
09 4f 01 84 63 95 74 f3 9a e5 a1 31 52 17 bc d7
|
||||
89 49 91 44 72 13 bb 22 6c 25 b5 4d a8 63 70 fb
|
||||
cd 98 43 80 37 46 66 bb 8f fc b5 bf 40 c2 54 b0
|
||||
67 d2 7c 51 ce 4a d5 fe d8 29 c9 0b 50 5a 57 1b
|
||||
7f 4d 1c ad 6a 52 3c da 77 0e 67 bc ea af 7e 89
|
||||
""")
|
||||
|
||||
output = hb("""
|
||||
79 cc c1 93 62 9d eb ca 04 7f 0b 70 60 4b f6 b6
|
||||
2c e3 dd 4a 96 26 e3 55 fa fc 61 98 e6 ea 2b 46
|
||||
d5 84 13 67 3b 99 b0 29 d6 65 c3 57 60 1f b4 26
|
||||
a0 b2 f4 bb a2 00 ee 9f 0a 43 d1 9b 57 1a 9c 71
|
||||
ef 11 42 e6 5d 5a 26 6f dd ca 83 2c e5 9f aa 7c
|
||||
ac 0b 9c f1 be 2b ff ca 30 0d 01 ee 38 76 19 c4
|
||||
ae 12 fd 44 38 f2 03 a0 e4 e1 c4 7e c3 14 86 1f
|
||||
4e 90 87 cb 33 39 6a 68 73 e8 f9 d2 53 9a 4b 8e
|
||||
""")
|
||||
|
||||
# NOTE: p value should be ignored, so testing w/ random inputs.
|
||||
engine = ScryptEngine(n=16, r=1, p=rng.randint(1, 1023))
|
||||
self.assertEqual(engine.smix(input), output)
|
||||
|
||||
def test_bmix(self):
|
||||
"""bmix()"""
|
||||
from passlib.crypto.scrypt._builtin import ScryptEngine
|
||||
rng = self.getRandom()
|
||||
|
||||
# NOTE: bmix() call signature currently takes in list of 32*r uint32 elements,
|
||||
# and writes to target buffer of same size.
|
||||
|
||||
def check_bmix(r, input, output):
|
||||
"""helper to check bmix() output against reference"""
|
||||
# NOTE: * n & p values should be ignored, so testing w/ rng inputs.
|
||||
# * target buffer contents should be ignored, so testing w/ random inputs.
|
||||
engine = ScryptEngine(r=r, n=1 << rng.randint(1, 32), p=rng.randint(1, 1023))
|
||||
target = [rng.randint(0, 1 << 32) for _ in range((2 * r) * 16)]
|
||||
engine.bmix(input, target)
|
||||
self.assertEqual(target, list(output))
|
||||
|
||||
# ScryptEngine special-cases bmix() for r=1.
|
||||
# this removes the special case patching, so we also test original bmix function.
|
||||
if r == 1:
|
||||
del engine.bmix
|
||||
target = [rng.randint(0, 1 << 32) for _ in range((2 * r) * 16)]
|
||||
engine.bmix(input, target)
|
||||
self.assertEqual(target, list(output))
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test vector from (expired) scrypt rfc draft
|
||||
# (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 8)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
# NOTE: this pair corresponds to the first input & output pair
|
||||
# from the test vector in test_smix(), above.
|
||||
# NOTE: original reference lists input & output as two separate 64 byte blocks.
|
||||
# current internal representation used by bmix() uses single 2*r*16 array of uint32,
|
||||
# combining all the B blocks into a single flat array.
|
||||
input = unpack_uint32_list(hb("""
|
||||
f7 ce 0b 65 3d 2d 72 a4 10 8c f5 ab e9 12 ff dd
|
||||
77 76 16 db bb 27 a7 0e 82 04 f3 ae 2d 0f 6f ad
|
||||
89 f6 8f 48 11 d1 e8 7b cc 3b d7 40 0a 9f fd 29
|
||||
09 4f 01 84 63 95 74 f3 9a e5 a1 31 52 17 bc d7
|
||||
|
||||
89 49 91 44 72 13 bb 22 6c 25 b5 4d a8 63 70 fb
|
||||
cd 98 43 80 37 46 66 bb 8f fc b5 bf 40 c2 54 b0
|
||||
67 d2 7c 51 ce 4a d5 fe d8 29 c9 0b 50 5a 57 1b
|
||||
7f 4d 1c ad 6a 52 3c da 77 0e 67 bc ea af 7e 89
|
||||
"""), 32)
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
a4 1f 85 9c 66 08 cc 99 3b 81 ca cb 02 0c ef 05
|
||||
04 4b 21 81 a2 fd 33 7d fd 7b 1c 63 96 68 2f 29
|
||||
b4 39 31 68 e3 c9 e6 bc fe 6b c5 b7 a0 6d 96 ba
|
||||
e4 24 cc 10 2c 91 74 5c 24 ad 67 3d c7 61 8f 81
|
||||
|
||||
20 ed c9 75 32 38 81 a8 05 40 f6 4c 16 2d cd 3c
|
||||
21 07 7c fe 5f 8d 5f e2 b1 a4 16 8f 95 36 78 b7
|
||||
7d 3b 3d 80 3b 60 e4 ab 92 09 96 e5 9b 4d 53 b6
|
||||
5d 2a 22 58 77 d5 ed f5 84 2c b9 f1 4e ef e4 25
|
||||
"""), 32)
|
||||
|
||||
# check_bmix(1, input, output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector for r=2
|
||||
# used to check for bmix() breakage while optimizing implementation.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
r = 2
|
||||
input = unpack_uint32_list(seed_bytes("bmix with r=2", 128 * r))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
ba240854954f4585f3d0573321f10beee96f12acdc1feb498131e40512934fd7
|
||||
43e8139c17d0743c89d09ac8c3582c273c60ab85db63e410d049a9e17a42c6a1
|
||||
|
||||
6c7831b11bf370266afdaff997ae1286920dea1dedf0f4a1795ba710ba9017f1
|
||||
a374400766f13ebd8969362de2d153965e9941bdde0768fa5b53e8522f116ce0
|
||||
|
||||
d14774afb88f46cd919cba4bc64af7fca0ecb8732d1fc2191e0d7d1b6475cb2e
|
||||
e3db789ee478d056c4eb6c6e28b99043602dbb8dfb60c6e048bf90719da8d57d
|
||||
|
||||
3c42250e40ab79a1ada6aae9299b9790f767f54f388d024a1465b30cbbe9eb89
|
||||
002d4f5c215c4259fac4d083bac5fb0b47463747d568f40bb7fa87c42f0a1dc1
|
||||
"""), 32 * r)
|
||||
|
||||
check_bmix(r, input, output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector for r=3
|
||||
# used to check for bmix() breakage while optimizing implementation.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
r = 3
|
||||
input = unpack_uint32_list(seed_bytes("bmix with r=3", 128 * r))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
11ddd8cf60c61f59a6e5b128239bdc77b464101312c88bd1ccf6be6e75461b29
|
||||
7370d4770c904d0b09c402573cf409bf2db47b91ba87d5a3de469df8fb7a003c
|
||||
|
||||
95a66af96dbdd88beddc8df51a2f72a6f588d67e7926e9c2b676c875da13161e
|
||||
b6262adac39e6b3003e9a6fbc8c1a6ecf1e227c03bc0af3e5f8736c339b14f84
|
||||
|
||||
c7ae5b89f5e16d0faf8983551165f4bb712d97e4f81426e6b78eb63892d3ff54
|
||||
80bf406c98e479496d0f76d23d728e67d2a3d2cdbc4a932be6db36dc37c60209
|
||||
|
||||
a5ca76ca2d2979f995f73fe8182eefa1ce0ba0d4fc27d5b827cb8e67edd6552f
|
||||
00a5b3ab6b371bd985a158e728011314eb77f32ade619b3162d7b5078a19886c
|
||||
|
||||
06f12bc8ae8afa46489e5b0239954d5216967c928982984101e4a88bae1f60ae
|
||||
3f8a456e169a8a1c7450e7955b8a13a202382ae19d41ce8ef8b6a15eeef569a7
|
||||
|
||||
20f54c48e44cb5543dda032c1a50d5ddf2919030624978704eb8db0290052a1f
|
||||
5d88989b0ef931b6befcc09e9d5162320e71e80b89862de7e2f0b6c67229b93f
|
||||
"""), 32 * r)
|
||||
|
||||
check_bmix(r, input, output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector for r=4
|
||||
# used to check for bmix() breakage while optimizing implementation.
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
r = 4
|
||||
input = unpack_uint32_list(seed_bytes("bmix with r=4", 128 * r))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
803fcf7362702f30ef43250f20bc6b1b8925bf5c4a0f5a14bbfd90edce545997
|
||||
3047bd81655f72588ca93f5c2f4128adaea805e0705a35e14417101fdb1c498c
|
||||
|
||||
33bec6f4e5950d66098da8469f3fe633f9a17617c0ea21275185697c0e4608f7
|
||||
e6b38b7ec71704a810424637e2c296ca30d9cbf8172a71a266e0393deccf98eb
|
||||
|
||||
abc430d5f144eb0805308c38522f2973b7b6a48498851e4c762874497da76b88
|
||||
b769b471fbfc144c0e8e859b2b3f5a11f51604d268c8fd28db55dff79832741a
|
||||
|
||||
1ac0dfdaff10f0ada0d93d3b1f13062e4107c640c51df05f4110bdda15f51b53
|
||||
3a75bfe56489a6d8463440c78fb8c0794135e38591bdc5fa6cec96a124178a4a
|
||||
|
||||
d1a976e985bfe13d2b4af51bd0fc36dd4cfc3af08efe033b2323a235205dc43d
|
||||
e57778a492153f9527338b3f6f5493a03d8015cd69737ee5096ad4cbe660b10f
|
||||
|
||||
b75b1595ddc96e3748f5c9f61fba1ef1f0c51b6ceef8bbfcc34b46088652e6f7
|
||||
edab61521cbad6e69b77be30c9c97ea04a4af359dafc205c7878cc9a6c5d122f
|
||||
|
||||
8d77f3cbe65ab14c3c491ef94ecb3f5d2c2dd13027ea4c3606262bb3c9ce46e7
|
||||
dc424729dc75f6e8f06096c0ad8ad4d549c42f0cad9b33cb95d10fb3cadba27c
|
||||
|
||||
5f4bf0c1ac677c23ba23b64f56afc3546e62d96f96b58d7afc5029f8168cbab4
|
||||
533fd29fc83c8d2a32b81923992e4938281334e0c3694f0ee56f8ff7df7dc4ae
|
||||
"""), 32 * r)
|
||||
|
||||
check_bmix(r, input, output)
|
||||
|
||||
def test_salsa(self):
|
||||
"""salsa20()"""
|
||||
from passlib.crypto.scrypt._builtin import salsa20
|
||||
|
||||
# NOTE: salsa2() currently operates on lists of 16 uint32 elements,
|
||||
# which is what unpack_uint32_list(hb(() is for...
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test vector from (expired) scrypt rfc draft
|
||||
# (https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 7)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
# NOTE: this pair corresponds to the first input & output pair
|
||||
# from the test vector in test_bmix(), above.
|
||||
|
||||
input = unpack_uint32_list(hb("""
|
||||
7e 87 9a 21 4f 3e c9 86 7c a9 40 e6 41 71 8f 26
|
||||
ba ee 55 5b 8c 61 c1 b5 0d f8 46 11 6d cd 3b 1d
|
||||
ee 24 f3 19 df 9b 3d 85 14 12 1e 4b 5a c5 aa 32
|
||||
76 02 1d 29 09 c7 48 29 ed eb c6 8d b8 b8 c2 5e
|
||||
"""))
|
||||
|
||||
output = unpack_uint32_list(hb("""
|
||||
a4 1f 85 9c 66 08 cc 99 3b 81 ca cb 02 0c ef 05
|
||||
04 4b 21 81 a2 fd 33 7d fd 7b 1c 63 96 68 2f 29
|
||||
b4 39 31 68 e3 c9 e6 bc fe 6b c5 b7 a0 6d 96 ba
|
||||
e4 24 cc 10 2c 91 74 5c 24 ad 67 3d c7 61 8f 81
|
||||
"""))
|
||||
self.assertEqual(salsa20(input), output)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# custom test vector,
|
||||
# used to check for salsa20() breakage while optimizing _gen_files output.
|
||||
#-----------------------------------------------------------------------
|
||||
input = list(range(16))
|
||||
output = unpack_uint32_list(hb("""
|
||||
f518dd4fb98883e0a87954c05cab867083bb8808552810752285a05822f56c16
|
||||
9d4a2a0fd2142523d758c60b36411b682d53860514b871d27659042a5afa475d
|
||||
"""))
|
||||
self.assertEqual(salsa20(input), output)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
||||
|
||||
#=============================================================================
|
||||
# test scrypt
|
||||
#=============================================================================
|
||||
class _CommonScryptTest(TestCase):
|
||||
"""
|
||||
base class for testing various scrypt backends against same set of reference vectors.
|
||||
"""
|
||||
#=============================================================================
|
||||
# class attrs
|
||||
#=============================================================================
|
||||
|
||||
@classproperty
|
||||
def descriptionPrefix(cls):
|
||||
return "passlib.utils.scrypt.scrypt() <%s backend>" % cls.backend
|
||||
backend = None
|
||||
|
||||
#=============================================================================
|
||||
# setup
|
||||
#=============================================================================
|
||||
def setUp(self):
|
||||
assert self.backend
|
||||
scrypt_mod._set_backend(self.backend)
|
||||
super(_CommonScryptTest, self).setUp()
|
||||
|
||||
#=============================================================================
|
||||
# reference vectors
|
||||
#=============================================================================
|
||||
|
||||
reference_vectors = [
|
||||
# entry format: (secret, salt, n, r, p, keylen, result)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
# test vectors from scrypt whitepaper --
|
||||
# http://www.tarsnap.com/scrypt/scrypt.pdf, appendix b
|
||||
#
|
||||
# also present in (expired) scrypt rfc draft --
|
||||
# https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01, section 11
|
||||
#------------------------------------------------------------------------
|
||||
("", "", 16, 1, 1, 64, hb("""
|
||||
77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97
|
||||
f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42
|
||||
fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17
|
||||
e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06
|
||||
""")),
|
||||
|
||||
("password", "NaCl", 1024, 8, 16, 64, hb("""
|
||||
fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe
|
||||
7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62
|
||||
2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da
|
||||
c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40
|
||||
""")),
|
||||
|
||||
# NOTE: the following are skipped for all backends unless TEST_MODE="full"
|
||||
|
||||
("pleaseletmein", "SodiumChloride", 16384, 8, 1, 64, hb("""
|
||||
70 23 bd cb 3a fd 73 48 46 1c 06 cd 81 fd 38 eb
|
||||
fd a8 fb ba 90 4f 8e 3e a9 b5 43 f6 54 5d a1 f2
|
||||
d5 43 29 55 61 3f 0f cf 62 d4 97 05 24 2a 9a f9
|
||||
e6 1e 85 dc 0d 65 1e 40 df cf 01 7b 45 57 58 87
|
||||
""")),
|
||||
|
||||
# NOTE: the following are always skipped for the builtin backend,
|
||||
# (just takes too long to be worth it)
|
||||
|
||||
("pleaseletmein", "SodiumChloride", 1048576, 8, 1, 64, hb("""
|
||||
21 01 cb 9b 6a 51 1a ae ad db be 09 cf 70 f8 81
|
||||
ec 56 8d 57 4a 2f fd 4d ab e5 ee 98 20 ad aa 47
|
||||
8e 56 fd 8f 4b a5 d0 9f fa 1c 6d 92 7c 40 f4 c3
|
||||
37 30 40 49 e8 a9 52 fb cb f4 5c 6f a7 7a 41 a4
|
||||
""")),
|
||||
]
|
||||
|
||||
def test_reference_vectors(self):
|
||||
"""reference vectors"""
|
||||
for secret, salt, n, r, p, keylen, result in self.reference_vectors:
|
||||
if n >= 1024 and TEST_MODE(max="default"):
|
||||
# skip large values unless we're running full test suite
|
||||
continue
|
||||
if n > 16384 and self.backend == "builtin":
|
||||
# skip largest vector for builtin, takes WAAY too long
|
||||
# (46s under pypy, ~5m under cpython)
|
||||
continue
|
||||
log.debug("scrypt reference vector: %r %r n=%r r=%r p=%r", secret, salt, n, r, p)
|
||||
self.assertEqual(scrypt_mod.scrypt(secret, salt, n, r, p, keylen), result)
|
||||
|
||||
#=============================================================================
|
||||
# fuzz testing
|
||||
#=============================================================================
|
||||
|
||||
_already_tested_others = None
|
||||
|
||||
def test_other_backends(self):
|
||||
"""compare output to other backends"""
|
||||
# only run once, since test is symetric.
|
||||
# maybe this means it should go somewhere else?
|
||||
if self._already_tested_others:
|
||||
raise self.skipTest("already run under %r backend test" % self._already_tested_others)
|
||||
self._already_tested_others = self.backend
|
||||
rng = self.getRandom()
|
||||
|
||||
# get available backends
|
||||
orig = scrypt_mod.backend
|
||||
available = set(name for name in scrypt_mod.backend_values
|
||||
if scrypt_mod._has_backend(name))
|
||||
scrypt_mod._set_backend(orig)
|
||||
available.discard(self.backend)
|
||||
if not available:
|
||||
raise self.skipTest("no other backends found")
|
||||
|
||||
warnings.filterwarnings("ignore", "(?i)using builtin scrypt backend",
|
||||
category=exc.PasslibSecurityWarning)
|
||||
|
||||
# generate some random options, and cross-check output
|
||||
for _ in range(10):
|
||||
# NOTE: keeping values low due to builtin test
|
||||
secret = getrandbytes(rng, rng.randint(0, 64))
|
||||
salt = getrandbytes(rng, rng.randint(0, 64))
|
||||
n = 1 << rng.randint(1, 10)
|
||||
r = rng.randint(1, 8)
|
||||
p = rng.randint(1, 3)
|
||||
ks = rng.randint(1, 64)
|
||||
previous = None
|
||||
backends = set()
|
||||
for name in available:
|
||||
scrypt_mod._set_backend(name)
|
||||
self.assertNotIn(scrypt_mod._scrypt, backends)
|
||||
backends.add(scrypt_mod._scrypt)
|
||||
result = hexstr(scrypt_mod.scrypt(secret, salt, n, r, p, ks))
|
||||
self.assertEqual(len(result), 2*ks)
|
||||
if previous is not None:
|
||||
self.assertEqual(result, previous,
|
||||
msg="%r output differs from others %r: %r" %
|
||||
(name, available, [secret, salt, n, r, p, ks]))
|
||||
|
||||
#=============================================================================
|
||||
# test input types
|
||||
#=============================================================================
|
||||
def test_backend(self):
|
||||
"""backend management"""
|
||||
# clobber backend
|
||||
scrypt_mod.backend = None
|
||||
scrypt_mod._scrypt = None
|
||||
self.assertRaises(TypeError, scrypt_mod.scrypt, 's', 's', 2, 2, 2, 16)
|
||||
|
||||
# reload backend
|
||||
scrypt_mod._set_backend(self.backend)
|
||||
self.assertEqual(scrypt_mod.backend, self.backend)
|
||||
scrypt_mod.scrypt('s', 's', 2, 2, 2, 16)
|
||||
|
||||
# throw error for unknown backend
|
||||
self.assertRaises(ValueError, scrypt_mod._set_backend, 'xxx')
|
||||
self.assertEqual(scrypt_mod.backend, self.backend)
|
||||
|
||||
def test_secret_param(self):
|
||||
"""'secret' parameter"""
|
||||
|
||||
def run_scrypt(secret):
|
||||
return hexstr(scrypt_mod.scrypt(secret, "salt", 2, 2, 2, 16))
|
||||
|
||||
# unicode
|
||||
TEXT = u("abc\u00defg")
|
||||
self.assertEqual(run_scrypt(TEXT), '05717106997bfe0da42cf4779a2f8bd8')
|
||||
|
||||
# utf8 bytes
|
||||
TEXT_UTF8 = b'abc\xc3\x9efg'
|
||||
self.assertEqual(run_scrypt(TEXT_UTF8), '05717106997bfe0da42cf4779a2f8bd8')
|
||||
|
||||
# latin1 bytes
|
||||
TEXT_LATIN1 = b'abc\xdefg'
|
||||
self.assertEqual(run_scrypt(TEXT_LATIN1), '770825d10eeaaeaf98e8a3c40f9f441d')
|
||||
|
||||
# accept empty string
|
||||
self.assertEqual(run_scrypt(""), 'ca1399e5fae5d3b9578dcd2b1faff6e2')
|
||||
|
||||
# reject other types
|
||||
self.assertRaises(TypeError, run_scrypt, None)
|
||||
self.assertRaises(TypeError, run_scrypt, 1)
|
||||
|
||||
def test_salt_param(self):
|
||||
"""'salt' parameter"""
|
||||
|
||||
def run_scrypt(salt):
|
||||
return hexstr(scrypt_mod.scrypt("secret", salt, 2, 2, 2, 16))
|
||||
|
||||
# unicode
|
||||
TEXT = u("abc\u00defg")
|
||||
self.assertEqual(run_scrypt(TEXT), 'a748ec0f4613929e9e5f03d1ab741d88')
|
||||
|
||||
# utf8 bytes
|
||||
TEXT_UTF8 = b'abc\xc3\x9efg'
|
||||
self.assertEqual(run_scrypt(TEXT_UTF8), 'a748ec0f4613929e9e5f03d1ab741d88')
|
||||
|
||||
# latin1 bytes
|
||||
TEXT_LATIN1 = b'abc\xdefg'
|
||||
self.assertEqual(run_scrypt(TEXT_LATIN1), '91d056fb76fb6e9a7d1cdfffc0a16cd1')
|
||||
|
||||
# reject other types
|
||||
self.assertRaises(TypeError, run_scrypt, None)
|
||||
self.assertRaises(TypeError, run_scrypt, 1)
|
||||
|
||||
def test_n_param(self):
|
||||
"""'n' (rounds) parameter"""
|
||||
|
||||
def run_scrypt(n):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", n, 2, 2, 16))
|
||||
|
||||
# must be > 1, and a power of 2
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertRaises(ValueError, run_scrypt, 1)
|
||||
self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
|
||||
self.assertRaises(ValueError, run_scrypt, 3)
|
||||
self.assertRaises(ValueError, run_scrypt, 15)
|
||||
self.assertEqual(run_scrypt(16), '0272b8fc72bc54b1159340ed99425233')
|
||||
|
||||
def test_r_param(self):
|
||||
"""'r' (block size) parameter"""
|
||||
def run_scrypt(r, n=2, p=2):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", n, r, p, 16))
|
||||
|
||||
# must be > 1
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertEqual(run_scrypt(1), '3d630447d9f065363b8a79b0b3670251')
|
||||
self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
|
||||
self.assertEqual(run_scrypt(5), '114f05e985a903c27237b5578e763736')
|
||||
|
||||
# reject r*p >= 2**30
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30), p=1)
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30) / 2, p=2)
|
||||
|
||||
def test_p_param(self):
|
||||
"""'p' (parallelism) parameter"""
|
||||
def run_scrypt(p, n=2, r=2):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", n, r, p, 16))
|
||||
|
||||
# must be > 1
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertEqual(run_scrypt(1), 'f2960ea8b7d48231fcec1b89b784a6fa')
|
||||
self.assertEqual(run_scrypt(2), 'dacf2bca255e2870e6636fa8c8957a66')
|
||||
self.assertEqual(run_scrypt(5), '848a0eeb2b3543e7f543844d6ca79782')
|
||||
|
||||
# reject r*p >= 2**30
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30), r=1)
|
||||
self.assertRaises(ValueError, run_scrypt, (1<<30) / 2, r=2)
|
||||
|
||||
def test_keylen_param(self):
|
||||
"""'keylen' parameter"""
|
||||
rng = self.getRandom()
|
||||
|
||||
def run_scrypt(keylen):
|
||||
return hexstr(scrypt_mod.scrypt("secret", "salt", 2, 2, 2, keylen))
|
||||
|
||||
# must be > 0
|
||||
self.assertRaises(ValueError, run_scrypt, -1)
|
||||
self.assertRaises(ValueError, run_scrypt, 0)
|
||||
self.assertEqual(run_scrypt(1), 'da')
|
||||
|
||||
# pick random value
|
||||
ksize = rng.randint(1, 1 << 10)
|
||||
self.assertEqual(len(run_scrypt(ksize)), 2*ksize) # 2 hex chars per output
|
||||
|
||||
# one more than upper bound
|
||||
self.assertRaises(ValueError, run_scrypt, ((2**32) - 1) * 32 + 1)
|
||||
|
||||
#=============================================================================
|
||||
# eoc
|
||||
#=============================================================================
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# check what backends 'should' be available
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
def _can_import_cffi_scrypt():
|
||||
try:
|
||||
import scrypt
|
||||
except ImportError as err:
|
||||
if "scrypt" in str(err):
|
||||
return False
|
||||
raise
|
||||
return True
|
||||
|
||||
has_cffi_scrypt = _can_import_cffi_scrypt()
|
||||
|
||||
|
||||
def _can_import_stdlib_scrypt():
|
||||
try:
|
||||
from hashlib import scrypt
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
has_stdlib_scrypt = _can_import_stdlib_scrypt()
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# test individual backends
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
# NOTE: builtin version runs VERY slow (except under PyPy, where it's only 11x slower),
|
||||
# so skipping under quick test mode.
|
||||
@skipUnless(PYPY or TEST_MODE(min="default"), "skipped under current test mode")
|
||||
class BuiltinScryptTest(_CommonScryptTest):
|
||||
backend = "builtin"
|
||||
|
||||
def setUp(self):
|
||||
super(BuiltinScryptTest, self).setUp()
|
||||
warnings.filterwarnings("ignore", "(?i)using builtin scrypt backend",
|
||||
category=exc.PasslibSecurityWarning)
|
||||
|
||||
def test_missing_backend(self):
|
||||
"""backend management -- missing backend"""
|
||||
if has_stdlib_scrypt or has_cffi_scrypt:
|
||||
raise self.skipTest("non-builtin backend is present")
|
||||
self.assertRaises(exc.MissingBackendError, scrypt_mod._set_backend, 'scrypt')
|
||||
|
||||
|
||||
@skipUnless(has_cffi_scrypt, "'scrypt' package not found")
|
||||
class ScryptPackageTest(_CommonScryptTest):
|
||||
backend = "scrypt"
|
||||
|
||||
def test_default_backend(self):
|
||||
"""backend management -- default backend"""
|
||||
if has_stdlib_scrypt:
|
||||
raise self.skipTest("higher priority backend present")
|
||||
scrypt_mod._set_backend("default")
|
||||
self.assertEqual(scrypt_mod.backend, "scrypt")
|
||||
|
||||
|
||||
@skipUnless(has_stdlib_scrypt, "'hashlib.scrypt()' not found")
|
||||
class StdlibScryptTest(_CommonScryptTest):
|
||||
backend = "stdlib"
|
||||
|
||||
def test_default_backend(self):
|
||||
"""backend management -- default backend"""
|
||||
scrypt_mod._set_backend("default")
|
||||
self.assertEqual(scrypt_mod.backend, "stdlib")
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
1080
venv/Lib/site-packages/passlib/tests/test_ext_django.py
Normal file
1080
venv/Lib/site-packages/passlib/tests/test_ext_django.py
Normal file
File diff suppressed because it is too large
Load Diff
250
venv/Lib/site-packages/passlib/tests/test_ext_django_source.py
Normal file
250
venv/Lib/site-packages/passlib/tests/test_ext_django_source.py
Normal file
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
test passlib.ext.django against django source tests
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import absolute_import, division, print_function
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.utils.compat import suppress_cause
|
||||
from passlib.ext.django.utils import DJANGO_VERSION, DjangoTranslator, _PasslibHasherWrapper
|
||||
# tests
|
||||
from passlib.tests.utils import TestCase, TEST_MODE
|
||||
from .test_ext_django import (
|
||||
has_min_django, stock_config, _ExtensionSupport,
|
||||
)
|
||||
if has_min_django:
|
||||
from .test_ext_django import settings
|
||||
# local
|
||||
__all__ = [
|
||||
"HashersTest",
|
||||
]
|
||||
#=============================================================================
|
||||
# HashersTest --
|
||||
# hack up the some of the real django tests to run w/ extension loaded,
|
||||
# to ensure we mimic their behavior.
|
||||
# however, the django tests were moved out of the package, and into a source-only location
|
||||
# as of django 1.7. so we disable tests from that point on unless test-runner specifies
|
||||
#=============================================================================
|
||||
|
||||
#: ref to django unittest root module (if found)
|
||||
test_hashers_mod = None
|
||||
|
||||
#: message about why test module isn't present (if not found)
|
||||
hashers_skip_msg = None
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# try to load django's tests/auth_tests/test_hasher.py module,
|
||||
# or note why we failed.
|
||||
#----------------------------------------------------------------------
|
||||
if TEST_MODE(max="quick"):
|
||||
hashers_skip_msg = "requires >= 'default' test mode"
|
||||
|
||||
elif has_min_django:
|
||||
import os
|
||||
import sys
|
||||
source_path = os.environ.get("PASSLIB_TESTS_DJANGO_SOURCE_PATH")
|
||||
|
||||
if source_path:
|
||||
if not os.path.exists(source_path):
|
||||
raise EnvironmentError("django source path not found: %r" % source_path)
|
||||
if not all(os.path.exists(os.path.join(source_path, name))
|
||||
for name in ["django", "tests"]):
|
||||
raise EnvironmentError("invalid django source path: %r" % source_path)
|
||||
log.info("using django tests from source path: %r", source_path)
|
||||
tests_path = os.path.join(source_path, "tests")
|
||||
sys.path.insert(0, tests_path)
|
||||
try:
|
||||
from auth_tests import test_hashers as test_hashers_mod
|
||||
except ImportError as err:
|
||||
raise suppress_cause(
|
||||
EnvironmentError("error trying to import django tests "
|
||||
"from source path (%r): %r" %
|
||||
(source_path, err)))
|
||||
finally:
|
||||
sys.path.remove(tests_path)
|
||||
|
||||
else:
|
||||
hashers_skip_msg = "requires PASSLIB_TESTS_DJANGO_SOURCE_PATH to be set"
|
||||
|
||||
if TEST_MODE("full"):
|
||||
# print warning so user knows what's happening
|
||||
sys.stderr.write("\nWARNING: $PASSLIB_TESTS_DJANGO_SOURCE_PATH is not set; "
|
||||
"can't run Django's own unittests against passlib.ext.django\n")
|
||||
|
||||
elif DJANGO_VERSION:
|
||||
hashers_skip_msg = "django version too old"
|
||||
|
||||
else:
|
||||
hashers_skip_msg = "django not installed"
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# if found module, create wrapper to run django's own tests,
|
||||
# but with passlib monkeypatched in.
|
||||
#----------------------------------------------------------------------
|
||||
if test_hashers_mod:
|
||||
from django.core.signals import setting_changed
|
||||
from django.dispatch import receiver
|
||||
from django.utils.module_loading import import_string
|
||||
from passlib.utils.compat import get_unbound_method_function
|
||||
|
||||
class HashersTest(test_hashers_mod.TestUtilsHashPass, _ExtensionSupport):
|
||||
"""
|
||||
Run django's hasher unittests against passlib's extension
|
||||
and workalike implementations
|
||||
"""
|
||||
|
||||
#==================================================================
|
||||
# helpers
|
||||
#==================================================================
|
||||
|
||||
# port patchAttr() helper method from passlib.tests.utils.TestCase
|
||||
patchAttr = get_unbound_method_function(TestCase.patchAttr)
|
||||
|
||||
#==================================================================
|
||||
# custom setup
|
||||
#==================================================================
|
||||
def setUp(self):
|
||||
#---------------------------------------------------------
|
||||
# install passlib.ext.django adapter, and get context
|
||||
#---------------------------------------------------------
|
||||
self.load_extension(PASSLIB_CONTEXT=stock_config, check=False)
|
||||
from passlib.ext.django.models import adapter
|
||||
context = adapter.context
|
||||
|
||||
#---------------------------------------------------------
|
||||
# patch tests module to use our versions of patched funcs
|
||||
# (which should be installed in hashers module)
|
||||
#---------------------------------------------------------
|
||||
from django.contrib.auth import hashers
|
||||
for attr in ["make_password",
|
||||
"check_password",
|
||||
"identify_hasher",
|
||||
"is_password_usable",
|
||||
"get_hasher"]:
|
||||
self.patchAttr(test_hashers_mod, attr, getattr(hashers, attr))
|
||||
|
||||
#---------------------------------------------------------
|
||||
# django tests expect empty django_des_crypt salt field
|
||||
#---------------------------------------------------------
|
||||
from passlib.hash import django_des_crypt
|
||||
self.patchAttr(django_des_crypt, "use_duplicate_salt", False)
|
||||
|
||||
#---------------------------------------------------------
|
||||
# install receiver to update scheme list if test changes settings
|
||||
#---------------------------------------------------------
|
||||
django_to_passlib_name = DjangoTranslator().django_to_passlib_name
|
||||
|
||||
@receiver(setting_changed, weak=False)
|
||||
def update_schemes(**kwds):
|
||||
if kwds and kwds['setting'] != 'PASSWORD_HASHERS':
|
||||
return
|
||||
assert context is adapter.context
|
||||
schemes = [
|
||||
django_to_passlib_name(import_string(hash_path)())
|
||||
for hash_path in settings.PASSWORD_HASHERS
|
||||
]
|
||||
# workaround for a few tests that only specify hex_md5,
|
||||
# but test for django_salted_md5 format.
|
||||
if "hex_md5" in schemes and "django_salted_md5" not in schemes:
|
||||
schemes.append("django_salted_md5")
|
||||
schemes.append("django_disabled")
|
||||
context.update(schemes=schemes, deprecated="auto")
|
||||
adapter.reset_hashers()
|
||||
|
||||
self.addCleanup(setting_changed.disconnect, update_schemes)
|
||||
|
||||
update_schemes()
|
||||
|
||||
#---------------------------------------------------------
|
||||
# need password_context to keep up to date with django_hasher.iterations,
|
||||
# which is frequently patched by django tests.
|
||||
#
|
||||
# HACK: to fix this, inserting wrapper around a bunch of context
|
||||
# methods so that any time adapter calls them,
|
||||
# attrs are resynced first.
|
||||
#---------------------------------------------------------
|
||||
|
||||
def update_rounds():
|
||||
"""
|
||||
sync django hasher config -> passlib hashers
|
||||
"""
|
||||
for handler in context.schemes(resolve=True):
|
||||
if 'rounds' not in handler.setting_kwds:
|
||||
continue
|
||||
hasher = adapter.passlib_to_django(handler)
|
||||
if isinstance(hasher, _PasslibHasherWrapper):
|
||||
continue
|
||||
rounds = getattr(hasher, "rounds", None) or \
|
||||
getattr(hasher, "iterations", None)
|
||||
if rounds is None:
|
||||
continue
|
||||
# XXX: this doesn't modify the context, which would
|
||||
# cause other weirdness (since it would replace handler factories completely,
|
||||
# instead of just updating their state)
|
||||
handler.min_desired_rounds = handler.max_desired_rounds = handler.default_rounds = rounds
|
||||
|
||||
_in_update = [False]
|
||||
|
||||
def update_wrapper(wrapped, *args, **kwds):
|
||||
"""
|
||||
wrapper around arbitrary func, that first triggers sync
|
||||
"""
|
||||
if not _in_update[0]:
|
||||
_in_update[0] = True
|
||||
try:
|
||||
update_rounds()
|
||||
finally:
|
||||
_in_update[0] = False
|
||||
return wrapped(*args, **kwds)
|
||||
|
||||
# sync before any context call
|
||||
for attr in ["schemes", "handler", "default_scheme", "hash",
|
||||
"verify", "needs_update", "verify_and_update"]:
|
||||
self.patchAttr(context, attr, update_wrapper, wrap=True)
|
||||
|
||||
# sync whenever adapter tries to resolve passlib hasher
|
||||
self.patchAttr(adapter, "django_to_passlib", update_wrapper, wrap=True)
|
||||
|
||||
def tearDown(self):
|
||||
# NOTE: could rely on addCleanup() instead, but need py26 compat
|
||||
self.unload_extension()
|
||||
super(HashersTest, self).tearDown()
|
||||
|
||||
#==================================================================
|
||||
# skip a few methods that can't be replicated properly
|
||||
# *want to minimize these as much as possible*
|
||||
#==================================================================
|
||||
|
||||
_OMIT = lambda self: self.skipTest("omitted by passlib")
|
||||
|
||||
# XXX: this test registers two classes w/ same algorithm id,
|
||||
# something we don't support -- how does django sanely handle
|
||||
# that anyways? get_hashers_by_algorithm() should throw KeyError, right?
|
||||
test_pbkdf2_upgrade_new_hasher = _OMIT
|
||||
|
||||
# TODO: support wrapping django's harden-runtime feature?
|
||||
# would help pass their tests.
|
||||
test_check_password_calls_harden_runtime = _OMIT
|
||||
test_bcrypt_harden_runtime = _OMIT
|
||||
test_pbkdf2_harden_runtime = _OMIT
|
||||
|
||||
#==================================================================
|
||||
# eoc
|
||||
#==================================================================
|
||||
|
||||
else:
|
||||
# otherwise leave a stub so test log tells why test was skipped.
|
||||
|
||||
class HashersTest(TestCase):
|
||||
|
||||
def test_external_django_hasher_tests(self):
|
||||
"""external django hasher tests"""
|
||||
raise self.skipTest(hashers_skip_msg)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
1819
venv/Lib/site-packages/passlib/tests/test_handlers.py
Normal file
1819
venv/Lib/site-packages/passlib/tests/test_handlers.py
Normal file
File diff suppressed because it is too large
Load Diff
507
venv/Lib/site-packages/passlib/tests/test_handlers_argon2.py
Normal file
507
venv/Lib/site-packages/passlib/tests/test_handlers_argon2.py
Normal file
@@ -0,0 +1,507 @@
|
||||
"""passlib.tests.test_handlers_argon2 - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
import re
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.utils.compat import unicode
|
||||
from passlib.tests.utils import HandlerCase, TEST_MODE
|
||||
from passlib.tests.test_handlers import UPASS_TABLE, PASS_TABLE_UTF8
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# a bunch of tests lifted nearlky verbatim from official argon2 UTs...
|
||||
# https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c
|
||||
#=============================================================================
|
||||
def hashtest(version, t, logM, p, secret, salt, hex_digest, hash):
|
||||
return dict(version=version, rounds=t, logM=logM, memory_cost=1<<logM, parallelism=p,
|
||||
secret=secret, salt=salt, hex_digest=hex_digest, hash=hash)
|
||||
|
||||
# version 1.3 "I" tests
|
||||
version = 0x10
|
||||
reference_data = [
|
||||
hashtest(version, 2, 16, 1, "password", "somesalt",
|
||||
"f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694",
|
||||
"$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"),
|
||||
hashtest(version, 2, 20, 1, "password", "somesalt",
|
||||
"9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9",
|
||||
"$argon2i$m=1048576,t=2,p=1$c29tZXNhbHQ"
|
||||
"$lpDsVdKNPtMlYvLnPqYrArAYdXZDoq5ueVKEWd6BBuk"),
|
||||
hashtest(version, 2, 18, 1, "password", "somesalt",
|
||||
"3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467",
|
||||
"$argon2i$m=262144,t=2,p=1$c29tZXNhbHQ"
|
||||
"$Pmiaqj0op3zyvHKlGsUxZnYXURgvHuKS4/Z3p9pMJGc"),
|
||||
hashtest(version, 2, 8, 1, "password", "somesalt",
|
||||
"fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06",
|
||||
"$argon2i$m=256,t=2,p=1$c29tZXNhbHQ"
|
||||
"$/U3YPXYsSb3q9XxHvc0MLxur+GP960kN9j7emXX8zwY"),
|
||||
hashtest(version, 2, 8, 2, "password", "somesalt",
|
||||
"b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb",
|
||||
"$argon2i$m=256,t=2,p=2$c29tZXNhbHQ"
|
||||
"$tsEVYKap1h6scGt5ovl9aLRGOqOth+AMB+KwHpDFZPs"),
|
||||
hashtest(version, 1, 16, 1, "password", "somesalt",
|
||||
"81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2",
|
||||
"$argon2i$m=65536,t=1,p=1$c29tZXNhbHQ"
|
||||
"$gWMFUrjzsfSM2xmSxMZ4ZD1JCytetP9sSzQ4tWIXJLI"),
|
||||
hashtest(version, 4, 16, 1, "password", "somesalt",
|
||||
"f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b",
|
||||
"$argon2i$m=65536,t=4,p=1$c29tZXNhbHQ"
|
||||
"$8hLwFhXm6110c03D70Ct4tUdBSRo2MaUQKOh8sHChHs"),
|
||||
hashtest(version, 2, 16, 1, "differentpassword", "somesalt",
|
||||
"e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3",
|
||||
"$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$6ckCB0tnVFMaOgvlGeW69ASzDOabPwGsO/ISKZYBCaM"),
|
||||
hashtest(version, 2, 16, 1, "password", "diffsalt",
|
||||
"79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497",
|
||||
"$argon2i$m=65536,t=2,p=1$ZGlmZnNhbHQ"
|
||||
"$eaEDuQ/orvhXDLMfyLIiWXeJFvgza3vaw4kladTxxJc"),
|
||||
]
|
||||
|
||||
# version 1.9 "I" tests
|
||||
version = 0x13
|
||||
reference_data.extend([
|
||||
hashtest(version, 2, 16, 1, "password", "somesalt",
|
||||
"c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0",
|
||||
"$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$wWKIMhR9lyDFvRz9YTZweHKfbftvj+qf+YFY4NeBbtA"),
|
||||
hashtest(version, 2, 20, 1, "password", "somesalt",
|
||||
"d1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41",
|
||||
"$argon2i$v=19$m=1048576,t=2,p=1$c29tZXNhbHQ"
|
||||
"$0Vh6ygkiw7XWqD7asxvuPE667zQu1hJ6VdGbI1GtH0E"),
|
||||
hashtest(version, 2, 18, 1, "password", "somesalt",
|
||||
"296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb",
|
||||
"$argon2i$v=19$m=262144,t=2,p=1$c29tZXNhbHQ"
|
||||
"$KW266AuAfNzqrUSudBtQbxTbCVkmexg7EY+bJCKbx8s"),
|
||||
hashtest(version, 2, 8, 1, "password", "somesalt",
|
||||
"89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f",
|
||||
"$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ"
|
||||
"$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8"),
|
||||
hashtest(version, 2, 8, 2, "password", "somesalt",
|
||||
"4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61",
|
||||
"$argon2i$v=19$m=256,t=2,p=2$c29tZXNhbHQ"
|
||||
"$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E"),
|
||||
hashtest(version, 1, 16, 1, "password", "somesalt",
|
||||
"d168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf",
|
||||
"$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQ"
|
||||
"$0WgHXE2YXhPr6uVgz4uUw7XYoWxRkWtvSsLaOsEbvs8"),
|
||||
hashtest(version, 4, 16, 1, "password", "somesalt",
|
||||
"aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b",
|
||||
"$argon2i$v=19$m=65536,t=4,p=1$c29tZXNhbHQ"
|
||||
"$qqlT1YrzcGzj3xrv1KZKhOMdf1QXUjHxKFJZ+IF0zls"),
|
||||
hashtest(version, 2, 16, 1, "differentpassword", "somesalt",
|
||||
"14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee",
|
||||
"$argon2i$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$FK6NoBr+qHAMI1jc73xTWNkCEoK9iGY6RWL1n7dNIu4"),
|
||||
hashtest(version, 2, 16, 1, "password", "diffsalt",
|
||||
"b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271",
|
||||
"$argon2i$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ"
|
||||
"$sDV8zPvvkfOGCw26RHsjSMvv7K2vmQq/6cxAcmxSEnE"),
|
||||
])
|
||||
|
||||
# version 1.9 "ID" tests
|
||||
version = 0x13
|
||||
reference_data.extend([
|
||||
hashtest(version, 2, 16, 1, "password", "somesalt",
|
||||
"09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7",
|
||||
"$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc"),
|
||||
hashtest(version, 2, 18, 1, "password", "somesalt",
|
||||
"78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c",
|
||||
"$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ"
|
||||
"$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow"),
|
||||
hashtest(version, 2, 8, 1, "password", "somesalt",
|
||||
"9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe",
|
||||
"$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ"
|
||||
"$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"),
|
||||
hashtest(version, 2, 8, 2, "password", "somesalt",
|
||||
"6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037",
|
||||
"$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ"
|
||||
"$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc"),
|
||||
hashtest(version, 1, 16, 1, "password", "somesalt",
|
||||
"f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98",
|
||||
"$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ"
|
||||
"$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg"),
|
||||
hashtest(version, 4, 16, 1, "password", "somesalt",
|
||||
"9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c",
|
||||
"$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ"
|
||||
"$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw"),
|
||||
hashtest(version, 2, 16, 1, "differentpassword", "somesalt",
|
||||
"0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde",
|
||||
"$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
|
||||
"$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94"),
|
||||
hashtest(version, 2, 16, 1, "password", "diffsalt",
|
||||
"bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c",
|
||||
"$argon2id$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ"
|
||||
"$vfMrBczELrFdWP0ZsfhWsRPaHppYdP3MVEMIVlqoFBw"),
|
||||
])
|
||||
|
||||
#=============================================================================
|
||||
# argon2
|
||||
#=============================================================================
|
||||
class _base_argon2_test(HandlerCase):
|
||||
handler = hash.argon2
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# sample test
|
||||
("password", '$argon2i$v=19$m=256,t=1,p=1$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A'),
|
||||
|
||||
# sample w/ all parameters different
|
||||
("password", '$argon2i$v=19$m=380,t=2,p=2$c29tZXNhbHQ$SrssP8n7m/12VWPM8dvNrw'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_TABLE, '$argon2i$v=19$m=512,t=2,p=2$1sV0O4PWLtc12Ypv1f7oGw$'
|
||||
'z+yqzlKtrq3SaNfXDfIDnQ'),
|
||||
(PASS_TABLE_UTF8, '$argon2i$v=19$m=512,t=2,p=2$1sV0O4PWLtc12Ypv1f7oGw$'
|
||||
'z+yqzlKtrq3SaNfXDfIDnQ'),
|
||||
|
||||
# ensure trailing null bytes handled correctly
|
||||
('password\x00', '$argon2i$v=19$m=512,t=2,p=2$c29tZXNhbHQ$Fb5+nPuLzZvtqKRwqUEtUQ'),
|
||||
|
||||
# sample with type D (generated via argon_cffi2.PasswordHasher)
|
||||
("password", '$argon2d$v=19$m=102400,t=2,p=8$g2RodLh8j8WbSdCp+lUy/A$zzAJqL/HSjm809PYQu6qkA'),
|
||||
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# unknown hash type
|
||||
"$argon2qq$v=19$t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# missing 'm' param
|
||||
"$argon2i$v=19$t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# 't' param > max uint32
|
||||
"$argon2i$v=19$m=65536,t=8589934592,p=4$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# unexpected param
|
||||
"$argon2i$v=19$m=65536,t=2,p=4,q=5$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# wrong param order
|
||||
"$argon2i$v=19$t=2,m=65536,p=4,q=5$c29tZXNhbHQAAAAAAAAAAA$QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY",
|
||||
|
||||
# constraint violation: m < 8 * p
|
||||
"$argon2i$v=19$m=127,t=2,p=16$c29tZXNhbHQ$IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4",
|
||||
]
|
||||
|
||||
known_parsehash_results = [
|
||||
('$argon2i$v=19$m=256,t=2,p=3$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A',
|
||||
dict(type="i", memory_cost=256, rounds=2, parallelism=3, salt=b'somesalt',
|
||||
checksum=b'\x00\x91H\xb0\xd6S0\xa4\xc0{\x00x\xf8D\xcd\xd4')),
|
||||
]
|
||||
|
||||
def setUpWarnings(self):
|
||||
super(_base_argon2_test, self).setUpWarnings()
|
||||
warnings.filterwarnings("ignore", ".*Using argon2pure backend.*")
|
||||
|
||||
def do_stub_encrypt(self, handler=None, **settings):
|
||||
if self.backend == "argon2_cffi":
|
||||
# overriding default since no way to get stub config from argon2._calc_hash()
|
||||
# (otherwise test_21b_max_rounds blocks trying to do max rounds)
|
||||
handler = (handler or self.handler).using(**settings)
|
||||
self = handler(use_defaults=True)
|
||||
self.checksum = self._stub_checksum
|
||||
assert self.checksum
|
||||
return self.to_string()
|
||||
else:
|
||||
return super(_base_argon2_test, self).do_stub_encrypt(handler, **settings)
|
||||
|
||||
def test_03_legacy_hash_workflow(self):
|
||||
# override base method
|
||||
raise self.skipTest("legacy 1.6 workflow not supported")
|
||||
|
||||
def test_keyid_parameter(self):
|
||||
# NOTE: keyid parameter currently not supported by official argon2 hash parser,
|
||||
# even though it's mentioned in the format spec.
|
||||
# we're trying to be consistent w/ this, so hashes w/ keyid should
|
||||
# always through a NotImplementedError.
|
||||
self.assertRaises(NotImplementedError, self.handler.verify, 'password',
|
||||
"$argon2i$v=19$m=65536,t=2,p=4,keyid=ABCD$c29tZXNhbHQ$"
|
||||
"IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4")
|
||||
|
||||
def test_data_parameter(self):
|
||||
# NOTE: argon2 c library doesn't support passing in a data parameter to argon2_hash();
|
||||
# but argon2_verify() appears to parse that info... but then discards it (!?).
|
||||
# not sure what proper behavior is, filed issue -- https://github.com/P-H-C/phc-winner-argon2/issues/143
|
||||
# For now, replicating behavior we have for the two backends, to detect when things change.
|
||||
handler = self.handler
|
||||
|
||||
# ref hash of 'password' when 'data' is correctly passed into argon2()
|
||||
sample1 = '$argon2i$v=19$m=512,t=2,p=2,data=c29tZWRhdGE$c29tZXNhbHQ$KgHyCesFyyjkVkihZ5VNFw'
|
||||
|
||||
# ref hash of 'password' when 'data' is silently discarded (same digest as w/o data)
|
||||
sample2 = '$argon2i$v=19$m=512,t=2,p=2,data=c29tZWRhdGE$c29tZXNhbHQ$uEeXt1dxN1iFKGhklseW4w'
|
||||
|
||||
# hash of 'password' w/o the data field
|
||||
sample3 = '$argon2i$v=19$m=512,t=2,p=2$c29tZXNhbHQ$uEeXt1dxN1iFKGhklseW4w'
|
||||
|
||||
#
|
||||
# test sample 1
|
||||
#
|
||||
|
||||
if self.backend == "argon2_cffi":
|
||||
# argon2_cffi v16.1 would incorrectly return False here.
|
||||
# but v16.2 patches so it throws error on data parameter.
|
||||
# our code should detect that, and adapt it into a NotImplementedError
|
||||
self.assertRaises(NotImplementedError, handler.verify, "password", sample1)
|
||||
|
||||
# incorrectly returns sample3, dropping data parameter
|
||||
self.assertEqual(handler.genhash("password", sample1), sample3)
|
||||
|
||||
else:
|
||||
assert self.backend == "argon2pure"
|
||||
# should parse and verify
|
||||
self.assertTrue(handler.verify("password", sample1))
|
||||
|
||||
# should preserve sample1
|
||||
self.assertEqual(handler.genhash("password", sample1), sample1)
|
||||
|
||||
#
|
||||
# test sample 2
|
||||
#
|
||||
|
||||
if self.backend == "argon2_cffi":
|
||||
# argon2_cffi v16.1 would incorrectly return True here.
|
||||
# but v16.2 patches so it throws error on data parameter.
|
||||
# our code should detect that, and adapt it into a NotImplementedError
|
||||
self.assertRaises(NotImplementedError, handler.verify,"password", sample2)
|
||||
|
||||
# incorrectly returns sample3, dropping data parameter
|
||||
self.assertEqual(handler.genhash("password", sample1), sample3)
|
||||
|
||||
else:
|
||||
assert self.backend == "argon2pure"
|
||||
# should parse, but fail to verify
|
||||
self.assertFalse(self.handler.verify("password", sample2))
|
||||
|
||||
# should return sample1 (corrected digest)
|
||||
self.assertEqual(handler.genhash("password", sample2), sample1)
|
||||
|
||||
def test_keyid_and_data_parameters(self):
|
||||
# test combination of the two, just in case
|
||||
self.assertRaises(NotImplementedError, self.handler.verify, 'stub',
|
||||
"$argon2i$v=19$m=65536,t=2,p=4,keyid=ABCD,data=EFGH$c29tZXNhbHQ$"
|
||||
"IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4")
|
||||
|
||||
def test_type_kwd(self):
|
||||
cls = self.handler
|
||||
|
||||
# XXX: this mirrors test_30_HasManyIdents();
|
||||
# maybe switch argon2 class to use that mixin instead of "type" kwd?
|
||||
|
||||
# check settings
|
||||
self.assertTrue("type" in cls.setting_kwds)
|
||||
|
||||
# check supported type_values
|
||||
for value in cls.type_values:
|
||||
self.assertIsInstance(value, unicode)
|
||||
self.assertTrue("i" in cls.type_values)
|
||||
self.assertTrue("d" in cls.type_values)
|
||||
|
||||
# check default
|
||||
self.assertTrue(cls.type in cls.type_values)
|
||||
|
||||
# check constructor validates ident correctly.
|
||||
handler = cls
|
||||
hash = self.get_sample_hash()[1]
|
||||
kwds = handler.parsehash(hash)
|
||||
del kwds['type']
|
||||
|
||||
# ... accepts good type
|
||||
handler(type=cls.type, **kwds)
|
||||
|
||||
# XXX: this is policy "ident" uses, maybe switch to it?
|
||||
# # ... requires type w/o defaults
|
||||
# self.assertRaises(TypeError, handler, **kwds)
|
||||
handler(**kwds)
|
||||
|
||||
# ... supplies default type
|
||||
handler(use_defaults=True, **kwds)
|
||||
|
||||
# ... rejects bad type
|
||||
self.assertRaises(ValueError, handler, type='xXx', **kwds)
|
||||
|
||||
def test_type_using(self):
|
||||
handler = self.handler
|
||||
|
||||
# XXX: this mirrors test_has_many_idents_using();
|
||||
# maybe switch argon2 class to use that mixin instead of "type" kwd?
|
||||
|
||||
orig_type = handler.type
|
||||
for alt_type in handler.type_values:
|
||||
if alt_type != orig_type:
|
||||
break
|
||||
else:
|
||||
raise AssertionError("expected to find alternate type: default=%r values=%r" %
|
||||
(orig_type, handler.type_values))
|
||||
|
||||
def effective_type(cls):
|
||||
return cls(use_defaults=True).type
|
||||
|
||||
# keep default if nothing else specified
|
||||
subcls = handler.using()
|
||||
self.assertEqual(subcls.type, orig_type)
|
||||
|
||||
# accepts alt type
|
||||
subcls = handler.using(type=alt_type)
|
||||
self.assertEqual(subcls.type, alt_type)
|
||||
self.assertEqual(handler.type, orig_type)
|
||||
|
||||
# check subcls actually *generates* default type,
|
||||
# and that we didn't affect orig handler
|
||||
self.assertEqual(effective_type(subcls), alt_type)
|
||||
self.assertEqual(effective_type(handler), orig_type)
|
||||
|
||||
# rejects bad type
|
||||
self.assertRaises(ValueError, handler.using, type='xXx')
|
||||
|
||||
# honor 'type' alias
|
||||
subcls = handler.using(type=alt_type)
|
||||
self.assertEqual(subcls.type, alt_type)
|
||||
self.assertEqual(handler.type, orig_type)
|
||||
|
||||
# check type aliases are being honored
|
||||
self.assertEqual(effective_type(handler.using(type="I")), "i")
|
||||
|
||||
def test_needs_update_w_type(self):
|
||||
handler = self.handler
|
||||
|
||||
hash = handler.hash("stub")
|
||||
self.assertFalse(handler.needs_update(hash))
|
||||
|
||||
hash2 = re.sub(r"\$argon2\w+\$", "$argon2d$", hash)
|
||||
self.assertTrue(handler.needs_update(hash2))
|
||||
|
||||
def test_needs_update_w_version(self):
|
||||
handler = self.handler.using(memory_cost=65536, time_cost=2, parallelism=4,
|
||||
digest_size=32)
|
||||
hash = ("$argon2i$m=65536,t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$"
|
||||
"QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY")
|
||||
if handler.max_version == 0x10:
|
||||
self.assertFalse(handler.needs_update(hash))
|
||||
else:
|
||||
self.assertTrue(handler.needs_update(hash))
|
||||
|
||||
def test_argon_byte_encoding(self):
|
||||
"""verify we're using right base64 encoding for argon2"""
|
||||
handler = self.handler
|
||||
if handler.version != 0x13:
|
||||
# TODO: make this fatal, and add refs for other version.
|
||||
raise self.skipTest("handler uses wrong version for sample hashes")
|
||||
|
||||
# 8 byte salt
|
||||
salt = b'somesalt'
|
||||
temp = handler.using(memory_cost=256, time_cost=2, parallelism=2, salt=salt,
|
||||
checksum_size=32, type="i")
|
||||
hash = temp.hash("password")
|
||||
self.assertEqual(hash, "$argon2i$v=19$m=256,t=2,p=2"
|
||||
"$c29tZXNhbHQ"
|
||||
"$T/XOJ2mh1/TIpJHfCdQan76Q5esCFVoT5MAeIM1Oq2E")
|
||||
|
||||
# 16 byte salt
|
||||
salt = b'somesalt\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
temp = handler.using(memory_cost=256, time_cost=2, parallelism=2, salt=salt,
|
||||
checksum_size=32, type="i")
|
||||
hash = temp.hash("password")
|
||||
self.assertEqual(hash, "$argon2i$v=19$m=256,t=2,p=2"
|
||||
"$c29tZXNhbHQAAAAAAAAAAA"
|
||||
"$rqnbEp1/jFDUEKZZmw+z14amDsFqMDC53dIe57ZHD38")
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
settings_map = HandlerCase.FuzzHashGenerator.settings_map.copy()
|
||||
settings_map.update(memory_cost="random_memory_cost", type="random_type")
|
||||
|
||||
def random_type(self):
|
||||
return self.rng.choice(self.handler.type_values)
|
||||
|
||||
def random_memory_cost(self):
|
||||
if self.test.backend == "argon2pure":
|
||||
return self.randintgauss(128, 384, 256, 128)
|
||||
else:
|
||||
return self.randintgauss(128, 32767, 16384, 4096)
|
||||
|
||||
# TODO: fuzz parallelism, digest_size
|
||||
|
||||
#-----------------------------------------
|
||||
# test suites for specific backends
|
||||
#-----------------------------------------
|
||||
|
||||
class argon2_argon2_cffi_test(_base_argon2_test.create_backend_case("argon2_cffi")):
|
||||
|
||||
# add some more test vectors that take too long under argon2pure
|
||||
known_correct_hashes = _base_argon2_test.known_correct_hashes + [
|
||||
#
|
||||
# sample hashes from argon2 cffi package's unittests,
|
||||
# which in turn were generated by official argon2 cmdline tool.
|
||||
#
|
||||
|
||||
# v1.2, type I, w/o a version tag
|
||||
('password', "$argon2i$m=65536,t=2,p=4$c29tZXNhbHQAAAAAAAAAAA$"
|
||||
"QWLzI4TY9HkL2ZTLc8g6SinwdhZewYrzz9zxCo0bkGY"),
|
||||
|
||||
# v1.3, type I
|
||||
('password', "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"IMit9qkFULCMA/ViizL57cnTLOa5DiVM9eMwpAvPwr4"),
|
||||
|
||||
# v1.3, type D
|
||||
('password', "$argon2d$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"cZn5d+rFh+ZfuRhm2iGUGgcrW5YLeM6q7L3vBsdmFA0"),
|
||||
|
||||
# v1.3, type ID
|
||||
('password', "$argon2id$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"GpZ3sK/oH9p7VIiV56G/64Zo/8GaUw434IimaPqxwCo"),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# ensure trailing null bytes handled correctly
|
||||
('password\x00', "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$"
|
||||
"Vpzuc0v0SrP88LcVvmg+z5RoOYpMDKH/lt6O+CZabIQ"),
|
||||
|
||||
]
|
||||
|
||||
# add reference hashes from argon2 clib tests
|
||||
known_correct_hashes.extend(
|
||||
(info['secret'], info['hash']) for info in reference_data
|
||||
if info['logM'] <= (18 if TEST_MODE("full") else 16)
|
||||
)
|
||||
|
||||
class argon2_argon2pure_test(_base_argon2_test.create_backend_case("argon2pure")):
|
||||
|
||||
# XXX: setting max_threads at 1 to prevent argon2pure from using multiprocessing,
|
||||
# which causes big problems when testing under pypy.
|
||||
# would like a "pure_use_threads" option instead, to make it use multiprocessing.dummy instead.
|
||||
handler = hash.argon2.using(memory_cost=32, parallelism=2)
|
||||
|
||||
# don't use multiprocessing for unittests, makes it a lot harder to ctrl-c
|
||||
# XXX: make this controlled by env var?
|
||||
handler.pure_use_threads = True
|
||||
|
||||
# add reference hashes from argon2 clib tests
|
||||
known_correct_hashes = _base_argon2_test.known_correct_hashes[:]
|
||||
|
||||
known_correct_hashes.extend(
|
||||
(info['secret'], info['hash']) for info in reference_data
|
||||
if info['logM'] < 16
|
||||
)
|
||||
|
||||
class FuzzHashGenerator(_base_argon2_test.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(1, 3, 2, 1)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
688
venv/Lib/site-packages/passlib/tests/test_handlers_bcrypt.py
Normal file
688
venv/Lib/site-packages/passlib/tests/test_handlers_bcrypt.py
Normal file
@@ -0,0 +1,688 @@
|
||||
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import os
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.handlers.bcrypt import IDENT_2, IDENT_2X
|
||||
from passlib.utils import repeat_string, to_bytes, is_safe_crypt_input
|
||||
from passlib.utils.compat import irange, PY3
|
||||
from passlib.tests.utils import HandlerCase, TEST_MODE
|
||||
from passlib.tests.test_handlers import UPASS_TABLE
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# bcrypt
|
||||
#=============================================================================
|
||||
class _bcrypt_test(HandlerCase):
|
||||
"""base for BCrypt test cases"""
|
||||
handler = hash.bcrypt
|
||||
reduce_default_rounds = True
|
||||
fuzz_salts_need_bcrypt_repair = 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'),
|
||||
|
||||
#
|
||||
# 8bit bug (fixed in 2y/2b)
|
||||
#
|
||||
|
||||
# NOTE: see assert_lacks_8bit_bug() for origins of this test vector.
|
||||
(b"\xd1\x91", "$2y$05$6bNw2HLQYeqHYyBfLMsv/OUcZd0LKP39b87nBw3.S2tVZSqiQX6eu"),
|
||||
|
||||
#
|
||||
# bsd wraparound bug (fixed in 2b)
|
||||
#
|
||||
|
||||
# NOTE: if backend is vulnerable, password will hash the same as '0'*72
|
||||
# ("$2a$04$R1lJ2gkNaoPGdafE.H.16.nVyh2niHsGJhayOHLMiXlI45o8/DU.6"),
|
||||
# rather than same as ("0123456789"*8)[:72]
|
||||
# 255 should be sufficient, but checking
|
||||
(("0123456789"*26)[:254], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
(("0123456789"*26)[:255], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
(("0123456789"*26)[:256], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
(("0123456789"*26)[:257], '$2a$04$R1lJ2gkNaoPGdafE.H.16.1MKHPvmKwryeulRe225LKProWYwt9Oi'),
|
||||
|
||||
|
||||
#
|
||||
# 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'),
|
||||
|
||||
# ensure 2b support
|
||||
(UPASS_TABLE,
|
||||
'$2b$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
|
||||
"$2f$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", None), # may be present via addon, e.g. debian's libpam-unix2
|
||||
("solaris", None), # depends on system 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] = "true"
|
||||
|
||||
super(_bcrypt_test, self).setUp()
|
||||
|
||||
# silence this warning, will come up a bunch during testing of old 2a hashes.
|
||||
warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*")
|
||||
|
||||
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 crypt_supports_variant(self, hash):
|
||||
"""check if OS crypt is expected to support given ident"""
|
||||
from passlib.handlers.bcrypt import bcrypt, IDENT_2X, IDENT_2Y
|
||||
from passlib.utils import safe_crypt
|
||||
ident = bcrypt.from_string(hash)
|
||||
return (safe_crypt("test", ident + "04$5BJqKfqMQvV7nS.yUguNcu") or "").startswith(ident)
|
||||
|
||||
fuzz_verifiers = HandlerCase.fuzz_verifiers + (
|
||||
"fuzz_verifier_bcrypt",
|
||||
"fuzz_verifier_pybcrypt",
|
||||
"fuzz_verifier_bcryptor",
|
||||
)
|
||||
|
||||
def fuzz_verifier_bcrypt(self):
|
||||
# test against bcrypt, if available
|
||||
from passlib.handlers.bcrypt import IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y, _detect_pybcrypt
|
||||
from passlib.utils import to_native_str, to_bytes
|
||||
try:
|
||||
import bcrypt
|
||||
except ImportError:
|
||||
return
|
||||
if _detect_pybcrypt():
|
||||
return
|
||||
def check_bcrypt(secret, hash):
|
||||
"""bcrypt"""
|
||||
secret = to_bytes(secret, self.FuzzHashGenerator.password_encoding)
|
||||
if hash.startswith(IDENT_2B):
|
||||
# bcrypt <1.1 lacks 2B support
|
||||
hash = IDENT_2A + hash[4:]
|
||||
elif hash.startswith(IDENT_2):
|
||||
# bcrypt 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)
|
||||
elif hash.startswith(IDENT_2Y) and bcrypt.__version__ == "3.0.0":
|
||||
hash = IDENT_2B + hash[4:]
|
||||
hash = to_bytes(hash)
|
||||
try:
|
||||
return bcrypt.hashpw(secret, hash) == hash
|
||||
except ValueError:
|
||||
raise ValueError("bcrypt rejected hash: %r (secret=%r)" % (hash, secret))
|
||||
return check_bcrypt
|
||||
|
||||
def fuzz_verifier_pybcrypt(self):
|
||||
# test against py-bcrypt, if available
|
||||
from passlib.handlers.bcrypt import (
|
||||
IDENT_2, IDENT_2A, IDENT_2B, IDENT_2X, IDENT_2Y,
|
||||
_PyBcryptBackend,
|
||||
)
|
||||
from passlib.utils import to_native_str
|
||||
|
||||
loaded = _PyBcryptBackend._load_backend_mixin("pybcrypt", False)
|
||||
if not loaded:
|
||||
return
|
||||
|
||||
from passlib.handlers.bcrypt import _pybcrypt as bcrypt_mod
|
||||
|
||||
lock = _PyBcryptBackend._calc_lock # reuse threadlock workaround for pybcrypt 0.2
|
||||
|
||||
def check_pybcrypt(secret, hash):
|
||||
"""pybcrypt"""
|
||||
secret = to_native_str(secret, self.FuzzHashGenerator.password_encoding)
|
||||
if len(secret) > 200: # vulnerable to wraparound bug
|
||||
secret = secret[:200]
|
||||
if hash.startswith((IDENT_2B, IDENT_2Y)):
|
||||
hash = IDENT_2A + hash[4:]
|
||||
try:
|
||||
if lock:
|
||||
with lock:
|
||||
return bcrypt_mod.hashpw(secret, hash) == hash
|
||||
else:
|
||||
return bcrypt_mod.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, IDENT_2B
|
||||
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.FuzzHashGenerator.password_encoding)
|
||||
if hash.startswith((IDENT_2B, 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
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def generate(self):
|
||||
opts = super(_bcrypt_test.FuzzHashGenerator, self).generate()
|
||||
|
||||
secret = opts['secret']
|
||||
other = opts['other']
|
||||
settings = opts['settings']
|
||||
ident = settings.get('ident')
|
||||
|
||||
if ident == IDENT_2X:
|
||||
# 2x is just recognized, not supported. don't test with it.
|
||||
del settings['ident']
|
||||
|
||||
elif ident == IDENT_2 and other 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.
|
||||
opts['secret'], opts['other'] = self.random_password_pair()
|
||||
|
||||
return opts
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.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 hash() / genconfig() don't generate invalid salts anymore
|
||||
#
|
||||
def check_padding(hash):
|
||||
assert hash.startswith(("$2a$", "$2b$")) and len(hash) >= 28, \
|
||||
"unexpectedly malformed hash: %r" % (hash,)
|
||||
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.using(rounds=bcrypt.min_rounds).hash("bob"))
|
||||
|
||||
#
|
||||
# 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, "$2b$05$" + "." * (22 + 31))
|
||||
|
||||
#
|
||||
# test public methods against good & bad hashes
|
||||
#
|
||||
samples = self.known_incorrect_padding
|
||||
for pwd, bad, good in samples:
|
||||
|
||||
# make sure genhash() corrects bad configs, leaves good unchanged
|
||||
with self.assertWarningList([corr_desc]):
|
||||
self.assertEqual(bcrypt.genhash(pwd, bad), good)
|
||||
with self.assertWarningList([]):
|
||||
self.assertEqual(bcrypt.genhash(pwd, good), good)
|
||||
|
||||
# make sure verify() works correctly with good & bad hashes
|
||||
with self.assertWarningList([corr_desc]):
|
||||
self.assertTrue(bcrypt.verify(pwd, bad))
|
||||
with self.assertWarningList([]):
|
||||
self.assertTrue(bcrypt.verify(pwd, good))
|
||||
|
||||
# make sure normhash() corrects bad hashes, leaves good unchanged
|
||||
with self.assertWarningList([corr_desc]):
|
||||
self.assertEqual(bcrypt.normhash(bad), good)
|
||||
with self.assertWarningList([]):
|
||||
self.assertEqual(bcrypt.normhash(good), good)
|
||||
|
||||
# make sure normhash() leaves non-bcrypt hashes alone
|
||||
self.assertEqual(bcrypt.normhash("$md5$abc"), "$md5$abc")
|
||||
|
||||
def test_needs_update_w_padding(self):
|
||||
"""needs_update corrects bcrypt padding"""
|
||||
# NOTE: see padding test above for details about issue this detects
|
||||
bcrypt = self.handler.using(rounds=4)
|
||||
|
||||
# PASS1 = "test"
|
||||
# bad contains invalid 'c' char at end of salt:
|
||||
# \/
|
||||
BAD1 = "$2a$04$yjDgE74RJkeqC0/1NheSScrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
GOOD1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
|
||||
self.assertTrue(bcrypt.needs_update(BAD1))
|
||||
self.assertFalse(bcrypt.needs_update(GOOD1))
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
# create test cases for specific backends
|
||||
bcrypt_bcrypt_test = _bcrypt_test.create_backend_case("bcrypt")
|
||||
bcrypt_pybcrypt_test = _bcrypt_test.create_backend_case("pybcrypt")
|
||||
bcrypt_bcryptor_test = _bcrypt_test.create_backend_case("bcryptor")
|
||||
|
||||
class bcrypt_os_crypt_test(_bcrypt_test.create_backend_case("os_crypt")):
|
||||
|
||||
# os crypt doesn't support non-utf8 secret bytes
|
||||
known_correct_hashes = [row for row in _bcrypt_test.known_correct_hashes
|
||||
if is_safe_crypt_input(row[0])]
|
||||
|
||||
# os crypt backend doesn't currently implement a per-call fallback if it fails
|
||||
has_os_crypt_fallback = False
|
||||
|
||||
bcrypt_builtin_test = _bcrypt_test.create_backend_case("builtin")
|
||||
|
||||
#=============================================================================
|
||||
# bcrypt
|
||||
#=============================================================================
|
||||
class _bcrypt_sha256_test(HandlerCase):
|
||||
"base for BCrypt-SHA256 test cases"
|
||||
handler = hash.bcrypt_sha256
|
||||
reduce_default_rounds = True
|
||||
forbidden_characters = None
|
||||
fuzz_salts_need_bcrypt_repair = True
|
||||
|
||||
known_correct_hashes = [
|
||||
#-------------------------------------------------------------------
|
||||
# custom test vectors for old v1 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# empty
|
||||
("",
|
||||
'$bcrypt-sha256$2a,5$E/e/2AOhqM5W/KJTFQzLce$F6dYSxOdAEoJZO2eoHUZWZljW/e0TXO'),
|
||||
|
||||
# ascii
|
||||
("password",
|
||||
'$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
|
||||
|
||||
# unicode / utf8
|
||||
(UPASS_TABLE,
|
||||
'$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
|
||||
(UPASS_TABLE.encode("utf-8"),
|
||||
'$bcrypt-sha256$2a,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
|
||||
|
||||
# ensure 2b support
|
||||
("password",
|
||||
'$bcrypt-sha256$2b,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
|
||||
(UPASS_TABLE,
|
||||
'$bcrypt-sha256$2b,5$.US1fQ4TQS.ZTz/uJ5Kyn.$QNdPDOTKKT5/sovNz1iWg26quOU4Pje'),
|
||||
|
||||
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
|
||||
# NOTE: test_60_truncate_size() handles this already, this is just for overkill :)
|
||||
(repeat_string("abc123", 72),
|
||||
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$r/hyEtqJ0teqPEmfTLoZ83ciAI1Q74.'),
|
||||
(repeat_string("abc123", 72) + "qwr",
|
||||
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$021KLEif6epjot5yoxk0m8I0929ohEa'),
|
||||
(repeat_string("abc123", 72) + "xyz",
|
||||
'$bcrypt-sha256$2b,5$X1g1nh3g0v4h6970O68cxe$7.1kgpHduMGEjvM3fX6e/QCvfn6OKja'),
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
# custom test vectors for v2 format
|
||||
# TODO: convert to v2 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# empty
|
||||
("",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$E/e/2AOhqM5W/KJTFQzLce$WFPIZKtDDTriqWwlmRFfHiOTeheAZWe'),
|
||||
|
||||
# ascii
|
||||
("password",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'),
|
||||
|
||||
# unicode / utf8
|
||||
(UPASS_TABLE,
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'),
|
||||
(UPASS_TABLE.encode("utf-8"),
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$.US1fQ4TQS.ZTz/uJ5Kyn.$pzzgp40k8reM1CuQb03PvE0IDPQSdV6'),
|
||||
|
||||
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
|
||||
# NOTE: test_60_truncate_size() handles this already, this is just for overkill :)
|
||||
(repeat_string("abc123", 72),
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zu1cloESVFIOsUIo7fCEgkdHaI9SSue'),
|
||||
(repeat_string("abc123", 72) + "qwr",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$CBF9csfEdW68xv3DwE6xSULXMtqEFP.'),
|
||||
(repeat_string("abc123", 72) + "xyz",
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$X1g1nh3g0v4h6970O68cxe$zC/1UDUG2ofEXB6Onr2vvyFzfhEOS3S'),
|
||||
]
|
||||
|
||||
known_correct_configs =[
|
||||
# v1
|
||||
('$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe',
|
||||
"password", '$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu'),
|
||||
# v2
|
||||
('$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe',
|
||||
"password", '$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$wOK1VFFtS8IGTrGa7.h5fs0u84qyPbS'),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
#-------------------------------------------------------------------
|
||||
# v1 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# bad char in otherwise correct hash
|
||||
# \/
|
||||
'$bcrypt-sha256$2a,5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unrecognized bcrypt variant
|
||||
'$bcrypt-sha256$2c,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unsupported bcrypt variant
|
||||
'$bcrypt-sha256$2x,5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# rounds zero-padded
|
||||
'$bcrypt-sha256$2a,05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# config string w/ $ added
|
||||
'$bcrypt-sha256$2a,5$5Hg1DKFqPE8C2aflZ5vVoe$',
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
# v2 format
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# bad char in otherwise correct hash
|
||||
# \/
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKF!PE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unsupported version (for this format)
|
||||
'$bcrypt-sha256$v=1,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unrecognized version
|
||||
'$bcrypt-sha256$v=3,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unrecognized bcrypt variant
|
||||
'$bcrypt-sha256$v=2,t=2c,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# unsupported bcrypt variant
|
||||
'$bcrypt-sha256$v=2,t=2a,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
'$bcrypt-sha256$v=2,t=2x,r=5$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# rounds zero-padded
|
||||
'$bcrypt-sha256$v=2,t=2b,r=05$5Hg1DKFqPE8C2aflZ5vVoe$12BjNE0p7axMg55.Y/mHsYiVuFBDQyu',
|
||||
|
||||
# config string w/ $ added
|
||||
'$bcrypt-sha256$v=2,t=2b,r=5$5Hg1DKFqPE8C2aflZ5vVoe$',
|
||||
]
|
||||
|
||||
#===================================================================
|
||||
# override some methods -- cloned from bcrypt
|
||||
#===================================================================
|
||||
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_sha256_test, self).setUp()
|
||||
warnings.filterwarnings("ignore", ".*backend is vulnerable to the bsd wraparound bug.*")
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# builtin is still just way too slow.
|
||||
if self.backend == "builtin":
|
||||
kwds.setdefault("rounds", 4)
|
||||
super(_bcrypt_sha256_test, self).populate_settings(kwds)
|
||||
|
||||
#===================================================================
|
||||
# override ident tests for now
|
||||
#===================================================================
|
||||
|
||||
def require_many_idents(self):
|
||||
raise self.skipTest("multiple idents not supported")
|
||||
|
||||
def test_30_HasOneIdent(self):
|
||||
# forbidding ident keyword, we only support "2b" for now
|
||||
handler = self.handler
|
||||
handler(use_defaults=True)
|
||||
self.assertRaises(ValueError, handler, ident="$2y$", use_defaults=True)
|
||||
|
||||
#===================================================================
|
||||
# fuzz testing -- cloned from bcrypt
|
||||
#===================================================================
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(5, 8, 6, 1)
|
||||
|
||||
def random_ident(self):
|
||||
return "2b"
|
||||
|
||||
#===================================================================
|
||||
# custom tests
|
||||
#===================================================================
|
||||
|
||||
def test_using_version(self):
|
||||
# default to v2
|
||||
handler = self.handler
|
||||
self.assertEqual(handler.version, 2)
|
||||
|
||||
# allow v1 explicitly
|
||||
subcls = handler.using(version=1)
|
||||
self.assertEqual(subcls.version, 1)
|
||||
|
||||
# forbid unknown ver
|
||||
self.assertRaises(ValueError, handler.using, version=999)
|
||||
|
||||
# allow '2a' only for v1
|
||||
subcls = handler.using(version=1, ident="2a")
|
||||
self.assertRaises(ValueError, handler.using, ident="2a")
|
||||
|
||||
def test_calc_digest_v2(self):
|
||||
"""
|
||||
test digest calc v2 matches bcrypt()
|
||||
"""
|
||||
from passlib.hash import bcrypt
|
||||
from passlib.crypto.digest import compile_hmac
|
||||
from passlib.utils.binary import b64encode
|
||||
|
||||
# manually calc intermediary digest
|
||||
salt = "nyKYxTAvjmy6lMDYMl11Uu"
|
||||
secret = "test"
|
||||
temp_digest = compile_hmac("sha256", salt.encode("ascii"))(secret.encode("ascii"))
|
||||
temp_digest = b64encode(temp_digest).decode("ascii")
|
||||
self.assertEqual(temp_digest, "J5TlyIDm+IcSWmKiDJm+MeICndBkFVPn4kKdJW8f+xY=")
|
||||
|
||||
# manually final hash from intermediary
|
||||
# XXX: genhash() could be useful here
|
||||
bcrypt_digest = bcrypt(ident="2b", salt=salt, rounds=12)._calc_checksum(temp_digest)
|
||||
self.assertEqual(bcrypt_digest, "M0wE0Ov/9LXoQFCe.jRHu3MSHPF54Ta")
|
||||
self.assertTrue(bcrypt.verify(temp_digest, "$2b$12$" + salt + bcrypt_digest))
|
||||
|
||||
# confirm handler outputs same thing.
|
||||
# XXX: genhash() could be useful here
|
||||
result = self.handler(ident="2b", salt=salt, rounds=12)._calc_checksum(secret)
|
||||
self.assertEqual(result, bcrypt_digest)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
# create test cases for specific backends
|
||||
bcrypt_sha256_bcrypt_test = _bcrypt_sha256_test.create_backend_case("bcrypt")
|
||||
bcrypt_sha256_pybcrypt_test = _bcrypt_sha256_test.create_backend_case("pybcrypt")
|
||||
bcrypt_sha256_bcryptor_test = _bcrypt_sha256_test.create_backend_case("bcryptor")
|
||||
|
||||
class bcrypt_sha256_os_crypt_test(_bcrypt_sha256_test.create_backend_case("os_crypt")):
|
||||
|
||||
@classmethod
|
||||
def _get_safe_crypt_handler_backend(cls):
|
||||
return bcrypt_os_crypt_test._get_safe_crypt_handler_backend()
|
||||
|
||||
has_os_crypt_fallback = False
|
||||
|
||||
bcrypt_sha256_builtin_test = _bcrypt_sha256_test.create_backend_case("builtin")
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
457
venv/Lib/site-packages/passlib/tests/test_handlers_cisco.py
Normal file
457
venv/Lib/site-packages/passlib/tests/test_handlers_cisco.py
Normal file
@@ -0,0 +1,457 @@
|
||||
"""
|
||||
passlib.tests.test_handlers_cisco - tests for Cisco-specific algorithms
|
||||
"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import absolute_import, division, print_function
|
||||
# core
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash, exc
|
||||
from passlib.utils.compat import u
|
||||
from .utils import UserHandlerMixin, HandlerCase, repeat_string
|
||||
from .test_handlers import UPASS_TABLE
|
||||
# module
|
||||
__all__ = [
|
||||
"cisco_pix_test",
|
||||
"cisco_asa_test",
|
||||
"cisco_type7_test",
|
||||
]
|
||||
#=============================================================================
|
||||
# shared code for cisco PIX & ASA
|
||||
#=============================================================================
|
||||
|
||||
class _PixAsaSharedTest(UserHandlerMixin, HandlerCase):
|
||||
"""
|
||||
class w/ shared info for PIX & ASA tests.
|
||||
"""
|
||||
__unittest_skip = True # for TestCase
|
||||
requires_user = False # for UserHandlerMixin
|
||||
|
||||
#: shared list of hashes which should be identical under pix & asa7
|
||||
#: (i.e. combined secret + user < 17 bytes)
|
||||
pix_asa_shared_hashes = [
|
||||
#
|
||||
# http://www.perlmonks.org/index.pl?node_id=797623
|
||||
#
|
||||
(("cisco", ""), "2KFQnbNIdI.2KYOU"), # confirmed ASA 9.6
|
||||
|
||||
#
|
||||
# http://www.hsc.fr/ressources/breves/pix_crack.html.en
|
||||
#
|
||||
(("hsc", ""), "YtT8/k6Np8F1yz2c"), # confirmed ASA 9.6
|
||||
|
||||
#
|
||||
# www.freerainbowtables.com/phpBB3/viewtopic.php?f=2&t=1441
|
||||
#
|
||||
(("", ""), "8Ry2YjIyt7RRXU24"), # confirmed ASA 9.6
|
||||
(("cisco", "john"), "hN7LzeyYjw12FSIU"),
|
||||
(("cisco", "jack"), "7DrfeZ7cyOj/PslD"),
|
||||
|
||||
#
|
||||
# http://comments.gmane.org/gmane.comp.security.openwall.john.user/2529
|
||||
#
|
||||
(("ripper", "alex"), "h3mJrcH0901pqX/m"),
|
||||
(("cisco", "cisco"), "3USUcOPFUiMCO4Jk"),
|
||||
(("cisco", "cisco1"), "3USUcOPFUiMCO4Jk"),
|
||||
(("CscFw-ITC!", "admcom"), "lZt7HSIXw3.QP7.R"),
|
||||
("cangetin", "TynyB./ftknE77QP"),
|
||||
(("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"),
|
||||
|
||||
#
|
||||
# http://openwall.info/wiki/john/sample-hashes
|
||||
#
|
||||
(("phonehome", "rharris"), "zyIIMSYjiPm0L7a6"),
|
||||
|
||||
#
|
||||
# http://www.openwall.com/lists/john-users/2010/08/08/3
|
||||
#
|
||||
(("cangetin", ""), "TynyB./ftknE77QP"),
|
||||
(("cangetin", "rramsey"), "jgBZqYtsWfGcUKDi"),
|
||||
|
||||
#
|
||||
# from JTR 1.7.9
|
||||
#
|
||||
("test1", "TRPEas6f/aa6JSPL"),
|
||||
("test2", "OMT6mXmAvGyzrCtp"),
|
||||
("test3", "gTC7RIy1XJzagmLm"),
|
||||
("test4", "oWC1WRwqlBlbpf/O"),
|
||||
("password", "NuLKvvWGg.x9HEKO"),
|
||||
("0123456789abcdef", ".7nfVBEIEu4KbF/1"),
|
||||
|
||||
#
|
||||
# http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html#wp5472
|
||||
#
|
||||
(("1234567890123456", ""), "feCkwUGktTCAgIbD"), # canonical source
|
||||
(("watag00s1am", ""), "jMorNbK0514fadBh"), # canonical source
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
(("cisco1", "cisco1"), "jmINXNH6p1BxUppp"),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_TABLE, 'CaiIvkLMu2TOHXGT'),
|
||||
|
||||
#
|
||||
# passlib reference vectors
|
||||
#
|
||||
# Some of these have been confirmed on various ASA firewalls,
|
||||
# and the exact version is noted next to each hash.
|
||||
# Would like to verify these under more PIX & ASA versions.
|
||||
#
|
||||
# Those without a note are generally an extrapolation,
|
||||
# to ensure the code stays consistent, but for various reasons,
|
||||
# hasn't been verified.
|
||||
#
|
||||
# * One such case is usernames w/ 1 & 2 digits --
|
||||
# ASA (9.6 at least) requires 3+ digits in username.
|
||||
#
|
||||
# The following hashes (below 13 chars) should be identical for PIX/ASA.
|
||||
# Ones which differ are listed separately in the known_correct_hashes
|
||||
# list for the two test classes.
|
||||
#
|
||||
|
||||
# 4 char password
|
||||
(('1234', ''), 'RLPMUQ26KL4blgFN'), # confirmed ASA 9.6
|
||||
|
||||
# 8 char password
|
||||
(('01234567', ''), '0T52THgnYdV1tlOF'), # confirmed ASA 9.6
|
||||
(('01234567', '3'), '.z0dT9Alkdc7EIGS'),
|
||||
(('01234567', '36'), 'CC3Lam53t/mHhoE7'),
|
||||
(('01234567', '365'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6
|
||||
(('01234567', '3333'), '.z0dT9Alkdc7EIGS'), # confirmed ASA 9.6
|
||||
(('01234567', '3636'), 'CC3Lam53t/mHhoE7'), # confirmed ASA 9.6
|
||||
(('01234567', '3653'), '8xPrWpNnBdD2DzdZ'), # confirmed ASA 9.6
|
||||
(('01234567', 'adm'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
|
||||
(('01234567', 'adma'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
|
||||
(('01234567', 'admad'), 'dfWs2qiao6KD/P2L'), # confirmed ASA 9.6
|
||||
(('01234567', 'user'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6
|
||||
(('01234567', 'user1234'), 'PNZ4ycbbZ0jp1.j1'), # confirmed ASA 9.6
|
||||
|
||||
# 12 char password
|
||||
(('0123456789ab', ''), 'S31BxZOGlAigndcJ'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '36'), 'wFqSX91X5.YaRKsi'),
|
||||
(('0123456789ab', '365'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '3333'), 'mcXPL/vIZcIxLUQs'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '3636'), 'wFqSX91X5.YaRKsi'), # confirmed ASA 9.6
|
||||
(('0123456789ab', '3653'), 'qjgo3kNgTVxExbno'), # confirmed ASA 9.6
|
||||
(('0123456789ab', 'user'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6
|
||||
(('0123456789ab', 'user1234'), 'f.T4BKdzdNkjxQl7'), # confirmed ASA 9.6
|
||||
|
||||
# NOTE: remaining reference vectors for 13+ char passwords
|
||||
# are split up between cisco_pix & cisco_asa tests.
|
||||
|
||||
# unicode passwords
|
||||
# ASA supposedly uses utf-8 encoding, but entering non-ascii
|
||||
# chars is error-prone, and while UTF-8 appears to be intended,
|
||||
# observed behaviors include:
|
||||
# * ssh cli stripping non-ascii chars entirely
|
||||
# * ASDM web iface double-encoding utf-8 strings
|
||||
((u("t\xe1ble").encode("utf-8"), 'user'), 'Og8fB4NyF0m5Ed9c'),
|
||||
((u("t\xe1ble").encode("utf-8").decode("latin-1").encode("utf-8"),
|
||||
'user'), 'cMvFC2XVBmK/68yB'), # confirmed ASA 9.6 when typed into ASDM
|
||||
]
|
||||
|
||||
def test_calc_digest_spoiler(self):
|
||||
"""
|
||||
_calc_checksum() -- spoil oversize passwords during verify
|
||||
|
||||
for details, see 'spoil_digest' flag instead that function.
|
||||
this helps cisco_pix/cisco_asa implement their policy of
|
||||
``.truncate_verify_reject=True``.
|
||||
"""
|
||||
def calc(secret, for_hash=False):
|
||||
return self.handler(use_defaults=for_hash)._calc_checksum(secret)
|
||||
|
||||
# short (non-truncated) password
|
||||
short_secret = repeat_string("1234", self.handler.truncate_size)
|
||||
short_hash = calc(short_secret)
|
||||
|
||||
# longer password should have totally different hash,
|
||||
# to prevent verify from matching (i.e. "spoiled").
|
||||
long_secret = short_secret + "X"
|
||||
long_hash = calc(long_secret)
|
||||
self.assertNotEqual(long_hash, short_hash)
|
||||
|
||||
# spoiled hash should depend on whole secret,
|
||||
# so that output isn't predictable
|
||||
alt_long_secret = short_secret + "Y"
|
||||
alt_long_hash = calc(alt_long_secret)
|
||||
self.assertNotEqual(alt_long_hash, short_hash)
|
||||
self.assertNotEqual(alt_long_hash, long_hash)
|
||||
|
||||
# for hash(), should throw error if password too large
|
||||
calc(short_secret, for_hash=True)
|
||||
self.assertRaises(exc.PasswordSizeError, calc, long_secret, for_hash=True)
|
||||
self.assertRaises(exc.PasswordSizeError, calc, alt_long_secret, for_hash=True)
|
||||
|
||||
#=============================================================================
|
||||
# cisco pix
|
||||
#=============================================================================
|
||||
class cisco_pix_test(_PixAsaSharedTest):
|
||||
handler = hash.cisco_pix
|
||||
|
||||
#: known correct pix hashes
|
||||
known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [
|
||||
#
|
||||
# passlib reference vectors (PIX-specific)
|
||||
#
|
||||
# NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors,
|
||||
# and general notes about the 'passlib reference vectors' test set.
|
||||
#
|
||||
# All of the following are PIX-specific, as ASA starts
|
||||
# to use a different padding size at 13 characters.
|
||||
#
|
||||
# TODO: these need confirming w/ an actual PIX system.
|
||||
#
|
||||
|
||||
# 13 char password
|
||||
(('0123456789abc', ''), 'eacOpB7vE7ZDukSF'),
|
||||
(('0123456789abc', '3'), 'ylJTd/qei66WZe3w'),
|
||||
(('0123456789abc', '36'), 'hDx8QRlUhwd6bU8N'),
|
||||
(('0123456789abc', '365'), 'vYOOtnkh1HXcMrM7'),
|
||||
(('0123456789abc', '3333'), 'ylJTd/qei66WZe3w'),
|
||||
(('0123456789abc', '3636'), 'hDx8QRlUhwd6bU8N'),
|
||||
(('0123456789abc', '3653'), 'vYOOtnkh1HXcMrM7'),
|
||||
(('0123456789abc', 'user'), 'f4/.SALxqDo59mfV'),
|
||||
(('0123456789abc', 'user1234'), 'f4/.SALxqDo59mfV'),
|
||||
|
||||
# 14 char password
|
||||
(('0123456789abcd', ''), '6r8888iMxEoPdLp4'),
|
||||
(('0123456789abcd', '3'), 'f5lvmqWYj9gJqkIH'),
|
||||
(('0123456789abcd', '36'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', '365'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', '3333'), 'f5lvmqWYj9gJqkIH'),
|
||||
(('0123456789abcd', '3636'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', '3653'), 'OJJ1Khg5HeAYBH1c'),
|
||||
(('0123456789abcd', 'adm'), 'DbPLCFIkHc2SiyDk'),
|
||||
(('0123456789abcd', 'adma'), 'DbPLCFIkHc2SiyDk'),
|
||||
(('0123456789abcd', 'user'), 'WfO2UiTapPkF/FSn'),
|
||||
(('0123456789abcd', 'user1234'), 'WfO2UiTapPkF/FSn'),
|
||||
|
||||
# 15 char password
|
||||
(('0123456789abcde', ''), 'al1e0XFIugTYLai3'),
|
||||
(('0123456789abcde', '3'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '36'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '365'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '3333'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '3636'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', '3653'), 'lYbwBu.f82OIApQB'),
|
||||
(('0123456789abcde', 'adm'), 'KgKx1UQvdR/09i9u'),
|
||||
(('0123456789abcde', 'adma'), 'KgKx1UQvdR/09i9u'),
|
||||
(('0123456789abcde', 'user'), 'qLopkenJ4WBqxaZN'),
|
||||
(('0123456789abcde', 'user1234'), 'qLopkenJ4WBqxaZN'),
|
||||
|
||||
# 16 char password
|
||||
(('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '36'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '365'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '3333'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '3636'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', '3653'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', 'user'), '.7nfVBEIEu4KbF/1'),
|
||||
(('0123456789abcdef', 'user1234'), '.7nfVBEIEu4KbF/1'),
|
||||
]
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# cisco asa
|
||||
#=============================================================================
|
||||
class cisco_asa_test(_PixAsaSharedTest):
|
||||
handler = hash.cisco_asa
|
||||
|
||||
known_correct_hashes = _PixAsaSharedTest.pix_asa_shared_hashes + [
|
||||
#
|
||||
# passlib reference vectors (ASA-specific)
|
||||
#
|
||||
# NOTE: See 'pix_asa_shared_hashes' for general PIX+ASA vectors,
|
||||
# and general notes about the 'passlib reference vectors' test set.
|
||||
#
|
||||
|
||||
# 13 char password
|
||||
# NOTE: past this point, ASA pads to 32 bytes instead of 16
|
||||
# for all cases where user is set (secret + 4 bytes > 16),
|
||||
# but still uses 16 bytes for enable pwds (secret <= 16).
|
||||
# hashes w/ user WON'T match PIX, but "enable" passwords will.
|
||||
(('0123456789abc', ''), 'eacOpB7vE7ZDukSF'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '36'), 'FRV9JG18UBEgX0.O'),
|
||||
(('0123456789abc', '365'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '3333'), 'NmrkP98nT7RAeKZz'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '3636'), 'FRV9JG18UBEgX0.O'), # confirmed ASA 9.6
|
||||
(('0123456789abc', '3653'), 'NIwkusG9hmmMy6ZQ'), # confirmed ASA 9.6
|
||||
(('0123456789abc', 'user'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6
|
||||
(('0123456789abc', 'user1234'), '8Q/FZeam5ai1A47p'), # confirmed ASA 9.6
|
||||
|
||||
# 14 char password
|
||||
(('0123456789abcd', ''), '6r8888iMxEoPdLp4'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3'), 'yxGoujXKPduTVaYB'),
|
||||
(('0123456789abcd', '36'), 'W0jckhnhjnr/DiT/'),
|
||||
(('0123456789abcd', '365'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3333'), 'yxGoujXKPduTVaYB'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3636'), 'W0jckhnhjnr/DiT/'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', '3653'), 'HuVOxfMQNahaoF8u'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'adm'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'adma'), 'RtOmSeoCs4AUdZqZ'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'user'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6
|
||||
(('0123456789abcd', 'user1234'), 'rrucwrcM0h25pr.m'), # confirmed ASA 9.6
|
||||
|
||||
# 15 char password
|
||||
(('0123456789abcde', ''), 'al1e0XFIugTYLai3'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3'), 'nAZrQoHaL.fgrIqt'),
|
||||
(('0123456789abcde', '36'), '2GxIQ6ICE795587X'),
|
||||
(('0123456789abcde', '365'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3333'), 'nAZrQoHaL.fgrIqt'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3636'), '2GxIQ6ICE795587X'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', '3653'), 'QmDsGwCRBbtGEKqM'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'adm'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'adma'), 'Aj2aP0d.nk62wl4m'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'user'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6
|
||||
(('0123456789abcde', 'user1234'), 'etxiXfo.bINJcXI7'), # confirmed ASA 9.6
|
||||
|
||||
# 16 char password
|
||||
(('0123456789abcdef', ''), '.7nfVBEIEu4KbF/1'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '36'), 'GhI8.yFSC5lwoafg'),
|
||||
(('0123456789abcdef', '365'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '3333'), 'Ghdi1IlsswgYzzMH'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '3636'), 'GhI8.yFSC5lwoafg'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', '3653'), 'KFBI6cNQauyY6h/G'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', 'user'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6
|
||||
(('0123456789abcdef', 'user1234'), 'IneB.wc9sfRzLPoh'), # confirmed ASA 9.6
|
||||
|
||||
# 17 char password
|
||||
# NOTE: past this point, ASA pads to 32 bytes instead of 16
|
||||
# for ALL cases, since secret > 16 bytes even for enable pwds;
|
||||
# and so none of these rest here should match PIX.
|
||||
(('0123456789abcdefq', ''), 'bKshl.EN.X3CVFRQ'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '36'), 'JAeTXHs0n30svlaG'),
|
||||
(('0123456789abcdefq', '365'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '3333'), 'USEJbxI6.VY4ecBP'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '3636'), 'JAeTXHs0n30svlaG'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', '3653'), '4fKSSUBHT1ChGqHp'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', 'user'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefq', 'user1234'), '/dwqyD7nGdwSrDwk'), # confirmed ASA 9.6
|
||||
|
||||
# 27 char password
|
||||
(('0123456789abcdefqwertyuiopa', ''), '4wp19zS3OCe.2jt5'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '36'), 'PjUoGqWBKPyV9qOe'),
|
||||
(('0123456789abcdefqwertyuiopa', '365'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '3333'), 'rd/ZMuGTJFIb2BNG'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '3636'), 'PjUoGqWBKPyV9qOe'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', '3653'), 'bfCy6xFAe5O/gzvM'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', 'user'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopa', 'user1234'), 'zynfWw3UtszxLMgL'), # confirmed ASA 9.6
|
||||
|
||||
# 28 char password
|
||||
# NOTE: past this point, ASA stops appending the username AT ALL,
|
||||
# even though there's still room for the first few chars.
|
||||
(('0123456789abcdefqwertyuiopas', ''), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopas', '36'), 'W6nbOddI0SutTK7m'),
|
||||
(('0123456789abcdefqwertyuiopas', '365'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopas', 'user'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopas', 'user1234'), 'W6nbOddI0SutTK7m'), # confirmed ASA 9.6
|
||||
|
||||
# 32 char password
|
||||
# NOTE: this is max size that ASA allows, and throws error for larger
|
||||
(('0123456789abcdefqwertyuiopasdfgh', ''), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopasdfgh', '36'), '5hPT/iC6DnoBxo6a'),
|
||||
(('0123456789abcdefqwertyuiopasdfgh', '365'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopasdfgh', 'user'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
(('0123456789abcdefqwertyuiopasdfgh', 'user1234'), '5hPT/iC6DnoBxo6a'), # confirmed ASA 9.6
|
||||
]
|
||||
|
||||
|
||||
#=============================================================================
|
||||
# cisco type 7
|
||||
#=============================================================================
|
||||
class cisco_type7_test(HandlerCase):
|
||||
handler = hash.cisco_type7
|
||||
salt_bits = 4
|
||||
salt_type = int
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# http://mccltd.net/blog/?p=1034
|
||||
#
|
||||
("secure ", "04480E051A33490E"),
|
||||
|
||||
#
|
||||
# http://insecure.org/sploits/cisco.passwords.html
|
||||
#
|
||||
("Its time to go to lunch!",
|
||||
"153B1F1F443E22292D73212D5300194315591954465A0D0B59"),
|
||||
|
||||
#
|
||||
# http://blog.ioshints.info/2007/11/type-7-decryption-in-cisco-ios.html
|
||||
#
|
||||
("t35t:pa55w0rd", "08351F1B1D431516475E1B54382F"),
|
||||
|
||||
#
|
||||
# http://www.m00nie.com/2011/09/cisco-type-7-password-decryption-and-encryption-with-perl/
|
||||
#
|
||||
("hiImTesting:)", "020E0D7206320A325847071E5F5E"),
|
||||
|
||||
#
|
||||
# http://packetlife.net/forums/thread/54/
|
||||
#
|
||||
("cisco123", "060506324F41584B56"),
|
||||
("cisco123", "1511021F07257A767B"),
|
||||
|
||||
#
|
||||
# source ?
|
||||
#
|
||||
('Supe&8ZUbeRp4SS', "06351A3149085123301517391C501918"),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(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)
|
||||
self.assertRaises(ValueError, handler, salt=100)
|
||||
|
||||
self.assertRaises(TypeError, handler.using, salt='abc')
|
||||
self.assertRaises(ValueError, handler.using, salt=-10)
|
||||
self.assertRaises(ValueError, handler.using, salt=100)
|
||||
with self.assertWarningList("salt/offset must be.*"):
|
||||
subcls = handler.using(salt=100, relaxed=True)
|
||||
self.assertEqual(subcls(use_defaults=True).salt, 52)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
413
venv/Lib/site-packages/passlib/tests/test_handlers_django.py
Normal file
413
venv/Lib/site-packages/passlib/tests/test_handlers_django.py
Normal file
@@ -0,0 +1,413 @@
|
||||
"""passlib.tests.test_handlers_django - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import re
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.utils import repeat_string
|
||||
from passlib.utils.compat import u
|
||||
from passlib.tests.utils import TestCase, HandlerCase, skipUnless, SkipTest
|
||||
from passlib.tests.test_handlers import UPASS_USD, UPASS_TABLE
|
||||
from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION, \
|
||||
check_django_hasher_has_backend
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# django
|
||||
#=============================================================================
|
||||
|
||||
# standard string django uses
|
||||
UPASS_LETMEIN = u('l\xe8tmein')
|
||||
|
||||
def vstr(version):
|
||||
return ".".join(str(e) for e in version)
|
||||
|
||||
class _DjangoHelper(TestCase):
|
||||
"""
|
||||
mixin for HandlerCase subclasses that are testing a hasher
|
||||
which is also present in django.
|
||||
"""
|
||||
__unittest_skip = True
|
||||
|
||||
#: minimum django version where hash alg is present / that we support testing against
|
||||
min_django_version = MIN_DJANGO_VERSION
|
||||
|
||||
#: max django version where hash alg is present
|
||||
#: TODO: for a bunch of the tests below, this is just max version where
|
||||
#: settings.PASSWORD_HASHERS includes it by default -- could add helper to patch
|
||||
#: desired django hasher back in for duration of test.
|
||||
#: XXX: change this to "disabled_in_django_version" instead?
|
||||
max_django_version = None
|
||||
|
||||
def _require_django_support(self):
|
||||
# make sure min django version
|
||||
if DJANGO_VERSION < self.min_django_version:
|
||||
raise self.skipTest("Django >= %s not installed" % vstr(self.min_django_version))
|
||||
if self.max_django_version and DJANGO_VERSION > self.max_django_version:
|
||||
raise self.skipTest("Django <= %s not installed" % vstr(self.max_django_version))
|
||||
|
||||
# make sure django has a backend for specified hasher
|
||||
name = self.handler.django_name
|
||||
if not check_django_hasher_has_backend(name):
|
||||
raise self.skipTest('django hasher %r not available' % name)
|
||||
|
||||
return True
|
||||
|
||||
extra_fuzz_verifiers = HandlerCase.fuzz_verifiers + (
|
||||
"fuzz_verifier_django",
|
||||
)
|
||||
|
||||
def fuzz_verifier_django(self):
|
||||
try:
|
||||
self._require_django_support()
|
||||
except SkipTest:
|
||||
return None
|
||||
from django.contrib.auth.hashers import check_password
|
||||
|
||||
def verify_django(secret, hash):
|
||||
"""django/check_password"""
|
||||
if self.handler.name == "django_bcrypt" and hash.startswith("bcrypt$$2y$"):
|
||||
hash = hash.replace("$$2y$", "$$2a$")
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
return check_password(secret, hash)
|
||||
return verify_django
|
||||
|
||||
def test_90_django_reference(self):
|
||||
"""run known correct hashes through Django's check_password()"""
|
||||
self._require_django_support()
|
||||
# XXX: esp. when it's no longer supported by django,
|
||||
# should verify it's *NOT* recognized
|
||||
from django.contrib.auth.hashers import check_password
|
||||
assert self.known_correct_hashes
|
||||
for secret, hash in self.iter_known_hashes():
|
||||
self.assertTrue(check_password(secret, hash),
|
||||
"secret=%r hash=%r failed to verify" %
|
||||
(secret, hash))
|
||||
self.assertFalse(check_password('x' + secret, hash),
|
||||
"mangled secret=%r hash=%r incorrect verified" %
|
||||
(secret, hash))
|
||||
|
||||
def test_91_django_generation(self):
|
||||
"""test against output of Django's make_password()"""
|
||||
self._require_django_support()
|
||||
# XXX: esp. when it's no longer supported by django,
|
||||
# should verify it's *NOT* recognized
|
||||
from passlib.utils import tick
|
||||
from django.contrib.auth.hashers import make_password
|
||||
name = self.handler.django_name # set for all the django_* handlers
|
||||
end = tick() + self.max_fuzz_time/2
|
||||
generator = self.FuzzHashGenerator(self, self.getRandom())
|
||||
while tick() < end:
|
||||
secret, other = generator.random_password_pair()
|
||||
if not secret: # django rejects empty passwords.
|
||||
continue
|
||||
if isinstance(secret, bytes):
|
||||
secret = secret.decode("utf-8")
|
||||
hash = make_password(secret, hasher=name)
|
||||
self.assertTrue(self.do_identify(hash))
|
||||
self.assertTrue(self.do_verify(secret, hash))
|
||||
self.assertFalse(self.do_verify(other, hash))
|
||||
|
||||
class django_disabled_test(HandlerCase):
|
||||
"""test django_disabled"""
|
||||
handler = hash.django_disabled
|
||||
disabled_contains_salt = True
|
||||
|
||||
known_correct_hashes = [
|
||||
# *everything* should hash to "!", and nothing should verify
|
||||
("password", "!"),
|
||||
("", "!"),
|
||||
(UPASS_TABLE, "!"),
|
||||
]
|
||||
|
||||
known_alternate_hashes = [
|
||||
# django 1.6 appends random alpnum string
|
||||
("!9wa845vn7098ythaehasldkfj", "password", "!"),
|
||||
]
|
||||
|
||||
class django_des_crypt_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_des_crypt"""
|
||||
handler = hash.django_des_crypt
|
||||
max_django_version = (1,9)
|
||||
|
||||
known_correct_hashes = [
|
||||
# ensures only first two digits of salt count.
|
||||
("password", 'crypt$c2$c2M87q...WWcU'),
|
||||
("password", 'crypt$c2e86$c2M87q...WWcU'),
|
||||
("passwordignoreme", 'crypt$c2.AZ$c2M87q...WWcU'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_USD, 'crypt$c2e86$c2hN1Bxd6ZiWs'),
|
||||
(UPASS_TABLE, 'crypt$0.aQs$0.wB.TT0Czvlo'),
|
||||
(u("hell\u00D6"), "crypt$sa$saykDgk3BPZ9E"),
|
||||
|
||||
# prevent regression of issue 22
|
||||
("foo", 'crypt$MNVY.9ajgdvDQ$MNVY.9ajgdvDQ'),
|
||||
]
|
||||
|
||||
known_alternate_hashes = [
|
||||
# ensure django 1.4 empty salt field is accepted;
|
||||
# but that salt field is re-filled (for django 1.0 compatibility)
|
||||
('crypt$$c2M87q...WWcU', "password", 'crypt$c2$c2M87q...WWcU'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
'sha1$aa$bb',
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# checksum too short
|
||||
'crypt$c2$c2M87q',
|
||||
|
||||
# salt must be >2
|
||||
'crypt$f$c2M87q...WWcU',
|
||||
|
||||
# make sure first 2 chars of salt & chk field agree.
|
||||
'crypt$ffe86$c2M87q...WWcU',
|
||||
]
|
||||
|
||||
class django_salted_md5_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_salted_md5"""
|
||||
handler = hash.django_salted_md5
|
||||
max_django_version = (1,9)
|
||||
|
||||
known_correct_hashes = [
|
||||
# test extra large salt
|
||||
("password", 'md5$123abcdef$c8272612932975ee80e8a35995708e80'),
|
||||
|
||||
# test django 1.4 alphanumeric salt
|
||||
("test", 'md5$3OpqnFAHW5CT$54b29300675271049a1ebae07b395e20'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_USD, 'md5$c2e86$92105508419a81a6babfaecf876a2fa0'),
|
||||
(UPASS_TABLE, 'md5$d9eb8$01495b32852bffb27cf5d4394fe7a54c'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
'sha1$aa$bb',
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# checksum too short
|
||||
'md5$aa$bb',
|
||||
]
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_salt_size(self):
|
||||
# workaround for django14 regression --
|
||||
# 1.4 won't accept hashes with empty salt strings, unlike 1.3 and earlier.
|
||||
# looks to be fixed in a future release -- https://code.djangoproject.com/ticket/18144
|
||||
# for now, we avoid salt_size==0 under 1.4
|
||||
handler = self.handler
|
||||
default = handler.default_salt_size
|
||||
assert handler.min_salt_size == 0
|
||||
lower = 1
|
||||
upper = handler.max_salt_size or default*4
|
||||
return self.randintgauss(lower, upper, default, default*.5)
|
||||
|
||||
class django_salted_sha1_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_salted_sha1"""
|
||||
handler = hash.django_salted_sha1
|
||||
max_django_version = (1,9)
|
||||
|
||||
known_correct_hashes = [
|
||||
# test extra large salt
|
||||
("password",'sha1$123abcdef$e4a1877b0e35c47329e7ed7e58014276168a37ba'),
|
||||
|
||||
# test django 1.4 alphanumeric salt
|
||||
("test", 'sha1$bcwHF9Hy8lxS$6b4cfa0651b43161c6f1471ce9523acf1f751ba3'),
|
||||
|
||||
# ensures utf-8 used for unicode
|
||||
(UPASS_USD, 'sha1$c2e86$0f75c5d7fbd100d587c127ef0b693cde611b4ada'),
|
||||
(UPASS_TABLE, 'sha1$6d853$ef13a4d8fb57aed0cb573fe9c82e28dc7fd372d4'),
|
||||
|
||||
# generic password
|
||||
("MyPassword", 'sha1$54123$893cf12e134c3c215f3a76bd50d13f92404a54d3'),
|
||||
]
|
||||
|
||||
known_unidentified_hashes = [
|
||||
'md5$aa$bb',
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# checksum too short
|
||||
'sha1$c2e86$0f75',
|
||||
]
|
||||
|
||||
# reuse custom random_salt_size() helper...
|
||||
FuzzHashGenerator = django_salted_md5_test.FuzzHashGenerator
|
||||
|
||||
class django_pbkdf2_sha256_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_pbkdf2_sha256"""
|
||||
handler = hash.django_pbkdf2_sha256
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom - generated via django 1.4 hasher
|
||||
#
|
||||
('not a password',
|
||||
'pbkdf2_sha256$10000$kjVJaVz6qsnJ$5yPHw3rwJGECpUf70daLGhOrQ5+AMxIJdz1c3bqK1Rs='),
|
||||
(UPASS_TABLE,
|
||||
'pbkdf2_sha256$10000$bEwAfNrH1TlQ$OgYUblFNUX1B8GfMqaCYUK/iHyO0pa7STTDdaEJBuY0='),
|
||||
]
|
||||
|
||||
class django_pbkdf2_sha1_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_pbkdf2_sha1"""
|
||||
handler = hash.django_pbkdf2_sha1
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom - generated via django 1.4 hashers
|
||||
#
|
||||
('not a password',
|
||||
'pbkdf2_sha1$10000$wz5B6WkasRoF$atJmJ1o+XfJxKq1+Nu1f1i57Z5I='),
|
||||
(UPASS_TABLE,
|
||||
'pbkdf2_sha1$10000$KZKWwvqb8BfL$rw5pWsxJEU4JrZAQhHTCO+u0f5Y='),
|
||||
]
|
||||
|
||||
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
|
||||
class django_bcrypt_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_bcrypt"""
|
||||
handler = hash.django_bcrypt
|
||||
# XXX: not sure when this wasn't in default list anymore. somewhere in [2.0 - 2.2]
|
||||
max_django_version = (2, 0)
|
||||
fuzz_salts_need_bcrypt_repair = True
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# just copied and adapted a few test vectors from bcrypt (above),
|
||||
# since django_bcrypt is just a wrapper for the real bcrypt class.
|
||||
#
|
||||
('', 'bcrypt$$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
|
||||
('abcdefghijklmnopqrstuvwxyz',
|
||||
'bcrypt$$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq'),
|
||||
(UPASS_TABLE,
|
||||
'bcrypt$$2a$05$Z17AXnnlpzddNUvnC6cZNOSwMA/8oNiKnHTHTwLlBijfucQQlHjaG'),
|
||||
]
|
||||
|
||||
# NOTE: the following have been cloned from _bcrypt_test()
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# speed up test w/ lower rounds
|
||||
kwds.setdefault("rounds", 4)
|
||||
super(django_bcrypt_test, self).populate_settings(kwds)
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(5, 8, 6, 1)
|
||||
|
||||
def random_ident(self):
|
||||
# omit multi-ident tests, only $2a$ counts for this class
|
||||
# XXX: enable this to check 2a / 2b?
|
||||
return None
|
||||
|
||||
@skipUnless(hash.bcrypt.has_backend(), "no bcrypt backends available")
|
||||
class django_bcrypt_sha256_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_bcrypt_sha256"""
|
||||
handler = hash.django_bcrypt_sha256
|
||||
forbidden_characters = None
|
||||
fuzz_salts_need_bcrypt_repair = True
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# custom - generated via django 1.6 hasher
|
||||
#
|
||||
('',
|
||||
'bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu'),
|
||||
(UPASS_LETMEIN,
|
||||
'bcrypt_sha256$$2a$08$NDjSAIcas.EcoxCRiArvT.MkNiPYVhrsrnJsRkLueZOoV1bsQqlmC'),
|
||||
(UPASS_TABLE,
|
||||
'bcrypt_sha256$$2a$06$kCXUnRFQptGg491siDKNTu8RxjBGSjALHRuvhPYNFsa4Ea5d9M48u'),
|
||||
|
||||
# test >72 chars is hashed correctly -- under bcrypt these hash the same.
|
||||
(repeat_string("abc123",72),
|
||||
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OySmyXA8FoY4PjGizjE1QSDfuL5MXNni'),
|
||||
(repeat_string("abc123",72)+"qwr",
|
||||
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61Ocy0BEz1RK6xslSNi8PlaLX2pe7x/KQG'),
|
||||
(repeat_string("abc123",72)+"xyz",
|
||||
'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OvY2zoRVUa2Pugv2ExVOUT2YmhvxUFUa'),
|
||||
]
|
||||
|
||||
known_malformed_hashers = [
|
||||
# data in django salt field
|
||||
'bcrypt_sha256$xyz$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu',
|
||||
]
|
||||
|
||||
# NOTE: the following have been cloned from _bcrypt_test()
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# speed up test w/ lower rounds
|
||||
kwds.setdefault("rounds", 4)
|
||||
super(django_bcrypt_sha256_test, self).populate_settings(kwds)
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(5, 8, 6, 1)
|
||||
|
||||
def random_ident(self):
|
||||
# omit multi-ident tests, only $2a$ counts for this class
|
||||
# XXX: enable this to check 2a / 2b?
|
||||
return None
|
||||
|
||||
from passlib.tests.test_handlers_argon2 import _base_argon2_test
|
||||
|
||||
@skipUnless(hash.argon2.has_backend(), "no argon2 backends available")
|
||||
class django_argon2_test(HandlerCase, _DjangoHelper):
|
||||
"""test django_bcrypt"""
|
||||
handler = hash.django_argon2
|
||||
|
||||
# NOTE: most of this adapted from _base_argon2_test & argon2pure test
|
||||
|
||||
known_correct_hashes = [
|
||||
# sample test
|
||||
("password", 'argon2$argon2i$v=19$m=256,t=1,p=1$c29tZXNhbHQ$AJFIsNZTMKTAewB4+ETN1A'),
|
||||
|
||||
# sample w/ all parameters different
|
||||
("password", 'argon2$argon2i$v=19$m=380,t=2,p=2$c29tZXNhbHQ$SrssP8n7m/12VWPM8dvNrw'),
|
||||
|
||||
# generated from django 1.10.3
|
||||
(UPASS_LETMEIN, 'argon2$argon2i$v=19$m=512,t=2,p=2$V25jN1l4UUJZWkR1$MxpA1BD2Gh7+D79gaAw6sQ'),
|
||||
]
|
||||
|
||||
def setUpWarnings(self):
|
||||
super(django_argon2_test, self).setUpWarnings()
|
||||
warnings.filterwarnings("ignore", ".*Using argon2pure backend.*")
|
||||
|
||||
def do_stub_encrypt(self, handler=None, **settings):
|
||||
# overriding default since no way to get stub config from argon2._calc_hash()
|
||||
# (otherwise test_21b_max_rounds blocks trying to do max rounds)
|
||||
handler = (handler or self.handler).using(**settings)
|
||||
self = handler.wrapped(use_defaults=True)
|
||||
self.checksum = self._stub_checksum
|
||||
assert self.checksum
|
||||
return handler._wrap_hash(self.to_string())
|
||||
|
||||
def test_03_legacy_hash_workflow(self):
|
||||
# override base method
|
||||
raise self.skipTest("legacy 1.6 workflow not supported")
|
||||
|
||||
class FuzzHashGenerator(_base_argon2_test.FuzzHashGenerator):
|
||||
|
||||
def random_type(self):
|
||||
# override default since django only uses type I (see note in class)
|
||||
return "I"
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(1, 3, 2, 1)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
480
venv/Lib/site-packages/passlib/tests/test_handlers_pbkdf2.py
Normal file
480
venv/Lib/site-packages/passlib/tests/test_handlers_pbkdf2.py
Normal file
@@ -0,0 +1,480 @@
|
||||
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
import warnings
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.utils.compat import u
|
||||
from passlib.tests.utils import TestCase, HandlerCase
|
||||
from passlib.tests.test_handlers import UPASS_WAV
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# ldap_pbkdf2_{digest}
|
||||
#=============================================================================
|
||||
# NOTE: since these are all wrappers for the pbkdf2_{digest} hasehs,
|
||||
# they don't extensive separate testing.
|
||||
|
||||
class ldap_pbkdf2_test(TestCase):
|
||||
|
||||
def test_wrappers(self):
|
||||
"""test ldap pbkdf2 wrappers"""
|
||||
|
||||
self.assertTrue(
|
||||
hash.ldap_pbkdf2_sha1.verify(
|
||||
"password",
|
||||
'{PBKDF2}1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI',
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
hash.ldap_pbkdf2_sha256.verify(
|
||||
"password",
|
||||
'{PBKDF2-SHA256}1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg'
|
||||
'.fJPeq1h/gXXY7acBp9/6c.tmQ'
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
hash.ldap_pbkdf2_sha512.verify(
|
||||
"password",
|
||||
'{PBKDF2-SHA512}1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1'
|
||||
'7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww'
|
||||
)
|
||||
)
|
||||
|
||||
#=============================================================================
|
||||
# pbkdf2 hashes
|
||||
#=============================================================================
|
||||
class atlassian_pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.atlassian_pbkdf2_sha1
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# generated using Jira
|
||||
#
|
||||
("admin", '{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/p'),
|
||||
(UPASS_WAV,
|
||||
"{PKCS5S2}cE9Yq6Am5tQGdHSHhky2XLeOnURwzaLBG2sur7FHKpvy2u0qDn6GcVGRjlmJoIUy"),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# bad char ---\/
|
||||
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy!0IPksHChwoTAVYFrhsgoq8/p'
|
||||
|
||||
# bad size, missing padding
|
||||
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/'
|
||||
|
||||
# bad size, with correct padding
|
||||
'{PKCS5S2}c4xaeTQM0lUieMS3V5voiexyX9XhqC2dBd5ecVy60IPksHChwoTAVYFrhsgoq8/='
|
||||
]
|
||||
|
||||
class pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.pbkdf2_sha1
|
||||
known_correct_hashes = [
|
||||
("password", '$pbkdf2$1212$OB.dtnSEXZK8U5cgxU/GYQ$y5LKPOplRmok7CZp/aqVDVg8zGI'),
|
||||
(UPASS_WAV,
|
||||
'$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc'),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# zero padded rounds field
|
||||
'$pbkdf2$01212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc',
|
||||
|
||||
# empty rounds field
|
||||
'$pbkdf2$$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc',
|
||||
|
||||
# too many field
|
||||
'$pbkdf2$1212$THDqatpidANpadlLeTeOEg$HV3oi1k5C5LQCgG1BMOL.BX4YZc$',
|
||||
]
|
||||
|
||||
class pbkdf2_sha256_test(HandlerCase):
|
||||
handler = hash.pbkdf2_sha256
|
||||
known_correct_hashes = [
|
||||
("password",
|
||||
'$pbkdf2-sha256$1212$4vjV83LKPjQzk31VI4E0Vw$hsYF68OiOUPdDZ1Fg.fJPeq1h/gXXY7acBp9/6c.tmQ'
|
||||
),
|
||||
(UPASS_WAV,
|
||||
'$pbkdf2-sha256$1212$3SABFJGDtyhrQMVt1uABPw$WyaUoqCLgvz97s523nF4iuOqZNbp5Nt8do/cuaa7AiI'
|
||||
),
|
||||
]
|
||||
|
||||
class pbkdf2_sha512_test(HandlerCase):
|
||||
handler = hash.pbkdf2_sha512
|
||||
known_correct_hashes = [
|
||||
("password",
|
||||
'$pbkdf2-sha512$1212$RHY0Fr3IDMSVO/RSZyb5ow$eNLfBK.eVozomMr.1gYa1'
|
||||
'7k9B7KIK25NOEshvhrSX.esqY3s.FvWZViXz4KoLlQI.BzY/YTNJOiKc5gBYFYGww'
|
||||
),
|
||||
(UPASS_WAV,
|
||||
'$pbkdf2-sha512$1212$KkbvoKGsAIcF8IslDR6skQ$8be/PRmd88Ps8fmPowCJt'
|
||||
'tH9G3vgxpG.Krjt3KT.NP6cKJ0V4Prarqf.HBwz0dCkJ6xgWnSj2ynXSV7MlvMa8Q'
|
||||
),
|
||||
]
|
||||
|
||||
class cta_pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.cta_pbkdf2_sha1
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# test vectors from original implementation
|
||||
#
|
||||
(u("hashy the \N{SNOWMAN}"), '$p5k2$1000$ZxK4ZBJCfQg=$jJZVscWtO--p1-xIZl6jhO2LKR0='),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
("password", "$p5k2$1$$h1TDLGSw9ST8UMAPeIE13i0t12c="),
|
||||
(UPASS_WAV,
|
||||
"$p5k2$4321$OTg3NjU0MzIx$jINJrSvZ3LXeIbUdrJkRpN62_WQ="),
|
||||
]
|
||||
|
||||
class dlitz_pbkdf2_sha1_test(HandlerCase):
|
||||
handler = hash.dlitz_pbkdf2_sha1
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# test vectors from original implementation
|
||||
#
|
||||
('cloadm', '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'),
|
||||
('gnu', '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'),
|
||||
('dcl', '$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL'),
|
||||
('spam', '$p5k2$3e8$H0NX9mT/$wk/sE8vv6OMKuMaqazCJYDSUhWY9YB2J'),
|
||||
(UPASS_WAV,
|
||||
'$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'),
|
||||
]
|
||||
|
||||
class grub_pbkdf2_sha512_test(HandlerCase):
|
||||
handler = hash.grub_pbkdf2_sha512
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# test vectors generated from cmd line tool
|
||||
#
|
||||
|
||||
# salt=32 bytes
|
||||
(UPASS_WAV,
|
||||
'grub.pbkdf2.sha512.10000.BCAC1CEC5E4341C8C511C529'
|
||||
'7FA877BE91C2817B32A35A3ECF5CA6B8B257F751.6968526A'
|
||||
'2A5B1AEEE0A29A9E057336B48D388FFB3F600233237223C21'
|
||||
'04DE1752CEC35B0DD1ED49563398A282C0F471099C2803FBA'
|
||||
'47C7919CABC43192C68F60'),
|
||||
|
||||
# salt=64 bytes
|
||||
('toomanysecrets',
|
||||
'grub.pbkdf2.sha512.10000.9B436BB6978682363D5C449B'
|
||||
'BEAB322676946C632208BC1294D51F47174A9A3B04A7E4785'
|
||||
'986CD4EA7470FAB8FE9F6BD522D1FC6C51109A8596FB7AD48'
|
||||
'7C4493.0FE5EF169AFFCB67D86E2581B1E251D88C777B98BA'
|
||||
'2D3256ECC9F765D84956FC5CA5C4B6FD711AA285F0A04DCF4'
|
||||
'634083F9A20F4B6F339A52FBD6BED618E527B'),
|
||||
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# scram hash
|
||||
#=============================================================================
|
||||
class scram_test(HandlerCase):
|
||||
handler = hash.scram
|
||||
|
||||
# TODO: need a bunch more reference vectors from some real
|
||||
# SCRAM transactions.
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# taken from example in SCRAM specification (rfc 5802)
|
||||
#
|
||||
('pencil', '$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# same as 5802 example hash, but with sha-256 & sha-512 added.
|
||||
('pencil', '$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'),
|
||||
|
||||
# test unicode passwords & saslprep (all the passwords below
|
||||
# should normalize to the same value: 'IX \xE0')
|
||||
(u('IX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$'
|
||||
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
|
||||
(u('\u2168\u3000a\u0300'), '$scram$6400$0BojBCBE6P2/N4bQ$'
|
||||
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
|
||||
(u('\u00ADIX \xE0'), '$scram$6400$0BojBCBE6P2/N4bQ$'
|
||||
'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
|
||||
]
|
||||
|
||||
known_malformed_hashes = [
|
||||
# zero-padding in rounds
|
||||
'$scram$04096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# non-digit in rounds
|
||||
'$scram$409A$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# bad char in salt ---\/
|
||||
'$scram$4096$QSXCR.Q6sek8bf9-$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# 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',
|
||||
|
||||
# too many chars in alg name
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'shaxxx-190=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# missing sha-1 alg
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha-256=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
|
||||
# non-iana name
|
||||
'$scram$4096$QSXCR.Q6sek8bf92$sha1=HZbuOlKbWl.eR8AfIposuKbhX30',
|
||||
]
|
||||
|
||||
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):
|
||||
"""test parsing of 'algs' setting"""
|
||||
defaults = dict(salt=b'A'*10, rounds=1000)
|
||||
def parse(algs, **kwds):
|
||||
for k in defaults:
|
||||
kwds.setdefault(k, defaults[k])
|
||||
return self.handler(algs=algs, **kwds).algs
|
||||
|
||||
# None -> default list
|
||||
self.assertEqual(parse(None, use_defaults=True), hash.scram.default_algs)
|
||||
self.assertRaises(TypeError, parse, None)
|
||||
|
||||
# strings should be parsed
|
||||
self.assertEqual(parse("sha1"), ["sha-1"])
|
||||
self.assertEqual(parse("sha1, sha256, md5"), ["md5","sha-1","sha-256"])
|
||||
|
||||
# lists should be normalized
|
||||
self.assertEqual(parse(["sha-1","sha256"]), ["sha-1","sha-256"])
|
||||
|
||||
# sha-1 required
|
||||
self.assertRaises(ValueError, parse, ["sha-256"])
|
||||
self.assertRaises(ValueError, parse, algs=[], use_defaults=True)
|
||||
|
||||
# alg names must be < 10 chars
|
||||
self.assertRaises(ValueError, parse, ["sha-1","shaxxx-190"])
|
||||
|
||||
# alg & checksum mutually exclusive.
|
||||
self.assertRaises(RuntimeError, parse, ['sha-1'],
|
||||
checksum={"sha-1": b"\x00"*20})
|
||||
|
||||
def test_90_checksums(self):
|
||||
"""test internal parsing of 'checksum' keyword"""
|
||||
# check non-bytes checksum values are rejected
|
||||
self.assertRaises(TypeError, self.handler, use_defaults=True,
|
||||
checksum={'sha-1': u('X')*20})
|
||||
|
||||
# check sha-1 is required
|
||||
self.assertRaises(ValueError, self.handler, use_defaults=True,
|
||||
checksum={'sha-256': b'X'*32})
|
||||
|
||||
# XXX: anything else that's not tested by the other code already?
|
||||
|
||||
def test_91_extract_digest_info(self):
|
||||
"""test scram.extract_digest_info()"""
|
||||
edi = self.handler.extract_digest_info
|
||||
|
||||
# return appropriate value or throw KeyError
|
||||
h = "$scram$10$AAAAAA$sha-1=AQ,bbb=Ag,ccc=Aw"
|
||||
s = b'\x00'*4
|
||||
self.assertEqual(edi(h,"SHA1"), (s,10, b'\x01'))
|
||||
self.assertEqual(edi(h,"bbb"), (s,10, b'\x02'))
|
||||
self.assertEqual(edi(h,"ccc"), (s,10, b'\x03'))
|
||||
self.assertRaises(KeyError, edi, h, "ddd")
|
||||
|
||||
# config strings should cause value error.
|
||||
c = "$scram$10$....$sha-1,bbb,ccc"
|
||||
self.assertRaises(ValueError, edi, c, "sha-1")
|
||||
self.assertRaises(ValueError, edi, c, "bbb")
|
||||
self.assertRaises(ValueError, edi, c, "ddd")
|
||||
|
||||
def test_92_extract_digest_algs(self):
|
||||
"""test scram.extract_digest_algs()"""
|
||||
eda = self.handler.extract_digest_algs
|
||||
|
||||
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,'
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ'),
|
||||
["sha-1","sha-256","sha-512"])
|
||||
|
||||
def test_93_derive_digest(self):
|
||||
"""test scram.derive_digest()"""
|
||||
# NOTE: this just does a light test, since derive_digest
|
||||
# is used by hash / verify, and is tested pretty well via those.
|
||||
hash = self.handler.derive_digest
|
||||
|
||||
# check various encodings of password work.
|
||||
s1 = b'\x01\x02\x03'
|
||||
d1 = b'\xb2\xfb\xab\x82[tNuPnI\x8aZZ\x19\x87\xcen\xe9\xd3'
|
||||
self.assertEqual(hash(u("\u2168"), s1, 1000, 'sha-1'), d1)
|
||||
self.assertEqual(hash(b"\xe2\x85\xa8", s1, 1000, 'SHA-1'), d1)
|
||||
self.assertEqual(hash(u("IX"), s1, 1000, 'sha1'), d1)
|
||||
self.assertEqual(hash(b"IX", s1, 1000, 'SHA1'), d1)
|
||||
|
||||
# check algs
|
||||
self.assertEqual(hash("IX", s1, 1000, 'md5'),
|
||||
b'3\x19\x18\xc0\x1c/\xa8\xbf\xe4\xa3\xc2\x8eM\xe8od')
|
||||
self.assertRaises(ValueError, hash, "IX", s1, 1000, 'sha-666')
|
||||
|
||||
# check rounds
|
||||
self.assertRaises(ValueError, hash, "IX", s1, 0, 'sha-1')
|
||||
|
||||
# unicode salts accepted as of passlib 1.7 (previous caused TypeError)
|
||||
self.assertEqual(hash(u("IX"), s1.decode("latin-1"), 1000, 'sha1'), d1)
|
||||
|
||||
def test_94_saslprep(self):
|
||||
"""test hash/verify use saslprep"""
|
||||
# NOTE: this just does a light test that saslprep() is being
|
||||
# called in various places, relying in saslpreps()'s tests
|
||||
# to verify full normalization behavior.
|
||||
|
||||
# hash unnormalized
|
||||
h = self.do_encrypt(u("I\u00ADX"))
|
||||
self.assertTrue(self.do_verify(u("IX"), h))
|
||||
self.assertTrue(self.do_verify(u("\u2168"), h))
|
||||
|
||||
# hash normalized
|
||||
h = self.do_encrypt(u("\xF3"))
|
||||
self.assertTrue(self.do_verify(u("o\u0301"), h))
|
||||
self.assertTrue(self.do_verify(u("\u200Do\u0301"), h))
|
||||
|
||||
# throws error if forbidden char provided
|
||||
self.assertRaises(ValueError, self.do_encrypt, u("\uFDD0"))
|
||||
self.assertRaises(ValueError, self.do_verify, u("\uFDD0"), h)
|
||||
|
||||
def test_94_using_w_default_algs(self, param="default_algs"):
|
||||
"""using() -- 'default_algs' parameter"""
|
||||
# create subclass
|
||||
handler = self.handler
|
||||
orig = list(handler.default_algs) # in case it's modified in place
|
||||
subcls = handler.using(**{param: "sha1,md5"})
|
||||
|
||||
# shouldn't have changed handler
|
||||
self.assertEqual(handler.default_algs, orig)
|
||||
|
||||
# should have own set
|
||||
self.assertEqual(subcls.default_algs, ["md5", "sha-1"])
|
||||
|
||||
# test hash output
|
||||
h1 = subcls.hash("dummy")
|
||||
self.assertEqual(handler.extract_digest_algs(h1), ["md5", "sha-1"])
|
||||
|
||||
def test_94_using_w_algs(self):
|
||||
"""using() -- 'algs' parameter"""
|
||||
self.test_94_using_w_default_algs(param="algs")
|
||||
|
||||
def test_94_needs_update_algs(self):
|
||||
"""needs_update() -- algs setting"""
|
||||
handler1 = self.handler.using(algs="sha1,md5")
|
||||
|
||||
# shouldn't need update, has same algs
|
||||
h1 = handler1.hash("dummy")
|
||||
self.assertFalse(handler1.needs_update(h1))
|
||||
|
||||
# *currently* shouldn't need update, has superset of algs required by handler2
|
||||
# (may change this policy)
|
||||
handler2 = handler1.using(algs="sha1")
|
||||
self.assertFalse(handler2.needs_update(h1))
|
||||
|
||||
# should need update, doesn't have all algs required by handler3
|
||||
handler3 = handler1.using(algs="sha1,sha256")
|
||||
self.assertTrue(handler3.needs_update(h1))
|
||||
|
||||
def test_95_context_algs(self):
|
||||
"""test handling of 'algs' in context object"""
|
||||
handler = self.handler
|
||||
from passlib.context import CryptContext
|
||||
c1 = CryptContext(["scram"], scram__algs="sha1,md5")
|
||||
|
||||
h = c1.hash("dummy")
|
||||
self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"])
|
||||
self.assertFalse(c1.needs_update(h))
|
||||
|
||||
c2 = c1.copy(scram__algs="sha1")
|
||||
self.assertFalse(c2.needs_update(h))
|
||||
|
||||
c2 = c1.copy(scram__algs="sha1,sha256")
|
||||
self.assertTrue(c2.needs_update(h))
|
||||
|
||||
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)
|
||||
|
||||
# reference
|
||||
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVY,'
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
|
||||
self.assertTrue(vfull('pencil', h))
|
||||
self.assertFalse(vfull('tape', h))
|
||||
|
||||
# catch truncated digests.
|
||||
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhV,' # -1 char
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
|
||||
self.assertRaises(ValueError, vfull, 'pencil', h)
|
||||
|
||||
# catch padded digests.
|
||||
h = ('$scram$4096$QSXCR.Q6sek8bf92$'
|
||||
'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
|
||||
'sha-256=qXUXrlcvnaxxWG00DdRgVioR2gnUpuX5r.3EZ1rdhVYa,' # +1 char
|
||||
'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
|
||||
'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
|
||||
self.assertRaises(ValueError, vfull, 'pencil', h)
|
||||
|
||||
# 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,' # '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)
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
111
venv/Lib/site-packages/passlib/tests/test_handlers_scrypt.py
Normal file
111
venv/Lib/site-packages/passlib/tests/test_handlers_scrypt.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""passlib.tests.test_handlers - tests for passlib hash algorithms"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash
|
||||
from passlib.tests.utils import HandlerCase, TEST_MODE
|
||||
from passlib.tests.test_handlers import UPASS_TABLE, PASS_TABLE_UTF8
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# scrypt hash
|
||||
#=============================================================================
|
||||
class _scrypt_test(HandlerCase):
|
||||
handler = hash.scrypt
|
||||
|
||||
known_correct_hashes = [
|
||||
#
|
||||
# excepted from test vectors from scrypt whitepaper
|
||||
# (http://www.tarsnap.com/scrypt/scrypt.pdf, appendix b),
|
||||
# and encoded using passlib's custom format
|
||||
#
|
||||
|
||||
# salt=b""
|
||||
("", "$scrypt$ln=4,r=1,p=1$$d9ZXYjhleyA7GcpCwYoEl/FrSETjB0ro39/6P+3iFEI"),
|
||||
|
||||
# salt=b"NaCl"
|
||||
("password", "$scrypt$ln=10,r=8,p=16$TmFDbA$/bq+HJ00cgB4VucZDQHp/nxq18vII3gw53N2Y0s3MWI"),
|
||||
|
||||
#
|
||||
# custom
|
||||
#
|
||||
|
||||
# simple test
|
||||
("test", '$scrypt$ln=8,r=8,p=1$wlhLyXmP8b53bm1NKYVQqg$mTpvG8lzuuDk+DWz8HZIB6Vum6erDuUm0As5yU+VxWA'),
|
||||
|
||||
# different block value
|
||||
("password", '$scrypt$ln=8,r=2,p=1$dO6d0xoDoLT2PofQGoNQag$g/Wf2A0vhHhaJM+addK61QPBthSmYB6uVTtQzh8CM3o'),
|
||||
|
||||
# different rounds
|
||||
(UPASS_TABLE, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'),
|
||||
|
||||
# alt encoding
|
||||
(PASS_TABLE_UTF8, '$scrypt$ln=7,r=8,p=1$jjGmtDamdA4BQAjBeA9BSA$OiWRHhQtpDx7M/793x6UXK14AD512jg/qNm/hkWZG4M'),
|
||||
|
||||
# diff block & parallel counts as well
|
||||
("nacl", '$scrypt$ln=1,r=4,p=2$yhnD+J+Tci4lZCwFgHCuVQ$fAsEWmxSHuC0cHKMwKVFPzrQukgvK09Sj+NueTSxKds')
|
||||
]
|
||||
|
||||
if TEST_MODE("full"):
|
||||
# add some hashes with larger rounds value.
|
||||
known_correct_hashes.extend([
|
||||
#
|
||||
# from scrypt whitepaper
|
||||
#
|
||||
|
||||
# salt=b"SodiumChloride"
|
||||
("pleaseletmein", "$scrypt$ln=14,r=8,p=1$U29kaXVtQ2hsb3JpZGU"
|
||||
"$cCO9yzr9c0hGHAbNgf046/2o+7qQT44+qbVD9lRdofI"),
|
||||
|
||||
#
|
||||
# openwall format (https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt)
|
||||
#
|
||||
("pleaseletmein",
|
||||
"$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D"),
|
||||
|
||||
])
|
||||
|
||||
known_malformed_hashes = [
|
||||
# missing 'p' value
|
||||
'$scrypt$ln=10,r=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
|
||||
# rounds too low
|
||||
'$scrypt$ln=0,r=1,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
|
||||
# invalid block size
|
||||
'$scrypt$ln=10,r=A,p=1$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
|
||||
# r*p too large
|
||||
'$scrypt$ln=10,r=134217728,p=8$wvif8/4fg1Cq9V7L2dv73w$bJcLia1lyfQ1X2x0xflehwVXPzWIUQWWdnlGwfVzBeQ',
|
||||
]
|
||||
|
||||
def setUpWarnings(self):
|
||||
super(_scrypt_test, self).setUpWarnings()
|
||||
warnings.filterwarnings("ignore", ".*using builtin scrypt backend.*")
|
||||
|
||||
def populate_settings(self, kwds):
|
||||
# builtin is still just way too slow.
|
||||
if self.backend == "builtin":
|
||||
kwds.setdefault("rounds", 6)
|
||||
super(_scrypt_test, self).populate_settings(kwds)
|
||||
|
||||
class FuzzHashGenerator(HandlerCase.FuzzHashGenerator):
|
||||
|
||||
def random_rounds(self):
|
||||
# decrease default rounds for fuzz testing to speed up volume.
|
||||
return self.randintgauss(4, 10, 6, 1)
|
||||
|
||||
# create test cases for specific backends
|
||||
scrypt_stdlib_test = _scrypt_test.create_backend_case("stdlib")
|
||||
scrypt_scrypt_test = _scrypt_test.create_backend_case("scrypt")
|
||||
scrypt_builtin_test = _scrypt_test.create_backend_case("builtin")
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
97
venv/Lib/site-packages/passlib/tests/test_hosts.py
Normal file
97
venv/Lib/site-packages/passlib/tests/test_hosts.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""test passlib.hosts"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hosts, hash as hashmod
|
||||
from passlib.utils import unix_crypt_schemes
|
||||
from passlib.tests.utils import TestCase
|
||||
# module
|
||||
|
||||
#=============================================================================
|
||||
# test predefined app contexts
|
||||
#=============================================================================
|
||||
class HostsTest(TestCase):
|
||||
"""perform general tests to make sure contexts work"""
|
||||
# NOTE: these tests are not really comprehensive,
|
||||
# since they would do little but duplicate
|
||||
# the presets in apps.py
|
||||
#
|
||||
# they mainly try to ensure no typos
|
||||
# or dynamic behavior foul-ups.
|
||||
|
||||
def check_unix_disabled(self, ctx):
|
||||
for hash in [
|
||||
"",
|
||||
"!",
|
||||
"*",
|
||||
"!$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0",
|
||||
]:
|
||||
self.assertEqual(ctx.identify(hash), 'unix_disabled')
|
||||
self.assertFalse(ctx.verify('test', hash))
|
||||
|
||||
def test_linux_context(self):
|
||||
ctx = hosts.linux_context
|
||||
for hash in [
|
||||
('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6'
|
||||
'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751'),
|
||||
('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny'
|
||||
'xDGgMlDcOsfaI17'),
|
||||
'$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0',
|
||||
'kAJJz.Rwp0A/I',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
self.check_unix_disabled(ctx)
|
||||
|
||||
def test_bsd_contexts(self):
|
||||
for ctx in [
|
||||
hosts.freebsd_context,
|
||||
hosts.openbsd_context,
|
||||
hosts.netbsd_context,
|
||||
]:
|
||||
for hash in [
|
||||
'$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0',
|
||||
'kAJJz.Rwp0A/I',
|
||||
]:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
h1 = "$2a$04$yjDgE74RJkeqC0/1NheSSOrvKeu9IbKDpcQf/Ox3qsrRS/Kw42qIS"
|
||||
if hashmod.bcrypt.has_backend():
|
||||
self.assertTrue(ctx.verify("test", h1))
|
||||
else:
|
||||
self.assertEqual(ctx.identify(h1), "bcrypt")
|
||||
self.check_unix_disabled(ctx)
|
||||
|
||||
def test_host_context(self):
|
||||
ctx = getattr(hosts, "host_context", None)
|
||||
if not ctx:
|
||||
return self.skipTest("host_context not available on this platform")
|
||||
|
||||
# validate schemes is non-empty,
|
||||
# and contains unix_disabled + at least one real scheme
|
||||
schemes = list(ctx.schemes())
|
||||
self.assertTrue(schemes, "appears to be unix system, but no known schemes supported by crypt")
|
||||
self.assertTrue('unix_disabled' in schemes)
|
||||
schemes.remove("unix_disabled")
|
||||
self.assertTrue(schemes, "should have schemes beside fallback scheme")
|
||||
self.assertTrue(set(unix_crypt_schemes).issuperset(schemes))
|
||||
|
||||
# check for hash support
|
||||
self.check_unix_disabled(ctx)
|
||||
for scheme, hash in [
|
||||
("sha512_crypt", ('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6'
|
||||
'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751')),
|
||||
("sha256_crypt", ('$5$rounds=31817$iZGmlyBQ99JSB5n6$p4E.pdPBWx19OajgjLRiOW0itGny'
|
||||
'xDGgMlDcOsfaI17')),
|
||||
("md5_crypt", '$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0'),
|
||||
("des_crypt", 'kAJJz.Rwp0A/I'),
|
||||
]:
|
||||
if scheme in schemes:
|
||||
self.assertTrue(ctx.verify("test", hash))
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
205
venv/Lib/site-packages/passlib/tests/test_pwd.py
Normal file
205
venv/Lib/site-packages/passlib/tests/test_pwd.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""passlib.tests -- tests for passlib.pwd"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
# core
|
||||
import itertools
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.tests.utils import TestCase
|
||||
# local
|
||||
__all__ = [
|
||||
"UtilsTest",
|
||||
"GenerateTest",
|
||||
"StrengthTest",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
class UtilsTest(TestCase):
|
||||
"""test internal utilities"""
|
||||
descriptionPrefix = "passlib.pwd"
|
||||
|
||||
def test_self_info_rate(self):
|
||||
"""_self_info_rate()"""
|
||||
from passlib.pwd import _self_info_rate
|
||||
|
||||
self.assertEqual(_self_info_rate(""), 0)
|
||||
|
||||
self.assertEqual(_self_info_rate("a" * 8), 0)
|
||||
|
||||
self.assertEqual(_self_info_rate("ab"), 1)
|
||||
self.assertEqual(_self_info_rate("ab" * 8), 1)
|
||||
|
||||
self.assertEqual(_self_info_rate("abcd"), 2)
|
||||
self.assertEqual(_self_info_rate("abcd" * 8), 2)
|
||||
self.assertAlmostEqual(_self_info_rate("abcdaaaa"), 1.5488, places=4)
|
||||
|
||||
# def test_total_self_info(self):
|
||||
# """_total_self_info()"""
|
||||
# from passlib.pwd import _total_self_info
|
||||
#
|
||||
# self.assertEqual(_total_self_info(""), 0)
|
||||
#
|
||||
# self.assertEqual(_total_self_info("a" * 8), 0)
|
||||
#
|
||||
# self.assertEqual(_total_self_info("ab"), 2)
|
||||
# self.assertEqual(_total_self_info("ab" * 8), 16)
|
||||
#
|
||||
# self.assertEqual(_total_self_info("abcd"), 8)
|
||||
# self.assertEqual(_total_self_info("abcd" * 8), 64)
|
||||
# self.assertAlmostEqual(_total_self_info("abcdaaaa"), 12.3904, places=4)
|
||||
|
||||
#=============================================================================
|
||||
# word generation
|
||||
#=============================================================================
|
||||
|
||||
# import subject
|
||||
from passlib.pwd import genword, default_charsets
|
||||
ascii_62 = default_charsets['ascii_62']
|
||||
hex = default_charsets['hex']
|
||||
|
||||
class WordGeneratorTest(TestCase):
|
||||
"""test generation routines"""
|
||||
descriptionPrefix = "passlib.pwd.genword()"
|
||||
|
||||
def setUp(self):
|
||||
super(WordGeneratorTest, self).setUp()
|
||||
|
||||
# patch some RNG references so they're reproducible.
|
||||
from passlib.pwd import SequenceGenerator
|
||||
self.patchAttr(SequenceGenerator, "rng",
|
||||
self.getRandom("pwd generator"))
|
||||
|
||||
def assertResultContents(self, results, count, chars, unique=True):
|
||||
"""check result list matches expected count & charset"""
|
||||
self.assertEqual(len(results), count)
|
||||
if unique:
|
||||
if unique is True:
|
||||
unique = count
|
||||
self.assertEqual(len(set(results)), unique)
|
||||
self.assertEqual(set("".join(results)), set(chars))
|
||||
|
||||
def test_general(self):
|
||||
"""general behavior"""
|
||||
|
||||
# basic usage
|
||||
result = genword()
|
||||
self.assertEqual(len(result), 9)
|
||||
|
||||
# malformed keyword should have useful error.
|
||||
self.assertRaisesRegex(TypeError, "(?i)unexpected keyword.*badkwd", genword, badkwd=True)
|
||||
|
||||
def test_returns(self):
|
||||
"""'returns' keyword"""
|
||||
# returns=int option
|
||||
results = genword(returns=5000)
|
||||
self.assertResultContents(results, 5000, ascii_62)
|
||||
|
||||
# returns=iter option
|
||||
gen = genword(returns=iter)
|
||||
results = [next(gen) for _ in range(5000)]
|
||||
self.assertResultContents(results, 5000, ascii_62)
|
||||
|
||||
# invalid returns option
|
||||
self.assertRaises(TypeError, genword, returns='invalid-type')
|
||||
|
||||
def test_charset(self):
|
||||
"""'charset' & 'chars' options"""
|
||||
# charset option
|
||||
results = genword(charset="hex", returns=5000)
|
||||
self.assertResultContents(results, 5000, hex)
|
||||
|
||||
# chars option
|
||||
# there are 3**3=27 possible combinations
|
||||
results = genword(length=3, chars="abc", returns=5000)
|
||||
self.assertResultContents(results, 5000, "abc", unique=27)
|
||||
|
||||
# chars + charset
|
||||
self.assertRaises(TypeError, genword, chars='abc', charset='hex')
|
||||
|
||||
# TODO: test rng option
|
||||
|
||||
#=============================================================================
|
||||
# phrase generation
|
||||
#=============================================================================
|
||||
|
||||
# import subject
|
||||
from passlib.pwd import genphrase
|
||||
simple_words = ["alpha", "beta", "gamma"]
|
||||
|
||||
class PhraseGeneratorTest(TestCase):
|
||||
"""test generation routines"""
|
||||
descriptionPrefix = "passlib.pwd.genphrase()"
|
||||
|
||||
def assertResultContents(self, results, count, words, unique=True, sep=" "):
|
||||
"""check result list matches expected count & charset"""
|
||||
self.assertEqual(len(results), count)
|
||||
if unique:
|
||||
if unique is True:
|
||||
unique = count
|
||||
self.assertEqual(len(set(results)), unique)
|
||||
out = set(itertools.chain.from_iterable(elem.split(sep) for elem in results))
|
||||
self.assertEqual(out, set(words))
|
||||
|
||||
def test_general(self):
|
||||
"""general behavior"""
|
||||
|
||||
# basic usage
|
||||
result = genphrase()
|
||||
self.assertEqual(len(result.split(" ")), 4) # 48 / log(7776, 2) ~= 3.7 -> 4
|
||||
|
||||
# malformed keyword should have useful error.
|
||||
self.assertRaisesRegex(TypeError, "(?i)unexpected keyword.*badkwd", genphrase, badkwd=True)
|
||||
|
||||
def test_entropy(self):
|
||||
"""'length' & 'entropy' keywords"""
|
||||
|
||||
# custom entropy
|
||||
result = genphrase(entropy=70)
|
||||
self.assertEqual(len(result.split(" ")), 6) # 70 / log(7776, 2) ~= 5.4 -> 6
|
||||
|
||||
# custom length
|
||||
result = genphrase(length=3)
|
||||
self.assertEqual(len(result.split(" ")), 3)
|
||||
|
||||
# custom length < entropy
|
||||
result = genphrase(length=3, entropy=48)
|
||||
self.assertEqual(len(result.split(" ")), 4)
|
||||
|
||||
# custom length > entropy
|
||||
result = genphrase(length=4, entropy=12)
|
||||
self.assertEqual(len(result.split(" ")), 4)
|
||||
|
||||
def test_returns(self):
|
||||
"""'returns' keyword"""
|
||||
# returns=int option
|
||||
results = genphrase(returns=1000, words=simple_words)
|
||||
self.assertResultContents(results, 1000, simple_words)
|
||||
|
||||
# returns=iter option
|
||||
gen = genphrase(returns=iter, words=simple_words)
|
||||
results = [next(gen) for _ in range(1000)]
|
||||
self.assertResultContents(results, 1000, simple_words)
|
||||
|
||||
# invalid returns option
|
||||
self.assertRaises(TypeError, genphrase, returns='invalid-type')
|
||||
|
||||
def test_wordset(self):
|
||||
"""'wordset' & 'words' options"""
|
||||
# wordset option
|
||||
results = genphrase(words=simple_words, returns=5000)
|
||||
self.assertResultContents(results, 5000, simple_words)
|
||||
|
||||
# words option
|
||||
results = genphrase(length=3, words=simple_words, returns=5000)
|
||||
self.assertResultContents(results, 5000, simple_words, unique=3**3)
|
||||
|
||||
# words + wordset
|
||||
self.assertRaises(TypeError, genphrase, words=simple_words, wordset='bip39')
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
228
venv/Lib/site-packages/passlib/tests/test_registry.py
Normal file
228
venv/Lib/site-packages/passlib/tests/test_registry.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""tests for passlib.hash -- (c) Assurance Technologies 2003-2009"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement
|
||||
# core
|
||||
from logging import getLogger
|
||||
import warnings
|
||||
import sys
|
||||
# site
|
||||
# pkg
|
||||
from passlib import hash, registry, exc
|
||||
from passlib.registry import register_crypt_handler, register_crypt_handler_path, \
|
||||
get_crypt_handler, list_crypt_handlers, _unload_handler_name as unload_handler_name
|
||||
import passlib.utils.handlers as uh
|
||||
from passlib.tests.utils import TestCase
|
||||
# module
|
||||
log = getLogger(__name__)
|
||||
|
||||
#=============================================================================
|
||||
# dummy handlers
|
||||
#
|
||||
# NOTE: these are defined outside of test case
|
||||
# since they're used by test_register_crypt_handler_path(),
|
||||
# which needs them to be available as module globals.
|
||||
#=============================================================================
|
||||
class dummy_0(uh.StaticHandler):
|
||||
name = "dummy_0"
|
||||
|
||||
class alt_dummy_0(uh.StaticHandler):
|
||||
name = "dummy_0"
|
||||
|
||||
dummy_x = 1
|
||||
|
||||
#=============================================================================
|
||||
# test registry
|
||||
#=============================================================================
|
||||
class RegistryTest(TestCase):
|
||||
|
||||
descriptionPrefix = "passlib.registry"
|
||||
|
||||
def setUp(self):
|
||||
super(RegistryTest, self).setUp()
|
||||
|
||||
# backup registry state & restore it after test.
|
||||
locations = dict(registry._locations)
|
||||
handlers = dict(registry._handlers)
|
||||
def restore():
|
||||
registry._locations.clear()
|
||||
registry._locations.update(locations)
|
||||
registry._handlers.clear()
|
||||
registry._handlers.update(handlers)
|
||||
self.addCleanup(restore)
|
||||
|
||||
def test_hash_proxy(self):
|
||||
"""test passlib.hash proxy object"""
|
||||
# check dir works
|
||||
dir(hash)
|
||||
|
||||
# check repr works
|
||||
repr(hash)
|
||||
|
||||
# check non-existent attrs raise error
|
||||
self.assertRaises(AttributeError, getattr, hash, 'fooey')
|
||||
|
||||
# GAE tries to set __loader__,
|
||||
# make sure that doesn't call register_crypt_handler.
|
||||
old = getattr(hash, "__loader__", None)
|
||||
test = object()
|
||||
hash.__loader__ = test
|
||||
self.assertIs(hash.__loader__, test)
|
||||
if old is None:
|
||||
del hash.__loader__
|
||||
self.assertFalse(hasattr(hash, "__loader__"))
|
||||
else:
|
||||
hash.__loader__ = old
|
||||
self.assertIs(hash.__loader__, old)
|
||||
|
||||
# check storing attr calls register_crypt_handler
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
hash.dummy_1 = dummy_1
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
# check storing under wrong name results in error
|
||||
self.assertRaises(ValueError, setattr, hash, "dummy_1x", dummy_1)
|
||||
|
||||
def test_register_crypt_handler_path(self):
|
||||
"""test register_crypt_handler_path()"""
|
||||
# NOTE: this messes w/ internals of registry, shouldn't be used publically.
|
||||
paths = registry._locations
|
||||
|
||||
# check namespace is clear
|
||||
self.assertTrue('dummy_0' not in paths)
|
||||
self.assertFalse(hasattr(hash, 'dummy_0'))
|
||||
|
||||
# check invalid names are rejected
|
||||
self.assertRaises(ValueError, register_crypt_handler_path,
|
||||
"dummy_0", ".test_registry")
|
||||
self.assertRaises(ValueError, register_crypt_handler_path,
|
||||
"dummy_0", __name__ + ":dummy_0:xxx")
|
||||
self.assertRaises(ValueError, register_crypt_handler_path,
|
||||
"dummy_0", __name__ + ":dummy_0.xxx")
|
||||
|
||||
# try lazy load
|
||||
register_crypt_handler_path('dummy_0', __name__)
|
||||
self.assertTrue('dummy_0' in list_crypt_handlers())
|
||||
self.assertTrue('dummy_0' not in list_crypt_handlers(loaded_only=True))
|
||||
self.assertIs(hash.dummy_0, dummy_0)
|
||||
self.assertTrue('dummy_0' in list_crypt_handlers(loaded_only=True))
|
||||
unload_handler_name('dummy_0')
|
||||
|
||||
# try lazy load w/ alt
|
||||
register_crypt_handler_path('dummy_0', __name__ + ':alt_dummy_0')
|
||||
self.assertIs(hash.dummy_0, alt_dummy_0)
|
||||
unload_handler_name('dummy_0')
|
||||
|
||||
# check lazy load w/ wrong type fails
|
||||
register_crypt_handler_path('dummy_x', __name__)
|
||||
self.assertRaises(TypeError, get_crypt_handler, 'dummy_x')
|
||||
|
||||
# check lazy load w/ wrong name fails
|
||||
register_crypt_handler_path('alt_dummy_0', __name__)
|
||||
self.assertRaises(ValueError, get_crypt_handler, "alt_dummy_0")
|
||||
unload_handler_name("alt_dummy_0")
|
||||
|
||||
# TODO: check lazy load which calls register_crypt_handler (warning should be issued)
|
||||
sys.modules.pop("passlib.tests._test_bad_register", None)
|
||||
register_crypt_handler_path("dummy_bad", "passlib.tests._test_bad_register")
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", "xxxxxxxxxx", DeprecationWarning)
|
||||
h = get_crypt_handler("dummy_bad")
|
||||
from passlib.tests import _test_bad_register as tbr
|
||||
self.assertIs(h, tbr.alt_dummy_bad)
|
||||
|
||||
def test_register_crypt_handler(self):
|
||||
"""test register_crypt_handler()"""
|
||||
|
||||
self.assertRaises(TypeError, register_crypt_handler, {})
|
||||
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name=None)))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="AB_CD")))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="ab-cd")))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="ab__cd")))
|
||||
self.assertRaises(ValueError, register_crypt_handler, type('x', (uh.StaticHandler,), dict(name="default")))
|
||||
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
|
||||
class dummy_1b(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
|
||||
self.assertTrue('dummy_1' not in list_crypt_handlers())
|
||||
|
||||
register_crypt_handler(dummy_1)
|
||||
register_crypt_handler(dummy_1)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
self.assertRaises(KeyError, register_crypt_handler, dummy_1b)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
register_crypt_handler(dummy_1b, force=True)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1b)
|
||||
|
||||
self.assertTrue('dummy_1' in list_crypt_handlers())
|
||||
|
||||
def test_get_crypt_handler(self):
|
||||
"""test get_crypt_handler()"""
|
||||
|
||||
class dummy_1(uh.StaticHandler):
|
||||
name = "dummy_1"
|
||||
|
||||
# without available handler
|
||||
self.assertRaises(KeyError, get_crypt_handler, "dummy_1")
|
||||
self.assertIs(get_crypt_handler("dummy_1", None), None)
|
||||
|
||||
# already loaded handler
|
||||
register_crypt_handler(dummy_1)
|
||||
self.assertIs(get_crypt_handler("dummy_1"), dummy_1)
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", "handler names should be lower-case, and use underscores instead of hyphens:.*", UserWarning)
|
||||
|
||||
# already loaded handler, using incorrect name
|
||||
self.assertIs(get_crypt_handler("DUMMY-1"), dummy_1)
|
||||
|
||||
# lazy load of unloaded handler, using incorrect name
|
||||
register_crypt_handler_path('dummy_0', __name__)
|
||||
self.assertIs(get_crypt_handler("DUMMY-0"), dummy_0)
|
||||
|
||||
# check system & private names aren't returned
|
||||
from passlib import hash
|
||||
hash.__dict__["_fake"] = "dummy"
|
||||
for name in ["_fake", "__package__"]:
|
||||
self.assertRaises(KeyError, get_crypt_handler, name)
|
||||
self.assertIs(get_crypt_handler(name, None), None)
|
||||
|
||||
def test_list_crypt_handlers(self):
|
||||
"""test list_crypt_handlers()"""
|
||||
from passlib.registry import list_crypt_handlers
|
||||
|
||||
# check system & private names aren't returned
|
||||
hash.__dict__["_fake"] = "dummy"
|
||||
for name in list_crypt_handlers():
|
||||
self.assertFalse(name.startswith("_"), "%r: " % name)
|
||||
unload_handler_name("_fake")
|
||||
|
||||
def test_handlers(self):
|
||||
"""verify we have tests for all builtin handlers"""
|
||||
from passlib.registry import list_crypt_handlers
|
||||
from passlib.tests.test_handlers import get_handler_case, conditionally_available_hashes
|
||||
for name in list_crypt_handlers():
|
||||
# skip some wrappers that don't need independant testing
|
||||
if name.startswith("ldap_") and name[5:] in list_crypt_handlers():
|
||||
continue
|
||||
if name in ["roundup_plaintext"]:
|
||||
continue
|
||||
# check the remaining ones all have a handler
|
||||
try:
|
||||
self.assertTrue(get_handler_case(name))
|
||||
except exc.MissingBackendError:
|
||||
if name in conditionally_available_hashes: # expected to fail on some setups
|
||||
continue
|
||||
raise
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
1604
venv/Lib/site-packages/passlib/tests/test_totp.py
Normal file
1604
venv/Lib/site-packages/passlib/tests/test_totp.py
Normal file
File diff suppressed because it is too large
Load Diff
1171
venv/Lib/site-packages/passlib/tests/test_utils.py
Normal file
1171
venv/Lib/site-packages/passlib/tests/test_utils.py
Normal file
File diff suppressed because it is too large
Load Diff
870
venv/Lib/site-packages/passlib/tests/test_utils_handlers.py
Normal file
870
venv/Lib/site-packages/passlib/tests/test_utils_handlers.py
Normal 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
|
||||
#=============================================================================
|
41
venv/Lib/site-packages/passlib/tests/test_utils_md4.py
Normal file
41
venv/Lib/site-packages/passlib/tests/test_utils_md4.py
Normal 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
|
||||
#=============================================================================
|
323
venv/Lib/site-packages/passlib/tests/test_utils_pbkdf2.py
Normal file
323
venv/Lib/site-packages/passlib/tests/test_utils_pbkdf2.py
Normal 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
|
||||
#=============================================================================
|
50
venv/Lib/site-packages/passlib/tests/test_win32.py
Normal file
50
venv/Lib/site-packages/passlib/tests/test_win32.py
Normal 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
|
||||
#=============================================================================
|
83
venv/Lib/site-packages/passlib/tests/tox_support.py
Normal file
83
venv/Lib/site-packages/passlib/tests/tox_support.py
Normal 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
|
||||
#=============================================================================
|
3621
venv/Lib/site-packages/passlib/tests/utils.py
Normal file
3621
venv/Lib/site-packages/passlib/tests/utils.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user