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@
25 #import <Foundation/Foundation.h>
26 #import <Foundation/NSArray.h>
27 #import <Foundation/Foundation.h>
29 #import <Security/SecBasePriv.h>
30 #import <Security/SecItemPriv.h>
31 #import <utilities/debugging.h>
34 #include <Security/CKBridge/SOSCloudKeychainConstants.h>
35 #include <Security/SecureObjectSync/SOSARCDefines.h>
36 #include <Security/SecureObjectSync/SOSCloudCircle.h>
37 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
38 #include <utilities/SecCFWrappers.h>
41 #import <os/activity.h>
43 #include <utilities/SecAKSWrappers.h>
44 #include <utilities/SecCFRelease.h>
45 #include <AssertMacros.h>
47 #import "IDSPersistentState.h"
48 #import "KeychainSyncingOverIDSProxy+ReceiveMessage.h"
49 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
52 static NSString *const kIDSNumberOfFragments = @"NumberOfIDSMessageFragments";
53 static NSString *const kIDSFragmentIndex = @"kFragmentIndex";
54 static NSString *const kIDSMessageRecipientID = @"RecipientPeerID";
55 static NSString *const kIDSMessageUseACKModel = @"UsesAckModel";
57 @implementation KeychainSyncingOverIDSProxy (ReceiveMessage)
60 -(int) countNumberOfValidObjects:(NSMutableArray*)fragmentsForDeviceID
62 __block int count = 0;
63 [fragmentsForDeviceID enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * _Nonnull stop) {
64 if(obj != [NSNull null]){
71 -(BOOL) checkForFragmentation:(NSDictionary*)message id:(NSString*)fromID data:(NSData*)messageData
73 BOOL handOffMessage = false;
75 if([message valueForKey:kIDSNumberOfFragments] != nil){
76 NSNumber *idsNumberOfFragments = [message objectForKey:kIDSNumberOfFragments];
77 NSNumber *index = [message objectForKey:kIDSFragmentIndex];
78 NSString *uuidString = [message objectForKey:(__bridge NSString*)kIDSMessageUniqueID];
80 if([KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages == nil)
81 [KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages = [NSMutableDictionary dictionary];
83 NSMutableDictionary *uniqueMessages = [[KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages objectForKey: fromID];
84 if(uniqueMessages == nil)
85 uniqueMessages = [NSMutableDictionary dictionary];
87 NSMutableArray *fragmentsForDeviceID = [uniqueMessages objectForKey: uuidString];
88 if(fragmentsForDeviceID == nil){
89 fragmentsForDeviceID = [ [NSMutableArray alloc] initWithCapacity: [idsNumberOfFragments longValue]];
90 for (int i = 0; i <[idsNumberOfFragments longValue] ; i++) {
91 [fragmentsForDeviceID addObject:[NSNull null]];
95 [fragmentsForDeviceID replaceObjectAtIndex: [index intValue] withObject:messageData ];
96 [uniqueMessages setObject: fragmentsForDeviceID forKey:uuidString];
97 [[KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages setObject:uniqueMessages forKey: fromID];
99 if([self countNumberOfValidObjects:fragmentsForDeviceID] == [idsNumberOfFragments longValue])
100 handOffMessage = true;
102 handOffMessage = false;
105 else //no fragmentation in the message, ready to hand off to securityd
106 handOffMessage = true;
108 return handOffMessage;
112 -(NSMutableDictionary*) combineMessage:(NSString*)ID peerID:(NSString*)peerID uuid:(NSString*)uuid
114 NSString *dataKey = [ NSString stringWithUTF8String: kMessageKeyIDSDataMessage ];
115 NSString *deviceIDKey = [ NSString stringWithUTF8String: kMessageKeyDeviceID ];
116 NSString *peerIDKey = [ NSString stringWithUTF8String: kMessageKeyPeerID ];
118 NSMutableDictionary *arrayOfFragmentedMessagesByUUID = [[KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages objectForKey:ID];
119 NSMutableArray *messagesForUUID = [arrayOfFragmentedMessagesByUUID objectForKey:uuid];
120 NSMutableData* completeMessage = [NSMutableData data];
122 [messagesForUUID enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
123 NSData *messageFragment = (NSData*)obj;
125 [completeMessage appendData: messageFragment];
127 //we've combined the message, now remove it from the fragmented messages dictionary
128 [arrayOfFragmentedMessagesByUUID removeObjectForKey:uuid];
130 return [NSMutableDictionary dictionaryWithObjectsAndKeys: completeMessage, dataKey, deviceID, deviceIDKey, peerID, peerIDKey, nil];
133 -(void) handleTestMessage:(NSString*)operation id:(NSString*)ID messageID:(NSString*)uniqueID senderPeerID:(NSString*)senderPeerID
135 int operationType = [operation intValue];
136 switch(operationType){
137 case kIDSPeerAvailabilityDone:
139 //set current timestamp to indicate success!
140 [self.peerNextSendCache setObject:[NSDate date] forKey:ID];
142 secnotice("IDS Transport","!received availability response!: %@", ID);
143 notify_post(kSOSCCPeerAvailable);
146 case kIDSEndPingTestMessage:
147 secnotice("IDS Transport","received pong message from other device: %@, ping test PASSED", ID);
149 case kIDSSendOneMessage:
150 secnotice("IDS Transport","received ping test message, dropping on the floor now");
153 case kIDSPeerAvailability:
154 case kIDSStartPingTestMessage:
157 if(operationType == kIDSPeerAvailability){
158 secnotice("IDS Transport","Received Availability Message from:%@!", ID);
159 asprintf(&messageCharS, "%d",kIDSPeerAvailabilityDone);
162 secnotice("IDS Transport","Received PingTest Message from: %@!", ID);
163 asprintf(&messageCharS, "%d", kIDSEndPingTestMessage);
166 NSString *operationString = [[NSString alloc] initWithUTF8String:messageCharS];
167 NSString* messageString = @"peer availability check finished";
168 NSDictionary* messsageDictionary = @{(__bridge NSString*)kIDSOperationType:operationString, (__bridge NSString*)kIDSMessageToSendKey:messageString};
170 // We can always hold on to a message and our remote peers would bother everyone
171 [self sendIDSMessage:messsageDictionary name:ID peer:@"me"];
177 case kIDSPeerReceivedACK:
179 //set current timestamp to indicate success!
180 [self.peerNextSendCache setObject:[[NSDate alloc] init] forKey:ID];
183 secnotice("IDS Transport", "received ack for: %@", uniqueID);
184 dispatch_async(self.pingQueue, ^{
185 //remove timer for message id
186 dispatch_source_t timer = [[KeychainSyncingOverIDSProxy idsProxy].pingTimers objectForKey:uniqueID];
188 dispatch_cancel(timer);
189 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:uniqueID];
190 dispatch_sync(self.dataQueue, ^{
191 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight removeObjectForKey:uniqueID];
193 [[KeychainSyncingOverIDSProxy idsProxy] persistState];
196 //call out to securityd to set a NULL
197 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
199 CFErrorRef localError = NULL;
200 SOSCCClearPeerMessageKeyInKVS((__bridge CFStringRef)senderPeerID, &localError);
210 - (void)sendACK:(NSString*)ID peerID:(NSString*)sendersPeerID uniqueID:(NSString*)uniqueID
213 NSString* messageString = @"ACK";
215 asprintf(&messageCharS, "%d",kIDSPeerReceivedACK);
216 NSString *operationString = [[NSString alloc] initWithUTF8String:messageCharS];
218 NSDictionary* messageDictionary = @{(__bridge NSString*)kIDSOperationType:operationString, (__bridge NSString*)kIDSMessageToSendKey:messageString, (__bridge NSString*)kIDSMessageUniqueID:uniqueID};
220 [self sendIDSMessage:messageDictionary name:ID peer:sendersPeerID];
226 - (void)updateDeviceList
228 self.deviceIDFromAuthToken = nil;
229 self.deviceIDFromAuthToken = [NSMutableDictionary dictionary];
230 [self calloutWith:^(NSMutableDictionary *pending, bool handlePendingMesssages, bool doSetDeviceID, dispatch_queue_t queue, void(^done)(NSMutableDictionary *, bool, bool)) {
233 - (void)service:(IDSService *)service account:(IDSAccount *)account incomingMessage:(NSDictionary *)message fromID:(NSString *)fromID context:(IDSMessageContext *)context
235 NSString *dataKey = [ NSString stringWithUTF8String: kMessageKeyIDSDataMessage ];
236 NSString *deviceIDKey = [ NSString stringWithUTF8String: kMessageKeyDeviceID ];
237 NSString *peerIDKey = [ NSString stringWithUTF8String: kMessageKeyPeerID ];
238 NSString *sendersPeerIDKey = [NSString stringWithUTF8String: kMessageKeySendersPeerID];
240 [KeychainSyncingOverIDSProxy idsProxy].incomingMessages++;
242 dispatch_async(self.calloutQueue, ^{
243 NSString* messageID = nil;
244 uint32_t operationType;
245 bool hadError = false;
246 CFStringRef errorMessage = NULL;
247 __block NSString* myPeerID = @"";
248 __block NSData *messageData = nil;
249 NSString* operationTypeAsString = nil;
250 NSMutableDictionary *messageDictionary = nil;
251 NSString *useAck = nil;
253 NSArray *devices = [self->_service devices];
254 for(NSUInteger i = 0; i < [ devices count ]; i++){
255 IDSDevice *device = devices[i];
256 if( [(IDSCopyIDForDevice(device)) containsString: fromID] == YES){
257 ID = device.uniqueID;
261 secnotice("IDS Transport", "Received message from: %@: %@ ", ID, message);
262 NSString *sendersPeerID = [message objectForKey: sendersPeerIDKey];
264 if(sendersPeerID == nil)
265 sendersPeerID = [NSString string];
268 require_action_quiet(ID, fail, hadError = true; errorMessage = CFSTR("require the sender's device ID"));
270 operationTypeAsString = [message objectForKey: (__bridge NSString*)kIDSOperationType];
271 messageDictionary = [message objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
273 messageID = [message objectForKey:(__bridge NSString*)kIDSMessageUniqueID];
274 useAck = [message objectForKey:kIDSMessageUseACKModel];
276 if(useAck != nil && [useAck compare:@"YES"] == NSOrderedSame)
277 require_quiet(messageID != nil, fail);
279 secnotice("IDS Transport","from peer %@, operation type as string: %@, as integer: %d", ID, operationTypeAsString, [operationTypeAsString intValue]);
280 operationType = [operationTypeAsString intValue];
282 if(operationType != kIDSKeychainSyncIDSFragmentation)
284 [self handleTestMessage:operationTypeAsString id:ID messageID:messageID senderPeerID:sendersPeerID];
288 [messageDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
289 myPeerID = (NSString*)key;
290 messageData = (NSData*)obj;
293 if(useAck != nil && [useAck compare:@"YES"] == NSOrderedSame)
294 [self sendACK:ID peerID:myPeerID uniqueID:messageID];
296 BOOL readyToHandOffToSecD = [self checkForFragmentation:message id:ID data:messageData];
298 NSMutableDictionary *messageAndFromID = nil;
300 if(readyToHandOffToSecD && ([message objectForKey:kIDSFragmentIndex])!= nil){
301 NSString* uuid = [message objectForKey:(__bridge NSString*)kIDSMessageUniqueID];
302 messageAndFromID = [self combineMessage:ID peerID:myPeerID uuid:uuid];
304 else if(readyToHandOffToSecD){
305 messageAndFromID = [NSMutableDictionary dictionaryWithObjectsAndKeys: messageData, dataKey, ID, deviceIDKey, myPeerID, peerIDKey, nil];
310 //set the sender's peer id so we can check it in securityd
311 [messageAndFromID setObject:sendersPeerID forKey:sendersPeerIDKey];
313 if([KeychainSyncingOverIDSProxy idsProxy].isLocked){
314 //hang on to the message and set the retry deadline
315 dispatch_sync(self.dataQueue, ^{
316 [self.unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
320 [self sendMessageToSecurity:messageAndFromID fromID:fromID];
325 secerror("error:%@", errorMessage);
330 - (void) handleAllPendingMessage
332 secnotice("IDS Transport", "Attempting to handle pending messsages");
334 NSMutableDictionary * __block copyOfUnhandled = nil;
335 dispatch_sync(self.dataQueue, ^{
336 if ([self.unhandledMessageBuffer count] > 0) {
337 secnotice("IDS Transport", "handling messages: %@", self.unhandledMessageBuffer);
338 copyOfUnhandled = [NSMutableDictionary dictionaryWithDictionary:self.unhandledMessageBuffer];
342 [copyOfUnhandled enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
344 NSMutableDictionary *messageAndFromID = (NSMutableDictionary*)obj;
345 NSString *fromID = (NSString*)key;
346 //remove the message from the official message buffer (if it fails to get handled it'll be reset again in sendMessageToSecurity)
347 dispatch_sync(self.dataQueue, ^{
348 [self.unhandledMessageBuffer removeObjectForKey: fromID];
350 [self sendMessageToSecurity:messageAndFromID fromID:fromID];
354 - (bool) shouldPersistMessage:(NSDictionary*) newMessageAndFromID id:(NSString*)fromID
356 //get the dictionary of messages for a particular device id
357 NSDictionary* __block messagesFromBuffer;
358 dispatch_sync(self.dataQueue, ^{
359 messagesFromBuffer = [self.unhandledMessageBuffer valueForKey:fromID];
362 if([messagesFromBuffer isEqual:newMessageAndFromID])
368 -(void)sendMessageToSecurity:(NSMutableDictionary*)messageAndFromID fromID:(NSString*)fromID
370 __block CFErrorRef cf_error = NULL;
371 __block HandleIDSMessageReason success = kHandleIDSMessageSuccess;
373 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
375 success = SOSCCHandleIDSMessage(((__bridge CFDictionaryRef)messageAndFromID), &cf_error);
376 //turns out the error needs to be evaluated as sync_and_do returns bools
379 if(CFErrorIsMalfunctioningKeybagError(cf_error)){
380 success = kHandleIDSMessageLocked;
384 if(success == kHandleIDSMessageLocked){
385 secnotice("IDS Transport","cannot handle messages from: %@ when locked, error:%@", fromID, cf_error);
386 // I don't think this is ever nil but it was like this when I got here
387 dispatch_sync(self.dataQueue, ^{
388 if(!self.unhandledMessageBuffer) {
389 self.unhandledMessageBuffer = [NSMutableDictionary dictionary];
393 //write message to disk if message is new to the unhandled queue
394 if([self shouldPersistMessage:messageAndFromID id:fromID]) {
398 dispatch_sync(self.dataQueue, ^{
399 [self.unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
400 secnotice("IDS Transport", "unhandledMessageBuffer: %@", self.unhandledMessageBuffer);
405 else if(success == kHandleIDSMessageNotReady){
406 secnotice("IDS Transport","not ready to handle message from: %@, error:%@", fromID, cf_error);
407 dispatch_sync(self.dataQueue, ^{
408 if(!self.unhandledMessageBuffer) {
409 self.unhandledMessageBuffer = [NSMutableDictionary dictionary];
411 [self.unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
412 secnotice("IDS Transport","unhandledMessageBuffer: %@", self.unhandledMessageBuffer);
414 //write message to disk if message is new to the unhandled queue
415 if([self shouldPersistMessage:messageAndFromID id:fromID])
418 [[KeychainSyncingOverIDSProxy idsProxy] scheduleRetryRequestTimer];
421 else if(success == kHandleIDSmessageDeviceIDMismatch){
422 secnotice("IDS Transport","message for a ghost! dropping message. error:%@", cf_error);
425 else if(success == kHandleIDSMessageDontHandle){
426 secnotice("IDS Transport","error in message, dropping message. error:%@", cf_error);
430 secnotice("IDS Transport","IDSProxy handled this message %@, from: %@", messageAndFromID, fromID);
431 return (NSMutableDictionary*)messageAndFromID;
434 CFReleaseNull(cf_error);