diff options
| author | Florian Apolloner <florian@apolloner.eu> | 2016-02-13 21:09:46 +0100 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2016-02-29 08:07:17 -0500 |
| commit | f4e6e02f7713a6924d16540be279909ff4091eb6 (patch) | |
| tree | 2de4c8d77db4ee8dbaf81f6bd20e958c543adb91 /tests | |
| parent | 382ab137312961ad62feb8109d70a5a581fe8350 (diff) | |
[1.8.x] Fixed CVE-2016-2513 -- Fixed user enumeration timing attack during login.
This is a security fix.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/auth_tests/test_hashers.py | 58 |
1 files changed, 57 insertions, 1 deletions
diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index 7eb47ccce5..489a930466 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -10,9 +10,10 @@ from django.contrib.auth.hashers import ( check_password, get_hasher, identify_hasher, is_password_usable, make_password, ) -from django.test import SimpleTestCase +from django.test import SimpleTestCase, mock from django.test.utils import override_settings from django.utils import six +from django.utils.encoding import force_bytes try: import crypt @@ -177,6 +178,28 @@ class TestUtilsHashPass(SimpleTestCase): self.assertTrue(check_password('', blank_encoded)) self.assertFalse(check_password(' ', blank_encoded)) + @skipUnless(bcrypt, "bcrypt not installed") + def test_bcrypt_harden_runtime(self): + hasher = get_hasher('bcrypt') + self.assertEqual('bcrypt', hasher.algorithm) + + with mock.patch.object(hasher, 'rounds', 4): + encoded = make_password('letmein', hasher='bcrypt') + + with mock.patch.object(hasher, 'rounds', 6), \ + mock.patch.object(hasher, 'encode', side_effect=hasher.encode): + hasher.harden_runtime('wrong_password', encoded) + + # Increasing rounds from 4 to 6 means an increase of 4 in workload, + # therefore hardening should run 3 times to make the timing the + # same (the original encode() call already ran once). + self.assertEqual(hasher.encode.call_count, 3) + + # Get the original salt (includes the original workload factor) + algorithm, data = encoded.split('$', 1) + expected_call = (('wrong_password', force_bytes(data[:29])),) + self.assertEqual(hasher.encode.call_args_list, [expected_call] * 3) + def test_unusable(self): encoded = make_password(None) self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH) @@ -284,6 +307,25 @@ class TestUtilsHashPass(SimpleTestCase): finally: hasher.iterations = old_iterations + def test_pbkdf2_harden_runtime(self): + hasher = get_hasher('default') + self.assertEqual('pbkdf2_sha256', hasher.algorithm) + + with mock.patch.object(hasher, 'iterations', 1): + encoded = make_password('letmein') + + with mock.patch.object(hasher, 'iterations', 6), \ + mock.patch.object(hasher, 'encode', side_effect=hasher.encode): + hasher.harden_runtime('wrong_password', encoded) + + # Encode should get called once ... + self.assertEqual(hasher.encode.call_count, 1) + + # ... with the original salt and 5 iterations. + algorithm, iterations, salt, hash = encoded.split('$', 3) + expected_call = (('wrong_password', salt, 5),) + self.assertEqual(hasher.encode.call_args, expected_call) + def test_pbkdf2_upgrade_new_hasher(self): hasher = get_hasher('default') self.assertEqual('pbkdf2_sha256', hasher.algorithm) @@ -312,6 +354,20 @@ class TestUtilsHashPass(SimpleTestCase): self.assertTrue(check_password('letmein', encoded, setter)) self.assertTrue(state['upgraded']) + def test_check_password_calls_harden_runtime(self): + hasher = get_hasher('default') + encoded = make_password('letmein') + + with mock.patch.object(hasher, 'harden_runtime'), \ + mock.patch.object(hasher, 'must_update', return_value=True): + # Correct password supplied, no hardening needed + check_password('letmein', encoded) + self.assertEqual(hasher.harden_runtime.call_count, 0) + + # Wrong password supplied, hardening needed + check_password('wrong_password', encoded) + self.assertEqual(hasher.harden_runtime.call_count, 1) + def test_load_library_no_algorithm(self): with self.assertRaises(ValueError) as e: BasePasswordHasher()._load_library() |
