mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
tests/krb5: Add tests for errors produced when logging in with unusable accounts
Heimdal matches Windows in the no‐FAST case, but produces NTSTATUS codes
when it shouldn’t in the FAST case.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15655
Signed-off-by: Jo Sutton <josutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
(cherry picked from commit c5ee0b60b2
)
This commit is contained in:
parent
d4c1e215a9
commit
41c8a42c8a
@ -58,11 +58,12 @@ from samba.tests import connect_samdb, env_get_var_value, env_loadparm
|
|||||||
|
|
||||||
from samba.tests.krb5.as_req_tests import AsReqBaseTest
|
from samba.tests.krb5.as_req_tests import AsReqBaseTest
|
||||||
from samba.tests.krb5 import kcrypto
|
from samba.tests.krb5 import kcrypto
|
||||||
from samba.tests.krb5.kdc_base_test import KDCBaseTest
|
from samba.tests.krb5.kdc_tgs_tests import KdcTgsBaseTests
|
||||||
from samba.tests.krb5.raw_testcase import KerberosCredentials
|
from samba.tests.krb5.raw_testcase import KerberosCredentials
|
||||||
import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1
|
import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1
|
||||||
from samba.tests.krb5.rfc4120_constants import (
|
from samba.tests.krb5.rfc4120_constants import (
|
||||||
KDC_ERR_CLIENT_REVOKED,
|
KDC_ERR_CLIENT_REVOKED,
|
||||||
|
KDC_ERR_KEY_EXPIRED,
|
||||||
KDC_ERR_PREAUTH_FAILED,
|
KDC_ERR_PREAUTH_FAILED,
|
||||||
KRB_AS_REP,
|
KRB_AS_REP,
|
||||||
KRB_ERROR,
|
KRB_ERROR,
|
||||||
@ -518,7 +519,7 @@ def ldap_pwd_change(pipe,
|
|||||||
return ConnectionResult.SUCCESS
|
return ConnectionResult.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
class LockoutTests(KDCBaseTest):
|
class LockoutTests(KdcTgsBaseTests):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
@ -611,6 +612,211 @@ class LockoutTests(KDCBaseTest):
|
|||||||
def test_lockout_transaction_kdc_ntstatus(self):
|
def test_lockout_transaction_kdc_ntstatus(self):
|
||||||
self.do_lockout_transaction(partial(connect_kdc, expect_status=True))
|
self.do_lockout_transaction(partial(connect_kdc, expect_status=True))
|
||||||
|
|
||||||
|
# Test that performing AS‐REQs with accounts in various states of
|
||||||
|
# unusability results in appropriate NTSTATUS and Kerberos error codes.
|
||||||
|
|
||||||
|
def test_lockout_status_disabled(self):
|
||||||
|
self._run_lockout_status(
|
||||||
|
self._get_creds_disabled(),
|
||||||
|
expected_status=ntstatus.NT_STATUS_ACCOUNT_DISABLED,
|
||||||
|
expected_error=KDC_ERR_CLIENT_REVOKED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_locked_out(self):
|
||||||
|
self._run_lockout_status(
|
||||||
|
self._get_creds_locked_out(),
|
||||||
|
expected_status=ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT,
|
||||||
|
expected_error=KDC_ERR_CLIENT_REVOKED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_expired(self):
|
||||||
|
self._run_lockout_status(
|
||||||
|
self._get_creds_expired(),
|
||||||
|
expected_status=ntstatus.NT_STATUS_ACCOUNT_EXPIRED,
|
||||||
|
expected_error=KDC_ERR_CLIENT_REVOKED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_must_change(self):
|
||||||
|
self._run_lockout_status(
|
||||||
|
self._get_creds_must_change(),
|
||||||
|
expected_status=ntstatus.NT_STATUS_PASSWORD_MUST_CHANGE,
|
||||||
|
expected_error=KDC_ERR_KEY_EXPIRED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_password_expired(self):
|
||||||
|
self._run_lockout_status(
|
||||||
|
self._get_creds_password_expired(),
|
||||||
|
expected_status=ntstatus.NT_STATUS_PASSWORD_EXPIRED,
|
||||||
|
expected_error=KDC_ERR_KEY_EXPIRED,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test that performing the same AS‐REQs, this time with FAST, does not
|
||||||
|
# result in NTSTATUS codes.
|
||||||
|
|
||||||
|
def test_lockout_status_disabled_fast(self):
|
||||||
|
self._run_lockout_status_fast(
|
||||||
|
self._get_creds_disabled(), expected_error=KDC_ERR_CLIENT_REVOKED
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_locked_out_fast(self):
|
||||||
|
self._run_lockout_status_fast(
|
||||||
|
self._get_creds_locked_out(), expected_error=KDC_ERR_CLIENT_REVOKED
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_expired_fast(self):
|
||||||
|
self._run_lockout_status_fast(
|
||||||
|
self._get_creds_expired(), expected_error=KDC_ERR_CLIENT_REVOKED
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_must_change_fast(self):
|
||||||
|
self._run_lockout_status_fast(
|
||||||
|
self._get_creds_must_change(), expected_error=KDC_ERR_KEY_EXPIRED
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_lockout_status_password_expired_fast(self):
|
||||||
|
self._run_lockout_status_fast(
|
||||||
|
self._get_creds_password_expired(), expected_error=KDC_ERR_KEY_EXPIRED
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_creds_disabled(self):
|
||||||
|
return self.get_cached_creds(
|
||||||
|
account_type=self.AccountType.USER, opts={"enabled": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_creds_locked_out(self) -> KerberosCredentials:
|
||||||
|
samdb = self.get_samdb()
|
||||||
|
|
||||||
|
user_creds = self.get_cached_creds(
|
||||||
|
account_type=self.AccountType.USER, use_cache=False
|
||||||
|
)
|
||||||
|
user_dn = user_creds.get_dn()
|
||||||
|
|
||||||
|
# Lock out the account.
|
||||||
|
|
||||||
|
old_utf16pw = '"Secret007"'.encode("utf-16le") # invalid pwd
|
||||||
|
new_utf16pw = '"Secret008"'.encode("utf-16le")
|
||||||
|
|
||||||
|
msg = ldb.Message(user_dn)
|
||||||
|
msg["0"] = ldb.MessageElement(old_utf16pw, ldb.FLAG_MOD_DELETE, "unicodePwd")
|
||||||
|
msg["1"] = ldb.MessageElement(new_utf16pw, ldb.FLAG_MOD_ADD, "unicodePwd")
|
||||||
|
|
||||||
|
for _ in range(self.lockout_threshold):
|
||||||
|
try:
|
||||||
|
samdb.modify(msg)
|
||||||
|
except ldb.LdbError as err:
|
||||||
|
num, _ = err.args
|
||||||
|
|
||||||
|
# We get an error, but the bad password count should
|
||||||
|
# still be updated.
|
||||||
|
self.assertEqual(num, ldb.ERR_CONSTRAINT_VIOLATION)
|
||||||
|
else:
|
||||||
|
self.fail("pwd change should have failed")
|
||||||
|
|
||||||
|
# Ensure the account is locked out.
|
||||||
|
|
||||||
|
res = samdb.search(
|
||||||
|
user_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-User-Account-Control-Computed"]
|
||||||
|
)
|
||||||
|
self.assertEqual(1, len(res))
|
||||||
|
|
||||||
|
uac = int(res[0].get("msDS-User-Account-Control-Computed", idx=0))
|
||||||
|
self.assertTrue(uac & dsdb.UF_LOCKOUT)
|
||||||
|
|
||||||
|
return user_creds
|
||||||
|
|
||||||
|
def _get_creds_expired(self) -> KerberosCredentials:
|
||||||
|
return self.get_cached_creds(
|
||||||
|
account_type=self.AccountType.USER,
|
||||||
|
opts={"additional_details": self.freeze({"accountExpires": "1"})},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_creds_must_change(self) -> KerberosCredentials:
|
||||||
|
return self.get_cached_creds(
|
||||||
|
account_type=self.AccountType.USER,
|
||||||
|
opts={"additional_details": self.freeze({"pwdLastSet": "0"})},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_creds_password_expired(self) -> KerberosCredentials:
|
||||||
|
samdb = self.get_samdb()
|
||||||
|
self.addCleanup(samdb.set_maxPwdAge, samdb.get_maxPwdAge())
|
||||||
|
low_pwd_age = -2
|
||||||
|
samdb.set_maxPwdAge(low_pwd_age)
|
||||||
|
|
||||||
|
return self.get_cached_creds(account_type=self.AccountType.USER)
|
||||||
|
|
||||||
|
def _run_lockout_status(
|
||||||
|
self,
|
||||||
|
user_creds: KerberosCredentials,
|
||||||
|
*,
|
||||||
|
expected_status: int,
|
||||||
|
expected_error: int,
|
||||||
|
) -> None:
|
||||||
|
user_name = user_creds.get_username()
|
||||||
|
cname = self.PrincipalName_create(
|
||||||
|
name_type=NT_PRINCIPAL, names=user_name.split("/")
|
||||||
|
)
|
||||||
|
|
||||||
|
krbtgt_creds = self.get_krbtgt_creds()
|
||||||
|
realm = krbtgt_creds.get_realm()
|
||||||
|
|
||||||
|
sname = self.get_krbtgt_sname()
|
||||||
|
|
||||||
|
preauth_key = self.PasswordKey_from_creds(user_creds, kcrypto.Enctype.AES256)
|
||||||
|
|
||||||
|
ts_enc_padata = self.get_enc_timestamp_pa_data_from_key(preauth_key)
|
||||||
|
padata = [ts_enc_padata]
|
||||||
|
|
||||||
|
def _generate_padata_copy(_kdc_exchange_dict, _callback_dict, req_body):
|
||||||
|
return padata, req_body
|
||||||
|
|
||||||
|
kdc_exchange_dict = self.as_exchange_dict(
|
||||||
|
creds=user_creds,
|
||||||
|
expected_crealm=realm,
|
||||||
|
expected_cname=cname,
|
||||||
|
expected_srealm=realm,
|
||||||
|
expected_sname=sname,
|
||||||
|
expected_account_name=user_name,
|
||||||
|
expected_supported_etypes=krbtgt_creds.tgs_supported_enctypes,
|
||||||
|
expect_edata=True,
|
||||||
|
expect_status=True,
|
||||||
|
expected_status=expected_status,
|
||||||
|
ticket_decryption_key=self.TicketDecryptionKey_from_creds(krbtgt_creds),
|
||||||
|
generate_padata_fn=_generate_padata_copy,
|
||||||
|
check_error_fn=self.generic_check_kdc_error,
|
||||||
|
check_rep_fn=None,
|
||||||
|
check_kdc_private_fn=self.generic_check_kdc_private,
|
||||||
|
expected_error_mode=expected_error,
|
||||||
|
expected_salt=user_creds.get_salt(),
|
||||||
|
preauth_key=preauth_key,
|
||||||
|
kdc_options=str(krb5_asn1.KDCOptions("postdated")),
|
||||||
|
pac_request=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try making a Kerberos AS-REQ to the KDC. This might fail, either due
|
||||||
|
# to the user's account being locked out or due to using the wrong
|
||||||
|
# password.
|
||||||
|
self._generic_kdc_exchange(
|
||||||
|
kdc_exchange_dict,
|
||||||
|
cname=cname,
|
||||||
|
realm=realm,
|
||||||
|
sname=sname,
|
||||||
|
till_time=self.get_KerberosTime(offset=36000),
|
||||||
|
etypes=self.get_default_enctypes(user_creds),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_lockout_status_fast(
|
||||||
|
self, user_creds: KerberosCredentials, *, expected_error: int
|
||||||
|
) -> None:
|
||||||
|
self._armored_as_req(
|
||||||
|
user_creds,
|
||||||
|
self.get_krbtgt_creds(),
|
||||||
|
self.get_tgt(self.get_mach_creds()),
|
||||||
|
expected_error=expected_error,
|
||||||
|
expect_edata=self.expect_padata_outer,
|
||||||
|
# FAST‐armored responses never contain an NTSTATUS code.
|
||||||
|
expect_status=False,
|
||||||
|
)
|
||||||
|
|
||||||
def test_lockout_transaction_ntlm(self):
|
def test_lockout_transaction_ntlm(self):
|
||||||
self.do_lockout_transaction(connect_ntlm)
|
self.do_lockout_transaction(connect_ntlm)
|
||||||
|
|
||||||
|
@ -156,3 +156,11 @@
|
|||||||
^samba\.tests\.krb5\.conditional_ace_tests\.samba\.tests\.krb5\.conditional_ace_tests\.TgsReqServicePolicyTests\.test_pac_device_info_no_compound_id_support_no_claims_valid_existing_device_claims_target_policy\(ad_dc\)$
|
^samba\.tests\.krb5\.conditional_ace_tests\.samba\.tests\.krb5\.conditional_ace_tests\.TgsReqServicePolicyTests\.test_pac_device_info_no_compound_id_support_no_claims_valid_existing_device_claims_target_policy\(ad_dc\)$
|
||||||
^samba\.tests\.krb5\.conditional_ace_tests\.samba\.tests\.krb5\.conditional_ace_tests\.TgsReqServicePolicyTests\.test_pac_device_info_no_compound_id_support_no_claims_valid_existing_device_claims\(ad_dc\)$
|
^samba\.tests\.krb5\.conditional_ace_tests\.samba\.tests\.krb5\.conditional_ace_tests\.TgsReqServicePolicyTests\.test_pac_device_info_no_compound_id_support_no_claims_valid_existing_device_claims\(ad_dc\)$
|
||||||
^samba\.tests\.krb5\.conditional_ace_tests\.samba\.tests\.krb5\.conditional_ace_tests\.TgsReqServicePolicyTests\.test_pac_device_info_rodc_issued\(ad_dc\)$
|
^samba\.tests\.krb5\.conditional_ace_tests\.samba\.tests\.krb5\.conditional_ace_tests\.TgsReqServicePolicyTests\.test_pac_device_info_rodc_issued\(ad_dc\)$
|
||||||
|
#
|
||||||
|
# Lockout tests
|
||||||
|
#
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_disabled_fast\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_expired_fast\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_locked_out_fast\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_must_change_fast\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_password_expired_fast\(ad_dc:local\)$
|
||||||
|
@ -532,6 +532,11 @@ samba.tests.krb5.as_canonicalization_tests.samba.tests.krb5.as_canonicalization_
|
|||||||
^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_kdc_ntstatus.ad_dc:local
|
^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_kdc_ntstatus.ad_dc:local
|
||||||
^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_rename_kdc.ad_dc:local
|
^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_rename_kdc.ad_dc:local
|
||||||
^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_rename_kdc_ntstatus.ad_dc:local
|
^samba.tests.krb5.lockout_tests.samba.tests.krb5.lockout_tests.LockoutTests.test_lockout_transaction_rename_kdc_ntstatus.ad_dc:local
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_disabled\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_expired\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_locked_out\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_must_change\(ad_dc:local\)$
|
||||||
|
^samba\.tests\.krb5\.lockout_tests\.samba\.tests\.krb5\.lockout_tests\.LockoutTests\.test_lockout_status_password_expired\(ad_dc:local\)$
|
||||||
#
|
#
|
||||||
# Encryption type tests
|
# Encryption type tests
|
||||||
#
|
#
|
||||||
|
Loading…
Reference in New Issue
Block a user