aboutsummaryrefslogtreecommitdiff
path: root/module/fast.c
diff options
context:
space:
mode:
Diffstat (limited to 'module/fast.c')
-rw-r--r--module/fast.c288
1 files changed, 288 insertions, 0 deletions
diff --git a/module/fast.c b/module/fast.c
new file mode 100644
index 000000000000..466199977fad
--- /dev/null
+++ b/module/fast.c
@@ -0,0 +1,288 @@
+/*
+ * Support for FAST (Flexible Authentication Secure Tunneling).
+ *
+ * FAST is a mechanism to protect Kerberos against password guessing attacks
+ * and provide other security improvements. It requires existing credentials
+ * to protect the initial preauthentication exchange. These can come either
+ * from a ticket cache for another principal or via anonymous PKINIT.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Contributions from Sam Hartman and Yair Yarom
+ * Copyright 2017, 2020 Russ Allbery <eagle@eyrie.org>
+ * Copyright 2010, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * SPDX-License-Identifier: BSD-3-clause or GPL-1+
+ */
+
+#include <config.h>
+#include <portable/krb5.h>
+#include <portable/system.h>
+
+#include <errno.h>
+
+#include <module/internal.h>
+#include <pam-util/args.h>
+#include <pam-util/logging.h>
+
+
+/*
+ * Initialize an internal anonymous ticket cache with a random name and store
+ * the resulting ticket cache in the ccache argument. Returns a Kerberos
+ * error code.
+ */
+#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ANONYMOUS
+
+static krb5_error_code
+cache_init_anonymous(struct pam_args *args, krb5_ccache *ccache UNUSED)
+{
+ putil_debug(args, "not built with anonymous FAST support");
+ return KRB5KDC_ERR_BADOPTION;
+}
+
+#else /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ANONYMOUS */
+
+static krb5_error_code
+cache_init_anonymous(struct pam_args *args, krb5_ccache *ccache)
+{
+ krb5_context c = args->config->ctx->context;
+ krb5_error_code retval;
+ krb5_principal princ = NULL;
+ char *realm;
+ char *name = NULL;
+ krb5_creds creds;
+ bool creds_valid = false;
+ krb5_get_init_creds_opt *opts = NULL;
+
+ *ccache = NULL;
+ memset(&creds, 0, sizeof(creds));
+
+ /* Construct the anonymous principal name. */
+ retval = krb5_get_default_realm(c, &realm);
+ if (retval != 0) {
+ putil_debug_krb5(args, retval, "cannot find realm for anonymous FAST");
+ return retval;
+ }
+ retval = krb5_build_principal_ext(
+ c, &princ, (unsigned int) strlen(realm), realm,
+ strlen(KRB5_WELLKNOWN_NAME), KRB5_WELLKNOWN_NAME,
+ strlen(KRB5_ANON_NAME), KRB5_ANON_NAME, NULL);
+ if (retval != 0) {
+ krb5_free_default_realm(c, realm);
+ putil_debug_krb5(args, retval, "cannot create anonymous principal");
+ return retval;
+ }
+ krb5_free_default_realm(c, realm);
+
+ /*
+ * Set up the credential cache the anonymous credentials. We use a
+ * memory cache whose name is based on the pointer value of our Kerberos
+ * context, since that should be unique among threads.
+ */
+ if (asprintf(&name, "MEMORY:%p", (void *) c) < 0) {
+ putil_crit(args, "malloc failure: %s", strerror(errno));
+ retval = errno;
+ goto done;
+ }
+ retval = krb5_cc_resolve(c, name, ccache);
+ if (retval != 0) {
+ putil_err_krb5(args, retval,
+ "cannot create anonymous FAST credential cache %s",
+ name);
+ goto done;
+ }
+
+ /* Obtain the credentials. */
+ retval = krb5_get_init_creds_opt_alloc(c, &opts);
+ if (retval != 0) {
+ putil_err_krb5(args, retval, "cannot create FAST credential options");
+ goto done;
+ }
+ krb5_get_init_creds_opt_set_anonymous(opts, 1);
+ krb5_get_init_creds_opt_set_tkt_life(opts, 60);
+# ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_OUT_CCACHE
+ krb5_get_init_creds_opt_set_out_ccache(c, opts, *ccache);
+# endif
+ retval = krb5_get_init_creds_password(c, &creds, princ, NULL, NULL, NULL,
+ 0, NULL, opts);
+ if (retval != 0) {
+ putil_debug_krb5(args, retval,
+ "cannot obtain anonymous credentials for FAST");
+ goto done;
+ }
+ creds_valid = true;
+
+ /*
+ * If set_out_ccache was available, we're done. Otherwise, we have to
+ * manually set up the ticket cache. Use the principal from the acquired
+ * credentials when initializing the ticket cache, since the realm will
+ * not match the realm of our input principal.
+ */
+# ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_OUT_CCACHE
+ retval = krb5_cc_initialize(c, *ccache, creds.client);
+ if (retval != 0) {
+ putil_err_krb5(args, retval, "cannot initialize FAST ticket cache");
+ goto done;
+ }
+ retval = krb5_cc_store_cred(c, *ccache, &creds);
+ if (retval != 0) {
+ putil_err_krb5(args, retval, "cannot store FAST credentials");
+ goto done;
+ }
+# endif /* !HAVE_KRB5_GET_INIT_CREDS_OPT_SET_OUT_CCACHE */
+
+done:
+ if (retval != 0 && *ccache != NULL) {
+ krb5_cc_destroy(c, *ccache);
+ *ccache = NULL;
+ }
+ if (princ != NULL)
+ krb5_free_principal(c, princ);
+ free(name);
+ if (opts != NULL)
+ krb5_get_init_creds_opt_free(c, opts);
+ if (creds_valid)
+ krb5_free_cred_contents(c, &creds);
+ return retval;
+}
+#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ANONYMOUS */
+
+
+/*
+ * Attempt to use an existing ticket cache for FAST. Checks whether
+ * fast_ccache is set in the options and, if so, opens that cache and does
+ * some sanity checks, returning the cache name to use if everything checks
+ * out in newly allocated memory. Caller is responsible for freeing. If not,
+ * returns NULL.
+ */
+UNUSED static char *
+fast_setup_cache(struct pam_args *args)
+{
+ krb5_context c = args->config->ctx->context;
+ krb5_error_code retval;
+ krb5_principal princ;
+ krb5_ccache ccache;
+ char *result;
+ const char *cache = args->config->fast_ccache;
+
+ if (cache == NULL)
+ return NULL;
+ retval = krb5_cc_resolve(c, cache, &ccache);
+ if (retval != 0) {
+ putil_debug_krb5(args, retval, "cannot open FAST ccache %s", cache);
+ return NULL;
+ }
+ retval = krb5_cc_get_principal(c, ccache, &princ);
+ if (retval != 0) {
+ putil_debug_krb5(args, retval,
+ "failed to get principal from FAST"
+ " ccache %s",
+ cache);
+ krb5_cc_close(c, ccache);
+ return NULL;
+ } else {
+ krb5_free_principal(c, princ);
+ krb5_cc_close(c, ccache);
+ result = strdup(cache);
+ if (result == NULL)
+ putil_crit(args, "strdup failure: %s", strerror(errno));
+ return result;
+ }
+}
+
+
+/*
+ * Attempt to use an anonymous ticket cache for FAST. Checks whether
+ * anon_fast is set in the options and, if so, opens that cache and does some
+ * sanity checks, returning the cache name to use if everything checks out in
+ * newly allocated memory. Caller is responsible for freeing. If not,
+ * returns NULL.
+ *
+ * If successful, store the anonymous FAST cache in the context where it will
+ * be freed following authentication.
+ */
+UNUSED static char *
+fast_setup_anon(struct pam_args *args)
+{
+ krb5_context c = args->config->ctx->context;
+ krb5_error_code retval;
+ krb5_ccache ccache;
+ char *cache, *result;
+
+ if (!args->config->anon_fast)
+ return NULL;
+ retval = cache_init_anonymous(args, &ccache);
+ if (retval != 0) {
+ putil_debug_krb5(args, retval, "skipping anonymous FAST");
+ return NULL;
+ }
+ retval = krb5_cc_get_full_name(c, ccache, &cache);
+ if (retval != 0) {
+ putil_debug_krb5(args, retval,
+ "cannot get name of anonymous FAST"
+ " credential cache");
+ krb5_cc_destroy(c, ccache);
+ return NULL;
+ }
+ result = strdup(cache);
+ if (result == NULL) {
+ putil_crit(args, "strdup failure: %s", strerror(errno));
+ krb5_cc_destroy(c, ccache);
+ }
+ krb5_free_string(c, cache);
+ putil_debug(args, "anonymous authentication for FAST succeeded");
+ if (args->config->ctx->fast_cache != NULL)
+ krb5_cc_destroy(c, args->config->ctx->fast_cache);
+ args->config->ctx->fast_cache = ccache;
+ return result;
+}
+
+
+/*
+ * Set initial credential options for FAST if support is available.
+ *
+ * If fast_ccache is set, we try to use that ticket cache first. Open it and
+ * read the principal from it first to ensure that the cache exists and
+ * contains credentials. If that fails, skip setting the FAST cache.
+ *
+ * If anon_fast is set and fast_ccache is not or is skipped for the reasons
+ * described above, try to obtain anonymous credentials and then use them as
+ * FAST armor.
+ *
+ * Note that this function cannot fail. If anything about FAST setup doesn't
+ * work, we continue without FAST.
+ */
+#ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME
+
+void
+pamk5_fast_setup(struct pam_args *args UNUSED,
+ krb5_get_init_creds_opt *opts UNUSED)
+{
+}
+
+#else /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME */
+
+void
+pamk5_fast_setup(struct pam_args *args, krb5_get_init_creds_opt *opts)
+{
+ krb5_context c = args->config->ctx->context;
+ krb5_error_code retval;
+ char *cache;
+
+ /* First try to use fast_ccache, and then fall back on anon_fast. */
+ cache = fast_setup_cache(args);
+ if (cache == NULL)
+ cache = fast_setup_anon(args);
+ if (cache == NULL)
+ return;
+
+ /* We have a valid FAST ticket cache. Set the option. */
+ retval = krb5_get_init_creds_opt_set_fast_ccache_name(c, opts, cache);
+ if (retval != 0)
+ putil_err_krb5(args, retval, "failed to set FAST ccache");
+ else
+ putil_debug(args, "setting FAST credential cache to %s", cache);
+ free(cache);
+}
+
+#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME */