SecItem.c   [plain text]


/*
 * Copyright (c) 2006-2013 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@
 */

/* 
 * SecItem.c - CoreFoundation-based constants and functions for
    access to Security items (certificates, keys, identities, and
    passwords.)
 */

#include <Security/SecBasePriv.h>
#include <Security/SecItem.h>
#include <Security/SecItemPriv.h>
#include <Security/SecItemInternal.h>
#ifndef SECITEM_SHIM_OSX
#include <Security/SecKey.h>
#include <Security/SecKeyPriv.h>
#include <Security/SecCertificateInternal.h>
#include <Security/SecIdentity.h>
#include <Security/SecIdentityPriv.h>
#include <Security/SecRandom.h>
#include <Security/SecBasePriv.h>
#endif // *** END SECITEM_SHIM_OSX ***
#include <Security/SecTask.h>
#include <errno.h>
#include <limits.h>
#include <sqlite3.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <Security/SecBase.h>
#include <CoreFoundation/CFData.h>
#include <CoreFoundation/CFDate.h>
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFURL.h>
#include <CommonCrypto/CommonDigest.h>
#include <libkern/OSByteOrder.h>
#include <utilities/array_size.h>
#include <utilities/debugging.h>
#include <utilities/SecCFError.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/SecIOFormat.h>
#include <utilities/SecXPCError.h>
#include <utilities/der_plist.h>
#include <assert.h>

#include <Security/SecInternal.h>
#include <TargetConditionals.h>
#include "securityd_client.h"
#include "SecuritydXPC.h"
#include <AssertMacros.h>
#include <asl.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#ifndef SECITEM_SHIM_OSX
#include <libDER/asn1Types.h>
#endif // *** END SECITEM_SHIM_OSX ***

/* label when certificate data is joined with key data */
#define CERTIFICATE_DATA_COLUMN_LABEL "certdata" 

#include <utilities/SecDb.h>
#include <IOKit/IOReturn.h>

/* Return an OSStatus for a sqlite3 error code. */
static OSStatus osstatus_for_s3e(int s3e)
{
	if (s3e > 0 && s3e <= SQLITE_DONE) switch (s3e)
	{
        case SQLITE_OK:
            return 0;
        case SQLITE_ERROR:
            return errSecNotAvailable; /* errSecDuplicateItem; */
        case SQLITE_FULL: /* Happens if we run out of uniqueids */
            return errSecNotAvailable; /* TODO: Replace with a better error code. */
        case SQLITE_PERM:
        case SQLITE_READONLY:
            return errSecNotAvailable;
        case SQLITE_CANTOPEN:
            return errSecNotAvailable;
        case SQLITE_EMPTY:
            return errSecNotAvailable;
        case SQLITE_CONSTRAINT:
            return errSecDuplicateItem;
        case SQLITE_ABORT:
            return -1;
        case SQLITE_MISMATCH:
            return errSecNoSuchAttr;
        case SQLITE_AUTH:
            return errSecNotAvailable;
        case SQLITE_NOMEM:
            return -2; /* TODO: Replace with a real error code. */
        case SQLITE_INTERNAL:
        default:
            return errSecNotAvailable; /* TODO: Replace with a real error code. */
	}
    return s3e;
}

static OSStatus osstatus_for_kern_return(CFIndex kernResult)
{
	switch (kernResult)
	{
        case KERN_SUCCESS:
            return errSecSuccess;
        case kIOReturnNotReadable:
        case kIOReturnNotWritable:
            return errSecAuthFailed;
        case kIOReturnNotPermitted:
        case kIOReturnNotPrivileged:
        case kIOReturnLockedRead:
        case kIOReturnLockedWrite:
            return errSecInteractionNotAllowed;
        case kIOReturnError:
            return errSecDecode;
        case kIOReturnBadArgument:
            return errSecParam;
        default:
            return errSecNotAvailable; /* TODO: Replace with a real error code. */
	}
}

static OSStatus osstatus_for_xpc_error(CFIndex xpcError) {
    switch (xpcError)
	{
        case kSecXPCErrorSuccess:
            return errSecSuccess;
        case kSecXPCErrorUnexpectedType:
        case kSecXPCErrorUnexpectedNull:
            return errSecParam;
        case kSecXPCErrorConnectionFailed:
            return errSecNotAvailable;
        case kSecXPCErrorUnknown:
        default:
            return errSecInternal;
    }
}

static OSStatus osstatus_for_der_error(CFIndex derError) {
    switch (derError)
	{
        case kSecDERErrorUnknownEncoding:
        //case kSecDERErrorUnsupportedDERType:
            return errSecDecode;
        case kSecDERErrorUnsupportedCFObject:
        case kSecDERErrorUnsupportedNumberType:
            return errSecParam;
        case kSecDERErrorAllocationFailure:
        case kSecDERErrorUnderlyingError:
            return errSecAllocate;
        default:
            return errSecInternal;
    }
}

// Convert from securityd error codes to OSStatus for legacy API.
OSStatus SecErrorGetOSStatus(CFErrorRef error) {
    OSStatus status;
    if (error == NULL) {
        status = errSecSuccess;
    } else {
        CFStringRef domain = CFErrorGetDomain(error);
        if (domain == NULL) {
            secerror("No error domain for error: %@", error);
            status = errSecInternal;
        } else if (CFEqual(kSecErrorDomain, domain)) {
            status = (OSStatus)CFErrorGetCode(error);
        } else if (CFEqual(kSecDbErrorDomain, domain)) {
            status = osstatus_for_s3e((int)CFErrorGetCode(error));
        } else if (CFEqual(kSecErrnoDomain, domain)) {
            status = (OSStatus)CFErrorGetCode(error);
        } else if (CFEqual(kSecKernDomain, domain)) {
            status = osstatus_for_kern_return(CFErrorGetCode(error));
        } else if (CFEqual(sSecXPCErrorDomain, domain)) {
            status = osstatus_for_xpc_error(CFErrorGetCode(error));
        } else if (CFEqual(sSecDERErrorDomain, domain)) {
            status = osstatus_for_der_error(CFErrorGetCode(error));
        } else {
            secerror("unknown error domain: %@ for error: %@", domain, error);
            status = errSecInternal;
        }
    }
    return status;
}

// Wrapper to provide a CFErrorRef for legacy API.
OSStatus SecOSStatusWith(bool (^perform)(CFErrorRef *error)) {
    CFErrorRef error = NULL;
    OSStatus status;
    if (perform(&error)) {
        assert(error == NULL);
        status = errSecSuccess;
    } else {
        assert(error);
        status = SecErrorGetOSStatus(error);
        if (status != errSecItemNotFound)           // Occurs in normal operation, so exclude
            secerror("error:[%" PRIdOSStatus "] %@", status, error);
        CFReleaseNull(error);
    }
    return status;
}


/* IPC uses CFPropertyList to un/marshall input/output data and can handle:
   CFData, CFString, CFArray, CFDictionary, CFDate, CFBoolean, and CFNumber

   Currently in need of conversion below:
   @@@ kSecValueRef allows SecKeychainItemRef and SecIdentityRef
   @@@ kSecMatchPolicy allows a query with a SecPolicyRef
   @@@ kSecUseItemList allows a query against a list of itemrefs, this isn't
       currently implemented at all, but when it is needs to short circuit to
	   local evaluation, different from the sql query abilities
*/

#ifndef SECITEM_SHIM_OSX
static CFDictionaryRef
SecItemCopyAttributeDictionary(CFTypeRef ref) {
	CFDictionaryRef refDictionary = NULL;
	CFTypeID typeID = CFGetTypeID(ref);
	if (typeID == SecKeyGetTypeID()) {
		refDictionary = SecKeyCopyAttributeDictionary((SecKeyRef)ref);
	} else if (typeID == SecCertificateGetTypeID()) {
		refDictionary =
			SecCertificateCopyAttributeDictionary((SecCertificateRef)ref);
	} else if (typeID == SecIdentityGetTypeID()) {
        assert(false);
        SecIdentityRef identity = (SecIdentityRef)ref;
        SecCertificateRef cert = NULL;
        SecKeyRef key = NULL;
        if (!SecIdentityCopyCertificate(identity, &cert) &&
            !SecIdentityCopyPrivateKey(identity, &key)) 
        {
            CFDataRef data = SecCertificateCopyData(cert);
            CFDictionaryRef key_dict = SecKeyCopyAttributeDictionary(key);
            
            if (key_dict && data) {
                refDictionary = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, key_dict);
                CFDictionarySetValue((CFMutableDictionaryRef)refDictionary, 
                    CFSTR(CERTIFICATE_DATA_COLUMN_LABEL), data);
            }
            CFReleaseNull(key_dict);
            CFReleaseNull(data);
        }
        CFReleaseNull(cert);
        CFReleaseNull(key);
    } else {
		refDictionary = NULL;
	}
	return refDictionary;
}

static CFTypeRef
SecItemCreateFromAttributeDictionary(CFDictionaryRef refAttributes) {
	CFTypeRef ref = NULL;
	CFStringRef class = CFDictionaryGetValue(refAttributes, kSecClass);
	if (CFEqual(class, kSecClassCertificate)) {
		ref = SecCertificateCreateFromAttributeDictionary(refAttributes);
	} else if (CFEqual(class, kSecClassKey)) {
		ref = SecKeyCreateFromAttributeDictionary(refAttributes);
	} else if (CFEqual(class, kSecClassIdentity)) {
		CFAllocatorRef allocator = NULL;
		CFDataRef data = CFDictionaryGetValue(refAttributes, CFSTR(CERTIFICATE_DATA_COLUMN_LABEL));
		SecCertificateRef cert = SecCertificateCreateWithData(allocator, data);
		SecKeyRef key = SecKeyCreateFromAttributeDictionary(refAttributes);
		if (key && cert)
			ref = SecIdentityCreate(allocator, cert, key);
		CFReleaseSafe(cert);
		CFReleaseSafe(key);
#if 0
	/* We don't support SecKeychainItemRefs yet. */
	} else if (CFEqual(class, kSecClassGenericPassword)) {
	} else if (CFEqual(class, kSecClassInternetPassword)) {
	} else if (CFEqual(class, kSecClassAppleSharePassword)) {
#endif
	} else {
		ref = NULL;
	}
	return ref;
}
#else

extern CFTypeRef SecItemCreateFromAttributeDictionary(CFDictionaryRef refAttributes);

#endif

/* Turn the returned dictionary that contains all the attributes to create a
   ref into the exact result the client asked for */
static CFTypeRef makeRef(CFTypeRef ref, bool return_data, bool return_attributes)
{
	CFTypeRef result = NULL;
	if (!ref || (CFGetTypeID(ref) != CFDictionaryGetTypeID()))
		return NULL;

	CFTypeRef return_ref = SecItemCreateFromAttributeDictionary(ref);

	if (return_data || return_attributes) {
		if (return_attributes)
			result = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, ref);
		else
			result = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

		if (return_ref) {
			CFDictionarySetValue((CFMutableDictionaryRef)result, kSecValueRef, return_ref);
			CFRelease(return_ref);
		}

		if (!return_data)
			CFDictionaryRemoveValue((CFMutableDictionaryRef)result, kSecValueData);
	} else
		result = return_ref;

	return result;
}

OSStatus
SecItemCopyDisplayNames(CFArrayRef items, CFArrayRef *displayNames)
{
    // @@@ TBI
    return -1 /* errSecUnimplemented */;
}

#ifndef SECITEM_SHIM_OSX
static void merge_dictionary_by_overwrite(const void *key, const void *value, void *context)
{
	if (!CFEqual(key, kSecValueRef))
		CFDictionarySetValue((CFMutableDictionaryRef)context, key, value);
}

static void copy_applier(const void *key, const void *value, void *context)
{
    CFDictionarySetValue(context, key, value);
}

static OSStatus cook_query(CFDictionaryRef query, CFMutableDictionaryRef *explode)
{
	/* If a ref was specified we get it's attribute dictionary and parse it. */
	CFMutableDictionaryRef args = NULL;
	CFTypeRef value = CFDictionaryGetValue(query, kSecValueRef);
    if (value) {
		CFDictionaryRef refAttributes = SecItemCopyAttributeDictionary(value);
		if (!refAttributes)
			return errSecValueRefUnsupported;
			
		/* Replace any attributes we already got from the ref with the ones
		   from the attributes dictionary the caller passed us.  This allows
		   a caller to add an item using attributes from the ref and still
		   override some of them in the dictionary directly.  */
		args = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, refAttributes);
		CFRelease(refAttributes);
		CFDictionaryApplyFunction(query, merge_dictionary_by_overwrite, args);
	}
    value = CFDictionaryGetValue(query, kSecAttrIssuer);
    if (value) {
        /* convert DN to canonical issuer, if value is DN (top level sequence) */
        const DERItem name = { (unsigned char *)CFDataGetBytePtr(value), CFDataGetLength(value) };
        DERDecodedInfo content;
        if (!DERDecodeItem(&name, &content) &&
            (content.tag == ASN1_CONSTR_SEQUENCE))
        {
            CFDataRef canonical_issuer = createNormalizedX501Name(kCFAllocatorDefault, &content.content);
            if (canonical_issuer) {
                if (!args) {
                    args = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                    /* This is necessary because we rely on non NULL callbacks */
                    CFDictionaryApplyFunction(query, copy_applier, args);
                }
                /* Overwrite with new issuer */
                CFDictionarySetValue(args, kSecAttrIssuer, canonical_issuer);
                CFRelease(canonical_issuer);
            }
        }
    }
	*explode = args;
	return errSecSuccess;
}

typedef OSStatus (*secitem_operation)(CFDictionaryRef attributes, CFTypeRef *result);

static bool explode_identity(CFDictionaryRef attributes, secitem_operation operation, 
    OSStatus *return_status, CFTypeRef *return_result)
{
    bool handled = false;
	CFTypeRef value = CFDictionaryGetValue(attributes, kSecValueRef);
    if (value) {
        CFTypeID typeID = CFGetTypeID(value);
        if (typeID == SecIdentityGetTypeID()) {
            handled = true;
            OSStatus status = errSecSuccess;
            SecIdentityRef identity = (SecIdentityRef)value;
            SecCertificateRef cert = NULL;
            SecKeyRef key = NULL;
            if (!SecIdentityCopyCertificate(identity, &cert) &&
                !SecIdentityCopyPrivateKey(identity, &key)) 
            {
                CFMutableDictionaryRef partial_query =
                    CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, attributes);
                CFDictionarySetValue(partial_query, kSecValueRef, cert);
                CFTypeRef result = NULL;
                bool duplicate_cert = false;
                /* an identity is first and foremost a key, but it can have multiple
                   certs associated with it: so we identify it by the cert */
                status = operation(partial_query, return_result ? &result : NULL);
                if ((operation == (secitem_operation)SecItemAdd) &&
                    (status == errSecDuplicateItem)) {
                        duplicate_cert = true;
                        status = errSecSuccess;
                }

                if (!status || status == errSecItemNotFound) {
					bool skip_key_operation = false;
	
					/* if the key is still in use, skip deleting it */
					if (operation == (secitem_operation)SecItemDelete) {
						// find certs with cert.pkhh == keys.klbl
						CFDictionaryRef key_dict = NULL, query_dict = NULL;
						CFDataRef pkhh = NULL;
						
						key_dict = SecKeyCopyAttributeDictionary(key);
						if (key_dict)
							pkhh = (CFDataRef)CFDictionaryGetValue(key_dict, kSecAttrApplicationLabel);
						const void *keys[] = { kSecClass, kSecAttrPublicKeyHash };
						const void *vals[] = { kSecClassCertificate, pkhh };
						if (pkhh)
							query_dict = CFDictionaryCreate(NULL, keys, 
					            vals, (array_size(keys)),
								NULL, NULL);
						if (query_dict)
							if (errSecSuccess == SecItemCopyMatching(query_dict, NULL))
								skip_key_operation = true;
						CFReleaseSafe(query_dict);
						CFReleaseSafe(key_dict);
					}
					
					if (!skip_key_operation) {
	                    /* now perform the operation for the key */
	                    CFDictionarySetValue(partial_query, kSecValueRef, key);
	                    CFDictionarySetValue(partial_query, kSecReturnPersistentRef, kCFBooleanFalse);
	                    status = operation(partial_query, NULL);
	                    if ((operation == (secitem_operation)SecItemAdd) &&
	                        (status == errSecDuplicateItem) &&
	                        !duplicate_cert)
	                            status = errSecSuccess;
					}
					
                    /* add and copy matching for an identityref have a persistent ref result */
                    if (result) {
                        if (!status) {
                            /* result is a persistent ref to a cert */
                            sqlite_int64 rowid;
                            if (_SecItemParsePersistentRef(result, NULL, &rowid)) {
                                *return_result = _SecItemMakePersistentRef(kSecClassIdentity, rowid);
                            }
                        }
                        CFRelease(result);
                    }
                }
                CFReleaseNull(partial_query);
            }
            else
                status = errSecInvalidItemRef;
            
            CFReleaseNull(cert);
            CFReleaseNull(key);
            *return_status = status;
        }
    } else {
		value = CFDictionaryGetValue(attributes, kSecClass);
		if (value && CFEqual(kSecClassIdentity, value) && 
			(operation == (secitem_operation)SecItemDelete)) {
			CFMutableDictionaryRef dict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, attributes);
			CFDictionaryRemoveValue(dict, kSecClass);
			CFDictionarySetValue(dict, kSecClass, kSecClassCertificate);
			OSStatus status = SecItemDelete(dict);
			if (!status) {
				CFDictionarySetValue(dict, kSecClass, kSecClassKey);
				status = SecItemDelete(dict);
			}
			CFRelease(dict);
			*return_status = status;
            handled = true;
		}
	}
    return handled;
}

static void infer_cert_label(CFDictionaryRef attributes, CFMutableDictionaryRef args)
{
	if (!args || !attributes)
		return;
	
	if (CFDictionaryContainsKey(attributes, kSecAttrLabel))
		return;
	
	CFTypeRef value_ref = CFDictionaryGetValue(attributes, kSecValueRef);
	if (!value_ref || (CFGetTypeID(value_ref) != SecCertificateGetTypeID()))
		return;
	
	SecCertificateRef certificate = (SecCertificateRef)value_ref;
	CFStringRef label = SecCertificateCopySubjectSummary(certificate);
	if (label) {
		CFDictionarySetValue(args, kSecAttrLabel, label);
		CFReleaseNull(label);
	}
}

/* A persistent ref is just the class and the rowid of the record. */
CF_RETURNS_RETAINED CFDataRef _SecItemMakePersistentRef(CFTypeRef class, sqlite_int64 rowid)
{
    uint8_t bytes[sizeof(sqlite_int64) + 4];
    if (rowid < 0)
        return NULL;
    if (CFStringGetCString(class, (char *)bytes, 4 + 1 /*null-term*/, 
        kCFStringEncodingUTF8))
    {
        OSWriteBigInt64(bytes + 4, 0, rowid);
        return CFDataCreate(NULL, bytes, sizeof(bytes));
    }
    return NULL;
}

/* AUDIT[securityd](done):
   persistent_ref (ok) is a caller provided, non NULL CFTypeRef.
 */
bool _SecItemParsePersistentRef(CFDataRef persistent_ref, CFStringRef *return_class, sqlite_int64 *return_rowid)
{
	bool valid_ref = false;
    if (CFGetTypeID(persistent_ref) == CFDataGetTypeID() &&
        CFDataGetLength(persistent_ref) == (CFIndex)(sizeof(sqlite_int64) + 4)) {
        const uint8_t *bytes = CFDataGetBytePtr(persistent_ref);
        sqlite_int64 rowid = OSReadBigInt64(bytes + 4, 0);
		
        CFStringRef class = CFStringCreateWithBytes(kCFAllocatorDefault, 
            bytes, CFStringGetLength(kSecClassGenericPassword), 
            kCFStringEncodingUTF8, true);
        const void *valid_classes[] = { kSecClassGenericPassword,
            kSecClassInternetPassword,
            kSecClassAppleSharePassword,
            kSecClassCertificate,
            kSecClassKey,
            kSecClassIdentity };
        
        unsigned i;
        for (i=0; i< array_size(valid_classes); i++) {
            if (CFEqual(valid_classes[i], class)) {
                if (return_class)
                    *return_class = valid_classes[i];
                if (return_rowid)
                    *return_rowid = rowid;
                valid_ref = true;
                break;
            }
        }
        CFRelease(class);
    }
    return valid_ref;
}

#endif // *** END SECITEM_SHIM_OSX ***

static bool cf_bool_value(CFTypeRef cf_bool)
{
	return (cf_bool && CFEqual(kCFBooleanTrue, cf_bool));
}


static void
result_post(CFDictionaryRef query, CFTypeRef raw_result, CFTypeRef *result) {
    if (!raw_result)
        return;

    if (!result) {
        CFRelease(raw_result);
        return;
    }

	bool return_ref = cf_bool_value(CFDictionaryGetValue(query, kSecReturnRef));
	bool return_data = cf_bool_value(CFDictionaryGetValue(query, kSecReturnData));
	bool return_attributes = cf_bool_value(CFDictionaryGetValue(query, kSecReturnAttributes));

	if (return_ref) {
		if (CFGetTypeID(raw_result) == CFArrayGetTypeID()) {
			CFIndex i, count = CFArrayGetCount(raw_result);
			CFMutableArrayRef tmp_array = CFArrayCreateMutable(kCFAllocatorDefault, count, &kCFTypeArrayCallBacks);
			for (i = 0; i < count; i++) {
				CFTypeRef ref = makeRef(CFArrayGetValueAtIndex(raw_result, i), return_data, return_attributes);
				if (ref) {
					CFArrayAppendValue(tmp_array, ref);
                    CFRelease(ref);
                }
			}
			*result = tmp_array;
		} else
			*result = makeRef(raw_result, return_data, return_attributes);

		CFRelease(raw_result);
	} else
		*result = raw_result;
}

#if SECITEM_SHIM_OSX
/* TODO: Should be in some header */
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);
#endif

static bool cftype_to_bool_cftype_error_request(enum SecXPCOperation op, CFTypeRef attributes, CFTypeRef *result, CFErrorRef *error)
{
    return securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) {
        return SecXPCDictionarySetPList(message, kSecXPCKeyQuery, attributes, error);
    }, ^bool(xpc_object_t response, CFErrorRef *error) {
        if (result) {
            return SecXPCDictionaryCopyPListOptional(response, kSecXPCKeyResult, result, error);
        }
        return true;
    });
}

static bool cftype_ag_to_bool_cftype_error_request(enum SecXPCOperation op, CFTypeRef attributes, __unused CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error) {
    return cftype_to_bool_cftype_error_request(op, attributes, result, error);
}

static bool dict_to_error_request(enum SecXPCOperation op, CFDictionaryRef query, CFErrorRef *error)
{
    return securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) {
        return SecXPCDictionarySetPList(message, kSecXPCKeyQuery, query, error);
    }, NULL);
}

static bool dict_ag_to_error_request(enum SecXPCOperation op, CFDictionaryRef query, __unused CFArrayRef accessGroups, CFErrorRef *error)
{
    return dict_to_error_request(op, query, error);
}

static CFDataRef data_data_to_data_error_request(enum SecXPCOperation op, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
    __block CFDataRef result = NULL;
    securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) {
        return SecXPCDictionarySetDataOptional(message, kSecXPCKeyKeybag, keybag, error)
        && SecXPCDictionarySetDataOptional(message, kSecXPCKeyUserPassword, passcode, error);
    }, ^bool(xpc_object_t response, CFErrorRef *error) {
        return (result = SecXPCDictionaryCopyData(response, kSecXPCKeyResult, error));
    });
    return result;
}

static bool data_data_data_to_error_request(enum SecXPCOperation op, CFDataRef backup, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
    return securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) {
        return SecXPCDictionarySetData(message, kSecXPCKeyBackup, backup, error)
        && SecXPCDictionarySetData(message, kSecXPCKeyKeybag, keybag, error)
        && SecXPCDictionarySetDataOptional(message, kSecXPCKeyUserPassword, passcode, error);
    } , NULL);
}

static bool dict_data_data_to_error_request(enum SecXPCOperation op, CFDictionaryRef backup, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
    return securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) {
        return SecXPCDictionarySetPList(message, kSecXPCKeyBackup, backup, error)
        && SecXPCDictionarySetData(message, kSecXPCKeyKeybag, keybag, error)
        && SecXPCDictionarySetDataOptional(message, kSecXPCKeyUserPassword, passcode, error);
    } , NULL);
}

static CFDictionaryRef data_data_dict_to_dict_error_request(enum SecXPCOperation op, CFDictionaryRef backup, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
    __block CFDictionaryRef dict = NULL;
    securityd_send_sync_and_do(op, error, ^bool(xpc_object_t message, CFErrorRef *error) {
        return SecXPCDictionarySetPListOptional(message, kSecXPCKeyBackup, backup, error)
        && SecXPCDictionarySetData(message, kSecXPCKeyKeybag, keybag, error)
        && SecXPCDictionarySetDataOptional(message, kSecXPCKeyUserPassword, passcode, error);
    }, ^bool(xpc_object_t response, CFErrorRef *error) {
        return (dict = SecXPCDictionaryCopyDictionary(response, kSecXPCKeyResult, error));
    });
    return dict;
}

OSStatus
#if SECITEM_SHIM_OSX
SecItemAdd_ios(CFDictionaryRef attributes, CFTypeRef *result)
#else
SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result)
#endif // *** END SECITEM_SHIM_OSX ***
{
    CFMutableDictionaryRef args = NULL;
    OSStatus status;

#ifndef SECITEM_SHIM_OSX
    require_quiet(!explode_identity(attributes, (secitem_operation)SecItemAdd, &status, result), errOut);
	require_noerr_quiet(status = cook_query(attributes, &args), errOut);
	infer_cert_label(attributes, args);
#endif // *** END SECITEM_SHIM_OSX ***
	if (args)
		attributes = args;

    status = SecOSStatusWith(^bool (CFErrorRef *error) {
        CFTypeRef raw_result = NULL;
        if (!SECURITYD_XPC(sec_item_add, cftype_ag_to_bool_cftype_error_request, attributes, SecAccessGroupsGetCurrent(), &raw_result, error))
            return false;

        result_post(attributes, raw_result, result);
        return true;
    });

#ifndef SECITEM_SHIM_OSX
errOut:
#endif // *** END SECITEM_SHIM_OSX ***
    CFReleaseSafe(args);
	return status;
}


OSStatus
#if SECITEM_SHIM_OSX
SecItemCopyMatching_ios(CFDictionaryRef inQuery, CFTypeRef *result)
#else
SecItemCopyMatching(CFDictionaryRef inQuery, CFTypeRef *result)
#endif // *** END SECITEM_SHIM_OSX ***
{
    __block CFDictionaryRef query = inQuery;
    __block CFMutableDictionaryRef args = NULL;
    OSStatus status;

#ifndef SECITEM_SHIM_OSX
    require_quiet(!explode_identity(query, (secitem_operation)SecItemCopyMatching, &status, result), errOut);
    require_noerr_quiet(status = cook_query(query, &args), errOut);
#endif // *** END SECITEM_SHIM_OSX ***
	if (args)
		query = args;

    status = SecOSStatusWith(^bool (CFErrorRef *error) {
        CFTypeRef raw_result = NULL;
        if (!SECURITYD_XPC(sec_item_copy_matching, cftype_ag_to_bool_cftype_error_request, query, SecAccessGroupsGetCurrent(), &raw_result, error))
            return false;

#ifdef SECITEM_SHIM_OSX
        if ((!cf_bool_value(CFDictionaryGetValue(query, kSecReturnPersistentRef)))  && cf_bool_value(CFDictionaryGetValue(query, kSecReturnRef))) {
            CFReleaseSafe(args);
            args = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, query);
            CFDictionaryAddValue(args, kSecReturnPersistentRef, kCFBooleanTrue);
            query = args;
        }
#endif
        result_post(query, raw_result, result);
        return true;
    });

#ifndef SECITEM_SHIM_OSX
errOut:
#endif // *** END SECITEM_SHIM_OSX ***
    CFReleaseSafe(args);
	return status;
}

OSStatus
#if SECITEM_SHIM_OSX
SecItemUpdate_ios(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
#else
SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
#endif // *** END SECITEM_SHIM_OSX ***
{
    CFMutableDictionaryRef args = NULL;
    __block OSStatus status; // TODO loose block once gSecurityd functions return CFErrorRefs
#ifndef SECITEM_SHIM_OSX
    require_noerr_quiet(status = cook_query(query, &args), errOut);
#endif
    if (args)
        query = args;

    status = SecOSStatusWith(^bool (CFErrorRef *error) {
        bool ok = false;
        if (gSecurityd) {
            // Ensure the dictionary passed to securityd has proper kCFTypeDictionaryKeyCallBacks.
            CFMutableDictionaryRef tmp = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
            CFDictionaryForEach(attributesToUpdate, ^(const void *key, const void *value) { CFDictionaryAddValue(tmp, key, value); });
            ok = gSecurityd->sec_item_update(query, tmp, SecAccessGroupsGetCurrent(), error);
            CFRelease(tmp);
        } else {
            xpc_object_t message = securityd_create_message(sec_item_update_id, error);
            if (message) {
                if (SecXPCDictionarySetPList(message, kSecXPCKeyQuery, query, error) &&
                    SecXPCDictionarySetPList(message, kSecXPCKeyAttributesToUpdate, attributesToUpdate, error)) {
                    xpc_object_t reply = securityd_message_with_reply_sync(message, error);
                    if (reply) {
                        ok = securityd_message_no_error(reply, error);
                        xpc_release(reply);
                    }
                }
                xpc_release(message);
            }
        }
        return ok;
    });

#ifndef SECITEM_SHIM_OSX
errOut:
#endif
    CFReleaseSafe(args);
    return status;
}

#ifndef SECITEM_SHIM_OSX
static void copy_all_keys_and_values(const void *key, const void *value, void *context)
{
    CFDictionaryAddValue((CFMutableDictionaryRef)context, key, value);
}
#endif 

#ifndef SECITEM_SHIM_OSX
static OSStatus explode_persistent_identity_ref(CFDictionaryRef query, CFMutableDictionaryRef *delete_query)
{
    OSStatus status = errSecSuccess;
    CFTypeRef persist = CFDictionaryGetValue(query, kSecValuePersistentRef);
    CFStringRef class;
    if (persist && _SecItemParsePersistentRef(persist, &class, NULL)
        && CFEqual(class, kSecClassIdentity)) {
        const void *keys[] = { kSecReturnRef, kSecValuePersistentRef };
        const void *vals[] = { kCFBooleanTrue, persist };
        CFDictionaryRef persistent_query = CFDictionaryCreate(NULL, keys, 
            vals, (array_size(keys)), NULL, NULL);
        CFTypeRef item_query = NULL;
        status = SecItemCopyMatching(persistent_query, &item_query);
        CFReleaseNull(persistent_query);
        if (status)
            return status;
        CFMutableDictionaryRef new_query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, 
            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        if (new_query) {
            CFDictionaryApplyFunction(query, copy_all_keys_and_values, new_query);
            CFDictionaryRemoveValue(new_query, kSecValuePersistentRef);
            CFDictionarySetValue(new_query, kSecValueRef, item_query);
            *delete_query = new_query;
        } else
            status = errSecAllocate;
        CFRelease(item_query);
    }

    return status;
}
#endif

OSStatus
#if SECITEM_SHIM_OSX
SecItemDelete_ios(CFDictionaryRef query)
#else
SecItemDelete(CFDictionaryRef query)
#endif // *** END SECITEM_SHIM_OSX *** 
{
	CFMutableDictionaryRef args1 = NULL, args2 = NULL;
    OSStatus status;

#ifndef SECITEM_SHIM_OSX
    require_noerr_quiet(status = explode_persistent_identity_ref(query, &args1), errOut);
    if (args1)
        query = args1;
    require_quiet(!explode_identity(query, (secitem_operation)SecItemDelete, &status, NULL), errOut);
	require_noerr_quiet(status = cook_query(query, &args2), errOut);
#endif // *** END SECITEM_SHIM_OSX ***    
    if (args2)
        query = args2;

    status = SecOSStatusWith(^bool (CFErrorRef *error) {
        return SECURITYD_XPC(sec_item_delete, dict_ag_to_error_request, query, SecAccessGroupsGetCurrent(), error);
    });
    
#ifndef SECITEM_SHIM_OSX
errOut:
#endif // *** END SECITEM_SHIM_OSX ***
    CFReleaseSafe(args1);
    CFReleaseSafe(args2);
	return status;
}

OSStatus
SecItemDeleteAll(void)
{
    return SecOSStatusWith(^bool (CFErrorRef *error) {
        bool ok;
        if (gSecurityd) {
#ifndef SECITEM_SHIM_OSX
            SecTrustStoreRef ts = SecTrustStoreForDomain(kSecTrustStoreDomainUser);
            if (!gSecurityd->sec_truststore_remove_all(ts, error))
                ok = SecError(errSecInternal, error, CFSTR("sec_truststore_remove_all is NULL"));
#endif // *** END SECITEM_SHIM_OSX ***
            if (!gSecurityd->sec_item_delete_all(error))
                ok = SecError(errSecInternal, error, CFSTR("sec_item_delete_all is NULL"));
            ok = true;
        } else {
            ok = securityd_send_sync_and_do(sec_delete_all_id, error, NULL, NULL);
        }
        return ok;
    });
}

CFDataRef _SecKeychainCopyOTABackup(void) {
    return SECURITYD_XPC(sec_keychain_backup, data_data_to_data_error_request, NULL, NULL, NULL);
}

CFDataRef _SecKeychainCopyBackup(CFDataRef backupKeybag, CFDataRef password) {
    return SECURITYD_XPC(sec_keychain_backup, data_data_to_data_error_request, backupKeybag, password, NULL);
}

OSStatus _SecKeychainRestoreBackup(CFDataRef backup, CFDataRef backupKeybag,
    CFDataRef password) {
    return SecOSStatusWith(^bool (CFErrorRef *error) {
        return SECURITYD_XPC(sec_keychain_restore, data_data_data_to_error_request, backup, backupKeybag, password, error);
    });
}

bool _SecKeychainSyncUpdate(CFDictionaryRef updates, CFErrorRef *error) {
    return SECURITYD_XPC(sec_keychain_sync_update, dict_to_error_request, updates, error);
}

OSStatus _SecKeychainBackupSyncable(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFDictionaryRef *backup_out)
{
    return SecOSStatusWith(^bool (CFErrorRef *error) {
        *backup_out = SECURITYD_XPC(sec_keychain_backup_syncable, data_data_dict_to_dict_error_request, backup_in, keybag, password, error);
        return *backup_out != NULL;
    });
}

OSStatus _SecKeychainRestoreSyncable(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in)
{
    return SecOSStatusWith(^bool (CFErrorRef *error) {
        return SECURITYD_XPC(sec_keychain_restore_syncable, dict_data_data_to_error_request, backup_in, keybag, password, error);
    });
}


#ifndef SECITEM_SHIM_OSX
OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement);

OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement)
{
    return -1; /* this is only on OS X currently */
}

#else

extern OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement);

#endif