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