2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
6 * SOSAccount.c - Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
10 #import <Foundation/Foundation.h>
12 #import "keychain/SecureObjectSync/SOSAccount.h"
13 #import <Security/SecureObjectSync/SOSPeerInfo.h>
14 #import "keychain/SecureObjectSync/SOSPeerInfoV2.h"
15 #import "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
16 #import "keychain/SecureObjectSync/SOSTransportCircle.h"
17 #import "keychain/SecureObjectSync/SOSTransportCircleKVS.h"
18 #import "keychain/SecureObjectSync/SOSTransportMessage.h"
19 #import "keychain/SecureObjectSync/SOSTransportMessageKVS.h"
20 #import "keychain/SecureObjectSync/SOSTransportKeyParameter.h"
21 #import "keychain/SecureObjectSync/SOSKVSKeys.h"
22 #import "keychain/SecureObjectSync/SOSTransport.h"
23 #import "keychain/SecureObjectSync/SOSPeerCoder.h"
24 #import "keychain/SecureObjectSync/SOSInternal.h"
25 #import "keychain/SecureObjectSync/SOSRing.h"
26 #import "keychain/SecureObjectSync/SOSRingUtils.h"
27 #import "keychain/SecureObjectSync/SOSRingRecovery.h"
28 #import "keychain/SecureObjectSync/SOSAccountTransaction.h"
29 #import "keychain/SecureObjectSync/SOSAccountGhost.h"
30 #import "keychain/SecureObjectSync/SOSPiggyback.h"
31 #import "keychain/SecureObjectSync/SOSControlHelper.h"
32 #import "keychain/SecureObjectSync/SOSAuthKitHelpers.h"
34 #import "keychain/SecureObjectSync/SOSAccountTrust.h"
35 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
36 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
37 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
38 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
39 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
40 #import "keychain/SecureObjectSync/SOSPeerOTRTimer.h"
41 #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h"
42 #import "keychain/SecureObjectSync/SOSTypes.h"
44 #import "keychain/ckks/CKKSViewManager.h"
45 #import "keychain/ckks/CKKSLockStateTracker.h"
46 #import "keychain/ckks/CKKSNearFutureScheduler.h"
47 #import "keychain/ckks/CKKSPBFileStorage.h"
49 #import "keychain/ot/OTManager.h"
50 #import "keychain/ot/ObjCImprovements.h"
51 #import "keychain/ot/OctagonStateMachine.h"
52 #import "keychain/ot/OctagonStateMachineHelpers.h"
54 #include <Security/SecItemInternal.h>
55 #include <Security/SecEntitlements.h>
56 #include "keychain/securityd/SecItemServer.h"
58 #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h"
59 #include "keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h"
62 #import "SecdWatchdog.h"
64 #include <utilities/SecCFWrappers.h>
65 #include <utilities/SecCFError.h>
66 #include <utilities/SecADWrapper.h>
67 #include <utilities/SecFileLocations.h>
69 #include <os/activity.h>
70 #include <os/state_private.h>
72 #include <utilities/SecCoreCrypto.h>
74 #include <utilities/der_plist.h>
75 #include <utilities/der_plist_internal.h>
76 #include <corecrypto/ccder.h>
79 const CFStringRef kSOSAccountName = CFSTR("AccountName");
80 const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
81 const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
82 const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout");
83 const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
84 const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
85 const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary");
86 const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey");
87 const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing");
88 const CFStringRef kSOSAccountUUID = CFSTR("UUID");
89 const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters");
90 const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef>
91 const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef>
92 const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount");
93 const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion");
94 NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry";
95 NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount";
96 NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup";
98 const uint64_t max_packet_size_over_idms = 500;
101 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
103 #define DATE_LENGTH 25
104 const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
107 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void);
109 @interface SOSAccount () <OctagonStateMachineEngine>
110 @property dispatch_queue_t stateMachineQueue;
111 @property (readwrite) OctagonStateMachine* stateMachine;
113 @property (readwrite) CKKSPBFileStorage<SOSAccountConfiguration*>* accountConfiguration;
115 @property CKKSNearFutureScheduler *performBackups;
119 @implementation SOSAccount
123 CFReleaseNull(self->_accountKey);
124 CFReleaseNull(self->_accountPrivateKey);
125 CFReleaseNull(self->_previousAccountKey);
129 @synthesize accountKey = _accountKey;
131 - (void) setAccountKey: (SecKeyRef) key {
132 CFRetainAssign(self->_accountKey, key);
135 @synthesize previousAccountKey = _previousAccountKey;
137 - (void) setPreviousAccountKey: (SecKeyRef) key {
138 CFRetainAssign(self->_previousAccountKey, key);
141 @synthesize accountPrivateKey = _accountPrivateKey;
143 - (void) setAccountPrivateKey: (SecKeyRef) key {
144 CFRetainAssign(self->_accountPrivateKey, key);
147 // Syntactic sugar getters
149 - (BOOL) hasPeerInfo {
150 return self.fullPeerInfo != nil;
153 - (SOSPeerInfoRef) peerInfo {
154 return self.trust.peerInfo;
157 - (SOSFullPeerInfoRef) fullPeerInfo {
158 return self.trust.fullPeerInfo;
161 - (NSString*) peerID {
162 return self.trust.peerID;
165 -(bool) ensureFactoryCircles
175 NSString* circle_name = (__bridge_transfer NSString*)SOSDataSourceFactoryCopyName(self.factory);
181 CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL));
183 return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL);
186 -(void)ensureOctagonPeerKeys
189 CKKSLockStateTracker *tracker = [CKKSViewManager manager].lockStateTracker;
190 if (tracker && tracker.isLocked == false) {
191 [self.trust ensureOctagonPeerKeys:self.circle_transport];
196 -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f
200 self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
202 self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)];
204 SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
207 self.factory = f; // We adopt the factory. kthanksbai.
209 self.isListeningForSync = false;
211 self.accountPrivateKey = NULL;
212 self._password_tmp = NULL;
213 self.user_private_timer = NULL;
214 self.lock_notification_token = NOTIFY_TOKEN_INVALID;
216 self.change_blocks = [NSMutableArray array];
218 self.key_transport = nil;
219 self.circle_transport = NULL;
220 self.ck_storage = nil;
221 self.kvs_message_transport = nil;
223 self.circle_rings_retirements_need_attention = false;
224 self.engine_peer_state_needs_repair = false;
225 self.key_interests_need_updating = false;
227 self.backup_key =nil;
230 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
231 self.accountKeyIsTrusted = false;
232 self.accountKeyDerivationParamters = NULL;
233 self.accountKey = NULL;
234 self.previousAccountKey = NULL;
236 self.saveBlock = nil;
238 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
241 [self setupStateMachine];
247 -(BOOL)isEqual:(id) object
249 if(![object isKindOfClass:[SOSAccount class]])
252 SOSAccount* left = object;
253 return ([self.gestalt isEqual: left.gestalt] &&
254 CFEqualSafe(self.trust.trustedCircle, left.trust.trustedCircle) &&
255 [self.trust.expansion isEqual: left.trust.expansion] &&
256 CFEqualSafe(self.trust.fullPeerInfo, left.trust.fullPeerInfo));
260 - (void)userPublicKey:(void ((^))(BOOL trusted, NSData *spki, NSError *error))reply
262 dispatch_async(self.queue, ^{
263 if (!self.accountKeyIsTrusted || self.accountKey == NULL) {
264 NSDictionary *userinfo = @{
265 (id)kCFErrorDescriptionKey : @"User public key not trusted",
267 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
271 NSData *data = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(self.accountKey));
273 NSDictionary *userinfo = @{
274 (id)kCFErrorDescriptionKey : @"User public not available",
276 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
279 reply(self.accountKeyIsTrusted, data, NULL);
283 - (void)kvsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply
285 /* Need to collect performance counters from all subsystems, not just circle transport, don't have counters yet though */
286 SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error)
288 reply((__bridge NSDictionary *)returnedValues);
292 - (void)rateLimitingPerformanceCounters:(void(^)(NSDictionary <NSString *, NSString *> *))reply
294 CFErrorRef error = NULL;
295 CFDictionaryRef rateLimitingCounters = (CFDictionaryRef)SOSAccountGetValue(self, kSOSRateLimitingCounters, &error);
296 reply((__bridge NSDictionary*)rateLimitingCounters ? (__bridge NSDictionary*)rateLimitingCounters : [NSDictionary dictionary]);
299 - (void)stashedCredentialPublicKey:(void(^)(NSData *, NSError *error))reply
301 dispatch_async(self.queue, ^{
302 CFErrorRef error = NULL;
304 SecKeyRef user_private = SOSAccountCopyStashedUserPrivateKey(self, &error);
305 if (user_private == NULL) {
306 reply(NULL, (__bridge NSError *)error);
307 CFReleaseNull(error);
311 NSData *publicKey = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(user_private));
312 CFReleaseNull(user_private);
313 reply(publicKey, NULL);
317 - (void)assertStashedAccountCredential:(void(^)(BOOL result, NSError *error))complete
319 dispatch_async(self.queue, ^{
320 CFErrorRef error = NULL;
321 bool result = SOSAccountAssertStashedAccountCredential(self, &error);
322 complete(result, (__bridge NSError *)error);
323 CFReleaseNull(error);
327 static bool SyncKVSAndWait(CFErrorRef *error) {
328 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
330 __block bool success = false;
332 secnoticeq("fresh", "EFP calling SOSCloudKeychainSynchronizeAndWait");
334 os_activity_initiate("CloudCircle EFRESH", OS_ACTIVITY_FLAG_DEFAULT, ^(void) {
335 SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) {
336 secnotice("fresh", "EFP returned, callback error: %@", sync_error);
338 success = (sync_error == NULL);
340 CFRetainAssign(*error, sync_error);
343 dispatch_semaphore_signal(wait_for);
347 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
348 secnotice("fresh", "EFP complete: %s %@", success ? "success" : "failure", error ? *error : NULL);
354 static bool Flush(CFErrorRef *error) {
355 __block bool success = false;
357 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
358 secnotice("flush", "Starting");
360 SOSCloudKeychainFlush(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
361 success = (sync_error == NULL);
363 CFRetainAssign(*error, sync_error);
366 dispatch_semaphore_signal(wait_for);
369 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
371 secnotice("flush", "Returned %s", success? "Success": "Failure");
376 - (bool)syncWaitAndFlush:(CFErrorRef *)error
378 secnotice("pairing", "sync and wait starting");
380 if (!SyncKVSAndWait(error)) {
381 secnotice("pairing", "failed sync and wait: %@", error ? *error : NULL);
385 secnotice("pairing", "failed flush: %@", error ? *error : NULL);
388 secnotice("pairing", "finished sync and wait");
392 - (void)validatedStashedAccountCredential:(void(^)(NSData *credential, NSError *error))complete
394 CFErrorRef syncerror = NULL;
396 if (![self syncWaitAndFlush:&syncerror]) {
397 complete(NULL, (__bridge NSError *)syncerror);
398 CFReleaseNull(syncerror);
402 dispatch_async(self.queue, ^{
403 CFErrorRef error = NULL;
404 SecKeyRef key = NULL;
405 key = SOSAccountCopyStashedUserPrivateKey(self, &error);
407 secnotice("pairing", "no stashed credential");
408 complete(NULL, (__bridge NSError *)error);
409 CFReleaseNull(error);
413 SecKeyRef publicKey = SecKeyCopyPublicKey(key);
415 secnotice("pairing", "returning stash credential: %@", publicKey);
416 CFReleaseNull(publicKey);
419 NSData *keydata = CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error));
421 complete(keydata, (__bridge NSError *)error);
422 CFReleaseNull(error);
426 - (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete
428 CFErrorRef syncerror = NULL;
430 if (![self syncWaitAndFlush:&syncerror]) {
431 complete(NULL, (__bridge NSError *)syncerror);
432 CFReleaseNull(syncerror);
436 sleep(1); // make up for keygen time in password based version - let syncdefaults catch up
438 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
439 SecKeyRef accountPrivateKey = NULL;
440 CFErrorRef error = NULL;
441 NSDictionary *attributes = @{
442 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
443 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
446 accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error);
447 if (accountPrivateKey == NULL) {
448 complete(false, (__bridge NSError *)error);
449 secnotice("pairing", "SecKeyCreateWithData failed: %@", error);
450 CFReleaseNull(error);
454 if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) {
455 CFReleaseNull(accountPrivateKey);
456 complete(false, (__bridge NSError *)error);
457 secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error);
458 CFReleaseNull(error);
462 secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded");
464 CFReleaseNull(accountPrivateKey);
465 complete(true, NULL);
468 // This makes getting the private key the same as Asserting the password - we read all the other things
469 // that we just expressed interest in.
470 CFErrorRef error = NULL;
471 if (!Flush(&error)) {
472 secnotice("pairing", "failed final flush: %@", error ? error : NULL);
475 CFReleaseNull(error);
478 - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete
480 __block CFErrorRef localError = NULL;
481 __block NSData *applicationBlob = NULL;
482 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
483 SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError);
485 applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError));
486 CFReleaseNull(application);
489 complete(applicationBlob, (__bridge NSError *)localError);
490 CFReleaseNull(localError);
493 - (void)circleHash:(void (^)(NSString *, NSError *))complete
495 __block CFErrorRef localError = NULL;
496 __block NSString *circleHash = NULL;
497 SecAKSDoWithUserBagLockAssertion(&localError, ^{
498 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
499 circleHash = CFBridgingRelease(SOSCircleCopyHashString(txn.account.trust.trustedCircle));
502 complete(circleHash, (__bridge NSError *)localError);
503 CFReleaseNull(localError);
508 #if TARGET_OS_OSX || TARGET_OS_IOS
511 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
512 OTManager *otm = [OTManager manager];
513 SOSAccountGhostBustingOptions options = ([otm ghostbustByMidEnabled] == YES ? SOSGhostBustByMID: 0) |
514 ([otm ghostbustBySerialEnabled] == YES ? SOSGhostBustBySerialNumber : 0) |
515 ([otm ghostbustByAgeEnabled] == YES ? SOSGhostBustSerialByAge: 0);
520 #define GHOSTBUSTDATE @"ghostbustdate"
522 - (NSDate *) ghostBustGetDate {
523 if(! self.settings) {
524 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
526 return [self.settings valueForKey:GHOSTBUSTDATE];
529 - (bool) ghostBustCheckDate {
530 NSDate *ghostBustDate = [self ghostBustGetDate];
531 if(ghostBustDate && ([ghostBustDate timeIntervalSinceNow] <= 0)) return true;
535 - (void) ghostBustFollowup {
536 if(! self.settings) {
537 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
539 NSTimeInterval earliestGB = 60*60*24*3; // wait at least 3 days
540 NSTimeInterval latestGB = 60*60*24*7; // wait at most 7 days
541 NSDate *ghostBustDate = SOSCreateRandomDateBetweenNowPlus(earliestGB, latestGB);
542 [self.settings setValue:ghostBustDate forKey:GHOSTBUSTDATE];
545 // GhostBusting initial scheduling
546 - (void)ghostBustSchedule {
547 NSDate *ghostBustDate = [self ghostBustGetDate];
549 [self ghostBustFollowup];
553 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
554 __block bool result = false;
555 __block CFErrorRef localError = NULL;
557 if([SOSAuthKitHelpers accountIsHSA2]) {
558 [SOSAuthKitHelpers activeMIDs:^(NSSet <SOSTrustedDeviceAttributes *> * _Nullable activeMIDs, NSError * _Nullable error) {
559 SOSAuthKitHelpers *akh = [[SOSAuthKitHelpers alloc] initWithActiveMIDS:activeMIDs];
561 SecAKSDoWithUserBagLockAssertion(&localError, ^{
562 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
563 result = SOSAccountGhostBustCircle(txn.account, akh, options, 1);
564 [self ghostBustFollowup];
568 complete(result, NULL);
571 complete(false, NULL);
575 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
576 NSDate *ghostBustDate = [self ghostBustGetDate];
577 if(([ghostBustDate timeIntervalSinceNow] <= 0)) {
579 [self ghostBust: options complete: complete];
581 complete(false, nil);
586 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
587 // if no particular options are presented use the ramp options.
588 // 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.
590 options = [SOSAccount ghostBustGetRampSettings];
592 [self ghostBust: options complete: complete];
595 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
596 // 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.
597 NSMutableDictionary *gbInfoDictionary = [NSMutableDictionary new];
598 SOSAccountGhostBustingOptions options = [SOSAccount ghostBustGetRampSettings];
599 NSString *ghostBustDate = [[self ghostBustGetDate] description];
601 gbInfoDictionary[@"SOSGhostBustBySerialNumber"] = (SOSGhostBustBySerialNumber & options) ? @"ON": @"OFF";
602 gbInfoDictionary[@"SOSGhostBustByMID"] = (SOSGhostBustByMID & options) ? @"ON": @"OFF";
603 gbInfoDictionary[@"SOSGhostBustSerialByAge"] = (SOSGhostBustSerialByAge & options) ? @"ON": @"OFF";
604 gbInfoDictionary[@"SOSAccountGhostBustDate"] = ghostBustDate;
607 NSData *json = [NSJSONSerialization dataWithJSONObject:gbInfoDictionary
608 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
611 secnotice("ghostbust", "Error during ghostBustInfo JSONification: %@", err.localizedDescription);
618 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
622 - (NSDate *) ghostBustGetDate {
626 - (void) ghostBustFollowup {
629 - (void)ghostBustSchedule {
632 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
633 complete(false, NULL);
636 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
637 complete(false, NULL);
640 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
641 complete(false, nil);
644 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
648 - (bool) ghostBustCheckDate {
654 - (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete
656 __block CFErrorRef localError = NULL;
657 __block NSData *blob = NULL;
658 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant);
660 complete(NULL, (__bridge NSError *)localError);
661 CFReleaseNull(localError);
665 SecAKSDoWithUserBagLockAssertionSoftly(^{
666 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
667 blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError));
673 complete(blob, (__bridge NSError *)localError);
674 CFReleaseNull(localError);
677 - (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete
679 __block CFErrorRef localError = NULL;
680 __block bool res = false;
682 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
683 res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError);
686 complete(res, (__bridge NSError *)localError);
687 CFReleaseNull(localError);
690 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
692 CFErrorRef error = NULL;
693 uint32_t isflags = 0;
695 if (flags & SOSControlInitialSyncFlagTLK)
696 isflags |= SecServerInitialSyncCredentialFlagTLK;
697 if (flags & SOSControlInitialSyncFlagPCS)
698 isflags |= SecServerInitialSyncCredentialFlagPCS;
699 if (flags & SOSControlInitialSyncFlagPCSNonCurrent)
700 isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent;
701 if (flags & SOSControlInitialSyncFlagBluetoothMigration)
702 isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration;
705 NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error));
706 complete(array, (__bridge NSError *)error);
707 CFReleaseNull(error);
710 - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete
712 CFErrorRef error = NULL;
713 bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error);
714 complete(res, (__bridge NSError *)error);
715 CFReleaseNull(error);
718 - (void)triggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
720 __block CFErrorRef localError = NULL;
721 __block bool res = false;
723 secnotice("sync", "trigger a forced sync for %@", peers);
725 SecAKSDoWithUserBagLockAssertion(&localError, ^{
726 [self performTransaction:^(SOSAccountTransaction *txn) {
728 NSSet *peersSet = [NSSet setWithArray:peers];
729 CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError);
730 if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) {
733 CFReleaseNull(handledPeers);
735 res = SOSAccountRequestSyncWithAllPeers(txn, &localError);
739 complete(res, (__bridge NSError *)localError);
740 CFReleaseNull(localError);
743 - (void)triggerBackup:(NSArray<NSString *>* _Nullable)backupPeers complete:(void (^)(NSError *error))complete
745 __block CFErrorRef localError = NULL;
747 if (backupPeers.count == 0) {
748 SOSEngineRef engine = (SOSEngineRef) [self.kvs_message_transport SOSTransportMessageGetEngine];
749 backupPeers = CFBridgingRelease(SOSEngineCopyBackupPeerNames(engine, &localError));
753 [self triggerBackupForPeers:backupPeers];
756 complete((__bridge NSError *)localError);
757 CFReleaseNull(localError);
760 - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete
762 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
763 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
765 NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters];
766 complete(parameters, nil);
769 complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
773 - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete
775 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
776 NSError* error = nil;
777 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
779 [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error];
783 complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
791 - (void) flattenToSaveBlock {
792 if (self.saveBlock) {
793 NSError* error = nil;
794 NSData* saveData = [self encodedData:&error];
796 (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error);
800 CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) {
801 return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt);
804 CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) {
805 CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL);
806 return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict);
809 static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){
810 SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
811 //send new DSID over account changed
812 [account.circle_transport kvsSendOfficialDSID:dsid err:NULL];
816 void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) {
817 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
818 if(accountDSID == NULL) {
819 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
821 SOSAccountUpdateDSID(account, dsid);
822 } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
823 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
825 //DSID has changed, blast the account!
826 SOSAccountSetToNew(account);
828 //update DSID to the new DSID
829 SOSAccountUpdateDSID(account, dsid);
831 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
836 void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews)
838 [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews];
839 [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews];
842 #pragma clang diagnostic push
843 #pragma clang diagnostic ignored "-Wunused-value"
844 SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) {
845 SOSViewResultCode retval = kSOSCCGeneralViewError;
846 // The V0 view switches on and off all on it's own, we allow people the delusion
847 // of control and status if it's what we're stuck at., otherwise error.
848 if (SOSAccountSyncingV0(account)) {
849 require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
850 retval = kSOSCCViewMember;
852 require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
853 retval = kSOSCCViewNotMember;
858 #pragma clang diagnostic pop
860 SOSAccount* SOSAccountCreate(CFAllocatorRef allocator,
861 CFDictionaryRef gestalt,
862 SOSDataSourceFactoryRef factory) {
864 SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory];
865 [a ensureFactoryCircles];
866 SOSAccountEnsureUUID(a);
867 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate");
868 a.key_interests_need_updating = true;
873 static OSStatus do_delete(CFDictionaryRef query) {
876 result = SecItemDelete(query);
878 secerror("SecItemDelete: %d", (int)result);
884 do_keychain_delete_aks_bags()
887 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
888 kSecClass, kSecClassGenericPassword,
889 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
890 kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
891 kSecAttrService, CFSTR("SecureBackupService"),
892 kSecAttrSynchronizable, kCFBooleanTrue,
893 kSecUseTombstones, kCFBooleanFalse,
896 result = do_delete(item);
903 do_keychain_delete_identities()
906 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
907 kSecClass, kSecClassKey,
908 kSecAttrSynchronizable, kCFBooleanTrue,
909 kSecUseTombstones, kCFBooleanFalse,
910 kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
913 result = do_delete(item);
920 do_keychain_delete_lakitu()
923 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
924 kSecClass, kSecClassGenericPassword,
925 kSecAttrSynchronizable, kCFBooleanTrue,
926 kSecUseTombstones, kCFBooleanFalse,
927 kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
928 kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
929 kSecAttrService, CFSTR("EscrowService"),
932 result = do_delete(item);
939 do_keychain_delete_sbd()
942 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
943 kSecClass, kSecClassGenericPassword,
944 kSecAttrSynchronizable, kCFBooleanTrue,
945 kSecUseTombstones, kCFBooleanFalse,
946 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
949 result = do_delete(item);
955 void SOSAccountSetToNew(SOSAccount* a)
957 secnotice("accountChange", "Setting Account to New");
960 /* remove all syncable items */
961 result = do_keychain_delete_aks_bags(); (void) result;
962 secdebug("set to new", "result for deleting aks bags: %d", result);
964 result = do_keychain_delete_identities(); (void) result;
965 secdebug("set to new", "result for deleting identities: %d", result);
967 result = do_keychain_delete_lakitu(); (void) result;
968 secdebug("set to new", "result for deleting lakitu: %d", result);
970 result = do_keychain_delete_sbd(); (void) result;
971 secdebug("set to new", "result for deleting sbd: %d", result);
973 a.accountKeyIsTrusted = false;
975 if (a.user_private_timer) {
976 dispatch_source_cancel(a.user_private_timer);
977 a.user_private_timer = NULL;
978 xpc_transaction_end();
981 if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) {
982 notify_cancel(a.lock_notification_token);
983 a.lock_notification_token = NOTIFY_TOKEN_INVALID;
990 // update_interest_block;
992 SOSUnregisterTransportKeyParameter(a.key_transport);
993 SOSUnregisterTransportMessage(a.kvs_message_transport);
994 SOSUnregisterTransportCircle(a.circle_transport);
996 a.circle_transport = NULL;
997 a.kvs_message_transport = nil;
1000 a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
1002 [a ensureFactoryCircles]; // Does rings too
1004 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
1005 SOSAccountEnsureUUID(a);
1006 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew");
1007 a.key_interests_need_updating = true;
1010 dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) {
1011 return account.queue;
1014 void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){
1015 account.accountKeyIsTrusted = true;
1018 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
1020 SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error];
1021 if (!SOSAccountHasPublicKey(self, error)) {
1022 if(circleStatus == kSOSCCInCircle) {
1024 CFReleaseNull(*error);
1025 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);
1028 circleStatus = kSOSCCError;
1030 return circleStatus;
1033 -(bool) isInCircle:(CFErrorRef *)error
1035 SOSCCStatus result = [self getCircleStatus:error];
1036 if (result != kSOSCCInCircle) {
1037 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
1044 bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) {
1046 SOSAccountTrustClassic *trust = account.trust;
1047 NSMutableSet* retirees = trust.retirees;
1048 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
1049 CFSetSetValue((__bridge CFMutableSetRef) retirees, peer);
1050 CFErrorRef cleanupError = NULL;
1051 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) {
1052 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
1054 CFReleaseSafe(cleanupError);
1059 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) {
1060 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
1061 SOSPeerInfoRef me = account.peerInfo;
1062 bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
1064 SOSAccountTrustClassic *trust = account.trust;
1065 NSMutableSet* retirees = trust.retirees;
1067 if(!new_circle) return NULL;
1068 __block bool workDone = false;
1070 CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) {
1071 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
1072 if (isSOSPeerInfo(pi)) {
1073 SOSCircleUpdatePeerInfo(new_circle, pi);
1079 if(workDone && SOSCircleCountPeers(new_circle) == 0) {
1080 SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
1084 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
1085 if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) ||
1086 ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){
1087 CFReleaseNull(new_circle);
1090 account.notifyBackupOnExit = true;
1092 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
1093 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
1094 // we have a chance to set things right with a SetCreds/Join sequence. This will cause
1095 // handleUpdateCircle to return false.
1096 CFReleaseNull(new_circle);
1100 // This case is when we aren't an applicant and the circle is retirement-empty.
1101 secnotice("circleOps", "Reset to empty with last retirement");
1102 SOSCircleResetToEmpty(new_circle, NULL);
1110 // MARK: Circle Membership change notificaion
1113 void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1114 SOSAccountCircleMembershipChangeBlock copy = changeBlock;
1115 [a.change_blocks addObject:copy];
1118 void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1119 [a.change_blocks removeObject:changeBlock];
1122 void SOSAccountPurgeIdentity(SOSAccount* account) {
1123 SOSAccountTrustClassic *trust = account.trust;
1124 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1127 // Purge private key but don't return error if we can't.
1128 CFErrorRef purgeError = NULL;
1129 if (!SOSFullPeerInfoPurgePersistentKey(identity, &purgeError)) {
1130 secwarning("Couldn't purge persistent key for %@ [%@]", identity, purgeError);
1132 CFReleaseNull(purgeError);
1134 trust.fullPeerInfo = nil;
1138 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, NSData* parentData, CFErrorRef* error) {
1139 SOSAccountTrustClassic *trust = account.trust;
1140 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1141 NSMutableSet* retirees = trust.retirees;
1143 NSError* localError = nil;
1144 SFSignInAnalytics* parent = nil;
1147 parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentData error:&localError];
1150 SOSFullPeerInfoRef fpi = identity;
1151 if(!fpi) return false;
1153 CFErrorRef retiredError = NULL;
1155 bool retval = false;
1157 SFSignInAnalytics *promoteToRetiredEvent = nil;
1160 promoteToRetiredEvent = [parent newSubTaskForEvent:@"promoteToRetiredEvent"];
1163 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
1165 if(promoteToRetiredEvent) {
1166 [promoteToRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
1168 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
1170 *error = retiredError;
1172 CFReleaseNull(retiredError);
1175 if(promoteToRetiredEvent) {
1176 [promoteToRetiredEvent stopWithAttributes:nil];
1180 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1182 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1183 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1184 // Remove our application if we have one.
1185 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1186 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1187 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1188 CFErrorRef cleanupError = NULL;
1189 SFSignInAnalytics *cleanupEvent = nil;
1192 cleanupEvent = [parent newSubTaskForEvent:@"cleanupAfterPeerEvent"];
1194 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1195 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1198 [cleanupEvent stopWithAttributes:nil];
1200 CFReleaseSafe(cleanupError);
1204 // Store the retirement record locally.
1205 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1207 trust.retirees = retirees;
1209 // Write retirement to Transport
1210 CFErrorRef postError = NULL;
1211 SFSignInAnalytics *postRetirementEvent = nil;
1214 postRetirementEvent = [parent newSubTaskForEvent:@"postRestirementEvent"];
1217 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1218 if(postRetirementEvent) {
1219 [postRetirementEvent logRecoverableError:(__bridge NSError*)postError];
1221 secwarning("Couldn't post retirement (%@)", postError);
1223 if(postRetirementEvent) {
1224 [postRetirementEvent stopWithAttributes:nil];
1227 SFSignInAnalytics *flushChangesEvent = nil;
1229 flushChangesEvent = [parent newSubTaskForEvent:@"flushChangesEvent"];
1232 if(![account.circle_transport flushChanges:&postError]){
1233 if(flushChangesEvent) {
1234 [flushChangesEvent logRecoverableError:(__bridge NSError*)postError];
1236 secwarning("Couldn't flush retirement data (%@)", postError);
1238 if(flushChangesEvent) {
1239 [flushChangesEvent stopWithAttributes:nil];
1241 CFReleaseNull(postError);
1243 SFSignInAnalytics *purgeIdentityEvent = nil;
1245 purgeIdentityEvent = [parent newSubTaskForEvent:@"purgeIdentityEvent"];
1247 SOSAccountPurgeIdentity(account);
1248 if(purgeIdentityEvent) {
1249 [purgeIdentityEvent stopWithAttributes:nil];
1253 CFReleaseNull(retire_peer);
1257 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1258 bool result = false;
1259 if (account.circle_transport) {
1260 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1266 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1267 local value that has been overwritten by a distant value. If there is no
1268 conflict between the local and the distant values when doing the initial
1269 sync (e.g. if the cloud has no data stored or the client has not stored
1270 any data yet), you'll never see that notification.
1272 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1273 with server but initial round trip with server does not imply
1274 NSUbiquitousKeyValueStoreInitialSyncChange.
1279 // MARK: Status summary
1283 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1285 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1286 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1287 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1288 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1289 case kSOSCCError: return CFSTR("kSOSCCError");
1291 return CFSTR("kSOSCCError");
1293 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1294 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1295 return kSOSCCInCircle;
1296 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1297 return kSOSCCInCircle;
1298 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1299 return kSOSCCNotInCircle;
1300 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1301 return kSOSCCRequestPending;
1302 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1303 return kSOSCCCircleAbsent;
1304 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1311 // MARK: Account Reset Circles
1314 // This needs to be called within a [trust modifyCircle()] block
1317 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1318 bool retval = false;
1320 SOSAccountTrustClassic *trust = account.trust;
1321 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1323 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1325 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1326 if(SOSPeerInfoIsCloudIdentity(peer)) {
1327 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1329 CFSetAddValue(iCloud2Remove, peer);
1331 CFReleaseNull(icfpi);
1335 if(CFSetGetCount(iCloud2Remove) > 0) {
1337 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1339 CFReleaseNull(iCloud2Remove);
1344 // MARK: start backups
1348 bool SOSAccountEnsureInBackupRings(SOSAccount* account) {
1349 __block bool result = false;
1350 __block CFErrorRef error = NULL;
1351 secnotice("backup", "Ensuring in rings");
1353 if(!account.backup_key){
1357 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){
1358 secnotice("backupkey", "account backup key isn't valid: %@", error);
1359 account.backup_key = nil;
1360 CFReleaseNull(error);
1364 NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(account.peerInfo);
1365 if(![peerBackupKey isEqual:account.backup_key]) {
1366 result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1367 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error);
1370 secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error);
1371 CFReleaseNull(error);
1376 // It's a good key, we're going with it. Stop backing up the old way.
1377 CFErrorRef localError = NULL;
1378 if (!SOSDeleteV0Keybag(&localError)) {
1379 secerror("Failed to delete v0 keybag: %@", localError);
1381 CFReleaseNull(localError);
1383 // Setup backups the new way.
1384 SOSAccountForEachBackupView(account, ^(const void *value) {
1385 CFStringRef viewName = asString(value, NULL);
1386 bool resetRing = SOSAccountValidateBackupRingForView(account, viewName, NULL);
1388 SOSAccountUpdateBackupRing(account, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
1389 SOSRingRef newRing = SOSAccountCreateBackupRingForView(account, viewName, error);
1396 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1398 CFReleaseNull(error);
1403 // MARK: Recovery Public Key Functions
1406 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1407 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1408 if(retval) secnotice("recovery", "successfully registered recovery public key");
1409 else secnotice("recovery", "could not register recovery public key: %@", *error);
1410 SOSClearErrorIfTrue(retval, error);
1414 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1415 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1416 if(retval) secnotice("recovery", "RK Cleared");
1417 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1418 SOSClearErrorIfTrue(retval, error);
1422 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1423 CFDataRef result = NULL;
1424 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1425 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1427 if (!isData(result)) {
1428 CFReleaseNull(result);
1430 SOSClearErrorIfTrue(result != NULL, error);
1439 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key, bool use_cloud_peer, NSData* parentEvent, CFErrorRef* error) {
1440 SOSAccount* account = aTxn.account;
1441 SOSAccountTrustClassic *trust = account.trust;
1442 __block bool result = false;
1443 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1444 SFSignInAnalytics *ensureFullPeerAvailableEvent = nil;
1445 NSError* localError = nil;
1446 SFSignInAnalytics* parent = nil;
1449 parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1452 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1454 ensureFullPeerAvailableEvent = [parent newSubTaskForEvent:@"ensureFullPeerAvailableEvent"];
1456 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1457 if(ensureFullPeerAvailableEvent) {
1458 [ensureFullPeerAvailableEvent stopWithAttributes:nil];
1461 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1462 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1463 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1464 // this also clears initial sync data
1465 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1467 SFSignInAnalytics *acceptApplicantEvent = nil;
1469 SOSAccountInitializeInitialSync(account);
1470 if (use_cloud_peer) {
1471 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1474 acceptApplicantEvent = [parent newSubTaskForEvent:@"acceptApplicantEvent"];
1476 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1477 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1478 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1479 trust.departureCode = kSOSNeverLeftCircle;
1480 if(result && cloud_full_peer) {
1481 CFErrorRef localError = NULL;
1482 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1483 require_quiet(cloudid, finish);
1484 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1485 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1488 if(acceptApplicantEvent) {
1489 [acceptApplicantEvent logRecoverableError:(__bridge NSError *)(localError)];
1491 secerror("Failed to join with cloud identity: %@", localError);
1492 CFReleaseNull(localError);
1497 if(acceptApplicantEvent) {
1498 [acceptApplicantEvent stopWithAttributes:nil];
1500 if (use_cloud_peer) {
1501 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1502 if(acceptApplicantEvent) {
1503 SFSignInAnalytics *updateOutOfDateSyncViewsEvent = [acceptApplicantEvent newSubTaskForEvent:@"updateOutOfDateSyncViewsEvent"];
1504 [updateOutOfDateSyncViewsEvent stopWithAttributes:nil];
1509 CFReleaseNull(cloud_full_peer);
1513 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, NSData* parentEvent, CFErrorRef* error) {
1514 SOSAccount* account = aTxn.account;
1515 SOSAccountTrustClassic *trust = account.trust;
1516 bool success = false;
1518 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1519 require_quiet(user_key, done); // Fail if we don't get one.
1521 if(!trust.trustedCircle || SOSCircleCountPeers(trust.trustedCircle) == 0 ) {
1522 secnotice("resetToOffering", "Resetting circle to offering because it's empty and we're joining");
1523 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1526 if(SOSCircleHasPeerWithID(trust.trustedCircle, (__bridge CFStringRef)(account.peerID), NULL)) {
1527 // We shouldn't be at this point if we're already in circle.
1528 secnotice("circleops", "attempt to join a circle we're in - continuing.");
1529 return true; // let things above us think we're in circle - because we are.
1532 if(!SOSCircleVerify(trust.trustedCircle, account.accountKey, NULL)) {
1533 secnotice("resetToOffering", "Resetting circle to offering since we are new and it doesn't verify with current userKey");
1534 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1537 if (trust.fullPeerInfo != NULL) {
1538 SOSPeerInfoRef myPeer = trust.peerInfo;
1539 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1540 require_quiet(!success, done);
1542 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1544 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1545 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1547 trust.fullPeerInfo = NULL;
1551 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, parentEvent, error);
1553 require_quiet(success, done);
1555 trust.departureCode = kSOSNeverLeftCircle;
1561 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1562 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1563 return SOSAccountJoinCircles_internal(aTxn, false, parentEvent, error);
1566 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1567 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1568 return SOSAccountJoinCircles_internal(aTxn, true, parentEvent, error);
1571 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, NSData* parentEvent, CFErrorRef* error)
1574 NSError* localError = nil;
1575 SFSignInAnalytics* parent = nil;
1578 parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1581 bool result = false;
1582 CFMutableSetRef peersToRemove = NULL;
1583 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1585 secnotice("circleOps", "Can't remove without userKey");
1588 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1589 SOSPeerInfoRef me = account.peerInfo;
1590 if(!(me_full && me))
1592 secnotice("circleOps", "Can't remove without being active peer");
1593 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1597 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1599 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1602 CFReleaseNull(peersToRemove);
1603 secnotice("circleOps", "No peerSet to remove");
1607 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1608 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1609 CFSetRemoveValue(peersToRemove, me);
1611 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1612 bool success = false;
1614 if(CFSetGetCount(peersToRemove) != 0) {
1615 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1617 SFSignInAnalytics *generationSignatureUpdateEvent = nil;
1619 generationSignatureUpdateEvent = [parent newSubTaskForEvent:@"generationSignatureUpdateEvent"];
1622 success = SOSAccountGenerationSignatureUpdate(account, error);
1623 if(error && *error){
1624 NSError* signatureUpdateError = (__bridge NSError*)*error;
1625 if(generationSignatureUpdateEvent) {
1626 [generationSignatureUpdateEvent logUnrecoverableError:signatureUpdateError];
1629 if(generationSignatureUpdateEvent) {
1630 [generationSignatureUpdateEvent stopWithAttributes:nil];
1632 } else success = true;
1634 if (success && leaveCircle) {
1635 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1636 success = sosAccountLeaveCircle(account, circle, parentEvent, error);
1645 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1646 secnotice("circleOps", "Removed Peers from circle %@", description);
1650 CFReleaseNull(peersToRemove);
1654 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1655 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1656 dispatch_group_t group = dispatch_group_create();
1657 SOSAccountTrustClassic *trust = account.trust;
1658 __block bool result = false;
1659 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1660 // Add a task to the group
1661 dispatch_group_async(group, queue, ^{
1662 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1663 secnotice("circleOps", "Leaving circle by client request (Bail)");
1664 return sosAccountLeaveCircle(account, circle, nil, error);
1667 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1668 dispatch_group_wait(group, milestone);
1670 trust.departureCode = kSOSWithdrewMembership;
1677 // MARK: Application
1680 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1681 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1682 SOSAccountTrustClassic *trust = account.trust;
1684 SOSPeerInfoRef me = trust.peerInfo;
1685 CFErrorRef peer_error = NULL;
1686 if (trust.trustedCircle && me &&
1687 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1688 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1689 __block bool modified = false;
1690 CFArrayForEach(peer_infos, ^(const void *value) {
1691 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1692 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1693 if (action(circle, trust.fullPeerInfo, peer)) {
1702 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1703 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1706 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1707 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1711 __block int64_t acceptedPeers = 0;
1713 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1714 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1720 if (acceptedPeers == CFArrayGetCount(applicants))
1725 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1726 __block bool success = true;
1727 __block int64_t num_peers = 0;
1729 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1730 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1734 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1741 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1742 SOSAccountTrustClassic *trust = account.trust;
1743 return trust.departureCode;
1746 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1747 SOSAccountTrustClassic *trust = account.trust;
1748 trust.departureCode = reason;
1752 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1753 CFArrayRef result = NULL;
1754 CFNumberRef generation = NULL;
1755 SOSAccountTrustClassic *trust = account.trust;
1757 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1758 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1760 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1761 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1767 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1768 if (!SOSAccountHasPublicKey(account, error))
1771 return account.accountKeyIsTrusted;
1774 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1775 // TODO: this result is never set or used
1777 SOSAccountTrustClassic *trust = account.trust;
1779 secnotice("updates", "Ensuring peer registration.");
1782 secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
1786 if([account getCircleStatus: NULL] != kSOSCCInCircle) {
1790 // If we are not in the circle, there is no point in setting up peers
1791 if(!SOSAccountIsMyPeerActive(account, NULL)) {
1795 // This code only uses the SOSFullPeerInfoRef for two things:
1796 // - Finding out if this device is in the trusted circle
1797 // - Using the peerID for this device to see if the current peer is "me"
1798 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
1800 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
1802 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
1803 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
1804 CFErrorRef localError = NULL;
1806 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
1808 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
1809 CFReleaseSafe(localError);
1817 // Value manipulation
1820 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
1821 SOSAccountTrustClassic *trust = account.trust;
1822 if (!trust.expansion) {
1825 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
1830 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
1831 bool success = false;
1833 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
1834 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
1839 void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) {
1840 if (![account isInCircle:NULL]) {
1843 __block bool updateRings = false;
1844 SOSAccountTrustClassic *trust = account.trust;
1845 [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
1846 __block bool updated = false;
1847 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
1848 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
1850 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
1852 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
1853 CFErrorRef cleanupError = NULL;
1854 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
1855 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
1856 CFReleaseSafe(cleanupError);
1863 SOSAccountProcessBackupRings(account, NULL);
1867 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
1869 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
1871 __block CFTypeRef object = NULL;
1873 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1874 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1876 CloudKeychainReplyBlock replyBlock =
1877 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
1879 object = returnedValues;
1884 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
1886 dispatch_semaphore_signal(waitSemaphore);
1889 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
1891 dispatch_semaphore_wait(waitSemaphore, finishTime);
1892 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
1897 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
1901 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
1903 CFStringRef uuid = SOSAccountCopyUUID(account);
1904 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1905 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1907 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1909 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
1911 dispatch_semaphore_signal(waitSemaphore);
1914 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
1915 dispatch_semaphore_wait(waitSemaphore, finishTime);
1916 CFReleaseNull(uuid);
1919 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
1921 NSDate *now = [NSDate date];
1922 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
1924 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
1926 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
1928 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
1930 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
1931 CFStringAppend(timeDescription, decription);
1933 CFStringAppend(timeDescription, CFSTR("]"));
1935 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
1936 CFReleaseNull(timeDescription);
1938 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1939 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1940 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1942 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1944 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
1946 dispatch_semaphore_signal(waitSemaphore);
1949 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
1950 dispatch_semaphore_wait(waitSemaphore, finishTime);
1953 // set the cleanup frequency to 3 days.
1954 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
1956 //Get all the key/values in KVS and remove old entries
1957 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
1959 // This should only happen on some number of days
1960 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
1961 NSDate *now = [NSDate date];
1962 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
1964 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
1968 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1970 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
1971 NSMutableArray *peerIDs = [NSMutableArray array];
1972 NSMutableArray *keysToRemove = [NSMutableArray array];
1974 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
1975 CFArrayForEach(peers, ^(const void *value) {
1976 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
1977 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
1979 //any peerid that is not ours gets added
1980 if(![[account.trust peerID] isEqualToString:peerID])
1981 [peerIDs addObject:peerID];
1983 CFReleaseNull(peers);
1985 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
1986 __block bool keyMatchesPeerID = false;
1988 //checks for full peer ids
1989 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
1990 //if key contains peerid of one active peer
1991 if([KVSKey containsString:PeerID]){
1992 secnotice("key-cleanup","key: %@", KVSKey);
1993 keyMatchesPeerID = true;
1996 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
1997 || [KVSKey hasPrefix:@"po"])
1998 [keysToRemove addObject:KVSKey];
2001 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
2002 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
2004 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
2006 //add last cleanup timestamp
2007 SOSAccountWriteLastCleanupTimestampToKVS(account);
2012 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2013 SOSPeerInfoRef applicant = NULL;
2014 SOSAccountTrustClassic *trust = account.trust;
2015 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2016 if(!userKey) return false;
2017 if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
2019 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2021 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2028 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2030 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2031 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2032 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2033 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2035 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2038 if([parentUUID isEqualToString:viewUUID] || authoriative){
2040 /* check if we already have this entry */
2041 if ([seenUUID containsObject:viewUUID])
2044 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2045 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2050 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2052 NSMutableDictionary* strippedDown = [@{
2053 (id)kSecValueData : key,
2054 (id)kSecAttrServer : viewName,
2055 (id)kSecAttrAccount : viewUUID
2058 strippedDown[@"auth"] = @YES;
2060 [results addObject:strippedDown];
2061 [seenUUID addObject:viewUUID];
2067 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
2070 CKKSViewManager* manager = [CKKSViewManager manager];
2072 NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
2074 for (NSString *view in items) {
2075 NSString *uuid = items[view];
2076 NSDictionary *query = @{
2077 (id)kSecClass : (id)kSecClassInternetPassword,
2078 (id)kSecUseDataProtectionKeychain : @YES,
2079 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2080 (id)kSecAttrAccount : uuid,
2081 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2082 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2083 (id)kSecReturnAttributes: @YES,
2084 (id)kSecReturnData: @YES,
2086 CFTypeRef result = NULL;
2087 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2088 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
2090 CFReleaseNull(result);
2096 NSArray<NSDictionary *>*
2097 SOSAccountGetAllTLKs(void)
2099 CFTypeRef result = NULL;
2100 NSMutableArray<NSDictionary *>* results = [NSMutableArray array];
2101 NSMutableSet *seenUUID = [NSMutableSet set];
2103 // first use the TLK from the view manager
2104 AddViewManagerResults(results, seenUUID);
2106 //try to grab tlk-piggy items
2107 NSDictionary* query = @{
2108 (id)kSecClass : (id)kSecClassInternetPassword,
2109 (id)kSecUseDataProtectionKeychain : @YES,
2110 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2111 (id)kSecAttrDescription: @"tlk",
2112 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2113 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2114 (id)kSecReturnAttributes: @YES,
2115 (id)kSecReturnData: @YES,
2118 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2119 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2121 CFReleaseNull(result);
2123 //try to grab tlk-piggy items
2125 (id)kSecClass : (id)kSecClassInternetPassword,
2126 (id)kSecUseDataProtectionKeychain : @YES,
2127 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2128 (id)kSecAttrDescription: @"tlk-piggy",
2129 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2130 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2131 (id)kSecReturnAttributes: @YES,
2132 (id)kSecReturnData: @YES,
2135 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2136 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2138 CFReleaseNull(result);
2140 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2145 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2146 const uint8_t *der, uint8_t *der_end)
2148 if (type != kTLKUnknown) {
2149 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2150 piggy_encode_data(keychainData, der,
2151 piggy_encode_data(uuid, der,
2152 ccder_encode_uint64((uint64_t)type, der, der_end))));
2154 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2155 piggy_encode_data(keychainData, der,
2156 piggy_encode_data(uuid, der,
2157 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2161 static uint8_t* piggy_encode_data(NSData* data,
2162 const uint8_t *der, uint8_t *der_end)
2164 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2165 ccder_encode_body(data.length, data.bytes, der, der_end));
2169 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2171 name2type(NSString *view)
2173 if ([view isEqualToString:@"Manatee"])
2175 else if ([view isEqualToString:@"Engram"])
2177 else if ([view isEqualToString:@"AutoUnlock"])
2178 return kTLKAutoUnlock;
2179 if ([view isEqualToString:@"Health"])
2184 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2186 rank_type(NSString *view)
2188 if ([view isEqualToString:@"Manatee"])
2190 else if ([view isEqualToString:@"Engram"])
2192 else if ([view isEqualToString:@"AutoUnlock"])
2194 if ([view isEqualToString:@"Health"])
2200 parse_uuid(NSString *uuidString)
2202 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2204 [uuid getUUIDBytes:uuidblob];
2205 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2208 piggy_sizeof_data(NSData* data)
2210 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2213 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2214 if (type != kTLKUnknown) {
2215 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2216 piggy_sizeof_data(keychainData) +
2217 piggy_sizeof_data(uuid) +
2218 ccder_sizeof_uint64(type));
2220 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2221 piggy_sizeof_data(keychainData) +
2222 piggy_sizeof_data(uuid) +
2223 der_sizeof_string((__bridge CFStringRef)name, NULL));
2227 NSArray<NSDictionary*>*
2228 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2230 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2232 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2233 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2234 if (obj1[@"auth"] != NULL)
2236 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2237 if (obj2[@"auth"] != NULL)
2241 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2242 * since we are sorting backward, the Ascending/Descending looks wrong below.
2244 if (rank1 > rank2) {
2245 return NSOrderedAscending;
2246 } else if (rank1 < rank2) {
2247 return NSOrderedDescending;
2249 return NSOrderedSame;
2255 static NSArray<NSData *> *
2256 build_tlks(NSArray<NSDictionary*>* tlks)
2258 NSMutableArray *array = [NSMutableArray array];
2259 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2261 for (NSDictionary *item in sortedTLKs) {
2262 NSData* keychainData = item[(__bridge id)kSecValueData];
2263 NSString* name = item[(__bridge id)kSecAttrServer];
2264 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2265 NSData* uuid = parse_uuid(uuidString);
2267 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2269 unsigned char *der = [tlk mutableBytes];
2270 unsigned char *der_end = der + [tlk length];
2272 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2275 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2277 [array addObject:tlk];
2282 static NSArray<NSData *> *
2283 build_identities(NSArray<NSData *>* identities)
2285 NSMutableArray *array = [NSMutableArray array];
2286 for (NSData *item in identities) {
2287 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2289 unsigned char *der = [ident mutableBytes];
2290 unsigned char *der_end = der + [ident length];
2292 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2293 [array addObject:ident];
2300 static unsigned char *
2301 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2303 unsigned char *body_end = der_end;
2304 for (NSData *datum in data) {
2305 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2306 if (der_end == NULL)
2309 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2312 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2314 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2315 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2316 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2319 static NSData *encode_piggy(size_t IdentitiesBudget,
2321 NSArray<NSData*>* identities,
2322 NSArray<NSDictionary*>* tlks)
2324 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2325 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2326 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2327 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2328 size_t payloadSize = 0, identitiesSize = 0;
2329 NSMutableData *result = NULL;
2331 for (NSData *tlk in encodedTLKs) {
2332 if (TLKBudget - payloadSize < [tlk length])
2334 [budgetArray addObject:tlk];
2335 payloadSize += tlk.length;
2337 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2339 for (NSData *ident in encodedIdentities) {
2340 if (IdentitiesBudget - identitiesSize < [ident length])
2342 [identitiesArray addObject:ident];
2343 identitiesSize += ident.length;
2345 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2348 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2350 result = [NSMutableData dataWithLength:piggySize];
2352 unsigned char *der = [result mutableBytes];
2353 unsigned char *der_end = der + [result length];
2355 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2356 encode_data_array(identitiesArray, der,
2357 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2363 static const size_t SOSCCIdentitiesBudget = 120;
2364 static const size_t SOSCCTLKBudget = 500;
2367 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2369 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2372 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2374 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2376 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2377 if(SOSPeerInfoIsCloudIdentity(peer)) {
2378 CFArrayAppendValue(identities, peer);
2384 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) {
2386 NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array];
2387 NSArray<NSDictionary *>* tlks = nil;
2389 if (flags & kSOSInitialSyncFlagiCloudIdentity) {
2390 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2391 secnotice("piggy", "identities: %@", identities);
2393 CFIndex i, count = CFArrayGetCount(identities);
2394 for (i = 0; i < count; i++) {
2395 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2396 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2398 [encodedIdenities addObject:data];
2400 CFRelease(identities);
2403 if (flags & kSOSInitialSyncFlagTLKs) {
2404 tlks = SOSAccountGetAllTLKs();
2407 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2410 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
2411 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
2412 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
2413 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
2414 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
2415 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
2416 if(accountName == NULL) {
2417 accountName = CFSTR("Unavailable");
2419 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
2421 secnotice("circleOps",
2422 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
2423 operation, accountName, version, gencount, pkeyID, sigID, circleHash);
2424 CFReleaseNull(pkeyID);
2425 CFReleaseNull(sigID);
2426 CFReleaseNull(circleHash);
2429 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2430 SOSGenCountRef gencount = NULL;
2431 CFDataRef signature = NULL;
2432 SecKeyRef ourKey = NULL;
2434 CFDataRef pbblob = NULL;
2435 SOSCircleRef prunedCircle = NULL;
2437 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2439 SOSCCStatus circleStat = [account getCircleStatus:error];
2440 if(circleStat != kSOSCCInCircle) {
2441 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
2445 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2446 require_quiet(userKey, errOut);
2448 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2449 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
2450 secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
2453 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2454 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2455 require_quiet(ourKey, errOut);
2458 SOSCircleRef currentCircle = [account.trust getCircle:error];
2459 require_quiet(currentCircle, errOut);
2461 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2462 require_quiet(prunedCircle, errOut);
2463 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2465 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2467 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2468 require_quiet(signature, errOut);
2469 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
2470 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2473 CFReleaseNull(prunedCircle);
2474 CFReleaseNull(gencount);
2475 CFReleaseNull(signature);
2476 CFReleaseNull(ourKey);
2478 if(!pbblob && error != NULL) {
2479 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
2485 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2486 bool retval = false;
2487 SecKeyRef userKey = NULL;
2488 SOSAccountTrustClassic *trust = account.trust;
2489 SOSGenCountRef gencount = NULL;
2490 CFDataRef signature = NULL;
2491 SecKeyRef pubKey = NULL;
2492 bool setInitialSyncTimeoutToV0 = false;
2494 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
2496 if (!isData(joiningBlob)) {
2497 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
2501 userKey = SOSAccountGetPrivateCredential(account, error);
2503 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2507 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
2508 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2512 if(setInitialSyncTimeoutToV0){
2513 secnotice("circleOps", "setting flag in account for piggyback v0");
2514 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2516 secnotice("circleOps", "clearing flag in account for piggyback v0");
2517 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2519 SOSAccountInitializeInitialSync(account);
2521 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
2523 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2524 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2528 trust.fullPeerInfo, error);;
2531 CFReleaseNull(gencount);
2532 CFReleaseNull(pubKey);
2533 CFReleaseNull(signature);
2538 static char boolToChars(bool val, char truechar, char falsechar) {
2539 return val? truechar: falsechar;
2542 #define ACCOUNTLOGSTATE "accountLogState"
2543 void SOSAccountLogState(SOSAccount* account) {
2544 bool hasPubKey = account.accountKey != NULL;
2545 SOSAccountTrustClassic *trust = account.trust;
2546 bool pubTrusted = account.accountKeyIsTrusted;
2547 bool hasPriv = account.accountPrivateKey != NULL;
2548 SOSCCStatus stat = [account getCircleStatus:NULL];
2550 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2551 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2553 secnotice(ACCOUNTLOGSTATE, "Start");
2555 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2556 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2558 SOSAccountGetSOSCCStatusString(stat)
2560 CFReleaseNull(userPubKeyID);
2561 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2562 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2565 void SOSAccountLogViewState(SOSAccount* account) {
2566 bool isInCircle = [account.trust isInCircleOnly:NULL];
2567 require_quiet(isInCircle, imOut);
2568 SOSPeerInfoRef mpi = account.peerInfo;
2569 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2570 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2572 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2573 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2574 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2575 boolToChars(isInitialComplete, 'I', 'i'),
2576 boolToChars(isBackupComplete, 'B', 'b'),
2579 CFReleaseNull(views);
2580 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2581 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2582 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2584 CFReleaseNull(unsyncedViews);
2587 secnotice(ACCOUNTLOGSTATE, "Finish");
2593 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2594 if(!isString(serial)) return;
2595 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2596 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2597 [account.trust updateV2Dictionary:account v2:newv2dict];
2600 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2602 secnotice("otrtimer", "timer fired!");
2603 CFErrorRef error = NULL;
2604 SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
2606 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2607 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2608 if(SOSCoderIsCoderInAwaitingState(coder)){
2609 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2610 CFErrorRef localError = NULL;
2611 SOSCoderReset(coder);
2612 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2613 secerror("Attempt to recover coder failed to restart: %@", localError);
2616 secnotice("otrtimer", "coder restarted!");
2617 SOSEngineSetCodersNeedSaving(engine, true);
2618 SOSPeerSetMustSendMessage(peer, true);
2619 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2621 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2622 SOSPeerRemoveOTRTimerEntry(peer);
2623 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2624 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2627 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2632 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2634 CFReleaseNull(error);
2637 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2639 __block SOSAccount* account = txn.account;
2640 CFErrorRef error = NULL;
2642 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2643 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2645 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2646 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2647 CFErrorRef error = NULL;
2648 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2651 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2652 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2654 if(!sendResult || error){
2655 secnotice("ratelimit", "could not send message: %@", error);
2658 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2659 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2660 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2665 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2667 CFReleaseNull(error);
2676 OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup";
2678 OctagonState* SOSStateReady = (OctagonState*)@"ready";
2679 OctagonState* SOSStateError = (OctagonState*)@"error";
2680 OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup";
2682 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void) {
2683 static NSDictionary<OctagonState*, NSNumber*>* map = nil;
2684 static dispatch_once_t onceToken;
2685 dispatch_once(&onceToken, ^{
2689 SOSStatePerformBackup: @2U,
2695 static NSSet<OctagonFlag*>* SOSFlagsSet(void) {
2696 static NSSet<OctagonFlag*>* set = nil;
2697 static dispatch_once_t onceToken;
2698 dispatch_once(&onceToken, ^{
2699 set = [NSSet setWithArray:@[
2700 SOSFlagTriggerBackup
2708 + (NSURL *)urlForSOSAccountSettings {
2709 return (__bridge NSURL *)SecCopyURLForFileInKeychainDirectory(CFSTR("SOSAccountSettings.pb"));
2713 - (void)setupStateMachine {
2716 self.accountConfiguration = [[CKKSPBFileStorage alloc] initWithStoragePath:[[self class] urlForSOSAccountSettings]
2717 storageClass:[SOSAccountConfiguration class]];
2719 NSAssert(self.stateMachine == nil, @"cant bootstrap more the once");
2721 self.stateMachineQueue = dispatch_queue_create("SOS-statemachine", NULL);
2723 self.stateMachine = [[OctagonStateMachine alloc] initWithName:@"sosaccount"
2724 states:[NSSet setWithArray:[SOSStateMap() allKeys]]
2726 initialState:SOSStateReady
2727 queue:self.stateMachineQueue
2729 lockStateTracker:[CKKSLockStateTracker globalTracker]];
2732 self.performBackups = [[CKKSNearFutureScheduler alloc] initWithName:@"performBackups"
2733 initialDelay:5*NSEC_PER_SEC
2734 continuingDelay:30*NSEC_PER_SEC
2735 keepProcessAlive:YES
2736 dependencyDescriptionCode:CKKSResultDescriptionNone
2739 [self addBackupFlag];
2742 SOSAccountConfiguration *conf = self.accountConfiguration.storage;
2744 if (conf.pendingBackupPeers.count) {
2745 [self addBackupFlag];
2748 [self.stateMachine startOperation];
2752 - (void)addBackupFlag {
2753 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup
2754 conditions:OctagonPendingConditionsDeviceUnlocked];
2755 [self.stateMachine handlePendingFlag:pendingFlag];
2758 - (void)triggerBackupForPeers:(NSArray<NSString*>*)backupPeers
2760 dispatch_sync(self.stateMachineQueue, ^{
2761 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2763 NSMutableSet *pending = [NSMutableSet set];
2764 if (storage.pendingBackupPeers) {
2765 [pending addObjectsFromArray:storage.pendingBackupPeers];
2768 [pending addObjectsFromArray:backupPeers];
2770 storage.pendingBackupPeers = [[pending allObjects] mutableCopy];
2771 [self.accountConfiguration setStorage:storage];
2772 [self.performBackups trigger];
2773 secnotice("sos-sm", "trigger backup for peers: %@", backupPeers);
2777 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
2778 flags:(nonnull OctagonFlags *)flags
2779 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
2781 dispatch_assert_queue(self.stateMachineQueue);
2785 secnotice("sos-sm", "currentState: %@ [flags: %@]", currentState, flags);
2787 if ([currentState isEqualToString:SOSStateReady]) {
2788 if([flags _onqueueContains:SOSFlagTriggerBackup]) {
2789 [flags _onqueueRemoveFlag:SOSFlagTriggerBackup];
2790 return [OctagonStateTransitionOperation named:@"perform-backup-flag"
2791 entering:SOSStatePerformBackup];
2793 secnotice("sos-sm", "Entering state ready");
2795 } else if ([currentState isEqualToString:SOSStateError]) {
2796 secnotice("sos-sm", "Entering state error");
2798 } else if ([currentState isEqualToString:SOSStatePerformBackup]) {
2800 return [OctagonStateTransitionOperation named:@"perform-backup-state"
2801 intending:SOSStateReady
2802 errorState:SOSStateError
2803 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) {
2805 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2807 secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers);
2809 if (storage.pendingBackupPeers.count) {
2810 SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers);
2811 [storage clearPendingBackupPeers];
2813 [self.accountConfiguration setStorage:storage];
2815 op.nextState = SOSStateReady;