diff options
| author | Cy Schubert <cy@FreeBSD.org> | 2025-04-17 02:13:41 +0000 |
|---|---|---|
| committer | Cy Schubert <cy@FreeBSD.org> | 2025-05-27 16:20:06 +0000 |
| commit | 24f0b4ca2d565cdbb4fe7839ff28320706bf2386 (patch) | |
| tree | bc9ce87edb73f767f5580887d0fc8c643b9d7a49 /module/fast.c | |
pam-krb5: Import/add pam-krb5 from eyeire.orgvendor/pam-krb5/4.11vendor/pam-krb5
From https://www.eyrie.org/~eagle/software/pam-krb5/:
pam-krb5 provides a Kerberos PAM module that supports authentication,
user ticket cache handling, simple authorization (via .k5login or
checking Kerberos principals against local usernames), and password
changing. It can be configured through either options in the PAM
configuration itself or through entries in the system krb5.conf file,
and it tries to work around PAM implementation flaws in commonly-used
PAM-enabled applications such as OpenSSH and xdm. It supports both
PKINIT and FAST to the extent that the underlying Kerberos libraries
support these features.
The reason for this import is to provide an MIT KRB5 compatible
pam_krb5 PAM module. The existing pam_krb5 in FreeBS only works
with Heimdal.
Sponsored by: The FreeBSD Foundation
Diffstat (limited to 'module/fast.c')
| -rw-r--r-- | module/fast.c | 288 |
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 */ |
