1/*- 2 * Copyright (c) 2009 - 2011 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2009 - 2011 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 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30#include "mech_locl.h" 31#include <heim_threads.h> 32 33#include <Security/Security.h> 34#include "krb5.h" 35 36/** 37 * Acquire a new initial credentials using long term credentials (password, certificate). 38 * 39 * Credentials acquired should be free-ed with gss_release_cred() or 40 * destroyed with (removed from storage) gss_destroy_cred(). 41 * 42 * Some mechanism types can not directly acquire or validate 43 * credential (for example PK-U2U, SCRAM, NTLM or IAKERB), for those 44 * mechanisms its instead the gss_init_sec_context() that will either acquire or 45 * force validation of the credential. 46 * 47 * This function is blocking and should not be used on threads used for UI updates. 48 * 49 * @param desired_name name to use to acquire credential. Import the name using gss_import_name(). The type of the name has to be supported by the desired_mech used. 50 * 51 * @param mech mechanism to use to acquire credential. GSS_C_NO_OID is not valid input and a mechanism must be selected. For example GSS_KRB5_MECHANISM, GSS_NTLM_MECHNISM or any other mechanisms supported by the implementation. See gss_indicate_mechs(). 52 * 53 * @param attributes CFDictionary that contains how to acquire the credential, see below for examples 54 * 55 * @param output_cred_handle the resulting credential handle, value is set to GSS_C_NO_CREDENTIAL on failure. 56 * 57 * @param error an CFErrorRef returned in case of an error, that needs to be released with CFRelease() by the caller, input can be NULL. 58 * 59 * @returns a gss_error code, see the CFErrorRef passed back in error for the failure message. 60 * 61 * attributes must contains one of the following keys 62 * * kGSSICPassword - CFStringRef password 63 * * kGSSICCertificate - SecIdentityRef to the certificate to use with PKINIT/PKU2U 64 * 65 * optional keys 66 * * kGSSCredentialUsage - one of kGSS_C_INITIATE, kGSS_C_ACCEPT, kGSS_C_BOTH, default if not given is kGSS_C_INITIATE 67 * * kGSSICVerifyCredential - validate the credential with a trusted source that there was no MITM 68 * * kGSSICLKDCHostname - CFStringRef hostname of LKDC hostname 69 * * kGSSICKerberosCacheName - CFStringRef name of cache that will be created (including type) 70 * * kGSSICAppIdentifierACL - CFArrayRef[CFStringRef] prefix of bundle ID allowed to access this credential 71 * 72 * 73 * 74 * @ingroup gssapi 75 */ 76 77OM_uint32 GSSAPI_LIB_FUNCTION 78gss_aapl_initial_cred(const gss_name_t desired_name, 79 gss_const_OID desired_mech, 80 CFDictionaryRef attributes, 81 gss_cred_id_t * output_cred_handle, 82 CFErrorRef *error) 83{ 84 OM_uint32 major_status, minor_status; 85 gss_buffer_desc credential; 86 CFStringRef usage; 87 CFTypeRef password, certificate; 88 gss_cred_usage_t cred_usage = GSS_C_INITIATE; 89 gss_const_OID cred_type; 90 void *cred_value; 91 92 credential.value = NULL; 93 credential.length = 0; 94 95 HEIM_WARN_BLOCKING("gss_aapl_initial_cred", warn_once); 96 97 if (error) 98 *error = NULL; 99 100 if (desired_mech == GSS_C_NO_OID) 101 return GSS_S_BAD_MECH; 102 if (desired_name == GSS_C_NO_NAME) 103 return GSS_S_BAD_NAME; 104 105 if (output_cred_handle == NULL) 106 return GSS_S_CALL_INACCESSIBLE_READ; 107 108 *output_cred_handle = GSS_C_NO_CREDENTIAL; 109 110 /* only support password right now */ 111 password = CFDictionaryGetValue(attributes, kGSSICPassword); 112 certificate = CFDictionaryGetValue(attributes, kGSSICCertificate); 113 if (password == NULL && certificate == NULL) 114 return GSS_S_CALL_INACCESSIBLE_READ; 115 116 /* check usage */ 117 usage = CFDictionaryGetValue(attributes, kGSSCredentialUsage); 118 if (usage && CFGetTypeID(usage) == CFStringGetTypeID()) { 119 if (CFStringCompare(usage, kGSS_C_INITIATE, 0) == kCFCompareEqualTo) 120 cred_usage = GSS_C_INITIATE; 121 else if (CFStringCompare(usage, kGSS_C_ACCEPT, 0) == kCFCompareEqualTo) 122 cred_usage = GSS_C_ACCEPT; 123 else if (CFStringCompare(usage, kGSS_C_BOTH, 0) == kCFCompareEqualTo) 124 cred_usage = GSS_C_BOTH; 125 else 126 return GSS_S_FAILURE; 127 } 128 129 if (gss_oid_equal(desired_mech, GSS_KRB5_MECHANISM)) { 130 131 cred_value = (void *)attributes; 132 cred_type = GSS_C_CRED_HEIMBASE; 133 134 } else if (password && CFGetTypeID(password) == CFStringGetTypeID()) { 135 char *str = rk_cfstring2cstring(password); 136 if (str == NULL) 137 return GSS_S_FAILURE; 138 139 credential.value = str; 140 credential.length = strlen(str); 141 cred_value = &credential; 142 cred_type = GSS_C_CRED_PASSWORD; 143 144 } else if (password && CFGetTypeID(password) == CFDataGetTypeID()) { 145 credential.value = malloc(CFDataGetLength(password)); 146 if (credential.value == NULL) 147 return GSS_S_FAILURE; 148 149 credential.length = CFDataGetLength(password); 150 memcpy(credential.value, CFDataGetBytePtr(password), CFDataGetLength(password)); 151 152 cred_value = &credential; 153 cred_type = GSS_C_CRED_PASSWORD; 154 } else if (certificate && CFGetTypeID(certificate) == SecIdentityGetTypeID()) { 155 cred_value = rk_UNCONST(certificate); 156 cred_type = GSS_C_CRED_SecIdentity; 157 } else if (certificate && CFGetTypeID(certificate) == SecCertificateGetTypeID()) { 158 cred_value = rk_UNCONST(certificate); 159 cred_type = GSS_C_CRED_SecIdentity; 160 } else 161 return GSS_S_FAILURE; 162 163 major_status = gss_acquire_cred_ext(&minor_status, 164 desired_name, 165 cred_type, 166 cred_value, 167 GSS_C_INDEFINITE, 168 desired_mech, 169 cred_usage, 170 output_cred_handle); 171 if (credential.length) { 172 memset(credential.value, 0, credential.length); 173 free(credential.value); 174 } 175 176 if (major_status && error) { 177 *error = _gss_mg_cferror(major_status, minor_status, desired_mech); 178 return major_status; 179 } 180 181 /** 182 * The credential can be validated by adding kGSSICVerifyCredential to the attributes with any value. 183 */ 184 185 if (CFDictionaryGetValue(attributes, kGSSICVerifyCredential)) { 186 gss_buffer_set_t bufferset = GSS_C_NO_BUFFER_SET; 187 188 major_status = gss_inquire_cred_by_oid(&minor_status, *output_cred_handle, 189 GSS_C_CRED_VALIDATE, &bufferset); 190 if (major_status == GSS_S_COMPLETE) 191 gss_release_buffer_set(&minor_status, &bufferset); 192 else { 193 if (error) 194 *error = _gss_mg_cferror(major_status, minor_status, desired_mech); 195 gss_destroy_cred(&minor_status, output_cred_handle); 196 } 197 } 198 199 return major_status; 200} 201 202OM_uint32 GSSAPI_LIB_FUNCTION 203gss_aapl_change_password(const gss_name_t name, 204 gss_const_OID mech, 205 CFDictionaryRef attributes, 206 CFErrorRef *error) 207{ 208 struct _gss_mechanism_name *mn = NULL; 209 char *oldpw = NULL, *newpw = NULL; 210 OM_uint32 maj_stat, min_stat; 211 gssapi_mech_interface m; 212 CFStringRef old, new; 213 214 _gss_load_mech(); 215 216 m = __gss_get_mechanism(mech); 217 if (m == NULL) { 218 maj_stat = GSS_S_BAD_MECH; 219 min_stat = 0; 220 goto out; 221 } 222 223 if (m->gm_aapl_change_password == NULL) { 224 maj_stat = GSS_S_UNAVAILABLE; 225 min_stat = 0; 226 goto out; 227 } 228 229 maj_stat = _gss_find_mn(&min_stat, (struct _gss_name *)name, mech, &mn); 230 if (maj_stat != GSS_S_COMPLETE) 231 goto out; 232 233 old = CFDictionaryGetValue(attributes, kGSSChangePasswordOldPassword); 234 new = CFDictionaryGetValue(attributes, kGSSChangePasswordNewPassword); 235 236 heim_assert(old != NULL, "old password missing"); 237 heim_assert(new != NULL, "new password missing"); 238 239 oldpw = rk_cfstring2cstring(old); 240 newpw = rk_cfstring2cstring(new); 241 242 if (oldpw == NULL || newpw == NULL) { 243 maj_stat = GSS_S_FAILURE; 244 min_stat = 0; 245 goto out; 246 } 247 248 maj_stat = m->gm_aapl_change_password(&min_stat, 249 mn->gmn_name, 250 oldpw, newpw); 251 if (maj_stat) 252 _gss_mg_error(m, min_stat); 253 254 out: 255 if (maj_stat && error) 256 *error = _gss_mg_cferror(maj_stat, min_stat, mech); 257 258 if (oldpw) { 259 memset(oldpw, 0, strlen(oldpw)); 260 free(oldpw); 261 } 262 if (newpw) { 263 memset(newpw, 0, strlen(newpw)); 264 free(newpw); 265 } 266 267 return maj_stat; 268} 269 270/** 271 * Returns a copy of the UUID of the GSS credential 272 * 273 * @param credential credential 274 * 275 * @returns CFUUIDRef that can be used to turn into a credential, 276 * normal CoreFoundaton rules for rules applies so the CFUUIDRef needs 277 * to be released. 278 * 279 * @ingroup gssapi 280 */ 281 282CFUUIDRef 283GSSCredentialCopyUUID(gss_cred_id_t cred) 284{ 285 OM_uint32 major, minor; 286 gss_buffer_set_t dataset = GSS_C_NO_BUFFER_SET; 287 krb5_error_code ret; 288 krb5_uuid uuid; 289 CFUUIDBytes cfuuid; 290 291 major = gss_inquire_cred_by_oid(&minor, cred, GSS_C_NT_UUID, &dataset); 292 if (major || dataset->count != 1) { 293 gss_release_buffer_set(&minor, &dataset); 294 return NULL; 295 } 296 297 if (dataset->elements[0].length != 36) { 298 gss_release_buffer_set(&minor, &dataset); 299 return NULL; 300 } 301 302 ret = krb5_string_to_uuid(dataset->elements[0].value, uuid); 303 gss_release_buffer_set(&minor, &dataset); 304 if (ret) 305 return NULL; 306 307 memcpy(&cfuuid, uuid, sizeof(uuid)); 308 309 return CFUUIDCreateFromUUIDBytes(NULL, cfuuid); 310} 311 312/** 313 * Returns a GSS credential for a given UUID if the credential exists. 314 * 315 * @param uuid the UUID of the credential to fetch 316 * 317 * @returns a gss_cred_id_t, normal CoreFoundaton rules for rules 318 * applies so the CFUUIDRef needs to be released with either CFRelease() or gss_release_name(). 319 * 320 * @ingroup gssapi 321 */ 322 323gss_cred_id_t GSSAPI_LIB_FUNCTION 324GSSCreateCredentialFromUUID(CFUUIDRef uuid) 325{ 326 OM_uint32 min_stat, maj_stat; 327 gss_cred_id_t cred; 328 CFStringRef name; 329 gss_name_t gname; 330 331 name = CFUUIDCreateString(NULL, uuid); 332 if (name == NULL) 333 return NULL; 334 335 gname = GSSCreateName(name, GSS_C_NT_UUID, NULL); 336 CFRelease(name); 337 if (gname == NULL) 338 return NULL; 339 340 maj_stat = gss_acquire_cred(&min_stat, gname, GSS_C_INDEFINITE, NULL, 341 GSS_C_INITIATE, &cred, NULL, NULL); 342 gss_release_name(&min_stat, &gname); 343 if (maj_stat != GSS_S_COMPLETE) 344 return NULL; 345 346 return cred; 347} 348 349static CFStringRef 350CopyFoldString(CFStringRef host) 351{ 352 CFMutableStringRef string = CFStringCreateMutableCopy(NULL, 0, host); 353 static dispatch_once_t once; 354 static CFLocaleRef locale; 355 dispatch_once(&once, ^{ 356 locale = CFLocaleCreate(NULL, CFSTR("C")); 357 }); 358 CFStringFold(string, kCFCompareCaseInsensitive, locale); 359 return string; 360} 361 362static CFStringRef 363CopyFoldedHostName(CFStringRef stringOrURL, CFStringRef *path) 364{ 365 CFStringRef string, hn = NULL; 366 CFURLRef url; 367 368 *path = NULL; 369 370 /* 371 * Try paring the hostname as an URL first 372 */ 373 374 url = CFURLCreateWithString(NULL, stringOrURL, NULL); 375 if (url) { 376 CFStringRef host = CFURLCopyHostName(url); 377 CFStringRef scheme = CFURLCopyScheme(url); 378 if (host == NULL) 379 host = CFSTR(""); 380 if (scheme == NULL) 381 scheme = CFSTR(""); 382 hn = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@-%@"), host, scheme); 383 *path = CFURLCopyPath(url); 384 if (CFStringCompare(*path, CFSTR(""), 0) == kCFCompareEqualTo) { 385 CFRelease(*path); 386 *path = CFSTR("/"); 387 } 388 CFRelease(url); 389 CFRelease(scheme); 390 CFRelease(host); 391 } 392 if (hn == NULL) { 393 hn = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@-host"), stringOrURL); 394 *path = CFSTR("/"); 395 } 396 397 string = CopyFoldString(hn); 398 CFRelease(hn); 399 return string; 400} 401 402/* 403 * 404 */ 405 406void 407GSSRuleAddMatch(CFMutableDictionaryRef rules, CFStringRef host, CFStringRef value) 408{ 409 CFMutableDictionaryRef match; 410 CFStringRef hostname, path; 411 412 hostname = CopyFoldedHostName(host, &path); 413 if (hostname) { 414 415 match = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 416 if (match) { 417 CFMutableArrayRef mutable; 418 419 CFDictionarySetValue(match, CFSTR("path"), path); 420 CFDictionarySetValue(match, CFSTR("value"), value); 421 422 CFArrayRef array = CFDictionaryGetValue(rules, hostname); 423 if (array) { 424 mutable = CFArrayCreateMutableCopy(NULL, 0, array); 425 } else { 426 mutable = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 427 } 428 if (mutable) { 429 430 CFIndex n, count = CFArrayGetCount(mutable); 431 432 for (n = 0; n < count; n++) { 433 CFDictionaryRef item = (CFDictionaryRef)CFArrayGetValueAtIndex(mutable, n); 434 CFStringRef p = CFDictionaryGetValue(item, CFSTR("path")); 435 436 if (CFStringHasPrefix(path, p)) { 437 CFArrayInsertValueAtIndex(mutable, n, match); 438 break; 439 } 440 } 441 if (n >= count) 442 CFArrayAppendValue(mutable, match); 443 444 CFDictionarySetValue(rules, hostname, mutable); 445 CFRelease(mutable); 446 } 447 CFRelease(hostname); 448 CFRelease(path); 449 } 450 CFRelease(match); 451 } 452} 453 454/* 455 * host is a URL string or hostname string 456 */ 457 458CFStringRef 459GSSRuleGetMatch(CFDictionaryRef rules, CFStringRef host) 460{ 461 CFTypeRef result = NULL; 462 CFStringRef hostFolded, path; 463 const char *p; 464 465 hostFolded = CopyFoldedHostName(host, &path); 466 if (hostFolded == NULL) 467 return NULL; 468 469 char *str = rk_cfstring2cstring(hostFolded); 470 CFRelease(hostFolded); 471 if (str == NULL) { 472 CFRelease(path); 473 return NULL; 474 } 475 476 if (str[0] == '\0') { 477 free(str); 478 return NULL; 479 } 480 481 for (p = str; p != NULL && result == NULL; p = strchr(p + 1, '.')) { 482 CFStringRef partial = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8); 483 CFArrayRef array = (CFArrayRef)CFDictionaryGetValue(rules, partial); 484 485 CFRelease(partial); 486 487 if (array) { 488 CFIndex n, count = CFArrayGetCount(array); 489 490 for (n = 0; n < count && result == NULL; n++) { 491 CFDictionaryRef item = (CFDictionaryRef)CFArrayGetValueAtIndex(array, n); 492 493 CFStringRef matchPath = CFDictionaryGetValue(item, CFSTR("path")); 494 if (CFStringHasPrefix(path, matchPath)) { 495 result = CFDictionaryGetValue(item, CFSTR("value")); 496 if (result) 497 CFRetain(result); 498 } 499 } 500 } 501 } 502 CFRelease(path); 503 free(str); 504 return result; 505} 506 507/** 508 * Create a GSS name from a buffer and type. 509 * 510 * @param name name buffer describing a credential, can be either a CFDataRef or CFStringRef of a name. 511 * @param name_type on OID of the GSS_C_NT_* OIDs constants specifiy the name type. 512 * @param error if an error happen, this may be set to a CFErrorRef describing the failure futher. 513 * 514 * @returns returns gss_name_t or NULL on failure. Must be freed using gss_release_name() or CFRelease(). Follows CoreFoundation Create/Copy rule. 515 * 516 * @ingroup gssapi 517 */ 518 519gss_name_t 520GSSCreateName(CFTypeRef name, gss_const_OID name_type, CFErrorRef *error) 521{ 522 OM_uint32 maj_stat, min_stat; 523 gss_buffer_desc buffer; 524 int free_data = 0; 525 gss_name_t n; 526 527 if (error) 528 *error = NULL; 529 530 if (CFGetTypeID(name) == CFStringGetTypeID()) { 531 buffer.value = rk_cfstring2cstring(name); 532 if (buffer.value == NULL) 533 return GSS_S_FAILURE; 534 buffer.length = strlen((char *)buffer.value); 535 free_data = 1; 536 } else if (CFGetTypeID(name) == CFDataGetTypeID()) { 537 buffer.value = (void *)CFDataGetBytePtr(name); 538 buffer.length = (void *)CFDataGetLength(name); 539 } else { 540 return GSS_C_NO_NAME; 541 } 542 543 maj_stat = gss_import_name(&min_stat, &buffer, name_type, &n); 544 545 if (free_data) 546 free(buffer.value); 547 548 if (maj_stat) 549 return GSS_C_NO_NAME; 550 551 return n; 552} 553 554/** 555 * Copy the name describing the credential 556 * 557 * @param cred the credential to get the name from 558 * 559 * @returns returns gss_name_t or NULL on failure. Must be freed using gss_release_name() or CFRelease(). Follows CoreFoundation Create/Copy rule. 560 * 561 * @ingroup gssapi 562 */ 563 564gss_name_t 565GSSCredentialCopyName(gss_cred_id_t cred) 566{ 567 OM_uint32 major, minor; 568 gss_name_t name; 569 570 major = gss_inquire_cred(&minor, cred, &name, NULL, NULL, NULL); 571 if (major != GSS_S_COMPLETE) 572 return NULL; 573 574 return name; 575} 576 577/** 578 * Return the lifetime (in seconds) left of the credential. 579 * 580 * @param cred the credential to get the name from 581 * 582 * @returns the lifetime of the credentials. 0 on failure and 583 * GSS_C_INDEFINITE on credentials that never expire. 584 * 585 * @ingroup gssapi 586 */ 587 588OM_uint32 589GSSCredentialGetLifetime(gss_cred_id_t cred) 590{ 591 OM_uint32 maj_stat, min_stat; 592 OM_uint32 lifetime; 593 594 maj_stat = gss_inquire_cred(&min_stat, cred, NULL, &lifetime, NULL, NULL); 595 if (maj_stat != GSS_S_COMPLETE) 596 return 0; 597 598 return lifetime; 599} 600 601/** 602 * Returns a string that is suitable for displaying to user, must not 603 * be used for verify subjects on an ACLs. 604 * 605 * @param name to get a display strings from 606 * 607 * @returns a string that is printable. Follows CoreFoundation Create/Copy rule. 608 * 609 * @ingroup gssapi 610 */ 611 612CFStringRef 613GSSNameCreateDisplayString(gss_name_t name) 614{ 615 OM_uint32 maj_stat, min_stat; 616 gss_buffer_desc buffer; 617 CFStringRef str; 618 619 maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); 620 if (maj_stat != GSS_S_COMPLETE) 621 return NULL; 622 623 str = CFStringCreateWithBytes(NULL, (const UInt8 *)buffer.value, buffer.length, kCFStringEncodingUTF8, false); 624 gss_release_buffer(&min_stat, &buffer); 625 626 return str; 627} 628 629/* deprecated */ 630OM_uint32 631GSSCredGetLifetime(gss_cred_id_t cred) 632{ 633 return GSSCredentialGetLifetime(cred); 634} 635 636gss_name_t 637GSSCredCopyName(gss_cred_id_t cred) 638{ 639 return GSSCredentialCopyName(cred); 640} 641