1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/gic_keytab.c */
3 /*
4 * Copyright (C) 2002, 2003, 2008 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26 #ifndef LEAN_CLIENT
27
28 #include "k5-int.h"
29 #include "int-proto.h"
30 #include "os-proto.h"
31 #include "init_creds_ctx.h"
32
33 static krb5_error_code
get_as_key_keytab(krb5_context context,krb5_principal client,krb5_enctype etype,krb5_prompter_fct prompter,void * prompter_data,krb5_data * salt,krb5_data * params,krb5_keyblock * as_key,void * gak_data,k5_response_items * ritems)34 get_as_key_keytab(krb5_context context,
35 krb5_principal client,
36 krb5_enctype etype,
37 krb5_prompter_fct prompter,
38 void *prompter_data,
39 krb5_data *salt,
40 krb5_data *params,
41 krb5_keyblock *as_key,
42 void *gak_data,
43 k5_response_items *ritems)
44 {
45 krb5_keytab keytab = (krb5_keytab) gak_data;
46 krb5_error_code ret;
47 krb5_keytab_entry kt_ent;
48
49 /* We don't need the password from the responder to create the AS key. */
50 if (as_key == NULL)
51 return 0;
52
53 /* if there's already a key of the correct etype, we're done.
54 if the etype is wrong, free the existing key, and make
55 a new one. */
56
57 if (as_key->length) {
58 if (as_key->enctype == etype)
59 return(0);
60
61 krb5_free_keyblock_contents(context, as_key);
62 as_key->length = 0;
63 }
64
65 if (!krb5_c_valid_enctype(etype))
66 return(KRB5_PROG_ETYPE_NOSUPP);
67
68 if ((ret = krb5_kt_get_entry(context, keytab, client,
69 0, /* don't have vno available */
70 etype, &kt_ent)))
71 return(ret);
72
73 /* Steal the keyblock from kt_ent for the caller. */
74 *as_key = kt_ent.key;
75 memset(&kt_ent.key, 0, sizeof(kt_ent.key));
76
77 (void) krb5_kt_free_entry(context, &kt_ent);
78
79 return 0;
80 }
81
82 /* Return the list of etypes available for client in keytab. */
83 static krb5_error_code
lookup_etypes_for_keytab(krb5_context context,krb5_keytab keytab,krb5_const_principal client,krb5_enctype ** etypes_out)84 lookup_etypes_for_keytab(krb5_context context, krb5_keytab keytab,
85 krb5_const_principal client,
86 krb5_enctype **etypes_out)
87 {
88 krb5_kt_cursor cursor;
89 krb5_keytab_entry entry;
90 krb5_enctype *p, *etypes = NULL, etype;
91 krb5_kvno max_kvno = 0, vno;
92 krb5_error_code ret;
93 krb5_boolean match;
94 size_t count = 0;
95
96 *etypes_out = NULL;
97
98 if (keytab->ops->start_seq_get == NULL)
99 return EINVAL;
100 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
101 if (ret != 0)
102 return ret;
103
104 while (!(ret = krb5_kt_next_entry(context, keytab, &entry, &cursor))) {
105 /* Extract what we need from the entry and free it. */
106 etype = entry.key.enctype;
107 vno = entry.vno;
108 match = krb5_principal_compare(context, entry.principal, client);
109 krb5_free_keytab_entry_contents(context, &entry);
110
111 /* Filter out old or non-matching entries and invalid enctypes. */
112 if (vno < max_kvno || !match || !krb5_c_valid_enctype(etype))
113 continue;
114
115 /* Update max_kvno and reset the list if we find a newer kvno. */
116 if (vno > max_kvno) {
117 max_kvno = vno;
118 free(etypes);
119 etypes = NULL;
120 count = 0;
121 }
122
123 /* Leave room for the terminator and possibly a second entry. */
124 p = realloc(etypes, (count + 3) * sizeof(*etypes));
125 if (p == NULL) {
126 ret = ENOMEM;
127 goto cleanup;
128 }
129 etypes = p;
130 etypes[count++] = etype;
131 etypes[count] = 0;
132 }
133 if (ret != KRB5_KT_END)
134 goto cleanup;
135 ret = 0;
136
137 *etypes_out = etypes;
138 etypes = NULL;
139
140 cleanup:
141 krb5_kt_end_seq_get(context, keytab, &cursor);
142 free(etypes);
143 return ret;
144 }
145
146 /* Move the entries in keytab_list (zero-terminated) to the front of req_list
147 * (of length req_len), preserving order otherwise. */
148 static krb5_error_code
sort_enctypes(krb5_enctype * req_list,int req_len,krb5_enctype * keytab_list)149 sort_enctypes(krb5_enctype *req_list, int req_len, krb5_enctype *keytab_list)
150 {
151 krb5_enctype *save_list;
152 int save_pos, req_pos, i;
153
154 save_list = malloc(req_len * sizeof(*save_list));
155 if (save_list == NULL)
156 return ENOMEM;
157
158 /* Sort req_list entries into the front of req_list or into save_list. */
159 req_pos = save_pos = 0;
160 for (i = 0; i < req_len; i++) {
161 if (k5_etypes_contains(keytab_list, req_list[i]))
162 req_list[req_pos++] = req_list[i];
163 else
164 save_list[save_pos++] = req_list[i];
165 }
166
167 /* Put the entries we saved back in at the end, in order. */
168 for (i = 0; i < save_pos; i++)
169 req_list[req_pos++] = save_list[i];
170 assert(req_pos == req_len);
171
172 free(save_list);
173 return 0;
174 }
175
176 krb5_error_code KRB5_CALLCONV
krb5_init_creds_set_keytab(krb5_context context,krb5_init_creds_context ctx,krb5_keytab keytab)177 krb5_init_creds_set_keytab(krb5_context context,
178 krb5_init_creds_context ctx,
179 krb5_keytab keytab)
180 {
181 krb5_enctype *etype_list = NULL;
182 krb5_error_code ret;
183 struct canonprinc iter = { ctx->request->client, .subst_defrealm = TRUE };
184 krb5_const_principal canonprinc;
185 krb5_principal copy;
186 char *name;
187
188 ctx->gak_fct = get_as_key_keytab;
189 ctx->gak_data = keytab;
190
191 /* We may be authenticating as a host-based principal. If so, look for
192 * each canonicalization candidate in the keytab. */
193 while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
194 canonprinc != NULL) {
195 ret = lookup_etypes_for_keytab(context, keytab, canonprinc,
196 &etype_list);
197 if (ret || etype_list != NULL)
198 break;
199 }
200 if (!ret && canonprinc != NULL) {
201 /* Authenticate as the principal we found in the keytab. */
202 ret = krb5_copy_principal(context, canonprinc, ©);
203 if (!ret) {
204 krb5_free_principal(context, ctx->request->client);
205 ctx->request->client = copy;
206 }
207 }
208 free_canonprinc(&iter);
209 if (ret) {
210 TRACE_INIT_CREDS_KEYTAB_LOOKUP_FAILED(context, ret);
211 free(etype_list);
212 return 0;
213 }
214 TRACE_INIT_CREDS_KEYTAB_LOOKUP(context, ctx->request->client, etype_list);
215
216 /* Error out if we have no keys for the client principal. */
217 if (etype_list == NULL) {
218 ret = krb5_unparse_name(context, ctx->request->client, &name);
219 if (ret == 0) {
220 k5_setmsg(context, KRB5_KT_NOTFOUND,
221 _("Keytab contains no suitable keys for %s"), name);
222 }
223 krb5_free_unparsed_name(context, name);
224 return KRB5_KT_NOTFOUND;
225 }
226
227 /* Sort the request enctypes so the ones in the keytab appear first. */
228 ret = sort_enctypes(ctx->request->ktype, ctx->request->nktypes,
229 etype_list);
230 free(etype_list);
231 return ret;
232 }
233
234 static krb5_error_code
get_init_creds_keytab(krb5_context context,krb5_creds * creds,krb5_principal client,krb5_keytab keytab,krb5_deltat start_time,const char * in_tkt_service,krb5_get_init_creds_opt * options,krb5_boolean use_primary,struct kdclist * kdcs)235 get_init_creds_keytab(krb5_context context, krb5_creds *creds,
236 krb5_principal client, krb5_keytab keytab,
237 krb5_deltat start_time, const char *in_tkt_service,
238 krb5_get_init_creds_opt *options,
239 krb5_boolean use_primary, struct kdclist *kdcs)
240 {
241 krb5_error_code ret;
242 krb5_init_creds_context ctx = NULL;
243
244 ret = krb5_init_creds_init(context, client, NULL, NULL, start_time,
245 options, &ctx);
246 if (ret != 0)
247 goto cleanup;
248
249 if (in_tkt_service) {
250 ret = krb5_init_creds_set_service(context, ctx, in_tkt_service);
251 if (ret != 0)
252 goto cleanup;
253 }
254
255 ret = krb5_init_creds_set_keytab(context, ctx, keytab);
256 if (ret != 0)
257 goto cleanup;
258
259 ret = k5_init_creds_get(context, ctx, use_primary, kdcs);
260 if (ret != 0)
261 goto cleanup;
262
263 ret = krb5_init_creds_get_creds(context, ctx, creds);
264 if (ret != 0)
265 goto cleanup;
266
267 cleanup:
268 krb5_init_creds_free(context, ctx);
269
270 return ret;
271 }
272
273 krb5_error_code KRB5_CALLCONV
krb5_get_init_creds_keytab(krb5_context context,krb5_creds * creds,krb5_principal client,krb5_keytab arg_keytab,krb5_deltat start_time,const char * in_tkt_service,krb5_get_init_creds_opt * options)274 krb5_get_init_creds_keytab(krb5_context context,
275 krb5_creds *creds,
276 krb5_principal client,
277 krb5_keytab arg_keytab,
278 krb5_deltat start_time,
279 const char *in_tkt_service,
280 krb5_get_init_creds_opt *options)
281 {
282 krb5_error_code ret;
283 krb5_keytab keytab;
284 struct errinfo errsave = EMPTY_ERRINFO;
285 struct kdclist *kdcs = NULL;
286
287 if (arg_keytab == NULL) {
288 if ((ret = krb5_kt_default(context, &keytab)))
289 return ret;
290 } else {
291 keytab = arg_keytab;
292 }
293
294 ret = k5_kdclist_create(&kdcs);
295 if (ret)
296 goto cleanup;
297
298 /* first try: get the requested tkt from any kdc */
299
300 ret = get_init_creds_keytab(context, creds, client, keytab, start_time,
301 in_tkt_service, options, FALSE, kdcs);
302
303 /* check for success */
304
305 if (ret == 0)
306 goto cleanup;
307
308 /* If all the kdc's are unavailable fail */
309
310 if ((ret == KRB5_KDC_UNREACH) || (ret == KRB5_REALM_CANT_RESOLVE))
311 goto cleanup;
312
313 /* If the reply did not come from the primary kdc, try again with
314 * the primary kdc. */
315
316 if (!k5_kdclist_any_replicas(context, kdcs)) {
317 k5_save_ctx_error(context, ret, &errsave);
318 ret = get_init_creds_keytab(context, creds, client, keytab,
319 start_time, in_tkt_service, options,
320 TRUE, NULL);
321 if (ret == 0)
322 goto cleanup;
323
324 /* If the primary is unreachable, return the error from the replica we
325 * were able to contact. */
326 if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
327 ret == KRB5_REALM_UNKNOWN)
328 ret = k5_restore_ctx_error(context, &errsave);
329 }
330
331 /* at this point, we have a response from the primary. Since we don't
332 do any prompting or changing for keytabs, that's it. */
333
334 cleanup:
335 if (arg_keytab == NULL)
336 krb5_kt_close(context, keytab);
337 k5_kdclist_free(kdcs);
338 k5_clear_error(&errsave);
339
340 return(ret);
341 }
342 krb5_error_code KRB5_CALLCONV
krb5_get_in_tkt_with_keytab(krb5_context context,krb5_flags options,krb5_address * const * addrs,krb5_enctype * ktypes,krb5_preauthtype * pre_auth_types,krb5_keytab arg_keytab,krb5_ccache ccache,krb5_creds * creds,krb5_kdc_rep ** ret_as_reply)343 krb5_get_in_tkt_with_keytab(krb5_context context, krb5_flags options,
344 krb5_address *const *addrs, krb5_enctype *ktypes,
345 krb5_preauthtype *pre_auth_types,
346 krb5_keytab arg_keytab, krb5_ccache ccache,
347 krb5_creds *creds, krb5_kdc_rep **ret_as_reply)
348 {
349 krb5_error_code retval;
350 krb5_get_init_creds_opt *opts;
351 char * server = NULL;
352 krb5_keytab keytab;
353 krb5_principal client_princ, server_princ;
354
355 retval = k5_populate_gic_opt(context, &opts, options, addrs, ktypes,
356 pre_auth_types, creds);
357 if (retval)
358 return retval;
359
360 if (arg_keytab == NULL) {
361 retval = krb5_kt_default(context, &keytab);
362 if (retval)
363 goto cleanup;
364 }
365 else keytab = arg_keytab;
366
367 retval = krb5_unparse_name( context, creds->server, &server);
368 if (retval)
369 goto cleanup;
370 server_princ = creds->server;
371 client_princ = creds->client;
372 retval = k5_get_init_creds(context, creds, creds->client,
373 krb5_prompter_posix, NULL, 0, server, opts,
374 get_as_key_keytab, (void *)keytab,
375 ret_as_reply);
376 krb5_free_unparsed_name( context, server);
377 if (retval) {
378 goto cleanup;
379 }
380 krb5_free_principal(context, creds->server);
381 krb5_free_principal(context, creds->client);
382 creds->client = client_princ;
383 creds->server = server_princ;
384
385 /* store it in the ccache! */
386 if (ccache)
387 if ((retval = krb5_cc_store_cred(context, ccache, creds)))
388 goto cleanup;
389 cleanup:
390 krb5_get_init_creds_opt_free(context, opts);
391 if (arg_keytab == NULL)
392 krb5_kt_close(context, keytab);
393 return retval;
394 }
395
396 #endif /* LEAN_CLIENT */
397