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
cache_init_anonymous(struct pam_args * args,krb5_ccache * ccache UNUSED)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
cache_init_anonymous(struct pam_args * args,krb5_ccache * ccache)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 *
fast_setup_cache(struct pam_args * args)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 *
fast_setup_anon(struct pam_args * args)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
pamk5_fast_setup(struct pam_args * args UNUSED,krb5_get_init_creds_opt * opts UNUSED)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
pamk5_fast_setup(struct pam_args * args,krb5_get_init_creds_opt * opts)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