Password and authentication handling
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2009
Copyright (C) Gerald Carter 2003
- Copyright (C) Stefan Metzmacher 2005
+ Copyright (C) Stefan Metzmacher 2005-2010
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
#include "libcli/auth/libcli_auth.h"
#include "libds/common/roles.h"
#include "lib/util/tevent_ntstatus.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "kdc/db-glue.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_AUTH
static NTSTATUS authsam_password_ok(struct auth4_context *auth_context,
TALLOC_CTX *mem_ctx,
const struct samr_Password *nt_pwd,
+ struct smb_krb5_context *smb_krb5_context,
+ const DATA_BLOB *stored_aes_256_key,
+ const krb5_data *salt,
const struct auth_usersupplied_info *user_info,
DATA_BLOB *user_sess_key,
DATA_BLOB *lm_sess_key)
case AUTH_PASSWORD_PLAIN:
{
const struct auth_usersupplied_info *user_info_temp;
+
+ if (nt_pwd == NULL && stored_aes_256_key != NULL && user_info->password.plaintext != NULL) {
+ bool pw_equal;
+ int krb5_ret;
+ DATA_BLOB supplied_aes_256_key;
+ krb5_keyblock key;
+ krb5_data cleartext_data = {
+ .data = user_info->password.plaintext,
+ .length = strlen(user_info->password.plaintext)
+ };
+
+ *lm_sess_key = data_blob_null;
+ *user_sess_key = data_blob_null;
+
+ krb5_ret = smb_krb5_create_key_from_string(smb_krb5_context->krb5_context,
+ NULL,
+ salt,
+ &cleartext_data,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ &key);
+ if (krb5_ret) {
+ DBG_ERR("generation of a aes256-cts-hmac-sha1-96 key for password comparison failed: %s",
+ smb_get_krb5_error_message(smb_krb5_context->krb5_context,
+ krb5_ret, mem_ctx));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ supplied_aes_256_key = data_blob_const(KRB5_KEY_DATA(&key),
+ KRB5_KEY_LENGTH(&key));
+
+ pw_equal = data_blob_equal_const_time(&supplied_aes_256_key,
+ stored_aes_256_key);
+
+ krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &key);
+ if (!pw_equal) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+ return NT_STATUS_OK;
+ }
+
status = encrypt_user_info(mem_ctx, auth_context,
AUTH_PASSWORD_HASH,
user_info, &user_info_temp);
TALLOC_FREE(tmp_ctx);
}
+static const struct samr_Password *hide_invalid_nthash(const struct samr_Password *in)
+{
+ /*
+ * This is the result of:
+ *
+ * E_md4hash("", zero_string_hash.hash);
+ */
+ static const struct samr_Password zero_string_hash = {
+ .hash = {
+ 0x31, 0xd6, 0xcf, 0xe0, 0xd1, 0x6a, 0xe9, 0x31,
+ 0xb7, 0x3c, 0x59, 0xd7, 0xe0, 0xc0, 0x89, 0xc0,
+ }
+ };
+
+ if (in == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Skip over any all-zero hashes in the history. No known software
+ * stores these but just to be sure
+ */
+ if (all_zero(in->hash, sizeof(in->hash))) {
+ return NULL;
+ }
+
+ /*
+ * This looks odd, but the password_hash module in the past has written
+ * this in the rare situation where (somehow) we didn't have an old NT
+ * hash (one of the old LM-only set paths)
+ *
+ * mem_equal_const_time() is used to avoid a timing attack
+ * when comparing secret data in the server with this constant
+ * value.
+ */
+ if (mem_equal_const_time(in->hash, zero_string_hash.hash, 16)) {
+ in = NULL;
+ }
+
+ return in;
+}
/*
* Check that a password is OK, and update badPwdCount if required.
const char * const attrs[] = { "pwdHistoryLength", NULL };
struct ldb_message *dom_msg;
struct samr_Password *nt_pwd;
+ DATA_BLOB _aes_256_key = data_blob_null;
+ DATA_BLOB *aes_256_key = NULL;
+ krb5_data _salt = { .data = NULL, .length = 0 };
+ krb5_data *salt = NULL;
+ 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;
tmp_ctx = talloc_new(mem_ctx);
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) {
}
}
- auth_status = authsam_password_ok(auth_context, tmp_ctx,
+ /*
+ * If we don't have an NT password, pull a kerberos key
+ * instead for plaintext.
+ */
+ if (nt_pwd == NULL &&
+ sc_val != NULL &&
+ user_info->password_state == AUTH_PASSWORD_PLAIN)
+ {
+ krb5_error_code krb5_ret;
+
+ krb5_ret = smb_krb5_init_context(tmp_ctx,
+ auth_context->lp_ctx,
+ &smb_krb5_context);
+ if (krb5_ret != 0) {
+ DBG_ERR("Failed to setup krb5_context: %s!",
+ error_message(krb5_ret));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Get the current salt from the record
+ */
+
+ 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,
+ &salt_data);
+ if (krb5_ret == 0) {
+ aes_256_key = &_aes_256_key;
+
+ _salt.data = (char *)salt_data.data;
+ _salt.length = salt_data.length;
+ salt = &_salt;
+ }
+ }
+
+ auth_status = authsam_password_ok(auth_context,
+ tmp_ctx,
nt_pwd,
+ smb_krb5_context,
+ aes_256_key,
+ salt,
user_info,
user_sess_key, lm_sess_key);
+
if (NT_STATUS_IS_OK(auth_status)) {
if (user_sess_key->data) {
talloc_steal(mem_ctx, user_sess_key->data);
}
for (i = 1; i < MIN(history_len, 3); i++) {
- struct samr_Password zero_string_hash;
const struct samr_Password *nt_history_pwd = NULL;
NTTIME pwdLastSet;
struct timeval tv_now;
int allowed_period_mins;
NTTIME allowed_period;
+ /* Reset these variables back to starting as empty */
+ aes_256_key = NULL;
+ salt = NULL;
+
+ /*
+ * Obtain the i'th old password from the NT password
+ * history for this user.
+ *
+ * We avoid issues with salts (which are not
+ * recorded for historical AES256 keys) by using the
+ * ntPwdHistory in preference.
+ */
nt_status = samdb_result_passwords_from_history(tmp_ctx,
auth_context->lp_ctx,
msg, i,
NULL,
&nt_history_pwd);
+
+ /*
+ * Belts and braces: note that
+ * samdb_result_passwords_from_history() currently
+ * does not fail for missing attributes, it only sets
+ * nt_history_pwd = NULL, so "break" and fall down to
+ * the bad password count upate if this happens
+ */
if (!NT_STATUS_IS_OK(nt_status)) {
- /*
- * If we don't find element 'i' we won't find
- * 'i+1' ...
- */
break;
}
+ nt_history_pwd = hide_invalid_nthash(nt_history_pwd);
+
/*
- * We choose to avoid any issues
- * around different LM and NT history
- * lengths by only checking the NT
- * history
+ * We don't have an NT hash from the
+ * ntPwdHistory, but we can still perform the
+ * password check with the AES256
+ * key.
+ *
+ * However, this is the second preference as
+ * it will fail if the account was renamed
+ * prior to a password change (as we won't
+ * have the correct salt available to
+ * calculate the AES256 key).
*/
- if (nt_history_pwd == NULL) {
+
+ if (nt_history_pwd == NULL && sc_val != NULL &&
+ user_info->password_state == AUTH_PASSWORD_PLAIN &&
+ current_kvno >= i)
+ {
+ krb5_error_code krb5_ret;
+ const uint32_t request_kvno = current_kvno - i;
+
/*
- * If we don't find element 'i' we won't find
- * 'i+1' ...
+ * Confirm we have a krb5_context set up
*/
- break;
- }
+ if (smb_krb5_context == NULL) {
+ /*
+ * We get here if we had a unicodePwd
+ * for the current password, no
+ * ntPwdHistory, a valid previous
+ * Kerberos history AND are processing
+ * a simple bind.
+ *
+ * This really is a corner case so
+ * favour cleaner code over trying to
+ * allow for an old password. It is
+ * more likely this is just a new
+ * account.
+ *
+ * "break" out of the loop and fall down
+ * to the bad password update
+ */
+ break;
+ }
- /* Skip over all-zero hashes in the history */
- if (all_zero(nt_history_pwd->hash,
- sizeof(nt_history_pwd->hash))) {
- continue;
- }
+ /*
+ * Get the current salt from the record
+ */
- /*
- * This looks odd, but the password_hash module writes this in if
- * (somehow) we didn't have an old NT hash
- */
+ 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,
+ &salt_data);
+ if (krb5_ret != 0) {
+ break;
+ }
- E_md4hash("", zero_string_hash.hash);
- if (mem_equal_const_time(nt_history_pwd->hash, zero_string_hash.hash, 16)) {
- continue;
+ aes_256_key = &_aes_256_key;
+
+ _salt.data = (char *)salt_data.data;
+ _salt.length = salt_data.length;
+ salt = &_salt;
+
+ } else if (nt_history_pwd == NULL) {
+ /*
+ * If we don't find element 'i' in the
+ * ntPwdHistory and can not fall back to the
+ * kerberos hash, we won't find 'i+1' ...
+ */
+ break;
}
auth_status = authsam_password_ok(auth_context, tmp_ctx,
nt_history_pwd,
+ smb_krb5_context,
+ aes_256_key,
+ salt,
user_info,
user_sess_key,
lm_sess_key);
+
if (!NT_STATUS_IS_OK(auth_status)) {
/*
* If this was not a correct password, try the next