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