summaryrefslogtreecommitdiff
path: root/passlib/tests
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2011-02-26 10:54:14 -0500
committerEli Collins <elic@assurancetechnologies.com>2011-02-26 10:54:14 -0500
commit6c9c467b5448e8b6fea751bdf2a0628c6f2c99ea (patch)
tree14a182e9c6eab586eb523952641295b807ff5d42 /passlib/tests
parent962ec21abda828138a3c8efed5918ad2f14850b8 (diff)
downloadpasslib-6c9c467b5448e8b6fea751bdf2a0628c6f2c99ea.tar.gz
HandlerCase
=========== * refactored common handler test case (HandlerCase) * removed some needless tests * added support for genhash / genconfig * bugfixes to des_crypt & nthash that above changes revealed
Diffstat (limited to 'passlib/tests')
-rw-r--r--passlib/tests/test_drivers.py131
-rw-r--r--passlib/tests/test_frontend.py15
-rw-r--r--passlib/tests/test_utils_drivers.py4
-rw-r--r--passlib/tests/utils.py312
4 files changed, 226 insertions, 236 deletions
diff --git a/passlib/tests/test_drivers.py b/passlib/tests/test_drivers.py
index 75ca8be..bf577a8 100644
--- a/passlib/tests/test_drivers.py
+++ b/passlib/tests/test_drivers.py
@@ -6,10 +6,6 @@ from __future__ import with_statement
#core
import hashlib
import logging; log = logging.getLogger(__name__)
-try:
- from warnings import catch_warnings
-except ImportError: #wasn't added until py26
- catch_warnings = None
import warnings
#site
#pkg
@@ -24,11 +20,11 @@ class AprMd5CryptTest(HandlerCase):
handler = apr_md5_crypt
#values taken from http://httpd.apache.org/docs/2.2/misc/password_encryptions.html
- known_correct = (
+ known_correct_hashes = (
('myPassword', '$apr1$r31.....$HqJZimcKQFAMYayBlzkrA/'),
)
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
'$apr1$r31.....$HqJZimcKQFAMYayBlzkrA!'
]
@@ -42,7 +38,7 @@ class BCryptTest(HandlerCase):
handler = bcrypt
secret_chars = 72
- known_correct = (
+ known_correct_hashes = (
#selected bcrypt test vectors
('', '$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s.'),
('a', '$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u'),
@@ -51,12 +47,12 @@ class BCryptTest(HandlerCase):
('~!@#$%^&*() ~!@#$%^&*()PNBFRD', '$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS'),
)
- known_invalid = [
+ known_unidentified_hashes = [
#unsupported minor version
"$2b$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
]
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
"$2a$12$EXRkfkdmXn!gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q",
#rounds not zero-padded (pybcrypt rejects this, therefore so do we)
@@ -79,13 +75,13 @@ class BigCryptTest(HandlerCase):
#TODO: find an authortative source of test vectors,
#these were found in docs and messages on the web.
- known_correct = [
+ known_correct_hashes = [
("passphrase", "qiyh4XPJGsOZ2MEAyLkfWqeQ"),
("This is very long passwd", "f8.SVpL2fvwjkAnxn8/rgTkwvrif6bjYB5c"),
]
#omit des_crypt from known other, it looks like bigcrypt
- known_other = filter(lambda row: row[0] != "des_crypt", HandlerCase.known_other)
+ known_other_hashes = filter(lambda row: row[0] != "des_crypt", HandlerCase.known_other_hashes)
#=========================================================
#bsdi crypt
@@ -95,14 +91,14 @@ from passlib.drivers.des_crypt import bsdi_crypt
class BSDiCryptTest(HandlerCase):
"test BSDiCrypt algorithm"
handler = bsdi_crypt
- known_correct = [
+ known_correct_hashes = [
(" ", "_K1..crsmZxOLzfJH8iw"),
("my", '_KR/.crsmykRplHbAvwA'), #<- to detect old 12-bit rounds bug
("my socra", "_K1..crsmf/9NzZr1fLM"),
("my socrates", '_K1..crsmOv1rbde9A9o'),
("my socrates note", "_K1..crsm/2qeAhdISMA"),
]
- known_invalid = [
+ known_unidentified_hashes = [
#bad char in otherwise correctly formatted hash
"_K1.!crsmZxOLzfJH8iw"
]
@@ -119,7 +115,7 @@ class Crypt16Test(HandlerCase):
#TODO: find an authortative source of test vectors
#instead of just msgs around the web
# (eg http://seclists.org/bugtraq/1999/Mar/76)
- known_correct = [
+ known_correct_hashes = [
("passphrase", "qi8H8R7OM4xMUNMPuRAZxlY."),
("printf", "aaCjFz4Sh8Eg2QSqAReePlq6"),
("printf", "AA/xje2RyeiSU0iBY3PDwjYo"),
@@ -140,7 +136,7 @@ class DesCryptTest(HandlerCase):
#TODO: test
- known_correct = (
+ known_correct_hashes = (
#secret, example hash which matches secret
('', 'OgAwTx2l6NADI'),
(' ', '/Hk.VPuwQTXbc'),
@@ -150,7 +146,7 @@ class DesCryptTest(HandlerCase):
('AlOtBsOl', 'cEpWz5IUCShqM'),
(u'hell\u00D6', 'saykDgk3BPZ9E'),
)
- known_invalid = [
+ known_unidentified_hashes = [
#bad char in otherwise correctly formatted hash
'!gAwTx2l6NADI',
]
@@ -164,7 +160,7 @@ from passlib.drivers.md5_crypt import md5_crypt
class Md5CryptTest(HandlerCase):
handler = md5_crypt
- known_correct = (
+ known_correct_hashes = (
('', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'),
(' ', '$1$m/5ee7ol$bZn0kIBFipq39e.KDXX8I0'),
('test', '$1$ec6XvcoW$ghEtNK2U1MC5l.Dwgi3020'),
@@ -173,7 +169,7 @@ class Md5CryptTest(HandlerCase):
('test', '$1$SuMrG47N$ymvzYjr7QcEQjaK5m1PGx1'),
)
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
'$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o!',
]
@@ -188,13 +184,10 @@ from passlib.drivers.mysql import mysql323, mysql41
class Mysql323Test(HandlerCase):
handler = mysql323
- #remove single space from secrets, since mysql-323 ignores all whitespace (?!)
- standard_secrets = [ x for x in HandlerCase.standard_secrets if x != ' ' ]
-
- known_correct = (
+ known_correct_hashes = (
('mypass', '6f8c114b58f2ce9e'),
)
- known_invalid = [
+ known_unidentified_hashes = [
#bad char in otherwise correct hash
'6z8c114b58f2ce9e',
]
@@ -207,10 +200,10 @@ class Mysql323Test(HandlerCase):
class Mysql41Test(HandlerCase):
handler = mysql41
- known_correct = (
+ known_correct_hashes = (
('mypass', '*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4'),
)
- known_invalid = [
+ known_unidentified_hashes = [
#bad char in otherwise correct hash
'*6Z8989366EAF75BB670AD8EA7A7FC1176A95CEF4',
]
@@ -223,12 +216,12 @@ from passlib.drivers.nthash import nthash
class NTHashTest(HandlerCase):
handler = nthash
- known_correct = (
+ known_correct_hashes = (
('passphrase', '$3$$7f8fe03093cc84b267b109625f6bbf4b'),
('passphrase', '$NT$7f8fe03093cc84b267b109625f6bbf4b'),
)
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
'$3$$7f8fe03093cc84b267b109625f6bbfxb',
]
@@ -241,13 +234,13 @@ from passlib.drivers.phpass import phpass
class PHPassTest(HandlerCase):
handler = phpass
- known_correct = (
+ known_correct_hashes = (
('', '$P$7JaFQsPzJSuenezefD/3jHgt5hVfNH0'),
('compL3X!', '$P$FiS0N5L672xzQx1rt1vgdJQRYKnQM9/'),
('test12345', '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'), #from the source
)
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
'$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r!L0',
]
@@ -261,13 +254,13 @@ from passlib.drivers.postgres import postgres_md5, postgres_plaintext
class PostgresMD5CryptTest(HandlerCase):
handler = postgres_md5
- known_correct = [
+ known_correct_hashes = [
# ((secret,user),hash)
(('mypass', 'postgres'), 'md55fba2ea04fd36069d2574ea71c8efe9d'),
(('mypass', 'root'), 'md540c31989b20437833f697e485811254b'),
(("testpassword",'testuser'), 'md5d4fc5129cc2c25465a5370113ae9835f'),
]
- known_invalid = [
+ known_unidentified_hashes = [
#bad 'z' char in otherwise correct hash
'md54zc31989b20437833f697e485811254b',
]
@@ -285,13 +278,12 @@ class PostgresMD5CryptTest(HandlerCase):
self.assertRaises(TypeError, self.handler.encrypt, 'mypass')
self.assertRaises(TypeError, self.handler.verify, 'mypass', 'md55fba2ea04fd36069d2574ea71c8efe9d')
- def do_concat(self, secret, prefix):
+ def create_mismatch(self, secret):
if isinstance(secret, tuple):
secret, user = secret
- secret = prefix + secret
- return secret, user
+ return 'x' + secret, user
else:
- return prefix + secret
+ return 'x' + secret
def do_encrypt(self, secret, **kwds):
if isinstance(secret, tuple):
@@ -309,6 +301,13 @@ class PostgresMD5CryptTest(HandlerCase):
user = 'default'
return self.handler.verify(secret, hash, user=user)
+ def do_genhash(self, secret, config):
+ if isinstance(secret, tuple):
+ secret, user = secret
+ else:
+ user = 'default'
+ return self.handler.genhash(secret, config, user=user)
+
#=========================================================
# (netbsd's) sha1 crypt
#=========================================================
@@ -317,12 +316,12 @@ from passlib.drivers.sha1_crypt import sha1_crypt
class SHA1CryptTest(HandlerCase):
handler = sha1_crypt
- known_correct = (
+ known_correct_hashes = (
("password", "$sha1$19703$iVdJqfSE$v4qYKl1zqYThwpjJAoKX6UvlHq/a"),
("password", "$sha1$21773$uV7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH"),
)
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
'$sha1$21773$u!7PTeux$I9oHnvwPZHMO0Nq6/WgyGV/tDJIH',
]
@@ -336,7 +335,7 @@ class SHA256CryptTest(HandlerCase):
handler = sha256_crypt
supports_unicode = True
- known_correct = [
+ known_correct_hashes = [
('', '$5$rounds=10428$uy/jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMe.ZGsGx2aBvxTvDFI613c3'),
(' ', '$5$rounds=10376$I5lNtXtRmf.OoMd8$Ko3AI1VvTANdyKhBPavaRjJzNpSatKU6QVN9uwS9MH.'),
('test', '$5$rounds=11858$WH1ABM5sKhxbkgCK$aTQsjPkz0rBsH3lQlJxw9HDTDXPKBxC0LlVeV69P.t1'),
@@ -345,7 +344,7 @@ class SHA256CryptTest(HandlerCase):
(u'with unic\u00D6de', '$5$rounds=1000$IbG0EuGQXw5EkMdP$LQ5AfPf13KufFsKtmazqnzSGZ4pxtUNw3woQ.ELRDF4'),
]
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
'$5$rounds=10428$uy/:jIAhCetNCTtb0$YWvUOXbkqlqhyoPMpN8BMeZGsGx2aBvxTvDFI613c3',
@@ -354,7 +353,7 @@ class SHA256CryptTest(HandlerCase):
]
#NOTE: these test cases taken from official specification at http://www.akkadia.org/drepper/SHA-crypt.txt
- cases256 = [
+ known_correct_configs = [
#config, secret, result
( "$5$saltstring", "Hello world!",
"$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5" ),
@@ -380,28 +379,9 @@ class SHA256CryptTest(HandlerCase):
"2bIC" ),
]
- def test_spec_vectors(self):
- "verify sha256-crypt passes specification test vectors"
- handler = self.handler
-
- #NOTE: the 'roundstoolow' test vector is known to raise a warning, which we silence here
- if catch_warnings:
- ctx = catch_warnings()
- ctx.__enter__()
+ def filter_known_config_warnings(self):
warnings.filterwarnings("ignore", "sha256_crypt does not allow less than 1000 rounds: 10", UserWarning)
- for config, secret, hash in self.cases256:
- #make sure we got expected result back
- result = handler.genhash(secret, config)
- self.assertEqual(result, hash, "hash=%r secret=%r:" % (hash, secret))
-
- #make sure parser truncated salts
- info = handler.from_string(config)
- self.assert_(len(info.salt) <= 16, "hash=%r secret=%r:" % (hash, secret))
-
- if catch_warnings:
- ctx.__exit__(None,None,None)
-
BuiltinSHA256CryptTest = create_backend_case(SHA256CryptTest, "builtin")
#=========================================================
@@ -413,7 +393,7 @@ class SHA512CryptTest(HandlerCase):
handler = sha512_crypt
supports_unicode = True
- known_correct = [
+ known_correct_hashes = [
('', '$6$rounds=11021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1'),
(' ', '$6$rounds=11104$ED9SA4qGmd57Fq2m$q/.PqACDM/JpAHKmr86nkPzzuR5.YpYa8ZJJvI8Zd89ZPUYTJExsFEIuTYbM7gAGcQtTkCEhBKmp1S1QZwaXx0'),
('test', '$6$rounds=11531$G/gkPn17kHYo0gTF$Kq.uZBHlSBXyzsOJXtxJruOOH4yc0Is13uY7yK0PvAvXxbvc1w8DO1RzREMhKsc82K/Jh8OquV8FZUlreYPJk1'),
@@ -421,7 +401,7 @@ class SHA512CryptTest(HandlerCase):
('4lpHa N|_|M3r1K W/ Cur5Es: #$%(*)(*%#', '$6$rounds=11065$5KXQoE1bztkY5IZr$Jf6krQSUKKOlKca4hSW07MSerFFzVIZt/N3rOTsUgKqp7cUdHrwV8MoIVNCk9q9WL3ZRMsdbwNXpVk0gVxKtz1'),
]
- known_identified_invalid = [
+ known_malformed_hashes = [
#zero-padded rounds
'$6$rounds=011021$KsvQipYPWpr93wWP$v7xjI4X6vyVptJjB1Y02vZC5SaSijBkGmq1uJhPr3cvqvvkd42Xvo48yLVPFt8dvhCsnlUgpX.//Cxn91H4qy1',
#bad char in otherwise correct hash
@@ -429,7 +409,7 @@ class SHA512CryptTest(HandlerCase):
]
#NOTE: these test cases taken from official specification at http://www.akkadia.org/drepper/SHA-crypt.txt
- cases512 = [
+ known_correct_configs = [
#config, secret, result
("$6$saltstring", "Hello world!",
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu"
@@ -463,28 +443,9 @@ class SHA512CryptTest(HandlerCase):
"hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." ),
]
- def test_spec_vectors(self):
- "verify sha512-crypt passes specification test vectors"
- handler = self.handler
-
- #NOTE: the 'roundstoolow' test vector is known to raise a warning, which we silence here
- if catch_warnings:
- ctx = catch_warnings()
- ctx.__enter__()
+ def filter_known_config_warnings(self):
warnings.filterwarnings("ignore", "sha512_crypt does not allow less than 1000 rounds: 10", UserWarning)
- for config, secret, hash in self.cases512:
- #make sure we got expected result back
- result = handler.genhash(secret, config)
- self.assertEqual(result, hash, "hash=%r secret=%r:" % (hash, secret))
-
- #make sure parser truncated salts
- info = handler.from_string(config)
- self.assert_(len(info.salt) <= 16, "hash=%r secret=%r:" % (hash, secret))
-
- if catch_warnings:
- ctx.__exit__(None,None,None)
-
BuiltinSHA512CryptTest = create_backend_case(SHA512CryptTest, "builtin")
#=========================================================
@@ -495,12 +456,12 @@ from passlib.drivers.sun_md5_crypt import sun_md5_crypt
class SunMD5CryptTest(HandlerCase):
handler = sun_md5_crypt
- known_correct = [
+ known_correct_hashes = [
#sample hash found at http://compgroups.net/comp.unix.solaris/password-file-in-linux-and-solaris-8-9
("passwd", "$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/"),
]
- known_identified_invalid = [
+ known_malformed_hashes = [
#bad char in otherwise correct hash
"$md5$RPgL!6IJ$WTvAlUJ7MqH5xak2FMEwS/"
]
diff --git a/passlib/tests/test_frontend.py b/passlib/tests/test_frontend.py
index 5fd54bb..8297d75 100644
--- a/passlib/tests/test_frontend.py
+++ b/passlib/tests/test_frontend.py
@@ -42,16 +42,14 @@ class QuickAccessTest(TestCase):
identify = mod.identify
for cc in self.crypt_cases:
name = cc.handler.name
- for _, hash in cc.known_correct:
+ for _, hash in cc.known_correct_hashes:
self.assertEqual(identify(hash), name)
- for _, hash in cc.known_incorrect:
- self.assertEqual(identify(hash), name)
- for other, hash in cc.known_other:
+ for other, hash in cc.known_other_hashes:
if other == name:
self.assertEqual(identify(hash), name)
else:
self.assertNotEqual(identify(hash), name)
- for hash in cc.known_invalid:
+ for hash in cc.known_unidentified_hashes:
self.assertEqual(identify(hash), None)
def test_01_verify(self):
@@ -59,13 +57,10 @@ class QuickAccessTest(TestCase):
verify = mod.verify
for cc in self.crypt_cases:
name = cc.handler.name
- for secret, hash in cc.known_correct[:3]:
+ for secret, hash in cc.known_correct_hashes[:3]:
self.assert_(verify(secret, hash))
self.assert_(verify(secret, hash, alg=name))
- for secret, hash in cc.known_incorrect[:3]:
- self.assert_(not verify(secret, hash))
- self.assert_(not verify(secret, hash, alg=name))
- for hash in cc.known_invalid[:3]:
+ for hash in cc.known_unidentified_hashes[:3]:
#context should raise ValueError because can't be identified
self.assertRaises(ValueError, verify, secret, hash)
diff --git a/passlib/tests/test_utils_drivers.py b/passlib/tests/test_utils_drivers.py
index 1e96df0..9ac7db5 100644
--- a/passlib/tests/test_utils_drivers.py
+++ b/passlib/tests/test_utils_drivers.py
@@ -78,12 +78,12 @@ class SaltedHash(ExtHash):
class UnsaltedHashTest(HandlerCase):
handler = UnsaltedHash
- known_correct = []
+ known_correct_hashes = []
class SaltedHashTest(HandlerCase):
handler = SaltedHash
- known_correct = []
+ known_correct_hashes = []
#=========================================================
#
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index 19d8891..cad2194 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -7,6 +7,11 @@ import logging; log = logging.getLogger(__name__)
import re
import os
import unittest
+import warnings
+try:
+ from warnings import catch_warnings
+except ImportError: #wasn't added until py26
+ catch_warnings = None
#site
from nose.plugins.skip import SkipTest
#pkg
@@ -203,7 +208,7 @@ class HandlerCase(TestCase):
#attrs to be filled in by subclass for testing specific handler
#=========================================================
- #specify handler object here
+ #specify handler object here (required)
handler = None
#this option is available for hashes which can't handle unicode
@@ -214,39 +219,29 @@ class HandlerCase(TestCase):
secret_chars = -1
#list of (secret,hash) pairs which handler should verify as matching
- known_correct = []
+ known_correct_hashes = []
- #list of (secret,hash) pairs which handler should verify as NOT matching
- known_incorrect = []
+ #list of (config, secret, hash) triples which handler should genhash & verify
+ known_correct_configs = []
- # list of handler's hashes with crucial invalidating typos, that handler shouldn't identify as belonging to it
- known_invalid = []
+ # hashes so malformed they aren't even identified properly
+ known_unidentified_hashes = []
- # list of handler's hashes that it *will* identify as it's own, but genhash will raise error due to invalid internal requirements
- known_identified_invalid = []
+ # hashes which are malformed - they should identify() as True, but cause error when passed to genhash/verify
+ known_malformed_hashes = []
- #list of (name, hash) pairs for other algorithm's hashes, that handler shouldn't identify as belonging to it
+ #list of (handler name, hash) pairs for other algorithm's hashes, that handler shouldn't identify as belonging to it
#this list should generally be sufficient (if handler name in list, that entry will be skipped)
- known_other = [
+ known_other_hashes = [
('des_crypt', '6f8c114b58f2c'),
('md5_crypt', '$1$dOHYPKoP$tnxS1T8Q6VVn3kpV8cN6o.'),
('sha512_crypt', "$6$rounds=123456$asaltof16chars..$BtCwjqMJGx5hrJhZywWvt0RLE8uZ4oPwc"
"elCjmw2kSYu.Ec6ycULevoBK25fs2xXgMNrCzIMVcgEJAstJeonj1"),
]
- #list of various secrets all algs are tested with to make sure they work
- standard_secrets = [
- '',
- ' ',
- 'my socrates note',
- 'Compl3X AlphaNu3meric',
- '4lpHa N|_|M3r1K W/ Cur51|\\|g: #$%(*)(*%#',
- 'Really Long Password (tm), which is all the rage nowadays. Maybe some Shakespeare?',
- ]
-
- unicode_secrets = [
- u'test with unic\u00D6de',
- ]
+ #=========================================================
+ #
+ #=========================================================
#optional prefix to prepend to name of test method as it's called,
#useful when multiple handler test classes being run.
@@ -262,11 +257,6 @@ class HandlerCase(TestCase):
#alg interface helpers - allows subclass to overide how
# default tests invoke the handler (eg for context_kwds)
#=========================================================
- def do_concat(self, secret, prefix):
- "concatenate prefix onto secret"
- #NOTE: this is subclassable mainly for some algorithms
- #which accept non-strings in secret
- return prefix + secret
def do_encrypt(self, secret, **kwds):
"call handler's encrypt method with specified options"
@@ -280,11 +270,25 @@ class HandlerCase(TestCase):
"call handler's identify method"
return self.handler.identify(hash)
+ def do_genconfig(self, **kwds):
+ "call handler's genconfig method with specified options"
+ return self.handler.genconfig(**kwds)
+
+ def do_genhash(self, secret, config):
+ "call handler's genhash method with specified options"
+ return self.handler.genhash(secret, config)
+
+ def create_mismatch(self, secret):
+ "return other secret which won't match"
+ #NOTE: this is subclassable mainly for some algorithms
+ #which accept non-strings in secret
+ return 'x' + secret
+
#=========================================================
#attributes
#=========================================================
- def test_00_attributes(self):
- "test handler attributes are all defined"
+ def test_00_required_attributes(self):
+ "test required handler attributes are defined"
handler = self.handler
def ga(name):
return getattr(handler, name, None)
@@ -294,7 +298,17 @@ class HandlerCase(TestCase):
self.assert_(name.lower() == name, "name not lower-case:")
self.assert_(re.match("^[a-z0-9_]+$", name), "name must be alphanum + underscore: %r" % (name,))
- def test_01_base_handler(self):
+ settings = ga("setting_kwds")
+ self.assert_(settings is not None, "setting_kwds must be defined:")
+ self.assertIsInstance(settings, tuple, "setting_kwds must be a tuple:")
+
+ context = ga("context_kwds")
+ self.assert_(context is not None, "context_kwds must be defined:")
+ self.assertIsInstance(context, tuple, "context_kwds must be a tuple:")
+
+ #TODO: check optional rounds attributes & salt attributes
+
+ def test_04_base_handler(self):
"check configuration of BaseHash-derived classes"
h = self.handler
if not isinstance(h, type) or not issubclass(h, BaseHash):
@@ -302,139 +316,174 @@ class HandlerCase(TestCase):
h.validate_class() #should raise AssertionError if something's wrong.
#=========================================================
- #identify
+ #identify()
#=========================================================
- def test_10_identify_other(self):
- "test identify() against other schemes' hashes"
- for name, hash in self.known_other:
- self.assertEqual(self.do_identify(hash), name == self.handler.name)
-
- def test_11_identify_positive(self):
+ def test_10_identify_hash(self):
"test identify() against scheme's own hashes"
- for secret, hash in self.known_correct:
- self.assertEqual(self.do_identify(hash), True)
+ for secret, hash in self.known_correct_hashes:
+ self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,))
- for secret, hash in self.known_incorrect:
- self.assertEqual(self.do_identify(hash), True)
+ for config, secret, hash in self.known_correct_configs:
+ self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,))
- for hash in self.known_identified_invalid:
- self.assertEqual(self.do_identify(hash), True)
+ def test_11_identify_config(self):
+ "test identify() against scheme's own config strings"
+ if not self.known_correct_configs:
+ raise SkipTest
+ for config, secret, hash in self.known_correct_configs:
+ self.assertEqual(self.do_identify(config), True, "config=%r:" % (config,))
- def test_12_identify_invalid(self):
- "test identify() against malformed instances of scheme's own hashes"
- if not self.known_invalid:
+ def test_12_identify_unidentified(self):
+ "test identify() against scheme's own hashes that are mangled beyond identification"
+ if not self.known_unidentified_hashes:
raise SkipTest
- for hash in self.known_invalid:
+ for hash in self.known_unidentified_hashes:
self.assertEqual(self.do_identify(hash), False, "hash=%r:" % (hash,))
- def test_13_identify_none(self):
+ def test_13_identify_malformed(self):
+ "test identify() against scheme's own hashes that are mangled but identifiable"
+ if not self.known_malformed_hashes:
+ raise SkipTest
+ for hash in self.known_malformed_hashes:
+ self.assertEqual(self.do_identify(hash), True, "hash=%r:" % (hash,))
+
+ def test_14_identify_other(self):
+ "test identify() against other schemes' hashes"
+ for name, hash in self.known_other_hashes:
+ self.assertEqual(self.do_identify(hash), name == self.handler.name, "scheme=%r, hash=%r:" % (name, hash))
+
+ def test_15_identify_none(self):
"test identify() against None / empty string"
self.assertEqual(self.do_identify(None), False)
self.assertEqual(self.do_identify(''), False)
#=========================================================
- #verify
+ #verify()
#=========================================================
def test_20_verify_positive(self):
"test verify() against known-correct secret/hash pairs"
- self.assert_(self.known_correct, "test must define known_correct hashes")
- for secret, hash in self.known_correct:
- self.assertEqual(self.do_verify(secret, hash), True, "known correct hash (secret=%r, hash=%r):" % (secret,hash))
+ self.assert_(self.known_correct_hashes or self.known_correct_configs,
+ "test must define at least one of known_correct_hashes or known_correct_configs")
- def test_21_verify_negative(self):
- "test verify() against known-incorrect secret/hash pairs"
- if not self.known_incorrect:
- raise SkipTest
- for secret, hash in self.known_incorrect:
- self.assertEqual(self.do_verify(secret, hash), False)
+ for secret, hash in self.known_correct_hashes:
+ self.assertEqual(self.do_verify(secret, hash), True,
+ "known correct hash (secret=%r, hash=%r):" % (secret,hash))
- #XXX: is this needed if known_incorrect is defined?
- def test_22_verify_derived_negative(self):
- "test verify() against derived incorrect secret/hash pairs"
- for secret, hash in self.known_correct:
- self.assertEqual(self.do_verify(self.do_concat(secret,'x'), hash), False)
+ for config, secret, hash in self.known_correct_configs:
+ self.assertEqual(self.do_verify(secret, hash), True,
+ "known correct hash (secret=%r, hash=%r):" % (secret,hash))
- def test_23_verify_other(self):
+ def test_21_verify_other(self):
"test verify() throws error against other algorithm's hashes"
- for name, hash in self.known_other:
+ for name, hash in self.known_other_hashes:
if name == self.handler.name:
continue
- self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify other %r %r:" % (name, hash))
+ self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="scheme=%r, hash=%r:" % (name, hash))
- def test_24_verify_invalid(self):
+ def test_22_verify_invalid(self):
"test verify() throws error against known-invalid hashes"
- if not self.known_invalid and not self.known_identified_invalid:
+ if not self.known_unidentified_hashes and not self.known_malformed_hashes:
raise SkipTest
- for hash in self.known_invalid + self.known_identified_invalid:
- self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="verify invalid %r:" % (hash,))
+ for hash in self.known_unidentified_hashes:
+ self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="hash=%r:" % (hash,))
+ for hash in self.known_malformed_hashes:
+ self.assertRaises(ValueError, self.do_verify, 'stub', hash, __msg__="hash=%r:" % (hash,))
- def test_25_verify_none(self):
+ def test_23_verify_none(self):
"test verify() throws error against hash=None/empty string"
#find valid hash so that doesn't mask error
- self.assertRaises(ValueError, self.do_verify, 'stub', None, __msg__="verify None:")
- self.assertRaises(ValueError, self.do_verify, 'stub', '', __msg__="verify empty:")
+ self.assertRaises(ValueError, self.do_verify, 'stub', None, __msg__="hash=None:")
+ self.assertRaises(ValueError, self.do_verify, 'stub', '', __msg__="hash='':")
+
+ #=========================================================
+ #genconfig()
+ #=========================================================
+ #NOTE: no specific genconfig tests yet, but testing in other cases
+
+ #=========================================================
+ #genhash()
+ #=========================================================
+ filter_known_config_warnings = None
+
+ def test_40_genhash_config(self):
+ "test genhash() against known config strings"
+ if not self.known_correct_configs:
+ raise SkipTest
+ fk = self.filter_known_config_warnings
+ if fk:
+ if catch_warnings:
+ ctx = catch_warnings()
+ ctx.__enter__()
+ fk()
+ for config, secret, hash in self.known_correct_configs:
+ result = self.do_genhash(secret, config)
+ self.assertEquals(result, hash, "config=%r,secret=%r:" % (config,secret))
+ if fk and catch_warnings:
+ ctx.__exit__(None,None,None)
+
+ def test_41_genhash_hash(self):
+ "test genhash() against known hash strings"
+ if not self.known_correct_hashes:
+ raise SkipTest
+ handler = self.handler
+ for secret, hash in self.known_correct_hashes:
+ result = self.do_genhash(secret, hash)
+ self.assertEquals(result, hash, "secret=%r:" % (secret,))
+
+ def test_42_genhash_genconfig(self):
+ "test genhash() against genconfig() output"
+ handler = self.handler
+ config = handler.genconfig()
+ hash = self.do_genhash("stub", config)
+ self.assert_(handler.identify(hash))
#=========================================================
- #encrypt
+ #encrypt()
#=========================================================
+ def test_50_encrypt_plain(self):
+ "test plain encrypt()"
+ if self.supports_unicode:
+ secret = u"unic\u00D6de"
+ else:
+ secret = "too many secrets"
+ result = self.do_encrypt(secret)
+ self.assert_(self.do_identify(result))
+ self.assert_(self.do_verify(secret, result))
+
+ def test_51_encrypt_none(self):
+ "test encrypt() refused secret=None"
+ self.assertRaises(TypeError, self.do_encrypt, None)
- #---------------------------------------------------------
- #test encryption against various secrets
- #---------------------------------------------------------
- def test_30_encrypt_standard(self):
- "test encrypt() against standard secrets"
- for secret in self.standard_secrets:
- self.check_encrypt(secret)
-
- def test_31_encrypt_unicode(self):
- "test encrypt() against unicode secrets"
- if not self.supports_unicode:
+ #=========================================================
+ #test salt generation
+ #=========================================================
+ def test_60_genconfig_salt(self):
+ "test genconfig() generates new salts"
+ if 'salt' not in self.handler.setting_kwds:
raise SkipTest
- for secret in self.unicode_secrets:
- self.check_encrypt(secret)
-
- #this is probably excessive
- ##def test_32_encrypt_positive(self):
- ## "test encrypt() against known-correct secret/hash pairs"
- ## for secret, hash in self.known_correct:
- ## self.check_encrypt(secret)
-
- def check_encrypt(self, secret):
- "check encrypt() behavior for a given secret"
- #hash the secret
- hash = self.do_encrypt(secret)
-
- #test identification
- self.assertEqual(self.do_identify(hash), True, "identify hash %r from secret %r:" % (hash, secret))
-
- #test positive verification
- self.assertEqual(self.do_verify(secret, hash), True, "verify hash %r from secret %r:" % (hash, secret))
-
- #test negative verification
- for other in ['', 'test', self.do_concat(secret,'x')]:
- if other != secret:
- self.assertEqual(self.do_verify(other, hash), False,
- "hash collision: %r and %r => %r" % (secret, other, hash))
-
- #---------------------------------------------------------
- #test salt handling
- #---------------------------------------------------------
- def test_33_encrypt_gensalt(self):
- "test encrypt() generates new salt each time"
+ c1 = self.do_genconfig()
+ c2 = self.do_genconfig()
+ self.assertNotEquals(c1,c2)
+
+ def test_61_encrypt_salt(self):
+ "test encrypt() generates new salts"
if 'salt' not in self.handler.setting_kwds:
raise SkipTest
- for secret, hash in self.known_correct:
- hash2 = self.do_encrypt(secret)
- self.assertNotEqual(hash, hash2)
+ if self.known_correct_hashes:
+ secret, hash = self.known_correct_hashes[0]
+ else:
+ _, secret, hash = self.known_correct_configs[0]
+ hash2 = self.do_encrypt(secret)
+ self.assertNotEquals(hash,hash2)
#TODO: test too-short user-provided salts
#TODO: test too-long user-provided salts
#TODO: test invalid char in user-provided salts
- #---------------------------------------------------------
- #test secret handling
- #---------------------------------------------------------
- def test_37_secret_chars(self):
+ #=========================================================
+ #test max password size
+ #=========================================================
+ def test_70_secret_chars(self):
"test secret_chars limit"
sc = self.secret_chars
@@ -472,21 +521,6 @@ class HandlerCase(TestCase):
hash = self.do_encrypt(secret)
self.assert_(not self.do_verify(secret[:-1] + alt, hash))
- def test_38_encrypt_none(self):
- "test encrypt() refused secret=None"
- self.assertRaises(TypeError, self.do_encrypt, None)
-
- #=========================================================
- #
- #=========================================================
-
- #TODO: check genhash works
- #TODO: check genconfig works
-
- #TODO: check parse method works
- #TODO: check render method works
- #TODO: check default/min/max_rounds valid if present
-
#=========================================================
#eoc
#=========================================================