]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSTransportMessage.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / SecureObjectSync / SOSTransportMessage.m
1 #include "keychain/SecureObjectSync/SOSTransport.h"
2 #include "keychain/SecureObjectSync/SOSTransportMessage.h"
3 #include "keychain/SecureObjectSync/SOSKVSKeys.h"
4 #include "keychain/SecureObjectSync/SOSPeerCoder.h"
5 #include "keychain/SecureObjectSync/SOSEngine.h"
6 #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h"
7 #import "keychain/SecureObjectSync/SOSPeerOTRTimer.h"
8
9 #import "utilities/SecCoreAnalytics.h"
10 #include <utilities/SecCFWrappers.h>
11 #include <utilities/SecPLWrappers.h>
12 #include "keychain/SecureObjectSync/SOSInternal.h"
13 #include "keychain/SecureObjectSync/SOSAccountPriv.h"
14 #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h"
15 #include "keychain/securityd/SecItemServer.h" // TODO: Remove this layer violation.
16
17 static const CFStringRef kSecSOSMessageRTT = CFSTR("com.apple.security.sos.messagertt");
18 static const CFStringRef kSecAccessGroupSecureBackupd = CFSTR("com.apple.securebackupd");
19 static const CFStringRef kSecAccessGroupSBD = CFSTR("com.apple.sbd");
20 static const CFStringRef kSecAccessGroupCKKS = CFSTR("com.apple.security.ckks");
21
22 @class SOSMessage;
23
24 @implementation SOSMessage
25
26 @synthesize engine = engine;
27 @synthesize account = account;
28 @synthesize circleName = circleName;
29
30 -(id) initWithAccount:(SOSAccount*)acct andName:(NSString*)name
31 {
32 if ((self = [super init])) {
33 SOSEngineRef e = SOSDataSourceFactoryGetEngineForDataSourceName(acct.factory, (__bridge CFStringRef)name, NULL);
34 engine = e;
35 account = acct;
36 circleName = [[NSString alloc]initWithString:name];
37 }
38 return self;
39 }
40
41 -(CFTypeRef) SOSTransportMessageGetEngine
42 {
43 return engine;
44 }
45
46 -(CFStringRef) SOSTransportMessageGetCircleName
47 {
48 return (__bridge CFStringRef)(circleName);
49 }
50
51 -(CFIndex) SOSTransportMessageGetTransportType
52 {
53 return kUnknown;
54 }
55
56 -(SOSAccount*) SOSTransportMessageGetAccount
57 {
58 return account;
59 }
60
61 -(bool) SOSTransportMessageSendMessages:(SOSMessage*) transport pm:(CFDictionaryRef) peer_messages err:(CFErrorRef *)error
62 {
63 return true;
64 }
65
66 -(bool) SOSTransportMessageFlushChanges:(SOSMessage*) transport err:(CFErrorRef *)error
67 {
68 return true;
69 }
70
71 -(bool) SOSTransportMessageSyncWithPeers:(SOSMessage*) transport p:(CFSetRef) peers err:(CFErrorRef *)error{
72 return true;
73 }
74 -(bool) SOSTransportMessageCleanupAfterPeerMessages:(SOSMessage*) transport peers:(CFDictionaryRef) peers err:(CFErrorRef*) error{
75 return true;
76 }
77
78 -(bool) SOSTransportMessageSendMessage:(SOSMessage*) transport id:(CFStringRef) peerID messageToSend:(CFDataRef) message err:(CFErrorRef *)error
79 {
80 CFDictionaryRef peerMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peerID, message, NULL);
81
82 bool result = [self SOSTransportMessageSendMessages:transport pm:peerMessage err:error];
83
84 CFReleaseNull(peerMessage);
85 return result;
86 }
87
88 bool SOSEngineHandleCodedMessage(SOSAccount* account, SOSEngineRef engine, CFStringRef peerID, CFDataRef codedMessage, CFErrorRef*error) {
89 __block bool result = true;
90 __block bool somethingChanged = false;
91
92 if(account && account.accountIsChanging) {
93 secnotice("engine", "SOSEngineHandleCodedMessage called before signing in to new account");
94 return true; // we want to drop sync message notifications when account is changing
95 }
96
97 result &= SOSEngineWithPeerID(engine, peerID, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *shouldSave) {
98 CFDataRef decodedMessage = NULL;
99 enum SOSCoderUnwrapStatus uwstatus = SOSPeerHandleCoderMessage(peer, coder, peerID, codedMessage, &decodedMessage, shouldSave, error);
100 NSMutableDictionary* attemptsPerPeer = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountRenegotiationRetryCount, NULL);
101 //clear the max retry only if negotiation has finished and a counter exists
102 if(coder != NULL && attemptsPerPeer != nil && !SOSCoderIsCoderInAwaitingState(coder) && ([attemptsPerPeer objectForKey:(__bridge NSString*)peerID] != NULL)){
103 secnotice("otrtimer", "otr negotiation completed! clearing max retry counter");
104 SOSPeerOTRTimerClearMaxRetryCount(account, (__bridge NSString*)peerID);
105 }
106 switch (uwstatus) {
107 case SOSCoderUnwrapDecoded: {
108 SOSMessageRef message = NULL;
109 if (decodedMessage && CFDataGetLength(decodedMessage)) {
110 // Only hand non empty messages to the engine, empty messages are an artifact
111 // of coder startup.
112 message = SOSMessageCreateWithData(kCFAllocatorDefault, decodedMessage, error);
113 }
114 if (message) {
115 bool engineHandleMessageDoesNotGetToRollbackTransactions = true;
116 result = SOSEngineHandleMessage_locked(engine, peerID, message, txn, &engineHandleMessageDoesNotGetToRollbackTransactions, &somethingChanged, error);
117 CFReleaseSafe(message);
118 if (!result) {
119 secnotice("engine", "Failed to handle message from peer %@: %@", peerID, error != NULL ? *error : NULL);
120 }
121 } else {
122 secnotice("engine", "Failed to turn a data gram into an SOSMessage: %@", error != NULL ? *error : NULL);
123 result = SOSErrorCreate(KSOSCantParseSOSMessage, error, NULL, CFSTR("Failed to parse SOSMessage"));
124 }
125 break;
126 }
127 case SOSCoderUnwrapHandled: {
128 secnotice("engine", "coder handled a negotiation message");
129 result = true;
130 break;
131 }
132 case SOSCoderUnwrapError:
133 default: {
134 secnotice("engine", "coder handled a error message: %d (error: %@)", (int)uwstatus, error != NULL ? *error : NULL);
135 result = false;
136 break;
137 }
138 }
139 CFReleaseNull(decodedMessage);
140 });
141
142 if (somethingChanged) {
143 SecKeychainChanged();
144 }
145
146 if (result) {
147 SOSCCRequestSyncWithPeer(peerID);
148 }
149
150 return result;
151 }
152 -(CFDictionaryRef) SOSTransportMessageHandlePeerMessageReturnsHandledCopy:(SOSMessage*) transport peerMessages:(CFMutableDictionaryRef) circle_peer_messages_table err:(CFErrorRef *)error
153 {
154 return CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
155 }
156
157 -(void) SOSTransportMessageCalculateNextTimer:(SOSAccount*)account rtt:(int)rtt peerid:(NSString*)peerid
158 {
159 const int minRTT = 60;
160 const int maxRTT = 3600;
161 int newrtt = rtt *2;
162
163 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account,kSOSAccountPeerNegotiationTimeouts, NULL);
164 if(!peerRTTs) {
165 peerRTTs = [NSMutableDictionary dictionary];
166 }
167
168 // if we already have a longer rtt than requested then bail, unless it got excessive previously
169 NSNumber *timeout = [peerRTTs objectForKey:peerid];
170 if(timeout && (timeout.intValue >= rtt*2) && (newrtt <= maxRTT)) {
171 return;
172 }
173
174 // make sure newrtt is a value between minRTT and maxRTT
175 newrtt = MAX(newrtt, minRTT);
176 newrtt = MIN(newrtt, maxRTT);
177
178 NSNumber* newTimeout = [[NSNumber alloc] initWithInt: (newrtt)];
179 [peerRTTs setObject:newTimeout forKey:peerid];
180 secnotice("otrtimer", "peerID: %@ New OTR RTT: %d", peerid, newrtt);
181 SOSAccountSetValue(account,kSOSAccountPeerNegotiationTimeouts, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
182 }
183
184 -(void) SOSTransportMessageUpdateRTTs:(NSString*)peerid
185 {
186 SOSAccount* account = [self SOSTransportMessageGetAccount];
187 NSMutableDictionary *peerToTimeLastSentDict = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
188 if(peerToTimeLastSentDict){
189 NSDate* storedDate = [peerToTimeLastSentDict objectForKey:peerid];
190 if(storedDate){
191 NSDate* currentDate = [NSDate date];
192 int rtt = [currentDate timeIntervalSinceDate:storedDate];
193 secnotice("otrtimer","peerID: %@ current date: %@, stored date: %@", peerid, currentDate, storedDate);
194 secnotice("otrtimer", "rtt: %d", rtt);
195 [self SOSTransportMessageCalculateNextTimer:account rtt:rtt peerid:peerid];
196
197 [SecCoreAnalytics sendEvent:(__bridge id)kSecSOSMessageRTT event:@{SecCoreAnalyticsValue: [NSNumber numberWithUnsignedInt:rtt]}];
198 [peerToTimeLastSentDict removeObjectForKey:peerid]; //remove last sent message date
199 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerToTimeLastSentDict, NULL);
200 }
201 }
202 }
203
204 -(bool) SOSTransportMessageHandlePeerMessage:(SOSMessage*) transport id:(CFStringRef) peer_id cm:(CFDataRef) codedMessage err:(CFErrorRef *)error
205 {
206 [self SOSTransportMessageUpdateRTTs:(__bridge NSString*)peer_id];
207 bool result = false;
208 require_quiet(SecRequirementError(transport.engine != NULL, error, CFSTR("Missing engine")), done);
209 result = SOSEngineHandleCodedMessage([transport SOSTransportMessageGetAccount], (SOSEngineRef)transport.engine, peer_id, codedMessage, error);
210 done:
211 return result;
212 }
213
214 static PeerRateLimiter* getRateLimiter(SOSPeerRef peer)
215 {
216 PeerRateLimiter *limiter = nil;
217
218 if(!(limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer))){
219 limiter = [[PeerRateLimiter alloc] initWithPeer:peer];
220 SOSPeerSetRateLimiter(peer, (__bridge void*)limiter);
221 }
222 return limiter;
223 }
224
225 static void SOSPeerSetNextTimeToSend(PeerRateLimiter* limiter, int nextTimeToSync, NSString *accessGroup, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
226 {
227 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time: %d", nextTimeToSync);
228
229 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
230 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, nextTimeToSync * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
231 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time to sync: %llu", (nextTimeToSync * NSEC_PER_SEC));
232
233 [limiter.accessGroupToNextMessageToSend setObject:(__bridge NSData*)message_to_send forKey:accessGroup];
234
235 CFStringRef peerid = CFRetainSafe(SOSPeerGetID(peer));
236 CFStringRef accessGroupRetained = CFRetainSafe((__bridge CFStringRef)accessGroup);
237
238 dispatch_source_set_event_handler(timer, ^{
239 SOSCCPeerRateLimiterSendNextMessage_Server(peerid, accessGroupRetained);
240 });
241
242 dispatch_source_set_cancel_handler(timer, ^{
243 CFReleaseSafe(peerid);
244 CFReleaseSafe(accessGroupRetained);
245 });
246
247 dispatch_resume(timer);
248 [limiter.accessGroupToTimer setObject:timer forKey:accessGroup];
249 }
250
251 static void setRateLimitingCounters(SOSAccount* account, PeerRateLimiter* limiter, NSString* attribute)
252 {
253 CFErrorRef error = NULL;
254
255 NSMutableDictionary* counters = (__bridge NSMutableDictionary*) SOSAccountGetValue(account, kSOSRateLimitingCounters, &error);
256
257 if(!counters){
258 counters = [[NSMutableDictionary alloc]init];
259 }
260
261 [counters setObject:[limiter diagnostics] forKey:attribute];
262 SOSAccountSetValue(account, kSOSRateLimitingCounters, (__bridge CFDictionaryRef)counters, &error);
263 }
264 //figure out whether or not an access group should be judged, held back, or sent
265 static bool SOSPeerShouldSend(CFArrayRef attributes, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
266 {
267 NSDate *date = [NSDate date];
268 __block bool peerShouldSend = false;
269 PeerRateLimiter *limiter = getRateLimiter(peer);
270 __block NSMutableDictionary *powerlogPayload = nil;
271 static NSMutableDictionary* attributeRateLimiting = nil;
272
273 static dispatch_once_t onceToken;
274 dispatch_once(&onceToken, ^{
275 attributeRateLimiting = [[NSMutableDictionary alloc] init];
276 });
277
278 if(!attributes){
279 peerShouldSend = true;
280 }
281 else{
282 secnotice("ratelimit", "number of attributes to review: %lu", CFArrayGetCount(attributes));
283 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
284 enum RateLimitState state = [limiter stateForAccessGroup:attribute];
285
286 switch(state){
287 case RateLimitStateCanSend:
288 {
289 NSDate *limit = nil;
290 KeychainItem *item = [[KeychainItem alloc] initWithAccessGroup: attribute];
291 NSInteger badness = [limiter judge:item at:date limitTime:&limit];
292 secnotice("ratelimit","accessGroup: %@, judged: %lu", attribute, (long)badness);
293
294 NSNumber *currentBadness = attributeRateLimiting[attribute];
295 NSNumber *newBadness = @(badness);
296
297 if (![currentBadness isEqual:newBadness]) {
298 attributeRateLimiting[attribute] = newBadness;
299 if (powerlogPayload == NULL) {
300 powerlogPayload = [[NSMutableDictionary alloc] init];
301 }
302 powerlogPayload[attribute] = newBadness;
303 }
304
305 double delta = [limit timeIntervalSinceDate:date];
306
307 if(delta > 0){
308 //setting counters for attribute being rate limited
309 setRateLimitingCounters([transport SOSTransportMessageGetAccount],limiter, attribute);
310
311 secnotice("ratelimit", "setting a timer for next sync: %@", limit);
312
313 SOSPeerSetNextTimeToSend(limiter, delta, attribute, peer, transport, message_to_send);
314 //set rate limit state to hold
315 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateHoldMessage] forKey:attribute];
316 }else{
317 peerShouldSend = true;
318 }
319 }
320 break;
321 case RateLimitStateHoldMessage:
322 {
323 secnotice("ratelimit","access group: %@ is being rate limited", attribute);
324 }
325 break;
326 default:
327 {
328 secnotice("ratelimit","no state for limiter for peer: %@", peer);
329 }
330 };
331 }];
332 }
333 if ([powerlogPayload count]) {
334 SecPLLogRegisteredEvent(@"SOSKVSRateLimitingEvent",
335 @{
336 @"timestamp" : @([date timeIntervalSince1970]),
337 @"peerShouldSend" : @(peerShouldSend),
338 @"attributeBadness" : powerlogPayload
339 });
340 }
341 return peerShouldSend;
342 }
343
344 //if a separate message containing a rate limited access group is going to be sent, make sure to send any pending messages first otherwise the OTR ordering will be wrong!
345 static void SOSTransportSendPendingMessage(CFArrayRef attributes, SOSMessage* transport, SOSPeerRef peer){
346 PeerRateLimiter *limiter = getRateLimiter(peer);
347
348 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
349 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:attribute];
350 if(message){
351 CFErrorRef error = NULL;
352 bool sendResult = [transport SOSTransportMessageSendMessage:transport id:SOSPeerGetID(peer) messageToSend:(__bridge CFDataRef)message err:&error];
353
354 if(!sendResult || error){
355 secnotice("ratelimit", "SOSTransportSendPendingMessage: could not send message: %@", error);
356 }
357 else{
358 secnotice("ratelimit", "SOSTransportSendPendingMessage: sent pending message: %@ for access group: %@", message, attribute);
359 }
360 [limiter.accessGroupToNextMessageToSend removeObjectForKey:attribute];
361 //cancel dispatch timer
362 dispatch_source_t timer = [limiter.accessGroupToTimer objectForKey:attribute];
363 if(timer)
364 dispatch_cancel(timer);
365 [limiter.accessGroupToTimer removeObjectForKey:attribute];
366 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:attribute];
367 }
368 }];
369 }
370
371 //update last time message sent for this peer
372 -(void) SOSTransportMessageUpdateLastMessageSentTimetstamp:(SOSAccount*)account peer:(SOSPeerRef)peer
373 {
374 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
375 if(!peerRTTs){
376 peerRTTs = [NSMutableDictionary dictionary];
377 }
378 if([peerRTTs objectForKey:(__bridge NSString*)SOSPeerGetID(peer) ] == nil){
379 [peerRTTs setObject:[NSDate date] forKey:(__bridge NSString*)SOSPeerGetID(peer)];
380 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
381 }
382 }
383 -(bool) SOSTransportMessageSendMessageIfNeeded:(SOSMessage*)transport
384 circleName:(CFStringRef)circle_id
385 pID:(CFStringRef)peer_id
386 err:(CFErrorRef *)error
387 {
388 __block bool ok = true;
389 SOSAccount* account = transport.account;
390
391 BOOL initialSyncDone = SOSAccountHasCompletedInitialSync(account);
392 ok &= SOSEngineWithPeerID((SOSEngineRef)transport.engine, peer_id, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
393 // Now under engine lock do stuff
394 CFDataRef message_to_send = NULL;
395 SOSEnginePeerMessageSentCallback* sentCallback = NULL;
396 CFMutableArrayRef attributes = NULL;
397 ok = SOSPeerCoderSendMessageIfNeeded([transport SOSTransportMessageGetAccount],(SOSEngineRef)transport.engine, txn, peer, coder, &message_to_send, peer_id, &attributes, &sentCallback, error);
398 secnotice("ratelimit","attribute list: %@", attributes);
399 bool shouldSend = true;
400
401 if(attributes == NULL){ //no attribute but still should be rate limited
402 attributes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
403 CFArrayAppendValue(attributes, CFSTR("NoAttribute"));
404 }
405
406 if(!initialSyncDone){
407 secnotice("ratelimit","not going to rate limit, currently in initial sync");
408 }
409 if(initialSyncDone && message_to_send){ //need to judge the message if not in initial sync
410 secnotice("ratelimit","not in initial sync!");
411 shouldSend = SOSPeerShouldSend(attributes, peer, transport, message_to_send);
412 CFRange range = CFRangeMake(0, CFArrayGetCount(attributes));
413 if(CFArrayContainsValue(attributes, range, kSecAccessGroupCKKS) ||
414 CFArrayContainsValue(attributes, range, kSecAccessGroupSBD) ||
415 CFArrayContainsValue(attributes, range, kSecAccessGroupSecureBackupd)){
416 shouldSend = true;
417 }
418
419 secnotice("ratelimit","should send? : %@", shouldSend ? @"YES" : @"NO");
420 }
421 if (shouldSend && message_to_send) {
422 SOSTransportSendPendingMessage(attributes, transport, peer);
423 ok = ok && [transport SOSTransportMessageSendMessage:transport id:peer_id messageToSend:message_to_send err:error];
424
425 SOSEngineMessageCallCallback(sentCallback, ok);
426
427 [transport SOSTransportMessageUpdateLastMessageSentTimetstamp:account peer:peer];
428
429 }else if(!shouldSend){
430 secnotice("ratelimit", "peer is rate limited: %@", peer_id);
431 }else{
432 secnotice("transport", "no message to send to peer: %@", peer_id);
433 }
434
435 SOSEngineFreeMessageCallback(sentCallback);
436 sentCallback = NULL;
437 CFReleaseSafe(message_to_send);
438 CFReleaseNull(attributes);
439
440 *forceSaveState = ok;
441 });
442
443 return ok;
444 }
445
446 @end
447