2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
6 * SOSAccount.c - Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
10 #import <Foundation/Foundation.h>
12 #import "keychain/SecureObjectSync/SOSAccount.h"
13 #import <Security/SecureObjectSync/SOSPeerInfo.h>
14 #import "keychain/SecureObjectSync/SOSPeerInfoV2.h"
15 #import "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
16 #import "keychain/SecureObjectSync/SOSTransportCircle.h"
17 #import "keychain/SecureObjectSync/SOSTransportCircleKVS.h"
18 #import "keychain/SecureObjectSync/SOSTransportMessage.h"
19 #import "keychain/SecureObjectSync/SOSTransportMessageKVS.h"
20 #import "keychain/SecureObjectSync/SOSTransportKeyParameter.h"
21 #import "keychain/SecureObjectSync/SOSKVSKeys.h"
22 #import "keychain/SecureObjectSync/SOSTransport.h"
23 #import "keychain/SecureObjectSync/SOSPeerCoder.h"
24 #import "keychain/SecureObjectSync/SOSInternal.h"
25 #import "keychain/SecureObjectSync/SOSRing.h"
26 #import "keychain/SecureObjectSync/SOSRingUtils.h"
27 #import "keychain/SecureObjectSync/SOSRingRecovery.h"
28 #import "keychain/SecureObjectSync/SOSAccountTransaction.h"
29 #import "keychain/SecureObjectSync/SOSAccountGhost.h"
30 #import "keychain/SecureObjectSync/SOSPiggyback.h"
31 #import "keychain/SecureObjectSync/SOSControlHelper.h"
32 #import "keychain/SecureObjectSync/SOSAuthKitHelpers.h"
34 #import "keychain/SecureObjectSync/SOSAccountTrust.h"
35 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
36 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
37 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
38 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
39 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
40 #import "keychain/SecureObjectSync/SOSPeerOTRTimer.h"
41 #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h"
42 #import "keychain/SecureObjectSync/SOSTypes.h"
44 #import "keychain/ckks/CKKSViewManager.h"
45 #import "keychain/ckks/CKKSLockStateTracker.h"
46 #import "keychain/ckks/CKKSNearFutureScheduler.h"
47 #import "keychain/ckks/CKKSPBFileStorage.h"
49 #import "keychain/ot/OTManager.h"
50 #import "keychain/ot/ObjCImprovements.h"
51 #import "keychain/ot/OctagonStateMachine.h"
52 #import "keychain/ot/OctagonStateMachineHelpers.h"
54 #include <Security/SecItemInternal.h>
55 #include <Security/SecEntitlements.h>
56 #include "keychain/securityd/SecItemServer.h"
58 #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h"
59 #include "keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h"
62 #import "SecdWatchdog.h"
64 #include <utilities/SecCFWrappers.h>
65 #include <utilities/SecCFError.h>
66 #include <utilities/SecADWrapper.h>
67 #include <utilities/SecFileLocations.h>
69 #include <os/activity.h>
70 #include <os/state_private.h>
72 #include <utilities/SecCoreCrypto.h>
74 #include <utilities/der_plist.h>
75 #include <utilities/der_plist_internal.h>
76 #include <corecrypto/ccder.h>
79 const CFStringRef kSOSAccountName = CFSTR("AccountName");
80 const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
81 const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
82 const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout");
83 const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
84 const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
85 const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary");
86 const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey");
87 const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing");
88 const CFStringRef kSOSAccountUUID = CFSTR("UUID");
89 const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters");
90 const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef>
91 const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef>
92 const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount");
93 const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion");
94 NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry";
95 NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount";
96 NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup";
98 const uint64_t max_packet_size_over_idms = 500;
101 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
103 #define DATE_LENGTH 25
104 const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
107 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void);
109 @interface SOSAccount () <OctagonStateMachineEngine>
110 @property dispatch_queue_t stateMachineQueue;
111 @property (readwrite) OctagonStateMachine* stateMachine;
113 @property (readwrite) CKKSPBFileStorage<SOSAccountConfiguration*>* accountConfiguration;
115 @property CKKSNearFutureScheduler *performBackups;
116 @property CKKSNearFutureScheduler *performRingUpdates;
120 @implementation SOSAccount
124 CFReleaseNull(self->_accountKey);
125 CFReleaseNull(self->_accountPrivateKey);
126 CFReleaseNull(self->_previousAccountKey);
128 [self.performBackups cancel];
129 [self.performRingUpdates cancel];
130 [self.stateMachine haltOperation];
135 @synthesize accountKey = _accountKey;
137 - (void) setAccountKey: (SecKeyRef) key {
138 CFRetainAssign(self->_accountKey, key);
141 @synthesize previousAccountKey = _previousAccountKey;
143 - (void) setPreviousAccountKey: (SecKeyRef) key {
144 CFRetainAssign(self->_previousAccountKey, key);
147 @synthesize accountPrivateKey = _accountPrivateKey;
149 - (void) setAccountPrivateKey: (SecKeyRef) key {
150 CFRetainAssign(self->_accountPrivateKey, key);
153 // Syntactic sugar getters
155 - (BOOL) hasPeerInfo {
156 return self.fullPeerInfo != nil;
159 - (SOSPeerInfoRef) peerInfo {
160 return self.trust.peerInfo;
163 - (SOSFullPeerInfoRef) fullPeerInfo {
164 return self.trust.fullPeerInfo;
167 - (NSString*) peerID {
168 return self.trust.peerID;
171 -(bool) ensureFactoryCircles
173 if (self.factory == nil){
177 NSString* circle_name = CFBridgingRelease(SOSDataSourceFactoryCopyName(self.factory));
182 CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL));
184 return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL);
187 -(void)ensureOctagonPeerKeys
190 CKKSLockStateTracker *tracker = [CKKSViewManager manager].lockStateTracker;
191 if (tracker && tracker.isLocked == false) {
192 [self.trust ensureOctagonPeerKeys:self.circle_transport];
197 -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f
201 self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
203 self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)];
205 SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
208 self.factory = f; // We adopt the factory. kthanksbai.
210 self.isListeningForSync = false;
212 self.accountPrivateKey = NULL;
213 self._password_tmp = NULL;
214 self.user_private_timer = NULL;
215 self.lock_notification_token = NOTIFY_TOKEN_INVALID;
217 self.change_blocks = [NSMutableArray array];
219 self.key_transport = nil;
220 self.circle_transport = NULL;
221 self.ck_storage = nil;
222 self.kvs_message_transport = nil;
224 self.circle_rings_retirements_need_attention = false;
225 self.engine_peer_state_needs_repair = false;
226 self.key_interests_need_updating = false;
228 self.backup_key =nil;
231 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
232 self.accountKeyIsTrusted = false;
233 self.accountKeyDerivationParamters = NULL;
234 self.accountKey = NULL;
235 self.previousAccountKey = NULL;
237 self.saveBlock = nil;
239 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
241 [self ensureFactoryCircles];
242 SOSAccountEnsureUUID(self);
245 [self setupStateMachine];
251 - (void)startStateMachine
254 [self.stateMachine startOperation];
258 -(BOOL)isEqual:(id) object
260 if(![object isKindOfClass:[SOSAccount class]])
263 SOSAccount* left = object;
264 return ([self.gestalt isEqual: left.gestalt] &&
265 CFEqualSafe(self.trust.trustedCircle, left.trust.trustedCircle) &&
266 [self.trust.expansion isEqual: left.trust.expansion] &&
267 CFEqualSafe(self.trust.fullPeerInfo, left.trust.fullPeerInfo));
271 - (void)userPublicKey:(void ((^))(BOOL trusted, NSData *spki, NSError *error))reply
273 dispatch_async(self.queue, ^{
274 if (!self.accountKeyIsTrusted || self.accountKey == NULL) {
275 NSDictionary *userinfo = @{
276 (id)kCFErrorDescriptionKey : @"User public key not trusted",
278 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
282 NSData *data = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(self.accountKey));
284 NSDictionary *userinfo = @{
285 (id)kCFErrorDescriptionKey : @"User public not available",
287 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
290 reply(self.accountKeyIsTrusted, data, NULL);
294 - (void)kvsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply
296 /* Need to collect performance counters from all subsystems, not just circle transport, don't have counters yet though */
297 SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error)
299 reply((__bridge NSDictionary *)returnedValues);
303 - (void)rateLimitingPerformanceCounters:(void(^)(NSDictionary <NSString *, NSString *> *))reply
305 CFErrorRef error = NULL;
306 CFDictionaryRef rateLimitingCounters = (CFDictionaryRef)SOSAccountGetValue(self, kSOSRateLimitingCounters, &error);
307 reply((__bridge NSDictionary*)rateLimitingCounters ? (__bridge NSDictionary*)rateLimitingCounters : [NSDictionary dictionary]);
310 - (void)stashedCredentialPublicKey:(void(^)(NSData *, NSError *error))reply
312 dispatch_async(self.queue, ^{
313 CFErrorRef error = NULL;
315 SecKeyRef user_private = SOSAccountCopyStashedUserPrivateKey(self, &error);
316 if (user_private == NULL) {
317 reply(NULL, (__bridge NSError *)error);
318 CFReleaseNull(error);
322 NSData *publicKey = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(user_private));
323 CFReleaseNull(user_private);
324 reply(publicKey, NULL);
328 - (void)assertStashedAccountCredential:(void(^)(BOOL result, NSError *error))complete
330 dispatch_async(self.queue, ^{
331 CFErrorRef error = NULL;
332 bool result = SOSAccountAssertStashedAccountCredential(self, &error);
333 complete(result, (__bridge NSError *)error);
334 CFReleaseNull(error);
338 static bool SyncKVSAndWait(CFErrorRef *error) {
339 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
341 __block bool success = false;
343 secnoticeq("fresh", "EFP calling SOSCloudKeychainSynchronizeAndWait");
345 os_activity_initiate("CloudCircle EFRESH", OS_ACTIVITY_FLAG_DEFAULT, ^(void) {
346 SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) {
347 secnotice("fresh", "EFP returned, callback error: %@", sync_error);
349 success = (sync_error == NULL);
351 CFRetainAssign(*error, sync_error);
354 dispatch_semaphore_signal(wait_for);
358 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
359 secnotice("fresh", "EFP complete: %s %@", success ? "success" : "failure", error ? *error : NULL);
365 static bool Flush(CFErrorRef *error) {
366 __block bool success = false;
368 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
369 secnotice("flush", "Starting");
371 SOSCloudKeychainFlush(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
372 success = (sync_error == NULL);
374 CFRetainAssign(*error, sync_error);
377 dispatch_semaphore_signal(wait_for);
380 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
382 secnotice("flush", "Returned %s", success? "Success": "Failure");
387 - (bool)syncWaitAndFlush:(CFErrorRef *)error
389 secnotice("pairing", "sync and wait starting");
391 if (!SyncKVSAndWait(error)) {
392 secnotice("pairing", "failed sync and wait: %@", error ? *error : NULL);
396 secnotice("pairing", "failed flush: %@", error ? *error : NULL);
399 secnotice("pairing", "finished sync and wait");
403 - (void)validatedStashedAccountCredential:(void(^)(NSData *credential, NSError *error))complete
405 CFErrorRef syncerror = NULL;
407 if (![self syncWaitAndFlush:&syncerror]) {
408 complete(NULL, (__bridge NSError *)syncerror);
409 CFReleaseNull(syncerror);
413 dispatch_async(self.queue, ^{
414 CFErrorRef error = NULL;
415 SecKeyRef key = NULL;
416 key = SOSAccountCopyStashedUserPrivateKey(self, &error);
418 secnotice("pairing", "no stashed credential");
419 complete(NULL, (__bridge NSError *)error);
420 CFReleaseNull(error);
424 SecKeyRef publicKey = SecKeyCopyPublicKey(key);
426 secnotice("pairing", "returning stash credential: %@", publicKey);
427 CFReleaseNull(publicKey);
430 NSData *keydata = CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error));
432 complete(keydata, (__bridge NSError *)error);
433 CFReleaseNull(error);
437 - (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete
439 CFErrorRef syncerror = NULL;
441 if (![self syncWaitAndFlush:&syncerror]) {
442 complete(NULL, (__bridge NSError *)syncerror);
443 CFReleaseNull(syncerror);
447 sleep(1); // make up for keygen time in password based version - let syncdefaults catch up
449 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
450 SecKeyRef accountPrivateKey = NULL;
451 CFErrorRef error = NULL;
452 NSDictionary *attributes = @{
453 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
454 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
457 accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error);
458 if (accountPrivateKey == NULL) {
459 complete(false, (__bridge NSError *)error);
460 secnotice("pairing", "SecKeyCreateWithData failed: %@", error);
461 CFReleaseNull(error);
465 if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) {
466 CFReleaseNull(accountPrivateKey);
467 complete(false, (__bridge NSError *)error);
468 secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error);
469 CFReleaseNull(error);
473 secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded");
475 CFReleaseNull(accountPrivateKey);
476 complete(true, NULL);
479 // This makes getting the private key the same as Asserting the password - we read all the other things
480 // that we just expressed interest in.
481 CFErrorRef error = NULL;
482 if (!Flush(&error)) {
483 secnotice("pairing", "failed final flush: %@", error ? error : NULL);
486 CFReleaseNull(error);
489 - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete
491 __block CFErrorRef localError = NULL;
492 __block NSData *applicationBlob = NULL;
493 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
494 SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError);
496 applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError));
497 CFReleaseNull(application);
500 complete(applicationBlob, (__bridge NSError *)localError);
501 CFReleaseNull(localError);
504 - (void)circleHash:(void (^)(NSString *, NSError *))complete
506 __block CFErrorRef localError = NULL;
507 __block NSString *circleHash = NULL;
508 SecAKSDoWithUserBagLockAssertion(&localError, ^{
509 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
510 circleHash = CFBridgingRelease(SOSCircleCopyHashString(txn.account.trust.trustedCircle));
513 complete(circleHash, (__bridge NSError *)localError);
514 CFReleaseNull(localError);
519 #if TARGET_OS_OSX || TARGET_OS_IOS
522 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
523 OTManager *otm = [OTManager manager];
524 SOSAccountGhostBustingOptions options = ([otm ghostbustByMidEnabled] == YES ? SOSGhostBustByMID: 0) |
525 ([otm ghostbustBySerialEnabled] == YES ? SOSGhostBustBySerialNumber : 0) |
526 ([otm ghostbustByAgeEnabled] == YES ? SOSGhostBustSerialByAge: 0);
531 #define GHOSTBUSTDATE @"ghostbustdate"
533 - (NSDate *) ghostBustGetDate {
534 if(! self.settings) {
535 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
537 return [self.settings valueForKey:GHOSTBUSTDATE];
540 - (bool) ghostBustCheckDate {
541 NSDate *ghostBustDate = [self ghostBustGetDate];
542 if(ghostBustDate && ([ghostBustDate timeIntervalSinceNow] <= 0)) return true;
546 - (void) ghostBustFollowup {
547 if(! self.settings) {
548 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
550 NSTimeInterval earliestGB = 60*60*24*3; // wait at least 3 days
551 NSTimeInterval latestGB = 60*60*24*7; // wait at most 7 days
552 NSDate *ghostBustDate = SOSCreateRandomDateBetweenNowPlus(earliestGB, latestGB);
553 [self.settings setValue:ghostBustDate forKey:GHOSTBUSTDATE];
556 // GhostBusting initial scheduling
557 - (void)ghostBustSchedule {
558 NSDate *ghostBustDate = [self ghostBustGetDate];
560 [self ghostBustFollowup];
564 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
565 __block bool result = false;
566 __block CFErrorRef localError = NULL;
568 if([SOSAuthKitHelpers accountIsHSA2]) {
569 [SOSAuthKitHelpers activeMIDs:^(NSSet <SOSTrustedDeviceAttributes *> * _Nullable activeMIDs, NSError * _Nullable error) {
570 SOSAuthKitHelpers *akh = [[SOSAuthKitHelpers alloc] initWithActiveMIDS:activeMIDs];
572 SecAKSDoWithUserBagLockAssertion(&localError, ^{
573 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
574 result = SOSAccountGhostBustCircle(txn.account, akh, options, 1);
575 [self ghostBustFollowup];
579 complete(result, NULL);
582 complete(false, NULL);
586 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
587 NSDate *ghostBustDate = [self ghostBustGetDate];
588 if(([ghostBustDate timeIntervalSinceNow] <= 0)) {
590 [self ghostBust: options complete: complete];
592 complete(false, nil);
597 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
598 // if no particular options are presented use the ramp options.
599 // 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.
601 options = [SOSAccount ghostBustGetRampSettings];
603 [self ghostBust: options complete: complete];
606 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
607 // 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.
608 NSMutableDictionary *gbInfoDictionary = [NSMutableDictionary new];
609 SOSAccountGhostBustingOptions options = [SOSAccount ghostBustGetRampSettings];
610 NSString *ghostBustDate = [[self ghostBustGetDate] description];
612 gbInfoDictionary[@"SOSGhostBustBySerialNumber"] = (SOSGhostBustBySerialNumber & options) ? @"ON": @"OFF";
613 gbInfoDictionary[@"SOSGhostBustByMID"] = (SOSGhostBustByMID & options) ? @"ON": @"OFF";
614 gbInfoDictionary[@"SOSGhostBustSerialByAge"] = (SOSGhostBustSerialByAge & options) ? @"ON": @"OFF";
615 gbInfoDictionary[@"SOSAccountGhostBustDate"] = ghostBustDate;
618 NSData *json = [NSJSONSerialization dataWithJSONObject:gbInfoDictionary
619 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
622 secnotice("ghostbust", "Error during ghostBustInfo JSONification: %@", err.localizedDescription);
629 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
633 - (NSDate *) ghostBustGetDate {
637 - (void) ghostBustFollowup {
640 - (void)ghostBustSchedule {
643 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
644 complete(false, NULL);
647 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
648 complete(false, NULL);
651 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
652 complete(false, nil);
655 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
659 - (bool) ghostBustCheckDate {
665 - (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete
667 __block CFErrorRef localError = NULL;
668 __block NSData *blob = NULL;
669 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant);
671 complete(NULL, (__bridge NSError *)localError);
672 CFReleaseNull(localError);
676 SecAKSDoWithUserBagLockAssertionSoftly(^{
677 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
678 blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError));
684 complete(blob, (__bridge NSError *)localError);
685 CFReleaseNull(localError);
688 - (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete
690 __block CFErrorRef localError = NULL;
691 __block bool res = false;
693 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
694 res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError);
697 complete(res, (__bridge NSError *)localError);
698 CFReleaseNull(localError);
701 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
703 CFErrorRef error = NULL;
704 uint32_t isflags = 0;
706 if (flags & SOSControlInitialSyncFlagTLK)
707 isflags |= SecServerInitialSyncCredentialFlagTLK;
708 if (flags & SOSControlInitialSyncFlagPCS)
709 isflags |= SecServerInitialSyncCredentialFlagPCS;
710 if (flags & SOSControlInitialSyncFlagPCSNonCurrent)
711 isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent;
712 if (flags & SOSControlInitialSyncFlagBluetoothMigration)
713 isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration;
716 NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error));
717 complete(array, (__bridge NSError *)error);
718 CFReleaseNull(error);
721 - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete
723 CFErrorRef error = NULL;
724 bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error);
725 complete(res, (__bridge NSError *)error);
726 CFReleaseNull(error);
729 - (void)rpcTriggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
731 __block CFErrorRef localError = NULL;
732 __block bool res = false;
734 secnotice("sync", "trigger a forced sync for %@", peers);
736 SecAKSDoWithUserBagLockAssertion(&localError, ^{
737 [self performTransaction:^(SOSAccountTransaction *txn) {
739 NSSet *peersSet = [NSSet setWithArray:peers];
740 CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError);
741 if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) {
744 CFReleaseNull(handledPeers);
746 res = SOSAccountRequestSyncWithAllPeers(txn, &localError);
750 complete(res, (__bridge NSError *)localError);
751 CFReleaseNull(localError);
754 - (void)rpcTriggerBackup:(NSArray<NSString *>* _Nullable)backupPeers complete:(void (^)(NSError *error))complete
756 __block CFErrorRef localError = NULL;
758 if (backupPeers.count == 0) {
759 SOSEngineRef engine = (SOSEngineRef) [self.kvs_message_transport SOSTransportMessageGetEngine];
760 backupPeers = CFBridgingRelease(SOSEngineCopyBackupPeerNames(engine, &localError));
764 [self triggerBackupForPeers:backupPeers];
767 complete((__bridge NSError *)localError);
768 CFReleaseNull(localError);
771 - (void)rpcTriggerRingUpdate:(void (^)(NSError *error))complete
774 [self triggerRingUpdate];
779 - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete
781 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
782 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
784 NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters];
785 complete(parameters, nil);
788 complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
792 - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete
794 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
795 NSError* error = nil;
796 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
798 [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error];
802 complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
810 - (void) flattenToSaveBlock {
811 if (self.saveBlock) {
812 NSError* error = nil;
813 NSData* saveData = [self encodedData:&error];
815 (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error);
819 CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) {
820 return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt);
823 CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) {
824 CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL);
825 return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict);
828 static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){
829 SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
830 //send new DSID over account changed
831 [account.circle_transport kvsSendOfficialDSID:dsid err:NULL];
835 void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) {
836 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
837 if(accountDSID == NULL) {
838 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
840 SOSAccountUpdateDSID(account, dsid);
841 } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
842 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
844 //DSID has changed, blast the account!
845 SOSAccountSetToNew(account);
847 //update DSID to the new DSID
848 SOSAccountUpdateDSID(account, dsid);
850 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
855 void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews)
857 [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews];
858 [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews];
861 #pragma clang diagnostic push
862 #pragma clang diagnostic ignored "-Wunused-value"
863 SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) {
864 SOSViewResultCode retval = kSOSCCGeneralViewError;
865 // The V0 view switches on and off all on it's own, we allow people the delusion
866 // of control and status if it's what we're stuck at., otherwise error.
867 if (SOSAccountSyncingV0(account)) {
868 require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
869 retval = kSOSCCViewMember;
871 require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
872 retval = kSOSCCViewNotMember;
877 #pragma clang diagnostic pop
879 SOSAccount* SOSAccountCreate(CFAllocatorRef allocator,
880 CFDictionaryRef gestalt,
881 SOSDataSourceFactoryRef factory) {
883 SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory];
884 dispatch_sync(a.queue, ^{
885 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate");
886 a.key_interests_need_updating = true;
892 static OSStatus do_delete(CFDictionaryRef query) {
895 result = SecItemDelete(query);
897 secerror("SecItemDelete: %d", (int)result);
903 do_keychain_delete_aks_bags()
906 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
907 kSecClass, kSecClassGenericPassword,
908 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
909 kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
910 kSecAttrService, CFSTR("SecureBackupService"),
911 kSecAttrSynchronizable, kCFBooleanTrue,
912 kSecUseTombstones, kCFBooleanFalse,
915 result = do_delete(item);
922 do_keychain_delete_identities()
925 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
926 kSecClass, kSecClassKey,
927 kSecAttrSynchronizable, kCFBooleanTrue,
928 kSecUseTombstones, kCFBooleanFalse,
929 kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
932 result = do_delete(item);
939 do_keychain_delete_lakitu()
942 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
943 kSecClass, kSecClassGenericPassword,
944 kSecAttrSynchronizable, kCFBooleanTrue,
945 kSecUseTombstones, kCFBooleanFalse,
946 kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
947 kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
948 kSecAttrService, CFSTR("EscrowService"),
951 result = do_delete(item);
958 do_keychain_delete_sbd()
961 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
962 kSecClass, kSecClassGenericPassword,
963 kSecAttrSynchronizable, kCFBooleanTrue,
964 kSecUseTombstones, kCFBooleanFalse,
965 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
968 result = do_delete(item);
974 void SOSAccountSetToNew(SOSAccount* a)
976 secnotice("accountChange", "Setting Account to New");
979 /* remove all syncable items */
980 result = do_keychain_delete_aks_bags(); (void) result;
981 secdebug("set to new", "result for deleting aks bags: %d", result);
983 result = do_keychain_delete_identities(); (void) result;
984 secdebug("set to new", "result for deleting identities: %d", result);
986 result = do_keychain_delete_lakitu(); (void) result;
987 secdebug("set to new", "result for deleting lakitu: %d", result);
989 result = do_keychain_delete_sbd(); (void) result;
990 secdebug("set to new", "result for deleting sbd: %d", result);
992 a.accountKeyIsTrusted = false;
994 if (a.user_private_timer) {
995 dispatch_source_cancel(a.user_private_timer);
996 a.user_private_timer = NULL;
997 xpc_transaction_end();
1000 if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) {
1001 notify_cancel(a.lock_notification_token);
1002 a.lock_notification_token = NOTIFY_TOKEN_INVALID;
1007 // Live Notification
1009 // update_interest_block;
1011 SOSUnregisterTransportKeyParameter(a.key_transport);
1012 SOSUnregisterTransportMessage(a.kvs_message_transport);
1013 SOSUnregisterTransportCircle(a.circle_transport);
1015 a.circle_transport = NULL;
1016 a.kvs_message_transport = nil;
1019 a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
1021 [a ensureFactoryCircles]; // Does rings too
1023 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
1024 SOSAccountEnsureUUID(a);
1025 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew");
1026 a.key_interests_need_updating = true;
1029 bool SOSAccountIsNew(SOSAccount* account, CFErrorRef *error){
1030 bool result = false;
1031 SOSAccountTrustClassic* trust = account.trust;
1032 if(account.accountKeyIsTrusted != false || trust.departureCode != kSOSNeverAppliedToCircle ||
1033 CFSetGetCount((__bridge CFSetRef)trust.retirees) != 0)
1036 if(trust.retirees != nil)
1038 if(trust.expansion != nil)
1041 if(account.user_private_timer != NULL || account.lock_notification_token != NOTIFY_TOKEN_INVALID)
1049 dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) {
1050 return account.queue;
1053 void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){
1054 account.accountKeyIsTrusted = true;
1057 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
1059 SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error];
1060 if (!SOSAccountHasPublicKey(self, error)) {
1061 if(circleStatus == kSOSCCInCircle) {
1063 CFReleaseNull(*error);
1064 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);
1067 circleStatus = kSOSCCError;
1069 return circleStatus;
1072 -(bool) isInCircle:(CFErrorRef *)error
1074 SOSCCStatus result = [self getCircleStatus:error];
1075 if (result != kSOSCCInCircle) {
1076 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
1083 bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) {
1085 SOSAccountTrustClassic *trust = account.trust;
1086 NSMutableSet* retirees = trust.retirees;
1087 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
1088 CFSetSetValue((__bridge CFMutableSetRef) retirees, peer);
1089 CFErrorRef cleanupError = NULL;
1090 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) {
1091 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
1093 CFReleaseSafe(cleanupError);
1098 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) {
1099 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
1100 SOSPeerInfoRef me = account.peerInfo;
1101 bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
1103 SOSAccountTrustClassic *trust = account.trust;
1104 NSMutableSet* retirees = trust.retirees;
1106 if(!new_circle) return NULL;
1107 __block bool workDone = false;
1109 CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) {
1110 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
1111 if (isSOSPeerInfo(pi)) {
1112 SOSCircleUpdatePeerInfo(new_circle, pi);
1118 if(workDone && SOSCircleCountPeers(new_circle) == 0) {
1119 SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
1123 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
1124 if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) ||
1125 ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){
1126 CFReleaseNull(new_circle);
1129 account.notifyBackupOnExit = true;
1131 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
1132 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
1133 // we have a chance to set things right with a SetCreds/Join sequence. This will cause
1134 // handleUpdateCircle to return false.
1135 CFReleaseNull(new_circle);
1139 // This case is when we aren't an applicant and the circle is retirement-empty.
1140 secnotice("circleOps", "Reset to empty with last retirement");
1141 SOSCircleResetToEmpty(new_circle, NULL);
1149 // MARK: Circle Membership change notificaion
1152 void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1153 SOSAccountCircleMembershipChangeBlock copy = changeBlock;
1154 [a.change_blocks addObject:copy];
1157 void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1158 [a.change_blocks removeObject:changeBlock];
1161 void SOSAccountPurgeIdentity(SOSAccount* account) {
1162 SOSAccountTrustClassic *trust = account.trust;
1163 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1166 // Purge private key but don't return error if we can't.
1167 CFErrorRef purgeError = NULL;
1168 if (!SOSFullPeerInfoPurgePersistentKey(identity, &purgeError)) {
1169 secwarning("Couldn't purge persistent key for %@ [%@]", identity, purgeError);
1171 CFReleaseNull(purgeError);
1173 trust.fullPeerInfo = nil;
1177 bool sosAccountLeaveCircleWithAnalytics(SOSAccount* account, SOSCircleRef circle, NSData* parentData, CFErrorRef* error) {
1178 SOSAccountTrustClassic *trust = account.trust;
1179 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1180 NSMutableSet* retirees = trust.retirees;
1182 NSError* localError = nil;
1183 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentData error:&localError];
1185 SOSFullPeerInfoRef fpi = identity;
1186 if(!fpi) return false;
1188 CFErrorRef retiredError = NULL;
1190 bool retval = false;
1192 SFSignInAnalytics *promoteToRetiredEvent = [parent newSubTaskForEvent:@"promoteToRetiredEvent"];
1193 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
1195 [promoteToRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
1196 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
1198 *error = retiredError;
1200 CFReleaseNull(retiredError);
1203 [promoteToRetiredEvent stopWithAttributes:nil];
1206 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1208 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1209 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1210 // Remove our application if we have one.
1211 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1212 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1213 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1214 CFErrorRef cleanupError = NULL;
1215 SFSignInAnalytics *cleanupEvent = [parent newSubTaskForEvent:@"cleanupAfterPeerEvent"];
1216 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1217 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1219 [cleanupEvent stopWithAttributes:nil];
1220 CFReleaseSafe(cleanupError);
1224 // Store the retirement record locally.
1225 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1227 trust.retirees = retirees;
1229 // Write retirement to Transport
1230 CFErrorRef postError = NULL;
1231 SFSignInAnalytics *postRestirementEvent = [parent newSubTaskForEvent:@"postRestirementEvent"];
1232 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1233 [postRestirementEvent logRecoverableError:(__bridge NSError*)postError];
1234 secwarning("Couldn't post retirement (%@)", postError);
1236 [postRestirementEvent stopWithAttributes:nil];
1238 SFSignInAnalytics *flushChangesEvent = [parent newSubTaskForEvent:@"flushChangesEvent"];
1240 if(![account.circle_transport flushChanges:&postError]){
1241 [flushChangesEvent logRecoverableError:(__bridge NSError*)postError];
1242 secwarning("Couldn't flush retirement data (%@)", postError);
1244 [flushChangesEvent stopWithAttributes:nil];
1245 CFReleaseNull(postError);
1247 SFSignInAnalytics *purgeIdentityEvent = [parent newSubTaskForEvent:@"purgeIdentityEvent"];
1248 SOSAccountPurgeIdentity(account);
1249 [purgeIdentityEvent stopWithAttributes:nil];
1252 CFReleaseNull(retire_peer);
1256 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) {
1257 SOSAccountTrustClassic *trust = account.trust;
1258 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1259 NSMutableSet* retirees = trust.retirees;
1261 SOSFullPeerInfoRef fpi = identity;
1262 if(!fpi) return false;
1264 CFErrorRef localError = NULL;
1266 bool retval = false;
1268 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
1270 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1272 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1273 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1274 // Remove our application if we have one.
1275 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1276 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1277 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1278 CFErrorRef cleanupError = NULL;
1279 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1280 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1282 CFReleaseSafe(cleanupError);
1286 // Store the retirement record locally.
1287 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1289 trust.retirees = retirees;
1291 // Write retirement to Transport
1292 CFErrorRef postError = NULL;
1293 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1294 secwarning("Couldn't post retirement (%@)", postError);
1296 if(![account.circle_transport flushChanges:&postError]){
1297 secwarning("Couldn't flush retirement data (%@)", postError);
1299 CFReleaseNull(postError);
1302 SOSAccountPurgeIdentity(account);
1306 CFReleaseNull(localError);
1307 CFReleaseNull(retire_peer);
1311 bool sosAccountLeaveRing(SOSAccount* account, SOSRingRef ring, CFErrorRef* error) {
1312 SOSAccountTrustClassic *trust = account.trust;
1313 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1315 SOSFullPeerInfoRef fpi = identity;
1316 if(!fpi) return false;
1317 SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
1318 CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
1320 CFErrorRef localError = NULL;
1322 bool retval = false;
1323 bool writeRing = false;
1324 bool writePeerInfo = false;
1326 if(SOSRingHasPeerID(ring, peerID)) {
1327 writePeerInfo = true;
1330 if(writePeerInfo || writeRing) {
1331 SOSRingWithdraw(ring, NULL, fpi, error);
1335 CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
1338 [account.circle_transport kvsRingPostRing:SOSRingGetName(ring) ring:ring_data err:NULL];
1340 CFReleaseNull(ring_data);
1343 CFReleaseNull(localError);
1347 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1348 bool result = false;
1349 if (account.circle_transport) {
1350 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1356 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1357 local value that has been overwritten by a distant value. If there is no
1358 conflict between the local and the distant values when doing the initial
1359 sync (e.g. if the cloud has no data stored or the client has not stored
1360 any data yet), you'll never see that notification.
1362 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1363 with server but initial round trip with server does not imply
1364 NSUbiquitousKeyValueStoreInitialSyncChange.
1369 // MARK: Status summary
1373 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1375 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1376 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1377 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1378 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1379 case kSOSCCError: return CFSTR("kSOSCCError");
1381 return CFSTR("kSOSCCError");
1383 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1384 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1385 return kSOSCCInCircle;
1386 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1387 return kSOSCCInCircle;
1388 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1389 return kSOSCCNotInCircle;
1390 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1391 return kSOSCCRequestPending;
1392 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1393 return kSOSCCCircleAbsent;
1394 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1401 // MARK: Account Reset Circles
1404 // This needs to be called within a [trust modifyCircle()] block
1407 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1408 bool retval = false;
1410 SOSAccountTrustClassic *trust = account.trust;
1411 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1413 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1415 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1416 if(SOSPeerInfoIsCloudIdentity(peer)) {
1417 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1419 CFSetAddValue(iCloud2Remove, peer);
1421 CFReleaseNull(icfpi);
1425 if(CFSetGetCount(iCloud2Remove) > 0) {
1427 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1429 CFReleaseNull(iCloud2Remove);
1434 // MARK: start backups
1438 - (bool)_onQueueEnsureInBackupRings {
1439 __block bool result = false;
1440 __block CFErrorRef error = NULL;
1441 secnotice("backup", "Ensuring in rings");
1443 dispatch_assert_queue(self.queue);
1445 if(!self.backup_key){
1449 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)self.backup_key, &error)){
1450 secnotice("backupkey", "account backup key isn't valid: %@", error);
1451 self.backup_key = nil;
1452 CFReleaseNull(error);
1456 NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(self.peerInfo);
1457 if(![peerBackupKey isEqual:self.backup_key]) {
1458 result = SOSAccountUpdatePeerInfo(self, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1459 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(self.backup_key), error);
1462 secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error);
1463 CFReleaseNull(error);
1468 // It's a good key, we're going with it. Stop backing up the old way.
1469 CFErrorRef localError = NULL;
1470 if (!SOSDeleteV0Keybag(&localError)) {
1471 secerror("Failed to delete v0 keybag: %@", localError);
1473 CFReleaseNull(localError);
1475 // Setup backups the new way.
1476 SOSAccountForEachBackupView(self, ^(const void *value) {
1477 CFStringRef viewName = asString(value, NULL);
1478 bool resetRing = SOSAccountValidateBackupRingForView(self, viewName, NULL);
1480 SOSAccountUpdateBackupRing(self, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
1481 SOSRingRef newRing = SOSAccountCreateBackupRingForView(self, viewName, error);
1488 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1490 CFReleaseNull(error);
1495 // MARK: Recovery Public Key Functions
1498 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1499 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1500 if(retval) secnotice("recovery", "successfully registered recovery public key");
1501 else secnotice("recovery", "could not register recovery public key: %@", *error);
1502 SOSClearErrorIfTrue(retval, error);
1506 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1507 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1508 if(retval) secnotice("recovery", "RK Cleared");
1509 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1510 SOSClearErrorIfTrue(retval, error);
1514 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1515 CFDataRef result = NULL;
1516 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1517 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1519 if (!isData(result)) {
1520 CFReleaseNull(result);
1522 SOSClearErrorIfTrue(result != NULL, error);
1531 static bool SOSAccountJoinCircleWithAnalytics(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1532 bool use_cloud_peer, NSData* parentEvent, CFErrorRef* error) {
1533 SOSAccount* account = aTxn.account;
1534 SOSAccountTrustClassic *trust = account.trust;
1535 __block bool result = false;
1536 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1537 SFSignInAnalytics *ensureFullPeerAvailableEvent = nil;
1538 NSError* localError = nil;
1539 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1541 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1542 ensureFullPeerAvailableEvent = [parent newSubTaskForEvent:@"ensureFullPeerAvailableEvent"];
1543 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1544 [ensureFullPeerAvailableEvent stopWithAttributes:nil];
1546 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1547 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1548 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1549 // this also clears initial sync data
1550 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1552 SOSAccountInitializeInitialSync(account);
1553 if (use_cloud_peer) {
1554 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1556 SFSignInAnalytics *acceptApplicantEvent = [parent newSubTaskForEvent:@"acceptApplicantEvent"];
1557 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1558 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1559 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1560 trust.departureCode = kSOSNeverLeftCircle;
1561 if(result && cloud_full_peer) {
1562 CFErrorRef localError = NULL;
1563 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1564 require_quiet(cloudid, finish);
1565 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1566 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1569 [acceptApplicantEvent logRecoverableError:(__bridge NSError *)(localError)];
1570 secerror("Failed to join with cloud identity: %@", localError);
1571 CFReleaseNull(localError);
1576 [acceptApplicantEvent stopWithAttributes:nil];
1577 if (use_cloud_peer) {
1578 SFSignInAnalytics *updateOutOfDateSyncViewsEvent = [acceptApplicantEvent newSubTaskForEvent:@"updateOutOfDateSyncViewsEvent"];
1579 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1580 [updateOutOfDateSyncViewsEvent stopWithAttributes:nil];
1584 CFReleaseNull(cloud_full_peer);
1588 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1589 bool use_cloud_peer, CFErrorRef* error) {
1590 SOSAccount* account = aTxn.account;
1591 SOSAccountTrustClassic *trust = account.trust;
1592 __block bool result = false;
1593 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1594 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1595 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1596 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1597 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1598 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1599 // this also clears initial sync data
1600 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1602 SOSAccountInitializeInitialSync(account);
1603 if (use_cloud_peer) {
1604 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1606 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1607 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1608 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1609 trust.departureCode = kSOSNeverLeftCircle;
1610 if(result && cloud_full_peer) {
1611 CFErrorRef localError = NULL;
1612 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1613 require_quiet(cloudid, finish);
1614 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1615 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1618 secerror("Failed to join with cloud identity: %@", localError);
1619 CFReleaseNull(localError);
1624 if (use_cloud_peer) {
1625 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1629 CFReleaseNull(cloud_full_peer);
1633 static bool SOSAccountJoinCirclesWithAnalytics_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, NSData* parentEvent, CFErrorRef* error) {
1634 SOSAccount* account = aTxn.account;
1635 SOSAccountTrustClassic *trust = account.trust;
1636 bool success = false;
1638 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1639 require_quiet(user_key, done); // Fail if we don't get one.
1641 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1643 if (trust.fullPeerInfo != NULL) {
1644 SOSPeerInfoRef myPeer = trust.peerInfo;
1645 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1646 require_quiet(!success, done);
1648 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1650 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1651 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1653 trust.fullPeerInfo = NULL;
1657 success = SOSAccountJoinCircleWithAnalytics(aTxn, user_key, use_cloud_identity, parentEvent, error);
1659 require_quiet(success, done);
1661 trust.departureCode = kSOSNeverLeftCircle;
1667 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) {
1668 SOSAccount* account = aTxn.account;
1669 SOSAccountTrustClassic *trust = account.trust;
1670 bool success = false;
1672 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1673 require_quiet(user_key, done); // Fail if we don't get one.
1675 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1677 if (trust.fullPeerInfo != NULL) {
1678 SOSPeerInfoRef myPeer = trust.peerInfo;
1679 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1680 require_quiet(!success, done);
1682 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1684 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1685 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1687 trust.fullPeerInfo = NULL;
1691 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
1693 require_quiet(success, done);
1695 trust.departureCode = kSOSNeverLeftCircle;
1701 bool SOSAccountJoinCirclesWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1702 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCirclesWithAnalytics)");
1703 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, false, parentEvent, error);
1706 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1707 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1708 return SOSAccountJoinCircles_internal(aTxn, false, error);
1711 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1712 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1713 return SOSAccountJoinCircles_internal(aTxn, true, error);
1716 bool SOSAccountJoinCirclesAfterRestoreWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1717 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1718 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, true, parentEvent, error);
1721 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error)
1723 bool result = false;
1724 CFMutableSetRef peersToRemove = NULL;
1725 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1727 secnotice("circleOps", "Can't remove without userKey");
1730 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1731 SOSPeerInfoRef me = account.peerInfo;
1732 if(!(me_full && me))
1734 secnotice("circleOps", "Can't remove without being active peer");
1735 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1739 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1741 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1744 CFReleaseNull(peersToRemove);
1745 secnotice("circleOps", "No peerSet to remove");
1749 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1750 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1751 CFSetRemoveValue(peersToRemove, me);
1753 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1754 bool success = false;
1756 if(CFSetGetCount(peersToRemove) != 0) {
1757 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1758 success = SOSAccountGenerationSignatureUpdate(account, error);
1759 } else success = true;
1761 if (success && leaveCircle) {
1762 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1763 success = sosAccountLeaveCircle(account, circle, error);
1772 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1773 secnotice("circleOps", "Removed Peers from circle %@", description);
1777 CFReleaseNull(peersToRemove);
1782 bool SOSAccountRemovePeersFromCircleWithAnalytics(SOSAccount* account, CFArrayRef peers, NSData* parentEvent, CFErrorRef* error)
1785 NSError* localError = nil;
1786 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1788 bool result = false;
1789 CFMutableSetRef peersToRemove = NULL;
1790 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1792 secnotice("circleOps", "Can't remove without userKey");
1795 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1796 SOSPeerInfoRef me = account.peerInfo;
1797 if(!(me_full && me))
1799 secnotice("circleOps", "Can't remove without being active peer");
1800 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1804 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1806 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1809 CFReleaseNull(peersToRemove);
1810 secnotice("circleOps", "No peerSet to remove");
1814 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1815 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1816 CFSetRemoveValue(peersToRemove, me);
1818 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1819 bool success = false;
1821 if(CFSetGetCount(peersToRemove) != 0) {
1822 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1823 SFSignInAnalytics *generationSignatureUpdateEvent = [parent newSubTaskForEvent:@"generationSignatureUpdateEvent"];
1824 success = SOSAccountGenerationSignatureUpdate(account, error);
1825 if(error && *error){
1826 NSError* signatureUpdateError = (__bridge NSError*)*error;
1827 [generationSignatureUpdateEvent logUnrecoverableError:signatureUpdateError];
1829 [generationSignatureUpdateEvent stopWithAttributes:nil];
1830 } else success = true;
1832 if (success && leaveCircle) {
1833 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1834 success = sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
1843 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1844 secnotice("circleOps", "Removed Peers from circle %@", description);
1848 CFReleaseNull(peersToRemove);
1852 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1853 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1854 dispatch_group_t group = dispatch_group_create();
1855 SOSAccountTrustClassic *trust = account.trust;
1856 __block bool result = false;
1857 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1858 // Add a task to the group
1859 dispatch_group_async(group, queue, ^{
1860 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1861 secnotice("circleOps", "Leaving circle by client request (Bail)");
1862 return sosAccountLeaveCircle(account, circle, error);
1865 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1866 dispatch_group_wait(group, milestone);
1868 trust.departureCode = kSOSWithdrewMembership;
1875 // MARK: Application
1878 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1879 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1880 SOSAccountTrustClassic *trust = account.trust;
1882 SOSPeerInfoRef me = trust.peerInfo;
1883 CFErrorRef peer_error = NULL;
1884 if (trust.trustedCircle && me &&
1885 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1886 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1887 __block bool modified = false;
1888 CFArrayForEach(peer_infos, ^(const void *value) {
1889 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1890 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1891 if (action(circle, trust.fullPeerInfo, peer)) {
1900 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1901 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1904 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1905 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1909 __block int64_t acceptedPeers = 0;
1911 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1912 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1918 if (acceptedPeers == CFArrayGetCount(applicants))
1923 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1924 __block bool success = true;
1925 __block int64_t num_peers = 0;
1927 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1928 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1932 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1940 CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccount* account, CFErrorRef* error) {
1941 return CFSTR("We're compatible, go away");
1944 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1945 SOSAccountTrustClassic *trust = account.trust;
1946 return trust.departureCode;
1949 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1950 SOSAccountTrustClassic *trust = account.trust;
1951 trust.departureCode = reason;
1955 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1956 CFArrayRef result = NULL;
1957 CFNumberRef generation = NULL;
1958 SOSAccountTrustClassic *trust = account.trust;
1960 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1961 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1963 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1964 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1970 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1971 if (!SOSAccountHasPublicKey(account, error))
1974 return account.accountKeyIsTrusted;
1977 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1978 // TODO: this result is never set or used
1980 SOSAccountTrustClassic *trust = account.trust;
1982 secnotice("updates", "Ensuring peer registration.");
1985 secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
1989 if([account getCircleStatus: NULL] != kSOSCCInCircle) {
1993 // If we are not in the circle, there is no point in setting up peers
1994 if(!SOSAccountIsMyPeerActive(account, NULL)) {
1998 // This code only uses the SOSFullPeerInfoRef for two things:
1999 // - Finding out if this device is in the trusted circle
2000 // - Using the peerID for this device to see if the current peer is "me"
2001 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
2003 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
2005 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
2006 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
2007 CFErrorRef localError = NULL;
2009 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
2011 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
2012 CFReleaseNull(localError);
2020 // Value manipulation
2023 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
2024 SOSAccountTrustClassic *trust = account.trust;
2025 if (!trust.expansion) {
2028 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
2031 bool SOSAccountAddEscrowRecords(SOSAccount* account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error){
2032 CFMutableDictionaryRef escrowRecords = (CFMutableDictionaryRef)SOSAccountGetValue(account, kSOSEscrowRecord, error);
2033 CFMutableDictionaryRef escrowCopied = NULL;
2034 bool success = false;
2036 if(isDictionary(escrowRecords) && escrowRecords != NULL)
2037 escrowCopied = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(escrowRecords), escrowRecords);
2039 escrowCopied = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2041 CFDictionaryAddValue(escrowCopied, dsid, record);
2042 SOSAccountSetValue(account, kSOSEscrowRecord, escrowCopied, error);
2047 CFReleaseNull(escrowCopied);
2053 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
2054 bool success = false;
2056 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
2057 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
2062 - (void)_onQueueRecordRetiredPeersInCircle {
2064 dispatch_assert_queue(self.queue);
2066 if (![self isInCircle:NULL]) {
2069 __block bool updateRings = false;
2070 SOSAccountTrustClassic *trust = self.trust;
2071 [trust modifyCircle:self.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
2072 __block bool updated = false;
2073 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
2074 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
2076 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
2078 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
2079 CFErrorRef cleanupError = NULL;
2080 if (![self.trust cleanupAfterPeer:self.kvs_message_transport circleTransport:self.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
2081 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
2082 CFReleaseSafe(cleanupError);
2089 SOSAccountProcessBackupRings(self, NULL);
2093 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
2095 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
2097 __block CFTypeRef object = NULL;
2099 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2100 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2102 CloudKeychainReplyBlock replyBlock =
2103 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
2105 object = returnedValues;
2110 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
2112 dispatch_semaphore_signal(waitSemaphore);
2115 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
2117 dispatch_semaphore_wait(waitSemaphore, finishTime);
2118 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
2123 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
2127 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
2129 CFStringRef uuid = SOSAccountCopyUUID(account);
2130 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2131 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2133 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2135 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
2137 dispatch_semaphore_signal(waitSemaphore);
2140 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
2141 dispatch_semaphore_wait(waitSemaphore, finishTime);
2142 CFReleaseNull(uuid);
2145 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
2147 NSDate *now = [NSDate date];
2148 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
2150 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
2152 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
2154 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
2156 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
2157 CFStringAppend(timeDescription, decription);
2159 CFStringAppend(timeDescription, CFSTR("]"));
2161 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
2162 CFReleaseNull(timeDescription);
2164 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2165 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2166 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2168 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2170 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2172 dispatch_semaphore_signal(waitSemaphore);
2175 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
2176 dispatch_semaphore_wait(waitSemaphore, finishTime);
2179 // set the cleanup frequency to 3 days.
2180 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
2182 //Get all the key/values in KVS and remove old entries
2183 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
2185 // This should only happen on some number of days
2186 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
2187 NSDate *now = [NSDate date];
2188 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
2190 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
2194 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2196 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
2197 NSMutableArray *peerIDs = [NSMutableArray array];
2198 NSMutableArray *keysToRemove = [NSMutableArray array];
2200 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
2201 CFArrayForEach(peers, ^(const void *value) {
2202 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
2203 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
2205 //any peerid that is not ours gets added
2206 if(![[account.trust peerID] isEqualToString:peerID])
2207 [peerIDs addObject:peerID];
2209 CFReleaseNull(peers);
2211 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
2212 __block bool keyMatchesPeerID = false;
2214 //checks for full peer ids
2215 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
2216 //if key contains peerid of one active peer
2217 if([KVSKey containsString:PeerID]){
2218 secnotice("key-cleanup","key: %@", KVSKey);
2219 keyMatchesPeerID = true;
2222 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
2223 || [KVSKey hasPrefix:@"po"])
2224 [keysToRemove addObject:KVSKey];
2227 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
2228 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
2230 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
2232 //add last cleanup timestamp
2233 SOSAccountWriteLastCleanupTimestampToKVS(account);
2238 bool SOSAccountPopulateKVSWithBadKeys(SOSAccount* account, CFErrorRef* error) {
2240 NSMutableDictionary *testKeysAndValues = [NSMutableDictionary dictionary];
2241 [testKeysAndValues setObject:@"deadbeef" forKey:@"-ak|asdfjkl;asdfjk;"];
2242 [testKeysAndValues setObject:@"foobar" forKey:@"ak|asdfasdfasdf:qwerqwerqwer"];
2243 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"poak|asdfasdfasdfasdf"];
2244 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"po|asdfasdfasdfasdfasdfasdf"];
2245 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"k>KeyParm"];
2247 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2248 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2249 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2251 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2253 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2255 dispatch_semaphore_signal(waitSemaphore);
2258 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(testKeysAndValues), processQueue, replyBlock);
2259 dispatch_semaphore_wait(waitSemaphore, finishTime);
2264 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2265 SOSPeerInfoRef applicant = NULL;
2266 SOSAccountTrustClassic *trust = account.trust;
2267 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2268 if(!userKey) return false;
2269 if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
2271 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2273 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2280 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2282 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2283 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2284 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2285 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2287 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2290 if([parentUUID isEqualToString:viewUUID] || authoriative){
2292 /* check if we already have this entry */
2293 if ([seenUUID containsObject:viewUUID])
2296 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2297 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2302 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2304 NSMutableDictionary* strippedDown = [@{
2305 (id)kSecValueData : key,
2306 (id)kSecAttrServer : viewName,
2307 (id)kSecAttrAccount : viewUUID
2310 strippedDown[@"auth"] = @YES;
2312 [results addObject:strippedDown];
2313 [seenUUID addObject:viewUUID];
2319 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
2322 CKKSViewManager* manager = [CKKSViewManager manager];
2324 NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
2326 for (NSString *view in items) {
2327 NSString *uuid = items[view];
2328 NSDictionary *query = @{
2329 (id)kSecClass : (id)kSecClassInternetPassword,
2330 (id)kSecUseDataProtectionKeychain : @YES,
2331 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2332 (id)kSecAttrAccount : uuid,
2333 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2334 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2335 (id)kSecReturnAttributes: @YES,
2336 (id)kSecReturnData: @YES,
2338 CFTypeRef result = NULL;
2339 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2340 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
2342 CFReleaseNull(result);
2348 NSArray<NSDictionary *>*
2349 SOSAccountGetAllTLKs(void)
2351 CFTypeRef result = NULL;
2352 NSMutableArray<NSDictionary *>* results = [NSMutableArray array];
2353 NSMutableSet *seenUUID = [NSMutableSet set];
2355 // first use the TLK from the view manager
2356 AddViewManagerResults(results, seenUUID);
2358 //try to grab tlk-piggy items
2359 NSDictionary* query = @{
2360 (id)kSecClass : (id)kSecClassInternetPassword,
2361 (id)kSecUseDataProtectionKeychain : @YES,
2362 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2363 (id)kSecAttrDescription: @"tlk",
2364 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2365 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2366 (id)kSecReturnAttributes: @YES,
2367 (id)kSecReturnData: @YES,
2370 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2371 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2373 CFReleaseNull(result);
2375 //try to grab tlk-piggy items
2377 (id)kSecClass : (id)kSecClassInternetPassword,
2378 (id)kSecUseDataProtectionKeychain : @YES,
2379 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2380 (id)kSecAttrDescription: @"tlk-piggy",
2381 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2382 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2383 (id)kSecReturnAttributes: @YES,
2384 (id)kSecReturnData: @YES,
2387 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2388 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2390 CFReleaseNull(result);
2392 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2397 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2398 const uint8_t *der, uint8_t *der_end)
2400 if (type != kTLKUnknown) {
2401 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2402 piggy_encode_data(keychainData, der,
2403 piggy_encode_data(uuid, der,
2404 ccder_encode_uint64((uint64_t)type, der, der_end))));
2406 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2407 piggy_encode_data(keychainData, der,
2408 piggy_encode_data(uuid, der,
2409 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2413 static uint8_t* piggy_encode_data(NSData* data,
2414 const uint8_t *der, uint8_t *der_end)
2416 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2417 ccder_encode_body(data.length, data.bytes, der, der_end));
2421 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2423 name2type(NSString *view)
2425 if ([view isEqualToString:@"Manatee"])
2427 else if ([view isEqualToString:@"Engram"])
2429 else if ([view isEqualToString:@"AutoUnlock"])
2430 return kTLKAutoUnlock;
2431 if ([view isEqualToString:@"Health"])
2436 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2438 rank_type(NSString *view)
2440 if ([view isEqualToString:@"Manatee"])
2442 else if ([view isEqualToString:@"Engram"])
2444 else if ([view isEqualToString:@"AutoUnlock"])
2446 if ([view isEqualToString:@"Health"])
2452 parse_uuid(NSString *uuidString)
2454 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2456 [uuid getUUIDBytes:uuidblob];
2457 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2460 piggy_sizeof_data(NSData* data)
2462 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2465 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2466 if (type != kTLKUnknown) {
2467 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2468 piggy_sizeof_data(keychainData) +
2469 piggy_sizeof_data(uuid) +
2470 ccder_sizeof_uint64(type));
2472 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2473 piggy_sizeof_data(keychainData) +
2474 piggy_sizeof_data(uuid) +
2475 der_sizeof_string((__bridge CFStringRef)name, NULL));
2479 NSArray<NSDictionary*>*
2480 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2482 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2484 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2485 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2486 if (obj1[@"auth"] != NULL)
2488 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2489 if (obj2[@"auth"] != NULL)
2493 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2494 * since we are sorting backward, the Ascending/Descending looks wrong below.
2496 if (rank1 > rank2) {
2497 return NSOrderedAscending;
2498 } else if (rank1 < rank2) {
2499 return NSOrderedDescending;
2501 return NSOrderedSame;
2507 static NSArray<NSData *> *
2508 build_tlks(NSArray<NSDictionary*>* tlks)
2510 NSMutableArray *array = [NSMutableArray array];
2511 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2513 for (NSDictionary *item in sortedTLKs) {
2514 NSData* keychainData = item[(__bridge id)kSecValueData];
2515 NSString* name = item[(__bridge id)kSecAttrServer];
2516 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2517 NSData* uuid = parse_uuid(uuidString);
2519 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2521 unsigned char *der = [tlk mutableBytes];
2522 unsigned char *der_end = der + [tlk length];
2524 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2527 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2529 [array addObject:tlk];
2534 static NSArray<NSData *> *
2535 build_identities(NSArray<NSData *>* identities)
2537 NSMutableArray *array = [NSMutableArray array];
2538 for (NSData *item in identities) {
2539 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2541 unsigned char *der = [ident mutableBytes];
2542 unsigned char *der_end = der + [ident length];
2544 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2545 [array addObject:ident];
2552 static unsigned char *
2553 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2555 unsigned char *body_end = der_end;
2556 for (NSData *datum in data) {
2557 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2558 if (der_end == NULL)
2561 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2564 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2566 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2567 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2568 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2571 static NSData *encode_piggy(size_t IdentitiesBudget,
2573 NSArray<NSData*>* identities,
2574 NSArray<NSDictionary*>* tlks)
2576 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2577 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2578 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2579 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2580 size_t payloadSize = 0, identitiesSize = 0;
2581 NSMutableData *result = NULL;
2583 for (NSData *tlk in encodedTLKs) {
2584 if (TLKBudget - payloadSize < [tlk length])
2586 [budgetArray addObject:tlk];
2587 payloadSize += tlk.length;
2589 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2591 for (NSData *ident in encodedIdentities) {
2592 if (IdentitiesBudget - identitiesSize < [ident length])
2594 [identitiesArray addObject:ident];
2595 identitiesSize += ident.length;
2597 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2600 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2602 result = [NSMutableData dataWithLength:piggySize];
2604 unsigned char *der = [result mutableBytes];
2605 unsigned char *der_end = der + [result length];
2607 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2608 encode_data_array(identitiesArray, der,
2609 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2615 static const size_t SOSCCIdentitiesBudget = 120;
2616 static const size_t SOSCCTLKBudget = 500;
2619 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2621 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2624 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2626 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2628 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2629 if(SOSPeerInfoIsCloudIdentity(peer)) {
2630 CFArrayAppendValue(identities, peer);
2636 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) {
2638 NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array];
2639 NSArray<NSDictionary *>* tlks = nil;
2641 if (flags & kSOSInitialSyncFlagiCloudIdentity) {
2642 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2643 secnotice("piggy", "identities: %@", identities);
2645 CFIndex i, count = CFArrayGetCount(identities);
2646 for (i = 0; i < count; i++) {
2647 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2648 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2650 [encodedIdenities addObject:data];
2652 CFRelease(identities);
2655 if (flags & kSOSInitialSyncFlagTLKs) {
2656 tlks = SOSAccountGetAllTLKs();
2659 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2662 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
2663 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
2664 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
2665 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
2666 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
2667 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
2668 if(accountName == NULL) {
2669 accountName = CFSTR("Unavailable");
2671 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
2673 secnotice("circleOps",
2674 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
2675 operation, accountName, version, gencount, pkeyID, sigID, circleHash);
2676 CFReleaseNull(pkeyID);
2677 CFReleaseNull(sigID);
2678 CFReleaseNull(circleHash);
2681 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2682 SOSGenCountRef gencount = NULL;
2683 CFDataRef signature = NULL;
2684 SecKeyRef ourKey = NULL;
2686 CFDataRef pbblob = NULL;
2687 SOSCircleRef prunedCircle = NULL;
2689 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2691 SOSCCStatus circleStat = [account getCircleStatus:error];
2692 if(circleStat != kSOSCCInCircle) {
2693 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
2697 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2698 require_quiet(userKey, errOut);
2700 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2701 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
2702 secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
2705 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2706 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2707 require_quiet(ourKey, errOut);
2710 SOSCircleRef currentCircle = [account.trust getCircle:error];
2711 require_quiet(currentCircle, errOut);
2713 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2714 require_quiet(prunedCircle, errOut);
2715 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2717 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2719 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2720 require_quiet(signature, errOut);
2721 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
2722 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2725 CFReleaseNull(prunedCircle);
2726 CFReleaseNull(gencount);
2727 CFReleaseNull(signature);
2728 CFReleaseNull(ourKey);
2730 if(!pbblob && error != NULL) {
2731 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
2737 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2738 bool retval = false;
2739 SecKeyRef userKey = NULL;
2740 SOSAccountTrustClassic *trust = account.trust;
2741 SOSGenCountRef gencount = NULL;
2742 CFDataRef signature = NULL;
2743 SecKeyRef pubKey = NULL;
2744 bool setInitialSyncTimeoutToV0 = false;
2746 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
2748 if (!isData(joiningBlob)) {
2749 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
2753 userKey = SOSAccountGetPrivateCredential(account, error);
2755 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2759 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
2760 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2764 if(setInitialSyncTimeoutToV0){
2765 secnotice("circleOps", "setting flag in account for piggyback v0");
2766 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2768 secnotice("circleOps", "clearing flag in account for piggyback v0");
2769 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2771 SOSAccountInitializeInitialSync(account);
2773 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
2775 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2776 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2780 trust.fullPeerInfo, error);;
2783 CFReleaseNull(gencount);
2784 CFReleaseNull(pubKey);
2785 CFReleaseNull(signature);
2790 static char boolToChars(bool val, char truechar, char falsechar) {
2791 return val? truechar: falsechar;
2794 #define ACCOUNTLOGSTATE "accountLogState"
2795 void SOSAccountLogState(SOSAccount* account) {
2796 bool hasPubKey = account.accountKey != NULL;
2797 SOSAccountTrustClassic *trust = account.trust;
2798 bool pubTrusted = account.accountKeyIsTrusted;
2799 bool hasPriv = account.accountPrivateKey != NULL;
2800 SOSCCStatus stat = [account getCircleStatus:NULL];
2802 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2803 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2805 secnotice(ACCOUNTLOGSTATE, "Start");
2807 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2808 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2810 SOSAccountGetSOSCCStatusString(stat)
2812 CFReleaseNull(userPubKeyID);
2813 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2814 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2817 void SOSAccountLogViewState(SOSAccount* account) {
2818 bool isInCircle = [account.trust isInCircleOnly:NULL];
2819 require_quiet(isInCircle, imOut);
2820 SOSPeerInfoRef mpi = account.peerInfo;
2821 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2822 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2824 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2825 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2826 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2827 boolToChars(isInitialComplete, 'I', 'i'),
2828 boolToChars(isBackupComplete, 'B', 'b'),
2831 CFReleaseNull(views);
2832 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2833 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2834 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2836 CFReleaseNull(unsyncedViews);
2839 secnotice(ACCOUNTLOGSTATE, "Finish");
2845 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2846 if(!isString(serial)) return;
2847 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2848 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2849 [account.trust updateV2Dictionary:account v2:newv2dict];
2852 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2854 secnotice("otrtimer", "timer fired!");
2855 CFErrorRef error = NULL;
2856 SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
2858 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2859 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2860 if(SOSCoderIsCoderInAwaitingState(coder)){
2861 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2862 CFErrorRef localError = NULL;
2863 SOSCoderReset(coder);
2864 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2865 secerror("Attempt to recover coder failed to restart: %@", localError);
2868 secnotice("otrtimer", "coder restarted!");
2869 SOSEngineSetCodersNeedSaving(engine, true);
2870 SOSPeerSetMustSendMessage(peer, true);
2871 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2873 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2874 SOSPeerRemoveOTRTimerEntry(peer);
2875 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2876 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2879 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2884 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2886 CFReleaseNull(error);
2889 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2891 __block SOSAccount* account = txn.account;
2892 CFErrorRef error = NULL;
2894 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2895 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2897 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2898 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2899 CFErrorRef error = NULL;
2900 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2903 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2904 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2906 if(!sendResult || error){
2907 secnotice("ratelimit", "could not send message: %@", error);
2910 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2911 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2912 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2917 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2919 CFReleaseNull(error);
2928 OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup";
2929 OctagonFlag* SOSFlagTriggerRingUpdate = (OctagonFlag*)@"trigger_ring_update";
2931 OctagonState* SOSStateReady = (OctagonState*)@"ready";
2932 OctagonState* SOSStateError = (OctagonState*)@"error";
2933 OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup";
2934 OctagonState* SOSStatePerformRingUpdate = (OctagonState*)@"perform_ring_update";
2936 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void) {
2937 static NSDictionary<OctagonState*, NSNumber*>* map = nil;
2938 static dispatch_once_t onceToken;
2939 dispatch_once(&onceToken, ^{
2943 SOSStatePerformBackup: @2U,
2944 SOSStatePerformRingUpdate: @3U,
2950 static NSSet<OctagonFlag*>* SOSFlagsSet(void) {
2951 static NSSet<OctagonFlag*>* set = nil;
2952 static dispatch_once_t onceToken;
2953 dispatch_once(&onceToken, ^{
2954 set = [NSSet setWithArray:@[
2955 SOSFlagTriggerBackup,
2956 SOSFlagTriggerRingUpdate,
2964 + (NSURL *)urlForSOSAccountSettings {
2965 return (__bridge NSURL *)SecCopyURLForFileInKeychainDirectory(CFSTR("SOSAccountSettings.pb"));
2969 - (void)setupStateMachine {
2972 self.accountConfiguration = [[CKKSPBFileStorage alloc] initWithStoragePath:[[self class] urlForSOSAccountSettings]
2973 storageClass:[SOSAccountConfiguration class]];
2975 NSAssert(self.stateMachine == nil, @"cant bootstrap more the once");
2977 self.stateMachineQueue = dispatch_queue_create("SOS-statemachine", NULL);
2979 self.stateMachine = [[OctagonStateMachine alloc] initWithName:@"sosaccount"
2980 states:[NSSet setWithArray:[SOSStateMap() allKeys]]
2982 initialState:SOSStateReady
2983 queue:self.stateMachineQueue
2985 lockStateTracker:[CKKSLockStateTracker globalTracker]];
2988 self.performBackups = [[CKKSNearFutureScheduler alloc] initWithName:@"performBackups"
2989 initialDelay:5*NSEC_PER_SEC
2990 continuingDelay:30*NSEC_PER_SEC
2991 keepProcessAlive:YES
2992 dependencyDescriptionCode:CKKSResultDescriptionNone
2995 [self addBackupFlag];
2998 self.performRingUpdates = [[CKKSNearFutureScheduler alloc] initWithName:@"performRingUpdates"
2999 initialDelay:5*NSEC_PER_SEC
3000 expontialBackoff:2.0
3001 maximumDelay:15*60*NSEC_PER_SEC
3002 keepProcessAlive:YES
3003 dependencyDescriptionCode:CKKSResultDescriptionNone
3006 [self addRingUpdateFlag];
3009 SOSAccountConfiguration *conf = self.accountConfiguration.storage;
3011 if (conf.pendingBackupPeers.count) {
3012 [self addBackupFlag];
3014 if (conf.ringUpdateFlag) {
3015 [self addRingUpdateFlag];
3021 * Flag adding to state machine
3024 - (void)addBackupFlag {
3025 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup
3026 conditions:OctagonPendingConditionsDeviceUnlocked];
3027 [self.stateMachine handlePendingFlag:pendingFlag];
3030 - (void)addRingUpdateFlag {
3031 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerRingUpdate
3032 conditions:OctagonPendingConditionsDeviceUnlocked];
3033 [self.stateMachine handlePendingFlag:pendingFlag];
3036 //Mark: -- Set up state for state machine
3039 - (void)triggerBackupForPeers:(NSArray<NSString*>*)backupPeers
3041 NSMutableSet *pending = [NSMutableSet set];
3043 [pending addObjectsFromArray:backupPeers];
3047 dispatch_async(self.stateMachineQueue, ^{
3050 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
3052 if (storage.pendingBackupPeers) {
3053 [pending addObjectsFromArray:storage.pendingBackupPeers];
3055 storage.pendingBackupPeers = [[pending allObjects] mutableCopy];
3056 [self.accountConfiguration setStorage:storage];
3057 [self.performBackups trigger];
3058 secnotice("sos-sm", "trigger backup for peers: %@ at %@",
3059 backupPeers, self.performBackups.nextFireTime);
3063 - (void)triggerRingUpdate
3067 dispatch_async(self.stateMachineQueue, ^{
3069 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
3070 storage.ringUpdateFlag = YES;
3071 [self.accountConfiguration setStorage:storage];
3072 [self.performRingUpdates trigger];
3073 secnotice("sos-sm", "trigger ring update at %@",
3074 self.performRingUpdates.nextFireTime);
3078 //Mark: -- State machine and opertions
3080 - (OctagonStateTransitionOperation *)performBackup {
3083 return [OctagonStateTransitionOperation named:@"perform-backup-state"
3084 intending:SOSStateReady
3085 errorState:SOSStateError
3086 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) {
3088 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
3090 secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers);
3092 if (storage.pendingBackupPeers.count) {
3093 SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers);
3094 [storage clearPendingBackupPeers];
3096 [self.accountConfiguration setStorage:storage];
3098 op.nextState = SOSStateReady;
3102 - (OctagonStateTransitionOperation *)performRingUpdate {
3105 return [OctagonStateTransitionOperation named:@"perform-ring-update"
3106 intending:SOSStateReady
3107 errorState:SOSStateError
3108 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) {
3111 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
3112 storage.ringUpdateFlag = NO;
3113 [self.accountConfiguration setStorage:storage];
3115 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
3117 [self _onQueueRecordRetiredPeersInCircle];
3119 SOSAccountEnsureRecoveryRing(self);
3120 [self _onQueueEnsureInBackupRings];
3122 CFErrorRef localError = NULL;
3123 if(![self.circle_transport flushChanges:&localError]){
3124 secerror("flush circle failed %@", localError);
3126 CFReleaseNull(localError);
3128 if(!SecCKKSTestDisableSOS()) {
3129 SOSAccountNotifyEngines(self);
3133 op.nextState = SOSStateReady;
3139 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
3140 flags:(nonnull OctagonFlags *)flags
3141 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
3143 dispatch_assert_queue(self.stateMachineQueue);
3145 secnotice("sos-sm", "Entering state: %@ [flags: %@]", currentState, flags);
3147 if ([currentState isEqualToString:SOSStateReady]) {
3148 if([flags _onqueueContains:SOSFlagTriggerBackup]) {
3149 [flags _onqueueRemoveFlag:SOSFlagTriggerBackup];
3150 return [OctagonStateTransitionOperation named:@"perform-backup-flag"
3151 entering:SOSStatePerformBackup];
3154 if ([flags _onqueueContains:SOSFlagTriggerRingUpdate]) {
3155 [flags _onqueueRemoveFlag:SOSFlagTriggerRingUpdate];
3156 return [OctagonStateTransitionOperation named:@"perform-ring-update-flag"
3157 entering:SOSStatePerformRingUpdate];
3161 } else if ([currentState isEqualToString:SOSStateError]) {
3163 } else if ([currentState isEqualToString:SOSStatePerformRingUpdate]) {
3164 return [self performRingUpdate];
3166 } else if ([currentState isEqualToString:SOSStatePerformBackup]) {
3167 return [self performBackup];