xref: /freebsd/crypto/krb5/src/lib/krb5/krb/gic_keytab.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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, &copy);
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