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