diff options
-rw-r--r-- | docs-xml/smbdotconf/security/nt_hash_store.xml | 70 | ||||
-rw-r--r-- | docs-xml/smbdotconf/security/ntlmauth.xml | 9 | ||||
-rw-r--r-- | lib/param/loadparm.c | 1 | ||||
-rw-r--r-- | lib/param/loadparm.h | 7 | ||||
-rw-r--r-- | lib/param/param_table.c | 7 | ||||
-rw-r--r-- | selftest/knownfail.d/nt-hash-support-gone | 3 | ||||
-rw-r--r-- | selftest/knownfail.d/password_settings | 2 | ||||
-rwxr-xr-x | selftest/target/Samba4.pm | 2 | ||||
-rw-r--r-- | source3/param/loadparm.c | 1 | ||||
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/password_hash.c | 368 | ||||
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/wscript_build_server | 2 | ||||
-rw-r--r-- | source4/dsdb/tests/python/password_settings.py | 5 |
12 files changed, 443 insertions, 34 deletions
diff --git a/docs-xml/smbdotconf/security/nt_hash_store.xml b/docs-xml/smbdotconf/security/nt_hash_store.xml new file mode 100644 index 00000000000..d7ed705de58 --- /dev/null +++ b/docs-xml/smbdotconf/security/nt_hash_store.xml @@ -0,0 +1,70 @@ +<samba:parameter name="nt hash store" + context="G" + type="enum" + enumlist="enum_nt_hash_store" + xmlns:samba="http://www.samba.org/samba/DTD/samba-doc"> +<description> + <para>This parameter determines whether or not <citerefentry><refentrytitle>samba</refentrytitle> + <manvolnum>8</manvolnum></citerefentry> will, as an AD DC, attempt to + store the NT password hash used in NTLM and NTLMv2 authentication for + users in this domain. </para> + + <para>If so configured, the Samba Active Directory Domain Controller, + will, except for trust accounts (computers, domain + controllers and inter-domain trusts) the + <emphasis>NOT store the NT hash</emphasis> + for new and changed accounts in the sam.ldb database.</para> + + <para>This avoids the storage of an unsalted hash for these + user-created passwords. As a consequence the + <constant>arcfour-hmac-md5</constant> Kerberos key type is + also unavailable in the KDC for these users - thankfully + <emphasis>modern clients will select an AES based key + instead.</emphasis></para> + + <para>NOTE: As the password history in Active Directory is + stored as an NT hash (and thus unavailable), a workaround is + used, relying instead on Kerberos password hash values. + This stores three passwords, the current, previous and second previous + password. This allows some checking against reuse. </para> + + <para>However as these values are salted, changing the + sAMAccountName, userAccountControl or userPrincipalName of + an account will cause the salt to change. After the rare + combination of both a rename and a password change only the + current password will be recognised for password history + purposes. + </para> + <para>The available settings are:</para> + + <itemizedlist> + <listitem> + <para><constant>always</constant> - Always store the NT hash + (as machine accounts will also always store an NT hash, + a hash will be stored for all accounts).</para> + + <para>This setting may be useful if <parameter + moreinfo="none">ntlm auth</parameter> is set to <constant>disabled</constant> + for a trial period</para> + + </listitem> + + <listitem> + <para><constant>never</constant> - Never store the NT hash + for user accounts, only for machine accounts</para> + </listitem> + + <listitem> + <para><constant>auto</constant> - Store an NT hash if <parameter + moreinfo="none">ntlm auth</parameter> is not set to <constant>disabled</constant>. + </para> + + </listitem> + + </itemizedlist> + +</description> + +<related>ntlm auth</related> +<value type="default">always</value> +</samba:parameter> diff --git a/docs-xml/smbdotconf/security/ntlmauth.xml b/docs-xml/smbdotconf/security/ntlmauth.xml index 8d31c98eb05..d7c84ccaf85 100644 --- a/docs-xml/smbdotconf/security/ntlmauth.xml +++ b/docs-xml/smbdotconf/security/ntlmauth.xml @@ -62,6 +62,14 @@ authentication to forward to a full DC. Setting this option to <constant>disabled</constant> will cause these forwarded authentications to fail.</para> + + <para>Additionally, for Samba acting as an Active Directory + Domain Controller, for user accounts, if <parameter moreinfo="none">nt hash store</parameter> + is set to the default setting of <constant>auto</constant>, + the <emphasis>NT hash will not be stored</emphasis> + in the sam.ldb database for new users and after a + password change.</para> + </listitem> </itemizedlist> @@ -72,6 +80,7 @@ behaviour is unchanged.</para> </description> +<related>nt hash store</related> <related>lanman auth</related> <related>raw NTLMv2 auth</related> <value type="default">ntlmv2-only</value> diff --git a/lib/param/loadparm.c b/lib/param/loadparm.c index 599c4b00966..d8646120e6b 100644 --- a/lib/param/loadparm.c +++ b/lib/param/loadparm.c @@ -2651,6 +2651,7 @@ struct loadparm_context *loadparm_init(TALLOC_CTX *mem_ctx) lpcfg_do_global_parameter(lp_ctx, "ClientNTLMv2Auth", "True"); lpcfg_do_global_parameter(lp_ctx, "LanmanAuth", "False"); lpcfg_do_global_parameter(lp_ctx, "NTLMAuth", "ntlmv2-only"); + lpcfg_do_global_parameter(lp_ctx, "NT hash store", "always"); lpcfg_do_global_parameter(lp_ctx, "RawNTLMv2Auth", "False"); lpcfg_do_global_parameter(lp_ctx, "client use spnego principal", "False"); diff --git a/lib/param/loadparm.h b/lib/param/loadparm.h index a3331436229..b1641ba88d2 100644 --- a/lib/param/loadparm.h +++ b/lib/param/loadparm.h @@ -262,6 +262,13 @@ enum samba_weak_crypto { SAMBA_WEAK_CRYPTO_DISALLOWED, }; +/* Controlling the storage of the NT password has on the AD DC */ +enum store_nt_hash { + NT_HASH_STORE_AUTO, + NT_HASH_STORE_NEVER, + NT_HASH_STORE_ALWAYS +}; + /* * Default passwd chat script. */ diff --git a/lib/param/param_table.c b/lib/param/param_table.c index 9fac73ef113..3ffa4bcc411 100644 --- a/lib/param/param_table.c +++ b/lib/param/param_table.c @@ -403,6 +403,13 @@ static const struct enum_list enum_ntlm_auth[] = { {-1, NULL} }; +static const struct enum_list enum_nt_hash_store[] = { + {NT_HASH_STORE_AUTO, "auto"}, + {NT_HASH_STORE_NEVER, "never"}, + {NT_HASH_STORE_ALWAYS, "always"}, +}; + + static const struct enum_list enum_spotlight_backend[] = { {SPOTLIGHT_BACKEND_NOINDEX, "noindex"}, {SPOTLIGHT_BACKEND_TRACKER, "tracker"}, diff --git a/selftest/knownfail.d/nt-hash-support-gone b/selftest/knownfail.d/nt-hash-support-gone index 1192a6e408f..bad1bc1e029 100644 --- a/selftest/knownfail.d/nt-hash-support-gone +++ b/selftest/knownfail.d/nt-hash-support-gone @@ -1,6 +1,7 @@ -^samba.tests.krb5.nt_hash_tests.samba.tests.krb5.nt_hash_tests.NtHashTests.test_nt_hash.ad_dc_no_ntlm:local ^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.ad_dc_no_ntlm..__main__.PasswordTests.test_old_password_rename_attempt_reuse_2.ad_dc_no_ntlm +^samba4.ldap.passwords.python.ad_dc_no_ntlm..__main__.PasswordTests.test_old_password_rename_simple_bind_2.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 diff --git a/selftest/knownfail.d/password_settings b/selftest/knownfail.d/password_settings deleted file mode 100644 index 6e521899f74..00000000000 --- a/selftest/knownfail.d/password_settings +++ /dev/null @@ -1,2 +0,0 @@ -# highlights a minor corner-case discrepancy between Windows and Samba -samba4.ldap.passwordsettings.python.password_settings.PasswordSettingsTestCase.test_domain_pwd_history_zero\(ad_dc_default_smb1\) diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm index 4c263f55de4..1762ae4ae79 100755 --- a/selftest/target/Samba4.pm +++ b/selftest/target/Samba4.pm @@ -2707,7 +2707,7 @@ sub setup_ad_dc_no_ntlm "ADNONTLMDOMAIN", "adnontlmdom.samba.example.com", undef, - "ntlm auth = disabled", + "ntlm auth = disabled\nnt hash store = never", undef); unless ($env) { return undef; diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c index 2b6e0bb248c..43838575f3b 100644 --- a/source3/param/loadparm.c +++ b/source3/param/loadparm.c @@ -705,6 +705,7 @@ static void init_globals(struct loadparm_context *lp_ctx, bool reinit_globals) Globals.client_plaintext_auth = false; /* Do NOT use a plaintext password even if is requested by the server */ Globals._lanman_auth = false; /* Do NOT use the LanMan hash, even if it is supplied */ Globals.ntlm_auth = NTLM_AUTH_NTLMV2_ONLY; /* Do NOT use NTLMv1 if it is supplied by the client (otherwise NTLMv2) */ + Globals.nt_hash_store = NT_HASH_STORE_ALWAYS; /* Fill in NT hash when setting password */ Globals.raw_ntlmv2_auth = false; /* Reject NTLMv2 without NTLMSSP */ Globals.client_ntlmv2_auth = true; /* Client should always use use NTLMv2, as we can't tell that the server supports it, but most modern servers do */ /* Note, that we will also use NTLM2 session security (which is different), if it is available */ diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c index 0ba0d9a884c..e90482df63e 100644 --- a/source4/dsdb/samdb/ldb_modules/password_hash.c +++ b/source4/dsdb/samdb/ldb_modules/password_hash.c @@ -52,6 +52,8 @@ #include "lib/crypto/gnutls_helpers.h" #include <gnutls/crypto.h> +#include "kdc/db-glue.h" + #ifdef ENABLE_GPGME #undef class #include <gpgme.h> @@ -149,13 +151,26 @@ struct setup_password_fields_io { bool is_krbtgt; uint32_t restrictions; struct dom_sid *account_sid; + bool store_nt_hash; } u; /* new credentials and old given credentials */ struct setup_password_fields_given { const struct ldb_val *cleartext_utf8; const struct ldb_val *cleartext_utf16; + struct samr_Password *nt_hash; + + /* + * The AES256 kerberos key to confirm the previous password was + * not reused (for n) and to prove the old password was known + * (for og). + * + * We don't have any old salts, so we won't catch password reuse + * if said password was used prior to an account rename and + * another password change. + */ + DATA_BLOB aes_256; } n, og; /* old credentials */ @@ -165,6 +180,15 @@ struct setup_password_fields_io { struct samr_Password *nt_history; const struct ldb_val *supplemental; struct supplementalCredentialsBlob scb; + + /* + * The AES256 kerberos key as stored in the DB. + * Used to confirm the given password was correct + * and in case the previous password was reused. + */ + DATA_BLOB aes_256; + DATA_BLOB salt; + uint32_t kvno; } o; /* generated credentials */ @@ -618,17 +642,34 @@ static int password_hash_bypass(struct ldb_module *module, struct ldb_request *r static int setup_nt_fields(struct setup_password_fields_io *io) { - struct ldb_context *ldb; + struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module); uint32_t i; - - io->g.nt_hash = io->n.nt_hash; - ldb = ldb_module_get_ctx(io->ac->module); + if (io->u.store_nt_hash) { + io->g.nt_hash = io->n.nt_hash; + } if (io->ac->status->domain_data.pwdHistoryLength == 0) { return LDB_SUCCESS; } /* We might not have an old NT password */ + + if (io->g.nt_hash == NULL) { + /* + * If there was not an NT hash specified, then don't + * store the NT password history. + * + * While the NTLM code on a Windows DC will cope with + * a missing unicodePwd, if it finds a last password + * in the ntPwdHistory, even if the bytes are zero , + * it will (quite reasonably) treat it as a valid NT + * hash. NTLM logins with the previous password are + * allowed for a short time after the password is + * changed to allow for password propagation delays. + */ + return LDB_SUCCESS; + } + io->g.nt_history = talloc_array(io->ac, struct samr_Password, io->ac->status->domain_data.pwdHistoryLength); @@ -642,15 +683,7 @@ static int setup_nt_fields(struct setup_password_fields_io *io) } io->g.nt_history_len = i + 1; - if (io->g.nt_hash) { - io->g.nt_history[0] = *io->g.nt_hash; - } else { - /* - * TODO: is this correct? - * the simular behavior is correct for the lm history case - */ - E_md4hash("", io->g.nt_history[0].hash); - } + io->g.nt_history[0] = *io->g.nt_hash; return LDB_SUCCESS; } @@ -784,6 +817,63 @@ static int setup_kerberos_keys(struct setup_password_fields_io *io) return LDB_SUCCESS; } +static int setup_kerberos_key_hash(struct setup_password_fields_io *io, + struct setup_password_fields_given *g) +{ + struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module); + krb5_error_code krb5_ret; + krb5_data salt; + krb5_keyblock key; + krb5_data cleartext_data; + + if (io->ac->search_res == NULL) { + /* No old data so nothing to do */ + return LDB_SUCCESS; + } + + if (io->o.salt.data == NULL) { + /* We didn't fetch the salt in setup_io(), so nothing to do */ + return LDB_SUCCESS; + } + + salt.data = (char *)io->o.salt.data; + salt.length = io->o.salt.length; + + cleartext_data.data = (char *)g->cleartext_utf8->data; + cleartext_data.length = g->cleartext_utf8->length; + + /* + * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of the salt + * and the cleartext password + */ + krb5_ret = smb_krb5_create_key_from_string(io->smb_krb5_context->krb5_context, + NULL, + &salt, + &cleartext_data, + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + &key); + if (krb5_ret) { + ldb_asprintf_errstring(ldb, + "setup_kerberos_key_hash: " + "generation of a aes256-cts-hmac-sha1-96 key failed: %s", + smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, + krb5_ret, io->ac)); + return LDB_ERR_OPERATIONS_ERROR; + } + + g->aes_256 = data_blob_talloc(io->ac, + KRB5_KEY_DATA(&key), + KRB5_KEY_LENGTH(&key)); + krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key); + if (g->aes_256.data == NULL) { + return ldb_oom(ldb); + } + + talloc_keep_secret(g->aes_256.data); + + return LDB_SUCCESS; +} + static int setup_primary_kerberos(struct setup_password_fields_io *io, const struct supplementalCredentialsBlob *old_scb, struct package_PrimaryKerberosBlob *pkb) @@ -1599,11 +1689,21 @@ static int setup_primary_userPassword( * used in preference to the NT password hash */ if (io->g.nt_hash == NULL) { - ldb_asprintf_errstring(ldb, - "No NT Hash, unable to calculate userPassword hashes"); - return LDB_ERR_UNWILLING_TO_PERFORM; + /* + * When the NT hash is not available, we use this field to store + * the first 16 bytes of the AES256 key instead. This allows + * 'samba-tool user' to verify that the user's password is in + * sync with the userPassword package. + */ + uint8_t hash_len = MIN(16, io->g.aes_256.length); + + ZERO_STRUCT(p_userPassword_b->current_nt_hash); + memcpy(p_userPassword_b->current_nt_hash.hash, + io->g.aes_256.data, + hash_len); + } else { + p_userPassword_b->current_nt_hash = *io->g.nt_hash; } - p_userPassword_b->current_nt_hash = *io->g.nt_hash; /* * Determine the number of hashes @@ -2360,9 +2460,7 @@ static int setup_last_set_field(struct setup_password_fields_io *io) static int setup_given_passwords(struct setup_password_fields_io *io, struct setup_password_fields_given *g) { - struct ldb_context *ldb; - - ldb = ldb_module_get_ctx(io->ac->module); + struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module); if (g->cleartext_utf8) { struct ldb_val *cleartext_utf16_blob; @@ -2437,6 +2535,24 @@ static int setup_given_passwords(struct setup_password_fields_io *io, g->cleartext_utf16->length); } + /* + * We need to build one more hash, so we can compare with what might + * have been stored in the old password (for the LDAP password change) + * + * We don't have any old salts, so we won't catch password reuse if said + * password was used prior to an account rename and another password + * change. + * + * We don't have to store the 'opaque' (string2key iterations) + * as Heimdal doesn't allow that to be changed. + */ + if (g->cleartext_utf8 != NULL) { + int ret = setup_kerberos_key_hash(io, g); + if (ret != LDB_SUCCESS) { + return ret; + } + } + return LDB_SUCCESS; } @@ -2526,6 +2642,11 @@ static int setup_password_fields(struct setup_password_fields_io *io) } } + /* + * This relies on setup_kerberos_keys to make a NT-hash-like + * value for password history purposes + */ + ret = setup_nt_fields(io); if (ret != LDB_SUCCESS) { return ret; @@ -2722,6 +2843,7 @@ static int check_password_restrictions(struct setup_password_fields_io *io, WERR { struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module); int ret; + uint32_t i; struct loadparm_context *lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), struct loadparm_context); @@ -2739,12 +2861,13 @@ static int check_password_restrictions(struct setup_password_fields_io *io, WERR */ if (!io->ac->pwd_reset && !(io->ac->change && io->ac->change->old_password_checked == DSDB_PASSWORD_CHECKED_AND_CORRECT)) { + bool hash_checked = false; /* * we need the old nt hash given by the client (this * is for the plaintext over LDAP password change, * Kpasswd and SAMR supply the control) */ - if (!io->og.nt_hash) { + if (io->og.nt_hash == NULL && io->og.aes_256.length == 0) { ldb_asprintf_errstring(ldb, "check_password_restrictions: " "You need to provide the old password in order " @@ -2752,9 +2875,21 @@ static int check_password_restrictions(struct setup_password_fields_io *io, WERR return LDB_ERR_UNWILLING_TO_PERFORM; } + /* + * First compare the ENCTYPE_AES256_CTS_HMAC_SHA1_96 password and see if we have a match + */ + + if (io->og.aes_256.length > 0 && io->o.aes_256.length) { + hash_checked = data_blob_equal_const_time(&io->og.aes_256, &io->o.aes_256); + } + /* The password modify through the NT hash is encouraged and has no problems at all */ - if (!io->o.nt_hash || !mem_equal_const_time(io->og.nt_hash->hash, io->o.nt_hash->hash, 16)) { + if (!hash_checked && io->og.nt_hash && io->o.nt_hash) { + hash_checked = mem_equal_const_time(io->og.nt_hash->hash, io->o.nt_hash->hash, 16); + } + + if (!hash_checked) { return make_error_and_update_badPwdCount(io, werror); } } @@ -2837,10 +2972,37 @@ static int check_password_restrictions(struct setup_password_fields_io *io, WERR return LDB_SUCCESS; } - if (io->n.nt_hash) { - uint32_t i; + /* + * This check works by using the current Kerberos password to + * make up a password history. We already did the salted hash + * creation to pass the password change check. + * + * We check the pwdHistoryLength to ensure we honour the + * policy on if the history should be checked + */ + if (io->ac->status->domain_data.pwdHistoryLength > 0 + && io->g.aes_256.length && io->o.aes_256.length) + { + bool equal = data_blob_equal_const_time(&io->g.aes_256, + &io->o.aes_256); + if (equal) { + ret = LDB_ERR_CONSTRAINT_VIOLATION; + *werror = WERR_PASSWORD_RESTRICTION; + ldb_asprintf_errstring(ldb, + "%08X: %s - check_password_restrictions: " + "the password was already used (previous password)!", + W_ERROR_V(*werror), + ldb_strerror(ret)); + io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; + return ret; + } + } - /* checks the NT hash password history */ + if (io->n.nt_hash) { + /* + * checks the NT hash password history, against the + * generated NT hash + */ for (i = 0; i < io->o.nt_history_len; i++) { bool pw_cmp = mem_equal_const_time(io->n.nt_hash, io->o.nt_history[i].hash, 16); if (pw_cmp) { @@ -2857,6 +3019,90 @@ static int check_password_restrictions(struct setup_password_fields_io *io, WERR } } + /* + * This check works by using the old Kerberos passwords + * (old and older) to make up a password history. + * + * We check the pwdHistoryLength to ensure we honour the + * policy on if the history should be checked + */ + for (i = 1; + i <= io->o.kvno && i < MIN(3, io->ac->status->domain_data.pwdHistoryLength); + i++) + { + krb5_error_code krb5_ret; + const uint32_t request_kvno = io->o.kvno - i; + DATA_BLOB db_key_blob; + bool pw_equal; + + if (io->n.cleartext_utf8 == NULL) { + /* + * No point checking history if we don't have + * a cleartext password. + */ + break; + } + + if (io->ac->search_res == NULL) { + /* + * This is an ADD, no existing history to check + */ + break; + } + + /* + * 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) { + break; + } + + /* + * Extract the old ENCTYPE_AES256_CTS_HMAC_SHA1_96 value from + * the supplementalCredentials. + */ + 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, + NULL); /* salt */ + if (krb5_ret == ENOENT) { + /* + * If there is no old AES hash (perhaps an imported DB with + * just unicodePwd) then we just wont have an old + * password to compare to if there is no NT hash + */ + break; + } else if (krb5_ret) { + ldb_asprintf_errstring(ldb, + "check_password_restrictions: " + "extraction of old[%u - %d = %d] aes256-cts-hmac-sha1-96 key failed: %s", + io->o.kvno, i, io->o.kvno - i, + smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, + krb5_ret, io->ac)); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* This is the actual history check */ + pw_equal = data_blob_equal_const_time(&io->n.aes_256, + &db_key_blob); + if (pw_equal) { + ret = LDB_ERR_CONSTRAINT_VIOLATION; + *werror = WERR_PASSWORD_RESTRICTION; + ldb_asprintf_errstring(ldb, + "%08X: %s - check_password_restrictions: " + "the password was already used (in history)!", + W_ERROR_V(*werror), + ldb_strerror(ret)); + io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY; + return ret; + } + } + /* are all password changes disallowed? */ if (io->ac->status->domain_data.pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) { ret = LDB_ERR_CONSTRAINT_VIOLATION; @@ -3176,6 +3422,8 @@ static int setup_io(struct ph_context *ac, struct ldb_context *ldb = ldb_module_get_ctx(ac->module); struct loadparm_context *lp_ctx = talloc_get_type( ldb_get_opaque(ldb, "loadparm"), struct loadparm_context); + enum store_nt_hash store_hash_setting = + lpcfg_nt_hash_store(lp_ctx); int ret; const struct ldb_message *info_msg = NULL; struct dom_sid *account_sid = NULL; @@ -3296,6 +3544,30 @@ static int setup_io(struct ph_context *ac, MAX(io->ac->status->domain_data.pwdHistoryLength, 3); } + /* + * Machine accounts need the NT hash to operate the NETLOGON + * ServerAuthenticate{,2,3} logic + */ + if (!(io->u.userAccountControl & UF_NORMAL_ACCOUNT)) { + store_hash_setting = NT_HASH_STORE_ALWAYS; + } + + switch (store_hash_setting) { + case NT_HASH_STORE_ALWAYS: + io->u.store_nt_hash = true; + break; + case NT_HASH_STORE_NEVER: + io->u.store_nt_hash = false; + break; + case NT_HASH_STORE_AUTO: + if (lpcfg_ntlm_auth(lp_ctx) == NTLM_AUTH_DISABLED) { + io->u.store_nt_hash = false; + break; + } + io->u.store_nt_hash = true; + break; + } + if (ac->userPassword) { ret = msg_find_old_and_new_pwd_val(client_msg, "userPassword", ac->req->operation, @@ -3612,6 +3884,10 @@ static int setup_io(struct ph_context *ac, if (existing_msg != NULL) { NTSTATUS status; + krb5_error_code krb5_ret; + DATA_BLOB key_blob; + DATA_BLOB salt_blob; + uint32_t kvno; if (ac->pwd_reset) { /* Get the old password from the database */ @@ -3664,6 +3940,47 @@ static int setup_io(struct ph_context *ac, return LDB_ERR_OPERATIONS_ERROR; } } + + /* + * 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. + */ + 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, + &salt_blob); + if (krb5_ret == ENOENT) { + /* + * If there is no old AES hash (perhaps an imported DB with + * just unicodePwd) then we just wont have an old + * password to compare to if there is no NT hash + */ + return LDB_SUCCESS; + } + if (krb5_ret) { + ldb_asprintf_errstring(ldb, + "setup_io: " + "extraction of salt for old aes256-cts-hmac-sha1-96 key failed: %s", + smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, + krb5_ret, io->ac)); + return LDB_ERR_OPERATIONS_ERROR; + } + + io->o.salt = salt_blob; + io->o.aes_256 = key_blob; + io->o.kvno = kvno; } return LDB_SUCCESS; @@ -4689,6 +5006,7 @@ static int password_hash_mod_search_self(struct ph_context *ac) "badPasswordTime", "badPwdCount", "lockoutTime", + "msDS-KeyVersionNumber", "msDS-SecondaryKrbTgtNumber", NULL }; struct ldb_request *search_req; diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server index 4d0febc7152..7a63f43726b 100644 --- a/source4/dsdb/samdb/ldb_modules/wscript_build_server +++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server @@ -195,7 +195,7 @@ bld.SAMBA_MODULE('ldb_password_hash', init_function='ldb_password_hash_module_init', module_init_name='ldb_init_module', internal_module=False, - deps='talloc samdb LIBCLI_AUTH NDR_DRSBLOBS authkrb5 krb5 gpgme DSDB_MODULE_HELPERS crypt' + deps='talloc samdb LIBCLI_AUTH NDR_DRSBLOBS authkrb5 krb5 gpgme DSDB_MODULE_HELPERS crypt db-glue' ) diff --git a/source4/dsdb/tests/python/password_settings.py b/source4/dsdb/tests/python/password_settings.py index e1c49d7bffb..bac89f3e3c8 100644 --- a/source4/dsdb/tests/python/password_settings.py +++ b/source4/dsdb/tests/python/password_settings.py @@ -869,11 +869,8 @@ unicodePwd:: %s # we can set the exact same password again because there's no history self.assert_password_valid(user, "NewPwd12#") - # There is a difference in behaviour here between Windows and Samba. # When going from zero to non-zero password-history, Windows treats # the current user's password as invalid (even though the password has - # not been altered since the setting changed). Whereas Samba accepts - # the current password (because it's not in the history until the - # *next* time the user's password changes. + # not been altered since the setting changed). self.set_domain_pwdHistoryLength("1") self.assert_password_invalid(user, "NewPwd12#") |