]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s3:libads: add kerberos_kinit_passwords_ext() helper
authorStefan Metzmacher <metze@samba.org>
Wed, 25 Sep 2024 14:02:02 +0000 (16:02 +0200)
committerStefan Metzmacher <metze@samba.org>
Thu, 5 Dec 2024 16:46:37 +0000 (16:46 +0000)
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>
source3/libads/kerberos.c
source3/libads/kerberos_proto.h
wscript_configure_embedded_heimdal
wscript_configure_system_heimdal
wscript_configure_system_mitkrb5

index ac5f339244c455e83d06e561f075fa6d86a2d134..75803500d3183c93e99d5f28b72e510473f6e344 100644 (file)
 #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;
index cc8af444ceb23f838f4f8fe4c31be5611d336c65..a96211c7289e8ecd79f16b32211ac11cf38216e8 100644 (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,
index 45f47721dec4246850cd306d8e9feaaac966c5f0..c1488e5506ead6a7979c14e7cdac3696d5e73db9 100644 (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)
index b6ca9e98c7e8cc0087000711b9dd90fc2535c4a0..c320a76ea17164cb9b686b3ffb84a53eb33b4678 100644 (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')
index e1fac843f0c8237de3e0229235f9d502298f29ec..58b9fb802d0330046ac0a2a59e7cacb06383a1a2 100644 (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')