]> git.saurik.com Git - apple/security.git/blob - KeychainSyncingOverIDSProxy/IDSProxy.m
Security-58286.20.16.tar.gz
[apple/security.git] / KeychainSyncingOverIDSProxy / IDSProxy.m
1 /*
2 * Copyright (c) 2012-2017 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 #import <Foundation/NSArray.h>
25 #import <Foundation/Foundation.h>
26
27 #import <Security/SecBasePriv.h>
28 #import <Security/SecItemPriv.h>
29 #import <utilities/debugging.h>
30 #import <notify.h>
31
32 #include <Security/CKBridge/SOSCloudKeychainConstants.h>
33 #include <Security/SecureObjectSync/SOSARCDefines.h>
34 #include <Security/SecureObjectSync/SOSCloudCircle.h>
35 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
36
37 #import <IDS/IDS.h>
38 #import <os/activity.h>
39
40 #include <utilities/SecAKSWrappers.h>
41 #include <utilities/SecCFWrappers.h>
42 #include <utilities/SecCFRelease.h>
43 #include <AssertMacros.h>
44
45 #import "IDSProxy.h"
46 #import "KeychainSyncingOverIDSProxy+ReceiveMessage.h"
47 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
48 #import "IDSPersistentState.h"
49
50 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
51 #define kSecServerPeerInfoAvailable "com.apple.security.fpiAvailable"
52
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
60 static NSString *const kOutgoingMessages = @"IDS Outgoing Messages";
61 static NSString *const kIncomingMessages = @"IDS Incoming Messages";
62
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);
66
67 CFIndex SECD_RUN_AS_ROOT_ERROR = 1041;
68
69 #define IDSPROXYSCOPE "IDSProxy"
70
71 @implementation KeychainSyncingOverIDSProxy
72
73 @synthesize deviceID = deviceID;
74 @synthesize deviceIDFromAuthToken = deviceIDFromAuthToken;
75
76 + (KeychainSyncingOverIDSProxy *) idsProxy
77 {
78 static KeychainSyncingOverIDSProxy *idsProxy;
79 if (!idsProxy) {
80 static dispatch_once_t onceToken;
81 dispatch_once(&onceToken, ^{
82 idsProxy = [[self alloc] init];
83 });
84 }
85 return idsProxy;
86 }
87
88 -(NSDictionary*) exportState
89 {
90 return @{ kMonitorState:self.monitor,
91 kExportUnhandledMessages:self.unhandledMessageBuffer,
92 kMessagesInFlight:self.messagesInFlight
93 };
94
95 }
96
97 -(NSDictionary*) retrievePendingMessages
98 {
99 NSDictionary * __block messages;
100 dispatch_sync(self.dataQueue, ^{
101 messages = [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight copy];
102 });
103 return messages;
104 }
105
106 @synthesize unhandledMessageBuffer = _unhandledMessageBuffer;
107
108 - (NSMutableDictionary *) unhandledMessageBuffer {
109 dispatch_assert_queue(self.dataQueue);
110 return _unhandledMessageBuffer;
111 }
112
113 - (void) setUnhandledMessageBuffer:(NSMutableDictionary *)unhandledMessageBuffer {
114 dispatch_assert_queue(self.dataQueue);
115 _unhandledMessageBuffer = unhandledMessageBuffer;
116 }
117
118 @ synthesize messagesInFlight = _messagesInFlight;
119
120 - (NSMutableDictionary *) messagesInFlight {
121 dispatch_assert_queue(self.dataQueue);
122 return _messagesInFlight;
123 }
124
125 - (void) setMessagesInFlight:(NSMutableDictionary *)messagesInFlight {
126 dispatch_assert_queue(self.dataQueue);
127 _messagesInFlight = messagesInFlight;
128 }
129
130 @synthesize monitor = _monitor;
131
132 - (NSMutableDictionary *) monitor {
133 dispatch_assert_queue(self.dataQueue);
134 return _monitor;
135 }
136
137 - (void) setMonitor:(NSMutableDictionary *)monitor {
138 dispatch_assert_queue(self.dataQueue);
139 _monitor = monitor;
140 }
141
142 - (void) persistState
143 {
144 dispatch_sync(self.dataQueue, ^{
145 [KeychainSyncingOverIDSProxyPersistentState setUnhandledMessages:[self exportState]];
146 });
147 }
148
149 - (BOOL) haveMessagesInFlight {
150 BOOL __block inFlight = NO;
151 dispatch_sync(self.dataQueue, ^{
152 inFlight = [self.messagesInFlight count] > 0;
153 });
154 return inFlight;
155 }
156
157 - (void) sendPersistedMessagesAgain
158 {
159 NSMutableDictionary * __block copy;
160
161 dispatch_sync(self.dataQueue, ^{
162 copy = [NSMutableDictionary dictionaryWithDictionary:self.messagesInFlight];
163 });
164
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;
169
170 NSString *peerID = (NSString*)[idsMessage objectForKey:(__bridge NSString*)kIDSMessageRecipientPeerID];
171 NSString *ID = (NSString*)[idsMessage objectForKey:(__bridge NSString*)kIDSMessageRecipientDeviceID];
172
173 dispatch_sync(self.dataQueue, ^{
174 [self.messagesInFlight removeObjectForKey:key];
175 });
176
177 if (!peerID || !ID) {
178 return;
179 }
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];
186 }
187 }
188 }];
189 }
190 }
191
192 - (id)init
193 {
194 if (self = [super init])
195 {
196 secnotice("event", "%@ start", self);
197
198 _isIDSInitDone = false;
199 _service = nil;
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);
213
214 [self doIDSInitialization];
215 if(_isIDSInitDone)
216 [self doSetIDSDeviceID];
217
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];
222 });
223
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, ^{
227 [self timerFired];
228 });
229 dispatch_resume(_retryTimer);
230 NSMutableDictionary *state = [KeychainSyncingOverIDSProxyPersistentState idsState];
231
232 _unhandledMessageBuffer = state[kExportUnhandledMessages];
233 if (!_unhandledMessageBuffer) {
234 _unhandledMessageBuffer = [NSMutableDictionary dictionary];
235 }
236 _messagesInFlight = state[kMessagesInFlight];
237 if(_messagesInFlight == nil) {
238 _messagesInFlight = [NSMutableDictionary dictionary];
239 }
240 _monitor = state[kMonitorState];
241 if(_monitor == nil) {
242 _monitor = [NSMutableDictionary dictionary];
243 }
244
245 if([_messagesInFlight count ] > 0)
246 _sendRestoredMessages = true;
247 int notificationToken;
248 notify_register_dispatch(kSecServerKeychainChangedNotification, &notificationToken, self.dataQueue,
249 ^ (int token __unused)
250 {
251 secinfo("backoff", "keychain changed, wiping backoff monitor state");
252 self.monitor = [NSMutableDictionary dictionary];
253 });
254 int peerInfo;
255 notify_register_dispatch(kSecServerPeerInfoAvailable, &peerInfo, dispatch_get_main_queue(),
256 ^ (int token __unused)
257 {
258 secinfo("IDS Transport", "secd has a peer info");
259 if(self.doesSecDHavePeer == false){
260 self.doesSecDHavePeer = true;
261 [self doSetIDSDeviceID];
262 }
263 });
264
265 [self updateUnlockedSinceBoot];
266 [self updateIsLocked];
267 if (!_isLocked)
268 [self keybagDidUnlock];
269
270
271 }
272 return self;
273 }
274
275 - (void)streamEvent:(xpc_object_t)notification
276 {
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];
282 }
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);
286 if (desc)
287 free((void *)desc);
288 #endif
289 }
290
291 - (void) keybagDidLock
292 {
293 secnotice("IDS Transport", "%@ locking!", self);
294 }
295
296 - (void) keybagDidUnlock
297 {
298 secnotice("IDS Transport", "%@ unlocking!", self);
299 [self handleAllPendingMessage];
300 }
301
302 - (BOOL) updateUnlockedSinceBoot
303 {
304 CFErrorRef aksError = NULL;
305 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
306 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
307 CFReleaseSafe(aksError);
308 return NO;
309 }
310 return YES;
311 }
312
313 - (BOOL) updateIsLocked
314 {
315 CFErrorRef aksError = NULL;
316 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
317 secerror("%@ Got error querying lock state: %@", self, aksError);
318 CFReleaseSafe(aksError);
319 return NO;
320 }
321 secerror("updateIsLocked: %d", _isLocked);
322 if (!_isLocked)
323 _unlockedSinceBoot = YES;
324 return YES;
325 }
326
327 - (void) keybagStateChange
328 {
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];
337 else
338 [self keybagDidUnlock];
339 }
340 });
341 }
342
343
344 - (void)timerFired
345 {
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];
351 }
352 });
353
354 if(self.isLocked) {
355 self.retryTimerScheduled = NO;
356 } else if(messagecount == 0) {
357 self.retryTimerScheduled = NO;
358 } else if (self.retryTimerScheduled && !self.isLocked) {
359 [self handleAllPendingMessage];
360 } else {
361 [[KeychainSyncingOverIDSProxy idsProxy] scheduleRetryRequestTimer];
362 }
363 }
364
365 - (void)scheduleRetryRequestTimer
366 {
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;
370 }
371
372 - (void)doIDSInitialization
373 {
374
375 dispatch_async(self.calloutQueue, ^{
376 secnotice("IDS Transport", "doIDSInitialization!");
377
378 self->_service = [[IDSService alloc] initWithService: @IDSServiceNameKeychainSync];
379
380 if( self->_service == nil ){
381 self->_isIDSInitDone = false;
382 secerror("Could not create ids service");
383 }
384 else{
385 secnotice("IDS Transport", "IDS Transport Successfully set up IDS!");
386 [self->_service addDelegate:self queue: dispatch_get_main_queue()];
387
388 self->_isIDSInitDone = true;
389 if(self->_isSecDRunningAsRoot == false)
390 [self doSetIDSDeviceID];
391
392 NSArray *ListOfIDSDevices = [self->_service devices];
393 self.listOfDevices = ListOfIDSDevices;
394
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];
399 }
400 }
401 });
402 }
403
404 - (void) doSetIDSDeviceID
405 {
406 NSInteger code = 0;
407 NSString *errorMessage = nil;
408
409 if(!_isIDSInitDone){
410 [self doIDSInitialization];
411 }
412 _setIDSDeviceID = YES;
413
414 if(_isSecDRunningAsRoot != false)
415 {
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);
419
420 }
421 else if(_doesSecDHavePeer != true)
422 {
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);
426
427 }
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);
432 }
433 else if(_isLocked){
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);
437 }
438
439 else{
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();
444 self->deviceID = ID;
445
446 if(ID){
447 handledSettingID = SOSCCSetDeviceID((__bridge CFStringRef) ID, &localError);
448
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;
453 }
454 else if (CFErrorIsMalfunctioningKeybagError(localError)) {
455 secnotice("IDS Transport", "system is unavailable, cannot set device ID, error: %@", localError);
456 self->_isLocked = true;
457 }
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;
461 }
462 }
463 else
464 self->_setIDSDeviceID = NO;
465
466 CFReleaseNull(localError);
467 dispatch_async(queue, ^{
468 done(nil, NO, YES);
469 });
470 } else {
471 dispatch_async(queue, ^{
472 done(nil, NO, NO);
473 });
474 }
475 if(errorMessage != nil){
476 secerror("Setting device ID error: KeychainSyncingOverIDSProxy could not retrieve device ID from keychain, code: %ld", (long)kSecIDSErrorNoDeviceID);
477 }
478 }];
479 }
480 }
481
482 - (void) calloutWith: (void(^)(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *handledMessages, bool handledPendingMessage, bool handledSettingDeviceID))) callout
483 {
484 // In KeychainSyncingOverIDSProxy serial queue
485 dispatch_queue_t idsproxy_queue = dispatch_get_main_queue();
486
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];
498 });
499 myHandlePendingMessage = self.handleAllPendingMessages;
500 myDoSetDeviceID = self.setIDSDeviceID;
501 wasLocked = self.isLocked;
502
503 self.inCallout = YES;
504
505 self.shadowHandleAllPendingMessages = NO;
506 });
507
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");
510
511 // In IDSKeychainSyncingProxy's serial queue
512 self->_inCallout = NO;
513
514 // Update setting device id
515 self->_setIDSDeviceID = ((myDoSetDeviceID && !handledSetDeviceID));
516
517 self->_shadowDoSetIDSDeviceID = NO;
518
519 xpc_transaction_end();
520 });
521 });
522 }
523
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;
527
528 NSMutableDictionary* handled = handleMessages(pending, &error);
529
530 dispatch_async(queue, ^{
531 if (!handled && error) {
532 secerror("%@ did not handle message: %@", self, error);
533 }
534
535 done(handled, NO, NO);
536 });
537 }];
538 }
539
540 NSString* createErrorString(NSString* format, ...)
541 {
542 va_list va;
543 va_start(va, format);
544 NSString* errorString = ([[NSString alloc] initWithFormat:format arguments:va]);
545 va_end(va);
546 return errorString;
547
548 }
549
550 - (NSDictionary*) collectStats{
551 [_counterValues setObject:[NSNumber numberWithInteger:[KeychainSyncingOverIDSProxy idsProxy].outgoingMessages] forKey:kOutgoingMessages];
552 [_counterValues setObject:[NSNumber numberWithInteger:[KeychainSyncingOverIDSProxy idsProxy].incomingMessages] forKey:kIncomingMessages];
553
554 return _counterValues;
555 }
556
557
558 @end