summaryrefslogtreecommitdiff
path: root/passlib/tests
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2012-01-09 23:17:30 -0500
committerEli Collins <elic@assurancetechnologies.com>2012-01-09 23:17:30 -0500
commit1c449d2ddea632f3b7770f6d0c08f8435ea0cd18 (patch)
treef2d57863635c81c7267340eaf35c684ce6154420 /passlib/tests
parent29e6db01cb272996a3e6f88cdbd8662f7024d605 (diff)
downloadpasslib-1c449d2ddea632f3b7770f6d0c08f8435ea0cd18.tar.gz
lots of work on scram hash
handler ------- * added 'scram' to default registry list * handler 'algs' keyword now parsed & validated correctly * digest names normalized -> IANA spec * saslprep() integrated into code * added config string format related ------- * added documentation (still needs cleaning up though) * added majority of UTs, still need to add a few edge cases other ----- * redid context->handler deprecation link - code now looks for handler._deprecated_detector(settings) to generate a callable, should be more efficient, and allow errors to be throw at bind-time instead of call-time. * pbkdf2() function now treats keylen = -1 as request for keylen = PRF digest size.
Diffstat (limited to 'passlib/tests')
-rw-r--r--passlib/tests/test_handlers.py218
-rw-r--r--passlib/tests/test_utils.py11
2 files changed, 228 insertions, 1 deletions
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index bbaeb71..81e3958 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -1008,6 +1008,224 @@ class PostgresMD5CryptTest(HandlerCase):
return self.handler.genhash(secret, config, user=user)
#=========================================================
+# scram hash
+#=========================================================
+class ScramTest(HandlerCase):
+ handler = hash.scram
+
+ known_correct_hashes = [
+ # taken from example in SCRAM specification.
+ ('pencil', '$scram$4096$QSXCR.Q6sek8bf92$'
+ 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'),
+
+ # previous example, 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'),
+
+ #############
+ # TODO: need a bunch more reference vectors from some real
+ # SCRAM transactions.
+ #############
+
+ #############
+ # TODO: verify the following against some other SCRAM implementation.
+ #############
+
+ # the following hash should verify against both normalized
+ # and unnormalized versions of the password.
+ (u('\u2168\u3000a\u0300'), '$scram$6400$0BojBCBE6P2/N4bQ$'
+ 'sha-1=YniLes.b8WFMvBhtSACZyyvxeCc'),
+ (u('IX \xE0'), '$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',
+
+# FIXME: bad chars raise TypeError
+ # bad char in salt
+# '$scram$4096$QSXCR.Q6sek8bf9-$sha-1=HZbuOlKbWl.eR8AfIposuKbhX30',
+
+ # bad char in digest
+# '$scram$4096$QSXCR.Q6sek8bf92$sha-1=HZbuOlKbWl.eR8AfIposuKbhX3-',
+
+ # too many chars in alg
+ '$scram$4096$QSXCR.Q6sek8bf92$shaxxx-190=HZbuOlKbWl.eR8AfIposuKbhX30',
+
+ # missing sha-1 alg
+ '$scram$4096$QSXCR.Q6sek8bf92$sha-256=HZbuOlKbWl.eR8AfIposuKbhX30',
+
+ ]
+
+ def test_100_algs(self):
+ "test parsing of 'algs' setting"
+ def parse(source):
+ return self.handler(algs=source).algs
+
+ # None -> default list
+ self.assertEquals(parse(None), ["sha-1","sha-256","sha-512"])
+
+ # strings should be parsed
+ self.assertEquals(parse("sha1"), ["sha-1"])
+ self.assertEquals(parse("sha1, sha256, md5"), ["md5","sha-1","sha-256"])
+
+ # lists should be normalized
+ self.assertEquals(parse(["sha-1","sha256"]), ["sha-1","sha-256"])
+
+ # sha-1 required
+ self.assertRaises(ValueError, parse, ["sha-256"])
+
+ # alg names < 10 chars
+ self.assertRaises(ValueError, parse, ["sha-1","shaxxx-890"])
+
+ # alg & checksum mutually exclusive.
+ self.assertRaises(RuntimeError, self.handler, algs=['sha-1'],
+ checksum={"sha-1": b("\x00"*20)})
+
+ def test_101_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,'\x01'))
+ self.assertEqual(edi(h,"bbb"), (s,10,'\x02'))
+ self.assertEqual(edi(h,"ccc"), (s,10,'\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_102_extract_digest_algs(self):
+ "test scram.extract_digest_algs()"
+ eda = self.handler.extract_digest_algs
+
+ self.assertEquals(eda('$scram$4096$QSXCR.Q6sek8bf92$'
+ 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30'), ["sha-1"])
+
+ self.assertEquals(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"])
+
+ # TODO.
+ def test_103_derive_digest(self):
+ "test scram.derive_digest()"
+ pass
+
+ def test_104_saslprep(self):
+ "test encrypt/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.
+
+ # encrypt unnormalized
+ h = self.do_encrypt(u("I\u00ADX"))
+ self.assertTrue(self.do_verify(u("IX"), h))
+ self.assertTrue(self.do_verify(u("\u2168"), h))
+
+ # encrypt 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_105_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.encrypt("dummy")
+ self.assertEqual(handler.extract_digest_algs(h), ["md5", "sha-1"])
+ self.assertFalse(c1.hash_needs_update(h))
+
+ c2 = c1.replace(scram__algs="sha1")
+ self.assertFalse(c2.hash_needs_update(h))
+
+ c2 = c1.replace(scram__algs="sha1,sha256")
+ self.assertTrue(c2.hash_needs_update(h))
+
+ def test_106_full_verify(self):
+ "test full_verify flag"
+ def vfull(s, h):
+ return self.handler.verify(s, h, full_verify=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.3EZ1rdhVY' # -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.3EZ1rdhVY,a' # +1 char
+ 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
+ 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
+ self.assertRaises(ValueError, vfull, 'pencil', h)
+
+ # catch digests belonging to diff passwords.
+ h = ('$scram$4096$QSXCR.Q6sek8bf92$'
+ 'sha-1=HZbuOlKbWl.eR8AfIposuKbhX30,'
+ 'sha-256=R7RJDWIbeKRTFwhE9oxh04kab0CllrQ3kCcpZUcligc' # 'tape'
+ 'sha-512=lzgniLFcvglRLS0gt.C4gy.NurS3OIOVRAU1zZOV4P.qFiVFO2/'
+ 'edGQSu/kD1LwdX0SNV/KsPdHSwEl5qRTuZQ')
+ self.assertRaises(ValueError, vfull, 'pencil', h)
+ self.assertRaises(ValueError, vfull, 'tape', h)
+
+ ndn_values = [
+ # normalized name, unnormalized names
+
+ # IANA assigned names
+ ("md5", "MD-5"),
+ ("sha-1", "SHA1"),
+ ("sha-256", "SHA_256", "sha2-256"),
+
+ # heuristic for unassigned names
+ ("abc6", "aBc-6"),
+ ("ripemd", "RIPEMD"),
+ ("ripemd-160", "RIPEmd160"),
+ ]
+
+ def test_107_norm_digest_name(self):
+ "test norm_digest_name helper"
+ from passlib.handlers.scram import norm_digest_name
+ for row in self.ndn_values:
+ result = row[0]
+ for value in row:
+ self.assertEqual(norm_digest_name(value), result)
+
+#=========================================================
# (netbsd's) sha1 crypt
#=========================================================
class _SHA1CryptTest(HandlerCase):
diff --git a/passlib/tests/test_utils.py b/passlib/tests/test_utils.py
index ef4ecd3..5396a89 100644
--- a/passlib/tests/test_utils.py
+++ b/passlib/tests/test_utils.py
@@ -849,7 +849,8 @@ class _Pbkdf2BackendTest(TestCase):
self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), b('salt'), 'x', 16)
#invalid keylen
- self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 20*(2**32-1)+1)
+ self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'),
+ 1, 20*(2**32-1)+1)
#invalid salt type
self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), 5, 1, 10)
@@ -862,6 +863,14 @@ class _Pbkdf2BackendTest(TestCase):
self.assertRaises(ValueError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 'foo')
self.assertRaises(TypeError, pbkdf2.pbkdf2, b('password'), b('salt'), 1, 16, 5)
+ def test_default_keylen(self):
+ "test keylen==-1"
+ self.assertEqual(len(pbkdf2.pbkdf2(b('password'), b('salt'), 1, -1,
+ prf='hmac-sha1')), 20)
+
+ self.assertEqual(len(pbkdf2.pbkdf2(b('password'), b('salt'), 1, -1,
+ prf='hmac-sha256')), 32)
+
def test_hmac_sha1(self):
"test independant hmac_sha1() method"
self.assertEqual(