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/SecFileLocations.h>
68 #include <os/activity.h>
69 #include <os/state_private.h>
71 #include <utilities/SecCoreCrypto.h>
73 #include <utilities/der_plist.h>
74 #include <utilities/der_plist_internal.h>
75 #include <corecrypto/ccder.h>
78 const CFStringRef kSOSAccountName = CFSTR("AccountName");
79 const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
80 const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
81 const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout");
82 const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
83 const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
84 const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary");
85 const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey");
86 const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing");
87 const CFStringRef kSOSAccountUUID = CFSTR("UUID");
88 const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters");
89 const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef>
90 const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef>
91 const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount");
92 const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion");
93 NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry";
94 NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount";
95 NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup";
96 NSString* const kSOSIdentityStatusCompleteIdentity = @"completeIdentity";
97 NSString* const kSOSIdentityStatusKeyOnly = @"keyOnly";
98 NSString* const kSOSIdentityStatusPeerOnly = @"peerOnly";
101 const uint64_t max_packet_size_over_idms = 500;
104 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
106 #define DATE_LENGTH 25
107 const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
110 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void);
112 @interface SOSAccount () <OctagonStateMachineEngine>
113 @property dispatch_queue_t stateMachineQueue;
114 @property (readwrite) OctagonStateMachine* stateMachine;
116 @property (readwrite) CKKSPBFileStorage<SOSAccountConfiguration*>* accountConfiguration;
118 @property CKKSNearFutureScheduler *performBackups;
119 @property CKKSNearFutureScheduler *performRingUpdates;
123 @implementation SOSAccount
127 CFReleaseNull(self->_accountKey);
128 CFReleaseNull(self->_accountPrivateKey);
129 CFReleaseNull(self->_previousAccountKey);
130 CFReleaseNull(self->_peerPublicKey);
131 CFReleaseNull(self->_octagonSigningFullKeyRef);
132 CFReleaseNull(self->_octagonEncryptionFullKeyRef);
134 [self.performBackups cancel];
135 [self.performRingUpdates cancel];
136 [self.stateMachine haltOperation];
141 @synthesize accountKey = _accountKey;
143 - (void) setAccountKey: (SecKeyRef) key {
144 CFRetainAssign(self->_accountKey, key);
147 @synthesize accountPrivateKey = _accountPrivateKey;
149 - (void) setAccountPrivateKey: (SecKeyRef) key {
150 CFRetainAssign(self->_accountPrivateKey, key);
153 @synthesize previousAccountKey = _previousAccountKey;
155 - (void) setPreviousAccountKey: (SecKeyRef) key {
156 CFRetainAssign(self->_previousAccountKey, key);
159 @synthesize peerPublicKey = _peerPublicKey;
161 - (void) setPeerPublicKey: (SecKeyRef) key {
162 CFRetainAssign(self->_peerPublicKey, key);
165 // Syntactic sugar getters
167 - (BOOL) hasPeerInfo {
168 return self.fullPeerInfo != nil;
171 - (SOSPeerInfoRef) peerInfo {
172 return self.trust.peerInfo;
175 - (SOSFullPeerInfoRef) fullPeerInfo {
176 return self.trust.fullPeerInfo;
179 - (NSString*) peerID {
180 return self.trust.peerID;
183 -(bool) ensureFactoryCircles
185 if (self.factory == nil){
189 NSString* circle_name = CFBridgingRelease(SOSDataSourceFactoryCopyName(self.factory));
194 CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL));
196 return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL);
199 -(void)ensureOctagonPeerKeys
202 CKKSLockStateTracker *tracker = [CKKSViewManager manager].lockStateTracker;
203 if (tracker && tracker.isLocked == false) {
204 [self.trust ensureOctagonPeerKeys:self.circle_transport];
209 -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f
211 if ((self = [super init])) {
212 self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
214 self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)];
216 SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
219 self.factory = f; // We adopt the factory. kthanksbai.
221 self.isListeningForSync = false;
223 self.accountPrivateKey = NULL;
224 self._password_tmp = NULL;
225 self.user_private_timer = NULL;
226 self.lock_notification_token = NOTIFY_TOKEN_INVALID;
228 self.change_blocks = [NSMutableArray array];
230 self.key_transport = nil;
231 self.circle_transport = NULL;
232 self.ck_storage = nil;
233 self.kvs_message_transport = nil;
235 self.circle_rings_retirements_need_attention = false;
236 self.engine_peer_state_needs_repair = false;
237 self.key_interests_need_updating = false;
238 self.need_backup_peers_created_after_backup_key_set = false;
240 self.backup_key =nil;
243 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
244 self.accountKeyIsTrusted = false;
245 self.accountKeyDerivationParameters = NULL;
246 self.accountKey = NULL;
247 self.previousAccountKey = NULL;
248 self.peerPublicKey = NULL;
250 self.saveBlock = nil;
252 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
254 [self ensureFactoryCircles];
255 SOSAccountEnsureUUID(self);
256 self.accountIsChanging = false;
259 [self setupStateMachine];
265 - (void)startStateMachine
268 [self.stateMachine startOperation];
272 -(BOOL)isEqual:(id) object
274 if(![object isKindOfClass:[SOSAccount class]])
277 SOSAccount* left = object;
278 return ([self.gestalt isEqual: left.gestalt] &&
279 CFEqualSafe(self.trust.trustedCircle, left.trust.trustedCircle) &&
280 [self.trust.expansion isEqual: left.trust.expansion] &&
281 CFEqualSafe(self.trust.fullPeerInfo, left.trust.fullPeerInfo));
285 - (void)userPublicKey:(void ((^))(BOOL trusted, NSData *spki, NSError *error))reply
287 dispatch_async(self.queue, ^{
288 if (!self.accountKeyIsTrusted || self.accountKey == NULL) {
289 NSDictionary *userinfo = @{
290 (id)kCFErrorDescriptionKey : @"User public key not trusted",
292 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
296 NSData *data = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(self.accountKey));
298 NSDictionary *userinfo = @{
299 (id)kCFErrorDescriptionKey : @"User public not available",
301 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
304 reply(self.accountKeyIsTrusted, data, NULL);
308 - (void)kvsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply
310 /* Need to collect performance counters from all subsystems, not just circle transport, don't have counters yet though */
311 SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error)
313 reply((__bridge NSDictionary *)returnedValues);
317 - (void)rateLimitingPerformanceCounters:(void(^)(NSDictionary <NSString *, NSString *> *))reply
319 CFErrorRef error = NULL;
320 CFDictionaryRef rateLimitingCounters = (CFDictionaryRef)SOSAccountGetValue(self, kSOSRateLimitingCounters, &error);
321 reply((__bridge NSDictionary*)rateLimitingCounters ? (__bridge NSDictionary*)rateLimitingCounters : [NSDictionary dictionary]);
324 - (void)stashedCredentialPublicKey:(void(^)(NSData *, NSError *error))reply
326 dispatch_async(self.queue, ^{
327 CFErrorRef error = NULL;
329 SecKeyRef user_private = SOSAccountCopyStashedUserPrivateKey(self, &error);
330 if (user_private == NULL) {
331 reply(NULL, (__bridge NSError *)error);
332 CFReleaseNull(error);
336 NSData *publicKey = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(user_private));
337 CFReleaseNull(user_private);
338 reply(publicKey, NULL);
342 - (void)assertStashedAccountCredential:(void(^)(BOOL result, NSError *error))complete
344 dispatch_async(self.queue, ^{
345 CFErrorRef error = NULL;
346 bool result = SOSAccountAssertStashedAccountCredential(self, &error);
347 complete(result, (__bridge NSError *)error);
348 CFReleaseNull(error);
352 static bool SyncKVSAndWait(CFErrorRef *error) {
353 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
355 __block bool success = false;
357 secnoticeq("fresh", "EFP calling SOSCloudKeychainSynchronizeAndWait");
359 os_activity_initiate("CloudCircle EFRESH", OS_ACTIVITY_FLAG_DEFAULT, ^(void) {
360 SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) {
361 secnotice("fresh", "EFP returned, callback error: %@", sync_error);
363 success = (sync_error == NULL);
365 CFRetainAssign(*error, sync_error);
368 dispatch_semaphore_signal(wait_for);
372 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
373 secnotice("fresh", "EFP complete: %s %@", success ? "success" : "failure", error ? *error : NULL);
379 static bool Flush(CFErrorRef *error) {
380 __block bool success = false;
382 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
383 secnotice("flush", "Starting");
385 SOSCloudKeychainFlush(dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
386 success = (sync_error == NULL);
388 CFRetainAssign(*error, sync_error);
391 dispatch_semaphore_signal(wait_for);
394 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
396 secnotice("flush", "Returned %s", success? "Success": "Failure");
401 - (bool)syncWaitAndFlush:(CFErrorRef *)error
403 secnotice("pairing", "sync and wait starting");
405 if (!SyncKVSAndWait(error)) {
406 secnotice("pairing", "failed sync and wait: %@", error ? *error : NULL);
410 secnotice("pairing", "failed flush: %@", error ? *error : NULL);
413 secnotice("pairing", "finished sync and wait");
417 - (void)validatedStashedAccountCredential:(void(^)(NSData *credential, NSError *error))complete
419 CFErrorRef syncerror = NULL;
421 if (![self syncWaitAndFlush:&syncerror]) {
422 complete(NULL, (__bridge NSError *)syncerror);
423 CFReleaseNull(syncerror);
427 dispatch_async(self.queue, ^{
428 CFErrorRef error = NULL;
429 SecKeyRef key = NULL;
430 key = SOSAccountCopyStashedUserPrivateKey(self, &error);
432 secnotice("pairing", "no stashed credential");
433 complete(NULL, (__bridge NSError *)error);
434 CFReleaseNull(error);
438 SecKeyRef publicKey = SecKeyCopyPublicKey(key);
440 secnotice("pairing", "returning stash credential: %@", publicKey);
441 CFReleaseNull(publicKey);
444 NSData *keydata = CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error));
446 complete(keydata, (__bridge NSError *)error);
447 CFReleaseNull(error);
450 - (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete
453 dispatch_sync(SOSCCCredentialQueue(), ^{
454 CFErrorRef syncerror = NULL;
456 if (![self syncWaitAndFlush:&syncerror]) {
457 complete(NULL, (__bridge NSError *)syncerror);
458 CFReleaseNull(syncerror);
460 __block bool success = false;
461 sleep(1); // make up for keygen time in password based version - let syncdefaults catch up
463 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
464 SecKeyRef accountPrivateKey = NULL;
465 CFErrorRef error = NULL;
466 NSDictionary *attributes = @{
467 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
468 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
471 accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error);
472 if (accountPrivateKey == NULL) {
473 complete(false, (__bridge NSError *)error);
474 secnotice("pairing", "SecKeyCreateWithData failed: %@", error);
475 CFReleaseNull(error);
479 if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) {
480 CFReleaseNull(accountPrivateKey);
481 complete(false, (__bridge NSError *)error);
482 secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error);
483 CFReleaseNull(error);
488 secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded");
490 CFReleaseNull(accountPrivateKey);
491 complete(true, NULL);
494 // This makes getting the private key the same as Asserting the password - we read all the other things
495 // that we just expressed interest in.
498 CFErrorRef localError = NULL;
499 if (!Flush(&localError)) {
500 // we're still setup with the private key - just report this.
501 secnotice("pairing", "failed final flush: %@", localError);
503 CFReleaseNull(localError);
509 - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete
511 __block CFErrorRef localError = NULL;
512 __block NSData *applicationBlob = NULL;
513 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
514 SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError);
516 applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError));
517 CFReleaseNull(application);
520 complete(applicationBlob, (__bridge NSError *)localError);
521 CFReleaseNull(localError);
524 - (void)circleHash:(void (^)(NSString *, NSError *))complete
526 __block CFErrorRef localError = NULL;
527 __block NSString *circleHash = NULL;
528 SecAKSDoWithUserBagLockAssertion(&localError, ^{
529 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
530 circleHash = CFBridgingRelease(SOSCircleCopyHashString(txn.account.trust.trustedCircle));
533 complete(circleHash, (__bridge NSError *)localError);
534 CFReleaseNull(localError);
539 #if TARGET_OS_OSX || TARGET_OS_IOS
542 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
543 OTManager *otm = [OTManager manager];
544 SOSAccountGhostBustingOptions options = ([otm ghostbustByMidEnabled] == YES ? SOSGhostBustByMID: 0) |
545 ([otm ghostbustBySerialEnabled] == YES ? SOSGhostBustBySerialNumber : 0) |
546 ([otm ghostbustByAgeEnabled] == YES ? SOSGhostBustSerialByAge: 0);
551 #define GHOSTBUSTDATE @"ghostbustdate"
553 - (NSDate *) ghostBustGetDate {
554 if(! self.settings) {
555 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
557 return [self.settings valueForKey:GHOSTBUSTDATE];
560 - (bool) ghostBustCheckDate {
561 NSDate *ghostBustDate = [self ghostBustGetDate];
562 if(ghostBustDate && ([ghostBustDate timeIntervalSinceNow] <= 0)) return true;
566 - (void) ghostBustFollowup {
567 if(! self.settings) {
568 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
570 NSTimeInterval earliestGB = 60*60*24*3; // wait at least 3 days
571 NSTimeInterval latestGB = 60*60*24*7; // wait at most 7 days
572 NSDate *ghostBustDate = SOSCreateRandomDateBetweenNowPlus(earliestGB, latestGB);
573 [self.settings setValue:ghostBustDate forKey:GHOSTBUSTDATE];
576 // GhostBusting initial scheduling
577 - (void)ghostBustSchedule {
578 NSDate *ghostBustDate = [self ghostBustGetDate];
580 [self ghostBustFollowup];
584 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
585 __block bool result = false;
586 __block CFErrorRef localError = NULL;
588 if([SOSAuthKitHelpers accountIsHSA2]) {
589 [SOSAuthKitHelpers activeMIDs:^(NSSet <SOSTrustedDeviceAttributes *> * _Nullable activeMIDs, NSError * _Nullable error) {
590 SOSAuthKitHelpers *akh = [[SOSAuthKitHelpers alloc] initWithActiveMIDS:activeMIDs];
592 SecAKSDoWithUserBagLockAssertion(&localError, ^{
593 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
594 result = SOSAccountGhostBustCircle(txn.account, akh, options, 1);
595 [self ghostBustFollowup];
599 complete(result, NULL);
602 complete(false, NULL);
606 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
607 NSDate *ghostBustDate = [self ghostBustGetDate];
608 if(([ghostBustDate timeIntervalSinceNow] <= 0)) {
610 [self ghostBust: options complete: complete];
612 complete(false, nil);
617 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
618 // if no particular options are presented use the ramp options.
619 // 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.
621 options = [SOSAccount ghostBustGetRampSettings];
623 [self ghostBust: options complete: complete];
626 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
627 // 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.
628 NSMutableDictionary *gbInfoDictionary = [NSMutableDictionary new];
629 SOSAccountGhostBustingOptions options = [SOSAccount ghostBustGetRampSettings];
630 NSString *ghostBustDate = [[self ghostBustGetDate] description];
632 gbInfoDictionary[@"SOSGhostBustBySerialNumber"] = (SOSGhostBustBySerialNumber & options) ? @"ON": @"OFF";
633 gbInfoDictionary[@"SOSGhostBustByMID"] = (SOSGhostBustByMID & options) ? @"ON": @"OFF";
634 gbInfoDictionary[@"SOSGhostBustSerialByAge"] = (SOSGhostBustSerialByAge & options) ? @"ON": @"OFF";
635 gbInfoDictionary[@"SOSAccountGhostBustDate"] = ghostBustDate;
638 NSData *json = [NSJSONSerialization dataWithJSONObject:gbInfoDictionary
639 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
642 secnotice("ghostbust", "Error during ghostBustInfo JSONification: %@", err.localizedDescription);
647 - (void) iCloudIdentityStatus_internal: (void(^)(NSDictionary *tableSpid, NSError *error))complete {
648 CFErrorRef localError = NULL;
649 NSMutableDictionary *tableSPID = [NSMutableDictionary new];
651 if(![self isInCircle: &localError]) {
652 complete(tableSPID, (__bridge NSError *)localError);
656 // Make set of SPIDs for iCloud Identity PeerInfos
657 NSMutableSet<NSString*> *peerInfoSPIDs = [[NSMutableSet alloc] init];
658 SOSCircleForEachiCloudIdentityPeer(self.trust.trustedCircle , ^(SOSPeerInfoRef peer) {
659 NSString *peerID = CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, SOSPeerInfoGetPeerID(peer)));
661 [ peerInfoSPIDs addObject:peerID];
665 // Make set of SPIDs for iCloud Identity Private Keys
666 NSMutableSet<NSString*> *privateKeySPIDs = [[NSMutableSet alloc] init];
667 SOSiCloudIdentityPrivateKeyForEach(^(SecKeyRef privKey) {
668 CFErrorRef localError = NULL;
669 CFStringRef keyID = SOSCopyIDOfKey(privKey, &localError);
671 NSString *peerID = CFBridgingRelease(keyID);
672 [ privateKeySPIDs addObject:peerID];
674 secnotice("iCloudIdentity", "couldn't make ID from key (%@)", localError);
676 CFReleaseNull(localError);
679 NSMutableSet<NSString*> *completeIdentity = [peerInfoSPIDs mutableCopy];
680 if([peerInfoSPIDs count] > 0 && [privateKeySPIDs count] > 0) {
681 [ completeIdentity intersectSet:privateKeySPIDs];
683 completeIdentity = nil;
686 NSMutableSet<NSString*> *keyOnly = [privateKeySPIDs mutableCopy];
687 if([peerInfoSPIDs count] > 0 && [keyOnly count] > 0) {
688 [ keyOnly minusSet: peerInfoSPIDs ];
691 NSMutableSet<NSString*> *peerOnly = [peerInfoSPIDs mutableCopy];
692 if([peerOnly count] > 0 && [privateKeySPIDs count] > 0) {
693 [ peerOnly minusSet: privateKeySPIDs ];
696 tableSPID[kSOSIdentityStatusCompleteIdentity] = [completeIdentity allObjects];
697 tableSPID[kSOSIdentityStatusKeyOnly] = [keyOnly allObjects];
698 tableSPID[kSOSIdentityStatusPeerOnly] = [peerOnly allObjects];
700 complete(tableSPID, nil);
703 - (void)iCloudIdentityStatus: (void (^)(NSData *json, NSError *error))complete {
704 [self iCloudIdentityStatus_internal:^(NSDictionary *tableSpid, NSError *error) {
706 NSData *json = [NSJSONSerialization dataWithJSONObject:tableSpid
707 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
710 secnotice("iCloudIdentity", "Error during iCloudIdentityStatus JSONification: %@", err.localizedDescription);
718 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
722 - (NSDate *) ghostBustGetDate {
726 - (void) ghostBustFollowup {
729 - (void)ghostBustSchedule {
732 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
733 complete(false, NULL);
736 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
737 complete(false, NULL);
740 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
741 complete(false, nil);
744 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
748 - (bool) ghostBustCheckDate {
752 - (void)iCloudIdentityStatus:(void (^)(NSData *, NSError *))complete {
757 - (void)iCloudIdentityStatus_internal:(void (^)(NSDictionary *, NSError *))complete {
761 #endif // !(TARGET_OS_OSX || TARGET_OS_IOS)
763 - (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete
765 __block CFErrorRef localError = NULL;
766 __block NSData *blob = NULL;
767 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant);
769 complete(NULL, (__bridge NSError *)localError);
770 CFReleaseNull(localError);
774 SecAKSDoWithUserBagLockAssertionSoftly(^{
775 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
776 blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError));
782 complete(blob, (__bridge NSError *)localError);
783 CFReleaseNull(localError);
786 - (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete
788 __block CFErrorRef localError = NULL;
789 __block bool res = false;
791 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
792 res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError);
795 complete(res, (__bridge NSError *)localError);
796 CFReleaseNull(localError);
799 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
801 CFErrorRef error = NULL;
802 uint32_t isflags = 0;
804 if (flags & SOSControlInitialSyncFlagTLK)
805 isflags |= SecServerInitialSyncCredentialFlagTLK;
806 if (flags & SOSControlInitialSyncFlagPCS)
807 isflags |= SecServerInitialSyncCredentialFlagPCS;
808 if (flags & SOSControlInitialSyncFlagPCSNonCurrent)
809 isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent;
810 if (flags & SOSControlInitialSyncFlagBluetoothMigration)
811 isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration;
814 NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error));
815 complete(array, (__bridge NSError *)error);
816 CFReleaseNull(error);
819 - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete
821 CFErrorRef error = NULL;
822 bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error);
823 complete(res, (__bridge NSError *)error);
824 CFReleaseNull(error);
827 - (void)rpcTriggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
829 __block CFErrorRef localError = NULL;
830 __block bool res = false;
832 secnotice("sync", "trigger a forced sync for %@", peers);
834 SecAKSDoWithUserBagLockAssertion(&localError, ^{
835 [self performTransaction:^(SOSAccountTransaction *txn) {
837 NSSet *peersSet = [NSSet setWithArray:peers];
838 CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError);
839 if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) {
842 CFReleaseNull(handledPeers);
844 res = SOSAccountRequestSyncWithAllPeers(txn, &localError);
848 complete(res, (__bridge NSError *)localError);
849 CFReleaseNull(localError);
852 - (void)rpcTriggerBackup:(NSArray<NSString *>* _Nullable)backupPeers complete:(void (^)(NSError *error))complete
854 __block CFErrorRef localError = NULL;
856 if (backupPeers.count == 0) {
857 SOSEngineRef engine = (SOSEngineRef) [self.kvs_message_transport SOSTransportMessageGetEngine];
858 backupPeers = CFBridgingRelease(SOSEngineCopyBackupPeerNames(engine, &localError));
862 [self triggerBackupForPeers:backupPeers];
865 complete((__bridge NSError *)localError);
866 CFReleaseNull(localError);
869 - (void)rpcTriggerRingUpdate:(void (^)(NSError *error))complete
872 [self triggerRingUpdate];
877 - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete
879 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
880 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
882 NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters];
883 complete(parameters, nil);
886 complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
890 - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete
892 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
893 NSError* error = nil;
894 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
896 [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error];
900 complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
908 - (void) flattenToSaveBlock {
909 if (self.saveBlock) {
910 NSError* error = nil;
911 NSData* saveData = [self encodedData:&error];
913 (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error);
917 CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) {
918 return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt);
921 CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) {
922 CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL);
923 return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict);
926 static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){
927 SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
928 //send new DSID over account changed
929 [account.circle_transport kvsSendOfficialDSID:dsid err:NULL];
933 void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) {
934 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
935 if(accountDSID == NULL) {
936 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
938 SOSAccountUpdateDSID(account, dsid);
939 } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
940 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
942 //DSID has changed, blast the account!
943 SOSAccountSetToNew(account);
945 //update DSID to the new DSID
946 SOSAccountUpdateDSID(account, dsid);
948 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
952 SecKeyRef SOSAccountCopyDevicePrivateKey(SOSAccount* account, CFErrorRef *error) {
953 if(account.peerPublicKey) {
954 return SecKeyCopyMatchingPrivateKey(account.peerPublicKey, error);
959 SecKeyRef SOSAccountCopyDevicePublicKey(SOSAccount* account, CFErrorRef *error) {
960 return SecKeyCopyPublicKey(account.peerPublicKey);
964 void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews)
966 [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews];
967 [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews];
970 #pragma clang diagnostic push
971 #pragma clang diagnostic ignored "-Wunused-value"
972 SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) {
973 SOSViewResultCode retval = kSOSCCGeneralViewError;
974 // The V0 view switches on and off all on it's own, we allow people the delusion
975 // of control and status if it's what we're stuck at., otherwise error.
976 if (SOSAccountSyncingV0(account)) {
977 require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
978 retval = kSOSCCViewMember;
980 require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
981 retval = kSOSCCViewNotMember;
986 #pragma clang diagnostic pop
988 SOSAccount* SOSAccountCreate(CFAllocatorRef allocator,
989 CFDictionaryRef gestalt,
990 SOSDataSourceFactoryRef factory) {
992 SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory];
993 dispatch_sync(a.queue, ^{
994 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate");
995 a.key_interests_need_updating = true;
1001 static OSStatus do_delete(CFDictionaryRef query) {
1004 result = SecItemDelete(query);
1006 secerror("SecItemDelete: %d", (int)result);
1012 do_keychain_delete_aks_bags()
1015 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
1016 kSecClass, kSecClassGenericPassword,
1017 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
1018 kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
1019 kSecAttrService, CFSTR("SecureBackupService"),
1020 kSecAttrSynchronizable, kCFBooleanTrue,
1021 kSecUseTombstones, kCFBooleanFalse,
1024 result = do_delete(item);
1025 CFReleaseSafe(item);
1031 do_keychain_delete_identities()
1034 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
1035 kSecClass, kSecClassKey,
1036 kSecAttrSynchronizable, kCFBooleanTrue,
1037 kSecUseTombstones, kCFBooleanFalse,
1038 kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
1041 result = do_delete(item);
1042 CFReleaseSafe(item);
1048 do_keychain_delete_lakitu()
1051 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
1052 kSecClass, kSecClassGenericPassword,
1053 kSecAttrSynchronizable, kCFBooleanTrue,
1054 kSecUseTombstones, kCFBooleanFalse,
1055 kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
1056 kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
1057 kSecAttrService, CFSTR("EscrowService"),
1060 result = do_delete(item);
1061 CFReleaseSafe(item);
1067 do_keychain_delete_sbd()
1070 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
1071 kSecClass, kSecClassGenericPassword,
1072 kSecAttrSynchronizable, kCFBooleanTrue,
1073 kSecUseTombstones, kCFBooleanFalse,
1074 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
1077 result = do_delete(item);
1078 CFReleaseSafe(item);
1083 void SOSAccountSetToNew(SOSAccount* a)
1085 secnotice("accountChange", "Setting Account to New");
1088 /* remove all syncable items */
1089 result = do_keychain_delete_aks_bags(); (void) result;
1090 secdebug("set to new", "result for deleting aks bags: %d", result);
1092 result = do_keychain_delete_identities(); (void) result;
1093 secdebug("set to new", "result for deleting identities: %d", result);
1095 result = do_keychain_delete_lakitu(); (void) result;
1096 secdebug("set to new", "result for deleting lakitu: %d", result);
1098 result = do_keychain_delete_sbd(); (void) result;
1099 secdebug("set to new", "result for deleting sbd: %d", result);
1102 if (a.user_private_timer) {
1103 dispatch_source_cancel(a.user_private_timer);
1104 a.user_private_timer = NULL;
1105 xpc_transaction_end();
1108 if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) {
1109 notify_cancel(a.lock_notification_token);
1110 a.lock_notification_token = NOTIFY_TOKEN_INVALID;
1115 // Live Notification
1117 // update_interest_block;
1119 SOSUnregisterTransportKeyParameter(a.key_transport);
1120 SOSUnregisterTransportMessage(a.kvs_message_transport);
1121 SOSUnregisterTransportCircle(a.circle_transport);
1123 a.circle_transport = NULL;
1124 a.kvs_message_transport = nil;
1125 a._password_tmp = nil;
1126 a.circle_rings_retirements_need_attention = true;
1127 a.engine_peer_state_needs_repair = true;
1128 a.key_interests_need_updating = true;
1129 a.need_backup_peers_created_after_backup_key_set = true;
1131 a.accountKeyIsTrusted = false;
1132 a.accountKeyDerivationParameters = nil;
1133 a.accountPrivateKey = NULL;
1134 a.accountKey = NULL;
1135 a.previousAccountKey = NULL;
1136 a.peerPublicKey = NULL;
1138 a.notifyCircleChangeOnExit = true;
1139 a.notifyViewChangeOnExit = true;
1140 a.notifyBackupOnExit = true;
1142 a.octagonSigningFullKeyRef = NULL;
1143 a.octagonEncryptionFullKeyRef = NULL;
1146 // setting a new trust object resets all the rings from this peer's point of view - they're in the SOSAccountTrustClassic dictionary
1147 a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
1148 [a ensureFactoryCircles];
1150 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
1151 SOSAccountEnsureUUID(a);
1152 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew");
1153 a.key_interests_need_updating = true;
1156 dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) {
1157 return account.queue;
1160 void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){
1161 account.accountKeyIsTrusted = true;
1164 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
1166 SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error];
1167 if (!SOSAccountHasPublicKey(self, error)) {
1168 if(circleStatus == kSOSCCInCircle) {
1170 CFReleaseNull(*error);
1171 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);
1174 circleStatus = kSOSCCError;
1176 return circleStatus;
1179 -(bool) isInCircle:(CFErrorRef *)error
1181 SOSCCStatus result = [self getCircleStatus:error];
1182 if (result != kSOSCCInCircle) {
1183 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
1190 bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) {
1192 SOSAccountTrustClassic *trust = account.trust;
1193 NSMutableSet* retirees = trust.retirees;
1194 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
1195 CFSetSetValue((__bridge CFMutableSetRef) retirees, peer);
1196 CFErrorRef cleanupError = NULL;
1197 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) {
1198 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
1200 CFReleaseSafe(cleanupError);
1205 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) {
1206 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
1207 SOSPeerInfoRef me = account.peerInfo;
1208 bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
1210 SOSAccountTrustClassic *trust = account.trust;
1211 NSMutableSet* retirees = trust.retirees;
1213 if(!new_circle) return NULL;
1214 __block bool workDone = false;
1216 CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) {
1217 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
1218 if (isSOSPeerInfo(pi)) {
1219 SOSCircleUpdatePeerInfo(new_circle, pi);
1225 if(workDone && SOSCircleCountPeers(new_circle) == 0) {
1226 SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
1230 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
1231 if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) ||
1232 ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){
1233 CFReleaseNull(new_circle);
1236 account.notifyBackupOnExit = true;
1238 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
1239 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
1240 // we have a chance to set things right with a SetCreds/Join sequence. This will cause
1241 // handleUpdateCircle to return false.
1242 CFReleaseNull(new_circle);
1246 // This case is when we aren't an applicant and the circle is retirement-empty.
1247 secnotice("circleOps", "Reset to empty with last retirement");
1248 SOSCircleResetToEmpty(new_circle, NULL);
1256 // MARK: Circle Membership change notificaion
1259 void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1260 SOSAccountCircleMembershipChangeBlock copy = changeBlock;
1261 [a.change_blocks addObject:copy];
1264 void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1265 [a.change_blocks removeObject:changeBlock];
1268 void SOSAccountPurgeIdentity(SOSAccount* account) {
1269 SOSAccountTrustClassic *trust = account.trust;
1270 [trust purgeIdentity];
1273 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) {
1274 SOSAccountTrustClassic *trust = account.trust;
1275 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1276 NSMutableSet* retirees = trust.retirees;
1278 NSError* localError = nil;
1279 SOSFullPeerInfoRef fpi = identity;
1280 if(!fpi) return false;
1282 CFErrorRef retiredError = NULL;
1284 bool retval = false;
1286 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
1288 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
1290 *error = retiredError;
1292 CFReleaseNull(retiredError);
1297 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1299 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1300 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1301 // Remove our application if we have one.
1302 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1303 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1304 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1305 CFErrorRef cleanupError = NULL;
1306 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1307 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1309 CFReleaseSafe(cleanupError);
1313 // Store the retirement record locally.
1314 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1316 trust.retirees = retirees;
1318 // Write retirement to Transport
1319 CFErrorRef postError = NULL;
1320 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1322 secwarning("Couldn't post retirement (%@)", postError);
1327 if(![account.circle_transport flushChanges:&postError]){
1328 secwarning("Couldn't flush retirement data (%@)", postError);
1331 CFReleaseNull(postError);
1334 SOSAccountPurgeIdentity(account);
1338 CFReleaseNull(retire_peer);
1342 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1343 bool result = false;
1344 if (account.circle_transport) {
1345 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1351 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1352 local value that has been overwritten by a distant value. If there is no
1353 conflict between the local and the distant values when doing the initial
1354 sync (e.g. if the cloud has no data stored or the client has not stored
1355 any data yet), you'll never see that notification.
1357 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1358 with server but initial round trip with server does not imply
1359 NSUbiquitousKeyValueStoreInitialSyncChange.
1364 // MARK: Status summary
1368 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1370 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1371 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1372 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1373 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1374 case kSOSCCError: return CFSTR("kSOSCCError");
1376 return CFSTR("kSOSCCError");
1378 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1379 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1380 return kSOSCCInCircle;
1381 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1382 return kSOSCCInCircle;
1383 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1384 return kSOSCCNotInCircle;
1385 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1386 return kSOSCCRequestPending;
1387 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1388 return kSOSCCCircleAbsent;
1389 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1396 // MARK: Account Reset Circles
1399 // This needs to be called within a [trust modifyCircle()] block
1402 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1403 bool retval = false;
1405 SOSAccountTrustClassic *trust = account.trust;
1406 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1408 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1410 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1411 if(SOSPeerInfoIsCloudIdentity(peer)) {
1412 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1414 CFSetAddValue(iCloud2Remove, peer);
1416 CFReleaseNull(icfpi);
1420 if(CFSetGetCount(iCloud2Remove) > 0) {
1422 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1424 CFReleaseNull(iCloud2Remove);
1429 // MARK: start backups
1433 - (bool)_onQueueEnsureInBackupRings {
1434 __block bool result = false;
1435 __block CFErrorRef error = NULL;
1436 secnotice("backup", "Ensuring in rings");
1438 dispatch_assert_queue(self.queue);
1440 if(!self.backup_key){
1444 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)self.backup_key, &error)){
1445 secnotice("backupkey", "account backup key isn't valid: %@", error);
1446 self.backup_key = nil;
1447 CFReleaseNull(error);
1451 NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(self.peerInfo);
1452 if(![peerBackupKey isEqual:self.backup_key]) {
1453 result = SOSAccountUpdatePeerInfo(self, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1454 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(self.backup_key), error);
1457 secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error);
1458 CFReleaseNull(error);
1463 // It's a good key, we're going with it. Stop backing up the old way.
1464 CFErrorRef localError = NULL;
1465 if (!SOSDeleteV0Keybag(&localError)) {
1466 secerror("Failed to delete v0 keybag: %@", localError);
1468 CFReleaseNull(localError);
1470 // Setup backups the new way.
1471 SOSAccountForEachBackupView(self, ^(const void *value) {
1472 CFStringRef viewName = asString(value, NULL);
1473 bool resetRing = SOSAccountValidateBackupRingForView(self, viewName, NULL);
1475 SOSAccountUpdateBackupRing(self, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
1476 SOSRingRef newRing = SOSAccountCreateBackupRingForView(self, viewName, error);
1483 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1485 CFReleaseNull(error);
1490 // MARK: Recovery Public Key Functions
1493 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1494 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1495 if(retval) secnotice("recovery", "successfully registered recovery public key");
1496 else secnotice("recovery", "could not register recovery public key: %@", *error);
1497 SOSClearErrorIfTrue(retval, error);
1501 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1502 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1503 if(retval) secnotice("recovery", "RK Cleared");
1504 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1505 SOSClearErrorIfTrue(retval, error);
1509 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1510 CFDataRef result = NULL;
1511 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1512 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1514 if (!isData(result)) {
1515 CFReleaseNull(result);
1517 SOSClearErrorIfTrue(result != NULL, error);
1526 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key, bool use_cloud_peer, CFErrorRef* error) {
1527 SOSAccount* account = aTxn.account;
1528 SOSAccountTrustClassic *trust = account.trust;
1529 __block bool result = false;
1530 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1531 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1533 require_quiet([account.trust ensureFullPeerAvailable: account err:error], fail);
1535 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1536 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1537 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1538 // this also clears initial sync data
1539 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1541 SOSAccountInitializeInitialSync(account);
1542 if (use_cloud_peer) {
1543 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1546 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1547 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1548 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1549 trust.departureCode = kSOSNeverLeftCircle;
1550 if(result && cloud_full_peer) {
1551 CFErrorRef localError = NULL;
1552 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1553 require_quiet(cloudid, finish);
1554 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1555 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1558 secerror("Failed to join with cloud identity: %@", localError);
1559 CFReleaseNull(localError);
1565 if (use_cloud_peer) {
1566 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1570 CFReleaseNull(cloud_full_peer);
1574 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) {
1575 SOSAccount* account = aTxn.account;
1576 SOSAccountTrustClassic *trust = account.trust;
1577 bool success = false;
1579 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1580 require_quiet(user_key, done); // Fail if we don't get one.
1582 if(!trust.trustedCircle || SOSCircleCountPeers(trust.trustedCircle) == 0 ) {
1583 secnotice("resetToOffering", "Resetting circle to offering because it's empty and we're joining");
1584 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1587 if(SOSCircleHasPeerWithID(trust.trustedCircle, (__bridge CFStringRef)(account.peerID), NULL)) {
1588 // We shouldn't be at this point if we're already in circle.
1589 secnotice("circleops", "attempt to join a circle we're in - continuing.");
1590 return true; // let things above us think we're in circle - because we are.
1593 if(!SOSCircleVerify(trust.trustedCircle, account.accountKey, NULL)) {
1594 secnotice("resetToOffering", "Resetting circle to offering since we are new and it doesn't verify with current userKey");
1595 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1598 if (trust.fullPeerInfo != NULL) {
1599 SOSPeerInfoRef myPeer = trust.peerInfo;
1600 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1601 require_quiet(!success, done);
1603 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1605 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1606 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1608 trust.fullPeerInfo = NULL;
1612 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
1614 require_quiet(success, done);
1616 trust.departureCode = kSOSNeverLeftCircle;
1622 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1623 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1624 return SOSAccountJoinCircles_internal(aTxn, false, error);
1627 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1628 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1629 return SOSAccountJoinCircles_internal(aTxn, true, error);
1632 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error)
1634 bool result = false;
1635 CFMutableSetRef peersToRemove = NULL;
1636 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1638 secnotice("circleOps", "Can't remove without userKey");
1642 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1643 SOSPeerInfoRef me = account.peerInfo;
1644 if (!(me_full && me)) {
1645 secnotice("circleOps", "Can't remove without being active peer");
1646 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1650 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1652 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1653 if (!peersToRemove) {
1654 CFReleaseNull(peersToRemove);
1655 secnotice("circleOps", "No peerSet to remove");
1659 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1660 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1661 CFSetRemoveValue(peersToRemove, me);
1663 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1664 bool success = false;
1666 if (CFSetGetCount(peersToRemove) != 0) {
1667 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1668 success = SOSAccountGenerationSignatureUpdate(account, error);
1673 if (success && leaveCircle) {
1674 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1675 success = sosAccountLeaveCircle(account, circle, error);
1684 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1685 secnotice("circleOps", "Removed Peers from circle %@", description);
1689 CFReleaseNull(peersToRemove);
1693 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1694 dispatch_queue_t queue = dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0);
1695 dispatch_group_t group = dispatch_group_create();
1696 SOSAccountTrustClassic *trust = account.trust;
1697 __block bool result = false;
1698 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1699 // Add a task to the group
1700 dispatch_group_async(group, queue, ^{
1701 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1702 secnotice("circleOps", "Leaving circle by client request (Bail)");
1703 return sosAccountLeaveCircle(account, circle, error);
1706 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1707 dispatch_group_wait(group, milestone);
1709 trust.departureCode = kSOSWithdrewMembership;
1715 // MARK: Application
1718 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1719 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1720 SOSAccountTrustClassic *trust = account.trust;
1722 SOSPeerInfoRef me = trust.peerInfo;
1723 CFErrorRef peer_error = NULL;
1724 if (trust.trustedCircle && me &&
1725 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1726 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1727 __block bool modified = false;
1728 CFArrayForEach(peer_infos, ^(const void *value) {
1729 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1730 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1731 if (action(circle, trust.fullPeerInfo, peer)) {
1740 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1741 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1744 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1745 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1749 __block int64_t acceptedPeers = 0;
1751 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1752 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1758 if (acceptedPeers == CFArrayGetCount(applicants))
1763 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1764 __block bool success = true;
1765 __block int64_t num_peers = 0;
1767 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1768 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1772 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1779 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1780 SOSAccountTrustClassic *trust = account.trust;
1781 return trust.departureCode;
1784 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1785 SOSAccountTrustClassic *trust = account.trust;
1786 trust.departureCode = reason;
1790 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1791 CFArrayRef result = NULL;
1792 CFNumberRef generation = NULL;
1793 SOSAccountTrustClassic *trust = account.trust;
1795 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1796 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1798 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1799 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1805 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1806 if (!SOSAccountHasPublicKey(account, error))
1809 return account.accountKeyIsTrusted;
1812 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1813 // TODO: this result is never set or used
1815 SOSAccountTrustClassic *trust = account.trust;
1817 secnotice("updates", "Ensuring peer registration.");
1820 secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
1824 if([account getCircleStatus: NULL] != kSOSCCInCircle) {
1828 // If we are not in the circle, there is no point in setting up peers
1829 if(!SOSAccountIsMyPeerActive(account, NULL)) {
1833 // This code only uses the SOSFullPeerInfoRef for two things:
1834 // - Finding out if this device is in the trusted circle
1835 // - Using the peerID for this device to see if the current peer is "me"
1836 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
1838 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
1840 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
1841 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
1842 CFErrorRef localError = NULL;
1844 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
1846 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
1847 CFReleaseNull(localError);
1855 // Value manipulation
1858 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
1859 SOSAccountTrustClassic *trust = account.trust;
1860 if (!trust.expansion) {
1863 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
1866 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
1867 bool success = false;
1869 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
1870 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
1875 - (void)_onQueueRecordRetiredPeersInCircle {
1877 dispatch_assert_queue(self.queue);
1879 if (![self isInCircle:NULL]) {
1882 __block bool updateRings = false;
1883 SOSAccountTrustClassic *trust = self.trust;
1884 [trust modifyCircle:self.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
1885 __block bool updated = false;
1886 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
1887 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
1889 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
1891 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
1892 CFErrorRef cleanupError = NULL;
1893 if (![self.trust cleanupAfterPeer:self.kvs_message_transport circleTransport:self.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
1894 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
1895 CFReleaseSafe(cleanupError);
1902 SOSAccountProcessBackupRings(self);
1906 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
1908 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
1910 __block CFTypeRef object = NULL;
1912 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1913 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1915 CloudKeychainReplyBlock replyBlock =
1916 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
1918 object = returnedValues;
1923 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
1925 dispatch_semaphore_signal(waitSemaphore);
1928 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
1930 dispatch_semaphore_wait(waitSemaphore, finishTime);
1931 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
1936 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
1940 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
1942 CFStringRef uuid = SOSAccountCopyUUID(account);
1943 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1944 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1946 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1948 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
1950 dispatch_semaphore_signal(waitSemaphore);
1953 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
1954 dispatch_semaphore_wait(waitSemaphore, finishTime);
1955 CFReleaseNull(uuid);
1958 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
1960 NSDate *now = [NSDate date];
1961 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
1963 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
1965 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
1967 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
1969 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
1970 CFStringAppend(timeDescription, decription);
1972 CFStringAppend(timeDescription, CFSTR("]"));
1974 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
1975 CFReleaseNull(timeDescription);
1977 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1978 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1979 dispatch_queue_t processQueue = dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0);
1981 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1983 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
1985 dispatch_semaphore_signal(waitSemaphore);
1988 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
1989 dispatch_semaphore_wait(waitSemaphore, finishTime);
1992 // set the cleanup frequency to 3 days.
1993 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
1995 //Get all the key/values in KVS and remove old entries
1996 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
1998 // This should only happen on some number of days
1999 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
2000 NSDate *now = [NSDate date];
2001 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
2003 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
2007 dispatch_queue_t processQueue = dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0);
2009 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
2010 NSMutableArray *peerIDs = [NSMutableArray array];
2011 NSMutableArray *keysToRemove = [NSMutableArray array];
2013 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
2014 CFArrayForEach(peers, ^(const void *value) {
2015 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
2016 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
2018 //any peerid that is not ours gets added
2019 if(![[account.trust peerID] isEqualToString:peerID])
2020 [peerIDs addObject:peerID];
2022 CFReleaseNull(peers);
2024 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
2025 __block bool keyMatchesPeerID = false;
2027 //checks for full peer ids
2028 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
2029 //if key contains peerid of one active peer
2030 if([KVSKey containsString:PeerID]){
2031 secnotice("key-cleanup","key: %@", KVSKey);
2032 keyMatchesPeerID = true;
2035 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
2036 || [KVSKey hasPrefix:@"po"])
2037 [keysToRemove addObject:KVSKey];
2040 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
2041 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
2043 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
2045 //add last cleanup timestamp
2046 SOSAccountWriteLastCleanupTimestampToKVS(account);
2051 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2052 SOSPeerInfoRef applicant = NULL;
2053 SOSAccountTrustClassic *trust = account.trust;
2054 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2055 if(!userKey) return false;
2056 if(![trust ensureFullPeerAvailable:account err:error])
2058 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2060 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2067 AddStrippedTLKs(NSMutableArray* results, NSArray<CKKSKeychainBackedKey*>* tlks, NSMutableSet *seenUUID, bool authoritative)
2069 for(CKKSKeychainBackedKey* tlk in tlks) {
2070 NSError* localerror = nil;
2071 CKKSAESSIVKey* keyBytes = [tlk ensureKeyLoaded:&localerror];
2073 if(!keyBytes || localerror) {
2074 secnotice("piggy", "Failed to load TLK %@: %@", tlk, localerror);
2078 NSMutableDictionary* strippedDown = [@{
2079 (id)kSecValueData : [keyBytes keyMaterial],
2080 (id)kSecAttrServer : tlk.zoneID.zoneName,
2081 (id)kSecAttrAccount : tlk.uuid,
2084 if (authoritative) {
2085 strippedDown[@"auth"] = @YES;
2088 secnotice("piggy", "sending TLK %@", tlk);
2090 [results addObject:strippedDown];
2091 [seenUUID addObject:tlk.uuid];
2097 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2099 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2100 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2101 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2102 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2104 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2107 if([parentUUID isEqualToString:viewUUID] || authoriative){
2109 /* check if we already have this entry */
2110 if ([seenUUID containsObject:viewUUID])
2113 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2114 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2119 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2121 NSMutableDictionary* strippedDown = [@{
2122 (id)kSecValueData : key,
2123 (id)kSecAttrServer : viewName,
2124 (id)kSecAttrAccount : viewUUID
2127 strippedDown[@"auth"] = @YES;
2129 [results addObject:strippedDown];
2130 [seenUUID addObject:viewUUID];
2136 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID, bool selectedTLKsOnly)
2139 NSError* localError = nil;
2140 NSArray<CKKSKeychainBackedKey*>* tlks = [[CKKSViewManager manager] currentTLKsFilteredByPolicy:selectedTLKsOnly error:&localError];
2143 secnotice("piggy", "unable to fetch TLKs: %@", localError);
2147 AddStrippedTLKs(results, tlks, seenUUID, true);
2151 NSArray<NSDictionary *>*
2152 SOSAccountGetSelectedTLKs(void)
2154 NSMutableArray<NSDictionary *>* results = [NSMutableArray array];
2155 NSMutableSet *seenUUID = [NSMutableSet set];
2157 AddViewManagerResults(results, seenUUID, true);
2163 NSArray<NSDictionary *>*
2164 SOSAccountGetAllTLKs(void)
2166 CFTypeRef result = NULL;
2167 NSMutableArray<NSDictionary *>* results = [NSMutableArray array];
2168 NSMutableSet *seenUUID = [NSMutableSet set];
2170 // first use all TLKs from the view manager
2171 AddViewManagerResults(results, seenUUID, false);
2173 //try to grab tlk-piggy items
2174 NSDictionary* query = @{
2175 (id)kSecClass : (id)kSecClassInternetPassword,
2176 (id)kSecUseDataProtectionKeychain : @YES,
2177 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2178 (id)kSecAttrDescription: @"tlk",
2179 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2180 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2181 (id)kSecReturnAttributes: @YES,
2182 (id)kSecReturnData: @YES,
2185 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2186 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2188 CFReleaseNull(result);
2190 //try to grab tlk-piggy items
2192 (id)kSecClass : (id)kSecClassInternetPassword,
2193 (id)kSecUseDataProtectionKeychain : @YES,
2194 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2195 (id)kSecAttrDescription: @"tlk-piggy",
2196 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2197 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2198 (id)kSecReturnAttributes: @YES,
2199 (id)kSecReturnData: @YES,
2202 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2203 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2205 CFReleaseNull(result);
2207 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2212 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2213 const uint8_t *der, uint8_t *der_end)
2215 if (type != kTLKUnknown) {
2216 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2217 piggy_encode_data(keychainData, der,
2218 piggy_encode_data(uuid, der,
2219 ccder_encode_uint64((uint64_t)type, der, der_end))));
2221 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2222 piggy_encode_data(keychainData, der,
2223 piggy_encode_data(uuid, der,
2224 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2228 static uint8_t* piggy_encode_data(NSData* data,
2229 const uint8_t *der, uint8_t *der_end)
2231 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2232 ccder_encode_body(data.length, data.bytes, der, der_end));
2236 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2238 name2type(NSString *view)
2240 if ([view isEqualToString:@"Manatee"])
2242 else if ([view isEqualToString:@"Engram"])
2244 else if ([view isEqualToString:@"AutoUnlock"])
2245 return kTLKAutoUnlock;
2246 if ([view isEqualToString:@"Health"])
2251 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2253 rank_type(NSString *view)
2255 if ([view isEqualToString:@"Manatee"])
2257 else if ([view isEqualToString:@"Engram"])
2259 else if ([view isEqualToString:@"AutoUnlock"])
2261 if ([view isEqualToString:@"Health"])
2267 parse_uuid(NSString *uuidString)
2269 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2271 [uuid getUUIDBytes:uuidblob];
2272 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2275 piggy_sizeof_data(NSData* data)
2277 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2280 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2281 if (type != kTLKUnknown) {
2282 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2283 piggy_sizeof_data(keychainData) +
2284 piggy_sizeof_data(uuid) +
2285 ccder_sizeof_uint64(type));
2287 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2288 piggy_sizeof_data(keychainData) +
2289 piggy_sizeof_data(uuid) +
2290 der_sizeof_string((__bridge CFStringRef)name, NULL));
2294 NSArray<NSDictionary*>*
2295 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2297 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2299 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2300 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2301 if (obj1[@"auth"] != NULL)
2303 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2304 if (obj2[@"auth"] != NULL)
2308 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2309 * since we are sorting backward, the Ascending/Descending looks wrong below.
2311 if (rank1 > rank2) {
2312 return NSOrderedAscending;
2313 } else if (rank1 < rank2) {
2314 return NSOrderedDescending;
2316 return NSOrderedSame;
2322 static NSArray<NSData *> *
2323 build_tlks(NSArray<NSDictionary*>* tlks)
2325 NSMutableArray *array = [NSMutableArray array];
2326 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2328 for (NSDictionary *item in sortedTLKs) {
2329 NSData* keychainData = item[(__bridge id)kSecValueData];
2330 NSString* name = item[(__bridge id)kSecAttrServer];
2331 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2332 NSData* uuid = parse_uuid(uuidString);
2334 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2336 unsigned char *der = [tlk mutableBytes];
2337 unsigned char *der_end = der + [tlk length];
2339 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2342 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2344 [array addObject:tlk];
2349 static NSArray<NSData *> *
2350 build_identities(NSArray<NSData *>* identities)
2352 NSMutableArray *array = [NSMutableArray array];
2353 for (NSData *item in identities) {
2354 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2356 unsigned char *der = [ident mutableBytes];
2357 unsigned char *der_end = der + [ident length];
2359 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2360 [array addObject:ident];
2367 static unsigned char *
2368 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2370 unsigned char *body_end = der_end;
2371 for (NSData *datum in data) {
2372 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2373 if (der_end == NULL)
2376 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2379 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2381 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2382 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2383 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2386 static NSData *encode_piggy(size_t IdentitiesBudget,
2388 NSArray<NSData*>* identities,
2389 NSArray<NSDictionary*>* tlks)
2391 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2392 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2393 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2394 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2395 size_t payloadSize = 0, identitiesSize = 0;
2396 NSMutableData *result = NULL;
2398 for (NSData *tlk in encodedTLKs) {
2399 if (TLKBudget - payloadSize < [tlk length])
2401 [budgetArray addObject:tlk];
2402 payloadSize += tlk.length;
2404 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2406 for (NSData *ident in encodedIdentities) {
2407 if (IdentitiesBudget - identitiesSize < [ident length])
2409 [identitiesArray addObject:ident];
2410 identitiesSize += ident.length;
2412 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2415 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2417 result = [NSMutableData dataWithLength:piggySize];
2419 unsigned char *der = [result mutableBytes];
2420 unsigned char *der_end = der + [result length];
2422 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2423 encode_data_array(identitiesArray, der,
2424 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2430 static const size_t SOSCCIdentitiesBudget = 120;
2431 static const size_t SOSCCTLKBudget = 500;
2434 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2436 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2439 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2441 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2443 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2444 if(SOSPeerInfoIsCloudIdentity(peer)) {
2445 CFArrayAppendValue(identities, peer);
2451 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) {
2453 NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array];
2454 NSArray<NSDictionary *>* tlks = nil;
2456 if (flags & kSOSInitialSyncFlagTLKsRequestOnly) {
2457 tlks = SOSAccountGetSelectedTLKs();
2459 if (flags & kSOSInitialSyncFlagiCloudIdentity) {
2460 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2461 secnotice("piggy", "identities: %@", identities);
2463 CFIndex i, count = CFArrayGetCount(identities);
2464 for (i = 0; i < count; i++) {
2465 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2466 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2468 [encodedIdenities addObject:data];
2470 CFRelease(identities);
2474 if (flags & kSOSInitialSyncFlagTLKs) {
2475 tlks = SOSAccountGetAllTLKs();
2479 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2482 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
2483 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
2484 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
2485 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
2486 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
2487 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
2488 if(accountName == NULL) {
2489 accountName = CFSTR("Unavailable");
2491 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
2493 secnotice("circleOps",
2494 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
2495 operation, accountName, version, gencount, pkeyID, sigID, circleHash);
2496 CFReleaseNull(pkeyID);
2497 CFReleaseNull(sigID);
2498 CFReleaseNull(circleHash);
2501 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2502 SOSGenCountRef gencount = NULL;
2503 CFDataRef signature = NULL;
2504 SecKeyRef ourKey = NULL;
2506 CFDataRef pbblob = NULL;
2507 SOSCircleRef prunedCircle = NULL;
2509 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2511 SOSCCStatus circleStat = [account getCircleStatus:error];
2512 if(circleStat != kSOSCCInCircle) {
2513 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
2517 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2518 require_quiet(userKey, errOut);
2520 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2521 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
2522 secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
2525 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2526 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2527 require_quiet(ourKey, errOut);
2530 SOSCircleRef currentCircle = [account.trust getCircle:error];
2531 require_quiet(currentCircle, errOut);
2533 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2534 require_quiet(prunedCircle, errOut);
2535 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2537 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2539 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2540 require_quiet(signature, errOut);
2541 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
2542 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2545 CFReleaseNull(prunedCircle);
2546 CFReleaseNull(gencount);
2547 CFReleaseNull(signature);
2548 CFReleaseNull(ourKey);
2550 if(!pbblob && error != NULL) {
2551 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
2557 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2558 bool retval = false;
2559 SecKeyRef userKey = NULL;
2560 SOSAccountTrustClassic *trust = account.trust;
2561 SOSGenCountRef gencount = NULL;
2562 CFDataRef signature = NULL;
2563 SecKeyRef pubKey = NULL;
2564 bool setInitialSyncTimeoutToV0 = false;
2566 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
2568 if (!isData(joiningBlob)) {
2569 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
2573 userKey = SOSAccountGetPrivateCredential(account, error);
2575 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2579 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
2580 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2584 if(setInitialSyncTimeoutToV0){
2585 secnotice("circleOps", "setting flag in account for piggyback v0");
2586 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2588 secnotice("circleOps", "clearing flag in account for piggyback v0");
2589 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2591 SOSAccountInitializeInitialSync(account);
2593 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
2595 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2596 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2600 trust.fullPeerInfo, error);;
2603 CFReleaseNull(gencount);
2604 CFReleaseNull(pubKey);
2605 CFReleaseNull(signature);
2610 static char boolToChars(bool val, char truechar, char falsechar) {
2611 return val? truechar: falsechar;
2614 #define ACCOUNTLOGSTATE "accountLogState"
2615 void SOSAccountLogState(SOSAccount* account) {
2616 bool hasPubKey = account.accountKey != NULL;
2617 SOSAccountTrustClassic *trust = account.trust;
2618 bool pubTrusted = account.accountKeyIsTrusted;
2619 bool hasPriv = account.accountPrivateKey != NULL;
2620 SOSCCStatus stat = [account getCircleStatus:NULL];
2622 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2623 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2625 secnotice(ACCOUNTLOGSTATE, "Start");
2627 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@] [UID: %d EUID: %d]",
2628 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2630 SOSAccountGetSOSCCStatusString(stat), getuid(), geteuid()
2632 CFReleaseNull(userPubKeyID);
2633 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2634 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2637 void SOSAccountLogViewState(SOSAccount* account) {
2638 bool isInCircle = [account.trust isInCircleOnly:NULL];
2639 require_quiet(isInCircle, imOut);
2640 SOSPeerInfoRef mpi = account.peerInfo;
2641 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2642 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2644 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2645 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2646 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2647 boolToChars(isInitialComplete, 'I', 'i'),
2648 boolToChars(isBackupComplete, 'B', 'b'),
2651 CFReleaseNull(views);
2652 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2653 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2654 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2656 CFReleaseNull(unsyncedViews);
2659 secnotice(ACCOUNTLOGSTATE, "Finish");
2665 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2666 if(!isString(serial)) return;
2667 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2668 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2669 [account.trust updateV2Dictionary:account v2:newv2dict];
2672 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2674 secnotice("otrtimer", "timer fired!");
2675 CFErrorRef error = NULL;
2677 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2678 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2679 if(SOSCoderIsCoderInAwaitingState(coder)){
2680 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2681 CFErrorRef localError = NULL;
2682 SOSCoderReset(coder);
2683 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2684 secerror("Attempt to recover coder failed to restart: %@", localError);
2687 secnotice("otrtimer", "coder restarted!");
2688 SOSEngineSetCodersNeedSaving(engine, true);
2689 SOSPeerSetMustSendMessage(peer, true);
2690 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2692 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2693 SOSPeerRemoveOTRTimerEntry(peer);
2694 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2695 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2698 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2703 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2705 CFReleaseNull(error);
2708 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2710 __block SOSAccount* account = txn.account;
2711 CFErrorRef error = NULL;
2713 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2714 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2716 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2717 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2718 CFErrorRef error = NULL;
2719 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2722 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2723 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2725 if(!sendResult || error){
2726 secnotice("ratelimit", "could not send message: %@", error);
2729 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2730 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2731 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2736 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2738 CFReleaseNull(error);
2747 OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup";
2748 OctagonFlag* SOSFlagTriggerRingUpdate = (OctagonFlag*)@"trigger_ring_update";
2750 OctagonState* SOSStateReady = (OctagonState*)@"ready";
2751 OctagonState* SOSStateError = (OctagonState*)@"error";
2752 OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup";
2753 OctagonState* SOSStatePerformRingUpdate = (OctagonState*)@"perform_ring_update";
2755 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void) {
2756 static NSDictionary<OctagonState*, NSNumber*>* map = nil;
2757 static dispatch_once_t onceToken;
2758 dispatch_once(&onceToken, ^{
2762 SOSStatePerformBackup: @2U,
2763 SOSStatePerformRingUpdate: @3U,
2769 static NSSet<OctagonFlag*>* SOSFlagsSet(void) {
2770 static NSSet<OctagonFlag*>* set = nil;
2771 static dispatch_once_t onceToken;
2772 dispatch_once(&onceToken, ^{
2773 set = [NSSet setWithArray:@[
2774 SOSFlagTriggerBackup,
2775 SOSFlagTriggerRingUpdate,
2783 + (NSURL *)urlForSOSAccountSettings {
2784 return (__bridge_transfer NSURL *)SecCopyURLForFileInKeychainDirectory(CFSTR("SOSAccountSettings.pb"));
2788 - (void)setupStateMachine {
2791 self.accountConfiguration = [[CKKSPBFileStorage alloc] initWithStoragePath:[[self class] urlForSOSAccountSettings]
2792 storageClass:[SOSAccountConfiguration class]];
2794 NSAssert(self.stateMachine == nil, @"can't bootstrap more than once");
2796 self.stateMachineQueue = dispatch_queue_create("SOS-statemachine", NULL);
2798 self.stateMachine = [[OctagonStateMachine alloc] initWithName:@"sosaccount"
2799 states:[NSSet setWithArray:[SOSStateMap() allKeys]]
2801 initialState:SOSStateReady
2802 queue:self.stateMachineQueue
2804 lockStateTracker:[CKKSLockStateTracker globalTracker]];
2807 self.performBackups = [[CKKSNearFutureScheduler alloc] initWithName:@"performBackups"
2808 initialDelay:5*NSEC_PER_SEC
2809 continuingDelay:30*NSEC_PER_SEC
2810 keepProcessAlive:YES
2811 dependencyDescriptionCode:CKKSResultDescriptionNone
2814 [self addBackupFlag];
2817 self.performRingUpdates = [[CKKSNearFutureScheduler alloc] initWithName:@"performRingUpdates"
2818 initialDelay:5*NSEC_PER_SEC
2819 expontialBackoff:2.0
2820 maximumDelay:15*60*NSEC_PER_SEC
2821 keepProcessAlive:YES
2822 dependencyDescriptionCode:CKKSResultDescriptionNone
2825 [self addRingUpdateFlag];
2828 SOSAccountConfiguration *conf = self.accountConfiguration.storage;
2830 if (conf.pendingBackupPeers.count) {
2831 [self addBackupFlag];
2833 if (conf.ringUpdateFlag) {
2834 [self addRingUpdateFlag];
2840 * Flag adding to state machine
2843 - (void)addBackupFlag {
2844 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup
2845 conditions:OctagonPendingConditionsDeviceUnlocked];
2846 [self.stateMachine handlePendingFlag:pendingFlag];
2849 - (void)addRingUpdateFlag {
2850 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerRingUpdate
2851 conditions:OctagonPendingConditionsDeviceUnlocked];
2852 [self.stateMachine handlePendingFlag:pendingFlag];
2855 //Mark: -- Set up state for state machine
2858 - (void)triggerBackupForPeers:(NSArray<NSString*>*)backupPeers
2860 NSMutableSet *pending = [NSMutableSet set];
2862 [pending addObjectsFromArray:backupPeers];
2866 dispatch_async(self.stateMachineQueue, ^{
2869 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2871 if (storage.pendingBackupPeers) {
2872 [pending addObjectsFromArray:storage.pendingBackupPeers];
2874 storage.pendingBackupPeers = [[pending allObjects] mutableCopy];
2875 [self.accountConfiguration setStorage:storage];
2876 [self.performBackups trigger];
2877 secnotice("sos-sm", "trigger backup for peers: %@ at %@",
2878 backupPeers, self.performBackups.nextFireTime);
2882 - (void)triggerRingUpdate
2885 dispatch_async(self.stateMachineQueue, ^{
2887 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2888 storage.ringUpdateFlag = YES;
2889 [self.accountConfiguration setStorage:storage];
2890 [self.performRingUpdates trigger];
2891 secnotice("sos-sm", "trigger ring update at %@",
2892 self.performRingUpdates.nextFireTime);
2896 //Mark: -- State machine and opertions
2898 - (OctagonStateTransitionOperation *)performBackup {
2901 return [OctagonStateTransitionOperation named:@"perform-backup-state"
2902 intending:SOSStateReady
2903 errorState:SOSStateError
2904 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) {
2906 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2908 secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers);
2910 if (storage.pendingBackupPeers.count) {
2911 SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers);
2912 [storage clearPendingBackupPeers];
2914 [self.accountConfiguration setStorage:storage];
2916 op.nextState = SOSStateReady;
2920 - (OctagonStateTransitionOperation *)performRingUpdate {
2923 return [OctagonStateTransitionOperation named:@"perform-ring-update"
2924 intending:SOSStateReady
2925 errorState:SOSStateError
2926 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) {
2929 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2930 storage.ringUpdateFlag = NO;
2931 [self.accountConfiguration setStorage:storage];
2933 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
2934 if([self accountKeyIsTrusted] && [self isInCircle:NULL]) {
2936 [self _onQueueRecordRetiredPeersInCircle];
2938 SOSAccountEnsureRecoveryRing(self);
2939 [self _onQueueEnsureInBackupRings];
2941 CFErrorRef localError = NULL;
2942 if(![self.circle_transport flushChanges:&localError]){
2943 secerror("flush circle failed %@", localError);
2945 CFReleaseNull(localError);
2947 if(!SecCKKSTestDisableSOS()) {
2948 SOSAccountNotifyEngines(self);
2953 op.nextState = SOSStateReady;
2959 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
2960 flags:(nonnull OctagonFlags *)flags
2961 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
2963 dispatch_assert_queue(self.stateMachineQueue);
2965 secnotice("sos-sm", "Entering state: %@ [flags: %@]", currentState, flags);
2967 if ([currentState isEqualToString:SOSStateReady]) {
2968 if([flags _onqueueContains:SOSFlagTriggerBackup]) {
2969 [flags _onqueueRemoveFlag:SOSFlagTriggerBackup];
2970 return [OctagonStateTransitionOperation named:@"perform-backup-flag"
2971 entering:SOSStatePerformBackup];
2974 if ([flags _onqueueContains:SOSFlagTriggerRingUpdate]) {
2975 [flags _onqueueRemoveFlag:SOSFlagTriggerRingUpdate];
2976 return [OctagonStateTransitionOperation named:@"perform-ring-update-flag"
2977 entering:SOSStatePerformRingUpdate];
2981 } else if ([currentState isEqualToString:SOSStateError]) {
2983 } else if ([currentState isEqualToString:SOSStatePerformRingUpdate]) {
2984 return [self performRingUpdate];
2986 } else if ([currentState isEqualToString:SOSStatePerformBackup]) {
2987 return [self performBackup];