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/SecCFWrappers.h>
48 #include <utilities/SecCFRelease.h>
49 #include <AssertMacros.h>
52 #import "KeychainSyncingOverIDSProxy+ReceiveMessage.h"
53 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
54 #import "KeychainSyncingOverIDSProxy+Throttle.h"
55 #import "IDSPersistentState.h"
57 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
58 #define kSecServerPeerInfoAvailable "com.apple.security.fpiAvailable"
60 #define IDSServiceNameKeychainSync "com.apple.private.alloy.keychainsync"
61 static NSString *kMonitorState = @"MonitorState";
62 static NSString *kExportUnhandledMessages = @"UnhandledMessages";
63 static NSString *kMessagesInFlight = @"MessagesInFlight";
64 static const char *kStreamName = "com.apple.notifyd.matching";
65 static NSString *const kIDSMessageRecipientPeerID = @"RecipientPeerID";
66 static NSString *const kIDSMessageRecipientDeviceID = @"RecipientDeviceID";
67 static NSString *const kIDSMessageUseACKModel = @"UsesAckModel";
69 NSString *const IDSSendMessageOptionForceEncryptionOffKey = @"IDSSendMessageOptionForceEncryptionOff";
70 static const int64_t kRetryTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for handling unhandled messages.
71 static const int64_t kMinMessageRetryDelay = (NSEC_PER_SEC * 8);
74 CFIndex kSOSErrorPeerNotFound = 1032;
75 CFIndex SECD_RUN_AS_ROOT_ERROR = 1041;
77 #define IDSPROXYSCOPE "IDSProxy"
79 @implementation KeychainSyncingOverIDSProxy
81 @synthesize deviceID = deviceID;
82 @synthesize deviceIDFromAuthToken = deviceIDFromAuthToken;
84 + (KeychainSyncingOverIDSProxy *) idsProxy
86 static KeychainSyncingOverIDSProxy *idsProxy;
88 static dispatch_once_t onceToken;
89 dispatch_once(&onceToken, ^{
90 idsProxy = [[self alloc] init];
96 -(NSDictionary*) exportState
98 return @{ kMonitorState:_monitor,
99 kExportUnhandledMessages:_unhandledMessageBuffer,
100 kMessagesInFlight:_messagesInFlight
105 -(NSDictionary*) retrievePendingMessages
107 return [KeychainSyncingOverIDSProxy idsProxy].messagesInFlight;
112 [KeychainSyncingOverIDSProxyPersistentState setUnhandledMessages:[self exportState]];
115 - (void) sendPersistedMessagesAgain
117 NSMutableDictionary *copy = [NSMutableDictionary dictionaryWithDictionary:_messagesInFlight];
119 if(copy && [copy count] > 0){
120 [copy enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
121 NSDictionary* idsMessage = (NSDictionary*)obj;
122 NSString *uniqueMessageID = (NSString*)key;
124 NSString *peerID = (NSString*)[idsMessage objectForKey:kIDSMessageRecipientPeerID];
126 [self->_messagesInFlight removeObjectForKey:key];
129 NSString *ID = (NSString*)[idsMessage objectForKey:kIDSMessageRecipientDeviceID];
131 [self->_messagesInFlight removeObjectForKey:key];
135 [self->_messagesInFlight removeObjectForKey:key];
136 secnotice("IDS Transport", "sending this message: %@", idsMessage);
137 if([self sendIDSMessage:idsMessage name:ID peer:peerID]){
138 NSString *useAckModel = [idsMessage objectForKey:kIDSMessageUseACKModel];
139 if([useAckModel compare:@"YES"] == NSOrderedSame){
140 secnotice("IDS Transport", "setting timer!");
141 [self setMessageTimer:uniqueMessageID deviceID:ID message:idsMessage];
148 - (void) importIDSState: (NSMutableDictionary*) state
150 _unhandledMessageBuffer = state[kExportUnhandledMessages];
151 if(!_unhandledMessageBuffer)
152 _unhandledMessageBuffer = [NSMutableDictionary dictionary];
154 _monitor = state[kMonitorState];
156 _monitor = [NSMutableDictionary dictionary];
158 _messagesInFlight = state[kMessagesInFlight];
159 if(_messagesInFlight == nil)
160 _messagesInFlight = [NSMutableDictionary dictionary];
165 if (self = [super init])
167 secnotice("event", "%@ start", self);
169 _isIDSInitDone = false;
171 _calloutQueue = dispatch_queue_create("IDSCallout", DISPATCH_QUEUE_SERIAL);
172 _unhandledMessageBuffer = [ [NSMutableDictionary alloc] initWithCapacity: 0];
173 _pingTimers = [ [NSMutableDictionary alloc] initWithCapacity: 0];
174 _messagesInFlight = [ [NSMutableDictionary alloc] initWithCapacity: 0];
175 deviceIDFromAuthToken = [ [NSMutableDictionary alloc] initWithCapacity: 0];
176 _peerNextSendCache = [ [NSMutableDictionary alloc] initWithCapacity: 0];
178 _listOfDevices = [[NSMutableArray alloc] initWithCapacity:0];
179 _isSecDRunningAsRoot = false;
180 _doesSecDHavePeer = true;
181 secdebug(IDSPROXYSCOPE, "%@ done", self);
183 [self doIDSInitialization];
185 [self doSetIDSDeviceID];
188 // Register for lock state changes
189 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
190 ^(xpc_object_t notification){
191 [self streamEvent:notification];
194 _retryTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
195 dispatch_source_set_timer(_retryTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
196 dispatch_source_set_event_handler(_retryTimer, ^{
199 dispatch_resume(_retryTimer);
200 [self importIDSState: [KeychainSyncingOverIDSProxyPersistentState idsState]];
202 if([_messagesInFlight count ] > 0)
203 _sendRestoredMessages = true;
204 int notificationToken;
205 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, dispatch_get_main_queue(),
206 ^ (int token __unused)
208 secinfo("backoff", "keychain changed, wiping backoff monitor state");
209 self->_monitor = [NSMutableDictionary dictionary];
212 notify_register_dispatch(kSecServerPeerInfoAvailable, &peerInfo, dispatch_get_main_queue(),
213 ^ (int token __unused)
215 secinfo("IDS Transport", "secd has a peer info");
216 if(self->_doesSecDHavePeer == false){
217 self->_doesSecDHavePeer = true;
218 [self doSetIDSDeviceID];
222 [self updateUnlockedSinceBoot];
223 [self updateIsLocked];
225 [self keybagDidUnlock];
232 - (void)streamEvent:(xpc_object_t)notification
234 #if (!TARGET_IPHONE_SIMULATOR)
235 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
236 if (!notificationName) {
237 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
238 return [self keybagStateChange];
240 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
241 char *desc = xpc_copy_description(notification);
242 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
248 - (void) keybagDidLock
250 secnotice("IDS Transport", "%@ locking!", self);
253 - (void) keybagDidUnlock
255 secnotice("IDS Transport", "%@ unlocking!", self);
256 [self handleAllPendingMessage];
259 - (BOOL) updateUnlockedSinceBoot
261 CFErrorRef aksError = NULL;
262 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
263 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
264 CFReleaseSafe(aksError);
270 - (BOOL) updateIsLocked
272 CFErrorRef aksError = NULL;
273 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
274 secerror("%@ Got error querying lock state: %@", self, aksError);
275 CFReleaseSafe(aksError);
278 secerror("updateIsLocked: %d", _isLocked);
280 _unlockedSinceBoot = YES;
284 - (void) keybagStateChange
286 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
287 secerror("keybagStateChange! was locked: %d", self->_isLocked);
288 BOOL wasLocked = self->_isLocked;
289 if ([self updateIsLocked]) {
290 if (wasLocked == self->_isLocked)
291 secdebug("IDS Transport", "%@ still %s ignoring", self, self->_isLocked ? "locked" : "unlocked");
292 else if (self->_isLocked)
293 [self keybagDidLock];
295 [self keybagDidUnlock];
303 if(_unhandledMessageBuffer)
304 secnotice("IDS Transport", "%@ attempting to hand unhandled messages to securityd, here is our message queue: %@", self, _unhandledMessageBuffer);
307 _retryTimerScheduled = NO;
308 else if([_unhandledMessageBuffer count] == 0)
309 _retryTimerScheduled = NO;
310 else if (_retryTimerScheduled && !_isLocked)
311 [self handleAllPendingMessage];
313 [[KeychainSyncingOverIDSProxy idsProxy] scheduleRetryRequestTimer];
317 - (void)scheduleRetryRequestTimer
319 secnotice("IDS Transport", "scheduling unhandled messages timer");
320 dispatch_source_set_timer(_retryTimer, dispatch_time(DISPATCH_TIME_NOW, kMinMessageRetryDelay), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
321 _retryTimerScheduled = YES;
324 - (void)doIDSInitialization
327 dispatch_async(self.calloutQueue, ^{
328 secnotice("IDS Transport", "doIDSInitialization!");
330 self->_service = [[IDSService alloc] initWithService: @IDSServiceNameKeychainSync];
332 if( self->_service == nil ){
333 self->_isIDSInitDone = false;
334 secerror("Could not create ids service");
337 secnotice("IDS Transport", "IDS Transport Successfully set up IDS!");
338 [self->_service addDelegate:self queue: dispatch_get_main_queue()];
340 self->_isIDSInitDone = true;
341 if(self->_isSecDRunningAsRoot == false)
342 [self doSetIDSDeviceID];
344 NSArray *ListOfIDSDevices = [self->_service devices];
345 self.listOfDevices = ListOfIDSDevices;
347 for(NSUInteger i = 0; i < [ self.listOfDevices count ]; i++){
348 IDSDevice *device = self.listOfDevices[i];
349 NSString *authToken = IDSCopyIDForDevice(device);
350 [self.deviceIDFromAuthToken setObject:device.uniqueID forKey:authToken];
356 - (void) doSetIDSDeviceID
359 NSString *errorMessage = nil;
362 [self doIDSInitialization];
364 _setIDSDeviceID = YES;
366 if(_isSecDRunningAsRoot != false)
368 errorMessage = @"cannot set IDS device ID, secd is running as root";
369 code = SECD_RUN_AS_ROOT_ERROR;
370 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
373 else if(_doesSecDHavePeer != true)
375 errorMessage = @"cannot set IDS deviceID, secd does not have a full peer info for account";
376 code = kSOSErrorPeerNotFound;
377 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
380 else if(!_isIDSInitDone){
381 errorMessage = @"KeychainSyncingOverIDSProxy can't set up the IDS service";
382 code = kSecIDSErrorNotRegistered;
383 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
386 errorMessage = @"KeychainSyncingOverIDSProxy can't set device ID, device is locked";
387 code = kSecIDSErrorDeviceIsLocked;
388 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
392 [self calloutWith:^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
393 CFErrorRef localError = NULL;
394 bool handledSettingID = false;
395 NSString *ID = IDSCopyLocalDeviceUniqueID();
399 handledSettingID = SOSCCSetDeviceID((__bridge CFStringRef) ID, &localError);
401 if(!handledSettingID && localError != NULL){
402 if(CFErrorGetCode(localError) == SECD_RUN_AS_ROOT_ERROR){
403 secerror("SETTING RUN AS ROOT ERROR: %@", localError);
404 self->_isSecDRunningAsRoot = true;
406 else if (CFErrorIsMalfunctioningKeybagError(localError)) {
407 secnotice("IDS Transport", "system is unavailable, cannot set device ID, error: %@", localError);
408 self->_isLocked = true;
410 else if (CFErrorGetCode(localError) == kSOSErrorPeerNotFound && CFStringCompare(CFErrorGetDomain(localError), kSOSErrorDomain, 0) == 0){
411 secnotice("IDS Transport","securityd does not have a peer yet , error: %@", localError);
412 self->_doesSecDHavePeer = false;
416 self->_setIDSDeviceID = NO;
418 CFReleaseNull(localError);
419 dispatch_async(queue, ^{
423 dispatch_async(queue, ^{
427 if(errorMessage != nil){
428 secerror("Setting device ID error: KeychainSyncingOverIDSProxy could not retrieve device ID from keychain, code: %ld", (long)kSecIDSErrorNoDeviceID);
434 - (void) calloutWith: (void(^)(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSettingDeviceID))) callout
436 // In KeychainSyncingOverIDSProxy serial queue
437 dispatch_queue_t idsproxy_queue = dispatch_get_main_queue();
439 // dispatch_get_global_queue - well-known global concurrent queue
440 // dispatch_get_main_queue - default queue that is bound to the main thread
441 xpc_transaction_begin();
442 dispatch_async(_calloutQueue, ^{
443 __block NSMutableDictionary *myPending;
444 __block bool myHandlePendingMessage;
445 __block bool myDoSetDeviceID;
446 __block bool wasLocked;
447 dispatch_sync(idsproxy_queue, ^{
448 myPending = [self->_unhandledMessageBuffer copy];
449 myHandlePendingMessage = self->_handleAllPendingMessages;
450 myDoSetDeviceID = self->_setIDSDeviceID;
451 wasLocked = self->_isLocked;
453 self->_inCallout = YES;
455 self->_shadowHandleAllPendingMessages = NO;
458 callout(myPending, myHandlePendingMessage, myDoSetDeviceID, idsproxy_queue, ^(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSetDeviceID) {
459 secdebug("event", "%@ %s%s before callout handled: %s%s", self, myHandlePendingMessage ? "P" : "p", myDoSetDeviceID ? "D" : "d", handledPendingMessage ? "H" : "h", handledSetDeviceID ? "I" : "i");
461 // In IDSKeychainSyncingProxy's serial queue
462 self->_inCallout = NO;
464 // Update setting device id
465 self->_setIDSDeviceID = ((myDoSetDeviceID && !handledSetDeviceID));
467 self->_shadowDoSetIDSDeviceID = NO;
469 xpc_transaction_end();
474 - (void) sendKeysCallout: (NSMutableDictionary*(^)(NSMutableDictionary* pending, NSError** error)) handleMessages {
475 [self calloutWith: ^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
476 NSError* error = NULL;
478 NSMutableDictionary* handled = handleMessages(pending, &error);
480 dispatch_async(queue, ^{
481 if (!handled && error) {
482 secerror("%@ did not handle message: %@", self, error);
485 done(handled, NO, NO);
490 NSString* createErrorString(NSString* format, ...)
493 va_start(va, format);
494 NSString* errorString = ([[NSString alloc] initWithFormat:format arguments:va]);