]> git.saurik.com Git - apple/security.git/blobdiff - KVSKeychainSyncingProxy/CKDKVSProxy.m
Security-58286.1.32.tar.gz
[apple/security.git] / KVSKeychainSyncingProxy / CKDKVSProxy.m
index 79839ce1a802c0858fdfc25f171cdb52dcd694c9..9b74ef606c493c92979cf4baba69cc175d115862 100644 (file)
 
 #include <Security/SecureObjectSync/SOSARCDefines.h>
 #include <utilities/SecCFWrappers.h>
+#include <utilities/SecPLWrappers.h>
 
 #include "SOSCloudKeychainConstants.h"
 
 #include <utilities/SecAKSWrappers.h>
 #include <utilities/SecADWrapper.h>
-
+#include <utilities/SecNSAdditions.h>
 #import "XPCNotificationDispatcher.h"
 
+
+CFStringRef const CKDAggdIncreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.increase");
+CFStringRef const CKDAggdDecreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.decrease");
+
+@interface NSSet (CKDLogging)
+- (NSString*) logKeys;
+- (NSString*) logIDs;
+@end
+
+@implementation NSSet (CKDLogging)
+- (NSString*) logKeys {
+    return [self sortedElementsJoinedByString:@" "];
+}
+
+- (NSString*) logIDs {
+    return [self sortedElementsTruncated:8 JoinedByString:@" "];
+}
+@end
+
+
 /*
  The total space available in your app’s iCloud key-value storage is 1 MB.
  The maximum number of keys you can specify is 1024, and the size limit for
@@ -99,119 +120,6 @@ const CFStringRef kSOSKVSOfficialDSIDKey = CFSTR("^OfficialDSID");
 
 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
 
-static int max_penalty_timeout = 32;
-static int seconds_per_minute = 60;
-
-static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250);      // 250ms leeway for sync events.
-
-static NSString* asNSString(NSObject* object) {
-    return [object isKindOfClass:[NSString class]] ? (NSString*) object : nil;
-}
-
-@interface NSMutableDictionary (FindAndRemove)
--(NSObject*)extractObjectForKey:(NSString*)key;
-@end
-
-@implementation NSMutableDictionary (FindAndRemove)
--(NSObject*)extractObjectForKey:(NSString*)key
-{
-    NSObject* result = [self objectForKey:key];
-    [self removeObjectForKey: key];
-    return result;
-}
-@end
-
-@interface NSSet (Emptiness)
-- (bool) isEmpty;
-@end
-
-@implementation NSSet (Emptiness)
-- (bool) isEmpty
-{
-    return [self count] == 0;
-}
-@end
-
-@interface NSSet (HasElements)
-- (bool) containsElementsNotIn: (NSSet*) other;
-@end
-
-@implementation NSSet (HasElements)
-- (bool) containsElementsNotIn: (NSSet*) other
-{
-    __block bool hasElements = false;
-    [self enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
-        if (![other containsObject:obj]) {
-            hasElements = true;
-            *stop = true;
-        }
-    }];
-    return hasElements;
-}
-
-@end
-
-@implementation NSSet (Stringizing)
-- (NSString*) sortedElementsJoinedByString: (NSString*) separator {
-    return [self sortedElementsTruncated: 0 JoinedByString: separator];
-}
-
-- (NSString*) sortedElementsTruncated: (NSUInteger) length JoinedByString: (NSString*) separator
-{
-    NSMutableArray* strings = [NSMutableArray array];
-
-    [self enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
-        NSString *stringToInsert = nil;
-        if ([obj isNSString__]) {
-            stringToInsert = obj;
-        } else {
-            stringToInsert = [obj description];
-        }
-
-        if (length > 0 && length < stringToInsert.length) {
-            stringToInsert = [stringToInsert substringToIndex:length];
-        }
-
-        [strings insertObject:stringToInsert atIndex:0];
-    }];
-
-    [strings sortUsingSelector: @selector(compare:)];
-
-    return [strings componentsJoinedByString:separator];
-}
-@end
-
-@implementation NSSet (CKDLogging)
-- (NSString*) logKeys {
-    return [self sortedElementsJoinedByString:@" "];
-}
-
-- (NSString*) logIDs {
-    return [self sortedElementsTruncated:8 JoinedByString:@" "];
-}
-@end
-
-
-@interface NSDictionary (SOSDictionaryFormat)
-- (NSString*) compactDescription;
-@end
-
-@implementation NSDictionary (SOSDictionaryFormat)
-- (NSString*) compactDescription
-{
-    NSMutableArray *elements = [NSMutableArray array];
-    [self enumerateKeysAndObjectsUsingBlock: ^(NSString *key, id obj, BOOL *stop) {
-        [elements addObject: [key stringByAppendingString: @":"]];
-        if ([obj isKindOfClass:[NSArray class]]) {
-            [elements addObject: [(NSArray *)obj componentsJoinedByString: @" "]];
-        } else {
-            [elements addObject: [NSString stringWithFormat:@"%@", obj]];
-        }
-    }];
-    return [elements componentsJoinedByString: @" "];
-}
-@end
-
 @interface UbiqitousKVSProxy ()
 @property (nonatomic) NSDictionary* persistentData;
 - (void) doSyncWithAllPeers;
@@ -403,310 +311,12 @@ static NSString* asNSString(NSObject* object) {
     }
 }
 
-
-#if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
-CFStringRef const CKDAggdIncreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.increase");
-CFStringRef const CKDAggdDecreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.decrease");
-#endif
-
-// MARK: Penalty measurement and handling
--(dispatch_source_t)setNewTimer:(int)timeout key:(NSString*)key
-{
-    __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _ckdkvsproxy_queue);
-    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
-    dispatch_source_set_event_handler(timer, ^{
-        [self penaltyTimerFired:key];
-    });
-    dispatch_resume(timer);
-    return timer;
-}
-
--(void) increasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
-{
-#if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
-    SecADAddValueForScalarKey(CKDAggdIncreaseThrottlingKey, 1);
-#endif
-
-    secnotice("backoff", "increasing penalty!");
-    int newPenalty = 0;
-    if([currentPenalty intValue] == max_penalty_timeout){
-        newPenalty = max_penalty_timeout;
-    }
-    else if ([currentPenalty intValue] == 0)
-        newPenalty = 1;
-    else
-        newPenalty = [currentPenalty intValue]*2;
-    
-    secnotice("backoff", "key %@, waiting %d minutes long to send next messages", key, newPenalty);
-    
-    NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
-    dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
-    
-    if(existingTimer != nil){
-        [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
-        dispatch_suspend(existingTimer);
-        dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
-        dispatch_resume(existingTimer);
-        [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
-    }
-    else{
-        dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
-        [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
-    }
-    
-    [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
-    [_monitor setObject:*keyEntry forKey:key];
-}
-
--(void) decreasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
+- (void)perfCounters:(void(^)(NSDictionary *counters))callback
 {
-#if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
-    SecADAddValueForScalarKey(CKDAggdDecreaseThrottlingKey, 1);
-#endif
-
-    int newPenalty = 0;
-    secnotice("backoff","decreasing penalty!");
-    if([currentPenalty intValue] == 0 || [currentPenalty intValue] == 1)
-        newPenalty = 0;
-    else
-        newPenalty = [currentPenalty intValue]/2;
-    
-    secnotice("backoff","key %@, waiting %d minutes long to send next messages", key, newPenalty);
-    
-    NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
-    
-    dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
-    if(existingTimer != nil){
-        [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
-        dispatch_suspend(existingTimer);
-        if(newPenalty != 0){
-            dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
-            dispatch_resume(existingTimer);
-            [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
-        }
-        else{
-            dispatch_resume(existingTimer);
-            dispatch_source_cancel(existingTimer);
-        }
-    }
-    else{
-        if(newPenalty != 0){
-            dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
-            [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
-        }
-    }
-    
-    [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
-    [_monitor setObject:*keyEntry forKey:key];
-    
-}
-
-- (void)penaltyTimerFired:(NSString*)key
-{
-    secnotice("backoff", "key: %@, !!!!!!!!!!!!!!!!penalty timeout is up!!!!!!!!!!!!", key);
-
-    NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
-    NSMutableDictionary *queuedMessages = [keyEntry objectForKey:kMonitorMessageQueue];
-    secnotice("backoff","key: %@, queuedMessages: %@", key, queuedMessages);
-    if(queuedMessages && [queuedMessages count] != 0){
-        secnotice("backoff","key: %@, message queue not empty, writing to KVS!", key);
-        [self setObjectsFromDictionary:queuedMessages];
-        [keyEntry setObject:[NSMutableDictionary dictionary] forKey:kMonitorMessageQueue];
-    }
-    
-    NSNumber *penalty_timeout = [keyEntry valueForKey:kMonitorPenaltyBoxKey];
-    secnotice("backoff", "key: %@, current penalty timeout: %@", key, penalty_timeout);
-
-    NSString* didWriteDuringTimeout = [keyEntry objectForKey:kMonitorDidWriteDuringPenalty];
-    if( didWriteDuringTimeout && [didWriteDuringTimeout isEqualToString:@"YES"] )
-    {
-        //increase timeout since we wrote during out penalty timeout
-        [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
-    }
-    else{
-        //decrease timeout since we successfully wrote messages out
-        [self decreasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
-    }
-    
-    //resetting the check
-    [keyEntry setObject: @"NO" forKey:kMonitorDidWriteDuringPenalty];
-    
-    //recompute the timetable and number of consecutive writes to KVS
-    NSMutableDictionary *timetable = [keyEntry valueForKey:kMonitorTimeTable];
-    NSNumber *consecutiveWrites = [keyEntry valueForKey:kMonitorConsecutiveWrites];
-    [self recordTimestampForAppropriateInterval:&timetable key:key consecutiveWrites:&consecutiveWrites];
-    
-    [keyEntry setObject:consecutiveWrites forKey:kMonitorConsecutiveWrites];
-    [keyEntry setObject:timetable forKey:kMonitorTimeTable];
-    [_monitor setObject:keyEntry forKey:key];
+    /* Collect and merge perf counters from other layers here too */
+    [self.store perfCounters:callback];
 }
 
--(NSMutableDictionary*)initializeTimeTable:(NSString*)key
-{
-    NSDate *currentTime = [NSDate date];
-    NSMutableDictionary *firstMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute], kMonitorFirstMinute, @"YES", kMonitorWroteInTimeSlice, nil];
-    NSMutableDictionary *secondMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 2],kMonitorSecondMinute, @"NO", kMonitorWroteInTimeSlice, nil];
-    NSMutableDictionary *thirdMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 3], kMonitorThirdMinute, @"NO",kMonitorWroteInTimeSlice, nil];
-    NSMutableDictionary *fourthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 4],kMonitorFourthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
-    NSMutableDictionary *fifthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 5], kMonitorFifthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
-    
-    NSMutableDictionary *timeTable = [NSMutableDictionary dictionaryWithObjectsAndKeys: firstMinute, kMonitorFirstMinute,
-                                      secondMinute, kMonitorSecondMinute,
-                                      thirdMinute, kMonitorThirdMinute,
-                                      fourthMinute, kMonitorFourthMinute,
-                                      fifthMinute, kMonitorFifthMinute, nil];
-    return timeTable;
-}
-
-- (void)initializeKeyEntry:(NSString*)key
-{
-    NSMutableDictionary *timeTable = [self initializeTimeTable:key];
-    NSDate *currentTime = [NSDate date];
-    
-    NSMutableDictionary *keyEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys: key, kMonitorMessageKey, @0, kMonitorConsecutiveWrites, currentTime, kMonitorLastWriteTimestamp, @0, kMonitorPenaltyBoxKey, timeTable, kMonitorTimeTable,[NSMutableDictionary dictionary], kMonitorMessageQueue, nil];
-    
-    [_monitor setObject:keyEntry forKey:key];
-    
-}
-
-- (void)recordTimestampForAppropriateInterval:(NSMutableDictionary**)timeTable key:(NSString*)key consecutiveWrites:(NSNumber**)consecutiveWrites
-{
-    NSDate *currentTime = [NSDate date];
-    __block int cWrites = [*consecutiveWrites intValue];
-    __block BOOL foundTimeSlot = NO;
-    __block NSMutableDictionary *previousTable = nil;
-    NSArray *sorted = [[*timeTable allKeys] sortedArrayUsingSelector:@selector(compare:)];
-    [sorted enumerateObjectsUsingBlock:^(id sortedKey, NSUInteger idx, BOOL *stop)
-     {
-         if(foundTimeSlot == YES)
-             return;
-         [*timeTable enumerateKeysAndObjectsUsingBlock: ^(id minute, id obj, BOOL *stop2)
-          {
-              if(foundTimeSlot == YES)
-                  return;
-              if([sortedKey isEqualToString:minute]){
-                  NSMutableDictionary *minutesTable = (NSMutableDictionary*)obj;
-                  NSString *minuteKey = (NSString*)minute;
-                  NSDate *date = [minutesTable valueForKey:minuteKey];
-                  if([date compare:currentTime] == NSOrderedDescending){
-                      foundTimeSlot = YES;
-                      NSString* written = [minutesTable valueForKey:kMonitorWroteInTimeSlice];
-                      if([written isEqualToString:@"NO"]){
-                          [minutesTable setObject:@"YES" forKey:kMonitorWroteInTimeSlice];
-                          if(previousTable != nil){
-                              written = [previousTable valueForKey:kMonitorWroteInTimeSlice];
-                              if([written isEqualToString:@"YES"]){
-                                  cWrites++;
-                              }
-                              else if ([written isEqualToString:@"NO"]){
-                                  cWrites = 0;
-                              }
-                          }
-                      }
-                      return;
-                  }
-                  previousTable = minutesTable;
-              }
-          }];
-     }];
-    
-    if(foundTimeSlot == NO){
-        //reset the time table
-        secnotice("backoff","didn't find a time slot, resetting the table");
-        NSMutableDictionary *lastTable = [*timeTable valueForKey:kMonitorFifthMinute];
-        NSDate *lastDate = [lastTable valueForKey:kMonitorFifthMinute];
-        
-        if((double)[currentTime timeIntervalSinceDate: lastDate] >= seconds_per_minute){
-            *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
-        }
-        else{
-            NSString* written = [lastTable valueForKey:kMonitorWroteInTimeSlice];
-            if([written isEqualToString:@"YES"]){
-                cWrites++;
-                *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
-            }
-            else{
-                *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
-            }
-        }
-        
-        *timeTable  = [self initializeTimeTable:key];
-        return;
-    }
-    *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
-}
-- (void)recordWriteToKVS:(NSDictionary *)values
-{
-    if([_monitor count] == 0){
-        [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
-         {
-             [self initializeKeyEntry: key];
-         }];
-    }
-    else{
-        [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
-         {
-             NSMutableDictionary *keyEntry = [self->_monitor objectForKey:key];
-             if(keyEntry == nil){
-                 [self initializeKeyEntry: key];
-             }
-             else{
-                 NSNumber *penalty_timeout = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
-                 NSDate *lastWriteTimestamp = [keyEntry objectForKey:kMonitorLastWriteTimestamp];
-                 NSMutableDictionary *timeTable = [keyEntry objectForKey: kMonitorTimeTable];
-                 NSNumber *existingWrites = [keyEntry objectForKey: kMonitorConsecutiveWrites];
-                 NSDate *currentTime = [NSDate date];
-                 
-                 [self recordTimestampForAppropriateInterval:&timeTable key:key consecutiveWrites:&existingWrites];
-                 
-                 int consecutiveWrites = [existingWrites intValue];
-                 secnotice("backoff","consecutive writes: %d", consecutiveWrites);
-                 [keyEntry setObject:existingWrites forKey:kMonitorConsecutiveWrites];
-                 [keyEntry setObject:timeTable forKey:kMonitorTimeTable];
-                 [keyEntry setObject:currentTime forKey:kMonitorLastWriteTimestamp];
-                 [self->_monitor setObject:keyEntry forKey:key];
-                 
-                 if([penalty_timeout intValue] != 0 || ((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites >= 5)){
-                     if([penalty_timeout intValue] != 0 && consecutiveWrites == 5){
-                         secnotice("backoff","written for 5 consecutive minutes, time to start throttling");
-                         [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
-                     }
-                     else
-                         secnotice("backoff","monitor: keys have been written for 5 or more minutes, recording we wrote during timeout");
-                   
-                     //record we wrote during a timeout
-                     [keyEntry setObject: @"YES" forKey:kMonitorDidWriteDuringPenalty];
-                 }
-                 //keep writing freely but record it
-                 else if((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites < 5){
-                     secnotice("backoff","monitor: still writing freely");
-                 }
-             }
-         }];
-    }
-}
-
-- (NSDictionary*)recordHaltedValuesAndReturnValuesToSafelyWrite:(NSDictionary *)values
-{
-    NSMutableDictionary *SafeMessages = [NSMutableDictionary dictionary];
-    [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
-     {
-         NSMutableDictionary *keyEntry = [self->_monitor objectForKey:key];
-         NSNumber *penalty = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
-         if([penalty intValue] != 0){
-             NSMutableDictionary* existingQueue = [keyEntry valueForKey:kMonitorMessageQueue];
-             
-             [existingQueue setObject:obj forKey:key];
-             
-             [keyEntry setObject:existingQueue forKey:kMonitorMessageQueue];
-             [self->_monitor setObject:keyEntry forKey:key];
-         }
-         else{
-             [SafeMessages setObject:obj forKey:key];
-         }
-     }];
-    return SafeMessages;
-}
 
 // MARK: Object setting
 
@@ -752,20 +362,17 @@ CFStringRef const CKDAggdDecreaseThrottlingKey = CFSTR("com.apple.cloudkeychainp
                      [self.store removeObjectForKey:key];
                  }
              }
+             [[self store] addOneToOutGoing];
              [self.store setObject:obj forKey:key];
          }
      }];
-
+    
     [self.store pushWrites];
 }
 
 - (void)setObjectsFromDictionary:(NSDictionary<NSString*, NSObject*> *)values
 {
-    [self recordWriteToKVS: values];
-    NSDictionary *safeValues = [self recordHaltedValuesAndReturnValuesToSafelyWrite: values];
-    if([safeValues count] !=0){
-        [self setStoreObjectsFromDictionary:safeValues];
-    }
+    [self setStoreObjectsFromDictionary:values];
 }
 
 - (void)waitForSynchronization:(void (^)(NSDictionary<NSString*, NSObject*> *results, NSError *err))handler
@@ -836,6 +443,22 @@ CFStringRef const CKDAggdDecreaseThrottlingKey = CFSTR("com.apple.cloudkeychainp
         [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
 }
 
+- (void)removeKeys: (NSArray*)keys forAccount: (NSString*) accountUUID
+{
+    secdebug(XPROXYSCOPE, "removeKeys: keys: %@", keys);
+    
+    // We only reset when we know the ID and they send the ID and it changes.
+    bool newAccount = accountUUID != nil && self.accountUUID != nil && ![accountUUID isEqualToString: self.accountUUID];
+    
+    if(newAccount){
+        secnotice(XPROXYSCOPE, "not removing keys, account UUID is for a new account");
+        return;
+    }
+    [keys enumerateObjectsUsingBlock:^(NSString*  _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
+        secnotice(XPROXYSCOPE, "removing from KVS store: %@", key);
+        [self.store removeObjectForKey:key];
+    }];
+}
 
 - (void)registerKeys: (NSDictionary*)keys forAccount: (NSString*) accountUUID
 {