Home | History | Annotate | Line # | Download | only in krb5
      1 /*	$NetBSD: acquire_cred.c,v 1.3 2023/06/19 21:41:43 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997 - 2005 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  *
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * 3. Neither the name of the Institute nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include "gsskrb5_locl.h"
     37 
     38 OM_uint32
     39 __gsskrb5_ccache_lifetime(OM_uint32 *minor_status,
     40 			  krb5_context context,
     41 			  krb5_ccache id,
     42 			  krb5_principal principal,
     43 			  OM_uint32 *lifetime)
     44 {
     45     krb5_error_code kret;
     46     time_t left;
     47 
     48     kret = krb5_cc_get_lifetime(context, id, &left);
     49     if (kret) {
     50         *minor_status = kret;
     51         return GSS_S_FAILURE;
     52     }
     53 
     54     *lifetime = left;
     55 
     56     return GSS_S_COMPLETE;
     57 }
     58 
     59 
     60 
     61 
     62 static krb5_error_code
     63 get_keytab(krb5_context context, krb5_keytab *keytab)
     64 {
     65     krb5_error_code kret;
     66 
     67     HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex);
     68 
     69     if (_gsskrb5_keytab != NULL) {
     70 	char *name = NULL;
     71 
     72 	kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name);
     73 	if (kret == 0) {
     74 	    kret = krb5_kt_resolve(context, name, keytab);
     75 	    krb5_xfree(name);
     76 	}
     77     } else
     78 	kret = krb5_kt_default(context, keytab);
     79 
     80     HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex);
     81 
     82     return (kret);
     83 }
     84 
     85 /*
     86  * This function produces a cred with a MEMORY ccache containing a TGT
     87  * acquired with a password.
     88  */
     89 static OM_uint32
     90 acquire_cred_with_password(OM_uint32 *minor_status,
     91                            krb5_context context,
     92                            const char *password,
     93                            OM_uint32 time_req,
     94                            gss_const_OID desired_mech,
     95                            gss_cred_usage_t cred_usage,
     96                            gsskrb5_cred handle)
     97 {
     98     OM_uint32 ret = GSS_S_FAILURE;
     99     krb5_creds cred;
    100     krb5_get_init_creds_opt *opt;
    101     krb5_ccache ccache = NULL;
    102     krb5_error_code kret;
    103     time_t now;
    104     OM_uint32 left;
    105     const char *realm;
    106 
    107     if (cred_usage == GSS_C_ACCEPT) {
    108         /*
    109          * TODO: Here we should eventually support user2user (when we get
    110          *       support for that via an extension to the mechanism
    111          *       allowing for more than two security context tokens),
    112          *       and/or new unique MEMORY keytabs (we have MEMORY keytab
    113          *       support, but we don't have a keytab equivalent of
    114          *       krb5_cc_new_unique()).  Either way, for now we can't
    115          *       support this.
    116          */
    117         *minor_status = ENOTSUP; /* XXX Better error? */
    118         return GSS_S_FAILURE;
    119     }
    120 
    121     memset(&cred, 0, sizeof(cred));
    122 
    123     if (handle->principal == NULL) {
    124         kret = krb5_get_default_principal(context, &handle->principal);
    125         if (kret)
    126             goto end;
    127     }
    128     kret = krb5_get_init_creds_opt_alloc(context, &opt);
    129     if (kret)
    130         goto end;
    131 
    132     realm = krb5_principal_get_realm(context, handle->principal);
    133 
    134     krb5_get_init_creds_opt_set_default_flags(context, "gss_krb5", realm, opt);
    135 
    136     /*
    137      * Get the current time before the AS exchange so we don't
    138      * accidentally end up returning a value that puts advertised
    139      * expiration past the real expiration.
    140      *
    141      * We need to do this because krb5_cc_get_lifetime() returns a
    142      * relative time that we need to add to the current time.  We ought
    143      * to have a version of krb5_cc_get_lifetime() that returns absolute
    144      * time...
    145      */
    146     krb5_timeofday(context, &now);
    147 
    148     kret = krb5_get_init_creds_password(context, &cred, handle->principal,
    149                                         password, NULL, NULL, 0, NULL, opt);
    150     krb5_get_init_creds_opt_free(context, opt);
    151     if (kret)
    152         goto end;
    153 
    154     kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
    155     if (kret)
    156         goto end;
    157 
    158     kret = krb5_cc_initialize(context, ccache, cred.client);
    159     if (kret)
    160         goto end;
    161 
    162     kret = krb5_cc_store_cred(context, ccache, &cred);
    163     if (kret)
    164         goto end;
    165 
    166     handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
    167 
    168     ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
    169                                     handle->principal, &left);
    170     if (ret != GSS_S_COMPLETE)
    171         goto end;
    172     handle->endtime = now + left;
    173     handle->ccache = ccache;
    174     ccache = NULL;
    175     ret = GSS_S_COMPLETE;
    176     kret = 0;
    177 
    178 end:
    179     if (ccache != NULL)
    180         krb5_cc_destroy(context, ccache);
    181     if (cred.client != NULL)
    182 	krb5_free_cred_contents(context, &cred);
    183     if (ret != GSS_S_COMPLETE && kret != 0)
    184 	*minor_status = kret;
    185     return (ret);
    186 }
    187 
    188 /*
    189  * Acquires an initiator credential from a ccache or using a keytab.
    190  */
    191 static OM_uint32
    192 acquire_initiator_cred(OM_uint32 *minor_status,
    193                        krb5_context context,
    194                        OM_uint32 time_req,
    195                        gss_const_OID desired_mech,
    196                        gss_cred_usage_t cred_usage,
    197                        gsskrb5_cred handle)
    198 {
    199     OM_uint32 ret = GSS_S_FAILURE;
    200     krb5_creds cred;
    201     krb5_get_init_creds_opt *opt;
    202     krb5_principal def_princ = NULL;
    203     krb5_ccache def_ccache = NULL;
    204     krb5_ccache ccache = NULL;  /* we may store into this ccache */
    205     krb5_keytab keytab = NULL;
    206     krb5_error_code kret = 0;
    207     OM_uint32 left;
    208     time_t lifetime = 0;
    209     time_t now;
    210 
    211     memset(&cred, 0, sizeof(cred));
    212 
    213     /*
    214      * Get current time early so we can set handle->endtime to a value that
    215      * cannot accidentally be past the real endtime.  We need a variant of
    216      * krb5_cc_get_lifetime() that returns absolute endtime.
    217      */
    218     krb5_timeofday(context, &now);
    219 
    220     /*
    221      * First look for a ccache that has the desired_name (which may be
    222      * the default credential name).
    223      *
    224      * If we don't have an unexpired credential, acquire one with a
    225      * keytab.
    226      *
    227      * If we acquire one with a keytab, save it in the ccache we found
    228      * with the expired credential, if any.
    229      *
    230      * If we don't have any such ccache, then use a MEMORY ccache.
    231      */
    232 
    233     if (handle->principal != NULL) {
    234         /*
    235          * Not default credential case.  See if we can find a ccache in
    236          * the cccol for the desired_name.
    237          */
    238 	kret = krb5_cc_cache_match(context,
    239 				   handle->principal,
    240 				   &ccache);
    241 	if (kret == 0) {
    242             kret = krb5_cc_get_lifetime(context, ccache, &lifetime);
    243             if (kret == 0) {
    244                 if (lifetime > 0)
    245                     goto found;
    246                 else
    247                     goto try_keytab;
    248             }
    249 	}
    250         /*
    251          * Fall through.  We shouldn't find this in the default ccache
    252          * either, but we'll give it a try, then we'll try using a keytab.
    253          */
    254     }
    255 
    256     /*
    257      * Either desired_name was GSS_C_NO_NAME (default cred) or
    258      * krb5_cc_cache_match() failed (or found expired).
    259      */
    260     kret = krb5_cc_default(context, &def_ccache);
    261     if (kret != 0)
    262         goto try_keytab;
    263     kret = krb5_cc_get_lifetime(context, def_ccache, &lifetime);
    264     if (kret != 0)
    265         lifetime = 0;
    266     kret = krb5_cc_get_principal(context, def_ccache, &def_princ);
    267     if (kret != 0)
    268         goto try_keytab;
    269     /*
    270      * Have a default ccache; see if it matches desired_name.
    271      */
    272     if (handle->principal == NULL ||
    273         krb5_principal_compare(context, handle->principal,
    274                                def_princ) == TRUE) {
    275         /*
    276          * It matches.
    277          *
    278          * If we end up trying a keytab then we can write the result to
    279          * the default ccache.
    280          */
    281         if (handle->principal == NULL) {
    282             kret = krb5_copy_principal(context, def_princ, &handle->principal);
    283             if (kret)
    284                 goto end;
    285         }
    286         if (ccache != NULL)
    287             krb5_cc_close(context, ccache);
    288         ccache = def_ccache;
    289         def_ccache = NULL;
    290         if (lifetime > 0)
    291             goto found;
    292         /* else we fall through and try using a keytab */
    293     }
    294 
    295 try_keytab:
    296     if (handle->principal == NULL) {
    297         /* We need to know what client principal to use */
    298         kret = krb5_get_default_principal(context, &handle->principal);
    299         if (kret)
    300             goto end;
    301     }
    302     kret = get_keytab(context, &keytab);
    303     if (kret)
    304         goto end;
    305 
    306     kret = krb5_get_init_creds_opt_alloc(context, &opt);
    307     if (kret)
    308         goto end;
    309     krb5_timeofday(context, &now);
    310     kret = krb5_get_init_creds_keytab(context, &cred, handle->principal,
    311                                       keytab, 0, NULL, opt);
    312     krb5_get_init_creds_opt_free(context, opt);
    313     if (kret)
    314         goto end;
    315 
    316     /*
    317      * We got a credential with a keytab.  Save it if we can.
    318      */
    319     if (ccache == NULL) {
    320         /*
    321          * There's no ccache we can overwrite with the credentials we acquired
    322          * with a keytab.  We'll use a MEMORY ccache then.
    323          *
    324          * Note that an application that falls into this repeatedly will do an
    325          * AS exchange every time it acquires a credential handle.  Hopefully
    326          * this doesn't happen much.  A workaround is to kinit -k once so that
    327          * we always re-initialize the matched/default ccache here.  I.e., once
    328          * there's a FILE/DIR ccache, we'll keep it frash automatically if we
    329          * have a keytab, but if there's no FILE/DIR ccache, then we'll
    330          * get a fresh credential *every* time we're asked.
    331          */
    332         kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache);
    333         if (kret)
    334             goto end;
    335         handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
    336     } /* else we'll re-initialize whichever ccache we matched above */
    337 
    338     kret = krb5_cc_initialize(context, ccache, cred.client);
    339     if (kret)
    340         goto end;
    341     kret = krb5_cc_store_cred(context, ccache, &cred);
    342     if (kret)
    343         goto end;
    344 
    345 found:
    346     assert(handle->principal != NULL);
    347     ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache,
    348                                     handle->principal, &left);
    349     if (ret != GSS_S_COMPLETE)
    350         goto end;
    351     handle->endtime = now + left;
    352     handle->ccache = ccache;
    353     ccache = NULL;
    354     ret = GSS_S_COMPLETE;
    355     kret = 0;
    356 
    357 end:
    358     if (ccache != NULL) {
    359         if ((handle->cred_flags & GSS_CF_DESTROY_CRED_ON_RELEASE) != 0)
    360             krb5_cc_destroy(context, ccache);
    361         else
    362             krb5_cc_close(context, ccache);
    363     }
    364     if (def_ccache != NULL)
    365         krb5_cc_close(context, def_ccache);
    366     if (cred.client != NULL)
    367 	krb5_free_cred_contents(context, &cred);
    368     if (def_princ != NULL)
    369 	krb5_free_principal(context, def_princ);
    370     if (keytab != NULL)
    371 	krb5_kt_close(context, keytab);
    372     if (ret != GSS_S_COMPLETE && kret != 0)
    373 	*minor_status = kret;
    374     return (ret);
    375 }
    376 
    377 static OM_uint32
    378 acquire_acceptor_cred(OM_uint32 * minor_status,
    379                       krb5_context context,
    380                       OM_uint32 time_req,
    381                       gss_const_OID desired_mech,
    382                       gss_cred_usage_t cred_usage,
    383                       gsskrb5_cred handle)
    384 {
    385     OM_uint32 ret;
    386     krb5_error_code kret;
    387 
    388     ret = GSS_S_FAILURE;
    389 
    390     kret = get_keytab(context, &handle->keytab);
    391     if (kret)
    392 	goto end;
    393 
    394     /* check that the requested principal exists in the keytab */
    395     if (handle->principal) {
    396 	krb5_keytab_entry entry;
    397 
    398 	kret = krb5_kt_get_entry(context, handle->keytab,
    399 				 handle->principal, 0, 0, &entry);
    400 	if (kret)
    401 	    goto end;
    402 	krb5_kt_free_entry(context, &entry);
    403 	ret = GSS_S_COMPLETE;
    404     } else {
    405 	/*
    406 	 * Check if there is at least one entry in the keytab before
    407 	 * declaring it as an useful keytab.
    408 	 */
    409 	krb5_keytab_entry tmp;
    410 	krb5_kt_cursor c;
    411 
    412 	kret = krb5_kt_start_seq_get (context, handle->keytab, &c);
    413 	if (kret)
    414 	    goto end;
    415 	if (krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) {
    416 	    krb5_kt_free_entry(context, &tmp);
    417 	    ret = GSS_S_COMPLETE; /* ok found one entry */
    418 	}
    419 	krb5_kt_end_seq_get (context, handle->keytab, &c);
    420     }
    421 end:
    422     if (ret != GSS_S_COMPLETE) {
    423 	if (handle->keytab != NULL)
    424 	    krb5_kt_close(context, handle->keytab);
    425 	if (kret != 0) {
    426 	    *minor_status = kret;
    427 	}
    428     }
    429     return (ret);
    430 }
    431 
    432 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred
    433 (OM_uint32 * minor_status,
    434  gss_const_name_t desired_name,
    435  OM_uint32 time_req,
    436  const gss_OID_set desired_mechs,
    437  gss_cred_usage_t cred_usage,
    438  gss_cred_id_t * output_cred_handle,
    439  gss_OID_set * actual_mechs,
    440  OM_uint32 * time_rec
    441     )
    442 {
    443     OM_uint32 ret;
    444 
    445     if (desired_mechs) {
    446 	int present = 0;
    447 
    448 	ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
    449 				      desired_mechs, &present);
    450 	if (ret)
    451 	    return ret;
    452 	if (!present) {
    453 	    *minor_status = 0;
    454 	    return GSS_S_BAD_MECH;
    455 	}
    456     }
    457 
    458     ret = _gsskrb5_acquire_cred_ext(minor_status,
    459 				    desired_name,
    460 				    GSS_C_NO_OID,
    461 				    NULL,
    462 				    time_req,
    463 				    GSS_KRB5_MECHANISM,
    464 				    cred_usage,
    465 				    output_cred_handle);
    466     if (ret)
    467 	return ret;
    468 
    469 
    470     ret = _gsskrb5_inquire_cred(minor_status, *output_cred_handle,
    471 				NULL, time_rec, NULL, actual_mechs);
    472     if (ret) {
    473 	OM_uint32 tmp;
    474 	_gsskrb5_release_cred(&tmp, output_cred_handle);
    475     }
    476 
    477     return ret;
    478 }
    479 
    480 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext
    481 (OM_uint32 * minor_status,
    482  gss_const_name_t desired_name,
    483  gss_const_OID credential_type,
    484  const void *credential_data,
    485  OM_uint32 time_req,
    486  gss_const_OID desired_mech,
    487  gss_cred_usage_t cred_usage,
    488  gss_cred_id_t * output_cred_handle
    489     )
    490 {
    491     krb5_context context;
    492     gsskrb5_cred handle;
    493     OM_uint32 ret;
    494 
    495     cred_usage &= GSS_C_OPTION_MASK;
    496 
    497     if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE &&
    498         cred_usage != GSS_C_BOTH) {
    499 	*minor_status = GSS_KRB5_S_G_BAD_USAGE;
    500 	return GSS_S_FAILURE;
    501     }
    502 
    503     GSSAPI_KRB5_INIT(&context);
    504 
    505     *output_cred_handle = GSS_C_NO_CREDENTIAL;
    506 
    507     handle = calloc(1, sizeof(*handle));
    508     if (handle == NULL) {
    509 	*minor_status = ENOMEM;
    510         return GSS_S_FAILURE;
    511     }
    512 
    513     HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
    514 
    515     if (desired_name != GSS_C_NO_NAME) {
    516 	ret = _gsskrb5_canon_name(minor_status, context,
    517 				  desired_name, &handle->principal);
    518 	if (ret) {
    519 	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    520 	    free(handle);
    521 	    return ret;
    522 	}
    523     }
    524 
    525     if (credential_type != GSS_C_NO_OID &&
    526         gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) {
    527         /* Acquire a cred with a password */
    528         gss_const_buffer_t pwbuf = credential_data;
    529         char *pw;
    530 
    531         if (pwbuf == NULL) {
    532             HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    533             free(handle);
    534             *minor_status = KRB5_NOCREDS_SUPPLIED; /* see below */
    535             return GSS_S_CALL_INACCESSIBLE_READ;
    536         }
    537 
    538         /* NUL-terminate the password, if it wasn't already */
    539         pw = strndup(pwbuf->value, pwbuf->length);
    540         if (pw == NULL) {
    541             HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    542             free(handle);
    543             *minor_status = krb5_enomem(context);
    544             return GSS_S_CALL_INACCESSIBLE_READ;
    545         }
    546         ret = acquire_cred_with_password(minor_status, context, pw, time_req,
    547                                          desired_mech, cred_usage, handle);
    548         free(pw);
    549         if (ret != GSS_S_COMPLETE) {
    550             HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    551             krb5_free_principal(context, handle->principal);
    552             free(handle);
    553             return (ret);
    554         }
    555     } else if (credential_type != GSS_C_NO_OID) {
    556         /*
    557          * _gss_acquire_cred_ext() called with something other than a password.
    558          *
    559          * Not supported.
    560          *
    561          * _gss_acquire_cred_ext() is not a supported public interface, so
    562          * we don't have to try too hard as to minor status codes here.
    563          */
    564         HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    565         free(handle);
    566         *minor_status = ENOTSUP;
    567         return GSS_S_FAILURE;
    568     } else {
    569         /*
    570          * Acquire a credential from the background credential store (ccache,
    571          * keytab).
    572          */
    573         if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) {
    574             ret = acquire_initiator_cred(minor_status, context, time_req,
    575                                          desired_mech, cred_usage, handle);
    576             if (ret != GSS_S_COMPLETE) {
    577                 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    578                 krb5_free_principal(context, handle->principal);
    579                 free(handle);
    580                 return (ret);
    581             }
    582         }
    583         if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) {
    584             ret = acquire_acceptor_cred(minor_status, context, time_req,
    585                                         desired_mech, cred_usage, handle);
    586             if (ret != GSS_S_COMPLETE) {
    587                 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    588                 krb5_free_principal(context, handle->principal);
    589                 free(handle);
    590                 return (ret);
    591             }
    592         }
    593     }
    594     ret = gss_create_empty_oid_set(minor_status, &handle->mechanisms);
    595     if (ret == GSS_S_COMPLETE)
    596     	ret = gss_add_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
    597 				     &handle->mechanisms);
    598     if (ret != GSS_S_COMPLETE) {
    599 	if (handle->mechanisms != NULL)
    600 	    gss_release_oid_set(NULL, &handle->mechanisms);
    601 	HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
    602 	krb5_free_principal(context, handle->principal);
    603 	free(handle);
    604 	return (ret);
    605     }
    606     handle->usage = cred_usage;
    607     *minor_status = 0;
    608     *output_cred_handle = (gss_cred_id_t)handle;
    609     return (GSS_S_COMPLETE);
    610 }
    611