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 sosAccountLeaveCircleWithAnalytics(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 = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentData error:&localError];
1146 SOSFullPeerInfoRef fpi = identity;
1147 if(!fpi) return false;
1149 CFErrorRef retiredError = NULL;
1151 bool retval = false;
1153 SFSignInAnalytics *promoteToRetiredEvent = [parent newSubTaskForEvent:@"promoteToRetiredEvent"];
1154 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
1156 [promoteToRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
1157 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
1159 *error = retiredError;
1161 CFReleaseNull(retiredError);
1164 [promoteToRetiredEvent stopWithAttributes:nil];
1167 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1169 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1170 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1171 // Remove our application if we have one.
1172 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1173 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1174 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1175 CFErrorRef cleanupError = NULL;
1176 SFSignInAnalytics *cleanupEvent = [parent newSubTaskForEvent:@"cleanupAfterPeerEvent"];
1177 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1178 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1180 [cleanupEvent stopWithAttributes:nil];
1181 CFReleaseSafe(cleanupError);
1185 // Store the retirement record locally.
1186 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1188 trust.retirees = retirees;
1190 // Write retirement to Transport
1191 CFErrorRef postError = NULL;
1192 SFSignInAnalytics *postRestirementEvent = [parent newSubTaskForEvent:@"postRestirementEvent"];
1193 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1194 [postRestirementEvent logRecoverableError:(__bridge NSError*)postError];
1195 secwarning("Couldn't post retirement (%@)", postError);
1197 [postRestirementEvent stopWithAttributes:nil];
1199 SFSignInAnalytics *flushChangesEvent = [parent newSubTaskForEvent:@"flushChangesEvent"];
1201 if(![account.circle_transport flushChanges:&postError]){
1202 [flushChangesEvent logRecoverableError:(__bridge NSError*)postError];
1203 secwarning("Couldn't flush retirement data (%@)", postError);
1205 [flushChangesEvent stopWithAttributes:nil];
1206 CFReleaseNull(postError);
1208 SFSignInAnalytics *purgeIdentityEvent = [parent newSubTaskForEvent:@"purgeIdentityEvent"];
1209 SOSAccountPurgeIdentity(account);
1210 [purgeIdentityEvent stopWithAttributes:nil];
1213 CFReleaseNull(retire_peer);
1217 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) {
1218 SOSAccountTrustClassic *trust = account.trust;
1219 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1220 NSMutableSet* retirees = trust.retirees;
1222 SOSFullPeerInfoRef fpi = identity;
1223 if(!fpi) return false;
1225 CFErrorRef localError = NULL;
1227 bool retval = false;
1229 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
1231 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1233 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1234 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1235 // Remove our application if we have one.
1236 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1237 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1238 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1239 CFErrorRef cleanupError = NULL;
1240 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1241 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1243 CFReleaseSafe(cleanupError);
1247 // Store the retirement record locally.
1248 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1250 trust.retirees = retirees;
1252 // Write retirement to Transport
1253 CFErrorRef postError = NULL;
1254 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1255 secwarning("Couldn't post retirement (%@)", postError);
1257 if(![account.circle_transport flushChanges:&postError]){
1258 secwarning("Couldn't flush retirement data (%@)", postError);
1260 CFReleaseNull(postError);
1263 SOSAccountPurgeIdentity(account);
1267 CFReleaseNull(localError);
1268 CFReleaseNull(retire_peer);
1272 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1273 bool result = false;
1274 if (account.circle_transport) {
1275 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1281 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1282 local value that has been overwritten by a distant value. If there is no
1283 conflict between the local and the distant values when doing the initial
1284 sync (e.g. if the cloud has no data stored or the client has not stored
1285 any data yet), you'll never see that notification.
1287 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1288 with server but initial round trip with server does not imply
1289 NSUbiquitousKeyValueStoreInitialSyncChange.
1294 // MARK: Status summary
1298 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1300 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1301 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1302 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1303 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1304 case kSOSCCError: return CFSTR("kSOSCCError");
1306 return CFSTR("kSOSCCError");
1308 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1309 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1310 return kSOSCCInCircle;
1311 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1312 return kSOSCCInCircle;
1313 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1314 return kSOSCCNotInCircle;
1315 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1316 return kSOSCCRequestPending;
1317 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1318 return kSOSCCCircleAbsent;
1319 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1326 // MARK: Account Reset Circles
1329 // This needs to be called within a [trust modifyCircle()] block
1332 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1333 bool retval = false;
1335 SOSAccountTrustClassic *trust = account.trust;
1336 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1338 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1340 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1341 if(SOSPeerInfoIsCloudIdentity(peer)) {
1342 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1344 CFSetAddValue(iCloud2Remove, peer);
1346 CFReleaseNull(icfpi);
1350 if(CFSetGetCount(iCloud2Remove) > 0) {
1352 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1354 CFReleaseNull(iCloud2Remove);
1359 // MARK: start backups
1363 bool SOSAccountEnsureInBackupRings(SOSAccount* account) {
1364 __block bool result = false;
1365 __block CFErrorRef error = NULL;
1366 secnotice("backup", "Ensuring in rings");
1368 if(!account.backup_key){
1372 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){
1373 secnotice("backupkey", "account backup key isn't valid: %@", error);
1374 account.backup_key = nil;
1375 CFReleaseNull(error);
1379 NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(account.peerInfo);
1380 if(![peerBackupKey isEqual:account.backup_key]) {
1381 result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1382 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error);
1385 secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error);
1386 CFReleaseNull(error);
1391 // It's a good key, we're going with it. Stop backing up the old way.
1392 CFErrorRef localError = NULL;
1393 if (!SOSDeleteV0Keybag(&localError)) {
1394 secerror("Failed to delete v0 keybag: %@", localError);
1396 CFReleaseNull(localError);
1398 // Setup backups the new way.
1399 SOSAccountForEachBackupView(account, ^(const void *value) {
1400 CFStringRef viewName = asString(value, NULL);
1401 bool resetRing = SOSAccountValidateBackupRingForView(account, viewName, NULL);
1403 SOSAccountUpdateBackupRing(account, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
1404 SOSRingRef newRing = SOSAccountCreateBackupRingForView(account, viewName, error);
1411 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1413 CFReleaseNull(error);
1418 // MARK: Recovery Public Key Functions
1421 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1422 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1423 if(retval) secnotice("recovery", "successfully registered recovery public key");
1424 else secnotice("recovery", "could not register recovery public key: %@", *error);
1425 SOSClearErrorIfTrue(retval, error);
1429 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1430 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1431 if(retval) secnotice("recovery", "RK Cleared");
1432 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1433 SOSClearErrorIfTrue(retval, error);
1437 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1438 CFDataRef result = NULL;
1439 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1440 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1442 if (!isData(result)) {
1443 CFReleaseNull(result);
1445 SOSClearErrorIfTrue(result != NULL, error);
1454 static bool SOSAccountJoinCircleWithAnalytics(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1455 bool use_cloud_peer, NSData* parentEvent, CFErrorRef* error) {
1456 SOSAccount* account = aTxn.account;
1457 SOSAccountTrustClassic *trust = account.trust;
1458 __block bool result = false;
1459 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1460 SFSignInAnalytics *ensureFullPeerAvailableEvent = nil;
1461 NSError* localError = nil;
1462 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1464 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1465 ensureFullPeerAvailableEvent = [parent newSubTaskForEvent:@"ensureFullPeerAvailableEvent"];
1466 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1467 [ensureFullPeerAvailableEvent stopWithAttributes:nil];
1469 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1470 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1471 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1472 // this also clears initial sync data
1473 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1475 SOSAccountInitializeInitialSync(account);
1476 if (use_cloud_peer) {
1477 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1479 SFSignInAnalytics *acceptApplicantEvent = [parent newSubTaskForEvent:@"acceptApplicantEvent"];
1480 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1481 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1482 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1483 trust.departureCode = kSOSNeverLeftCircle;
1484 if(result && cloud_full_peer) {
1485 CFErrorRef localError = NULL;
1486 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1487 require_quiet(cloudid, finish);
1488 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1489 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1492 [acceptApplicantEvent logRecoverableError:(__bridge NSError *)(localError)];
1493 secerror("Failed to join with cloud identity: %@", localError);
1494 CFReleaseNull(localError);
1499 [acceptApplicantEvent stopWithAttributes:nil];
1500 if (use_cloud_peer) {
1501 SFSignInAnalytics *updateOutOfDateSyncViewsEvent = [acceptApplicantEvent newSubTaskForEvent:@"updateOutOfDateSyncViewsEvent"];
1502 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1503 [updateOutOfDateSyncViewsEvent stopWithAttributes:nil];
1507 CFReleaseNull(cloud_full_peer);
1511 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1512 bool use_cloud_peer, CFErrorRef* error) {
1513 SOSAccount* account = aTxn.account;
1514 SOSAccountTrustClassic *trust = account.trust;
1515 __block bool result = false;
1516 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1517 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1518 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1519 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1520 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1521 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1522 // this also clears initial sync data
1523 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1525 SOSAccountInitializeInitialSync(account);
1526 if (use_cloud_peer) {
1527 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1529 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1530 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1531 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1532 trust.departureCode = kSOSNeverLeftCircle;
1533 if(result && cloud_full_peer) {
1534 CFErrorRef localError = NULL;
1535 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1536 require_quiet(cloudid, finish);
1537 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1538 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1541 secerror("Failed to join with cloud identity: %@", localError);
1542 CFReleaseNull(localError);
1547 if (use_cloud_peer) {
1548 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1552 CFReleaseNull(cloud_full_peer);
1556 static bool SOSAccountJoinCirclesWithAnalytics_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, NSData* parentEvent, CFErrorRef* error) {
1557 SOSAccount* account = aTxn.account;
1558 SOSAccountTrustClassic *trust = account.trust;
1559 bool success = false;
1561 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1562 require_quiet(user_key, done); // Fail if we don't get one.
1564 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1566 if (trust.fullPeerInfo != NULL) {
1567 SOSPeerInfoRef myPeer = trust.peerInfo;
1568 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1569 require_quiet(!success, done);
1571 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1573 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1574 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1576 trust.fullPeerInfo = NULL;
1580 success = SOSAccountJoinCircleWithAnalytics(aTxn, user_key, use_cloud_identity, parentEvent, error);
1582 require_quiet(success, done);
1584 trust.departureCode = kSOSNeverLeftCircle;
1590 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) {
1591 SOSAccount* account = aTxn.account;
1592 SOSAccountTrustClassic *trust = account.trust;
1593 bool success = false;
1595 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1596 require_quiet(user_key, done); // Fail if we don't get one.
1598 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1600 if (trust.fullPeerInfo != NULL) {
1601 SOSPeerInfoRef myPeer = trust.peerInfo;
1602 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1603 require_quiet(!success, done);
1605 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1607 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1608 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1610 trust.fullPeerInfo = NULL;
1614 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
1616 require_quiet(success, done);
1618 trust.departureCode = kSOSNeverLeftCircle;
1624 bool SOSAccountJoinCirclesWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1625 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCirclesWithAnalytics)");
1626 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, false, parentEvent, error);
1629 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1630 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1631 return SOSAccountJoinCircles_internal(aTxn, false, error);
1634 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1635 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1636 return SOSAccountJoinCircles_internal(aTxn, true, error);
1639 bool SOSAccountJoinCirclesAfterRestoreWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1640 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1641 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, true, parentEvent, error);
1644 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error)
1646 bool result = false;
1647 CFMutableSetRef peersToRemove = NULL;
1648 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1650 secnotice("circleOps", "Can't remove without userKey");
1653 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1654 SOSPeerInfoRef me = account.peerInfo;
1655 if(!(me_full && me))
1657 secnotice("circleOps", "Can't remove without being active peer");
1658 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1662 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1664 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1667 CFReleaseNull(peersToRemove);
1668 secnotice("circleOps", "No peerSet to remove");
1672 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1673 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1674 CFSetRemoveValue(peersToRemove, me);
1676 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1677 bool success = false;
1679 if(CFSetGetCount(peersToRemove) != 0) {
1680 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1681 success = SOSAccountGenerationSignatureUpdate(account, error);
1682 } else success = true;
1684 if (success && leaveCircle) {
1685 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1686 success = sosAccountLeaveCircle(account, circle, error);
1695 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1696 secnotice("circleOps", "Removed Peers from circle %@", description);
1700 CFReleaseNull(peersToRemove);
1705 bool SOSAccountRemovePeersFromCircleWithAnalytics(SOSAccount* account, CFArrayRef peers, NSData* parentEvent, CFErrorRef* error)
1708 NSError* localError = nil;
1709 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1711 bool result = false;
1712 CFMutableSetRef peersToRemove = NULL;
1713 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1715 secnotice("circleOps", "Can't remove without userKey");
1718 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1719 SOSPeerInfoRef me = account.peerInfo;
1720 if(!(me_full && me))
1722 secnotice("circleOps", "Can't remove without being active peer");
1723 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1727 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1729 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1732 CFReleaseNull(peersToRemove);
1733 secnotice("circleOps", "No peerSet to remove");
1737 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1738 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1739 CFSetRemoveValue(peersToRemove, me);
1741 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1742 bool success = false;
1744 if(CFSetGetCount(peersToRemove) != 0) {
1745 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1746 SFSignInAnalytics *generationSignatureUpdateEvent = [parent newSubTaskForEvent:@"generationSignatureUpdateEvent"];
1747 success = SOSAccountGenerationSignatureUpdate(account, error);
1748 if(error && *error){
1749 NSError* signatureUpdateError = (__bridge NSError*)*error;
1750 [generationSignatureUpdateEvent logUnrecoverableError:signatureUpdateError];
1752 [generationSignatureUpdateEvent stopWithAttributes:nil];
1753 } else success = true;
1755 if (success && leaveCircle) {
1756 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1757 success = sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
1766 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1767 secnotice("circleOps", "Removed Peers from circle %@", description);
1771 CFReleaseNull(peersToRemove);
1775 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1776 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1777 dispatch_group_t group = dispatch_group_create();
1778 SOSAccountTrustClassic *trust = account.trust;
1779 __block bool result = false;
1780 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1781 // Add a task to the group
1782 dispatch_group_async(group, queue, ^{
1783 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1784 secnotice("circleOps", "Leaving circle by client request (Bail)");
1785 return sosAccountLeaveCircle(account, circle, error);
1788 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1789 dispatch_group_wait(group, milestone);
1791 trust.departureCode = kSOSWithdrewMembership;
1798 // MARK: Application
1801 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1802 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1803 SOSAccountTrustClassic *trust = account.trust;
1805 SOSPeerInfoRef me = trust.peerInfo;
1806 CFErrorRef peer_error = NULL;
1807 if (trust.trustedCircle && me &&
1808 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1809 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1810 __block bool modified = false;
1811 CFArrayForEach(peer_infos, ^(const void *value) {
1812 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1813 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1814 if (action(circle, trust.fullPeerInfo, peer)) {
1823 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1824 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1827 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1828 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1832 __block int64_t acceptedPeers = 0;
1834 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1835 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1841 if (acceptedPeers == CFArrayGetCount(applicants))
1846 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1847 __block bool success = true;
1848 __block int64_t num_peers = 0;
1850 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1851 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1855 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1862 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1863 SOSAccountTrustClassic *trust = account.trust;
1864 return trust.departureCode;
1867 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1868 SOSAccountTrustClassic *trust = account.trust;
1869 trust.departureCode = reason;
1873 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1874 CFArrayRef result = NULL;
1875 CFNumberRef generation = NULL;
1876 SOSAccountTrustClassic *trust = account.trust;
1878 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1879 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1881 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1882 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1888 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1889 if (!SOSAccountHasPublicKey(account, error))
1892 return account.accountKeyIsTrusted;
1895 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1896 // TODO: this result is never set or used
1898 SOSAccountTrustClassic *trust = account.trust;
1900 secnotice("updates", "Ensuring peer registration.");
1903 secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
1907 if([account getCircleStatus: NULL] != kSOSCCInCircle) {
1911 // If we are not in the circle, there is no point in setting up peers
1912 if(!SOSAccountIsMyPeerActive(account, NULL)) {
1916 // This code only uses the SOSFullPeerInfoRef for two things:
1917 // - Finding out if this device is in the trusted circle
1918 // - Using the peerID for this device to see if the current peer is "me"
1919 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
1921 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
1923 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
1924 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
1925 CFErrorRef localError = NULL;
1927 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
1929 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
1930 CFReleaseSafe(localError);
1938 // Value manipulation
1941 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
1942 SOSAccountTrustClassic *trust = account.trust;
1943 if (!trust.expansion) {
1946 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
1951 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
1952 bool success = false;
1954 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
1955 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
1960 void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) {
1961 if (![account isInCircle:NULL]) {
1964 __block bool updateRings = false;
1965 SOSAccountTrustClassic *trust = account.trust;
1966 [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
1967 __block bool updated = false;
1968 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
1969 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
1971 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
1973 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
1974 CFErrorRef cleanupError = NULL;
1975 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
1976 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
1977 CFReleaseSafe(cleanupError);
1984 SOSAccountProcessBackupRings(account, NULL);
1988 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
1990 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
1992 __block CFTypeRef object = NULL;
1994 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1995 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1997 CloudKeychainReplyBlock replyBlock =
1998 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
2000 object = returnedValues;
2005 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
2007 dispatch_semaphore_signal(waitSemaphore);
2010 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
2012 dispatch_semaphore_wait(waitSemaphore, finishTime);
2013 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
2018 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
2022 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
2024 CFStringRef uuid = SOSAccountCopyUUID(account);
2025 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2026 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2028 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2030 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
2032 dispatch_semaphore_signal(waitSemaphore);
2035 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
2036 dispatch_semaphore_wait(waitSemaphore, finishTime);
2037 CFReleaseNull(uuid);
2040 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
2042 NSDate *now = [NSDate date];
2043 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
2045 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
2047 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
2049 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
2051 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
2052 CFStringAppend(timeDescription, decription);
2054 CFStringAppend(timeDescription, CFSTR("]"));
2056 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
2057 CFReleaseNull(timeDescription);
2059 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2060 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2061 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2063 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2065 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2067 dispatch_semaphore_signal(waitSemaphore);
2070 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
2071 dispatch_semaphore_wait(waitSemaphore, finishTime);
2074 // set the cleanup frequency to 3 days.
2075 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
2077 //Get all the key/values in KVS and remove old entries
2078 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
2080 // This should only happen on some number of days
2081 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
2082 NSDate *now = [NSDate date];
2083 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
2085 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
2089 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2091 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
2092 NSMutableArray *peerIDs = [NSMutableArray array];
2093 NSMutableArray *keysToRemove = [NSMutableArray array];
2095 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
2096 CFArrayForEach(peers, ^(const void *value) {
2097 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
2098 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
2100 //any peerid that is not ours gets added
2101 if(![[account.trust peerID] isEqualToString:peerID])
2102 [peerIDs addObject:peerID];
2104 CFReleaseNull(peers);
2106 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
2107 __block bool keyMatchesPeerID = false;
2109 //checks for full peer ids
2110 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
2111 //if key contains peerid of one active peer
2112 if([KVSKey containsString:PeerID]){
2113 secnotice("key-cleanup","key: %@", KVSKey);
2114 keyMatchesPeerID = true;
2117 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
2118 || [KVSKey hasPrefix:@"po"])
2119 [keysToRemove addObject:KVSKey];
2122 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
2123 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
2125 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
2127 //add last cleanup timestamp
2128 SOSAccountWriteLastCleanupTimestampToKVS(account);
2133 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2134 SOSPeerInfoRef applicant = NULL;
2135 SOSAccountTrustClassic *trust = account.trust;
2136 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2137 if(!userKey) return false;
2138 if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
2140 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2142 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2149 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2151 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2152 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2153 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2154 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2156 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2159 if([parentUUID isEqualToString:viewUUID] || authoriative){
2161 /* check if we already have this entry */
2162 if ([seenUUID containsObject:viewUUID])
2165 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2166 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2171 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2173 NSMutableDictionary* strippedDown = [@{
2174 (id)kSecValueData : key,
2175 (id)kSecAttrServer : viewName,
2176 (id)kSecAttrAccount : viewUUID
2179 strippedDown[@"auth"] = @YES;
2181 [results addObject:strippedDown];
2182 [seenUUID addObject:viewUUID];
2188 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
2191 CKKSViewManager* manager = [CKKSViewManager manager];
2193 NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
2195 for (NSString *view in items) {
2196 NSString *uuid = items[view];
2197 NSDictionary *query = @{
2198 (id)kSecClass : (id)kSecClassInternetPassword,
2199 (id)kSecUseDataProtectionKeychain : @YES,
2200 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2201 (id)kSecAttrAccount : uuid,
2202 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2203 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2204 (id)kSecReturnAttributes: @YES,
2205 (id)kSecReturnData: @YES,
2207 CFTypeRef result = NULL;
2208 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2209 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
2211 CFReleaseNull(result);
2217 NSArray<NSDictionary *>*
2218 SOSAccountGetAllTLKs(void)
2220 CFTypeRef result = NULL;
2221 NSMutableArray<NSDictionary *>* results = [NSMutableArray array];
2222 NSMutableSet *seenUUID = [NSMutableSet set];
2224 // first use the TLK from the view manager
2225 AddViewManagerResults(results, seenUUID);
2227 //try to grab tlk-piggy items
2228 NSDictionary* query = @{
2229 (id)kSecClass : (id)kSecClassInternetPassword,
2230 (id)kSecUseDataProtectionKeychain : @YES,
2231 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2232 (id)kSecAttrDescription: @"tlk",
2233 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2234 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2235 (id)kSecReturnAttributes: @YES,
2236 (id)kSecReturnData: @YES,
2239 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2240 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2242 CFReleaseNull(result);
2244 //try to grab tlk-piggy items
2246 (id)kSecClass : (id)kSecClassInternetPassword,
2247 (id)kSecUseDataProtectionKeychain : @YES,
2248 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2249 (id)kSecAttrDescription: @"tlk-piggy",
2250 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2251 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2252 (id)kSecReturnAttributes: @YES,
2253 (id)kSecReturnData: @YES,
2256 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2257 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2259 CFReleaseNull(result);
2261 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2266 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2267 const uint8_t *der, uint8_t *der_end)
2269 if (type != kTLKUnknown) {
2270 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2271 piggy_encode_data(keychainData, der,
2272 piggy_encode_data(uuid, der,
2273 ccder_encode_uint64((uint64_t)type, der, der_end))));
2275 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2276 piggy_encode_data(keychainData, der,
2277 piggy_encode_data(uuid, der,
2278 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2282 static uint8_t* piggy_encode_data(NSData* data,
2283 const uint8_t *der, uint8_t *der_end)
2285 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2286 ccder_encode_body(data.length, data.bytes, der, der_end));
2290 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2292 name2type(NSString *view)
2294 if ([view isEqualToString:@"Manatee"])
2296 else if ([view isEqualToString:@"Engram"])
2298 else if ([view isEqualToString:@"AutoUnlock"])
2299 return kTLKAutoUnlock;
2300 if ([view isEqualToString:@"Health"])
2305 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2307 rank_type(NSString *view)
2309 if ([view isEqualToString:@"Manatee"])
2311 else if ([view isEqualToString:@"Engram"])
2313 else if ([view isEqualToString:@"AutoUnlock"])
2315 if ([view isEqualToString:@"Health"])
2321 parse_uuid(NSString *uuidString)
2323 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2325 [uuid getUUIDBytes:uuidblob];
2326 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2329 piggy_sizeof_data(NSData* data)
2331 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2334 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2335 if (type != kTLKUnknown) {
2336 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2337 piggy_sizeof_data(keychainData) +
2338 piggy_sizeof_data(uuid) +
2339 ccder_sizeof_uint64(type));
2341 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2342 piggy_sizeof_data(keychainData) +
2343 piggy_sizeof_data(uuid) +
2344 der_sizeof_string((__bridge CFStringRef)name, NULL));
2348 NSArray<NSDictionary*>*
2349 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2351 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2353 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2354 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2355 if (obj1[@"auth"] != NULL)
2357 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2358 if (obj2[@"auth"] != NULL)
2362 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2363 * since we are sorting backward, the Ascending/Descending looks wrong below.
2365 if (rank1 > rank2) {
2366 return NSOrderedAscending;
2367 } else if (rank1 < rank2) {
2368 return NSOrderedDescending;
2370 return NSOrderedSame;
2376 static NSArray<NSData *> *
2377 build_tlks(NSArray<NSDictionary*>* tlks)
2379 NSMutableArray *array = [NSMutableArray array];
2380 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2382 for (NSDictionary *item in sortedTLKs) {
2383 NSData* keychainData = item[(__bridge id)kSecValueData];
2384 NSString* name = item[(__bridge id)kSecAttrServer];
2385 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2386 NSData* uuid = parse_uuid(uuidString);
2388 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2390 unsigned char *der = [tlk mutableBytes];
2391 unsigned char *der_end = der + [tlk length];
2393 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2396 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2398 [array addObject:tlk];
2403 static NSArray<NSData *> *
2404 build_identities(NSArray<NSData *>* identities)
2406 NSMutableArray *array = [NSMutableArray array];
2407 for (NSData *item in identities) {
2408 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2410 unsigned char *der = [ident mutableBytes];
2411 unsigned char *der_end = der + [ident length];
2413 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2414 [array addObject:ident];
2421 static unsigned char *
2422 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2424 unsigned char *body_end = der_end;
2425 for (NSData *datum in data) {
2426 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2427 if (der_end == NULL)
2430 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2433 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2435 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2436 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2437 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2440 static NSData *encode_piggy(size_t IdentitiesBudget,
2442 NSArray<NSData*>* identities,
2443 NSArray<NSDictionary*>* tlks)
2445 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2446 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2447 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2448 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2449 size_t payloadSize = 0, identitiesSize = 0;
2450 NSMutableData *result = NULL;
2452 for (NSData *tlk in encodedTLKs) {
2453 if (TLKBudget - payloadSize < [tlk length])
2455 [budgetArray addObject:tlk];
2456 payloadSize += tlk.length;
2458 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2460 for (NSData *ident in encodedIdentities) {
2461 if (IdentitiesBudget - identitiesSize < [ident length])
2463 [identitiesArray addObject:ident];
2464 identitiesSize += ident.length;
2466 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2469 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2471 result = [NSMutableData dataWithLength:piggySize];
2473 unsigned char *der = [result mutableBytes];
2474 unsigned char *der_end = der + [result length];
2476 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2477 encode_data_array(identitiesArray, der,
2478 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2484 static const size_t SOSCCIdentitiesBudget = 120;
2485 static const size_t SOSCCTLKBudget = 500;
2488 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2490 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2493 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2495 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2497 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2498 if(SOSPeerInfoIsCloudIdentity(peer)) {
2499 CFArrayAppendValue(identities, peer);
2505 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) {
2507 NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array];
2508 NSArray<NSDictionary *>* tlks = nil;
2510 if (flags & kSOSInitialSyncFlagiCloudIdentity) {
2511 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2512 secnotice("piggy", "identities: %@", identities);
2514 CFIndex i, count = CFArrayGetCount(identities);
2515 for (i = 0; i < count; i++) {
2516 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2517 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2519 [encodedIdenities addObject:data];
2521 CFRelease(identities);
2524 if (flags & kSOSInitialSyncFlagTLKs) {
2525 tlks = SOSAccountGetAllTLKs();
2528 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2531 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
2532 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
2533 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
2534 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
2535 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
2536 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
2537 if(accountName == NULL) {
2538 accountName = CFSTR("Unavailable");
2540 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
2542 secnotice("circleOps",
2543 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
2544 operation, accountName, version, gencount, pkeyID, sigID, circleHash);
2545 CFReleaseNull(pkeyID);
2546 CFReleaseNull(sigID);
2547 CFReleaseNull(circleHash);
2550 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2551 SOSGenCountRef gencount = NULL;
2552 CFDataRef signature = NULL;
2553 SecKeyRef ourKey = NULL;
2555 CFDataRef pbblob = NULL;
2556 SOSCircleRef prunedCircle = NULL;
2558 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2560 SOSCCStatus circleStat = [account getCircleStatus:error];
2561 if(circleStat != kSOSCCInCircle) {
2562 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
2566 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2567 require_quiet(userKey, errOut);
2569 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2570 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
2571 secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
2574 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2575 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2576 require_quiet(ourKey, errOut);
2579 SOSCircleRef currentCircle = [account.trust getCircle:error];
2580 require_quiet(currentCircle, errOut);
2582 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2583 require_quiet(prunedCircle, errOut);
2584 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2586 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2588 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2589 require_quiet(signature, errOut);
2590 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
2591 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2594 CFReleaseNull(prunedCircle);
2595 CFReleaseNull(gencount);
2596 CFReleaseNull(signature);
2597 CFReleaseNull(ourKey);
2599 if(!pbblob && error != NULL) {
2600 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
2606 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2607 bool retval = false;
2608 SecKeyRef userKey = NULL;
2609 SOSAccountTrustClassic *trust = account.trust;
2610 SOSGenCountRef gencount = NULL;
2611 CFDataRef signature = NULL;
2612 SecKeyRef pubKey = NULL;
2613 bool setInitialSyncTimeoutToV0 = false;
2615 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
2617 if (!isData(joiningBlob)) {
2618 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
2622 userKey = SOSAccountGetPrivateCredential(account, error);
2624 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2628 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
2629 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2633 if(setInitialSyncTimeoutToV0){
2634 secnotice("circleOps", "setting flag in account for piggyback v0");
2635 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2637 secnotice("circleOps", "clearing flag in account for piggyback v0");
2638 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2640 SOSAccountInitializeInitialSync(account);
2642 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
2644 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2645 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2649 trust.fullPeerInfo, error);;
2652 CFReleaseNull(gencount);
2653 CFReleaseNull(pubKey);
2654 CFReleaseNull(signature);
2659 static char boolToChars(bool val, char truechar, char falsechar) {
2660 return val? truechar: falsechar;
2663 #define ACCOUNTLOGSTATE "accountLogState"
2664 void SOSAccountLogState(SOSAccount* account) {
2665 bool hasPubKey = account.accountKey != NULL;
2666 SOSAccountTrustClassic *trust = account.trust;
2667 bool pubTrusted = account.accountKeyIsTrusted;
2668 bool hasPriv = account.accountPrivateKey != NULL;
2669 SOSCCStatus stat = [account getCircleStatus:NULL];
2671 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2672 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2674 secnotice(ACCOUNTLOGSTATE, "Start");
2676 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2677 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2679 SOSAccountGetSOSCCStatusString(stat)
2681 CFReleaseNull(userPubKeyID);
2682 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2683 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2686 void SOSAccountLogViewState(SOSAccount* account) {
2687 bool isInCircle = [account.trust isInCircleOnly:NULL];
2688 require_quiet(isInCircle, imOut);
2689 SOSPeerInfoRef mpi = account.peerInfo;
2690 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2691 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2693 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2694 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2695 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2696 boolToChars(isInitialComplete, 'I', 'i'),
2697 boolToChars(isBackupComplete, 'B', 'b'),
2700 CFReleaseNull(views);
2701 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2702 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2703 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2705 CFReleaseNull(unsyncedViews);
2708 secnotice(ACCOUNTLOGSTATE, "Finish");
2714 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2715 if(!isString(serial)) return;
2716 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2717 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2718 [account.trust updateV2Dictionary:account v2:newv2dict];
2721 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2723 secnotice("otrtimer", "timer fired!");
2724 CFErrorRef error = NULL;
2725 SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
2727 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2728 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2729 if(SOSCoderIsCoderInAwaitingState(coder)){
2730 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2731 CFErrorRef localError = NULL;
2732 SOSCoderReset(coder);
2733 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2734 secerror("Attempt to recover coder failed to restart: %@", localError);
2737 secnotice("otrtimer", "coder restarted!");
2738 SOSEngineSetCodersNeedSaving(engine, true);
2739 SOSPeerSetMustSendMessage(peer, true);
2740 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2742 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2743 SOSPeerRemoveOTRTimerEntry(peer);
2744 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2745 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2748 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2753 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2755 CFReleaseNull(error);
2758 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2760 __block SOSAccount* account = txn.account;
2761 CFErrorRef error = NULL;
2763 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2764 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2766 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2767 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2768 CFErrorRef error = NULL;
2769 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2772 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2773 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2775 if(!sendResult || error){
2776 secnotice("ratelimit", "could not send message: %@", error);
2779 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2780 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2781 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2786 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2788 CFReleaseNull(error);
2797 OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup";
2799 OctagonState* SOSStateReady = (OctagonState*)@"ready";
2800 OctagonState* SOSStateError = (OctagonState*)@"error";
2801 OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup";
2803 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void) {
2804 static NSDictionary<OctagonState*, NSNumber*>* map = nil;
2805 static dispatch_once_t onceToken;
2806 dispatch_once(&onceToken, ^{
2810 SOSStatePerformBackup: @2U,
2816 static NSSet<OctagonFlag*>* SOSFlagsSet(void) {
2817 static NSSet<OctagonFlag*>* set = nil;
2818 static dispatch_once_t onceToken;
2819 dispatch_once(&onceToken, ^{
2820 set = [NSSet setWithArray:@[
2821 SOSFlagTriggerBackup
2829 + (NSURL *)urlForSOSAccountSettings {
2830 return (__bridge NSURL *)SecCopyURLForFileInKeychainDirectory(CFSTR("SOSAccountSettings.pb"));
2834 - (void)setupStateMachine {
2837 self.accountConfiguration = [[CKKSPBFileStorage alloc] initWithStoragePath:[[self class] urlForSOSAccountSettings]
2838 storageClass:[SOSAccountConfiguration class]];
2840 NSAssert(self.stateMachine == nil, @"cant bootstrap more the once");
2842 self.stateMachineQueue = dispatch_queue_create("SOS-statemachine", NULL);
2844 self.stateMachine = [[OctagonStateMachine alloc] initWithName:@"sosaccount"
2845 states:[NSSet setWithArray:[SOSStateMap() allKeys]]
2847 initialState:SOSStateReady
2848 queue:self.stateMachineQueue
2850 lockStateTracker:[CKKSLockStateTracker globalTracker]];
2853 self.performBackups = [[CKKSNearFutureScheduler alloc] initWithName:@"performBackups"
2854 initialDelay:5*NSEC_PER_SEC
2855 continuingDelay:30*NSEC_PER_SEC
2856 keepProcessAlive:YES
2857 dependencyDescriptionCode:CKKSResultDescriptionNone
2860 [self addBackupFlag];
2863 SOSAccountConfiguration *conf = self.accountConfiguration.storage;
2865 if (conf.pendingBackupPeers.count) {
2866 [self addBackupFlag];
2869 [self.stateMachine startOperation];
2873 - (void)addBackupFlag {
2874 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup
2875 conditions:OctagonPendingConditionsDeviceUnlocked];
2876 [self.stateMachine handlePendingFlag:pendingFlag];
2879 - (void)triggerBackupForPeers:(NSArray<NSString*>*)backupPeers
2881 dispatch_sync(self.stateMachineQueue, ^{
2882 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2884 NSMutableSet *pending = [NSMutableSet set];
2885 if (storage.pendingBackupPeers) {
2886 [pending addObjectsFromArray:storage.pendingBackupPeers];
2889 [pending addObjectsFromArray:backupPeers];
2891 storage.pendingBackupPeers = [[pending allObjects] mutableCopy];
2892 [self.accountConfiguration setStorage:storage];
2893 [self.performBackups trigger];
2894 secnotice("sos-sm", "trigger backup for peers: %@", backupPeers);
2898 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
2899 flags:(nonnull OctagonFlags *)flags
2900 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
2902 dispatch_assert_queue(self.stateMachineQueue);
2906 secnotice("sos-sm", "currentState: %@ [flags: %@]", currentState, flags);
2908 if ([currentState isEqualToString:SOSStateReady]) {
2909 if([flags _onqueueContains:SOSFlagTriggerBackup]) {
2910 [flags _onqueueRemoveFlag:SOSFlagTriggerBackup];
2911 return [OctagonStateTransitionOperation named:@"perform-backup-flag"
2912 entering:SOSStatePerformBackup];
2914 secnotice("sos-sm", "Entering state ready");
2916 } else if ([currentState isEqualToString:SOSStateError]) {
2917 secnotice("sos-sm", "Entering state error");
2919 } else if ([currentState isEqualToString:SOSStatePerformBackup]) {
2921 return [OctagonStateTransitionOperation named:@"perform-backup-state"
2922 intending:SOSStateReady
2923 errorState:SOSStateError
2924 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) {
2926 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2928 secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers);
2930 if (storage.pendingBackupPeers.count) {
2931 SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers);
2932 [storage clearPendingBackupPeers];
2934 [self.accountConfiguration setStorage:storage];
2936 op.nextState = SOSStateReady;