1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* kdc/kdc_authdata.c - Authorization data routines for the KDC */ 3 /* 4 * Copyright (C) 2007 Apple Inc. All Rights Reserved. 5 * Copyright (C) 2008, 2009 by the Massachusetts Institute of Technology. 6 * 7 * Export of this software from the United States of America may 8 * require a specific license from the United States Government. 9 * It is the responsibility of any person or organization contemplating 10 * export to obtain such a license before exporting. 11 * 12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 13 * distribute this software and its documentation for any purpose and 14 * without fee is hereby granted, provided that the above copyright 15 * notice appear in all copies and that both that copyright notice and 16 * this permission notice appear in supporting documentation, and that 17 * the name of M.I.T. not be used in advertising or publicity pertaining 18 * to distribution of the software without specific, written prior 19 * permission. Furthermore if you modify this software you must label 20 * your software as modified software and not distribute it in such a 21 * fashion that it might be confused with the original M.I.T. software. 22 * M.I.T. makes no representations about the suitability of 23 * this software for any purpose. It is provided "as is" without express 24 * or implied warranty. 25 */ 26 27 #include "k5-int.h" 28 #include "kdc_util.h" 29 #include "extern.h" 30 #include <stdio.h> 31 #include "adm_proto.h" 32 33 #include <syslog.h> 34 35 #include <assert.h> 36 #include <krb5/kdcauthdata_plugin.h> 37 38 typedef struct kdcauthdata_handle_st { 39 struct krb5_kdcauthdata_vtable_st vt; 40 krb5_kdcauthdata_moddata data; 41 } kdcauthdata_handle; 42 43 static kdcauthdata_handle *authdata_modules; 44 static size_t n_authdata_modules; 45 46 /* Load authdata plugin modules. */ 47 krb5_error_code 48 load_authdata_plugins(krb5_context context) 49 { 50 krb5_error_code ret; 51 krb5_plugin_initvt_fn *modules = NULL, *mod; 52 kdcauthdata_handle *list, *h; 53 size_t count; 54 55 ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_KDCAUTHDATA, &modules); 56 if (ret) 57 return ret; 58 59 /* Allocate a large enough list of handles. */ 60 for (count = 0; modules[count] != NULL; count++); 61 list = calloc(count + 1, sizeof(*list)); 62 if (list == NULL) { 63 k5_plugin_free_modules(context, modules); 64 return ENOMEM; 65 } 66 67 /* Initialize each module's vtable and module data. */ 68 count = 0; 69 for (mod = modules; *mod != NULL; mod++) { 70 h = &list[count]; 71 memset(h, 0, sizeof(*h)); 72 ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt); 73 if (ret) /* Version mismatch, keep going. */ 74 continue; 75 if (h->vt.init != NULL) { 76 ret = h->vt.init(context, &h->data); 77 if (ret) { 78 kdc_err(context, ret, _("while loading authdata module %s"), 79 h->vt.name); 80 continue; 81 } 82 } 83 count++; 84 } 85 86 authdata_modules = list; 87 n_authdata_modules = count; 88 k5_plugin_free_modules(context, modules); 89 return 0; 90 } 91 92 krb5_error_code 93 unload_authdata_plugins(krb5_context context) 94 { 95 kdcauthdata_handle *h; 96 size_t i; 97 98 for (i = 0; i < n_authdata_modules; i++) { 99 h = &authdata_modules[i]; 100 if (h->vt.fini != NULL) 101 h->vt.fini(context, h->data); 102 } 103 free(authdata_modules); 104 authdata_modules = NULL; 105 return 0; 106 } 107 108 /* Return true if authdata should be filtered when copying from untrusted 109 * authdata. If desired_type is non-zero, look only for that type. */ 110 static krb5_boolean 111 is_kdc_issued_authdatum(krb5_authdata *authdata, 112 krb5_authdatatype desired_type) 113 { 114 krb5_boolean result = FALSE; 115 krb5_authdatatype ad_type; 116 unsigned int i, count = 0; 117 krb5_authdatatype *ad_types, *containee_types = NULL; 118 119 if (authdata->ad_type == KRB5_AUTHDATA_IF_RELEVANT) { 120 if (krb5int_get_authdata_containee_types(NULL, authdata, &count, 121 &containee_types) != 0) 122 goto cleanup; 123 ad_types = containee_types; 124 } else { 125 ad_type = authdata->ad_type; 126 count = 1; 127 ad_types = &ad_type; 128 } 129 130 for (i = 0; i < count; i++) { 131 switch (ad_types[i]) { 132 case KRB5_AUTHDATA_SIGNTICKET: 133 case KRB5_AUTHDATA_KDC_ISSUED: 134 case KRB5_AUTHDATA_WIN2K_PAC: 135 case KRB5_AUTHDATA_CAMMAC: 136 case KRB5_AUTHDATA_AUTH_INDICATOR: 137 result = desired_type ? (desired_type == ad_types[i]) : TRUE; 138 break; 139 default: 140 result = FALSE; 141 break; 142 } 143 if (result) 144 break; 145 } 146 147 cleanup: 148 free(containee_types); 149 return result; 150 } 151 152 /* Return true if authdata contains any mandatory-for-KDC elements. */ 153 static krb5_boolean 154 has_mandatory_for_kdc_authdata(krb5_context context, krb5_authdata **authdata) 155 { 156 int i; 157 158 if (authdata == NULL) 159 return FALSE; 160 for (i = 0; authdata[i] != NULL; i++) { 161 if (authdata[i]->ad_type == KRB5_AUTHDATA_MANDATORY_FOR_KDC) 162 return TRUE; 163 } 164 return FALSE; 165 } 166 167 /* Add elements from *new_elements to *existing_list, reallocating as 168 * necessary. On success, release *new_elements and set it to NULL. */ 169 static krb5_error_code 170 merge_authdata(krb5_authdata ***existing_list, krb5_authdata ***new_elements) 171 { 172 size_t count = 0, ncount = 0; 173 krb5_authdata **list = *existing_list, **nlist = *new_elements; 174 175 if (nlist == NULL) 176 return 0; 177 178 for (count = 0; list != NULL && list[count] != NULL; count++); 179 for (ncount = 0; nlist[ncount] != NULL; ncount++); 180 181 list = realloc(list, (count + ncount + 1) * sizeof(*list)); 182 if (list == NULL) 183 return ENOMEM; 184 185 memcpy(list + count, nlist, ncount * sizeof(*nlist)); 186 list[count + ncount] = NULL; 187 free(nlist); 188 189 if (list[0] == NULL) { 190 free(list); 191 list = NULL; 192 } 193 194 *new_elements = NULL; 195 *existing_list = list; 196 return 0; 197 } 198 199 /* Add a copy of new_elements to *existing_list, omitting KDC-issued 200 * authdata. */ 201 static krb5_error_code 202 add_filtered_authdata(krb5_authdata ***existing_list, 203 krb5_authdata **new_elements) 204 { 205 krb5_error_code ret; 206 krb5_authdata **copy; 207 size_t i, j; 208 209 if (new_elements == NULL) 210 return 0; 211 212 ret = krb5_copy_authdata(NULL, new_elements, ©); 213 if (ret) 214 return ret; 215 216 /* Remove KDC-issued elements from copy. */ 217 j = 0; 218 for (i = 0; copy[i] != NULL; i++) { 219 if (is_kdc_issued_authdatum(copy[i], 0)) { 220 free(copy[i]->contents); 221 free(copy[i]); 222 } else { 223 copy[j++] = copy[i]; 224 } 225 } 226 copy[j] = NULL; 227 228 /* Destructively merge the filtered copy into existing_list. */ 229 ret = merge_authdata(existing_list, ©); 230 krb5_free_authdata(NULL, copy); 231 return ret; 232 } 233 234 /* Copy TGS-REQ authorization data into the ticket authdata. */ 235 static krb5_error_code 236 copy_request_authdata(krb5_context context, krb5_keyblock *client_key, 237 krb5_kdc_req *req, krb5_enc_tkt_part *enc_tkt_req, 238 krb5_authdata ***tkt_authdata) 239 { 240 krb5_error_code ret; 241 krb5_data plaintext; 242 243 assert(enc_tkt_req != NULL); 244 245 ret = alloc_data(&plaintext, req->authorization_data.ciphertext.length); 246 if (ret) 247 return ret; 248 249 /* 250 * RFC 4120 requires authdata in the TGS body to be encrypted in the subkey 251 * with usage 5 if a subkey is present, and in the TGS session key with key 252 * usage 4 if it is not. Prior to krb5 1.7, we got this wrong, always 253 * decrypting the authorization data with the TGS session key and usage 4. 254 * For the sake of conservatism, try the decryption the old way (wrong if 255 * client_key is a subkey) first, and then try again the right way (in the 256 * case where client_key is a subkey) if the first way fails. 257 */ 258 ret = krb5_c_decrypt(context, enc_tkt_req->session, 259 KRB5_KEYUSAGE_TGS_REQ_AD_SESSKEY, 0, 260 &req->authorization_data, &plaintext); 261 if (ret) { 262 ret = krb5_c_decrypt(context, client_key, 263 KRB5_KEYUSAGE_TGS_REQ_AD_SUBKEY, 0, 264 &req->authorization_data, &plaintext); 265 } 266 if (ret) 267 goto cleanup; 268 269 /* Decode the decrypted authdata and make it available to modules in the 270 * request. */ 271 ret = decode_krb5_authdata(&plaintext, &req->unenc_authdata); 272 if (ret) 273 goto cleanup; 274 275 if (has_mandatory_for_kdc_authdata(context, req->unenc_authdata)) { 276 ret = KRB5KDC_ERR_POLICY; 277 goto cleanup; 278 } 279 280 ret = add_filtered_authdata(tkt_authdata, req->unenc_authdata); 281 282 cleanup: 283 free(plaintext.data); 284 return ret; 285 } 286 287 /* Copy TGT authorization data into the ticket authdata. */ 288 static krb5_error_code 289 copy_tgt_authdata(krb5_context context, krb5_kdc_req *request, 290 krb5_authdata **tgt_authdata, krb5_authdata ***tkt_authdata) 291 { 292 if (has_mandatory_for_kdc_authdata(context, tgt_authdata)) 293 return KRB5KDC_ERR_POLICY; 294 295 return add_filtered_authdata(tkt_authdata, tgt_authdata); 296 } 297 298 /* Add authentication indicator authdata to enc_tkt_reply, wrapped in a CAMMAC 299 * and an IF-RELEVANT container. */ 300 static krb5_error_code 301 add_auth_indicators(krb5_context context, krb5_data *const *auth_indicators, 302 krb5_keyblock *server_key, krb5_db_entry *krbtgt, 303 krb5_keyblock *krbtgt_key, 304 krb5_enc_tkt_part *enc_tkt_reply) 305 { 306 krb5_error_code ret; 307 krb5_data *der_indicators = NULL; 308 krb5_authdata ad, *list[2], **cammac = NULL; 309 310 if (auth_indicators == NULL || *auth_indicators == NULL) 311 return 0; 312 313 /* Format the authentication indicators into an authdata list. */ 314 ret = encode_utf8_strings(auth_indicators, &der_indicators); 315 if (ret) 316 goto cleanup; 317 ad.ad_type = KRB5_AUTHDATA_AUTH_INDICATOR; 318 ad.length = der_indicators->length; 319 ad.contents = (uint8_t *)der_indicators->data; 320 list[0] = &ad; 321 list[1] = NULL; 322 323 /* Wrap the list in CAMMAC and IF-RELEVANT containers. */ 324 ret = cammac_create(context, enc_tkt_reply, server_key, krbtgt, krbtgt_key, 325 list, &cammac); 326 if (ret) 327 goto cleanup; 328 329 /* Add the wrapped authdata to the ticket, without copying or filtering. */ 330 ret = merge_authdata(&enc_tkt_reply->authorization_data, &cammac); 331 332 cleanup: 333 krb5_free_data(context, der_indicators); 334 krb5_free_authdata(context, cammac); 335 return ret; 336 } 337 338 /* Extract any properly verified authentication indicators from the authdata in 339 * enc_tkt. */ 340 krb5_error_code 341 get_auth_indicators(krb5_context context, krb5_enc_tkt_part *enc_tkt, 342 krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key, 343 krb5_data ***indicators_out) 344 { 345 krb5_error_code ret; 346 krb5_authdata **cammacs = NULL, **adp; 347 krb5_cammac *cammac = NULL; 348 krb5_data **indicators = NULL, der_cammac; 349 350 *indicators_out = NULL; 351 352 ret = krb5_find_authdata(context, enc_tkt->authorization_data, NULL, 353 KRB5_AUTHDATA_CAMMAC, &cammacs); 354 if (ret) 355 goto cleanup; 356 357 for (adp = cammacs; adp != NULL && *adp != NULL; adp++) { 358 der_cammac = make_data((*adp)->contents, (*adp)->length); 359 ret = decode_krb5_cammac(&der_cammac, &cammac); 360 if (ret) 361 goto cleanup; 362 if (cammac_check_kdcver(context, cammac, enc_tkt, local_tgt, 363 local_tgt_key)) { 364 ret = authind_extract(context, cammac->elements, &indicators); 365 if (ret) 366 goto cleanup; 367 } 368 k5_free_cammac(context, cammac); 369 cammac = NULL; 370 } 371 372 *indicators_out = indicators; 373 indicators = NULL; 374 375 cleanup: 376 krb5_free_authdata(context, cammacs); 377 k5_free_cammac(context, cammac); 378 k5_free_data_ptr_list(indicators); 379 return ret; 380 } 381 382 static krb5_error_code 383 update_delegation_info(krb5_context context, krb5_kdc_req *req, 384 krb5_pac old_pac, krb5_pac new_pac) 385 { 386 krb5_error_code ret; 387 krb5_data ndr_di_in = empty_data(), ndr_di_out = empty_data(); 388 struct pac_s4u_delegation_info *di = NULL; 389 char *namestr = NULL; 390 391 ret = krb5_pac_get_buffer(context, old_pac, KRB5_PAC_DELEGATION_INFO, 392 &ndr_di_in); 393 if (ret && ret != ENOENT) 394 goto cleanup; 395 if (ret) { 396 /* Create new delegation info. */ 397 di = k5alloc(sizeof(*di), &ret); 398 if (di == NULL) 399 goto cleanup; 400 di->transited_services = k5calloc(1, sizeof(char *), &ret); 401 if (di->transited_services == NULL) 402 goto cleanup; 403 } else { 404 /* Decode and modify old delegation info. */ 405 ret = ndr_dec_delegation_info(&ndr_di_in, &di); 406 if (ret) 407 goto cleanup; 408 } 409 410 /* Set proxy_target to the requested server, without realm. */ 411 ret = krb5_unparse_name_flags(context, req->server, 412 KRB5_PRINCIPAL_UNPARSE_DISPLAY | 413 KRB5_PRINCIPAL_UNPARSE_NO_REALM, 414 &namestr); 415 if (ret) 416 goto cleanup; 417 free(di->proxy_target); 418 di->proxy_target = namestr; 419 420 /* Add a transited entry for the requesting service, with realm. */ 421 assert(req->second_ticket != NULL && req->second_ticket[0] != NULL); 422 ret = krb5_unparse_name(context, req->second_ticket[0]->server, &namestr); 423 if (ret) 424 goto cleanup; 425 di->transited_services[di->transited_services_length++] = namestr; 426 427 ret = ndr_enc_delegation_info(di, &ndr_di_out); 428 if (ret) 429 goto cleanup; 430 431 ret = krb5_pac_add_buffer(context, new_pac, KRB5_PAC_DELEGATION_INFO, 432 &ndr_di_out); 433 434 cleanup: 435 krb5_free_data_contents(context, &ndr_di_in); 436 krb5_free_data_contents(context, &ndr_di_out); 437 ndr_free_delegation_info(di); 438 return ret; 439 } 440 441 static krb5_error_code 442 copy_pac_buffer(krb5_context context, uint32_t buffer_type, krb5_pac old_pac, 443 krb5_pac new_pac) 444 { 445 krb5_error_code ret; 446 krb5_data data; 447 448 ret = krb5_pac_get_buffer(context, old_pac, buffer_type, &data); 449 if (ret) 450 return ret; 451 ret = krb5_pac_add_buffer(context, new_pac, buffer_type, &data); 452 krb5_free_data_contents(context, &data); 453 return ret; 454 } 455 456 /* 457 * Possibly add a signed PAC to enc_tkt_reply. Also possibly add auth 458 * indicators; these are handled here so that the KDB module's issue_pac() 459 * method can alter the auth indicator list. 460 */ 461 static krb5_error_code 462 handle_pac(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client, 463 krb5_db_entry *server, krb5_db_entry *subject_server, 464 krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key, 465 krb5_keyblock *server_key, krb5_keyblock *subject_key, 466 krb5_keyblock *replaced_reply_key, krb5_enc_tkt_part *subject_tkt, 467 krb5_pac subject_pac, krb5_kdc_req *req, 468 krb5_const_principal altcprinc, krb5_timestamp authtime, 469 krb5_enc_tkt_part *enc_tkt_reply, krb5_data ***auth_indicators) 470 { 471 krb5_context context = realm->realm_context; 472 krb5_error_code ret; 473 krb5_pac new_pac = NULL; 474 krb5_const_principal pac_client = NULL; 475 krb5_boolean with_realm, is_as_req = (req->msg_type == KRB5_AS_REQ); 476 krb5_db_entry *signing_tgt; 477 krb5_keyblock *privsvr_key = NULL; 478 479 /* Don't add a PAC or auth indicators if the server disables authdata. */ 480 if (server->attributes & KRB5_KDB_NO_AUTH_DATA_REQUIRED) 481 return 0; 482 483 /* 484 * Don't add a PAC if the realm disables them, or to an anonymous ticket, 485 * or for an AS-REQ if the client requested not to get one, or for a 486 * TGS-REQ if the subject ticket didn't contain one. 487 */ 488 if (realm->realm_disable_pac || 489 (enc_tkt_reply->flags & TKT_FLG_ANONYMOUS) || 490 (is_as_req && !include_pac_p(context, req)) || 491 (!is_as_req && subject_pac == NULL)) { 492 return add_auth_indicators(context, *auth_indicators, server_key, 493 local_tgt, local_tgt_key, enc_tkt_reply); 494 } 495 496 ret = krb5_pac_init(context, &new_pac); 497 if (ret) 498 goto cleanup; 499 500 if (subject_pac == NULL) 501 signing_tgt = NULL; 502 else if (krb5_is_tgs_principal(subject_server->princ)) 503 signing_tgt = subject_server; 504 else 505 signing_tgt = local_tgt; 506 507 ret = krb5_db_issue_pac(context, flags, client, replaced_reply_key, server, 508 signing_tgt, authtime, subject_pac, new_pac, 509 auth_indicators); 510 if (ret) { 511 if (ret == KRB5_PLUGIN_OP_NOTSUPP) 512 ret = 0; 513 if (ret) 514 goto cleanup; 515 } 516 517 ret = add_auth_indicators(context, *auth_indicators, server_key, 518 local_tgt, local_tgt_key, enc_tkt_reply); 519 520 if ((flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) && 521 !(flags & KRB5_KDB_FLAG_CROSS_REALM)) { 522 /* Add delegation info for the first S4U2Proxy request. */ 523 ret = update_delegation_info(context, req, subject_pac, new_pac); 524 if (ret) 525 goto cleanup; 526 } else if (subject_pac != NULL) { 527 /* Copy delegation info if it was present in the subject PAC. */ 528 ret = copy_pac_buffer(context, KRB5_PAC_DELEGATION_INFO, subject_pac, 529 new_pac); 530 if (ret && ret != ENOENT) 531 goto cleanup; 532 } 533 534 if ((flags & KRB5_KDB_FLAGS_S4U) && 535 (flags & KRB5_KDB_FLAG_ISSUING_REFERRAL)) { 536 /* When issuing a referral for either kind of S4U request, add client 537 * info for the subject with realm. */ 538 pac_client = altcprinc; 539 with_realm = TRUE; 540 } else if (subject_pac == NULL || (flags & KRB5_KDB_FLAGS_S4U)) { 541 /* For a new PAC or when issuing a final ticket for either kind of S4U 542 * request, add client info for the ticket client without the realm. */ 543 pac_client = enc_tkt_reply->client; 544 with_realm = FALSE; 545 } else { 546 /* 547 * For regular TGS and transitive RBCD requests, copy the client info 548 * from the incoming PAC, and don't add client info during signing. We 549 * validated the incoming client info in validate_tgs_request(). 550 */ 551 ret = copy_pac_buffer(context, KRB5_PAC_CLIENT_INFO, subject_pac, 552 new_pac); 553 if (ret) 554 goto cleanup; 555 pac_client = NULL; 556 with_realm = FALSE; 557 } 558 559 ret = pac_privsvr_key(context, server, local_tgt_key, &privsvr_key); 560 if (ret) 561 goto cleanup; 562 ret = krb5_kdc_sign_ticket(context, enc_tkt_reply, new_pac, server->princ, 563 pac_client, server_key, privsvr_key, 564 with_realm); 565 if (ret) 566 goto cleanup; 567 568 ret = 0; 569 570 cleanup: 571 krb5_pac_free(context, new_pac); 572 krb5_free_keyblock(context, privsvr_key); 573 return ret; 574 } 575 576 krb5_error_code 577 handle_authdata(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client, 578 krb5_db_entry *server, krb5_db_entry *subject_server, 579 krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key, 580 krb5_keyblock *client_key, krb5_keyblock *server_key, 581 krb5_keyblock *subject_key, krb5_keyblock *replaced_reply_key, 582 krb5_data *req_pkt, krb5_kdc_req *req, 583 krb5_const_principal altcprinc, krb5_pac subject_pac, 584 krb5_enc_tkt_part *enc_tkt_req, krb5_data ***auth_indicators, 585 krb5_enc_tkt_part *enc_tkt_reply) 586 { 587 krb5_context context = realm->realm_context; 588 kdcauthdata_handle *h; 589 krb5_error_code ret = 0; 590 size_t i; 591 592 if (req->msg_type == KRB5_TGS_REQ && 593 req->authorization_data.ciphertext.data != NULL) { 594 /* Copy TGS request authdata. This must be done first so that modules 595 * have access to the unencrypted request authdata. */ 596 ret = copy_request_authdata(context, client_key, req, enc_tkt_req, 597 &enc_tkt_reply->authorization_data); 598 if (ret) 599 return ret; 600 } 601 602 /* Invoke loaded module handlers. */ 603 if (!isflagset(enc_tkt_reply->flags, TKT_FLG_ANONYMOUS)) { 604 for (i = 0; i < n_authdata_modules; i++) { 605 h = &authdata_modules[i]; 606 ret = h->vt.handle(context, h->data, flags, client, server, 607 subject_server, client_key, server_key, 608 subject_key, req_pkt, req, altcprinc, 609 enc_tkt_req, enc_tkt_reply); 610 if (ret) 611 kdc_err(context, ret, "from authdata module %s", h->vt.name); 612 } 613 } 614 615 if (req->msg_type == KRB5_TGS_REQ) { 616 /* Copy authdata from the TGT to the issued ticket. */ 617 ret = copy_tgt_authdata(context, req, enc_tkt_req->authorization_data, 618 &enc_tkt_reply->authorization_data); 619 if (ret) 620 return ret; 621 } 622 623 return handle_pac(realm, flags, client, server, subject_server, local_tgt, 624 local_tgt_key, server_key, subject_key, 625 replaced_reply_key, enc_tkt_req, subject_pac, req, 626 altcprinc, enc_tkt_reply->times.authtime, enc_tkt_reply, 627 auth_indicators); 628 } 629