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