1/* 2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2009 - 2010 Apple Inc. 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 "krb5_locl.h" 37#include <assert.h> 38 39/* 40 * Take the `body' and encode it into `padata' using the credentials 41 * in `creds'. 42 */ 43 44static krb5_error_code 45make_pa_tgs_req(krb5_context context, 46 krb5_auth_context ac, 47 KDC_REQ_BODY *body, 48 krb5_ccache ccache, 49 krb5_creds *creds, 50 krb5_data *tgs_req) 51{ 52 krb5_error_code ret; 53 krb5_data in_data; 54 size_t buf_size; 55 size_t len = 0; 56 uint8_t *buf; 57 58 ASN1_MALLOC_ENCODE(KDC_REQ_BODY, buf, buf_size, body, &len, ret); 59 if (ret) 60 return ret; 61 62 if(buf_size != len) 63 krb5_abortx(context, "internal error in ASN.1 encoder"); 64 65 in_data.length = len; 66 in_data.data = buf; 67 ret = _krb5_mk_req_internal(context, &ac, 0, &in_data, 68 ccache, creds, 69 tgs_req, 70 KRB5_KU_TGS_REQ_AUTH_CKSUM, 71 KRB5_KU_TGS_REQ_AUTH); 72 free (buf); 73 return ret; 74} 75 76/* 77 * Set the `enc-authorization-data' in `req_body' based on `authdata' 78 */ 79 80static krb5_error_code 81set_auth_data (krb5_context context, 82 KDC_REQ_BODY *req_body, 83 krb5_authdata *authdata, 84 krb5_keyblock *subkey) 85{ 86 if(authdata->len) { 87 size_t len = 0, buf_size; 88 unsigned char *buf; 89 krb5_crypto crypto; 90 krb5_error_code ret; 91 92 ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, authdata, 93 &len, ret); 94 if (ret) 95 return ret; 96 if (buf_size != len) 97 krb5_abortx(context, "internal error in ASN.1 encoder"); 98 99 ALLOC(req_body->enc_authorization_data, 1); 100 if (req_body->enc_authorization_data == NULL) { 101 free (buf); 102 krb5_set_error_message(context, ENOMEM, 103 N_("malloc: out of memory", "")); 104 return ENOMEM; 105 } 106 ret = krb5_crypto_init(context, subkey, 0, &crypto); 107 if (ret) { 108 free (buf); 109 free (req_body->enc_authorization_data); 110 req_body->enc_authorization_data = NULL; 111 return ret; 112 } 113 krb5_encrypt_EncryptedData(context, 114 crypto, 115 KRB5_KU_TGS_REQ_AUTH_DAT_SUBKEY, 116 buf, 117 len, 118 0, 119 req_body->enc_authorization_data); 120 free (buf); 121 krb5_crypto_destroy(context, crypto); 122 } else { 123 req_body->enc_authorization_data = NULL; 124 } 125 return 0; 126} 127 128/* 129 * Create a tgs-req in `t' with `addresses', `flags', `second_ticket' 130 * (if not-NULL), `in_creds', `krbtgt', and returning the generated 131 * subkey in `subkey'. 132 */ 133 134krb5_error_code 135_krb5_init_tgs_req(krb5_context context, 136 krb5_ccache ccache, 137 struct krb5_fast_state *state, 138 krb5_addresses *addresses, 139 krb5_kdc_flags flags, 140 krb5_const_principal impersonate_principal, 141 Ticket *second_ticket, 142 krb5_creds *in_creds, 143 krb5_creds *krbtgt, 144 unsigned nonce, 145 METHOD_DATA *padata, 146 krb5_keyblock **subkey, 147 TGS_REQ *t) 148{ 149 krb5_auth_context ac = NULL; 150 krb5_error_code ret = 0; 151 krb5_data tgs_req; 152 153 memset(t, 0, sizeof(*t)); 154 krb5_data_zero(&tgs_req); 155 156 /* inherit the forwardable/proxyable flags from the krbtgt */ 157 flags.b.forwardable = krbtgt->flags.b.forwardable; 158 flags.b.proxiable = krbtgt->flags.b.proxiable; 159 160 if (ccache->ops->tgt_req) { 161 KERB_TGS_REQ_OUT out; 162 KERB_TGS_REQ_IN in; 163 164 memset(&in, 0, sizeof(in)); 165 memset(&out, 0, sizeof(out)); 166 167 ret = ccache->ops->tgt_req(context, ccache, &in, &out); 168 if (ret) 169 return ret; 170 171 free_KERB_TGS_REQ_OUT(&out); 172 return 0; 173 } 174 175 176 if (impersonate_principal) { 177 krb5_crypto crypto; 178 PA_S4U2Self self; 179 krb5_data data; 180 void *buf; 181 size_t size = 0, len; 182 183 self.name = impersonate_principal->name; 184 self.realm = impersonate_principal->realm; 185 self.auth = rk_UNCONST("Kerberos"); 186 187 ret = _krb5_s4u2self_to_checksumdata(context, &self, &data); 188 if (ret) 189 goto fail; 190 191 ret = krb5_crypto_init(context, &krbtgt->session, 0, &crypto); 192 if (ret) { 193 krb5_data_free(&data); 194 goto fail; 195 } 196 197 ret = krb5_create_checksum(context, 198 crypto, 199 KRB5_KU_OTHER_CKSUM, 200 0, 201 data.data, 202 data.length, 203 &self.cksum); 204 krb5_crypto_destroy(context, crypto); 205 krb5_data_free(&data); 206 if (ret) 207 goto fail; 208 209 ASN1_MALLOC_ENCODE(PA_S4U2Self, buf, len, &self, &size, ret); 210 free_Checksum(&self.cksum); 211 if (ret) 212 goto fail; 213 if (len != size) 214 krb5_abortx(context, "internal asn1 error"); 215 216 ret = krb5_padata_add(context, padata, KRB5_PADATA_FOR_USER, buf, len); 217 if (ret) 218 goto fail; 219 } 220 221 t->pvno = 5; 222 t->msg_type = krb_tgs_req; 223 if (in_creds->session.keytype) { 224 ALLOC_SEQ(&t->req_body.etype, 1); 225 if(t->req_body.etype.val == NULL) { 226 ret = ENOMEM; 227 krb5_set_error_message(context, ret, 228 N_("malloc: out of memory", "")); 229 goto fail; 230 } 231 t->req_body.etype.val[0] = in_creds->session.keytype; 232 } else { 233 ret = _krb5_init_etype(context, 234 KRB5_PDU_TGS_REQUEST, 235 &t->req_body.etype.len, 236 &t->req_body.etype.val, 237 NULL); 238 } 239 if (ret) 240 goto fail; 241 t->req_body.addresses = addresses; 242 t->req_body.kdc_options = flags.b; 243 ret = copy_Realm(&in_creds->server->realm, &t->req_body.realm); 244 if (ret) 245 goto fail; 246 ALLOC(t->req_body.sname, 1); 247 if (t->req_body.sname == NULL) { 248 ret = ENOMEM; 249 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 250 goto fail; 251 } 252 253 /* some versions of some code might require that the client be 254 present in TGS-REQs, but this is clearly against the spec */ 255 256 ret = copy_PrincipalName(&in_creds->server->name, t->req_body.sname); 257 if (ret) 258 goto fail; 259 260 /* req_body.till should be NULL if there is no endtime specified, 261 but old MIT code (like DCE secd) doesn't like that */ 262 ALLOC(t->req_body.till, 1); 263 if(t->req_body.till == NULL){ 264 ret = ENOMEM; 265 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 266 goto fail; 267 } 268 *t->req_body.till = in_creds->times.endtime; 269 270 t->req_body.nonce = nonce; 271 if(second_ticket){ 272 ALLOC(t->req_body.additional_tickets, 1); 273 if (t->req_body.additional_tickets == NULL) { 274 ret = ENOMEM; 275 krb5_set_error_message(context, ret, 276 N_("malloc: out of memory", "")); 277 goto fail; 278 } 279 ALLOC_SEQ(t->req_body.additional_tickets, 1); 280 if (t->req_body.additional_tickets->val == NULL) { 281 ret = ENOMEM; 282 krb5_set_error_message(context, ret, 283 N_("malloc: out of memory", "")); 284 goto fail; 285 } 286 ret = copy_Ticket(second_ticket, t->req_body.additional_tickets->val); 287 if (ret) 288 goto fail; 289 } 290 291 if (padata) { 292 if (t->padata == NULL) { 293 ALLOC(t->padata, 1); 294 if (t->padata == NULL) { 295 ret = krb5_enomem(context); 296 goto fail; 297 } 298 } 299 300 ret = copy_METHOD_DATA(padata, t->padata); 301 if (ret) 302 goto fail; 303 } 304 305 ret = krb5_auth_con_init(context, &ac); 306 if(ret) 307 goto fail; 308 309 ret = krb5_auth_con_generatelocalsubkey(context, ac, &krbtgt->session); 310 if (ret) 311 goto fail; 312 313 ret = set_auth_data(context, &t->req_body, 314 &in_creds->authdata, ac->local_subkey); 315 if (ret) 316 goto fail; 317 318 if (t->padata == NULL) { 319 ALLOC(t->padata, 1); 320 if (t->padata == NULL) { 321 ret = krb5_enomem(context); 322 goto fail; 323 } 324 } 325 326 ret = make_pa_tgs_req(context, 327 ac, 328 &t->req_body, 329 ccache, 330 krbtgt, 331 &tgs_req); 332 if(ret) 333 goto fail; 334 335 if (state) { 336 337 state->armor_ac = ac; 338 ret = _krb5_fast_create_armor(context, state, NULL); 339 state->armor_ac = NULL; 340 if (ret) 341 goto fail; 342 343 ret = _krb5_fast_wrap_req(context, state, &tgs_req, t); 344 if (ret) 345 goto fail; 346 347 /* Its ok if there is no fast in the TGS-REP, older heimdal only support it in the AS code path */ 348 state->flags &= ~KRB5_FAST_EXPECTED; 349 } 350 351 ret = krb5_padata_add(context, t->padata, KRB5_PADATA_TGS_REQ, 352 tgs_req.data, tgs_req.length); 353 if (ret) 354 goto fail; 355 356 krb5_data_zero(&tgs_req); 357 358 ret = krb5_auth_con_getlocalsubkey(context, ac, subkey); 359 if (ret) 360 goto fail; 361 362fail: 363 if (ac) 364 krb5_auth_con_free(context, ac); 365 if (ret) { 366 t->req_body.addresses = NULL; 367 free_TGS_REQ(t); 368 } 369 krb5_data_free(&tgs_req); 370 371 return ret; 372} 373 374/* DCE compatible decrypt proc */ 375krb5_error_code KRB5_CALLCONV 376_krb5_decrypt_tkt_with_subkey (krb5_context context, 377 krb5_keyblock *key, 378 krb5_key_usage usage, 379 krb5_const_pointer skey, 380 krb5_kdc_rep *dec_rep) 381{ 382 struct krb5_decrypt_tkt_with_subkey_state *state; 383 krb5_error_code ret = 0; 384 krb5_data data; 385 size_t size; 386 krb5_crypto crypto; 387 krb5_keyblock extract_key; 388 389 state = (struct krb5_decrypt_tkt_with_subkey_state *)skey; 390 391 assert(usage == 0); 392 393 krb5_data_zero(&data); 394 krb5_keyblock_zero(&extract_key); 395 396 /* 397 * start out with trying with subkey if we have one 398 */ 399 if (state->subkey) { 400 401 ret = _krb5_fast_tgs_strengthen_key(context, state->fast_state, state->subkey, &extract_key); 402 if (ret) 403 return ret; 404 405 ret = krb5_crypto_init(context, &extract_key, 0, &crypto); 406 krb5_free_keyblock_contents(context, &extract_key); 407 if (ret) 408 return ret; 409 410 ret = krb5_decrypt_EncryptedData (context, 411 crypto, 412 KRB5_KU_TGS_REP_ENC_PART_SUB_KEY, 413 &dec_rep->kdc_rep.enc_part, 414 &data); 415 /* 416 * If the is Windows 2000 DC, we need to retry with key usage 417 * 8 when doing ARCFOUR. 418 */ 419 if (ret && state->subkey->keytype == ETYPE_ARCFOUR_HMAC_MD5) { 420 ret = krb5_decrypt_EncryptedData(context, 421 crypto, 422 8, 423 &dec_rep->kdc_rep.enc_part, 424 &data); 425 } 426 krb5_crypto_destroy(context, crypto); 427 } 428 if (state->subkey == NULL || ret) { 429 430 ret = _krb5_fast_tgs_strengthen_key(context, state->fast_state, key, &extract_key); 431 if (ret) 432 return ret; 433 434 ret = krb5_crypto_init(context, &extract_key, 0, &crypto); 435 krb5_free_keyblock_contents(context, &extract_key); 436 if (ret) 437 return ret; 438 ret = krb5_decrypt_EncryptedData (context, 439 crypto, 440 KRB5_KU_TGS_REP_ENC_PART_SESSION, 441 &dec_rep->kdc_rep.enc_part, 442 &data); 443 krb5_crypto_destroy(context, crypto); 444 } 445 if (ret) 446 return ret; 447 448 ret = decode_EncASRepPart(data.data, 449 data.length, 450 &dec_rep->enc_part, 451 &size); 452 if (ret) 453 ret = decode_EncTGSRepPart(data.data, 454 data.length, 455 &dec_rep->enc_part, 456 &size); 457 if (ret) 458 krb5_set_error_message(context, ret, 459 N_("Failed to decode encpart in ticket", "")); 460 krb5_data_free (&data); 461 return ret; 462} 463 464static int 465not_found(krb5_context context, krb5_const_principal p, krb5_error_code code) 466{ 467 krb5_error_code ret; 468 char *str; 469 470 ret = krb5_unparse_name(context, p, &str); 471 if(ret) { 472 krb5_clear_error_message(context); 473 return code; 474 } 475 krb5_set_error_message(context, code, 476 N_("Matching credential (%s) not found", ""), str); 477 free(str); 478 return code; 479} 480 481static krb5_error_code 482find_cred(krb5_context context, 483 krb5_ccache id, 484 krb5_principal server, 485 krb5_creds **tgts, 486 krb5_creds *out_creds) 487{ 488 krb5_error_code ret; 489 krb5_creds mcreds; 490 491 krb5_cc_clear_mcred(&mcreds); 492 mcreds.server = server; 493 ret = krb5_cc_retrieve_cred(context, id, KRB5_TC_DONT_MATCH_REALM, 494 &mcreds, out_creds); 495 if(ret == 0) 496 return 0; 497 while(tgts && *tgts){ 498 if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM, 499 &mcreds, *tgts)){ 500 ret = krb5_copy_creds_contents(context, *tgts, out_creds); 501 return ret; 502 } 503 tgts++; 504 } 505 return not_found(context, server, KRB5_CC_NOTFOUND); 506} 507 508static krb5_error_code 509add_cred(krb5_context context, krb5_creds const *tkt, krb5_creds ***tgts) 510{ 511 int i; 512 krb5_error_code ret; 513 krb5_creds **tmp = *tgts; 514 515 for(i = 0; tmp && tmp[i]; i++); /* XXX */ 516 tmp = realloc(tmp, (i+2)*sizeof(*tmp)); 517 if(tmp == NULL) { 518 krb5_set_error_message(context, ENOMEM, 519 N_("malloc: out of memory", "")); 520 return ENOMEM; 521 } 522 *tgts = tmp; 523 ret = krb5_copy_creds(context, tkt, &tmp[i]); 524 tmp[i+1] = NULL; 525 return ret; 526} 527 528/* 529 * Store all credentials that seems not to be _the_ krbtgt 530 * credential. 531 */ 532 533static void 534store_tgts(krb5_context context, krb5_ccache ccache, krb5_creds **tgts) 535{ 536 size_t n; 537 538 for (n = 0; tgts && tgts[n]; n++) { 539 krb5_const_principal server = tgts[n]->server; 540 541 if (krb5_principal_is_krbtgt(context, server) && strcmp(server->name.name_string.val[1], server->realm) != 0) 542 krb5_cc_store_cred(context, ccache, tgts[n]); 543 } 544 for (n = 0; tgts && tgts[n]; n++) 545 krb5_free_creds(context, tgts[n]); 546} 547 548/* 549 * 550 */ 551 552typedef krb5_error_code 553(*tkt_step_state)(krb5_context, krb5_tkt_creds_context, 554 krb5_data *, krb5_data *, krb5_realm *, unsigned int *); 555 556 557struct krb5_tkt_creds_context_data { 558 struct heim_base_uniq base; 559 krb5_context context; 560 tkt_step_state state; 561 unsigned int options; 562 char *server_name; 563 krb5_error_code error; 564 565 /* input data */ 566 krb5_kdc_flags req_kdc_flags; 567 krb5_principal impersonate_principal; /* s4u2(self) */ 568 krb5_addresses *addreseses; 569 krb5_ccache ccache; 570 krb5_creds *in_cred; 571 572 /* current tgs state, reset/free with tkt_reset() */ 573 krb5_kdc_flags kdc_flags; 574 int32_t nonce; 575 krb5_keyblock *subkey; 576 krb5_creds tgt; 577 krb5_creds next; /* next name we are going to fetch */ 578 krb5_creds **tickets; 579 int ok_as_delegate; 580 581 struct krb5_fast_state fast_state; 582 583 /* output */ 584 krb5_creds *cred; 585}; 586 587#define TKT_STEP(funcname) \ 588static krb5_error_code tkt_##funcname(krb5_context, \ 589 krb5_tkt_creds_context, \ 590 krb5_data *, krb5_data *, krb5_realm *, \ 591 unsigned int *) 592 593TKT_STEP(init); 594TKT_STEP(referral_init); 595TKT_STEP(referral_recv); 596TKT_STEP(referral_send); 597TKT_STEP(direct_init); 598TKT_STEP(capath_init); 599TKT_STEP(store); 600#undef TKT_STEP 601 602 603/* 604 * Setup state to transmit the first request and send the request 605 */ 606 607static krb5_error_code 608tkt_init(krb5_context context, 609 krb5_tkt_creds_context ctx, 610 krb5_data *in, 611 krb5_data *out, 612 krb5_realm *realm, 613 unsigned int *flags) 614{ 615 _krb5_debugx(context, 10, "tkt_init: %s", ctx->server_name); 616 ctx->state = tkt_referral_init; 617 return 0; 618} 619 620static void 621tkt_reset(krb5_context context, krb5_tkt_creds_context ctx) 622{ 623 _krb5_fast_free(context, &ctx->fast_state); 624 625 krb5_free_cred_contents(context, &ctx->tgt); 626 krb5_free_cred_contents(context, &ctx->next); 627 krb5_free_keyblock(context, ctx->subkey); 628 ctx->subkey = NULL; 629} 630 631/* 632 * Get 633 */ 634 635static krb5_error_code 636tkt_referral_init(krb5_context context, 637 krb5_tkt_creds_context ctx, 638 krb5_data *in, 639 krb5_data *out, 640 krb5_realm *realm, 641 unsigned int *flags) 642{ 643 krb5_error_code ret; 644 krb5_creds ticket; 645 krb5_const_realm client_realm; 646 krb5_principal tgtname; 647 648 _krb5_debugx(context, 10, "tkt_step_referrals: %s", ctx->server_name); 649 650 memset(&ticket, 0, sizeof(ticket)); 651 memset(&ctx->kdc_flags, 0, sizeof(ctx->kdc_flags)); 652 memset(&ctx->next, 0, sizeof(ctx->next)); 653 654 ctx->kdc_flags.b.canonicalize = 1; 655 656 client_realm = krb5_principal_get_realm(context, ctx->in_cred->client); 657 658 /* find tgt for the clients base realm */ 659 ret = krb5_make_principal(context, &tgtname, 660 client_realm, 661 KRB5_TGS_NAME, 662 client_realm, 663 NULL); 664 if(ret) 665 goto out; 666 667 ret = find_cred(context, ctx->ccache, tgtname, NULL, &ctx->tgt); 668 krb5_free_principal(context, tgtname); 669 if (ret) 670 goto out; 671 672 673 ret = krb5_copy_principal(context, ctx->in_cred->client, &ctx->next.client); 674 if (ret) 675 goto out; 676 677 ret = krb5_copy_principal(context, ctx->in_cred->server, &ctx->next.server); 678 if (ret) 679 goto out; 680 681 ret = krb5_principal_set_realm(context, ctx->next.server, ctx->tgt.server->realm); 682 if (ret) 683 goto out; 684 685 686 out: 687 if (ret) { 688 ctx->error = ret; 689 ctx->state = tkt_direct_init; 690 } else { 691 ctx->error = 0; 692 ctx->state = tkt_referral_send; 693 } 694 695 return 0; 696} 697 698static krb5_error_code 699tkt_referral_send(krb5_context context, 700 krb5_tkt_creds_context ctx, 701 krb5_data *in, 702 krb5_data *out, 703 krb5_realm *realm, 704 unsigned int *flags) 705{ 706 krb5_error_code ret; 707 TGS_REQ req; 708 size_t len = 0; 709 METHOD_DATA padata; 710 711 memset(&req, 0, sizeof(req)); 712 713 padata.val = NULL; 714 padata.len = 0; 715 716 krb5_generate_random_block(&ctx->nonce, sizeof(ctx->nonce)); 717 ctx->nonce &= 0xffffffff; 718 719 if (_krb5_have_debug(context, 10)) { 720 char *sname, *tgtname; 721 krb5_unparse_name(context, ctx->tgt.server, &tgtname); 722 krb5_unparse_name(context, ctx->next.server, &sname); 723 _krb5_debugx(context, 10, "sending TGS-REQ for %s using %s", sname, tgtname); 724 } 725 726 ret = _krb5_init_tgs_req(context, 727 ctx->ccache, 728 &ctx->fast_state, 729 ctx->addreseses, 730 ctx->kdc_flags, 731 ctx->impersonate_principal, 732 NULL, 733 &ctx->next, 734 &ctx->tgt, 735 ctx->nonce, 736 &padata, 737 &ctx->subkey, 738 &req); 739 if (ret) 740 goto out; 741 742 ASN1_MALLOC_ENCODE(TGS_REQ, out->data, out->length, &req, &len, ret); 743 if (ret) 744 goto out; 745 if(out->length != len) 746 krb5_abortx(context, "internal error in ASN.1 encoder"); 747 748 /* don't free addresses */ 749 req.req_body.addresses = NULL; 750 free_TGS_REQ(&req); 751 752 *realm = ctx->tgt.server->name.name_string.val[1]; 753 754 *flags |= KRB5_TKT_STATE_CONTINUE; 755 756 ctx->error = 0; 757 ctx->state = tkt_referral_recv; 758 759 return 0; 760 761out: 762 ctx->error = ret; 763 ctx->state = NULL; 764 return ret; 765} 766 767krb5_error_code 768_krb5_fast_tgs_strengthen_key(krb5_context context, 769 struct krb5_fast_state *state, 770 krb5_keyblock *reply_key, 771 krb5_keyblock *extract_key) 772{ 773 krb5_error_code ret; 774 775 if (state && state->strengthen_key) { 776 777 _krb5_debugx(context, 5, "_krb5_fast_tgs_strengthen_key"); 778 779 if (state->strengthen_key->keytype != reply_key->keytype) { 780 krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED, 781 N_("strengthen_key %d not same enctype as reply key %d", ""), 782 state->strengthen_key->keytype, reply_key->keytype); 783 return KRB5KRB_AP_ERR_MODIFIED; 784 } 785 786 _krb5_debug_keyblock(context, 10, "tkt: strengthen_key", state->strengthen_key); 787 _krb5_debug_keyblock(context, 10, "tkt: old reply_key", reply_key); 788 789 ret = _krb5_fast_cf2(context, 790 state->strengthen_key, 791 "strengthenkey", 792 reply_key, 793 "replykey", 794 extract_key, 795 NULL); 796 if (ret) 797 return ret; 798 } else { 799 ret = krb5_copy_keyblock_contents(context, reply_key, extract_key); 800 if (ret) 801 return ret; 802 } 803 804 _krb5_debug_keyblock(context, 10, "tkt: extract key", extract_key); 805 806 return 0; 807} 808 809static krb5_error_code 810parse_tgs_rep(krb5_context context, 811 krb5_tkt_creds_context ctx, 812 krb5_data *in, 813 krb5_creds *outcred) 814{ 815 krb5_error_code ret; 816 krb5_kdc_rep rep; 817 size_t len; 818 819 memset(&rep, 0, sizeof(rep)); 820 memset(outcred, 0, sizeof(*outcred)); 821 822 if (ctx->ccache->ops->tgt_rep) { 823 return EINVAL; 824 } 825 826 if(decode_TGS_REP(in->data, in->length, &rep.kdc_rep, &len) == 0) { 827 struct krb5_decrypt_tkt_with_subkey_state state; 828 unsigned eflags = 0; 829 830 ret = _krb5_fast_unwrap_kdc_rep(context, ctx->nonce, NULL, 831 &ctx->fast_state, &rep.kdc_rep); 832 if (ret) 833 return ret; 834 835 ret = krb5_copy_principal(context, 836 ctx->next.client, 837 &outcred->client); 838 if(ret) 839 return ret; 840 ret = krb5_copy_principal(context, 841 ctx->next.server, 842 &outcred->server); 843 if(ret) 844 return ret; 845 /* this should go someplace else */ 846 outcred->times.endtime = ctx->in_cred->times.endtime; 847 848#define constrained_delegation request_anonymous 849 if (ctx->kdc_flags.b.constrained_delegation || ctx->impersonate_principal) 850 eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH; 851#undef constrained_delegation 852 853 state.subkey = ctx->subkey; 854 state.fast_state = &ctx->fast_state; 855 856 ret = _krb5_extract_ticket(context, 857 &rep, 858 outcred, 859 &ctx->tgt.session, 860 0, 861 &ctx->tgt.addresses, 862 ctx->nonce, 863 eflags, 864 NULL, 865 _krb5_decrypt_tkt_with_subkey, 866 &state); 867 868 } else if(krb5_rd_error(context, in, &rep.error) == 0) { 869 METHOD_DATA md; 870 871 memset(&md, 0, sizeof(md)); 872 873 if (rep.error.e_data) { 874 ret = decode_METHOD_DATA(rep.error.e_data->data, 875 rep.error.e_data->length, 876 &md, NULL); 877 if (ret) { 878 krb5_set_error_message(context, ret, 879 N_("Failed to decode METHOD-DATA", "")); 880 goto out; 881 } 882 } 883 884 ret = _krb5_fast_unwrap_error(context, &ctx->fast_state, 885 &md, &rep.error); 886 free_METHOD_DATA(&md); 887 if (ret) 888 goto out; 889 890 ret = krb5_error_from_rd_error(context, &rep.error, ctx->in_cred); 891 892 /* log the failure */ 893 if (_krb5_have_debug(context, 5)) { 894 const char *str = krb5_get_error_message(context, ret); 895 _krb5_debugx(context, 5, "parse_tgs_rep: KRB-ERROR %d/%s", ret, str); 896 krb5_free_error_message(context, str); 897 } 898 899 } else { 900 ret = KRB5KRB_AP_ERR_MSG_TYPE; 901 krb5_clear_error_message(context); 902 } 903 out: 904 krb5_free_kdc_rep(context, &rep); 905 return ret; 906} 907 908 909static krb5_error_code 910tkt_referral_recv(krb5_context context, 911 krb5_tkt_creds_context ctx, 912 krb5_data *in, 913 krb5_data *out, 914 krb5_realm *realm, 915 unsigned int *flags) 916{ 917 krb5_error_code ret; 918 krb5_creds outcred, mcred; 919 unsigned long n; 920 921 _krb5_debugx(context, 10, "tkt_referral_recv: %s", ctx->server_name); 922 923 memset(&outcred, 0, sizeof(outcred)); 924 925 ret = parse_tgs_rep(context, ctx, in, &outcred); 926 if (ret) { 927 _krb5_debugx(context, 10, "tkt_referral_recv: parse_tgs_rep %d", ret); 928 tkt_reset(context, ctx); 929 ctx->state = tkt_capath_init; 930 return 0; 931 } 932 933 /* 934 * Check if we found the right ticket 935 */ 936 937 if (krb5_principal_compare_any_realm(context, ctx->next.server, outcred.server)) { 938 ret = krb5_copy_creds(context, &outcred, &ctx->cred); 939 if (ret) 940 return (ctx->error = ret); 941 krb5_free_cred_contents(context, &outcred); 942 ctx->state = tkt_store; 943 return 0; 944 } 945 946 if (!krb5_principal_is_krbtgt(context, outcred.server)) { 947 krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US, 948 N_("Got back an non krbtgt " 949 "ticket referrals", "")); 950 krb5_free_cred_contents(context, &outcred); 951 ctx->state = tkt_capath_init; 952 return 0; 953 } 954 955 _krb5_debugx(context, 10, "KDC for realm %s sends a referrals to %s", 956 ctx->tgt.server->realm, outcred.server->name.name_string.val[1]); 957 958 /* 959 * check if there is a loop 960 */ 961 krb5_cc_clear_mcred(&mcred); 962 mcred.server = outcred.server; 963 964 for (n = 0; ctx->tickets && ctx->tickets[n]; n++) { 965 if(krb5_compare_creds(context, 966 KRB5_TC_DONT_MATCH_REALM, 967 &mcred, 968 ctx->tickets[n])) 969 { 970 _krb5_debugx(context, 5, "Referral from %s loops back to realm %s", 971 ctx->tgt.server->realm, 972 outcred.server->realm); 973 ctx->state = tkt_capath_init; 974 return 0; 975 } 976 } 977#define MAX_KDC_REFERRALS_LOOPS 15 978 if (n > MAX_KDC_REFERRALS_LOOPS) { 979 ctx->state = tkt_capath_init; 980 return 0; 981 } 982 983 /* 984 * filter out ok-as-delegate if needed 985 */ 986 987 if (ctx->ok_as_delegate == 0 || outcred.flags.b.ok_as_delegate == 0) { 988 ctx->ok_as_delegate = 0; 989 outcred.flags.b.ok_as_delegate = 0; 990 } 991 992 /* add to iteration cache */ 993 ret = add_cred(context, &outcred, &ctx->tickets); 994 if (ret) { 995 ctx->state = tkt_capath_init; 996 return 0; 997 } 998 999 /* set up next server to talk to */ 1000 krb5_free_cred_contents(context, &ctx->tgt); 1001 ctx->tgt = outcred; 1002 1003 /* 1004 * Setup next target principal to target 1005 */ 1006 1007 ret = krb5_principal_set_realm(context, ctx->next.server, 1008 ctx->tgt.server->realm); 1009 if (ret) { 1010 ctx->state = tkt_capath_init; 1011 return 0; 1012 } 1013 1014 ctx->state = tkt_referral_send; 1015 1016 return 0; 1017} 1018 1019static krb5_error_code 1020tkt_direct_init(krb5_context context, 1021 krb5_tkt_creds_context ctx, 1022 krb5_data *in, 1023 krb5_data *out, 1024 krb5_realm *realm, 1025 unsigned int *flags) 1026{ 1027 _krb5_debugx(context, 10, "tkt_direct_init: %s", ctx->server_name); 1028 1029 tkt_reset(context, ctx); 1030 1031 ctx->error = EINVAL; 1032 ctx->state = tkt_capath_init; 1033 1034 return 0; 1035} 1036 1037static krb5_error_code 1038tkt_capath_init(krb5_context context, 1039 krb5_tkt_creds_context ctx, 1040 krb5_data *in, 1041 krb5_data *out, 1042 krb5_realm *realm, 1043 unsigned int *flags) 1044{ 1045 _krb5_debugx(context, 10, "tkt_step_capath: %s", ctx->server_name); 1046 1047 tkt_reset(context, ctx); 1048 1049 ctx->error = EINVAL; 1050 ctx->state = NULL; 1051 1052 return 0; 1053} 1054 1055 1056static krb5_error_code 1057tkt_store(krb5_context context, 1058 krb5_tkt_creds_context ctx, 1059 krb5_data *in, 1060 krb5_data *out, 1061 krb5_realm *realm, 1062 unsigned int *flags) 1063{ 1064 krb5_boolean bret; 1065 1066 _krb5_debugx(context, 10, "tkt_step_store: %s", ctx->server_name); 1067 1068 ctx->error = 0; 1069 ctx->state = NULL; 1070 1071 if (ctx->options & KRB5_GC_NO_STORE) 1072 return 0; 1073 1074 if (ctx->tickets) { 1075 store_tgts(context, ctx->ccache, ctx->tickets); 1076 free(ctx->tickets); 1077 ctx->tickets = NULL; 1078 } 1079 1080 heim_assert(ctx->cred != NULL, "store but no credential"); 1081 1082 krb5_cc_store_cred(context, ctx->ccache, ctx->cred); 1083 /* 1084 * Store an referrals entry since the server changed from that 1085 * expected and if we want to find it again next time, it 1086 * better have the right name. 1087 * 1088 * We only need to compare any realm since the referrals 1089 * matching code will do the same for us. 1090 */ 1091 bret = krb5_principal_compare_any_realm(context, 1092 ctx->cred->server, 1093 ctx->in_cred->server); 1094 if (!bret) { 1095 krb5_creds ref = *ctx->cred; 1096 krb5_principal_data refp = *ctx->in_cred->server; 1097 refp.realm = ""; 1098 ref.server = &refp; 1099 krb5_cc_store_cred(context, ctx->ccache, &ref); 1100 } 1101 1102 return 0; 1103} 1104 1105 1106static void 1107tkt_release(void *ptr) 1108{ 1109 krb5_tkt_creds_context ctx = ptr; 1110 krb5_free_creds(ctx->context, ctx->cred); 1111 tkt_reset(ctx->context, ctx); 1112 if (ctx->tickets) { 1113 size_t n; 1114 for (n = 0; ctx->tickets[n]; n++) 1115 krb5_free_creds(ctx->context, ctx->tickets[n]); 1116 free(ctx->tickets); 1117 } 1118 free(ctx->server_name); 1119} 1120 1121/** 1122 * Create a context for a credential fetching process 1123 */ 1124 1125KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1126krb5_tkt_creds_init(krb5_context context, 1127 krb5_ccache ccache, 1128 krb5_creds *in_cred, 1129 krb5_flags options, 1130 krb5_tkt_creds_context *pctx) 1131{ 1132 krb5_tkt_creds_context ctx; 1133 krb5_error_code ret; 1134 1135 *pctx = NULL; 1136 1137 ctx = heim_uniq_alloc(sizeof(*ctx), "tkt-ctx", tkt_release); 1138 if (ctx == NULL) 1139 return ENOMEM; 1140 1141 ctx->context = context; 1142 ctx->state = tkt_init; 1143 ctx->options = options; 1144 ctx->ccache = ccache; 1145 1146 if (ctx->options & KRB5_GC_FORWARDABLE) 1147 ctx->req_kdc_flags.b.forwardable = 1; 1148 if (ctx->options & KRB5_GC_USER_USER) { 1149 ctx->req_kdc_flags.b.enc_tkt_in_skey = 1; 1150 ctx->options |= KRB5_GC_NO_STORE; 1151 } 1152 if (options & KRB5_GC_CANONICALIZE) 1153 ctx->req_kdc_flags.b.canonicalize = 1; 1154 1155 ret = krb5_copy_creds(context, in_cred, &ctx->in_cred); 1156 if (ret) { 1157 heim_release(ctx); 1158 return ret; 1159 } 1160 1161 ret = krb5_unparse_name(context, ctx->in_cred->server, &ctx->server_name); 1162 if (ret) { 1163 heim_release(ctx); 1164 return ret; 1165 } 1166 1167 *pctx = ctx; 1168 1169 return 0; 1170} 1171 1172/** 1173 * Step though next step in the TGS-REP process 1174 * 1175 * Pointer returned in realm is valid to next call to 1176 * krb5_tkt_creds_step() or krb5_tkt_creds_free(). 1177 */ 1178 1179KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1180krb5_tkt_creds_step(krb5_context context, 1181 krb5_tkt_creds_context ctx, 1182 krb5_data *in, 1183 krb5_data *out, 1184 krb5_realm *realm, 1185 unsigned int *flags) 1186{ 1187 krb5_error_code ret; 1188 1189 krb5_data_zero(out); 1190 *flags = 0; 1191 *realm = NULL; 1192 1193 ret = ctx->error = 0; 1194 1195 while (out->length == 0 && ctx->state != NULL) { 1196 ret = ctx->state(context, ctx, in, out, realm, flags); 1197 if (ret) { 1198 heim_assert(ctx->error == ret, "error not same as saved"); 1199 break; 1200 } 1201 1202 if ((*flags) & KRB5_TKT_STATE_CONTINUE) { 1203 heim_assert(out->length != 0, "no data to send to KDC"); 1204 heim_assert(*realm != NULL, "no realm to send data too"); 1205 break; 1206 } else { 1207 heim_assert(out->length == 0, "out state but not state continue"); 1208 } 1209 } 1210 1211 return ret; 1212} 1213 1214/** 1215 * 1216 */ 1217 1218KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1219krb5_tkt_creds_get_creds(krb5_context context, 1220 krb5_tkt_creds_context ctx, 1221 krb5_creds **cred) 1222{ 1223 if (ctx->state != NULL) 1224 return EINVAL; 1225 1226 if (ctx->cred) 1227 return krb5_copy_creds(context, ctx->cred, cred); 1228 1229 return ctx->error; 1230} 1231 1232/** 1233 * 1234 */ 1235 1236KRB5_LIB_FUNCTION void KRB5_LIB_CALL 1237krb5_tkt_creds_free(krb5_context context, 1238 krb5_tkt_creds_context ctx) 1239{ 1240 heim_release(ctx); 1241} 1242