/ OSX / libsecurity_keychain / lib / SecIdentity.cpp
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