xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/mech/acquire_cred.c (revision 24da5b34f49324ed742a340010ed5bd3d4e06625)
1 /*
2  * Copyright 2007 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 2000 by the Massachusetts Institute of Technology.
10  * All Rights Reserved.
11  *
12  * Export of this software from the United States of America may
13  *   require a specific license from the United States Government.
14  *   It is the responsibility of any person or organization contemplating
15  *   export to obtain such a license before exporting.
16  *
17  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
18  * distribute this software and its documentation for any purpose and
19  * without fee is hereby granted, provided that the above copyright
20  * notice appear in all copies and that both that copyright notice and
21  * this permission notice appear in supporting documentation, and that
22  * the name of M.I.T. not be used in advertising or publicity pertaining
23  * to distribution of the software without specific, written prior
24  * permission.  Furthermore if you modify this software you must label
25  * your software as modified software and not distribute it in such a
26  * fashion that it might be confused with the original M.I.T. software.
27  * M.I.T. makes no representations about the suitability of
28  * this software for any purpose.  It is provided "as is" without express
29  * or implied warranty.
30  *
31  */
32 
33 /*
34  * Copyright 1993 by OpenVision Technologies, Inc.
35  *
36  * Permission to use, copy, modify, distribute, and sell this software
37  * and its documentation for any purpose is hereby granted without fee,
38  * provided that the above copyright notice appears in all copies and
39  * that both that copyright notice and this permission notice appear in
40  * supporting documentation, and that the name of OpenVision not be used
41  * in advertising or publicity pertaining to distribution of the software
42  * without specific, written prior permission. OpenVision makes no
43  * representations about the suitability of this software for any
44  * purpose.  It is provided "as is" without express or implied warranty.
45  *
46  * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
47  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
48  * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
49  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
50  * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
51  * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
52  * PERFORMANCE OF THIS SOFTWARE.
53  */
54 
55 /*
56  * Copyright (C) 1998 by the FundsXpress, INC.
57  *
58  * All rights reserved.
59  *
60  * Export of this software from the United States of America may require
61  * a specific license from the United States Government.  It is the
62  * responsibility of any person or organization contemplating export to
63  * obtain such a license before exporting.
64  *
65  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
66  * distribute this software and its documentation for any purpose and
67  * without fee is hereby granted, provided that the above copyright
68  * notice appear in all copies and that both that copyright notice and
69  * this permission notice appear in supporting documentation, and that
70  * the name of FundsXpress. not be used in advertising or publicity pertaining
71  * to distribution of the software without specific, written prior
72  * permission.  FundsXpress makes no representations about the suitability of
73  * this software for any purpose.  It is provided "as is" without express
74  * or implied warranty.
75  *
76  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
77  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
78  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
79  */
80 
81 #include <gssapiP_krb5.h>
82 #include <k5-int.h>
83 
84 #ifdef HAVE_STRING_H
85 #include <string.h>
86 #else
87 #include <strings.h>
88 #endif
89 
90 /*
91  * $Id: acquire_cred.c,v 1.25.6.2 2000/05/22 20:41:32 meeroh Exp $
92  */
93 
94 /* get credentials corresponding to a key in the krb5 keytab.
95    If the default name is requested, return the name in output_princ.
96      If output_princ is non-NULL, the caller will use or free it, regardless
97      of the return value.
98    If successful, set the keytab-specific fields in cred
99    */
100 
101 static OM_uint32
102 acquire_accept_cred(context, minor_status, desired_name, output_princ, cred)
103      krb5_context context;
104      OM_uint32 *minor_status;
105      gss_name_t desired_name;
106      krb5_principal *output_princ;
107      krb5_gss_cred_id_rec *cred;
108 {
109    krb5_error_code code;
110    krb5_principal princ;
111    krb5_keytab kt;
112    krb5_keytab_entry entry;
113 
114    *output_princ = NULL;
115    cred->keytab = NULL;
116 
117    /* open the default keytab */
118 
119    if ((code = krb5_kt_default(context, &kt))) {
120       *minor_status = code;
121       /* NOTE: GSS_S_CRED_UNAVAIL is not RFC 2743 compliant */
122       return(GSS_S_NO_CRED);
123    }
124 
125    if (desired_name != GSS_C_NO_NAME) {
126       princ = (krb5_principal) desired_name;
127       if ((code = krb5_kt_get_entry(context, kt, princ, 0, 0, &entry))) {
128 	 (void) krb5_kt_close(context, kt);
129 	 if (code == KRB5_KT_NOTFOUND)
130 	    *minor_status = KG_KEYTAB_NOMATCH;
131 	 else
132 	    *minor_status = code;
133       /* NOTE: GSS_S_CRED_UNAVAIL is not RFC 2743 compliant */
134 	 return(GSS_S_NO_CRED);
135       }
136       krb5_kt_free_entry(context, &entry);
137 
138       /* Open the replay cache for this principal. */
139       if ((code = krb5_get_server_rcache(context,
140 					 krb5_princ_component(context, princ, 0),
141 					 &cred->rcache))) {
142 	 *minor_status = code;
143 	 return(GSS_S_FAILURE);
144       }
145 
146    }
147 
148    /* hooray.  we made it */
149 
150    cred->keytab = kt;
151 
152    return(GSS_S_COMPLETE);
153 }
154 
155 /* get credentials corresponding to the default credential cache.
156    If the default name is requested, return the name in output_princ.
157      If output_princ is non-NULL, the caller will use or free it, regardless
158      of the return value.
159    If successful, set the ccache-specific fields in cred.
160    */
161 
162 static OM_uint32
163 acquire_init_cred(context, minor_status, desired_name, output_princ, cred)
164      krb5_context context;
165      OM_uint32 *minor_status;
166      gss_name_t desired_name;
167      krb5_principal *output_princ;
168      krb5_gss_cred_id_rec *cred;
169 {
170    krb5_error_code code;
171    krb5_ccache ccache;
172    krb5_principal princ, tmp_princ;
173    krb5_flags flags;
174    krb5_cc_cursor cur;
175    krb5_creds creds;
176    int got_endtime;
177 
178    cred->ccache = NULL;
179 
180    /* SUNW14resync - do we need this? */
181 #if 0
182    /* load the GSS ccache name into the kg_context */
183    if (GSS_ERROR(kg_sync_ccache_name(context, minor_status)))
184        return(GSS_S_FAILURE);
185 #endif
186 
187    /* open the default credential cache */
188 
189    code = krb5int_cc_default(context, &ccache);
190    if (code) {
191       *minor_status = code;
192       return(GSS_S_NO_CRED);
193    }
194 
195    /* turn off OPENCLOSE mode while extensive frobbing is going on */
196    /*
197     * SUNW14resync
198     * Added calls to krb5_cc_set_flags(... KRB5_TC_OPENCLOSE)
199     * on the error returns cuz the 1.4 krb5_cc_close does not always close
200     * the file like it used to and caused STC test gss.27 to fail.
201     */
202    flags = 0;		/* turns off OPENCLOSE mode */
203    if ((code = krb5_cc_set_flags(context, ccache, flags)) != 0) {
204       (void)krb5_cc_close(context, ccache);
205       *minor_status = code;
206       return(GSS_S_NO_CRED);
207    }
208 
209    /* get out the principal name and see if it matches */
210 
211    if ((code = krb5_cc_get_principal(context, ccache, &princ)) != 0) {
212       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
213       (void)krb5_cc_close(context, ccache);
214       *minor_status = code;
215       return(GSS_S_FAILURE);
216    }
217 
218    if (desired_name != (gss_name_t) NULL) {
219       if (! krb5_principal_compare(context, princ, (krb5_principal) desired_name)) {
220 	 (void)krb5_free_principal(context, princ);
221          (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
222 	 (void)krb5_cc_close(context, ccache);
223 	 *minor_status = KG_CCACHE_NOMATCH;
224 	 return(GSS_S_NO_CRED);
225       }
226       (void)krb5_free_principal(context, princ);
227       princ = (krb5_principal) desired_name;
228    } else {
229       *output_princ = princ;
230    }
231 
232    /* iterate over the ccache, find the tgt */
233 
234    if ((code = krb5_cc_start_seq_get(context, ccache, &cur)) != 0) {
235       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
236       (void)krb5_cc_close(context, ccache);
237       *minor_status = code;
238       return(GSS_S_FAILURE);
239    }
240 
241    /* this is hairy.  If there's a tgt for the principal's local realm
242       in here, that's what we want for the expire time.  But if
243       there's not, then we want to use the first key.  */
244 
245    got_endtime = 0;
246 
247    code = krb5_build_principal_ext(context, &tmp_princ,
248 				   krb5_princ_realm(context, princ)->length,
249 				   krb5_princ_realm(context, princ)->data,
250 				   6, "krbtgt",
251 				   krb5_princ_realm(context, princ)->length,
252 				   krb5_princ_realm(context, princ)->data,
253 				   0);
254    if (code) {
255       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
256       (void)krb5_cc_close(context, ccache);
257       *minor_status = code;
258       return(GSS_S_FAILURE);
259    }
260    while ((code = krb5_cc_next_cred(context, ccache, &cur, &creds)) == 0) {
261       if (krb5_principal_compare(context, tmp_princ, creds.server)) {
262 	 cred->tgt_expire = creds.times.endtime;
263 	 got_endtime = 1;
264 	 *minor_status = 0;
265 	 code = 0;
266 	 krb5_free_cred_contents(context, &creds);
267 	 break;
268       }
269       if (got_endtime == 0) {
270 	 cred->tgt_expire = creds.times.endtime;
271 	 got_endtime = 1;
272       }
273       krb5_free_cred_contents(context, &creds);
274    }
275    krb5_free_principal(context, tmp_princ);
276 
277    if (code && code != KRB5_CC_END) {
278       /* this means some error occurred reading the ccache */
279       (void)krb5_cc_end_seq_get(context, ccache, &cur);
280       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
281       (void)krb5_cc_close(context, ccache);
282       *minor_status = code;
283       return(GSS_S_FAILURE);
284    } else if (! got_endtime) {
285       /* this means the ccache was entirely empty */
286       (void)krb5_cc_end_seq_get(context, ccache, &cur);
287       (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
288       (void)krb5_cc_close(context, ccache);
289       *minor_status = KG_EMPTY_CCACHE;
290       return(GSS_S_FAILURE);
291    } else {
292       /* this means that we found an endtime to use. */
293       if ((code = krb5_cc_end_seq_get(context, ccache, &cur)) != 0) {
294 	 (void)krb5_cc_set_flags(context, ccache, KRB5_TC_OPENCLOSE);
295 	 (void)krb5_cc_close(context, ccache);
296 	 *minor_status = code;
297 	 return(GSS_S_FAILURE);
298       }
299       flags = KRB5_TC_OPENCLOSE;	/* turns on OPENCLOSE mode */
300       if ((code = krb5_cc_set_flags(context, ccache, flags)) != 0) {
301 	 (void)krb5_cc_close(context, ccache);
302 	 *minor_status = code;
303 	 return(GSS_S_FAILURE);
304       }
305    }
306 
307    /* the credentials match and are valid */
308 
309    cred->ccache = ccache;
310    /* minor_status is set while we are iterating over the ccache */
311    return(GSS_S_COMPLETE);
312 }
313 
314 OM_uint32
315 krb5_gss_acquire_cred(ctx, minor_status, desired_name, time_req,
316 		      desired_mechs, cred_usage, output_cred_handle,
317 		      actual_mechs, time_rec)
318      void *ctx;
319      OM_uint32 *minor_status;
320      gss_name_t desired_name;
321      OM_uint32 time_req;
322      gss_OID_set desired_mechs;
323      gss_cred_usage_t cred_usage;
324      gss_cred_id_t *output_cred_handle;
325      gss_OID_set *actual_mechs;
326      OM_uint32 *time_rec;
327 {
328     OM_uint32 ret;
329 
330     mutex_lock(&krb5_mutex);
331     ret = krb5_gss_acquire_cred_no_lock(ctx, minor_status, desired_name,
332 	    time_req, desired_mechs, cred_usage, output_cred_handle,
333 	    actual_mechs, time_rec);
334     mutex_unlock(&krb5_mutex);
335     return(ret);
336 }
337 
338 /*ARGSUSED*/
339 OM_uint32
340 krb5_gss_acquire_cred_no_lock(ctx, minor_status, desired_name, time_req,
341 		      desired_mechs, cred_usage, output_cred_handle,
342 		      actual_mechs, time_rec)
343      void *ctx;
344      OM_uint32 *minor_status;
345      gss_name_t desired_name;
346      OM_uint32 time_req;
347      gss_OID_set desired_mechs;
348      gss_cred_usage_t cred_usage;
349      gss_cred_id_t *output_cred_handle;
350      gss_OID_set *actual_mechs;
351      OM_uint32 *time_rec;
352 {
353    krb5_context context;
354    size_t i;
355    krb5_gss_cred_id_t cred;
356    gss_OID_set ret_mechs = GSS_C_NULL_OID_SET;
357    const gss_OID_set_desc * valid_mechs;
358    int req_old, req_new;
359    OM_uint32 ret;
360    krb5_error_code code;
361 
362    /* Solaris Kerberos:  for MT safety, we avoid the use of a default
363     * context via kg_get_context() */
364 #if 0
365    if (GSS_ERROR(kg_get_context(minor_status, &context)))
366       return(GSS_S_FAILURE);
367 #endif
368 
369    context = ctx;
370 
371    /* make sure all outputs are valid */
372 
373    *output_cred_handle = NULL;
374    if (actual_mechs)
375       *actual_mechs = NULL;
376    if (time_rec)
377       *time_rec = 0;
378 
379    /* validate the name */
380 
381    /*SUPPRESS 29*/
382    if ((desired_name != (gss_name_t) NULL) &&
383        (! kg_validate_name(desired_name))) {
384       *minor_status = (OM_uint32) G_VALIDATE_FAILED;
385       return(GSS_S_CALL_BAD_STRUCTURE|GSS_S_BAD_NAME);
386    }
387 
388    /* verify that the requested mechanism set is the default, or
389       contains krb5 */
390 
391    if (desired_mechs == GSS_C_NULL_OID_SET) {
392       valid_mechs = gss_mech_set_krb5_both;
393       req_old = 1;
394       req_new = 1;
395    } else {
396       req_old = 0;
397       req_new = 0;
398 
399       for (i=0; i<desired_mechs->count; i++) {
400 	 if (g_OID_equal(gss_mech_krb5_old, &(desired_mechs->elements[i])))
401 	    req_old++;
402 	 if (g_OID_equal(gss_mech_krb5, &(desired_mechs->elements[i])))
403 	    req_new++;
404       }
405 
406       if (!req_old && !req_new) {
407 	 *minor_status = 0;
408 	 return(GSS_S_BAD_MECH);
409       }
410    }
411 
412    /* create the gss cred structure */
413 
414    if ((cred =
415 	(krb5_gss_cred_id_t) xmalloc(sizeof(krb5_gss_cred_id_rec))) == NULL) {
416       *minor_status = ENOMEM;
417       return(GSS_S_FAILURE);
418    }
419    memset(cred, 0, sizeof(krb5_gss_cred_id_rec));
420 
421    cred->usage = cred_usage;
422    cred->princ = NULL;
423    cred->actual_mechs = valid_mechs;
424    cred->prerfc_mech = req_old;
425    cred->rfc_mech = req_new;
426 
427    cred->keytab = NULL;
428    cred->ccache = NULL;
429 
430    if ((cred_usage != GSS_C_INITIATE) &&
431        (cred_usage != GSS_C_ACCEPT) &&
432        (cred_usage != GSS_C_BOTH)) {
433       xfree(cred);
434       *minor_status = (OM_uint32) G_BAD_USAGE;
435       return(GSS_S_FAILURE);
436    }
437 
438    /* if requested, acquire credentials for accepting */
439    /* this will fill in cred->princ if the desired_name is not specified */
440 
441    if ((cred_usage == GSS_C_ACCEPT) ||
442        (cred_usage == GSS_C_BOTH))
443       if ((ret = acquire_accept_cred(context, minor_status, desired_name,
444 				     &(cred->princ), cred))
445 	  != GSS_S_COMPLETE) {
446 	 if (cred->princ)
447 	    krb5_free_principal(context, cred->princ);
448 	 xfree(cred);
449 	 /* minor_status set by acquire_accept_cred() */
450 	 return(ret);
451       }
452 
453    /* if requested, acquire credentials for initiation */
454    /* this will fill in cred->princ if it wasn't set above, and
455       the desired_name is not specified */
456 
457    if ((cred_usage == GSS_C_INITIATE) ||
458        (cred_usage == GSS_C_BOTH))
459       if ((ret =
460 	   acquire_init_cred(context, minor_status,
461 			     cred->princ?(gss_name_t)cred->princ:desired_name,
462 			     &(cred->princ), cred))
463 	  != GSS_S_COMPLETE) {
464 	 if (cred->keytab)
465 	    (void) krb5_kt_close(context, cred->keytab);
466 	 if (cred->princ)
467 	    krb5_free_principal(context, cred->princ);
468 	 xfree(cred);
469 	 /* minor_status set by acquire_init_cred() */
470 	 return(ret);
471       }
472 
473    /* Solaris Kerberos:
474     * if the princ wasn't filled in already, fill it in now unless
475     * a cred with no associated princ is requested (will invoke default
476     * behaviour when gss_accept_init_context() is called).
477     */
478    if (!cred->princ && (desired_name != GSS_C_NO_NAME))
479       if ((code = krb5_copy_principal(context, (krb5_principal) desired_name,
480 				      &(cred->princ)))) {
481 	 if (cred->ccache)
482 	    (void)krb5_cc_close(context, cred->ccache);
483 	 if (cred->keytab)
484 	    (void)krb5_kt_close(context, cred->keytab);
485 	 xfree(cred);
486 	 *minor_status = code;
487 	 return(GSS_S_FAILURE);
488       }
489 
490    /*** at this point, the cred structure has been completely created */
491 
492    /* compute time_rec */
493 
494    if (cred_usage == GSS_C_ACCEPT) {
495       if (time_rec)
496 	 *time_rec = GSS_C_INDEFINITE;
497    } else {
498       krb5_timestamp now;
499 
500       if ((code = krb5_timeofday(context, &now))) {
501 	 if (cred->ccache)
502 	    (void)krb5_cc_close(context, cred->ccache);
503 	 if (cred->keytab)
504 	    (void)krb5_kt_close(context, cred->keytab);
505 	 if (cred->princ)
506 	    krb5_free_principal(context, cred->princ);
507 	 xfree(cred);
508 	 *minor_status = code;
509 	 return(GSS_S_FAILURE);
510       }
511 
512       if (time_rec)
513 	 *time_rec = (cred->tgt_expire > now) ? (cred->tgt_expire - now) : 0;
514    }
515 
516    /* create mechs */
517 
518    if (actual_mechs) {
519        if (GSS_ERROR(ret = gss_create_empty_oid_set(minor_status,
520 							    &ret_mechs)) ||
521 	   (cred->prerfc_mech &&
522 	    GSS_ERROR(ret = gss_add_oid_set_member(minor_status,
523 							   (gss_OID) gss_mech_krb5_old,
524 							   &ret_mechs))) ||
525 	   (cred->rfc_mech &&
526 	    GSS_ERROR(ret = gss_add_oid_set_member(minor_status,
527 							   (gss_OID) gss_mech_krb5,
528 							   &ret_mechs)))) {
529 	   if (cred->ccache)
530 	       (void)krb5_cc_close(context, cred->ccache);
531 	   if (cred->keytab)
532 	       (void)krb5_kt_close(context, cred->keytab);
533 	   if (cred->princ)
534 	       krb5_free_principal(context, cred->princ);
535 	   xfree(cred);
536 	   /* (*minor_status) set above */
537 	   return(ret);
538        }
539    }
540 
541    /* intern the credential handle */
542 
543    if (! kg_save_cred_id((gss_cred_id_t) cred)) {
544       (void) gss_release_oid_set(NULL, &ret_mechs);
545       free(ret_mechs->elements);
546       free(ret_mechs);
547       if (cred->ccache)
548 	 (void)krb5_cc_close(context, cred->ccache);
549       if (cred->keytab)
550 	 (void)krb5_kt_close(context, cred->keytab);
551       if (cred->princ)
552 	 krb5_free_principal(context, cred->princ);
553       xfree(cred);
554       *minor_status = (OM_uint32) G_VALIDATE_FAILED;
555       return(GSS_S_FAILURE);
556    }
557 
558    /* return success */
559 
560    *minor_status = 0;
561    *output_cred_handle = (gss_cred_id_t) cred;
562    if (actual_mechs)
563       *actual_mechs = ret_mechs;
564    return(GSS_S_COMPLETE);
565 }
566