]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSTransportMessage.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / SecureObjectSync / SOSTransportMessage.m
1 #include "keychain/SecureObjectSync/SOSTransport.h"
2 #include "keychain/SecureObjectSync/SOSTransportMessage.h"
3 #include "keychain/SecureObjectSync/SOSKVSKeys.h"
4 #include "keychain/SecureObjectSync/SOSPeerCoder.h"
5 #include "keychain/SecureObjectSync/SOSEngine.h"
6 #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h"
7 #import "keychain/SecureObjectSync/SOSPeerOTRTimer.h"
8 #include <utilities/SecADWrapper.h>
9
10 #include <utilities/SecCFWrappers.h>
11 #include <utilities/SecPLWrappers.h>
12 #include "keychain/SecureObjectSync/SOSInternal.h"
13 #include "keychain/SecureObjectSync/SOSAccountPriv.h"
14 #include "keychain/SecureObjectSync/CKBridge/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 switch (uwstatus) {
102 case 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 if (!result) {
114 secnotice("engine", "Failed to handle message from peer %@: %@", peerID, error != NULL ? *error : NULL);
115 }
116 } else {
117 secnotice("engine", "Failed to turn a data gram into an SOSMessage: %@", error != NULL ? *error : NULL);
118 result = SOSErrorCreate(KSOSCantParseSOSMessage, error, NULL, CFSTR("Failed to parse SOSMessage"));
119 }
120 break;
121 }
122 case SOSCoderUnwrapHandled: {
123 secnotice("engine", "coder handled a negotiation message");
124 result = true;
125 break;
126 }
127 case SOSCoderUnwrapError:
128 default: {
129 secnotice("engine", "coder handled a error message: %d (error: %@)", (int)uwstatus, error != NULL ? *error : NULL);
130 result = false;
131 break;
132 }
133 }
134 CFReleaseNull(decodedMessage);
135 });
136
137 if (somethingChanged) {
138 SecKeychainChanged();
139 }
140
141 if (result) {
142 SOSCCRequestSyncWithPeer(peerID);
143 }
144
145 return result;
146 }
147 -(CFDictionaryRef) SOSTransportMessageHandlePeerMessageReturnsHandledCopy:(SOSMessage*) transport peerMessages:(CFMutableDictionaryRef) circle_peer_messages_table err:(CFErrorRef *)error
148 {
149 return CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
150 }
151
152 -(void) SOSTransportMessageCalculateNextTimer:(SOSAccount*)account rtt:(int)rtt peerid:(NSString*)peerid
153 {
154 const int minRTT = 60;
155 const int maxRTT = 3600;
156 int newrtt = rtt *2;
157
158 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account,kSOSAccountPeerNegotiationTimeouts, NULL);
159 if(!peerRTTs) {
160 peerRTTs = [NSMutableDictionary dictionary];
161 }
162
163 // if we already have a longer rtt than requested then bail, unless it got excessive previously
164 NSNumber *timeout = [peerRTTs objectForKey:peerid];
165 if(timeout && (timeout.intValue >= rtt*2) && (newrtt <= maxRTT)) {
166 return;
167 }
168
169 // make sure newrtt is a value between minRTT and maxRTT
170 newrtt = MAX(newrtt, minRTT);
171 newrtt = MIN(newrtt, maxRTT);
172
173 NSNumber* newTimeout = [[NSNumber alloc] initWithInt: (newrtt)];
174 [peerRTTs setObject:newTimeout forKey:peerid];
175 secnotice("otrtimer", "peerID: %@ New OTR RTT: %d", peerid, newrtt);
176 SOSAccountSetValue(account,kSOSAccountPeerNegotiationTimeouts, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
177 }
178
179 -(void) SOSTransportMessageUpdateRTTs:(NSString*)peerid
180 {
181 SOSAccount* account = [self SOSTransportMessageGetAccount];
182 NSMutableDictionary *peerToTimeLastSentDict = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
183 if(peerToTimeLastSentDict){
184 NSDate* storedDate = [peerToTimeLastSentDict objectForKey:peerid];
185 if(storedDate){
186 NSDate* currentDate = [NSDate date];
187 int rtt = [currentDate timeIntervalSinceDate:storedDate];
188 secnotice("otrtimer","peerID: %@ current date: %@, stored date: %@", peerid, currentDate, storedDate);
189 secnotice("otrtimer", "rtt: %d", rtt);
190 [self SOSTransportMessageCalculateNextTimer:account rtt:rtt peerid:peerid];
191
192 SecADClientPushValueForDistributionKey(kSecSOSMessageRTT, rtt);
193 [peerToTimeLastSentDict removeObjectForKey:peerid]; //remove last sent message date
194 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerToTimeLastSentDict, NULL);
195 }
196 }
197 }
198
199 -(bool) SOSTransportMessageHandlePeerMessage:(SOSMessage*) transport id:(CFStringRef) peer_id cm:(CFDataRef) codedMessage err:(CFErrorRef *)error
200 {
201 [self SOSTransportMessageUpdateRTTs:(__bridge NSString*)peer_id];
202 bool result = false;
203 require_quiet(SecRequirementError(transport.engine != NULL, error, CFSTR("Missing engine")), done);
204 result = SOSEngineHandleCodedMessage([transport SOSTransportMessageGetAccount], (SOSEngineRef)transport.engine, peer_id, codedMessage, error);
205 done:
206 return result;
207 }
208
209 static PeerRateLimiter* getRateLimiter(SOSPeerRef peer)
210 {
211 PeerRateLimiter *limiter = nil;
212
213 if(!(limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer))){
214 limiter = [[PeerRateLimiter alloc] initWithPeer:peer];
215 SOSPeerSetRateLimiter(peer, (__bridge void*)limiter);
216 }
217 return limiter;
218 }
219
220 static void SOSPeerSetNextTimeToSend(PeerRateLimiter* limiter, int nextTimeToSync, NSString *accessGroup, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
221 {
222 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time: %d", nextTimeToSync);
223
224 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
225 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, nextTimeToSync * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
226 secnotice("ratelimit", "SOSPeerSetNextTimeToSend next time to sync: %llu", (nextTimeToSync * NSEC_PER_SEC));
227
228 [limiter.accessGroupToNextMessageToSend setObject:(__bridge NSData*)message_to_send forKey:accessGroup];
229
230 CFStringRef peerid = CFRetainSafe(SOSPeerGetID(peer));
231 CFStringRef accessGroupRetained = CFRetainSafe((__bridge CFStringRef)accessGroup);
232
233 dispatch_source_set_event_handler(timer, ^{
234 SOSCCPeerRateLimiterSendNextMessage_Server(peerid, accessGroupRetained);
235 });
236
237 dispatch_source_set_cancel_handler(timer, ^{
238 CFReleaseSafe(peerid);
239 CFReleaseSafe(accessGroupRetained);
240 });
241
242 dispatch_resume(timer);
243 [limiter.accessGroupToTimer setObject:timer forKey:accessGroup];
244 }
245
246 static void setRateLimitingCounters(SOSAccount* account, PeerRateLimiter* limiter, NSString* attribute)
247 {
248 CFErrorRef error = NULL;
249
250 NSMutableDictionary* counters = (__bridge NSMutableDictionary*) SOSAccountGetValue(account, kSOSRateLimitingCounters, &error);
251
252 if(!counters){
253 counters = [[NSMutableDictionary alloc]init];
254 }
255
256 [counters setObject:[limiter diagnostics] forKey:attribute];
257 SOSAccountSetValue(account, kSOSRateLimitingCounters, (__bridge CFDictionaryRef)counters, &error);
258 }
259 //figure out whether or not an access group should be judged, held back, or sent
260 static bool SOSPeerShouldSend(CFArrayRef attributes, SOSPeerRef peer, SOSMessage* transport, CFDataRef message_to_send)
261 {
262 NSDate *date = [NSDate date];
263 __block bool peerShouldSend = false;
264 PeerRateLimiter *limiter = getRateLimiter(peer);
265 __block NSMutableDictionary *powerlogPayload = nil;
266 static NSMutableDictionary* attributeRateLimiting = nil;
267
268 static dispatch_once_t onceToken;
269 dispatch_once(&onceToken, ^{
270 attributeRateLimiting = [[NSMutableDictionary alloc] init];
271 });
272
273 if(!attributes){
274 peerShouldSend = true;
275 }
276 else{
277 secnotice("ratelimit", "number of attributes to review: %lu", CFArrayGetCount(attributes));
278 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
279 enum RateLimitState state = [limiter stateForAccessGroup:attribute];
280
281 switch(state){
282 case RateLimitStateCanSend:
283 {
284 NSDate *limit = nil;
285 KeychainItem *item = [[KeychainItem alloc] initWithAccessGroup: attribute];
286 NSInteger badness = [limiter judge:item at:date limitTime:&limit];
287 secnotice("ratelimit","accessGroup: %@, judged: %lu", attribute, (long)badness);
288
289 NSNumber *currentBadness = attributeRateLimiting[attribute];
290 NSNumber *newBadness = @(badness);
291
292 if (![currentBadness isEqual:newBadness]) {
293 attributeRateLimiting[attribute] = newBadness;
294 if (powerlogPayload == NULL) {
295 powerlogPayload = [[NSMutableDictionary alloc] init];
296 }
297 powerlogPayload[attribute] = newBadness;
298 }
299
300 double delta = [limit timeIntervalSinceDate:date];
301
302 if(delta > 0){
303 //setting counters for attribute being rate limited
304 setRateLimitingCounters([transport SOSTransportMessageGetAccount],limiter, attribute);
305
306 secnotice("ratelimit", "setting a timer for next sync: %@", limit);
307
308 SOSPeerSetNextTimeToSend(limiter, delta, attribute, peer, transport, message_to_send);
309 //set rate limit state to hold
310 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateHoldMessage] forKey:attribute];
311 }else{
312 peerShouldSend = true;
313 }
314 }
315 break;
316 case RateLimitStateHoldMessage:
317 {
318 secnotice("ratelimit","access group: %@ is being rate limited", attribute);
319 }
320 break;
321 default:
322 {
323 secnotice("ratelimit","no state for limiter for peer: %@", peer);
324 }
325 };
326 }];
327 }
328 if ([powerlogPayload count]) {
329 SecPLLogRegisteredEvent(@"SOSKVSRateLimitingEvent",
330 @{
331 @"timestamp" : @([date timeIntervalSince1970]),
332 @"peerShouldSend" : @(peerShouldSend),
333 @"attributeBadness" : powerlogPayload
334 });
335 }
336 return peerShouldSend;
337 }
338
339 //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!
340 static void SOSTransportSendPendingMessage(CFArrayRef attributes, SOSMessage* transport, SOSPeerRef peer){
341 PeerRateLimiter *limiter = getRateLimiter(peer);
342
343 [(__bridge NSArray*)attributes enumerateObjectsUsingBlock:^(NSString* attribute, NSUInteger idx, BOOL * _Nonnull stop) {
344 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:attribute];
345 if(message){
346 CFErrorRef error = NULL;
347 bool sendResult = [transport SOSTransportMessageSendMessage:transport id:SOSPeerGetID(peer) messageToSend:(__bridge CFDataRef)message err:&error];
348
349 if(!sendResult || error){
350 secnotice("ratelimit", "SOSTransportSendPendingMessage: could not send message: %@", error);
351 }
352 else{
353 secnotice("ratelimit", "SOSTransportSendPendingMessage: sent pending message: %@ for access group: %@", message, attribute);
354 }
355 [limiter.accessGroupToNextMessageToSend removeObjectForKey:attribute];
356 //cancel dispatch timer
357 dispatch_source_t timer = [limiter.accessGroupToTimer objectForKey:attribute];
358 if(timer)
359 dispatch_cancel(timer);
360 [limiter.accessGroupToTimer removeObjectForKey:attribute];
361 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:attribute];
362 }
363 }];
364 }
365
366 //update last time message sent for this peer
367 -(void) SOSTransportMessageUpdateLastMessageSentTimetstamp:(SOSAccount*)account peer:(SOSPeerRef)peer
368 {
369 NSMutableDictionary *peerRTTs = (__bridge NSMutableDictionary*)SOSAccountGetValue(account, kSOSAccountPeerLastSentTimestamp, NULL);
370 if(!peerRTTs){
371 peerRTTs = [NSMutableDictionary dictionary];
372 }
373 if([peerRTTs objectForKey:(__bridge NSString*)SOSPeerGetID(peer) ] == nil){
374 [peerRTTs setObject:[NSDate date] forKey:(__bridge NSString*)SOSPeerGetID(peer)];
375 SOSAccountSetValue(account, kSOSAccountPeerLastSentTimestamp, (__bridge CFMutableDictionaryRef)peerRTTs, NULL);
376 }
377 }
378 -(bool) SOSTransportMessageSendMessageIfNeeded:(SOSMessage*)transport
379 circleName:(CFStringRef)circle_id
380 pID:(CFStringRef)peer_id
381 err:(CFErrorRef *)error
382 {
383 __block bool ok = true;
384 SOSAccount* account = transport.account;
385
386 BOOL initialSyncDone = SOSAccountHasCompletedInitialSync(account);
387 ok &= SOSEngineWithPeerID((SOSEngineRef)transport.engine, peer_id, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
388 // Now under engine lock do stuff
389 CFDataRef message_to_send = NULL;
390 SOSEnginePeerMessageSentCallback* sentCallback = NULL;
391 CFMutableArrayRef attributes = NULL;
392 ok = SOSPeerCoderSendMessageIfNeeded([transport SOSTransportMessageGetAccount],(SOSEngineRef)transport.engine, txn, peer, coder, &message_to_send, peer_id, &attributes, &sentCallback, error);
393 secnotice("ratelimit","attribute list: %@", attributes);
394 bool shouldSend = true;
395
396 if(attributes == NULL){ //no attribute but still should be rate limited
397 attributes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
398 CFArrayAppendValue(attributes, CFSTR("NoAttribute"));
399 }
400
401 if(!initialSyncDone){
402 secnotice("ratelimit","not going to rate limit, currently in initial sync");
403 }
404 if(initialSyncDone && message_to_send){ //need to judge the message if not in initial sync
405 secnotice("ratelimit","not in initial sync!");
406 shouldSend = SOSPeerShouldSend(attributes, peer, transport, message_to_send);
407 CFRange range = CFRangeMake(0, CFArrayGetCount(attributes));
408 if(CFArrayContainsValue(attributes, range, kSecAccessGroupCKKS) ||
409 CFArrayContainsValue(attributes, range, kSecAccessGroupSBD) ||
410 CFArrayContainsValue(attributes, range, kSecAccessGroupSecureBackupd)){
411 shouldSend = true;
412 }
413
414 secnotice("ratelimit","should send? : %@", shouldSend ? @"YES" : @"NO");
415 }
416 if (shouldSend && message_to_send) {
417 SOSTransportSendPendingMessage(attributes, transport, peer);
418 ok = ok && [transport SOSTransportMessageSendMessage:transport id:peer_id messageToSend:message_to_send err:error];
419
420 SOSEngineMessageCallCallback(sentCallback, ok);
421
422 [transport SOSTransportMessageUpdateLastMessageSentTimetstamp:account peer:peer];
423
424 }else if(!shouldSend){
425 secnotice("ratelimit", "peer is rate limited: %@", peer_id);
426 }else{
427 secnotice("transport", "no message to send to peer: %@", peer_id);
428 }
429
430 SOSEngineFreeMessageCallback(sentCallback);
431 sentCallback = NULL;
432 CFReleaseSafe(message_to_send);
433 CFReleaseNull(attributes);
434
435 *forceSaveState = ok;
436 });
437
438 return ok;
439 }
440
441 @end
442