2 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 #include "gsskrb5_locl.h"
37 * Find an element in a cred store. Returns GSS_S_COMPLETE if the cred store
38 * is absent or well formed, irrespective of whether the element exists. The
39 * caller should check for *value != NULL before using; values are typically
40 * optional, hence this behavior. (The caller should validate the return
41 * value at least once though, to check it is well-formed.)
44 __gsskrb5_cred_store_find(OM_uint32
*minor_status
,
45 gss_const_key_value_set_t cred_store
,
53 if (cred_store
== GSS_C_NO_CRED_STORE
)
54 return GSS_S_COMPLETE
;
55 else if (cred_store
->count
== 0) {
56 *minor_status
= GSS_KRB5_S_G_BAD_USAGE
;
60 for (i
= 0; i
< cred_store
->count
; i
++) {
61 if (strcmp(key
, cred_store
->elements
[i
].key
) == 0) {
64 *minor_status
= GSS_KRB5_S_G_BAD_USAGE
;
65 return GSS_S_DUPLICATE_ELEMENT
;
67 *value
= cred_store
->elements
[i
].value
;
71 return GSS_S_COMPLETE
;
75 __gsskrb5_ccache_lifetime(OM_uint32
*minor_status
,
78 krb5_principal principal
,
84 kret
= krb5_cc_get_lifetime(context
, id
, &left
);
92 return GSS_S_COMPLETE
;
98 static krb5_error_code
99 get_system_keytab(krb5_context context
,
100 gss_const_key_value_set_t cred_store
,
103 krb5_error_code kret
;
104 const char *cs_ktname
;
107 __gsskrb5_cred_store_find(&tmp
, cred_store
, "keytab", &cs_ktname
);
109 HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex
);
112 kret
= krb5_kt_resolve(context
, cs_ktname
, keytab
);
113 else if (_gsskrb5_keytab
!= NULL
) {
116 kret
= krb5_kt_get_full_name(context
, _gsskrb5_keytab
, &name
);
118 kret
= krb5_kt_resolve(context
, name
, keytab
);
122 kret
= krb5_kt_default(context
, keytab
);
124 HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex
);
129 static krb5_error_code
130 get_client_keytab(krb5_context context
,
131 gss_const_key_value_set_t cred_store
,
132 krb5_const_principal principal
,
136 const char *cs_ktname
;
139 __gsskrb5_cred_store_find(&tmp
, cred_store
, "client_keytab", &cs_ktname
);
142 ret
= krb5_kt_resolve(context
, cs_ktname
, keytab
);
145 ret
= _krb5_kt_client_default_name(context
, &name
);
147 ret
= krb5_kt_resolve(context
, name
, keytab
);
151 if (ret
== 0 && principal
) {
152 krb5_keytab_entry entry
;
154 ret
= krb5_kt_get_entry(context
, *keytab
, principal
,
157 krb5_kt_free_entry(context
, &entry
);
162 krb5_kt_close(context
, *keytab
);
166 ret
= get_system_keytab(context
, GSS_C_NO_CRED_STORE
, keytab
);
173 is_valid_password_cred_store(gss_const_key_value_set_t cred_store
)
177 if (cred_store
== GSS_C_NO_CRED_STORE
)
180 /* XXX don't check keytab, someday we will allow password+acceptor creds */
181 for (i
= 0; i
< cred_store
->count
; i
++) {
182 if (strcmp(cred_store
->elements
[i
].key
, "ccache") == 0 ||
183 strcmp(cred_store
->elements
[i
].key
, "client_keytab") == 0)
191 * This function produces a cred with a MEMORY ccache containing a TGT
192 * acquired with a password.
195 acquire_cred_with_password(OM_uint32
*minor_status
,
196 krb5_context context
,
197 const char *password
,
199 gss_OID_set desired_mechs
,
200 gss_cred_usage_t cred_usage
,
201 gss_const_key_value_set_t cred_store
,
204 OM_uint32 ret
= GSS_S_FAILURE
;
206 krb5_init_creds_context ctx
= NULL
;
207 krb5_get_init_creds_opt
*opt
= NULL
;
208 krb5_ccache ccache
= NULL
;
209 krb5_error_code kret
;
214 if (!is_valid_password_cred_store(cred_store
)) {
215 *minor_status
= GSS_KRB5_S_G_BAD_PASSWORD_CRED_STORE
;
216 return GSS_S_NO_CRED
;
219 if (cred_usage
== GSS_C_ACCEPT
) {
221 * TODO: Here we should eventually support user2user (when we get
222 * support for that via an extension to the mechanism
223 * allowing for more than two security context tokens),
224 * and/or new unique MEMORY keytabs (we have MEMORY keytab
225 * support, but we don't have a keytab equivalent of
226 * krb5_cc_new_unique()). Either way, for now we can't
229 *minor_status
= ENOTSUP
; /* XXX Better error? */
230 return GSS_S_FAILURE
;
233 memset(&cred
, 0, sizeof(cred
));
235 if (handle
->principal
== NULL
) {
236 kret
= krb5_get_default_principal(context
, &handle
->principal
);
240 realm
= krb5_principal_get_realm(context
, handle
->principal
);
242 kret
= krb5_get_init_creds_opt_alloc(context
, &opt
);
244 krb5_get_init_creds_opt_set_default_flags(context
, "gss_krb5", realm
,
246 kret
= krb5_init_creds_init(context
, handle
->principal
, NULL
, NULL
, 0,
250 kret
= _krb5_init_creds_set_fast_anon_pkinit_optimistic(context
, ctx
);
252 kret
= krb5_init_creds_set_password(context
, ctx
, password
);
255 * Get the current time before the AS exchange so we don't
256 * accidentally end up returning a value that puts advertised
257 * expiration past the real expiration.
259 * We need to do this because krb5_cc_get_lifetime() returns a
260 * relative time that we need to add to the current time. We ought
261 * to have a version of krb5_cc_get_lifetime() that returns absolute
264 krb5_timeofday(context
, &now
);
267 kret
= krb5_init_creds_get(context
, ctx
);
269 kret
= krb5_init_creds_get_creds(context
, ctx
, &cred
);
271 kret
= krb5_cc_new_unique(context
, krb5_cc_type_memory
, NULL
, &ccache
);
273 kret
= krb5_cc_initialize(context
, ccache
, cred
.client
);
275 kret
= krb5_init_creds_store(context
, ctx
, ccache
);
277 kret
= krb5_cc_store_cred(context
, ccache
, &cred
);
281 handle
->cred_flags
|= GSS_CF_DESTROY_CRED_ON_RELEASE
;
283 ret
= __gsskrb5_ccache_lifetime(minor_status
, context
, ccache
,
284 handle
->principal
, &left
);
285 if (ret
!= GSS_S_COMPLETE
)
287 handle
->endtime
= now
+ left
;
288 handle
->ccache
= ccache
;
290 ret
= GSS_S_COMPLETE
;
293 krb5_get_init_creds_opt_free(context
, opt
);
295 krb5_init_creds_free(context
, ctx
);
297 krb5_cc_destroy(context
, ccache
);
298 if (cred
.client
!= NULL
)
299 krb5_free_cred_contents(context
, &cred
);
300 if (ret
!= GSS_S_COMPLETE
)
301 *minor_status
= kret
;
306 * Acquires an initiator credential from a ccache or using a keytab.
309 acquire_initiator_cred(OM_uint32
*minor_status
,
310 krb5_context context
,
312 gss_OID_set desired_mechs
,
313 gss_cred_usage_t cred_usage
,
314 gss_const_key_value_set_t cred_store
,
319 krb5_get_init_creds_opt
*opt
;
320 krb5_principal def_princ
= NULL
;
321 krb5_ccache def_ccache
= NULL
;
322 krb5_ccache ccache
= NULL
; /* we may store into this ccache */
323 krb5_keytab keytab
= NULL
;
324 krb5_error_code kret
= 0;
326 const char *cs_ccache_name
;
330 memset(&cred
, 0, sizeof(cred
));
332 ret
= __gsskrb5_cred_store_find(minor_status
, cred_store
,
333 "ccache", &cs_ccache_name
);
340 * Get current time early so we can set handle->endtime to a value that
341 * cannot accidentally be past the real endtime. We need a variant of
342 * krb5_cc_get_lifetime() that returns absolute endtime.
344 krb5_timeofday(context
, &now
);
347 * First look for a ccache that has the desired_name (which may be
348 * the default credential name), unless a specific credential cache
349 * was included in cred_store.
351 * If we don't have an unexpired credential, acquire one with a
354 * If we acquire one with a keytab, save it in the ccache we found
355 * with the expired credential, if any.
357 * If we don't have any such ccache, then use a MEMORY ccache.
360 if (handle
->principal
!= NULL
&& cs_ccache_name
== NULL
) {
362 * Not default credential case. See if we can find a ccache in
363 * the cccol for the desired_name.
365 kret
= krb5_cc_cache_match(context
,
369 kret
= krb5_cc_get_lifetime(context
, ccache
, &lifetime
);
378 * Fall through. We shouldn't find this in the default ccache
379 * either, but we'll give it a try, then we'll try using a keytab.
384 * Either desired_name was GSS_C_NO_NAME (default cred) or
385 * krb5_cc_cache_match() failed (or found expired).
388 kret
= krb5_cc_resolve(context
, cs_ccache_name
, &def_ccache
);
390 kret
= krb5_cc_default(context
, &def_ccache
);
393 kret
= krb5_cc_get_lifetime(context
, def_ccache
, &lifetime
);
396 kret
= krb5_cc_get_principal(context
, def_ccache
, &def_princ
);
400 * Have a default ccache; see if it matches desired_name.
402 if (handle
->principal
== NULL
||
403 krb5_principal_compare(context
, handle
->principal
,
404 def_princ
) == TRUE
) {
408 * If we end up trying a keytab then we can write the result to
409 * the default ccache.
411 if (handle
->principal
== NULL
) {
412 kret
= krb5_copy_principal(context
, def_princ
, &handle
->principal
);
417 krb5_cc_close(context
, ccache
);
422 /* else we fall through and try using a keytab */
426 if (handle
->principal
== NULL
) {
427 /* We need to know what client principal to use */
428 kret
= krb5_get_default_principal(context
, &handle
->principal
);
432 kret
= get_client_keytab(context
, cred_store
, handle
->principal
, &keytab
);
436 kret
= krb5_get_init_creds_opt_alloc(context
, &opt
);
439 krb5_timeofday(context
, &now
);
440 kret
= krb5_get_init_creds_keytab(context
, &cred
, handle
->principal
,
441 keytab
, 0, NULL
, opt
);
442 krb5_get_init_creds_opt_free(context
, opt
);
447 * We got a credential with a keytab. Save it if we can.
449 if (ccache
== NULL
) {
451 * There's no ccache we can overwrite with the credentials we acquired
452 * with a keytab. We'll use a MEMORY ccache then.
454 * Note that an application that falls into this repeatedly will do an
455 * AS exchange every time it acquires a credential handle. Hopefully
456 * this doesn't happen much. A workaround is to kinit -k once so that
457 * we always re-initialize the matched/default ccache here. I.e., once
458 * there's a FILE/DIR ccache, we'll keep it frash automatically if we
459 * have a keytab, but if there's no FILE/DIR ccache, then we'll
460 * get a fresh credential *every* time we're asked.
462 kret
= krb5_cc_new_unique(context
, krb5_cc_type_memory
, NULL
, &ccache
);
465 handle
->cred_flags
|= GSS_CF_DESTROY_CRED_ON_RELEASE
;
466 } /* else we'll re-initialize whichever ccache we matched above */
468 kret
= krb5_cc_initialize(context
, ccache
, cred
.client
);
471 kret
= krb5_cc_store_cred(context
, ccache
, &cred
);
476 assert(handle
->principal
!= NULL
);
477 ret
= __gsskrb5_ccache_lifetime(minor_status
, context
, ccache
,
478 handle
->principal
, &left
);
479 if (ret
!= GSS_S_COMPLETE
)
481 handle
->endtime
= now
+ left
;
482 handle
->ccache
= ccache
;
484 ret
= GSS_S_COMPLETE
;
488 if (ccache
!= NULL
) {
489 if ((handle
->cred_flags
& GSS_CF_DESTROY_CRED_ON_RELEASE
) != 0)
490 krb5_cc_destroy(context
, ccache
);
492 krb5_cc_close(context
, ccache
);
494 if (def_ccache
!= NULL
)
495 krb5_cc_close(context
, def_ccache
);
496 if (cred
.client
!= NULL
)
497 krb5_free_cred_contents(context
, &cred
);
498 if (def_princ
!= NULL
)
499 krb5_free_principal(context
, def_princ
);
501 krb5_kt_close(context
, keytab
);
502 if (ret
!= GSS_S_COMPLETE
&& kret
!= 0)
503 *minor_status
= kret
;
508 acquire_acceptor_cred(OM_uint32
* minor_status
,
509 krb5_context context
,
511 gss_OID_set desired_mechs
,
512 gss_cred_usage_t cred_usage
,
513 gss_const_key_value_set_t cred_store
,
517 krb5_error_code kret
;
521 kret
= get_system_keytab(context
, cred_store
, &handle
->keytab
);
525 /* check that the requested principal exists in the keytab */
526 if (handle
->principal
) {
527 krb5_keytab_entry entry
;
529 kret
= krb5_kt_get_entry(context
, handle
->keytab
,
530 handle
->principal
, 0, 0, &entry
);
533 krb5_kt_free_entry(context
, &entry
);
534 ret
= GSS_S_COMPLETE
;
537 * Check if there is at least one entry in the keytab before
538 * declaring it as an useful keytab.
540 krb5_keytab_entry tmp
;
543 kret
= krb5_kt_start_seq_get (context
, handle
->keytab
, &c
);
546 if (krb5_kt_next_entry(context
, handle
->keytab
, &tmp
, &c
) == 0) {
547 krb5_kt_free_entry(context
, &tmp
);
548 ret
= GSS_S_COMPLETE
; /* ok found one entry */
550 krb5_kt_end_seq_get (context
, handle
->keytab
, &c
);
553 if (ret
!= GSS_S_COMPLETE
) {
554 if (handle
->keytab
!= NULL
)
555 krb5_kt_close(context
, handle
->keytab
);
557 *minor_status
= kret
;
564 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_from
565 (OM_uint32
* minor_status
,
566 gss_const_name_t desired_name
,
568 gss_OID_set desired_mechs
,
569 gss_cred_usage_t cred_usage
,
570 gss_const_key_value_set_t cred_store
,
571 gss_cred_id_t
* output_cred_handle
,
572 gss_OID_set
*actual_mechs
,
576 krb5_context context
;
579 const char *password
= NULL
;
584 ret
= gss_test_oid_set_member(minor_status
, GSS_KRB5_MECHANISM
,
585 desired_mechs
, &present
);
590 return GSS_S_BAD_MECH
;
594 cred_usage
&= GSS_C_OPTION_MASK
;
596 if (cred_usage
!= GSS_C_ACCEPT
&& cred_usage
!= GSS_C_INITIATE
&&
597 cred_usage
!= GSS_C_BOTH
) {
598 *minor_status
= GSS_KRB5_S_G_BAD_USAGE
;
599 return GSS_S_FAILURE
;
602 ret
= __gsskrb5_cred_store_find(minor_status
, cred_store
,
603 "password", &password
);
607 GSSAPI_KRB5_INIT(&context
);
609 *output_cred_handle
= GSS_C_NO_CREDENTIAL
;
611 handle
= calloc(1, sizeof(*handle
));
612 if (handle
== NULL
) {
613 *minor_status
= ENOMEM
;
614 return GSS_S_FAILURE
;
617 handle
->destination_realm
= NULL
;
618 HEIMDAL_MUTEX_init(&handle
->cred_id_mutex
);
620 if (desired_name
!= GSS_C_NO_NAME
) {
621 ret
= _gsskrb5_canon_name(minor_status
, context
,
622 desired_name
, &handle
->principal
);
624 HEIMDAL_MUTEX_destroy(&handle
->cred_id_mutex
);
631 ret
= acquire_cred_with_password(minor_status
, context
, password
, time_req
,
632 desired_mechs
, cred_usage
, cred_store
, handle
);
633 if (ret
!= GSS_S_COMPLETE
) {
634 HEIMDAL_MUTEX_destroy(&handle
->cred_id_mutex
);
635 krb5_free_principal(context
, handle
->principal
);
641 * Acquire a credential from the specified or background credential
642 * store (ccache, keytab).
644 if (cred_usage
== GSS_C_INITIATE
|| cred_usage
== GSS_C_BOTH
) {
645 ret
= acquire_initiator_cred(minor_status
, context
, time_req
,
646 desired_mechs
, cred_usage
,
648 if (ret
!= GSS_S_COMPLETE
) {
649 HEIMDAL_MUTEX_destroy(&handle
->cred_id_mutex
);
650 krb5_free_principal(context
, handle
->principal
);
655 if (cred_usage
== GSS_C_ACCEPT
|| cred_usage
== GSS_C_BOTH
) {
656 ret
= acquire_acceptor_cred(minor_status
, context
, time_req
,
657 desired_mechs
, cred_usage
,
659 if (ret
!= GSS_S_COMPLETE
) {
660 HEIMDAL_MUTEX_destroy(&handle
->cred_id_mutex
);
661 krb5_free_principal(context
, handle
->principal
);
667 ret
= gss_create_empty_oid_set(minor_status
, &handle
->mechanisms
);
668 if (ret
== GSS_S_COMPLETE
)
669 ret
= gss_add_oid_set_member(minor_status
, GSS_KRB5_MECHANISM
,
670 &handle
->mechanisms
);
671 handle
->usage
= cred_usage
;
672 if (ret
== GSS_S_COMPLETE
)
673 ret
= _gsskrb5_inquire_cred(minor_status
, (gss_cred_id_t
)handle
,
674 NULL
, time_rec
, NULL
, actual_mechs
);
675 if (ret
!= GSS_S_COMPLETE
) {
676 if (handle
->mechanisms
!= NULL
)
677 gss_release_oid_set(NULL
, &handle
->mechanisms
);
678 HEIMDAL_MUTEX_destroy(&handle
->cred_id_mutex
);
679 krb5_free_principal(context
, handle
->principal
);
684 *output_cred_handle
= (gss_cred_id_t
)handle
;
685 return (GSS_S_COMPLETE
);