kc-30-xara-item-helpers.h   [plain text]


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