1 /* 2 * Support for FAST (Flexible Authentication Secure Tunneling). 3 * 4 * FAST is a mechanism to protect Kerberos against password guessing attacks 5 * and provide other security improvements. It requires existing credentials 6 * to protect the initial preauthentication exchange. These can come either 7 * from a ticket cache for another principal or via anonymous PKINIT. 8 * 9 * Written by Russ Allbery <eagle@eyrie.org> 10 * Contributions from Sam Hartman and Yair Yarom 11 * Copyright 2017, 2020 Russ Allbery <eagle@eyrie.org> 12 * Copyright 2010, 2012 13 * The Board of Trustees of the Leland Stanford Junior University 14 * 15 * SPDX-License-Identifier: BSD-3-clause or GPL-1+ 16 */ 17 18 #include <config.h> 19 #include <portable/krb5.h> 20 #include <portable/system.h> 21 22 #include <errno.h> 23 24 #include <module/internal.h> 25 #include <pam-util/args.h> 26 #include <pam-util/logging.h> 27 28 29 /* 30 * Initialize an internal anonymous ticket cache with a random name and store 31 * the resulting ticket cache in the ccache argument. Returns a Kerberos 32 * error code. 33 */ 34 #ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ANONYMOUS 35 36 static krb5_error_code 37 cache_init_anonymous(struct pam_args *args, krb5_ccache *ccache UNUSED) 38 { 39 putil_debug(args, "not built with anonymous FAST support"); 40 return KRB5KDC_ERR_BADOPTION; 41 } 42 43 #else /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ANONYMOUS */ 44 45 static krb5_error_code 46 cache_init_anonymous(struct pam_args *args, krb5_ccache *ccache) 47 { 48 krb5_context c = args->config->ctx->context; 49 krb5_error_code retval; 50 krb5_principal princ = NULL; 51 char *realm; 52 char *name = NULL; 53 krb5_creds creds; 54 bool creds_valid = false; 55 krb5_get_init_creds_opt *opts = NULL; 56 57 *ccache = NULL; 58 memset(&creds, 0, sizeof(creds)); 59 60 /* Construct the anonymous principal name. */ 61 retval = krb5_get_default_realm(c, &realm); 62 if (retval != 0) { 63 putil_debug_krb5(args, retval, "cannot find realm for anonymous FAST"); 64 return retval; 65 } 66 retval = krb5_build_principal_ext( 67 c, &princ, (unsigned int) strlen(realm), realm, 68 strlen(KRB5_WELLKNOWN_NAME), KRB5_WELLKNOWN_NAME, 69 strlen(KRB5_ANON_NAME), KRB5_ANON_NAME, NULL); 70 if (retval != 0) { 71 krb5_free_default_realm(c, realm); 72 putil_debug_krb5(args, retval, "cannot create anonymous principal"); 73 return retval; 74 } 75 krb5_free_default_realm(c, realm); 76 77 /* 78 * Set up the credential cache the anonymous credentials. We use a 79 * memory cache whose name is based on the pointer value of our Kerberos 80 * context, since that should be unique among threads. 81 */ 82 if (asprintf(&name, "MEMORY:%p", (void *) c) < 0) { 83 putil_crit(args, "malloc failure: %s", strerror(errno)); 84 retval = errno; 85 goto done; 86 } 87 retval = krb5_cc_resolve(c, name, ccache); 88 if (retval != 0) { 89 putil_err_krb5(args, retval, 90 "cannot create anonymous FAST credential cache %s", 91 name); 92 goto done; 93 } 94 95 /* Obtain the credentials. */ 96 retval = krb5_get_init_creds_opt_alloc(c, &opts); 97 if (retval != 0) { 98 putil_err_krb5(args, retval, "cannot create FAST credential options"); 99 goto done; 100 } 101 krb5_get_init_creds_opt_set_anonymous(opts, 1); 102 krb5_get_init_creds_opt_set_tkt_life(opts, 60); 103 # ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_OUT_CCACHE 104 krb5_get_init_creds_opt_set_out_ccache(c, opts, *ccache); 105 # endif 106 retval = krb5_get_init_creds_password(c, &creds, princ, NULL, NULL, NULL, 107 0, NULL, opts); 108 if (retval != 0) { 109 putil_debug_krb5(args, retval, 110 "cannot obtain anonymous credentials for FAST"); 111 goto done; 112 } 113 creds_valid = true; 114 115 /* 116 * If set_out_ccache was available, we're done. Otherwise, we have to 117 * manually set up the ticket cache. Use the principal from the acquired 118 * credentials when initializing the ticket cache, since the realm will 119 * not match the realm of our input principal. 120 */ 121 # ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_OUT_CCACHE 122 retval = krb5_cc_initialize(c, *ccache, creds.client); 123 if (retval != 0) { 124 putil_err_krb5(args, retval, "cannot initialize FAST ticket cache"); 125 goto done; 126 } 127 retval = krb5_cc_store_cred(c, *ccache, &creds); 128 if (retval != 0) { 129 putil_err_krb5(args, retval, "cannot store FAST credentials"); 130 goto done; 131 } 132 # endif /* !HAVE_KRB5_GET_INIT_CREDS_OPT_SET_OUT_CCACHE */ 133 134 done: 135 if (retval != 0 && *ccache != NULL) { 136 krb5_cc_destroy(c, *ccache); 137 *ccache = NULL; 138 } 139 if (princ != NULL) 140 krb5_free_principal(c, princ); 141 free(name); 142 if (opts != NULL) 143 krb5_get_init_creds_opt_free(c, opts); 144 if (creds_valid) 145 krb5_free_cred_contents(c, &creds); 146 return retval; 147 } 148 #endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ANONYMOUS */ 149 150 151 /* 152 * Attempt to use an existing ticket cache for FAST. Checks whether 153 * fast_ccache is set in the options and, if so, opens that cache and does 154 * some sanity checks, returning the cache name to use if everything checks 155 * out in newly allocated memory. Caller is responsible for freeing. If not, 156 * returns NULL. 157 */ 158 UNUSED static char * 159 fast_setup_cache(struct pam_args *args) 160 { 161 krb5_context c = args->config->ctx->context; 162 krb5_error_code retval; 163 krb5_principal princ; 164 krb5_ccache ccache; 165 char *result; 166 const char *cache = args->config->fast_ccache; 167 168 if (cache == NULL) 169 return NULL; 170 retval = krb5_cc_resolve(c, cache, &ccache); 171 if (retval != 0) { 172 putil_debug_krb5(args, retval, "cannot open FAST ccache %s", cache); 173 return NULL; 174 } 175 retval = krb5_cc_get_principal(c, ccache, &princ); 176 if (retval != 0) { 177 putil_debug_krb5(args, retval, 178 "failed to get principal from FAST" 179 " ccache %s", 180 cache); 181 krb5_cc_close(c, ccache); 182 return NULL; 183 } else { 184 krb5_free_principal(c, princ); 185 krb5_cc_close(c, ccache); 186 result = strdup(cache); 187 if (result == NULL) 188 putil_crit(args, "strdup failure: %s", strerror(errno)); 189 return result; 190 } 191 } 192 193 194 /* 195 * Attempt to use an anonymous ticket cache for FAST. Checks whether 196 * anon_fast is set in the options and, if so, opens that cache and does some 197 * sanity checks, returning the cache name to use if everything checks out in 198 * newly allocated memory. Caller is responsible for freeing. If not, 199 * returns NULL. 200 * 201 * If successful, store the anonymous FAST cache in the context where it will 202 * be freed following authentication. 203 */ 204 UNUSED static char * 205 fast_setup_anon(struct pam_args *args) 206 { 207 krb5_context c = args->config->ctx->context; 208 krb5_error_code retval; 209 krb5_ccache ccache; 210 char *cache, *result; 211 212 if (!args->config->anon_fast) 213 return NULL; 214 retval = cache_init_anonymous(args, &ccache); 215 if (retval != 0) { 216 putil_debug_krb5(args, retval, "skipping anonymous FAST"); 217 return NULL; 218 } 219 retval = krb5_cc_get_full_name(c, ccache, &cache); 220 if (retval != 0) { 221 putil_debug_krb5(args, retval, 222 "cannot get name of anonymous FAST" 223 " credential cache"); 224 krb5_cc_destroy(c, ccache); 225 return NULL; 226 } 227 result = strdup(cache); 228 if (result == NULL) { 229 putil_crit(args, "strdup failure: %s", strerror(errno)); 230 krb5_cc_destroy(c, ccache); 231 } 232 krb5_free_string(c, cache); 233 putil_debug(args, "anonymous authentication for FAST succeeded"); 234 if (args->config->ctx->fast_cache != NULL) 235 krb5_cc_destroy(c, args->config->ctx->fast_cache); 236 args->config->ctx->fast_cache = ccache; 237 return result; 238 } 239 240 241 /* 242 * Set initial credential options for FAST if support is available. 243 * 244 * If fast_ccache is set, we try to use that ticket cache first. Open it and 245 * read the principal from it first to ensure that the cache exists and 246 * contains credentials. If that fails, skip setting the FAST cache. 247 * 248 * If anon_fast is set and fast_ccache is not or is skipped for the reasons 249 * described above, try to obtain anonymous credentials and then use them as 250 * FAST armor. 251 * 252 * Note that this function cannot fail. If anything about FAST setup doesn't 253 * work, we continue without FAST. 254 */ 255 #ifndef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME 256 257 void 258 pamk5_fast_setup(struct pam_args *args UNUSED, 259 krb5_get_init_creds_opt *opts UNUSED) 260 { 261 } 262 263 #else /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME */ 264 265 void 266 pamk5_fast_setup(struct pam_args *args, krb5_get_init_creds_opt *opts) 267 { 268 krb5_context c = args->config->ctx->context; 269 krb5_error_code retval; 270 char *cache; 271 272 /* First try to use fast_ccache, and then fall back on anon_fast. */ 273 cache = fast_setup_cache(args); 274 if (cache == NULL) 275 cache = fast_setup_anon(args); 276 if (cache == NULL) 277 return; 278 279 /* We have a valid FAST ticket cache. Set the option. */ 280 retval = krb5_get_init_creds_opt_set_fast_ccache_name(c, opts, cache); 281 if (retval != 0) 282 putil_err_krb5(args, retval, "failed to set FAST ccache"); 283 else 284 putil_debug(args, "setting FAST credential cache to %s", cache); 285 free(cache); 286 } 287 288 #endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_FAST_CCACHE_NAME */ 289