mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
s4-auth: For LDAP simple bind, fall back to checking the ENCTYPE_AES256_CTS_HMAC_SHA1_96 if stored
Since we don't store a salt per-key, but only a single salt, when we do not have the NT hash in the unicodePwd (eg ntlm auth = disabled), the check will fail for a previous password if the account was renamed prior to a newer password being set. Pair-Programmed-With: Stefan Metzmacher <metze@samba.org> Signed-off-by: Andrew Bartlett <abartlet@samba.org> Signed-off-by: Stefan Metzmacher <metze@samba.org> Reviewed-by: Stefan Metzmacher <metze@samba.org>
This commit is contained in:
parent
18f2a6b231
commit
6029e2250c
@ -129,6 +129,9 @@ struct auth4_context {
|
||||
/* Private data for the callbacks on this auth context */
|
||||
void *private_data;
|
||||
|
||||
/* Kerberos context, set up on demand */
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
|
||||
struct tevent_req *(*check_ntlm_password_send)(TALLOC_CTX *mem_ctx,
|
||||
struct tevent_context *ev,
|
||||
struct auth4_context *auth_ctx,
|
||||
|
@ -3,7 +3,7 @@
|
||||
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
|
||||
@ -37,6 +37,9 @@
|
||||
#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
|
||||
@ -53,6 +56,9 @@ extern const char *domain_ref_attrs[];
|
||||
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)
|
||||
@ -63,6 +69,46 @@ static NTSTATUS authsam_password_ok(struct auth4_context *auth_context,
|
||||
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);
|
||||
@ -197,6 +243,47 @@ static void auth_sam_trigger_repl_secret(TALLOC_CTX *mem_ctx,
|
||||
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.
|
||||
@ -220,7 +307,15 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
|
||||
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);
|
||||
@ -243,6 +338,10 @@ 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) {
|
||||
@ -273,10 +372,55 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@ -317,7 +461,6 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
|
||||
}
|
||||
|
||||
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;
|
||||
@ -325,54 +468,120 @@ static NTSTATUS authsam_password_check_and_record(struct auth4_context *auth_con
|
||||
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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the current salt from the record
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Skip over all-zero hashes in the history */
|
||||
if (all_zero(nt_history_pwd->hash,
|
||||
sizeof(nt_history_pwd->hash))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* This looks odd, but the password_hash module writes this in if
|
||||
* (somehow) we didn't have an old NT hash
|
||||
*/
|
||||
|
||||
E_md4hash("", zero_string_hash.hash);
|
||||
if (mem_equal_const_time(nt_history_pwd->hash, zero_string_hash.hash, 16)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -4,7 +4,8 @@ bld.SAMBA_MODULE('auth4_sam_module',
|
||||
source='auth_sam.c',
|
||||
subsystem='auth4',
|
||||
init_function='auth4_sam_init',
|
||||
deps='samdb auth4_sam NTLMSSP_COMMON samba-hostconfig RPC_NDR_IRPC MESSAGING'
|
||||
deps='samdb auth4_sam NTLMSSP_COMMON samba-hostconfig RPC_NDR_IRPC MESSAGING db-glue',
|
||||
enabled=bld.AD_DC_BUILD_IS_ENABLED()
|
||||
)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user