Security-58286.31.2.tar.gz
[apple/security.git] / KeychainSyncingOverIDSProxy / KeychainSyncingOverIDSProxy+ReceiveMessage.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
25 #import <Foundation/Foundation.h>
26 #import <Foundation/NSArray.h>
27 #import <Foundation/Foundation.h>
28
29 #import <Security/SecBasePriv.h>
30 #import <Security/SecItemPriv.h>
31 #import <utilities/debugging.h>
32 #import <notify.h>
33
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>
39
40 #import <IDS/IDS.h>
41 #import <os/activity.h>
42
43 #include <utilities/SecAKSWrappers.h>
44 #include <utilities/SecCFRelease.h>
45 #include <AssertMacros.h>
46
47 #import "IDSPersistentState.h"
48 #import "KeychainSyncingOverIDSProxy+ReceiveMessage.h"
49 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
50 #import "IDSProxy.h"
51
52 static NSString *const kIDSNumberOfFragments = @"NumberOfIDSMessageFragments";
53 static NSString *const kIDSFragmentIndex = @"kFragmentIndex";
54 static NSString *const kIDSMessageRecipientID = @"RecipientPeerID";
55 static NSString *const kIDSMessageUseACKModel = @"UsesAckModel";
56
57 @implementation KeychainSyncingOverIDSProxy (ReceiveMessage)
58
59
60 -(int) countNumberOfValidObjects:(NSMutableArray*)fragmentsForDeviceID
61 {
62 __block int count = 0;
63 [fragmentsForDeviceID enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * _Nonnull stop) {
64 if(obj != [NSNull null]){
65 count++;
66 }
67 }];
68 return count;
69 }
70
71 -(BOOL) checkForFragmentation:(NSDictionary*)message id:(NSString*)fromID data:(NSData*)messageData
72 {
73 BOOL handOffMessage = false;
74
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];
79
80 if([KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages == nil)
81 [KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages = [NSMutableDictionary dictionary];
82
83 NSMutableDictionary *uniqueMessages = [[KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages objectForKey: fromID];
84 if(uniqueMessages == nil)
85 uniqueMessages = [NSMutableDictionary dictionary];
86
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]];
92 }
93 }
94
95 [fragmentsForDeviceID replaceObjectAtIndex: [index intValue] withObject:messageData ];
96 [uniqueMessages setObject: fragmentsForDeviceID forKey:uuidString];
97 [[KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages setObject:uniqueMessages forKey: fromID];
98
99 if([self countNumberOfValidObjects:fragmentsForDeviceID] == [idsNumberOfFragments longValue])
100 handOffMessage = true;
101 else
102 handOffMessage = false;
103
104 }
105 else //no fragmentation in the message, ready to hand off to securityd
106 handOffMessage = true;
107
108 return handOffMessage;
109
110 }
111
112 -(NSMutableDictionary*) combineMessage:(NSString*)ID peerID:(NSString*)peerID uuid:(NSString*)uuid
113 {
114 NSString *dataKey = [ NSString stringWithUTF8String: kMessageKeyIDSDataMessage ];
115 NSString *deviceIDKey = [ NSString stringWithUTF8String: kMessageKeyDeviceID ];
116 NSString *peerIDKey = [ NSString stringWithUTF8String: kMessageKeyPeerID ];
117
118 NSMutableDictionary *arrayOfFragmentedMessagesByUUID = [[KeychainSyncingOverIDSProxy idsProxy].allFragmentedMessages objectForKey:ID];
119 NSMutableArray *messagesForUUID = [arrayOfFragmentedMessagesByUUID objectForKey:uuid];
120 NSMutableData* completeMessage = [NSMutableData data];
121
122 [messagesForUUID enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
123 NSData *messageFragment = (NSData*)obj;
124
125 [completeMessage appendData: messageFragment];
126 }];
127 //we've combined the message, now remove it from the fragmented messages dictionary
128 [arrayOfFragmentedMessagesByUUID removeObjectForKey:uuid];
129
130 return [NSMutableDictionary dictionaryWithObjectsAndKeys: completeMessage, dataKey, deviceID, deviceIDKey, peerID, peerIDKey, nil];
131 }
132
133 -(void) handleTestMessage:(NSString*)operation id:(NSString*)ID messageID:(NSString*)uniqueID senderPeerID:(NSString*)senderPeerID
134 {
135 int operationType = [operation intValue];
136 switch(operationType){
137 case kIDSPeerAvailabilityDone:
138 {
139 //set current timestamp to indicate success!
140 [self.peerNextSendCache setObject:[NSDate date] forKey:ID];
141
142 secnotice("IDS Transport","!received availability response!: %@", ID);
143 notify_post(kSOSCCPeerAvailable);
144 break;
145 }
146 case kIDSEndPingTestMessage:
147 secnotice("IDS Transport","received pong message from other device: %@, ping test PASSED", ID);
148 break;
149 case kIDSSendOneMessage:
150 secnotice("IDS Transport","received ping test message, dropping on the floor now");
151 break;
152
153 case kIDSPeerAvailability:
154 case kIDSStartPingTestMessage:
155 {
156 char* messageCharS;
157 if(operationType == kIDSPeerAvailability){
158 secnotice("IDS Transport","Received Availability Message from:%@!", ID);
159 asprintf(&messageCharS, "%d",kIDSPeerAvailabilityDone);
160 }
161 else{
162 secnotice("IDS Transport","Received PingTest Message from: %@!", ID);
163 asprintf(&messageCharS, "%d", kIDSEndPingTestMessage);
164 }
165
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};
169
170 // We can always hold on to a message and our remote peers would bother everyone
171 [self sendIDSMessage:messsageDictionary name:ID peer:@"me"];
172
173 free(messageCharS);
174
175 break;
176 }
177 case kIDSPeerReceivedACK:
178 {
179 //set current timestamp to indicate success!
180 [self.peerNextSendCache setObject:[[NSDate alloc] init] forKey:ID];
181
182 //cancel timer!
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];
187 if(timer != nil){
188 dispatch_cancel(timer);
189 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:uniqueID];
190 dispatch_sync(self.dataQueue, ^{
191 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight removeObjectForKey:uniqueID];
192 });
193 [[KeychainSyncingOverIDSProxy idsProxy] persistState];
194 }
195 });
196 //call out to securityd to set a NULL
197 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
198
199 CFErrorRef localError = NULL;
200 SOSCCClearPeerMessageKeyInKVS((__bridge CFStringRef)senderPeerID, &localError);
201 return NULL;
202 }];
203 break;
204 }
205 default:
206 break;
207 }
208 }
209
210 - (void)sendACK:(NSString*)ID peerID:(NSString*)sendersPeerID uniqueID:(NSString*)uniqueID
211 {
212 char* messageCharS;
213 NSString* messageString = @"ACK";
214
215 asprintf(&messageCharS, "%d",kIDSPeerReceivedACK);
216 NSString *operationString = [[NSString alloc] initWithUTF8String:messageCharS];
217
218 NSDictionary* messageDictionary = @{(__bridge NSString*)kIDSOperationType:operationString, (__bridge NSString*)kIDSMessageToSendKey:messageString, (__bridge NSString*)kIDSMessageUniqueID:uniqueID};
219
220 [self sendIDSMessage:messageDictionary name:ID peer:sendersPeerID];
221
222 free(messageCharS);
223
224 }
225
226 - (void)updateDeviceList
227 {
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)) {
231 }];
232 }
233 - (void)service:(IDSService *)service account:(IDSAccount *)account incomingMessage:(NSDictionary *)message fromID:(NSString *)fromID context:(IDSMessageContext *)context
234 {
235 NSString *dataKey = [ NSString stringWithUTF8String: kMessageKeyIDSDataMessage ];
236 NSString *deviceIDKey = [ NSString stringWithUTF8String: kMessageKeyDeviceID ];
237 NSString *peerIDKey = [ NSString stringWithUTF8String: kMessageKeyPeerID ];
238 NSString *sendersPeerIDKey = [NSString stringWithUTF8String: kMessageKeySendersPeerID];
239
240 [KeychainSyncingOverIDSProxy idsProxy].incomingMessages++;
241
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;
252 NSString *ID = 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;
258 break;
259 }
260 }
261 secnotice("IDS Transport", "Received message from: %@: %@ ", ID, message);
262 NSString *sendersPeerID = [message objectForKey: sendersPeerIDKey];
263
264 if(sendersPeerID == nil)
265 sendersPeerID = [NSString string];
266
267
268 require_action_quiet(ID, fail, hadError = true; errorMessage = CFSTR("require the sender's device ID"));
269
270 operationTypeAsString = [message objectForKey: (__bridge NSString*)kIDSOperationType];
271 messageDictionary = [message objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
272
273 messageID = [message objectForKey:(__bridge NSString*)kIDSMessageUniqueID];
274 useAck = [message objectForKey:kIDSMessageUseACKModel];
275
276 if(useAck != nil && [useAck compare:@"YES"] == NSOrderedSame)
277 require_quiet(messageID != nil, fail);
278
279 secnotice("IDS Transport","from peer %@, operation type as string: %@, as integer: %d", ID, operationTypeAsString, [operationTypeAsString intValue]);
280 operationType = [operationTypeAsString intValue];
281
282 if(operationType != kIDSKeychainSyncIDSFragmentation)
283 {
284 [self handleTestMessage:operationTypeAsString id:ID messageID:messageID senderPeerID:sendersPeerID];
285 }
286 else{
287
288 [messageDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
289 myPeerID = (NSString*)key;
290 messageData = (NSData*)obj;
291 }];
292
293 if(useAck != nil && [useAck compare:@"YES"] == NSOrderedSame)
294 [self sendACK:ID peerID:myPeerID uniqueID:messageID];
295
296 BOOL readyToHandOffToSecD = [self checkForFragmentation:message id:ID data:messageData];
297
298 NSMutableDictionary *messageAndFromID = nil;
299
300 if(readyToHandOffToSecD && ([message objectForKey:kIDSFragmentIndex])!= nil){
301 NSString* uuid = [message objectForKey:(__bridge NSString*)kIDSMessageUniqueID];
302 messageAndFromID = [self combineMessage:ID peerID:myPeerID uuid:uuid];
303 }
304 else if(readyToHandOffToSecD){
305 messageAndFromID = [NSMutableDictionary dictionaryWithObjectsAndKeys: messageData, dataKey, ID, deviceIDKey, myPeerID, peerIDKey, nil];
306 }
307 else
308 return;
309
310 //set the sender's peer id so we can check it in securityd
311 [messageAndFromID setObject:sendersPeerID forKey:sendersPeerIDKey];
312
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];
317 });
318 }
319 else
320 [self sendMessageToSecurity:messageAndFromID fromID:fromID];
321 }
322
323 fail:
324 if(hadError)
325 secerror("error:%@", errorMessage);
326 });
327 }
328
329
330 - (void) handleAllPendingMessage
331 {
332 secnotice("IDS Transport", "Attempting to handle pending messsages");
333
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];
339 }
340 });
341
342 [copyOfUnhandled enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
343 {
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];
349 });
350 [self sendMessageToSecurity:messageAndFromID fromID:fromID];
351 }];
352 }
353
354 - (bool) shouldPersistMessage:(NSDictionary*) newMessageAndFromID id:(NSString*)fromID
355 {
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];
360 });
361
362 if([messagesFromBuffer isEqual:newMessageAndFromID])
363 return false;
364
365 return true;
366 }
367
368 -(void)sendMessageToSecurity:(NSMutableDictionary*)messageAndFromID fromID:(NSString*)fromID
369 {
370 __block CFErrorRef cf_error = NULL;
371 __block HandleIDSMessageReason success = kHandleIDSMessageSuccess;
372
373 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
374
375 success = SOSCCHandleIDSMessage(((__bridge CFDictionaryRef)messageAndFromID), &cf_error);
376 //turns out the error needs to be evaluated as sync_and_do returns bools
377 if(cf_error != NULL)
378 {
379 if(CFErrorIsMalfunctioningKeybagError(cf_error)){
380 success = kHandleIDSMessageLocked;
381 }
382 }
383
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];
390 }
391 });
392
393 //write message to disk if message is new to the unhandled queue
394 if([self shouldPersistMessage:messageAndFromID id:fromID]) {
395 [self persistState];
396 }
397
398 dispatch_sync(self.dataQueue, ^{
399 [self.unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
400 secnotice("IDS Transport", "unhandledMessageBuffer: %@", self.unhandledMessageBuffer);
401 });
402
403 return NULL;
404 }
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];
410 }
411 [self.unhandledMessageBuffer setObject: messageAndFromID forKey: fromID];
412 secnotice("IDS Transport","unhandledMessageBuffer: %@", self.unhandledMessageBuffer);
413 });
414 //write message to disk if message is new to the unhandled queue
415 if([self shouldPersistMessage:messageAndFromID id:fromID])
416 [self persistState];
417
418 [[KeychainSyncingOverIDSProxy idsProxy] scheduleRetryRequestTimer];
419 return NULL;
420 }
421 else if(success == kHandleIDSmessageDeviceIDMismatch){
422 secnotice("IDS Transport","message for a ghost! dropping message. error:%@", cf_error);
423 return NULL;
424 }
425 else if(success == kHandleIDSMessageDontHandle){
426 secnotice("IDS Transport","error in message, dropping message. error:%@", cf_error);
427 return NULL;
428 }
429 else{
430 secnotice("IDS Transport","IDSProxy handled this message %@, from: %@", messageAndFromID, fromID);
431 return (NSMutableDictionary*)messageAndFromID;
432 }
433
434 CFReleaseNull(cf_error);
435 }];
436 }
437
438 @end