CKClient.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@
 */

//
//  ckdxpcclient.c
//  ckd-xpc
//

/*
    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 <utilities/debugging.h>
#include <utilities/SecCFWrappers.h>

#include "CKConstants.h"

#define __CKDXPC_CLIENT_PRIVATE_INDIRECT__ 1
#include "CKClient.h"


#define pdebug(format...) secerror(format)

#define verboseCKDDebugging 1

#ifndef NDEBUG
    #define xpdebug(format...) \
        do {  \
            if (verboseCKDDebugging) \
                printf(format);  \
        } while (0)
#else
    //empty
    #define xpdebug(format...)
#endif


static xpc_connection_t serviceConnection = NULL;
static dispatch_queue_t xpc_queue = NULL;
static CloudKeychainReplyBlock itemsChangedBlock;

static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event);
static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error);

// extern CFTypeRef _CFXPCCreateCFObjectFromXPCObject(xpc_object_t xo);
// extern xpc_object_t _CFXPCCreateXPCObjectFromCFObject(CFTypeRef cf);

// Debug
static void describeXPCObject(char *prefix, xpc_object_t object);
void describeXPCType(char *prefix, xpc_type_t xtype);

static CFStringRef sErrorDomain = CFSTR("com.apple.security.cloudkeychain");

enum {
    kSOSObjectMallocFailed = 1,
    kAddDuplicateEntry,
    kSOSObjectNotFoundError = 1,
    kSOSObjectCantBeConvertedToXPCObject,
    kSOSOUnexpectedConnectionEvent,
    kSOSOUnexpectedXPCEvent,
    kSOSConnectionNotOpen
};

#define WANTXPCREPLY 0

#pragma mark ----- utilities -----

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

#pragma mark ----- SPI -----

void initXPCConnection()
{
    pdebug("initXPCConnection\n");
    
    xpc_queue = dispatch_queue_create(xpcServiceName, DISPATCH_QUEUE_SERIAL);

    serviceConnection = xpc_connection_create_mach_service(xpcServiceName, xpc_queue, 0);

//    serviceConnection = xpc_connection_create(xpcServiceName, xpc_queue);
    pdebug("serviceConnection: %p\n", serviceConnection);

    xpc_connection_set_event_handler(serviceConnection, ^(xpc_object_t event)
    {
        pdebug("xpc_connection_set_event_handler\n");
        handle_xpc_event(serviceConnection, event);
    });

    xpc_connection_resume(serviceConnection);
    xpc_retain(serviceConnection);
}

void closeXPCConnection()
{
    pdebug("closeXPCConnection\n");
    xpc_release(serviceConnection);
}

void setItemsChangedBlock(CloudKeychainReplyBlock icb)
{
    if (icb != itemsChangedBlock)
    {
        if (itemsChangedBlock)
            Block_release(itemsChangedBlock);
        itemsChangedBlock = icb;
        Block_copy(itemsChangedBlock);
    }
}

// typedef void (^CloudKeychainReplyBlock)(CFDictionaryRef returnedValues, CFErrorRef error);

static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event)
{
    CFErrorRef localError = NULL;
    pdebug(">>>>> handle_connection_event via event_handler <<<<<\n");
    bool result = false;
    if ((result = xpc_event_filter(peer, event, &localError)))
    {
        const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
        if (!operation || strcmp(operation, kMessageOperationItemChanged))  // some op we don't care about
        {
            pdebug("operation: %s", operation);
            return result;
        }
        
        xpc_object_t xrv = xpc_dictionary_get_value(event, kMessageKeyValue);
        if (!xrv)
        {
            pdebug("xrv null for kMessageKeyValue");
            return result;
        }
        describeXPCObject("xrv", xrv);
        
        CFDictionaryRef returnedValues = _CFXPCCreateCFObjectFromXPCObject(xrv);
        pdebug("returnedValues: %@", returnedValues);

        if (itemsChangedBlock)
            itemsChangedBlock(returnedValues, localError);
    }
    CFReleaseSafe(localError);

    return result;
}

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)
    pdebug("handle_connection_event\n");
    xpc_type_t xtype = xpc_get_type(event);
    describeXPCType("handle_xpc_event", xtype);
    if (XPC_TYPE_CONNECTION == xtype)
    {
        pdebug("handle_xpc_event: XPC_TYPE_CONNECTION (unexpected)");
        // The client of an XPC service does not get connection events
        // For nwo, 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)
    {
        pdebug("default: xpc error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
        if (error)
            *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX
    }
    else
    if (XPC_TYPE_DICTIONARY == xtype)
    {
        pdebug("received dictionary event %p\n", event);
        return true;
    }
    else
    {
        pdebug("default: unexpected connection event %p\n", event);
        describeXPCObject("handle_xpc_event: obj : ", event);
        if (error)
            *error = makeError(kSOSOUnexpectedXPCEvent);
    }
    return false;
}

static void talkWithKVS(xpc_object_t message, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    secerror("start");
    __block CFErrorRef error = NULL;
    __block CFTypeRef object = NULL;
    
    dispatch_block_t callback = ^{
            secerror("callback");
            if (replyBlock)
                replyBlock(object, error);
    //        if (object)
     //           CFRelease(object);
            if (error)
            {
                secerror("callback error: %@", error);
     //           CFRelease(error);
            }
            dispatch_release(processQueue);
        };
    
    require_action(serviceConnection, xit, error = makeError(kSOSConnectionNotOpen));
    require_action(message, xit, error = makeError(kSOSObjectNotFoundError));
    dispatch_retain(processQueue);
    secerror("xpc_connection_send_message_with_reply called");
    
    Block_copy(callback);
    
//#if !WANTXPCREPLY
 //   xpc_connection_send_message(serviceConnection, message);            // Send message; don't want a reply
//#else
    xpc_connection_send_message_with_reply(serviceConnection, message, xpc_queue, ^(xpc_object_t reply)
        {
            secerror("xpc_connection_send_message_with_reply handler called back");
            if (xpc_event_filter(serviceConnection, reply, &error) && reply)
            {
                describeXPCObject("getValuesFromKVS: reply : ", reply);
                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
                    secerror("converted CF object: %@", object);
                }
                else
                    secerror("missing value reply");
            }
            dispatch_async(processQueue, callback);
        });
//#endif

//sleep(5);   // DEBUG DEBUG FIX
 //   xpc_release(message);
    return;
    
xit:
    secerror("talkWithKVS error: %@", error);
    if (replyBlock)
        dispatch_async(processQueue, callback);
}

void putValuesWithXPC(CFDictionaryRef values, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    CFErrorRef error = NULL;

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

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

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

void synchronizeKVS(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    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(message, processQueue, replyBlock);
}

void clearAll(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    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(message, processQueue, replyBlock);
}

/*
extern xpc_object_t xpc_create_reply_with_format(xpc_object_t original, const char * format, ...);
            xpc_object_t reply = xpc_create_reply_with_format(event, 
                "{keychain-paths: %value, all-paths: %value, extensions: %value, keychain-home: %value}",
                keychain_paths, all_paths, sandbox_extensions, home);
*/

void getValuesFromKVS(CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    secerror("start");
    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));

    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(message, processQueue, replyBlock);

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

void registerKeysForKVS(CFArrayRef keysToGet, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
    secerror("start");

    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);
    
    if (clientIdentifier)
    {
        char *clientid = CFStringToCString(clientIdentifier);
        if (clientid)
        {
            xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid);
            free(clientid);
        }
    }

    setItemsChangedBlock(replyBlock);
    talkWithKVS(message, processQueue, replyBlock);

    xpc_release(message);
}

void unregisterKeysForKVS(CFArrayRef keysToUnregister, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
{
#if NO_SERVERz
    if (gCKD->unregisterKeys) {
        return gCKD->unregisterKeys(...);
    }
#endif

    secerror("start");

    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);
    
    if (clientIdentifier)
    {
        char *clientid = CFStringToCString(clientIdentifier);
        if (clientid)
        {
            xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid);
            free(clientid);
        }
    }

    talkWithKVS(message, processQueue, replyBlock);

    xpc_release(message);
}

#pragma mark ----- CF-XPC Utilities -----


#pragma mark ----- DEBUG Utilities -----

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

void clearStore()
{
    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationClearStore);
    xpc_connection_send_message(serviceConnection, message);  // Send message; don't wait for a reply
    xpc_release(message);
}

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

void describeXPCType(char *prefix, xpc_type_t xtype)
{
    /*
        Add these as necessary:
        XPC_TYPE_ENDPOINT
        XPC_TYPE_NULL
        XPC_TYPE_BOOL
        XPC_TYPE_INT64
        XPC_TYPE_UINT64
        XPC_TYPE_DOUBLE
        XPC_TYPE_DATE
        XPC_TYPE_DATA
        XPC_TYPE_STRING
        XPC_TYPE_UUID
        XPC_TYPE_FD
        XPC_TYPE_SHMEM
        XPC_TYPE_ARRAY
    */
    
#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>");

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