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 Högskolan 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