1/* 2 * Copyright (c) 2010 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 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 38#ifdef __APPLE__ 39 40#include <CoreFoundation/CoreFoundation.h> 41#include <Security/Security.h> 42#include <uuid/uuid.h> 43 44/* 45 * kSecAttrDescription 46 * 'Kerberos ticket for %@' 47 * kSecAttrServer 48 * the name of the credentials cache 49 * kSecAttrCreator 50 * 'GSSL' 51 * kSecAttrType 52 * 'iTGT' 53 * 'oTGT' 54 * kSecAttrLabel 55 * 'Kerberos ticket for %@' 56 * kSecAttrIsInvisible 57 * 'Yes' 58 * kSecAttrIsNegative 59 * set on the keychain that is default 60 * kSecAttrAccount 61 * The service principal name 62 * kSecAttrService 63 * 'GSS-API' 64 * kSecAttrGeneric 65 * iTGT 66 * kdc offset: int32_t 67 * oTGT 68 * 'The kerberos ticket' 69 */ 70 71 72typedef struct krb5_kcache{ 73 char *name; 74 CFStringRef cname; 75 int version; 76}krb5_kcache; 77 78#define KCACHE(X) ((krb5_kcache*)(X)->data.data) 79 80struct kcc_cursor { 81 CFArrayRef array; 82 CFIndex offset; 83}; 84 85static CFTypeRef kGSSiTGT; 86static CFTypeRef kGSSoTGT; 87static CFTypeRef kGSSCreator; 88#ifndef __APPLE_TARGET_EMBEDDED__ 89static SecAccessRef kGSSAnyAccess; 90#endif 91 92static void 93create_constants(void) 94{ 95 static dispatch_once_t once; 96 dispatch_once(&once, ^{ 97 int32_t num; 98 99 num = 'iTGT'; 100 kGSSiTGT = CFNumberCreate(NULL, kCFNumberSInt32Type, &num); 101 num = 'oTGT'; 102 kGSSoTGT = CFNumberCreate(NULL, kCFNumberSInt32Type, &num); 103 num = 'GSSL'; 104 kGSSCreator = CFNumberCreate(NULL, kCFNumberSInt32Type, &num); 105 106#ifndef __APPLE_TARGET_EMBEDDED__ 107 OSStatus ret; 108 109 ret = SecAccessCreate(CFSTR("GSS-credential"), NULL, &kGSSAnyAccess); 110 if (ret) 111 return; 112 113 SecKeychainPromptSelector promptSelector; 114 CFStringRef promptDescription = NULL; 115 CFArrayRef appList = NULL, aclList = NULL; 116 SecACLRef acl; 117 118 aclList = SecAccessCopyMatchingACLList(kGSSAnyAccess, kSecACLAuthorizationDecrypt); 119 if (aclList == NULL) 120 return; 121 122 if (CFArrayGetCount(aclList) < 1) { 123 CFRelease(aclList); 124 return; 125 } 126 127 acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0); 128 129 ret = SecACLCopyContents(acl, &appList, &promptDescription, &promptSelector); 130 if (ret == 0) { 131 promptSelector &= ~kSecKeychainPromptRequirePassphase; 132 (void)SecACLSetContents(acl, NULL, promptDescription, promptSelector); 133 } 134 135 CFRelease(promptDescription); 136 CFRelease(appList); 137 CFRelease(aclList); 138#endif 139 }); 140} 141 142static void 143delete_type(krb5_context context, krb5_kcache *k) 144{ 145#ifndef __APPLE_TARGET_EMBEDDED__ 146#define USE_DELETE 1 /* rdar://8736785 */ 147#endif 148 149 const void *keys[] = { 150#ifdef USE_DELETE 151 kSecReturnRef, 152#endif 153 kSecClass, 154 kSecAttrCreator, 155 kSecAttrServer, 156 }; 157 const void *values[] = { 158#ifdef USE_DELETE 159 kCFBooleanTrue, 160#endif 161 kSecClassInternetPassword, 162 kGSSCreator, 163 k->cname, 164 }; 165 CFDictionaryRef query; 166 OSStatus ret; 167 168 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), NULL, NULL); 169 heim_assert(query != NULL, "out of memory"); 170 171#ifdef USE_DELETE 172 CFArrayRef array; 173 174 ret = SecItemCopyMatching(query, (CFTypeRef *)&array); 175 if (ret == 0) { 176 CFIndex n, count = CFArrayGetCount(array); 177 178 for (n = 0; n < count; n++) 179 SecKeychainItemDelete((SecKeychainItemRef)CFArrayGetValueAtIndex(array, n)); 180 CFRelease(array); 181 } 182 183#else 184 ret = SecItemDelete(query); 185 if (ret) 186 _krb5_debugx(context, 5, "failed to delete credential %s: %d\n", k->name, (int)ret); 187#endif 188 CFRelease(query); 189} 190 191static CFDictionaryRef 192CreateSecItemFromCache(krb5_kcache *k) 193{ 194 CFDictionaryRef result = NULL, query = NULL; 195 OSStatus ret; 196 197 create_constants(); 198 199 const void *keys[] = { 200 kSecClass, 201 kSecReturnAttributes, 202 kSecAttrType, 203 kSecAttrServer 204 }; 205 const void *values[] = { 206 kSecClassInternetPassword, 207 kCFBooleanTrue, 208 kGSSiTGT, 209 k->cname 210 }; 211 212 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), 213 &kCFTypeDictionaryKeyCallBacks, 214 &kCFTypeDictionaryValueCallBacks); 215 heim_assert(query != NULL, "out of memory"); 216 217 ret = SecItemCopyMatching(query, (CFTypeRef *)&result); 218 CFRelease(query); 219 if (ret) 220 return NULL; 221 222 return result; 223} 224 225static const char* 226kcc_get_name(krb5_context context, 227 krb5_ccache id) 228{ 229 krb5_kcache *k = KCACHE(id); 230 return k->name; 231} 232 233static krb5_error_code 234kcc_resolve(krb5_context context, krb5_ccache *id, const char *res) 235{ 236 krb5_kcache *k; 237 238 k = malloc(sizeof(*k)); 239 if (k == NULL) { 240 krb5_set_error_message(context, KRB5_CC_NOMEM, 241 N_("malloc: out of memory", "")); 242 return KRB5_CC_NOMEM; 243 } 244 k->name = strdup(res); 245 if (k->name == NULL){ 246 free(k); 247 krb5_set_error_message(context, KRB5_CC_NOMEM, 248 N_("malloc: out of memory", "")); 249 return KRB5_CC_NOMEM; 250 } 251 k->cname = CFStringCreateWithCString(NULL, k->name, kCFStringEncodingUTF8); 252 if (k->cname == NULL) { 253 free(k->name); 254 free(k); 255 krb5_set_error_message(context, KRB5_CC_NOMEM, 256 N_("malloc: out of memory", "")); 257 return KRB5_CC_NOMEM; 258 259 } 260 (*id)->data.data = k; 261 (*id)->data.length = sizeof(*k); 262 return 0; 263} 264 265static krb5_error_code 266kcc_gen_new(krb5_context context, krb5_ccache *id) 267{ 268 krb5_kcache *k; 269 uuid_t uuid; 270 uuid_string_t uuidstr; 271 272 k = malloc(sizeof(*k)); 273 if (k == NULL) { 274 krb5_set_error_message(context, KRB5_CC_NOMEM, 275 N_("malloc: out of memory", "")); 276 return KRB5_CC_NOMEM; 277 } 278 279 uuid_generate_random(uuid); 280 uuid_unparse(uuid, uuidstr); 281 282 k->name = strdup(uuidstr); 283 if (k->name == NULL) { 284 free(k); 285 krb5_set_error_message(context, KRB5_CC_NOMEM, 286 N_("malloc: out of memory", "")); 287 return KRB5_CC_NOMEM; 288 } 289 290 k->cname = CFStringCreateWithCString(NULL, k->name, kCFStringEncodingUTF8); 291 if (k->cname == NULL) { 292 free(k->name); 293 free(k); 294 krb5_set_error_message(context, KRB5_CC_NOMEM, 295 N_("malloc: out of memory", "")); 296 return KRB5_CC_NOMEM; 297 298 } 299 300 (*id)->data.data = k; 301 (*id)->data.length = sizeof(*k); 302 return 0; 303} 304 305static CFStringRef 306CFStringCreateFromPrincipal(CFAllocatorRef alloc, krb5_context context, krb5_principal principal) 307{ 308 CFStringRef str; 309 char *p; 310 311 if (krb5_unparse_name(context, principal, &p) != 0) 312 return NULL; 313 314 str = CFStringCreateWithCString(alloc, p, kCFStringEncodingUTF8); 315 krb5_xfree(p); 316 317 return str; 318} 319 320 321static krb5_error_code 322kcc_initialize(krb5_context context, 323 krb5_ccache id, 324 krb5_principal primary_principal) 325{ 326 krb5_error_code ret = 0; 327 krb5_kcache *k = KCACHE(id); 328 CFDictionaryRef query; 329 CFTypeRef item = NULL; 330 CFStringRef principal = NULL; 331 CFStringRef label = NULL; 332 OSStatus osret; 333 334 create_constants(); 335 336 /* 337 delete all entries for 338 339 kSecAttrServer == k->name 340 */ 341 342 delete_type(context, k); 343 344 /* 345 add entry with 346 347 kSecAttrServer, k->name 348 kSecAttrLabel, "credential for %@", primary_principal 349 kSecAttrType, ITGT 350 kSecAttrAccount, primary_principal 351 */ 352 353 principal = CFStringCreateFromPrincipal(NULL, context, primary_principal); 354 if (principal == NULL) { 355 ret = krb5_enomem(context); 356 goto out; 357 } 358 359 label = CFStringCreateWithFormat(NULL, NULL, CFSTR("Kerberos credentials for %@"), principal); 360 if (label == NULL) { 361 ret = krb5_enomem(context); 362 goto out; 363 } 364 365 const void *add_keys[] = { 366 kSecAttrCreator, 367#ifdef __APPLE_TARGET_EMBEDDED__ 368 kSecAttrAccessible, 369#else 370 kSecAttrAccess, 371#endif 372 kSecClass, 373 kSecAttrType, 374 kSecAttrServer, 375 kSecAttrAccount, 376 kSecAttrLabel, 377 kSecAttrIsInvisible 378 }; 379 const void *add_values[] = { 380 kGSSCreator, 381#ifdef __APPLE_TARGET_EMBEDDED__ 382 kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 383#else 384 kGSSAnyAccess, 385#endif 386 kSecClassInternetPassword, 387 kGSSiTGT, 388 k->cname, 389 principal, 390 label, 391 kCFBooleanTrue 392 }; 393 394 query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL); 395 heim_assert(query != NULL, "out of memory"); 396 397 398 osret = SecItemAdd(query, &item); 399 CFRelease(query); 400 if (osret) { 401 _krb5_debugx(context, 5, "failed to store credential: %d\n", (int)osret); 402 ret = EINVAL; 403 krb5_set_error_message(context, ret, "failed to store credential: %d", (int)osret); 404 goto out; 405 } 406 407 out: 408 if (label) 409 CFRelease(label); 410 if (item) 411 CFRelease(item); 412 if (principal) 413 CFRelease(principal); 414 415 return ret; 416} 417 418static krb5_error_code 419kcc_close(krb5_context context, 420 krb5_ccache id) 421{ 422 krb5_kcache *k = KCACHE(id); 423 424 if (k->cname) 425 CFRelease(k->cname); 426 free(k->name); 427 krb5_data_free(&id->data); 428 return 0; 429} 430 431static krb5_error_code 432kcc_destroy(krb5_context context, 433 krb5_ccache id) 434{ 435 krb5_kcache *k = KCACHE(id); 436 437 create_constants(); 438 439 delete_type(context, k); 440 441 return 0; 442} 443 444static krb5_error_code 445kcc_store_cred(krb5_context context, 446 krb5_ccache id, 447 krb5_creds *creds) 448{ 449 krb5_kcache *k = KCACHE(id); 450 krb5_storage *sp = NULL; 451 CFDataRef dref = NULL; 452 krb5_data data; 453 CFStringRef principal = NULL; 454 CFStringRef label = NULL; 455 CFDictionaryRef query = NULL; 456 krb5_error_code ret; 457 CFTypeRef item = NULL; 458 459 create_constants(); 460 461 krb5_data_zero(&data); 462 463 sp = krb5_storage_emem(); 464 if (sp == NULL) { 465 ret = krb5_enomem(context); 466 goto out; 467 } 468 469 ret = krb5_store_creds(sp, creds); 470 if (ret) 471 goto out; 472 473 krb5_storage_to_data(sp, &data); 474 475 dref = CFDataCreateWithBytesNoCopy(NULL, data.data, data.length, kCFAllocatorNull); 476 if (dref == NULL) { 477 ret = krb5_enomem(context); 478 goto out; 479 } 480 481 principal = CFStringCreateFromPrincipal(NULL, context, creds->server); 482 if (principal == NULL) { 483 ret = krb5_enomem(context); 484 goto out; 485 } 486 487 label = CFStringCreateWithFormat(NULL, NULL, CFSTR("kerberos credentials for %@"), principal); 488 if (label == NULL) { 489 ret = krb5_enomem(context); 490 goto out; 491 } 492 493 /* 494 store 495 496 kSecAttrServer, k->name 497 kSecAttrLabel, "credential for %@", cred.server 498 kSecAttrType, ITGT 499 kSecAttrAccount, cred.server 500 kSecAttrGeneric, data 501 */ 502 503 const void *add_keys[] = { 504 kSecAttrCreator, 505#ifdef __APPLE_TARGET_EMBEDDED__ 506 kSecAttrAccessible, 507#else 508 kSecAttrAccess, 509#endif 510 kSecClass, 511 kSecAttrType, 512 kSecAttrServer, 513 kSecAttrAccount, 514 kSecAttrLabel, 515 kSecValueData, 516 kSecAttrIsInvisible 517 }; 518 const void *add_values[] = { 519 kGSSCreator, 520#ifdef __APPLE_TARGET_EMBEDDED__ 521 kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 522#else 523 kGSSAnyAccess, 524#endif 525 kSecClassInternetPassword, 526 kGSSoTGT, 527 k->cname, 528 principal, 529 label, 530 dref, 531 kCFBooleanTrue 532 }; 533 534 query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL); 535 heim_assert(query != NULL, "out of memory"); 536 537 ret = SecItemAdd(query, &item); 538 CFRelease(query); 539 if (ret) { 540 _krb5_debugx(context, 5, "failed to add credential to %s: %d\n", k->name, (int)ret); 541 ret = EINVAL; 542 krb5_set_error_message(context, ret, "failed to store credential to %s", k->name); 543 goto out; 544 } 545 546 out: 547 if (item) 548 CFRelease(item); 549 if (sp) 550 krb5_storage_free(sp); 551 if (dref) 552 CFRelease(dref); 553 if (principal) 554 CFRelease(principal); 555 if (label) 556 CFRelease(label); 557 krb5_data_free(&data); 558 559 return ret; 560} 561 562static krb5_error_code 563kcc_get_principal(krb5_context context, 564 krb5_ccache id, 565 krb5_principal *principal) 566{ 567 krb5_error_code ret = 0; 568 krb5_kcache *k = KCACHE(id); 569 CFDictionaryRef result; 570 571 /* 572 lookup: 573 kSecAttrServer, k->name 574 kSecAttrType, ITGT 575 576 to get: 577 578 kSecAttrAccount, primary_principal 579 */ 580 581 582 result = CreateSecItemFromCache(k); 583 if (result == NULL) { 584 krb5_set_error_message(context, KRB5_CC_END, 585 "failed finding cache: %s", k->name); 586 ret = KRB5_CC_END; 587 goto out; 588 } 589 590 CFStringRef p = CFDictionaryGetValue(result, kSecAttrAccount); 591 if (p == NULL) { 592 ret = KRB5_CC_END; 593 goto out; 594 } 595 596 char *str = rk_cfstring2cstring(p); 597 if (str == NULL) { 598 ret = krb5_enomem(context); 599 goto out; 600 } 601 602 krb5_parse_name(context, str, principal); 603 free(str); 604 605 out: 606 if (result) 607 CFRelease(result); 608 609 return ret; 610} 611 612static void 613free_cursor(struct kcc_cursor *c) 614{ 615 if (c->array) 616 CFRelease(c->array); 617 free(c); 618} 619 620static krb5_error_code 621kcc_get_first (krb5_context context, 622 krb5_ccache id, 623 krb5_cc_cursor *cursor) 624{ 625 krb5_error_code ret; 626 krb5_kcache *k = KCACHE(id); 627 CFDictionaryRef query = NULL; 628 CFArrayRef result = NULL; 629 struct kcc_cursor *c; 630 631 create_constants(); 632 633 c = calloc(1, sizeof(*c)); 634 if (c == NULL) 635 return krb5_enomem(context); 636 637 /* 638 lookup: 639 kSecAttrServer, k->name 640 kSecAttrType, OTGT 641 */ 642 643 const void *keys[] = { 644 kSecClass, 645 kSecReturnData, 646 kSecMatchLimit, 647 kSecAttrType, 648 kSecAttrServer 649 }; 650 const void *values[] = { 651 kSecClassInternetPassword, 652 kCFBooleanTrue, 653 kSecMatchLimitAll, 654 kGSSoTGT, 655 k->cname 656 }; 657 658 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), 659 &kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks); 660 heim_assert(query != NULL, "out of memory"); 661 662 ret = SecItemCopyMatching(query, (CFTypeRef *)&result); 663 if (ret) { 664 result = NULL; 665 ret = 0; 666 goto out; 667 } 668 if (CFGetTypeID(result) != CFArrayGetTypeID()) { 669 CFRelease(result); 670 ret = 0; 671 result = NULL; 672 } 673 674 /* sort by creation date */ 675 676 c->array = result; 677 c->offset = 0; 678 679 out: 680 if (query) 681 CFRelease(query); 682 if (ret) { 683 free_cursor(c); 684 } else { 685 *cursor = c; 686 } 687 688 return ret; 689} 690 691static krb5_error_code 692kcc_get_next (krb5_context context, 693 krb5_ccache id, 694 krb5_cc_cursor *cursor, 695 krb5_creds *creds) 696{ 697 struct kcc_cursor *c = *cursor; 698 krb5_error_code ret; 699 krb5_storage *sp; 700 CFDataRef data; 701 702 if (c->array == NULL) 703 return KRB5_CC_END; 704 705 if (c->offset >= CFArrayGetCount(c->array)) 706 return KRB5_CC_END; 707 708 data = CFArrayGetValueAtIndex(c->array, c->offset++); 709 if (data == NULL) 710 return KRB5_CC_END; 711 712 sp = krb5_storage_from_readonly_mem(CFDataGetBytePtr(data), CFDataGetLength(data)); 713 if (sp == NULL) 714 return KRB5_CC_END; 715 716 ret = krb5_ret_creds(sp, creds); 717 krb5_storage_free(sp); 718 719 return ret; 720} 721 722static krb5_error_code 723kcc_end_get (krb5_context context, 724 krb5_ccache id, 725 krb5_cc_cursor *cursor) 726{ 727 free_cursor((struct kcc_cursor *)*cursor); 728 *cursor = NULL; 729 730 return 0; 731} 732 733static krb5_error_code 734kcc_remove_cred(krb5_context context, 735 krb5_ccache id, 736 krb5_flags which, 737 krb5_creds *cred) 738{ 739 /* lookup cred and delete */ 740 741 return 0; 742} 743 744static krb5_error_code 745kcc_set_flags(krb5_context context, 746 krb5_ccache id, 747 krb5_flags flags) 748{ 749 return 0; /* XXX */ 750} 751 752static int 753kcc_get_version(krb5_context context, 754 krb5_ccache id) 755{ 756 return 0; 757} 758 759static krb5_error_code 760kcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor) 761{ 762 struct kcc_cursor *c; 763 CFArrayRef result; 764 CFDictionaryRef query; 765 OSStatus osret; 766 767 create_constants(); 768 769 c = calloc(1, sizeof(*c)); 770 if (c == NULL) 771 return krb5_enomem(context); 772 773 const void *keys[] = { 774 kSecClass, 775 kSecReturnAttributes, 776 kSecMatchLimit, 777 kSecAttrType, 778 }; 779 const void *values[] = { 780 kSecClassInternetPassword, 781 kCFBooleanTrue, 782 kSecMatchLimitAll, 783 kGSSiTGT, 784 }; 785 786 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), 787 &kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks); 788 heim_assert(query != NULL, "out of memory"); 789 790 osret = SecItemCopyMatching(query, (CFTypeRef *)&result); 791 CFRelease(query); 792 if (osret) 793 result = NULL; 794 795 /* sort by creation date */ 796 797 c->array = result; 798 c->offset = 0; 799 800 *cursor = c; 801 return 0; 802} 803 804static krb5_error_code 805kcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id) 806{ 807 struct kcc_cursor *c = cursor; 808 CFDictionaryRef item; 809 krb5_error_code ret; 810 CFStringRef sref; 811 char *name; 812 813 if (c->array == NULL) 814 return KRB5_CC_END; 815 816 if (c->offset >= CFArrayGetCount(c->array)) 817 return KRB5_CC_END; 818 819 item = CFArrayGetValueAtIndex(c->array, c->offset++); 820 if (item == NULL) 821 return KRB5_CC_END; 822 823 sref = CFDictionaryGetValue(item, kSecAttrServer); 824 if (sref == NULL) 825 return KRB5_CC_END; 826 827 name = rk_cfstring2cstring(sref); 828 if (name == NULL) 829 return KRB5_CC_NOMEM; 830 831 ret = _krb5_cc_allocate(context, &krb5_kcc_ops, id); 832 if (ret) { 833 free(name); 834 return ret; 835 } 836 837 ret = kcc_resolve(context, id, name); 838 free(name); 839 if (ret) 840 krb5_cc_close(context, *id); 841 842 return ret; 843} 844 845static krb5_error_code 846kcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor) 847{ 848 free_cursor((struct kcc_cursor *)cursor); 849 return 0; 850} 851 852static krb5_error_code 853kcc_move(krb5_context context, krb5_ccache from, krb5_ccache to) 854{ 855 krb5_kcache *kfrom = KCACHE(from), *kto = KCACHE(to); 856 CFDictionaryRef query = NULL, update = NULL; 857 krb5_error_code ret = 0; 858 OSStatus osret; 859 860 /* if we are ask to overwrite ourself, we are done */ 861 if (strcmp(kfrom->name, kto->name) == 0) 862 return 0; 863 864 create_constants(); 865 866 delete_type(context, kto); 867 868 /* lookup 869 kSecAttrServer, k->name 870 871 and rename 872 */ 873 874 const void *keys[] = { 875 kSecClass, 876 kSecReturnRef, 877 kSecMatchLimit, 878 kSecAttrServer 879 }; 880 const void *values[] = { 881 kSecClassInternetPassword, 882 kCFBooleanTrue, 883 kSecMatchLimitAll, 884 kfrom->cname 885 }; 886 887 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), 888 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 889 heim_assert(query != NULL, "out of memory"); 890 891 892 const void *update_keys[] = { 893 kSecAttrServer 894 }; 895 const void *update_values[] = { 896 kto->cname 897 }; 898 899 update = CFDictionaryCreate(NULL, update_keys, update_values, sizeof(update_keys) / sizeof(update_keys[0]), 900 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 901 heim_assert(query != NULL, "out of memory"); 902 903 osret = SecItemUpdate(query, update); 904 if (osret) 905 krb5_set_error_message(context, (ret = KRB5_CC_END), "Failed to rename credential: %s with error %d", kfrom->name, (int)osret); 906 907 CFRelease(query); 908 CFRelease(update); 909 910 return ret; 911} 912 913static void 914set_default(CFStringRef name, bool clear_first) 915{ 916 CFDictionaryRef update = NULL, query = NULL; 917 918 /* search for all 919 kSecAttrType, ITGT 920 kSecAttrIsNegative, True 921 922 and change to 923 to -> kSecAttrIsNegative, false 924 */ 925 926 /* change kSecAttrIsNegative -> true for id */ 927 928 const void *keys[] = { 929 kSecClass, 930 kSecMatchLimit, 931 kSecAttrCreator, 932#ifdef __APPLE_TARGET_EMBEDDED__ 933 kSecAttrAccessible, 934#endif 935 kSecAttrType, 936 kSecAttrServer 937 }; 938 const void *values[] = { 939 kSecClassInternetPassword, 940 kSecMatchLimitAll, 941 kGSSCreator, 942#ifdef __APPLE_TARGET_EMBEDDED__ 943 kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 944#endif 945 kGSSiTGT, 946 name 947 }; 948 949 const void *update_keys[] = { 950 kSecAttrIsNegative 951 }; 952 const void *update_values_false[] = { 953 kCFBooleanFalse 954 }; 955 const void *update_values_true[] = { 956 kCFBooleanTrue 957 }; 958 959 /* 960 * Clear all default flags 961 */ 962 if (clear_first) { 963 964 query = CFDictionaryCreate(NULL, keys, values, (sizeof(keys) / sizeof(keys[0])) - 1, 965 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 966 heim_assert(query != NULL, "out of memory"); 967 968 969 update = CFDictionaryCreate(NULL, update_keys, update_values_false, sizeof(update_keys) / sizeof(update_keys[0]), 970 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 971 heim_assert(update != NULL, "out of memory"); 972 973 (void)SecItemUpdate(query, update); 974 975 CFRelease(query); 976 CFRelease(update); 977 } 978 979 /* 980 * Set default flag 981 */ 982 983 query = CFDictionaryCreate(NULL, keys, values, (sizeof(keys) / sizeof(keys[0])), 984 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 985 heim_assert(query != NULL, "out of memory"); 986 987 update = CFDictionaryCreate(NULL, update_keys, update_values_true, sizeof(update_keys) / sizeof(update_keys[0]), 988 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 989 heim_assert(update != NULL, "out of memory"); 990 991 (void)SecItemUpdate(query, update); 992 CFRelease(query); 993 CFRelease(update); 994} 995 996 997static krb5_error_code 998kcc_get_default_name(krb5_context context, char **str) 999{ 1000 CFDictionaryRef result = NULL, query = NULL; 1001 OSStatus ret; 1002 1003 *str = NULL; 1004 1005 create_constants(); 1006 1007 const void *keys[] = { 1008 kSecClass, 1009 kSecReturnAttributes, 1010#ifdef __APPLE_TARGET_EMBEDDED__ 1011 kSecAttrAccessible, 1012#endif 1013 kSecAttrType, 1014 kSecAttrIsNegative 1015 }; 1016 const void *values[] = { 1017 kSecClassInternetPassword, 1018 kCFBooleanTrue, 1019#ifdef __APPLE_TARGET_EMBEDDED__ 1020 kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 1021#endif 1022 kGSSiTGT, 1023 kCFBooleanTrue 1024 }; 1025 1026 /* lookup 1027 kSecAttrType, ITGT 1028 kSecAttrIsNegative, True 1029 */ 1030 1031 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), 1032 &kCFTypeDictionaryKeyCallBacks, 1033 &kCFTypeDictionaryValueCallBacks); 1034 heim_assert(query != NULL, "out of memory"); 1035 1036 ret = SecItemCopyMatching(query, (CFTypeRef *)&result); 1037 CFRelease(query); 1038 1039 if (ret) { 1040 /* 1041 * Didn't find one, just pick one 1042 */ 1043 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]) - 1, 1044 &kCFTypeDictionaryKeyCallBacks, 1045 &kCFTypeDictionaryValueCallBacks); 1046 heim_assert(query != NULL, "out of memory"); 1047 1048 ret = SecItemCopyMatching(query, (CFTypeRef *)&result); 1049 CFRelease(query); 1050 1051 if (ret == 0) { 1052 CFStringRef name = CFDictionaryGetValue(result, kSecAttrServer); 1053 set_default(name, false); 1054 } 1055 } 1056 1057 if (ret == 0) { 1058 CFStringRef name = CFDictionaryGetValue(result, kSecAttrServer); 1059 char *n; 1060 1061 if (name == NULL) 1062 return krb5_enomem(context); 1063 1064 n = rk_cfstring2cstring(name); 1065 if (n == NULL) 1066 return ENOMEM; 1067 1068 asprintf(str, "KCC:%s", n); 1069 free(n); 1070 if (*str == NULL) 1071 return krb5_enomem(context); 1072 return 0; 1073 } else { 1074 1075 *str = strdup("KCC:default-kcache"); 1076 if (*str == NULL) 1077 return krb5_enomem(context); 1078 } 1079 return 0; 1080} 1081 1082static krb5_error_code 1083kcc_set_default(krb5_context context, krb5_ccache id) 1084{ 1085 krb5_kcache *k = KCACHE(id); 1086 1087 create_constants(); 1088 1089 set_default(k->cname, true); 1090 1091 return 0; 1092} 1093 1094static krb5_error_code 1095kcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime) 1096{ 1097 /* lookup modification date */ 1098 *mtime = 0; 1099 return 0; 1100} 1101 1102static krb5_error_code 1103kcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset) 1104{ 1105 return 0; 1106} 1107 1108static krb5_error_code 1109kcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset) 1110{ 1111 *kdc_offset = 0; 1112 return 0; 1113} 1114 1115static krb5_error_code 1116kcc_get_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid) 1117{ 1118 krb5_kcache *k = KCACHE(id); 1119 uuid_t kuuid; 1120 heim_assert(sizeof(uuid_t) == sizeof(krb5_uuid), "uuid size different"); 1121 uuid_parse(k->name, kuuid); 1122 memcpy(uuid, kuuid, sizeof(krb5_uuid)); 1123 return 0; 1124} 1125 1126static krb5_error_code 1127kcc_resolve_by_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid) 1128{ 1129 uuid_string_t uuidstr; 1130 uuid_unparse(uuid, uuidstr); 1131 return kcc_resolve(context, &id, uuidstr); 1132} 1133 1134 1135 1136/** 1137 * Variable containing the KEYCHAIN based credential cache implemention. 1138 * 1139 * @ingroup krb5_ccache 1140 */ 1141 1142KRB5_LIB_VARIABLE const krb5_cc_ops krb5_kcc_ops = { 1143 KRB5_CC_OPS_VERSION, 1144 "KCC", 1145 kcc_get_name, 1146 kcc_resolve, 1147 kcc_gen_new, 1148 kcc_initialize, 1149 kcc_destroy, 1150 kcc_close, 1151 kcc_store_cred, 1152 NULL, 1153 kcc_get_principal, 1154 kcc_get_first, 1155 kcc_get_next, 1156 kcc_end_get, 1157 kcc_remove_cred, 1158 kcc_set_flags, 1159 kcc_get_version, 1160 kcc_get_cache_first, 1161 kcc_get_cache_next, 1162 kcc_end_cache_get, 1163 kcc_move, 1164 kcc_get_default_name, 1165 kcc_set_default, 1166 kcc_lastchange, 1167 kcc_set_kdc_offset, 1168 kcc_get_kdc_offset, 1169 NULL, 1170 NULL, 1171 kcc_get_uuid, 1172 kcc_resolve_by_uuid 1173}; 1174 1175#endif /* __APPLE__ */ 1176