From: Joseph Sutton Date: Mon, 11 Apr 2022 04:43:42 +0000 (+1200) Subject: tests/passwords: Add tests for password history with simple binds X-Git-Tag: talloc-2.3.4~260 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c294f729110f59b68c567bfe2b6da3a297a829a9;p=thirdparty%2Fsamba.git tests/passwords: Add tests for password history with simple binds Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- diff --git a/selftest/knownfail.d/nt-hash-support-gone b/selftest/knownfail.d/nt-hash-support-gone index 6cda102ee92..94672c402cb 100644 --- a/selftest/knownfail.d/nt-hash-support-gone +++ b/selftest/knownfail.d/nt-hash-support-gone @@ -1,2 +1,8 @@ ^samba.tests.samba_tool.user.samba.tests.samba_tool.user.UserCmdTestCase.test_setpassword.ad_dc_no_ntlm:local ^samba4.ldap.login_basics.python.ad_dc_no_ntlm..__main__.BasicUserAuthTests.test_login_basics_ntlm.ad_dc_no_ntlm +^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_attempt_reuse.fl2003dc +^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_attempt_reuse.fl2003dc +^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_attempt_reuse_2.fl2003dc +^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_simple_bind.fl2003dc +^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_rename_simple_bind_2.fl2003dc +^samba4.ldap.passwords.python.fl2003dc..__main__.PasswordTests.test_old_password_simple_bind.fl2003dc diff --git a/source4/dsdb/tests/python/passwords.py b/source4/dsdb/tests/python/passwords.py index 1de3c3cf202..dbc21695eda 100755 --- a/source4/dsdb/tests/python/passwords.py +++ b/source4/dsdb/tests/python/passwords.py @@ -23,15 +23,18 @@ import samba.getopt as options from samba.auth import system_session from samba.credentials import Credentials -from samba.dcerpc import security +from samba.dcerpc import drsblobs, misc, security +from samba.drs_utils import drsuapi_connect +from samba.ndr import ndr_unpack from ldb import SCOPE_BASE, LdbError from ldb import ERR_ATTRIBUTE_OR_VALUE_EXISTS from ldb import ERR_UNWILLING_TO_PERFORM, ERR_INSUFFICIENT_ACCESS_RIGHTS from ldb import ERR_NO_SUCH_ATTRIBUTE from ldb import ERR_CONSTRAINT_VIOLATION +from ldb import ERR_INVALID_CREDENTIALS from ldb import Message, MessageElement, Dn from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE -from samba import gensec +from samba import gensec, net, werror from samba.samdb import SamDB from samba.tests import delete_force @@ -70,12 +73,6 @@ class PasswordTests(PasswordTestCase): super(PasswordTests, self).setUp() self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) - # Gets back the basedn - base_dn = self.ldb.domain_dn() - - # Gets back the configuration basedn - configuration_dn = self.ldb.get_config_basedn().get_linearized() - # permit password changes during this test self.allow_password_changes() @@ -148,6 +145,7 @@ add: userPassword creds2.set_gensec_features(creds2.get_gensec_features() | gensec.FEATURE_SEAL) self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp) + self.creds = creds2 def test_unicodePwd_hash_set(self): """Performs a password hash set operation on 'unicodePwd' which should be prevented""" @@ -236,6 +234,241 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')). self.assertEqual(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) + def test_old_password_simple_bind(self): + '''Shows that we can log in with the immediate previous password, but not any earlier passwords.''' + + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show we can still log in using the previous password. + self.creds.set_bind_dn(user_dn_str) + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + # Change the account password a second time. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#3', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show we can no longer log in using the original password. + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError as err: + HRES_SEC_E_INVALID_TOKEN = '80090308' + + num, estr = err.args + self.assertEqual(ERR_INVALID_CREDENTIALS, num) + self.assertIn(HRES_SEC_E_INVALID_TOKEN, estr) + else: + self.fail('should have failed to login with previous password!') + + def test_old_password_attempt_reuse(self): + '''Shows that we cannot reuse the original password after changing the password twice.''' + res = self.ldb.search(self.ldb.domain_dn(), scope=SCOPE_BASE, + attrs=['pwdHistoryLength']) + + history_len = int(res[0].get('pwdHistoryLength', idx=0)) + self.assertGreaterEqual(history_len, 3) + + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + first_pwd = self.creds.get_password() + previous_pwd = first_pwd + + for new_pwd in ['Password#0', 'Password#1']: + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(previous_pwd, + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(new_pwd, + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that the original password is in the history by trying to + # set it as our new password. + m = Message(user_dn) + m['0'] = MessageElement(new_pwd, + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(first_pwd, + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + + previous_pwd = new_pwd + + def test_old_password_rename_simple_bind(self): + '''Shows that we can log in with the previous password after renaming the account.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show we can still log in using the previous password. + self.creds.set_bind_dn(user_dn_str) + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Show that a simple bind can still be performed using the previous + # password. + self.creds.set_username('testuser_2') + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + def test_old_password_rename_simple_bind_2(self): + '''Shows that we can rename the account, change the password and log in with the previous password.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Change the account password, causing the new salt to be stored. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that a simple bind can still be performed using the previous + # password. + self.creds.set_bind_dn(user_dn_str) + self.creds.set_username('testuser_2') + try: + SamDB(url=host_ldaps, + credentials=self.creds, lp=lp) + except LdbError: + self.fail('failed to login with previous password!') + + def test_old_password_rename_attempt_reuse(self): + '''Shows that we cannot reuse the original password after renaming the account.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Change the account password. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that the previous password is in the history by trying to set it + # as our new password. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(self.creds.get_password(), + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Show that the previous password is still in the history by trying to + # set it as our new password. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(self.creds.get_password(), + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + + def test_old_password_rename_attempt_reuse_2(self): + '''Shows that we cannot reuse the original password after renaming the account and changing the password.''' + user_dn_str = f'CN=testuser,CN=Users,{self.base_dn}' + user_dn = Dn(self.ldb, user_dn_str) + + # Rename the account, causing the salt to change. + m = Message(user_dn) + m['1'] = MessageElement('testuser_2', + FLAG_MOD_REPLACE, 'sAMAccountName') + self.ldb.modify(m) + + # Change the account password, causing the new salt to be stored. + m = Message(user_dn) + m['0'] = MessageElement(self.creds.get_password(), + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement('Password#2', + FLAG_MOD_ADD, 'userPassword') + self.ldb.modify(m) + + # Show that the previous password is in the history by trying to set it + # as our new password. + m = Message(user_dn) + m['0'] = MessageElement('Password#2', + FLAG_MOD_DELETE, 'userPassword') + m['1'] = MessageElement(self.creds.get_password(), + FLAG_MOD_ADD, 'userPassword') + try: + self.ldb.modify(m) + except LdbError as err: + num, estr = err.args + self.assertEqual(ERR_CONSTRAINT_VIOLATION, num) + self.assertIn(f'{werror.WERR_PASSWORD_RESTRICTION:08X}', estr) + else: + self.fail('should not have been able to reuse password!') + def test_protected_unicodePwd_clear_set(self): """Performs a password cleartext set operation on 'unicodePwd' with the user in the Protected Users group""" @@ -1212,8 +1445,10 @@ unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')). if "://" not in host: if os.path.isfile(host): + host_ldaps = None host = "tdb://%s" % host else: + host_ldaps = "ldaps://%s" % host host = "ldap://%s" % host TestProgram(module=__name__, opts=subunitopts)