]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
tests/passwords: Add tests for password history with simple binds
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Mon, 11 Apr 2022 04:43:42 +0000 (16:43 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 5 May 2022 00:27:33 +0000 (00:27 +0000)
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
selftest/knownfail.d/nt-hash-support-gone
source4/dsdb/tests/python/passwords.py

index 6cda102ee9254f52d7eb47c36dfb0d8cbe60e30e..94672c402cbc2c81362e7e401a6f49065ff98dfe 100644 (file)
@@ -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
index 1de3c3cf2029a6708435a6a0b3f42a2cc985415f..dbc21695edadb382c24842aebc9be7bb9cb6d0d1 100755 (executable)
@@ -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)