]> git.saurik.com Git - apple/security.git/blob - KeychainSyncingOverIDSProxy/KeychainSyncingOverIDSProxy+SendMessage.m
Security-58286.1.32.tar.gz
[apple/security.git] / KeychainSyncingOverIDSProxy / KeychainSyncingOverIDSProxy+SendMessage.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/NSArray.h>
26 #import <Foundation/Foundation.h>
27
28 #import <Security/SecBasePriv.h>
29 #import <Security/SecItemPriv.h>
30 #import <utilities/debugging.h>
31 #import <notify.h>
32
33 #include <Security/CKBridge/SOSCloudKeychainConstants.h>
34 #include <Security/SecureObjectSync/SOSARCDefines.h>
35 #include <Security/SecureObjectSync/SOSCloudCircle.h>
36 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
37
38 #import <IDS/IDS.h>
39 #import <os/activity.h>
40
41 #include <utilities/SecAKSWrappers.h>
42 #include <utilities/SecCFRelease.h>
43 #include <AssertMacros.h>
44
45 #import "IDSProxy.h"
46 #import "IDSPersistentState.h"
47 #import "KeychainSyncingOverIDSProxy+SendMessage.h"
48 #include <Security/SecItemInternal.h>
49
50
51 static NSString *const IDSSendMessageOptionForceEncryptionOffKey = @"IDSSendMessageOptionForceEncryptionOff";
52
53 static NSString *const kIDSNumberOfFragments = @"NumberOfIDSMessageFragments";
54 static NSString *const kIDSFragmentIndex = @"kFragmentIndex";
55 static NSString *const kIDSMessageUseACKModel = @"UsesAckModel";
56 static NSString *const kIDSDeviceID = @"deviceID";
57
58 static const int64_t kRetryTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for handling unhandled messages.
59 static const int64_t timeout = 3ull;
60 static const int64_t KVS_BACKOFF = 5;
61
62 static const NSUInteger kMaxIDSMessagePayloadSize = 64000;
63
64
65 @implementation KeychainSyncingOverIDSProxy (SendMessage)
66
67
68 -(bool) chunkAndSendKeychainPayload:(NSData*)keychainData deviceID:(NSString*)deviceName ourPeerID:(NSString*)ourPeerID theirPeerID:(NSString*) theirPeerID operation:(NSString*)operationTypeAsString uuid:(NSString*)uuidString error:(NSError**) error
69 {
70 __block BOOL result = true;
71
72 NSUInteger keychainDataLength = [keychainData length];
73 int fragmentIndex = 0;
74 int startingPosition = 0;
75
76 NSUInteger totalNumberOfFragments = (keychainDataLength + kMaxIDSMessagePayloadSize - 1)/kMaxIDSMessagePayloadSize;
77 secnotice("IDS Transport", "sending %lu number of fragments to: %@", (unsigned long)totalNumberOfFragments, deviceName);
78 NSMutableDictionary* fragmentDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
79 deviceName, kIDSDeviceID,
80 [NSNumber numberWithUnsignedInteger:totalNumberOfFragments], kIDSNumberOfFragments,
81 [NSNumber numberWithInt:fragmentIndex], kIDSFragmentIndex,
82 deviceName, kIDSMessageRecipientDeviceID, theirPeerID, kIDSMessageRecipientPeerID,
83 operationTypeAsString, kIDSOperationType,
84 uuidString, kIDSMessageUniqueID,
85 nil];
86
87 NSUInteger remainingLength = keychainDataLength;
88 while(remainingLength > 0 && result == true){
89 NSUInteger fragmentLength = MIN(remainingLength, kMaxIDSMessagePayloadSize);
90 NSData *fragment = [keychainData subdataWithRange:NSMakeRange(startingPosition, fragmentLength)];
91
92 // Insert the current fragment data in dictionary with key peerID and message key.
93 [fragmentDictionary setObject:@{theirPeerID:fragment}
94 forKey:(__bridge NSString*)kIDSMessageToSendKey];
95 // Insert the fragment number in the dictionary
96 [fragmentDictionary setObject:[NSNumber numberWithInt:fragmentIndex]
97 forKey:kIDSFragmentIndex];
98
99 result = [self sendIDSMessage:fragmentDictionary name:deviceName peer:ourPeerID];
100 if(!result)
101 secerror("Could not send fragmented message");
102
103 startingPosition+=fragmentLength;
104 remainingLength-=fragmentLength;
105 fragmentIndex++;
106 }
107
108 return result;
109 }
110
111 - (void)sendToKVS: (NSString*) theirPeerID message: (NSData*) message
112 {
113 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
114 CFErrorRef cf_error = NULL;
115
116 bool success = SOSCCRequestSyncWithPeerOverKVS(((__bridge CFStringRef)theirPeerID), (__bridge CFDataRef)message, &cf_error);
117
118 if(success){
119 secnotice("IDSPing", "sent peerID: %@ to securityd to sync over KVS", theirPeerID);
120 }
121 else{
122 secerror("Could not hand peerID: %@ to securityd, error: %@", theirPeerID, cf_error);
123 }
124
125 CFReleaseNull(cf_error);
126 return NULL;
127 }];
128 }
129
130 - (void) sendMessageToKVS: (NSDictionary<NSString*, NSDictionary*>*) encapsulatedKeychainMessage
131 {
132 [encapsulatedKeychainMessage enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
133 if ([key isKindOfClass: [NSString class]] && [obj isKindOfClass:[NSData class]]) {
134 [self sendToKVS:key message:obj];
135 } else {
136 secerror("Couldn't send to KVS key: %@ obj: %@", key, obj);
137 }
138 }];
139 }
140
141
142 - (void)pingTimerFired:(NSString*)IDSid peerID:(NSString*)peerID identifier:(NSString*)identifier
143 {
144 //setting next time to send
145 [self updateNextTimeToSendFor5Minutes:IDSid];
146
147 secnotice("IDS Transport", "device ID: %@ !!!!!!!!!!!!!!!!Ping timeout is up!!!!!!!!!!!!", IDSid);
148 //call securityd to sync with device over KVS
149 __block CFErrorRef cf_error = NULL;
150 __block bool success = kHandleIDSMessageSuccess;
151
152 //cleanup timers
153 dispatch_async(self.pingQueue, ^{
154 dispatch_source_t timer = [[KeychainSyncingOverIDSProxy idsProxy].pingTimers objectForKey:IDSid]; //remove timer
155 dispatch_cancel(timer); //cancel timer
156 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:IDSid];
157 });
158
159 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
160
161 success = SOSCCRequestSyncWithPeerOverKVSUsingIDOnly(((__bridge CFStringRef)IDSid), &cf_error);
162
163 if(success){
164 secnotice("IDSPing", "sent peerID: %@ to securityd to sync over KVS", IDSid);
165 }
166 else{
167 secerror("Could not hand peerID: %@ to securityd, error: %@", IDSid, cf_error);
168 }
169
170 return NULL;
171 }];
172 CFReleaseSafe(cf_error);
173 }
174
175 -(void) pingDevices:(NSArray*)list peerID:(NSString*)peerID
176 {
177 NSDictionary *messageDictionary = @{(__bridge NSString*)kIDSOperationType : [NSString stringWithFormat:@"%d", kIDSPeerAvailability], (__bridge NSString*)kIDSMessageToSendKey : @"checking peers"};
178
179 [list enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * top) {
180 NSString* IDSid = (NSString*)obj;
181 NSString* identifier = [NSString string];
182 bool result = false;
183 secnotice("IDS Transport", "sending to id: %@", IDSid);
184
185 result = [self sendIDSMessage:messageDictionary name:IDSid peer:peerID];
186
187 if(!result){
188 secerror("Could not send message over IDS");
189 [self sendKeysCallout:^NSMutableDictionary *(NSMutableDictionary *pending, NSError** error) {
190 CFErrorRef kvsError = nil;
191 bool success = SOSCCRequestSyncWithPeerOverKVSUsingIDOnly(((__bridge CFStringRef)IDSid), &kvsError);
192
193 if(success){
194 secnotice("IDSPing", "sent peerID: %@ to securityd to sync over KVS", IDSid);
195 }
196 else{
197 secerror("Could not hand peerID: %@ to securityd, error: %@", IDSid, kvsError);
198 }
199 CFReleaseNull(kvsError);
200 return NULL;
201 }];
202 }
203 else{
204 dispatch_async(self.pingQueue, ^{
205 //create a timer!
206 if( [self.pingTimers objectForKey:IDSid] == nil){
207 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
208 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
209 dispatch_source_set_event_handler(timer, ^{
210 [self pingTimerFired:IDSid peerID:peerID identifier:identifier];
211 });
212 dispatch_resume(timer);
213
214 [self.pingTimers setObject:timer forKey:IDSid];
215 }
216 });
217 }
218 }];
219 }
220
221 -(BOOL) shouldProxySendMessage:(NSString*)deviceName
222 {
223 BOOL result = false;
224
225 //checking peer cache to see if the message should be sent over IDS or back to KVS
226 if(self.peerNextSendCache == nil)
227 {
228 self.peerNextSendCache = [[NSMutableDictionary alloc]initWithCapacity:0];
229 }
230 NSDate *nextTimeToSend = [self.peerNextSendCache objectForKey:deviceName];
231 if(nextTimeToSend != nil)
232 {
233 //check if the timestamp is stale or set sometime in the future
234 NSDate *currentTime = [[NSDate alloc] init];
235 //if the current time is greater than the next time to send -> time to send!
236 if([[nextTimeToSend laterDate:currentTime] isEqual:currentTime]){
237 result = true;
238 }
239 }
240 else{ //next time to send is not set yet
241 result = true;
242 }
243 return result;
244 }
245
246 -(BOOL) isMessageAPing:(NSDictionary*)data
247 {
248 NSDictionary *messageDictionary = [data objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
249 BOOL isPingMessage = false;
250
251 if(messageDictionary && ![messageDictionary isKindOfClass:[NSDictionary class]])
252 {
253 NSString* messageString = [data objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
254 if(messageString && [messageString isKindOfClass:[NSString class]])
255 isPingMessage = true;
256 }
257 else if(!messageDictionary){
258 secerror("IDS Transport: message is null?");
259 }
260
261 return isPingMessage;
262 }
263
264 -(BOOL) sendFragmentedIDSMessages:(NSDictionary*)data name:(NSString*) deviceName peer:(NSString*) ourPeerID error:(NSError**) error
265 {
266 BOOL result = false;
267 BOOL isPingMessage = false;
268
269 NSError* localError = nil;
270
271 NSString* operationTypeAsString = [data objectForKey: (__bridge NSString*)kIDSOperationType];
272 NSMutableDictionary *messageDictionary = [data objectForKey: (__bridge NSString*)kIDSMessageToSendKey];
273
274 isPingMessage = [self isMessageAPing:data];
275
276 //check the peer cache for the next time to send timestamp
277 //if the timestamp is set in the future, reroute the message to KVS
278 //otherwise send the message over IDS
279 if(![self shouldProxySendMessage:deviceName])
280 {
281 if(isPingMessage){
282 secnotice("IDS Transport", "peer negative cache check: peer cannot send yet. not sending ping message");
283 return true;
284 }
285 else{
286 secnotice("IDS Transport", "peer negative cache check: peer cannot send yet. rerouting message to be sent over KVS: %@", messageDictionary);
287 [self sendMessageToKVS:messageDictionary];
288 return true;
289 }
290 }
291
292 if(isPingMessage){ //foward the ping message, no processing
293 result = [self sendIDSMessage:data
294 name:deviceName
295 peer:ourPeerID];
296 if(!result){
297 secerror("Could not send ping message");
298 }
299 return result;
300 }
301
302 NSString *localMessageIdentifier = [[NSUUID UUID] UUIDString];
303
304 bool fragment = [operationTypeAsString intValue] == kIDSKeychainSyncIDSFragmentation;
305 bool useAckModel = fragment && [[data objectForKey:kIDSMessageUseACKModel] compare: @"YES"] == NSOrderedSame;
306
307 __block NSData *keychainData = nil;
308 __block NSString *theirPeerID = nil;
309
310 [messageDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
311 if ([key isKindOfClass:[NSString class]] && [obj isKindOfClass:[NSData class]]) {
312 theirPeerID = (NSString*)key;
313 keychainData = (NSData*)obj;
314 }
315 *stop = YES;
316 }];
317
318 if(fragment && keychainData && [keychainData length] >= kMaxIDSMessagePayloadSize){
319 secnotice("IDS Transport","sending chunked keychain messages");
320 result = [self chunkAndSendKeychainPayload:keychainData
321 deviceID:deviceName
322 ourPeerID:ourPeerID
323 theirPeerID:theirPeerID
324 operation:operationTypeAsString
325 uuid:localMessageIdentifier
326 error:&localError];
327 }
328 else{
329 NSMutableDictionary* dataCopy = [NSMutableDictionary dictionaryWithDictionary:data];
330 [dataCopy setObject:localMessageIdentifier forKey:(__bridge NSString*)kIDSMessageUniqueID];
331 result = [self sendIDSMessage:dataCopy
332 name:deviceName
333 peer:ourPeerID];
334 }
335
336 if(result && useAckModel){
337 secnotice("IDS Transport", "setting ack timer");
338 [self setMessageTimer:localMessageIdentifier deviceID:deviceName message:data];
339 }
340
341 secnotice("IDS Transport","returning result: %d, error: %@", result, error ? *error : nil);
342 return result;
343 }
344
345 -(void) updateNextTimeToSendFor5Minutes:(NSString*)ID
346 {
347 secnotice("IDS Transport", "Setting next time to send in 5 minutes for device: %@", ID);
348
349 NSTimeInterval backOffInterval = (KVS_BACKOFF * 60);
350 NSDate *nextTimeToTransmit = [NSDate dateWithTimeInterval:backOffInterval sinceDate:[NSDate date]];
351
352 [self.peerNextSendCache setObject:nextTimeToTransmit forKey:ID];
353 }
354
355 - (void)ackTimerFired:(NSString*)identifier deviceID:(NSString*)ID
356 {
357 secnotice("IDS Transport", "IDS device id: %@, Ping timeout is up for message identifier: %@", ID, identifier);
358
359 //call securityd to sync with device over KVS
360 NSMutableDictionary * __block message;
361 dispatch_sync(self.dataQueue, ^{
362 message = [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight objectForKey:identifier];
363 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight removeObjectForKey:identifier];
364 });
365 if(!message){
366 return;
367 }
368 NSDictionary *encapsulatedKeychainMessage = [message objectForKey:(__bridge NSString*)kIDSMessageToSendKey];
369
370 secnotice("IDS Transport", "Encapsulated message: %@", encapsulatedKeychainMessage);
371 //cleanup timers
372 dispatch_async(self.pingQueue, ^{
373 dispatch_source_t timer = [[KeychainSyncingOverIDSProxy idsProxy].pingTimers objectForKey:identifier]; //remove timer
374 if(timer != nil)
375 dispatch_cancel(timer); //cancel timer
376 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:identifier];
377 });
378
379 [self sendMessageToKVS:encapsulatedKeychainMessage];
380
381 //setting next time to send
382 [self updateNextTimeToSendFor5Minutes:ID];
383
384 [[KeychainSyncingOverIDSProxy idsProxy] persistState];
385 }
386
387 -(void) setMessageTimer:(NSString*)identifier deviceID:(NSString*)ID message:(NSDictionary*)message
388 {
389 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
390 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, kRetryTimerLeeway);
391
392 dispatch_source_set_event_handler(timer, ^{
393 [self ackTimerFired:identifier deviceID:ID];
394 });
395 dispatch_resume(timer);
396 //restructure message in flight
397
398
399
400 //set the timer for message id
401 dispatch_async(self.pingQueue, ^{
402 [self.pingTimers setObject:timer forKey:identifier];
403 });
404
405 dispatch_sync(self.dataQueue, ^{
406 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight setObject:message forKey:identifier];
407 });
408 [[KeychainSyncingOverIDSProxy idsProxy] persistState];
409 }
410
411 //had an immediate error, remove it from messages in flight, and immediately send it over KVS
412 -(void) cleanupAfterHardIDSError:(NSDictionary*)data
413 {
414 NSString *messageIdentifier = [data objectForKey:(__bridge NSString*)kIDSMessageUniqueID];
415 NSMutableDictionary * __block messageToSendToKVS = nil;
416
417 if(messageIdentifier != nil){
418 secerror("removing message id: %@ from message timers", messageIdentifier);
419 dispatch_sync(self.dataQueue, ^{
420 messageToSendToKVS = [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight objectForKey:messageIdentifier];
421 [[KeychainSyncingOverIDSProxy idsProxy].messagesInFlight removeObjectForKey:messageIdentifier];
422 });
423 if(!messageToSendToKVS){
424 secnotice("IDS Transport", "no message for identifier: %@", messageIdentifier);
425 return;
426 }
427 secnotice("IDS Transport", "sending over KVS: %@", messageToSendToKVS);
428
429
430
431 //cleanup timer for message
432 dispatch_async(self.pingQueue, ^{
433 dispatch_source_t timer = [[KeychainSyncingOverIDSProxy idsProxy].pingTimers objectForKey:messageIdentifier]; //remove timer
434 if(timer)
435 dispatch_cancel(timer); //cancel timer
436 [[KeychainSyncingOverIDSProxy idsProxy].pingTimers removeObjectForKey:messageIdentifier];
437 });
438 }
439
440 NSDictionary *encapsulatedKeychainMessage = [messageToSendToKVS objectForKey:(__bridge NSString*)kIDSMessageToSendKey];
441
442 if([encapsulatedKeychainMessage isKindOfClass:[NSDictionary class]]){
443 secnotice("IDS Transport", "Encapsulated message: %@", encapsulatedKeychainMessage);
444 [self sendMessageToKVS:encapsulatedKeychainMessage];
445 }
446 }
447
448 -(BOOL) sendIDSMessage:(NSDictionary*)data name:(NSString*) deviceName peer:(NSString*) peerID
449 {
450
451 if(!self->_service){
452 secerror("Could not send message to peer: %@: IDS delegate uninitialized, can't use IDS to send this message", deviceName);
453 return NO;
454 }
455
456 dispatch_async(self.calloutQueue, ^{
457
458 IDSMessagePriority priority = IDSMessagePriorityHigh;
459 BOOL encryptionOff = YES;
460 NSString *sendersPeerIDKey = [ NSString stringWithUTF8String: kMessageKeySendersPeerID];
461
462 secnotice("backoff","!!writing these keys to IDS!!: %@", data);
463
464 NSDictionary *options = @{IDSSendMessageOptionForceEncryptionOffKey : [NSNumber numberWithBool:encryptionOff] };
465
466 NSMutableDictionary *dataCopy = [NSMutableDictionary dictionaryWithDictionary: data];
467
468 //set our peer id and a unique id for this message
469 [dataCopy setObject:peerID forKey:sendersPeerIDKey];
470 secnotice("IDS Transport", "%@ sending message %@ to: %@", peerID, data, deviceName);
471
472 NSDictionary *info;
473 NSInteger errorCode = 0;
474 NSInteger numberOfDevices = 0;
475 NSString *errMessage = nil;
476 NSMutableSet *destinations = nil;
477 NSError *localError = nil;
478 NSString *identifier = nil;
479 IDSDevice *device = nil;
480 numberOfDevices = [self.listOfDevices count];
481
482 require_action_quiet(numberOfDevices > 0, fail, errorCode = kSecIDSErrorNotRegistered; errMessage=createErrorString(@"Could not send message to peer: %@: IDS devices are not registered yet", deviceName));
483 secnotice("IDS Transport","List of devices: %@", [self->_service devices]);
484
485 destinations = [NSMutableSet set];
486 for(NSUInteger i = 0; i < [ self.listOfDevices count ]; i++){
487 device = self.listOfDevices[i];
488 if( [ deviceName compare:device.uniqueID ] == 0){
489 [destinations addObject: IDSCopyIDForDevice(device)];
490 }
491 }
492 require_action_quiet([destinations count] != 0, fail, errorCode = kSecIDSErrorCouldNotFindMatchingAuthToken; errMessage = createErrorString(@"Could not send message to peer: %@: IDS device ID for peer does not match any devices within an IDS Account", deviceName));
493
494 bool result = [self->_service sendMessage:dataCopy toDestinations:destinations priority:priority options:options identifier:&identifier error:&localError ] ;
495
496 [KeychainSyncingOverIDSProxy idsProxy].outgoingMessages++;
497 require_action_quiet(localError == nil && result, fail, errorCode = kSecIDSErrorFailedToSend; errMessage = createErrorString(@"Had an error sending IDS message to peer: %@", deviceName));
498
499 secnotice("IDS Transport","successfully sent to peer:%@, message: %@", deviceName, dataCopy);
500 fail:
501
502 if(errMessage != nil){
503 info = [ NSDictionary dictionaryWithObjectsAndKeys:errMessage, NSLocalizedDescriptionKey, nil ];
504 localError = [[NSError alloc] initWithDomain:@"com.apple.security.ids.error" code:errorCode userInfo:info ];
505 secerror("%@", localError);
506 [self cleanupAfterHardIDSError: data];
507 }
508 });
509
510 return YES;
511 }
512
513 @end