/* * 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; }