The branch, master has been updated via 20ce68f1594 tests/krb5: Test retrieving a denied gMSA password over an unsealed connection via 7ba61811592 s4:ldap_server: Update gMSA keys when DSDB_CONTROL_GMSA_UPDATE_OID control is specified via 24f109c59ff s4:dsdb:tests: Make use of ‘ldb’ parameter via 02d7ab13ee2 lib:crypto: Add more unit tests for GKDI functions via b2d777a1ed2 s4:dsdb: Make use of DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS search flag via 118f3ba78fd s4:dsdb: Implement DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS search flag via 9149d1d338f s4:kdc: Correctly extract older NT hash via c6fec5156fe tests/krb5: Note that lockout tests use password checks via ed371ff0fa1 tests/krb5: Fix malapropism via a916928acaf s4:kdc: Remove unnecessary cast via 8dca32eba2c pyglue: Remove unnecessary declaration via 460b1935b96 s4:kdc: Fix grammar via faba757175f auth:credentials: Remove unnecessary declaration via b6b8f9539b8 auth:credentials: Fix code spelling via 56dd910b837 python: Reformat code via e25c6a21208 s4-gmsa: Do not attempt password set on remote LDAP connections via 977f5753fc8 s4:dsdb: Add dsdb_update_gmsa_keys() via 245dc1f0f2b s4:dsdb: Move the responsibility for determining whether an account is a gMSA out of gmsa_recalculate_managed_pwd() via 2f2d3b7cf28 s4:dsdb: Indicate to the LDAP server physical passwords that need to be refreshed via 99071bbcf4b s4:dsdb: Store found managed password ID as part of gMSA update structure via 8bcefaaa5c4 s4:dsdb: Store account DN as part of gMSA update structure via 6613aeca93a s4:dsdb: Only reuse the current password ID as the previous password ID when appropriate via dcc5724ed75 s4:dsdb: Add a note that administrators should not set the clock too far in the future via a397029813f s4:dsdb: No longer pass DSDB_SEARCH_ONE_ONLY flag to dsdb_search_dn() via cdc63fa68d8 s4:dsdb: Explicitly return success error code via 1b765edbc95 tests/krb5: Add tests that gMSA keys are updated in the database when appropriate via 47c519af8e9 tests/krb5: Import MAX_CLOCK_SKEW more directly via 21d46f3ece3 tests/krb5: Extract method to unpack supplementalCredentials blob via 502070cd9a5 tests/krb5: Skip loop iteration if attribute has no values via 5eea17a71bd ldb: Check result of py_ldb_msg_keys() from 0159c48e897 ctdb-scripts: Do not de-duplicate the interfaces list
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 20ce68f15940b3e8d4d53c10a71729b16cfb3908 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Fri Apr 19 14:16:03 2024 +1200 tests/krb5: Test retrieving a denied gMSA password over an unsealed connection Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> Autobuild-User(master): Jo Sutton <jsut...@samba.org> Autobuild-Date(master): Sun Apr 21 23:17:53 UTC 2024 on atb-devel-224 commit 7ba6181159215e99d8a0f2f3974ee0d46d146f35 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Mon Apr 15 15:13:45 2024 +1200 s4:ldap_server: Update gMSA keys when DSDB_CONTROL_GMSA_UPDATE_OID control is specified Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 24f109c59ff22a8a1f22ba4cdc118795e7b4d512 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Mon Apr 15 13:21:10 2024 +1200 s4:dsdb:tests: Make use of ‘ldb’ parameter Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 02d7ab13ee271448efe5715bdaaf5e6907d32e08 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Mon Apr 15 12:19:12 2024 +1200 lib:crypto: Add more unit tests for GKDI functions Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit b2d777a1ed23dfb968057411f43e92334f55705b Author: Jo Sutton <josut...@catalyst.net.nz> Date: Mon Apr 15 11:42:50 2024 +1200 s4:dsdb: Make use of DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS search flag Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 118f3ba78fd1135fb7b254d1a2bb152eb5759923 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 9 16:24:43 2024 +1200 s4:dsdb: Implement DSDB_SEARCH_UPDATE_MANAGED_PASSWORDS search flag View with ‘git show -b’. Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 9149d1d338f109f2220f6408418a6db6f3c43a11 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Thu Apr 11 17:17:54 2024 +1200 s4:kdc: Correctly extract older NT hash Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit c6fec5156fe20da6a424d7239ee234aed0aa96c0 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 16 16:01:44 2024 +1200 tests/krb5: Note that lockout tests use password checks Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit ed371ff0fa1bf3f67ad72ee206b67a693266f4b2 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Thu Apr 11 16:31:51 2024 +1200 tests/krb5: Fix malapropism Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit a916928acaf91a2d238b7940b3f882db7d548564 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Wed Apr 10 12:01:09 2024 +1200 s4:kdc: Remove unnecessary cast Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 8dca32eba2cc3b2947df029839d6962df971acc4 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Wed Apr 10 11:53:43 2024 +1200 pyglue: Remove unnecessary declaration Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 460b1935b966f920cb117da6ca5a6ba9c48e7725 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 9 15:07:23 2024 +1200 s4:kdc: Fix grammar Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit faba757175fdd56c6a489f76becde7e5f71694e3 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 9 14:31:11 2024 +1200 auth:credentials: Remove unnecessary declaration This declaration is a hold‐over from the Python 2 module initialization pattern. Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit b6b8f9539b8fd91a1a9fdec6f181479759f04dd3 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Mon Apr 8 17:29:40 2024 +1200 auth:credentials: Fix code spelling Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 56dd910b8372bee82e9cbe26b730dd4c938c9803 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Mar 5 12:33:33 2024 +1300 python: Reformat code Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit e25c6a212085f8c6ee7e99ed5cff68fde957e1db Author: Andrew Bartlett <abart...@samba.org> Date: Tue Mar 5 16:18:34 2024 +1300 s4-gmsa: Do not attempt password set on remote LDAP connections Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Jo Sutton <josut...@catalyst.net.nz> commit 977f5753fc866854e652918d5295718b347e7c3d Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Feb 13 16:09:57 2024 +1300 s4:dsdb: Add dsdb_update_gmsa_keys() Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 245dc1f0f2b10912dcba5502489acb0db13b830a Author: Jo Sutton <josut...@catalyst.net.nz> Date: Wed Apr 17 13:27:19 2024 +1200 s4:dsdb: Move the responsibility for determining whether an account is a gMSA out of gmsa_recalculate_managed_pwd() and into its callers. Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 2f2d3b7cf284cc9f263060a36c3e4c58ca4a12bc Author: Jo Sutton <josut...@catalyst.net.nz> Date: Thu Apr 11 20:15:07 2024 +1200 s4:dsdb: Indicate to the LDAP server physical passwords that need to be refreshed Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 99071bbcf4b8fa3718b4c1bc3f17bccb21f4f74c Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 16 14:03:36 2024 +1200 s4:dsdb: Store found managed password ID as part of gMSA update structure Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 8bcefaaa5c4117ae4afc829f08ae239af7ebb00e Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 16 14:03:05 2024 +1200 s4:dsdb: Store account DN as part of gMSA update structure Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 6613aeca93aba3e8edf96be4ceba0f349001b1dd Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 16 14:00:44 2024 +1200 s4:dsdb: Only reuse the current password ID as the previous password ID when appropriate This should already be the case given the current logic, but let’s make it explicit. Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit dcc5724ed757ced4c84900ae4589425c5c384b1d Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 16 13:58:15 2024 +1200 s4:dsdb: Add a note that administrators should not set the clock too far in the future Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit a397029813fe32c393c8dabbce8e210a0355811c Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 16 13:49:04 2024 +1200 s4:dsdb: No longer pass DSDB_SEARCH_ONE_ONLY flag to dsdb_search_dn() As dsdb_search_dn() ignores this flag, passing it in doesn’t achieve anything. Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit cdc63fa68d81deb0ea1398b32adea5d4be0f6279 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Tue Apr 16 16:28:55 2024 +1200 s4:dsdb: Explicitly return success error code Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 1b765edbc95b5cf9fead82c4e40af747123c6855 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Thu Apr 18 10:13:04 2024 +1200 tests/krb5: Add tests that gMSA keys are updated in the database when appropriate Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 47c519af8e9cf57c3c1abd76c70eabbb8c7ba87c Author: Jo Sutton <josut...@catalyst.net.nz> Date: Fri Apr 19 12:59:52 2024 +1200 tests/krb5: Import MAX_CLOCK_SKEW more directly Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 21d46f3ece3a48873b9a279b276868417f124fd1 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Fri Apr 19 12:58:36 2024 +1200 tests/krb5: Extract method to unpack supplementalCredentials blob Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 502070cd9a5eb7eef0f98d73b967f1d63b9403f0 Author: Jo Sutton <josut...@catalyst.net.nz> Date: Fri Apr 19 12:57:50 2024 +1200 tests/krb5: Skip loop iteration if attribute has no values Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit 5eea17a71bd69f39226a32725a0b09b60dd5308c Author: Jo Sutton <josut...@catalyst.net.nz> Date: Thu Apr 18 12:47:28 2024 +1200 ldb: Check result of py_ldb_msg_keys() Passing NULL into PyObject_GetIter() can cause a segmentation fault. Signed-off-by: Jo Sutton <josut...@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abart...@samba.org> ----------------------------------------------------------------------- Summary of changes: auth/credentials/pycredentials.c | 2 - auth/credentials/tests/bind.py | 2 +- lib/crypto/test_gkdi.c | 183 +++++++++++ lib/ldb/pyldb.c | 3 + python/pyglue.c | 1 - python/samba/nt_time.py | 18 +- python/samba/tests/krb5/gmsa_tests.py | 202 +++++++++++- python/samba/tests/krb5/kdc_base_test.py | 52 +-- python/samba/tests/krb5/lockout_tests.py | 5 +- selftest/knownfail_mit_kdc_1_20 | 1 + source3/passdb/pdb_samba_dsdb.c | 7 +- source4/auth/sam.c | 11 +- source4/dsdb/common/util.c | 149 ++++++--- source4/dsdb/gmsa/gkdi.c | 2 +- source4/dsdb/gmsa/util.c | 378 +++++++++++++++++++++- source4/dsdb/gmsa/util.h | 25 ++ source4/dsdb/samdb/ldb_modules/managed_pwd.c | 23 ++ source4/dsdb/samdb/ldb_modules/samba_dsdb.c | 1 + source4/dsdb/tests/python/unicodepwd_encrypted.py | 4 +- source4/kdc/db-glue.c | 25 +- source4/kdc/kdc-heimdal.c | 3 +- source4/kdc/wscript_build | 2 +- source4/ldap_server/ldap_backend.c | 44 +++ source4/ntp_signd/ntp_signd.c | 28 +- source4/ntp_signd/wscript_build | 3 +- source4/rpc_server/netlogon/dcerpc_netlogon.c | 90 ++++-- source4/rpc_server/wscript_build | 1 + 27 files changed, 1087 insertions(+), 178 deletions(-) Changeset truncated at 500 lines: diff --git a/auth/credentials/pycredentials.c b/auth/credentials/pycredentials.c index 5cdbe7796e6..0bcb894f920 100644 --- a/auth/credentials/pycredentials.c +++ b/auth/credentials/pycredentials.c @@ -35,8 +35,6 @@ #include "auth/kerberos/kerberos.h" #include "libcli/smb/smb_constants.h" -void initcredentials(void); - static PyObject *py_creds_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { return pytalloc_steal(type, cli_credentials_init(NULL)); diff --git a/auth/credentials/tests/bind.py b/auth/credentials/tests/bind.py index ce81b736e86..97370666b3b 100755 --- a/auth/credentials/tests/bind.py +++ b/auth/credentials/tests/bind.py @@ -140,7 +140,7 @@ unicodePwd:: """ + base64.b64encode(u"\"P@ssw0rd\"".encode('utf-16-le')).decode( res = ldb_virtual.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) def test_computer_account_bind(self): - # create a computer acocount for the test + # create a computer account for the test delete_force(self.ldb, self.computer_dn) self.ldb.add_ldif(""" dn: """ + self.computer_dn + """ diff --git a/lib/crypto/test_gkdi.c b/lib/crypto/test_gkdi.c index e6d3b28ae58..083d71eefd3 100644 --- a/lib/crypto/test_gkdi.c +++ b/lib/crypto/test_gkdi.c @@ -136,10 +136,193 @@ static void test_password_based_on_key_id(void **state) talloc_free(mem_ctx); } +static void assert_gkid_equal(const struct Gkid g1, const struct Gkid g2) +{ + assert_int_equal(g1.l0_idx, g2.l0_idx); + assert_int_equal(g1.l1_idx, g2.l1_idx); + assert_int_equal(g1.l2_idx, g2.l2_idx); +} + +static void test_gkdi_rollover_interval(void **state) +{ + NTTIME interval; + bool ok; + + ok = gkdi_rollover_interval(0, &interval); + assert_true(ok); + assert_int_equal(0, interval); + + ok = gkdi_rollover_interval(1, &interval); + assert_true(ok); + assert_int_equal(UINT64_C(720000000000), interval); + + ok = gkdi_rollover_interval(2, &interval); + assert_true(ok); + assert_int_equal(UINT64_C(1440000000000), interval); + + ok = gkdi_rollover_interval(3, &interval); + assert_true(ok); + assert_int_equal(UINT64_C(2520000000000), interval); + + ok = gkdi_rollover_interval(4, &interval); + assert_true(ok); + assert_int_equal(UINT64_C(3240000000000), interval); + + ok = gkdi_rollover_interval(5, &interval); + assert_true(ok); + assert_int_equal(UINT64_C(4320000000000), interval); + + ok = gkdi_rollover_interval(-1, &interval); + assert_false(ok); + + ok = gkdi_rollover_interval(-2, &interval); + assert_false(ok); + + ok = gkdi_rollover_interval(10675199, &interval); + assert_true(ok); + assert_int_equal(UINT64_C(9223371720000000000), interval); + + ok = gkdi_rollover_interval(-10675198, &interval); + assert_false(ok); + + ok = gkdi_rollover_interval(10675200, &interval); + assert_true(ok); + assert_int_equal(UINT64_C(9223372800000000000), interval); + + ok = gkdi_rollover_interval(-10675199, &interval); + assert_false(ok); + + ok = gkdi_rollover_interval(21350398, &interval); + /* + * If we accepted this high of an interval, the result would be + * 18446743800000000000. + */ + assert_false(ok); + + ok = gkdi_rollover_interval(-21350397, &interval); + assert_false(ok); + + ok = gkdi_rollover_interval(21350399, &interval); + assert_false(ok); /* too large to be represented */ + + ok = gkdi_rollover_interval(-21350398, &interval); + assert_false(ok); /* too small to be represented */ +} + +static void assert_get_interval_id(const NTTIME time, + const struct Gkid expected_gkid) +{ + { + const bool valid = gkid_is_valid(expected_gkid); + assert_true(valid); + } + + { + const struct Gkid interval_id = gkdi_get_interval_id(time); + assert_gkid_equal(expected_gkid, interval_id); + } +} + +static void test_get_interval_id(void **state) +{ + assert_get_interval_id(0, Gkid(0, 0, 0)); + + assert_get_interval_id(gkdi_key_cycle_duration - 1, Gkid(0, 0, 0)); + + assert_get_interval_id(gkdi_key_cycle_duration, Gkid(0, 0, 1)); + + assert_get_interval_id(27 * gkdi_key_cycle_duration, Gkid(0, 0, 27)); + + assert_get_interval_id((gkdi_l2_key_iteration - 1) * + gkdi_key_cycle_duration, + Gkid(0, 0, gkdi_l2_key_iteration - 1)); + + assert_get_interval_id(gkdi_l2_key_iteration * gkdi_key_cycle_duration, + Gkid(0, 1, 0)); + + assert_get_interval_id(17 * gkdi_l2_key_iteration * + gkdi_key_cycle_duration, + Gkid(0, 17, 0)); + + assert_get_interval_id(((gkdi_l1_key_iteration - 1) * + gkdi_l2_key_iteration + + 3) * gkdi_key_cycle_duration, + Gkid(0, gkdi_l1_key_iteration - 1, 3)); + + assert_get_interval_id(gkdi_l1_key_iteration * gkdi_l2_key_iteration * + gkdi_key_cycle_duration, + Gkid(1, 0, 0)); + + assert_get_interval_id(((1234 * gkdi_l1_key_iteration + 8) * + gkdi_l2_key_iteration + + 13) * gkdi_key_cycle_duration, + Gkid(1234, 8, 13)); + + assert_get_interval_id(INT64_MAX, Gkid(25019, 31, 29)); + + assert_get_interval_id(UINT64_MAX, Gkid(50039, 31, 27)); +} + +static void test_get_key_start_time(void **state) +{ + NTTIME start_time = 0; + bool ok; + + /* Try passing an invalid GKID. */ + ok = gkdi_get_key_start_time(invalid_gkid, &start_time); + assert_false(ok); + + /* Try passing an L1 GKID rather than an L2 GKID. */ + ok = gkdi_get_key_start_time(Gkid(0, 0, -1), &start_time); + assert_false(ok); + + /* Test some L2 GKIDs. */ + + ok = gkdi_get_key_start_time(Gkid(0, 0, 0), &start_time); + assert_true(ok); + assert_int_equal(0, start_time); + + ok = gkdi_get_key_start_time(Gkid(0, 0, 1), &start_time); + assert_true(ok); + assert_int_equal(gkdi_key_cycle_duration, start_time); + + ok = gkdi_get_key_start_time(Gkid(123, 18, 2), &start_time); + assert_true(ok); + assert_int_equal(126530 * gkdi_key_cycle_duration, start_time); + + ok = gkdi_get_key_start_time(Gkid(25019, 31, 29), &start_time); + assert_true(ok); + assert_int_equal(25620477 * gkdi_key_cycle_duration, start_time); + + ok = gkdi_get_key_start_time(Gkid(25019, 31, 30), &start_time); + assert_true(ok); + assert_int_equal(UINT64_C(25620478) * gkdi_key_cycle_duration, + start_time); + + ok = gkdi_get_key_start_time(Gkid(50039, 31, 27), &start_time); + assert_true(ok); + assert_int_equal(UINT64_C(51240955) * gkdi_key_cycle_duration, + start_time); + + /* + * Test GKIDs so high that their start times can’t be represented in + * NTTIME. + */ + + ok = gkdi_get_key_start_time(Gkid(50039, 31, 28), &start_time); + assert_false(ok); + + ok = gkdi_get_key_start_time(Gkid(INT32_MAX, 31, 31), &start_time); + assert_false(ok); +} + int main(int argc, char *argv[]) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_password_based_on_key_id), + cmocka_unit_test(test_gkdi_rollover_interval), + cmocka_unit_test(test_get_interval_id), + cmocka_unit_test(test_get_key_start_time), }; if (argc == 2) { diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c index f416bfe6d5d..d54a952ac01 100644 --- a/lib/ldb/pyldb.c +++ b/lib/ldb/pyldb.c @@ -4143,6 +4143,9 @@ static PyObject *py_ldb_msg_iter(PyObject *self) PyObject *list, *iter; list = py_ldb_msg_keys(self, NULL); + if (list == NULL) { + return NULL; + } iter = PyObject_GetIter(list); Py_DECREF(list); return iter; diff --git a/python/pyglue.c b/python/pyglue.c index c24d1b033a4..27cd41d5b9c 100644 --- a/python/pyglue.c +++ b/python/pyglue.c @@ -29,7 +29,6 @@ #include "lib/cmdline/cmdline.h" #include "lib/crypto/gkdi.h" -void init_glue(void); static PyObject *PyExc_NTSTATUSError; static PyObject *PyExc_WERRORError; static PyObject *PyExc_HRESULTError; diff --git a/python/samba/nt_time.py b/python/samba/nt_time.py index 098748f4f3c..714f9e97ef4 100644 --- a/python/samba/nt_time.py +++ b/python/samba/nt_time.py @@ -85,19 +85,21 @@ def nt_time_from_string(s: str) -> NtTime: UTC). """ try: - if s == 'now': + if s == "now": dt = datetime.datetime.now(datetime.timezone.utc) - elif re.match(r'^\d{14}\.0Z$', s): + elif re.match(r"^\d{14}\.0Z$", s): # "20230127223641.0Z" - dt = datetime.datetime.strptime(s, '%Y%m%d%H%M%S.0Z') + dt = datetime.datetime.strptime(s, "%Y%m%d%H%M%S.0Z") else: dt = datetime.datetime.fromisoformat(s) except ValueError: - raise ValueError("Expected a date in either " - "ISO8601 'YYYY-MM-DD HH:MM:SS' format, " - "LDAP timestamp 'YYYYmmddHHMMSS.0Z', " - "or the literal string 'now'. " - f" Got '{s}'.") + raise ValueError( + "Expected a date in either " + "ISO8601 'YYYY-MM-DD HH:MM:SS' format, " + "LDAP timestamp 'YYYYmmddHHMMSS.0Z', " + "or the literal string 'now'. " + f" Got '{s}'." + ) if dt.tzinfo is None: # This is a cursed timestamp with no timezone info. We have to diff --git a/python/samba/tests/krb5/gmsa_tests.py b/python/samba/tests/krb5/gmsa_tests.py index 1d3787af478..80529daf7d0 100755 --- a/python/samba/tests/krb5/gmsa_tests.py +++ b/python/samba/tests/krb5/gmsa_tests.py @@ -30,7 +30,7 @@ from itertools import chain import ldb -from samba import auth, dsdb, gensec +from samba import auth, dsdb, gensec, werror from samba.dcerpc import gkdi, gmsa, misc, netlogon, security from samba.ndr import ndr_pack, ndr_unpack from samba.nt_time import ( @@ -46,11 +46,12 @@ from samba.gkdi import ( Gkid, GroupKey, KEY_CYCLE_DURATION, + MAX_CLOCK_SKEW, ) from samba.tests import connect_samdb from samba.tests.krb5 import kcrypto -from samba.tests.gkdi import GkdiBaseTest, MAX_CLOCK_SKEW +from samba.tests.gkdi import GkdiBaseTest, ROOT_KEY_START_TIME from samba.tests.krb5.kdc_base_test import KDCBaseTest from samba.tests.krb5.raw_testcase import KerberosCredentials from samba.tests.krb5.rfc4120_constants import ( @@ -632,9 +633,17 @@ class GmsaTests(GkdiBaseTest, KDCBaseTest): ) def check_managed_password_access( - self, creds: Credentials, *, expect_access + self, + creds: Credentials, + *, + samdb: Optional[SamDB] = None, + expect_access: bool = False, + expected_werror: int = werror.WERR_SUCCESS, ) -> None: - samdb = self.get_samdb() + if samdb is None: + samdb = self.get_samdb() + if expected_werror: + self.assertFalse(expect_access) managed_service_accounts_dn = self.get_managed_service_accounts_dn() username = creds.get_username() @@ -648,12 +657,24 @@ class GmsaTests(GkdiBaseTest, KDCBaseTest): for dn, scope in searches: # Perform a search and see whether we’re allowed to view the managed password. - res = samdb.search( - dn, - scope=scope, - expression=f"sAMAccountName={username}", - attrs=["msDS-ManagedPassword"], - ) + try: + res = samdb.search( + dn, + scope=scope, + expression=f"sAMAccountName={username}", + attrs=["msDS-ManagedPassword"], + ) + except ldb.LdbError as err: + self.assertTrue(expected_werror, "got an unexpected error") + + num, estr = err.args + if num != ldb.ERR_OPERATIONS_ERROR: + raise + + self.assertIn(f"{expected_werror:08X}", estr) + return + + self.assertFalse(expected_werror, "expected to get an error") self.assertEqual(1, len(res), "should always find the gMSA") managed_password = res[0].get("msDS-ManagedPassword", idx=0) @@ -677,6 +698,36 @@ class GmsaTests(GkdiBaseTest, KDCBaseTest): self.gmsa_account(msa_membership=deny_world_sddl), expect_access=False ) + def test_retrieving_denied_password_over_unsealed_connection(self): + # Requires --use-kerberos=required, or it automatically upgrades to an + # encrypted connection. + + # Remove FEATURE_SEAL which gets added by insta_creds. + creds = self.insta_creds(template=self.get_admin_creds()) + creds.set_gensec_features(creds.get_gensec_features() & ~gensec.FEATURE_SEAL) + + lp = self.get_lp() + + sasl_wrap = lp.get("client ldap sasl wrapping") + self.addCleanup(lp.set, "client ldap sasl wrapping", sasl_wrap) + lp.set("client ldap sasl wrapping", "sign") + + # Create a second ldb connection without seal. + samdb = SamDB( + f"ldap://{self.dc_host}", + credentials=creds, + session_info=auth.system_session(lp), + lp=lp, + ) + + # Deny anyone from being able to view the password. + deny_world_sddl = "O:SYD:(D;;RP;;;WD)" + self.check_managed_password_access( + self.gmsa_account(msa_membership=deny_world_sddl), + samdb=samdb, + expected_werror=werror.WERR_DS_CONFIDENTIALITY_REQUIRED, + ) + def future_gkid(self) -> Gkid: """Return (6333, 26, 5)—an arbitrary GKID far enough in the future that it’s situated beyond any reasonable rollover period. But not so far in @@ -839,6 +890,137 @@ class GmsaTests(GkdiBaseTest, KDCBaseTest): ) self.check_managed_pwd(samdb, creds, expected_managed_pwd=expected) + def test_retrieving_managed_password_triggers_keys_update(self): + # Create a root key with a start time early enough to be usable at the + # time the gMSA is purported to be created. + samdb = self.get_samdb() + domain_dn = self.get_server_dn(samdb) + self.create_root_key(samdb, domain_dn, use_start_time=ROOT_KEY_START_TIME) + + password_interval = 16 + + local_samdb = self.get_local_samdb() + series = GmsaSeries(Gkid(100, 0, 0), gkdi_rollover_interval(password_interval)) + self.set_db_time(local_samdb, series.start_of_interval(0)) + + creds = self.gmsa_account(samdb=local_samdb, interval=password_interval) + dn = creds.get_dn() + + current_nt_time = self.current_nt_time(local_samdb) + self.set_db_time(local_samdb, current_nt_time) + + # Search the local database for the account’s keys. + res = local_samdb.search( + dn, scope=ldb.SCOPE_BASE, attrs=["unicodePwd", "supplementalCredentials"] + ) + self.assertEqual(1, len(res)) + + previous_nt_hash = res[0].get("unicodePwd", idx=0) + previous_supplemental_creds = self.unpack_supplemental_credentials( + res[0].get("supplementalCredentials", idx=0) + ) + + # Search for the managed password over LDAP, triggering an update of the + # keys in the database. + res = samdb.search(dn, scope=ldb.SCOPE_BASE, attrs=["msDS-ManagedPassword"]) + self.assertEqual(1, len(res)) + + # Verify that the password is present in the result. + managed_password = res[0].get("msDS-ManagedPassword", idx=0) + self.assertIsNotNone(managed_password, "should be allowed to view the password") + + # Search the local database again for the account’s keys, which should + # have been updated. + res = local_samdb.search( + dn, scope=ldb.SCOPE_BASE, attrs=["unicodePwd", "supplementalCredentials"] + ) + self.assertEqual(1, len(res)) + + nt_hash = res[0].get("unicodePwd", idx=0) + supplemental_creds = self.unpack_supplemental_credentials( + res[0].get("supplementalCredentials", idx=0) + ) + + self.assertNotEqual( + previous_nt_hash, nt_hash, "NT hash has not been updated (yet)" + ) + self.assertNotEqual( + previous_supplemental_creds, + supplemental_creds, + "supplementalCredentials has not been updated (yet)", + ) + + def test_authentication_triggers_keys_update(self): + # Create a root key with a start time early enough to be usable at the + # time the gMSA is purported to be created. But don’t create it on a + # local samdb with a specifically set time, because (if the key isn’t + # deleted later) we could end up with multiple keys with identical + # creation and start times, and tests failing when the test and the + # server don’t agree on which root key to use at a specific time. + samdb = self.get_samdb() + domain_dn = self.get_server_dn(samdb) + self.create_root_key(samdb, domain_dn, use_start_time=ROOT_KEY_START_TIME) + + password_interval = 16 + + local_samdb = self.get_local_samdb() + series = GmsaSeries(Gkid(100, 0, 0), gkdi_rollover_interval(password_interval)) + self.set_db_time(local_samdb, series.start_of_interval(0)) + + creds = self.gmsa_account(samdb=local_samdb, interval=password_interval) + dn = creds.get_dn() + + current_nt_time = self.current_nt_time(local_samdb) + self.set_db_time(local_samdb, current_nt_time) + + # Search the local database for the account’s keys. + res = local_samdb.search( + dn, scope=ldb.SCOPE_BASE, attrs=["unicodePwd", "supplementalCredentials"] + ) + self.assertEqual(1, len(res)) + + previous_nt_hash = res[0].get("unicodePwd", idx=0) + previous_supplemental_creds = self.unpack_supplemental_credentials( + res[0].get("supplementalCredentials", idx=0) + ) + + # Calculate the password with which to authenticate. + managed_pwd = self.expected_current_gmsa_password_blob( + samdb, creds, future_key_is_acceptable=False -- Samba Shared Repository