The Samba-Bugzilla – Attachment 18125 Details for
Bug 15045
[SECURITY] UF_SMARTCARD_REQUIRED not consistently honoured
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
WIP patch for Samba
bug-15045-samba.patch (text/plain), 37.55 KB, created by
Jennifer Sutton
on 2023-09-26 03:03:29 UTC
(
hide
)
Description:
WIP patch for Samba
Filename:
MIME Type:
Creator:
Jennifer Sutton
Created:
2023-09-26 03:03:29 UTC
Size:
37.55 KB
patch
obsolete
>From d9c5387208cfee87c845261e7cdc68da4f3ec88b Mon Sep 17 00:00:00 2001 >From: Joseph Sutton <josephsutton@catalyst.net.nz> >Date: Fri, 29 Apr 2022 12:24:26 +1200 >Subject: [PATCH 1/6] s4:kdc: Don't generate random keys for accounts requiring > smartcard login > >Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> >--- > source4/kdc/db-glue.c | 36 ------------------------------------ > 1 file changed, 36 deletions(-) > >diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c >index d421e6ead61..c50ea0706ba 100644 >--- a/source4/kdc/db-glue.c >+++ b/source4/kdc/db-glue.c >@@ -412,31 +412,6 @@ out: > return ret; > } > >- >-static int samba_kdc_set_random_keys(krb5_context context, >- uint32_t supported_enctypes, >- struct sdb_keys *keys) >-{ >- struct ldb_val secret_val; >- uint8_t secretbuffer[32]; >- >- /* >- * Fake keys until we have a better way to reject >- * non-pkinit requests. >- * >- * We just need to indicate which encryption types are >- * supported. >- */ >- generate_secret_buffer(secretbuffer, sizeof(secretbuffer)); >- >- secret_val = data_blob_const(secretbuffer, >- sizeof(secretbuffer)); >- return samba_kdc_set_fixed_keys(context, >- &secret_val, >- supported_enctypes, >- keys); >-} >- > struct samba_kdc_user_keys { > struct sdb_keys *skeys; > uint32_t kvno; >@@ -629,17 +604,6 @@ krb5_error_code samba_kdc_message2entry_keys(krb5_context context, > } > } > >- if ((ent_type == SAMBA_KDC_ENT_TYPE_CLIENT) >- && (userAccountControl & UF_SMARTCARD_REQUIRED)) { >- ret = samba_kdc_set_random_keys(context, >- supported_enctypes, >- &entry->keys); >- >- *supported_enctypes_out = supported_enctypes & ENC_ALL_TYPES; >- >- goto out; >- } >- > current_kvno = ldb_msg_find_attr_as_int(msg, "msDS-KeyVersionNumber", 0); > if (current_kvno > 1) { > old_kvno = current_kvno - 1; >-- >2.39.1 > > >From 509b1eaa063de5cf6d9bf3f1affd84c7f8423fdc Mon Sep 17 00:00:00 2001 >From: Joseph Sutton <josephsutton@catalyst.net.nz> >Date: Tue, 17 May 2022 20:31:39 +1200 >Subject: [PATCH 2/6] s4:kdc: Remove unused parameters from > samba_kdc_message2entry_keys() > >Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> >--- > source4/auth/ntlm/auth_sam.c | 7 ------- > source4/dsdb/samdb/ldb_modules/password_hash.c | 2 -- > source4/kdc/db-glue.c | 11 ++--------- > source4/kdc/db-glue.h | 3 --- > 4 files changed, 2 insertions(+), 21 deletions(-) > >diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c >index 7b65b3084f8..b4b231a8ca8 100644 >--- a/source4/auth/ntlm/auth_sam.c >+++ b/source4/auth/ntlm/auth_sam.c >@@ -317,7 +317,6 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con > DATA_BLOB salt_data = data_blob_null; > struct smb_krb5_context *smb_krb5_context = NULL; > const struct ldb_val *sc_val; >- uint32_t userAccountControl = 0; > uint32_t current_kvno = 0; > bool am_rodc; > >@@ -341,10 +340,6 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con > return nt_status; > } > >- userAccountControl = ldb_msg_find_attr_as_uint(msg, >- "userAccountControl", >- 0); >- > sc_val = ldb_msg_find_ldb_val(msg, "supplementalCredentials"); > > if (nt_pwd == NULL && sc_val == NULL) { >@@ -401,7 +396,6 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con > krb5_ret = dsdb_extract_aes_256_key(smb_krb5_context->krb5_context, > tmp_ctx, > msg, >- userAccountControl, > NULL, /* kvno */ > ¤t_kvno, /* kvno_out */ > &_aes_256_key, >@@ -552,7 +546,6 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con > krb5_ret = dsdb_extract_aes_256_key(smb_krb5_context->krb5_context, > tmp_ctx, > msg, >- userAccountControl, > &request_kvno, /* kvno */ > NULL, /* kvno_out */ > &_aes_256_key, >diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c >index 8baf5085215..39df97d88ed 100644 >--- a/source4/dsdb/samdb/ldb_modules/password_hash.c >+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c >@@ -3165,7 +3165,6 @@ static int check_password_restrictions(struct setup_password_fields_io *io, WERR > krb5_ret = dsdb_extract_aes_256_key(io->smb_krb5_context->krb5_context, > io->ac, > io->ac->search_res->message, >- io->u.userAccountControl, > &request_kvno, /* kvno */ > NULL, /* kvno_out */ > &db_key_blob, >@@ -4064,7 +4063,6 @@ static int setup_io(struct ph_context *ac, > krb5_ret = dsdb_extract_aes_256_key(io->smb_krb5_context->krb5_context, > io->ac, > existing_msg, >- io->u.userAccountControl, > NULL, /* kvno */ > &kvno, /* kvno_out */ > &key_blob, >diff --git a/source4/kdc/db-glue.c b/source4/kdc/db-glue.c >index c50ea0706ba..5377a41e428 100644 >--- a/source4/kdc/db-glue.c >+++ b/source4/kdc/db-glue.c >@@ -558,8 +558,6 @@ krb5_error_code samba_kdc_message2entry_keys(krb5_context context, > const struct ldb_message *msg, > bool is_krbtgt, > bool is_rodc, >- uint32_t userAccountControl, >- enum samba_kdc_ent_type ent_type, > unsigned flags, > krb5_kvno requested_kvno, > struct sdb_entry *entry, >@@ -1559,8 +1557,7 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context, > /* Get keys from the db */ > ret = samba_kdc_message2entry_keys(context, p, msg, > is_krbtgt, is_rodc, >- userAccountControl, >- ent_type, flags, kvno, entry, >+ flags, kvno, entry, > supported_enctypes, > &available_enctypes); > if (ret) { >@@ -1585,8 +1582,7 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context, > supported_enctypes = ENC_RC4_HMAC_MD5; > ret = samba_kdc_message2entry_keys(context, p, msg, > is_krbtgt, is_rodc, >- userAccountControl, >- ent_type, flags, kvno, entry, >+ flags, kvno, entry, > supported_enctypes, > &available_enctypes); > if (ret) { >@@ -3642,7 +3638,6 @@ NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_conte > krb5_error_code dsdb_extract_aes_256_key(krb5_context context, > TALLOC_CTX *mem_ctx, > const struct ldb_message *msg, >- uint32_t user_account_control, > const uint32_t *kvno, > uint32_t *kvno_out, > DATA_BLOB *aes_256_key, >@@ -3662,8 +3657,6 @@ krb5_error_code dsdb_extract_aes_256_key(krb5_context context, > msg, > false, /* is_krbtgt */ > false, /* is_rodc */ >- user_account_control, >- SAMBA_KDC_ENT_TYPE_CLIENT, > flags, > (kvno != NULL) ? *kvno : 0, > &sentry, >diff --git a/source4/kdc/db-glue.h b/source4/kdc/db-glue.h >index f37e6e96731..76ecde2cd3a 100644 >--- a/source4/kdc/db-glue.h >+++ b/source4/kdc/db-glue.h >@@ -45,8 +45,6 @@ krb5_error_code samba_kdc_message2entry_keys(krb5_context context, > const struct ldb_message *msg, > bool is_krbtgt, > bool is_rodc, >- uint32_t userAccountControl, >- enum samba_kdc_ent_type ent_type, > unsigned flags, > krb5_kvno requested_kvno, > struct sdb_entry *entry, >@@ -104,7 +102,6 @@ NTSTATUS samba_kdc_setup_db_ctx(TALLOC_CTX *mem_ctx, struct samba_kdc_base_conte > krb5_error_code dsdb_extract_aes_256_key(krb5_context context, > TALLOC_CTX *mem_ctx, > const struct ldb_message *msg, >- uint32_t user_account_control, > const uint32_t *kvno, > uint32_t *kvno_out, > DATA_BLOB *aes_256_key, >-- >2.39.1 > > >From d395e77ae86f2d69adcd0ef6738cc4754d3ebb29 Mon Sep 17 00:00:00 2001 >From: Joseph Sutton <josephsutton@catalyst.net.nz> >Date: Tue, 24 May 2022 18:31:34 +1200 >Subject: [PATCH 3/6] dsdb: Extract the AES256 key even if the account requires > a smartcard for login > >Having this early return makes things inconsistent, since we'll fall >back to NT hashes in check_password_restrictions(); that will fail iff >NTLM authentication is disabled. Either we should disable password >changes entirely with smartcard_required, or always allow them -- >irrespective of NTLM authentication. > >Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> >--- > source4/dsdb/samdb/ldb_modules/password_hash.c | 8 -------- > 1 file changed, 8 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c >index 39df97d88ed..b2142b0b985 100644 >--- a/source4/dsdb/samdb/ldb_modules/password_hash.c >+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c >@@ -4048,14 +4048,6 @@ static int setup_io(struct ph_context *ac, > } > } > >- /* >- * If this account requires a smartcard for login, we don't >- * attempt a comparison with the old password. >- */ >- if (io->u.userAccountControl & UF_SMARTCARD_REQUIRED) { >- return LDB_SUCCESS; >- } >- > /* > * Extract the old ENCTYPE_AES256_CTS_HMAC_SHA1_96 > * value from the supplementalCredentials. >-- >2.39.1 > > >From 559df3d7e26128dac7bad21347fc2be7c4b3f203 Mon Sep 17 00:00:00 2001 >From: Joseph Sutton <josephsutton@catalyst.net.nz> >Date: Tue, 24 May 2022 20:00:39 +1200 >Subject: [PATCH 4/6] [TODO] tests/krb5: Add tests for accounts requiring a > smartcard for logon > >Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> >--- > python/samba/tests/krb5/kpasswd_tests.py | 4 - > python/samba/tests/krb5/raw_testcase.py | 4 + > python/samba/tests/krb5/smartcard_tests.py | 448 +++++++++++++++++++++ > source4/selftest/tests.py | 7 + > 4 files changed, 459 insertions(+), 4 deletions(-) > create mode 100755 python/samba/tests/krb5/smartcard_tests.py > >diff --git a/python/samba/tests/krb5/kpasswd_tests.py b/python/samba/tests/krb5/kpasswd_tests.py >index 961feeac243..7c63d81c96d 100755 >--- a/python/samba/tests/krb5/kpasswd_tests.py >+++ b/python/samba/tests/krb5/kpasswd_tests.py >@@ -90,10 +90,6 @@ class KpasswdTests(KDCBaseTest): > > return creds > >- def get_kpasswd_sname(self): >- return self.PrincipalName_create(name_type=NT_PRINCIPAL, >- names=['kadmin', 'changepw']) >- > def get_ticket_lifetime(self, ticket): > enc_part = ticket.ticket_private > >diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py >index 3ba0b9df08b..78579103e29 100644 >--- a/python/samba/tests/krb5/raw_testcase.py >+++ b/python/samba/tests/krb5/raw_testcase.py >@@ -5963,6 +5963,10 @@ class RawKerberosTest(TestCase): > > return krbtgt_sname > >+ def get_kpasswd_sname(self): >+ return self.PrincipalName_create(name_type=NT_PRINCIPAL, >+ names=['kadmin', 'changepw']) >+ > def add_requester_sid(self, pac, sid): > pac_buffers = pac.buffers > >diff --git a/python/samba/tests/krb5/smartcard_tests.py b/python/samba/tests/krb5/smartcard_tests.py >new file mode 100755 >index 00000000000..6ec70246cf0 >--- /dev/null >+++ b/python/samba/tests/krb5/smartcard_tests.py >@@ -0,0 +1,448 @@ >+#!/usr/bin/env python3 >+# Unix SMB/CIFS implementation. >+# Copyright (C) Stefan Metzmacher 2020 >+# >+# This program is free software; you can redistribute it and/or modify >+# it under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# This program is distributed in the hope that it will be useful, >+# but WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with this program. If not, see <http://www.gnu.org/licenses/>. >+# >+ >+import os >+import subprocess >+import sys >+ >+import ldb >+ >+from samba import generate_random_password, ntstatus >+from samba.dcerpc import netlogon >+from samba.dsdb import UF_SMARTCARD_REQUIRED >+from samba.samdb import SamDB >+ >+from samba.tests.krb5 import kcrypto >+from samba.tests.krb5.kdc_base_test import KDCBaseTest >+from samba.tests.krb5.rfc4120_constants import ( >+ AES256_CTS_HMAC_SHA1_96, >+ ARCFOUR_HMAC_MD5, >+ KDC_ERR_POLICY, >+ KDC_ERR_PREAUTH_REQUIRED, >+ KPASSWD_SUCCESS, >+ NT_PRINCIPAL, >+ NT_SRV_INST, >+) >+import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1 >+ >+sys.path.insert(0, 'bin/python') >+os.environ['PYTHONUNBUFFERED'] = '1' >+ >+global_asn1_print = False >+global_hexdump = False >+ >+ >+class SmartcardTests(KDCBaseTest): >+ >+ def setUp(self): >+ super().setUp() >+ self.do_asn1_print = global_asn1_print >+ self.do_hexdump = global_hexdump >+ >+ samdb = self.get_samdb() >+ >+ # Get the old 'dSHeuristics' if it was set >+ dsheuristics = samdb.get_dsheuristics() >+ >+ # Reset the 'dSHeuristics' as they were before >+ self.addCleanup(samdb.set_dsheuristics, dsheuristics) >+ >+ # Set the 'dSHeuristics' to activate the correct 'userPassword' >+ # behaviour >+ samdb.set_dsheuristics('000000001') >+ >+ # Get the old 'minPwdAge' >+ minPwdAge = samdb.get_minPwdAge() >+ >+ # Reset the 'minPwdAge' as it was before >+ self.addCleanup(samdb.set_minPwdAge, minPwdAge) >+ >+ # Set it temporarily to '0' >+ samdb.set_minPwdAge('0') >+ >+ def _get_creds(self, ntlm=False): >+ opts = { >+ 'kerberos_enabled': not ntlm, >+ } >+ >+ # Create the account. >+ creds = self.get_cached_creds(account_type=self.AccountType.USER, >+ opts=opts, >+ use_cache=False) >+ >+ return creds >+ >+ def require_smartcard(self, creds): >+ samdb = self.get_samdb() >+ dn = creds.get_dn() >+ >+ res = samdb.search(dn, scope=ldb.SCOPE_BASE, >+ attrs=['userAccountControl']) >+ uac = int(res[0].get('userAccountControl', idx=0)) >+ >+ # Set the SMARTCARD_REQUIRED bit on the account. >+ uac |= UF_SMARTCARD_REQUIRED >+ >+ msg = ldb.Message(dn) >+ msg['userAccountControl'] = ldb.MessageElement( >+ str(uac), >+ ldb.FLAG_MOD_REPLACE, >+ 'userAccountControl') >+ samdb.modify(msg) >+ >+ # The account's keys have been randomized, so set the password to >+ # something we know. >+ >+ new_password = generate_random_password(32, 32) >+ >+ msg = ldb.Message(dn) >+ msg['userPassword'] = ldb.MessageElement(new_password, >+ ldb.FLAG_MOD_REPLACE, >+ 'userPassword') >+ samdb.modify(msg) >+ >+ creds.update_password(new_password) >+ >+ def test_ntlm(self): >+ creds = self._get_creds(ntlm=True) >+ >+ if self.expect_nt_hash: >+ error = None >+ else: >+ error = f'{ntstatus.NT_STATUS_NTLM_BLOCKED & 0xffff:x}' >+ self._connect(creds, simple_bind=False, expect_error=error) >+ >+ def test_ntlm_smartcard(self): >+ creds = self._get_creds(ntlm=True) >+ self.require_smartcard(creds) >+ >+ if self.expect_nt_hash: >+ error = None >+ else: >+ error = f'{ntstatus.NT_STATUS_NTLM_BLOCKED & 0xffff:x}' >+ self._connect(creds, simple_bind=False, expect_error=error) >+ >+ def test_simple_bind(self): >+ creds = self._get_creds() >+ >+ self._connect(creds, simple_bind=True) >+ >+ def test_simple_bind_smartcard(self): >+ creds = self._get_creds() >+ self.require_smartcard(creds) >+ >+ error = f'{ntstatus.NT_STATUS_SMARTCARD_LOGON_REQUIRED & 0xffff:x}' >+ self._connect(creds, simple_bind=True, expect_error=error) >+ >+ def test_get_tgt(self): >+ creds = self._get_creds() >+ >+ self._check_tgt(creds) >+ >+ def test_get_tgt_smartcard(self): >+ creds = self._get_creds() >+ self.require_smartcard(creds) >+ >+ self._check_tgt(creds, expect_error=True) >+ >+ def _check_tgt(self, creds, expect_error=False): >+ user_name = creds.get_username() >+ >+ realm = creds.get_realm() >+ >+ salt = creds.get_salt() >+ >+ etype = AES256_CTS_HMAC_SHA1_96, ARCFOUR_HMAC_MD5 >+ cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, >+ names=user_name.split('/')) >+ sname = self.PrincipalName_create(name_type=NT_SRV_INST, >+ names=['krbtgt', realm]) >+ >+ till = self.get_KerberosTime(offset=36000) >+ >+ krbtgt_creds = self.get_krbtgt_creds() >+ ticket_decryption_key = ( >+ self.TicketDecryptionKey_from_creds(krbtgt_creds)) >+ >+ expected_etypes = krbtgt_creds.tgs_supported_enctypes >+ >+ kdc_options = ('forwardable,' >+ 'renewable,' >+ 'canonicalize,' >+ 'renewable-ok') >+ kdc_options = krb5_asn1.KDCOptions(kdc_options) >+ >+ pac_request = True >+ pac_options = '1' # supports claims >+ >+ rep, kdc_exchange_dict = self._test_as_exchange( >+ cname=cname, >+ realm=realm, >+ sname=sname, >+ till=till, >+ client_as_etypes=etype, >+ expected_error_mode=KDC_ERR_PREAUTH_REQUIRED, >+ expected_crealm=realm, >+ expected_cname=cname, >+ expected_srealm=realm, >+ expected_sname=sname, >+ expected_salt=salt, >+ expected_supported_etypes=expected_etypes, >+ etypes=etype, >+ padata=None, >+ kdc_options=kdc_options, >+ preauth_key=None, >+ ticket_decryption_key=ticket_decryption_key, >+ pac_request=pac_request, >+ pac_options=pac_options) >+ self.check_error_rep(rep, KDC_ERR_PREAUTH_REQUIRED) >+ >+ preauth_key = self.PasswordKey_from_creds(creds, >+ kcrypto.Enctype.AES256) >+ >+ ts_enc_padata = self.get_enc_timestamp_pa_data_from_key(preauth_key) >+ >+ padata = [ts_enc_padata] >+ >+ if expect_error: >+ expected_error_mode = KDC_ERR_POLICY >+ # TODO: the status code is not currently checked >+ expect_status = True >+ expected_status = ntstatus.NT_STATUS_SMARTCARD_LOGON_REQUIRED >+ else: >+ expected_error_mode = 0 >+ expect_status = False >+ expected_status = None >+ >+ rep, kdc_exchange_dict = self._test_as_exchange( >+ cname=cname, >+ realm=realm, >+ sname=sname, >+ till=till, >+ client_as_etypes=etype, >+ expected_error_mode=expected_error_mode, >+ expect_status=expect_status, >+ expected_status=expected_status, >+ expected_crealm=realm, >+ expected_cname=cname, >+ expected_srealm=realm, >+ expected_sname=sname, >+ expected_salt=salt, >+ expected_supported_etypes=expected_etypes, >+ etypes=etype, >+ padata=padata, >+ kdc_options=kdc_options, >+ preauth_key=preauth_key, >+ ticket_decryption_key=ticket_decryption_key, >+ pac_request=pac_request, >+ pac_options=pac_options) >+ if expected_error_mode: >+ self.check_error_rep(rep, expected_error_mode) >+ else: >+ self.check_as_reply(rep) >+ >+ def test_kpasswd_set_smartcard(self): >+ # Create an account for testing. >+ creds = self._get_creds() >+ >+ # Get an initial ticket to kpasswd. >+ ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(), >+ kdc_options='0') >+ >+ # Require a smartcard for login. >+ self.require_smartcard(creds) >+ >+ expected_code = KPASSWD_SUCCESS >+ expected_msg = b'Password changed' >+ >+ # Set the password. >+ new_password = generate_random_password(32, 32) >+ self.kpasswd_exchange(ticket, >+ new_password, >+ expected_code, >+ expected_msg, >+ mode=self.KpasswdMode.SET) >+ >+ def test_kpasswd_change_smartcard(self): >+ # Create an account for testing. >+ creds = self._get_creds() >+ >+ # Get an initial ticket to kpasswd. >+ ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(), >+ kdc_options='0') >+ >+ # Require a smartcard for login. >+ self.require_smartcard(creds) >+ >+ expected_code = KPASSWD_SUCCESS >+ expected_msg = b'Password changed' >+ >+ # Change the password. >+ new_password = generate_random_password(32, 32) >+ self.kpasswd_exchange(ticket, >+ new_password, >+ expected_code, >+ expected_msg, >+ mode=self.KpasswdMode.CHANGE) >+ >+ def test_kpasswd_client_set_account(self): >+ creds = self._get_creds() >+ tgt = self.get_tgt(creds) >+ >+ self._kpasswd_set_account(creds, tgt, expect_error=False) >+ >+ def test_kpasswd_client_set_account_smartcard(self): >+ creds = self._get_creds() >+ tgt = self.get_tgt(creds) >+ self.require_smartcard(creds) >+ >+ self._kpasswd_set_account(creds, tgt, expect_error=True) >+ >+ def test_samr_change(self): >+ creds = self._get_creds() >+ >+ self._test_samr_change_password( >+ creds, >+ expect_error=ntstatus.NT_STATUS_WRONG_PASSWORD) >+ >+ def test_samr_change_smartcard(self): >+ creds = self._get_creds() >+ self.require_smartcard(creds) >+ >+ self._test_samr_change_password( >+ creds, >+ expect_error=ntstatus.NT_STATUS_ACCOUNT_RESTRICTION) >+ >+ def test_ldap_change(self): >+ creds = self._get_creds() >+ tgt = self.get_tgt(creds) >+ >+ self._ldap_change(creds, tgt) >+ >+ def test_ldap_change_smartcard(self): >+ creds = self._get_creds() >+ tgt = self.get_tgt(creds) >+ self.require_smartcard(creds) >+ >+ self._ldap_change(creds, tgt) >+ >+ def _ldap_change(self, creds, tgt): >+ dc_creds = self.get_dc_creds() >+ >+ host = self.get_samdb().host_dns_name() >+ >+ ticket = self.get_service_ticket(tgt, dc_creds, >+ service='ldap', >+ target_name=host) >+ ccache_creds, cachefile = self.create_ccache_with_ticket(creds, ticket) >+ # Remove the cached credentials file. >+ self.addCleanup(os.remove, cachefile.name) >+ >+ samdb = SamDB(url=f'ldap://{host}', >+ credentials=ccache_creds, >+ lp=self.get_lp()) >+ >+ new_password = generate_random_password(32, 32) >+ >+ msg = ldb.Message(creds.get_dn()) >+ msg['0'] = ldb.MessageElement(creds.get_password(), >+ ldb.FLAG_MOD_DELETE, >+ 'userPassword') >+ msg['1'] = ldb.MessageElement(new_password, >+ ldb.FLAG_MOD_ADD, >+ 'userPassword') >+ samdb.modify(msg) >+ >+ def _kpasswd_set_account(self, creds, tgt, expect_error): >+ ccache_creds, cachefile = self.create_ccache_with_ticket(creds, >+ ticket=tgt) >+ >+ # Remove the cached credentials file. >+ self.addCleanup(os.remove, cachefile.name) >+ >+ username = creds.get_username() >+ realm = creds.get_realm() >+ password = creds.get_password() >+ >+ new_password = generate_random_password(32, 32) >+ # Avoid problems providing passwords to texpect >+ new_password = new_password.replace('#', '+') >+ >+ script = f'''expect {username}@{realm}'s Password: >+send {password}\\n >+''' >+ if expect_error: >+ script += ('expect samba4kpasswd: ' >+ 'krb5_get_init_creds: KDC policy rejects request') >+ else: >+ script += f'''expect New password for {username}@{realm}: >+send {new_password}\\n >+expect Verify password - New password for {username}@{realm}: >+send {new_password}\\n >+expect Success >+''' >+ >+ cmd = ['bin/texpect', '-t', '5', '/dev/stdin', >+ 'bin/samba4kpasswd', >+ username, >+ ] >+ >+ result = subprocess.run(cmd, >+ input=script.encode('utf-8'), >+ stdout=subprocess.PIPE, >+ stderr=subprocess.PIPE, >+ timeout=10) >+ self.assertEqual(int(expect_error), result.returncode, >+ ('expected success:', >+ result.stdout.decode('utf-8'), >+ result.stderr.decode('utf-8'))) >+ >+ # Test interactive SamLogon. >+ def test_samlogon_interactive(self): >+ client_creds = self._get_creds(ntlm=True) >+ self._test_samlogon(creds=client_creds, >+ logon_type=netlogon.NetlogonInteractiveInformation) >+ >+ # Test interactive SamLogon when a smartcard is required. >+ def test_samlogon_interactive_smartcard(self): >+ client_creds = self._get_creds(ntlm=True) >+ self.require_smartcard(client_creds) >+ self._test_samlogon( >+ creds=client_creds, >+ logon_type=netlogon.NetlogonInteractiveInformation, >+ expect_error=ntstatus.NT_STATUS_SMARTCARD_LOGON_REQUIRED) >+ >+ # Test network SamLogon. >+ def test_samlogon_network(self): >+ client_creds = self._get_creds(ntlm=True) >+ self._test_samlogon(creds=client_creds, >+ logon_type=netlogon.NetlogonNetworkInformation) >+ >+ # Test network SamLogon when a smartcard is required. >+ def test_samlogon_network_smartcard(self): >+ client_creds = self._get_creds(ntlm=True) >+ self.require_smartcard(client_creds) >+ self._test_samlogon(creds=client_creds, >+ logon_type=netlogon.NetlogonNetworkInformation) >+ >+ >+if __name__ == '__main__': >+ global_asn1_print = False >+ global_hexdump = False >+ import unittest >+ unittest.main() >diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py >index e41f4677553..67ddc5b605f 100755 >--- a/source4/selftest/tests.py >+++ b/source4/selftest/tests.py >@@ -1969,6 +1969,13 @@ for env, nt_hash in [("ad_dc:local", True), > **krb5_environ, > 'EXPECT_NT_HASH': int(nt_hash), > }) >+ planoldpythontestsuite( >+ env, >+ 'samba.tests.krb5.smartcard_tests', >+ environ={ >+ **krb5_environ, >+ 'EXPECT_NT_HASH': int(nt_hash), >+ }) > planoldpythontestsuite( > 'ad_dc', > 'samba.tests.krb5.kpasswd_tests', >-- >2.39.1 > > >From 132b16c28d684fdd6344cae5c6c6512c89edac21 Mon Sep 17 00:00:00 2001 >From: Joseph Sutton <josephsutton@catalyst.net.nz> >Date: Thu, 26 May 2022 20:53:47 +1200 >Subject: [PATCH 5/6] TODO: smartcard > >--- > python/samba/tests/krb5/kdc_base_test.py | 32 ++++++++++++++++ > python/samba/tests/krb5/kpasswd_tests.py | 44 ++++++++++++++++++++++ > python/samba/tests/krb5/smartcard_tests.py | 32 ---------------- > 3 files changed, 76 insertions(+), 32 deletions(-) > >diff --git a/python/samba/tests/krb5/kdc_base_test.py b/python/samba/tests/krb5/kdc_base_test.py >index 6d47b3feca7..d1a585702fe 100644 >--- a/python/samba/tests/krb5/kdc_base_test.py >+++ b/python/samba/tests/krb5/kdc_base_test.py >@@ -80,6 +80,7 @@ from samba.dsdb import ( > UF_NOT_DELEGATED, > UF_PARTIAL_SECRETS_ACCOUNT, > UF_SERVER_TRUST_ACCOUNT, >+ UF_SMARTCARD_REQUIRED, > UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION > ) > from samba.dcerpc.misc import ( >@@ -3030,6 +3031,37 @@ class KDCBaseTest(TestCaseInTempDir, RawKerberosTest): > msg[name] = ldb.MessageElement([], flag, name) > samdb.modify(msg) > >+ def require_smartcard(self, creds): >+ samdb = self.get_samdb() >+ dn = creds.get_dn() >+ >+ res = samdb.search(dn, scope=ldb.SCOPE_BASE, >+ attrs=['userAccountControl']) >+ uac = int(res[0].get('userAccountControl', idx=0)) >+ >+ # Set the SMARTCARD_REQUIRED bit on the account. >+ uac |= UF_SMARTCARD_REQUIRED >+ >+ msg = ldb.Message(dn) >+ msg['userAccountControl'] = ldb.MessageElement( >+ str(uac), >+ ldb.FLAG_MOD_REPLACE, >+ 'userAccountControl') >+ samdb.modify(msg) >+ >+ # The account's keys have been randomized, so set the password to >+ # something we know. >+ >+ new_password = generate_random_password(32, 32) >+ >+ msg = ldb.Message(dn) >+ msg['userPassword'] = ldb.MessageElement(new_password, >+ ldb.FLAG_MOD_REPLACE, >+ 'userPassword') >+ samdb.modify(msg) >+ >+ creds.update_password(new_password) >+ > def create_ccache(self, cname, ticket, enc_part): > """ Lay out a version 4 on-disk credentials cache, to be read using the > FILE: protocol. >diff --git a/python/samba/tests/krb5/kpasswd_tests.py b/python/samba/tests/krb5/kpasswd_tests.py >index 7c63d81c96d..b50dd633d2d 100755 >--- a/python/samba/tests/krb5/kpasswd_tests.py >+++ b/python/samba/tests/krb5/kpasswd_tests.py >@@ -934,6 +934,50 @@ class KpasswdTests(KDCBaseTest): > expected_msg, > mode=self.KpasswdMode.CHANGE) > >+ def test_kpasswd_set_smartcard(self): >+ # Create an account for testing. >+ creds = self._get_creds() >+ >+ # Get an initial ticket to kpasswd. >+ ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(), >+ kdc_options='0') >+ >+ # Require a smartcard for login. >+ self.require_smartcard(creds) >+ >+ expected_code = KPASSWD_SUCCESS >+ expected_msg = b'Password changed' >+ >+ # Set the password. >+ new_password = generate_random_password(32, 32) >+ self.kpasswd_exchange(ticket, >+ new_password, >+ expected_code, >+ expected_msg, >+ mode=self.KpasswdMode.SET) >+ >+ def test_kpasswd_change_smartcard(self): >+ # Create an account for testing. >+ creds = self._get_creds() >+ >+ # Get an initial ticket to kpasswd. >+ ticket = self.get_tgt(creds, sname=self.get_kpasswd_sname(), >+ kdc_options='0') >+ >+ # Require a smartcard for login. >+ self.require_smartcard(creds) >+ >+ expected_code = KPASSWD_SUCCESS >+ expected_msg = b'Password changed' >+ >+ # Change the password. >+ new_password = generate_random_password(32, 32) >+ self.kpasswd_exchange(ticket, >+ new_password, >+ expected_code, >+ expected_msg, >+ mode=self.KpasswdMode.CHANGE) >+ > > if __name__ == '__main__': > global_asn1_print = False >diff --git a/python/samba/tests/krb5/smartcard_tests.py b/python/samba/tests/krb5/smartcard_tests.py >index 6ec70246cf0..c8af3b924b5 100755 >--- a/python/samba/tests/krb5/smartcard_tests.py >+++ b/python/samba/tests/krb5/smartcard_tests.py >@@ -24,7 +24,6 @@ import ldb > > from samba import generate_random_password, ntstatus > from samba.dcerpc import netlogon >-from samba.dsdb import UF_SMARTCARD_REQUIRED > from samba.samdb import SamDB > > from samba.tests.krb5 import kcrypto >@@ -87,37 +86,6 @@ class SmartcardTests(KDCBaseTest): > > return creds > >- def require_smartcard(self, creds): >- samdb = self.get_samdb() >- dn = creds.get_dn() >- >- res = samdb.search(dn, scope=ldb.SCOPE_BASE, >- attrs=['userAccountControl']) >- uac = int(res[0].get('userAccountControl', idx=0)) >- >- # Set the SMARTCARD_REQUIRED bit on the account. >- uac |= UF_SMARTCARD_REQUIRED >- >- msg = ldb.Message(dn) >- msg['userAccountControl'] = ldb.MessageElement( >- str(uac), >- ldb.FLAG_MOD_REPLACE, >- 'userAccountControl') >- samdb.modify(msg) >- >- # The account's keys have been randomized, so set the password to >- # something we know. >- >- new_password = generate_random_password(32, 32) >- >- msg = ldb.Message(dn) >- msg['userPassword'] = ldb.MessageElement(new_password, >- ldb.FLAG_MOD_REPLACE, >- 'userPassword') >- samdb.modify(msg) >- >- creds.update_password(new_password) >- > def test_ntlm(self): > creds = self._get_creds(ntlm=True) > >-- >2.39.1 > > >From 529d93c754c5be2f9bba82c39fd4a56ff433765e Mon Sep 17 00:00:00 2001 >From: Joseph Sutton <josephsutton@catalyst.net.nz> >Date: Fri, 29 Apr 2022 12:25:08 +1200 >Subject: [PATCH 6/6] s4-auth: Disable password authentication for accounts > requiring smartcard login > >Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> >--- > auth/auth_log.c | 27 +-------------------------- > auth/auth_util.c | 25 +++++++++++++++++++++++++ > auth/auth_util.h | 17 +++++++++++++++++ > source4/auth/auth.h | 1 + > source4/auth/ntlm/auth_sam.c | 3 +++ > 5 files changed, 47 insertions(+), 26 deletions(-) > >diff --git a/auth/auth_log.c b/auth/auth_log.c >index 9a110fd0b48..87765535889 100644 >--- a/auth/auth_log.c >+++ b/auth/auth_log.c >@@ -52,6 +52,7 @@ > > #include "includes.h" > #include "../lib/tsocket/tsocket.h" >+#include "auth_util.h" > #include "common_auth.h" > #include "lib/util/util_str_escape.h" > #include "libcli/security/dom_sid.h" >@@ -62,7 +63,6 @@ > #include "lib/util/server_id_db.h" > #include "lib/param/param.h" > #include "librpc/ndr/libndr.h" >-#include "librpc/gen_ndr/windows_event_ids.h" > #include "lib/audit_logging/audit_logging.h" > > /* >@@ -96,31 +96,6 @@ static void log_json(struct imessaging_context *msg_ctx, > } > } > >-/* >- * Determine the Windows logon type for the current authorisation attempt. >- * >- * Currently Samba only supports >- * >- * 2 Interactive A user logged on to this computer. >- * 3 Network A user or computer logged on to this computer from >- * the network. >- * 8 NetworkCleartext A user logged on to this computer from the network. >- * The user's password was passed to the authentication >- * package in its unhashed form. >- * >- */ >-static enum event_logon_type get_logon_type( >- const struct auth_usersupplied_info *ui) >-{ >- if ((ui->logon_parameters & MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED) >- || (ui->password_state == AUTH_PASSWORD_PLAIN)) { >- return EVT_LOGON_NETWORK_CLEAR_TEXT; >- } else if (ui->flags & USER_INFO_INTERACTIVE_LOGON) { >- return EVT_LOGON_INTERACTIVE; >- } >- return EVT_LOGON_NETWORK; >-} >- > /* > * Write a machine parsable json formatted authentication log entry. > * >diff --git a/auth/auth_util.c b/auth/auth_util.c >index ec9094d0f15..b91dff378ba 100644 >--- a/auth/auth_util.c >+++ b/auth/auth_util.c >@@ -69,3 +69,28 @@ struct auth_session_info *copy_session_info(TALLOC_CTX *mem_ctx, > TALLOC_FREE(frame); > return dst; > } >+ >+/* >+ * Determine the Windows logon type for the current authorisation attempt. >+ * >+ * Currently Samba only supports >+ * >+ * 2 Interactive A user logged on to this computer. >+ * 3 Network A user or computer logged on to this computer from >+ * the network. >+ * 8 NetworkCleartext A user logged on to this computer from the network. >+ * The user's password was passed to the authentication >+ * package in its unhashed form. >+ * >+ */ >+enum event_logon_type get_logon_type( >+ const struct auth_usersupplied_info *ui) >+{ >+ if ((ui->logon_parameters & MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED) >+ || (ui->password_state == AUTH_PASSWORD_PLAIN)) { >+ return EVT_LOGON_NETWORK_CLEAR_TEXT; >+ } else if (ui->flags & USER_INFO_INTERACTIVE_LOGON) { >+ return EVT_LOGON_INTERACTIVE; >+ } >+ return EVT_LOGON_NETWORK; >+} >diff --git a/auth/auth_util.h b/auth/auth_util.h >index 0af6ff5ec6c..ebd95621dbd 100644 >--- a/auth/auth_util.h >+++ b/auth/auth_util.h >@@ -24,9 +24,26 @@ > #include "replace.h" > #include <talloc.h> > #include "librpc/gen_ndr/auth.h" >+#include "librpc/gen_ndr/windows_event_ids.h" >+#include "common_auth.h" > > struct auth_session_info *copy_session_info( > TALLOC_CTX *mem_ctx, > const struct auth_session_info *src); > >+/* >+ * Determine the Windows logon type for the current authorisation attempt. >+ * >+ * Currently Samba only supports >+ * >+ * 2 Interactive A user logged on to this computer. >+ * 3 Network A user or computer logged on to this computer from >+ * the network. >+ * 8 NetworkCleartext A user logged on to this computer from the network. >+ * The user's password was passed to the authentication >+ * package in its unhashed form. >+ * >+ */ >+enum event_logon_type get_logon_type(const struct auth_usersupplied_info *ui); >+ > #endif >diff --git a/source4/auth/auth.h b/source4/auth/auth.h >index 1ea4f11d581..14842e45cac 100644 >--- a/source4/auth/auth.h >+++ b/source4/auth/auth.h >@@ -23,6 +23,7 @@ > > #include "librpc/gen_ndr/ndr_krb5pac.h" > #include "librpc/gen_ndr/auth.h" >+#include "../auth/auth_util.h" > #include "../auth/common_auth.h" > > extern const char *krbtgt_attrs[]; >diff --git a/source4/auth/ntlm/auth_sam.c b/source4/auth/ntlm/auth_sam.c >index b4b231a8ca8..ebb46ef6e67 100644 >--- a/source4/auth/ntlm/auth_sam.c >+++ b/source4/auth/ntlm/auth_sam.c >@@ -857,6 +857,9 @@ static NTSTATUS authsam_authenticate(struct auth4_context *auth_context, > TALLOC_FREE(tmp_ctx); > return NT_STATUS_NO_SUCH_USER; > } >+ } >+ >+ if (get_logon_type(user_info) != EVT_LOGON_NETWORK) { > if (acct_flags & ACB_SMARTCARD_REQUIRED) { > if (acct_flags & ACB_DISABLED) { > DEBUG(2,("authsam_authenticate: Account for user '%s' " >-- >2.39.1 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Actions:
View
Attachments on
bug 15045
: 18125 |
18126