1 #include <Security/SecureObjectSync/SOSTransport.h>
2 #include <Security/SecureObjectSync/SOSTransportMessage.h>
3 #include <Security/SecureObjectSync/SOSKVSKeys.h>
4 #include <Security/SecureObjectSync/SOSPeerCoder.h>
5 #include <Security/SecureObjectSync/SOSEngine.h>
6 #import "Security/SecureObjectSync/SOSPeerRateLimiter.h"
7 #import "Security/SecureObjectSync/SOSPeerOTRTimer.h"
8 #include <utilities/SecADWrapper.h>
10 #include <utilities/SecCFWrappers.h>
11 #include <utilities/SecPLWrappers.h>
12 #include <SOSInternal.h>
13 #include <Security/SecureObjectSync/SOSAccountPriv.h>
14 #include <SOSCloudKeychainClient.h>
15 #include <securityd/SecItemServer.h> // TODO: Remove this layer violation.
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");
24 @implementation SOSMessage
26 @synthesize engine = engine;
27 @synthesize account = account;
28 @synthesize circleName = circleName;
30 -(id) initWithAccount:(SOSAccount*)acct andName:(NSString*)name
34 SOSEngineRef e = SOSDataSourceFactoryGetEngineForDataSourceName(acct.factory, (__bridge CFStringRef)name, NULL);
37 circleName = [[NSString alloc]initWithString:name];
42 -(CFTypeRef) SOSTransportMessageGetEngine
47 -(CFStringRef) SOSTransportMessageGetCircleName
49 return (__bridge CFStringRef)(circleName);
52 -(CFIndex) SOSTransportMessageGetTransportType
57 -(SOSAccount*) SOSTransportMessageGetAccount
62 -(bool) SOSTransportMessageSendMessages:(SOSMessage*) transport pm:(CFDictionaryRef) peer_messages err:(CFErrorRef *)error
67 -(bool) SOSTransportMessageFlushChanges:(SOSMessage*) transport err:(CFErrorRef *)error
72 -(bool) SOSTransportMessageSyncWithPeers:(SOSMessage*) transport p:(CFSetRef) peers err:(CFErrorRef *)error{
75 -(bool) SOSTransportMessageCleanupAfterPeerMessages:(SOSMessage*) transport peers:(CFDictionaryRef) peers err:(CFErrorRef*) error{
79 -(bool) SOSTransportMessageSendMessage:(SOSMessage*) transport id:(CFStringRef) peerID messageToSend:(CFDataRef) message err:(CFErrorRef *)error
81 CFDictionaryRef peerMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peerID, message, NULL);
83 bool result = [self SOSTransportMessageSendMessages:transport pm:peerMessage err:error];
85 CFReleaseNull(peerMessage);
89 bool SOSEngineHandleCodedMessage(SOSAccount* account, SOSEngineRef engine, CFStringRef peerID, CFDataRef codedMessage, CFErrorRef*error) {
90 __block bool result = true;
91 __block bool somethingChanged = false;
92 result &= SOSEngineWithPeerID(engine, peerID, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *shouldSave) {
93 CFDataRef decodedMessage = NULL;
94 enum SOSCoderUnwrapStatus uwstatus = SOSPeerHandleCoderMessage(peer, coder, peerID, codedMessage, &decodedMessage, shouldSave, error);
95 NSMutableDictionary* attemptsPerPeer = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountRenegotiationRetryCount, NULL);
96 //clear the max retry only if negotiation has finished and a counter exists
97 if(coder != NULL && attemptsPerPeer != nil && !SOSCoderIsCoderInAwaitingState(coder) && ([attemptsPerPeer objectForKey:(__bridge NSString*)peerID] != NULL)){
98 secnotice("otrtimer", "otr negotiation completed! clearing max retry counter");
99 SOSPeerOTRTimerClearMaxRetryCount(account, (__bridge NSString*)peerID);
101 if (uwstatus == SOSCoderUnwrapDecoded) {
102 SOSMessageRef message = NULL;
103 if (decodedMessage && CFDataGetLength(decodedMessage)) {
104 // Only hand non empty messages to the engine, empty messages are an artifact
106 message = SOSMessageCreateWithData(kCFAllocatorDefault, decodedMessage, error);
109 bool engineHandleMessageDoesNotGetToRollbackTransactions = true;
110 result = SOSEngineHandleMessage_locked(engine, peerID, message, txn, &engineHandleMessageDoesNotGetToRollbackTransactions, &somethingChanged, error);
111 CFReleaseSafe(message);
114 result = uwstatus != SOSCoderUnwrapError;
116 CFReleaseNull(decodedMessage);
119 if (somethingChanged) {
120 SecKeychainChanged();
124 SOSCCRequestSyncWithPeer(peerID);
129 -(CFDictionaryRef) SOSTransportMessageHandlePeerMessageReturnsHandledCopy:(SOSMessage*) transport peerMessages:(CFMutableDictionaryRef) circle_peer_messages_table err:(CFErrorRef *)error
131 return CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
134 -(void) SOSTransportMessageCalculateNextTimer:(SOSAccount*)account rtt:(int)rtt peerid:(NSString*)peerid
136 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account,kSOSAccountPeerNegotiationTimeouts, NULL);
138 peerRTTs = [NSMutableDictionary dictionary];
141 NSNumber *timeout = [peerRTTs objectForKey:peerid];
143 if(timeout.intValue < (rtt * 2)){
144 NSNumber* newTimeout = [[NSNumber alloc] initWithInt: (rtt * 2)];
145 [peerRTTs setObject:newTimeout forKey:peerid];
146 secnotice("otrtimer", "New OTR RTT: %d", [newTimeout intValue]);
151 timeout = [[NSNumber alloc]initWithInt:(30 * 2)];
154 timeout = [[NSNumber alloc]initWithInt:(rtt * 2)];
156 [peerRTTs setObject:timeout forKey:peerid];
157 secnotice("otrtimer", "setting initial timeout value to rtt*2: %d", [timeout intValue]);
160 SOSAccountSetValue(account,kSOSAccountPeerNegotiationTimeouts, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
163 -(void) SOSTransportMessageUpdateRTTs:(NSString*)peerid
165 SOSAccount* account = [self SOSTransportMessageGetAccount];
166 NSMutableDictionary *peerToTimeLastSentDict = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
167 if(peerToTimeLastSentDict){
168 NSDate* storedDate = [peerToTimeLastSentDict objectForKey:peerid];
170 NSDate* currentDate = [NSDate date];
171 int rtt = [currentDate timeIntervalSinceDate:storedDate];
172 secnotice("otrtimer","current date: %@, stored date: %@", currentDate, storedDate);
173 secnotice("otrtimer", "rtt: %d", rtt);
174 [self SOSTransportMessageCalculateNextTimer:account rtt:rtt peerid:peerid];
176 SecADClientPushValueForDistributionKey(kSecSOSMessageRTT, rtt);
177 [peerToTimeLastSentDict removeObjectForKey:peerid]; //remove last sent message date
178 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerToTimeLastSentDict, NULL);
183 -(bool) SOSTransportMessageHandlePeerMessage:(SOSMessage*) transport id:(CFStringRef) peer_id cm:(CFDataRef) codedMessage err:(CFErrorRef *)error
185 [self SOSTransportMessageUpdateRTTs:(__bridge NSString*)peer_id];
187 require_quiet(SecRequirementError(transport.engine != NULL, error, CFSTR("Missing engine")), done);
188 result = SOSEngineHandleCodedMessage([transport SOSTransportMessageGetAccount], (SOSEngineRef)transport.engine, peer_id, codedMessage, error);
193 static PeerRateLimiter* getRateLimiter(SOSPeerRef peer)
195 PeerRateLimiter *limiter = nil;
197 if(!(limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer))){
198 limiter = [[PeerRateLimiter alloc] initWithPeer:peer];
199 SOSPeerSetRateLimiter(peer, (__bridge void*)limiter);
204 static void SOSPeerSetNextTimeToSend(PeerRateLimiter* limiter, int nextTimeToSync, NSString *accessGroup, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
206 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time: %d", nextTimeToSync);
208 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
209 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, nextTimeToSync * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
210 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time to sync: %llu", (nextTimeToSync * NSEC_PER_SEC));
212 [limiter.accessGroupToNextMessageToSend setObject:(__bridge NSData*)message_to_send forKey:accessGroup];
214 CFStringRef peerid = CFRetainSafe(SOSPeerGetID(peer));
215 CFStringRef accessGroupRetained = CFRetainSafe((__bridge CFStringRef)accessGroup);
217 dispatch_source_set_event_handler(timer, ^{
218 SOSCCPeerRateLimiterSendNextMessage_Server(peerid, accessGroupRetained);
221 dispatch_source_set_cancel_handler(timer, ^{
222 CFReleaseSafe(peerid);
223 CFReleaseSafe(accessGroupRetained);
226 dispatch_resume(timer);
227 [limiter.accessGroupToTimer setObject:timer forKey:accessGroup];
230 static void setRateLimitingCounters(SOSAccount* account, PeerRateLimiter* limiter, NSString* attribute)
232 CFErrorRef error = NULL;
234 NSMutableDictionary* counters = (__bridge NSMutableDictionary*) SOSAccountGetValue(account, kSOSRateLimitingCounters, &error);
237 counters = [[NSMutableDictionary alloc]init];
240 [counters setObject:[limiter diagnostics] forKey:attribute];
241 SOSAccountSetValue(account, kSOSRateLimitingCounters, (__bridge CFDictionaryRef)counters, &error);
243 //figure out whether or not an access group should be judged, held back, or sent
244 static bool SOSPeerShouldSend(CFArrayRef attributes, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
246 NSDate *date = [NSDate date];
247 __block bool peerShouldSend = false;
248 PeerRateLimiter *limiter = getRateLimiter(peer);
249 __block NSMutableDictionary *powerlogPayload = nil;
250 static NSMutableDictionary* attributeRateLimiting = nil;
252 static dispatch_once_t onceToken;
253 dispatch_once(&onceToken, ^{
254 attributeRateLimiting = [[NSMutableDictionary alloc] init];
258 peerShouldSend = true;
261 secnotice("ratelimit", "number of attributes to review: %lu", CFArrayGetCount(attributes));
262 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
263 enum RateLimitState state = [limiter stateForAccessGroup:attribute];
266 case RateLimitStateCanSend:
269 KeychainItem *item = [[KeychainItem alloc] initWithAccessGroup: attribute];
270 NSInteger badness = [limiter judge:item at:date limitTime:&limit];
271 secnotice("ratelimit","accessGroup: %@, judged: %lu", attribute, (long)badness);
273 NSNumber *currentBadness = attributeRateLimiting[attribute];
274 NSNumber *newBadness = @(badness);
276 if (![currentBadness isEqual:newBadness]) {
277 attributeRateLimiting[attribute] = newBadness;
278 if (powerlogPayload == NULL) {
279 powerlogPayload = [[NSMutableDictionary alloc] init];
281 powerlogPayload[attribute] = newBadness;
284 double delta = [limit timeIntervalSinceDate:date];
287 //setting counters for attribute being rate limited
288 setRateLimitingCounters([transport SOSTransportMessageGetAccount],limiter, attribute);
290 secnotice("ratelimit", "setting a timer for next sync: %@", limit);
292 SOSPeerSetNextTimeToSend(limiter, delta, attribute, peer, transport, message_to_send);
293 //set rate limit state to hold
294 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateHoldMessage] forKey:attribute];
296 peerShouldSend = true;
300 case RateLimitStateHoldMessage:
302 secnotice("ratelimit","access group: %@ is being rate limited", attribute);
307 secnotice("ratelimit","no state for limiter for peer: %@", peer);
312 if ([powerlogPayload count]) {
313 SecPLLogRegisteredEvent(@"SOSKVSRateLimitingEvent",
315 @"timestamp" : @([date timeIntervalSince1970]),
316 @"peerShouldSend" : @(peerShouldSend),
317 @"attributeBadness" : powerlogPayload
320 return peerShouldSend;
323 //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!
324 static void SOSTransportSendPendingMessage(CFArrayRef attributes, SOSMessage* transport, SOSPeerRef peer){
325 PeerRateLimiter *limiter = getRateLimiter(peer);
327 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
328 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:attribute];
330 CFErrorRef error = NULL;
331 bool sendResult = [transport SOSTransportMessageSendMessage:transport id:SOSPeerGetID(peer) messageToSend:(__bridge CFDataRef)message err:&error];
333 if(!sendResult || error){
334 secnotice("ratelimit", "SOSTransportSendPendingMessage: could not send message: %@", error);
337 secnotice("ratelimit", "SOSTransportSendPendingMessage: sent pending message: %@ for access group: %@", message, attribute);
339 [limiter.accessGroupToNextMessageToSend removeObjectForKey:attribute];
340 //cancel dispatch timer
341 dispatch_source_t timer = [limiter.accessGroupToTimer objectForKey:attribute];
343 dispatch_cancel(timer);
344 [limiter.accessGroupToTimer removeObjectForKey:attribute];
345 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:attribute];
350 //update last time message sent for this peer
351 -(void) SOSTransportMessageUpdateLastMessageSentTimetstamp:(SOSAccount*)account peer:(SOSPeerRef)peer
353 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
355 peerRTTs = [NSMutableDictionary dictionary];
357 if([peerRTTs objectForKey:(__bridge NSString*)SOSPeerGetID(peer) ] == nil){
358 [peerRTTs setObject:[NSDate date] forKey:(__bridge NSString*)SOSPeerGetID(peer)];
359 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
362 -(bool) SOSTransportMessageSendMessageIfNeeded:(SOSMessage*) transport id:(CFStringRef) circle_id pID:(CFStringRef) peer_id err:(CFErrorRef *)error
364 __block bool ok = true;
365 SOSAccount* account = transport.account;
367 BOOL initialSync = account.isInitialSyncing;
368 ok &= SOSEngineWithPeerID((SOSEngineRef)transport.engine, peer_id, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
369 // Now under engine lock do stuff
370 CFDataRef message_to_send = NULL;
371 SOSEnginePeerMessageSentCallback* sentCallback = NULL;
372 CFMutableArrayRef attributes = NULL;
373 ok = SOSPeerCoderSendMessageIfNeeded([transport SOSTransportMessageGetAccount],(SOSEngineRef)transport.engine, txn, peer, coder, &message_to_send, peer_id, &attributes, &sentCallback, error);
374 secnotice("ratelimit","attribute list: %@", attributes);
375 bool shouldSend = true;
377 if(attributes == NULL){ //no attribute but still should be rate limited
378 attributes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
379 CFArrayAppendValue(attributes, CFSTR("NoAttribute"));
383 secnotice("ratelimit","not going to rate limit, currently in initial sync");
385 if(!initialSync && message_to_send){ //need to judge the message if not in initial sync
386 secnotice("ratelimit","not in initial sync!");
387 shouldSend = SOSPeerShouldSend(attributes, peer, transport, message_to_send);
388 CFRange range = CFRangeMake(0, CFArrayGetCount(attributes));
389 if(CFArrayContainsValue(attributes, range, kSecAccessGroupCKKS) ||
390 CFArrayContainsValue(attributes, range, kSecAccessGroupSBD) ||
391 CFArrayContainsValue(attributes, range, kSecAccessGroupSecureBackupd)){
395 secnotice("ratelimit","should send? : %@", shouldSend ? @"YES" : @"NO");
397 if (shouldSend && message_to_send) {
398 SOSTransportSendPendingMessage(attributes, transport, peer);
399 ok = ok && [transport SOSTransportMessageSendMessage:transport id:peer_id messageToSend:message_to_send err:error];
401 SOSEngineMessageCallCallback(sentCallback, ok);
403 [transport SOSTransportMessageUpdateLastMessageSentTimetstamp:account peer:peer];
405 }else if(!shouldSend){
406 secnotice("ratelimit", "peer is rate limited: %@", peer_id);
408 secnotice("transport", "no message to send to peer: %@", peer_id);
411 SOSEngineFreeMessageCallback(sentCallback);
413 CFReleaseSafe(message_to_send);
414 CFReleaseNull(attributes);
416 *forceSaveState = ok;