summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs-xml/smbdotconf/security/nt_hash_store.xml70
-rw-r--r--docs-xml/smbdotconf/security/ntlmauth.xml9
-rw-r--r--lib/param/loadparm.c1
-rw-r--r--lib/param/loadparm.h7
-rw-r--r--lib/param/param_table.c7
-rw-r--r--selftest/knownfail.d/nt-hash-support-gone3
-rw-r--r--selftest/knownfail.d/password_settings2
-rwxr-xr-xselftest/target/Samba4.pm2
-rw-r--r--source3/param/loadparm.c1
-rw-r--r--source4/dsdb/samdb/ldb_modules/password_hash.c368
-rw-r--r--source4/dsdb/samdb/ldb_modules/wscript_build_server2
-rw-r--r--source4/dsdb/tests/python/password_settings.py5
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#")