]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSTransportMessage.m
Security-58286.270.3.0.1.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/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>
9
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.
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 self = [super init];
33 if(self){
34 SOSEngineRef e = SOSDataSourceFactoryGetEngineForDataSourceName(acct.factory, (__bridge CFStringRef)name, NULL);
35 engine = e;
36 account = acct;
37 circleName = [[NSString alloc]initWithString:name];
38 }
39 return self;
40 }
41
42 -(CFTypeRef) SOSTransportMessageGetEngine
43 {
44 return engine;
45 }
46
47 -(CFStringRef) SOSTransportMessageGetCircleName
48 {
49 return (__bridge CFStringRef)(circleName);
50 }
51
52 -(CFIndex) SOSTransportMessageGetTransportType
53 {
54 return kUnknown;
55 }
56
57 -(SOSAccount*) SOSTransportMessageGetAccount
58 {
59 return account;
60 }
61
62 -(bool) SOSTransportMessageSendMessages:(SOSMessage*) transport pm:(CFDictionaryRef) peer_messages err:(CFErrorRef *)error
63 {
64 return true;
65 }
66
67 -(bool) SOSTransportMessageFlushChanges:(SOSMessage*) transport err:(CFErrorRef *)error
68 {
69 return true;
70 }
71
72 -(bool) SOSTransportMessageSyncWithPeers:(SOSMessage*) transport p:(CFSetRef) peers err:(CFErrorRef *)error{
73 return true;
74 }
75 -(bool) SOSTransportMessageCleanupAfterPeerMessages:(SOSMessage*) transport peers:(CFDictionaryRef) peers err:(CFErrorRef*) error{
76 return true;
77 }
78
79 -(bool) SOSTransportMessageSendMessage:(SOSMessage*) transport id:(CFStringRef) peerID messageToSend:(CFDataRef) message err:(CFErrorRef *)error
80 {
81 CFDictionaryRef peerMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peerID, message, NULL);
82
83 bool result = [self SOSTransportMessageSendMessages:transport pm:peerMessage err:error];
84
85 CFReleaseNull(peerMessage);
86 return result;
87 }
88
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);
100 }
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
105 // of coder startup.
106 message = SOSMessageCreateWithData(kCFAllocatorDefault, decodedMessage, error);
107 }
108 if (message) {
109 bool engineHandleMessageDoesNotGetToRollbackTransactions = true;
110 result = SOSEngineHandleMessage_locked(engine, peerID, message, txn, &engineHandleMessageDoesNotGetToRollbackTransactions, &somethingChanged, error);
111 CFReleaseSafe(message);
112 }
113 } else {
114 result = uwstatus != SOSCoderUnwrapError;
115 }
116 CFReleaseNull(decodedMessage);
117 });
118
119 if (somethingChanged) {
120 SecKeychainChanged();
121 }
122
123 if (result) {
124 SOSCCRequestSyncWithPeer(peerID);
125 }
126
127 return result;
128 }
129 -(CFDictionaryRef) SOSTransportMessageHandlePeerMessageReturnsHandledCopy:(SOSMessage*) transport peerMessages:(CFMutableDictionaryRef) circle_peer_messages_table err:(CFErrorRef *)error
130 {
131 return CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
132 }
133
134 -(void) SOSTransportMessageCalculateNextTimer:(SOSAccount*)account rtt:(int)rtt peerid:(NSString*)peerid
135 {
136 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account,kSOSAccountPeerNegotiationTimeouts, NULL);
137 if(!peerRTTs){
138 peerRTTs = [NSMutableDictionary dictionary];
139 }
140 else{
141 NSNumber *timeout = [peerRTTs objectForKey:peerid];
142 if(timeout){
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]);
147 }
148 }
149 else{
150 if(rtt < 15){
151 timeout = [[NSNumber alloc]initWithInt:(30 * 2)];
152 }
153 else{
154 timeout = [[NSNumber alloc]initWithInt:(rtt * 2)];
155 }
156 [peerRTTs setObject:timeout forKey:peerid];
157 secnotice("otrtimer", "setting initial timeout value to rtt*2: %d", [timeout intValue]);
158 }
159 }
160 SOSAccountSetValue(account,kSOSAccountPeerNegotiationTimeouts, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
161 }
162
163 -(void) SOSTransportMessageUpdateRTTs:(NSString*)peerid
164 {
165 SOSAccount* account = [self SOSTransportMessageGetAccount];
166 NSMutableDictionary *peerToTimeLastSentDict = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
167 if(peerToTimeLastSentDict){
168 NSDate* storedDate = [peerToTimeLastSentDict objectForKey:peerid];
169 if(storedDate){
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];
175
176 SecADClientPushValueForDistributionKey(kSecSOSMessageRTT, rtt);
177 [peerToTimeLastSentDict removeObjectForKey:peerid]; //remove last sent message date
178 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerToTimeLastSentDict, NULL);
179 }
180 }
181 }
182
183 -(bool) SOSTransportMessageHandlePeerMessage:(SOSMessage*) transport id:(CFStringRef) peer_id cm:(CFDataRef) codedMessage err:(CFErrorRef *)error
184 {
185 [self SOSTransportMessageUpdateRTTs:(__bridge NSString*)peer_id];
186 bool result = false;
187 require_quiet(SecRequirementError(transport.engine != NULL, error, CFSTR("Missing engine")), done);
188 result = SOSEngineHandleCodedMessage([transport SOSTransportMessageGetAccount], (SOSEngineRef)transport.engine, peer_id, codedMessage, error);
189 done:
190 return result;
191 }
192
193 static PeerRateLimiter* getRateLimiter(SOSPeerRef peer)
194 {
195 PeerRateLimiter *limiter = nil;
196
197 if(!(limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer))){
198 limiter = [[PeerRateLimiter alloc] initWithPeer:peer];
199 SOSPeerSetRateLimiter(peer, (__bridge void*)limiter);
200 }
201 return limiter;
202 }
203
204 static void SOSPeerSetNextTimeToSend(PeerRateLimiter* limiter, int nextTimeToSync, NSString *accessGroup, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
205 {
206 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time: %d", nextTimeToSync);
207
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));
211
212 [limiter.accessGroupToNextMessageToSend setObject:(__bridge NSData*)message_to_send forKey:accessGroup];
213
214 CFStringRef peerid = CFRetainSafe(SOSPeerGetID(peer));
215 CFStringRef accessGroupRetained = CFRetainSafe((__bridge CFStringRef)accessGroup);
216
217 dispatch_source_set_event_handler(timer, ^{
218 SOSCCPeerRateLimiterSendNextMessage_Server(peerid, accessGroupRetained);
219 });
220
221 dispatch_source_set_cancel_handler(timer, ^{
222 CFReleaseSafe(peerid);
223 CFReleaseSafe(accessGroupRetained);
224 });
225
226 dispatch_resume(timer);
227 [limiter.accessGroupToTimer setObject:timer forKey:accessGroup];
228 }
229
230 static void setRateLimitingCounters(SOSAccount* account, PeerRateLimiter* limiter, NSString* attribute)
231 {
232 CFErrorRef error = NULL;
233
234 NSMutableDictionary* counters = (__bridge NSMutableDictionary*) SOSAccountGetValue(account, kSOSRateLimitingCounters, &error);
235
236 if(!counters){
237 counters = [[NSMutableDictionary alloc]init];
238 }
239
240 [counters setObject:[limiter diagnostics] forKey:attribute];
241 SOSAccountSetValue(account, kSOSRateLimitingCounters, (__bridge CFDictionaryRef)counters, &error);
242 }
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)
245 {
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;
251
252 static dispatch_once_t onceToken;
253 dispatch_once(&onceToken, ^{
254 attributeRateLimiting = [[NSMutableDictionary alloc] init];
255 });
256
257 if(!attributes){
258 peerShouldSend = true;
259 }
260 else{
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];
264
265 switch(state){
266 case RateLimitStateCanSend:
267 {
268 NSDate *limit = nil;
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);
272
273 NSNumber *currentBadness = attributeRateLimiting[attribute];
274 NSNumber *newBadness = @(badness);
275
276 if (![currentBadness isEqual:newBadness]) {
277 attributeRateLimiting[attribute] = newBadness;
278 if (powerlogPayload == NULL) {
279 powerlogPayload = [[NSMutableDictionary alloc] init];
280 }
281 powerlogPayload[attribute] = newBadness;
282 }
283
284 double delta = [limit timeIntervalSinceDate:date];
285
286 if(delta > 0){
287 //setting counters for attribute being rate limited
288 setRateLimitingCounters([transport SOSTransportMessageGetAccount],limiter, attribute);
289
290 secnotice("ratelimit", "setting a timer for next sync: %@", limit);
291
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];
295 }else{
296 peerShouldSend = true;
297 }
298 }
299 break;
300 case RateLimitStateHoldMessage:
301 {
302 secnotice("ratelimit","access group: %@ is being rate limited", attribute);
303 }
304 break;
305 default:
306 {
307 secnotice("ratelimit","no state for limiter for peer: %@", peer);
308 }
309 };
310 }];
311 }
312 if ([powerlogPayload count]) {
313 SecPLLogRegisteredEvent(@"SOSKVSRateLimitingEvent",
314 @{
315 @"timestamp" : @([date timeIntervalSince1970]),
316 @"peerShouldSend" : @(peerShouldSend),
317 @"attributeBadness" : powerlogPayload
318 });
319 }
320 return peerShouldSend;
321 }
322
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);
326
327 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
328 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:attribute];
329 if(message){
330 CFErrorRef error = NULL;
331 bool sendResult = [transport SOSTransportMessageSendMessage:transport id:SOSPeerGetID(peer) messageToSend:(__bridge CFDataRef)message err:&error];
332
333 if(!sendResult || error){
334 secnotice("ratelimit", "SOSTransportSendPendingMessage: could not send message: %@", error);
335 }
336 else{
337 secnotice("ratelimit", "SOSTransportSendPendingMessage: sent pending message: %@ for access group: %@", message, attribute);
338 }
339 [limiter.accessGroupToNextMessageToSend removeObjectForKey:attribute];
340 //cancel dispatch timer
341 dispatch_source_t timer = [limiter.accessGroupToTimer objectForKey:attribute];
342 if(timer)
343 dispatch_cancel(timer);
344 [limiter.accessGroupToTimer removeObjectForKey:attribute];
345 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:attribute];
346 }
347 }];
348 }
349
350 //update last time message sent for this peer
351 -(void) SOSTransportMessageUpdateLastMessageSentTimetstamp:(SOSAccount*)account peer:(SOSPeerRef)peer
352 {
353 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
354 if(!peerRTTs){
355 peerRTTs = [NSMutableDictionary dictionary];
356 }
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);
360 }
361 }
362 -(bool) SOSTransportMessageSendMessageIfNeeded:(SOSMessage*) transport id:(CFStringRef) circle_id pID:(CFStringRef) peer_id err:(CFErrorRef *)error
363 {
364 __block bool ok = true;
365 SOSAccount* account = transport.account;
366
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;
376
377 if(attributes == NULL){ //no attribute but still should be rate limited
378 attributes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
379 CFArrayAppendValue(attributes, CFSTR("NoAttribute"));
380 }
381
382 if(initialSync){
383 secnotice("ratelimit","not going to rate limit, currently in initial sync");
384 }
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)){
392 shouldSend = true;
393 }
394
395 secnotice("ratelimit","should send? : %@", shouldSend ? @"YES" : @"NO");
396 }
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];
400
401 SOSEngineMessageCallCallback(sentCallback, ok);
402
403 [transport SOSTransportMessageUpdateLastMessageSentTimetstamp:account peer:peer];
404
405 }else if(!shouldSend){
406 secnotice("ratelimit", "peer is rate limited: %@", peer_id);
407 }else{
408 secnotice("transport", "no message to send to peer: %@", peer_id);
409 }
410
411 SOSEngineFreeMessageCallback(sentCallback);
412 sentCallback = NULL;
413 CFReleaseSafe(message_to_send);
414 CFReleaseNull(attributes);
415
416 *forceSaveState = ok;
417 });
418
419 return ok;
420 }
421
422 @end
423