]> git.saurik.com Git - apple/security.git/blob - KeychainSyncingOverIDSProxy/IDSProxy.m
29f8e1aebef5c84e593be7ce4a509e0f6ffa2fd8
[apple/security.git] / KeychainSyncingOverIDSProxy / IDSProxy.m
1 /*
2 * Copyright (c) 2012-2014 Apple 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 // IDSProxy.m
26 // ids-xpc
27 //
28
29
30 #import <Foundation/NSArray.h>
31 #import <Foundation/Foundation.h>
32
33 #import <Security/SecBasePriv.h>
34 #import <Security/SecItemPriv.h>
35 #import <utilities/debugging.h>
36 #import <notify.h>
37
38 #include <Security/CKBridge/SOSCloudKeychainConstants.h>
39 #include <Security/SecureObjectSync/SOSARCDefines.h>
40 #include <Security/SecureObjectSync/SOSCloudCircle.h>
41 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
42
43 #import <IDS/IDS.h>
44 #import <os/activity.h>
45
46 #include <utilities/SecAKSWrappers.h>
47 #include <utilities/SecCFWrappers.h>
48 #include <utilities/SecCFRelease.h>
49 #include <AssertMacros.h>
50
51 #import "IDSProxy.h"
52 #import "KeychainSyncingOverIDSProxy+ReceiveMessage.h"
53 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
54 #import "KeychainSyncingOverIDSProxy+Throttle.h"
55 #import "IDSPersistentState.h"
56
57 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
58 #define kSecServerPeerInfoAvailable "com.apple.security.fpiAvailable"
59
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";
68
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);
72
73
74 CFIndex kSOSErrorPeerNotFound = 1032;
75 CFIndex SECD_RUN_AS_ROOT_ERROR = 1041;
76
77 #define IDSPROXYSCOPE "IDSProxy"
78
79 @implementation KeychainSyncingOverIDSProxy
80
81 @synthesize deviceID = deviceID;
82 @synthesize deviceIDFromAuthToken = deviceIDFromAuthToken;
83
84 + (KeychainSyncingOverIDSProxy *) idsProxy
85 {
86 static KeychainSyncingOverIDSProxy *idsProxy;
87 if (!idsProxy) {
88 static dispatch_once_t onceToken;
89 dispatch_once(&onceToken, ^{
90 idsProxy = [[self alloc] init];
91 });
92 }
93 return idsProxy;
94 }
95
96 -(NSDictionary*) exportState
97 {
98 return @{ kMonitorState:_monitor,
99 kExportUnhandledMessages:_unhandledMessageBuffer,
100 kMessagesInFlight:_messagesInFlight
101 };
102
103 }
104
105 -(NSDictionary*) retrievePendingMessages
106 {
107 return [KeychainSyncingOverIDSProxy idsProxy].messagesInFlight;
108 }
109
110 - (void)persistState
111 {
112 [KeychainSyncingOverIDSProxyPersistentState setUnhandledMessages:[self exportState]];
113 }
114
115 - (void) sendPersistedMessagesAgain
116 {
117 NSMutableDictionary *copy = [NSMutableDictionary dictionaryWithDictionary:_messagesInFlight];
118
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;
123
124 NSString *peerID = (NSString*)[idsMessage objectForKey:kIDSMessageRecipientPeerID];
125 if(!peerID){
126 [self->_messagesInFlight removeObjectForKey:key];
127 return;
128 }
129 NSString *ID = (NSString*)[idsMessage objectForKey:kIDSMessageRecipientDeviceID];
130 if(!ID){
131 [self->_messagesInFlight removeObjectForKey:key];
132 return;
133 }
134
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];
142 }
143 }
144 }];
145 }
146 }
147
148 - (void) importIDSState: (NSMutableDictionary*) state
149 {
150 _unhandledMessageBuffer = state[kExportUnhandledMessages];
151 if(!_unhandledMessageBuffer)
152 _unhandledMessageBuffer = [NSMutableDictionary dictionary];
153
154 _monitor = state[kMonitorState];
155 if(_monitor == nil)
156 _monitor = [NSMutableDictionary dictionary];
157
158 _messagesInFlight = state[kMessagesInFlight];
159 if(_messagesInFlight == nil)
160 _messagesInFlight = [NSMutableDictionary dictionary];
161 }
162
163 - (id)init
164 {
165 if (self = [super init])
166 {
167 secnotice("event", "%@ start", self);
168
169 _isIDSInitDone = false;
170 _service = nil;
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];
177
178 _listOfDevices = [[NSMutableArray alloc] initWithCapacity:0];
179 _isSecDRunningAsRoot = false;
180 _doesSecDHavePeer = true;
181 secdebug(IDSPROXYSCOPE, "%@ done", self);
182
183 [self doIDSInitialization];
184 if(_isIDSInitDone)
185 [self doSetIDSDeviceID];
186
187
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];
192 });
193
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, ^{
197 [self timerFired];
198 });
199 dispatch_resume(_retryTimer);
200 [self importIDSState: [KeychainSyncingOverIDSProxyPersistentState idsState]];
201
202 if([_messagesInFlight count ] > 0)
203 _sendRestoredMessages = true;
204 int notificationToken;
205 notify_register_dispatch(kSecServerKeychainChangedNotification, &notificationToken, dispatch_get_main_queue(),
206 ^ (int token __unused)
207 {
208 secinfo("backoff", "keychain changed, wiping backoff monitor state");
209 self->_monitor = [NSMutableDictionary dictionary];
210 });
211 int peerInfo;
212 notify_register_dispatch(kSecServerPeerInfoAvailable, &peerInfo, dispatch_get_main_queue(),
213 ^ (int token __unused)
214 {
215 secinfo("IDS Transport", "secd has a peer info");
216 if(self->_doesSecDHavePeer == false){
217 self->_doesSecDHavePeer = true;
218 [self doSetIDSDeviceID];
219 }
220 });
221
222 [self updateUnlockedSinceBoot];
223 [self updateIsLocked];
224 if (!_isLocked)
225 [self keybagDidUnlock];
226
227
228 }
229 return self;
230 }
231
232 - (void)streamEvent:(xpc_object_t)notification
233 {
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];
239 }
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);
243 if (desc)
244 free((void *)desc);
245 #endif
246 }
247
248 - (void) keybagDidLock
249 {
250 secnotice("IDS Transport", "%@ locking!", self);
251 }
252
253 - (void) keybagDidUnlock
254 {
255 secnotice("IDS Transport", "%@ unlocking!", self);
256 [self handleAllPendingMessage];
257 }
258
259 - (BOOL) updateUnlockedSinceBoot
260 {
261 CFErrorRef aksError = NULL;
262 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
263 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
264 CFReleaseSafe(aksError);
265 return NO;
266 }
267 return YES;
268 }
269
270 - (BOOL) updateIsLocked
271 {
272 CFErrorRef aksError = NULL;
273 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
274 secerror("%@ Got error querying lock state: %@", self, aksError);
275 CFReleaseSafe(aksError);
276 return NO;
277 }
278 secerror("updateIsLocked: %d", _isLocked);
279 if (!_isLocked)
280 _unlockedSinceBoot = YES;
281 return YES;
282 }
283
284 - (void) keybagStateChange
285 {
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];
294 else
295 [self keybagDidUnlock];
296 }
297 });
298 }
299
300
301 - (void)timerFired
302 {
303 if(_unhandledMessageBuffer)
304 secnotice("IDS Transport", "%@ attempting to hand unhandled messages to securityd, here is our message queue: %@", self, _unhandledMessageBuffer);
305
306 if(_isLocked)
307 _retryTimerScheduled = NO;
308 else if([_unhandledMessageBuffer count] == 0)
309 _retryTimerScheduled = NO;
310 else if (_retryTimerScheduled && !_isLocked)
311 [self handleAllPendingMessage];
312 else
313 [[KeychainSyncingOverIDSProxy idsProxy] scheduleRetryRequestTimer];
314
315 }
316
317 - (void)scheduleRetryRequestTimer
318 {
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;
322 }
323
324 - (void)doIDSInitialization
325 {
326
327 dispatch_async(self.calloutQueue, ^{
328 secnotice("IDS Transport", "doIDSInitialization!");
329
330 self->_service = [[IDSService alloc] initWithService: @IDSServiceNameKeychainSync];
331
332 if( self->_service == nil ){
333 self->_isIDSInitDone = false;
334 secerror("Could not create ids service");
335 }
336 else{
337 secnotice("IDS Transport", "IDS Transport Successfully set up IDS!");
338 [self->_service addDelegate:self queue: dispatch_get_main_queue()];
339
340 self->_isIDSInitDone = true;
341 if(self->_isSecDRunningAsRoot == false)
342 [self doSetIDSDeviceID];
343
344 NSArray *ListOfIDSDevices = [self->_service devices];
345 self.listOfDevices = ListOfIDSDevices;
346
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];
351 }
352 }
353 });
354 }
355
356 - (void) doSetIDSDeviceID
357 {
358 NSInteger code = 0;
359 NSString *errorMessage = nil;
360
361 if(!_isIDSInitDone){
362 [self doIDSInitialization];
363 }
364 _setIDSDeviceID = YES;
365
366 if(_isSecDRunningAsRoot != false)
367 {
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);
371
372 }
373 else if(_doesSecDHavePeer != true)
374 {
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);
378
379 }
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);
384 }
385 else if(_isLocked){
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);
389 }
390
391 else{
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();
396 self->deviceID = ID;
397
398 if(ID){
399 handledSettingID = SOSCCSetDeviceID((__bridge CFStringRef) ID, &localError);
400
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;
405 }
406 else if (CFErrorIsMalfunctioningKeybagError(localError)) {
407 secnotice("IDS Transport", "system is unavailable, cannot set device ID, error: %@", localError);
408 self->_isLocked = true;
409 }
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;
413 }
414 }
415 else
416 self->_setIDSDeviceID = NO;
417
418 CFReleaseNull(localError);
419 dispatch_async(queue, ^{
420 done(nil, NO, YES);
421 });
422 } else {
423 dispatch_async(queue, ^{
424 done(nil, NO, NO);
425 });
426 }
427 if(errorMessage != nil){
428 secerror("Setting device ID error: KeychainSyncingOverIDSProxy could not retrieve device ID from keychain, code: %ld", (long)kSecIDSErrorNoDeviceID);
429 }
430 }];
431 }
432 }
433
434 - (void) calloutWith: (void(^)(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSettingDeviceID))) callout
435 {
436 // In KeychainSyncingOverIDSProxy serial queue
437 dispatch_queue_t idsproxy_queue = dispatch_get_main_queue();
438
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;
452
453 self->_inCallout = YES;
454
455 self->_shadowHandleAllPendingMessages = NO;
456 });
457
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");
460
461 // In IDSKeychainSyncingProxy's serial queue
462 self->_inCallout = NO;
463
464 // Update setting device id
465 self->_setIDSDeviceID = ((myDoSetDeviceID && !handledSetDeviceID));
466
467 self->_shadowDoSetIDSDeviceID = NO;
468
469 xpc_transaction_end();
470 });
471 });
472 }
473
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;
477
478 NSMutableDictionary* handled = handleMessages(pending, &error);
479
480 dispatch_async(queue, ^{
481 if (!handled && error) {
482 secerror("%@ did not handle message: %@", self, error);
483 }
484
485 done(handled, NO, NO);
486 });
487 }];
488 }
489
490 NSString* createErrorString(NSString* format, ...)
491 {
492 va_list va;
493 va_start(va, format);
494 NSString* errorString = ([[NSString alloc] initWithFormat:format arguments:va]);
495 va_end(va);
496 return errorString;
497
498 }
499
500 @end