From 83022d279c585be2c4173b36a92d4399e738150e Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 22 Jul 2021 09:42:07 +0200 Subject: [PATCH] Refs #32508 -- Raised TypeError/ValueError instead of using "assert" in encode() methods of some password hashers. --- django/contrib/auth/hashers.py | 15 ++++++++------ tests/auth_tests/test_hashers.py | 34 ++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index e53e4da193..ccbac336dc 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -209,6 +209,12 @@ class BasePasswordHasher: """Check if the given password is correct.""" raise NotImplementedError('subclasses of BasePasswordHasher must provide a verify() method') + def _check_encode_args(self, password, salt): + if password is None: + raise TypeError('password must be provided.') + if not salt or '$' in salt: + raise ValueError('salt must be provided and cannot contain $.') + def encode(self, password, salt): """ Create an encoded database value. @@ -269,8 +275,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher): digest = hashlib.sha256 def encode(self, password, salt, iterations=None): - assert password is not None - assert salt and '$' not in salt + self._check_encode_args(password, salt) iterations = iterations or self.iterations hash = pbkdf2(password, salt, iterations, digest=self.digest) hash = base64.b64encode(hash).decode('ascii').strip() @@ -519,8 +524,7 @@ class SHA1PasswordHasher(BasePasswordHasher): algorithm = "sha1" def encode(self, password, salt): - assert password is not None - assert salt and '$' not in salt + self._check_encode_args(password, salt) hash = hashlib.sha1((salt + password).encode()).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) @@ -561,8 +565,7 @@ class MD5PasswordHasher(BasePasswordHasher): algorithm = "md5" def encode(self, password, salt): - assert password is not None - assert salt and '$' not in salt + self._check_encode_args(password, salt) hash = hashlib.md5((salt + password).encode()).hexdigest() return "%s$%s$%s" % (self.algorithm, salt, hash) diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index 411bcea8e9..46ac4062f3 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -4,8 +4,9 @@ from django.conf.global_settings import PASSWORD_HASHERS from django.contrib.auth.hashers import ( UNUSABLE_PASSWORD_PREFIX, UNUSABLE_PASSWORD_SUFFIX_LENGTH, BasePasswordHasher, BCryptPasswordHasher, BCryptSHA256PasswordHasher, - PBKDF2PasswordHasher, PBKDF2SHA1PasswordHasher, check_password, get_hasher, - identify_hasher, is_password_usable, make_password, + MD5PasswordHasher, PBKDF2PasswordHasher, PBKDF2SHA1PasswordHasher, + SHA1PasswordHasher, check_password, get_hasher, identify_hasher, + is_password_usable, make_password, ) from django.test import SimpleTestCase from django.test.utils import override_settings @@ -474,6 +475,35 @@ class TestUtilsHashPass(SimpleTestCase): check_password('wrong_password', encoded) self.assertEqual(hasher.harden_runtime.call_count, 1) + def test_encode_invalid_salt(self): + hasher_classes = [ + MD5PasswordHasher, + PBKDF2PasswordHasher, + PBKDF2SHA1PasswordHasher, + SHA1PasswordHasher, + ] + msg = 'salt must be provided and cannot contain $.' + for hasher_class in hasher_classes: + hasher = hasher_class() + for salt in [None, '', 'sea$salt']: + with self.subTest(hasher_class.__name__, salt=salt): + with self.assertRaisesMessage(ValueError, msg): + hasher.encode('password', salt) + + def test_encode_password_required(self): + hasher_classes = [ + MD5PasswordHasher, + PBKDF2PasswordHasher, + PBKDF2SHA1PasswordHasher, + SHA1PasswordHasher, + ] + msg = 'password must be provided.' + for hasher_class in hasher_classes: + hasher = hasher_class() + with self.subTest(hasher_class.__name__): + with self.assertRaisesMessage(TypeError, msg): + hasher.encode(None, 'seasalt') + class BasePasswordHasherTests(SimpleTestCase): not_implemented_msg = 'subclasses of BasePasswordHasher must provide %s() method'