2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
30 #import <Foundation/NSArray.h>
31 #import <Foundation/Foundation.h>
33 #import <Security/SecBasePriv.h>
34 #import <Security/SecItemPriv.h>
35 #import <utilities/debugging.h>
38 #include <Security/CKBridge/SOSCloudKeychainConstants.h>
39 #include <Security/SecureObjectSync/SOSARCDefines.h>
40 #include <Security/SecureObjectSync/SOSCloudCircle.h>
41 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
44 #import <os/activity.h>
46 #include <utilities/SecAKSWrappers.h>
47 #include <utilities/SecCFRelease.h>
48 #include <AssertMacros.h>
51 #import "IDSPersistentState.h"
53 static const char *kStreamName = "com.apple.notifyd.matching";
54 NSString *const IDSSendMessageOptionForceEncryptionOffKey = @"IDSSendMessageOptionForceEncryptionOff";
56 static const int64_t kAttemptFlushBufferInterval = (NSEC_PER_SEC * 15);
57 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for handling unhandled messages.
58 static const int64_t kMaxMessageRetryDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
59 static const int64_t kMinMessageRetryDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before attempting to retry handling messages.
61 #define SECD_RUN_AS_ROOT_ERROR 550
64 @implementation IDSKeychainSyncingProxy
66 + (IDSKeychainSyncingProxy *) idsProxy
68 static IDSKeychainSyncingProxy *idsProxy;
70 static dispatch_once_t onceToken;
71 dispatch_once(&onceToken, ^{
72 idsProxy = [[self alloc] init];
80 if([_unhandledMessageBuffer count] > 0){
81 [IDSKeychainSyncingProxyPersistentState setUnhandledMessages:_unhandledMessageBuffer];
85 - (void) importKeyInterests: (NSMutableDictionary*) unhandledMessages
87 _unhandledMessageBuffer = unhandledMessages;
92 if (self = [super init])
94 secnotice("event", "%@ start", self);
96 _isIDSInitDone = false;
98 _calloutQueue = dispatch_queue_create("IDSCallout", DISPATCH_QUEUE_SERIAL);
99 _unhandledMessageBuffer = [ [NSMutableDictionary alloc] initWithCapacity: 0];
100 _isSecDRunningAsRoot = false;
101 secdebug(IDSPROXYSCOPE, "%@ done", self);
103 [self doIDSInitialization];
105 [self doSetIDSDeviceID:nil];
107 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
108 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
109 dispatch_source_set_event_handler(_syncTimer, ^{
112 dispatch_resume(_syncTimer);
114 [self importKeyInterests: [IDSKeychainSyncingProxyPersistentState unhandledMessages]];
116 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
117 ^(xpc_object_t notification){
118 [self streamEvent:notification];
121 [self updateUnlockedSinceBoot];
122 [self updateIsLocked];
124 [self keybagDidUnlock];
130 - (void) keybagDidLock
132 secnotice("event", "%@", self);
135 - (void) keybagDidUnlock
137 secnotice("event", "%@", self);
138 [self handleAllPendingMessage];
141 - (BOOL) updateUnlockedSinceBoot
143 CFErrorRef aksError = NULL;
144 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
145 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
146 CFReleaseSafe(aksError);
152 - (BOOL) updateIsLocked
154 CFErrorRef aksError = NULL;
155 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
156 secerror("%@ Got error querying lock state: %@", self, aksError);
157 CFReleaseSafe(aksError);
161 _unlockedSinceBoot = YES;
165 - (void) keybagStateChange
167 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
168 BOOL wasLocked = _isLocked;
169 if ([self updateIsLocked]) {
170 if (wasLocked == _isLocked)
171 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
173 [self keybagDidLock];
175 [self keybagDidUnlock];
180 - (void)streamEvent:(xpc_object_t)notification
182 #if (!TARGET_IPHONE_SIMULATOR)
183 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
184 if (!notificationName) {
185 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
186 return [self keybagStateChange];
188 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
189 char *desc = xpc_copy_description(notification);
190 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
198 secdebug("IDS Transport", "%@ attempting to hand unhandled messages to securityd, here is our message queue: %@", self, _unhandledMessageBuffer);
199 if([_unhandledMessageBuffer count] == 0)
200 _syncTimerScheduled = NO;
201 else if (_syncTimerScheduled && !_isLocked){
202 [self handleAllPendingMessage];
206 - (dispatch_time_t) nextSyncTime
208 secdebug("IDS Transport", "nextSyncTime");
210 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinMessageRetryDelay);
212 // Don't sync again unless we waited at least kAttemptFlushBufferInterval
214 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kAttemptFlushBufferInterval);
215 if (nextSync < soonest || _deadline < soonest) {
216 secdebug("timer", "%@ backing off", self);
221 // Don't delay more than kMaxMessageRetryDelay after the first request.
222 if (nextSync > _deadline) {
223 secdebug("timer", "%@ hit deadline", self);
227 // Bump the timer by kMinMessageRetryDelay
228 if (_syncTimerScheduled)
229 secdebug("timer", "%@ bumped timer", self);
231 secdebug("timer", "%@ scheduled timer", self);
236 - (void)scheduleSyncRequestTimer
238 secdebug("IDS Transport", "scheduling sync request timer");
239 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
240 _syncTimerScheduled = YES;
244 - (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
246 self->itemsChangedCallback = itemsChangedBlock;
249 - (void)doIDSInitialization{
251 secnotice("IDS Transport", "doIDSInitialization!");
253 _service = [[IDSService alloc] initWithService: @IDSServiceNameKeychainSync];
255 if( _service == nil ){
256 _isIDSInitDone = false;
257 secerror("Could not create ids service");
260 secnotice("IDS Transport", "IDS Transport Successfully set up IDS!");
261 [_service addDelegate:self queue: dispatch_get_main_queue()];
263 _isIDSInitDone = true;
264 if(_isSecDRunningAsRoot == false)
265 [self doSetIDSDeviceID:nil];
269 - (void) calloutWith: (void(^)(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSettingDeviceID))) callout
271 // In IDSKeychainSyncingProxy serial queue
272 dispatch_queue_t idsproxy_queue = dispatch_get_main_queue();
276 // dispatch_get_global_queue - well-known global concurrent queue
277 // dispatch_get_main_queue - default queue that is bound to the main thread
278 xpc_transaction_begin();
279 dispatch_async(_calloutQueue, ^{
280 __block NSMutableDictionary *myPending;
281 __block bool myHandlePendingMessage;
282 __block bool myDoSetDeviceID;
283 __block bool wasLocked;
284 dispatch_sync(idsproxy_queue, ^{
285 myPending = [_unhandledMessageBuffer copy];
286 myHandlePendingMessage = _handleAllPendingMessages;
287 myDoSetDeviceID = _setIDSDeviceID;
288 wasLocked = _isLocked;
292 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
294 _shadowHandleAllPendingMessages = NO;
297 callout(myPending, myHandlePendingMessage, myDoSetDeviceID, idsproxy_queue, ^(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSetDeviceID) {
298 secdebug("event", "%@ %s%s before callout handled: %s%s", self, myHandlePendingMessage ? "P" : "p", myDoSetDeviceID ? "D" : "d", handledPendingMessage ? "H" : "h", handledSetDeviceID ? "I" : "i");
300 // In IDSKeychainSyncingProxy's serial queue
306 // Update setting device id
307 _setIDSDeviceID = ((myDoSetDeviceID && !handledSetDeviceID) || _shadowHandleAllPendingMessages);
309 _shadowDoSetIDSDeviceID = NO;
311 if(_setIDSDeviceID && !_isLocked && _isSecDRunningAsRoot == false)
312 [self doSetIDSDeviceID:&error];
314 // Update handling pending messages
315 _handleAllPendingMessages = ((myHandlePendingMessage && (!handledPendingMessage)) || _shadowHandleAllPendingMessages);
317 _shadowHandleAllPendingMessages = NO;
319 if (handledPendingMessage)
320 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
322 // Update pending messages and handle them
323 [handledMessages enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop){
324 NSString* fromID = (NSString*)key;
325 [_unhandledMessageBuffer removeObjectForKey:fromID];
328 // Write state to disk
331 if ([_unhandledMessageBuffer count] > 0 || (!_isLocked && wasLocked))
332 [self handleAllPendingMessage];
334 xpc_transaction_end();
339 - (BOOL) doSetIDSDeviceID: (NSError**)error
342 NSDictionary *userInfo;
344 NSString *errorMessage;
345 __block NSString* deviceID;
346 __block CFErrorRef localError = NULL;
347 __block bool handledSettingID = false;
350 [self doIDSInitialization];
352 if(_isSecDRunningAsRoot == true)
354 secerror("cannot set IDS device ID, secd is running as root");
357 require_action_quiet(_isIDSInitDone, fail, errorMessage = @"IDSKeychainSyncingProxy can't set up the IDS service"; code = kSecIDSErrorNotRegistered);
358 require_action_quiet(!_isLocked, fail, errorMessage = @"IDSKeychainSyncingProxy can't set device ID, device is locked"; code = kSecIDSErrorDeviceIsLocked);
360 deviceID = IDSCopyLocalDeviceUniqueID();
361 secdebug("IDS Transport", "This is our IDS device ID: %@", deviceID);
363 require_action_quiet(deviceID != nil, fail, errorMessage = @"IDSKeychainSyncingProxy could not retrieve device ID from keychain"; code = kSecIDSErrorNoDeviceID);
365 if(_inCallout && _isSecDRunningAsRoot == false){
366 _shadowDoSetIDSDeviceID = YES;
370 _setIDSDeviceID = YES;
371 [self calloutWith:^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
372 handledSettingID = SOSCCSetDeviceID((__bridge CFStringRef) deviceID, &localError);
374 dispatch_async(queue, ^{
376 if(CFErrorGetCode(localError) == SECD_RUN_AS_ROOT_ERROR){
377 secerror("SETTING RUN AS ROOT ERROR");
378 _isSecDRunningAsRoot = true;
381 *error = (__bridge NSError *)(localError);
383 handledSettingID = YES;
384 done(nil, NO, handledSettingID);
387 result = handledSettingID;
392 userInfo = [ NSDictionary dictionaryWithObjectsAndKeys:errorMessage, NSLocalizedDescriptionKey, nil ];
394 *error = [NSError errorWithDomain:@"com.apple.security.ids.error" code:code userInfo:userInfo];
395 secerror("%@", *error);
400 -(BOOL) sendIDSMessage:(NSDictionary*)data name:(NSString*) deviceName peer:(NSString*) peerID error:(NSError**) error
403 NSDictionary *userInfo;
406 NSString *errorMessage;
407 NSString* identifier = [NSString string];
408 NSMutableSet *destinations = [NSMutableSet set];
409 NSArray *ListOfIDSDevices = nil;
410 IDSMessagePriority priority = IDSMessagePriorityHigh;
411 IDSDevice *device = nil;
412 BOOL encryptionOff = YES;
414 NSDictionary *options = [ NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:encryptionOff], IDSSendMessageOptionForceEncryptionOffKey, nil ];
416 require_action_quiet(_service, fail, errorMessage = @"Could not send message: IDS delegate uninitialized, can't use IDS to send this message"; code = kSecIDSErrorNotRegistered);
418 secdebug("IDS Transport", "[_service devices]: %@, we have their deviceName: %@", [_service devices], deviceName);
419 ListOfIDSDevices = [_service devices];
421 require_action_quiet([ListOfIDSDevices count]> 0, fail, errorMessage=@"Could not send message: IDS devices are not registered yet"; code = kSecIDSErrorNotRegistered);
422 secinfo("IDS Transport", "This is our list of devices: %@", ListOfIDSDevices);
424 for(NSUInteger i = 0; i < [ ListOfIDSDevices count ]; i++){
425 device = ListOfIDSDevices[i];
426 if( [ deviceName compare:device.uniqueID ] == 0){
427 [destinations addObject: IDSCopyIDForDevice(device)];
430 require_action_quiet([destinations count] != 0, fail, errorMessage = @"Could not send message: IDS device ID for peer does not match any devices within an IDS Account"; code = kSecIDSErrorCouldNotFindMatchingAuthToken);
432 result = [_service sendMessage:data toDestinations:destinations priority:priority options:options identifier:&identifier error:error ] ;
434 require_action_quiet(*error == nil, fail, errorMessage = @"Had an error sending IDS message"; code = kSecIDSErrorFailedToSend);
436 secdebug("IDS Transport", "IDSKeychainSyncingProxy sent this message over IDS: %@", data);
441 userInfo = [ NSDictionary dictionaryWithObjectsAndKeys:errorMessage, NSLocalizedDescriptionKey, nil ];
443 *error = [NSError errorWithDomain:@"com.apple.security.ids.error" code:code userInfo:userInfo];
444 secerror("%@", *error);
451 - (void) sendKeysCallout: (NSMutableDictionary*(^)(NSMutableDictionary* pending, NSError** error)) handleMessages {
452 [self calloutWith: ^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
453 NSError* error = NULL;
455 NSMutableDictionary* handled = handleMessages(pending, &error);
457 dispatch_async(queue, ^{
458 if (!handled && error) {
459 secerror("%@ did not handle message: %@", self, error);
462 done(handled, NO, NO);
467 - (void) handleAllPendingMessage
469 if([_unhandledMessageBuffer count] > 0){
470 secinfo("IDS Transport", "handling Message: %@", _unhandledMessageBuffer);
471 [_unhandledMessageBuffer enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
473 NSDictionary *messageAndFromID = (NSDictionary*)obj;
474 NSString *fromID = (NSString*)key;
477 _shadowHandleAllPendingMessages = YES;
480 __block CFErrorRef cf_error = NULL;
481 __block HandleIDSMessageReason success = kHandleIDSMessageSuccess;
482 _handleAllPendingMessages = YES;
484 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
485 success = SOSCCHandleIDSMessage(((__bridge CFDictionaryRef)messageAndFromID), &cf_error);
487 if(success == kHandleIDSMessageLocked){
488 secdebug("IDS Transport", "cannot handle messages when locked, error:%@", cf_error);
489 [_unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
491 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
492 _deadline = dispatch_time(DISPATCH_TIME_NOW, kAttemptFlushBufferInterval);
494 [self scheduleSyncRequestTimer];
497 else if(success == kHandleIDSMessageNotReady){
498 secdebug("IDS Transport", "not ready to handle message, error:%@", cf_error);
499 [_unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
500 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
501 _deadline = dispatch_time(DISPATCH_TIME_NOW, kAttemptFlushBufferInterval);
503 [self scheduleSyncRequestTimer];
506 else if(success == kHandleIDSMessageOtherFail){
507 secdebug("IDS Transport", "not ready to handle message, error:%@", cf_error);
508 [_unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
509 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
510 _deadline = dispatch_time(DISPATCH_TIME_NOW, kAttemptFlushBufferInterval);
512 [self scheduleSyncRequestTimer];
516 secdebug("IDS Transport", "IDSProxy handled this message! %@", messageAndFromID);
517 _syncTimerScheduled = NO;
518 return (NSMutableDictionary*)messageAndFromID;
526 - (void)service:(IDSService *)service account:(IDSAccount *)account incomingMessage:(NSDictionary *)message fromID:(NSString *)fromID context:(IDSMessageContext *)context;
528 secdebug("IDS Transport", "IDSKeychainSyncingProxy handling this message sent over IDS%@", message);
529 NSString *dataKey = [ NSString stringWithUTF8String: kMessageKeyIDSDataMessage ];
530 NSString *deviceIDKey = [ NSString stringWithUTF8String: kMessageKeyDeviceID ];
532 uint32_t operationType;
533 bool hadError = false;
534 CFStringRef errorMessage = NULL;
535 __block NSString* operation = nil;
536 NSString *messageString = nil;
537 __block NSData *messageData = nil;
539 NSArray *devices = [_service devices];
540 for(NSUInteger i = 0; i < [ devices count ]; i++){
541 IDSDevice *device = devices[i];
542 if( [(IDSCopyIDForDevice(device)) containsString: fromID] == YES){
543 ID = device.uniqueID;
547 require_action_quiet(ID, fail, hadError = true; errorMessage = CFSTR("require the sender's device ID"));
548 require_action_quiet([message count] == 1, fail, hadError = true; errorMessage = CFSTR("message contained too many objects"););
550 [message enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop){
551 operation = (NSString*)key;
552 messageData = (NSData*)obj;
555 operationType = [operation intValue];
557 switch(operationType){
558 case kIDSPeerAvailabilityDone:
560 secdebug("ids transport", "received availability done!");
561 notify_post(kSOSCCPeerAvailable);
564 case kIDSEndPingTestMessage:
565 secdebug("ids transport", "received pong message from other device: %@, ping test PASSED", ID);
567 case kIDSSendOneMessage:
568 secdebug("ids transport","received ping test message, dropping on the floor now");
571 case kIDSPeerAvailability:
572 case kIDSStartPingTestMessage:
575 if(operationType == kIDSPeerAvailability){
576 secdebug("ids transport", "Received Availability Message!");
577 asprintf(&messageCharS, "%d",kIDSPeerAvailabilityDone);
580 secdebug("ids transport", "Received PingTest Message!");
581 asprintf(&messageCharS, "%d", kIDSEndPingTestMessage);
584 NSString *operationString = [[NSString alloc] initWithUTF8String:messageCharS];
585 messageString = @"peer availability check finished";
586 NSDictionary* messsageDictionary = @{operationString : messageString};
588 NSError *localError = NULL;
589 [self sendIDSMessage:messsageDictionary name:ID peer:@"me" error:&localError];
597 NSDictionary *messageAndFromID = @{dataKey : messageData, deviceIDKey: ID};
599 //hang on to the message and set the retry deadline
600 [_unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
601 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxMessageRetryDelay);
604 __block CFErrorRef cf_error = NULL;
605 __block HandleIDSMessageReason success = kHandleIDSMessageSuccess;
606 _handleAllPendingMessages = YES;
608 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
610 success = SOSCCHandleIDSMessage(((__bridge CFDictionaryRef)messageAndFromID), &cf_error);
612 if(success == kHandleIDSMessageLocked){
613 secdebug("IDS Transport", "cannot handle messages when locked, error:%@", cf_error);
614 [_unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
616 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
617 _deadline = dispatch_time(DISPATCH_TIME_NOW, kAttemptFlushBufferInterval);
619 [self scheduleSyncRequestTimer];
622 else if(success == kHandleIDSMessageNotReady){
623 secdebug("IDS Transport", "not ready to handle message, error:%@", cf_error);
624 [_unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
625 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
626 _deadline = dispatch_time(DISPATCH_TIME_NOW, kAttemptFlushBufferInterval);
628 [self scheduleSyncRequestTimer];
631 else if(success == kHandleIDSMessageOtherFail){
632 secdebug("IDS Transport", "not ready to handle message, error:%@", cf_error);
633 [_unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
634 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
635 _deadline = dispatch_time(DISPATCH_TIME_NOW, kAttemptFlushBufferInterval);
637 [self scheduleSyncRequestTimer];
641 secdebug("IDS Transport", "IDSProxy handled this message! %@", messageAndFromID);
642 return (NSMutableDictionary*)messageAndFromID;
645 CFReleaseSafe(cf_error);
652 secerror("error:%@", errorMessage);