xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/gc_frm_kdc.c (revision 4bc0a2ef2b7ba50a7a717e7ddbf31472ad28e358)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * Copyright (c) 1994,2003 by the Massachusetts Institute of Technology.
10  * Copyright (c) 1994 CyberSAFE Corporation
11  * Copyright (c) 1993 Open Computing Security Group
12  * Copyright (c) 1990,1991 by the Massachusetts Institute of Technology.
13  * All Rights Reserved.
14  *
15  * Export of this software from the United States of America may
16  *   require a specific license from the United States Government.
17  *   It is the responsibility of any person or organization contemplating
18  *   export to obtain such a license before exporting.
19  *
20  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
21  * distribute this software and its documentation for any purpose and
22  * without fee is hereby granted, provided that the above copyright
23  * notice appear in all copies and that both that copyright notice and
24  * this permission notice appear in supporting documentation, and that
25  * the name of M.I.T. not be used in advertising or publicity pertaining
26  * to distribution of the software without specific, written prior
27  * permission.  Furthermore if you modify this software you must label
28  * your software as modified software and not distribute it in such a
29  * fashion that it might be confused with the original M.I.T. software.
30  * Neither M.I.T., the Open Computing Security Group, nor
31  * CyberSAFE Corporation make any representations about the suitability of
32  * this software for any purpose.  It is provided "as is" without express
33  * or implied warranty.
34  *
35  * krb5_get_cred_from_kdc()
36  * Get credentials from some KDC somewhere, possibly accumulating tgts
37  * along the way.
38  */
39 
40 #include <k5-int.h>
41 #include <stdio.h>
42 #include "int-proto.h"
43 
44 /*
45  * Retrieve credentials for principal in_cred->client,
46  * server in_cred->server, ticket flags creds->ticket_flags, possibly
47  * second_ticket if needed by ticket_flags.
48  *
49  * Credentials are requested from the KDC for the server's realm.  Any
50  * TGT credentials obtained in the process of contacting the KDC are
51  * returned in an array of credentials; tgts is filled in to point to an
52  * array of pointers to credential structures (if no TGT's were used, the
53  * pointer is zeroed).  TGT's may be returned even if no useful end ticket
54  * was obtained.
55  *
56  * The returned credentials are NOT cached.
57  *
58  * This routine should not be called if the credentials are already in
59  * the cache.
60  *
61  * If credentials are obtained, creds is filled in with the results;
62  * creds->ticket and creds->keyblock->key are set to allocated storage,
63  * which should be freed by the caller when finished.
64  *
65  * returns errors, system errors.
66  */
67 
68 /* helper macro: convert flags to necessary KDC options */
69 
70 #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
71 
72 static krb5_error_code
73 krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts, int kdcopt)
74 {
75   krb5_creds      **ret_tgts = NULL;
76   int             ntgts = 0;
77 
78   krb5_creds      tgt, tgtq, *tgtr = NULL;
79   krb5_error_code retval;
80   krb5_principal  int_server = NULL;    /* Intermediate server for request */
81 
82   krb5_principal  *tgs_list = NULL;
83   krb5_principal  *top_server = NULL;
84   krb5_principal  *next_server = NULL;
85   unsigned int             nservers = 0;
86   krb5_boolean	  old_use_conf_ktypes = context->use_conf_ktypes;
87 
88   /* in case we never get a TGT, zero the return */
89 
90   *tgts = NULL;
91 
92   memset((char *)&tgtq, 0, sizeof(tgtq));
93   memset((char *)&tgt, 0, sizeof(tgt));
94 
95   /*
96    * we know that the desired credentials aren't in the cache yet.
97    *
98    * To get them, we first need a tgt for the realm of the server.
99    * first, we see if we have such a TGT in cache.  if not, then
100    * we ask the kdc to give us one.  if that doesn't work, then
101    * we try to get a tgt for a realm that is closest to the target.
102    * once we have that, then we ask that realm if it can give us
103    * tgt for the target.  if not, we do the process over with this
104    * new tgt.
105    */
106 
107   /*
108    * (the ticket may be issued by some other intermediate
109    *  realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY)
110    */
111   if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client)))
112       goto cleanup;
113 
114   /* get target tgt from cache */
115   if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server),
116 			     krb5_princ_realm(context, in_cred->client),
117 			     &int_server))) {
118       goto cleanup;
119   }
120 
121   if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) {
122       goto cleanup;
123   }
124 
125   /* set endtime to now so krb5_cc_retrieve_cred won't return an expired tik */
126   if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
127 	goto cleanup;
128   }
129 
130   context->use_conf_ktypes = 1;
131   if ((retval = krb5_cc_retrieve_cred(context, ccache,
132 				    KRB5_TC_MATCH_SRV_NAMEONLY |
133 				    KRB5_TC_SUPPORTED_KTYPES |
134 				    KRB5_TC_MATCH_TIMES,
135 				    &tgtq, &tgt)) != 0) {
136 
137     if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
138 	goto cleanup;
139     }
140 
141     /*
142      * Note that we want to request a TGT from our local KDC, even
143      * if we already have a TGT for some intermediate realm.  The
144      * reason is that our local KDC may have a shortcut to the
145      * destination realm, and if it does we want to use the
146      * shortcut because it will provide greater security. - bcn
147      */
148 
149     /*
150      * didn't find it in the cache so it is time to get a local
151      * tgt and walk the realms tree.
152      */
153     krb5_free_principal(context, int_server);
154     int_server = NULL;
155     if ((retval = krb5_tgtname(context,
156 			       krb5_princ_realm(context, in_cred->client),
157 			       krb5_princ_realm(context, in_cred->client),
158 			       &int_server))) {
159 	goto cleanup;
160     }
161 
162     krb5_free_cred_contents(context, &tgtq);
163     memset((char *)&tgtq, 0, sizeof(tgtq));
164     if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client)))
165 	goto cleanup;
166     if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
167 	goto cleanup;
168 
169     if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
170 	goto cleanup;
171     }
172 
173     if ((retval = krb5_cc_retrieve_cred(context, ccache,
174 					KRB5_TC_MATCH_SRV_NAMEONLY |
175 					KRB5_TC_SUPPORTED_KTYPES |
176 					KRB5_TC_MATCH_TIMES,
177 					&tgtq, &tgt)) != 0) {
178 	goto cleanup;
179     }
180 
181     /* get a list of realms to consult */
182 
183     if ((retval = krb5_walk_realm_tree(context,
184 				       krb5_princ_realm(context,in_cred->client),
185 				       krb5_princ_realm(context,in_cred->server),
186 				       &tgs_list,
187 				       KRB5_REALM_BRANCH_CHAR))) {
188 	goto cleanup;
189     }
190 
191     for (nservers = 0; tgs_list[nservers]; nservers++)
192       ;
193 
194     /* allocate storage for TGT pointers. */
195 
196     if (!(ret_tgts = (krb5_creds **) calloc(nservers+1, sizeof(krb5_creds)))) {
197       retval = ENOMEM;
198       goto cleanup;
199     }
200     *tgts = ret_tgts;
201 
202     /*
203      * step one is to take the current tgt and see if there is a tgt for
204      * krbtgt/realmof(target)@realmof(tgt).  if not, try to get one with
205      * the tgt.
206      *
207      * if we don't get a tgt for the target, then try to find a tgt as
208      * close to the target realm as possible. at each step if there isn't
209      * a tgt in the cache we have to try and get one with our latest tgt.
210      * once we have a tgt for a closer realm, we go back to step one.
211      *
212      * once we have a tgt for the target, we go try and get credentials.
213      */
214 
215     for (top_server = tgs_list;
216          top_server < tgs_list + nservers;
217          top_server = next_server) {
218 
219       /* look in cache for a tgt for the destination */
220 
221       krb5_free_cred_contents(context, &tgtq);
222       memset(&tgtq, 0, sizeof(tgtq));
223       if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client)))
224 	  goto cleanup;
225 
226       krb5_free_principal(context, int_server);
227       int_server = NULL;
228       if ((retval = krb5_tgtname(context,
229 				 krb5_princ_realm(context, in_cred->server),
230 				 krb5_princ_realm(context, *top_server),
231 				 &int_server))) {
232 	  goto cleanup;
233       }
234 
235       if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
236 	  goto cleanup;
237 
238       if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
239 	    goto cleanup;
240       }
241 
242       if ((retval = krb5_cc_retrieve_cred(context, ccache,
243 					KRB5_TC_MATCH_SRV_NAMEONLY |
244 					KRB5_TC_SUPPORTED_KTYPES |
245 					KRB5_TC_MATCH_TIMES,
246 					  &tgtq, &tgt)) != 0) {
247 
248 	if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) {
249 	    goto cleanup;
250 	}
251 
252 	/* didn't find it in the cache so try and get one */
253 	/* with current tgt.                              */
254 
255 	if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) {
256 	    retval = KRB5_PROG_ETYPE_NOSUPP;
257 	    goto cleanup;
258 	}
259 
260 	krb5_free_cred_contents(context, &tgtq);
261 	memset(&tgtq, 0, sizeof(tgtq));
262 	tgtq.times        = tgt.times;
263 
264 	if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client)))
265 	    goto cleanup;
266 	if ((retval = krb5_copy_principal(context, int_server, &tgtq.server)))
267 	    goto cleanup;
268 	tgtq.is_skey      = FALSE;
269 	tgtq.ticket_flags = tgt.ticket_flags;
270 	retval = krb5_get_cred_via_tkt(context, &tgt,
271 					    FLAGS2OPTS(tgtq.ticket_flags),
272 				            tgt.addresses, &tgtq, &tgtr);
273 	if (retval) {
274 
275        /*
276 	* couldn't get one so now loop backwards through the realms
277 	* list and try and get a tgt for a realm as close to the
278 	* target as possible. the kdc should give us a tgt for the
279 	* closest one it knows about, but not all kdc's do this yet.
280 	*/
281 
282 	  for (next_server = tgs_list + nservers - 1;
283 	       next_server > top_server;
284 	       next_server--) {
285 	    krb5_free_cred_contents(context, &tgtq);
286 	    memset(&tgtq, 0, sizeof(tgtq));
287 	    if ((retval = krb5_copy_principal(context, tgt.client,
288 					      &tgtq.client)))
289 		goto cleanup;
290 
291 	    krb5_free_principal(context, int_server);
292 	    int_server = NULL;
293 	    if ((retval = krb5_tgtname(context,
294 				       krb5_princ_realm(context, *next_server),
295 				       krb5_princ_realm(context, *top_server),
296 				       &int_server))) {
297 		goto cleanup;
298 	    }
299 
300 	    if ((retval = krb5_copy_principal(context, int_server,
301 					      &tgtq.server)))
302 		goto cleanup;
303 
304 	    if ((retval = krb5_timeofday(context,
305 					&(tgtq.times.endtime))) != 0) {
306 		goto cleanup;
307 	    }
308 
309 	    if ((retval = krb5_cc_retrieve_cred(context, ccache,
310 						KRB5_TC_MATCH_SRV_NAMEONLY |
311 						KRB5_TC_SUPPORTED_KTYPES |
312 						KRB5_TC_MATCH_TIMES,
313 						&tgtq, &tgt)) != 0) {
314 	      if (retval != KRB5_CC_NOTFOUND) {
315 		  goto cleanup;
316 	      }
317 
318 	      /* not in the cache so try and get one with our current tgt. */
319 
320 	      if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) {
321 		  retval = KRB5_PROG_ETYPE_NOSUPP;
322 		  goto cleanup;
323 	      }
324 
325 	      krb5_free_cred_contents(context, &tgtq);
326 	      memset(&tgtq, 0, sizeof(tgtq));
327 	      tgtq.times        = tgt.times;
328 	      if ((retval = krb5_copy_principal(context, tgt.client,
329 						&tgtq.client)))
330 		  goto cleanup;
331 	      if ((retval = krb5_copy_principal(context, int_server,
332 						&tgtq.server)))
333 		  goto cleanup;
334 	      tgtq.is_skey      = FALSE;
335 	      tgtq.ticket_flags = tgt.ticket_flags;
336 	      retval = krb5_get_cred_via_tkt(context, &tgt,
337 					     FLAGS2OPTS(tgtq.ticket_flags),
338 					     tgt.addresses,
339 					     &tgtq, &tgtr);
340 	      if (retval)
341 		  continue;
342 
343 	      /* save tgt in return array */
344 	      if ((retval = krb5_copy_creds(context, tgtr,
345 					    &ret_tgts[ntgts]))) {
346 		  goto cleanup;
347 	      }
348 	      krb5_free_creds(context, tgtr);
349 	      tgtr = NULL;
350 
351 	      tgt = *ret_tgts[ntgts++];
352 	    }
353 
354 	    /* got one as close as possible, now start all over */
355 
356 	    break;
357 	  }
358 
359 	  if (next_server == top_server) {
360 	      goto cleanup;
361 	  }
362 	  continue;
363         }
364 
365 	/*
366 	 * Got a tgt.  If it is for the target realm we can go try for the
367 	 * credentials.  If it is not for the target realm, then make sure it
368 	 * is in the realms hierarchy and if so, save it and start the loop
369 	 * over from there.  Note that we only need to compare the instance
370 	 * names since that is the target realm of the tgt.
371 	 */
372 
373 	for (next_server = top_server; *next_server; next_server++) {
374             krb5_data *realm_1 = krb5_princ_component(context, next_server[0], 1);
375             krb5_data *realm_2 = krb5_princ_component(context, tgtr->server, 1);
376             if (realm_1 != NULL &&
377 		realm_2 != NULL &&
378 		realm_1->length == realm_2->length &&
379                 !memcmp(realm_1->data, realm_2->data, realm_1->length)) {
380 		break;
381             }
382 	}
383 
384 	if (!next_server) {
385 	    retval = KRB5_KDCREP_MODIFIED;
386 	    goto cleanup;
387 	}
388 
389 	if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) {
390 	    goto cleanup;
391 	}
392 	krb5_free_creds(context, tgtr);
393 	tgtr = NULL;
394 
395         tgt = *ret_tgts[ntgts++];
396 
397         /* we're done if it is the target */
398 
399         if (!*next_server++) break;
400       }
401     }
402   }
403 
404   /* got/finally have tgt!  try for the creds */
405 
406   if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) {
407     retval = KRB5_PROG_ETYPE_NOSUPP;
408     goto cleanup;
409   }
410 
411   context->use_conf_ktypes = old_use_conf_ktypes;
412   retval = krb5_get_cred_via_tkt(context, &tgt,
413 				 FLAGS2OPTS(tgt.ticket_flags) |
414 				 kdcopt |
415   				 (in_cred->second_ticket.length ?
416 				  KDC_OPT_ENC_TKT_IN_SKEY : 0),
417 				 tgt.addresses, in_cred, out_cred);
418 
419   /* cleanup and return */
420 
421 cleanup:
422 
423   if (tgtr) krb5_free_creds(context, tgtr);
424   if(tgs_list)  krb5_free_realm_tree(context, tgs_list);
425   krb5_free_cred_contents(context, &tgtq);
426   if (int_server) krb5_free_principal(context, int_server);
427   if (ntgts == 0) {
428       *tgts = NULL;
429       if (ret_tgts)  free(ret_tgts);
430       krb5_free_cred_contents(context, &tgt);
431   }
432   context->use_conf_ktypes = old_use_conf_ktypes;
433   return(retval);
434 }
435 
436 krb5_error_code
437 krb5_get_cred_from_kdc(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts)
438 {
439 
440   return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
441 				    0);
442 }
443 
444 krb5_error_code
445 krb5_get_cred_from_kdc_validate(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts)
446 {
447 
448   return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
449 				    KDC_OPT_VALIDATE);
450 }
451 
452 krb5_error_code
453 krb5_get_cred_from_kdc_renew(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts)
454 {
455 
456   return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
457 				    KDC_OPT_RENEW);
458 }
459