--- /dev/null
+/*
+ * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
+ */
+
+/*
+ * SOSAccount.c - Implementation of the secure object syncing account.
+ * An account contains a SOSCircle for each protection domain synced.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "keychain/SecureObjectSync/SOSAccount.h"
+#import <Security/SecureObjectSync/SOSPeerInfo.h>
+#import "keychain/SecureObjectSync/SOSPeerInfoV2.h"
+#import "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
+#import "keychain/SecureObjectSync/SOSTransportCircle.h"
+#import "keychain/SecureObjectSync/SOSTransportCircleKVS.h"
+#import "keychain/SecureObjectSync/SOSTransportMessage.h"
+#import "keychain/SecureObjectSync/SOSTransportMessageKVS.h"
+#import "keychain/SecureObjectSync/SOSTransportKeyParameter.h"
+#import "keychain/SecureObjectSync/SOSKVSKeys.h"
+#import "keychain/SecureObjectSync/SOSTransport.h"
+#import "keychain/SecureObjectSync/SOSPeerCoder.h"
+#import "keychain/SecureObjectSync/SOSInternal.h"
+#import "keychain/SecureObjectSync/SOSRing.h"
+#import "keychain/SecureObjectSync/SOSRingUtils.h"
+#import "keychain/SecureObjectSync/SOSRingRecovery.h"
+#import "keychain/SecureObjectSync/SOSAccountTransaction.h"
+#import "keychain/SecureObjectSync/SOSAccountGhost.h"
+#import "keychain/SecureObjectSync/SOSPiggyback.h"
+#import "keychain/SecureObjectSync/SOSControlHelper.h"
+#import "keychain/SecureObjectSync/SOSAuthKitHelpers.h"
+
+
+#import "keychain/SecureObjectSync/SOSAccountTrust.h"
+#import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
+#import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
+#import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
+#import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
+#import "keychain/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
+#import "keychain/SecureObjectSync/SOSPeerOTRTimer.h"
+#import "keychain/SecureObjectSync/SOSPeerRateLimiter.h"
+#import "keychain/SecureObjectSync/SOSTypes.h"
+#if OCTAGON
+#import "keychain/ckks/CKKSViewManager.h"
+#import "keychain/ckks/CKKSLockStateTracker.h"
+#import "keychain/ot/OTContext.h"
+#import "keychain/ot/OTManager.h"
+#endif
+#include <Security/SecItemInternal.h>
+#include <Security/SecEntitlements.h>
+#include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h"
+#include <securityd/SecItemServer.h>
+
+
+#import "SecdWatchdog.h"
+
+#include <utilities/SecCFWrappers.h>
+#include <utilities/SecCFError.h>
+#include <utilities/SecADWrapper.h>
+
+#include <os/activity.h>
+#include <os/state_private.h>
+
+#include <utilities/SecCoreCrypto.h>
+
+#include <utilities/der_plist.h>
+#include <utilities/der_plist_internal.h>
+#include <corecrypto/ccder.h>
+
+const CFStringRef kSOSAccountName = CFSTR("AccountName");
+const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
+const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
+const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout");
+const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
+const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
+const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary");
+const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey");
+const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing");
+const CFStringRef kSOSAccountUUID = CFSTR("UUID");
+const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters");
+const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef>
+const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef>
+const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount");
+const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion");
+NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry";
+NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount";
+NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup";
+
+const uint64_t max_packet_size_over_idms = 500;
+
+
+#define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
+
+#define DATE_LENGTH 25
+const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
+
+@implementation SOSAccount
+
+// Auto synthesis for most fields works great.
+// A few CF fields need retention work when assigning.
+
+-(id) init
+{
+ self = [super init];
+ if(self){
+ self.gestalt = [NSMutableDictionary dictionary];
+ self.backup_key = nil;
+ self.deviceID = nil;
+
+ self.trust = [SOSAccountTrustClassic trustClassic];
+
+ self.queue = NULL;
+ self.user_private_timer = NULL;
+ self.factory = NULL;
+
+ self._password_tmp = nil;
+ self.isListeningForSync = false;
+ self.lock_notification_token = -1;
+
+ self.circle_transport = NULL;
+
+ self.circle_rings_retirements_need_attention = false;
+ self.engine_peer_state_needs_repair = false;
+ self.key_interests_need_updating = false;
+
+ self.change_blocks = [NSMutableArray array];
+
+ self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
+
+ self.accountKeyIsTrusted = false;
+ self.accountKeyDerivationParamters = nil;
+
+ self.accountKey = NULL;
+ self.accountPrivateKey = NULL;
+ self.previousAccountKey = NULL;
+
+ self.saveBlock = nil;
+
+ self.notifyCircleChangeOnExit = false;
+ self.notifyViewChangeOnExit = false;
+ self.notifyBackupOnExit = false;
+
+ self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if(self) {
+ CFReleaseNull(self->_accountKey);
+ CFReleaseNull(self->_accountPrivateKey);
+ CFReleaseNull(self->_previousAccountKey);
+ }
+}
+
+@synthesize accountKey = _accountKey;
+
+- (void) setAccountKey: (SecKeyRef) key {
+ CFRetainAssign(self->_accountKey, key);
+}
+
+@synthesize previousAccountKey = _previousAccountKey;
+
+- (void) setPreviousAccountKey: (SecKeyRef) key {
+ CFRetainAssign(self->_previousAccountKey, key);
+}
+
+@synthesize accountPrivateKey = _accountPrivateKey;
+
+- (void) setAccountPrivateKey: (SecKeyRef) key {
+ CFRetainAssign(self->_accountPrivateKey, key);
+}
+
+// Syntactic sugar getters
+
+- (BOOL) hasPeerInfo {
+ return self.fullPeerInfo != nil;
+}
+
+- (SOSPeerInfoRef) peerInfo {
+ return self.trust.peerInfo;
+}
+
+- (SOSFullPeerInfoRef) fullPeerInfo {
+ return self.trust.fullPeerInfo;
+}
+
+- (NSString*) peerID {
+ return self.trust.peerID;
+}
+
+-(bool) ensureFactoryCircles
+{
+ if (!self){
+ return false;
+ }
+
+ if (!self.factory){
+ return false;
+ }
+
+ NSString* circle_name = (__bridge_transfer NSString*)SOSDataSourceFactoryCopyName(self.factory);
+
+ if (!circle_name){
+ return false;
+ }
+
+ CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL));
+
+ return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL);
+}
+
+-(void)ensureOctagonPeerKeys
+{
+#if OCTAGON
+ CKKSLockStateTracker *tracker = [CKKSViewManager manager].lockStateTracker;
+ if (tracker && tracker.isLocked == false) {
+ [self.trust ensureOctagonPeerKeys:self.circle_transport];
+ }
+#endif
+}
+
+-(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f
+{
+ self = [super init];
+ if(self){
+ self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
+
+ self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)];
+
+ SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
+
+ self.trust = t;
+ self.factory = f; // We adopt the factory. kthanksbai.
+
+ self.isListeningForSync = false;
+
+ self.accountPrivateKey = NULL;
+ self._password_tmp = NULL;
+ self.user_private_timer = NULL;
+ self.lock_notification_token = NOTIFY_TOKEN_INVALID;
+
+ self.change_blocks = [NSMutableArray array];
+ self.waitForInitialSync_blocks = NULL;
+
+ self.key_transport = nil;
+ self.circle_transport = NULL;
+ self.ck_storage = nil;
+ self.kvs_message_transport = nil;
+
+ self.circle_rings_retirements_need_attention = false;
+ self.engine_peer_state_needs_repair = false;
+ self.key_interests_need_updating = false;
+
+ self.backup_key =nil;
+ self.deviceID = nil;
+
+ self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
+ self.accountKeyIsTrusted = false;
+ self.accountKeyDerivationParamters = NULL;
+ self.accountKey = NULL;
+ self.previousAccountKey = NULL;
+
+ self.saveBlock = nil;
+ }
+ return self;
+}
+
+-(BOOL)isEqual:(id) object
+{
+ if(![object isKindOfClass:[SOSAccount class]])
+ return NO;
+
+ SOSAccount* left = object;
+ return ([self.gestalt isEqual: left.gestalt] &&
+ CFEqualSafe(self.trust.trustedCircle, left.trust.trustedCircle) &&
+ [self.trust.expansion isEqual: left.trust.expansion] &&
+ CFEqualSafe(self.trust.fullPeerInfo, left.trust.fullPeerInfo));
+
+}
+
+- (void)userPublicKey:(void ((^))(BOOL trusted, NSData *spki, NSError *error))reply
+{
+ dispatch_async(self.queue, ^{
+ if (!self.accountKeyIsTrusted || self.accountKey == NULL) {
+ NSDictionary *userinfo = @{
+ (id)kCFErrorDescriptionKey : @"User public key not trusted",
+ };
+ reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
+ return;
+ }
+
+ NSData *data = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(self.accountKey));
+ if (data == NULL) {
+ NSDictionary *userinfo = @{
+ (id)kCFErrorDescriptionKey : @"User public not available",
+ };
+ reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
+ return;
+ }
+ reply(self.accountKeyIsTrusted, data, NULL);
+ });
+}
+
+- (void)kvsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply
+{
+ /* Need to collect performance counters from all subsystems, not just circle transport, don't have counters yet though */
+ SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error)
+ {
+ reply((__bridge NSDictionary *)returnedValues);
+ });
+}
+
+- (void)rateLimitingPerformanceCounters:(void(^)(NSDictionary <NSString *, NSString *> *))reply
+{
+ CFErrorRef error = NULL;
+ CFDictionaryRef rateLimitingCounters = (CFDictionaryRef)SOSAccountGetValue(self, kSOSRateLimitingCounters, &error);
+ reply((__bridge NSDictionary*)rateLimitingCounters ? (__bridge NSDictionary*)rateLimitingCounters : [NSDictionary dictionary]);
+}
+
+- (void)stashedCredentialPublicKey:(void(^)(NSData *, NSError *error))reply
+{
+ dispatch_async(self.queue, ^{
+ CFErrorRef error = NULL;
+
+ SecKeyRef user_private = SOSAccountCopyStashedUserPrivateKey(self, &error);
+ if (user_private == NULL) {
+ reply(NULL, (__bridge NSError *)error);
+ CFReleaseNull(error);
+ return;
+ }
+
+ NSData *publicKey = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(user_private));
+ CFReleaseNull(user_private);
+ reply(publicKey, NULL);
+ });
+}
+
+- (void)assertStashedAccountCredential:(void(^)(BOOL result, NSError *error))complete
+{
+ dispatch_async(self.queue, ^{
+ CFErrorRef error = NULL;
+ bool result = SOSAccountAssertStashedAccountCredential(self, &error);
+ complete(result, (__bridge NSError *)error);
+ CFReleaseNull(error);
+ });
+}
+
+static bool SyncKVSAndWait(CFErrorRef *error) {
+ dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
+
+ __block bool success = false;
+
+ secnoticeq("fresh", "EFP calling SOSCloudKeychainSynchronizeAndWait");
+
+ os_activity_initiate("CloudCircle EFRESH", OS_ACTIVITY_FLAG_DEFAULT, ^(void) {
+ SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) {
+ secnotice("fresh", "EFP returned, callback error: %@", sync_error);
+
+ success = (sync_error == NULL);
+ if (error) {
+ CFRetainAssign(*error, sync_error);
+ }
+
+ dispatch_semaphore_signal(wait_for);
+ });
+
+
+ dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
+ secnotice("fresh", "EFP complete: %s %@", success ? "success" : "failure", error ? *error : NULL);
+ });
+
+ return success;
+}
+
+static bool Flush(CFErrorRef *error) {
+ __block bool success = false;
+
+ dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
+ secnotice("flush", "Starting");
+
+ SOSCloudKeychainFlush(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
+ success = (sync_error == NULL);
+ if (error) {
+ CFRetainAssign(*error, sync_error);
+ }
+
+ dispatch_semaphore_signal(wait_for);
+ });
+
+ dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
+
+ secnotice("flush", "Returned %s", success? "Success": "Failure");
+
+ return success;
+}
+
+- (bool)syncWaitAndFlush:(CFErrorRef *)error
+{
+ secnotice("pairing", "sync and wait starting");
+
+ if (!SyncKVSAndWait(error)) {
+ secnotice("pairing", "failed sync and wait: %@", error ? *error : NULL);
+ return false;
+ }
+ if (!Flush(error)) {
+ secnotice("pairing", "failed flush: %@", error ? *error : NULL);
+ return false;
+ }
+ secnotice("pairing", "finished sync and wait");
+ return true;
+}
+
+- (void)validatedStashedAccountCredential:(void(^)(NSData *credential, NSError *error))complete
+{
+ CFErrorRef syncerror = NULL;
+
+ if (![self syncWaitAndFlush:&syncerror]) {
+ complete(NULL, (__bridge NSError *)syncerror);
+ CFReleaseNull(syncerror);
+ return;
+ }
+
+ dispatch_async(self.queue, ^{
+ CFErrorRef error = NULL;
+ SecKeyRef key = NULL;
+ key = SOSAccountCopyStashedUserPrivateKey(self, &error);
+ if (key == NULL) {
+ secnotice("pairing", "no stashed credential");
+ complete(NULL, (__bridge NSError *)error);
+ CFReleaseNull(error);
+ return;
+ }
+
+ SecKeyRef publicKey = SecKeyCopyPublicKey(key);
+ if (publicKey) {
+ secnotice("pairing", "returning stash credential: %@", publicKey);
+ CFReleaseNull(publicKey);
+ }
+
+ NSData *keydata = CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error));
+ CFReleaseNull(key);
+ complete(keydata, (__bridge NSError *)error);
+ CFReleaseNull(error);
+ });
+}
+
+- (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete
+{
+ CFErrorRef syncerror = NULL;
+
+ if (![self syncWaitAndFlush:&syncerror]) {
+ complete(NULL, (__bridge NSError *)syncerror);
+ CFReleaseNull(syncerror);
+ return;
+ }
+
+ sleep(1); // make up for keygen time in password based version - let syncdefaults catch up
+
+ [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
+ SecKeyRef accountPrivateKey = NULL;
+ CFErrorRef error = NULL;
+ NSDictionary *attributes = @{
+ (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
+ (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
+ };
+
+ accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error);
+ if (accountPrivateKey == NULL) {
+ complete(false, (__bridge NSError *)error);
+ secnotice("pairing", "SecKeyCreateWithData failed: %@", error);
+ CFReleaseNull(error);
+ return;
+ }
+
+ if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) {
+ CFReleaseNull(accountPrivateKey);
+ complete(false, (__bridge NSError *)error);
+ secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error);
+ CFReleaseNull(error);
+ return;
+ }
+
+ secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded");
+
+ CFReleaseNull(accountPrivateKey);
+ complete(true, NULL);
+ }];
+
+ // This makes getting the private key the same as Asserting the password - we read all the other things
+ // that we just expressed interest in.
+ CFErrorRef error = NULL;
+ if (!Flush(&error)) {
+ secnotice("pairing", "failed final flush: %@", error ? error : NULL);
+ return;
+ }
+ CFReleaseNull(error);
+}
+
+- (void)myPeerInfo:(void (^)(NSData *, NSError *))complete
+{
+ __block CFErrorRef localError = NULL;
+ __block NSData *applicationBlob = NULL;
+ [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
+ SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError);
+ if (application) {
+ applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError));
+ CFReleaseNull(application);
+ }
+ }];
+ complete(applicationBlob, (__bridge NSError *)localError);
+ CFReleaseNull(localError);
+}
+
+- (void)circleHash:(void (^)(NSString *, NSError *))complete
+{
+ __block CFErrorRef localError = NULL;
+ __block NSString *circleHash = NULL;
+ SecAKSDoWithUserBagLockAssertion(&localError, ^{
+ [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
+ circleHash = CFBridgingRelease(SOSCircleCopyHashString(txn.account.trust.trustedCircle));
+ }];
+ });
+ complete(circleHash, (__bridge NSError *)localError);
+ CFReleaseNull(localError);
+
+}
+
+
+#if TARGET_OS_OSX || TARGET_OS_IOS
+
+
++ (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
+ OTManager *otm = [OTManager manager];
+ SOSAccountGhostBustingOptions options = ([otm ghostbustByMidEnabled] == YES ? SOSGhostBustByMID: 0) |
+ ([otm ghostbustBySerialEnabled] == YES ? SOSGhostBustBySerialNumber : 0) |
+ ([otm ghostbustByAgeEnabled] == YES ? SOSGhostBustSerialByAge: 0);
+ return options;
+}
+
+
+#define GHOSTBUSTDATE @"ghostbustdate"
+
+- (NSDate *) ghostBustGetDate {
+ if(! self.settings) {
+ self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
+ }
+ return [self.settings valueForKey:GHOSTBUSTDATE];
+}
+
+- (bool) ghostBustCheckDate {
+ NSDate *ghostBustDate = [self ghostBustGetDate];
+ if(ghostBustDate && ([ghostBustDate timeIntervalSinceNow] <= 0)) return true;
+ return false;
+}
+
+- (void) ghostBustFollowup {
+ if(! self.settings) {
+ self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
+ }
+ NSTimeInterval earliestGB = 60*60*24*3; // wait at least 3 days
+ NSTimeInterval latestGB = 60*60*24*7; // wait at most 7 days
+ NSDate *ghostBustDate = SOSCreateRandomDateBetweenNowPlus(earliestGB, latestGB);
+ [self.settings setValue:ghostBustDate forKey:GHOSTBUSTDATE];
+}
+
+// GhostBusting initial scheduling
+- (void)ghostBustSchedule {
+ NSDate *ghostBustDate = [self ghostBustGetDate];
+ if(!ghostBustDate) {
+ [self ghostBustFollowup];
+ }
+}
+
+- (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
+ __block bool result = false;
+ __block CFErrorRef localError = NULL;
+
+ if([SOSAuthKitHelpers accountIsHSA2]) {
+ [SOSAuthKitHelpers activeMIDs:^(NSSet <SOSTrustedDeviceAttributes *> * _Nullable activeMIDs, NSError * _Nullable error) {
+ SOSAuthKitHelpers *akh = [[SOSAuthKitHelpers alloc] initWithActiveMIDS:activeMIDs];
+ if(akh) {
+ SecAKSDoWithUserBagLockAssertion(&localError, ^{
+ [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
+ result = SOSAccountGhostBustCircle(txn.account, akh, options, 1);
+ [self ghostBustFollowup];
+ }];
+ });
+ }
+ complete(result, NULL);
+ }];
+ } else {
+ complete(false, NULL);
+ }
+}
+
+- (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
+ NSDate *ghostBustDate = [self ghostBustGetDate];
+ if(([ghostBustDate timeIntervalSinceNow] <= 0)) {
+ if(options) {
+ [self ghostBust: options complete: complete];
+ } else {
+ complete(false, nil);
+ }
+ }
+}
+
+- (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
+ // if no particular options are presented use the ramp options.
+ // If TLKs haven't been set yet this will cause a deadlock. THis interface should only be used by the security tool for internal testing.
+ if(options == 0) {
+ options = [SOSAccount ghostBustGetRampSettings];
+ }
+ [self ghostBust: options complete: complete];
+}
+
+- (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
+ // If TLKs haven't been set yet this will cause a deadlock. THis interface should only be used by the security tool for internal testing.
+ NSMutableDictionary *gbInfoDictionary = [NSMutableDictionary new];
+ SOSAccountGhostBustingOptions options = [SOSAccount ghostBustGetRampSettings];
+ NSString *ghostBustDate = [[self ghostBustGetDate] description];
+
+ gbInfoDictionary[@"SOSGhostBustBySerialNumber"] = (SOSGhostBustBySerialNumber & options) ? @"ON": @"OFF";
+ gbInfoDictionary[@"SOSGhostBustByMID"] = (SOSGhostBustByMID & options) ? @"ON": @"OFF";
+ gbInfoDictionary[@"SOSGhostBustSerialByAge"] = (SOSGhostBustSerialByAge & options) ? @"ON": @"OFF";
+ gbInfoDictionary[@"SOSAccountGhostBustDate"] = ghostBustDate;
+
+ NSError *err = nil;
+ NSData *json = [NSJSONSerialization dataWithJSONObject:gbInfoDictionary
+ options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
+ error:&err];
+ if (!json) {
+ secnotice("ghostbust", "Error during ghostBustInfo JSONification: %@", err.localizedDescription);
+ }
+ complete(json, err);
+}
+
+#else
+
++ (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
+ return 0;
+}
+
+- (NSDate *) ghostBustGetDate {
+ return nil;
+}
+
+- (void) ghostBustFollowup {
+}
+
+- (void)ghostBustSchedule {
+}
+
+- (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
+ complete(false, NULL);
+}
+
+- (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
+ complete(false, NULL);
+}
+
+- (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
+ complete(false, nil);
+}
+
+- (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
+ complete(nil, nil);
+}
+
+- (bool) ghostBustCheckDate {
+ return false;
+}
+
+#endif
+
+- (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete
+{
+ __block CFErrorRef localError = NULL;
+ __block NSData *blob = NULL;
+ SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant);
+ if (peer == NULL) {
+ complete(NULL, (__bridge NSError *)localError);
+ CFReleaseNull(localError);
+ return;
+ }
+
+ SecAKSDoWithUserBagLockAssertionSoftly(^{
+ [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
+ blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError));
+ }];
+ });
+
+ CFReleaseNull(peer);
+
+ complete(blob, (__bridge NSError *)localError);
+ CFReleaseNull(localError);
+}
+
+- (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete
+{
+ __block CFErrorRef localError = NULL;
+ __block bool res = false;
+
+ [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
+ res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError);
+ }];
+
+ complete(res, (__bridge NSError *)localError);
+ CFReleaseNull(localError);
+}
+
+- (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
+{
+ CFErrorRef error = NULL;
+ uint32_t isflags = 0;
+
+ if (flags & SOSControlInitialSyncFlagTLK)
+ isflags |= SecServerInitialSyncCredentialFlagTLK;
+ if (flags & SOSControlInitialSyncFlagPCS)
+ isflags |= SecServerInitialSyncCredentialFlagPCS;
+ if (flags & SOSControlInitialSyncFlagPCSNonCurrent)
+ isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent;
+ if (flags & SOSControlInitialSyncFlagBluetoothMigration)
+ isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration;
+
+
+ NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error));
+ complete(array, (__bridge NSError *)error);
+ CFReleaseNull(error);
+}
+
+- (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete
+{
+ CFErrorRef error = NULL;
+ bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error);
+ complete(res, (__bridge NSError *)error);
+ CFReleaseNull(error);
+}
+
+- (void)triggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
+{
+ __block CFErrorRef localError = NULL;
+ __block bool res = false;
+
+ secnotice("sync", "trigger a forced sync for %@", peers);
+
+ SecAKSDoWithUserBagLockAssertion(&localError, ^{
+ [self performTransaction:^(SOSAccountTransaction *txn) {
+ if ([peers count]) {
+ NSSet *peersSet = [NSSet setWithArray:peers];
+ CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError);
+ if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) {
+ res = true;
+ }
+ CFReleaseNull(handledPeers);
+ } else {
+ res = SOSAccountRequestSyncWithAllPeers(txn, &localError);
+ }
+ }];
+ });
+ complete(res, (__bridge NSError *)localError);
+ CFReleaseNull(localError);
+}
+
+- (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete
+{
+ // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
+ Class watchdogClass = NSClassFromString(@"SecdWatchdog");
+ if (watchdogClass) {
+ NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters];
+ complete(parameters, nil);
+ }
+ else {
+ complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
+ }
+}
+
+- (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete
+{
+ // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
+ NSError* error = nil;
+ Class watchdogClass = NSClassFromString(@"SecdWatchdog");
+ if (watchdogClass) {
+ [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error];
+ complete(error);
+ }
+ else {
+ complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
+ }
+}
+
+//
+// MARK: Save Block
+//
+
+- (void) flattenToSaveBlock {
+ if (self.saveBlock) {
+ NSError* error = nil;
+ NSData* saveData = [self encodedData:&error];
+
+ (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error);
+ }
+}
+
+CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) {
+ return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt);
+}
+
+CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) {
+ CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL);
+ return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict);
+}
+
+static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){
+ SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
+ //send new DSID over account changed
+ [account.circle_transport kvsSendOfficialDSID:dsid err:NULL];
+ return true;
+}
+
+void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) {
+ CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
+ if(accountDSID == NULL) {
+ secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
+
+ SOSAccountUpdateDSID(account, dsid);
+ } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
+ secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
+
+ //DSID has changed, blast the account!
+ SOSAccountSetToNew(account);
+
+ //update DSID to the new DSID
+ SOSAccountUpdateDSID(account, dsid);
+ } else {
+ secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
+ }
+}
+
+
+void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews)
+{
+ [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews];
+ [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews];
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value"
+SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) {
+ SOSViewResultCode retval = kSOSCCGeneralViewError;
+ // The V0 view switches on and off all on it's own, we allow people the delusion
+ // of control and status if it's what we're stuck at., otherwise error.
+ if (SOSAccountSyncingV0(account)) {
+ require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
+ retval = kSOSCCViewMember;
+ } else {
+ require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
+ retval = kSOSCCViewNotMember;
+ }
+errOut:
+ return retval;
+}
+#pragma clang diagnostic pop
+
+SOSAccount* SOSAccountCreate(CFAllocatorRef allocator,
+ CFDictionaryRef gestalt,
+ SOSDataSourceFactoryRef factory) {
+
+ SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory];
+ [a ensureFactoryCircles];
+ SOSAccountEnsureUUID(a);
+ secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate");
+ a.key_interests_need_updating = true;
+
+ return a;
+}
+
+static OSStatus do_delete(CFDictionaryRef query) {
+ OSStatus result;
+
+ result = SecItemDelete(query);
+ if (result) {
+ secerror("SecItemDelete: %d", (int)result);
+ }
+ return result;
+}
+
+static int
+do_keychain_delete_aks_bags()
+{
+ OSStatus result;
+ CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+ kSecClass, kSecClassGenericPassword,
+ kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
+ kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
+ kSecAttrService, CFSTR("SecureBackupService"),
+ kSecAttrSynchronizable, kCFBooleanTrue,
+ kSecUseTombstones, kCFBooleanFalse,
+ NULL);
+
+ result = do_delete(item);
+ CFReleaseSafe(item);
+
+ return result;
+}
+
+static int
+do_keychain_delete_identities()
+{
+ OSStatus result;
+ CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+ kSecClass, kSecClassKey,
+ kSecAttrSynchronizable, kCFBooleanTrue,
+ kSecUseTombstones, kCFBooleanFalse,
+ kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
+ NULL);
+
+ result = do_delete(item);
+ CFReleaseSafe(item);
+
+ return result;
+}
+
+static int
+do_keychain_delete_lakitu()
+{
+ OSStatus result;
+ CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+ kSecClass, kSecClassGenericPassword,
+ kSecAttrSynchronizable, kCFBooleanTrue,
+ kSecUseTombstones, kCFBooleanFalse,
+ kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
+ kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
+ kSecAttrService, CFSTR("EscrowService"),
+ NULL);
+
+ result = do_delete(item);
+ CFReleaseSafe(item);
+
+ return result;
+}
+
+static int
+do_keychain_delete_sbd()
+{
+ OSStatus result;
+ CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
+ kSecClass, kSecClassGenericPassword,
+ kSecAttrSynchronizable, kCFBooleanTrue,
+ kSecUseTombstones, kCFBooleanFalse,
+ kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
+ NULL);
+
+ result = do_delete(item);
+ CFReleaseSafe(item);
+
+ return result;
+}
+
+void SOSAccountSetToNew(SOSAccount* a)
+{
+ secnotice("accountChange", "Setting Account to New");
+ int result = 0;
+
+ /* remove all syncable items */
+ result = do_keychain_delete_aks_bags(); (void) result;
+ secdebug("set to new", "result for deleting aks bags: %d", result);
+
+ result = do_keychain_delete_identities(); (void) result;
+ secdebug("set to new", "result for deleting identities: %d", result);
+
+ result = do_keychain_delete_lakitu(); (void) result;
+ secdebug("set to new", "result for deleting lakitu: %d", result);
+
+ result = do_keychain_delete_sbd(); (void) result;
+ secdebug("set to new", "result for deleting sbd: %d", result);
+
+ a.accountKeyIsTrusted = false;
+
+ if (a.user_private_timer) {
+ dispatch_source_cancel(a.user_private_timer);
+ a.user_private_timer = NULL;
+ xpc_transaction_end();
+
+ }
+ if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) {
+ notify_cancel(a.lock_notification_token);
+ a.lock_notification_token = NOTIFY_TOKEN_INVALID;
+ }
+
+ // keeping gestalt;
+ // keeping factory;
+ // Live Notification
+ // change_blocks;
+ // update_interest_block;
+ // update_block;
+ SOSUnregisterTransportKeyParameter(a.key_transport);
+ SOSUnregisterTransportMessage(a.kvs_message_transport);
+ SOSUnregisterTransportCircle(a.circle_transport);
+
+ a.circle_transport = NULL;
+ a.kvs_message_transport = nil;
+
+ a.trust = nil;
+ a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
+
+ [a ensureFactoryCircles]; // Does rings too
+
+ // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
+ SOSAccountEnsureUUID(a);
+ secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew");
+ a.key_interests_need_updating = true;
+}
+
+bool SOSAccountIsNew(SOSAccount* account, CFErrorRef *error){
+ bool result = false;
+ SOSAccountTrustClassic* trust = account.trust;
+ if(account.accountKeyIsTrusted != false || trust.departureCode != kSOSNeverAppliedToCircle ||
+ CFSetGetCount((__bridge CFSetRef)trust.retirees) != 0)
+ return result;
+
+ if(trust.retirees != nil)
+ return result;
+ if(trust.expansion != nil)
+ return result;
+
+ if(account.user_private_timer != NULL || account.lock_notification_token != NOTIFY_TOKEN_INVALID)
+ return result;
+
+ result = true;
+
+ return result;
+}
+
+dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) {
+ return account.queue;
+}
+
+void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){
+ account.accountKeyIsTrusted = true;
+}
+
+-(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
+{
+ SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error];
+ if (!SOSAccountHasPublicKey(self, error)) {
+ if(circleStatus == kSOSCCInCircle) {
+ if(error) {
+ CFReleaseNull(*error);
+ SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key isn't available, this peer is in the circle, but invalid. The iCloud Password must be provided to keychain syncing subsystem to repair this."), NULL, error);
+ }
+ }
+ circleStatus = kSOSCCError;
+ }
+ return circleStatus;
+}
+
+-(bool) isInCircle:(CFErrorRef *)error
+{
+ SOSCCStatus result = [self getCircleStatus:error];
+ if (result != kSOSCCInCircle) {
+ SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
+ return false;
+ }
+ return true;
+}
+
+
+bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) {
+
+ SOSAccountTrustClassic *trust = account.trust;
+ NSMutableSet* retirees = trust.retirees;
+ SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
+ CFSetSetValue((__bridge CFMutableSetRef) retirees, peer);
+ CFErrorRef cleanupError = NULL;
+ if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) {
+ secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
+ }
+ CFReleaseSafe(cleanupError);
+ });
+ return true;
+}
+
+SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) {
+ SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
+ SOSPeerInfoRef me = account.peerInfo;
+ bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
+
+ SOSAccountTrustClassic *trust = account.trust;
+ NSMutableSet* retirees = trust.retirees;
+
+ if(!new_circle) return NULL;
+ __block bool workDone = false;
+ if (retirees) {
+ CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) {
+ SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
+ if (isSOSPeerInfo(pi)) {
+ SOSCircleUpdatePeerInfo(new_circle, pi);
+ workDone = true;
+ }
+ });
+ }
+
+ if(workDone && SOSCircleCountPeers(new_circle) == 0) {
+ SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
+
+ if(iAmApplicant) {
+ if(userPrivKey) {
+ secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
+ if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) ||
+ ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){
+ CFReleaseNull(new_circle);
+ return NULL;
+ }
+ account.notifyBackupOnExit = true;
+ } else {
+ // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
+ // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
+ // we have a chance to set things right with a SetCreds/Join sequence. This will cause
+ // handleUpdateCircle to return false.
+ CFReleaseNull(new_circle);
+ return NULL;
+ }
+ } else {
+ // This case is when we aren't an applicant and the circle is retirement-empty.
+ secnotice("circleOps", "Reset to empty with last retirement");
+ SOSCircleResetToEmpty(new_circle, NULL);
+ }
+ }
+
+ return new_circle;
+}
+
+//
+// MARK: Circle Membership change notificaion
+//
+
+void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
+ SOSAccountCircleMembershipChangeBlock copy = changeBlock;
+ [a.change_blocks addObject:copy];
+}
+
+void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
+ [a.change_blocks removeObject:changeBlock];
+}
+
+void SOSAccountPurgeIdentity(SOSAccount* account) {
+ SOSAccountTrustClassic *trust = account.trust;
+ SOSFullPeerInfoRef identity = trust.fullPeerInfo;
+
+ if (identity) {
+ // Purge private key but don't return error if we can't.
+ CFErrorRef purgeError = NULL;
+ if (!SOSFullPeerInfoPurgePersistentKey(identity, &purgeError)) {
+ secwarning("Couldn't purge persistent key for %@ [%@]", identity, purgeError);
+ }
+ CFReleaseNull(purgeError);
+
+ trust.fullPeerInfo = nil;
+ }
+}
+
+bool sosAccountLeaveCircleWithAnalytics(SOSAccount* account, SOSCircleRef circle, NSData* parentData, CFErrorRef* error) {
+ SOSAccountTrustClassic *trust = account.trust;
+ SOSFullPeerInfoRef identity = trust.fullPeerInfo;
+ NSMutableSet* retirees = trust.retirees;
+
+ NSError* localError = nil;
+ SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentData error:&localError];
+
+ SOSFullPeerInfoRef fpi = identity;
+ if(!fpi) return false;
+
+ CFErrorRef retiredError = NULL;
+
+ bool retval = false;
+
+ SFSignInAnalytics *promoteToRetiredEvent = [parent newSubTaskForEvent:@"promoteToRetiredEvent"];
+ SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
+ if(retiredError){
+ [promoteToRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
+ secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
+ if(error){
+ *error = retiredError;
+ }else{
+ CFReleaseNull(retiredError);
+ }
+ }
+ [promoteToRetiredEvent stopWithAttributes:nil];
+
+ if (!retire_peer) {
+ secerror("Create ticket failed for peer %@: %@", fpi, localError);
+ } else {
+ // See if we need to repost the circle we could either be an applicant or a peer already in the circle
+ if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
+ // Remove our application if we have one.
+ SOSCircleWithdrawRequest(circle, retire_peer, NULL);
+ } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
+ if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
+ CFErrorRef cleanupError = NULL;
+ SFSignInAnalytics *cleanupEvent = [parent newSubTaskForEvent:@"cleanupAfterPeerEvent"];
+ if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
+ secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
+ }
+ [cleanupEvent stopWithAttributes:nil];
+ CFReleaseSafe(cleanupError);
+ }
+ }
+
+ // Store the retirement record locally.
+ CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
+
+ trust.retirees = retirees;
+
+ // Write retirement to Transport
+ CFErrorRef postError = NULL;
+ SFSignInAnalytics *postRestirementEvent = [parent newSubTaskForEvent:@"postRestirementEvent"];
+ if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
+ [postRestirementEvent logRecoverableError:(__bridge NSError*)postError];
+ secwarning("Couldn't post retirement (%@)", postError);
+ }
+ [postRestirementEvent stopWithAttributes:nil];
+
+ SFSignInAnalytics *flushChangesEvent = [parent newSubTaskForEvent:@"flushChangesEvent"];
+
+ if(![account.circle_transport flushChanges:&postError]){
+ [flushChangesEvent logRecoverableError:(__bridge NSError*)postError];
+ secwarning("Couldn't flush retirement data (%@)", postError);
+ }
+ [flushChangesEvent stopWithAttributes:nil];
+ CFReleaseNull(postError);
+ }
+ SFSignInAnalytics *purgeIdentityEvent = [parent newSubTaskForEvent:@"purgeIdentityEvent"];
+ SOSAccountPurgeIdentity(account);
+ [purgeIdentityEvent stopWithAttributes:nil];
+ retval = true;
+
+ CFReleaseNull(retire_peer);
+ return retval;
+}
+
+bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) {
+ SOSAccountTrustClassic *trust = account.trust;
+ SOSFullPeerInfoRef identity = trust.fullPeerInfo;
+ NSMutableSet* retirees = trust.retirees;
+
+ SOSFullPeerInfoRef fpi = identity;
+ if(!fpi) return false;
+
+ CFErrorRef localError = NULL;
+
+ bool retval = false;
+
+ SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
+ if (!retire_peer) {
+ secerror("Create ticket failed for peer %@: %@", fpi, localError);
+ } else {
+ // See if we need to repost the circle we could either be an applicant or a peer already in the circle
+ if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
+ // Remove our application if we have one.
+ SOSCircleWithdrawRequest(circle, retire_peer, NULL);
+ } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
+ if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
+ CFErrorRef cleanupError = NULL;
+ if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
+ secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
+ }
+ CFReleaseSafe(cleanupError);
+ }
+ }
+
+ // Store the retirement record locally.
+ CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
+
+ trust.retirees = retirees;
+
+ // Write retirement to Transport
+ CFErrorRef postError = NULL;
+ if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
+ secwarning("Couldn't post retirement (%@)", postError);
+ }
+ if(![account.circle_transport flushChanges:&postError]){
+ secwarning("Couldn't flush retirement data (%@)", postError);
+ }
+ CFReleaseNull(postError);
+ }
+
+ SOSAccountPurgeIdentity(account);
+
+ retval = true;
+
+ CFReleaseNull(localError);
+ CFReleaseNull(retire_peer);
+ return retval;
+}
+
+bool sosAccountLeaveRing(SOSAccount* account, SOSRingRef ring, CFErrorRef* error) {
+ SOSAccountTrustClassic *trust = account.trust;
+ SOSFullPeerInfoRef identity = trust.fullPeerInfo;
+
+ SOSFullPeerInfoRef fpi = identity;
+ if(!fpi) return false;
+ SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
+ CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
+
+ CFErrorRef localError = NULL;
+
+ bool retval = false;
+ bool writeRing = false;
+ bool writePeerInfo = false;
+
+ if(SOSRingHasPeerID(ring, peerID)) {
+ writePeerInfo = true;
+ }
+
+ if(writePeerInfo || writeRing) {
+ SOSRingWithdraw(ring, NULL, fpi, error);
+ }
+
+ if (writeRing) {
+ CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
+
+ if (ring_data) {
+ [account.circle_transport kvsRingPostRing:SOSRingGetName(ring) ring:ring_data err:NULL];
+ }
+ CFReleaseNull(ring_data);
+ }
+ retval = true;
+ CFReleaseNull(localError);
+ return retval;
+}
+
+bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
+ bool result = false;
+ if (account.circle_transport) {
+ result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
+ }
+ return result;
+}
+
+/*
+ NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
+ local value that has been overwritten by a distant value. If there is no
+ conflict between the local and the distant values when doing the initial
+ sync (e.g. if the cloud has no data stored or the client has not stored
+ any data yet), you'll never see that notification.
+
+ NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
+ with server but initial round trip with server does not imply
+ NSUbiquitousKeyValueStoreInitialSyncChange.
+ */
+
+
+//
+// MARK: Status summary
+//
+
+
+CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
+ switch(status) {
+ case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
+ case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
+ case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
+ case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
+ case kSOSCCError: return CFSTR("kSOSCCError");
+ }
+ return CFSTR("kSOSCCError");
+}
+SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
+ if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
+ return kSOSCCInCircle;
+ } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
+ return kSOSCCInCircle;
+ } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
+ return kSOSCCNotInCircle;
+ } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
+ return kSOSCCRequestPending;
+ } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
+ return kSOSCCCircleAbsent;
+ } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
+ return kSOSCCError;
+ }
+ return kSOSCCError;
+}
+
+//
+// MARK: Account Reset Circles
+//
+
+// This needs to be called within a [trust modifyCircle()] block
+
+
+bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
+ bool retval = false;
+
+ SOSAccountTrustClassic *trust = account.trust;
+ SOSFullPeerInfoRef identity = trust.fullPeerInfo;
+
+ CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
+ if(SOSPeerInfoIsCloudIdentity(peer)) {
+ SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
+ if(!icfpi) {
+ CFSetAddValue(iCloud2Remove, peer);
+ }
+ CFReleaseNull(icfpi);
+ }
+ });
+
+ if(CFSetGetCount(iCloud2Remove) > 0) {
+ retval = true;
+ SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
+ }
+ CFReleaseNull(iCloud2Remove);
+ return retval;
+}
+
+//
+// MARK: start backups
+//
+
+
+bool SOSAccountEnsureInBackupRings(SOSAccount* account) {
+ __block bool result = false;
+ __block CFErrorRef error = NULL;
+ secnotice("backup", "Ensuring in rings");
+
+ if(!account.backup_key){
+ return true;
+ }
+
+ if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){
+ secnotice("backupkey", "account backup key isn't valid: %@", error);
+ account.backup_key = nil;
+ CFReleaseNull(error);
+ return false;
+ }
+
+ NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(account.peerInfo);
+ if(![peerBackupKey isEqual:account.backup_key]) {
+ result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
+ return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error);
+ });
+ if (!result) {
+ secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error);
+ CFReleaseNull(error);
+ return result;
+ }
+ }
+
+ // It's a good key, we're going with it. Stop backing up the old way.
+ CFErrorRef localError = NULL;
+ if (!SOSDeleteV0Keybag(&localError)) {
+ secerror("Failed to delete v0 keybag: %@", localError);
+ }
+ CFReleaseNull(localError);
+
+ // Setup backups the new way.
+ SOSAccountForEachBackupView(account, ^(const void *value) {
+ CFStringRef viewName = asString(value, NULL);
+ bool resetRing = SOSAccountValidateBackupRingForView(account, viewName, NULL);
+ if(resetRing) {
+ SOSAccountUpdateBackupRing(account, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
+ SOSRingRef newRing = SOSAccountCreateBackupRingForView(account, viewName, error);
+ return newRing;
+ });
+ }
+ });
+
+ if (!result) {
+ secnotice("backupkey", "Failed to setup backup public key: %@", error);
+ }
+ CFReleaseNull(error);
+ return result;
+}
+
+//
+// MARK: Recovery Public Key Functions
+//
+
+bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
+ bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
+ if(retval) secnotice("recovery", "successfully registered recovery public key");
+ else secnotice("recovery", "could not register recovery public key: %@", *error);
+ SOSClearErrorIfTrue(retval, error);
+ return retval;
+}
+
+bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
+ bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
+ if(retval) secnotice("recovery", "RK Cleared");
+ else secnotice("recovery", "Couldn't clear RK(%@)", *error);
+ SOSClearErrorIfTrue(retval, error);
+ return retval;
+}
+
+CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
+ CFDataRef result = NULL;
+ result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
+ if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
+
+ if (!isData(result)) {
+ CFReleaseNull(result);
+ }
+ SOSClearErrorIfTrue(result != NULL, error);
+
+ return result;
+}
+
+//
+// MARK: Joining
+//
+
+static bool SOSAccountJoinCircleWithAnalytics(SOSAccountTransaction* aTxn, SecKeyRef user_key,
+ bool use_cloud_peer, NSData* parentEvent, CFErrorRef* error) {
+ SOSAccount* account = aTxn.account;
+ SOSAccountTrustClassic *trust = account.trust;
+ __block bool result = false;
+ __block SOSFullPeerInfoRef cloud_full_peer = NULL;
+ SFSignInAnalytics *ensureFullPeerAvailableEvent = nil;
+ NSError* localError = nil;
+ SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
+
+ require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
+ ensureFullPeerAvailableEvent = [parent newSubTaskForEvent:@"ensureFullPeerAvailableEvent"];
+ require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
+ [ensureFullPeerAvailableEvent stopWithAttributes:nil];
+
+ SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
+ if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
+ secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
+ // this also clears initial sync data
+ result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
+ } else {
+ SOSAccountInitializeInitialSync(account);
+ if (use_cloud_peer) {
+ cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
+ }
+ SFSignInAnalytics *acceptApplicantEvent = [parent newSubTaskForEvent:@"acceptApplicantEvent"];
+ [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
+ result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
+ result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
+ trust.departureCode = kSOSNeverLeftCircle;
+ if(result && cloud_full_peer) {
+ CFErrorRef localError = NULL;
+ CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
+ require_quiet(cloudid, finish);
+ require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
+ require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
+ finish:
+ if (localError){
+ [acceptApplicantEvent logRecoverableError:(__bridge NSError *)(localError)];
+ secerror("Failed to join with cloud identity: %@", localError);
+ CFReleaseNull(localError);
+ }
+ }
+ return result;
+ }];
+ [acceptApplicantEvent stopWithAttributes:nil];
+ if (use_cloud_peer) {
+ SFSignInAnalytics *updateOutOfDateSyncViewsEvent = [acceptApplicantEvent newSubTaskForEvent:@"updateOutOfDateSyncViewsEvent"];
+ SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
+ [updateOutOfDateSyncViewsEvent stopWithAttributes:nil];
+ }
+ }
+fail:
+ CFReleaseNull(cloud_full_peer);
+ return result;
+}
+
+static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key,
+ bool use_cloud_peer, CFErrorRef* error) {
+ SOSAccount* account = aTxn.account;
+ SOSAccountTrustClassic *trust = account.trust;
+ __block bool result = false;
+ __block SOSFullPeerInfoRef cloud_full_peer = NULL;
+ require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
+ require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
+ SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
+ if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
+ secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
+ // this also clears initial sync data
+ result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
+ } else {
+ SOSAccountInitializeInitialSync(account);
+ if (use_cloud_peer) {
+ cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
+ }
+ [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
+ result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
+ result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
+ trust.departureCode = kSOSNeverLeftCircle;
+ if(result && cloud_full_peer) {
+ CFErrorRef localError = NULL;
+ CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
+ require_quiet(cloudid, finish);
+ require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
+ require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
+ finish:
+ if (localError){
+ secerror("Failed to join with cloud identity: %@", localError);
+ CFReleaseNull(localError);
+ }
+ }
+ return result;
+ }];
+ if (use_cloud_peer) {
+ SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
+ }
+ }
+fail:
+ CFReleaseNull(cloud_full_peer);
+ return result;
+}
+
+static bool SOSAccountJoinCirclesWithAnalytics_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, NSData* parentEvent, CFErrorRef* error) {
+ SOSAccount* account = aTxn.account;
+ SOSAccountTrustClassic *trust = account.trust;
+ bool success = false;
+
+ SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+ require_quiet(user_key, done); // Fail if we don't get one.
+
+ require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
+
+ if (trust.fullPeerInfo != NULL) {
+ SOSPeerInfoRef myPeer = trust.peerInfo;
+ success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
+ require_quiet(!success, done);
+
+ SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
+
+ if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
+ secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
+
+ trust.fullPeerInfo = NULL;
+ }
+ }
+
+ success = SOSAccountJoinCircleWithAnalytics(aTxn, user_key, use_cloud_identity, parentEvent, error);
+
+ require_quiet(success, done);
+
+ trust.departureCode = kSOSNeverLeftCircle;
+
+done:
+ return success;
+}
+
+static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) {
+ SOSAccount* account = aTxn.account;
+ SOSAccountTrustClassic *trust = account.trust;
+ bool success = false;
+
+ SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+ require_quiet(user_key, done); // Fail if we don't get one.
+
+ require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
+
+ if (trust.fullPeerInfo != NULL) {
+ SOSPeerInfoRef myPeer = trust.peerInfo;
+ success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
+ require_quiet(!success, done);
+
+ SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
+
+ if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
+ secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
+
+ trust.fullPeerInfo = NULL;
+ }
+ }
+
+ success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
+
+ require_quiet(success, done);
+
+ trust.departureCode = kSOSNeverLeftCircle;
+
+done:
+ return success;
+}
+
+bool SOSAccountJoinCirclesWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
+ secnotice("circleOps", "Normal path circle join (SOSAccountJoinCirclesWithAnalytics)");
+ return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, false, parentEvent, error);
+}
+
+bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) {
+ secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
+ return SOSAccountJoinCircles_internal(aTxn, false, error);
+}
+
+bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) {
+ secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
+ return SOSAccountJoinCircles_internal(aTxn, true, error);
+}
+
+bool SOSAccountJoinCirclesAfterRestoreWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
+ secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
+ return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, true, parentEvent, error);
+}
+
+bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error)
+{
+ bool result = false;
+ CFMutableSetRef peersToRemove = NULL;
+ SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+ if(!user_key){
+ secnotice("circleOps", "Can't remove without userKey");
+ return result;
+ }
+ SOSFullPeerInfoRef me_full = account.fullPeerInfo;
+ SOSPeerInfoRef me = account.peerInfo;
+ if(!(me_full && me))
+ {
+ secnotice("circleOps", "Can't remove without being active peer");
+ SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
+ return result;
+ }
+
+ result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
+
+ peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
+ if(!peersToRemove)
+ {
+ CFReleaseNull(peersToRemove);
+ secnotice("circleOps", "No peerSet to remove");
+ return result;
+ }
+
+ // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
+ bool leaveCircle = CFSetContainsValue(peersToRemove, me);
+ CFSetRemoveValue(peersToRemove, me);
+
+ result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
+ bool success = false;
+
+ if(CFSetGetCount(peersToRemove) != 0) {
+ require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
+ success = SOSAccountGenerationSignatureUpdate(account, error);
+ } else success = true;
+
+ if (success && leaveCircle) {
+ secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
+ success = sosAccountLeaveCircle(account, circle, error);
+ }
+
+ done:
+ return success;
+
+ }];
+
+ if(result) {
+ CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
+ secnotice("circleOps", "Removed Peers from circle %@", description);
+ });
+ }
+
+ CFReleaseNull(peersToRemove);
+ return result;
+}
+
+
+bool SOSAccountRemovePeersFromCircleWithAnalytics(SOSAccount* account, CFArrayRef peers, NSData* parentEvent, CFErrorRef* error)
+{
+
+ NSError* localError = nil;
+ SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
+
+ bool result = false;
+ CFMutableSetRef peersToRemove = NULL;
+ SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+ if(!user_key){
+ secnotice("circleOps", "Can't remove without userKey");
+ return result;
+ }
+ SOSFullPeerInfoRef me_full = account.fullPeerInfo;
+ SOSPeerInfoRef me = account.peerInfo;
+ if(!(me_full && me))
+ {
+ secnotice("circleOps", "Can't remove without being active peer");
+ SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
+ return result;
+ }
+
+ result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
+
+ peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
+ if(!peersToRemove)
+ {
+ CFReleaseNull(peersToRemove);
+ secnotice("circleOps", "No peerSet to remove");
+ return result;
+ }
+
+ // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
+ bool leaveCircle = CFSetContainsValue(peersToRemove, me);
+ CFSetRemoveValue(peersToRemove, me);
+
+ result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
+ bool success = false;
+
+ if(CFSetGetCount(peersToRemove) != 0) {
+ require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
+ SFSignInAnalytics *generationSignatureUpdateEvent = [parent newSubTaskForEvent:@"generationSignatureUpdateEvent"];
+ success = SOSAccountGenerationSignatureUpdate(account, error);
+ if(error && *error){
+ NSError* signatureUpdateError = (__bridge NSError*)*error;
+ [generationSignatureUpdateEvent logUnrecoverableError:signatureUpdateError];
+ }
+ [generationSignatureUpdateEvent stopWithAttributes:nil];
+ } else success = true;
+
+ if (success && leaveCircle) {
+ secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
+ success = sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
+ }
+
+ done:
+ return success;
+
+ }];
+
+ if(result) {
+ CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
+ secnotice("circleOps", "Removed Peers from circle %@", description);
+ });
+ }
+
+ CFReleaseNull(peersToRemove);
+ return result;
+}
+
+bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
+ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_group_t group = dispatch_group_create();
+ SOSAccountTrustClassic *trust = account.trust;
+ __block bool result = false;
+ secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
+ // Add a task to the group
+ dispatch_group_async(group, queue, ^{
+ [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
+ secnotice("circleOps", "Leaving circle by client request (Bail)");
+ return sosAccountLeaveCircle(account, circle, error);
+ }];
+ });
+ dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
+ dispatch_group_wait(group, milestone);
+
+ trust.departureCode = kSOSWithdrewMembership;
+
+ return result;
+}
+
+
+//
+// MARK: Application
+//
+
+static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
+ bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
+ SOSAccountTrustClassic *trust = account.trust;
+
+ SOSPeerInfoRef me = trust.peerInfo;
+ CFErrorRef peer_error = NULL;
+ if (trust.trustedCircle && me &&
+ SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
+ [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
+ __block bool modified = false;
+ CFArrayForEach(peer_infos, ^(const void *value) {
+ SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
+ if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
+ if (action(circle, trust.fullPeerInfo, peer)) {
+ modified = true;
+ }
+ }
+ });
+ return modified;
+ }];
+ }
+ if (peer_error)
+ secerror("Got error in SOSCircleHasPeer: %@", peer_error);
+ CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
+}
+
+bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
+ SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+ if (!user_key)
+ return false;
+
+ __block int64_t acceptedPeers = 0;
+
+ for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
+ bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
+ if (accepted)
+ acceptedPeers++;
+ return accepted;
+ });
+
+ if (acceptedPeers == CFArrayGetCount(applicants))
+ return true;
+ return false;
+}
+
+bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
+ __block bool success = true;
+ __block int64_t num_peers = 0;
+
+ for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
+ bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
+ if (!rejected)
+ success = false;
+ else
+ num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
+ return rejected;
+ });
+
+ return success;
+}
+
+
+CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccount* account, CFErrorRef* error) {
+ return CFSTR("We're compatible, go away");
+}
+
+enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
+ SOSAccountTrustClassic *trust = account.trust;
+ return trust.departureCode;
+}
+
+void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
+ SOSAccountTrustClassic *trust = account.trust;
+ trust.departureCode = reason;
+}
+
+
+CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
+ CFArrayRef result = NULL;
+ CFNumberRef generation = NULL;
+ SOSAccountTrustClassic *trust = account.trust;
+
+ require_quiet(SOSAccountHasPublicKey(account, error), fail);
+ require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
+
+ generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
+ result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
+
+fail:
+ return result;
+}
+
+bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
+ if (!SOSAccountHasPublicKey(account, error))
+ return NULL;
+
+ return account.accountKeyIsTrusted;
+}
+
+bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
+ // TODO: this result is never set or used
+ bool result = true;
+ SOSAccountTrustClassic *trust = account.trust;
+
+ secnotice("updates", "Ensuring peer registration.");
+
+ if(!trust) {
+ secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
+ return result;
+ }
+
+ if([account getCircleStatus: NULL] != kSOSCCInCircle) {
+ return result;
+ }
+
+ // If we are not in the circle, there is no point in setting up peers
+ if(!SOSAccountIsMyPeerActive(account, NULL)) {
+ return result;
+ }
+
+ // This code only uses the SOSFullPeerInfoRef for two things:
+ // - Finding out if this device is in the trusted circle
+ // - Using the peerID for this device to see if the current peer is "me"
+ // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
+
+ CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
+
+ SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
+ if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
+ CFErrorRef localError = NULL;
+
+ SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
+ if (localError)
+ secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
+ CFReleaseSafe(localError);
+ }
+ });
+
+ return result;
+}
+
+//
+// Value manipulation
+//
+
+CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
+ SOSAccountTrustClassic *trust = account.trust;
+ if (!trust.expansion) {
+ return NULL;
+ }
+ return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
+}
+
+bool SOSAccountAddEscrowRecords(SOSAccount* account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error){
+ CFMutableDictionaryRef escrowRecords = (CFMutableDictionaryRef)SOSAccountGetValue(account, kSOSEscrowRecord, error);
+ CFMutableDictionaryRef escrowCopied = NULL;
+ bool success = false;
+
+ if(isDictionary(escrowRecords) && escrowRecords != NULL)
+ escrowCopied = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(escrowRecords), escrowRecords);
+ else
+ escrowCopied = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ CFDictionaryAddValue(escrowCopied, dsid, record);
+ SOSAccountSetValue(account, kSOSEscrowRecord, escrowCopied, error);
+
+ if(*error == NULL)
+ success = true;
+
+ CFReleaseNull(escrowCopied);
+
+ return success;
+
+}
+
+bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
+ bool success = false;
+
+ CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
+ success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
+
+ return success;
+}
+
+void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) {
+ if (![account isInCircle:NULL]) {
+ return;
+ }
+ __block bool updateRings = false;
+ SOSAccountTrustClassic *trust = account.trust;
+ [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
+ __block bool updated = false;
+ CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
+ SOSPeerInfoRef retiree = asSOSPeerInfo(element);
+
+ if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
+ updated = true;
+ secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
+ CFErrorRef cleanupError = NULL;
+ if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
+ secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
+ CFReleaseSafe(cleanupError);
+ updateRings = true;
+ }
+ });
+ return updated;
+ }];
+ if(updateRings) {
+ SOSAccountProcessBackupRings(account, NULL);
+ }
+}
+
+static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
+
+static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
+{
+ __block CFTypeRef object = NULL;
+
+ dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
+ dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
+
+ CloudKeychainReplyBlock replyBlock =
+ ^ (CFDictionaryRef returnedValues, CFErrorRef error)
+ {
+ object = returnedValues;
+ if (object)
+ CFRetain(object);
+ if (error)
+ {
+ secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
+ }
+ dispatch_semaphore_signal(waitSemaphore);
+ };
+
+ SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
+
+ dispatch_semaphore_wait(waitSemaphore, finishTime);
+ if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
+ {
+ CFRelease(object);
+ object = NULL;
+ }
+ return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
+}
+
+
+static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
+{
+ CFStringRef uuid = SOSAccountCopyUUID(account);
+ dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
+ dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
+
+ CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
+ if (error){
+ secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
+ }
+ dispatch_semaphore_signal(waitSemaphore);
+ };
+
+ SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
+ dispatch_semaphore_wait(waitSemaphore, finishTime);
+ CFReleaseNull(uuid);
+}
+
+static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
+{
+ NSDate *now = [NSDate date];
+ [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
+
+ NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
+
+ CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
+
+ CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
+
+ withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
+ CFStringAppend(timeDescription, decription);
+ });
+ CFStringAppend(timeDescription, CFSTR("]"));
+
+ [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
+ CFReleaseNull(timeDescription);
+
+ dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
+ dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
+ dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+
+ CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
+ if (error){
+ secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
+ }
+ dispatch_semaphore_signal(waitSemaphore);
+ };
+
+ SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
+ dispatch_semaphore_wait(waitSemaphore, finishTime);
+}
+
+// set the cleanup frequency to 3 days.
+#define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
+
+//Get all the key/values in KVS and remove old entries
+bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
+{
+ // This should only happen on some number of days
+ NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
+ NSDate *now = [NSDate date];
+ NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
+
+ if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
+ return true;
+ }
+
+ dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+
+ NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
+ NSMutableArray *peerIDs = [NSMutableArray array];
+ NSMutableArray *keysToRemove = [NSMutableArray array];
+
+ CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
+ CFArrayForEach(peers, ^(const void *value) {
+ SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
+ NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
+
+ //any peerid that is not ours gets added
+ if(![[account.trust peerID] isEqualToString:peerID])
+ [peerIDs addObject:peerID];
+ });
+ CFReleaseNull(peers);
+
+ [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
+ __block bool keyMatchesPeerID = false;
+
+ //checks for full peer ids
+ [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
+ //if key contains peerid of one active peer
+ if([KVSKey containsString:PeerID]){
+ secnotice("key-cleanup","key: %@", KVSKey);
+ keyMatchesPeerID = true;
+ }
+ }];
+ if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
+ || [KVSKey hasPrefix:@"po"])
+ [keysToRemove addObject:KVSKey];
+ }];
+
+ secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
+ secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
+
+ SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
+
+ //add last cleanup timestamp
+ SOSAccountWriteLastCleanupTimestampToKVS(account);
+ return true;
+
+}
+
+bool SOSAccountPopulateKVSWithBadKeys(SOSAccount* account, CFErrorRef* error) {
+
+ NSMutableDictionary *testKeysAndValues = [NSMutableDictionary dictionary];
+ [testKeysAndValues setObject:@"deadbeef" forKey:@"-ak|asdfjkl;asdfjk;"];
+ [testKeysAndValues setObject:@"foobar" forKey:@"ak|asdfasdfasdf:qwerqwerqwer"];
+ [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"poak|asdfasdfasdfasdf"];
+ [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"po|asdfasdfasdfasdfasdfasdf"];
+ [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"k>KeyParm"];
+
+ dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
+ dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
+ dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+
+ CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
+ if (error){
+ secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
+ }
+ dispatch_semaphore_signal(waitSemaphore);
+ };
+
+ SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(testKeysAndValues), processQueue, replyBlock);
+ dispatch_semaphore_wait(waitSemaphore, finishTime);
+
+ return true;
+}
+
+SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
+ SOSPeerInfoRef applicant = NULL;
+ SOSAccountTrustClassic *trust = account.trust;
+ SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
+ if(!userKey) return false;
+ if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
+ return applicant;
+ if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
+ return applicant;
+ applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
+
+ return applicant;
+}
+
+
+static void
+AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
+{
+ [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
+ NSString* parentUUID = keychainItem[(id)kSecAttrPath];
+ NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
+ NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
+
+ if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
+ return;
+
+ if([parentUUID isEqualToString:viewUUID] || authoriative){
+
+ /* check if we already have this entry */
+ if ([seenUUID containsObject:viewUUID])
+ return;
+
+ NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
+ NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
+
+ if (key == NULL)
+ return;
+
+ secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
+
+ NSMutableDictionary* strippedDown = [@{
+ (id)kSecValueData : key,
+ (id)kSecAttrServer : viewName,
+ (id)kSecAttrAccount : viewUUID
+ } mutableCopy];
+ if (authoriative)
+ strippedDown[@"auth"] = @YES;
+
+ [results addObject:strippedDown];
+ [seenUUID addObject:viewUUID];
+ }
+ }];
+}
+
+static void
+AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
+{
+#if OCTAGON
+ CKKSViewManager* manager = [CKKSViewManager manager];
+
+ NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
+
+ for (NSString *view in items) {
+ NSString *uuid = items[view];
+ NSDictionary *query = @{
+ (id)kSecClass : (id)kSecClassInternetPassword,
+ (id)kSecUseDataProtectionKeychain : @YES,
+ (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
+ (id)kSecAttrAccount : uuid,
+ (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+ (id)kSecMatchLimit : (id)kSecMatchLimitAll,
+ (id)kSecReturnAttributes: @YES,
+ (id)kSecReturnData: @YES,
+ };
+ CFTypeRef result = NULL;
+ if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
+ AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
+ }
+ CFReleaseNull(result);
+ }
+#endif
+}
+
+
+NSMutableArray*
+SOSAccountGetAllTLKs(void)
+{
+ CFTypeRef result = NULL;
+ NSMutableArray* results = [NSMutableArray array];
+ NSMutableSet *seenUUID = [NSMutableSet set];
+
+ // first use the TLK from the view manager
+ AddViewManagerResults(results, seenUUID);
+
+ //try to grab tlk-piggy items
+ NSDictionary* query = @{
+ (id)kSecClass : (id)kSecClassInternetPassword,
+ (id)kSecUseDataProtectionKeychain : @YES,
+ (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
+ (id)kSecAttrDescription: @"tlk",
+ (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+ (id)kSecMatchLimit : (id)kSecMatchLimitAll,
+ (id)kSecReturnAttributes: @YES,
+ (id)kSecReturnData: @YES,
+ };
+
+ if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
+ AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
+ }
+ CFReleaseNull(result);
+
+ //try to grab tlk-piggy items
+ query = @{
+ (id)kSecClass : (id)kSecClassInternetPassword,
+ (id)kSecUseDataProtectionKeychain : @YES,
+ (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
+ (id)kSecAttrDescription: @"tlk-piggy",
+ (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
+ (id)kSecMatchLimit : (id)kSecMatchLimitAll,
+ (id)kSecReturnAttributes: @YES,
+ (id)kSecReturnData: @YES,
+ };
+
+ if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
+ AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
+ }
+ CFReleaseNull(result);
+
+ secnotice("piggy", "Found %d TLKs", (int)[results count]);
+
+ return results;
+}
+
+static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
+ const uint8_t *der, uint8_t *der_end)
+{
+ if (type != kTLKUnknown) {
+ return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+ piggy_encode_data(keychainData, der,
+ piggy_encode_data(uuid, der,
+ ccder_encode_uint64((uint64_t)type, der, der_end))));
+ } else {
+ return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+ piggy_encode_data(keychainData, der,
+ piggy_encode_data(uuid, der,
+ der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
+ }
+}
+
+static uint8_t* piggy_encode_data(NSData* data,
+ const uint8_t *der, uint8_t *der_end)
+{
+ return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
+ ccder_encode_body(data.length, data.bytes, der, der_end));
+
+}
+
+static kTLKTypes
+name2type(NSString *view)
+{
+ if ([view isEqualToString:@"Manatee"])
+ return kTLKManatee;
+ else if ([view isEqualToString:@"Engram"])
+ return kTLKEngram;
+ else if ([view isEqualToString:@"AutoUnlock"])
+ return kTLKAutoUnlock;
+ if ([view isEqualToString:@"Health"])
+ return kTLKHealth;
+ return kTLKUnknown;
+}
+
+static unsigned
+rank_type(NSString *view)
+{
+ if ([view isEqualToString:@"Manatee"])
+ return 5;
+ else if ([view isEqualToString:@"Engram"])
+ return 4;
+ else if ([view isEqualToString:@"AutoUnlock"])
+ return 3;
+ if ([view isEqualToString:@"Health"])
+ return 2;
+ return 0;
+}
+
+static NSData *
+parse_uuid(NSString *uuidString)
+{
+ NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
+ uuid_t uuidblob;
+ [uuid getUUIDBytes:uuidblob];
+ return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
+}
+static size_t
+piggy_sizeof_data(NSData* data)
+{
+ return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
+}
+
+static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
+ if (type != kTLKUnknown) {
+ return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
+ piggy_sizeof_data(keychainData) +
+ piggy_sizeof_data(uuid) +
+ ccder_sizeof_uint64(type));
+ } else {
+ return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
+ piggy_sizeof_data(keychainData) +
+ piggy_sizeof_data(uuid) +
+ der_sizeof_string((__bridge CFStringRef)name, NULL));
+ }
+}
+
+NSArray<NSDictionary*>*
+SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
+{
+ NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
+
+ [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
+ unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
+ if (obj1[@"auth"] != NULL)
+ rank1 += 1000;
+ unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
+ if (obj2[@"auth"] != NULL)
+ rank2 += 1000;
+
+ /*
+ * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
+ * since we are sorting backward, the Ascending/Descending looks wrong below.
+ */
+ if (rank1 > rank2) {
+ return NSOrderedAscending;
+ } else if (rank1 < rank2) {
+ return NSOrderedDescending;
+ }
+ return NSOrderedSame;
+ }];
+
+ return sortedTLKs;
+}
+
+static NSArray<NSData *> *
+build_tlks(NSArray<NSDictionary*>* tlks)
+{
+ NSMutableArray *array = [NSMutableArray array];
+ NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
+
+ for (NSDictionary *item in sortedTLKs) {
+ NSData* keychainData = item[(__bridge id)kSecValueData];
+ NSString* name = item[(__bridge id)kSecAttrServer];
+ NSString *uuidString = item[(__bridge id)kSecAttrAccount];
+ NSData* uuid = parse_uuid(uuidString);
+
+ NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
+
+ unsigned char *der = [tlk mutableBytes];
+ unsigned char *der_end = der + [tlk length];
+
+ if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
+ return NULL;
+
+ secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
+
+ [array addObject:tlk];
+ }
+ return array;
+}
+
+static NSArray<NSData *> *
+build_identities(NSArray<NSData *>* identities)
+{
+ NSMutableArray *array = [NSMutableArray array];
+ for (NSData *item in identities) {
+ NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
+
+ unsigned char *der = [ident mutableBytes];
+ unsigned char *der_end = der + [ident length];
+
+ ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
+ [array addObject:ident];
+ }
+ return array;
+}
+
+
+
+static unsigned char *
+encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
+{
+ unsigned char *body_end = der_end;
+ for (NSData *datum in data) {
+ der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
+ if (der_end == NULL)
+ return NULL;
+ }
+ return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
+}
+
+static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
+{
+ return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
+ ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
+ ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
+}
+
+static NSData *encode_piggy(size_t IdentitiesBudget,
+ size_t TLKBudget,
+ NSArray<NSData*>* identities,
+ NSArray<NSDictionary*>* tlks)
+{
+ NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
+ NSArray<NSData *> *encodedIdentities = build_identities(identities);
+ NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
+ NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
+ size_t payloadSize = 0, identitiesSize = 0;
+ NSMutableData *result = NULL;
+
+ for (NSData *tlk in encodedTLKs) {
+ if (TLKBudget - payloadSize < [tlk length])
+ break;
+ [budgetArray addObject:tlk];
+ payloadSize += tlk.length;
+ }
+ secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
+
+ for (NSData *ident in encodedIdentities) {
+ if (IdentitiesBudget - identitiesSize < [ident length])
+ break;
+ [identitiesArray addObject:ident];
+ identitiesSize += ident.length;
+ }
+ secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
+
+
+ size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
+
+ result = [NSMutableData dataWithLength:piggySize];
+
+ unsigned char *der = [result mutableBytes];
+ unsigned char *der_end = der + [result length];
+
+ if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
+ encode_data_array(identitiesArray, der,
+ encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
+ return NULL;
+
+ return result;
+}
+
+static const size_t SOSCCIdentitiesBudget = 120;
+static const size_t SOSCCTLKBudget = 500;
+
+NSData *
+SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
+{
+ return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
+}
+
+CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
+{
+ CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
+ if(SOSPeerInfoIsCloudIdentity(peer)) {
+ CFArrayAppendValue(identities, peer);
+ }
+ });
+ return identities;
+}
+
+CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, CFErrorRef *error) {
+ CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
+ secnotice("piggy", "identities: %@", identities);
+
+ NSMutableArray *encodedIdenities = [NSMutableArray array];
+ CFIndex i, count = CFArrayGetCount(identities);
+ for (i = 0; i < count; i++) {
+ SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
+ NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
+ if (data)
+ [encodedIdenities addObject:data];
+ }
+ CFRelease(identities);
+
+ NSMutableArray* tlks = SOSAccountGetAllTLKs();
+
+ return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
+}
+
+static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
+ CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
+ if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
+ CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
+ if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
+ CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
+ if(accountName == NULL) {
+ accountName = CFSTR("Unavailable");
+ }
+ CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
+
+ secnotice("circleOps",
+ "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
+ operation, accountName, version, gencount, pkeyID, sigID, circleHash);
+ CFReleaseNull(pkeyID);
+ CFReleaseNull(sigID);
+ CFReleaseNull(circleHash);
+}
+
+CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
+ SOSGenCountRef gencount = NULL;
+ CFDataRef signature = NULL;
+ SecKeyRef ourKey = NULL;
+
+ CFDataRef pbblob = NULL;
+ SOSCircleRef prunedCircle = NULL;
+
+ secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
+
+ SOSCCStatus circleStat = [account getCircleStatus:error];
+ if(circleStat != kSOSCCInCircle) {
+ secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
+ return NULL;
+ }
+
+ SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
+ require_quiet(userKey, errOut);
+
+ require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
+ require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
+ secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
+
+ {
+ SOSFullPeerInfoRef fpi = account.fullPeerInfo;
+ ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
+ require_quiet(ourKey, errOut);
+ }
+
+ SOSCircleRef currentCircle = [account.trust getCircle:error];
+ require_quiet(currentCircle, errOut);
+
+ prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
+ require_quiet(prunedCircle, errOut);
+ require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
+
+ gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
+
+ signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
+ require_quiet(signature, errOut);
+ pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
+ pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
+
+errOut:
+ CFReleaseNull(prunedCircle);
+ CFReleaseNull(gencount);
+ CFReleaseNull(signature);
+ CFReleaseNull(ourKey);
+
+ if(!pbblob && error != NULL) {
+ secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
+ }
+
+ return pbblob;
+}
+
+bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
+ bool retval = false;
+ SecKeyRef userKey = NULL;
+ SOSAccountTrustClassic *trust = account.trust;
+ SOSGenCountRef gencount = NULL;
+ CFDataRef signature = NULL;
+ SecKeyRef pubKey = NULL;
+ bool setInitialSyncTimeoutToV0 = false;
+
+ secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
+
+ if (!isData(joiningBlob)) {
+ secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
+ return false;
+ }
+
+ userKey = SOSAccountGetPrivateCredential(account, error);
+ if(!userKey) {
+ secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
+ return retval;
+ }
+
+ if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
+ secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
+ return retval;
+ }
+
+ if(setInitialSyncTimeoutToV0){
+ secnotice("circleOps", "setting flag in account for piggyback v0");
+ SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
+ } else {
+ secnotice("circleOps", "clearing flag in account for piggyback v0");
+ SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
+ }
+ SOSAccountInitializeInitialSync(account);
+
+ pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
+
+ retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
+ return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
+ gencount,
+ pubKey,
+ signature,
+ trust.fullPeerInfo, error);;
+ }];
+
+ CFReleaseNull(gencount);
+ CFReleaseNull(pubKey);
+ CFReleaseNull(signature);
+
+ return retval;
+}
+
+static char boolToChars(bool val, char truechar, char falsechar) {
+ return val? truechar: falsechar;
+}
+
+#define ACCOUNTLOGSTATE "accountLogState"
+void SOSAccountLogState(SOSAccount* account) {
+ bool hasPubKey = account.accountKey != NULL;
+ SOSAccountTrustClassic *trust = account.trust;
+ bool pubTrusted = account.accountKeyIsTrusted;
+ bool hasPriv = account.accountPrivateKey != NULL;
+ SOSCCStatus stat = [account getCircleStatus:NULL];
+
+ CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
+ CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
+
+ secnotice(ACCOUNTLOGSTATE, "Start");
+
+ secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
+ boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
+ userPubKeyID,
+ SOSAccountGetSOSCCStatusString(stat)
+ );
+ CFReleaseNull(userPubKeyID);
+ if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
+ else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
+}
+
+void SOSAccountLogViewState(SOSAccount* account) {
+ bool isInCircle = [account.trust isInCircleOnly:NULL];
+ require_quiet(isInCircle, imOut);
+ SOSPeerInfoRef mpi = account.peerInfo;
+ bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
+ bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
+
+ CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
+ CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
+ secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
+ boolToChars(isInitialComplete, 'I', 'i'),
+ boolToChars(isBackupComplete, 'B', 'b'),
+ description);
+ });
+ CFReleaseNull(views);
+ CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
+ CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
+ secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
+ });
+ CFReleaseNull(unsyncedViews);
+
+imOut:
+ secnotice(ACCOUNTLOGSTATE, "Finish");
+
+ return;
+}
+
+
+void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
+ if(!isString(serial)) return;
+ CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+ CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
+ [account.trust updateV2Dictionary:account v2:newv2dict];
+}
+
+void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
+{
+ secnotice("otrtimer", "timer fired!");
+ CFErrorRef error = NULL;
+ SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
+
+ SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
+ SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
+ if(SOSCoderIsCoderInAwaitingState(coder)){
+ secnotice("otrtimer", "coder is in awaiting state, restarting coder");
+ CFErrorRef localError = NULL;
+ SOSCoderReset(coder);
+ if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
+ secerror("Attempt to recover coder failed to restart: %@", localError);
+ }
+ else{
+ secnotice("otrtimer", "coder restarted!");
+ SOSEngineSetCodersNeedSaving(engine, true);
+ SOSPeerSetMustSendMessage(peer, true);
+ SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
+ }
+ SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
+ SOSPeerRemoveOTRTimerEntry(peer);
+ SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
+ SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
+ }
+ else{
+ secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
+ }
+ });
+ if(error)
+ {
+ secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
+ }
+ CFReleaseNull(error);
+}
+
+void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
+{
+ __block SOSAccount* account = txn.account;
+ CFErrorRef error = NULL;
+
+ SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
+ SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
+
+ NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
+ PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
+ CFErrorRef error = NULL;
+ NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
+
+ if(message){
+ secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
+ bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
+
+ if(!sendResult || error){
+ secnotice("ratelimit", "could not send message: %@", error);
+ }
+ }
+ [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
+ [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
+ [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
+ });
+
+ if(error)
+ {
+ secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
+ }
+ CFReleaseNull(error);
+}
+
+@end
+
+