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// main.m 26// ckd-xpc 27// 28// Created by John Hurley on 7/19/12. 29// Copyright (c) 2012 John Hurley. All rights reserved. 30// 31 32/* 33 This XPC service is essentially just a proxy to iCloud KVS, which exists since 34 the main security code cannot link against Foundation. 35 36 See sendTSARequestWithXPC in tsaSupport.c for how to call the service 37 38 send message to app with xpc_connection_send_message 39 40 For now, build this with: 41 42 ~rc/bin/buildit . --rootsDirectory=/var/tmp -noverify -offline -target CloudKeychainProxy 43 44 and install or upgrade with: 45 46 darwinup install /var/tmp/sec.roots/sec~dst 47 darwinup upgrade /var/tmp/sec.roots/sec~dst 48 49 You must use darwinup during development to update system caches 50*/ 51 52//------------------------------------------------------------------------------------------------ 53 54#include <AssertMacros.h> 55 56#import <Foundation/Foundation.h> 57#import <Security/Security.h> 58#import <utilities/SecCFRelease.h> 59#import <xpc/xpc.h> 60#import <xpc/private.h> 61#import <CoreFoundation/CFXPCBridge.h> 62#import <sysexits.h> 63#import <syslog.h> 64#import <CommonCrypto/CommonDigest.h> 65#include <utilities/SecXPCError.h> 66 67#import "SOSCloudKeychainConstants.h" 68#import "CKDKVSProxy.h" 69 70void finalize_connection(void *not_used); 71void handle_connection_event(const xpc_connection_t peer); 72static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event); 73 74static bool operation_put_dictionary(xpc_object_t event); 75static bool operation_get_v2(xpc_object_t event); 76 77int ckdproxymain(int argc, const char *argv[]); 78 79static void describeXPCObject(char *prefix, xpc_object_t object) 80{ 81//#ifndef NDEBUG 82 // This is useful for debugging. 83 if (object) 84 { 85 char *desc = xpc_copy_description(object); 86 secdebug(XPROXYSCOPE, "%s%s\n", prefix, desc); 87 free(desc); 88 } 89 else 90 secdebug(XPROXYSCOPE, "%s<NULL>\n", prefix); 91 92//#endif 93} 94 95static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event) 96{ 97 bool result = false; 98 int err = 0; 99 100 require_action_string(xpc_get_type(event) == XPC_TYPE_DICTIONARY, xit, err = -51, "expected XPC_TYPE_DICTIONARY"); 101 102 const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation); 103 require_action(operation, xit, result = false); 104 105 // Check protocol version 106 uint64_t version = xpc_dictionary_get_uint64(event, kMessageKeyVersion); 107 secdebug(XPROXYSCOPE, "Reply version: %lld\n", version); 108 require_action(version == kCKDXPCVersion, xit, result = false); 109 110 // Operations 111 secdebug(XPROXYSCOPE, "Handling %s operation", operation); 112 113 if (operation && !strcmp(operation, kOperationPUTDictionary)) 114 operation_put_dictionary(event); 115 else 116 if (operation && !strcmp(operation, kOperationGETv2)) 117 operation_get_v2(event); 118 else 119 if (operation && !strcmp(operation, kOperationClearStore)) 120 { 121 [[UbiqitousKVSProxy sharedKVSProxy] clearStore]; 122 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 123 if (replyMessage) // Caller wanted an ACK, so give one 124 { 125 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK"); 126 xpc_connection_send_message(peer, replyMessage); 127 } 128 } 129 else if (operation && !strcmp(operation, kOperationRemoveObjectForKey)) 130 { 131 const char *keyToRemove = xpc_dictionary_get_string(event, kMessageKeyKey); 132 [[UbiqitousKVSProxy sharedKVSProxy] removeObjectForKey:[NSString stringWithCString:keyToRemove encoding:NSUTF8StringEncoding]]; 133 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 134 if (replyMessage) // Caller wanted an ACK, so give one 135 { 136 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK"); 137 xpc_connection_send_message(peer, replyMessage); 138 } 139 } 140 else if (operation && !strcmp(operation, kOperationSynchronize)) 141 { 142 [[UbiqitousKVSProxy sharedKVSProxy] requestSynchronization:YES]; 143 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 144 if (replyMessage) // Caller wanted an ACK, so give one 145 { 146 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK"); 147 xpc_connection_send_message(peer, replyMessage); 148 } 149 } 150 else if (operation && !strcmp(operation, kOperationSynchronizeAndWait)) 151 { 152 xpc_object_t xkeysToGetDict = xpc_dictionary_get_value(event, kMessageKeyValue); 153 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 154 xpc_object_t xkeys = xpc_dictionary_get_value(xkeysToGetDict, kMessageKeyKeysToGet); 155 NSArray *keysToGet = (__bridge NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xkeys)); 156 [[UbiqitousKVSProxy sharedKVSProxy] waitForSynchronization:keysToGet 157 handler:^(NSDictionary *values, NSError *err) { 158 if (replyMessage) // Caller wanted an ACK, so give one 159 { 160 secdebug("keytrace", "Result from [[UbiqitousKVSProxy sharedKVSProxy] waitForSynchronization:]: %@", err); 161 xpc_object_t xobject = values ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(values)) : xpc_null_create(); 162 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, xobject); 163 if (err) 164 { 165 xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((CFErrorRef)CFBridgingRetain(err)); 166 xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj); 167 CFReleaseSafe((__bridge CFTypeRef)(err)); 168 } 169 xpc_connection_send_message(peer, replyMessage); 170 } 171 }]; 172 CFReleaseSafe((__bridge CFTypeRef)(keysToGet)); 173 } 174 else if (operation && !strcmp(operation, kOperationRegisterKeysAndGet)) 175 { 176 xpc_object_t xkeysToRegisterDict = xpc_dictionary_get_value(event, kMessageKeyValue); 177// describeXPCObject("xkeysToRegister: ", xkeysToRegisterDict); 178 // KTR = keysToRegister 179 bool getNewKeysOnly = xpc_dictionary_get_bool(xkeysToRegisterDict, kMessageKeyGetNewKeysOnly); 180 xpc_object_t xKTRallkeys = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageKeyKeysToGet); 181 xpc_object_t xKTRrequiresFirstUnlock = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageKeyKeysRequireFirstUnlock); 182 xpc_object_t xKTRrequiresUnlock = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageKeyKeysRequiresUnlocked); 183 184 NSArray *KTRallkeys = (__bridge NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys)); 185 NSArray *KTRrequiresFirstUnlock = (__bridge NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xKTRrequiresFirstUnlock)); 186 NSArray *KTRrequiresUnlock = (__bridge NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xKTRrequiresUnlock)); 187 188 id object = [[UbiqitousKVSProxy sharedKVSProxy] registerKeysAndGet:getNewKeysOnly always:KTRallkeys reqFirstUnlock:KTRrequiresFirstUnlock reqUnlocked:KTRrequiresUnlock]; 189 190 CFReleaseSafe((__bridge CFTypeRef)(KTRallkeys)); 191 CFReleaseSafe((__bridge CFTypeRef)(KTRrequiresFirstUnlock)); 192 CFReleaseSafe((__bridge CFTypeRef)(KTRrequiresUnlock)); 193 194 secdebug("keytrace", "Result from [[UbiqitousKVSProxy sharedKVSProxy] registerKeysAndGet:]: %@", object); 195 196 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(object)) : xpc_null_create(); 197 198 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 199 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, xobject); 200 xpc_connection_send_message(peer, replyMessage); 201 secdebug(XPROXYSCOPE, "RegisterKeysAndGet message sent"); 202 } 203 else if (operation && !strcmp(operation, kOperationUILocalNotification)) 204 { 205 xpc_object_t xLocalNotificationDict = xpc_dictionary_get_value(event, kMessageKeyValue); 206// describeXPCObject("xLocalNotificationDict: ", xLocalNotificationDict); 207 NSDictionary *localNotificationDict = (__bridge NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xLocalNotificationDict)); 208 int64_t outFlags = 0; 209 id object = [[UbiqitousKVSProxy sharedKVSProxy] localNotification:localNotificationDict outFlags:&outFlags]; 210 secdebug(XPROXYSCOPE, "Result from [[UbiqitousKVSProxy sharedKVSProxy] localNotification:]: %@", object); 211 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(object)) : xpc_null_create(); 212 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 213 xpc_dictionary_set_int64(xobject, kMessageKeyNotificationFlags, outFlags); 214 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, xobject); 215 xpc_connection_send_message(peer, replyMessage); 216 secdebug(XPROXYSCOPE, "localNotification reply sent"); 217 CFReleaseSafe((__bridge CFTypeRef)(localNotificationDict)); 218 } 219 else if (operation && !strcmp(operation, kOperationSetParams)) 220 { 221 xpc_object_t xParamsDict = xpc_dictionary_get_value(event, kMessageKeyValue); 222 NSDictionary *paramsDict = (__bridge NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xParamsDict)); 223 [[UbiqitousKVSProxy sharedKVSProxy] setParams:paramsDict]; 224 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 225 if (replyMessage) // Caller wanted an ACK, so give one 226 { 227 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK"); 228 xpc_connection_send_message(peer, replyMessage); 229 } 230 secdebug(XPROXYSCOPE, "setParams reply sent"); 231 CFReleaseSafe((__bridge CFTypeRef)(paramsDict)); 232 } 233 else if (operation && !strcmp(operation, kOperationRequestSyncWithAllPeers)) 234 { 235 [[UbiqitousKVSProxy sharedKVSProxy] requestSyncWithAllPeers]; 236 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 237 if (replyMessage) // Caller wanted an ACK, so give one 238 { 239 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK"); 240 xpc_connection_send_message(peer, replyMessage); 241 } 242 secdebug(XPROXYSCOPE, "RequestSyncWithAllPeers reply sent"); 243 } 244 else 245 { 246 char *description = xpc_copy_description(event); 247 secdebug(XPROXYSCOPE, "Unknown op=%s request from pid %d: %s", operation, xpc_connection_get_pid(peer), description); 248 free(description); 249 } 250 result = true; 251xit: 252 if (!result) 253 describeXPCObject("handle_operation fail: ", event); 254} 255 256void finalize_connection(void *not_used) 257{ 258 secdebug(XPROXYSCOPE, "finalize_connection"); 259 [[UbiqitousKVSProxy sharedKVSProxy] requestSynchronization:YES]; 260 xpc_transaction_end(); 261} 262 263static bool operation_put_dictionary(xpc_object_t event) 264{ 265 // PUT a set of objects into the KVS store. Return false if error 266 267 describeXPCObject("operation_put_dictionary event: ", event); 268 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue); 269 if (!xvalue) 270 return false; 271 272 CFTypeRef cfvalue = _CFXPCCreateCFObjectFromXPCObject(xvalue); 273 if (cfvalue && (CFGetTypeID(cfvalue)==CFDictionaryGetTypeID())) 274 { 275 [[UbiqitousKVSProxy sharedKVSProxy] setObjectsFromDictionary:(__bridge NSDictionary *)cfvalue]; 276 CFReleaseSafe(cfvalue); 277 return true; 278 } 279 else{ 280 describeXPCObject("operation_put_dictionary unable to convert to CF: ", xvalue); 281 CFReleaseSafe(cfvalue); 282 } 283 return false; 284} 285 286static bool operation_get_v2(xpc_object_t event) 287{ 288 // GET a set of objects from the KVS store. Return false if error 289 describeXPCObject("operation_get_v2 event: ", event); 290 xpc_connection_t peer = xpc_dictionary_get_remote_connection(event); 291 describeXPCObject("operation_get_v2: peer: ", peer); 292 293 xpc_object_t replyMessage = xpc_dictionary_create_reply(event); 294 if (!replyMessage) 295 { 296 secdebug(XPROXYSCOPE, "can't create replyMessage"); 297 assert(true); //must have a reply handler 298 return false; 299 } 300 xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0); 301 if (!returnedValues) 302 { 303 secdebug(XPROXYSCOPE, "can't create returnedValues"); 304 assert(true); // must have a spot for the returned values 305 return false; 306 } 307 308 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue); 309 if (!xvalue) 310 { 311 secdebug(XPROXYSCOPE, "missing \"value\" key"); 312 return false; 313 } 314 315 xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet); 316 if (xkeystoget) 317 { 318 secdebug(XPROXYSCOPE, "got xkeystoget"); 319 CFTypeRef keystoget = _CFXPCCreateCFObjectFromXPCObject(xkeystoget); 320 if (!keystoget || (CFGetTypeID(keystoget)!=CFArrayGetTypeID())) // not "getAll", this is an error of some kind 321 { 322 secdebug(XPROXYSCOPE, "can't convert keystoget or is not an array"); 323 CFReleaseSafe(keystoget); 324 return false; 325 } 326 327 [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop) 328 { 329 NSString *key = (NSString *)obj; 330 id object = [[UbiqitousKVSProxy sharedKVSProxy] get:key]; 331 secdebug(XPROXYSCOPE, "[UbiqitousKVSProxy sharedKVSProxy] get: key: %@, object: %@", key, object); 332 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)object) : xpc_null_create(); 333 xpc_dictionary_set_value(returnedValues, [key UTF8String], xobject); 334 describeXPCObject("operation_get_v2: value from kvs: ", xobject); 335 }]; 336 } 337 else // get all values from kvs 338 { 339 secdebug(XPROXYSCOPE, "get all values from kvs"); 340 NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll]; 341 [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop) 342 { 343 xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create(); 344 xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject); 345 }]; 346 } 347 348 xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion); 349 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues); 350 xpc_connection_send_message(peer, replyMessage); 351 352 return true; 353} 354 355static void initializeProxyObjectWithConnection(const xpc_connection_t connection) 356{ 357 [[UbiqitousKVSProxy sharedKVSProxy] setItemsChangedBlock:^(CFDictionaryRef values) 358 { 359 secdebug(XPROXYSCOPE, "UbiqitousKVSProxy called back"); 360 xpc_object_t xobj = _CFXPCCreateXPCObjectFromCFObject(values); 361 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 362 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion); 363 xpc_dictionary_set_string(message, kMessageKeyOperation, kMessageOperationItemChanged); 364 xpc_dictionary_set_value(message, kMessageKeyValue, xobj?xobj:xpc_null_create()); 365 xpc_connection_send_message(connection, message); // Send message; don't wait for a reply 366 }]; 367} 368 369static void cloudkeychainproxy_peer_event_handler(xpc_connection_t peer, xpc_object_t event) 370{ 371 describeXPCObject("peer: ", peer); 372 xpc_type_t type = xpc_get_type(event); 373 if (type == XPC_TYPE_ERROR) { 374 if (event == XPC_ERROR_CONNECTION_INVALID) { 375 // The client process on the other end of the connection has either 376 // crashed or cancelled the connection. After receiving this error, 377 // the connection is in an invalid state, and you do not need to 378 // call xpc_connection_cancel(). Just tear down any associated state 379 // here. 380 } else if (event == XPC_ERROR_TERMINATION_IMMINENT) { 381 // Handle per-connection termination cleanup. 382 } 383 } else { 384 assert(type == XPC_TYPE_DICTIONARY); 385 // Handle the message. 386 // describeXPCObject("dictionary:", event); 387 cloudkeychainproxy_peer_dictionary_handler(peer, event); 388 } 389} 390 391static void cloudkeychainproxy_event_handler(xpc_connection_t peer) 392{ 393 // By defaults, new connections will target the default dispatch 394 // concurrent queue. 395 396 if (xpc_get_type(peer) != XPC_TYPE_CONNECTION) 397 { 398 secdebug(XPROXYSCOPE, "expected XPC_TYPE_CONNECTION"); 399 return; 400 } 401 initializeProxyObjectWithConnection(peer); 402 xpc_connection_set_event_handler(peer, ^(xpc_object_t event) 403 { 404 cloudkeychainproxy_peer_event_handler(peer, event); 405 }); 406 407 // This will tell the connection to begin listening for events. If you 408 // have some other initialization that must be done asynchronously, then 409 // you can defer this call until after that initialization is done. 410 xpc_connection_resume(peer); 411} 412 413static void diagnostics(int argc, const char *argv[]) 414{ 415 @autoreleasepool 416 { 417 NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll]; 418 NSLog(@"All: %@",all); 419 } 420} 421 422int ckdproxymain(int argc, const char *argv[]) 423{ 424 secdebug(XPROXYSCOPE, "Starting CloudKeychainProxy"); 425 char *wait4debugger = getenv("WAIT4DEBUGGER"); 426 if (wait4debugger && !strcasecmp("YES", wait4debugger)) 427 { 428 syslog(LOG_ERR, "Waiting for debugger"); 429 kill(getpid(), SIGSTOP); 430 } 431 432 if (argc > 1) { 433 diagnostics(argc, argv); 434 return 0; 435 } 436 437#if !TARGET_IPHONE_SIMULATOR 438 xpc_track_activity(); // sets us up for idle timeout handling 439#endif 440 441 // DISPATCH_TARGET_QUEUE_DEFAULT 442 xpc_connection_t listener = xpc_connection_create_mach_service(xpcServiceName, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER); 443 xpc_connection_set_event_handler(listener, ^(xpc_object_t object){ cloudkeychainproxy_event_handler(object); }); 444 445 [UbiqitousKVSProxy sharedKVSProxy]; 446 447 // It looks to me like there is insufficient locking to allow a request to come in on the XPC connection while doing the initial all items. 448 // Therefore I'm leaving the XPC connection suspended until that has time to process. 449 xpc_connection_resume(listener); 450 451 @autoreleasepool 452 { 453 secdebug(XPROXYSCOPE, "Starting mainRunLoop"); 454 NSRunLoop *runLoop = [NSRunLoop mainRunLoop]; 455 [runLoop run]; 456 } 457 458 secdebug(XPROXYSCOPE, "Exiting CloudKeychainProxy"); 459 460 return EXIT_FAILURE; 461} 462