--- /dev/null
+/*
+ * Copyright (c) 2015 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 "kc-30-xara-helpers.h"
+
+#ifndef kc_30_xara_item_helpers_h
+#define kc_30_xara_item_helpers_h
+
+#if TARGET_OS_MAC
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-variable"
+#pragma clang diagnostic ignored "-Wunused-function"
+
+
+static CFMutableDictionaryRef makeBaseItemDictionary(CFStringRef itemclass) {
+ CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionarySetValue(query, kSecClass, itemclass);
+
+ if(CFEqual(itemclass, kSecClassInternetPassword)) {
+ CFDictionarySetValue(query, kSecAttrServer, CFSTR("test_service"));
+ CFDictionarySetValue(query, kSecAttrAuthenticationType, CFSTR("dflt")); // Default, I guess?
+ } else {
+ // Generic passwords have services
+ CFDictionarySetValue(query, kSecAttrService, CFSTR("test_service"));
+ }
+ return query;
+}
+
+static CFMutableDictionaryRef makeQueryItemDictionary(SecKeychainRef kc, CFStringRef itemclass) {
+ CFMutableDictionaryRef query = makeBaseItemDictionary(itemclass);
+
+ CFMutableArrayRef searchList = (CFMutableArrayRef) CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks);
+ CFArrayAppendValue((CFMutableArrayRef)searchList, kc);
+ CFDictionarySetValue(query, kSecMatchSearchList, searchList);
+
+ CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ return query;
+}
+
+static CFMutableDictionaryRef makeQueryCustomItemDictionary(SecKeychainRef kc, CFStringRef itemclass, CFStringRef label) {
+ CFMutableDictionaryRef query = makeQueryItemDictionary(kc, itemclass);
+ CFDictionarySetValue(query, kSecAttrLabel, label);
+ return query;
+}
+
+static CFMutableDictionaryRef makeAddCustomItemDictionary(SecKeychainRef kc, CFStringRef itemclass, CFStringRef label, CFStringRef account) {
+ CFMutableDictionaryRef query = makeBaseItemDictionary(itemclass);
+
+ CFDictionaryAddValue(query, kSecUseKeychain, kc);
+ CFDictionarySetValue(query, kSecAttrAccount, account);
+ CFDictionarySetValue(query, kSecAttrComment, CFSTR("a comment"));
+ CFDictionarySetValue(query, kSecAttrLabel, label);
+ CFDictionarySetValue(query, kSecValueData, CFDataCreate(NULL, (void*)"data", 4));
+ return query;
+}
+
+static CFMutableDictionaryRef makeAddItemDictionary(SecKeychainRef kc, CFStringRef itemclass, CFStringRef label) {
+ return makeAddCustomItemDictionary(kc, itemclass, label, CFSTR("test_account"));
+}
+
+static SecKeychainItemRef makeCustomItem(const char* name, SecKeychainRef kc, CFDictionaryRef addDictionary) {
+ CFTypeRef result = NULL;
+ ok_status(SecItemAdd(addDictionary, &result), "%s: SecItemAdd", name);
+ ok(result != NULL, "%s: SecItemAdd returned a result", name);
+
+ SecKeychainItemRef item = (SecKeychainItemRef) result;
+ ok(item != NULL, "%s: Couldn't convert into SecKeychainItemRef", name);
+
+ return item;
+}
+#define makeCustomItemTests 3
+
+static SecKeychainItemRef makeItem(const char* name, SecKeychainRef kc, CFStringRef itemclass, CFStringRef label) {
+ CFMutableDictionaryRef query = makeAddItemDictionary(kc, itemclass, label);
+
+ SecKeychainItemRef item = makeCustomItem(name, kc, query);
+
+ CFReleaseNull(query);
+ return item;
+}
+#define makeItemTests makeCustomItemTests
+
+static void makeCustomDuplicateItem(const char* name, SecKeychainRef kc, CFStringRef itemclass, CFStringRef label) {
+ CFMutableDictionaryRef query = makeAddItemDictionary(kc, itemclass, label);
+
+ CFTypeRef result = NULL;
+ is(SecItemAdd(query, &result), errSecDuplicateItem, "%s: SecItemAdd (duplicate)", name);
+
+ CFReleaseNull(query);
+}
+#define makeCustomDuplicateItemTests 1
+
+static void makeDuplicateItem(const char* name, SecKeychainRef kc, CFStringRef itemclass) {
+ return makeCustomDuplicateItem(name, kc, itemclass, CFSTR("test_label"));
+}
+#define makeDuplicateItemTests makeCustomDuplicateItemTests
+
+static void makeCustomItemWithIntegrity(const char* name, SecKeychainRef kc, CFStringRef itemclass, CFStringRef label, CFStringRef expectedHash) {
+ SecKeychainItemRef item = makeItem(name, kc, itemclass, label);
+ checkIntegrityHash(name, item, expectedHash);
+ CFReleaseNull(item);
+}
+#define makeCustomItemWithIntegrityTests (makeItemTests + checkIntegrityHashTests)
+
+static void makeItemWithIntegrity(const char* name, SecKeychainRef kc, CFStringRef itemclass, CFStringRef expectedHash) {
+ makeCustomItemWithIntegrity(name, kc, itemclass, CFSTR("test_label"), expectedHash);
+}
+#define makeItemWithIntegrityTests (makeCustomItemWithIntegrityTests)
+
+static void testAddItem(CFStringRef itemclass, CFStringRef expectedHash) {
+ char name[100];
+ sprintf(name, "testAddItem[%s]", CFStringGetCStringPtr(itemclass, kCFStringEncodingUTF8));
+ secdebugfunc("integrity", "************************************* %s", name);
+
+ SecKeychainRef kc = newKeychain(name);
+ makeItemWithIntegrity(name, kc, itemclass, expectedHash);
+
+ ok_status(SecKeychainDelete(kc), "%s: SecKeychainDelete", name);
+}
+#define testAddItemTests (newKeychainTests + makeItemWithIntegrityTests + 1)
+
+static void testCopyMatchingItem(CFStringRef itemclass, CFStringRef expectedHash) {
+ char name[100];
+ sprintf(name, "testCopyMatchingItem[%s]", CFStringGetCStringPtr(itemclass, kCFStringEncodingUTF8));
+ secdebugfunc("integrity", "************************************* %s", name);
+
+ SecKeychainRef kc = newKeychain(name);
+ makeItemWithIntegrity(name, kc, itemclass, expectedHash);
+
+ SecKeychainItemRef item = checkN(name, makeQueryItemDictionary(kc, itemclass), 1);
+ checkIntegrityHash(name, item, expectedHash);
+ ok_status(SecKeychainDelete(kc), "%s: SecKeychainDelete", name);
+}
+#define testCopyMatchingItemTests (newKeychainTests + makeItemWithIntegrityTests + checkNTests + checkIntegrityHashTests + 1)
+
+static void testUpdateItem(CFStringRef itemclass, CFStringRef expectedHashOrig, CFStringRef expectedHashAfter) {
+ char name[100];
+ sprintf(name, "testUpdateItem[%s]", CFStringGetCStringPtr(itemclass, kCFStringEncodingUTF8));
+ secdebugfunc("integrity", "************************************* %s", name);
+
+ SecKeychainRef kc = newKeychain(name);
+ makeItemWithIntegrity(name, kc, itemclass, expectedHashOrig);
+
+ CFMutableDictionaryRef query = makeQueryItemDictionary(kc, itemclass);
+ CFMutableDictionaryRef update = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(update, kSecAttrComment, CFSTR("a modification"));
+ CFDictionarySetValue(update, kSecAttrAccount, CFSTR("a account modification"));
+ CFDictionarySetValue(update, kSecAttrLabel, CFSTR("a label modification"));
+ ok_status(SecItemUpdate(query, update), "%s: SecItemUpdate", name);
+
+ CFReleaseNull(update);
+
+ SecKeychainItemRef item = checkN(name, makeQueryItemDictionary(kc, itemclass), 1);
+ checkIntegrityHash(name, item, expectedHashAfter);
+ CFReleaseNull(item);
+
+ // Check that updating data works
+ update = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(update, kSecValueData, CFDataCreate(NULL, (void*)"data", 4));
+ ok_status(SecItemUpdate(query, update), "%s: SecItemUpdate", name);
+
+ item = checkN(name, makeQueryItemDictionary(kc, itemclass), 1);
+ checkIntegrityHash(name, item, expectedHashAfter);
+
+ CFReleaseNull(query);
+ CFReleaseNull(update);
+
+ ok_status(SecKeychainDelete(kc), "%s: SecKeychainDelete", name);
+}
+#define testUpdateItemTests (newKeychainTests + makeItemWithIntegrityTests \
+ + 1 + checkNTests + checkIntegrityHashTests \
+ + 1 + checkNTests + checkIntegrityHashTests \
+ + 1)
+
+static void testAddDuplicateItem(CFStringRef itemclass, CFStringRef expectedHash) {
+ char name[100];
+ sprintf(name, "testAddDuplicateItem[%s]", CFStringGetCStringPtr(itemclass, kCFStringEncodingUTF8));
+ secdebugfunc("integrity", "************************************* %s", name);
+
+ SecKeychainRef kc = newKeychain(name);
+ makeItemWithIntegrity(name, kc, itemclass, expectedHash);
+
+ makeDuplicateItem(name, kc, itemclass);
+
+ ok_status(SecKeychainDelete(kc), "%s: SecKeychainDelete", name);
+}
+#define testAddDuplicateItemTests (newKeychainTests + makeItemWithIntegrityTests + makeDuplicateItemTests + 1)
+
+static void testDeleteItem(CFStringRef itemclass, CFStringRef expectedHash) {
+ char name[100];
+ sprintf(name, "testDeleteItem[%s]", CFStringGetCStringPtr(itemclass, kCFStringEncodingUTF8));
+ secdebugfunc("integrity", "************************************* %s", name);
+
+ SecKeychainRef kc = newKeychain(name);
+ makeItemWithIntegrity(name, kc, itemclass, expectedHash);
+
+ SecKeychainItemRef item = checkN(name, makeQueryItemDictionary(kc, itemclass), 1);
+ checkIntegrityHash(name, item, expectedHash);
+
+ ok_status(SecKeychainItemDelete(item), "%s: SecKeychainItemDelete", name);
+ checkN(name, makeQueryItemDictionary(kc, itemclass), 0);
+ ok_status(SecKeychainDelete(kc), "%s: SecKeychainDelete", name);
+}
+#define testDeleteItemTests (newKeychainTests + makeItemWithIntegrityTests + checkNTests + checkIntegrityHashTests + 1 + checkNTests + 1)
+
+static void writeEmptyV512Keychain(const char* name, const char* keychainFile);
+
+// This test is to find <rdar://problem/23515265> CrashTracer: accountsd at …curity: Security::KeychainCore::CCallbackMgr::consume + 387
+//
+// The issue was that items could remain in the Keychain cache, even after the
+// actual object was freed. The main path involved updating an item so that it
+// had the same primary key as an item which was in the cache but not in the
+// database (this could be caused by another process deleting the item and us
+// not receiving the notification).
+//
+// This test should pass. Failure is shown by crashing.
+//
+static void testUpdateRetainedItem(CFStringRef itemclass) {
+ char name[100];
+ sprintf(name, "testUpdateRetainedItem[%s]", CFStringGetCStringPtr(itemclass, kCFStringEncodingUTF8));
+ secdebugfunc("integrity", "************************************* %s", name);
+
+ writeEmptyV512Keychain(name, keychainFile);
+ SecKeychainRef kc = openCustomKeychain(name, "test.keychain", "password");
+
+ SecKeychainItemRef item = makeCustomItem(name, kc, makeAddCustomItemDictionary(kc, itemclass, CFSTR("test_label"), CFSTR("account1")));
+
+ CFRelease(checkN(name, makeQueryCustomItemDictionary(kc, itemclass, CFSTR("test_label")), 1));
+
+ is(CFGetRetainCount(item), 1, "%s: CFGetRetainCount(item)", name);
+
+ // Bump our local database version number a few times, so we'll re-read the database when we reset it later
+ CFReleaseSafe(makeCustomItem(name, kc, makeAddCustomItemDictionary(kc, itemclass, CFSTR("version"), CFSTR("version"))));
+ CFReleaseSafe(makeCustomItem(name, kc, makeAddCustomItemDictionary(kc, itemclass, CFSTR("bump"), CFSTR("bump"))));
+
+ // Simulate another process deleting the items we just made, and us not receiving the notification
+ writeEmptyV512Keychain(name, keychainFile);
+
+ // Generate some keychain notifications on a different keychain so the AppleDatabase will reload test.keychain
+ SecKeychainRef kc2 = newCustomKeychain(name, "unrelated.keychain", "password");
+ CFReleaseSafe(makeCustomItem(name, kc2, makeAddCustomItemDictionary(kc, itemclass, CFSTR("unrelated1_label"), CFSTR("unrelated1"))));
+ ok_status(SecKeychainDelete(kc2), "%s: SecKeychainDelete", name);
+
+ secdebugfunc("integrity", "************************************* should reload database\n");
+
+ SecKeychainItemRef item2 = makeCustomItem(name, kc, makeAddCustomItemDictionary(kc, itemclass, CFSTR("not_a_test_label"), CFSTR("account2")));
+ CFReleaseSafe(checkN(name, makeQueryCustomItemDictionary(kc, itemclass, CFSTR("not_a_test_label")), 1));
+ is(CFGetRetainCount(item2), 1, "%s: CFGetRetainCount(item2)", name);
+
+ // Now, update the second item so it would collide with the first
+ CFMutableDictionaryRef query = makeQueryCustomItemDictionary(kc, itemclass, CFSTR("not_a_test_label"));
+ CFMutableDictionaryRef update = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFDictionarySetValue(update, kSecAttrAccount, CFSTR("account1"));
+ CFDictionarySetValue(update, kSecAttrLabel, CFSTR("test_label"));
+ ok_status(SecItemUpdate(query, update), "%s: SecItemUpdate", name);
+
+ is(CFGetRetainCount(item), 1, "%s: CFGetRetainCount(item)", name);
+ CFReleaseNull(item);
+
+ SecKeychainItemRef result = checkN(name, makeQueryCustomItemDictionary(kc, itemclass, CFSTR("test_label")), 1);
+ CFReleaseNull(result);
+ ok_status(SecKeychainDelete(kc), "%s: SecKeychainDelete", name);
+}
+#define testUpdateRetainedItemTests (openCustomKeychainTests + makeCustomItemTests + checkNTests \
+ + 1 + makeCustomItemTests + makeCustomItemTests \
+ + newCustomKeychainTests + makeCustomItemTests + 1 \
+ + makeCustomItemTests + checkNTests + 1 \
+ + 1 + 1 + checkNTests + 1)
+
+#pragma clang pop
+#else
+
+#endif /* TARGET_OS_MAC */
+
+
+#endif /* kc_30_xara_item_helpers_h */