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