X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/SecIdentity.cpp?ds=inline diff --git a/Security/libsecurity_keychain/lib/SecIdentity.cpp b/Security/libsecurity_keychain/lib/SecIdentity.cpp new file mode 100644 index 00000000..5c16a1b3 --- /dev/null +++ b/Security/libsecurity_keychain/lib/SecIdentity.cpp @@ -0,0 +1,1119 @@ +/* + * Copyright (c) 2002-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include + +#include "SecBridge.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +/* private function declarations */ +OSStatus +SecIdentityFindPreferenceItemWithNameAndKeyUsage( + CFTypeRef keychainOrArray, + CFStringRef name, + int32_t keyUsage, + SecKeychainItemRef *itemRef); + +OSStatus SecIdentityDeletePreferenceItemWithNameAndKeyUsage( + CFTypeRef keychainOrArray, + CFStringRef name, + int32_t keyUsage); + + +CSSM_KEYUSE ConvertArrayToKeyUsage(CFArrayRef usage) +{ + CFIndex count = 0; + CSSM_KEYUSE result = (CSSM_KEYUSE) 0; + + if ((NULL == usage) || (0 == (count = CFArrayGetCount(usage)))) + { + return result; + } + + for (CFIndex iCnt = 0; iCnt < count; iCnt++) + { + CFStringRef keyUsageStr = NULL; + keyUsageStr = (CFStringRef)CFArrayGetValueAtIndex(usage,iCnt); + if (NULL != keyUsageStr) + { + if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanEncrypt, keyUsageStr, 0)) + { + result |= CSSM_KEYUSE_ENCRYPT; + } + else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanDecrypt, keyUsageStr, 0)) + { + result |= CSSM_KEYUSE_DECRYPT; + } + else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanDerive, keyUsageStr, 0)) + { + result |= CSSM_KEYUSE_DERIVE; + } + else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanSign, keyUsageStr, 0)) + { + result |= CSSM_KEYUSE_SIGN; + } + else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanVerify, keyUsageStr, 0)) + { + result |= CSSM_KEYUSE_VERIFY; + } + else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanWrap, keyUsageStr, 0)) + { + result |= CSSM_KEYUSE_WRAP; + } + else if (kCFCompareEqualTo == CFStringCompare((CFStringRef)kSecAttrCanUnwrap, keyUsageStr, 0)) + { + result |= CSSM_KEYUSE_UNWRAP; + } + } + } + + return result; +} + + +CFTypeID +SecIdentityGetTypeID(void) +{ + BEGIN_SECAPI + + return gTypes().Identity.typeID; + + END_SECAPI1(_kCFRuntimeNotATypeID) +} + + +OSStatus +SecIdentityCopyCertificate( + SecIdentityRef identityRef, + SecCertificateRef *certificateRef) +{ + BEGIN_SECAPI + + SecPointer certificatePtr(Identity::required(identityRef)->certificate()); + Required(certificateRef) = certificatePtr->handle(); + + END_SECAPI +} + + +OSStatus +SecIdentityCopyPrivateKey( + SecIdentityRef identityRef, + SecKeyRef *privateKeyRef) +{ + BEGIN_SECAPI + + SecPointer keyItemPtr(Identity::required(identityRef)->privateKey()); + Required(privateKeyRef) = keyItemPtr->handle(); + + END_SECAPI +} + +OSStatus +SecIdentityCreateWithCertificate( + CFTypeRef keychainOrArray, + SecCertificateRef certificateRef, + SecIdentityRef *identityRef) +{ + BEGIN_SECAPI + + SecPointer certificatePtr(Certificate::required(certificateRef)); + StorageManager::KeychainList keychains; + globals().storageManager.optionalSearchList(keychainOrArray, keychains); + SecPointer identityPtr(new Identity(keychains, certificatePtr)); + Required(identityRef) = identityPtr->handle(); + + END_SECAPI +} + +SecIdentityRef +SecIdentityCreate( + CFAllocatorRef allocator, + SecCertificateRef certificate, + SecKeyRef privateKey) +{ + SecIdentityRef identityRef = NULL; + OSStatus __secapiresult; + try { + SecPointer certificatePtr(Certificate::required(certificate)); + SecPointer keyItemPtr(KeyItem::required(privateKey)); + SecPointer identityPtr(new Identity(keyItemPtr, certificatePtr)); + identityRef = identityPtr->handle(); + + __secapiresult=errSecSuccess; + } + catch (const MacOSError &err) { __secapiresult=err.osStatus(); } + catch (const CommonError &err) { __secapiresult=SecKeychainErrFromOSStatus(err.osStatus()); } + catch (const std::bad_alloc &) { __secapiresult=errSecAllocate; } + catch (...) { __secapiresult=errSecInternalComponent; } + return identityRef; +} + +CFComparisonResult +SecIdentityCompare( + SecIdentityRef identity1, + SecIdentityRef identity2, + CFOptionFlags compareOptions) +{ + if (!identity1 || !identity2) + { + if (identity1 == identity2) + return kCFCompareEqualTo; + else if (identity1 < identity2) + return kCFCompareLessThan; + else + return kCFCompareGreaterThan; + } + + BEGIN_SECAPI + + SecPointer id1(Identity::required(identity1)); + SecPointer id2(Identity::required(identity2)); + + if (id1 == id2) + return kCFCompareEqualTo; + else if (id1 < id2) + return kCFCompareLessThan; + else + return kCFCompareGreaterThan; + + END_SECAPI1(kCFCompareGreaterThan); +} + +static +CFArrayRef _SecIdentityCopyPossiblePaths( + CFStringRef name) +{ + // utility function to build and return an array of possible paths for the given name. + // if name is not a URL, this returns a single-element array. + // if name is a URL, the array may contain 1..N elements, one for each level of the path hierarchy. + + CFMutableArrayRef names = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + if (!name) { + return names; + } + CFIndex oldLength = CFStringGetLength(name); + CFArrayAppendValue(names, name); + + CFURLRef url = CFURLCreateWithString(NULL, name, NULL); + if (url) { + if (CFURLCanBeDecomposed(url)) { + // first, remove the query portion of this URL, if any + CFStringRef qs = CFURLCopyQueryString(url, NULL); + if (qs) { + CFMutableStringRef newName = CFStringCreateMutableCopy(NULL, oldLength, name); + if (newName) { + CFIndex qsLength = CFStringGetLength(qs) + 1; // include the '?' + CFStringDelete(newName, CFRangeMake(oldLength-qsLength, qsLength)); + CFRelease(url); + url = CFURLCreateWithString(NULL, newName, NULL); + CFArraySetValueAtIndex(names, 0, newName); + CFRelease(newName); + } + CFRelease(qs); + } + // now add an entry for each level of the path + while (url) { + CFURLRef parent = CFURLCreateCopyDeletingLastPathComponent(NULL, url); + if (parent) { + CFStringRef parentURLString = CFURLGetString(parent); + if (parentURLString) { + CFIndex newLength = CFStringGetLength(parentURLString); + // check that string length has decreased as expected; for file URLs, + // CFURLCreateCopyDeletingLastPathComponent can insert './' or '../' + if ((newLength >= oldLength) || (!CFStringHasPrefix(name, parentURLString))) { + CFRelease(parent); + CFRelease(url); + break; + } + oldLength = newLength; + CFArrayAppendValue(names, parentURLString); + } + } + CFRelease(url); + url = parent; + } + } + else { + CFRelease(url); + } + } + // finally, add wildcard entries for each subdomain + url = CFURLCreateWithString(NULL, name, NULL); + if (url) { + if (CFURLCanBeDecomposed(url)) { + CFStringRef netLocString = CFURLCopyNetLocation(url); + if (netLocString) { + // first strip off port number, if present + CFStringRef tmpLocString = netLocString; + CFArrayRef hostnameArray = CFStringCreateArrayBySeparatingStrings(NULL, netLocString, CFSTR(":")); + tmpLocString = (CFStringRef)CFRetain((CFStringRef)CFArrayGetValueAtIndex(hostnameArray, 0)); + CFRelease(netLocString); + CFRelease(hostnameArray); + netLocString = tmpLocString; + // split remaining string into domain components + hostnameArray = CFStringCreateArrayBySeparatingStrings(NULL, netLocString, CFSTR(".")); + CFIndex subdomainCount = CFArrayGetCount(hostnameArray); + CFIndex i = 0; + while (++i < subdomainCount) { + CFIndex j = i; + CFMutableStringRef wildcardString = CFStringCreateMutable(NULL, 0); + if (wildcardString) { + CFStringAppendCString(wildcardString, "*", kCFStringEncodingUTF8); + while (j < subdomainCount) { + CFStringRef domainString = (CFStringRef)CFArrayGetValueAtIndex(hostnameArray, j++); + if (CFStringGetLength(domainString) > 0) { + CFStringAppendCString(wildcardString, ".", kCFStringEncodingUTF8); + CFStringAppend(wildcardString, domainString); + } + } + if (CFStringGetLength(wildcardString) > 1) { + CFArrayAppendValue(names, wildcardString); + } + CFRelease(wildcardString); + } + } + CFRelease(hostnameArray); + CFRelease(netLocString); + } + } + CFRelease(url); + } + + return names; +} + +static +OSStatus _SecIdentityCopyPreferenceMatchingName( + CFStringRef name, + CSSM_KEYUSE keyUsage, + CFArrayRef validIssuers, + SecIdentityRef *identity) +{ + // this is NOT exported, and called only from SecIdentityCopyPreference (below), so no BEGIN/END macros here; + // caller must handle exceptions + + StorageManager::KeychainList keychains; + globals().storageManager.getSearchList(keychains); + KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); + + char idUTF8[MAXPATHLEN]; + Required(name); + if (!CFStringGetCString(name, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8)) + idUTF8[0] = (char)'\0'; + CssmData service(const_cast(idUTF8), strlen(idUTF8)); + FourCharCode itemType = 'iprf'; + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service); + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), itemType); + if (keyUsage) + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); + + Item prefItem; + if (!cursor->next(prefItem)) + return errSecItemNotFound; + + // get persistent certificate reference + SecKeychainAttribute itemAttrs[] = { { kSecGenericItemAttr, 0, NULL } }; + SecKeychainAttributeList itemAttrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs }; + prefItem->getContent(NULL, &itemAttrList, NULL, NULL); + + // find certificate, given persistent reference data + CFDataRef pItemRef = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)itemAttrs[0].data, itemAttrs[0].length, kCFAllocatorNull); + SecKeychainItemRef certItemRef = nil; + OSStatus status = SecKeychainItemCopyFromPersistentReference(pItemRef, &certItemRef); //%%% need to make this a method of ItemImpl + prefItem->freeContent(&itemAttrList, NULL); + if (pItemRef) + CFRelease(pItemRef); + if (status) + return status; + + // filter on valid issuers, if provided + if (validIssuers) { + //%%%TBI + } + + // create identity reference, given certificate + Item certItem = ItemImpl::required(SecKeychainItemRef(certItemRef)); + SecPointer certificate(static_cast(certItem.get())); + SecPointer identity_ptr(new Identity(keychains, certificate)); + if (certItemRef) + CFRelease(certItemRef); + + Required(identity) = identity_ptr->handle(); + + return status; +} + +SecIdentityRef SecIdentityCopyPreferred(CFStringRef name, CFArrayRef keyUsage, CFArrayRef validIssuers) +{ + // This function will look for a matching preference in the following order: + // - matches the name and the supplied key use + // - matches the name and the special 'ANY' key use + // - matches the name with no key usage constraint + + SecIdentityRef identityRef = NULL; + CSSM_KEYUSE keyUse = ConvertArrayToKeyUsage(keyUsage); + OSStatus status = SecIdentityCopyPreference(name, keyUse, validIssuers, &identityRef); + if (status != errSecSuccess && keyUse != CSSM_KEYUSE_ANY) + status = SecIdentityCopyPreference(name, CSSM_KEYUSE_ANY, validIssuers, &identityRef); + if (status != errSecSuccess && keyUse != 0) + status = SecIdentityCopyPreference(name, 0, validIssuers, &identityRef); + + return identityRef; +} + +OSStatus SecIdentityCopyPreference( + CFStringRef name, + CSSM_KEYUSE keyUsage, + CFArrayRef validIssuers, + SecIdentityRef *identity) +{ + // The original implementation of SecIdentityCopyPreference matches the exact string only. + // That implementation has been moved to _SecIdentityCopyPreferenceMatchingName (above), + // and this function is a wrapper which calls it, so that existing clients will get the + // extended behavior of server domain matching for items that specify URLs. + // (Note that behavior is unchanged if the specified name is not a URL.) + + BEGIN_SECAPI + + CFTypeRef val = (CFTypeRef)CFPreferencesCopyValue(CFSTR("LogIdentityPreferenceLookup"), + CFSTR("com.apple.security"), + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost); + Boolean logging = false; + if (val && CFGetTypeID(val) == CFBooleanGetTypeID()) { + logging = CFBooleanGetValue((CFBooleanRef)val); + CFRelease(val); + } + + OSStatus status = errSecItemNotFound; + CFArrayRef names = _SecIdentityCopyPossiblePaths(name); + if (!names) { + return status; + } + + CFIndex idx, total = CFArrayGetCount(names); + for (idx = 0; idx < total; idx++) { + CFStringRef aName = (CFStringRef)CFArrayGetValueAtIndex(names, idx); + try { + status = _SecIdentityCopyPreferenceMatchingName(aName, keyUsage, validIssuers, identity); + } + catch (...) { status = errSecItemNotFound; } + + if (logging) { + // get identity label + CFStringRef labelString = NULL; + if (!status && identity && *identity) { + try { + SecPointer cert(Identity::required(*identity)->certificate()); + cert->inferLabel(false, &labelString); + } + catch (...) { labelString = NULL; }; + } + char *labelBuf = NULL; + CFIndex labelBufSize = (labelString) ? CFStringGetLength(labelString) * 4 : 4; + labelBuf = (char *)malloc(labelBufSize); + if (!labelString || !CFStringGetCString(labelString, labelBuf, labelBufSize, kCFStringEncodingUTF8)) { + labelBuf[0] = 0; + } + if (labelString) { + CFRelease(labelString); + } + + // get service name + char *serviceBuf = NULL; + CFIndex serviceBufSize = CFStringGetLength(aName) * 4; + serviceBuf = (char *)malloc(serviceBufSize); + if (!CFStringGetCString(aName, serviceBuf, serviceBufSize, kCFStringEncodingUTF8)) { + serviceBuf[0] = 0; + } + + syslog(LOG_NOTICE, "preferred identity: \"%s\" found for \"%s\"\n", labelBuf, serviceBuf); + if (!status && name) { + char *nameBuf = NULL; + CFIndex nameBufSize = CFStringGetLength(name) * 4; + nameBuf = (char *)malloc(nameBufSize); + if (!CFStringGetCString(name, nameBuf, nameBufSize, kCFStringEncodingUTF8)) { + nameBuf[0] = 0; + } + syslog(LOG_NOTICE, "lookup complete; will use: \"%s\" for \"%s\"\n", labelBuf, nameBuf); + free(nameBuf); + } + + free(labelBuf); + free(serviceBuf); + } + + if (status == errSecSuccess) { + break; // match found + } + } + + CFRelease(names); + return status; + + END_SECAPI +} + +OSStatus SecIdentitySetPreference( + SecIdentityRef identity, + CFStringRef name, + CSSM_KEYUSE keyUsage) +{ + if (!name) { + return errSecParam; + } + if (!identity) { + // treat NULL identity as a request to clear the preference + // (note: if keyUsage is 0, this clears all key usage prefs for name) + return SecIdentityDeletePreferenceItemWithNameAndKeyUsage(NULL, name, keyUsage); + } + + BEGIN_SECAPI + + SecPointer certificate(Identity::required(identity)->certificate()); + + // determine the account attribute + // + // This attribute must be synthesized from certificate label + pref item type + key usage, + // as only the account and service attributes can make a generic keychain item unique. + // For 'iprf' type items (but not 'cprf'), we append a trailing space. This insures that + // we can save a certificate preference if an identity preference already exists for the + // given service name, and vice-versa. + // If the key usage is 0 (i.e. the normal case), we omit the appended key usage string. + // + CFStringRef labelStr = nil; + certificate->inferLabel(false, &labelStr); + if (!labelStr) { + MacOSError::throwMe(errSecDataTooLarge); // data is "in a format which cannot be displayed" + } + CFIndex accountUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(labelStr), kCFStringEncodingUTF8) + 1; + const char *templateStr = "%s [key usage 0x%X]"; + const int keyUsageMaxStrLen = 8; + accountUTF8Len += strlen(templateStr) + keyUsageMaxStrLen; + char accountUTF8[accountUTF8Len]; + if (!CFStringGetCString(labelStr, accountUTF8, accountUTF8Len-1, kCFStringEncodingUTF8)) + accountUTF8[0] = (char)'\0'; + if (keyUsage) + snprintf(accountUTF8, accountUTF8Len-1, templateStr, accountUTF8, keyUsage); + snprintf(accountUTF8, accountUTF8Len-1, "%s ", accountUTF8); + CssmData account(const_cast(accountUTF8), strlen(accountUTF8)); + CFRelease(labelStr); + + // service attribute (name provided by the caller) + CFIndex serviceUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(name), kCFStringEncodingUTF8) + 1;; + char serviceUTF8[serviceUTF8Len]; + if (!CFStringGetCString(name, serviceUTF8, serviceUTF8Len-1, kCFStringEncodingUTF8)) + serviceUTF8[0] = (char)'\0'; + CssmData service(const_cast(serviceUTF8), strlen(serviceUTF8)); + + // look for existing identity preference item, in case this is an update + StorageManager::KeychainList keychains; + globals().storageManager.getSearchList(keychains); + KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); + FourCharCode itemType = 'iprf'; + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service); + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), itemType); + if (keyUsage) { + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); + } + + Item item(kSecGenericPasswordItemClass, 'aapl', 0, NULL, false); + bool add = (!cursor->next(item)); + // at this point, we either have a new item to add or an existing item to update + + // set item attribute values + item->setAttribute(Schema::attributeInfo(kSecServiceItemAttr), service); + item->setAttribute(Schema::attributeInfo(kSecTypeItemAttr), itemType); + item->setAttribute(Schema::attributeInfo(kSecAccountItemAttr), account); + item->setAttribute(Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); + item->setAttribute(Schema::attributeInfo(kSecLabelItemAttr), service); + + // generic attribute (store persistent certificate reference) + CFDataRef pItemRef = nil; + certificate->copyPersistentReference(pItemRef); + if (!pItemRef) { + MacOSError::throwMe(errSecInvalidItemRef); + } + const UInt8 *dataPtr = CFDataGetBytePtr(pItemRef); + CFIndex dataLen = CFDataGetLength(pItemRef); + CssmData pref(const_cast(reinterpret_cast(dataPtr)), dataLen); + item->setAttribute(Schema::attributeInfo(kSecGenericItemAttr), pref); + CFRelease(pItemRef); + + if (add) { + Keychain keychain = nil; + try { + keychain = globals().storageManager.defaultKeychain(); + if (!keychain->exists()) + MacOSError::throwMe(errSecNoSuchKeychain); // Might be deleted or not available at this time. + } + catch(...) { + keychain = globals().storageManager.defaultKeychainUI(item); + } + + try { + keychain->add(item); + } + catch (const MacOSError &err) { + if (err.osStatus() != errSecDuplicateItem) + throw; // if item already exists, fall through to update + } + } + item->update(); + + END_SECAPI +} + +OSStatus +SecIdentitySetPreferred(SecIdentityRef identity, CFStringRef name, CFArrayRef keyUsage) +{ + CSSM_KEYUSE keyUse = ConvertArrayToKeyUsage(keyUsage); + return SecIdentitySetPreference(identity, name, keyUse); +} + +OSStatus +SecIdentityFindPreferenceItem( + CFTypeRef keychainOrArray, + CFStringRef idString, + SecKeychainItemRef *itemRef) +{ + BEGIN_SECAPI + + StorageManager::KeychainList keychains; + globals().storageManager.optionalSearchList(keychainOrArray, keychains); + KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); + + char idUTF8[MAXPATHLEN]; + idUTF8[0] = (char)'\0'; + if (idString) + { + if (!CFStringGetCString(idString, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8)) + idUTF8[0] = (char)'\0'; + } + size_t idUTF8Len = strlen(idUTF8); + if (!idUTF8Len) + MacOSError::throwMe(errSecParam); + + CssmData service(const_cast(idUTF8), idUTF8Len); + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service); + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf'); + + Item item; + if (!cursor->next(item)) + MacOSError::throwMe(errSecItemNotFound); + + if (itemRef) + *itemRef=item->handle(); + + END_SECAPI +} + +OSStatus +SecIdentityFindPreferenceItemWithNameAndKeyUsage( + CFTypeRef keychainOrArray, + CFStringRef name, + int32_t keyUsage, + SecKeychainItemRef *itemRef) +{ + BEGIN_SECAPI + + StorageManager::KeychainList keychains; + globals().storageManager.optionalSearchList(keychainOrArray, keychains); + KCCursor cursor(keychains, kSecGenericPasswordItemClass, NULL); + + char idUTF8[MAXPATHLEN]; + idUTF8[0] = (char)'\0'; + if (name) + { + if (!CFStringGetCString(name, idUTF8, sizeof(idUTF8)-1, kCFStringEncodingUTF8)) + idUTF8[0] = (char)'\0'; + } + size_t idUTF8Len = strlen(idUTF8); + if (!idUTF8Len) + MacOSError::throwMe(errSecParam); + + CssmData service(const_cast(idUTF8), idUTF8Len); + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecServiceItemAttr), service); + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf'); + if (keyUsage) + cursor->add(CSSM_DB_EQUAL, Schema::attributeInfo(kSecScriptCodeItemAttr), (sint32)keyUsage); + + Item item; + if (!cursor->next(item)) + MacOSError::throwMe(errSecItemNotFound); + + if (itemRef) + *itemRef=item->handle(); + + END_SECAPI +} + +OSStatus SecIdentityDeletePreferenceItemWithNameAndKeyUsage( + CFTypeRef keychainOrArray, + CFStringRef name, + int32_t keyUsage) +{ + // when a specific key usage is passed, we'll only match & delete that pref; + // when a key usage of 0 is passed, all matching prefs should be deleted. + // maxUsages represents the most matches there could theoretically be, so + // cut things off at that point if we're still finding items (if they can't + // be deleted for some reason, we'd never break out of the loop.) + + OSStatus status; + SecKeychainItemRef item = NULL; + int count = 0, maxUsages = 12; + while (++count <= maxUsages && + (status = SecIdentityFindPreferenceItemWithNameAndKeyUsage(keychainOrArray, name, keyUsage, &item)) == errSecSuccess) { + status = SecKeychainItemDelete(item); + CFRelease(item); + item = NULL; + } + + // it's not an error if the item isn't found + return (status == errSecItemNotFound) ? errSecSuccess : status; +} + + +static +OSStatus _SecIdentityAddPreferenceItemWithName( + SecKeychainRef keychainRef, + SecIdentityRef identityRef, + CFStringRef idString, + SecKeychainItemRef *itemRef) +{ + // this is NOT exported, and called only from SecIdentityAddPreferenceItem (below), so no BEGIN/END macros here; + // caller must handle exceptions + + if (!identityRef || !idString) + return errSecParam; + SecPointer cert(Identity::required(identityRef)->certificate()); + Item item(kSecGenericPasswordItemClass, 'aapl', 0, NULL, false); + sint32 keyUsage = 0; + + // determine the account attribute + // + // This attribute must be synthesized from certificate label + pref item type + key usage, + // as only the account and service attributes can make a generic keychain item unique. + // For 'iprf' type items (but not 'cprf'), we append a trailing space. This insures that + // we can save a certificate preference if an identity preference already exists for the + // given service name, and vice-versa. + // If the key usage is 0 (i.e. the normal case), we omit the appended key usage string. + // + CFStringRef labelStr = nil; + cert->inferLabel(false, &labelStr); + if (!labelStr) { + return errSecDataTooLarge; // data is "in a format which cannot be displayed" + } + CFIndex accountUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(labelStr), kCFStringEncodingUTF8) + 1; + const char *templateStr = "%s [key usage 0x%X]"; + const int keyUsageMaxStrLen = 8; + accountUTF8Len += strlen(templateStr) + keyUsageMaxStrLen; + char accountUTF8[accountUTF8Len]; + if (!CFStringGetCString(labelStr, accountUTF8, accountUTF8Len-1, kCFStringEncodingUTF8)) + accountUTF8[0] = (char)'\0'; + if (keyUsage) + snprintf(accountUTF8, accountUTF8Len-1, templateStr, accountUTF8, keyUsage); + snprintf(accountUTF8, accountUTF8Len-1, "%s ", accountUTF8); + CssmData account(const_cast(accountUTF8), strlen(accountUTF8)); + CFRelease(labelStr); + + // service attribute (name provided by the caller) + CFIndex serviceUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(idString), kCFStringEncodingUTF8) + 1;; + char serviceUTF8[serviceUTF8Len]; + if (!CFStringGetCString(idString, serviceUTF8, serviceUTF8Len-1, kCFStringEncodingUTF8)) + serviceUTF8[0] = (char)'\0'; + CssmData service(const_cast(serviceUTF8), strlen(serviceUTF8)); + + // set item attribute values + item->setAttribute(Schema::attributeInfo(kSecServiceItemAttr), service); + item->setAttribute(Schema::attributeInfo(kSecLabelItemAttr), service); + item->setAttribute(Schema::attributeInfo(kSecTypeItemAttr), (FourCharCode)'iprf'); + item->setAttribute(Schema::attributeInfo(kSecAccountItemAttr), account); + item->setAttribute(Schema::attributeInfo(kSecScriptCodeItemAttr), keyUsage); + + // generic attribute (store persistent certificate reference) + CFDataRef pItemRef = nil; + OSStatus status = SecKeychainItemCreatePersistentReference((SecKeychainItemRef)cert->handle(), &pItemRef); + if (!pItemRef) + status = errSecInvalidItemRef; + if (status) + return status; + const UInt8 *dataPtr = CFDataGetBytePtr(pItemRef); + CFIndex dataLen = CFDataGetLength(pItemRef); + CssmData pref(const_cast(reinterpret_cast(dataPtr)), dataLen); + item->setAttribute(Schema::attributeInfo(kSecGenericItemAttr), pref); + CFRelease(pItemRef); + + Keychain keychain = nil; + try { + keychain = Keychain::optional(keychainRef); + if (!keychain->exists()) + MacOSError::throwMe(errSecNoSuchKeychain); // Might be deleted or not available at this time. + } + catch(...) { + keychain = globals().storageManager.defaultKeychainUI(item); + } + + try { + keychain->add(item); + } + catch (const MacOSError &err) { + if (err.osStatus() != errSecDuplicateItem) + throw; // if item already exists, fall through to update + } + + item->update(); + + if (itemRef) + *itemRef = item->handle(); + + return status; +} + +OSStatus SecIdentityAddPreferenceItem( + SecKeychainRef keychainRef, + SecIdentityRef identityRef, + CFStringRef idString, + SecKeychainItemRef *itemRef) +{ + // The original implementation of SecIdentityAddPreferenceItem adds the exact string only. + // That implementation has been moved to _SecIdentityAddPreferenceItemWithName (above), + // and this function is a wrapper which calls it, so that existing clients will get the + // extended behavior of server domain matching for items that specify URLs. + // (Note that behavior is unchanged if the specified idString is not a URL.) + + BEGIN_SECAPI + + OSStatus status = errSecInternalComponent; + CFArrayRef names = _SecIdentityCopyPossiblePaths(idString); + if (!names) { + return status; + } + + CFIndex total = CFArrayGetCount(names); + if (total > 0) { + // add item for name (first element in array) + CFStringRef aName = (CFStringRef)CFArrayGetValueAtIndex(names, 0); + try { + status = _SecIdentityAddPreferenceItemWithName(keychainRef, identityRef, aName, itemRef); + } + catch (const MacOSError &err) { status=err.osStatus(); } + catch (const CommonError &err) { status=SecKeychainErrFromOSStatus(err.osStatus()); } + catch (const std::bad_alloc &) { status=errSecAllocate; } + catch (...) { status=errSecInternalComponent; } + } + if (total > 2) { + Boolean setDomainDefaultIdentity = FALSE; + CFTypeRef val = (CFTypeRef)CFPreferencesCopyValue(CFSTR("SetDomainDefaultIdentity"), + CFSTR("com.apple.security.identities"), + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost); + if (val) { + if (CFGetTypeID(val) == CFBooleanGetTypeID()) + setDomainDefaultIdentity = CFBooleanGetValue((CFBooleanRef)val) ? TRUE : FALSE; + CFRelease(val); + } + if (setDomainDefaultIdentity) { + // add item for domain (second-to-last element in array, e.g. "*.apple.com") + OSStatus tmpStatus = errSecSuccess; + CFStringRef aName = (CFStringRef)CFArrayGetValueAtIndex(names, total-2); + try { + tmpStatus = _SecIdentityAddPreferenceItemWithName(keychainRef, identityRef, aName, itemRef); + } + catch (const MacOSError &err) { tmpStatus=err.osStatus(); } + catch (const CommonError &err) { tmpStatus=SecKeychainErrFromOSStatus(err.osStatus()); } + catch (const std::bad_alloc &) { tmpStatus=errSecAllocate; } + catch (...) { tmpStatus=errSecInternalComponent; } + } + } + + CFRelease(names); + return status; + + END_SECAPI +} + +/* deprecated in 10.5 */ +OSStatus SecIdentityUpdatePreferenceItem( + SecKeychainItemRef itemRef, + SecIdentityRef identityRef) +{ + BEGIN_SECAPI + + if (!itemRef || !identityRef) + MacOSError::throwMe(errSecParam); + SecPointer certificate(Identity::required(identityRef)->certificate()); + Item prefItem = ItemImpl::required(itemRef); + + // get the current key usage value for this item + sint32 keyUsage = 0; + UInt32 actLen = 0; + SecKeychainAttribute attr = { kSecScriptCodeItemAttr, sizeof(sint32), &keyUsage }; + try { + prefItem->getAttribute(attr, &actLen); + } + catch(...) { + keyUsage = 0; + }; + + // set the account attribute + // + // This attribute must be synthesized from certificate label + pref item type + key usage, + // as only the account and service attributes can make a generic keychain item unique. + // For 'iprf' type items (but not 'cprf'), we append a trailing space. This insures that + // we can save a certificate preference if an identity preference already exists for the + // given service name, and vice-versa. + // If the key usage is 0 (i.e. the normal case), we omit the appended key usage string. + // + CFStringRef labelStr = nil; + certificate->inferLabel(false, &labelStr); + if (!labelStr) { + MacOSError::throwMe(errSecDataTooLarge); // data is "in a format which cannot be displayed" + } + CFIndex accountUTF8Len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(labelStr), kCFStringEncodingUTF8) + 1; + const char *templateStr = "%s [key usage 0x%X]"; + const int keyUsageMaxStrLen = 8; + accountUTF8Len += strlen(templateStr) + keyUsageMaxStrLen; + char accountUTF8[accountUTF8Len]; + if (!CFStringGetCString(labelStr, accountUTF8, accountUTF8Len-1, kCFStringEncodingUTF8)) + accountUTF8[0] = (char)'\0'; + if (keyUsage) + snprintf(accountUTF8, accountUTF8Len-1, templateStr, accountUTF8, keyUsage); + snprintf(accountUTF8, accountUTF8Len-1, "%s ", accountUTF8); + CssmData account(const_cast(accountUTF8), strlen(accountUTF8)); + prefItem->setAttribute(Schema::attributeInfo(kSecAccountItemAttr), account); + CFRelease(labelStr); + + // generic attribute (store persistent certificate reference) + CFDataRef pItemRef = nil; + OSStatus status = SecKeychainItemCreatePersistentReference((SecKeychainItemRef)certificate->handle(), &pItemRef); + if (!pItemRef) + status = errSecInvalidItemRef; + if (status) + MacOSError::throwMe(status); + const UInt8 *dataPtr = CFDataGetBytePtr(pItemRef); + CFIndex dataLen = CFDataGetLength(pItemRef); + CssmData pref(const_cast(reinterpret_cast(dataPtr)), dataLen); + prefItem->setAttribute(Schema::attributeInfo(kSecGenericItemAttr), pref); + CFRelease(pItemRef); + + prefItem->update(); + + END_SECAPI +} + +OSStatus SecIdentityCopyFromPreferenceItem( + SecKeychainItemRef itemRef, + SecIdentityRef *identityRef) +{ + BEGIN_SECAPI + + if (!itemRef || !identityRef) + MacOSError::throwMe(errSecParam); + Item prefItem = ItemImpl::required(itemRef); + + // get persistent certificate reference + SecKeychainAttribute itemAttrs[] = { { kSecGenericItemAttr, 0, NULL } }; + SecKeychainAttributeList itemAttrList = { sizeof(itemAttrs) / sizeof(itemAttrs[0]), itemAttrs }; + prefItem->getContent(NULL, &itemAttrList, NULL, NULL); + + // find certificate, given persistent reference data + CFDataRef pItemRef = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)itemAttrs[0].data, itemAttrs[0].length, kCFAllocatorNull); + SecKeychainItemRef certItemRef = nil; + OSStatus status = SecKeychainItemCopyFromPersistentReference(pItemRef, &certItemRef); //%%% need to make this a method of ItemImpl + prefItem->freeContent(&itemAttrList, NULL); + if (pItemRef) + CFRelease(pItemRef); + if (status) + return status; + + // create identity reference, given certificate + StorageManager::KeychainList keychains; + globals().storageManager.optionalSearchList((CFTypeRef)NULL, keychains); + Item certItem = ItemImpl::required(SecKeychainItemRef(certItemRef)); + SecPointer certificate(static_cast(certItem.get())); + SecPointer identity(new Identity(keychains, certificate)); + if (certItemRef) + CFRelease(certItemRef); + + Required(identityRef) = identity->handle(); + + END_SECAPI +} + +/* + * System Identity Support. + */ + +/* plist domain (in /Library/Preferences) */ +#define IDENTITY_DOMAIN "com.apple.security.systemidentities" + +/* + * Our plist is a dictionary whose entries have the following format: + * key = domain name as CFString + * value = public key hash as CFData + */ + +#define SYSTEM_KEYCHAIN_PATH kSystemKeychainDir "/" kSystemKeychainName + +/* + * All accesses to system identities and its associated plist are + * protected by this lock. + */ +ModuleNexus systemIdentityLock; + +OSStatus SecIdentityCopySystemIdentity( + CFStringRef domain, + SecIdentityRef *idRef, + CFStringRef *actualDomain) /* optional */ +{ + BEGIN_SECAPI + + StLock _(systemIdentityLock()); + auto_ptr identDict; + + /* get top-level dictionary - if not present, we're done */ + Dictionary* d = Dictionary::CreateDictionary(IDENTITY_DOMAIN, Dictionary::US_System); + if (d == NULL) + { + return errSecNotAvailable; + } + + identDict.reset(d); + + /* see if there's an entry for specified domain */ + CFDataRef entryValue = identDict->getDataValue(domain); + if(entryValue == NULL) { + /* try for default entry if we're not already looking for default */ + if(!CFEqual(domain, kSecIdentityDomainDefault)) { + entryValue = identDict->getDataValue(kSecIdentityDomainDefault); + } + if(entryValue == NULL) { + /* no default identity */ + MacOSError::throwMe(errSecItemNotFound); + } + + /* remember that we're not fetching the requested domain */ + domain = kSecIdentityDomainDefault; + } + + /* open system keychain - error here is fatal */ + Keychain systemKc = globals().storageManager.make(SYSTEM_KEYCHAIN_PATH, false); + CFRef systemKcRef(systemKc->handle()); + StorageManager::KeychainList keychains; + globals().storageManager.optionalSearchList(systemKcRef, keychains); + + /* search for specified cert */ + SecKeychainAttributeList attrList; + SecKeychainAttribute attr; + attr.tag = kSecPublicKeyHashItemAttr; + attr.length = (UInt32)CFDataGetLength(entryValue); + attr.data = (void *)CFDataGetBytePtr(entryValue); + attrList.count = 1; + attrList.attr = &attr; + + KCCursor cursor(keychains, kSecCertificateItemClass, &attrList); + Item certItem; + if(!cursor->next(certItem)) { + MacOSError::throwMe(errSecItemNotFound); + } + + /* found the cert; try matching with key to cook up identity */ + SecPointer certificate(static_cast(certItem.get())); + SecPointer identity(new Identity(keychains, certificate)); + + Required(idRef) = identity->handle(); + if(actualDomain) { + *actualDomain = domain; + CFRetain(*actualDomain); + } + + END_SECAPI +} + +OSStatus SecIdentitySetSystemIdentity( + CFStringRef domain, + SecIdentityRef idRef) +{ + BEGIN_SECAPI + + StLock _(systemIdentityLock()); + if(geteuid() != 0) { + MacOSError::throwMe(errSecAuthFailed); + } + + auto_ptr identDict; + MutableDictionary *d = MutableDictionary::CreateMutableDictionary(IDENTITY_DOMAIN, Dictionary::US_System); + if (d) + { + identDict.reset(d); + } + else + { + if(idRef == NULL) { + /* nothing there, nothing to set - done */ + return errSecSuccess; + } + identDict.reset(new MutableDictionary()); + } + + if(idRef == NULL) { + /* Just delete the possible entry for this domain */ + identDict->removeValue(domain); + } + else { + /* obtain public key hash of identity's cert */ + SecPointer identity(Identity::required(idRef)); + SecPointer cert = identity->certificate(); + const CssmData &pubKeyHash = cert->publicKeyHash(); + CFRef pubKeyHashData(CFDataCreate(NULL, pubKeyHash.Data, + pubKeyHash.Length)); + + /* add/replace to dictionary */ + identDict->setValue(domain, pubKeyHashData); + } + + /* flush to disk */ + if(!identDict->writePlistToPrefs(IDENTITY_DOMAIN, Dictionary::US_System)) { + MacOSError::throwMe(errSecIO); + } + + END_SECAPI +} + +const CFStringRef kSecIdentityDomainDefault = CFSTR("com.apple.systemdefault"); +const CFStringRef kSecIdentityDomainKerberosKDC = CFSTR("com.apple.kerberos.kdc"); +