1/* 2 * Copyright (c) 2012 Apple Computer, Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 25/* 26 * SOSCloudTransport.c - Implementation of the transport layer from CKBridge to SOSAccount/SOSCircle 27 */ 28/* 29 This XPC service is essentially just a proxy to iCloud KVS, which exists since 30 the main security code cannot link against Foundation. 31 32 See sendTSARequestWithXPC in tsaSupport.c for how to call the service 33 34 The client of an XPC service does not get connection events, nor does it 35 need to deal with transactions. 36*/ 37 38#include <SOSCloudTransport.h> 39#include <utilities/SecCFError.h> 40#include <dispatch/dispatch.h> 41#include <stdlib.h> 42 43static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.transport.error"); 44 45enum 46{ 47 kSOSObjectMallocFailed = 1, 48 kAddDuplicateEntry, 49 kSOSObjectNotFoundError = 1, 50 kSOSObjectCantBeConvertedToXPCObject, 51 kSOSOUnexpectedConnectionEvent, 52 kSOSOUnexpectedXPCEvent, 53 kSOSConnectionNotOpen 54}; 55 56/* SOSCloudTransport, a statically initialized transport singleton. */ 57struct SOSCloudTransport 58{ 59 struct CloudTransport t; 60 unsigned version; 61 CFStringRef kvsID; 62 dispatch_once_t once; 63}; 64 65#pragma mark ---------- Communication with XPC ---------- 66 67 68//------------------------------------------------------------------------------------------------ 69 70#include <AssertMacros.h> 71 72#include <xpc/xpc.h> 73#include <CoreFoundation/CoreFoundation.h> 74#include <CoreFoundation/CFXPCBridge.h> 75#include <sysexits.h> 76#include <syslog.h> 77 78#include <utilities/debugging.h> 79#include <utilities/SecCFWrappers.h> 80 81#include "CKConstants.h" 82 83#define zsecdebug(format...) secerror(format) 84 85static xpc_connection_t serviceConnection = NULL; 86static dispatch_queue_t xpc_queue = NULL; 87static CloudKeychainReplyBlock itemsChangedBlock; 88 89static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event); 90static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error); 91 92// extern CFTypeRef _CFXPCCreateCFObjectFromXPCObject(xpc_object_t xo); 93// extern xpc_object_t _CFXPCCreateXPCObjectFromCFObject(CFTypeRef cf); 94 95// Debug 96static void describeXPCObject(char *prefix, xpc_object_t object); 97static void describeXPCType(char *prefix, xpc_type_t xtype); 98 99 100 101#define WANTXPCREPLY 0 102 103#pragma mark ----- utilities ----- 104 105static CFErrorRef makeError(CFIndex which) 106{ 107 CFDictionaryRef userInfo = NULL; 108 return CFErrorCreate(kCFAllocatorDefault, sErrorDomain, which, userInfo); 109} 110 111#pragma mark ----- SPI ----- 112 113static void initXPCConnection() 114{ 115 zsecdebug("initXPCConnection\n"); 116 117 xpc_queue = dispatch_queue_create(xpcServiceName, DISPATCH_QUEUE_SERIAL); 118 119 serviceConnection = xpc_connection_create_mach_service(xpcServiceName, xpc_queue, 0); 120 121// serviceConnection = xpc_connection_create(xpcServiceName, xpc_queue); 122 zsecdebug("serviceConnection: %p\n", serviceConnection); 123 124 xpc_connection_set_event_handler(serviceConnection, ^(xpc_object_t event) 125 { 126 zsecdebug("xpc_connection_set_event_handler\n"); 127 handle_xpc_event(serviceConnection, event); 128 }); 129 130 xpc_connection_resume(serviceConnection); 131 xpc_retain(serviceConnection); 132} 133 134#if 0 // unused 135static void closeXPCConnection() 136{ 137 zsecdebug("closeXPCConnection\n"); 138 xpc_release(serviceConnection); 139} 140#endif 141 142static void setItemsChangedBlock(CloudKeychainReplyBlock icb) 143{ 144 if (icb != itemsChangedBlock) 145 { 146 if (itemsChangedBlock) 147 Block_release(itemsChangedBlock); 148 itemsChangedBlock = icb; 149 Block_copy(itemsChangedBlock); 150 } 151} 152 153// typedef void (^CloudKeychainReplyBlock)(CFDictionaryRef returnedValues, CFErrorRef error); 154 155static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event) 156{ 157 CFErrorRef localError = NULL; 158 zsecdebug(">>>>> handle_connection_event via event_handler <<<<<\n"); 159 bool result = false; 160 if ((result = xpc_event_filter(peer, event, &localError))) 161 { 162 const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation); 163 if (!operation || strcmp(operation, kMessageOperationItemChanged)) // some op we don't care about 164 { 165 zsecdebug("operation: %s", operation); 166 return result; 167 } 168 169 xpc_object_t xrv = xpc_dictionary_get_value(event, kMessageKeyValue); 170 if (!xrv) 171 { 172 zsecdebug("xrv null for kMessageKeyValue"); 173 return result; 174 } 175 describeXPCObject("xrv", xrv); 176 177 CFDictionaryRef returnedValues = _CFXPCCreateCFObjectFromXPCObject(xrv); 178 zsecdebug("returnedValues: %@", returnedValues); 179 180 if (itemsChangedBlock) 181 itemsChangedBlock(returnedValues, localError); 182 } 183 CFReleaseSafe(localError); 184 185 return result; 186} 187 188static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error) 189{ 190 // return true if the type is XPC_TYPE_DICTIONARY (and therefore something more to process) 191 zsecdebug("handle_connection_event\n"); 192 xpc_type_t xtype = xpc_get_type(event); 193 describeXPCType("handle_xpc_event", xtype); 194 if (XPC_TYPE_CONNECTION == xtype) 195 { 196 zsecdebug("handle_xpc_event: XPC_TYPE_CONNECTION (unexpected)"); 197 // The client of an XPC service does not get connection events 198 // For nwo, we log this and keep going 199 describeXPCObject("handle_xpc_event: XPC_TYPE_CONNECTION, obj : ", event); 200#if 0 201 if (error) 202 *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX 203 assert(true); 204#endif 205 } 206 else 207 if (XPC_TYPE_ERROR == xtype) 208 { 209 zsecdebug("default: xpc error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); 210 if (error) 211 *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX 212 } 213 else 214 if (XPC_TYPE_DICTIONARY == xtype) 215 { 216 zsecdebug("received dictionary event %p\n", event); 217 return true; 218 } 219 else 220 { 221 zsecdebug("default: unexpected connection event %p\n", event); 222 describeXPCObject("handle_xpc_event: obj : ", event); 223 if (error) 224 *error = makeError(kSOSOUnexpectedXPCEvent); 225 } 226 return false; 227} 228 229static void talkWithKVS(xpc_object_t message, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 230{ 231 secerror("start"); 232 __block CFErrorRef error = NULL; 233 __block CFTypeRef object = NULL; 234 235 dispatch_block_t callback = ^{ 236 secerror("callback"); 237 if (replyBlock) 238 replyBlock(object, error); 239 // if (object) 240 // CFRelease(object); 241 if (error) 242 { 243 secerror("callback error: %@", error); 244 // CFRelease(error); 245 } 246 dispatch_release(processQueue); 247 }; 248 249 require_action(serviceConnection, xit, error = makeError(kSOSConnectionNotOpen)); 250 require_action(message, xit, error = makeError(kSOSObjectNotFoundError)); 251 dispatch_retain(processQueue); 252 secerror("xpc_connection_send_message_with_reply called"); 253 254 Block_copy(callback); // TODO -- this should not be needed, I think 255 256//#if !WANTXPCREPLY 257 // xpc_connection_send_message(serviceConnection, message); // Send message; don't want a reply 258//#else 259 xpc_connection_send_message_with_reply(serviceConnection, message, xpc_queue, ^(xpc_object_t reply) 260 { 261 secerror("xpc_connection_send_message_with_reply handler called back"); 262 if (xpc_event_filter(serviceConnection, reply, &error) && reply) 263 { 264 describeXPCObject("getValuesFromKVS: reply : ", reply); 265 xpc_object_t xrv = xpc_dictionary_get_value(reply, kMessageKeyValue); 266 if (xrv) 267 { 268 describeXPCObject("talkWithKVS: xrv: ", xrv); 269 /* 270 * The given XPC object must be one that was previously returned by 271 * _CFXPCCreateXPCMessageWithCFObject(). 272 */ 273 object = _CFXPCCreateCFObjectFromXPCObject(xrv); // CF object is retained; release in callback 274 secerror("converted CF object: %@", object); 275 } 276 else 277 secerror("missing value reply"); 278 } 279 dispatch_async(processQueue, callback); 280 }); 281//#endif 282 283//sleep(5); // DEBUG DEBUG FIX 284 // xpc_release(message); 285 return; 286 287xit: 288 secerror("talkWithKVS error: %@", error); 289 if (replyBlock) 290 dispatch_async(processQueue, callback); 291} 292 293static void putValuesWithXPC(CFDictionaryRef values, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 294{ 295 CFErrorRef error = NULL; 296 297 require_action(values, xit, error = makeError(kSOSObjectNotFoundError)); 298 299 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 300 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion); 301 xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationPUTDictionary); 302 303 xpc_object_t xobject = _CFXPCCreateXPCObjectFromCFObject(values); 304 require_action(xobject, xit, error = makeError(kSOSObjectCantBeConvertedToXPCObject)); 305 xpc_dictionary_set_value(message, kMessageKeyValue, xobject); 306 307 talkWithKVS(message, processQueue, replyBlock); 308 return; 309 310xit: 311 if (replyBlock) 312 replyBlock(NULL, error); 313} 314 315static void synchronizeKVS(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 316{ 317 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 318 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion); 319 xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationSynchronize); 320 talkWithKVS(message, processQueue, replyBlock); 321} 322 323static void clearAll(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 324{ 325 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 326 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion); 327 xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationClearStore); 328 talkWithKVS(message, processQueue, replyBlock); 329} 330 331static void getValuesFromKVS(CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 332{ 333 secerror("start"); 334 CFErrorRef error = NULL; 335 xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0); 336 xpc_object_t xkeysToGet = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create(); 337 338 require_action(xkeysToGet, xit, error = makeError(kSOSObjectNotFoundError)); 339 340 xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToGet); 341 342 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 343 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion); 344 xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationGETv2); 345 xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest); 346 347 talkWithKVS(message, processQueue, replyBlock); 348 349 xpc_release(message); 350 return; 351 352xit: 353 if (replyBlock) 354 replyBlock(NULL, error); 355} 356 357static void registerKeysForKVS(CFArrayRef keysToGet, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 358{ 359 secerror("start"); 360 361 xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0); 362 xpc_object_t xkeysToRegister = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create(); 363 xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToRegister); 364 365 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 366 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion); 367 xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationRegisterKeysAndGet); 368 xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest); 369 370 if (clientIdentifier) 371 { 372 char *clientid = CFStringToCString(clientIdentifier); 373 if (clientid) 374 { 375 xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid); 376 free(clientid); 377 } 378 } 379 380 setItemsChangedBlock(replyBlock); 381 talkWithKVS(message, processQueue, replyBlock); 382 383 xpc_release(message); 384} 385 386static void unregisterKeysForKVS(CFArrayRef keysToUnregister, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 387{ 388#if NO_SERVERz 389 if (gCKD->unregisterKeys) { 390 return gCKD->unregisterKeys(...); 391 } 392#endif 393 394 secerror("start"); 395 396 xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0); 397 xpc_object_t xkeysToUnregister = keysToUnregister ? _CFXPCCreateXPCObjectFromCFObject(keysToUnregister) : xpc_null_create(); 398 xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToUnregister); 399 400 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 401 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion); 402 xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationUnregisterKeys); 403 xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest); 404 405 if (clientIdentifier) 406 { 407 char *clientid = CFStringToCString(clientIdentifier); 408 if (clientid) 409 { 410 xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid); 411 free(clientid); 412 } 413 } 414 415 talkWithKVS(message, processQueue, replyBlock); 416 417 xpc_release(message); 418} 419 420#pragma mark ----- DEBUG Utilities ----- 421 422//------------------------------------------------------------------------------------------------ 423// DEBUG only 424//------------------------------------------------------------------------------------------------ 425 426static void describeXPCObject(char *prefix, xpc_object_t object) 427{ 428//#ifndef NDEBUG 429 // This is useful for debugging. 430 if (object) 431 { 432 char *desc = xpc_copy_description(object); 433 zsecdebug("%s%s\n", prefix, desc); 434 free(desc); 435 } 436 else 437 zsecdebug("%s<NULL>\n", prefix); 438//#endif 439} 440 441static void describeXPCType(char *prefix, xpc_type_t xtype) 442{ 443 /* 444 Add these as necessary: 445 XPC_TYPE_ENDPOINT 446 XPC_TYPE_NULL 447 XPC_TYPE_BOOL 448 XPC_TYPE_INT64 449 XPC_TYPE_UINT64 450 XPC_TYPE_DOUBLE 451 XPC_TYPE_DATE 452 XPC_TYPE_DATA 453 XPC_TYPE_STRING 454 XPC_TYPE_UUID 455 XPC_TYPE_FD 456 XPC_TYPE_SHMEM 457 XPC_TYPE_ARRAY 458 */ 459 460#ifndef NDEBUG 461 // This is useful for debugging. 462 char msg[256]={0,}; 463 if (XPC_TYPE_CONNECTION == xtype) 464 strcpy(msg, "XPC_TYPE_CONNECTION"); 465 else if (XPC_TYPE_ERROR == xtype) 466 strcpy(msg, "XPC_TYPE_ERROR"); 467 else if (XPC_TYPE_DICTIONARY == xtype) 468 strcpy(msg, "XPC_TYPE_DICTIONARY"); 469 else 470 strcpy(msg, "<unknown>"); 471 472 zsecdebug("%s type:%s\n", prefix, msg); 473#endif 474} 475 476#pragma mark ---------- SOSCloudTransport ---------- 477 478static void SOSCloudTransportPut(SOSCloudTransportRef transport, CFDictionaryRef valuesToPut, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 479{ 480 secerror("%@", valuesToPut); 481 putValuesWithXPC(valuesToPut, processQueue, replyBlock); 482} 483 484static void SOSCloudTransportGet(SOSCloudTransportRef transport, CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 485{ 486// struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport; 487 /* Get from KVS */ 488 secerror("%@", keysToGet); 489 getValuesFromKVS(keysToGet, processQueue, replyBlock); 490} 491 492static void SOSCloudTransportRegisterKeys(SOSCloudTransportRef transport, CFArrayRef keysToGet, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 493{ 494 /* RegisterKeys with KVS */ 495 registerKeysForKVS(keysToGet, clientIdentifier, processQueue, replyBlock); 496} 497 498static void SOSCloudTransportUnregisterKeys(SOSCloudTransportRef transport, CFArrayRef keysToUnregister, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 499{ 500 /* unregister from KVS */ 501 unregisterKeysForKVS(keysToUnregister, clientIdentifier, processQueue, replyBlock); 502} 503 504static void SOSCloudTransportGetAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 505{ 506 /* Get from KVS */ 507 getValuesFromKVS(NULL, processQueue, replyBlock); 508} 509 510static void SOSCloudTransportSync(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 511{ 512// struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport; 513 /* Sync KVS */ 514 synchronizeKVS(processQueue, replyBlock); 515} 516 517static void SOSCloudTransportClearAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock) 518{ 519// struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport; 520 /* clear KVS */ 521 clearAll(processQueue, replyBlock); 522} 523 524static void SOSCloudTransportSetItemsChangedBlock(SOSCloudTransportRef transport, CloudKeychainReplyBlock itemsChangedBlock) 525{ 526// struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport; 527 /* clear KVS */ 528 setItemsChangedBlock(itemsChangedBlock); 529} 530 531static void SOSCloudTransportInit(void *ctx) 532{ 533 struct SOSCloudTransport *st = (struct SOSCloudTransport *)ctx; 534 st->t.put = SOSCloudTransportPut; 535 st->t.registerKeys = SOSCloudTransportRegisterKeys; 536 st->t.unregisterKeys = SOSCloudTransportUnregisterKeys; 537 st->t.get = SOSCloudTransportGet; 538 st->t.getAll = SOSCloudTransportGetAll; 539 st->t.synchronize = SOSCloudTransportSync; 540 st->t.clearAll = SOSCloudTransportClearAll; 541 st->t.setItemsChangedBlock = SOSCloudTransportSetItemsChangedBlock; 542 543 if (st->kvsID) 544 CFRetain(st->kvsID); 545 546 st->version = 0; 547 initXPCConnection(); 548} 549 550SOSCloudTransportRef SOSCloudTransportDefaultTransport(CFStringRef kvsID, uint32_t options) 551{ 552 static struct SOSCloudTransport cloudTransport = {}; 553 cloudTransport.kvsID = kvsID; 554 dispatch_once_f(&cloudTransport.once, &cloudTransport, SOSCloudTransportInit); 555 return &cloudTransport.t; 556} 557 558 559