1 #include <Security/SecureObjectSync/SOSTransport.h>
2 #include <Security/SecureObjectSync/SOSTransportMessage.h>
3 #include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
4 #include <Security/SecureObjectSync/SOSKVSKeys.h>
5 #include <Security/SecureObjectSync/SOSPeerCoder.h>
6 #include <Security/SecureObjectSync/SOSEngine.h>
7 #import "Security/SecureObjectSync/SOSPeerRateLimiter.h"
8 #import "Security/SecureObjectSync/SOSPeerOTRTimer.h"
9 #include <utilities/SecADWrapper.h>
11 #include <utilities/SecCFWrappers.h>
12 #include <utilities/SecPLWrappers.h>
13 #include <SOSInternal.h>
14 #include <Security/SecureObjectSync/SOSAccountPriv.h>
15 #include <SOSCloudKeychainClient.h>
16 #include <securityd/SecItemServer.h> // TODO: Remove this layer violation.
18 static const CFStringRef kSecSOSMessageRTT = CFSTR("com.apple.security.sos.messagertt");
19 static const CFStringRef kSecAccessGroupSecureBackupd = CFSTR("com.apple.securebackupd");
20 static const CFStringRef kSecAccessGroupSBD = CFSTR("com.apple.sbd");
21 static const CFStringRef kSecAccessGroupCKKS = CFSTR("com.apple.security.ckks");
25 @implementation SOSMessage
27 @synthesize engine = engine;
28 @synthesize account = account;
29 @synthesize circleName = circleName;
31 -(id) initWithAccount:(SOSAccount*)acct andName:(NSString*)name
35 SOSEngineRef e = SOSDataSourceFactoryGetEngineForDataSourceName(acct.factory, (__bridge CFStringRef)name, NULL);
38 circleName = [[NSString alloc]initWithString:name];
43 -(CFTypeRef) SOSTransportMessageGetEngine
48 -(CFStringRef) SOSTransportMessageGetCircleName
50 return (__bridge CFStringRef)(circleName);
53 -(CFIndex) SOSTransportMessageGetTransportType
58 -(SOSAccount*) SOSTransportMessageGetAccount
63 -(bool) SOSTransportMessageSendMessages:(SOSMessage*) transport pm:(CFDictionaryRef) peer_messages err:(CFErrorRef *)error
68 -(bool) SOSTransportMessageFlushChanges:(SOSMessage*) transport err:(CFErrorRef *)error
73 -(bool) SOSTransportMessageSyncWithPeers:(SOSMessage*) transport p:(CFSetRef) peers err:(CFErrorRef *)error{
76 -(bool) SOSTransportMessageCleanupAfterPeerMessages:(SOSMessage*) transport peers:(CFDictionaryRef) peers err:(CFErrorRef*) error{
80 -(bool) SOSTransportMessageSendMessage:(SOSMessage*) transport id:(CFStringRef) peerID messageToSend:(CFDataRef) message err:(CFErrorRef *)error
82 CFDictionaryRef peerMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peerID, message, NULL);
84 bool result = [self SOSTransportMessageSendMessages:transport pm:peerMessage err:error];
86 CFReleaseNull(peerMessage);
90 bool SOSEngineHandleCodedMessage(SOSAccount* account, SOSEngineRef engine, CFStringRef peerID, CFDataRef codedMessage, CFErrorRef*error) {
91 __block bool result = true;
92 __block bool somethingChanged = false;
93 result &= SOSEngineWithPeerID(engine, peerID, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *shouldSave) {
94 CFDataRef decodedMessage = NULL;
95 enum SOSCoderUnwrapStatus uwstatus = SOSPeerHandleCoderMessage(peer, coder, peerID, codedMessage, &decodedMessage, shouldSave, error);
96 NSMutableDictionary* attemptsPerPeer = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountRenegotiationRetryCount, NULL);
97 //clear the max retry only if negotiation has finished and a counter exists
98 if(coder != NULL && attemptsPerPeer != nil && !SOSCoderIsCoderInAwaitingState(coder) && ([attemptsPerPeer objectForKey:(__bridge NSString*)peerID] != NULL)){
99 secnotice("otrtimer", "otr negotiation completed! clearing max retry counter");
100 SOSPeerOTRTimerClearMaxRetryCount(account, (__bridge NSString*)peerID);
102 if (uwstatus == SOSCoderUnwrapDecoded) {
103 SOSMessageRef message = NULL;
104 if (decodedMessage && CFDataGetLength(decodedMessage)) {
105 // Only hand non empty messages to the engine, empty messages are an artifact
107 message = SOSMessageCreateWithData(kCFAllocatorDefault, decodedMessage, error);
110 bool engineHandleMessageDoesNotGetToRollbackTransactions = true;
111 result = SOSEngineHandleMessage_locked(engine, peerID, message, txn, &engineHandleMessageDoesNotGetToRollbackTransactions, &somethingChanged, error);
112 CFReleaseSafe(message);
115 result = uwstatus != SOSCoderUnwrapError;
117 CFReleaseNull(decodedMessage);
120 if (somethingChanged) {
121 SecKeychainChanged();
125 SOSCCRequestSyncWithPeer(peerID);
130 -(CFDictionaryRef) SOSTransportMessageHandlePeerMessageReturnsHandledCopy:(SOSMessage*) transport peerMessages:(CFMutableDictionaryRef) circle_peer_messages_table err:(CFErrorRef *)error
132 return CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
135 -(void) SOSTransportMessageCalculateNextTimer:(SOSAccount*)account rtt:(int)rtt peerid:(NSString*)peerid
137 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account,kSOSAccountPeerNegotiationTimeouts, NULL);
139 peerRTTs = [NSMutableDictionary dictionary];
142 NSNumber *timeout = [peerRTTs objectForKey:peerid];
144 if(timeout.intValue < (rtt * 2)){
145 NSNumber* newTimeout = [[NSNumber alloc] initWithInt: (rtt * 2)];
146 [peerRTTs setObject:newTimeout forKey:peerid];
147 secnotice("otrtimer", "New OTR RTT: %d", [newTimeout intValue]);
152 timeout = [[NSNumber alloc]initWithInt:(30 * 2)];
155 timeout = [[NSNumber alloc]initWithInt:(rtt * 2)];
157 [peerRTTs setObject:timeout forKey:peerid];
158 secnotice("otrtimer", "setting initial timeout value to rtt*2: %d", [timeout intValue]);
161 SOSAccountSetValue(account,kSOSAccountPeerNegotiationTimeouts, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
164 -(void) SOSTransportMessageUpdateRTTs:(NSString*)peerid
166 SOSAccount* account = [self SOSTransportMessageGetAccount];
167 NSMutableDictionary *peerToTimeLastSentDict = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
168 if(peerToTimeLastSentDict){
169 NSDate* storedDate = [peerToTimeLastSentDict objectForKey:peerid];
171 NSDate* currentDate = [NSDate date];
172 int rtt = [currentDate timeIntervalSinceDate:storedDate];
173 secnotice("otrtimer","current date: %@, stored date: %@", currentDate, storedDate);
174 secnotice("otrtimer", "rtt: %d", rtt);
175 [self SOSTransportMessageCalculateNextTimer:account rtt:rtt peerid:peerid];
177 SecADClientPushValueForDistributionKey(kSecSOSMessageRTT, rtt);
178 [peerToTimeLastSentDict removeObjectForKey:peerid]; //remove last sent message date
179 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerToTimeLastSentDict, NULL);
184 -(bool) SOSTransportMessageHandlePeerMessage:(SOSMessage*) transport id:(CFStringRef) peer_id cm:(CFDataRef) codedMessage err:(CFErrorRef *)error
186 [self SOSTransportMessageUpdateRTTs:(__bridge NSString*)peer_id];
188 require_quiet(SecRequirementError(transport.engine != NULL, error, CFSTR("Missing engine")), done);
189 result = SOSEngineHandleCodedMessage([transport SOSTransportMessageGetAccount], (SOSEngineRef)transport.engine, peer_id, codedMessage, error);
194 static PeerRateLimiter* getRateLimiter(SOSPeerRef peer)
196 PeerRateLimiter *limiter = nil;
198 if(!(limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer))){
199 limiter = [[PeerRateLimiter alloc] initWithPeer:peer];
200 SOSPeerSetRateLimiter(peer, (__bridge void*)limiter);
205 static void SOSPeerSetNextTimeToSend(PeerRateLimiter* limiter, int nextTimeToSync, NSString *accessGroup, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
207 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time: %d", nextTimeToSync);
209 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
210 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, nextTimeToSync * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
211 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time to sync: %llu", (nextTimeToSync * NSEC_PER_SEC));
213 [limiter.accessGroupToNextMessageToSend setObject:(__bridge NSData*)message_to_send forKey:accessGroup];
215 CFStringRef peerid = CFRetainSafe(SOSPeerGetID(peer));
216 CFStringRef accessGroupRetained = CFRetainSafe((__bridge CFStringRef)accessGroup);
218 dispatch_source_set_event_handler(timer, ^{
219 SOSCCPeerRateLimiterSendNextMessage_Server(peerid, accessGroupRetained);
222 dispatch_source_set_cancel_handler(timer, ^{
223 CFReleaseSafe(peerid);
224 CFReleaseSafe(accessGroupRetained);
227 dispatch_resume(timer);
228 [limiter.accessGroupToTimer setObject:timer forKey:accessGroup];
231 static void setRateLimitingCounters(SOSAccount* account, PeerRateLimiter* limiter, NSString* attribute)
233 CFErrorRef error = NULL;
235 NSMutableDictionary* counters = (__bridge NSMutableDictionary*) SOSAccountGetValue(account, kSOSRateLimitingCounters, &error);
238 counters = [[NSMutableDictionary alloc]init];
241 [counters setObject:[limiter diagnostics] forKey:attribute];
242 SOSAccountSetValue(account, kSOSRateLimitingCounters, (__bridge CFDictionaryRef)counters, &error);
244 //figure out whether or not an access group should be judged, held back, or sent
245 static bool SOSPeerShouldSend(CFArrayRef attributes, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
247 NSDate *date = [NSDate date];
248 __block bool peerShouldSend = false;
249 PeerRateLimiter *limiter = getRateLimiter(peer);
250 __block NSMutableDictionary *powerlogPayload = nil;
251 static NSMutableDictionary* attributeRateLimiting = nil;
253 static dispatch_once_t onceToken;
254 dispatch_once(&onceToken, ^{
255 attributeRateLimiting = [[NSMutableDictionary alloc] init];
259 peerShouldSend = true;
262 secnotice("ratelimit", "number of attributes to review: %lu", CFArrayGetCount(attributes));
263 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
264 enum RateLimitState state = [limiter stateForAccessGroup:attribute];
267 case RateLimitStateCanSend:
270 KeychainItem *item = [[KeychainItem alloc] initWithAccessGroup: attribute];
271 NSInteger badness = [limiter judge:item at:date limitTime:&limit];
272 secnotice("ratelimit","accessGroup: %@, judged: %lu", attribute, (long)badness);
274 NSNumber *currentBadness = attributeRateLimiting[attribute];
275 NSNumber *newBadness = @(badness);
277 if (![currentBadness isEqual:newBadness]) {
278 attributeRateLimiting[attribute] = newBadness;
279 if (powerlogPayload == NULL) {
280 powerlogPayload = [[NSMutableDictionary alloc] init];
282 powerlogPayload[attribute] = newBadness;
285 double delta = [limit timeIntervalSinceDate:date];
288 //setting counters for attribute being rate limited
289 setRateLimitingCounters([transport SOSTransportMessageGetAccount],limiter, attribute);
291 secnotice("ratelimit", "setting a timer for next sync: %@", limit);
293 SOSPeerSetNextTimeToSend(limiter, delta, attribute, peer, transport, message_to_send);
294 //set rate limit state to hold
295 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateHoldMessage] forKey:attribute];
297 peerShouldSend = true;
301 case RateLimitStateHoldMessage:
303 secnotice("ratelimit","access group: %@ is being rate limited", attribute);
308 secnotice("ratelimit","no state for limiter for peer: %@", peer);
313 if ([powerlogPayload count]) {
314 SecPLLogRegisteredEvent(@"SOSKVSRateLimitingEvent",
316 @"timestamp" : @([date timeIntervalSince1970]),
317 @"peerShouldSend" : @(peerShouldSend),
318 @"attributeBadness" : powerlogPayload
321 return peerShouldSend;
324 //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!
325 static void SOSTransportSendPendingMessage(CFArrayRef attributes, SOSMessage* transport, SOSPeerRef peer){
326 PeerRateLimiter *limiter = getRateLimiter(peer);
328 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
329 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:attribute];
331 CFErrorRef error = NULL;
332 bool sendResult = [transport SOSTransportMessageSendMessage:transport id:SOSPeerGetID(peer) messageToSend:(__bridge CFDataRef)message err:&error];
334 if(!sendResult || error){
335 secnotice("ratelimit", "SOSTransportSendPendingMessage: could not send message: %@", error);
338 secnotice("ratelimit", "SOSTransportSendPendingMessage: sent pending message: %@ for access group: %@", message, attribute);
340 [limiter.accessGroupToNextMessageToSend removeObjectForKey:attribute];
341 //cancel dispatch timer
342 dispatch_source_t timer = [limiter.accessGroupToTimer objectForKey:attribute];
344 dispatch_cancel(timer);
345 [limiter.accessGroupToTimer removeObjectForKey:attribute];
346 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:attribute];
351 //update last time message sent for this peer
352 -(void) SOSTransportMessageUpdateLastMessageSentTimetstamp:(SOSAccount*)account peer:(SOSPeerRef)peer
354 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
356 peerRTTs = [NSMutableDictionary dictionary];
358 if([peerRTTs objectForKey:(__bridge NSString*)SOSPeerGetID(peer) ] == nil){
359 [peerRTTs setObject:[NSDate date] forKey:(__bridge NSString*)SOSPeerGetID(peer)];
360 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
363 -(bool) SOSTransportMessageSendMessageIfNeeded:(SOSMessage*) transport id:(CFStringRef) circle_id pID:(CFStringRef) peer_id err:(CFErrorRef *)error
365 __block bool ok = true;
366 SOSAccount* account = transport.account;
368 BOOL initialSync = account.isInitialSyncing;
369 ok &= SOSEngineWithPeerID((SOSEngineRef)transport.engine, peer_id, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
370 // Now under engine lock do stuff
371 CFDataRef message_to_send = NULL;
372 SOSEnginePeerMessageSentCallback* sentCallback = NULL;
373 CFMutableArrayRef attributes = NULL;
374 ok = SOSPeerCoderSendMessageIfNeeded([transport SOSTransportMessageGetAccount],(SOSEngineRef)transport.engine, txn, peer, coder, &message_to_send, peer_id, &attributes, &sentCallback, error);
375 secnotice("ratelimit","attribute list: %@", attributes);
376 bool shouldSend = true;
378 if(attributes == NULL){ //no attribute but still should be rate limited
379 attributes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
380 CFArrayAppendValue(attributes, CFSTR("NoAttribute"));
384 secnotice("ratelimit","not going to rate limit, currently in initial sync");
386 if(!initialSync && message_to_send){ //need to judge the message if not in initial sync
387 secnotice("ratelimit","not in initial sync!");
388 shouldSend = SOSPeerShouldSend(attributes, peer, transport, message_to_send);
389 CFRange range = CFRangeMake(0, CFArrayGetCount(attributes));
390 if(CFArrayContainsValue(attributes, range, kSecAccessGroupCKKS) ||
391 CFArrayContainsValue(attributes, range, kSecAccessGroupSBD) ||
392 CFArrayContainsValue(attributes, range, kSecAccessGroupSecureBackupd)){
396 secnotice("ratelimit","should send? : %@", shouldSend ? @"YES" : @"NO");
398 if (shouldSend && message_to_send) {
399 SOSTransportSendPendingMessage(attributes, transport, peer);
400 ok = ok && [transport SOSTransportMessageSendMessage:transport id:peer_id messageToSend:message_to_send err:error];
402 SOSEngineMessageCallCallback(sentCallback, ok);
404 [transport SOSTransportMessageUpdateLastMessageSentTimetstamp:account peer:peer];
406 }else if(!shouldSend){
407 secnotice("ratelimit", "peer is rate limited: %@", peer_id);
409 secnotice("transport", "no message to send to peer: %@", peer_id);
412 SOSEngineFreeMessageCallback(sentCallback);
414 CFReleaseSafe(message_to_send);
415 CFReleaseNull(attributes);
417 *forceSaveState = ok;