xref: /freebsd/crypto/krb5/src/lib/krb5/krb/vfy_increds.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/vfy_increds.c - Verify initial credentials with keytab */
3 /*
4  * Copyright (C) 1998, 2011, 2012 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "k5-int.h"
34 #include "int-proto.h"
35 
36 /* Return true if configuration demands that a keytab be present.  (By default
37  * verification will be skipped if no keytab exists.) */
38 static krb5_boolean
nofail(krb5_context context,krb5_verify_init_creds_opt * options,krb5_creds * creds)39 nofail(krb5_context context, krb5_verify_init_creds_opt *options,
40        krb5_creds *creds)
41 {
42     int val;
43 
44     if (options &&
45         (options->flags & KRB5_VERIFY_INIT_CREDS_OPT_AP_REQ_NOFAIL))
46         return (options->ap_req_nofail != 0);
47     if (krb5int_libdefault_boolean(context, &creds->client->realm,
48                                    KRB5_CONF_VERIFY_AP_REQ_NOFAIL,
49                                    &val) == 0)
50         return (val != 0);
51     return FALSE;
52 }
53 
54 static krb5_error_code
copy_creds_except(krb5_context context,krb5_ccache incc,krb5_ccache outcc,krb5_principal princ)55 copy_creds_except(krb5_context context, krb5_ccache incc,
56                   krb5_ccache outcc, krb5_principal princ)
57 {
58     krb5_error_code ret, ret2;
59     krb5_cc_cursor cur = NULL;
60     krb5_creds creds;
61 
62     ret = krb5_cc_start_seq_get(context, incc, &cur);
63     if (ret)
64         return ret;
65 
66     while (!(ret = krb5_cc_next_cred(context, incc, &cur, &creds))) {
67         if (!krb5_principal_compare(context, princ, creds.server))
68             ret = krb5_cc_store_cred(context, outcc, &creds);
69         krb5_free_cred_contents(context, &creds);
70         if (ret)
71             break;
72     }
73 
74     ret2 = krb5_cc_end_seq_get(context, incc, &cur);
75     return (ret == KRB5_CC_END) ? ret2 : ret;
76 }
77 
78 static krb5_error_code
get_vfy_cred(krb5_context context,krb5_creds * creds,krb5_principal server,krb5_keytab keytab,krb5_ccache * ccache_arg)79 get_vfy_cred(krb5_context context, krb5_creds *creds, krb5_principal server,
80              krb5_keytab keytab, krb5_ccache *ccache_arg)
81 {
82     krb5_error_code ret;
83     krb5_ccache ccache = NULL, retcc = NULL;
84     krb5_creds in_creds, *out_creds = NULL;
85     krb5_auth_context authcon = NULL;
86     krb5_data ap_req = empty_data();
87 
88     /* If the creds are for the server principal, we're set, just do a mk_req.
89      * Otherwise, do a get_credentials first. */
90     if (krb5_principal_compare(context, server, creds->server)) {
91         /* Make an ap-req. */
92         ret = krb5_mk_req_extended(context, &authcon, 0, NULL, creds, &ap_req);
93         if (ret)
94             goto cleanup;
95     } else {
96         /*
97          * This is unclean, but it's the easiest way without ripping the
98          * library into very small pieces.  store the client's initial cred
99          * in a memory ccache, then call the library.  Later, we'll copy
100          * everything except the initial cred into the ccache we return to
101          * the user.  A clean implementation would involve library
102          * internals with a coherent idea of "in" and "out".
103          */
104 
105         /* Insert the initial cred into the ccache. */
106         ret = krb5_cc_new_unique(context, "MEMORY", NULL, &ccache);
107         if (ret)
108             goto cleanup;
109         ret = krb5_cc_initialize(context, ccache, creds->client);
110         if (ret)
111             goto cleanup;
112         ret = krb5_cc_store_cred(context, ccache, creds);
113         if (ret)
114             goto cleanup;
115 
116         /* Get credentials with get_creds. */
117         memset(&in_creds, 0, sizeof(in_creds));
118         in_creds.client = creds->client;
119         in_creds.server = server;
120         ret = krb5_timeofday(context, &in_creds.times.endtime);
121         if (ret)
122             goto cleanup;
123         in_creds.times.endtime = ts_incr(in_creds.times.endtime, 5 * 60);
124         ret = krb5_get_credentials(context, 0, ccache, &in_creds, &out_creds);
125         if (ret)
126             goto cleanup;
127 
128         /* Make an ap-req. */
129         ret = krb5_mk_req_extended(context, &authcon, 0, NULL, out_creds,
130                                    &ap_req);
131         if (ret)
132             goto cleanup;
133     }
134 
135     /* Wipe the auth context created by mk_req. */
136     if (authcon) {
137         krb5_auth_con_free(context, authcon);
138         authcon = NULL;
139     }
140 
141     /* Build an auth context that won't bother with replay checks -- it's
142      * not as if we're going to mount a replay attack on ourselves here. */
143     ret = krb5_auth_con_init(context, &authcon);
144     if (ret)
145         goto cleanup;
146     ret = krb5_auth_con_setflags(context, authcon, 0);
147     if (ret)
148         goto cleanup;
149 
150     /* Verify the ap_req. */
151     ret = krb5_rd_req(context, &authcon, &ap_req, server, keytab, NULL, NULL);
152     if (ret)
153         goto cleanup;
154 
155     /* If we get this far, then the verification succeeded.  We can
156      * still fail if the library stuff here fails, but that's it. */
157     if (ccache_arg != NULL && ccache != NULL) {
158         if (*ccache_arg == NULL) {
159             ret = krb5_cc_resolve(context, "MEMORY:rd_req2", &retcc);
160             if (ret)
161                 goto cleanup;
162             ret = krb5_cc_initialize(context, retcc, creds->client);
163             if (ret)
164                 goto cleanup;
165             ret = copy_creds_except(context, ccache, retcc, creds->server);
166             if (ret)
167                 goto cleanup;
168             *ccache_arg = retcc;
169             retcc = NULL;
170         } else {
171             ret = copy_creds_except(context, ccache, *ccache_arg, server);
172         }
173     }
174 
175 cleanup:
176     if (retcc != NULL)
177         krb5_cc_destroy(context, retcc);
178     if (ccache != NULL)
179         krb5_cc_destroy(context, ccache);
180     krb5_free_creds(context, out_creds);
181     krb5_auth_con_free(context, authcon);
182     krb5_free_data_contents(context, &ap_req);
183     return ret;
184 }
185 
186 /* Free the principals in plist and plist itself. */
187 static void
free_princ_list(krb5_context context,krb5_principal * plist)188 free_princ_list(krb5_context context, krb5_principal *plist)
189 {
190     size_t i;
191 
192     if (plist == NULL)
193         return;
194     for (i = 0; plist[i] != NULL; i++)
195         krb5_free_principal(context, plist[i]);
196     free(plist);
197 }
198 
199 /* Add princ to plist if it isn't already there. */
200 static krb5_error_code
add_princ_list(krb5_context context,krb5_const_principal princ,krb5_principal ** plist)201 add_princ_list(krb5_context context, krb5_const_principal princ,
202                krb5_principal **plist)
203 {
204     size_t i;
205     krb5_principal *newlist;
206 
207     /* Check if princ is already in plist, and count the elements. */
208     for (i = 0; (*plist) != NULL && (*plist)[i] != NULL; i++) {
209         if (krb5_principal_compare(context, princ, (*plist)[i]))
210             return 0;
211     }
212 
213     newlist = realloc(*plist, (i + 2) * sizeof(*newlist));
214     if (newlist == NULL)
215         return ENOMEM;
216     *plist = newlist;
217     newlist[i] = newlist[i + 1] = NULL; /* terminate the list */
218     return krb5_copy_principal(context, princ, &newlist[i]);
219 }
220 
221 /* Return a list of all unique host service princs in keytab. */
222 static krb5_error_code
get_host_princs_from_keytab(krb5_context context,krb5_keytab keytab,krb5_principal ** princ_list_out)223 get_host_princs_from_keytab(krb5_context context, krb5_keytab keytab,
224                             krb5_principal **princ_list_out)
225 {
226     krb5_error_code ret;
227     krb5_kt_cursor cursor;
228     krb5_keytab_entry kte;
229     krb5_principal *plist = NULL, p;
230 
231     *princ_list_out = NULL;
232 
233     ret = krb5_kt_start_seq_get(context, keytab, &cursor);
234     if (ret)
235         goto cleanup;
236 
237     while ((ret = krb5_kt_next_entry(context, keytab, &kte, &cursor)) == 0) {
238         p = kte.principal;
239         if (p->length == 2 && data_eq_string(p->data[0], "host"))
240             ret = add_princ_list(context, p, &plist);
241         krb5_kt_free_entry(context, &kte);
242         if (ret)
243             break;
244     }
245     (void)krb5_kt_end_seq_get(context, keytab, &cursor);
246     if (ret == KRB5_KT_END)
247         ret = 0;
248     if (ret)
249         goto cleanup;
250 
251     *princ_list_out = plist;
252     plist = NULL;
253 
254 cleanup:
255     free_princ_list(context, plist);
256     return ret;
257 }
258 
259 krb5_error_code KRB5_CALLCONV
krb5_verify_init_creds(krb5_context context,krb5_creds * creds,krb5_principal server,krb5_keytab keytab,krb5_ccache * ccache,krb5_verify_init_creds_opt * options)260 krb5_verify_init_creds(krb5_context context, krb5_creds *creds,
261                        krb5_principal server, krb5_keytab keytab,
262                        krb5_ccache *ccache,
263                        krb5_verify_init_creds_opt *options)
264 {
265     krb5_error_code ret;
266     krb5_principal *host_princs = NULL;
267     krb5_keytab defkeytab = NULL;
268     krb5_keytab_entry kte;
269     krb5_boolean have_keys = FALSE;
270     size_t i;
271 
272     if (keytab == NULL) {
273         ret = krb5_kt_default(context, &defkeytab);
274         if (ret)
275             goto cleanup;
276         keytab = defkeytab;
277     }
278 
279     if (server != NULL) {
280         /* Check if server exists in keytab first. */
281         ret = krb5_kt_get_entry(context, keytab, server, 0, 0, &kte);
282         if (ret)
283             goto cleanup;
284         krb5_kt_free_entry(context, &kte);
285         have_keys = TRUE;
286         ret = get_vfy_cred(context, creds, server, keytab, ccache);
287     } else {
288         /* Try using the host service principals from the keytab. */
289         if (keytab->ops->start_seq_get == NULL) {
290             ret = EINVAL;
291             goto cleanup;
292         }
293         ret = get_host_princs_from_keytab(context, keytab, &host_princs);
294         if (ret)
295             goto cleanup;
296         if (host_princs == NULL) {
297             ret = KRB5_KT_NOTFOUND;
298             goto cleanup;
299         }
300         have_keys = TRUE;
301 
302         /* Try all host principals until one succeeds or they all fail. */
303         for (i = 0; host_princs[i] != NULL; i++) {
304             ret = get_vfy_cred(context, creds, host_princs[i], keytab, ccache);
305             if (ret == 0)
306                 break;
307         }
308     }
309 
310 cleanup:
311     /* If we have no key to verify with, pretend to succeed unless
312      * configuration directs otherwise. */
313     if (!have_keys && !nofail(context, options, creds))
314         ret = 0;
315 
316     if (defkeytab != NULL)
317         krb5_kt_close(context, defkeytab);
318     free_princ_list(context, host_princs);
319 
320     return ret;
321 }
322