1 /* $NetBSD: acquire_cred.c,v 1.2 2017/01/28 21:31:46 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 106 if (cred_usage == GSS_C_ACCEPT) { 107 /* 108 * TODO: Here we should eventually support user2user (when we get 109 * support for that via an extension to the mechanism 110 * allowing for more than two security context tokens), 111 * and/or new unique MEMORY keytabs (we have MEMORY keytab 112 * support, but we don't have a keytab equivalent of 113 * krb5_cc_new_unique()). Either way, for now we can't 114 * support this. 115 */ 116 *minor_status = ENOTSUP; /* XXX Better error? */ 117 return GSS_S_FAILURE; 118 } 119 120 memset(&cred, 0, sizeof(cred)); 121 122 if (handle->principal == NULL) { 123 kret = krb5_get_default_principal(context, &handle->principal); 124 if (kret) 125 goto end; 126 } 127 kret = krb5_get_init_creds_opt_alloc(context, &opt); 128 if (kret) 129 goto end; 130 131 /* 132 * Get the current time before the AS exchange so we don't 133 * accidentally end up returning a value that puts advertised 134 * expiration past the real expiration. 135 * 136 * We need to do this because krb5_cc_get_lifetime() returns a 137 * relative time that we need to add to the current time. We ought 138 * to have a version of krb5_cc_get_lifetime() that returns absolute 139 * time... 140 */ 141 krb5_timeofday(context, &now); 142 143 kret = krb5_get_init_creds_password(context, &cred, handle->principal, 144 password, NULL, NULL, 0, NULL, opt); 145 krb5_get_init_creds_opt_free(context, opt); 146 if (kret) 147 goto end; 148 149 kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache); 150 if (kret) 151 goto end; 152 153 kret = krb5_cc_initialize(context, ccache, cred.client); 154 if (kret) 155 goto end; 156 157 kret = krb5_cc_store_cred(context, ccache, &cred); 158 if (kret) 159 goto end; 160 161 handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; 162 163 ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache, 164 handle->principal, &left); 165 if (ret != GSS_S_COMPLETE) 166 goto end; 167 handle->endtime = now + left; 168 handle->ccache = ccache; 169 ccache = NULL; 170 ret = GSS_S_COMPLETE; 171 kret = 0; 172 173 end: 174 if (ccache != NULL) 175 krb5_cc_destroy(context, ccache); 176 if (cred.client != NULL) 177 krb5_free_cred_contents(context, &cred); 178 if (ret != GSS_S_COMPLETE && kret != 0) 179 *minor_status = kret; 180 return (ret); 181 } 182 183 /* 184 * Acquires an initiator credential from a ccache or using a keytab. 185 */ 186 static OM_uint32 187 acquire_initiator_cred(OM_uint32 *minor_status, 188 krb5_context context, 189 OM_uint32 time_req, 190 gss_const_OID desired_mech, 191 gss_cred_usage_t cred_usage, 192 gsskrb5_cred handle) 193 { 194 OM_uint32 ret = GSS_S_FAILURE; 195 krb5_creds cred; 196 krb5_get_init_creds_opt *opt; 197 krb5_principal def_princ = NULL; 198 krb5_ccache def_ccache = NULL; 199 krb5_ccache ccache = NULL; /* we may store into this ccache */ 200 krb5_keytab keytab = NULL; 201 krb5_error_code kret = 0; 202 OM_uint32 left; 203 time_t lifetime = 0; 204 time_t now; 205 206 memset(&cred, 0, sizeof(cred)); 207 208 /* 209 * Get current time early so we can set handle->endtime to a value that 210 * cannot accidentally be past the real endtime. We need a variant of 211 * krb5_cc_get_lifetime() that returns absolute endtime. 212 */ 213 krb5_timeofday(context, &now); 214 215 /* 216 * First look for a ccache that has the desired_name (which may be 217 * the default credential name). 218 * 219 * If we don't have an unexpired credential, acquire one with a 220 * keytab. 221 * 222 * If we acquire one with a keytab, save it in the ccache we found 223 * with the expired credential, if any. 224 * 225 * If we don't have any such ccache, then use a MEMORY ccache. 226 */ 227 228 if (handle->principal != NULL) { 229 /* 230 * Not default credential case. See if we can find a ccache in 231 * the cccol for the desired_name. 232 */ 233 kret = krb5_cc_cache_match(context, 234 handle->principal, 235 &ccache); 236 if (kret == 0) { 237 kret = krb5_cc_get_lifetime(context, ccache, &lifetime); 238 if (kret == 0) { 239 if (lifetime > 0) 240 goto found; 241 else 242 goto try_keytab; 243 } 244 } 245 /* 246 * Fall through. We shouldn't find this in the default ccache 247 * either, but we'll give it a try, then we'll try using a keytab. 248 */ 249 } 250 251 /* 252 * Either desired_name was GSS_C_NO_NAME (default cred) or 253 * krb5_cc_cache_match() failed (or found expired). 254 */ 255 kret = krb5_cc_default(context, &def_ccache); 256 if (kret != 0) 257 goto try_keytab; 258 kret = krb5_cc_get_lifetime(context, def_ccache, &lifetime); 259 if (kret != 0) 260 lifetime = 0; 261 kret = krb5_cc_get_principal(context, def_ccache, &def_princ); 262 if (kret != 0) 263 goto try_keytab; 264 /* 265 * Have a default ccache; see if it matches desired_name. 266 */ 267 if (handle->principal == NULL || 268 krb5_principal_compare(context, handle->principal, 269 def_princ) == TRUE) { 270 /* 271 * It matches. 272 * 273 * If we end up trying a keytab then we can write the result to 274 * the default ccache. 275 */ 276 if (handle->principal == NULL) { 277 kret = krb5_copy_principal(context, def_princ, &handle->principal); 278 if (kret) 279 goto end; 280 } 281 if (ccache != NULL) 282 krb5_cc_close(context, ccache); 283 ccache = def_ccache; 284 def_ccache = NULL; 285 if (lifetime > 0) 286 goto found; 287 /* else we fall through and try using a keytab */ 288 } 289 290 try_keytab: 291 if (handle->principal == NULL) { 292 /* We need to know what client principal to use */ 293 kret = krb5_get_default_principal(context, &handle->principal); 294 if (kret) 295 goto end; 296 } 297 kret = get_keytab(context, &keytab); 298 if (kret) 299 goto end; 300 301 kret = krb5_get_init_creds_opt_alloc(context, &opt); 302 if (kret) 303 goto end; 304 krb5_timeofday(context, &now); 305 kret = krb5_get_init_creds_keytab(context, &cred, handle->principal, 306 keytab, 0, NULL, opt); 307 krb5_get_init_creds_opt_free(context, opt); 308 if (kret) 309 goto end; 310 311 /* 312 * We got a credential with a keytab. Save it if we can. 313 */ 314 if (ccache == NULL) { 315 /* 316 * There's no ccache we can overwrite with the credentials we acquired 317 * with a keytab. We'll use a MEMORY ccache then. 318 * 319 * Note that an application that falls into this repeatedly will do an 320 * AS exchange every time it acquires a credential handle. Hopefully 321 * this doesn't happen much. A workaround is to kinit -k once so that 322 * we always re-initialize the matched/default ccache here. I.e., once 323 * there's a FILE/DIR ccache, we'll keep it frash automatically if we 324 * have a keytab, but if there's no FILE/DIR ccache, then we'll 325 * get a fresh credential *every* time we're asked. 326 */ 327 kret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, &ccache); 328 if (kret) 329 goto end; 330 handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; 331 } /* else we'll re-initialize whichever ccache we matched above */ 332 333 kret = krb5_cc_initialize(context, ccache, cred.client); 334 if (kret) 335 goto end; 336 kret = krb5_cc_store_cred(context, ccache, &cred); 337 if (kret) 338 goto end; 339 340 found: 341 assert(handle->principal != NULL); 342 ret = __gsskrb5_ccache_lifetime(minor_status, context, ccache, 343 handle->principal, &left); 344 if (ret != GSS_S_COMPLETE) 345 goto end; 346 handle->endtime = now + left; 347 handle->ccache = ccache; 348 ccache = NULL; 349 ret = GSS_S_COMPLETE; 350 kret = 0; 351 352 end: 353 if (ccache != NULL) { 354 if ((handle->cred_flags & GSS_CF_DESTROY_CRED_ON_RELEASE) != 0) 355 krb5_cc_destroy(context, ccache); 356 else 357 krb5_cc_close(context, ccache); 358 } 359 if (def_ccache != NULL) 360 krb5_cc_close(context, def_ccache); 361 if (cred.client != NULL) 362 krb5_free_cred_contents(context, &cred); 363 if (def_princ != NULL) 364 krb5_free_principal(context, def_princ); 365 if (keytab != NULL) 366 krb5_kt_close(context, keytab); 367 if (ret != GSS_S_COMPLETE && kret != 0) 368 *minor_status = kret; 369 return (ret); 370 } 371 372 static OM_uint32 373 acquire_acceptor_cred(OM_uint32 * minor_status, 374 krb5_context context, 375 OM_uint32 time_req, 376 gss_const_OID desired_mech, 377 gss_cred_usage_t cred_usage, 378 gsskrb5_cred handle) 379 { 380 OM_uint32 ret; 381 krb5_error_code kret; 382 383 ret = GSS_S_FAILURE; 384 385 kret = get_keytab(context, &handle->keytab); 386 if (kret) 387 goto end; 388 389 /* check that the requested principal exists in the keytab */ 390 if (handle->principal) { 391 krb5_keytab_entry entry; 392 393 kret = krb5_kt_get_entry(context, handle->keytab, 394 handle->principal, 0, 0, &entry); 395 if (kret) 396 goto end; 397 krb5_kt_free_entry(context, &entry); 398 ret = GSS_S_COMPLETE; 399 } else { 400 /* 401 * Check if there is at least one entry in the keytab before 402 * declaring it as an useful keytab. 403 */ 404 krb5_keytab_entry tmp; 405 krb5_kt_cursor c; 406 407 kret = krb5_kt_start_seq_get (context, handle->keytab, &c); 408 if (kret) 409 goto end; 410 if (krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) { 411 krb5_kt_free_entry(context, &tmp); 412 ret = GSS_S_COMPLETE; /* ok found one entry */ 413 } 414 krb5_kt_end_seq_get (context, handle->keytab, &c); 415 } 416 end: 417 if (ret != GSS_S_COMPLETE) { 418 if (handle->keytab != NULL) 419 krb5_kt_close(context, handle->keytab); 420 if (kret != 0) { 421 *minor_status = kret; 422 } 423 } 424 return (ret); 425 } 426 427 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred 428 (OM_uint32 * minor_status, 429 gss_const_name_t desired_name, 430 OM_uint32 time_req, 431 const gss_OID_set desired_mechs, 432 gss_cred_usage_t cred_usage, 433 gss_cred_id_t * output_cred_handle, 434 gss_OID_set * actual_mechs, 435 OM_uint32 * time_rec 436 ) 437 { 438 OM_uint32 ret; 439 440 if (desired_mechs) { 441 int present = 0; 442 443 ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM, 444 desired_mechs, &present); 445 if (ret) 446 return ret; 447 if (!present) { 448 *minor_status = 0; 449 return GSS_S_BAD_MECH; 450 } 451 } 452 453 ret = _gsskrb5_acquire_cred_ext(minor_status, 454 desired_name, 455 GSS_C_NO_OID, 456 NULL, 457 time_req, 458 GSS_KRB5_MECHANISM, 459 cred_usage, 460 output_cred_handle); 461 if (ret) 462 return ret; 463 464 465 ret = _gsskrb5_inquire_cred(minor_status, *output_cred_handle, 466 NULL, time_rec, NULL, actual_mechs); 467 if (ret) { 468 OM_uint32 tmp; 469 _gsskrb5_release_cred(&tmp, output_cred_handle); 470 } 471 472 return ret; 473 } 474 475 OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext 476 (OM_uint32 * minor_status, 477 gss_const_name_t desired_name, 478 gss_const_OID credential_type, 479 const void *credential_data, 480 OM_uint32 time_req, 481 gss_const_OID desired_mech, 482 gss_cred_usage_t cred_usage, 483 gss_cred_id_t * output_cred_handle 484 ) 485 { 486 krb5_context context; 487 gsskrb5_cred handle; 488 OM_uint32 ret; 489 490 cred_usage &= GSS_C_OPTION_MASK; 491 492 if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE && 493 cred_usage != GSS_C_BOTH) { 494 *minor_status = GSS_KRB5_S_G_BAD_USAGE; 495 return GSS_S_FAILURE; 496 } 497 498 GSSAPI_KRB5_INIT(&context); 499 500 *output_cred_handle = GSS_C_NO_CREDENTIAL; 501 502 handle = calloc(1, sizeof(*handle)); 503 if (handle == NULL) { 504 *minor_status = ENOMEM; 505 return GSS_S_FAILURE; 506 } 507 508 HEIMDAL_MUTEX_init(&handle->cred_id_mutex); 509 510 if (desired_name != GSS_C_NO_NAME) { 511 ret = _gsskrb5_canon_name(minor_status, context, 512 desired_name, &handle->principal); 513 if (ret) { 514 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 515 free(handle); 516 return ret; 517 } 518 } 519 520 if (credential_type != GSS_C_NO_OID && 521 gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) { 522 /* Acquire a cred with a password */ 523 gss_const_buffer_t pwbuf = credential_data; 524 char *pw; 525 526 if (pwbuf == NULL) { 527 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 528 free(handle); 529 *minor_status = KRB5_NOCREDS_SUPPLIED; /* see below */ 530 return GSS_S_CALL_INACCESSIBLE_READ; 531 } 532 533 /* NUL-terminate the password, if it wasn't already */ 534 pw = strndup(pwbuf->value, pwbuf->length); 535 if (pw == NULL) { 536 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 537 free(handle); 538 *minor_status = krb5_enomem(context); 539 return GSS_S_CALL_INACCESSIBLE_READ; 540 } 541 ret = acquire_cred_with_password(minor_status, context, pw, time_req, 542 desired_mech, cred_usage, handle); 543 free(pw); 544 if (ret != GSS_S_COMPLETE) { 545 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 546 krb5_free_principal(context, handle->principal); 547 free(handle); 548 return (ret); 549 } 550 } else if (credential_type != GSS_C_NO_OID) { 551 /* 552 * _gss_acquire_cred_ext() called with something other than a password. 553 * 554 * Not supported. 555 * 556 * _gss_acquire_cred_ext() is not a supported public interface, so 557 * we don't have to try too hard as to minor status codes here. 558 */ 559 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 560 free(handle); 561 *minor_status = ENOTSUP; 562 return GSS_S_FAILURE; 563 } else { 564 /* 565 * Acquire a credential from the background credential store (ccache, 566 * keytab). 567 */ 568 if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) { 569 ret = acquire_initiator_cred(minor_status, context, time_req, 570 desired_mech, cred_usage, handle); 571 if (ret != GSS_S_COMPLETE) { 572 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 573 krb5_free_principal(context, handle->principal); 574 free(handle); 575 return (ret); 576 } 577 } 578 if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) { 579 ret = acquire_acceptor_cred(minor_status, context, time_req, 580 desired_mech, cred_usage, handle); 581 if (ret != GSS_S_COMPLETE) { 582 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 583 krb5_free_principal(context, handle->principal); 584 free(handle); 585 return (ret); 586 } 587 } 588 } 589 ret = gss_create_empty_oid_set(minor_status, &handle->mechanisms); 590 if (ret == GSS_S_COMPLETE) 591 ret = gss_add_oid_set_member(minor_status, GSS_KRB5_MECHANISM, 592 &handle->mechanisms); 593 if (ret != GSS_S_COMPLETE) { 594 if (handle->mechanisms != NULL) 595 gss_release_oid_set(NULL, &handle->mechanisms); 596 HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex); 597 krb5_free_principal(context, handle->principal); 598 free(handle); 599 return (ret); 600 } 601 handle->usage = cred_usage; 602 *minor_status = 0; 603 *output_cred_handle = (gss_cred_id_t)handle; 604 return (GSS_S_COMPLETE); 605 } 606