SOSCloudKeychainClient.c   [plain text]


/*
 * Copyright (c) 2012 Apple Computer, 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@
 */

/*
    SOSCloudTransport.c -  Implementation of the transport layer from CKBridge to SOSAccount/SOSCircle
    These are the exported functions from CloudKeychainProxy
*/

/*
    This XPC service is essentially just a proxy to iCloud KVS, which exists since
    the main security code cannot link against Foundation.
    
    See sendTSARequestWithXPC in tsaSupport.c for how to call the service
    
    The client of an XPC service does not get connection events, nor does it
    need to deal with transactions.
*/

#include <AssertMacros.h>

#include <xpc/xpc.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFXPCBridge.h>
#include <sysexits.h>
#include <syslog.h>
#include <CoreFoundation/CFUserNotification.h>

#include <utilities/debugging.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/SecXPCError.h>

#include "SOSCloudKeychainConstants.h"
#include "SOSCloudKeychainClient.h"

static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.transport.error");

#define SOSCKCSCOPE "sync"

// MARK: ---------- SOSCloudTransport ----------

/* SOSCloudTransport, a statically initialized transport singleton. */
static SOSCloudTransportRef sTransport = NULL;

static SOSCloudTransportRef SOSCloudTransportCreateXPCTransport(void);

void SOSCloudKeychainSetTransport(SOSCloudTransportRef transport) {
    sTransport = transport;
}

/* Return the singleton cloud transport instance. */
static SOSCloudTransportRef SOSCloudTransportDefaultTransport(void)
{
    static dispatch_once_t sTransportOnce;
    dispatch_once(&sTransportOnce, ^{
        if (!sTransport)
            SOSCloudKeychainSetTransport(SOSCloudTransportCreateXPCTransport());
    });
    return sTransport;
}


// MARK: ----- utilities -----

static CFErrorRef makeError(CFIndex which)
{
    CFDictionaryRef userInfo = NULL;
    return CFErrorCreate(kCFAllocatorDefault, sErrorDomain, which, userInfo);
}

// MARK: ----- DEBUG Utilities -----

//------------------------------------------------------------------------------------------------
//          DEBUG only
//------------------------------------------------------------------------------------------------

static void describeXPCObject(char *prefix, xpc_object_t object)
{
//#ifndef NDEBUG
    // This is useful for debugging.
    if (object)
    {
        char *desc = xpc_copy_description(object);
        secdebug(SOSCKCSCOPE, "%s%s\n", prefix, desc);
        free(desc);
    }
    else
        secdebug(SOSCKCSCOPE, "%s<NULL>\n", prefix);
//#endif
}

static void describeXPCType(char *prefix, xpc_type_t xtype)
{
    // Add others as necessary, e.g. XPC_TYPE_DOUBLE
#ifndef NDEBUG
    // This is useful for debugging.
    char msg[256]={0,};
    if (XPC_TYPE_CONNECTION == xtype)
        strcpy(msg, "XPC_TYPE_CONNECTION");
    else if (XPC_TYPE_ERROR == xtype)
        strcpy(msg, "XPC_TYPE_ERROR");
    else if  (XPC_TYPE_DICTIONARY == xtype)
        strcpy(msg, "XPC_TYPE_DICTIONARY");
    else
        strcpy(msg, "<unknown>");

    secdebug(SOSCKCSCOPE, "%s type:%s\n", prefix, msg);
#endif
}

// MARK: ---------- SOSXPCCloudTransport ----------

typedef struct SOSXPCCloudTransport *SOSXPCCloudTransportRef;
struct SOSXPCCloudTransport
{
    struct SOSCloudTransport transport;
    xpc_connection_t serviceConnection;
    dispatch_queue_t xpc_queue;
};

static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error)
{
    // return true if the type is XPC_TYPE_DICTIONARY (and therefore something more to process)
    secdebug(SOSCKCSCOPE, "handle_connection_event\n");
    xpc_type_t xtype = xpc_get_type(event);
    describeXPCType("handle_xpc_event", xtype);
    if (XPC_TYPE_CONNECTION == xtype)
    {
        secdebug(SOSCKCSCOPE, "handle_xpc_event: XPC_TYPE_CONNECTION (unexpected)");
        // The client of an XPC service does not get connection events
        // For now, we log this and keep going
        describeXPCObject("handle_xpc_event: XPC_TYPE_CONNECTION, obj : ", event);
#if 0
        if (error)
            *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX
        assert(true);
#endif
    }
    else
    if (XPC_TYPE_ERROR == xtype)
    {
#ifndef NDEBUG
        const char *estr = xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION);
#endif
        secdebug(SOSCKCSCOPE, "default: xpc error: %s\n", estr);
#if 0   // just log for now
        CFStringRef errStr = CFStringCreateWithCString(kCFAllocatorDefault, estr, kCFStringEncodingUTF8);
        CFMutableDictionaryRef userInfo = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        if (errStr)
            CFDictionaryAddValue(userInfo, kCFErrorLocalizedDescriptionKey, errStr);
        if (error)
            *error = CFErrorCreate(kCFAllocatorDefault, sErrorDomain, kSOSOXPCErrorEvent, userInfo);
        CFReleaseSafe(errStr);
        CFReleaseSafe(userInfo);
#endif
    }
    else
    if (XPC_TYPE_DICTIONARY == xtype)
    {
        secdebug(SOSCKCSCOPE, "received dictionary event %p\n", event);
        return true;
    }
    else
    {
        secdebug(SOSCKCSCOPE, "default: unexpected connection event %p\n", event);
        describeXPCObject("handle_xpc_event: obj : ", event);
        if (error)
            *error = makeError(kSOSOUnexpectedXPCEvent);
    }
    return false;
}

static bool handle_xpc_event(SOSXPCCloudTransportRef transport, xpc_object_t event)
{
    CFErrorRef localError = NULL;
    // See <rdar://problem/14566253>
    secerror(">>>>> handle_connection_event via event_handler <<<<<, WTF?");
    bool result = false;
    if ((result = xpc_event_filter(transport->serviceConnection, event, &localError)))
    {
        const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
        if (!operation || strcmp(operation, kMessageOperationItemChanged))  // some op we don't care about
        {
            secdebug(SOSCKCSCOPE, "operation: %s", operation);
            return result;
        }

        xpc_object_t xrv = xpc_dictionary_get_value(event, kMessageKeyValue);
        if (!xrv)
        {
            secdebug(SOSCKCSCOPE, "xrv null for kMessageKeyValue");
            return result;
        }
        describeXPCObject("xrv", xrv);

        CFDictionaryRef returnedValues = _CFXPCCreateCFObjectFromXPCObject(xrv);
        secdebug(SOSCKCSCOPE, "returnedValues: %@", returnedValues);

        SOSCloudKeychainHandleUpdate(returnedValues);

        CFReleaseNull(returnedValues);
    }
    CFReleaseSafe(localError);

    return result;
}

static void SOSXPCCloudTransportInit(SOSXPCCloudTransportRef transport)
{
    secdebug(SOSCKCSCOPE, "initXPCConnection\n");

    transport->xpc_queue = dispatch_queue_create(xpcServiceName, DISPATCH_QUEUE_SERIAL);

    transport->serviceConnection = xpc_connection_create_mach_service(xpcServiceName, transport->xpc_queue, 0);

    secdebug(SOSCKCSCOPE, "serviceConnection: %p\n", transport->serviceConnection);

    xpc_connection_set_event_handler(transport->serviceConnection, ^(xpc_object_t event)
    {
        secdebug(SOSCKCSCOPE, "xpc_connection_set_event_handler\n");
        handle_xpc_event(transport, event);
    });

    xpc_connection_resume(transport->serviceConnection);
    xpc_retain(transport->serviceConnection);
}

static void talkWithKVS(SOSXPCCloudTransportRef transport, xpc_object_t message, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    __block CFErrorRef error = NULL;
    __block CFTypeRef object = NULL;
    
    dispatch_block_t callback = ^{
            if (replyBlock)
                replyBlock(object, error);
            if(object)
                CFReleaseNull(object);
            if (error)
            {
                secerror("callback error: %@", error);
                CFReleaseNull(error);
            }
            dispatch_release(processQueue);
        };
    
    require_action(transport->serviceConnection, xit, error = makeError(kSOSConnectionNotOpen));
    require_action(message, xit, error = makeError(kSOSObjectNotFoundError));
    dispatch_retain(processQueue);
        
    xpc_connection_send_message_with_reply(transport->serviceConnection, message, transport->xpc_queue, ^(xpc_object_t reply)
        {
            if (xpc_event_filter(transport->serviceConnection, reply, &error) && reply)
            {
                describeXPCObject("getValuesFromKVS: reply : ", reply);
                if (error)
                    secerror("Error from xpc_event_filter: %@", error);
                xpc_object_t xrv = xpc_dictionary_get_value(reply, kMessageKeyValue);
                if (xrv)
                {
                    describeXPCObject("talkWithKVS: xrv: ", xrv);
                    /*
                        * The given XPC object must be one that was previously returned by
                        * _CFXPCCreateXPCMessageWithCFObject().
                    */
                    object = _CFXPCCreateCFObjectFromXPCObject(xrv);   // CF object is retained; release in callback
                    secnotice("talkwithkvs", "converted CF object: %@", object);
                }
                else
                    secerror("missing value reply");

                xpc_object_t xerror = xpc_dictionary_get_value(reply, kMessageKeyError);
                if (xerror)
                    error = SecCreateCFErrorWithXPCObject(xerror);  // use SecCFCreateErrorWithFormat?
            }
            dispatch_async(processQueue, callback);
        });
    return;
    
xit:
    secerror("talkWithKVS error: %@", error);
    if (replyBlock)
        dispatch_async(processQueue, callback);
    CFReleaseSafe(error);
}

// MARK: ---------- SOSXPCCloudTransport Client Calls ----------

/* Concrete function backend implementations. */
static void SOSCloudTransportSetItemsChangedBlock(SOSCloudTransportRef transport,
                                                  CloudItemsChangedBlock itemsChangedBlock) {
    if (transport->itemsChangedBlock != itemsChangedBlock)
    {
        if (transport->itemsChangedBlock)
            Block_release(transport->itemsChangedBlock);
        transport->itemsChangedBlock = Block_copy(itemsChangedBlock);
    }
}

/* Virtual function backend implementations. */
static void SOSCloudTransportPut(SOSCloudTransportRef transport, CFDictionaryRef values, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "%@", values);
    CFErrorRef error = NULL;
    xpc_object_t message = NULL;
    xpc_object_t xobject = NULL;
    require_action(values, xit, error = makeError(kSOSObjectNotFoundError));

    message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationPUTDictionary);
    
    xobject = _CFXPCCreateXPCObjectFromCFObject(values);
    require_action(xobject, xit, error = makeError(kSOSObjectCantBeConvertedToXPCObject));
    xpc_dictionary_set_value(message, kMessageKeyValue, xobject);
    xpc_release(xobject);
    
    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(message);
    return;

xit:
    if (replyBlock)
        replyBlock(NULL, error);
    CFReleaseSafe(error);
}

/* Get from KVS */
static void SOSCloudTransportGet(SOSCloudTransportRef transport, CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "%@", keysToGet);
    CFErrorRef error = NULL;
    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
    xpc_object_t xkeysToGet = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create();

    require_action(xkeysToGet, xit, error = makeError(kSOSObjectNotFoundError));

    if (keysToGet)  // don't add if nulll; will call getall
        xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToGet);

    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationGETv2);
    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
    
    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(xkeysToGet);
    xpc_release(xkeysOfInterest);
    xpc_release(message);
    return;
    
xit:
    if(xkeysOfInterest)
        xpc_release(xkeysOfInterest);
    if(xkeysToGet)
        xpc_release(xkeysToGet);
    if (replyBlock)
        replyBlock(NULL, error);
    CFReleaseSafe(error);
}

static void SOSCloudTransportRegisterKeys(SOSCloudTransportRef transport, CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock, CloudItemsChangedBlock notificationBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "%@", keysToGet);
    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
    xpc_object_t xkeysToRegister = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create();
    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToRegister);

    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationRegisterKeysAndGet);
    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);

    SOSCloudTransportSetItemsChangedBlock(transport, notificationBlock);
    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(xkeysOfInterest);
    xpc_release(xkeysToRegister);
    xpc_release(message);
}

//
// Handles NULL by seting xpc_null.
static void SecXPCDictionarySetCFObject(xpc_object_t xdict, const char *key, CFTypeRef object)
{
    xpc_object_t xpc_obj = object ? _CFXPCCreateXPCObjectFromCFObject(object) : xpc_null_create();
    xpc_dictionary_set_value(xdict, key, xpc_obj);
    xpc_release(xpc_obj);
}

static bool SOSCloudTransportUpdateKeys(SOSCloudTransportRef transport,
                                        bool getNewKeysOnly,
                                        CFArrayRef alwaysKeys,
                                        CFArrayRef afterFirstUnlockKeys,
                                        CFArrayRef unlockedKeys,
                                        CFErrorRef *error)
{
    __block bool success = true;
    dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    CloudKeychainReplyBlock replyBlock = ^(CFDictionaryRef returnedValues, CFErrorRef returnedError)
    {
        if (returnedError) {
            success = false;
            if (error) {
                *error = returnedError;
                CFRetain(*error);
            }
        }
        CFReleaseSafe(returnedError);
    };

    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;

    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_bool(xkeysOfInterest, kMessageKeyGetNewKeysOnly, getNewKeysOnly);
    SecXPCDictionarySetCFObject(xkeysOfInterest, kMessageKeyKeysToGet, alwaysKeys);
    SecXPCDictionarySetCFObject(xkeysOfInterest, kMessageKeyKeysRequireFirstUnlock, afterFirstUnlockKeys);
    SecXPCDictionarySetCFObject(xkeysOfInterest, kMessageKeyKeysRequiresUnlocked, unlockedKeys);

    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationRegisterKeysAndGet);
    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
    
    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(message);
    xpc_release(xkeysOfInterest);
    
    return success;
}

static void SOSCloudTransportUnregisterKeys(SOSCloudTransportRef transport, CFArrayRef keysToUnregister, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "%@", keysToUnregister);
    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
    xpc_object_t xkeysToUnregister = keysToUnregister ? _CFXPCCreateXPCObjectFromCFObject(keysToUnregister) : xpc_null_create();
    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToUnregister);

    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationUnregisterKeys);
    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);

    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(xkeysOfInterest);
    xpc_release(xkeysToUnregister);
    xpc_release(message);
}

static void SOSCloudTransportGetAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    secdebug(SOSCKCSCOPE, "start");
    SOSCloudTransportGet(transport, NULL, processQueue, replyBlock);
}

static void SOSCloudTransportSync(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "start");
    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationSynchronize);
    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(message);
}

static void SOSCloudTransportSyncAndWait(SOSCloudTransportRef transport, CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "%@", keysToGet);
    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);

    xpc_object_t xkeysToRegister = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create();
    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToRegister);
    xpc_release(xkeysToRegister);
    xkeysToRegister = NULL;

    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationSynchronizeAndWait);
    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
    xpc_release(xkeysOfInterest);
    xkeysOfInterest = NULL;

    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(message);
}

static void SOSCloudTransportClearAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "start");
    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationClearStore);
    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(message);
}

static void SOSCloudTransportRemoveObjectForKey(SOSCloudTransportRef transport, CFStringRef keyToRemove, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "start");
    CFErrorRef error = NULL;
    xpc_object_t message = NULL;
    xpc_object_t xkeytoremove = NULL;
    
    require_action(keyToRemove, xit, error = makeError(kSOSObjectNotFoundError));

    message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationRemoveObjectForKey);

    xkeytoremove = _CFXPCCreateXPCObjectFromCFObject(keyToRemove);
    require_action(xkeytoremove, xit, error = makeError(kSOSObjectCantBeConvertedToXPCObject));
    xpc_dictionary_set_value(message, kMessageKeyKey, xkeytoremove);
    xpc_release(xkeytoremove);

    talkWithKVS(xpcTransport, message, processQueue, replyBlock);
    xpc_release(message);
    return;

xit:
    if(xkeytoremove)
        xpc_release(xkeytoremove);
    if(message)
        xpc_release(message);
    if (replyBlock)
        replyBlock(NULL, error);
    CFReleaseSafe(error);
}

static void SOSCloudTransportLocalNotification(SOSCloudTransportRef transport, CFStringRef messageToUser, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    secdebug(SOSCKCSCOPE, "start");
    xpc_object_t xLocalNotificationDict = xpc_dictionary_create(NULL, NULL, 0);
    char *headerKey = CFStringToCString(kCFUserNotificationAlertHeaderKey);
    char *message = CFStringToCString(messageToUser);
    xpc_dictionary_set_string(xLocalNotificationDict, headerKey, message);

    xpc_object_t xpcmessage = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(xpcmessage, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(xpcmessage, kMessageKeyOperation, kOperationUILocalNotification);
    xpc_dictionary_set_value (xpcmessage, kMessageKeyValue, xLocalNotificationDict);
    xpc_release(xLocalNotificationDict);

    talkWithKVS(xpcTransport, xpcmessage, processQueue, replyBlock);

    free(headerKey);
    free(message);
    xpc_release(xpcmessage);
}

static void SOSCloudTransportSetParams(SOSCloudTransportRef transport, CFDictionaryRef paramsDict, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    secdebug(SOSCKCSCOPE, "start");
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    
    xpc_object_t xParamsDict = paramsDict ? _CFXPCCreateXPCObjectFromCFObject(paramsDict) : xpc_null_create();
    
    xpc_object_t xpcmessage = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(xpcmessage, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(xpcmessage, kMessageKeyOperation, kOperationSetParams);
    xpc_dictionary_set_value (xpcmessage, kMessageKeyValue, xParamsDict);
    xpc_release(xParamsDict);
    
    talkWithKVS(xpcTransport, xpcmessage, processQueue, replyBlock);
    
    xpc_release(xpcmessage);
}

static void SOSCloudTransportRequestSyncWithAllPeers(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    secdebug(SOSCKCSCOPE, "start");
    SOSXPCCloudTransportRef xpcTransport = (SOSXPCCloudTransportRef)transport;
    
    xpc_object_t xpcmessage = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(xpcmessage, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(xpcmessage, kMessageKeyOperation, kOperationRequestSyncWithAllPeers);
    
    talkWithKVS(xpcTransport, xpcmessage, processQueue, replyBlock);
    
    xpc_release(xpcmessage);
}

static SOSCloudTransportRef SOSCloudTransportCreateXPCTransport(void)
{
    SOSXPCCloudTransportRef st;
    st = calloc(1, sizeof(*st));
    st->transport.put = SOSCloudTransportPut;
    st->transport.registerKeys = SOSCloudTransportRegisterKeys;
    st->transport.updateKeys = SOSCloudTransportUpdateKeys;
    st->transport.unregisterKeys = SOSCloudTransportUnregisterKeys;
    st->transport.get = SOSCloudTransportGet;
    st->transport.getAll = SOSCloudTransportGetAll;
    st->transport.synchronize = SOSCloudTransportSync;
    st->transport.synchronizeAndWait = SOSCloudTransportSyncAndWait;
    st->transport.clearAll = SOSCloudTransportClearAll;
    st->transport.removeObjectForKey = SOSCloudTransportRemoveObjectForKey;
    st->transport.localNotification = SOSCloudTransportLocalNotification;
    st->transport.setParams = SOSCloudTransportSetParams;
    st->transport.requestSyncWithAllPeers = SOSCloudTransportRequestSyncWithAllPeers;
    SOSXPCCloudTransportInit(st);
    return &st->transport;
}

// MARK: ---------- SOSCloudKeychain concrete client APIs ----------
void SOSCloudKeychainSetItemsChangedBlock(CloudItemsChangedBlock itemsChangedBlock)
{
    secdebug(SOSCKCSCOPE, "start");
    SOSCloudTransportSetItemsChangedBlock(SOSCloudTransportDefaultTransport(),
                                          itemsChangedBlock);
}

// MARK: ---------- SOSCloudKeychain virtual client APIs ----------

void SOSCloudKeychainPutObjectsInCloud(CFDictionaryRef objects, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->put(cTransportRef, objects, processQueue, replyBlock);
}

void SOSCloudKeychainRegisterKeysAndGet(CFArrayRef keysToRegister, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock, CloudItemsChangedBlock notificationBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->registerKeys(cTransportRef, keysToRegister, processQueue, replyBlock, notificationBlock);
}

bool SOSCloudKeychainUpdateKeys(bool getNewKeysOnly,
                                CFArrayRef alwaysKeys,
                                CFArrayRef afterFirstUnlockKeys,
                                CFArrayRef unlockedKeys,
                                CFErrorRef *error)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        return cTransportRef->updateKeys(cTransportRef, getNewKeysOnly, alwaysKeys, afterFirstUnlockKeys, unlockedKeys, error);
    
    return false;
}

void SOSCloudKeychainUnRegisterKeys(CFArrayRef keysToUnregister, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->unregisterKeys(cTransportRef, keysToUnregister, processQueue, replyBlock);
}

void SOSCloudKeychainHandleUpdate(CFDictionaryRef updates)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef->itemsChangedBlock)
        ((CloudItemsChangedBlock)cTransportRef->itemsChangedBlock)(updates);
}

void SOSCloudKeychainGetObjectsFromCloud(CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->get(cTransportRef, keysToGet, processQueue, replyBlock);
}

void SOSCloudKeychainGetAllObjectsFromCloud(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->getAll(cTransportRef, processQueue, replyBlock);
}

void SOSCloudKeychainSynchronizeAndWait(CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->synchronizeAndWait(cTransportRef, keysToGet, processQueue, replyBlock);
}

//DEBUG ONLY
void SOSCloudKeychainSynchronize(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->synchronize(cTransportRef, processQueue, replyBlock);
}

//DEBUG ONLY
void SOSCloudKeychainClearAll(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->clearAll(cTransportRef, processQueue, replyBlock);
}

void SOSCloudKeychainRemoveObjectForKey(CFStringRef keyToRemove, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->removeObjectForKey(cTransportRef, keyToRemove, processQueue, replyBlock);
}

void SOSCloudKeychainUserNotification(CFStringRef messageToUser, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->localNotification(cTransportRef, messageToUser, processQueue, replyBlock);
}

void SOSCloudKeychainSetParams(CFDictionaryRef paramsDict, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->setParams(cTransportRef, paramsDict, processQueue, replyBlock);
}

void SOSCloudKeychainRequestSyncWithAllPeers(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    SOSCloudTransportRef cTransportRef = SOSCloudTransportDefaultTransport();
    if (cTransportRef)
        cTransportRef->requestSyncWithAllPeers(cTransportRef, processQueue, replyBlock);
}

void SOSCloudKeychainSetCallbackMethodXPC(void)
{
    // Call this before making any other calls to CloudKeychainProxy
    CFDictionaryRef paramsDict = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
        kParamCallbackMethod, kParamCallbackMethodXPC, NULL);
    dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    SOSCloudKeychainSetParams(paramsDict, processQueue, ^(CFDictionaryRef returnedValues, CFErrorRef error)
        {
            secerror("set params called back");
        });
    CFReleaseSafe(paramsDict);
}