1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-03 01:18:10 +03:00

s3:libads: add kerberos_kinit_passwords_ext() helper

This can check more than one password and is designed to
support getting a TGT for our machine account also falling
back to older passwords...

If we don't have a plaintext password it falls back to an nt_hash.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andreas Schneider <asn@samba.org>
This commit is contained in:
Stefan Metzmacher 2024-09-25 16:02:02 +02:00
parent 017e6e1cb1
commit 4dbbfcb004
5 changed files with 596 additions and 0 deletions

View File

@ -26,10 +26,13 @@
#include "system/filesys.h"
#include "smb_krb5.h"
#include "../librpc/gen_ndr/ndr_misc.h"
#include "../librpc/gen_ndr/samr.h"
#include "libads/kerberos_proto.h"
#include "libads/netlogon_ping.h"
#include "secrets.h"
#include "../lib/tsocket/tsocket.h"
#include "../libcli/util/tstream.h"
#include "../lib/util/tevent_ntstatus.h"
#include "lib/util/asn1.h"
#include "librpc/gen_ndr/netlogon.h"
@ -349,6 +352,583 @@ int kerberos_kinit_password_ext(const char *given_principal,
ntstatus);
}
struct kerberos_transaction_cache {
struct tsocket_address *local_addr;
struct tsocket_address *kdc_addr;
uint32_t timeout_msec;
};
static NTSTATUS kerberos_transaction_cache_create(
const char *explicit_kdc,
uint32_t timeout_msec,
TALLOC_CTX *mem_ctx,
struct kerberos_transaction_cache **_kc)
{
struct kerberos_transaction_cache *kc = NULL;
int ret;
kc = talloc_zero(mem_ctx, struct kerberos_transaction_cache);
if (kc == NULL) {
return NT_STATUS_NO_MEMORY;
}
kc->timeout_msec = timeout_msec;
/* parse the address of explicit kdc */
ret = tsocket_address_inet_from_strings(kc,
"ip",
explicit_kdc,
DEFAULT_KRB5_PORT,
&kc->kdc_addr);
if (ret != 0) {
NTSTATUS status = map_nt_error_from_unix_common(errno);
TALLOC_FREE(kc);
return status;
}
/* get an address for us to use locally */
ret = tsocket_address_inet_from_strings(kc,
"ip",
NULL,
0,
&kc->local_addr);
if (ret != 0) {
NTSTATUS status = map_nt_error_from_unix_common(errno);
TALLOC_FREE(kc);
return status;
}
*_kc = kc;
return NT_STATUS_OK;
}
#ifdef HAVE_KRB5_INIT_CREDS_STEP
struct kerberos_transaction_state {
struct tevent_context *ev;
struct kerberos_transaction_cache *kc;
struct tstream_context *stream;
uint8_t in_hdr[4];
struct iovec in_iov[2];
DATA_BLOB rep_blob;
};
static void kerberos_transaction_connect_done(struct tevent_req *subreq);
static struct tevent_req *kerberos_transaction_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct kerberos_transaction_cache *kc,
const char *realm,
const DATA_BLOB req_blob)
{
struct tevent_req *req = NULL;
struct kerberos_transaction_state *state = NULL;
struct tevent_req *subreq = NULL;
struct timeval end;
req = tevent_req_create(mem_ctx, &state,
struct kerberos_transaction_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->kc = kc;
PUSH_BE_U32(state->in_hdr, 0, req_blob.length);
state->in_iov[0].iov_base = (char *)state->in_hdr;
state->in_iov[0].iov_len = 4;
state->in_iov[1].iov_base = (char *)req_blob.data;
state->in_iov[1].iov_len = req_blob.length;
end = timeval_current_ofs_msec(state->kc->timeout_msec);
if (!tevent_req_set_endtime(req, state->ev, end)) {
return tevent_req_post(req, state->ev);
}
subreq = tstream_inet_tcp_connect_send(state,
state->ev,
state->kc->local_addr,
state->kc->kdc_addr);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, state->ev);
}
tevent_req_set_callback(subreq, kerberos_transaction_connect_done, req);
return req;
}
static void kerberos_transaction_writev_done(struct tevent_req *subreq);
static void kerberos_transaction_read_pdu_done(struct tevent_req *subreq);
static void kerberos_transaction_connect_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct kerberos_transaction_state *state =
tevent_req_data(req,
struct kerberos_transaction_state);
int ret, sys_errno;
ret = tstream_inet_tcp_connect_recv(subreq,
&sys_errno,
state,
&state->stream,
NULL);
TALLOC_FREE(subreq);
if (ret != 0) {
NTSTATUS status = map_nt_error_from_unix_common(sys_errno);
tevent_req_nterror(req, status);
return;
}
subreq = tstream_writev_send(state,
state->ev,
state->stream,
state->in_iov, 2);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, kerberos_transaction_writev_done, req);
subreq = tstream_read_pdu_blob_send(state,
state->ev,
state->stream,
4, /* initial_read_size */
tstream_full_request_u32,
req);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, kerberos_transaction_read_pdu_done, req);
}
static void kerberos_transaction_writev_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
int ret, sys_errno;
ret = tstream_writev_recv(subreq, &sys_errno);
TALLOC_FREE(subreq);
if (ret == -1) {
NTSTATUS status = map_nt_error_from_unix_common(sys_errno);
tevent_req_nterror(req, status);
return;
}
}
static void kerberos_transaction_read_pdu_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct kerberos_transaction_state *state =
tevent_req_data(req,
struct kerberos_transaction_state);
NTSTATUS status;
status = tstream_read_pdu_blob_recv(subreq, state, &state->rep_blob);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
/*
* raw blob has the length in the first 4 bytes,
* which we do not need here.
*/
memmove(state->rep_blob.data,
state->rep_blob.data + 4,
state->rep_blob.length - 4);
state->rep_blob.length -= 4;
tevent_req_done(req);
}
static NTSTATUS kerberos_transaction_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
DATA_BLOB *rep_blob)
{
struct kerberos_transaction_state *state =
tevent_req_data(req,
struct kerberos_transaction_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
tevent_req_received(req);
return status;
}
rep_blob->data = talloc_move(mem_ctx, &state->rep_blob.data);
rep_blob->length = state->rep_blob.length;
tevent_req_received(req);
return NT_STATUS_OK;
}
static NTSTATUS kerberos_transaction(
struct kerberos_transaction_cache *kc,
const char *realm,
const DATA_BLOB req_blob,
TALLOC_CTX *mem_ctx,
DATA_BLOB *rep_blob)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev = NULL;
struct tevent_req *req = NULL;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = kerberos_transaction_send(frame, ev, kc, realm, req_blob);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = kerberos_transaction_recv(req, mem_ctx, rep_blob);
fail:
TALLOC_FREE(frame);
return status;
}
#endif /* HAVE_KRB5_INIT_CREDS_STEP */
struct kerberos_kinit_passwords_ext_private {
const char *explicit_kdc;
uint32_t timeout_msec;
struct kerberos_transaction_cache *kc;
const char *password;
const struct samr_Password *nt_hash;
};
static krb5_error_code kerberos_kinit_passwords_ext_cb(krb5_context context,
krb5_creds *creds,
krb5_principal client,
krb5_get_init_creds_opt *options,
void *private_data)
{
struct kerberos_kinit_passwords_ext_private *ep =
(struct kerberos_kinit_passwords_ext_private *)private_data;
TALLOC_CTX *frame = talloc_stackframe();
krb5_deltat start_time = 0;
krb5_init_creds_context ctx = NULL;
krb5_keytab keytab = NULL;
krb5_error_code ret;
#ifdef HAVE_KRB5_INIT_CREDS_STEP
DATA_BLOB rep_blob = { .length = 0, };
#endif /* HAVE_KRB5_INIT_CREDS_STEP */
ZERO_STRUCTP(creds);
ret = krb5_init_creds_init(context,
client,
NULL, /* prompter */
NULL, /* prompter_data */
start_time,
options,
&ctx);
if (ret) {
TALLOC_FREE(frame);
return ret;
}
if (ep->password != NULL) {
ret = krb5_init_creds_set_password(context, ctx, ep->password);
if (ret) {
krb5_init_creds_free(context, ctx);
TALLOC_FREE(frame);
return ret;
}
} else if (ep->nt_hash != NULL) {
const char *keytab_name = "MEMORY:kerberos_kinit_passwords_ext_cb";
krb5_keytab_entry entry = { .principal = client, };
ret = krb5_kt_resolve(context, keytab_name, &keytab);
if (ret) {
krb5_init_creds_free(context, ctx);
TALLOC_FREE(frame);
return ret;
}
ret = smb_krb5_keyblock_init_contents(context,
ENCTYPE_ARCFOUR_HMAC,
ep->nt_hash->hash,
ARRAY_SIZE(ep->nt_hash->hash),
KRB5_KT_KEY(&entry));
if (ret) {
krb5_init_creds_free(context, ctx);
krb5_kt_close(context, keytab);
TALLOC_FREE(frame);
return ret;
}
ret = krb5_kt_add_entry(context, keytab, &entry);
krb5_free_keyblock_contents(context, KRB5_KT_KEY(&entry));
if (ret) {
krb5_init_creds_free(context, ctx);
krb5_kt_close(context, keytab);
TALLOC_FREE(frame);
return ret;
}
ret = krb5_init_creds_set_keytab(context, ctx, keytab);
if (ret) {
krb5_init_creds_free(context, ctx);
krb5_kt_close(context, keytab);
TALLOC_FREE(frame);
return ret;
}
} else {
ret = EINVAL;
krb5_init_creds_free(context, ctx);
TALLOC_FREE(frame);
return ret;
}
if (ep->kc == NULL) {
/*
* Use the logic from the krb5 libraries
* to find the KDC
*/
ret = krb5_init_creds_get(context, ctx);
if (ret) {
krb5_init_creds_free(context, ctx);
if (keytab != NULL) {
krb5_kt_close(context, keytab);
}
TALLOC_FREE(frame);
return ret;
}
goto got_creds;
}
#ifdef HAVE_KRB5_INIT_CREDS_STEP
while (true) {
#if defined(HAVE_KRB5_REALM_TYPE)
/* Heimdal. */
krb5_realm krealm = NULL;
#else
/* MIT */
krb5_data krealm = { .length = 0, };
#endif
unsigned int flags = 0;
const char *realm = NULL;
krb5_data in = { .length = 0, };
krb5_data out = { .length = 0, };
DATA_BLOB req_blob = { .length = 0, };
NTSTATUS status;
in.data = (void *)rep_blob.data;
in.length = rep_blob.length;
flags = 0;
ret = krb5_init_creds_step(context,
ctx,
&in,
&out,
&krealm,
&flags);
data_blob_free(&rep_blob);
in = (krb5_data) { .length = 0, };
if (ret) {
krb5_init_creds_free(context, ctx);
if (keytab != NULL) {
krb5_kt_close(context, keytab);
}
TALLOC_FREE(frame);
return ret;
}
if ((flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE) == 0) {
smb_krb5_free_data_contents(context, &out);
#if defined(HAVE_KRB5_REALM_TYPE)
/* Heimdal. */
SAFE_FREE(krealm);
#else
/* MIT */
smb_krb5_free_data_contents(context, &krealm);
#endif
break;
}
#if defined(HAVE_KRB5_REALM_TYPE)
/* Heimdal. */
realm = talloc_strdup(frame, krealm);
SAFE_FREE(krealm);
#else
/* MIT */
realm = talloc_strndup(frame, krealm.data, krealm.length);
smb_krb5_free_data_contents(context, &krealm);
#endif
if (realm == NULL) {
smb_krb5_free_data_contents(context, &out);
krb5_init_creds_free(context, ctx);
if (keytab != NULL) {
krb5_kt_close(context, keytab);
}
TALLOC_FREE(frame);
return ENOMEM;
}
req_blob.data = (uint8_t *)out.data;
req_blob.length = out.length;
status = kerberos_transaction(ep->kc,
realm,
req_blob,
frame,
&rep_blob);
smb_krb5_free_data_contents(context, &out);
req_blob = (DATA_BLOB) { .length = 0, };
if (!NT_STATUS_IS_OK(status)) {
ret = map_errno_from_nt_status(status);
krb5_init_creds_free(context, ctx);
if (keytab != NULL) {
krb5_kt_close(context, keytab);
}
TALLOC_FREE(frame);
return ret;
}
}
#else /* HAVE_KRB5_INIT_CREDS_STEP */
#ifdef USING_EMBEDDED_HEIMDAL
#error missing HAVE_KRB5_INIT_CREDS_STEP
#endif /* USING_EMBEDDED_HEIMDAL */
/* Caller should already check! */
smb_panic("krb5_init_creds_step not available");
#endif /* HAVE_KRB5_INIT_CREDS_STEP */
got_creds:
ret = krb5_init_creds_get_creds(context, ctx, creds);
if (ret) {
krb5_init_creds_free(context, ctx);
if (keytab != NULL) {
krb5_kt_close(context, keytab);
}
TALLOC_FREE(frame);
return ret;
}
krb5_init_creds_free(context, ctx);
if (keytab != NULL) {
krb5_kt_close(context, keytab);
}
TALLOC_FREE(frame);
return 0;
}
/*
simulate a kinit, putting the tgt in the given cache location.
cache_name == NULL is not allowed.
This tries all given passwords until we don't get
KDC_ERR_PREAUTH_FAILED.
If passwords[i] is NULL it falls back to nt_hashes[i]
*/
int kerberos_kinit_passwords_ext(const char *given_principal,
uint8_t num_passwords,
const char * const *passwords,
const struct samr_Password * const *nt_hashes,
uint8_t *used_idx,
const char *explicit_kdc,
const char *cache_name,
TALLOC_CTX *mem_ctx,
char **_canon_principal,
char **_canon_realm,
NTSTATUS *ntstatus)
{
TALLOC_CTX *frame = talloc_stackframe();
struct kerberos_kinit_passwords_ext_private ep = {
.explicit_kdc = explicit_kdc,
.timeout_msec = 15*1000,
};
NTSTATUS status;
krb5_error_code ret;
uint8_t i;
krb5_error_code first_ret = EINVAL;
NTSTATUS first_status = NT_STATUS_UNSUCCESSFUL;
if (num_passwords == 0) {
TALLOC_FREE(frame);
return EINVAL;
}
if (num_passwords >= INT8_MAX) {
TALLOC_FREE(frame);
return EINVAL;
}
#ifndef HAVE_KRB5_INIT_CREDS_STEP
if (ep.explicit_kdc != NULL) {
DBG_ERR("Using explicit_kdc requires krb5_init_creds_step!\n");
TALLOC_FREE(frame);
return EINVAL;
}
#endif /* ! HAVE_KRB5_INIT_CREDS_STEP */
DBG_DEBUG("explicit_kdc[%s] given_principal[%s] "
"num_passwords[%u] cache_name[%s]\n",
ep.explicit_kdc,
given_principal,
num_passwords,
cache_name);
if (ep.explicit_kdc != NULL) {
status = kerberos_transaction_cache_create(ep.explicit_kdc,
ep.timeout_msec,
frame,
&ep.kc);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(frame);
return map_errno_from_nt_status(status);
}
}
for (i = 0; i < num_passwords; i++) {
ep.password = passwords[i];
ep.nt_hash = nt_hashes[i];
ret = kerberos_kinit_generic_once(given_principal,
kerberos_kinit_passwords_ext_cb,
&ep,
0, /* time_offset */
0, /* expire_time */
0, /* renew_till_time */
cache_name,
true, /* request_pac */
NULL, /* add_netbios_addr */
0, /* renewable_time */
mem_ctx,
_canon_principal,
_canon_realm,
ntstatus);
if (ret == 0) {
*used_idx = i;
TALLOC_FREE(frame);
return 0;
}
if (i == 0) {
first_ret = ret;
first_status = *ntstatus;
}
if (ret != KRB5KDC_ERR_PREAUTH_FAILED) {
*used_idx = i;
TALLOC_FREE(frame);
return ret;
}
}
*used_idx = 0;
*ntstatus = first_status;
TALLOC_FREE(frame);
return first_ret;
}
int ads_kdestroy(const char *cc_name)
{
krb5_error_code code;

View File

@ -33,6 +33,7 @@
#include "system/kerberos.h"
struct PAC_DATA_CTR;
struct samr_Password;
#define DEFAULT_KRB5_PORT 88
@ -53,6 +54,17 @@ int kerberos_kinit_password_ext(const char *given_principal,
char **_canon_principal,
char **_canon_realm,
NTSTATUS *ntstatus);
int kerberos_kinit_passwords_ext(const char *given_principal,
uint8_t num_passwords,
const char * const *passwords,
const struct samr_Password * const *nt_hashes,
uint8_t *used_idx,
const char *explicit_kdc,
const char *cache_name,
TALLOC_CTX *mem_ctx,
char **_canon_principal,
char **_canon_realm,
NTSTATUS *ntstatus);
int ads_kdestroy(const char *cc_name);
int kerberos_kinit_password(const char *principal,

View File

@ -13,3 +13,5 @@ conf.RECURSE('third_party/heimdal_build')
# when this will be available also in
# system libraries...
conf.define('HAVE_CLIENT_GSS_C_CHANNEL_BOUND_FLAG', 1)
conf.define('HAVE_KRB5_INIT_CREDS_STEP', 1)

View File

@ -62,6 +62,7 @@ conf.define('USING_SYSTEM_HEIMDAL', 1)
conf.CHECK_FUNCS('''
krb5_get_init_creds_opt_set_fast_ccache
krb5_get_init_creds_opt_set_fast_flags
krb5_init_creds_step
''',
lib='krb5',
headers='krb5.h')

View File

@ -170,6 +170,7 @@ conf.CHECK_FUNCS('''
krb5_free_string
krb5_get_init_creds_opt_set_fast_ccache
krb5_get_init_creds_opt_set_fast_flags
krb5_init_creds_step
''',
lib='krb5 k5crypto',
headers='krb5.h')