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";
59 static NSString *const kIDSNumberOfFragments = @"NumberOfIDSMessageFragments";
60 static NSString *const kIDSFragmentIndex = @"kFragmentIndex";
62 static NSString *const kOutgoingMessages = @"IDS Outgoing Messages";
63 static NSString *const kIncomingMessages = @"IDS Incoming Messages";
65 NSString *const IDSSendMessageOptionForceEncryptionOffKey = @"IDSSendMessageOptionForceEncryptionOff";
66 static const int64_t kRetryTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for handling unhandled messages.
67 static const int64_t kMinMessageRetryDelay = (NSEC_PER_SEC * 8);
69 CFIndex SECD_RUN_AS_ROOT_ERROR = 1041;
71 #define IDSPROXYSCOPE "IDSProxy"
73 @implementation KeychainSyncingOverIDSProxy
75 @synthesize deviceID = deviceID;
76 @synthesize deviceIDFromAuthToken = deviceIDFromAuthToken;
78 + (KeychainSyncingOverIDSProxy *) idsProxy
80 static KeychainSyncingOverIDSProxy *idsProxy;
82 static dispatch_once_t onceToken;
83 dispatch_once(&onceToken, ^{
84 idsProxy = [[self alloc] init];
90 -(NSDictionary*) exportState
92 return @{ kMonitorState:self.monitor,
93 kExportUnhandledMessages:self.unhandledMessageBuffer,
94 kMessagesInFlight:self.messagesInFlight
99 -(NSDictionary*) retrievePendingMessages
101 NSDictionary * __block messages;
102 dispatch_sync(self.dataQueue, ^{
103 messages = [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight copy];
108 @synthesize unhandledMessageBuffer = _unhandledMessageBuffer;
110 - (NSMutableDictionary *) unhandledMessageBuffer {
111 dispatch_assert_queue(self.dataQueue);
112 return _unhandledMessageBuffer;
115 - (void) setUnhandledMessageBuffer:(NSMutableDictionary *)unhandledMessageBuffer {
116 dispatch_assert_queue(self.dataQueue);
117 _unhandledMessageBuffer = unhandledMessageBuffer;
120 @ synthesize messagesInFlight = _messagesInFlight;
122 - (NSMutableDictionary *) messagesInFlight {
123 dispatch_assert_queue(self.dataQueue);
124 return _messagesInFlight;
127 - (void) setMessagesInFlight:(NSMutableDictionary *)messagesInFlight {
128 dispatch_assert_queue(self.dataQueue);
129 _messagesInFlight = messagesInFlight;
132 @synthesize monitor = _monitor;
134 - (NSMutableDictionary *) monitor {
135 dispatch_assert_queue(self.dataQueue);
139 - (void) setMonitor:(NSMutableDictionary *)monitor {
140 dispatch_assert_queue(self.dataQueue);
144 - (void) persistState
146 dispatch_sync(self.dataQueue, ^{
147 [KeychainSyncingOverIDSProxyPersistentState setUnhandledMessages:[self exportState]];
151 - (BOOL) haveMessagesInFlight {
152 BOOL __block inFlight = NO;
153 dispatch_sync(self.dataQueue, ^{
154 inFlight = [self.messagesInFlight count] > 0;
159 - (void) sendPersistedMessagesAgain
161 NSMutableDictionary * __block copy;
163 dispatch_sync(self.dataQueue, ^{
164 copy = [NSMutableDictionary dictionaryWithDictionary:self.messagesInFlight];
167 if(copy && [copy count] > 0){
168 [copy enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
169 NSDictionary* idsMessage = (NSDictionary*)obj;
170 NSString *uniqueMessageID = (NSString*)key;
172 NSString *peerID = (NSString*)[idsMessage objectForKey:(__bridge NSString*)kIDSMessageRecipientPeerID];
173 NSString *ID = (NSString*)[idsMessage objectForKey:(__bridge NSString*)kIDSMessageRecipientDeviceID];
174 NSString *senderDeviceID = (NSString*)[idsMessage objectForKey:(__bridge NSString*)kIDSMessageSenderDeviceID];
175 dispatch_sync(self.dataQueue, ^{
176 [self.messagesInFlight removeObjectForKey:key];
179 if (!peerID || !ID) {
182 [self printMessage:idsMessage state:@"sending persisted message"];
184 if([self sendIDSMessage:idsMessage name:ID peer:peerID senderDeviceID:senderDeviceID]){
185 NSString *useAckModel = [idsMessage objectForKey:kIDSMessageUseACKModel];
186 if([useAckModel compare:@"YES"] == NSOrderedSame && [KeychainSyncingOverIDSProxy idsProxy].allowKVSFallBack){
187 secnotice("IDS Transport", "setting timer!");
188 [self setMessageTimer:uniqueMessageID deviceID:ID message:idsMessage];
197 if (self = [super init])
199 secnotice("event", "%@ start", self);
201 _isIDSInitDone = false;
203 _calloutQueue = dispatch_queue_create("IDSCallout", DISPATCH_QUEUE_SERIAL);
204 _pingQueue = dispatch_queue_create("PingQueue", DISPATCH_QUEUE_SERIAL);
205 _dataQueue = dispatch_queue_create("DataQueue", DISPATCH_QUEUE_SERIAL);
206 _pingTimers = [[NSMutableDictionary alloc] init];
207 deviceIDFromAuthToken = [[NSMutableDictionary alloc] init];
208 _peerNextSendCache = [[NSMutableDictionary alloc] init];
209 _counterValues = [[NSMutableDictionary alloc] init];
210 _outgoingMessages = 0;
211 _incomingMessages = 0;
212 _isSecDRunningAsRoot = false;
213 _doesSecDHavePeer = true;
214 _allowKVSFallBack = true;
215 secdebug(IDSPROXYSCOPE, "%@ done", self);
217 [self doIDSInitialization];
219 [self doSetIDSDeviceID];
221 // Register for lock state changes
222 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
223 ^(xpc_object_t notification){
224 [self streamEvent:notification];
227 _retryTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
228 dispatch_source_set_timer(_retryTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
229 dispatch_source_set_event_handler(_retryTimer, ^{
232 dispatch_resume(_retryTimer);
233 NSMutableDictionary *state = [KeychainSyncingOverIDSProxyPersistentState idsState];
235 _unhandledMessageBuffer = state[kExportUnhandledMessages];
236 if (!_unhandledMessageBuffer) {
237 _unhandledMessageBuffer = [NSMutableDictionary dictionary];
239 _messagesInFlight = state[kMessagesInFlight];
240 if(_messagesInFlight == nil) {
241 _messagesInFlight = [NSMutableDictionary dictionary];
243 _monitor = state[kMonitorState];
244 if(_monitor == nil) {
245 _monitor = [NSMutableDictionary dictionary];
248 if([_messagesInFlight count ] > 0)
249 _sendRestoredMessages = true;
250 int notificationToken;
251 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, self.dataQueue,
252 ^ (int token __unused)
254 secinfo("backoff", "keychain changed, wiping backoff monitor state");
255 self.monitor = [NSMutableDictionary dictionary];
258 notify_register_dispatch(kSecServerPeerInfoAvailable, &peerInfo, dispatch_get_main_queue(),
259 ^ (int token __unused)
261 secinfo("IDS Transport", "secd has a peer info");
262 if(self.doesSecDHavePeer == false){
263 self.doesSecDHavePeer = true;
264 [self doSetIDSDeviceID];
268 [self updateUnlockedSinceBoot];
269 [self updateIsLocked];
271 [self keybagDidUnlock];
278 - (void)streamEvent:(xpc_object_t)notification
280 #if (!TARGET_IPHONE_SIMULATOR)
281 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
282 if (!notificationName) {
283 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
284 return [self keybagStateChange];
286 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
287 char *desc = xpc_copy_description(notification);
288 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
294 - (void) keybagDidLock
296 secnotice("IDS Transport", "%@ locking!", self);
299 - (void) keybagDidUnlock
301 secnotice("IDS Transport", "%@ unlocking!", self);
302 [self handleAllPendingMessage];
305 - (BOOL) updateUnlockedSinceBoot
307 CFErrorRef aksError = NULL;
308 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
309 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
310 CFReleaseSafe(aksError);
316 - (BOOL) updateIsLocked
318 CFErrorRef aksError = NULL;
319 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
320 secerror("%@ Got error querying lock state: %@", self, aksError);
321 CFReleaseSafe(aksError);
324 secerror("updateIsLocked: %d", _isLocked);
326 _unlockedSinceBoot = YES;
330 - (void) keybagStateChange
332 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
333 secerror("keybagStateChange! was locked: %d", self->_isLocked);
334 BOOL wasLocked = self->_isLocked;
335 if ([self updateIsLocked]) {
336 if (wasLocked == self->_isLocked)
337 secdebug("IDS Transport", "%@ still %s ignoring", self, self->_isLocked ? "locked" : "unlocked");
338 else if (self->_isLocked)
339 [self keybagDidLock];
341 [self keybagDidUnlock];
349 NSUInteger __block messagecount = 0;
350 dispatch_sync(self.dataQueue, ^{
351 if(self.unhandledMessageBuffer) {
352 secnotice("IDS Transport", "%@ attempting to hand unhandled messages to securityd, here is our message queue: %@", self, self.unhandledMessageBuffer);
353 messagecount = [self.unhandledMessageBuffer count];
358 self.retryTimerScheduled = NO;
359 } else if(messagecount == 0) {
360 self.retryTimerScheduled = NO;
361 } else if (self.retryTimerScheduled && !self.isLocked) {
362 [self handleAllPendingMessage];
364 [[KeychainSyncingOverIDSProxy idsProxy] scheduleRetryRequestTimer];
368 - (void)scheduleRetryRequestTimer
370 secnotice("IDS Transport", "scheduling unhandled messages timer");
371 dispatch_source_set_timer(_retryTimer, dispatch_time(DISPATCH_TIME_NOW, kMinMessageRetryDelay), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
372 _retryTimerScheduled = YES;
375 - (void)doIDSInitialization
378 dispatch_async(self.calloutQueue, ^{
379 secnotice("IDS Transport", "doIDSInitialization!");
381 self->_service = [[IDSService alloc] initWithService: @IDSServiceNameKeychainSync];
383 if( self->_service == nil ){
384 self->_isIDSInitDone = false;
385 secerror("Could not create ids service");
388 secnotice("IDS Transport", "IDS Transport Successfully set up IDS!");
389 [self->_service addDelegate:self queue: dispatch_get_main_queue()];
391 self->_isIDSInitDone = true;
392 if(self->_isSecDRunningAsRoot == false)
393 [self doSetIDSDeviceID];
398 - (void) doSetIDSDeviceID
401 NSString *errorMessage = nil;
404 [self doIDSInitialization];
406 _setIDSDeviceID = YES;
408 if(_isSecDRunningAsRoot != false)
410 errorMessage = @"cannot set IDS device ID, secd is running as root";
411 code = SECD_RUN_AS_ROOT_ERROR;
412 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
415 else if(_doesSecDHavePeer != true)
417 errorMessage = @"cannot set IDS deviceID, secd does not have a full peer info for account";
418 code = kSOSErrorPeerNotFound;
419 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
422 else if(!_isIDSInitDone){
423 errorMessage = @"KeychainSyncingOverIDSProxy can't set up the IDS service";
424 code = kSecIDSErrorNotRegistered;
425 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
428 errorMessage = @"KeychainSyncingOverIDSProxy can't set device ID, device is locked";
429 code = kSecIDSErrorDeviceIsLocked;
430 secerror("Setting device ID error: %@, code: %ld", errorMessage, (long)code);
434 [self calloutWith:^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
435 CFErrorRef localError = NULL;
436 bool handledSettingID = false;
437 NSString *ID = IDSCopyLocalDeviceUniqueID();
441 handledSettingID = SOSCCSetDeviceID((__bridge CFStringRef) ID, &localError);
443 if(!handledSettingID && localError != NULL){
444 if(CFErrorGetCode(localError) == SECD_RUN_AS_ROOT_ERROR){
445 secerror("SETTING RUN AS ROOT ERROR: %@", localError);
446 self->_isSecDRunningAsRoot = true;
448 else if (CFErrorIsMalfunctioningKeybagError(localError)) {
449 secnotice("IDS Transport", "system is unavailable, cannot set device ID, error: %@", localError);
450 self->_isLocked = true;
452 else if (CFErrorGetCode(localError) == kSOSErrorPeerNotFound && CFStringCompare(CFErrorGetDomain(localError), kSOSErrorDomain, 0) == 0){
453 secnotice("IDS Transport","securityd does not have a peer yet , error: %@", localError);
454 self->_doesSecDHavePeer = false;
458 self->_setIDSDeviceID = NO;
460 CFReleaseNull(localError);
461 dispatch_async(queue, ^{
465 dispatch_async(queue, ^{
469 if(errorMessage != nil){
470 secerror("Setting device ID error: KeychainSyncingOverIDSProxy could not retrieve device ID from keychain, code: %ld", (long)kSecIDSErrorNoDeviceID);
476 - (void) calloutWith: (void(^)(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSettingDeviceID))) callout
478 // In KeychainSyncingOverIDSProxy serial queue
479 dispatch_queue_t idsproxy_queue = dispatch_get_main_queue();
481 // dispatch_get_global_queue - well-known global concurrent queue
482 // dispatch_get_main_queue - default queue that is bound to the main thread
483 xpc_transaction_begin();
484 dispatch_async(_calloutQueue, ^{
485 __block NSMutableDictionary *myPending;
486 __block bool myHandlePendingMessage;
487 __block bool myDoSetDeviceID;
488 __block bool wasLocked;
489 dispatch_sync(idsproxy_queue, ^{
490 dispatch_sync(self.dataQueue, ^{
491 myPending = [self.unhandledMessageBuffer copy];
493 myHandlePendingMessage = self.handleAllPendingMessages;
494 myDoSetDeviceID = self.setIDSDeviceID;
495 wasLocked = self.isLocked;
497 self.inCallout = YES;
499 self.shadowHandleAllPendingMessages = NO;
502 callout(myPending, myHandlePendingMessage, myDoSetDeviceID, idsproxy_queue, ^(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSetDeviceID) {
503 secdebug("event", "%@ %s%s before callout handled: %s%s", self, myHandlePendingMessage ? "P" : "p", myDoSetDeviceID ? "D" : "d", handledPendingMessage ? "H" : "h", handledSetDeviceID ? "I" : "i");
505 // In IDSKeychainSyncingProxy's serial queue
506 self->_inCallout = NO;
508 // Update setting device id
509 self->_setIDSDeviceID = ((myDoSetDeviceID && !handledSetDeviceID));
511 self->_shadowDoSetIDSDeviceID = NO;
513 xpc_transaction_end();
518 - (void) sendKeysCallout: (NSMutableDictionary*(^)(NSMutableDictionary* pending, NSError** error)) handleMessages {
519 [self calloutWith: ^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
520 NSError* error = NULL;
522 NSMutableDictionary* handled = handleMessages(pending, &error);
524 dispatch_async(queue, ^{
525 if (!handled && error) {
526 secerror("%@ did not handle message: %@", self, error);
529 done(handled, NO, NO);
534 NSString* createErrorString(NSString* format, ...)
537 va_start(va, format);
538 NSString* errorString = ([[NSString alloc] initWithFormat:format arguments:va]);
544 - (NSDictionary*) collectStats{
545 [_counterValues setObject:[NSNumber numberWithInteger:[KeychainSyncingOverIDSProxy idsProxy].outgoingMessages] forKey:kOutgoingMessages];
546 [_counterValues setObject:[NSNumber numberWithInteger:[KeychainSyncingOverIDSProxy idsProxy].incomingMessages] forKey:kIncomingMessages];
548 return _counterValues;
551 -(void) printMessage:(NSDictionary*) message state:(NSString*)state
553 secnotice("IDS Transport", "message state: %@", state);
554 secnotice("IDS Transport", "msg id: %@", message[(__bridge NSString*)kIDSMessageUniqueID]);
555 secnotice("IDS Transport", "receiver ids device id: %@", message[(__bridge NSString*)kIDSMessageRecipientDeviceID]);
556 secnotice("IDS Transport", "sender device id: %@", message[(__bridge NSString*)kIDSMessageSenderDeviceID]);
557 secnotice("IDS Transport", "receiver peer id: %@", message[(__bridge NSString*)kIDSMessageRecipientPeerID]);
558 secnotice("IDS Transport", "fragment index: %@", (NSNumber*)message[kIDSFragmentIndex]);
559 secnotice("IDS Transport", "total number of fragments: %@", (NSNumber*)message[kIDSNumberOfFragments]);
560 secnotice("IDS Transport", "%@ data: %@", state, message[(__bridge NSString*)kIDSMessageToSendKey]);