SecIdentity.cpp
1 /* 2 * Copyright (c) 2002-2020 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 #include <Security/SecIdentity.h> 25 #include <Security/SecIdentityPriv.h> 26 #include <Security/SecKeychainItemPriv.h> 27 #include <Security/SecItem.h> 28 #include <Security/SecIdentityPriv.h> 29 #include <Security/SecCertificatePriv.h> 30 31 #include "SecBridge.h" 32 #include <security_keychain/Certificate.h> 33 #include <security_keychain/Identity.h> 34 #include <security_keychain/KeyItem.h> 35 #include <security_keychain/KCCursor.h> 36 #include <security_cdsa_utilities/Schema.h> 37 #include <security_utilities/simpleprefs.h> 38 #include <utilities/SecCFRelease.h> 39 #include <utilities/SecXPCUtils.h> 40 #include <sys/param.h> 41 #include <syslog.h> 42 #include <os/activity.h> 43 #include "LegacyAPICounts.h" 44 45 /* private function declarations */ 46 OSStatus 47 SecIdentityFindPreferenceItemWithNameAndKeyUsage( 48 CFTypeRef keychainOrArray, 49 CFStringRef name, 50 int32_t keyUsage, 51 SecKeychainItemRef *itemRef); 52 53 OSStatus SecIdentityDeletePreferenceItemWithNameAndKeyUsage( 54 CFTypeRef keychainOrArray, 55 CFStringRef name, 56 int32_t keyUsage); 57 58 59 CSSM_KEYUSE ConvertArrayToKeyUsage(CFArrayRef usage) 60 { 61 CFIndex count = 0; 62 CSSM_KEYUSE result = (CSSM_KEYUSE) 0; 63 64 if ((NULL == usage) || (0 == (count = CFArrayGetCount(usage)))) 65 { 66 return result; 67 } 68 69 for (CFIndex iCnt = 0; iCnt < count; iCnt++) 70 { 71 CFStringRef keyUsageStr = NULL; 72 keyUsageStr = (CFStringRef)CFArrayGetValueAtIndex(usage,iCnt); 73 if (NULL != keyUsageStr) 74 { 75 if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanEncrypt, keyUsageStr, 0)) 76 { 77 result |= CSSM_KEYUSE_ENCRYPT; 78 } 79 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanDecrypt, keyUsageStr, 0)) 80 { 81 result |= CSSM_KEYUSE_DECRYPT; 82 } 83 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanDerive, keyUsageStr, 0)) 84 { 85 result |= CSSM_KEYUSE_DERIVE; 86 } 87 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanSign, keyUsageStr, 0)) 88 { 89 result |= CSSM_KEYUSE_SIGN; 90 } 91 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanVerify, keyUsageStr, 0)) 92 { 93 result |= CSSM_KEYUSE_VERIFY; 94 } 95 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanWrap, keyUsageStr, 0)) 96 { 97 result |= CSSM_KEYUSE_WRAP; 98 } 99 else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanUnwrap, keyUsageStr, 0)) 100 { 101 result |= CSSM_KEYUSE_UNWRAP; 102 } 103 } 104 } 105 106 return result; 107 } 108 109 110 CFTypeID 111 SecIdentityGetTypeID(void) 112 { 113 BEGIN_SECAPI 114 115 return gTypes().Identity.typeID; 116 117 END_SECAPI1(_kCFRuntimeNotATypeID) 118 } 119 120 121 OSStatus 122 SecIdentityCopyCertificate( 123 SecIdentityRef identityRef, 124 SecCertificateRef *certificateRef) 125 { 126 BEGIN_SECAPI 127 os_activity_t activity = os_activity_create("SecIdentityCopyCertificate", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 128 os_activity_scope(activity); 129 os_release(activity); 130 131 if (!identityRef || !certificateRef) { 132 return errSecParam; 133 } 134 CFTypeID itemType = CFGetTypeID(identityRef); 135 if (itemType == SecIdentityGetTypeID()) { 136 SecPointer<Certificate> certificatePtr(Identity::required(identityRef)->certificate()); 137 Required(certificateRef) = certificatePtr->handle(); 138 139 /* convert outgoing certificate item to a unified SecCertificateRef */ 140 CssmData certData = certificatePtr->data(); 141 CFDataRef data = NULL; 142 if (certData.Data && certData.Length) { 143 data = CFDataCreate(NULL, certData.Data, certData.Length); 144 } 145 if (!data) { 146 *certificateRef = NULL; 147 syslog(LOG_ERR, "ERROR: SecIdentityCopyCertificate failed to retrieve certificate data (length=%ld, data=0x%lX)", 148 (long)certData.Length, (uintptr_t)certData.Data); 149 return errSecInternal; 150 } 151 SecCertificateRef tmpRef = *certificateRef; 152 *certificateRef = SecCertificateCreateWithKeychainItem(NULL, data, tmpRef); 153 if (data) { 154 CFRelease(data); 155 } 156 if (tmpRef) { 157 CFRelease(tmpRef); 158 } 159 } 160 else if (itemType == SecCertificateGetTypeID()) { 161 // rdar://24483382 162 // reconstituting a persistent identity reference could return the certificate 163 SecCertificateRef certificate = (SecCertificateRef)identityRef; 164 165 /* convert outgoing certificate item to a unified SecCertificateRef, if needed */ 166 if (SecCertificateIsItemImplInstance(certificate)) { 167 *certificateRef = SecCertificateCreateFromItemImplInstance(certificate); 168 } 169 else { 170 *certificateRef = (SecCertificateRef) CFRetain(certificate); 171 } 172 return errSecSuccess; 173 } 174 else { 175 return errSecParam; 176 } 177 178 END_SECAPI 179 } 180 181 182 OSStatus 183 SecIdentityCopyPrivateKey( 184 SecIdentityRef identityRef, 185 SecKeyRef *privateKeyRef) 186 { 187 BEGIN_SECAPI 188 os_activity_t activity = os_activity_create("SecIdentityCopyPrivateKey", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 189 os_activity_scope(activity); 190 os_release(activity); 191 192 Required(privateKeyRef) = (SecKeyRef)CFRetain(Identity::required(identityRef)->privateKeyRef()); 193 194 END_SECAPI 195 } 196 197 OSStatus 198 SecIdentityCreateWithCertificate( 199 CFTypeRef keychainOrArray, 200 SecCertificateRef certificate, 201 SecIdentityRef *identityRef) 202 { 203 // This macro converts a new-style SecCertificateRef to an old-style ItemImpl 204 BEGIN_SECCERTAPI 205 206 SecPointer<Certificate> certificatePtr(Certificate::required(__itemImplRef)); 207 StorageManager::KeychainList keychains; 208 globals().storageManager.optionalSearchList(keychainOrArray, keychains); 209 SecPointer<Identity> identityPtr(new Identity(keychains, certificatePtr)); 210 Required(identityRef) = identityPtr->handle(); 211 212 END_SECCERTAPI 213 } 214 215 SecIdentityRef 216 SecIdentityCreate( 217 CFAllocatorRef allocator, 218 SecCertificateRef certificate, 219 SecKeyRef privateKey) 220 { 221 COUNTLEGACYAPI 222 SecIdentityRef identityRef = NULL; 223 OSStatus __secapiresult; 224 SecCertificateRef __itemImplRef = NULL; 225 if (SecCertificateIsItemImplInstance(certificate)) { 226 __itemImplRef=(SecCertificateRef)CFRetain(certificate); 227 } 228 if (!__itemImplRef && certificate) { 229 __itemImplRef=(SecCertificateRef)SecCertificateCopyKeychainItem(certificate); 230 } 231 if (!__itemImplRef && certificate) { 232 __itemImplRef=SecCertificateCreateItemImplInstance(certificate); 233 (void)SecCertificateSetKeychainItem(certificate,__itemImplRef); 234 } 235 try { 236 SecPointer<Certificate> certificatePtr(Certificate::required(__itemImplRef)); 237 SecPointer<Identity> identityPtr(new Identity(privateKey, certificatePtr)); 238 identityRef = identityPtr->handle(); 239 240 __secapiresult=errSecSuccess; 241 } 242 catch (const MacOSError &err) { __secapiresult=err.osStatus(); } 243 catch (const CommonError &err) { __secapiresult=SecKeychainErrFromOSStatus(err.osStatus()); } 244 catch (const std::bad_alloc &) { __secapiresult=errSecAllocate; } 245 catch (...) { __secapiresult=errSecInternalComponent; } 246 if (__itemImplRef) { CFRelease(__itemImplRef); } 247 return identityRef; 248 } 249 250 static 251 bool _SecIdentityNameIsURL(CFStringRef name) 252 { 253 CFURLRef url = (name) ? CFURLCreateWithString(NULL, name, NULL) : NULL; 254 bool result = (url && CFURLCanBeDecomposed(url)); 255 if (result) { 256 CFStringRef schemeStr = CFURLCopyScheme(url); 257 result = (schemeStr && (CFStringGetLength(schemeStr) > 0)); 258 CFReleaseSafe(schemeStr); 259 } 260 CFReleaseSafe(url); 261 return result; 262 } 263 264 static 265 CFArrayRef _SecIdentityCopyPossiblePaths( 266 CFStringRef name, CFStringRef appIdentifier) 267 { 268 // utility function to build and return an array of possible paths for the given name. 269 // if name is not a URL, this returns a single-element array. 270 // if name is a URL, the array may contain 1..N elements, one for each level of the path hierarchy. 271 // if name is a URL and appIdentifier is non-NULL, it is appended in parentheses to each array entry. 272 273 CFMutableArrayRef names = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 274 if (!name) { 275 return names; 276 } 277 CFIndex oldLength = CFStringGetLength(name); 278 CFArrayAppendValue(names, name); 279 280 CFURLRef url = CFURLCreateWithString(NULL, name, NULL); 281 if (url) { 282 if (CFURLCanBeDecomposed(url)) { 283 // first, remove the query portion of this URL, if any 284 CFStringRef qs = CFURLCopyQueryString(url, NULL); 285 if (qs) { 286 CFMutableStringRef newName = CFStringCreateMutableCopy(NULL, oldLength, name); 287 if (newName) { 288 CFIndex qsLength = CFStringGetLength(qs) + 1; // include the '?' 289 CFStringDelete(newName, CFRangeMake(oldLength-qsLength, qsLength)); 290 CFRelease(url); 291 url = CFURLCreateWithString(NULL, newName, NULL); 292 CFArraySetValueAtIndex(names, 0, newName); 293 CFRelease(newName); 294 } 295 CFRelease(qs); 296 } 297 // now add an entry for each level of the path 298 while (url) { 299 CFURLRef parent = CFURLCreateCopyDeletingLastPathComponent(NULL, url); 300 if (parent) { 301 CFStringRef parentURLString = CFURLGetString(parent); 302 if (parentURLString) { 303 CFIndex newLength = CFStringGetLength(parentURLString); 304 // check that string length has decreased as expected; for file URLs, 305 // CFURLCreateCopyDeletingLastPathComponent can insert './' or '../' 306 if ((newLength >= oldLength) || (!CFStringHasPrefix(name, parentURLString))) { 307 CFRelease(parent); 308 CFRelease(url); 309 break; 310 } 311 oldLength = newLength; 312 CFArrayAppendValue(names, parentURLString); 313 } 314 } 315 CFRelease(url); 316 url = parent; 317 } 318 } 319 else { 320 CFRelease(url); 321 } 322 } 323 // finally, add wildcard entries for each subdomain 324 url = CFURLCreateWithString(NULL, name, NULL); 325 if (url) { 326 if (CFURLCanBeDecomposed(url)) { 327 CFStringRef netLocString = CFURLCopyNetLocation(url); 328 if (netLocString) { 329 // first strip off port number, if present 330 CFStringRef tmpLocString = netLocString; 331 CFArrayRef hostnameArray = CFStringCreateArrayBySeparatingStrings(NULL, netLocString, CFSTR(":")); 332 tmpLocString = (CFStringRef)CFRetain((CFStringRef)CFArrayGetValueAtIndex(hostnameArray, 0)); 333 CFRelease(netLocString); 334 CFRelease(hostnameArray); 335 netLocString = tmpLocString; 336 // split remaining string into domain components 337 hostnameArray = CFStringCreateArrayBySeparatingStrings(NULL, netLocString, CFSTR(".")); 338 CFIndex subdomainCount = CFArrayGetCount(hostnameArray); 339 CFIndex i = 0; 340 while (++i < subdomainCount) { 341 CFIndex j = i; 342 CFMutableStringRef wildcardString = CFStringCreateMutable(NULL, 0); 343 if (wildcardString) { 344 CFStringAppendCString(wildcardString, "*", kCFStringEncodingUTF8); 345 while (j < subdomainCount) { 346 CFStringRef domainString = (CFStringRef)CFArrayGetValueAtIndex(hostnameArray, j++); 347 if (CFStringGetLength(domainString) > 0) { 348 CFStringAppendCString(wildcardString, ".", kCFStringEncodingUTF8); 349 CFStringAppend(wildcardString, domainString); 350 } 351 } 352 if (CFStringGetLength(wildcardString) > 1) { 353 CFArrayAppendValue(names, wildcardString); 354 } 355 CFRelease(wildcardString); 356 } 357 } 358 CFRelease(hostnameArray); 359 CFRelease(netLocString); 360 } 361 } 362 CFRelease(url); 363 } 364 if (appIdentifier) { 365 CFIndex count = CFArrayGetCount(names); 366 for (CFIndex idx=0; idx < count; idx++) { 367 CFStringRef oldStr = (CFStringRef)CFArrayGetValueAtIndex(names, idx); 368 CFStringRef appStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), oldStr, appIdentifier); 369 if (appStr) { 370 // only use app identifier string if name is a URL 371 if (_SecIdentityNameIsURL(oldStr)) { 372 CFArraySetValueAtIndex(names, idx, appStr); 373 } 374 CFRelease(appStr); 375 } 376 } 377 } 378 379 return names; 380 } 381 382 static 383 OSStatus _SecIdentityCopyPreferenceMatchingName( 384 CFStringRef name, 385 CSSM_KEYUSE keyUsage, 386 CFArrayRef validIssuers, 387 SecIdentityRef *identity) 388 { 389 // this is NOT exported, and called only from SecIdentityCopyPreference (below), so no BEGIN/END macros here; 390 // caller must handle exceptions 391 392 StorageManager::KeychainList keychains; 393 globals().storageManager.getSearchList(keychains); 394 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); 395 396 char idUTF8[MAXPATHLEN]; 397 Required(name); 398 if (!CFStringGetCString(name, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8)) 399 idUTF8[0] = (char)'\0'; 400 CssmData service(const_cast<char *>(idUTF8), strlen(idUTF8)); 401 FourCharCode itemType = 'iprf'; 402 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service); 403 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), itemType); 404 if (keyUsage) { 405 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); 406 } 407 408 Item prefItem; 409 if (!cursor->next(prefItem)) 410 return errSecItemNotFound; 411 412 // get persistent certificate reference 413 SecKeychainAttribute itemAttrs[] = { { kSecGenericItemAttr, 0, NULL } }; 414 SecKeychainAttributeList itemAttrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs }; 415 prefItem->getContent(NULL, &itemAttrList, NULL, NULL); 416 417 // find certificate, given persistent reference data 418 CFDataRef pItemRef = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)itemAttrs[0].data, itemAttrs[0].length, kCFAllocatorNull); 419 SecKeychainItemRef certItemRef = nil; 420 OSStatus status = SecKeychainItemCopyFromPersistentReference(pItemRef, &certItemRef); //%%% need to make this a method of ItemImpl 421 prefItem->freeContent(&itemAttrList, NULL); 422 if (pItemRef) 423 CFRelease(pItemRef); 424 if (status) 425 return status; 426 427 // filter on valid issuers, if provided 428 if (validIssuers) { 429 //%%%TBI 430 } 431 432 // create identity reference, given certificate 433 status = SecIdentityCreateWithCertificate(NULL, (SecCertificateRef)certItemRef, identity); 434 if (certItemRef) { 435 CFRelease(certItemRef); 436 } 437 438 return status; 439 } 440 441 static CFStringRef SecIdentityCopyPerAppNameForName(CFStringRef name) 442 { 443 CFStringRef perAppName = NULL; 444 // Currently, per-app identity preferences require a URL form name, 445 // and the client must not be one whose role is to edit the item's ownership. 446 if (_SecIdentityNameIsURL(name) && !SecXPCClientCanEditPreferenceOwnership()) { 447 CFStringRef identifier = SecXPCCopyClientApplicationIdentifier(); 448 if (identifier) { 449 // create per-app name with application identifier in parentheses 450 perAppName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), name, identifier); 451 } 452 CFReleaseNull(identifier); 453 } 454 if (!perAppName && name) { 455 // no application identifier, use name unchanged 456 perAppName = (CFStringRef)CFRetain(name); 457 } 458 return perAppName; 459 } 460 461 SecIdentityRef SecIdentityCopyPreferred(CFStringRef name, CFArrayRef keyUsage, CFArrayRef validIssuers) 462 { 463 // This function will look for a matching preference in the following order: 464 // - matches the name and the supplied key use 465 // - matches the name and the special 'ANY' key use 466 // - matches the name with no key usage constraint 467 468 SecIdentityRef identityRef = NULL; 469 CSSM_KEYUSE keyUse = ConvertArrayToKeyUsage(keyUsage); 470 OSStatus status = SecIdentityCopyPreference(name, keyUse, validIssuers, &identityRef); 471 if (status != errSecSuccess && keyUse != CSSM_KEYUSE_ANY) 472 status = SecIdentityCopyPreference(name, CSSM_KEYUSE_ANY, validIssuers, &identityRef); 473 if (status != errSecSuccess && keyUse != 0) 474 status = SecIdentityCopyPreference(name, 0, validIssuers, &identityRef); 475 476 return identityRef; 477 } 478 479 OSStatus SecIdentityCopyPreference( 480 CFStringRef name, 481 CSSM_KEYUSE keyUsage, 482 CFArrayRef validIssuers, 483 SecIdentityRef *identity) 484 { 485 // The original implementation of SecIdentityCopyPreference matches the exact string only. 486 // That implementation has been moved to _SecIdentityCopyPreferenceMatchingName (above), 487 // and this function is a wrapper which calls it, so that existing clients will get the 488 // extended behavior of server domain matching for items that specify URLs. 489 // (Note that behavior is unchanged if the specified name is not a URL.) 490 491 BEGIN_SECAPI 492 os_activity_t activity = os_activity_create("SecIdentityCopyPreference", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 493 os_activity_scope(activity); 494 os_release(activity); 495 496 CFTypeRef val = (CFTypeRef)CFPreferencesCopyValue(CFSTR("LogIdentityPreferenceLookup"), 497 CFSTR("com.apple.security"), 498 kCFPreferencesCurrentUser, 499 kCFPreferencesAnyHost); 500 Boolean logging = false; 501 if (val) { 502 if (CFGetTypeID(val) == CFBooleanGetTypeID()) { 503 logging = CFBooleanGetValue((CFBooleanRef)val); 504 } 505 } 506 CFReleaseNull(val); 507 508 OSStatus status = errSecItemNotFound; 509 CFStringRef appIdentifier = NULL; 510 // We don't want to include the identifier if the app creating this pref has an editing role, 511 // e.g. Keychain Access or security; this lets it create an item for another client, e.g. Safari. 512 if (!SecXPCClientCanEditPreferenceOwnership()) { 513 appIdentifier = SecXPCCopyClientApplicationIdentifier(); 514 } 515 CFArrayRef names = _SecIdentityCopyPossiblePaths(name, appIdentifier); 516 CFReleaseNull(appIdentifier); 517 if (!names) { 518 return status; 519 } 520 521 CFIndex idx, total = CFArrayGetCount(names); 522 for (idx = 0; idx < total; idx++) { 523 CFStringRef aName = (CFStringRef)CFArrayGetValueAtIndex(names, idx); 524 try { 525 status = _SecIdentityCopyPreferenceMatchingName(aName, keyUsage, validIssuers, identity); 526 } 527 catch (...) { status = errSecItemNotFound; } 528 529 if (logging) { 530 // get identity label 531 CFStringRef labelString = NULL; 532 if (!status && identity && *identity) { 533 try { 534 SecPointer<Certificate> cert(Identity::required(*identity)->certificate()); 535 cert->inferLabel(false, &labelString); 536 } 537 catch (...) { labelString = NULL; }; 538 } 539 char *labelBuf = NULL; 540 CFIndex labelBufSize = (labelString) ? CFStringGetLength(labelString) * 4 : 4; 541 labelBuf = (char *)malloc(labelBufSize); 542 if (!labelString || !CFStringGetCString(labelString, labelBuf, labelBufSize, kCFStringEncodingUTF8)) { 543 labelBuf[0] = 0; 544 } 545 if (labelString) { 546 CFRelease(labelString); 547 } 548 549 // get service name 550 char *serviceBuf = NULL; 551 CFIndex serviceBufSize = CFStringGetLength(aName) * 4; 552 serviceBuf = (char *)malloc(serviceBufSize); 553 if (!CFStringGetCString(aName, serviceBuf, serviceBufSize, kCFStringEncodingUTF8)) { 554 serviceBuf[0] = 0; 555 } 556 557 syslog(LOG_NOTICE, "preferred identity: \"%s\" found for \"%s\"\n", labelBuf, serviceBuf); 558 if (!status && aName) { 559 char *nameBuf = NULL; 560 CFIndex nameBufSize = CFStringGetLength(aName) * 4; 561 nameBuf = (char *)malloc(nameBufSize); 562 if (!CFStringGetCString(aName, nameBuf, nameBufSize, kCFStringEncodingUTF8)) { 563 nameBuf[0] = 0; 564 } 565 syslog(LOG_NOTICE, "lookup complete; will use: \"%s\" for \"%s\"\n", labelBuf, nameBuf); 566 free(nameBuf); 567 } 568 569 free(labelBuf); 570 free(serviceBuf); 571 } 572 573 if (status == errSecSuccess) { 574 break; // match found 575 } 576 } 577 578 CFRelease(names); 579 return status; 580 581 END_SECAPI 582 } 583 584 OSStatus SecIdentitySetPreference( 585 SecIdentityRef identity, 586 CFStringRef name, 587 CSSM_KEYUSE keyUsage) 588 { 589 if (!name) { 590 return errSecParam; 591 } 592 CFStringRef perAppName = SecIdentityCopyPerAppNameForName(name); 593 if (!perAppName) { 594 return errSecInternal; 595 } 596 if (!identity) { 597 // treat NULL identity as a request to clear the preference 598 // (note: if keyUsage is 0, this clears all key usage prefs for name) 599 OSStatus result = SecIdentityDeletePreferenceItemWithNameAndKeyUsage(NULL, perAppName, keyUsage); 600 CFReleaseNull(perAppName); 601 return result; 602 } 603 604 BEGIN_SECAPI 605 os_activity_t activity = os_activity_create("SecIdentitySetPreference", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 606 os_activity_scope(activity); 607 os_release(activity); 608 609 CFRef<SecCertificateRef> certRef; 610 OSStatus status = SecIdentityCopyCertificate(identity, certRef.take()); 611 if(status != errSecSuccess) { 612 CFReleaseNull(perAppName); 613 MacOSError::throwMe(status); 614 } 615 616 // determine the account attribute 617 // 618 // This attribute must be synthesized from certificate label + pref item type + key usage, 619 // as only the account and service attributes can make a generic keychain item unique. 620 // For 'iprf' type items (but not 'cprf'), we append a trailing space. This insures that 621 // we can save a certificate preference if an identity preference already exists for the 622 // given service name, and vice-versa. 623 // If the key usage is 0 (i.e. the normal case), we omit the appended key usage string. 624 // 625 CFStringRef labelStr = nil; 626 SecCertificateInferLabel(certRef.get(), &labelStr); 627 if (!labelStr) { 628 CFReleaseNull(perAppName); 629 MacOSError::throwMe(errSecDataTooLarge); // data is "in a format which cannot be displayed" 630 } 631 CFIndex accountUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(labelStr), kCFStringEncodingUTF8) + 1; 632 const char *templateStr = "%s [key usage 0x%X]"; 633 const int keyUsageMaxStrLen = 8; 634 accountUTF8Len += strlen(templateStr) + keyUsageMaxStrLen; 635 char *accountUTF8 = (char *)malloc(accountUTF8Len); 636 if (!accountUTF8) { 637 CFReleaseNull(perAppName); 638 MacOSError::throwMe(errSecMemoryError); 639 } 640 if (!CFStringGetCString(labelStr, accountUTF8, accountUTF8Len-1, kCFStringEncodingUTF8)) { 641 accountUTF8[0] = (char)'\0'; 642 } 643 if (keyUsage) { 644 snprintf(accountUTF8, accountUTF8Len-1, templateStr, accountUTF8, keyUsage); 645 } 646 snprintf(accountUTF8, accountUTF8Len-1, "%s ", accountUTF8); 647 CssmDataContainer account(const_cast<char *>(accountUTF8), strlen(accountUTF8)); 648 free(accountUTF8); 649 CFRelease(labelStr); 650 651 // service attribute (name provided by the caller) 652 CFIndex serviceUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(perAppName), kCFStringEncodingUTF8) + 1;; 653 char *serviceUTF8 = (char *)malloc(serviceUTF8Len); 654 if (!serviceUTF8) { 655 CFReleaseNull(perAppName); 656 MacOSError::throwMe(errSecMemoryError); 657 } 658 if (!CFStringGetCString(perAppName, serviceUTF8, serviceUTF8Len-1, kCFStringEncodingUTF8)) { 659 serviceUTF8[0] = (char)'\0'; 660 } 661 CssmDataContainer service(const_cast<char *>(serviceUTF8), strlen(serviceUTF8)); 662 free(serviceUTF8); 663 CFRelease(perAppName); 664 665 // look for existing identity preference item, in case this is an update 666 StorageManager::KeychainList keychains; 667 globals().storageManager.getSearchList(keychains); 668 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); 669 FourCharCode itemType = 'iprf'; 670 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service); 671 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), itemType); 672 if (keyUsage) { 673 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); 674 } 675 676 Item item(kSecGenericPasswordItemClass, 'aapl', 0, NULL, false); 677 bool add = (!cursor->next(item)); 678 // at this point, we either have a new item to add or an existing item to update 679 680 // set item attribute values 681 item->setAttribute(Schema::attributeInfo(kSecServiceItemAttr), service); 682 item->setAttribute(Schema::attributeInfo(kSecTypeItemAttr), itemType); 683 item->setAttribute(Schema::attributeInfo(kSecAccountItemAttr), account); 684 item->setAttribute(Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); 685 item->setAttribute(Schema::attributeInfo(kSecLabelItemAttr), service); 686 687 // generic attribute (store persistent certificate reference) 688 CFDataRef pItemRef = nil; 689 SecKeychainItemCreatePersistentReference((SecKeychainItemRef)certRef.get(), &pItemRef); 690 if (!pItemRef) { 691 MacOSError::throwMe(errSecInvalidItemRef); 692 } 693 const UInt8 *dataPtr = CFDataGetBytePtr(pItemRef); 694 CFIndex dataLen = CFDataGetLength(pItemRef); 695 CssmData pref(const_cast<void *>(reinterpret_cast<const void *>(dataPtr)), dataLen); 696 item->setAttribute(Schema::attributeInfo(kSecGenericItemAttr), pref); 697 CFRelease(pItemRef); 698 699 if (add) { 700 Keychain keychain = nil; 701 try { 702 keychain = globals().storageManager.defaultKeychain(); 703 if (!keychain->exists()) 704 MacOSError::throwMe(errSecNoSuchKeychain); // Might be deleted or not available at this time. 705 } 706 catch(...) { 707 keychain = globals().storageManager.defaultKeychainUI(item); 708 } 709 710 try { 711 keychain->add(item); 712 } 713 catch (const MacOSError &err) { 714 if (err.osStatus() != errSecDuplicateItem) { 715 throw; // if item already exists, fall through to update 716 } 717 } 718 } 719 item->update(); 720 721 END_SECAPI 722 } 723 724 OSStatus 725 SecIdentitySetPreferred(SecIdentityRef identity, CFStringRef name, CFArrayRef keyUsage) 726 { 727 COUNTLEGACYAPI 728 CSSM_KEYUSE keyUse = ConvertArrayToKeyUsage(keyUsage); 729 return SecIdentitySetPreference(identity, name, keyUse); 730 } 731 732 OSStatus 733 SecIdentityFindPreferenceItemWithNameAndKeyUsage( 734 CFTypeRef keychainOrArray, 735 CFStringRef name, 736 int32_t keyUsage, 737 SecKeychainItemRef *itemRef) 738 { 739 BEGIN_SECAPI 740 os_activity_t activity = os_activity_create("SecIdentityFindPreferenceItemWithNameAndKeyUsage", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 741 os_activity_scope(activity); 742 os_release(activity); 743 744 StorageManager::KeychainList keychains; 745 globals().storageManager.optionalSearchList(keychainOrArray, keychains); 746 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); 747 748 char idUTF8[MAXPATHLEN]; 749 idUTF8[0] = (char)'\0'; 750 if (name) 751 { 752 if (!CFStringGetCString(name, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8)) 753 idUTF8[0] = (char)'\0'; 754 } 755 size_t idUTF8Len = strlen(idUTF8); 756 if (!idUTF8Len) 757 MacOSError::throwMe(errSecParam); 758 759 CssmData service(const_cast<char *>(idUTF8), idUTF8Len); 760 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service); 761 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf'); 762 if (keyUsage) { 763 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); 764 } 765 766 Item item; 767 if (!cursor->next(item)) 768 MacOSError::throwMe(errSecItemNotFound); 769 770 if (itemRef) 771 *itemRef=item->handle(); 772 773 END_SECAPI 774 } 775 776 OSStatus SecIdentityDeletePreferenceItemWithNameAndKeyUsage( 777 CFTypeRef keychainOrArray, 778 CFStringRef name, 779 int32_t keyUsage) 780 { 781 COUNTLEGACYAPI 782 // when a specific key usage is passed, we'll only match & delete that pref; 783 // when a key usage of 0 is passed, all matching prefs should be deleted. 784 // maxUsages represents the most matches there could theoretically be, so 785 // cut things off at that point if we're still finding items (if they can't 786 // be deleted for some reason, we'd never break out of the loop.) 787 788 OSStatus status = errSecInternalError; 789 SecKeychainItemRef item = NULL; 790 int count = 0, maxUsages = 12; 791 while (++count <= maxUsages && 792 (status = SecIdentityFindPreferenceItemWithNameAndKeyUsage(keychainOrArray, name, keyUsage, &item)) == errSecSuccess) { 793 status = SecKeychainItemDelete(item); 794 CFRelease(item); 795 item = NULL; 796 } 797 798 // it's not an error if the item isn't found 799 return (status == errSecItemNotFound) ? errSecSuccess : status; 800 } 801 802 OSStatus SecIdentityDeleteApplicationPreferenceItems(void) 803 { 804 BEGIN_SECAPI 805 os_activity_t activity = os_activity_create("SecIdentityDeleteApplicationPreferenceItems", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 806 os_activity_scope(activity); 807 os_release(activity); 808 809 StorageManager::KeychainList keychains; 810 globals().storageManager.optionalSearchList(NULL, keychains); 811 KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); 812 cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf'); 813 814 CFMutableArrayRef matches = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 815 CFStringRef appIdentifier = SecXPCCopyClientApplicationIdentifier(); 816 CFStringRef suffixString = NULL; 817 if (appIdentifier) { 818 suffixString = CFStringCreateWithFormat(NULL, NULL, CFSTR(" (%@)"), appIdentifier); 819 CFReleaseNull(appIdentifier); 820 } 821 if (!suffixString || !matches) { 822 CFReleaseNull(matches); 823 CFReleaseNull(suffixString); 824 MacOSError::throwMe(errSecItemNotFound); 825 } 826 // iterate through candidate items and add matches to array 827 Item item; 828 while (cursor->next(item)) { 829 SecKeychainAttribute attr[] = { 830 { kSecServiceItemAttr, 0, NULL }, 831 }; 832 SecKeychainAttributeList attrList = { sizeof(attr) / sizeof(SecKeychainAttribute), attr }; 833 SecKeychainItemRef itemRef = item->handle(); 834 if (errSecSuccess == SecKeychainItemCopyContent(itemRef, NULL, &attrList, NULL, NULL)) { 835 if (attr[0].length > 0 && attr[0].data != NULL) { 836 CFStringRef serviceString = CFStringCreateWithBytes(NULL, 837 (const UInt8 *)attr[0].data, attr[0].length, 838 kCFStringEncodingUTF8, false); 839 if (serviceString && (CFStringHasSuffix(serviceString, suffixString))) { 840 CFArrayAppendValue(matches, itemRef); 841 } 842 CFReleaseNull(serviceString); 843 } 844 SecKeychainItemFreeContent(&attrList, NULL); 845 } 846 CFReleaseNull(itemRef); 847 } 848 // delete matching items, if any 849 CFIndex numDeleted=0, count = CFArrayGetCount(matches); 850 OSStatus status = (count > 0) ? errSecSuccess : errSecItemNotFound; 851 for (CFIndex idx=0; idx < count; idx++) { 852 SecKeychainItemRef itemRef = (SecKeychainItemRef)CFArrayGetValueAtIndex(matches, idx); 853 OSStatus tmpStatus = SecKeychainItemDelete(itemRef); 854 if (errSecSuccess == tmpStatus) { 855 ++numDeleted; 856 } else { 857 status = tmpStatus; // remember failure, but keep going 858 } 859 } 860 syslog(LOG_NOTICE, "identity preferences found: %ld, deleted: %ld (status: %ld)", 861 (long)count, (long)numDeleted, (long)status); 862 CFReleaseNull(matches); 863 CFReleaseNull(suffixString); 864 865 if (status != errSecSuccess) { 866 MacOSError::throwMe(status); 867 } 868 END_SECAPI 869 } 870 871 /* 872 * System Identity Support. 873 */ 874 875 /* plist domain (in /Library/Preferences) */ 876 #define IDENTITY_DOMAIN "com.apple.security.systemidentities" 877 878 /* 879 * Our plist is a dictionary whose entries have the following format: 880 * key = domain name as CFString 881 * value = public key hash as CFData 882 */ 883 884 #define SYSTEM_KEYCHAIN_PATH kSystemKeychainDir "/" kSystemKeychainName 885 886 /* 887 * All accesses to system identities and its associated plist are 888 * protected by this lock. 889 */ 890 ModuleNexus<Mutex> systemIdentityLock; 891 892 OSStatus SecIdentityCopySystemIdentity( 893 CFStringRef domain, 894 SecIdentityRef *idRef, 895 CFStringRef *actualDomain) /* optional */ 896 { 897 BEGIN_SECAPI 898 os_activity_t activity = os_activity_create("SecIdentityCopySystemIdentity", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 899 os_activity_scope(activity); 900 os_release(activity); 901 902 StLock<Mutex> _(systemIdentityLock()); 903 unique_ptr<Dictionary> identDict; 904 905 /* get top-level dictionary - if not present, we're done */ 906 Dictionary* d = Dictionary::CreateDictionary(IDENTITY_DOMAIN, Dictionary::US_System); 907 if (d == NULL) 908 { 909 return errSecNotAvailable; 910 } 911 912 identDict.reset(d); 913 914 /* see if there's an entry for specified domain */ 915 CFDataRef entryValue = identDict->getDataValue(domain); 916 if(entryValue == NULL) { 917 /* try for default entry if we're not already looking for default */ 918 if(!CFEqual(domain, kSecIdentityDomainDefault)) { 919 entryValue = identDict->getDataValue(kSecIdentityDomainDefault); 920 } 921 if(entryValue == NULL) { 922 /* no default identity */ 923 MacOSError::throwMe(errSecItemNotFound); 924 } 925 926 /* remember that we're not fetching the requested domain */ 927 domain = kSecIdentityDomainDefault; 928 } 929 930 /* open system keychain - error here is fatal */ 931 Keychain systemKc = globals().storageManager.make(SYSTEM_KEYCHAIN_PATH, false); 932 CFRef<SecKeychainRef> systemKcRef(systemKc->handle()); 933 StorageManager::KeychainList keychains; 934 globals().storageManager.optionalSearchList(systemKcRef, keychains); 935 936 /* search for specified cert */ 937 SecKeychainAttributeList attrList; 938 SecKeychainAttribute attr; 939 attr.tag = kSecPublicKeyHashItemAttr; 940 attr.length = (UInt32)CFDataGetLength(entryValue); 941 attr.data = (void *)CFDataGetBytePtr(entryValue); 942 attrList.count = 1; 943 attrList.attr = &attr; 944 945 KCCursor cursor(keychains, kSecCertificateItemClass, &attrList); 946 Item certItem; 947 if(!cursor->next(certItem)) { 948 MacOSError::throwMe(errSecItemNotFound); 949 } 950 951 /* found the cert; try matching with key to cook up identity */ 952 SecPointer<Certificate> certificate(static_cast<Certificate *>(certItem.get())); 953 SecPointer<Identity> identity(new Identity(keychains, certificate)); 954 955 Required(idRef) = identity->handle(); 956 if(actualDomain) { 957 *actualDomain = domain; 958 CFRetain(*actualDomain); 959 } 960 961 END_SECAPI 962 } 963 964 OSStatus SecIdentitySetSystemIdentity( 965 CFStringRef domain, 966 SecIdentityRef idRef) 967 { 968 BEGIN_SECAPI 969 os_activity_t activity = os_activity_create("SecIdentitySetSystemIdentity", OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_IF_NONE_PRESENT); 970 os_activity_scope(activity); 971 os_release(activity); 972 973 StLock<Mutex> _(systemIdentityLock()); 974 if(geteuid() != 0) { 975 MacOSError::throwMe(errSecAuthFailed); 976 } 977 978 unique_ptr<MutableDictionary> identDict; 979 MutableDictionary *d = MutableDictionary::CreateMutableDictionary(IDENTITY_DOMAIN, Dictionary::US_System); 980 if (d) 981 { 982 identDict.reset(d); 983 } 984 else 985 { 986 if(idRef == NULL) { 987 /* nothing there, nothing to set - done */ 988 return errSecSuccess; 989 } 990 identDict.reset(new MutableDictionary()); 991 } 992 993 if(idRef == NULL) { 994 /* Just delete the possible entry for this domain */ 995 identDict->removeValue(domain); 996 } 997 else { 998 /* obtain public key hash of identity's cert */ 999 SecPointer<Identity> identity(Identity::required(idRef)); 1000 SecPointer<Certificate> cert = identity->certificate(); 1001 const CssmData &pubKeyHash = cert->publicKeyHash(); 1002 CFRef<CFDataRef> pubKeyHashData(CFDataCreate(NULL, pubKeyHash.Data, 1003 pubKeyHash.Length)); 1004 1005 /* add/replace to dictionary */ 1006 identDict->setValue(domain, pubKeyHashData); 1007 } 1008 1009 /* flush to disk */ 1010 if(!identDict->writePlistToPrefs(IDENTITY_DOMAIN, Dictionary::US_System)) { 1011 MacOSError::throwMe(errSecIO); 1012 } 1013 1014 END_SECAPI 1015 } 1016 1017 const CFStringRef kSecIdentityDomainDefault = CFSTR("com.apple.systemdefault"); 1018 const CFStringRef kSecIdentityDomainKerberosKDC = CFSTR("com.apple.kerberos.kdc"); 1019