X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/SecItem.cpp?ds=sidebyside diff --git a/Security/libsecurity_keychain/lib/SecItem.cpp b/Security/libsecurity_keychain/lib/SecItem.cpp new file mode 100644 index 00000000..87fe7af3 --- /dev/null +++ b/Security/libsecurity_keychain/lib/SecItem.cpp @@ -0,0 +1,4784 @@ +/* + * Copyright (c) 2006-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 "SecBridge.h" +#include "SecInternal.h" +#include +#include +#include +#include +#include +#include +#include "cssmdatetime.h" +#include "SecItem.h" +#include "SecItemPriv.h" +#include "SecIdentitySearchPriv.h" +#include "SecKeychainPriv.h" +#include "SecCertificatePriv.h" +#include "SecCertificatePrivP.h" +#include "TrustAdditions.h" + +#include +#include + +#include +#include +#include +#include +#include + +const uint8_t kUUIDStringLength = 36; + +OSStatus SecItemAdd_osx(CFDictionaryRef attributes, CFTypeRef *result); +OSStatus SecItemCopyMatching_osx(CFDictionaryRef query, CFTypeRef *result); +OSStatus SecItemUpdate_osx(CFDictionaryRef query, CFDictionaryRef attributesToUpdate); +OSStatus SecItemDelete_osx(CFDictionaryRef query); + +extern "C" { +OSStatus SecItemAdd_ios(CFDictionaryRef attributes, CFTypeRef *result); +OSStatus SecItemCopyMatching_ios(CFDictionaryRef query, CFTypeRef *result); +OSStatus SecItemUpdate_ios(CFDictionaryRef query, CFDictionaryRef attributesToUpdate); +OSStatus SecItemDelete_ios(CFDictionaryRef query); + +CFTypeRef SecItemCreateFromAttributeDictionary(CFDictionaryRef refAttributes); +CFTypeRef SecItemCopyMergedResults(CFDictionaryRef query, CFTypeRef result_osx, CFTypeRef result_ios); +OSStatus SecItemValidateAppleApplicationGroupAccess(CFStringRef group); +CFDictionaryRef SecItemCopyTranslatedAttributes(CFDictionaryRef inOSXDict, CFTypeRef itemClass, + bool iOSOut, bool pruneMatch, bool pruneSync, bool pruneReturn, bool pruneData, bool pruneAccess); +} + +static Boolean SecItemSynchronizable(CFDictionaryRef query); + +static void secitemlog(int priority, const char *format, ...) +{ +#ifndef NDEBUG + // log everything +#else + if (priority < LOG_NOTICE) // log warnings and errors +#endif + { + va_list list; + va_start(list, format); + vsyslog(priority, format, list); + va_end(list); + } +} + +static void secitemshow(CFTypeRef obj, const char *context) +{ +#ifndef NDEBUG + CFStringRef desc = CFCopyDescription(obj); + if (!desc) return; + + CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(desc), kCFStringEncodingUTF8) + 1; + char* buffer = (char*) malloc(length); + if (buffer) { + Boolean converted = CFStringGetCString(desc, buffer, length, kCFStringEncodingUTF8); + if (converted) { + const char *prefix = (context) ? context : ""; + const char *separator = (context) ? " " : ""; + secitemlog(LOG_NOTICE, "%s%s%s", prefix, separator, buffer); + } + free(buffer); + } + CFRelease(desc); +#endif +} + + +#define CFDataGetBytePtrVoid CFDataGetBytePtr + +#pragma mark SecItem private utility functions + +/******************************************************************************/ + +struct ProtocolAttributeInfo { + const CFTypeRef *protocolValue; + SecProtocolType protocolType; +}; + +static ProtocolAttributeInfo gProtocolTypes[] = { + { &kSecAttrProtocolFTP, kSecProtocolTypeFTP }, + { &kSecAttrProtocolFTPAccount, kSecProtocolTypeFTPAccount }, + { &kSecAttrProtocolHTTP, kSecProtocolTypeHTTP }, + { &kSecAttrProtocolIRC, kSecProtocolTypeIRC }, + { &kSecAttrProtocolNNTP, kSecProtocolTypeNNTP }, + { &kSecAttrProtocolPOP3, kSecProtocolTypePOP3 }, + { &kSecAttrProtocolSMTP, kSecProtocolTypeSMTP }, + { &kSecAttrProtocolSOCKS, kSecProtocolTypeSOCKS }, + { &kSecAttrProtocolIMAP, kSecProtocolTypeIMAP }, + { &kSecAttrProtocolLDAP, kSecProtocolTypeLDAP }, + { &kSecAttrProtocolAppleTalk, kSecProtocolTypeAppleTalk }, + { &kSecAttrProtocolAFP, kSecProtocolTypeAFP }, + { &kSecAttrProtocolTelnet, kSecProtocolTypeTelnet }, + { &kSecAttrProtocolSSH, kSecProtocolTypeSSH }, + { &kSecAttrProtocolFTPS, kSecProtocolTypeFTPS }, + { &kSecAttrProtocolHTTPS, kSecProtocolTypeHTTPS }, + { &kSecAttrProtocolHTTPProxy, kSecProtocolTypeHTTPProxy }, + { &kSecAttrProtocolHTTPSProxy, kSecProtocolTypeHTTPSProxy }, + { &kSecAttrProtocolFTPProxy, kSecProtocolTypeFTPProxy }, + { &kSecAttrProtocolSMB, kSecProtocolTypeSMB }, + { &kSecAttrProtocolRTSP, kSecProtocolTypeRTSP }, + { &kSecAttrProtocolRTSPProxy, kSecProtocolTypeRTSPProxy }, + { &kSecAttrProtocolDAAP, kSecProtocolTypeDAAP }, + { &kSecAttrProtocolEPPC, kSecProtocolTypeEPPC }, + { &kSecAttrProtocolIPP, kSecProtocolTypeIPP }, + { &kSecAttrProtocolNNTPS, kSecProtocolTypeNNTPS }, + { &kSecAttrProtocolLDAPS, kSecProtocolTypeLDAPS }, + { &kSecAttrProtocolTelnetS, kSecProtocolTypeTelnetS }, + { &kSecAttrProtocolIMAPS, kSecProtocolTypeIMAPS }, + { &kSecAttrProtocolIRCS, kSecProtocolTypeIRCS }, + { &kSecAttrProtocolPOP3S, kSecProtocolTypePOP3S } +}; + +static const int kNumberOfProtocolTypes = sizeof(gProtocolTypes) / sizeof(ProtocolAttributeInfo); + +/* + * _SecProtocolTypeForSecAttrProtocol converts a SecAttrProtocol to a SecProtocolType. + */ +static SecProtocolType +_SecProtocolTypeForSecAttrProtocol( + CFTypeRef protocol) +{ + SecProtocolType result = kSecProtocolTypeAny; + + if (protocol != NULL) { + CFIndex count; + for (count=0; count= infoNumItems) + { + // if we got here, we aren't interested in this item. + valuesPtr[i] = NULL; + } + } + + // now we can make the result array + attrList->count = (UInt32)count; + attrList->attr = (SecKeychainAttribute*) malloc(sizeof(SecKeychainAttribute) * count); + + // fill out the array + int resultPointer = 0; + for (i = 0; i < itemsInDictionary; ++i) + { + if (values[i] != NULL) + { + attrList->attr[resultPointer].tag = tags[i]; + + // we have to clone the data pointer. The caller will need to make sure to throw these away + // with _FreeAttrList when it is done... + attrList->attr[resultPointer].data = CloneDataByType(types[i], valuesPtr[i], attrList->attr[resultPointer].length); + resultPointer += 1; + } + } + + return errSecSuccess; +} + + + +static OSStatus +_ConvertOldFormatToNewFormat( + CFAllocatorRef allocator, + const InternalAttributeListInfo* info, + int infoNumItems, + SecKeychainItemRef itemRef, + CFMutableDictionaryRef& dictionaryRef) +{ + SecKeychainAttributeList list; + list.count = infoNumItems; + list.attr = (SecKeychainAttribute*) calloc(infoNumItems, sizeof(SecKeychainAttribute)); + + // fill out the array. We only need to fill in the tags, since calloc zeros what it returns + int i; + for (i = 0; i < infoNumItems; ++i) + { + list.attr[i].tag = info[i].oldItemType; + } + + OSStatus result = SecKeychainItemCopyContent(itemRef, NULL, &list, NULL, NULL); + if (result != errSecSuccess) + { + dictionaryRef = NULL; + free(list.attr); + return result; + } + + // create the dictionary + dictionaryRef = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + // add the pairs + for (i = 0; i < infoNumItems; ++i) + { + if (list.attr[i].data == NULL) + continue; + + switch (info[i].itemRepresentation) + { + case kStringRepresentation: + { + CFStringRef stringRef; + if (info[i].oldItemType == kSecKeyKeyClass) { + // special case: kSecKeyKeyClass is a UInt32 value that maps to a CFStringRef constant + uint32_t keyRecordValue = *((uint32_t*)list.attr[i].data); + bool retainString = true; + switch (keyRecordValue) { + case CSSM_DL_DB_RECORD_PUBLIC_KEY : + stringRef = (CFStringRef) kSecAttrKeyClassPublic; + break; + case CSSM_DL_DB_RECORD_PRIVATE_KEY: + stringRef = (CFStringRef) kSecAttrKeyClassPrivate; + break; + case CSSM_DL_DB_RECORD_SYMMETRIC_KEY: + stringRef = (CFStringRef) kSecAttrKeyClassSymmetric; + break; + default: + stringRef = CFStringCreateWithFormat(allocator, NULL, CFSTR("%d"), keyRecordValue); + break; + } + if (stringRef) { + if (retainString) CFRetain(stringRef); + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), stringRef); + CFRelease(stringRef); + } + } + else if (info[i].oldItemType == kSecKeyKeyType) { + // special case: kSecKeyKeyType is a UInt32 value that maps to a CFStringRef constant + uint32_t keyAlgValue = *((uint32_t*)list.attr[i].data); + bool retainString = true; + switch (keyAlgValue) { + case CSSM_ALGID_RSA : + stringRef = (CFStringRef) kSecAttrKeyTypeRSA; + break; + case CSSM_ALGID_DSA : + stringRef = (CFStringRef) kSecAttrKeyTypeDSA; + break; + case CSSM_ALGID_AES : + stringRef = (CFStringRef) kSecAttrKeyTypeAES; + break; + case CSSM_ALGID_DES : + stringRef = (CFStringRef) kSecAttrKeyTypeDES; + break; + case CSSM_ALGID_3DES : + stringRef = (CFStringRef) kSecAttrKeyType3DES; + break; + case CSSM_ALGID_RC4 : + stringRef = (CFStringRef) kSecAttrKeyTypeRC4; + break; + case CSSM_ALGID_RC2 : + stringRef = (CFStringRef) kSecAttrKeyTypeRC2; + break; + case CSSM_ALGID_CAST : + stringRef = (CFStringRef) kSecAttrKeyTypeCAST; + break; + case CSSM_ALGID_ECDSA : + stringRef = (CFStringRef) kSecAttrKeyTypeEC; + break; + default : + stringRef = CFStringCreateWithFormat(allocator, NULL, CFSTR("%d"), keyAlgValue); + retainString = false; + break; + } + if (stringRef) { + if (retainString) CFRetain(stringRef); + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), stringRef); + CFRelease(stringRef); + } + } + else { + // normal case: attribute contains a string + stringRef = CFStringCreateWithBytes(allocator, (UInt8*)list.attr[i].data, list.attr[i].length, kCFStringEncodingUTF8, FALSE); + if (stringRef == NULL) + stringRef = (CFStringRef) CFRetain(kCFNull); + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), stringRef); + CFRelease(stringRef); + } + } + break; + + case kDataRepresentation: + { + if ((info[i].oldItemType == kSecKeyLabel) && (list.attr[i].length == kUUIDStringLength)) { + // It's possible that there could be a string here because the key label may have a UUID + CFStringRef stringRef = CFStringCreateWithBytes(allocator, (UInt8*)list.attr[i].data, list.attr[i].length, kCFStringEncodingUTF8, FALSE); + if (stringRef == NULL) + stringRef = (CFStringRef) CFRetain(kCFNull); + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), stringRef); + CFRelease(stringRef); + break; + } + CFDataRef dataRef = CFDataCreate(allocator, (UInt8*) list.attr[i].data, list.attr[i].length); + if (dataRef == NULL) + dataRef = (CFDataRef) CFRetain(kCFNull); + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), dataRef); + CFRelease(dataRef); + } + break; + + case kNumberRepresentation: + { + CFNumberRef numberRef = CFNumberCreate(allocator, kCFNumberSInt32Type, list.attr[i].data); + if (numberRef == NULL) + numberRef = (CFNumberRef) CFRetain(kCFNull); + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), numberRef); + CFRelease(numberRef); + } + break; + + case kBooleanRepresentation: + { + uint32_t value = *((uint32_t*)list.attr[i].data); + CFBooleanRef boolRef = (value) ? kCFBooleanTrue : kCFBooleanFalse; + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), boolRef); + } + break; + + case kDateRepresentation: + { + CFDateRef dateRef = NULL; + CSSMDateTimeUtils::CssmDateStringToCFDate((const char *)list.attr[i].data, list.attr[i].length, &dateRef); + if (dateRef == NULL) + dateRef = (CFDateRef) CFRetain(kCFNull); + CFDictionaryAddValue(dictionaryRef, *(info[i].newItemType), dateRef); + CFRelease(dateRef); + } + break; + } + } + + // cleanup + SecKeychainItemFreeContent(&list, NULL); + free(list.attr); + + return result; +} + + + +// +/* + * _CreateAttributesDictionaryFromGenericPasswordItem creates a CFDictionaryRef using the + * attributes of item. + */ +static OSStatus +_CreateAttributesDictionaryFromGenericPasswordItem( + CFAllocatorRef allocator, + SecKeychainItemRef item, + CFDictionaryRef *dictionary) +{ + // do the basic allocations + CFMutableDictionaryRef dict = NULL; + OSStatus result = _ConvertOldFormatToNewFormat(allocator, gGenericPasswordAttributes, kNumberOfGenericPasswordAttributes, item, dict); + if (result == errSecSuccess) // did we complete OK + { + CFDictionaryAddValue(dict, kSecClass, kSecClassGenericPassword); + } + + *dictionary = dict; + + return result; +} + + + +/* + * _CreateAttributesDictionaryFromCertificateItem creates a CFDictionaryRef using the + * attributes of item. + */ +static OSStatus +_CreateAttributesDictionaryFromCertificateItem( + CFAllocatorRef allocator, + SecKeychainItemRef item, + CFDictionaryRef *dictionary) +{ + // do the basic allocations + CFMutableDictionaryRef dict = NULL; + OSStatus result = _ConvertOldFormatToNewFormat(allocator, gCertificateAttributes, kNumberOfCertificateAttributes, item, dict); + if (result == errSecSuccess) // did we complete OK + { + CFDictionaryAddValue(dict, kSecClass, kSecClassCertificate); + } + + *dictionary = dict; + + return errSecSuccess; +} + +/* + * _CreateAttributesDictionaryFromKeyItem creates a CFDictionaryRef using the + * attributes of item. + */ +static OSStatus +_CreateAttributesDictionaryFromKeyItem( + CFAllocatorRef allocator, + SecKeychainItemRef item, + CFDictionaryRef *dictionary) +{ +#if 0 +//%%%FIXME this ought to work, but the call to SecKeychainCopyContent in _ConvertOldFormatToNewFormat fails. +// Need to rewrite _ConvertOldFormatToNewFormat so that it uses SecKeychainAttributeInfoForItemID and +// SecKeychainItemCopyAttributesAndData to get the attributes, rather than SecKeychainCopyContent. + + if (status) { + goto error_exit; // unable to get the attribute info (i.e. database schema) + } + + status = SecKeychainItemCopyAttributesAndData(item, info, &itemClass, &attrList, NULL, NULL); + + // do the basic allocations + CFMutableDictionaryRef dict = NULL; + OSStatus result = _ConvertOldFormatToNewFormat(allocator, gKeyAttributes, kNumberOfKeyAttributes, item, dict); + if (result == errSecSuccess) // did we complete OK + { + CFDictionaryAddValue(dict, kSecClass, kSecClassKey); + } + + *dictionary = dict; + + return errSecSuccess; +#endif + + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + unsigned int ix; + SecItemClass itemClass = 0; + UInt32 itemID; + SecKeychainAttributeList *attrList = NULL; + SecKeychainAttributeInfo *info = NULL; + SecKeychainRef keychain = NULL; + + OSStatus status = SecKeychainItemCopyAttributesAndData(item, NULL, &itemClass, NULL, NULL, NULL); + if (status) { + goto error_exit; // item must have an itemClass + } + + switch (itemClass) + { + case kSecInternetPasswordItemClass: + itemID = CSSM_DL_DB_RECORD_INTERNET_PASSWORD; + break; + case kSecGenericPasswordItemClass: + itemID = CSSM_DL_DB_RECORD_GENERIC_PASSWORD; + break; + case 'ashp': /* kSecAppleSharePasswordItemClass */ + itemID = CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD; + break; + default: + itemID = itemClass; + break; + } + + status = SecKeychainItemCopyKeychain(item, &keychain); + if (status) { + goto error_exit; // item must have a keychain, so we can get the attribute info for it + } + + status = SecKeychainAttributeInfoForItemID(keychain, itemID, &info); + if (status) { + goto error_exit; // unable to get the attribute info (i.e. database schema) + } + + status = SecKeychainItemCopyAttributesAndData(item, info, &itemClass, &attrList, NULL, NULL); + if (status) { + goto error_exit; // unable to get the attribute info (i.e. database schema) + } + + for (ix = 0; ix < info->count; ++ix) + { + SecKeychainAttribute *attribute = &attrList->attr[ix]; + if (!attribute->length && !attribute->data) + continue; + + UInt32 j, count = kNumberOfKeyAttributes; + InternalAttributeListInfo *intInfo = NULL; + for (j=0; jtag[ix]) { + intInfo = &gKeyAttributes[j]; + break; + } + } + if (!intInfo) + continue; + + switch (intInfo->itemRepresentation) + { + case kStringRepresentation: + { + CFStringRef stringRef; + if (intInfo->oldItemType == kSecKeyKeyClass) { + // special case: kSecKeyKeyClass is a UInt32 value that maps to a CFStringRef constant + UInt32 keyRecordValue = *((UInt32*)attribute->data); + bool retainString = true; + switch (keyRecordValue) { + case CSSM_DL_DB_RECORD_PUBLIC_KEY : + stringRef = (CFStringRef) kSecAttrKeyClassPublic; + break; + case CSSM_DL_DB_RECORD_PRIVATE_KEY: + stringRef = (CFStringRef) kSecAttrKeyClassPrivate; + break; + case CSSM_DL_DB_RECORD_SYMMETRIC_KEY: + stringRef = (CFStringRef) kSecAttrKeyClassSymmetric; + break; + default: + stringRef = CFStringCreateWithFormat(allocator, NULL, CFSTR("%u"), (unsigned int)keyRecordValue); + break; + } + if (stringRef) { + if (retainString) CFRetain(stringRef); + CFDictionaryAddValue(dict, *(intInfo->newItemType), stringRef); + CFRelease(stringRef); + } + } + else if (intInfo->oldItemType == kSecKeyKeyType) { + // special case: kSecKeyKeyType is a UInt32 value that maps to a CFStringRef constant + UInt32 keyAlgValue = *((UInt32*)attribute->data); + bool retainString = true; + switch (keyAlgValue) { + case CSSM_ALGID_RSA : + stringRef = (CFStringRef) kSecAttrKeyTypeRSA; + break; + case CSSM_ALGID_DSA : + stringRef = (CFStringRef) kSecAttrKeyTypeDSA; + break; + case CSSM_ALGID_AES : + stringRef = (CFStringRef) kSecAttrKeyTypeAES; + break; + case CSSM_ALGID_DES : + stringRef = (CFStringRef) kSecAttrKeyTypeDES; + break; + case CSSM_ALGID_3DES : + stringRef = (CFStringRef) kSecAttrKeyType3DES; + break; + case CSSM_ALGID_RC4 : + stringRef = (CFStringRef) kSecAttrKeyTypeRC4; + break; + case CSSM_ALGID_RC2 : + stringRef = (CFStringRef) kSecAttrKeyTypeRC2; + break; + case CSSM_ALGID_CAST : + stringRef = (CFStringRef) kSecAttrKeyTypeCAST; + break; + case CSSM_ALGID_ECDSA : + stringRef = (CFStringRef) kSecAttrKeyTypeEC; + break; + default : + stringRef = CFStringCreateWithFormat(allocator, NULL, CFSTR("%u"), (unsigned int)keyAlgValue); + retainString = false; + break; + } + if (stringRef) { + if (retainString) CFRetain(stringRef); + CFDictionaryAddValue(dict, *(intInfo->newItemType), stringRef); + CFRelease(stringRef); + } + } + else { + // normal case: attribute contains a string + stringRef = CFStringCreateWithBytes(allocator, (UInt8*)attribute->data, attribute->length, kCFStringEncodingUTF8, FALSE); + if (stringRef == NULL) + stringRef = (CFStringRef) CFRetain(kCFNull); + CFDictionaryAddValue(dict, *(intInfo->newItemType), stringRef); + CFRelease(stringRef); + } + } + break; + + case kDataRepresentation: + { + if ((intInfo->oldItemType == kSecKeyLabel) && (attribute->length == kUUIDStringLength)) { + // It's possible that there could be a string here because the key label may have a UUID + CFStringRef stringRef = CFStringCreateWithBytes(allocator, (UInt8*)attribute->data, attribute->length, kCFStringEncodingUTF8, FALSE); + if (stringRef == NULL) + stringRef = (CFStringRef) CFRetain(kCFNull); + CFDictionaryAddValue(dict, *(intInfo->newItemType), stringRef); + CFRelease(stringRef); + break; + } + + CFDataRef dataRef = CFDataCreate(allocator, (UInt8*)attribute->data, attribute->length); + if (dataRef == NULL) + dataRef = (CFDataRef) CFRetain(kCFNull); + CFDictionaryAddValue(dict, *(intInfo->newItemType), dataRef); + CFRelease(dataRef); + } + break; + + case kNumberRepresentation: + { + CFNumberRef numberRef = CFNumberCreate(allocator, kCFNumberSInt32Type, attribute->data); + if (numberRef == NULL) + numberRef = (CFNumberRef) CFRetain(kCFNull); + CFDictionaryAddValue(dict, *(intInfo->newItemType), numberRef); + CFRelease(numberRef); + } + break; + + case kBooleanRepresentation: + { + UInt32 value = *((UInt32*)attribute->data); + CFBooleanRef boolRef = (value) ? kCFBooleanTrue : kCFBooleanFalse; + CFDictionaryAddValue(dict, *(intInfo->newItemType), boolRef); + } + break; + + case kDateRepresentation: + { + //%%% FIXME need to convert from a CSSM date string to a CFDateRef here + CFDateRef dateRef = NULL; + if (dateRef == NULL) + dateRef = (CFDateRef) CFRetain(kCFNull); + CFDictionaryAddValue(dict, *(intInfo->newItemType), dateRef); + CFRelease(dateRef); + } + break; + } + } + + CFDictionaryAddValue(dict, kSecClass, kSecClassKey); + +error_exit: + + if (attrList) + SecKeychainItemFreeAttributesAndData(attrList, NULL); + + if (info) + SecKeychainFreeAttributeInfo(info); + + if (keychain) + CFRelease(keychain); + + *dictionary = dict; + + return status; +} + + +/* + * _CreateAttributesDictionaryFromInternetPasswordItem creates a CFDictionaryRef using the + * attributes of item. + */ +static OSStatus +_CreateAttributesDictionaryFromInternetPasswordItem( + CFAllocatorRef allocator, + SecKeychainItemRef item, + CFDictionaryRef *dictionary) +{ + OSStatus status; + SecKeychainAttribute attr[] = { + { kSecServerItemAttr, 0, NULL }, /* [0] server */ + { kSecSecurityDomainItemAttr, 0, NULL }, /* [1] securityDomain */ + { kSecAccountItemAttr, 0, NULL }, /* [2] account */ + { kSecPathItemAttr, 0, NULL }, /* [3] path */ + { kSecPortItemAttr, 0, NULL }, /* [4] port */ + { kSecProtocolItemAttr, 0, NULL }, /* [5] protocol */ + { kSecAuthenticationTypeItemAttr, 0, NULL }, /* [6] authenticationType */ + { kSecCommentItemAttr, 0, NULL }, /* [7] comment */ + { kSecDescriptionItemAttr, 0, NULL }, /* [8] description */ + { kSecLabelItemAttr, 0, NULL }, /* [9] label */ + { kSecCreationDateItemAttr, 0, NULL }, /* [10] creation date */ + { kSecModDateItemAttr, 0, NULL }, /* [11] modification date */ + { kSecCreatorItemAttr, 0, NULL }, /* [12] creator */ + { kSecTypeItemAttr, 0, NULL }, /* [13] type */ + { kSecInvisibleItemAttr, 0, NULL }, /* [14] invisible */ + { kSecNegativeItemAttr, 0, NULL }, /* [15] negative */ + }; + SecKeychainAttributeList attrList = { sizeof(attr) / sizeof(SecKeychainAttribute), attr }; + CFIndex numValues; + CFIndex index; + CFTypeRef keys[(sizeof(attr) / sizeof(SecKeychainAttribute)) + 2]; + CFTypeRef values[(sizeof(attr) / sizeof(SecKeychainAttribute)) + 2]; + + *dictionary = NULL; + + // copy the item's attributes + status = SecKeychainItemCopyContent(item, NULL, &attrList, NULL, NULL); + require_noerr(status, SecKeychainItemCopyContent_failed); + + numValues = 0; + + // add kSecClass + keys[numValues] = kSecClass; + values[numValues] = kSecClassInternetPassword; + ++numValues; + + // add kSecAttrServer + if ( attrList.attr[0].length > 0 ) { + keys[numValues] = kSecAttrServer; + values[numValues] = CFStringCreateWithBytes(allocator, (UInt8 *)attrList.attr[0].data, attrList.attr[0].length, kCFStringEncodingUTF8, FALSE); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrSecurityDomain + if ( attrList.attr[1].length > 0 ) { + keys[numValues] = kSecAttrSecurityDomain; + values[numValues] = CFStringCreateWithBytes(allocator, (UInt8 *)attrList.attr[1].data, attrList.attr[1].length, kCFStringEncodingUTF8, FALSE); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrAccount + if ( attrList.attr[2].length > 0 ) { + keys[numValues] = kSecAttrAccount; + values[numValues] = CFStringCreateWithBytes(allocator, (UInt8 *)attrList.attr[2].data, attrList.attr[2].length, kCFStringEncodingUTF8, FALSE); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrPath + if ( attrList.attr[3].length > 0 ) { + keys[numValues] = kSecAttrPath; + values[numValues] = CFStringCreateWithBytes(allocator, (UInt8 *)attrList.attr[3].data, attrList.attr[3].length, kCFStringEncodingUTF8, FALSE); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrPort + if ( attrList.attr[4].length > 0 ) { + keys[numValues] = kSecAttrPort; + values[numValues] = CFNumberCreate(allocator, kCFNumberSInt32Type, attrList.attr[4].data); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrProtocol + if ( attrList.attr[5].length > 0 ) { + keys[numValues] = kSecAttrProtocol; + values[numValues] = _SecAttrProtocolForSecProtocolType(*(SecProtocolType*)attrList.attr[5].data); + if ( values[numValues] != NULL ) { + CFRetain(values[numValues]); + ++numValues; + } + } + + // add kSecAttrAuthenticationType + if ( attrList.attr[6].length > 0 ) { + keys[numValues] = kSecAttrAuthenticationType; + values[numValues] = _SecAttrAuthenticationTypeForSecAuthenticationType(*(SecProtocolType*)attrList.attr[6].data); + if ( values[numValues] != NULL ) { + CFRetain(values[numValues]); + ++numValues; + } + } + + // add kSecAttrComment + if ( attrList.attr[7].length > 0 ) { + keys[numValues] = kSecAttrComment; + values[numValues] = CFStringCreateWithBytes(allocator, (UInt8 *)attrList.attr[7].data, attrList.attr[7].length, kCFStringEncodingUTF8, FALSE); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrDescription + if ( attrList.attr[8].length > 0 ) { + keys[numValues] = kSecAttrDescription; + values[numValues] = CFStringCreateWithBytes(allocator, (UInt8 *)attrList.attr[8].data, attrList.attr[8].length, kCFStringEncodingUTF8, FALSE); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrLabel + if ( attrList.attr[9].length > 0 ) { + keys[numValues] = kSecAttrLabel; + values[numValues] = CFStringCreateWithBytes(allocator, (UInt8 *)attrList.attr[9].data, attrList.attr[9].length, kCFStringEncodingUTF8, FALSE); + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrCreationDate + if ( attrList.attr[10].length > 0 ) { + CFDateRef creationDate = NULL; + CSSMDateTimeUtils::CssmDateStringToCFDate((const char *)attrList.attr[10].data, attrList.attr[10].length, &creationDate); + keys[numValues] = kSecAttrCreationDate; + values[numValues] = creationDate; + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecAttrModificationDate + if ( attrList.attr[11].length > 0 ) { + CFDateRef modDate = NULL; + CSSMDateTimeUtils::CssmDateStringToCFDate((const char *)attrList.attr[11].data, attrList.attr[11].length, &modDate); + keys[numValues] = kSecAttrModificationDate; + values[numValues] = modDate; + if ( values[numValues] != NULL ) { + ++numValues; + } + } + + // add kSecCreatorItemAttr + if ( attrList.attr[12].length > 0 ) { + CFNumberRef numberRef = CFNumberCreate(allocator, kCFNumberSInt32Type, attrList.attr[12].data); + keys[numValues] = kSecAttrCreator; + values[numValues] = numberRef; + if ( values[numValues] != NULL ) { + CFRetain(values[numValues]); + ++numValues; + } + } + + // add kSecTypeItemAttr + if ( attrList.attr[13].length > 0 ) { + CFNumberRef numberRef = CFNumberCreate(allocator, kCFNumberSInt32Type, attrList.attr[13].data); + keys[numValues] = kSecAttrType; + values[numValues] = numberRef; + if ( values[numValues] != NULL ) { + CFRetain(values[numValues]); + ++numValues; + } + } + + // add kSecInvisibleItemAttr + if ( attrList.attr[14].length > 0 ) { + uint32_t value = *((uint32_t*)attrList.attr[14].data); + CFBooleanRef boolRef = (value) ? kCFBooleanTrue : kCFBooleanFalse; + keys[numValues] = kSecAttrIsInvisible; + values[numValues] = boolRef; + if ( values[numValues] != NULL ) { + CFRetain(values[numValues]); + ++numValues; + } + } + + // add kSecNegativeItemAttr + if ( attrList.attr[15].length > 0 ) { + uint32_t value = *((uint32_t*)attrList.attr[15].data); + CFBooleanRef boolRef = (value) ? kCFBooleanTrue : kCFBooleanFalse; + keys[numValues] = kSecAttrIsNegative; + values[numValues] = boolRef; + if ( values[numValues] != NULL ) { + CFRetain(values[numValues]); + ++numValues; + } + } + + // create the dictionary + *dictionary = CFDictionaryCreate(allocator, keys, values, numValues, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + // release the values added to the dictionary + for ( index = 0; index < numValues; ++index ) + { + CFRelease(values[index]); + } + + // and free the attributes + (void) SecKeychainItemFreeContent(&attrList, NULL); + +SecKeychainItemCopyContent_failed: + + return ( status ); +} + + +/* + * _CreateAttributesDictionaryFromItem creates a CFDictionaryRef using the + * attributes of the specified item class and item. + */ +static OSStatus +_CreateAttributesDictionaryFromItem( + CFAllocatorRef allocator, + SecItemClass itemClass, + SecKeychainItemRef item, + CFDictionaryRef *dictionary) +{ + switch (itemClass) + { + case kSecInternetPasswordItemClass: + return _CreateAttributesDictionaryFromInternetPasswordItem(allocator, item, dictionary); + + case kSecGenericPasswordItemClass: + return _CreateAttributesDictionaryFromGenericPasswordItem(allocator, item, dictionary); + + case kSecCertificateItemClass: + return _CreateAttributesDictionaryFromCertificateItem(allocator, item, dictionary); + + case kSecPublicKeyItemClass: + case kSecPrivateKeyItemClass: + case kSecSymmetricKeyItemClass: + return _CreateAttributesDictionaryFromKeyItem(allocator, item, dictionary); + + default: + *dictionary = NULL; + break; + } + return errSecParam; +} + + +/* + * _FreeAttrList frees the memory allocated for the SecKeychainAttributeList + * by the _CreateSecKeychainAttributeListFromDictionary function. + */ +static void +_FreeAttrList( + SecKeychainAttributeList *attrListPtr) +{ + UInt32 index; + + if ( attrListPtr != NULL ) { + if ( attrListPtr->attr != NULL ) { + // free any attribute data + for ( index = 0; index < attrListPtr->count; ++index ) { + free(attrListPtr->attr[index].data); + } + // free the attribute array + free(attrListPtr->attr); + } + // free the attribute list + free(attrListPtr); + } +} + +/* + * _CFDataCreateAttribute initializes the SecKeychainAttribute pointed to by + * attr using the data and tag parameters. + * + * The memory for the SecKeychainAttribute's data field is allocated with malloc + * and must be released by the caller (this is normally done by calling _FreeAttrList). + */ +static OSStatus +_CFDataCreateAttribute( + CFDataRef data, + SecKeychainAttrType tag, + SecKeychainAttributePtr attr) +{ + OSStatus status = errSecSuccess; + CFRange range; + + // set the attribute tag + attr->tag = tag; + + // determine the attribute length + attr->length = (UInt32) CFDataGetLength(data); + range = CFRangeMake(0, (CFIndex)attr->length); + + // allocate memory for the attribute bytes + attr->data = malloc(attr->length); + require_action(attr->data != NULL, malloc_failed, status = errSecBufferTooSmall); + + // get the attribute bytes + CFDataGetBytes(data, range, (UInt8 *)attr->data); + +malloc_failed: + + return ( status ); +} + +/* + * _CFStringCreateAttribute initializes the SecKeychainAttribute pointed to by + * attr using the string and tag parameters. + * + * The memory for the SecKeychainAttribute's data field is allocated with malloc + * and must be released by the caller (this is normally done by calling _FreeAttrList). + */ +static OSStatus +_CFStringCreateAttribute( + CFStringRef string, + SecKeychainAttrType tag, + SecKeychainAttributePtr attr) +{ + OSStatus status = errSecSuccess; + CFRange range; + + // set the attribute tag + attr->tag = tag; + + // determine the attribute length + range = CFRangeMake(0, CFStringGetLength(string)); + CFStringGetBytes(string, range, kCFStringEncodingUTF8, 0, FALSE, NULL, 0, (CFIndex *)&attr->length); + + // allocate memory for the attribute bytes + attr->data = malloc(attr->length); + require_action(attr->data != NULL, malloc_failed, status = errSecBufferTooSmall); + + // get the attribute bytes + CFStringGetBytes(string, range, kCFStringEncodingUTF8, 0, FALSE, (UInt8 *)attr->data, attr->length, NULL); + +malloc_failed: + + return ( status ); +} + + +/* + * _CreateSecKeychainGenericPasswordAttributeListFromDictionary creates a SecKeychainAttributeList + * from the attribute key/values in attrDictionary. + * + * If this function returns errSecSuccess, the pointer to the SecKeychainAttributeList + * must be freed by the caller with _FreeAttrList() + */ +static OSStatus +_CreateSecKeychainGenericPasswordAttributeListFromDictionary( + CFDictionaryRef attrDictionary, + SecKeychainAttributeList **attrList) +{ + return _ConvertNewFormatToOldFormat(NULL, gGenericPasswordAttributes, kNumberOfGenericPasswordAttributes, attrDictionary, *attrList); +} + + +/* + * _CreateSecKeychainCertificateAttributeListFromDictionary creates a SecKeychainAttributeList + * from the attribute key/values in attrDictionary. + * + * If this function returns errSecSuccess, the pointer to the SecKeychainAttributeList + * must be freed by the caller with _FreeAttrList() + */ +static OSStatus +_CreateSecKeychainCertificateAttributeListFromDictionary( + CFDictionaryRef attrDictionary, + SecKeychainAttributeList **attrList) +{ + return _ConvertNewFormatToOldFormat(NULL, gCertificateAttributes, kNumberOfCertificateAttributes, attrDictionary, *attrList); +} + + +/* + * _CreateSecKeychainKeyAttributeListFromDictionary creates a SecKeychainAttributeList + * from the attribute key/values in attrDictionary. + * + * If this function returns errSecSuccess, the pointer to the SecKeychainAttributeList + * must be freed by the caller with _FreeAttrList() + */ +static OSStatus +_CreateSecKeychainKeyAttributeListFromDictionary( + CFDictionaryRef attrDictionary, + SecKeychainAttributeList **attrList) +{ +#if 0 + //%%%FIXME this function should work for key attributes, but currently doesn't; need to debug + return _ConvertNewFormatToOldFormat(NULL, gKeyAttributes, kNumberOfKeyAttributes, attrDictionary, *attrList); +#else + // explicitly build attribute list for supported key attributes + // NOTE: this code supports only MaxSecKeyAttributes (15) attributes + const int MaxSecKeyAttributes = 15; + + OSStatus status; + CFTypeRef value; + SecKeychainAttributeList *attrListPtr; + + attrListPtr = (SecKeychainAttributeList*)calloc(1, sizeof(SecKeychainAttributeList)); + require_action(attrListPtr != NULL, calloc_attrListPtr_failed, status = errSecBufferTooSmall); + + attrListPtr->attr = (SecKeychainAttribute*)calloc(MaxSecKeyAttributes, sizeof(SecKeychainAttribute)); + require_action(attrListPtr->attr != NULL, malloc_attrPtr_failed, status = errSecBufferTooSmall); + + // [0] get the kSecKeyKeyClass value + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrKeyClass, (const void **)&value) && value) { + UInt32 keyRecordValue = 0; + if (CFEqual(kSecAttrKeyClassPublic, value)) + keyRecordValue = CSSM_DL_DB_RECORD_PUBLIC_KEY; + else if (CFEqual(kSecAttrKeyClassPrivate, value)) + keyRecordValue = CSSM_DL_DB_RECORD_PRIVATE_KEY; + else if (CFEqual(kSecAttrKeyClassSymmetric, value)) + keyRecordValue = CSSM_DL_DB_RECORD_SYMMETRIC_KEY; + + // only use this attribute if we recognize the value! + if (keyRecordValue != 0) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyKeyClass; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = keyRecordValue; + + ++attrListPtr->count; + } + } + + // [1] get the kSecKeyPrintName string + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrLabel, (const void **)&value) && value) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecKeyPrintName, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [2] get the kSecKeyPermanent boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrIsPermanent, (const void **)&value) && value) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyPermanent; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + + // [3] get the kSecKeyLabel string + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrApplicationLabel, (const void **)&value) && value) { + if (CFStringGetTypeID() == CFGetTypeID(value)) + status = _CFStringCreateAttribute((CFStringRef)value, kSecKeyLabel, &attrListPtr->attr[attrListPtr->count]); + else if (CFDataGetTypeID() == CFGetTypeID(value)) + status = _CFDataCreateAttribute((CFDataRef)value, kSecKeyLabel, &attrListPtr->attr[attrListPtr->count]); + else + status = errSecParam; + + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [4] get the kSecKeyApplicationTag data + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrApplicationTag, (const void **)&value) && value) { + if (CFStringGetTypeID() == CFGetTypeID(value)) + status = _CFStringCreateAttribute((CFStringRef)value, kSecKeyApplicationTag, &attrListPtr->attr[attrListPtr->count]); + else if (CFDataGetTypeID() == CFGetTypeID(value)) + status = _CFDataCreateAttribute((CFDataRef)value, kSecKeyApplicationTag, &attrListPtr->attr[attrListPtr->count]); + else + status = errSecParam; + + require_noerr_quiet(status, CFDataCreateAttribute_failed); + ++attrListPtr->count; + } + + // [5] get the kSecKeyKeyType number + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrKeyType, (const void **)&value) && value) { + UInt32 keyAlgValue = _SecAlgorithmTypeFromSecAttrKeyType(kSecAttrKeyType); + if (keyAlgValue != 0) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyKeyType; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = keyAlgValue; + + ++attrListPtr->count; + } + } + + // [6] get the kSecKeyKeySizeInBits number + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrKeySizeInBits, (const void **)&value) && value) { + if (CFNumberGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyKeySizeInBits; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + CFNumberGetValue((CFNumberRef)value, kCFNumberSInt32Type, attrListPtr->attr[attrListPtr->count].data); + + ++attrListPtr->count; + } + } + + // [7] get the kSecKeyEffectiveKeySize number + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrEffectiveKeySize, (const void **)&value) && value) { + if (CFNumberGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyEffectiveKeySize; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + CFNumberGetValue((CFNumberRef)value, kCFNumberSInt32Type, attrListPtr->attr[attrListPtr->count].data); + + ++attrListPtr->count; + } + } + + // [8] get the kSecKeyEncrypt boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCanEncrypt, (const void **)&value) && value) { + if (CFBooleanGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyEncrypt; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + } + + // [9] get the kSecKeyDecrypt boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCanDecrypt, (const void **)&value) && value) { + if (CFBooleanGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyDecrypt; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + } + + // [10] get the kSecKeyDerive boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCanDerive, (const void **)&value) && value) { + if (CFBooleanGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyDerive; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + } + + // [11] get the kSecKeySign boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCanSign, (const void **)&value) && value) { + if (CFBooleanGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeySign; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + } + + // [12] get the kSecKeyVerify boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCanVerify, (const void **)&value) && value) { + if (CFBooleanGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyVerify; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + } + + // [13] get the kSecKeyWrap boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCanWrap, (const void **)&value) && value) { + if (CFBooleanGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyWrap; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + } + + // [14] get the kSecKeyUnwrap boolean + if (CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCanUnwrap, (const void **)&value) && value) { + if (CFBooleanGetTypeID() == CFGetTypeID(value)) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_number_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecKeyUnwrap; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *((UInt32*)attrListPtr->attr[attrListPtr->count].data) = (CFEqual(kCFBooleanTrue, value)) ? 1 : 0; + + ++attrListPtr->count; + } + } + + // return the pointer to the attrList + *attrList = attrListPtr; + + return ( errSecSuccess ); + + /***************/ + +malloc_number_failed: +CFDataCreateAttribute_failed: +CFStringCreateAttribute_failed: +malloc_attrPtr_failed: + + // free any attributes + _FreeAttrList(attrListPtr); + +calloc_attrListPtr_failed: + + return ( errSecBufferTooSmall ); + +#endif +} + +static CFTypeRef copyNumber(CFTypeRef obj) +{ + if (!obj) + return NULL; + + CFTypeID tid = CFGetTypeID(obj); + if (tid == CFNumberGetTypeID()) + { + CFRetain(obj); + return obj; + } + + if (tid == CFBooleanGetTypeID()) + { + SInt32 value = CFBooleanGetValue((CFBooleanRef)obj); + return CFNumberCreate(0, kCFNumberSInt32Type, &value); + } + + if (tid == CFStringGetTypeID()) + { + SInt32 value = CFStringGetIntValue((CFStringRef)obj); + CFStringRef t = CFStringCreateWithFormat(0, 0, CFSTR("%ld"), (long) value); + /* If a string converted to an int isn't equal to the int printed as + a string, return a NULL instead. */ + if (!CFEqual(t, obj)) + { + CFRelease(t); + return NULL; + } + CFRelease(t); + return CFNumberCreate(0, kCFNumberSInt32Type, &value); + } + return NULL; +} + +/* + * _CreateSecKeychainInternetPasswordAttributeListFromDictionary creates a SecKeychainAttributeList + * from the attribute key/values in attrDictionary. + * + * If this function returns errSecSuccess, the pointer to the SecKeychainAttributeList + * must be freed by the caller with _FreeAttrList() + */ +static OSStatus +_CreateSecKeychainInternetPasswordAttributeListFromDictionary( + CFDictionaryRef attrDictionary, + SecKeychainAttributeList **attrList) +{ + // explicitly build attribute list for supported key attributes + // NOTE: this code supports only MaxSecKeychainAttributes (14) attributes + const int MaxSecKeychainAttributes = 14; + + OSStatus status; + CFTypeRef value; + SecKeychainAttributeList *attrListPtr; + + attrListPtr = (SecKeychainAttributeList*)calloc(1, sizeof(SecKeychainAttributeList)); + require_action(attrListPtr != NULL, calloc_attrListPtr_failed, status = errSecBufferTooSmall); + + attrListPtr->attr = (SecKeychainAttribute*)calloc(MaxSecKeychainAttributes, sizeof(SecKeychainAttribute)); + require_action(attrListPtr->attr != NULL, malloc_attrPtr_failed, status = errSecBufferTooSmall); + + + // [0] get the serverName string + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrServer, (const void **)&value) ) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecServerItemAttr, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [1] get the securityDomain string + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrSecurityDomain, (const void **)&value) ) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecSecurityDomainItemAttr, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [2] get the accountName string + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrAccount, (const void **)&value) ) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecAccountItemAttr, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [3] get the path string + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrPath, (const void **)&value) ) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecPathItemAttr, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [4] get the port number + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrPort, (const void **)&value) ) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt16)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_port_failed, status = errSecBufferTooSmall); + + CFTypeRef num = copyNumber(value); + require_action(num != NULL, CFStringCreateAttribute_failed, status = errSecParam); + attrListPtr->attr[attrListPtr->count].tag = kSecPortItemAttr; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt16); + CFNumberGetValue((CFNumberRef)num, kCFNumberSInt16Type, attrListPtr->attr[attrListPtr->count].data); + CFRelease(num); + + ++attrListPtr->count; + } + + // [5] get the protocol + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrProtocol, (const void **)&value) ) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(SecProtocolType)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_protocol_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecProtocolItemAttr; + attrListPtr->attr[attrListPtr->count].length = sizeof(SecProtocolType); + *(SecProtocolType *)(attrListPtr->attr[attrListPtr->count].data) = _SecProtocolTypeForSecAttrProtocol(value); + + ++attrListPtr->count; + } + + // [6] get the authenticationType + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrAuthenticationType, (const void **)&value) ) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(SecAuthenticationType)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_authenticationType_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecAuthenticationTypeItemAttr; + attrListPtr->attr[attrListPtr->count].length = sizeof(SecAuthenticationType); + *(SecAuthenticationType *)(attrListPtr->attr[attrListPtr->count].data) = _SecAuthenticationTypeForSecAttrAuthenticationType(value); + + ++attrListPtr->count; + } + + // [7] get the comment string + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrComment, (const void **)&value) ) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecCommentItemAttr, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [8] get the description string + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrDescription, (const void **)&value) ) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecDescriptionItemAttr, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [9] get the label string + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrLabel, (const void **)&value) ) { + status = _CFStringCreateAttribute((CFStringRef)value, kSecLabelItemAttr, &attrListPtr->attr[attrListPtr->count]); + require_noerr_quiet(status, CFStringCreateAttribute_failed); + + ++attrListPtr->count; + } + + // [10] get the creator code + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrCreator, (const void **)&value) ) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_port_failed, status = errSecBufferTooSmall); + + CFTypeRef num = copyNumber(value); + require_action(num != NULL, CFStringCreateAttribute_failed, status = errSecParam); + attrListPtr->attr[attrListPtr->count].tag = kSecCreatorItemAttr; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + CFNumberGetValue((CFNumberRef)num, kCFNumberSInt32Type, attrListPtr->attr[attrListPtr->count].data); + CFRelease(num); + + ++attrListPtr->count; + } + + // [11] get the type code + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrType, (const void **)&value) ) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_port_failed, status = errSecBufferTooSmall); + + CFTypeRef num = copyNumber(value); + require_action(num != NULL, CFStringCreateAttribute_failed, status = errSecParam); + attrListPtr->attr[attrListPtr->count].tag = kSecTypeItemAttr; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + CFNumberGetValue((CFNumberRef)num, kCFNumberSInt32Type, attrListPtr->attr[attrListPtr->count].data); + CFRelease(num); + + ++attrListPtr->count; + } + + // [12] get the invisible flag + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrIsInvisible, (const void **)&value) ) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_port_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecInvisibleItemAttr; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *(UInt32 *)(attrListPtr->attr[attrListPtr->count].data) = (CFBooleanGetValue((CFBooleanRef)value)) ? 1 : 0; + + ++attrListPtr->count; + } + + // [13] get the negative flag + if ( CFDictionaryGetValueIfPresent(attrDictionary, kSecAttrIsNegative, (const void **)&value) ) { + attrListPtr->attr[attrListPtr->count].data = malloc(sizeof(UInt32)); + require_action(attrListPtr->attr[attrListPtr->count].data != NULL, malloc_port_failed, status = errSecBufferTooSmall); + + attrListPtr->attr[attrListPtr->count].tag = kSecNegativeItemAttr; + attrListPtr->attr[attrListPtr->count].length = sizeof(UInt32); + *(UInt32 *)(attrListPtr->attr[attrListPtr->count].data) = (CFBooleanGetValue((CFBooleanRef)value)) ? 1 : 0; + + ++attrListPtr->count; + } + + // return the pointer to the attrList + *attrList = attrListPtr; + + return ( errSecSuccess ); + + /***************/ + +malloc_authenticationType_failed: +malloc_protocol_failed: +malloc_port_failed: +CFStringCreateAttribute_failed: +malloc_attrPtr_failed: + + // free any attributes + _FreeAttrList(attrListPtr); + +calloc_attrListPtr_failed: + + return ( errSecBufferTooSmall ); +} + + +/* + * _CreateSecKeychainAttributeListFromDictionary creates a SecKeychainAttributeList + * from the attribute key/values in attrDictionary for the specified item class. + * + * If this function returns errSecSuccess, the pointer to the SecKeychainAttributeList + * must be freed by the caller with _FreeAttrList() + */ +static OSStatus +_CreateSecKeychainAttributeListFromDictionary( + CFDictionaryRef attrDictionary, + SecItemClass itemClass, + SecKeychainAttributeList **attrList) +{ + switch (itemClass) + { + case kSecInternetPasswordItemClass: + return _CreateSecKeychainInternetPasswordAttributeListFromDictionary(attrDictionary, attrList); + + case kSecGenericPasswordItemClass: + return _CreateSecKeychainGenericPasswordAttributeListFromDictionary(attrDictionary, attrList); + + case kSecCertificateItemClass: + return _CreateSecKeychainCertificateAttributeListFromDictionary(attrDictionary, attrList); + + case kSecPublicKeyItemClass: + case kSecPrivateKeyItemClass: + case kSecSymmetricKeyItemClass: + return _CreateSecKeychainKeyAttributeListFromDictionary(attrDictionary, attrList); + + default: + break; + } + return errSecParam; +} + + +/* + * _AppNameFromSecTrustedApplication attempts to pull the name of the + * application/tool from the SecTrustedApplicationRef. + */ +static CFStringRef +_AppNameFromSecTrustedApplication( + CFAllocatorRef alloc, + SecTrustedApplicationRef appRef) +{ + CFStringRef result; + OSStatus status; + CFDataRef appDataRef; + + result = NULL; + + // get the data for item's application/tool + status = SecTrustedApplicationCopyData(appRef, &appDataRef); + if ( status == errSecSuccess ) { + CFStringRef path; + + // convert it to a CFString potentially containing the path + path = CFStringCreateWithCString(NULL, (char *)CFDataGetBytePtrVoid(appDataRef), kCFStringEncodingUTF8); + if ( path != NULL ) { + // the path has to start with a "/" and cannot contain "://" + if ( CFStringHasPrefix(path, CFSTR("/")) && (CFStringFind(path, CFSTR("://"), 0).location == kCFNotFound) ) { + CFRange nameRange, compRg; + + nameRange = CFRangeMake(0, CFStringGetLength(path)); + + // remove the trailing slashes (if any) + while ( (nameRange.length > 0) && (CFStringGetCharacterAtIndex(path, nameRange.length - 1) == '/') ) { + nameRange.length --; + } + + if ( nameRange.length > 0 ) { + // find last slash and adjust nameRange to be everything after it + if ( CFStringFindWithOptions(path, CFSTR("/"), nameRange, kCFCompareBackwards, &compRg) ) { + nameRange.length = nameRange.location + nameRange.length - (compRg.location + 1); + nameRange.location = compRg.location + 1; + } + + result = CFStringCreateWithSubstring(alloc, path, nameRange); + } + } + CFRelease(path); + } + CFRelease(appDataRef); + } + + return ( result ); +} + +/* (This function really belongs in SecIdentity.cpp!) + * + * Returns the public key item corresponding to the identity, if it exists in + * the same keychain as the private key. Note that the public key might not + * exist in the same keychain (e.g. if the identity was imported via PKCS12), + * in which case it will not be found. + */ +static OSStatus +_SecIdentityCopyPublicKey( + SecIdentityRef identityRef, + SecKeyRef *publicKeyRef) +{ + OSStatus status; + UInt32 count; + SecKeychainAttribute attr = { kSecKeyLabel, 0, NULL }; + SecKeychainAttributeList attrList = { 1, &attr }; + SecKeychainAttributeList *keyAttrList = NULL; + SecKeychainAttributeInfo *info = NULL; + SecKeychainSearchRef search = NULL; + SecKeychainRef keychain = NULL; + SecKeychainItemRef privateKey = NULL; + SecKeychainItemRef publicKey = NULL; + + status = SecIdentityCopyPrivateKey(identityRef, (SecKeyRef *)&privateKey); + if (status) { + goto error_exit; // identity must have a private key + } + status = SecKeychainItemCopyKeychain(privateKey, &keychain); + if (status) { + goto error_exit; // private key must have a keychain, so we can get the attribute info for it + } + status = SecKeychainAttributeInfoForItemID(keychain, kSecPrivateKeyItemClass, &info); + if (status) { + goto error_exit; // unable to get the attribute info (i.e. database schema) for private keys + } + status = SecKeychainItemCopyAttributesAndData(privateKey, info, NULL, &keyAttrList, NULL, NULL); + if (status) { + goto error_exit; // unable to get the key label attribute for the private key + } + + // use the found kSecKeyLabel attribute from the private key in a separate attribute list for searching + for (count = 0; count < keyAttrList->count; count++) { + if (keyAttrList->attr[count].tag == kSecKeyLabel) { + attr.length = keyAttrList->attr[count].length; + attr.data = keyAttrList->attr[count].data; + break; + } + } + if (!attr.length || !attr.data) { + status = errSecNoSuchAttr; + goto error_exit; // the private key didn't have the hash of the public key in its kSecKeyLabel + } + status = SecKeychainSearchCreateFromAttributes(keychain, kSecPublicKeyItemClass, &attrList, &search); + if (status) { + goto error_exit; // unable to create the search reference + } + status = SecKeychainSearchCopyNext(search, &publicKey); + if (status) { + goto error_exit; // unable to find the public key + } + + if (publicKeyRef) + *publicKeyRef = (SecKeyRef)publicKey; + else + CFRelease(publicKey); + +error_exit: + if (status != errSecSuccess) { + if (publicKeyRef) + *publicKeyRef = NULL; + if (publicKey) + CFRelease(publicKey); + } + if (search) + CFRelease(search); + + if (keyAttrList) + SecKeychainItemFreeAttributesAndData(keyAttrList, NULL); + + if (info) + SecKeychainFreeAttributeInfo(info); + + if (keychain) + CFRelease(keychain); + + if (privateKey) + CFRelease(privateKey); + + return status; +} + + +/* + * Deletes a keychain item if the current application/tool is the only application/tool + * with decrypt access to that keychain item. If more than one application/tool + * has decrypt access to the keychain item, the item is left on the keychain. + * + * TBD: If more than one app/tool has access to the keychain item, we should remove + * the current app/tool's decrypt access. There's no easy way to do that with + * current keychain APIs without bringing up the security UI. + */ +static OSStatus +_SafeSecKeychainItemDelete( + SecKeychainItemRef itemRef) +{ + OSStatus status; + SecAccessRef access = NULL; + CFArrayRef aclList = NULL; + SecACLRef acl = NULL; + CFArrayRef appList = NULL; + CFStringRef description = NULL; + CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR promptSelector; + CFIndex count = 0; + SecTrustedApplicationRef currentAppRef = NULL; + CFStringRef itemAppName = NULL, currentAppName = NULL; + + SecItemClass itemClass = (SecItemClass)0; + status = SecKeychainItemCopyAttributesAndData(itemRef, NULL, &itemClass, NULL, NULL, NULL); + if (!(itemClass == kSecInternetPasswordItemClass || itemClass == kSecGenericPasswordItemClass)) { + // only perform the access control safety check on deletion of password credentials; + // if the item is of some other type, delete it normally. + return SecKeychainItemDelete(itemRef); + } + + // skip access control checking for web form passwords: + // This permits Safari to manage the removal of all web form passwords, + // regardless of whether they are shared by multiple applications. + if (itemClass == kSecInternetPasswordItemClass) { + UInt32 tags[1] = { kSecAuthenticationTypeItemAttr }; + SecKeychainAttributeInfo attrInfo = { 1, tags, NULL }; + SecKeychainAttributeList *attrs = NULL; + status = SecKeychainItemCopyAttributesAndData(itemRef, &attrInfo, NULL, &attrs, NULL, NULL); + if (!status && attrs) { + bool webFormPassword = (attrs->attr[0].length == 4 && (!memcmp(attrs->attr[0].data, "form", 4))); + SecKeychainItemFreeAttributesAndData(attrs, NULL); + if (webFormPassword) { + return SecKeychainItemDelete(itemRef); + } + } + } + + // copy the access of the keychain item + status = SecKeychainItemCopyAccess(itemRef, &access); + require_noerr(status, finish); + require_quiet(access != NULL, finish); + + // copy the decrypt access control lists -- this is what has access to the keychain item + status = SecAccessCopySelectedACLList(access, CSSM_ACL_AUTHORIZATION_DECRYPT, &aclList); + require_noerr(status, finish); + require_quiet(aclList != NULL, finish); + + // get the access control list + acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0); + require_quiet(acl != NULL, finish); + + // copy the application list, description, and CSSM prompt selector for a given access control list entry + status = SecACLCopySimpleContents(acl, &appList, &description, &promptSelector); + require_noerr(status, finish); + require_quiet(appList != NULL, finish); + + // does only a single application/tool have decrypt access to this item? + count = CFArrayGetCount(appList); + if ( count == 1 ) { + // get SecTrustedApplicationRef for item's application/tool + SecTrustedApplicationRef itemAppRef = (SecTrustedApplicationRef)CFArrayGetValueAtIndex(appList, 0); + require_quiet(itemAppRef != NULL, finish); + + // copy the name out + itemAppName = _AppNameFromSecTrustedApplication(CFGetAllocator(itemRef), itemAppRef); + if (itemAppName == NULL) { + /* + * If there is no app name, it's probably because it's not an appname + * in the ACE but an entitlement/info.plist based rule instead; + * just let the caller have it. */ + count--; + goto finish; + } + + // create SecTrustedApplicationRef for current application/tool + status = SecTrustedApplicationCreateFromPath(NULL, ¤tAppRef); + require_noerr(status, finish); + require_quiet(currentAppRef != NULL, finish); + + // copy the name out + currentAppName = _AppNameFromSecTrustedApplication(CFGetAllocator(itemRef), currentAppRef); + require_quiet(currentAppName != NULL, finish); + + // compare the names to see if we own the decrypt access + if ( CFStringCompare(currentAppName, itemAppName, 0) == kCFCompareEqualTo ) { + // decrement the count to zero, which will remove the item below + --count; + } + } + +finish: + + CFReleaseSafe(currentAppName); + CFReleaseSafe(itemAppName); + CFReleaseSafe(currentAppRef); + CFReleaseSafe(description); + CFReleaseSafe(appList); + CFReleaseSafe(aclList); + CFReleaseSafe(access); + + if ((count == 0) || (status == errSecVerifyFailed)) { + // no "owners" remain in the ACL list (or unable to get ACL) + status = SecKeychainItemDelete(itemRef); + } else { + // caller is not the "owner" of the item + status = errSecInvalidOwnerEdit; + } + + return status; +} + +static OSStatus +_UpdateKeychainItem(CFTypeRef item, CFDictionaryRef changedAttributes) +{ + // This function updates a single keychain item, which may be specified as + // a reference, persistent reference or attribute dictionary, with the + // attributes provided. + + OSStatus status = errSecSuccess; + if (!item) { + return errSecParam; + } + + SecItemClass itemClass; + SecAccessRef access = NULL; + SecKeychainAttributeList *changeAttrList = NULL; + SecKeychainItemRef itemToUpdate = NULL; + CFDataRef theData = NULL; + CFTypeID itemType = CFGetTypeID(item); + + // validate input item (must be convertible to a SecKeychainItemRef) + if (SecKeychainItemGetTypeID() == itemType || + SecCertificateGetTypeID() == itemType || + SecKeyGetTypeID() == itemType) { + // item is already a reference, retain it + itemToUpdate = (SecKeychainItemRef) CFRetain(item); + } + else if (CFDataGetTypeID() == itemType) { + // item is a persistent reference, must convert it + status = SecKeychainItemCopyFromPersistentReference((CFDataRef)item, &itemToUpdate); + } + else if (CFDictionaryGetTypeID() == itemType) { + // item is a dictionary + CFTypeRef value = NULL; + if (CFDictionaryGetValueIfPresent((CFDictionaryRef)item, kSecValueRef, &value)) { + // kSecValueRef value is a SecKeychainItemRef, retain it + itemToUpdate = (SecKeychainItemRef) CFRetain(value); + } + else if (CFDictionaryGetValueIfPresent((CFDictionaryRef)item, kSecValuePersistentRef, &value)) { + // kSecValuePersistentRef value is a persistent reference, must convert it + status = SecKeychainItemCopyFromPersistentReference((CFDataRef)value, &itemToUpdate); + } + } + else if (SecIdentityGetTypeID() == itemType) { + // item is a certificate + private key; since we can't really change the + // certificate's attributes, assume we want to update the private key + status = SecIdentityCopyPrivateKey((SecIdentityRef)item, (SecKeyRef*)&itemToUpdate); + } + require_action(itemToUpdate != NULL, update_failed, status = errSecInvalidItemRef); + require_noerr(status, update_failed); + + status = SecKeychainItemCopyContent(itemToUpdate, &itemClass, NULL, NULL, NULL); + require_noerr(status, update_failed); + + // build changeAttrList from changedAttributes dictionary + switch (itemClass) + { + case kSecInternetPasswordItemClass: + { + status = _CreateSecKeychainInternetPasswordAttributeListFromDictionary(changedAttributes, &changeAttrList); + require_noerr(status, update_failed); + } + break; + + case kSecGenericPasswordItemClass: + { + status = _CreateSecKeychainGenericPasswordAttributeListFromDictionary(changedAttributes, &changeAttrList); + require_noerr(status, update_failed); + } + break; + + case kSecCertificateItemClass: + { + status = _CreateSecKeychainCertificateAttributeListFromDictionary(changedAttributes, &changeAttrList); + require_noerr(status, update_failed); + } + break; + + case kSecPublicKeyItemClass: + case kSecPrivateKeyItemClass: + case kSecSymmetricKeyItemClass: + { + status = _CreateSecKeychainKeyAttributeListFromDictionary(changedAttributes, &changeAttrList); + require_noerr(status, update_failed); + } + } + + // get the password + // (if the caller is not updating the password, this value will be NULL) + theData = (CFDataRef)CFDictionaryGetValue(changedAttributes, kSecValueData); + if (theData != NULL) { + require_action(CFDataGetTypeID() == CFGetTypeID(theData), update_failed, status = errSecParam); + } + // update item + status = SecKeychainItemModifyContent(itemToUpdate, + (changeAttrList->count == 0) ? NULL : changeAttrList, + (theData != NULL) ? (UInt32)CFDataGetLength(theData) : 0, + (theData != NULL) ? CFDataGetBytePtrVoid(theData) : NULL); + + // one more thing... update access? + if (CFDictionaryGetValueIfPresent(changedAttributes, kSecAttrAccess, (const void **)&access)) { + status = SecKeychainItemSetAccess(itemToUpdate, access); + } + +update_failed: + if (itemToUpdate) + CFRelease(itemToUpdate); + _FreeAttrList(changeAttrList); + return status; +} + +static OSStatus +_DeleteKeychainItem(CFTypeRef item) +{ + // This function deletes a single keychain item, which may be specified as + // a reference, persistent reference or attribute dictionary. It will not + // delete non-keychain items or aggregate items (such as a SecIdentityRef); + // it is assumed that the caller will pass identity components separately. + + OSStatus status = errSecSuccess; + if (!item) { + return errSecParam; + } + + SecKeychainItemRef itemToDelete = NULL; + CFTypeID itemType = CFGetTypeID(item); + if (SecKeychainItemGetTypeID() == itemType || + SecCertificateGetTypeID() == itemType || + SecKeyGetTypeID() == itemType) { + // item is already a reference, retain it + itemToDelete = (SecKeychainItemRef) CFRetain(item); + } + else if (CFDataGetTypeID() == itemType) { + // item is a persistent reference, must convert it + status = SecKeychainItemCopyFromPersistentReference((CFDataRef)item, &itemToDelete); + } + else if (CFDictionaryGetTypeID() == itemType) { + // item is a dictionary + CFTypeRef value = NULL; + if (CFDictionaryGetValueIfPresent((CFDictionaryRef)item, kSecValueRef, &value)) { + // kSecValueRef value is a SecKeychainItemRef, retain it + itemToDelete = (SecKeychainItemRef) CFRetain(value); + } + else if (CFDictionaryGetValueIfPresent((CFDictionaryRef)item, kSecValuePersistentRef, &value)) { + // kSecValuePersistentRef value is a persistent reference, must convert it + status = SecKeychainItemCopyFromPersistentReference((CFDataRef)value, &itemToDelete); + } + } + + if (itemToDelete) { + if (!status) { + status = _SafeSecKeychainItemDelete(itemToDelete); + } + CFRelease(itemToDelete); + } + + return status; +} + +static OSStatus +_DeleteIdentity(SecIdentityRef identity) +{ + OSStatus status, result = errSecSuccess; + SecKeyRef privateKey = NULL; + SecCertificateRef certificate = NULL; + + status = SecIdentityCopyPrivateKey(identity, &privateKey); + if (!status) { + SecKeyRef publicKey = NULL; + status = _SecIdentityCopyPublicKey(identity, &publicKey); + if (!status) { + status = _DeleteKeychainItem(publicKey); + CFRelease(publicKey); + } + status = _DeleteKeychainItem(privateKey); + } + + if (privateKey) CFRelease(privateKey); + if (status) result = status; + + status = SecIdentityCopyCertificate(identity, &certificate); + if (!status) { + status = _DeleteKeychainItem(certificate); + } + + if (certificate) CFRelease(certificate); + if (status) result = status; + + return result; +} + +static OSStatus +_UpdateAggregateStatus(OSStatus newStatus, OSStatus curStatus, OSStatus baseStatus) +{ + // This function is used when atomically processing multiple items, + // where an overall error result must be returned for the entire operation. + // When newStatus is something other than errSecSuccess, we want to keep the "most + // interesting" status (which usually will be newStatus, unless curStatus is + // already set; in that case, newStatus can trump curStatus only by being + // something different than baseStatus.) + + OSStatus result = curStatus; + + if (newStatus != errSecSuccess) { + result = newStatus; + if (curStatus != errSecSuccess) { + result = (newStatus != baseStatus) ? newStatus : curStatus; + } + } + return result; +} + +static void +_AddDictValueToOtherDict(const void *key, const void *value, void *context) +{ + // CFDictionaryApplierFunction + // This function just takes the given key/value pair, + // and adds it to another dictionary supplied in the context argument. + + CFMutableDictionaryRef dict = *((CFMutableDictionaryRef*) context); + if (key && value) { + CFDictionaryAddValue(dict, key, value); + } +} + +static CFStringCompareFlags +_StringCompareFlagsFromQuery(CFDictionaryRef query) +{ + CFTypeRef value; + CFStringCompareFlags flags = 0; + if (!query) return flags; + + if (CFDictionaryGetValueIfPresent(query, kSecMatchSubjectStartsWith, (const void **)&value) || + CFDictionaryGetValueIfPresent(query, kSecMatchSubjectEndsWith, (const void **)&value)) + flags |= kCFCompareAnchored; + + if (CFDictionaryGetValueIfPresent(query, kSecMatchSubjectEndsWith, (const void **)&value)) + flags |= kCFCompareBackwards; + + if (CFDictionaryGetValueIfPresent(query, kSecMatchCaseInsensitive, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + flags |= kCFCompareCaseInsensitive; + + if (CFDictionaryGetValueIfPresent(query, kSecMatchDiacriticInsensitive, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + flags |= kCFCompareDiacriticInsensitive; + + if (CFDictionaryGetValueIfPresent(query, kSecMatchWidthInsensitive, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + flags |= kCFCompareWidthInsensitive; + + return flags; +} + +static uint32 +_CssmKeyUsageFromQuery(CFDictionaryRef query) +{ + CFTypeRef value; + uint32 keyUsage = 0; + if (!query) return keyUsage; + + if (CFDictionaryGetValueIfPresent(query, kSecAttrCanEncrypt, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + keyUsage |= CSSM_KEYUSE_ENCRYPT; + + if (CFDictionaryGetValueIfPresent(query, kSecAttrCanDecrypt, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + keyUsage |= CSSM_KEYUSE_DECRYPT; + + if (CFDictionaryGetValueIfPresent(query, kSecAttrCanSign, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + keyUsage |= CSSM_KEYUSE_SIGN; + + if (CFDictionaryGetValueIfPresent(query, kSecAttrCanVerify, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + keyUsage |= CSSM_KEYUSE_VERIFY; + + if (CFDictionaryGetValueIfPresent(query, kSecAttrCanWrap, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + keyUsage |= CSSM_KEYUSE_WRAP; + + if (CFDictionaryGetValueIfPresent(query, kSecAttrCanUnwrap, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + keyUsage |= CSSM_KEYUSE_UNWRAP; + + if (CFDictionaryGetValueIfPresent(query, kSecAttrCanDerive, (const void **)&value) && CFEqual(kCFBooleanTrue, value)) + keyUsage |= CSSM_KEYUSE_DERIVE; + + return keyUsage; +} + +static SecItemClass +_ConvertItemClass(const void* item, const void* keyClass, Boolean *isIdentity) +{ + SecItemClass itemClass = (SecItemClass) 0; + if (isIdentity) *isIdentity = false; + + if (CFEqual(item, kSecClassGenericPassword)) { + itemClass = kSecGenericPasswordItemClass; + } + else if (CFEqual(item, kSecClassInternetPassword)) { + itemClass = kSecInternetPasswordItemClass; + } + else if (CFEqual(item, kSecClassCertificate)) { + itemClass = kSecCertificateItemClass; + } + else if (CFEqual(item, kSecClassIdentity)) { + // will perform a certificate lookup + itemClass = kSecCertificateItemClass; + if (isIdentity) *isIdentity = true; + } + else if (CFEqual(item, kSecClassKey)) { + // examine second parameter to determine type of key + if (!keyClass || CFEqual(keyClass, kSecAttrKeyClassSymmetric)) { + itemClass = kSecSymmetricKeyItemClass; + } + else if (keyClass && CFEqual(keyClass, kSecAttrKeyClassPublic)) { + itemClass = kSecPublicKeyItemClass; + } + else if (keyClass && CFEqual(keyClass, kSecAttrKeyClassPrivate)) { + itemClass = kSecPrivateKeyItemClass; + } + } + + return itemClass; +} + +static SecItemClass +_ItemClassFromItemList(CFArrayRef itemList) +{ + // Given a list of items (standard or persistent references), + // determine whether they all have the same item class. Returns + // the item class, or 0 if multiple classes in list. + SecItemClass result = 0; + CFIndex index, count = (itemList) ? CFArrayGetCount(itemList) : 0; + for (index=0; index < count; index++) { + CFTypeRef item = (CFTypeRef) CFArrayGetValueAtIndex(itemList, index); + if (item) { + SecKeychainItemRef itemRef = NULL; + OSStatus status; + if (CFGetTypeID(item) == CFDataGetTypeID()) { + // persistent reference, resolve first + status = SecKeychainItemCopyFromPersistentReference((CFDataRef)item, &itemRef); + } + else { + itemRef = (SecKeychainItemRef) CFRetain(item); + } + if (itemRef) { + SecItemClass itemClass = 0; + CFTypeID itemTypeID = CFGetTypeID(itemRef); + if (itemTypeID == SecIdentityGetTypeID() || itemTypeID == SecCertificateGetTypeID()) { + // Identities and certificates have the same underlying item class + itemClass = kSecCertificateItemClass; + } + else if (itemTypeID == SecKeychainItemGetTypeID()) { + // Reference to item in a keychain + status = SecKeychainItemCopyAttributesAndData(itemRef, NULL, &itemClass, NULL, NULL, NULL); + } + else if (itemTypeID == SecKeyGetTypeID()) { + // SecKey that isn't stored in a keychain + // %%% will need to change this code when SecKey is no longer CSSM-based %%% + const CSSM_KEY *cssmKey; + status = SecKeyGetCSSMKey((SecKeyRef)itemRef, &cssmKey); + if (status == errSecSuccess) { + if (cssmKey->KeyHeader.KeyClass == CSSM_KEYCLASS_PUBLIC_KEY) + itemClass = kSecPublicKeyItemClass; + else if (cssmKey->KeyHeader.KeyClass == CSSM_KEYCLASS_PRIVATE_KEY) + itemClass = kSecPrivateKeyItemClass; + else + itemClass = kSecSymmetricKeyItemClass; + } + } + CFRelease(itemRef); + if (itemClass != 0) { + if (result != 0 && result != itemClass) { + return 0; // different item classes in list; bail out + } + result = itemClass; + } + } + } + } + return result; +} + +// SecItemParams contains a validated set of input parameters, as well as a +// search reference and attribute list built from those parameters. It is +// designed to be allocated with _CreateSecItemParamsFromDictionary, and +// freed with _FreeSecItemParams. + +struct SecItemParams { + CFDictionaryRef query; // caller-supplied query + int numResultTypes; // number of result types requested + int maxMatches; // max number of matches to return + uint32 keyUsage; // key usage(s) requested + Boolean returningAttributes; // true if returning attributes dictionary + Boolean returningData; // true if returning item's data + Boolean returningRef; // true if returning item reference + Boolean returningPersistentRef; // true if returing a persistent reference + Boolean returnAllMatches; // true if we should return all matches + Boolean returnIdentity; // true if we are returning a SecIdentityRef + Boolean trustedOnly; // true if we only return trusted certs + Boolean issuerAndSNToMatch; // true if both issuer and SN were provided + SecItemClass itemClass; // item class for this query + SecPolicyRef policy; // value for kSecMatchPolicy (may be NULL) + SecKeychainRef keychain; // value for kSecUseKeychain (may be NULL) + CFArrayRef useItems; // value for kSecUseItemList (may be NULL) + CFArrayRef itemList; // value for kSecMatchItemList (may be NULL) + CFTypeRef searchList; // value for kSecMatchSearchList (may be NULL) + CFTypeRef matchLimit; // value for kSecMatchLimit (may be NULL) + CFTypeRef emailAddrToMatch; // value for kSecMatchEmailAddressIfPresent (may be NULL) + CFTypeRef validOnDate; // value for kSecMatchValidOnDate (may be NULL) + CFTypeRef keyClass; // value for kSecAttrKeyClass (may be NULL) + CFTypeRef service; // value for kSecAttrService (may be NULL) + CFTypeRef issuer; // value for kSecAttrIssuer (may be NULL) + CFTypeRef serialNumber; // value for kSecAttrSerialNumber (may be NULL) + CFTypeRef search; // search reference for this query (SecKeychainSearchRef or SecIdentitySearchRef) + CFTypeRef assumedKeyClass; // if no kSecAttrKeyClass provided, holds the current class we're searching for + CFIndex itemListIndex; // if no search reference but we have itemList, holds index of next item to return + SecKeychainAttributeList *attrList; // attribute list for this query + SecAccessRef access; // access reference (for SecItemAdd only, not used to find items) + CFDataRef itemData; // item data (for SecItemAdd only, not used to find items) + CFTypeRef itemRef; // item reference (to find, add, update or delete, depending on context) + SecIdentityRef identityRef; // identity reference (input as kSecValueRef) + CFDataRef itemPersistentRef; // item persistent reference (to find, add, update or delete, depending on context) + Boolean isPCSItem; // true if this query is for a Protected Cloud Storage item +}; + +static OSStatus +_ValidateDictionaryEntry(CFDictionaryRef dict, CFTypeRef key, const void **value, CFTypeID expectedTypeID, CFTypeID altTypeID) +{ + if (!dict || !key || !value || !expectedTypeID) + return errSecParam; + + if (!CFDictionaryGetValueIfPresent(dict, key, value)) { + // value was not provided for this key (not an error!) + *value = NULL; + } + else if (!(*value)) { + // provided value is NULL (also not an error!) + return errSecSuccess; + } + else { + CFTypeID actualTypeID = CFGetTypeID(*value); + if (!((expectedTypeID == actualTypeID) || (altTypeID && altTypeID == actualTypeID))) { + // provided value does not have the expected (or alternate) CF type ID + if ((expectedTypeID == SecKeychainItemGetTypeID()) && + (actualTypeID == SecKeyGetTypeID() || actualTypeID == SecCertificateGetTypeID())) { + // provided value is a "floating" reference which is not yet in a keychain + CFRetain(*value); + return errSecSuccess; + } + return errSecItemInvalidValue; + } + else { + // provided value is OK; retain it + CFRetain(*value); + } + } + return errSecSuccess; +} + +static void +_EnsureUserDefaultKeychainIsSearched(SecItemParams *itemParams) +{ + OSStatus status; + CFArrayRef tmpList = (CFArrayRef) itemParams->searchList; + if (tmpList) { + // search list exists; make it mutable + itemParams->searchList = (CFArrayRef) CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, tmpList); + CFRelease(tmpList); + } else { + // no search list; start with default list + status = SecKeychainCopySearchList(&tmpList); + if (!status && tmpList) { + itemParams->searchList = (CFArrayRef) CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, tmpList); + CFRelease(tmpList); + } + else { + itemParams->searchList = (CFArrayRef) CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + } + } + + SecKeychainRef userKeychain = NULL; + status = SecKeychainCopyDomainDefault(kSecPreferencesDomainUser, &userKeychain); + if (!status && userKeychain) { + if (!CFArrayContainsValue((CFArrayRef)itemParams->searchList, + CFRangeMake(0, CFArrayGetCount((CFArrayRef)itemParams->searchList)), userKeychain)) { + // user's default keychain isn't currently in the search list, so append it + CFArrayAppendValue((CFMutableArrayRef)itemParams->searchList, userKeychain); + } + CFRelease(userKeychain); + } +} + +static void +_EnsureUserDefaultKeychainIsTargeted(SecItemParams *itemParams) +{ + if (itemParams->keychain) { + return; // keychain is already explicitly specified, assume it's correct + } + SecKeychainRef userKeychain = NULL; + OSStatus status = SecKeychainCopyDomainDefault(kSecPreferencesDomainUser, &userKeychain); + if (!status && userKeychain) { + itemParams->keychain = userKeychain; + } +} + +static void +_FreeSecItemParams(SecItemParams *itemParams) +{ + if (!itemParams) + return; + + if (itemParams->query) CFRelease(itemParams->query); + if (itemParams->policy) CFRelease(itemParams->policy); + if (itemParams->keychain) CFRelease(itemParams->keychain); + if (itemParams->useItems) CFRelease(itemParams->useItems); + if (itemParams->itemList) CFRelease(itemParams->itemList); + if (itemParams->searchList) CFRelease(itemParams->searchList); + if (itemParams->matchLimit) CFRelease(itemParams->matchLimit); + if (itemParams->emailAddrToMatch) CFRelease(itemParams->emailAddrToMatch); + if (itemParams->validOnDate) CFRelease(itemParams->validOnDate); + if (itemParams->keyClass) CFRelease(itemParams->keyClass); + if (itemParams->service) CFRelease(itemParams->service); + if (itemParams->issuer) CFRelease(itemParams->issuer); + if (itemParams->serialNumber) CFRelease(itemParams->serialNumber); + if (itemParams->search) CFRelease(itemParams->search); + if (itemParams->access) CFRelease(itemParams->access); + if (itemParams->itemData) CFRelease(itemParams->itemData); + if (itemParams->itemRef) CFRelease(itemParams->itemRef); + if (itemParams->identityRef) CFRelease(itemParams->identityRef); + if (itemParams->itemPersistentRef) CFRelease(itemParams->itemPersistentRef); + + _FreeAttrList(itemParams->attrList); + + free(itemParams); +} + +static SecItemParams* +_CreateSecItemParamsFromDictionary(CFDictionaryRef dict, OSStatus *error) +{ + OSStatus status; + CFTypeRef value = NULL; + SecItemParams *itemParams = (SecItemParams *) malloc(sizeof(SecItemParams)); + + require_action(itemParams != NULL, error_exit, status = errSecAllocate); + require_action(dict && (CFDictionaryGetTypeID() == CFGetTypeID(dict)), error_exit, status = errSecParam); + + memset(itemParams, 0, sizeof(SecItemParams)); + itemParams->query = (CFDictionaryRef) CFRetain(dict); + + // validate input search parameters + require_noerr(status = _ValidateDictionaryEntry(dict, kSecMatchPolicy, (const void **)&itemParams->policy, SecPolicyGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecMatchSearchList, (const void **)&itemParams->searchList, CFArrayGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecMatchItemList, (const void **)&itemParams->itemList, CFArrayGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecMatchEmailAddressIfPresent, (const void **)&itemParams->emailAddrToMatch, CFStringGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecMatchValidOnDate, (const void **)&itemParams->validOnDate, CFDateGetTypeID(), CFNullGetTypeID()), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecMatchLimit, (const void **)&itemParams->matchLimit, CFStringGetTypeID(), CFNumberGetTypeID()), error_exit); + + require_noerr(status = _ValidateDictionaryEntry(dict, kSecUseItemList, (const void **)&itemParams->useItems, CFArrayGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecUseKeychain, (const void **)&itemParams->keychain, SecKeychainGetTypeID(), NULL), error_exit); + + // validate a subset of input attributes (used to create an appropriate search reference) + require_noerr(status = _ValidateDictionaryEntry(dict, kSecAttrIssuer, (const void **)&itemParams->issuer, CFDataGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecAttrSerialNumber, (const void **)&itemParams->serialNumber, CFDataGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecAttrService, (const void **)&itemParams->service, CFStringGetTypeID(), NULL), error_exit); + require_noerr(status = _ValidateDictionaryEntry(dict, kSecAttrKeyClass, (const void **)&itemParams->keyClass, CFStringGetTypeID(), NULL), error_exit); + + if (itemParams->service && CFStringHasPrefix((CFStringRef)itemParams->service, CFSTR("ProtectedCloudStorage"))) { + itemParams->isPCSItem = true; + if (!SecItemSynchronizable(dict)) { + _EnsureUserDefaultKeychainIsSearched(itemParams); // for SecItemCopyMatching, SecItemUpdate, SecItemDelete + _EnsureUserDefaultKeychainIsTargeted(itemParams); // for SecItemAdd + } + } + + // validate the payload (password, key or certificate data), used for SecItemAdd but not for finding items + require_noerr(status = _ValidateDictionaryEntry(dict, kSecValueData, (const void **)&itemParams->itemData, CFDataGetTypeID(), CFStringGetTypeID()), error_exit); + + // validate item references + require_noerr(status = _ValidateDictionaryEntry(dict, kSecValueRef, (const void **)&itemParams->itemRef, SecKeychainItemGetTypeID(), SecIdentityGetTypeID()), error_exit); + if (itemParams->itemRef && (CFGetTypeID(itemParams->itemRef) == SecIdentityGetTypeID())) { + itemParams->identityRef = (SecIdentityRef)itemParams->itemRef; + itemParams->itemRef = NULL; + SecIdentityCopyCertificate(itemParams->identityRef, (SecCertificateRef *)&itemParams->itemRef); + } + require_noerr(status = _ValidateDictionaryEntry(dict, kSecValuePersistentRef, (const void **)&itemParams->itemPersistentRef, CFDataGetTypeID(), NULL), error_exit); + if (itemParams->itemRef || itemParams->itemPersistentRef) { + // Caller is trying to add or find an item by reference. + // The supported method for doing that is to provide a kSecUseItemList array + // for SecItemAdd, or a kSecMatchItemList array for SecItemCopyMatching et al, + // so add the item reference to those arrays here. + if (itemParams->useItems) { + CFArrayRef tmpItems = itemParams->useItems; + itemParams->useItems = (CFArrayRef) CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, tmpItems); + CFRelease(tmpItems); + } else { + itemParams->useItems = (CFArrayRef) CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + } + if (itemParams->itemRef) CFArrayAppendValue((CFMutableArrayRef)itemParams->useItems, itemParams->itemRef); + if (itemParams->itemPersistentRef) CFArrayAppendValue((CFMutableArrayRef)itemParams->useItems, itemParams->itemPersistentRef); + + if (itemParams->itemList) { + CFArrayRef tmpItems = itemParams->itemList; + itemParams->itemList = (CFArrayRef) CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, tmpItems); + CFRelease(tmpItems); + } else { + itemParams->itemList = (CFArrayRef) CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + } + if (itemParams->itemRef) CFArrayAppendValue((CFMutableArrayRef)itemParams->itemList, itemParams->itemRef); + if (itemParams->itemPersistentRef) CFArrayAppendValue((CFMutableArrayRef)itemParams->itemList, itemParams->itemPersistentRef); + } + + // must have an explicit item class, unless one of the following is true: + // - we have an item list to add or search (kSecUseItemList) + // - we have an item reference or persistent reference for the thing we want to look up + // Note that both of these cases will set itemParams->useItems. + // If we have an item list to match (kSecMatchItemList), that still requires an item class, + // so we can perform a search and see if the results match items in the list. + // + if (!CFDictionaryGetValueIfPresent(dict, kSecClass, (const void**) &value) && !itemParams->useItems) { + require_action(false, error_exit, status = errSecItemClassMissing); + } + else if (value) { + itemParams->itemClass = _ConvertItemClass(value, itemParams->keyClass, &itemParams->returnIdentity); + if (itemParams->itemClass == kSecSymmetricKeyItemClass && !itemParams->keyClass) { + itemParams->assumedKeyClass = kSecAttrKeyClassSymmetric; // no key class specified, so start with symmetric key class; will search the others later + } + require_action(!(itemParams->itemClass == 0 && !itemParams->useItems), error_exit, status = errSecItemClassMissing); + } + + itemParams->keyUsage = _CssmKeyUsageFromQuery(dict); + itemParams->trustedOnly = CFDictionaryGetValueIfPresent(dict, kSecMatchTrustedOnly, (const void **)&value) && value && CFEqual(kCFBooleanTrue, value); + itemParams->issuerAndSNToMatch = (itemParams->issuer != NULL && itemParams->serialNumber != NULL); + + // other input attributes, used for SecItemAdd but not for finding items + require_noerr(status = _ValidateDictionaryEntry(dict, kSecAttrAccess, (const void **)&itemParams->access, SecAccessGetTypeID(), NULL), error_exit); + if (itemParams->access == NULL) { + // check for the old definition of kSecAttrAccess from SecItem-shim (see ) + require_noerr(status = _ValidateDictionaryEntry(dict, CFSTR("kSecAttrAccess"), (const void **)&itemParams->access, SecAccessGetTypeID(), NULL), error_exit); + } + + // determine how to return the result + itemParams->numResultTypes = 0; + itemParams->returningRef = CFDictionaryGetValueIfPresent(dict, kSecReturnRef, (const void **)&value) && value && CFEqual(kCFBooleanTrue, value); + if (itemParams->returningRef) ++itemParams->numResultTypes; + itemParams->returningPersistentRef = CFDictionaryGetValueIfPresent(dict, kSecReturnPersistentRef, (const void **)&value) && value && CFEqual(kCFBooleanTrue, value); + if (itemParams->returningPersistentRef) ++itemParams->numResultTypes; + itemParams->returningAttributes = CFDictionaryGetValueIfPresent(dict, kSecReturnAttributes, (const void **)&value) && value && CFEqual(kCFBooleanTrue, value); + if (itemParams->returningAttributes) ++itemParams->numResultTypes; + itemParams->returningData = CFDictionaryGetValueIfPresent(dict, kSecReturnData, (const void **)&value) && value && CFEqual(kCFBooleanTrue, value); + if (itemParams->returningData) ++itemParams->numResultTypes; + + // default is kSecReturnRef if no result types were specified + if (!itemParams->numResultTypes) { + itemParams->returningRef = TRUE; + itemParams->numResultTypes = 1; + } + + // determine if one, some or all matches should be returned (default is kSecMatchLimitOne) + itemParams->maxMatches = 1; + itemParams->returnAllMatches = FALSE; + if (itemParams->matchLimit) { + if (CFStringGetTypeID() == CFGetTypeID(itemParams->matchLimit)) { + itemParams->returnAllMatches = CFEqual(kSecMatchLimitAll, itemParams->matchLimit); + } + else if (CFNumberGetTypeID() == CFGetTypeID(itemParams->matchLimit)) { + CFNumberGetValue((CFNumberRef)itemParams->matchLimit, kCFNumberIntType, &itemParams->maxMatches); + require_action(!(itemParams->maxMatches < 0), error_exit, status = errSecMatchLimitUnsupported); + } + } + if (itemParams->returnAllMatches) { + itemParams->maxMatches = INT32_MAX; + // if we're returning all matches, then we don't support getting passwords as data (which could require authentication for each) + if ((itemParams->itemClass==kSecInternetPasswordItemClass || itemParams->itemClass==kSecGenericPasswordItemClass) && itemParams->returningData) + status = errSecReturnDataUnsupported; + require_noerr(status, error_exit); + } + + // if we already have an item list (to add or find items in), we don't need an item class, attribute list or a search reference + if (itemParams->useItems) { + if (itemParams->itemClass == 0) { + itemParams->itemClass = _ItemClassFromItemList(itemParams->useItems); + } + status = errSecSuccess; + goto error_exit; // all done here + } + + // build a SecKeychainAttributeList from the query dictionary for the specified item class + require_noerr(status = _CreateSecKeychainAttributeListFromDictionary(dict, itemParams->itemClass, &itemParams->attrList), error_exit); + + // create a search reference (either a SecKeychainSearchRef or a SecIdentitySearchRef) + if ((itemParams->itemClass == kSecCertificateItemClass) && itemParams->emailAddrToMatch) { + // searching for certificates by email address + char *nameBuf = (char*)malloc(MAXPATHLEN); + if (!nameBuf) { + status = errSecAllocate; + } + else if (CFStringGetCString((CFStringRef)itemParams->emailAddrToMatch, nameBuf, (CFIndex)MAXPATHLEN-1, kCFStringEncodingUTF8)) { + status = SecKeychainSearchCreateForCertificateByEmail(itemParams->searchList, (const char *)nameBuf, (SecKeychainSearchRef*)&itemParams->search); + } + else { + status = errSecItemInvalidValue; + } + if (nameBuf) free(nameBuf); + } + else if ((itemParams->itemClass == kSecCertificateItemClass) && itemParams->issuerAndSNToMatch) { + // searching for certificates by issuer and serial number + status = SecKeychainSearchCreateForCertificateByIssuerAndSN_CF(itemParams->searchList, + (CFDataRef)itemParams->issuer, + (CFDataRef)itemParams->serialNumber, + (SecKeychainSearchRef*)&itemParams->search); + } + else if (itemParams->returnIdentity && itemParams->policy) { + // searching for identities by policy + status = SecIdentitySearchCreateWithPolicy(itemParams->policy, + (CFStringRef)itemParams->service, + itemParams->keyUsage, + itemParams->searchList, + itemParams->trustedOnly, + (SecIdentitySearchRef*)&itemParams->search); + } + else if (itemParams->returnIdentity) { + // searching for identities + status = SecIdentitySearchCreate(itemParams->searchList, + itemParams->keyUsage, + (SecIdentitySearchRef*)&itemParams->search); + } + else { + // normal keychain item search + status = SecKeychainSearchCreateFromAttributes(itemParams->searchList, + itemParams->itemClass, + (itemParams->attrList->count == 0) ? NULL : itemParams->attrList, + (SecKeychainSearchRef*)&itemParams->search); + } + +error_exit: + if (status) { + _FreeSecItemParams(itemParams); + itemParams = NULL; + } + if (error) { + *error = status; + } + return itemParams; +} + + +static OSStatus +_ImportKey( + SecKeyRef keyRef, + SecKeychainRef keychainRef, + SecAccessRef accessRef, + SecKeychainAttributeList *attrList, + SecKeychainItemRef *outItemRef) +{ + BEGIN_SECAPI + + // We must specify the access, since a free-floating key won't have one yet by default + SecPointer access; + if (accessRef) { + access = Access::required(accessRef); + } + else { + CFStringRef descriptor = NULL; + if (attrList) { + for (UInt32 index=0; index < attrList->count; index++) { + SecKeychainAttribute attr = attrList->attr[index]; + if (attr.tag == kSecKeyPrintName) { + descriptor = CFStringCreateWithBytes(NULL, (const UInt8 *)attr.data, attr.length, kCFStringEncodingUTF8, FALSE); + break; + } + } + } + if (descriptor == NULL) { + descriptor = (CFStringRef) CFRetain(CFSTR("")); + } + access = new Access(cfString(descriptor)); + CFRelease(descriptor); + } + + KeyItem *key = KeyItem::required(keyRef); + Item item = key->importTo(Keychain::optional(keychainRef), access, attrList); + if (outItemRef) + *outItemRef = item->handle(); + + END_SECAPI +} + +static Boolean +_CanIgnoreLeafStatusCodes(CSSM_TP_APPLE_EVIDENCE_INFO *evidence) +{ + /* Check for ignorable status codes in leaf certificate's evidence */ + Boolean result = true; + unsigned int i; + for (i=0; i < evidence->NumStatusCodes; i++) { + CSSM_RETURN scode = evidence->StatusCodes[i]; + if (scode == CSSMERR_APPLETP_INVALID_CA) { + // the TP has rejected this CA cert because it's in the leaf position + result = true; + } + else if (ignorableRevocationStatusCode(scode)) { + result = true; + } + else { + result = false; + break; + } + } + return result; +} + +static OSStatus +_FilterWithPolicy(SecPolicyRef policy, CFDateRef date, SecCertificateRef cert) +{ + CFDictionaryRef props = NULL; + CFArrayRef keychains = NULL; + CFArrayRef anchors = NULL; + CFArrayRef certs = NULL; + CFArrayRef chain = NULL; + SecTrustRef trust = NULL; + + SecTrustResultType trustResult; + CSSM_TP_APPLE_EVIDENCE_INFO *evidence = NULL; + Boolean needChain = false; + OSStatus status; + if (!policy || !cert) return errSecParam; + + certs = CFArrayCreate(NULL, (const void **)&cert, (CFIndex)1, &kCFTypeArrayCallBacks); + status = SecTrustCreateWithCertificates(certs, policy, &trust); + if(status) goto cleanup; + + /* Set evaluation date, if specified (otherwise current date is implied) */ + if (date && (CFGetTypeID(date) == CFDateGetTypeID())) { + status = SecTrustSetVerifyDate(trust, date); + if(status) goto cleanup; + } + + /* Check whether this is the X509 Basic policy, which means chain building */ + props = SecPolicyCopyProperties(policy); + if (props) { + CFTypeRef oid = (CFTypeRef) CFDictionaryGetValue(props, kSecPolicyOid); + if (oid && CFEqual(oid, kSecPolicyAppleX509Basic)) { + needChain = true; + } + } + + if (!needChain) { + /* To make the evaluation as lightweight as possible, specify an empty array + * of keychains which will be searched for certificates. + */ + keychains = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks); + status = SecTrustSetKeychains(trust, keychains); + if(status) goto cleanup; + + /* To make the evaluation as lightweight as possible, specify an empty array + * of trusted anchors. + */ + anchors = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks); + status = SecTrustSetAnchorCertificates(trust, anchors); + if(status) goto cleanup; + } + + /* All parameters are locked and loaded, ready to evaluate! */ + status = SecTrustEvaluate(trust, &trustResult); + if(status) goto cleanup; + + /* If we didn't provide trust anchors or a way to look for them, + * the evaluation will fail with kSecTrustResultRecoverableTrustFailure. + * However, we can tell whether the policy evaluation succeeded by + * looking at the per-cert status codes in the returned evidence. + */ + status = SecTrustGetResult(trust, &trustResult, &chain, &evidence); + if(status) goto cleanup; + + if (!(trustResult == kSecTrustResultProceed || + trustResult == kSecTrustResultUnspecified || + trustResult == kSecTrustResultRecoverableTrustFailure)) { + /* The evaluation failed in a non-recoverable way */ + status = errSecCertificateCannotOperate; + goto cleanup; + } + + /* If there are no per-cert policy status codes, + * and the cert has not expired, consider it valid for the policy. + */ + if((evidence != NULL) && _CanIgnoreLeafStatusCodes(evidence) && + ((evidence[0].StatusBits & CSSM_CERT_STATUS_EXPIRED) == 0) && + ((evidence[0].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET) == 0)) { + status = errSecSuccess; + } + else { + status = errSecCertificateCannotOperate; + } + +cleanup: + if(props) CFRelease(props); + if(chain) CFRelease(chain); + if(anchors) CFRelease(anchors); + if(keychains) CFRelease(keychains); + if(certs) CFRelease(certs); + if(trust) CFRelease(trust); + + return status; +} + +static OSStatus +_FilterWithDate(CFTypeRef validOnDate, SecCertificateRef cert) +{ + if (!validOnDate || !cert) return errSecParam; + + CFAbsoluteTime at, nb, na; + if (CFGetTypeID(validOnDate) == CFDateGetTypeID()) + at = CFDateGetAbsoluteTime((CFDateRef)validOnDate); + else + at = CFAbsoluteTimeGetCurrent(); + + OSStatus status = errSecSuccess; + nb = SecCertificateNotValidBefore(cert); + na = SecCertificateNotValidAfter(cert); + + if (nb == 0 || na == 0 || nb == na) + status = errSecCertificateCannotOperate; + else if (at < nb) + status = errSecCertificateNotValidYet; + else if (at > na) + status = errSecCertificateExpired; + + return status; +} + +static OSStatus +_FilterWithTrust(Boolean trustedOnly, SecCertificateRef cert) +{ + if (!cert) return errSecParam; + if (!trustedOnly) return errSecSuccess; + + CFArrayRef certArray = CFArrayCreate(NULL, (const void**)&cert, 1, &kCFTypeArrayCallBacks); + SecPolicyRef policy = SecPolicyCreateWithOID(kSecPolicyAppleX509Basic); + OSStatus status = (policy == NULL) ? errSecPolicyNotFound : errSecSuccess; + + if (!status) { + SecTrustRef trust = NULL; + status = SecTrustCreateWithCertificates(certArray, policy, &trust); + if (!status) { + SecTrustResultType trustResult; + status = SecTrustEvaluate(trust, &trustResult); + if (!status) { + if (!(trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified)) { + status = (trustResult == kSecTrustResultDeny) ? errSecTrustSettingDeny : errSecNotTrusted; + } + } + CFRelease(trust); + } + CFRelease(policy); + } + if (certArray) { + CFRelease(certArray); + } + + return status; +} + +static SecKeychainItemRef +CopyResolvedKeychainItem(CFTypeRef item) +{ + SecKeychainItemRef kcItem = NULL; + OSStatus status; + if (item) { + if (CFGetTypeID(item) == CFDataGetTypeID()) { + // persistent reference, resolve first + status = SecKeychainItemCopyFromPersistentReference((CFDataRef)item, &kcItem); + } + else { + // normal reference + kcItem = (SecKeychainItemRef) CFRetain(item); + } + if (kcItem) { + // ask for the item's class: + // will return an error if the item has been deleted + SecItemClass itemClass; + SecKeychainItemRef certRef = NULL; + if (CFGetTypeID(kcItem) == SecIdentityGetTypeID()) { + status = SecIdentityCopyCertificate((SecIdentityRef)kcItem, (SecCertificateRef *)&certRef); + } + status = SecKeychainItemCopyAttributesAndData((certRef) ? certRef : kcItem, NULL, &itemClass, NULL, NULL, NULL); + if (certRef) { + CFRelease(certRef); + } + if (status) { + CFRelease(kcItem); + kcItem = NULL; + } + } + } + return kcItem; +} + +static OSStatus +UpdateKeychainSearchAndCopyNext(SecItemParams *params, CFTypeRef *item) +{ + // This function refreshes the search parameters in the specific case where + // the caller is searching for kSecClassKey items but did not provide the + // kSecAttrKeyClass. In that case, params->assumedKeyClass will be set, and + // we must perform separate searches to obtain all results. + + OSStatus status = errSecItemNotFound; + if (!params || !params->assumedKeyClass || !params->query || !item) + return status; + + // Free the previous search reference and attribute list. + if (params->search) + CFRelease(params->search); + params->search = NULL; + _FreeAttrList(params->attrList); + params->attrList = NULL; + + // Make a copy of the query dictionary so we can set the key class parameter. + CFMutableDictionaryRef dict = CFDictionaryCreateMutableCopy(NULL, 0, params->query); + CFRelease(params->query); + params->query = dict; + CFDictionarySetValue(dict, kSecAttrKeyClass, params->assumedKeyClass); + + // Determine the current item class for this search, and the next assumed key class. + if (CFEqual(params->assumedKeyClass, kSecAttrKeyClassSymmetric)) { + params->itemClass = kSecSymmetricKeyItemClass; + params->assumedKeyClass = kSecAttrKeyClassPublic; + } else if (CFEqual(params->assumedKeyClass, kSecAttrKeyClassPublic)) { + params->itemClass = kSecPublicKeyItemClass; + params->assumedKeyClass = kSecAttrKeyClassPrivate; + } else { + params->itemClass = kSecPrivateKeyItemClass; + params->assumedKeyClass = NULL; + } + + // Rebuild the attribute list for the new key class. + if (_CreateSecKeychainAttributeListFromDictionary(dict, params->itemClass, ¶ms->attrList) == errSecSuccess) { + // Create a new search reference for the new attribute list. + if (SecKeychainSearchCreateFromAttributes(params->searchList, + params->itemClass, + (params->attrList->count == 0) ? NULL : params->attrList, + (SecKeychainSearchRef*)¶ms->search) == errSecSuccess) { + // Return the first matching item from the new search. + // We won't come back here again until there are no more matching items for this search. + status = SecKeychainSearchCopyNext((SecKeychainSearchRef)params->search, (SecKeychainItemRef*)item); + } + } + return status; +} + + +static OSStatus +SecItemSearchCopyNext(SecItemParams *params, CFTypeRef *item) +{ + // Generic "copy next match" function for SecKeychainSearchRef or SecIdentitySearchRef. + // Returns either a SecKeychainItemRef or a SecIdentityRef in the output parameter, + // depending on the type of search reference. + + OSStatus status; + CFTypeRef search = (params) ? params->search : NULL; + CFTypeID typeID = (search) ? CFGetTypeID(search) : 0; + if (typeID == SecIdentitySearchGetTypeID()) { + status = SecIdentitySearchCopyNext((SecIdentitySearchRef)search, (SecIdentityRef*)item); + } + else if (typeID == SecKeychainSearchGetTypeID()) { + status = SecKeychainSearchCopyNext((SecKeychainSearchRef)search, (SecKeychainItemRef*)item); + // Check if we need to refresh the search for the next key class + while (status == errSecItemNotFound && params->assumedKeyClass != NULL) + status = UpdateKeychainSearchAndCopyNext(params, item); + } + else if (typeID == 0 && (params->useItems || params->itemList)) { + // No search available, but there is an item list available. + // Return the next candidate item from the caller's item list + CFArrayRef itemList = (params->useItems) ? params->useItems : params->itemList; + CFIndex count = CFArrayGetCount(itemList); + *item = (CFTypeRef) NULL; + if (params->itemListIndex < count) { + *item = (CFTypeRef)CFArrayGetValueAtIndex(itemList, params->itemListIndex++); + if (*item) { + // Potentially resolve persistent item references here, and + // verify the item reference we're about to hand back is still + // valid (it could have been deleted from the keychain while + // our query was holding onto the itemList). + *item = CopyResolvedKeychainItem(*item); + if (*item && (CFGetTypeID(*item) == SecIdentityGetTypeID())) { + // Persistent reference resolved to an identity, so return that type. + params->returnIdentity = true; + } + } + } + status = (*item) ? errSecSuccess : errSecItemNotFound; + } + else { + status = errSecItemNotFound; + } + return status; +} + +static OSStatus +FilterCandidateItem(CFTypeRef *item, SecItemParams *itemParams, SecIdentityRef *identity) +{ + if (!item || *item == NULL || !itemParams) + return errSecItemNotFound; + + OSStatus status; + CFStringRef commonName = NULL; + SecIdentityRef foundIdentity = NULL; + if (CFGetTypeID(*item) == SecIdentityGetTypeID()) { + // we found a SecIdentityRef, rather than a SecKeychainItemRef; + // replace the found "item" with its associated certificate (which is the + // item we actually want for purposes of getting attributes, data, or a + // persistent data reference), and return the identity separately. + SecCertificateRef certificate; + status = SecIdentityCopyCertificate((SecIdentityRef) *item, &certificate); + if (itemParams->returnIdentity) { + foundIdentity = (SecIdentityRef) *item; + if (identity) { + *identity = foundIdentity; + } + } + else { + CFRelease(*item); + } + *item = (CFTypeRef)certificate; + } + + CFDictionaryRef query = itemParams->query; + + if (itemParams->itemClass == kSecCertificateItemClass) { + // perform string comparisons first + CFStringCompareFlags flags = _StringCompareFlagsFromQuery(query); + CFStringRef nameContains, nameStarts, nameEnds, nameExact; + if (!CFDictionaryGetValueIfPresent(query, kSecMatchSubjectContains, (const void **)&nameContains)) + nameContains = NULL; + if (!CFDictionaryGetValueIfPresent(query, kSecMatchSubjectStartsWith, (const void **)&nameStarts)) + nameStarts = NULL; + if (!CFDictionaryGetValueIfPresent(query, kSecMatchSubjectEndsWith, (const void **)&nameEnds)) + nameEnds = NULL; + if (!CFDictionaryGetValueIfPresent(query, kSecMatchSubjectWholeString, (const void **)&nameExact)) + nameExact = NULL; + if (nameContains || nameStarts || nameEnds || nameExact) { + status = SecCertificateCopyCommonName((SecCertificateRef)*item, &commonName); + if (status || !commonName) goto filterOut; + } + if (nameContains) { + CFRange range = CFStringFind(commonName, nameContains, flags); + if (range.length < 1) + goto filterOut; + // certificate item contains string; proceed to next check + } + if (nameStarts) { + CFRange range = CFStringFind(commonName, nameStarts, flags); + if (range.length < 1 || range.location > 1) + goto filterOut; + // certificate item starts with string; proceed to next check + } + if (nameEnds) { + CFRange range = CFStringFind(commonName, nameEnds, flags); + if (range.length < 1 || range.location != (CFStringGetLength(commonName) - CFStringGetLength(nameEnds))) + goto filterOut; + // certificate item ends with string; proceed to next check + } + if (nameExact) { + CFRange range = CFStringFind(commonName, nameExact, flags); + if (range.length < 1 || (CFStringGetLength(commonName) != CFStringGetLength(nameExact))) + goto filterOut; + // certificate item exactly matches string; proceed to next check + } + if (itemParams->returnIdentity) { + // if we already found and returned the identity, we can skip this + if (!foundIdentity) { + status = SecIdentityCreateWithCertificate(itemParams->searchList, (SecCertificateRef) *item, identity); + if (status) goto filterOut; + } + // certificate item is part of an identity; proceed to next check + } + if (itemParams->policy) { + status = _FilterWithPolicy(itemParams->policy, (CFDateRef)itemParams->validOnDate, (SecCertificateRef) *item); + if (status) goto filterOut; + // certificate item is valid for specified policy (and optionally specified date) + } + if (itemParams->validOnDate) { + status = _FilterWithDate(itemParams->validOnDate, (SecCertificateRef) *item); + if (status) goto filterOut; + // certificate item is valid for specified date + } + if (itemParams->trustedOnly) { + // if we are getting candidate items from a SecIdentitySearchCreateWithPolicy search, + // their trust has already been validated and we can skip this part. + if (!(foundIdentity && itemParams->returnIdentity && itemParams->policy)) { + status = _FilterWithTrust(itemParams->trustedOnly, (SecCertificateRef) *item); + if (status) goto filterOut; + } + // certificate item is trusted on this system + } + } + if (itemParams->itemList) { + Boolean foundMatch = FALSE; + CFIndex idx, count = CFArrayGetCount(itemParams->itemList); + for (idx=0; idxitemList, idx); + SecKeychainItemRef realItem = NULL; + SecCertificateRef aCert = NULL; + if (anItem == NULL) { + continue; + } + if (CFDataGetTypeID() == CFGetTypeID(anItem) && + errSecSuccess == SecKeychainItemCopyFromPersistentReference((CFDataRef)anItem, &realItem)) { + anItem = realItem; + } + if (SecIdentityGetTypeID() == CFGetTypeID(anItem) && + errSecSuccess == SecIdentityCopyCertificate((SecIdentityRef)anItem, &aCert)) { + anItem = aCert; + } + if (CFEqual(anItem, (CFTypeRef) *item)) { + foundMatch = TRUE; + } + if (aCert) { + CFRelease(aCert); + } + if (realItem) { + CFRelease(realItem); + } + if (foundMatch) { + break; + } + } + if (!foundMatch) goto filterOut; + // item was found on provided list + } + + if (foundIdentity && !identity) { + CFRelease(foundIdentity); + } + if (commonName) { + CFRelease(commonName); + } + + // if we get here, consider the item a match + return errSecSuccess; + +filterOut: + if (commonName) { + CFRelease(commonName); + } + CFRelease(*item); + *item = NULL; + if (foundIdentity) { + CFRelease(foundIdentity); + if (identity) { + *identity = NULL; + } + } + return errSecItemNotFound; +} + +static OSStatus +AddItemResults(SecKeychainItemRef item, + SecIdentityRef identity, + SecItemParams *itemParams, + CFAllocatorRef allocator, + CFMutableArrayRef *items, + CFTypeRef *result) +{ + // Given a found item (which may also be an identity), this function adds + // the requested result types (specified in itemParams) to the appropriate + // container as follows: + // + // 1. If there is only one result type (numResultTypes == 1) and only one + // match requested (maxMatches == 1), set *result directly. + // + // 2. If there are multiple result types (numResultTypes > 1), and only one + // match requested (maxMatches == 1), add each result type to itemDict + // and set itemDict as the value of *result. + // + // 3. If there is only one result type (numResultTypes == 1) and multiple + // possible matches (maxMatches > 1), add the result type to *items + // and set *items as the value of *result. + // + // 4. If there are multiple result types (numResultTypes > 1) and multiple + // possible matches (maxMatches > 1), add each result type to itemDict, + // add itemDict to *items, and set *items as the value of *result. + // + // Note that we allocate *items if needed. + + if (!item || !itemParams || !result) + return errSecParam; + + if (itemParams->maxMatches > 1) { + // if we can return more than one item, we must have an array + if (!items) + return errSecParam; + else if (*items == NULL) + *items = CFArrayCreateMutable(allocator, 0, &kCFTypeArrayCallBacks); + } + + OSStatus tmpStatus, status = errSecSuccess; + CFMutableArrayRef itemArray = (items) ? *items : NULL; + CFMutableDictionaryRef itemDict = NULL; + if (itemParams->numResultTypes > 1) { + // if we're returning more than one result type, each item we return must be a dictionary + itemDict = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } + + if (itemParams->returningRef) { + const void* itemRef = (identity) ? (const void*)identity : (const void*)item; + if (itemDict) { + CFDictionaryAddValue(itemDict, kSecValueRef, itemRef); + } + else if (itemArray) { + CFArrayAppendValue(itemArray, itemRef); + } + else { + *result = CFRetain((CFTypeRef)itemRef); + } + } + + if (itemParams->returningPersistentRef) { + CFDataRef persistentRef; + SecKeychainItemRef tmpItem = item; + if (itemParams->identityRef) { + tmpItem = (SecKeychainItemRef)itemParams->identityRef; + } + tmpStatus = SecKeychainItemCreatePersistentReference(tmpItem, &persistentRef); + if (tmpStatus == errSecSuccess) { + if (itemDict) { + CFDictionaryAddValue(itemDict, kSecValuePersistentRef, persistentRef); + } + else if (itemArray) { + CFArrayAppendValue(itemArray, persistentRef); + } + else { + *result = CFRetain(persistentRef); + } + CFRelease(persistentRef); + } + else if (status == errSecSuccess) { + status = tmpStatus; + } + } + + if (itemParams->returningData) { + UInt32 length; + void *data; + tmpStatus = SecKeychainItemCopyContent(item, NULL, NULL, &length, &data); + if (tmpStatus == errSecSuccess) { + CFDataRef dataRef = CFDataCreate(allocator, (UInt8 *)data, length); + if (itemDict) { + CFDictionaryAddValue(itemDict, kSecValueData, dataRef); + } + else if (itemArray) { + CFArrayAppendValue(itemArray, dataRef); + } + else { + *result = CFRetain(dataRef); + } + CFRelease(dataRef); + (void) SecKeychainItemFreeContent(NULL, data); + } + else if (status == errSecSuccess) { + status = tmpStatus; + } + } + + if (itemParams->returningAttributes) { + CFDictionaryRef attrsDict = NULL; + SecItemClass itemClass; + // since we have an item, allow its actual class to override the query-specified item class + tmpStatus = SecKeychainItemCopyAttributesAndData(item, NULL, &itemClass, NULL, NULL, NULL); + if (tmpStatus) { + itemClass = itemParams->itemClass; + } + tmpStatus = _CreateAttributesDictionaryFromItem(allocator, itemClass, item, &attrsDict); + if (attrsDict) { + if (itemDict) { + // add all keys and values from attrsDict to the item dictionary + CFDictionaryApplyFunction(attrsDict, _AddDictValueToOtherDict, &itemDict); + } + else if (itemArray) { + CFArrayAppendValue(itemArray, attrsDict); + } + else { + *result = CFRetain(attrsDict); + } + CFRelease(attrsDict); + } + if (tmpStatus && (status == errSecSuccess)) { + status = tmpStatus; + } + } + + if (itemDict) { + if (itemArray) { + CFArrayAppendValue(itemArray, itemDict); + CFRelease(itemDict); + *result = itemArray; + } + else { + *result = itemDict; + } + } + else if (itemArray) { + *result = itemArray; + } + + return status; +} + +CFDataRef _SecItemGetPersistentReference(CFTypeRef raw_item) +{ + try { + Item item = ItemImpl::required((SecKeychainItemRef)raw_item); + return item->getPersistentRef(); + } catch(...) { + return NULL; + } +} + +/******************************************************************************/ +#pragma mark SecItem API functions +/******************************************************************************/ + +// +// Approximate result of using iOS sec's copyNumber, 0 return could be zero, or error. +// +static SInt32 readNumber(CFTypeRef obj) { + CFTypeID tid = CFGetTypeID(obj); + SInt32 v = 0; + if (tid == CFNumberGetTypeID()) { + CFNumberGetValue((CFNumberRef)obj, kCFNumberSInt32Type, &v); + return v; + } else if (tid == CFBooleanGetTypeID()) { + v = CFBooleanGetValue((CFBooleanRef)obj); + return v; + } else if (tid == CFStringGetTypeID()) { + v = CFStringGetIntValue((CFStringRef)obj); + CFStringRef t = CFStringCreateWithFormat(0, 0, CFSTR("%ld"), (long)v); + /* If a string converted to an int isn't equal to the int printed as + a string, return a CFStringRef instead. */ + if (!CFEqual(t, obj)) { + CFRelease(t); + return 0; + } + CFRelease(t); + return v; + } else + return NULL; +} + +// +// Function to ensure the syncable keychain is unlocked. +// Currently, this means unlocking the login keychain, +// which will also unlock the keybag as a side effect. +// +static OSStatus SecItemUnlockSynchronizableKeychain() +{ + SecKeychainRef keychain = NULL; + OSStatus status = SecKeychainCopyLogin(&keychain); + if (!status) { + status = SecKeychainUnlock(keychain, 0, NULL, false); + } + CFReleaseSafe(keychain); + return status; +} + +// +// Function to check whether the kSecAttrSynchronizable flag is set in the query. +// +static Boolean SecItemSynchronizable(CFDictionaryRef query) +{ + CFTypeRef value = CFDictionaryGetValue(query, kSecAttrSynchronizable); + Boolean result = (value && readNumber(value)); + + return result; +} + +// +// Function to check whether the kSecAttrSynchronizable flag is set in the query, +// and has the special value of kSecAttrSynchronizableAny. +// +static Boolean SecItemSynchronizableAny(CFDictionaryRef query) +{ + CFTypeRef value = CFDictionaryGetValue(query, kSecAttrSynchronizable); + if (value) { + return (CFGetTypeID(value) == CFStringGetTypeID() && + CFEqual(value, kSecAttrSynchronizableAny)); + } + return false; +} + +// +// Function to check whether the kSecAttrSynchronizable attribute is being updated. +// +static Boolean SecItemHasSynchronizableUpdate(Boolean synchronizable, CFDictionaryRef changes) +{ + CFTypeRef newValue = CFDictionaryGetValue(changes, kSecAttrSynchronizable); + if (!newValue) + return false; + + Boolean new_sync = readNumber(newValue); + Boolean old_sync = synchronizable; + + return (old_sync != new_sync); +} + +// +// Returns true if keychain syncing is globally enabled. +// +static Boolean SecItemSyncEnabled() +{ + static dispatch_once_t onceToken; + static Boolean syncEnabled = true; + + //sudo defaults write /Library/Preferences/com.apple.security SecItemSynchronizable -bool YES + dispatch_once(&onceToken, ^{ + CFTypeRef sync = (CFNumberRef)CFPreferencesCopyValue(CFSTR("SecItemSynchronizable"), CFSTR("com.apple.security"), kCFPreferencesAnyUser, kCFPreferencesCurrentHost); + + if (sync && CFGetTypeID(sync) == CFBooleanGetTypeID()) { + syncEnabled = CFBooleanGetValue((CFBooleanRef)sync); + CFRelease(sync); + } + }); + + return syncEnabled; +} + +// +// Function to check whether a synchronizable persistent reference was provided. +// +static Boolean SecItemHasSynchronizablePersistentReference(CFDictionaryRef query) +{ + CFTypeRef value = CFDictionaryGetValue(query, kSecValuePersistentRef); + if (value) { + /* Synchronizable persistent ref consists of the sqlite rowid and 4-byte class value */ + const CFIndex kSynchronizablePersistentRefLength = sizeof(int64_t) + 4; + return (CFGetTypeID(value) == CFDataGetTypeID() && + CFDataGetLength((CFDataRef)value) == kSynchronizablePersistentRefLength); + } + return false; +} + +// +// Function to apply changes to a mutable dictionary. +// (CFDictionaryApplierFunction, called by CFDictionaryApplyFunction) +// +static void SecItemApplyChanges(const void *key, const void *value, void *context) +{ + CFMutableDictionaryRef dict = (CFMutableDictionaryRef) context; + if (!dict) return; + + CFDictionarySetValue(dict, key, value); +} + +// +// Function to change matching items from non-syncable to syncable +// (if toSyncable is true), otherwise from syncable to non-syncable. +// This currently moves items between keychain containers. +// +static OSStatus SecItemChangeSynchronizability(CFDictionaryRef query, CFDictionaryRef changes, Boolean toSyncable) +{ + // Note: the input query dictionary is a mutable copy of the query originally + // provided by the caller as the first parameter to SecItemUpdate. It may not + // specify returning attributes or data, but we will need both to make a copy. + // + CFDictionaryRemoveValue((CFMutableDictionaryRef)query, kSecReturnRef); + CFDictionaryRemoveValue((CFMutableDictionaryRef)query, kSecReturnPersistentRef); + CFDictionaryRemoveValue((CFMutableDictionaryRef)query, kSecReturnData); + CFDictionarySetValue((CFMutableDictionaryRef)query, kSecReturnAttributes, kCFBooleanTrue); + if (NULL == CFDictionaryGetValue(changes, kSecValueData)) + CFDictionarySetValue((CFMutableDictionaryRef)query, kSecReturnData, kCFBooleanTrue); + + CFTypeRef result; + OSStatus status; + if (toSyncable) + status = SecItemCopyMatching_osx(query, &result); + else + status = SecItemCopyMatching_ios(query, &result); + + if (status) + return status; + if (!result) + return errSecItemNotFound; + + CFMutableArrayRef items; + if (CFGetTypeID(result) != CFArrayGetTypeID()) { + items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + CFArrayAppendValue(items, result); + CFRelease(result); + } + else { + items = (CFMutableArrayRef)result; + } + + CFIndex idx, count = (items) ? CFArrayGetCount(items) : 0; + int priority = LOG_DEBUG; + OSStatus err = 0; + for (idx = 0; idx < count; idx++) { + CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(items, idx); + CFMutableDictionaryRef item = (CFMutableDictionaryRef) + SecItemCopyTranslatedAttributes(dict, + CFDictionaryGetValue(query, kSecClass), + (toSyncable) ? true : false /*iOSOut*/, + true /*pruneMatch*/, + true /*pruneSync*/, + true /*pruneReturn*/, + false /*pruneData*/, + (toSyncable) ? true : false /*pruneAccess*/); + // hold onto the query before applying changes, in case the item already exists. + // note that we cannot include the creation or modification dates from our + // found item in this query, as they may not match the item in the other keychain. + CFMutableDictionaryRef itemQuery = CFDictionaryCreateMutableCopy(NULL, 0, item); + CFDictionaryRemoveValue(itemQuery, kSecAttrCreationDate); + CFDictionaryRemoveValue(itemQuery, kSecAttrModificationDate); + // apply changes to the item dictionary that we will pass to SecItemAdd + CFDictionaryApplyFunction(changes, SecItemApplyChanges, item); + if (toSyncable) { + CFDictionarySetValue(item, kSecAttrSynchronizable, kCFBooleanTrue); + status = SecItemAdd_ios(item, NULL); + secitemlog(priority, "ChangeSync: SecItemAdd_ios=%d", status); + if (errSecDuplicateItem == status) { + // find and apply changes to the existing syncable item. + CFDictionarySetValue(itemQuery, kSecAttrSynchronizable, kCFBooleanTrue); + status = SecItemUpdate_ios(itemQuery, changes); + secitemlog(priority, "ChangeSync: SecItemUpdate_ios=%d", status); + } + if (errSecSuccess == status) { + CFDictionarySetValue(itemQuery, kSecAttrSynchronizable, kCFBooleanFalse); + status = SecItemDelete_osx(itemQuery); + secitemlog(priority, "ChangeSync: SecItemDelete_osx=%d", status); + } + } + else { + CFDictionarySetValue(item, kSecAttrSynchronizable, kCFBooleanFalse); + status = SecItemAdd_osx(item, NULL); + secitemlog(priority, "ChangeSync: SecItemAdd_osx=%d", status); + if (errSecDuplicateItem == status) { + // find and apply changes to the existing non-syncable item. + CFDictionarySetValue(itemQuery, kSecAttrSynchronizable, kCFBooleanFalse); + status = SecItemUpdate_osx(itemQuery, changes); + secitemlog(priority, "ChangeSync: SecItemUpdate_osx=%d", status); + } + if (errSecSuccess == status) { + CFDictionarySetValue(itemQuery, kSecAttrSynchronizable, kCFBooleanTrue); + status = SecItemDelete_ios(itemQuery); + secitemlog(priority, "ChangeSync: SecItemDelete_ios=%d", status); + } + } + CFReleaseSafe(item); + CFReleaseSafe(itemQuery); + if (status) + err = status; + } + CFReleaseSafe(items); + + return err; +} + + +extern "C" { + +CFTypeRef +SecItemCreateFromAttributeDictionary(CFDictionaryRef refAttributes) { + CFTypeRef ref = NULL; + CFStringRef key_class_string = (CFStringRef)CFDictionaryGetValue(refAttributes, kSecClass); + SecItemClass key_class; + bool key_class_found = false; + + if (CFEqual(key_class_string, kSecClassGenericPassword)) { + key_class = kSecGenericPasswordItemClass; + key_class_found = true; + } + if (CFEqual(key_class_string, kSecClassInternetPassword)) { + key_class = kSecInternetPasswordItemClass; + key_class_found = true; + } + + if (key_class_found) { + // we carry v_Data around here so the *_ios calls can find it and locate + // their own data. Putting things in the attribute list doesn't help as + // the osx keychainitem and item calls bail when they don't see a keychain + // object. If we need to make them work we either have to bridge them, or + // find a way to craft a workable keychain object. #if'ed code left below + // in case we need to go down that path. + + struct SecKeychainAttributeList *attrs = (struct SecKeychainAttributeList *)malloc(sizeof(struct SecKeychainAttributeList) + sizeof(struct SecKeychainAttribute) * 0); + attrs->attr = (struct SecKeychainAttribute *)(attrs + 1); + attrs->count = 0; + CFTypeRef v; +#if 0 + // The C++ string objects need to last at least as long as the attr struct. + string account; + + v = CFDictionaryGetValue(refAttributes, CFSTR("mdat")); + if (v) { + attrs->attr[attrs->count].tag = kSecModDateItemAttr; + // XXX need to convert to YYYYMMDDhhmmSSZ + attrs->attr[attrs->count].data = (void*)"19690223140232Z"; + attrs->attr[attrs->count].length = strlen((char*)(attrs->attr[attrs->count].data)); + attrs->count++; + } + v = CFDictionaryGetValue(refAttributes, CFSTR("cdat")); + if (v) { + attrs->attr[attrs->count].tag = kSecCreationDateItemAttr; + // XXX need to convert to YYYYMMDDhhmmSSZ + attrs->attr[attrs->count].data = (void*)"19690223140232Z"; + attrs->attr[attrs->count].length = strlen((char*)(attrs->attr[attrs->count].data)); + attrs->count++; + } + + v = CFDictionaryGetValue(refAttributes, CFSTR("acct")); + if (v) { + attrs->attr[attrs->count].tag = kSecAccountItemAttr; + account = cfString((CFStringRef)v); + attrs->attr[attrs->count].data = (void*)(account.c_str()); + attrs->attr[attrs->count].length = account.length(); + attrs->count++; + } + + // class isn't treated as an attribute by the creation API + + v = CFDictionaryGetValue(refAttributes, CFSTR("svce")); + if (v) { + attrs->attr[attrs->count].tag = kSecServiceItemAttr; + account = cfString((CFStringRef)v); + attrs->attr[attrs->count].data = (void*)(account.c_str()); + attrs->attr[attrs->count].length = account.length(); + attrs->count++; + } + + v = CFDictionaryGetValue(refAttributes, CFSTR("acct")); + if (v) { + attrs->attr[attrs->count].tag = kSecLabelItemAttr; + account = cfString((CFStringRef)v); + attrs->attr[attrs->count].data = (void*)(account.c_str()); + attrs->attr[attrs->count].length = account.length(); + attrs->count++; + } +#endif + Item item = Item(key_class, attrs, 0, ""); + ItemImpl *real_item = item.get(); + v = CFDictionaryGetValue(refAttributes, kSecValuePersistentRef); + if (v) { + real_item->setPersistentRef((CFDataRef)v); + } + ref = real_item->handle(); + } else { + // keys, certs, identities are not currently sync'able. + ref = NULL; + } + return ref; +} + +/* + * SecItemValidateAppleApplicationGroupAccess determines if the caller + * is a member of the specified application group, and is signed by Apple. + */ +OSStatus +SecItemValidateAppleApplicationGroupAccess(CFStringRef group) +{ + SecTrustedApplicationRef app = NULL; + SecRequirementRef requirement = NULL; + SecCodeRef code = NULL; + OSStatus status = errSecParam; + + if (group) { + CFIndex length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(group), kCFStringEncodingUTF8) + 1; + char* buffer = (char*) malloc(length); + if (buffer) { + if (CFStringGetCString(group, buffer, length, kCFStringEncodingUTF8)) { + status = SecTrustedApplicationCreateApplicationGroup(buffer, NULL, &app); + } + free(buffer); + } else { + status = errSecMemoryError; + } + } + if (!status) { + status = SecTrustedApplicationCopyRequirement(app, &requirement); + } + if (!status) { + status = SecCodeCopySelf(kSecCSDefaultFlags, &code); + } + if (!status) { + status = SecCodeCheckValidity(code, kSecCSDefaultFlags, requirement); + } + + CFReleaseSafe(code); + CFReleaseSafe(requirement); + CFReleaseSafe(app); + return status; +} + +/* + * SecItemCopyTranslatedAttributes accepts a user-provided attribute dictionary + * and attempts to return a sanitized copy for passing to the underlying + * platform-specific implementation code. + * + * If iOSOut is true, one or more translations may apply: + * - SecKeychain refs are removed, since there aren't multiple keychains + * - SecPolicy refs are removed, since they can't be externalized + * - SecAccess refs are removed, and potentially translated to entitlements + * + * If pruneMatch is true, kSecMatch* attributes are removed; this avoids + * parameter errors due to strict input checks in secd, which only permits + * these constants for calls to SecItemCopyMatching. + * + * If pruneSync is true, the kSecAttrSynchronizable attribute is removed. + * This permits a query to be reused for non-synchronizable items, or to + * resolve a search based on a persistent item reference for iOS. + * + * If pruneReturn is true, kSecReturn* attributes are removed; this avoids + * parameter errors due to strict input checks in secd, which do not permit + * these constants for calls to SecItemUpdate. + */ +CFDictionaryRef +SecItemCopyTranslatedAttributes(CFDictionaryRef inOSXDict, CFTypeRef itemClass, + bool iOSOut, bool pruneMatch, bool pruneSync, bool pruneReturn, bool pruneData, bool pruneAccess) +{ + CFMutableDictionaryRef result = CFDictionaryCreateMutableCopy(NULL, 0, inOSXDict); + if (result == NULL) { + return result; + } + + if (pruneSync) { + CFDictionaryRemoveValue(result, kSecAttrSynchronizable); + } + + if (pruneMatch) { + /* Match constants are only supported on iOS for SecItemCopyMatching, + * and will generate an error if passed to other SecItem API functions; + * on OS X, they're just ignored if not applicable for the context. + */ + CFDictionaryRemoveValue(result, kSecMatchPolicy); + CFDictionaryRemoveValue(result, kSecMatchItemList); + CFDictionaryRemoveValue(result, kSecMatchSearchList); + CFDictionaryRemoveValue(result, kSecMatchIssuers); + CFDictionaryRemoveValue(result, kSecMatchEmailAddressIfPresent); + CFDictionaryRemoveValue(result, kSecMatchSubjectContains); + CFDictionaryRemoveValue(result, kSecMatchCaseInsensitive); + CFDictionaryRemoveValue(result, kSecMatchTrustedOnly); + CFDictionaryRemoveValue(result, kSecMatchValidOnDate); + CFDictionaryRemoveValue(result, kSecMatchLimit); + CFDictionaryRemoveValue(result, kSecMatchLimitOne); + CFDictionaryRemoveValue(result, kSecMatchLimitAll); + } + + if (pruneReturn) { + /* Return constants are not supported on iOS for SecItemUpdate, + * where they will generate an error; on OS X, they're just ignored + * if not applicable for the context. + */ + CFDictionaryRemoveValue(result, kSecReturnData); + CFDictionaryRemoveValue(result, kSecReturnAttributes); + CFDictionaryRemoveValue(result, kSecReturnRef); + CFDictionaryRemoveValue(result, kSecReturnPersistentRef); + } + + if (pruneData) { + /* Searching on data is not supported. */ + CFDictionaryRemoveValue(result, kSecValueData); + } + + if (pruneAccess) { + /* Searching on access lists is not supported */ + CFDictionaryRemoveValue(result, kSecAttrAccess); + } + + if (iOSOut) { + /* Remove kSecMatchSearchList (value is array of SecKeychainRef); + * cannot specify a keychain search list on iOS + */ + CFDictionaryRemoveValue(result, kSecMatchSearchList); + + /* Remove kSecUseKeychain (value is a SecKeychainRef); + * cannot specify a keychain on iOS + */ + CFDictionaryRemoveValue(result, kSecUseKeychain); + + /* Remove kSecMatchPolicy (value is a SecPolicyRef); + * TODO: need a way to externalize and restore a policy instance + */ + CFDictionaryRemoveValue(result, kSecMatchPolicy); + + /* Potentially translate kSecAttrAccess (value is a SecAccessRef), + * unless kSecAttrAccessGroup has already been specified. + */ + SecAccessRef access = (SecAccessRef) CFDictionaryGetValue(result, kSecAttrAccess); + CFStringRef accessGroup = (CFStringRef) CFDictionaryGetValue(result, kSecAttrAccessGroup); + if (access != NULL && accessGroup == NULL) { + /* Translate "InternetAccounts" application group to an access group */ + if (errSecSuccess == SecItemValidateAppleApplicationGroupAccess(CFSTR("InternetAccounts"))) { + /* The caller is a valid member of the application group. */ + CFStringRef groupName = CFSTR("appleaccount"); + CFTypeRef value = CFDictionaryGetValue(result, kSecAttrAuthenticationType); + if (value && CFEqual(value, kSecAttrAuthenticationTypeHTMLForm)) { + groupName = CFSTR("com.apple.cfnetwork"); + } + CFDictionarySetValue(result, kSecAttrAccessGroup, groupName); + } + } + CFDictionaryRemoveValue(result, kSecAttrAccess); + + /* If item is specified by direct reference, and this is an iOS search, + * replace it with a persistent reference. + */ + CFTypeRef directRef = CFDictionaryGetValue(result, kSecValueRef); + if (directRef) { + CFDataRef persistentRef = _SecItemGetPersistentReference(directRef); + if (persistentRef) { + CFDictionarySetValue(result, kSecValuePersistentRef, persistentRef); + } + CFDictionaryRemoveValue(result, kSecValueRef); + } + + /* If item is specified by persistent reference, and this is an iOS search, + * remove the synchronizable attribute as it will be rejected by secd. + */ + CFTypeRef persistentRef = CFDictionaryGetValue(result, kSecValuePersistentRef); + if (persistentRef) { + CFDictionaryRemoveValue(result, kSecAttrSynchronizable); + } + + /* Remove kSecAttrModificationDate; this should never be used as criteria + * for a search, or to add/modify an item. (If we are cloning an item + * and want to keep its modification date, we don't call this function.) + * It turns out that some clients are using the full attributes dictionary + * returned by SecItemCopyMatching as a query to find the same item later, + * which won't work once the item is updated. + */ + CFDictionaryRemoveValue(result, kSecAttrModificationDate); + } + else { + /* iOS doesn't add the class attribute, so we must do it here. */ + if (itemClass) + CFDictionarySetValue(result, kSecClass, itemClass); + + /* Remove attributes which are not part of the OS X database schema. */ + CFDictionaryRemoveValue(result, kSecAttrAccessible); + CFDictionaryRemoveValue(result, kSecAttrAccessGroup); + CFDictionaryRemoveValue(result, kSecAttrSynchronizable); + CFDictionaryRemoveValue(result, kSecAttrTombstone); + } + + return result; +} + +/* + * SecItemCopyMergedResults takes two input objects, which may be containers, + * and returns a retained object which merges the results. Merging depends on the + * result type. If each result is valid and is not an array, then only one match was + * requested; in that case, the syncable (ios) match is preferred. + * + * FIXME: There are some edge cases still to deal with; e.g. if the OSX search specified a + * particular keychain to search, we do not want to merge in any IOS results. Also, may need + * to filter out duplicates if two items differ only in the sync attribute. + */ +CFTypeRef +SecItemCopyMergedResults(CFDictionaryRef query, CFTypeRef result_osx, CFTypeRef result_ios) +{ + CFTypeID id_osx = (result_osx) ? CFGetTypeID(result_osx) : 0; + CFTypeID id_ios = (result_ios) ? CFGetTypeID(result_ios) : 0; + CFTypeID id_array = CFArrayGetTypeID(); + if ((id_osx == id_array) && (id_ios == id_array)) { + // Fold the arrays into one. + CFMutableArrayRef results = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + CFArrayAppendArray(results, (CFArrayRef)result_ios, CFRangeMake(0, CFArrayGetCount((CFArrayRef)result_ios))); + CFArrayAppendArray(results, (CFArrayRef)result_osx, CFRangeMake(0, CFArrayGetCount((CFArrayRef)result_osx))); + return results; + } + // Result type is not an array, so only one match can be returned. + return (id_ios) ? CFRetain(result_ios) : CFRetain(result_osx); +} + +} /* extern "C" */ + + +OSStatus +SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) +{ + secitemlog(LOG_NOTICE, "SecItemCopyMatching"); + if (!query) { + return errSecParam; + } + secitemshow(query, "SecItemCopyMatching query:"); + + OSStatus status_osx = errSecItemNotFound, status_ios = errSecItemNotFound; + CFTypeRef result_osx = NULL, result_ios = NULL; + Boolean sync_enabled = SecItemSyncEnabled(); + Boolean search_ios = SecItemSynchronizable(query); + Boolean merge_search = SecItemSynchronizableAny(query); + Boolean persistref_ios = SecItemHasSynchronizablePersistentReference(query); + + if (sync_enabled && (merge_search || persistref_ios || search_ios)) { + CFDictionaryRef attrs_ios = SecItemCopyTranslatedAttributes(query, + CFDictionaryGetValue(query, kSecClass), true, false, false, false, true, true); + if (!attrs_ios) { + status_ios = errSecParam; + } + else { + SecItemUnlockSynchronizableKeychain(); + status_ios = SecItemCopyMatching_ios(attrs_ios, &result_ios); + CFRelease(attrs_ios); + } + secitemlog(LOG_NOTICE, "SecItemCopyMatching_ios result: %d", status_ios); + if (!merge_search || persistref_ios) { + AssignOrReleaseResult(result_ios, result); + return status_ios; // no need to search non-syncable keychains + } + } + + CFDictionaryRef attrs_osx = SecItemCopyTranslatedAttributes(query, + CFDictionaryGetValue(query, kSecClass), false, false, true, false, true, true); + if (!attrs_osx) { + status_osx = errSecParam; + } + else { + status_osx = SecItemCopyMatching_osx(attrs_osx, &result_osx); + CFRelease(attrs_osx); + } + secitemlog(LOG_NOTICE, "SecItemCopyMatching_osx result: %d", status_osx); + + // If one of the searches failed to occur or produce results, we can eliminate it + if (result_ios == NULL) { + AssignOrReleaseResult(result_osx, result); + return status_osx; // we can only have non-syncable results + } + if (result_osx == NULL) { + AssignOrReleaseResult(result_ios, result); + return status_ios; // we can only have syncable results + } + + // If we get here, need to merge results + CFTypeRef result_merged = SecItemCopyMergedResults(query, result_osx, result_ios); + CFReleaseSafe(result_osx); + CFReleaseSafe(result_ios); + AssignOrReleaseResult(result_merged, result); + + if (status_osx == status_ios) { + return status_osx; // both searches produced the same result + } + else if (!status_osx || !status_ios) { + return errSecSuccess; // one of the searches succeeded + } + else if (status_osx == errSecItemNotFound) { + return status_ios; // this failure was more interesting + } + return status_osx; +} + +OSStatus +SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) +{ + secitemlog(LOG_NOTICE, "SecItemAdd"); + if (!attributes) { + return errSecParam; + } + else if (result) { + *result = NULL; + } + secitemshow(attributes, "SecItemAdd attrs:"); + + OSStatus status_osx, status_ios; + CFTypeRef result_osx = NULL, result_ios = NULL; + Boolean sync_enabled = SecItemSyncEnabled(); + Boolean add_ios = SecItemSynchronizable(attributes); + + if (sync_enabled && add_ios) { + CFDictionaryRef attrs_ios = SecItemCopyTranslatedAttributes(attributes, + NULL, true, true, false, false, false, false); + if (!attrs_ios) { + status_ios = errSecParam; + } + else { + SecItemUnlockSynchronizableKeychain(); + status_ios = SecItemAdd_ios(attrs_ios, &result_ios); + CFRelease(attrs_ios); + } + secitemlog(LOG_NOTICE, "SecItemAdd_ios result: %d", status_ios); + if (result) + *result = result_ios; + else + CFReleaseSafe(result_ios); + return status_ios; + } + + CFDictionaryRef attrs_osx = SecItemCopyTranslatedAttributes(attributes, + NULL, false, false, true, false, false, false); + if (!attrs_osx) { + status_osx = errSecParam; + } + else { + status_osx = SecItemAdd_osx(attrs_osx, &result_osx); + CFRelease(attrs_osx); + } + secitemlog(LOG_NOTICE, "SecItemAdd_osx result: %d", status_osx); + if (result) + *result = result_osx; + else + CFReleaseSafe(result_osx); + return status_osx; +} + +OSStatus +SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) +{ + secitemlog(LOG_NOTICE, "SecItemUpdate"); + if (!query || !attributesToUpdate) { + return errSecParam; + } + secitemshow(query, "SecItemUpdate query:"); + secitemshow(attributesToUpdate, "SecItemUpdate attrs:"); + + OSStatus status_osx = errSecItemNotFound, status_ios = errSecItemNotFound; + Boolean sync_enabled = SecItemSyncEnabled(); + Boolean search_ios = SecItemSynchronizable(query); + Boolean merge_search = SecItemSynchronizableAny(query); + Boolean persistref_ios = SecItemHasSynchronizablePersistentReference(query); + + if (sync_enabled && (merge_search || persistref_ios || search_ios)) { + CFDictionaryRef attrs_ios = SecItemCopyTranslatedAttributes(query, + CFDictionaryGetValue(query, kSecClass), true, true, false, true, true, true); + if (!attrs_ios) { + status_ios = errSecParam; + } + else { + SecItemUnlockSynchronizableKeychain(); + if (SecItemHasSynchronizableUpdate(true, attributesToUpdate)) + status_ios = SecItemChangeSynchronizability(attrs_ios, attributesToUpdate, false); + else + status_ios = SecItemUpdate_ios(attrs_ios, attributesToUpdate); + CFRelease(attrs_ios); + } + secitemlog(LOG_NOTICE, "SecItemUpdate_ios result: %d", status_ios); + if (!merge_search || persistref_ios) + return status_ios; + } + + CFDictionaryRef attrs_osx = SecItemCopyTranslatedAttributes(query, + CFDictionaryGetValue(query, kSecClass), false, false, true, true, true, true); + if (!attrs_osx) { + status_osx = errSecParam; + } + else { + if (SecItemHasSynchronizableUpdate(false, attributesToUpdate)) + status_osx = SecItemChangeSynchronizability(attrs_osx, attributesToUpdate, true); + else + status_osx = SecItemUpdate_osx(attrs_osx, attributesToUpdate); + + CFRelease(attrs_osx); + } + secitemlog(LOG_NOTICE, "SecItemUpdate_osx result: %d", status_osx); + if (merge_search) { + // Harmonize the result of the update attempts. + if (status_osx == status_ios) { + // both updates produced the same result + return status_ios; + } + else if (!status_osx || !status_ios) { + // one of the updates succeeded, but the other failed + if (status_osx == errSecItemNotFound || status_ios == errSecItemNotFound) + return errSecSuccess; // item only found in one keychain + else + return (status_osx) ? status_osx : status_ios; // return the error + } + else if (status_osx == errSecItemNotFound) { + // both updates failed, status_ios failure is more interesting + // since the item was actually found + return status_ios; + } + } + return status_osx; +} + +OSStatus +SecItemDelete(CFDictionaryRef query) +{ + secitemlog(LOG_NOTICE, "SecItemDelete"); + if (!query) { + return errSecParam; + } + secitemshow(query, "SecItemDelete query:"); + + OSStatus status_osx = errSecItemNotFound, status_ios = errSecItemNotFound; + Boolean sync_enabled = SecItemSyncEnabled(); + Boolean search_ios = SecItemSynchronizable(query); + Boolean merge_search = SecItemSynchronizableAny(query); + Boolean persistref_ios = SecItemHasSynchronizablePersistentReference(query); + + if (sync_enabled && (merge_search || persistref_ios || search_ios)) { + CFDictionaryRef attrs_ios = SecItemCopyTranslatedAttributes(query, + NULL, true, true, false, true, true, true); + if (!attrs_ios) { + status_ios = errSecParam; + } + else { + SecItemUnlockSynchronizableKeychain(); + status_ios = SecItemDelete_ios(attrs_ios); + CFRelease(attrs_ios); + } + secitemlog(LOG_NOTICE, "SecItemDelete_ios result: %d", status_ios); + if (!merge_search || persistref_ios) + return status_ios; + } + + CFDictionaryRef attrs_osx = SecItemCopyTranslatedAttributes(query, + NULL, false, false, true, true, true, true); + if (!attrs_osx) { + status_osx = errSecParam; + } + else { + status_osx = SecItemDelete_osx(attrs_osx); + CFRelease(attrs_osx); + } + secitemlog(LOG_NOTICE, "SecItemDelete_osx result: %d", status_osx); + + if (merge_search) { + // Harmonize the result of the delete attempts. + if (status_osx == status_ios) { + // both deletes produced the same result + return status_ios; + } + else if (!status_osx || !status_ios) { + // one of the deletes succeeded, but the other failed + if (status_osx == errSecItemNotFound || status_ios == errSecItemNotFound) + return errSecSuccess; // item only found in one keychain + else + return (status_osx) ? status_osx : status_ios; // return the error + } + else if (status_osx == errSecItemNotFound) { + // both deletes failed, status_ios failure is more interesting + // since the item was actually found + return status_ios; + } + } + return status_osx; +} + +OSStatus +SecItemCopyMatching_osx( + CFDictionaryRef query, + CFTypeRef *result) +{ + if (!query || !result) + return errSecParam; + else + *result = NULL; + + CFAllocatorRef allocator = CFGetAllocator(query); + CFIndex matchCount = 0; + CFMutableArrayRef itemArray = NULL; + SecKeychainItemRef item = NULL; + SecIdentityRef identity = NULL; + OSStatus tmpStatus, status = errSecSuccess; + + // validate input query parameters and create the search reference + SecItemParams *itemParams = _CreateSecItemParamsFromDictionary(query, &status); + require_action(itemParams != NULL, error_exit, itemParams = NULL); + + // find the next match until we hit maxMatches, or no more matches found + while ( !(!itemParams->returnAllMatches && matchCount >= itemParams->maxMatches) && + SecItemSearchCopyNext(itemParams, (CFTypeRef*)&item) == errSecSuccess) { + + if (FilterCandidateItem((CFTypeRef*)&item, itemParams, &identity)) + continue; // move on to next item + + ++matchCount; // we have a match + + tmpStatus = AddItemResults(item, identity, itemParams, allocator, &itemArray, result); + if (tmpStatus && (status == errSecSuccess)) + status = tmpStatus; + + if (item) { + CFRelease(item); + item = NULL; + } + if (identity) { + CFRelease(identity); + identity = NULL; + } + } + + if (status == errSecSuccess) + status = (matchCount > 0) ? errSecSuccess : errSecItemNotFound; + +error_exit: + if (status != errSecSuccess && result != NULL && *result != NULL) { + CFRelease(*result); + *result = NULL; + } + _FreeSecItemParams(itemParams); + + return status; +} + +OSStatus +SecItemCopyDisplayNames( + CFArrayRef items, + CFArrayRef *displayNames) +{ + BEGIN_SECAPI + Required(items); + Required(displayNames); + //%%%TBI + return errSecUnimplemented; + END_SECAPI +} + +OSStatus +SecItemAdd_osx( + CFDictionaryRef attributes, + CFTypeRef *result) +{ + if (!attributes) + return errSecParam; + else if (result) + *result = NULL; + + CFAllocatorRef allocator = CFGetAllocator(attributes); + CFMutableArrayRef itemArray = NULL; + SecKeychainItemRef item = NULL; + OSStatus tmpStatus, status = errSecSuccess; + + // validate input attribute parameters + SecItemParams *itemParams = _CreateSecItemParamsFromDictionary(attributes, &status); + require_action(itemParams != NULL, error_exit, itemParams = NULL); + + // currently, we don't support adding SecIdentityRef items (an aggregate item class), + // since the private key should already be in a keychain by definition. We could support + // this as a copy operation for the private key if a different keychain is specified, + // but in any case it should try to add the certificate. See . + require_action(!itemParams->returnIdentity, error_exit, status = errSecItemInvalidValue); + + if (!itemParams->useItems) { + // create a single keychain item specified by the input attributes + status = SecKeychainItemCreateFromContent(itemParams->itemClass, + itemParams->attrList, + (itemParams->itemData) ? (UInt32)CFDataGetLength(itemParams->itemData) : 0, + (itemParams->itemData) ? CFDataGetBytePtrVoid(itemParams->itemData) : NULL, + itemParams->keychain, + itemParams->access, + &item); + require_noerr(status, error_exit); + + // return results (if requested) + if (result) { + itemParams->maxMatches = 1; // in case kSecMatchLimit was set to > 1 + tmpStatus = AddItemResults(item, NULL, itemParams, allocator, &itemArray, result); + if (tmpStatus && (status == errSecSuccess)) + status = tmpStatus; + } + CFRelease(item); + } + else { + // add multiple items which are specified in the itemParams->useItems array. + // -- SecCertificateRef or SecKeyRef items may or may not be in a keychain. + // -- SecKeychainItemRef items are in a keychain (by definition), but may be copied to another keychain. + // -- CFDataRef items are a persistent reference; the represented item may be copied to another keychain. + // + OSStatus aggregateStatus = errSecSuccess; + CFIndex ix, count = CFArrayGetCount(itemParams->useItems); + itemParams->maxMatches = (count > 1) ? (int)count : 2; // force results to always be returned as an array + for (ix=0; ix < count; ix++) { + CFTypeRef anItem = (CFTypeRef) CFArrayGetValueAtIndex(itemParams->useItems, ix); + if (anItem) { + if (SecCertificateGetTypeID() == CFGetTypeID(anItem)) { + // SecCertificateRef item + tmpStatus = SecCertificateAddToKeychain((SecCertificateRef)anItem, itemParams->keychain); + if (!tmpStatus && result) { + tmpStatus = AddItemResults((SecKeychainItemRef)anItem, NULL, itemParams, allocator, &itemArray, result); + } + aggregateStatus = _UpdateAggregateStatus(tmpStatus, aggregateStatus, errSecDuplicateItem); + } + else if (SecKeyGetTypeID() == CFGetTypeID(anItem)) { + // SecKeyRef item + SecKeychainRef itemKeychain = NULL; + tmpStatus = SecKeychainItemCopyKeychain((SecKeychainItemRef)anItem, &itemKeychain); + if (tmpStatus == errSecSuccess) { + // key was in a keychain, so we can attempt to copy it + SecKeychainItemRef itemCopy = NULL; + tmpStatus = SecKeychainItemCreateCopy((SecKeychainItemRef)anItem, itemParams->keychain, itemParams->access, &itemCopy); + if (!tmpStatus && result) { + tmpStatus = AddItemResults(itemCopy, NULL, itemParams, allocator, &itemArray, result); + } + if (itemCopy) { + CFRelease(itemCopy); + } + } + else { + // key was not in any keychain, so must be imported + SecKeychainItemRef keyItem = NULL; + tmpStatus = _ImportKey((SecKeyRef)anItem, itemParams->keychain, itemParams->access, itemParams->attrList, &keyItem); + if (!tmpStatus && result) { + tmpStatus = AddItemResults(keyItem, NULL, itemParams, allocator, &itemArray, result); + } + if (keyItem) { + CFRelease(keyItem); + } + } + if (itemKeychain) { + CFRelease(itemKeychain); + } + aggregateStatus = _UpdateAggregateStatus(tmpStatus, aggregateStatus, errSecDuplicateItem); + } + else if (SecKeychainItemGetTypeID() == CFGetTypeID(anItem)) { + // SecKeychainItemRef item + SecKeychainItemRef itemCopy = NULL; + tmpStatus = SecKeychainItemCreateCopy((SecKeychainItemRef)anItem, itemParams->keychain, itemParams->access, &itemCopy); + if (!tmpStatus && result) { + tmpStatus = AddItemResults(itemCopy, NULL, itemParams, allocator, &itemArray, result); + } + if (itemCopy) { + CFRelease(itemCopy); + } + aggregateStatus = _UpdateAggregateStatus(tmpStatus, aggregateStatus, errSecDuplicateItem); + } + else if (CFDataGetTypeID() == CFGetTypeID(anItem)) { + // CFDataRef item (persistent reference) + SecKeychainItemRef realItem = NULL; + tmpStatus = SecKeychainItemCopyFromPersistentReference((CFDataRef)anItem, &realItem); + if (tmpStatus == errSecSuccess) { + // persistent reference resolved to a keychain item, so we can attempt to copy it + SecKeychainItemRef itemCopy = NULL; + tmpStatus = SecKeychainItemCreateCopy(realItem, itemParams->keychain, itemParams->access, &itemCopy); + if (!tmpStatus && result) { + tmpStatus = AddItemResults(itemCopy, NULL, itemParams, allocator, &itemArray, result); + } + if (itemCopy) { + CFRelease(itemCopy); + } + } + if (realItem) { + CFRelease(realItem); + } + aggregateStatus = _UpdateAggregateStatus(tmpStatus, aggregateStatus, errSecDuplicateItem); + } + } + } // end of itemList array loop + status = aggregateStatus; + } // end processing multiple items + +error_exit: + if (status != errSecSuccess && result != NULL && *result != NULL) { + CFRelease(*result); + *result = NULL; + } + _FreeSecItemParams(itemParams); + + return status; +} + +OSStatus +SecItemUpdate_osx( + CFDictionaryRef query, + CFDictionaryRef attributesToUpdate) +{ + if (!query || !attributesToUpdate) + return errSecParam; + + // run the provided query to get a list of items to update + CFTypeRef results = NULL; + OSStatus status = SecItemCopyMatching(query, &results); + if (status != errSecSuccess) + return status; // nothing was matched, or the query was bad + + CFArrayRef items = NULL; + if (CFArrayGetTypeID() == CFGetTypeID(results)) { + items = (CFArrayRef) results; + } + else { + items = CFArrayCreate(NULL, &results, 1, &kCFTypeArrayCallBacks); + CFRelease(results); + } + + OSStatus result = errSecSuccess; + CFIndex ix, count = CFArrayGetCount(items); + for (ix=0; ix < count; ix++) { + CFTypeRef anItem = (CFTypeRef) CFArrayGetValueAtIndex(items, ix); + if (anItem) { + status = _UpdateKeychainItem(anItem, attributesToUpdate); + result = _UpdateAggregateStatus(status, result, errSecSuccess); + } + } + + if (items) { + CFRelease(items); + } + return result; +} + +OSStatus +SecItemDelete_osx( + CFDictionaryRef query) +{ + if (!query) + return errSecParam; + + // run the provided query to get a list of items to delete + CFTypeRef results = NULL; + OSStatus status = SecItemCopyMatching_osx(query, &results); + if (status != errSecSuccess) + return status; // nothing was matched, or the query was bad + + CFArrayRef items = NULL; + if (CFArrayGetTypeID() == CFGetTypeID(results)) { + items = (CFArrayRef) results; + } + else { + items = CFArrayCreate(NULL, &results, 1, &kCFTypeArrayCallBacks); + CFRelease(results); + } + + OSStatus result = errSecSuccess; + CFIndex ix, count = CFArrayGetCount(items); + for (ix=0; ix < count; ix++) { + CFTypeRef anItem = (CFTypeRef) CFArrayGetValueAtIndex(items, ix); + if (anItem) { + if (SecIdentityGetTypeID() == CFGetTypeID(anItem)) { + status = _DeleteIdentity((SecIdentityRef)anItem); + } + else { + status = _DeleteKeychainItem(anItem); + } + result = _UpdateAggregateStatus(status, result, errSecSuccess); + } + } + + if (items) + CFRelease(items); + + return result; +}