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 #include <Security/SecureObjectSync/SOSAccount.h>
13 #include <Security/SecureObjectSync/SOSPeerInfo.h>
14 #include <Security/SecureObjectSync/SOSPeerInfoV2.h>
15 #include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
16 #import "Security/SecureObjectSync/SOSTransportCircle.h"
17 #import "Security/SecureObjectSync/SOSTransportCircleKVS.h"
18 #include <Security/SecureObjectSync/SOSTransportMessage.h>
19 #include <Security/SecureObjectSync/SOSTransportMessageKVS.h>
20 #include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
21 #include <Security/SecureObjectSync/SOSKVSKeys.h>
22 #include <Security/SecureObjectSync/SOSTransport.h>
23 #include <Security/SecureObjectSync/SOSPeerCoder.h>
24 #include <Security/SecureObjectSync/SOSInternal.h>
25 #include <Security/SecureObjectSync/SOSRing.h>
26 #include <Security/SecureObjectSync/SOSRingUtils.h>
27 #include <Security/SecureObjectSync/SOSRingRecovery.h>
28 #include <Security/SecureObjectSync/SOSAccountTransaction.h>
29 #include <Security/SecureObjectSync/SOSAccountGhost.h>
30 #include <Security/SecureObjectSync/SOSPiggyback.h>
31 #include <Security/SecureObjectSync/SOSControlHelper.h>
33 #import <Security/SecureObjectSync/SOSAccountTrust.h>
34 #import <Security/SecureObjectSync/SOSAccountTrustClassic.h>
35 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
36 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
37 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
38 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
39 #import "Security/SecureObjectSync/SOSPeerOTRTimer.h"
40 #import "Security/SecureObjectSync/SOSPeerRateLimiter.h"
41 #import "Security/SecureObjectSync/SOSTypes.h"
43 #import "keychain/ckks/CKKSViewManager.h"
44 #import "keychain/ckks/CKKSLockStateTracker.h"
45 #import "keychain/ot/OTContext.h"
47 #include <Security/SecItemInternal.h>
48 #include <Security/SecEntitlements.h>
49 #include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
51 #include <SecItemServer.h>
53 #import "SecdWatchdog.h"
55 #include <utilities/SecCFWrappers.h>
56 #include <utilities/SecCFError.h>
57 #include <utilities/SecADWrapper.h>
59 #include <os/activity.h>
60 #include <os/state_private.h>
62 #include <utilities/SecCoreCrypto.h>
64 #include <utilities/der_plist.h>
65 #include <utilities/der_plist_internal.h>
66 #include <corecrypto/ccder.h>
68 const CFStringRef kSOSAccountName = CFSTR("AccountName");
69 const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
70 const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
71 const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout");
72 const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
73 const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
74 const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary");
75 const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey");
76 const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing");
77 const CFStringRef kSOSAccountUUID = CFSTR("UUID");
78 const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters");
79 const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef>
80 const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef>
81 const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount");
82 const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion");
83 NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry";
84 NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount";
85 NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup";
87 const uint64_t max_packet_size_over_idms = 500;
90 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
92 #define DATE_LENGTH 25
93 const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
95 @implementation SOSAccount
97 // Auto synthesis for most fields works great.
98 // A few CF fields need retention work when assigning.
104 self.gestalt = [NSMutableDictionary dictionary];
105 self.backup_key = nil;
108 self.trust = [SOSAccountTrustClassic trustClassic];
111 self.user_private_timer = NULL;
114 self._password_tmp = nil;
115 self.isListeningForSync = false;
116 self.lock_notification_token = -1;
118 self.circle_transport = NULL;
120 self.circle_rings_retirements_need_attention = false;
121 self.engine_peer_state_needs_repair = false;
122 self.key_interests_need_updating = false;
124 self.change_blocks = [NSMutableArray array];
126 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
127 self.isInitialSyncing = false;
129 self.accountKeyIsTrusted = false;
130 self.accountKeyDerivationParamters = nil;
132 self.accountKey = NULL;
133 self.accountPrivateKey = NULL;
134 self.previousAccountKey = NULL;
136 self.saveBlock = nil;
138 self.notifyCircleChangeOnExit = false;
139 self.notifyViewChangeOnExit = false;
140 self.notifyBackupOnExit = false;
142 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
149 CFReleaseNull(self->_accountKey);
150 CFReleaseNull(self->_accountPrivateKey);
151 CFReleaseNull(self->_previousAccountKey);
155 @synthesize accountKey = _accountKey;
157 - (void) setAccountKey: (SecKeyRef) key {
158 CFRetainAssign(self->_accountKey, key);
161 @synthesize previousAccountKey = _previousAccountKey;
163 - (void) setPreviousAccountKey: (SecKeyRef) key {
164 CFRetainAssign(self->_previousAccountKey, key);
167 @synthesize accountPrivateKey = _accountPrivateKey;
169 - (void) setAccountPrivateKey: (SecKeyRef) key {
170 CFRetainAssign(self->_accountPrivateKey, key);
173 // Syntactic sugar getters
175 - (BOOL) hasPeerInfo {
176 return self.fullPeerInfo != nil;
179 - (SOSPeerInfoRef) peerInfo {
180 return self.trust.peerInfo;
183 - (SOSFullPeerInfoRef) fullPeerInfo {
184 return self.trust.fullPeerInfo;
187 - (NSString*) peerID {
188 return self.trust.peerID;
191 -(bool) ensureFactoryCircles
201 NSString* circle_name = (__bridge_transfer NSString*)SOSDataSourceFactoryCopyName(self.factory);
207 CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL));
209 return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL);
212 -(void)ensureOctagonPeerKeys
215 CKKSLockStateTracker *tracker = [CKKSViewManager manager].lockStateTracker;
216 if (tracker && tracker.isLocked == false) {
217 [self.trust ensureOctagonPeerKeys:self.circle_transport];
222 -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f
226 self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
228 self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)];
230 SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
233 self.factory = f; // We adopt the factory. kthanksbai.
235 self.isListeningForSync = false;
237 self.accountPrivateKey = NULL;
238 self._password_tmp = NULL;
239 self.user_private_timer = NULL;
240 self.lock_notification_token = NOTIFY_TOKEN_INVALID;
242 self.change_blocks = [NSMutableArray array];
243 self.waitForInitialSync_blocks = NULL;
245 self.key_transport = nil;
246 self.circle_transport = NULL;
247 self.ck_storage = nil;
248 self.kvs_message_transport = nil;
250 self.circle_rings_retirements_need_attention = false;
251 self.engine_peer_state_needs_repair = false;
252 self.key_interests_need_updating = false;
254 self.backup_key =nil;
257 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
258 self.isInitialSyncing = false;
259 self.accountKeyIsTrusted = false;
260 self.accountKeyDerivationParamters = NULL;
261 self.accountKey = NULL;
262 self.previousAccountKey = NULL;
264 self.saveBlock = nil;
269 -(BOOL)isEqual:(id) object
271 if(![object isKindOfClass:[SOSAccount class]])
274 SOSAccount* left = object;
275 return ([self.gestalt isEqual: left.gestalt] &&
276 CFEqualSafe(self.trust.trustedCircle, left.trust.trustedCircle) &&
277 [self.trust.expansion isEqual: left.trust.expansion] &&
278 CFEqualSafe(self.trust.fullPeerInfo, left.trust.fullPeerInfo));
282 - (void)userPublicKey:(void ((^))(BOOL trusted, NSData *spki, NSError *error))reply
284 dispatch_async(self.queue, ^{
285 if (!self.accountKeyIsTrusted || self.accountKey == NULL) {
286 NSDictionary *userinfo = @{
287 (id)kCFErrorDescriptionKey : @"User public key not trusted",
289 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
293 NSData *data = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(self.accountKey));
295 NSDictionary *userinfo = @{
296 (id)kCFErrorDescriptionKey : @"User public not available",
298 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
301 reply(self.accountKeyIsTrusted, data, NULL);
305 - (void)kvsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply
307 /* Need to collect performance counters from all subsystems, not just circle transport, don't have counters yet though */
308 SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error)
310 reply((__bridge NSDictionary *)returnedValues);
314 - (void)rateLimitingPerformanceCounters:(void(^)(NSDictionary <NSString *, NSString *> *))reply
316 CFErrorRef error = NULL;
317 CFDictionaryRef rateLimitingCounters = (CFDictionaryRef)SOSAccountGetValue(self, kSOSRateLimitingCounters, &error);
318 reply((__bridge NSDictionary*)rateLimitingCounters ? (__bridge NSDictionary*)rateLimitingCounters : [NSDictionary dictionary]);
321 - (void)stashedCredentialPublicKey:(void(^)(NSData *, NSError *error))reply
323 dispatch_async(self.queue, ^{
324 CFErrorRef error = NULL;
326 SecKeyRef user_private = SOSAccountCopyStashedUserPrivateKey(self, &error);
327 if (user_private == NULL) {
328 reply(NULL, (__bridge NSError *)error);
329 CFReleaseNull(error);
333 NSData *publicKey = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(user_private));
334 CFReleaseNull(user_private);
335 reply(publicKey, NULL);
339 - (void)assertStashedAccountCredential:(void(^)(BOOL result, NSError *error))complete
341 dispatch_async(self.queue, ^{
342 CFErrorRef error = NULL;
343 bool result = SOSAccountAssertStashedAccountCredential(self, &error);
344 complete(result, (__bridge NSError *)error);
345 CFReleaseNull(error);
349 static bool SyncKVSAndWait(CFErrorRef *error) {
350 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
352 __block bool success = false;
354 secnoticeq("fresh", "EFP calling SOSCloudKeychainSynchronizeAndWait");
356 os_activity_initiate("CloudCircle EFRESH", OS_ACTIVITY_FLAG_DEFAULT, ^(void) {
357 SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) {
358 secnotice("fresh", "EFP returned, callback error: %@", sync_error);
360 success = (sync_error == NULL);
362 CFRetainAssign(*error, sync_error);
365 dispatch_semaphore_signal(wait_for);
369 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
370 secnotice("fresh", "EFP complete: %s %@", success ? "success" : "failure", error ? *error : NULL);
376 static bool Flush(CFErrorRef *error) {
377 __block bool success = false;
379 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
380 secnotice("flush", "Starting");
382 SOSCloudKeychainFlush(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
383 success = (sync_error == NULL);
385 CFRetainAssign(*error, sync_error);
388 dispatch_semaphore_signal(wait_for);
391 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
393 secnotice("flush", "Returned %s", success? "Success": "Failure");
398 - (bool)syncWaitAndFlush:(CFErrorRef *)error
400 secnotice("pairing", "sync and wait starting");
402 if (!SyncKVSAndWait(error)) {
403 secnotice("pairing", "failed sync and wait: %@", error ? *error : NULL);
407 secnotice("pairing", "failed flush: %@", error ? *error : NULL);
410 secnotice("pairing", "finished sync and wait");
414 - (void)validatedStashedAccountCredential:(void(^)(NSData *credential, NSError *error))complete
416 CFErrorRef syncerror = NULL;
418 if (![self syncWaitAndFlush:&syncerror]) {
419 complete(NULL, (__bridge NSError *)syncerror);
420 CFReleaseNull(syncerror);
424 dispatch_async(self.queue, ^{
425 CFErrorRef error = NULL;
426 SecKeyRef key = NULL;
427 key = SOSAccountCopyStashedUserPrivateKey(self, &error);
429 secnotice("pairing", "no stashed credential");
430 complete(NULL, (__bridge NSError *)error);
431 CFReleaseNull(error);
435 SecKeyRef publicKey = SecKeyCopyPublicKey(key);
437 secnotice("pairing", "returning stash credential: %@", publicKey);
438 CFReleaseNull(publicKey);
441 NSData *keydata = CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error));
443 complete(keydata, (__bridge NSError *)error);
444 CFReleaseNull(error);
448 - (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete
450 CFErrorRef syncerror = NULL;
452 if (![self syncWaitAndFlush:&syncerror]) {
453 complete(NULL, (__bridge NSError *)syncerror);
454 CFReleaseNull(syncerror);
458 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
459 SecKeyRef accountPrivateKey = NULL;
460 CFErrorRef error = NULL;
461 NSDictionary *attributes = @{
462 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
463 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
466 accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error);
467 if (accountPrivateKey == NULL) {
468 complete(false, (__bridge NSError *)error);
469 secnotice("pairing", "SecKeyCreateWithData failed: %@", error);
470 CFReleaseNull(error);
474 if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) {
475 CFReleaseNull(accountPrivateKey);
476 complete(false, (__bridge NSError *)error);
477 secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error);
478 CFReleaseNull(error);
482 secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded");
484 CFReleaseNull(accountPrivateKey);
485 complete(true, NULL);
488 // This makes getting the private key the same as Asserting the password - we read all the other things
489 // that we just expressed interest in.
490 CFErrorRef error = NULL;
491 if (!Flush(&error)) {
492 secnotice("pairing", "failed final flush: %@", error ? error : NULL);
495 CFReleaseNull(error);
498 - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete
500 __block CFErrorRef localError = NULL;
501 __block NSData *applicationBlob = NULL;
502 SecAKSDoWhileUserBagLocked(&localError, ^{
503 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
504 SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError);
506 applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError));
507 CFReleaseNull(application);
511 complete(applicationBlob, (__bridge NSError *)localError);
512 CFReleaseNull(localError);
515 - (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete
517 __block CFErrorRef localError = NULL;
518 __block NSData *blob = NULL;
519 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant);
521 complete(NULL, (__bridge NSError *)localError);
522 CFReleaseNull(localError);
525 SecAKSDoWhileUserBagLocked(&localError, ^{
526 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
527 blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError));
533 complete(blob, (__bridge NSError *)localError);
534 CFReleaseNull(localError);
537 - (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete
539 __block CFErrorRef localError = NULL;
540 __block bool res = false;
542 SecAKSDoWhileUserBagLocked(&localError, ^{
543 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
544 res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError);
548 complete(res, (__bridge NSError *)localError);
549 CFReleaseNull(localError);
552 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
554 CFErrorRef error = NULL;
555 uint32_t isflags = 0;
557 if (flags & SOSControlInitialSyncFlagTLK)
558 isflags |= SecServerInitialSyncCredentialFlagTLK;
559 if (flags & SOSControlInitialSyncFlagPCS)
560 isflags |= SecServerInitialSyncCredentialFlagPCS;
561 if (flags & SOSControlInitialSyncFlagPCSNonCurrent)
562 isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent;
563 if (flags & SOSControlInitialSyncFlagBluetoothMigration)
564 isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration;
567 NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error));
568 complete(array, (__bridge NSError *)error);
569 CFReleaseNull(error);
572 - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete
574 CFErrorRef error = NULL;
575 bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error);
576 complete(res, (__bridge NSError *)error);
577 CFReleaseNull(error);
580 - (void)triggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
582 __block CFErrorRef localError = NULL;
583 __block bool res = false;
585 secnotice("sync", "trigger a forced sync for %@", peers);
587 SecAKSDoWhileUserBagLocked(&localError, ^{
588 [self performTransaction:^(SOSAccountTransaction *txn) {
590 NSSet *peersSet = [NSSet setWithArray:peers];
591 CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError);
592 if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) {
595 CFReleaseNull(handledPeers);
597 res = SOSAccountRequestSyncWithAllPeers(txn, &localError);
601 complete(res, (__bridge NSError *)localError);
602 CFReleaseNull(localError);
605 - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete
607 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
608 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
610 NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters];
611 complete(parameters, nil);
614 complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
618 - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete
620 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
621 NSError* error = nil;
622 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
624 [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error];
628 complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
636 - (void) flattenToSaveBlock {
637 if (self.saveBlock) {
638 NSError* error = nil;
639 NSData* saveData = [self encodedData:&error];
641 (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error);
645 CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) {
646 return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt);
649 CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) {
650 CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL);
651 return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict);
654 static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){
655 SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
656 //send new DSID over account changed
657 [account.circle_transport kvsSendOfficialDSID:dsid err:NULL];
661 void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) {
662 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
663 if(accountDSID == NULL) {
664 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
666 SOSAccountUpdateDSID(account, dsid);
667 } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
668 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
670 //DSID has changed, blast the account!
671 SOSAccountSetToNew(account);
673 //update DSID to the new DSID
674 SOSAccountUpdateDSID(account, dsid);
676 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
681 void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews)
683 [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews];
684 [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews];
687 #pragma clang diagnostic push
688 #pragma clang diagnostic ignored "-Wunused-value"
689 SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) {
690 SOSViewResultCode retval = kSOSCCGeneralViewError;
691 // The V0 view switches on and off all on it's own, we allow people the delusion
692 // of control and status if it's what we're stuck at., otherwise error.
693 if (SOSAccountSyncingV0(account)) {
694 require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
695 retval = kSOSCCViewMember;
697 require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
698 retval = kSOSCCViewNotMember;
703 #pragma clang diagnostic pop
705 SOSAccount* SOSAccountCreate(CFAllocatorRef allocator,
706 CFDictionaryRef gestalt,
707 SOSDataSourceFactoryRef factory) {
709 SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory];
710 [a ensureFactoryCircles];
711 SOSAccountEnsureUUID(a);
712 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate");
713 a.key_interests_need_updating = true;
718 static OSStatus do_delete(CFDictionaryRef query) {
721 result = SecItemDelete(query);
723 secerror("SecItemDelete: %d", (int)result);
729 do_keychain_delete_aks_bags()
732 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
733 kSecClass, kSecClassGenericPassword,
734 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
735 kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
736 kSecAttrService, CFSTR("SecureBackupService"),
737 kSecAttrSynchronizable, kCFBooleanTrue,
738 kSecUseTombstones, kCFBooleanFalse,
741 result = do_delete(item);
748 do_keychain_delete_identities()
751 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
752 kSecClass, kSecClassKey,
753 kSecAttrSynchronizable, kCFBooleanTrue,
754 kSecUseTombstones, kCFBooleanFalse,
755 kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
758 result = do_delete(item);
765 do_keychain_delete_lakitu()
768 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
769 kSecClass, kSecClassGenericPassword,
770 kSecAttrSynchronizable, kCFBooleanTrue,
771 kSecUseTombstones, kCFBooleanFalse,
772 kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
773 kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
774 kSecAttrService, CFSTR("EscrowService"),
777 result = do_delete(item);
784 do_keychain_delete_sbd()
787 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
788 kSecClass, kSecClassGenericPassword,
789 kSecAttrSynchronizable, kCFBooleanTrue,
790 kSecUseTombstones, kCFBooleanFalse,
791 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
794 result = do_delete(item);
800 void SOSAccountSetToNew(SOSAccount* a)
802 secnotice("accountChange", "Setting Account to New");
805 /* remove all syncable items */
806 result = do_keychain_delete_aks_bags(); (void) result;
807 secdebug("set to new", "result for deleting aks bags: %d", result);
809 result = do_keychain_delete_identities(); (void) result;
810 secdebug("set to new", "result for deleting identities: %d", result);
812 result = do_keychain_delete_lakitu(); (void) result;
813 secdebug("set to new", "result for deleting lakitu: %d", result);
815 result = do_keychain_delete_sbd(); (void) result;
816 secdebug("set to new", "result for deleting sbd: %d", result);
818 a.accountKeyIsTrusted = false;
820 if (a.user_private_timer) {
821 dispatch_source_cancel(a.user_private_timer);
822 a.user_private_timer = NULL;
823 xpc_transaction_end();
826 if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) {
827 notify_cancel(a.lock_notification_token);
828 a.lock_notification_token = NOTIFY_TOKEN_INVALID;
835 // update_interest_block;
837 SOSUnregisterTransportKeyParameter(a.key_transport);
838 SOSUnregisterTransportMessage(a.kvs_message_transport);
839 SOSUnregisterTransportCircle(a.circle_transport);
841 a.circle_transport = NULL;
842 a.kvs_message_transport = nil;
845 a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
847 [a ensureFactoryCircles]; // Does rings too
849 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
850 SOSAccountEnsureUUID(a);
851 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew");
852 a.key_interests_need_updating = true;
855 bool SOSAccountIsNew(SOSAccount* account, CFErrorRef *error){
857 SOSAccountTrustClassic* trust = account.trust;
858 if(account.accountKeyIsTrusted != false || trust.departureCode != kSOSNeverAppliedToCircle ||
859 CFSetGetCount((__bridge CFSetRef)trust.retirees) != 0)
862 if(trust.retirees != nil)
864 if(trust.expansion != nil)
867 if(account.user_private_timer != NULL || account.lock_notification_token != NOTIFY_TOKEN_INVALID)
875 dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) {
876 return account.queue;
879 void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){
880 account.accountKeyIsTrusted = true;
883 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
885 SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error];
886 if (!SOSAccountHasPublicKey(self, error)) {
887 if(circleStatus == kSOSCCInCircle) {
889 CFReleaseNull(*error);
890 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);
893 circleStatus = kSOSCCError;
898 -(bool) isInCircle:(CFErrorRef *)error
900 SOSCCStatus result = [self getCircleStatus:error];
901 if (result != kSOSCCInCircle) {
902 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
909 bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) {
911 SOSAccountTrustClassic *trust = account.trust;
912 NSMutableSet* retirees = trust.retirees;
913 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
914 CFSetSetValue((__bridge CFMutableSetRef) retirees, peer);
915 CFErrorRef cleanupError = NULL;
916 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) {
917 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
919 CFReleaseSafe(cleanupError);
924 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) {
925 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
926 SOSPeerInfoRef me = account.peerInfo;
927 bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
929 SOSAccountTrustClassic *trust = account.trust;
930 NSMutableSet* retirees = trust.retirees;
932 if(!new_circle) return NULL;
933 __block bool workDone = false;
935 CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) {
936 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
937 if (isSOSPeerInfo(pi)) {
938 SOSCircleUpdatePeerInfo(new_circle, pi);
944 if(workDone && SOSCircleCountPeers(new_circle) == 0) {
945 SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
949 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
950 if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) ||
951 ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){
952 CFReleaseNull(new_circle);
955 account.notifyBackupOnExit = true;
957 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
958 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
959 // we have a chance to set things right with a SetCreds/Join sequence. This will cause
960 // handleUpdateCircle to return false.
961 CFReleaseNull(new_circle);
965 // This case is when we aren't an applicant and the circle is retirement-empty.
966 secnotice("circleOps", "Reset to empty with last retirement");
967 SOSCircleResetToEmpty(new_circle, NULL);
975 // MARK: Circle Membership change notificaion
978 void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
979 SOSAccountCircleMembershipChangeBlock copy = changeBlock;
980 [a.change_blocks addObject:copy];
983 void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
984 [a.change_blocks removeObject:changeBlock];
987 void SOSAccountPurgeIdentity(SOSAccount* account) {
988 SOSAccountTrustClassic *trust = account.trust;
989 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
992 // Purge private key but don't return error if we can't.
993 CFErrorRef purgeError = NULL;
994 if (!SOSFullPeerInfoPurgePersistentKey(identity, &purgeError)) {
995 secwarning("Couldn't purge persistent key for %@ [%@]", identity, purgeError);
997 CFReleaseNull(purgeError);
999 trust.fullPeerInfo = nil;
1003 bool sosAccountLeaveCircleWithAnalytics(SOSAccount* account, SOSCircleRef circle, NSData* parentData, CFErrorRef* error) {
1004 SOSAccountTrustClassic *trust = account.trust;
1005 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1006 NSMutableSet* retirees = trust.retirees;
1008 NSError* localError = nil;
1009 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentData error:&localError];
1011 SOSFullPeerInfoRef fpi = identity;
1012 if(!fpi) return false;
1014 CFErrorRef retiredError = NULL;
1016 bool retval = false;
1018 SFSignInAnalytics *promoteToRetiredEvent = [parent newSubTaskForEvent:@"promoteToRetiredEvent"];
1019 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
1021 [promoteToRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
1022 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
1024 *error = retiredError;
1026 CFReleaseNull(retiredError);
1029 [promoteToRetiredEvent stopWithAttributes:nil];
1032 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1034 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1035 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1036 // Remove our application if we have one.
1037 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1038 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1039 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1040 CFErrorRef cleanupError = NULL;
1041 SFSignInAnalytics *cleanupEvent = [parent newSubTaskForEvent:@"cleanupAfterPeerEvent"];
1042 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1043 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1045 [cleanupEvent stopWithAttributes:nil];
1046 CFReleaseSafe(cleanupError);
1050 // Store the retirement record locally.
1051 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1053 trust.retirees = retirees;
1055 // Write retirement to Transport
1056 CFErrorRef postError = NULL;
1057 SFSignInAnalytics *postRestirementEvent = [parent newSubTaskForEvent:@"postRestirementEvent"];
1058 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1059 [postRestirementEvent logRecoverableError:(__bridge NSError*)postError];
1060 secwarning("Couldn't post retirement (%@)", postError);
1062 [postRestirementEvent stopWithAttributes:nil];
1064 SFSignInAnalytics *flushChangesEvent = [parent newSubTaskForEvent:@"flushChangesEvent"];
1066 if(![account.circle_transport flushChanges:&postError]){
1067 [flushChangesEvent logRecoverableError:(__bridge NSError*)postError];
1068 secwarning("Couldn't flush retirement data (%@)", postError);
1070 [flushChangesEvent stopWithAttributes:nil];
1071 CFReleaseNull(postError);
1073 SFSignInAnalytics *purgeIdentityEvent = [parent newSubTaskForEvent:@"purgeIdentityEvent"];
1074 SOSAccountPurgeIdentity(account);
1075 [purgeIdentityEvent stopWithAttributes:nil];
1078 CFReleaseNull(retire_peer);
1082 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) {
1083 SOSAccountTrustClassic *trust = account.trust;
1084 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1085 NSMutableSet* retirees = trust.retirees;
1087 SOSFullPeerInfoRef fpi = identity;
1088 if(!fpi) return false;
1090 CFErrorRef localError = NULL;
1092 bool retval = false;
1094 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
1096 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1098 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1099 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1100 // Remove our application if we have one.
1101 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1102 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1103 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1104 CFErrorRef cleanupError = NULL;
1105 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1106 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1108 CFReleaseSafe(cleanupError);
1112 // Store the retirement record locally.
1113 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1115 trust.retirees = retirees;
1117 // Write retirement to Transport
1118 CFErrorRef postError = NULL;
1119 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1120 secwarning("Couldn't post retirement (%@)", postError);
1122 if(![account.circle_transport flushChanges:&postError]){
1123 secwarning("Couldn't flush retirement data (%@)", postError);
1125 CFReleaseNull(postError);
1128 SOSAccountPurgeIdentity(account);
1132 CFReleaseNull(localError);
1133 CFReleaseNull(retire_peer);
1137 bool sosAccountLeaveRing(SOSAccount* account, SOSRingRef ring, CFErrorRef* error) {
1138 SOSAccountTrustClassic *trust = account.trust;
1139 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1141 SOSFullPeerInfoRef fpi = identity;
1142 if(!fpi) return false;
1143 SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
1144 CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
1146 CFErrorRef localError = NULL;
1148 bool retval = false;
1149 bool writeRing = false;
1150 bool writePeerInfo = false;
1152 if(SOSRingHasPeerID(ring, peerID)) {
1153 writePeerInfo = true;
1156 if(writePeerInfo || writeRing) {
1157 SOSRingWithdraw(ring, NULL, fpi, error);
1161 CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
1164 [account.circle_transport kvsRingPostRing:SOSRingGetName(ring) ring:ring_data err:NULL];
1166 CFReleaseNull(ring_data);
1169 CFReleaseNull(localError);
1173 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1174 bool result = false;
1175 if (account.circle_transport) {
1176 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1182 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1183 local value that has been overwritten by a distant value. If there is no
1184 conflict between the local and the distant values when doing the initial
1185 sync (e.g. if the cloud has no data stored or the client has not stored
1186 any data yet), you'll never see that notification.
1188 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1189 with server but initial round trip with server does not imply
1190 NSUbiquitousKeyValueStoreInitialSyncChange.
1195 // MARK: Status summary
1199 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1201 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1202 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1203 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1204 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1205 case kSOSCCError: return CFSTR("kSOSCCError");
1207 return CFSTR("kSOSCCError");
1209 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1210 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1211 return kSOSCCInCircle;
1212 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1213 return kSOSCCInCircle;
1214 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1215 return kSOSCCNotInCircle;
1216 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1217 return kSOSCCRequestPending;
1218 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1219 return kSOSCCCircleAbsent;
1220 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1227 // MARK: Account Reset Circles
1230 // This needs to be called within a [trust modifyCircle()] block
1233 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1234 bool retval = false;
1236 SOSAccountTrustClassic *trust = account.trust;
1237 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1239 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1241 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1242 if(SOSPeerInfoIsCloudIdentity(peer)) {
1243 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1245 CFSetAddValue(iCloud2Remove, peer);
1247 CFReleaseNull(icfpi);
1251 if(CFSetGetCount(iCloud2Remove) > 0) {
1253 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1255 CFReleaseNull(iCloud2Remove);
1260 // MARK: start backups
1264 bool SOSAccountEnsureInBackupRings(SOSAccount* account) {
1265 __block bool result = false;
1266 __block CFErrorRef error = NULL;
1267 secnotice("backup", "Ensuring in rings");
1269 NSData *backupKey = nil;
1271 if(!account.backup_key){
1276 backupKey = (__bridge_transfer NSData*)SOSPeerInfoV2DictionaryCopyData(account.peerInfo, sBackupKeyKey);
1278 bool updateBackupKey = ![backupKey isEqual:account.backup_key];
1280 if(updateBackupKey) {
1281 result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1282 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error);
1285 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1286 CFReleaseNull(error);
1290 if(!account.backup_key)
1293 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1295 CFReleaseNull(error);
1298 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){
1300 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1302 CFReleaseNull(error);
1306 CFDataRef recoveryKeyBackFromRing = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, &error);
1308 if(updateBackupKey || recoveryKeyBackFromRing) {
1309 // It's a good key, we're going with it. Stop backing up the old way.
1310 CFErrorRef localError = NULL;
1311 if (!SOSDeleteV0Keybag(&localError)) {
1312 secerror("Failed to delete v0 keybag: %@", localError);
1314 CFReleaseNull(localError);
1318 // Setup backups the new way.
1319 SOSAccountForEachBackupView(account, ^(const void *value) {
1320 CFStringRef viewName = (CFStringRef)value;
1321 if(updateBackupKey || (recoveryKeyBackFromRing && !SOSAccountRecoveryKeyIsInBackupAndCurrentInView(account, viewName))) {
1322 result &= SOSAccountNewBKSBForView(account, viewName, &error);
1328 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1330 CFReleaseNull(error);
1335 // MARK: Recovery Public Key Functions
1338 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1339 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1340 if(retval) secnotice("recovery", "successfully registered recovery public key");
1341 else secnotice("recovery", "could not register recovery public key: %@", *error);
1342 SOSClearErrorIfTrue(retval, error);
1346 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1347 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1348 if(retval) secnotice("recovery", "RK Cleared");
1349 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1350 SOSClearErrorIfTrue(retval, error);
1354 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1355 CFDataRef result = NULL;
1356 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1357 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1359 if (!isData(result)) {
1360 CFReleaseNull(result);
1362 SOSClearErrorIfTrue(result != NULL, error);
1371 static bool SOSAccountJoinCircleWithAnalytics(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1372 bool use_cloud_peer, NSData* parentEvent, CFErrorRef* error) {
1373 SOSAccount* account = aTxn.account;
1374 SOSAccountTrustClassic *trust = account.trust;
1375 __block bool result = false;
1376 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1377 SFSignInAnalytics *ensureFullPeerAvailableEvent = nil;
1378 NSError* localError = nil;
1379 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1381 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1382 ensureFullPeerAvailableEvent = [parent newSubTaskForEvent:@"ensureFullPeerAvailableEvent"];
1383 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1384 [ensureFullPeerAvailableEvent stopWithAttributes:nil];
1386 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1387 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1388 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1389 // this also clears initial sync data
1390 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1392 SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
1393 if (use_cloud_peer) {
1394 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1396 SFSignInAnalytics *acceptApplicantEvent = [parent newSubTaskForEvent:@"acceptApplicantEvent"];
1397 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1398 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1399 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1400 trust.departureCode = kSOSNeverLeftCircle;
1401 if(result && cloud_full_peer) {
1402 CFErrorRef localError = NULL;
1403 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1404 require_quiet(cloudid, finish);
1405 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1406 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1409 [acceptApplicantEvent logRecoverableError:(__bridge NSError *)(localError)];
1410 secerror("Failed to join with cloud identity: %@", localError);
1411 CFReleaseNull(localError);
1416 [acceptApplicantEvent stopWithAttributes:nil];
1417 if (use_cloud_peer) {
1418 SFSignInAnalytics *updateOutOfDateSyncViewsEvent = [acceptApplicantEvent newSubTaskForEvent:@"updateOutOfDateSyncViewsEvent"];
1419 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1420 [updateOutOfDateSyncViewsEvent stopWithAttributes:nil];
1424 CFReleaseNull(cloud_full_peer);
1428 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1429 bool use_cloud_peer, CFErrorRef* error) {
1430 SOSAccount* account = aTxn.account;
1431 SOSAccountTrustClassic *trust = account.trust;
1432 __block bool result = false;
1433 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1434 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1435 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1436 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1437 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1438 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1439 // this also clears initial sync data
1440 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1442 SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
1443 if (use_cloud_peer) {
1444 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1446 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1447 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1448 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1449 trust.departureCode = kSOSNeverLeftCircle;
1450 if(result && cloud_full_peer) {
1451 CFErrorRef localError = NULL;
1452 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1453 require_quiet(cloudid, finish);
1454 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1455 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1458 secerror("Failed to join with cloud identity: %@", localError);
1459 CFReleaseNull(localError);
1464 if (use_cloud_peer) {
1465 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1469 CFReleaseNull(cloud_full_peer);
1473 static bool SOSAccountJoinCirclesWithAnalytics_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, NSData* parentEvent, CFErrorRef* error) {
1474 SOSAccount* account = aTxn.account;
1475 SOSAccountTrustClassic *trust = account.trust;
1476 bool success = false;
1478 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1479 require_quiet(user_key, done); // Fail if we don't get one.
1481 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1483 if (trust.fullPeerInfo != NULL) {
1484 SOSPeerInfoRef myPeer = trust.peerInfo;
1485 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1486 require_quiet(!success, done);
1488 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1490 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1491 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1493 trust.fullPeerInfo = NULL;
1497 success = SOSAccountJoinCircleWithAnalytics(aTxn, user_key, use_cloud_identity, parentEvent, error);
1499 require_quiet(success, done);
1501 trust.departureCode = kSOSNeverLeftCircle;
1507 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) {
1508 SOSAccount* account = aTxn.account;
1509 SOSAccountTrustClassic *trust = account.trust;
1510 bool success = false;
1512 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1513 require_quiet(user_key, done); // Fail if we don't get one.
1515 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1517 if (trust.fullPeerInfo != NULL) {
1518 SOSPeerInfoRef myPeer = trust.peerInfo;
1519 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1520 require_quiet(!success, done);
1522 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1524 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1525 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1527 trust.fullPeerInfo = NULL;
1531 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
1533 require_quiet(success, done);
1535 trust.departureCode = kSOSNeverLeftCircle;
1541 bool SOSAccountJoinCirclesWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1542 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCirclesWithAnalytics)");
1543 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, false, parentEvent, error);
1546 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1547 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1548 return SOSAccountJoinCircles_internal(aTxn, false, error);
1551 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1552 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1553 return SOSAccountJoinCircles_internal(aTxn, true, error);
1556 bool SOSAccountJoinCirclesAfterRestoreWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1557 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1558 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, true, parentEvent, error);
1561 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error)
1563 bool result = false;
1564 CFMutableSetRef peersToRemove = NULL;
1565 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1567 secnotice("circleOps", "Can't remove without userKey");
1570 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1571 SOSPeerInfoRef me = account.peerInfo;
1572 if(!(me_full && me))
1574 secnotice("circleOps", "Can't remove without being active peer");
1575 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1579 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1581 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1584 CFReleaseNull(peersToRemove);
1585 secnotice("circleOps", "No peerSet to remove");
1589 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1590 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1591 CFSetRemoveValue(peersToRemove, me);
1593 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1594 bool success = false;
1596 if(CFSetGetCount(peersToRemove) != 0) {
1597 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1598 success = SOSAccountGenerationSignatureUpdate(account, error);
1599 } else success = true;
1601 if (success && leaveCircle) {
1602 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1603 success = sosAccountLeaveCircle(account, circle, error);
1612 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1613 secnotice("circleOps", "Removed Peers from circle %@", description);
1617 CFReleaseNull(peersToRemove);
1622 bool SOSAccountRemovePeersFromCircleWithAnalytics(SOSAccount* account, CFArrayRef peers, NSData* parentEvent, CFErrorRef* error)
1625 NSError* localError = nil;
1626 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1628 bool result = false;
1629 CFMutableSetRef peersToRemove = NULL;
1630 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1632 secnotice("circleOps", "Can't remove without userKey");
1635 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1636 SOSPeerInfoRef me = account.peerInfo;
1637 if(!(me_full && me))
1639 secnotice("circleOps", "Can't remove without being active peer");
1640 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1644 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1646 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1649 CFReleaseNull(peersToRemove);
1650 secnotice("circleOps", "No peerSet to remove");
1654 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1655 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1656 CFSetRemoveValue(peersToRemove, me);
1658 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1659 bool success = false;
1661 if(CFSetGetCount(peersToRemove) != 0) {
1662 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1663 SFSignInAnalytics *generationSignatureUpdateEvent = [parent newSubTaskForEvent:@"generationSignatureUpdateEvent"];
1664 success = SOSAccountGenerationSignatureUpdate(account, error);
1665 if(error && *error){
1666 NSError* signatureUpdateError = (__bridge NSError*)*error;
1667 [generationSignatureUpdateEvent logUnrecoverableError:signatureUpdateError];
1669 [generationSignatureUpdateEvent stopWithAttributes:nil];
1670 } else success = true;
1672 if (success && leaveCircle) {
1673 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1674 success = sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
1683 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1684 secnotice("circleOps", "Removed Peers from circle %@", description);
1688 CFReleaseNull(peersToRemove);
1692 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1693 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1694 dispatch_group_t group = dispatch_group_create();
1695 SOSAccountTrustClassic *trust = account.trust;
1696 __block bool result = false;
1697 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1698 // Add a task to the group
1699 dispatch_group_async(group, queue, ^{
1700 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1701 secnotice("circleOps", "Leaving circle by client request (Bail)");
1702 return sosAccountLeaveCircle(account, circle, error);
1705 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1706 dispatch_group_wait(group, milestone);
1708 trust.departureCode = kSOSWithdrewMembership;
1715 // MARK: Application
1718 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1719 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1720 SOSAccountTrustClassic *trust = account.trust;
1722 SOSPeerInfoRef me = trust.peerInfo;
1723 CFErrorRef peer_error = NULL;
1724 if (trust.trustedCircle && me &&
1725 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1726 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1727 __block bool modified = false;
1728 CFArrayForEach(peer_infos, ^(const void *value) {
1729 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1730 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1731 if (action(circle, trust.fullPeerInfo, peer)) {
1740 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1741 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1744 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1745 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1749 __block int64_t acceptedPeers = 0;
1751 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1752 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1758 if (acceptedPeers == CFArrayGetCount(applicants))
1763 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1764 __block bool success = true;
1765 __block int64_t num_peers = 0;
1767 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1768 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1772 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1780 CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccount* account, CFErrorRef* error) {
1781 return CFSTR("We're compatible, go away");
1784 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1785 SOSAccountTrustClassic *trust = account.trust;
1786 return trust.departureCode;
1789 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1790 SOSAccountTrustClassic *trust = account.trust;
1791 trust.departureCode = reason;
1795 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1796 CFArrayRef result = NULL;
1797 CFNumberRef generation = NULL;
1798 SOSAccountTrustClassic *trust = account.trust;
1800 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1801 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1803 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1804 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1810 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1811 if (!SOSAccountHasPublicKey(account, error))
1814 return account.accountKeyIsTrusted;
1817 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1818 // TODO: this result is never set or used
1820 SOSAccountTrustClassic *trust = account.trust;
1822 secnotice("updates", "Ensuring peer registration.");
1825 secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
1829 if([account getCircleStatus: NULL] != kSOSCCInCircle) {
1833 // If we are not in the circle, there is no point in setting up peers
1834 if(!SOSAccountIsMyPeerActive(account, NULL)) {
1838 // This code only uses the SOSFullPeerInfoRef for two things:
1839 // - Finding out if this device is in the trusted circle
1840 // - Using the peerID for this device to see if the current peer is "me"
1841 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
1843 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
1845 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
1846 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
1847 CFErrorRef localError = NULL;
1849 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
1851 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
1852 CFReleaseSafe(localError);
1860 // Value manipulation
1863 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
1864 SOSAccountTrustClassic *trust = account.trust;
1865 if (!trust.expansion) {
1868 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
1871 bool SOSAccountAddEscrowRecords(SOSAccount* account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error){
1872 CFMutableDictionaryRef escrowRecords = (CFMutableDictionaryRef)SOSAccountGetValue(account, kSOSEscrowRecord, error);
1873 CFMutableDictionaryRef escrowCopied = NULL;
1874 bool success = false;
1876 if(isDictionary(escrowRecords) && escrowRecords != NULL)
1877 escrowCopied = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(escrowRecords), escrowRecords);
1879 escrowCopied = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
1881 CFDictionaryAddValue(escrowCopied, dsid, record);
1882 SOSAccountSetValue(account, kSOSEscrowRecord, escrowCopied, error);
1887 CFReleaseNull(escrowCopied);
1893 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
1894 bool success = false;
1896 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
1897 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
1902 void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) {
1903 if (![account isInCircle:NULL]) {
1906 SOSAccountTrustClassic *trust = account.trust;
1907 [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
1908 __block bool updated = false;
1909 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
1910 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
1912 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
1914 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
1915 CFErrorRef cleanupError = NULL;
1916 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
1917 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
1918 CFReleaseSafe(cleanupError);
1925 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
1927 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
1929 __block CFTypeRef object = NULL;
1931 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1932 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1934 CloudKeychainReplyBlock replyBlock =
1935 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
1937 object = returnedValues;
1942 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
1944 dispatch_semaphore_signal(waitSemaphore);
1947 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
1949 dispatch_semaphore_wait(waitSemaphore, finishTime);
1950 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
1955 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
1959 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
1961 CFStringRef uuid = SOSAccountCopyUUID(account);
1962 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1963 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1965 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1967 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
1969 dispatch_semaphore_signal(waitSemaphore);
1972 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
1973 dispatch_semaphore_wait(waitSemaphore, finishTime);
1974 CFReleaseNull(uuid);
1977 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
1979 NSDate *now = [NSDate date];
1980 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
1982 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
1984 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
1986 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
1988 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
1989 CFStringAppend(timeDescription, decription);
1991 CFStringAppend(timeDescription, CFSTR("]"));
1993 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
1994 CFReleaseNull(timeDescription);
1996 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1997 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1998 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2000 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2002 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2004 dispatch_semaphore_signal(waitSemaphore);
2007 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
2008 dispatch_semaphore_wait(waitSemaphore, finishTime);
2011 // set the cleanup frequency to 3 days.
2012 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
2014 //Get all the key/values in KVS and remove old entries
2015 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
2017 // This should only happen on some number of days
2018 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
2019 NSDate *now = [NSDate date];
2020 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
2022 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
2026 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2028 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
2029 NSMutableArray *peerIDs = [NSMutableArray array];
2030 NSMutableArray *keysToRemove = [NSMutableArray array];
2032 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
2033 CFArrayForEach(peers, ^(const void *value) {
2034 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
2035 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
2037 //any peerid that is not ours gets added
2038 if(![[account.trust peerID] isEqualToString:peerID])
2039 [peerIDs addObject:peerID];
2041 CFReleaseNull(peers);
2043 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
2044 __block bool keyMatchesPeerID = false;
2046 //checks for full peer ids
2047 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
2048 //if key contains peerid of one active peer
2049 if([KVSKey containsString:PeerID]){
2050 secnotice("key-cleanup","key: %@", KVSKey);
2051 keyMatchesPeerID = true;
2054 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
2055 || [KVSKey hasPrefix:@"po"])
2056 [keysToRemove addObject:KVSKey];
2059 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
2060 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
2062 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
2064 //add last cleanup timestamp
2065 SOSAccountWriteLastCleanupTimestampToKVS(account);
2070 bool SOSAccountPopulateKVSWithBadKeys(SOSAccount* account, CFErrorRef* error) {
2072 NSMutableDictionary *testKeysAndValues = [NSMutableDictionary dictionary];
2073 [testKeysAndValues setObject:@"deadbeef" forKey:@"-ak|asdfjkl;asdfjk;"];
2074 [testKeysAndValues setObject:@"foobar" forKey:@"ak|asdfasdfasdf:qwerqwerqwer"];
2075 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"poak|asdfasdfasdfasdf"];
2076 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"po|asdfasdfasdfasdfasdfasdf"];
2077 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"k>KeyParm"];
2079 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2080 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2081 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2083 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2085 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2087 dispatch_semaphore_signal(waitSemaphore);
2090 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(testKeysAndValues), processQueue, replyBlock);
2091 dispatch_semaphore_wait(waitSemaphore, finishTime);
2096 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2097 SOSPeerInfoRef applicant = NULL;
2098 SOSAccountTrustClassic *trust = account.trust;
2099 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2100 if(!userKey) return false;
2101 if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
2103 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2105 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2112 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2114 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2115 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2116 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2117 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2119 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2122 if([parentUUID isEqualToString:viewUUID] || authoriative){
2124 /* check if we already have this entry */
2125 if ([seenUUID containsObject:viewUUID])
2128 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2129 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2134 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2136 NSMutableDictionary* strippedDown = [@{
2137 (id)kSecValueData : key,
2138 (id)kSecAttrServer : viewName,
2139 (id)kSecAttrAccount : viewUUID
2142 strippedDown[@"auth"] = @YES;
2144 [results addObject:strippedDown];
2145 [seenUUID addObject:viewUUID];
2151 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
2154 CKKSViewManager* manager = [CKKSViewManager manager];
2156 NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
2158 for (NSString *view in items) {
2159 NSString *uuid = items[view];
2160 NSDictionary *query = @{
2161 (id)kSecClass : (id)kSecClassInternetPassword,
2162 (id)kSecAttrNoLegacy : @YES,
2163 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2164 (id)kSecAttrAccount : uuid,
2165 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2166 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2167 (id)kSecReturnAttributes: @YES,
2168 (id)kSecReturnData: @YES,
2170 CFTypeRef result = NULL;
2171 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2172 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
2174 CFReleaseNull(result);
2181 SOSAccountGetAllTLKs(void)
2183 CFTypeRef result = NULL;
2184 NSMutableArray* results = [NSMutableArray array];
2185 NSMutableSet *seenUUID = [NSMutableSet set];
2187 // first use the TLK from the view manager
2188 AddViewManagerResults(results, seenUUID);
2190 //try to grab tlk-piggy items
2191 NSDictionary* query = @{
2192 (id)kSecClass : (id)kSecClassInternetPassword,
2193 (id)kSecAttrNoLegacy : @YES,
2194 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2195 (id)kSecAttrDescription: @"tlk",
2196 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2197 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2198 (id)kSecReturnAttributes: @YES,
2199 (id)kSecReturnData: @YES,
2202 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2203 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2205 CFReleaseNull(result);
2207 //try to grab tlk-piggy items
2209 (id)kSecClass : (id)kSecClassInternetPassword,
2210 (id)kSecAttrNoLegacy : @YES,
2211 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2212 (id)kSecAttrDescription: @"tlk-piggy",
2213 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2214 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2215 (id)kSecReturnAttributes: @YES,
2216 (id)kSecReturnData: @YES,
2219 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2220 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2222 CFReleaseNull(result);
2224 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2229 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2230 const uint8_t *der, uint8_t *der_end)
2232 if (type != kTLKUnknown) {
2233 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2234 piggy_encode_data(keychainData, der,
2235 piggy_encode_data(uuid, der,
2236 ccder_encode_uint64((uint64_t)type, der, der_end))));
2238 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2239 piggy_encode_data(keychainData, der,
2240 piggy_encode_data(uuid, der,
2241 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2245 static uint8_t* piggy_encode_data(NSData* data,
2246 const uint8_t *der, uint8_t *der_end)
2248 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2249 ccder_encode_body(data.length, data.bytes, der, der_end));
2254 name2type(NSString *view)
2256 if ([view isEqualToString:@"Manatee"])
2258 else if ([view isEqualToString:@"Engram"])
2260 else if ([view isEqualToString:@"AutoUnlock"])
2261 return kTLKAutoUnlock;
2262 if ([view isEqualToString:@"Health"])
2268 rank_type(NSString *view)
2270 if ([view isEqualToString:@"Manatee"])
2272 else if ([view isEqualToString:@"Engram"])
2274 else if ([view isEqualToString:@"AutoUnlock"])
2276 if ([view isEqualToString:@"Health"])
2282 parse_uuid(NSString *uuidString)
2284 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2286 [uuid getUUIDBytes:uuidblob];
2287 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2290 piggy_sizeof_data(NSData* data)
2292 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2295 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2296 if (type != kTLKUnknown) {
2297 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2298 piggy_sizeof_data(keychainData) +
2299 piggy_sizeof_data(uuid) +
2300 ccder_sizeof_uint64(type));
2302 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2303 piggy_sizeof_data(keychainData) +
2304 piggy_sizeof_data(uuid) +
2305 der_sizeof_string((__bridge CFStringRef)name, NULL));
2309 NSArray<NSDictionary*>*
2310 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2312 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2314 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2315 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2316 if (obj1[@"auth"] != NULL)
2318 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2319 if (obj2[@"auth"] != NULL)
2323 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2324 * since we are sorting backward, the Ascending/Descending looks wrong below.
2326 if (rank1 > rank2) {
2327 return NSOrderedAscending;
2328 } else if (rank1 < rank2) {
2329 return NSOrderedDescending;
2331 return NSOrderedSame;
2337 static NSArray<NSData *> *
2338 build_tlks(NSArray<NSDictionary*>* tlks)
2340 NSMutableArray *array = [NSMutableArray array];
2341 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2343 for (NSDictionary *item in sortedTLKs) {
2344 NSData* keychainData = item[(__bridge id)kSecValueData];
2345 NSString* name = item[(__bridge id)kSecAttrServer];
2346 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2347 NSData* uuid = parse_uuid(uuidString);
2349 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2351 unsigned char *der = [tlk mutableBytes];
2352 unsigned char *der_end = der + [tlk length];
2354 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2357 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2359 [array addObject:tlk];
2364 static NSArray<NSData *> *
2365 build_identities(NSArray<NSData *>* identities)
2367 NSMutableArray *array = [NSMutableArray array];
2368 for (NSData *item in identities) {
2369 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2371 unsigned char *der = [ident mutableBytes];
2372 unsigned char *der_end = der + [ident length];
2374 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2375 [array addObject:ident];
2382 static unsigned char *
2383 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2385 unsigned char *body_end = der_end;
2386 for (NSData *datum in data) {
2387 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2388 if (der_end == NULL)
2391 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2394 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2396 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2397 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2398 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2401 static NSData *encode_piggy(size_t IdentitiesBudget,
2403 NSArray<NSData*>* identities,
2404 NSArray<NSDictionary*>* tlks)
2406 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2407 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2408 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2409 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2410 size_t payloadSize = 0, identitiesSize = 0;
2411 NSMutableData *result = NULL;
2413 for (NSData *tlk in encodedTLKs) {
2414 if (TLKBudget - payloadSize < [tlk length])
2416 [budgetArray addObject:tlk];
2417 payloadSize += tlk.length;
2419 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2421 for (NSData *ident in encodedIdentities) {
2422 if (IdentitiesBudget - identitiesSize < [ident length])
2424 [identitiesArray addObject:ident];
2425 identitiesSize += ident.length;
2427 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2430 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2432 result = [NSMutableData dataWithLength:piggySize];
2434 unsigned char *der = [result mutableBytes];
2435 unsigned char *der_end = der + [result length];
2437 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2438 encode_data_array(identitiesArray, der,
2439 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2445 static const size_t SOSCCIdentitiesBudget = 120;
2446 static const size_t SOSCCTLKBudget = 500;
2449 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2451 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2454 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2456 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2458 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2459 if(SOSPeerInfoIsCloudIdentity(peer)) {
2460 CFArrayAppendValue(identities, peer);
2466 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, CFErrorRef *error) {
2467 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2468 secnotice("piggy", "identities: %@", identities);
2470 NSMutableArray *encodedIdenities = [NSMutableArray array];
2471 CFIndex i, count = CFArrayGetCount(identities);
2472 for (i = 0; i < count; i++) {
2473 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2474 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2476 [encodedIdenities addObject:data];
2478 CFRelease(identities);
2480 NSMutableArray* tlks = SOSAccountGetAllTLKs();
2482 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2485 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
2486 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
2487 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
2488 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
2489 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
2490 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
2491 if(accountName == NULL) {
2492 accountName = CFSTR("Unavailable");
2494 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
2496 secnotice("circleOps",
2497 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
2498 operation, accountName, version, gencount, pkeyID, sigID, circleHash);
2499 CFReleaseNull(pkeyID);
2500 CFReleaseNull(sigID);
2501 CFReleaseNull(circleHash);
2504 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2505 SOSGenCountRef gencount = NULL;
2506 CFDataRef signature = NULL;
2507 SecKeyRef ourKey = NULL;
2509 CFDataRef pbblob = NULL;
2510 SOSCircleRef prunedCircle = NULL;
2512 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2514 SOSCCStatus circleStat = [account getCircleStatus:error];
2515 if(circleStat != kSOSCCInCircle) {
2516 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
2520 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2521 require_quiet(userKey, errOut);
2523 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2524 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
2525 secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
2528 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2529 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2530 require_quiet(ourKey, errOut);
2533 SOSCircleRef currentCircle = [account.trust getCircle:error];
2534 require_quiet(currentCircle, errOut);
2536 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2537 require_quiet(prunedCircle, errOut);
2538 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2540 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2542 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2543 require_quiet(signature, errOut);
2544 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
2545 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2548 CFReleaseNull(prunedCircle);
2549 CFReleaseNull(gencount);
2550 CFReleaseNull(signature);
2551 CFReleaseNull(ourKey);
2553 if(!pbblob && error != NULL) {
2554 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
2560 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2561 bool retval = false;
2562 SecKeyRef userKey = NULL;
2563 SOSAccountTrustClassic *trust = account.trust;
2564 SOSGenCountRef gencount = NULL;
2565 CFDataRef signature = NULL;
2566 SecKeyRef pubKey = NULL;
2567 bool setInitialSyncTimeoutToV0 = false;
2569 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
2571 if (!isData(joiningBlob)) {
2572 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
2576 userKey = SOSAccountGetPrivateCredential(account, error);
2578 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2582 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
2583 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2587 if(setInitialSyncTimeoutToV0){
2588 secnotice("circleOps", "setting flag in account for piggyback v0");
2589 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2591 secnotice("circleOps", "clearing flag in account for piggyback v0");
2592 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2594 SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
2596 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
2598 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2599 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2603 trust.fullPeerInfo, error);;
2606 CFReleaseNull(gencount);
2607 CFReleaseNull(pubKey);
2608 CFReleaseNull(signature);
2613 static char boolToChars(bool val, char truechar, char falsechar) {
2614 return val? truechar: falsechar;
2617 #define ACCOUNTLOGSTATE "accountLogState"
2618 void SOSAccountLogState(SOSAccount* account) {
2619 bool hasPubKey = account.accountKey != NULL;
2620 SOSAccountTrustClassic *trust = account.trust;
2621 bool pubTrusted = account.accountKeyIsTrusted;
2622 bool hasPriv = account.accountPrivateKey != NULL;
2623 SOSCCStatus stat = [account getCircleStatus:NULL];
2625 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2626 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2628 secnotice(ACCOUNTLOGSTATE, "Start");
2630 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2631 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2633 SOSAccountGetSOSCCStatusString(stat)
2635 CFReleaseNull(userPubKeyID);
2636 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2637 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2640 void SOSAccountLogViewState(SOSAccount* account) {
2641 bool isInCircle = [account.trust isInCircleOnly:NULL];
2642 require_quiet(isInCircle, imOut);
2643 SOSPeerInfoRef mpi = account.peerInfo;
2644 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2645 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2647 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2648 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2649 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2650 boolToChars(isInitialComplete, 'I', 'i'),
2651 boolToChars(isBackupComplete, 'B', 'b'),
2654 CFReleaseNull(views);
2655 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2656 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2657 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2659 CFReleaseNull(unsyncedViews);
2662 secnotice(ACCOUNTLOGSTATE, "Finish");
2668 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2669 if(!isString(serial)) return;
2670 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2671 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2672 [account.trust updateV2Dictionary:account v2:newv2dict];
2675 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2677 secnotice("otrtimer", "timer fired!");
2678 CFErrorRef error = NULL;
2679 SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
2681 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2682 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2683 if(SOSCoderIsCoderInAwaitingState(coder)){
2684 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2685 CFErrorRef localError = NULL;
2686 SOSCoderReset(coder);
2687 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2688 secerror("Attempt to recover coder failed to restart: %@", localError);
2691 secnotice("otrtimer", "coder restarted!");
2692 SOSEngineSetCodersNeedSaving(engine, true);
2693 SOSPeerSetMustSendMessage(peer, true);
2694 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2696 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2697 SOSPeerRemoveOTRTimerEntry(peer);
2698 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2699 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2702 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2707 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2709 CFReleaseNull(error);
2712 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2714 __block SOSAccount* account = txn.account;
2715 CFErrorRef error = NULL;
2717 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2718 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2720 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2721 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2722 CFErrorRef error = NULL;
2723 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2726 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2727 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2729 if(!sendResult || error){
2730 secnotice("ratelimit", "could not send message: %@", error);
2733 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2734 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2735 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2740 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2742 CFReleaseNull(error);