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