2 * Copyright (c) 2012-2017 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@
24 #import <Foundation/NSArray.h>
25 #import <Foundation/Foundation.h>
27 #import <Security/SecBasePriv.h>
28 #import <Security/SecItemPriv.h>
29 #import <utilities/debugging.h>
32 #include <Security/CKBridge/SOSCloudKeychainConstants.h>
33 #include <Security/SecureObjectSync/SOSARCDefines.h>
34 #include <Security/SecureObjectSync/SOSCloudCircle.h>
35 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
38 #import <os/activity.h>
40 #include <utilities/SecAKSWrappers.h>
41 #include <utilities/SecCFWrappers.h>
42 #include <utilities/SecCFRelease.h>
43 #include <AssertMacros.h>
46 #import "KeychainSyncingOverIDSProxy+ReceiveMessage.h"
47 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
48 #import "IDSPersistentState.h"
50 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
51 #define kSecServerPeerInfoAvailable "com.apple.security.fpiAvailable"
53 #define IDSServiceNameKeychainSync "com.apple.private.alloy.keychainsync"
54 static NSString *kMonitorState = @"MonitorState";
55 static NSString *kExportUnhandledMessages = @"UnhandledMessages";
56 static NSString *kMessagesInFlight = @"MessagesInFlight";
57 static const char *kStreamName = "com.apple.notifyd.matching";
58 static NSString *const kIDSMessageUseACKModel = @"UsesAckModel";
60 static NSString *const kOutgoingMessages = @"IDS Outgoing Messages";
61 static NSString *const kIncomingMessages = @"IDS Incoming Messages";
63 NSString *const IDSSendMessageOptionForceEncryptionOffKey = @"IDSSendMessageOptionForceEncryptionOff";
64 static const int64_t kRetryTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for handling unhandled messages.
65 static const int64_t kMinMessageRetryDelay = (NSEC_PER_SEC * 8);
67 CFIndex SECD_RUN_AS_ROOT_ERROR = 1041;
69 #define IDSPROXYSCOPE "IDSProxy"
71 @implementation KeychainSyncingOverIDSProxy
73 @synthesize deviceID = deviceID;
74 @synthesize deviceIDFromAuthToken = deviceIDFromAuthToken;
76 + (KeychainSyncingOverIDSProxy *) idsProxy
78 static KeychainSyncingOverIDSProxy *idsProxy;
80 static dispatch_once_t onceToken;
81 dispatch_once(&onceToken, ^{
82 idsProxy = [[self alloc] init];
88 -(NSDictionary*) exportState
90 return @{ kMonitorState:self.monitor,
91 kExportUnhandledMessages:self.unhandledMessageBuffer,
92 kMessagesInFlight:self.messagesInFlight
97 -(NSDictionary*) retrievePendingMessages
99 NSDictionary * __block messages;
100 dispatch_sync(self.dataQueue, ^{
101 messages = [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight copy];
106 @synthesize unhandledMessageBuffer = _unhandledMessageBuffer;
108 - (NSMutableDictionary *) unhandledMessageBuffer {
109 dispatch_assert_queue(self.dataQueue);
110 return _unhandledMessageBuffer;
113 - (void) setUnhandledMessageBuffer:(NSMutableDictionary *)unhandledMessageBuffer {
114 dispatch_assert_queue(self.dataQueue);
115 _unhandledMessageBuffer = unhandledMessageBuffer;
118 @ synthesize messagesInFlight = _messagesInFlight;
120 - (NSMutableDictionary *) messagesInFlight {
121 dispatch_assert_queue(self.dataQueue);
122 return _messagesInFlight;
125 - (void) setMessagesInFlight:(NSMutableDictionary *)messagesInFlight {
126 dispatch_assert_queue(self.dataQueue);
127 _messagesInFlight = messagesInFlight;
130 @synthesize monitor = _monitor;
132 - (NSMutableDictionary *) monitor {
133 dispatch_assert_queue(self.dataQueue);
137 - (void) setMonitor:(NSMutableDictionary *)monitor {
138 dispatch_assert_queue(self.dataQueue);
142 - (void) persistState
144 dispatch_sync(self.dataQueue, ^{
145 [KeychainSyncingOverIDSProxyPersistentState setUnhandledMessages:[self exportState]];
149 - (BOOL) haveMessagesInFlight {
150 BOOL __block inFlight = NO;
151 dispatch_sync(self.dataQueue, ^{
152 inFlight = [self.messagesInFlight count] > 0;
157 - (void) sendPersistedMessagesAgain
159 NSMutableDictionary * __block copy;
161 dispatch_sync(self.dataQueue, ^{
162 copy = [NSMutableDictionary dictionaryWithDictionary:self.messagesInFlight];
165 if(copy && [copy count] > 0){
166 [copy enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
167 NSDictionary* idsMessage = (NSDictionary*)obj;
168 NSString *uniqueMessageID = (NSString*)key;
170 NSString *peerID = (NSString*)[idsMessage objectForKey:(__bridge NSString*)kIDSMessageRecipientPeerID];
171 NSString *ID = (NSString*)[idsMessage objectForKey:(__bridge NSString*)kIDSMessageRecipientDeviceID];
173 dispatch_sync(self.dataQueue, ^{
174 [self.messagesInFlight removeObjectForKey:key];
177 if (!peerID || !ID) {
180 secnotice("IDS Transport", "sending this message: %@", idsMessage);
181 if([self sendIDSMessage:idsMessage name:ID peer:peerID]){
182 NSString *useAckModel = [idsMessage objectForKey:kIDSMessageUseACKModel];
183 if([useAckModel compare:@"YES"] == NSOrderedSame){
184 secnotice("IDS Transport", "setting timer!");
185 [self setMessageTimer:uniqueMessageID deviceID:ID message:idsMessage];
194 if (self = [super init])
196 secnotice("event", "%@ start", self);
198 _isIDSInitDone = false;
200 _calloutQueue = dispatch_queue_create("IDSCallout", DISPATCH_QUEUE_SERIAL);
201 _pingQueue = dispatch_queue_create("PingQueue", DISPATCH_QUEUE_SERIAL);
202 _dataQueue = dispatch_queue_create("DataQueue", DISPATCH_QUEUE_SERIAL);
203 _pingTimers = [[NSMutableDictionary alloc] init];
204 deviceIDFromAuthToken = [[NSMutableDictionary alloc] init];
205 _peerNextSendCache = [[NSMutableDictionary alloc] init];
206 _counterValues = [[NSMutableDictionary alloc] init];
207 _listOfDevices = [[NSMutableArray alloc] init];
208 _outgoingMessages = 0;
209 _incomingMessages = 0;
210 _isSecDRunningAsRoot = false;
211 _doesSecDHavePeer = true;
212 secdebug(IDSPROXYSCOPE, "%@ done", self);
214 [self doIDSInitialization];
216 [self doSetIDSDeviceID];
218 // Register for lock state changes
219 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
220 ^(xpc_object_t notification){
221 [self streamEvent:notification];
224 _retryTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
225 dispatch_source_set_timer(_retryTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
226 dispatch_source_set_event_handler(_retryTimer, ^{
229 dispatch_resume(_retryTimer);
230 NSMutableDictionary *state = [KeychainSyncingOverIDSProxyPersistentState idsState];
232 _unhandledMessageBuffer = state[kExportUnhandledMessages];
233 if (!_unhandledMessageBuffer) {
234 _unhandledMessageBuffer = [NSMutableDictionary dictionary];
236 _messagesInFlight = state[kMessagesInFlight];
237 if(_messagesInFlight == nil) {
238 _messagesInFlight = [NSMutableDictionary dictionary];
240 _monitor = state[kMonitorState];
241 if(_monitor == nil) {
242 _monitor = [NSMutableDictionary dictionary];
245 if([_messagesInFlight count ] > 0)
246 _sendRestoredMessages = true;
247 int notificationToken;
248 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, self.dataQueue,
249 ^ (int token __unused)
251 secinfo("backoff", "keychain changed, wiping backoff monitor state");
252 self.monitor = [NSMutableDictionary dictionary];
255 notify_register_dispatch(kSecServerPeerInfoAvailable, &peerInfo, dispatch_get_main_queue(),
256 ^ (int token __unused)
258 secinfo("IDS Transport", "secd has a peer info");
259 if(self.doesSecDHavePeer == false){
260 self.doesSecDHavePeer = true;
261 [self doSetIDSDeviceID];
265 [self updateUnlockedSinceBoot];
266 [self updateIsLocked];
268 [self keybagDidUnlock];
275 - (void)streamEvent:(xpc_object_t)notification
277 #if (!TARGET_IPHONE_SIMULATOR)
278 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
279 if (!notificationName) {
280 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
281 return [self keybagStateChange];
283 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
284 char *desc = xpc_copy_description(notification);
285 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
291 - (void) keybagDidLock
293 secnotice("IDS Transport", "%@ locking!", self);
296 - (void) keybagDidUnlock
298 secnotice("IDS Transport", "%@ unlocking!", self);
299 [self handleAllPendingMessage];
302 - (BOOL) updateUnlockedSinceBoot
304 CFErrorRef aksError = NULL;
305 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
306 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
307 CFReleaseSafe(aksError);
313 - (BOOL) updateIsLocked
315 CFErrorRef aksError = NULL;
316 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
317 secerror("%@ Got error querying lock state: %@", self, aksError);
318 CFReleaseSafe(aksError);
321 secerror("updateIsLocked: %d", _isLocked);
323 _unlockedSinceBoot = YES;
327 - (void) keybagStateChange
329 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
330 secerror("keybagStateChange! was locked: %d", self->_isLocked);
331 BOOL wasLocked = self->_isLocked;
332 if ([self updateIsLocked]) {
333 if (wasLocked == self->_isLocked)
334 secdebug("IDS Transport", "%@ still %s ignoring", self, self->_isLocked ? "locked" : "unlocked");
335 else if (self->_isLocked)
336 [self keybagDidLock];
338 [self keybagDidUnlock];
346 NSUInteger __block messagecount = 0;
347 dispatch_sync(self.dataQueue, ^{
348 if(self.unhandledMessageBuffer) {
349 secnotice("IDS Transport", "%@ attempting to hand unhandled messages to securityd, here is our message queue: %@", self, self.unhandledMessageBuffer);
350 messagecount = [self.unhandledMessageBuffer count];
355 self.retryTimerScheduled = NO;
356 } else if(messagecount == 0) {
357 self.retryTimerScheduled = NO;
358 } else if (self.retryTimerScheduled && !self.isLocked) {
359 [self handleAllPendingMessage];
361 [[KeychainSyncingOverIDSProxy idsProxy] scheduleRetryRequestTimer];
365 - (void)scheduleRetryRequestTimer
367 secnotice("IDS Transport", "scheduling unhandled messages timer");
368 dispatch_source_set_timer(_retryTimer, dispatch_time(DISPATCH_TIME_NOW, kMinMessageRetryDelay), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
369 _retryTimerScheduled = YES;
372 - (void)doIDSInitialization
375 dispatch_async(self.calloutQueue, ^{
376 secnotice("IDS Transport", "doIDSInitialization!");
378 self->_service = [[IDSService alloc] initWithService: @IDSServiceNameKeychainSync];
380 if( self->_service == nil ){
381 self->_isIDSInitDone = false;
382 secerror("Could not create ids service");
385 secnotice("IDS Transport", "IDS Transport Successfully set up IDS!");
386 [self->_service addDelegate:self queue: dispatch_get_main_queue()];
388 self->_isIDSInitDone = true;
389 if(self->_isSecDRunningAsRoot == false)
390 [self doSetIDSDeviceID];
392 NSArray *ListOfIDSDevices = [self->_service devices];
393 self.listOfDevices = ListOfIDSDevices;
395 for(NSUInteger i = 0; i < [ self.listOfDevices count ]; i++){
396 IDSDevice *device = self.listOfDevices[i];
397 NSString *authToken = IDSCopyIDForDevice(device);
398 [self.deviceIDFromAuthToken setObject:device.uniqueID forKey:authToken];
404 - (void) doSetIDSDeviceID
407 NSString *errorMessage = nil;
410 [self doIDSInitialization];
412 _setIDSDeviceID = YES;
414 if(_isSecDRunningAsRoot != false)
416 errorMessage = @"cannot set IDS device ID, secd is running as root";
417 code = SECD_RUN_AS_ROOT_ERROR;
418 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
421 else if(_doesSecDHavePeer != true)
423 errorMessage = @"cannot set IDS deviceID, secd does not have a full peer info for account";
424 code = kSOSErrorPeerNotFound;
425 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
428 else if(!_isIDSInitDone){
429 errorMessage = @"KeychainSyncingOverIDSProxy can't set up the IDS service";
430 code = kSecIDSErrorNotRegistered;
431 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
434 errorMessage = @"KeychainSyncingOverIDSProxy can't set device ID, device is locked";
435 code = kSecIDSErrorDeviceIsLocked;
436 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
440 [self calloutWith:^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
441 CFErrorRef localError = NULL;
442 bool handledSettingID = false;
443 NSString *ID = IDSCopyLocalDeviceUniqueID();
447 handledSettingID = SOSCCSetDeviceID((__bridge CFStringRef) ID, &localError);
449 if(!handledSettingID && localError != NULL){
450 if(CFErrorGetCode(localError) == SECD_RUN_AS_ROOT_ERROR){
451 secerror("SETTING RUN AS ROOT ERROR: %@", localError);
452 self->_isSecDRunningAsRoot = true;
454 else if (CFErrorIsMalfunctioningKeybagError(localError)) {
455 secnotice("IDS Transport", "system is unavailable, cannot set device ID, error: %@", localError);
456 self->_isLocked = true;
458 else if (CFErrorGetCode(localError) == kSOSErrorPeerNotFound && CFStringCompare(CFErrorGetDomain(localError), kSOSErrorDomain, 0) == 0){
459 secnotice("IDS Transport","securityd does not have a peer yet , error: %@", localError);
460 self->_doesSecDHavePeer = false;
464 self->_setIDSDeviceID = NO;
466 CFReleaseNull(localError);
467 dispatch_async(queue, ^{
471 dispatch_async(queue, ^{
475 if(errorMessage != nil){
476 secerror("Setting device ID error: KeychainSyncingOverIDSProxy could not retrieve device ID from keychain, code: %ld", (long)kSecIDSErrorNoDeviceID);
482 - (void) calloutWith: (void(^)(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSettingDeviceID))) callout
484 // In KeychainSyncingOverIDSProxy serial queue
485 dispatch_queue_t idsproxy_queue = dispatch_get_main_queue();
487 // dispatch_get_global_queue - well-known global concurrent queue
488 // dispatch_get_main_queue - default queue that is bound to the main thread
489 xpc_transaction_begin();
490 dispatch_async(_calloutQueue, ^{
491 __block NSMutableDictionary *myPending;
492 __block bool myHandlePendingMessage;
493 __block bool myDoSetDeviceID;
494 __block bool wasLocked;
495 dispatch_sync(idsproxy_queue, ^{
496 dispatch_sync(self.dataQueue, ^{
497 myPending = [self.unhandledMessageBuffer copy];
499 myHandlePendingMessage = self.handleAllPendingMessages;
500 myDoSetDeviceID = self.setIDSDeviceID;
501 wasLocked = self.isLocked;
503 self.inCallout = YES;
505 self.shadowHandleAllPendingMessages = NO;
508 callout(myPending, myHandlePendingMessage, myDoSetDeviceID, idsproxy_queue, ^(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSetDeviceID) {
509 secdebug("event", "%@ %s%s before callout handled: %s%s", self, myHandlePendingMessage ? "P" : "p", myDoSetDeviceID ? "D" : "d", handledPendingMessage ? "H" : "h", handledSetDeviceID ? "I" : "i");
511 // In IDSKeychainSyncingProxy's serial queue
512 self->_inCallout = NO;
514 // Update setting device id
515 self->_setIDSDeviceID = ((myDoSetDeviceID && !handledSetDeviceID));
517 self->_shadowDoSetIDSDeviceID = NO;
519 xpc_transaction_end();
524 - (void) sendKeysCallout: (NSMutableDictionary*(^)(NSMutableDictionary* pending, NSError** error)) handleMessages {
525 [self calloutWith: ^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
526 NSError* error = NULL;
528 NSMutableDictionary* handled = handleMessages(pending, &error);
530 dispatch_async(queue, ^{
531 if (!handled && error) {
532 secerror("%@ did not handle message: %@", self, error);
535 done(handled, NO, NO);
540 NSString* createErrorString(NSString* format, ...)
543 va_start(va, format);
544 NSString* errorString = ([[NSString alloc] initWithFormat:format arguments:va]);
550 - (NSDictionary*) collectStats{
551 [_counterValues setObject:[NSNumber numberWithInteger:[KeychainSyncingOverIDSProxy idsProxy].outgoingMessages] forKey:kOutgoingMessages];
552 [_counterValues setObject:[NSNumber numberWithInteger:[KeychainSyncingOverIDSProxy idsProxy].incomingMessages] forKey:kIncomingMessages];
554 return _counterValues;