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/SOSTransportMessageIDS.h>
21 #include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
22 #include <Security/SecureObjectSync/SOSKVSKeys.h>
23 #include <Security/SecureObjectSync/SOSTransport.h>
24 #include <Security/SecureObjectSync/SOSPeerCoder.h>
25 #include <Security/SecureObjectSync/SOSInternal.h>
26 #include <Security/SecureObjectSync/SOSRing.h>
27 #include <Security/SecureObjectSync/SOSRingUtils.h>
28 #include <Security/SecureObjectSync/SOSRingRecovery.h>
29 #include <Security/SecureObjectSync/SOSPeerInfoSecurityProperties.h>
30 #include <Security/SecureObjectSync/SOSAccountTransaction.h>
31 #include <Security/SecureObjectSync/SOSAccountGhost.h>
32 #include <Security/SecureObjectSync/SOSPiggyback.h>
33 #include <Security/SecureObjectSync/SOSControlHelper.h>
35 #import <Security/SecureObjectSync/SOSAccountTrust.h>
36 #import <Security/SecureObjectSync/SOSAccountTrustClassic.h>
37 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
38 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
39 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
40 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
41 #import "Security/SecureObjectSync/SOSPeerOTRTimer.h"
42 #import "Security/SecureObjectSync/SOSPeerRateLimiter.h"
43 #import "Security/SecureObjectSync/SOSTypes.h"
45 #import "keychain/ckks/CKKSViewManager.h"
46 #import "keychain/ckks/CKKSLockStateTracker.h"
47 #import "keychain/ot/OTContext.h"
49 #include <Security/SecItemInternal.h>
50 #include <Security/SecEntitlements.h>
51 #include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
53 #include <SecItemServer.h>
55 #import "SecdWatchdog.h"
57 #include <utilities/SecCFWrappers.h>
58 #include <utilities/SecCFError.h>
59 #include <utilities/SecADWrapper.h>
61 #include <os/activity.h>
62 #include <os/state_private.h>
64 #include <utilities/SecCoreCrypto.h>
66 #include <utilities/der_plist.h>
67 #include <utilities/der_plist_internal.h>
68 #include <corecrypto/ccder.h>
70 const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
71 const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
72 const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout");
73 const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
74 const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
75 const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary");
76 const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey");
77 const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing");
78 const CFStringRef kSOSAccountUUID = CFSTR("UUID");
79 const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters");
80 const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef>
81 const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef>
82 const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount");
83 const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion");
84 NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry";
86 const uint64_t max_packet_size_over_idms = 500;
89 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
91 #define DATE_LENGTH 25
92 const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
94 @implementation SOSAccount
96 // Auto synthesis for most fields works great.
97 // A few CF fields need retention work when assigning.
103 self.gestalt = [NSMutableDictionary dictionary];
104 self.backup_key = nil;
107 self.trust = [SOSAccountTrustClassic trustClassic];
110 self.user_private_timer = NULL;
113 self._password_tmp = nil;
114 self.isListeningForSync = false;
115 self.lock_notification_token = -1;
117 self.key_transport = nil;
118 self.circle_transport = NULL;
119 self.kvs_message_transport = nil;
120 self.ids_message_transport = nil;
121 self.ck_storage = nil;
123 self.circle_rings_retirements_need_attention = false;
124 self.engine_peer_state_needs_repair = false;
125 self.key_interests_need_updating = false;
127 self.change_blocks = [NSMutableArray array];
129 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
130 self.isInitialSyncing = false;
132 self.accountKeyIsTrusted = false;
133 self.accountKeyDerivationParamters = nil;
135 self.accountKey = NULL;
136 self.accountPrivateKey = NULL;
137 self.previousAccountKey = NULL;
139 self.saveBlock = nil;
146 CFReleaseNull(self->_accountKey);
147 CFReleaseNull(self->_accountPrivateKey);
148 CFReleaseNull(self->_previousAccountKey);
152 @synthesize accountKey = _accountKey;
154 - (void) setAccountKey: (SecKeyRef) key {
155 CFRetainAssign(self->_accountKey, key);
158 @synthesize previousAccountKey = _previousAccountKey;
160 - (void) setPreviousAccountKey: (SecKeyRef) key {
161 CFRetainAssign(self->_previousAccountKey, key);
164 @synthesize accountPrivateKey = _accountPrivateKey;
166 - (void) setAccountPrivateKey: (SecKeyRef) key {
167 CFRetainAssign(self->_accountPrivateKey, key);
170 // Syntactic sugar getters
172 - (BOOL) hasPeerInfo {
173 return self.fullPeerInfo != nil;
176 - (SOSPeerInfoRef) peerInfo {
177 return self.trust.peerInfo;
180 - (SOSFullPeerInfoRef) fullPeerInfo {
181 return self.trust.fullPeerInfo;
184 - (NSString*) peerID {
185 return self.trust.peerID;
188 -(bool) ensureFactoryCircles
198 NSString* circle_name = (__bridge_transfer NSString*)SOSDataSourceFactoryCopyName(self.factory);
204 CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL));
206 return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL);
209 -(void)ensureOctagonPeerKeys
212 CKKSLockStateTracker *tracker = [CKKSViewManager manager].lockStateTracker;
213 if (tracker && tracker.isLocked == false) {
214 [self.trust ensureOctagonPeerKeys:self.circle_transport];
219 -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f
223 self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
225 self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)];
227 SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
230 self.factory = f; // We adopt the factory. kthanksbai.
232 self.isListeningForSync = false;
234 self.accountPrivateKey = NULL;
235 self._password_tmp = NULL;
236 self.user_private_timer = NULL;
237 self.lock_notification_token = NOTIFY_TOKEN_INVALID;
239 self.change_blocks = [NSMutableArray array];
240 self.waitForInitialSync_blocks = NULL;
242 self.key_transport = nil;
243 self.circle_transport = NULL;
244 self.ck_storage = nil;
245 self.kvs_message_transport = nil;
246 self.ids_message_transport = nil;
248 self.circle_rings_retirements_need_attention = false;
249 self.engine_peer_state_needs_repair = false;
250 self.key_interests_need_updating = false;
252 self.backup_key =nil;
255 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
256 self.isInitialSyncing = false;
257 self.accountKeyIsTrusted = false;
258 self.accountKeyDerivationParamters = NULL;
259 self.accountKey = NULL;
260 self.previousAccountKey = NULL;
262 self.saveBlock = nil;
267 -(BOOL)isEqual:(id) object
269 if(![object isKindOfClass:[SOSAccount class]])
272 SOSAccount* left = object;
273 return ([self.gestalt isEqual: left.gestalt] &&
274 CFEqualSafe(self.trust.trustedCircle, left.trust.trustedCircle) &&
275 [self.trust.expansion isEqual: left.trust.expansion] &&
276 CFEqualSafe(self.trust.fullPeerInfo, left.trust.fullPeerInfo));
280 - (void)userPublicKey:(void ((^))(BOOL trusted, NSData *spki, NSError *error))reply
282 dispatch_async(self.queue, ^{
283 if (!self.accountKeyIsTrusted || self.accountKey == NULL) {
284 NSDictionary *userinfo = @{
285 (id)kCFErrorDescriptionKey : @"User public key not trusted",
287 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
291 NSData *data = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(self.accountKey));
293 NSDictionary *userinfo = @{
294 (id)kCFErrorDescriptionKey : @"User public not available",
296 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]);
299 reply(self.accountKeyIsTrusted, data, NULL);
303 - (void)kvsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply
305 /* Need to collect performance counters from all subsystems, not just circle transport, don't have counters yet though */
306 SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error)
308 reply((__bridge NSDictionary *)returnedValues);
311 - (void)idsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply
313 SOSCloudKeychainRetrieveCountersFromIDSProxy(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error)
315 reply((__bridge NSDictionary *)returnedValues);
319 - (void)rateLimitingPerformanceCounters:(void(^)(NSDictionary <NSString *, NSString *> *))reply
321 CFErrorRef error = NULL;
322 CFDictionaryRef rateLimitingCounters = (CFDictionaryRef)SOSAccountGetValue(self, kSOSRateLimitingCounters, &error);
323 reply((__bridge NSDictionary*)rateLimitingCounters ? (__bridge NSDictionary*)rateLimitingCounters : [NSDictionary dictionary]);
326 - (void)stashedCredentialPublicKey:(void(^)(NSData *, NSError *error))reply
328 dispatch_async(self.queue, ^{
329 CFErrorRef error = NULL;
331 SecKeyRef user_private = SOSAccountCopyStashedUserPrivateKey(self, &error);
332 if (user_private == NULL) {
333 reply(NULL, (__bridge NSError *)error);
334 CFReleaseNull(error);
338 NSData *publicKey = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(user_private));
339 CFReleaseNull(user_private);
340 reply(publicKey, NULL);
344 - (void)assertStashedAccountCredential:(void(^)(BOOL result, NSError *error))complete
346 dispatch_async(self.queue, ^{
347 CFErrorRef error = NULL;
348 bool result = SOSAccountAssertStashedAccountCredential(self, &error);
349 complete(result, (__bridge NSError *)error);
350 CFReleaseNull(error);
354 static bool SyncKVSAndWait(CFErrorRef *error) {
355 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
357 __block bool success = false;
359 secnoticeq("fresh", "EFP calling SOSCloudKeychainSynchronizeAndWait");
361 os_activity_initiate("CloudCircle EFRESH", OS_ACTIVITY_FLAG_DEFAULT, ^(void) {
362 SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) {
363 secnotice("fresh", "EFP returned, callback error: %@", sync_error);
365 success = (sync_error == NULL);
367 CFRetainAssign(*error, sync_error);
370 dispatch_semaphore_signal(wait_for);
374 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
375 secnotice("fresh", "EFP complete: %s %@", success ? "success" : "failure", error ? *error : NULL);
381 static bool Flush(CFErrorRef *error) {
382 __block bool success = false;
384 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
385 secnotice("flush", "Starting");
387 SOSCloudKeychainFlush(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
388 success = (sync_error == NULL);
390 CFRetainAssign(*error, sync_error);
393 dispatch_semaphore_signal(wait_for);
396 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
398 secnotice("flush", "Returned %s", success? "Success": "Failure");
403 - (bool)syncWaitAndFlush:(CFErrorRef *)error
405 secnotice("pairing", "sync and wait starting");
407 if (!SyncKVSAndWait(error)) {
408 secnotice("pairing", "failed sync and wait: %@", error ? *error : NULL);
412 secnotice("pairing", "failed flush: %@", error ? *error : NULL);
415 secnotice("pairing", "finished sync and wait");
419 - (void)validatedStashedAccountCredential:(void(^)(NSData *credential, NSError *error))complete
421 CFErrorRef syncerror = NULL;
423 if (![self syncWaitAndFlush:&syncerror]) {
424 complete(NULL, (__bridge NSError *)syncerror);
425 CFReleaseNull(syncerror);
429 dispatch_async(self.queue, ^{
430 CFErrorRef error = NULL;
431 SecKeyRef key = NULL;
432 key = SOSAccountCopyStashedUserPrivateKey(self, &error);
434 secnotice("pairing", "no stashed credential");
435 complete(NULL, (__bridge NSError *)error);
436 CFReleaseNull(error);
440 SecKeyRef publicKey = SecKeyCopyPublicKey(key);
442 secnotice("pairing", "returning stash credential: %@", publicKey);
443 CFReleaseNull(publicKey);
446 NSData *keydata = CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error));
448 complete(keydata, (__bridge NSError *)error);
449 CFReleaseNull(error);
453 - (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete
455 CFErrorRef syncerror = NULL;
457 if (![self syncWaitAndFlush:&syncerror]) {
458 complete(NULL, (__bridge NSError *)syncerror);
459 CFReleaseNull(syncerror);
463 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
464 SecKeyRef accountPrivateKey = NULL;
465 CFErrorRef error = NULL;
466 NSDictionary *attributes = @{
467 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
468 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
471 accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error);
472 if (accountPrivateKey == NULL) {
473 complete(false, (__bridge NSError *)error);
474 secnotice("pairing", "SecKeyCreateWithData failed: %@", error);
475 CFReleaseNull(error);
479 if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) {
480 CFReleaseNull(accountPrivateKey);
481 complete(false, (__bridge NSError *)error);
482 secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error);
483 CFReleaseNull(error);
487 secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded");
489 CFReleaseNull(accountPrivateKey);
490 complete(true, NULL);
493 // This makes getting the private key the same as Asserting the password - we read all the other things
494 // that we just expressed interest in.
495 CFErrorRef error = NULL;
496 if (!Flush(&error)) {
497 secnotice("pairing", "failed final flush: %@", error ? error : NULL);
500 CFReleaseNull(error);
503 - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete
505 __block CFErrorRef localError = NULL;
506 __block NSData *applicationBlob = NULL;
507 SecAKSDoWhileUserBagLocked(&localError, ^{
508 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
509 SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError);
511 applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError));
512 CFReleaseNull(application);
516 complete(applicationBlob, (__bridge NSError *)localError);
517 CFReleaseNull(localError);
520 - (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete
522 __block CFErrorRef localError = NULL;
523 __block NSData *blob = NULL;
524 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant);
526 complete(NULL, (__bridge NSError *)localError);
527 CFReleaseNull(localError);
530 SecAKSDoWhileUserBagLocked(&localError, ^{
531 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
532 blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError));
538 complete(blob, (__bridge NSError *)localError);
539 CFReleaseNull(localError);
542 - (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete
544 __block CFErrorRef localError = NULL;
545 __block bool res = false;
547 SecAKSDoWhileUserBagLocked(&localError, ^{
548 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
549 res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError);
553 complete(res, (__bridge NSError *)localError);
554 CFReleaseNull(localError);
557 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
559 CFErrorRef error = NULL;
560 uint32_t isflags = 0;
562 if (flags & SOSControlInitialSyncFlagTLK)
563 isflags |= SecServerInitialSyncCredentialFlagTLK;
564 if (flags & SOSControlInitialSyncFlagPCS)
565 isflags |= SecServerInitialSyncCredentialFlagPCS;
566 if (flags & SOSControlInitialSyncFlagPCSNonCurrent)
567 isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent;
568 if (flags & SOSControlInitialSyncFlagBluetoothMigration)
569 isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration;
572 NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error));
573 complete(array, (__bridge NSError *)error);
574 CFReleaseNull(error);
577 - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete
579 CFErrorRef error = NULL;
580 bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error);
581 complete(res, (__bridge NSError *)error);
582 CFReleaseNull(error);
585 - (void)triggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
587 __block CFErrorRef localError = NULL;
588 __block bool res = false;
590 secnotice("sync", "trigger a forced sync for %@", peers);
592 SecAKSDoWhileUserBagLocked(&localError, ^{
593 [self performTransaction:^(SOSAccountTransaction *txn) {
595 NSSet *peersSet = [NSSet setWithArray:peers];
596 CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError);
597 if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) {
600 CFReleaseNull(handledPeers);
602 res = SOSAccountRequestSyncWithAllPeers(txn, &localError);
606 complete(res, (__bridge NSError *)localError);
607 CFReleaseNull(localError);
610 - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete
612 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
613 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
615 NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters];
616 complete(parameters, nil);
619 complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
623 - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete
625 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
626 NSError* error = nil;
627 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
629 [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error];
633 complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
641 - (void) flattenToSaveBlock {
642 if (self.saveBlock) {
643 NSError* error = nil;
644 NSData* saveData = [self encodedData:&error];
646 (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error);
650 CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) {
651 return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt);
654 CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) {
655 CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL);
656 return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict);
659 static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){
660 SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
661 //send new DSID over account changed
662 [account.circle_transport kvsSendOfficialDSID:dsid err:NULL];
666 void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) {
667 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
668 if(accountDSID == NULL) {
669 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
671 SOSAccountUpdateDSID(account, dsid);
672 } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
673 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
675 //DSID has changed, blast the account!
676 SOSAccountSetToNew(account);
678 //update DSID to the new DSID
679 SOSAccountUpdateDSID(account, dsid);
681 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
686 void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews)
688 [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews];
689 [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews];
692 SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) {
693 SOSViewResultCode retval = kSOSCCGeneralViewError;
694 // The V0 view switches on and off all on it's own, we allow people the delusion
695 // of control and status if it's what we're stuck at., otherwise error.
696 if (SOSAccountSyncingV0(account)) {
697 require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
698 retval = kSOSCCViewMember;
700 require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
701 retval = kSOSCCViewNotMember;
707 SOSAccount* SOSAccountCreate(CFAllocatorRef allocator,
708 CFDictionaryRef gestalt,
709 SOSDataSourceFactoryRef factory) {
711 SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory];
712 [a ensureFactoryCircles];
713 SOSAccountEnsureUUID(a);
714 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate");
715 a.key_interests_need_updating = true;
720 static OSStatus do_delete(CFDictionaryRef query) {
723 result = SecItemDelete(query);
725 secerror("SecItemDelete: %d", (int)result);
731 do_keychain_delete_aks_bags()
734 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
735 kSecClass, kSecClassGenericPassword,
736 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
737 kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
738 kSecAttrService, CFSTR("SecureBackupService"),
739 kSecAttrSynchronizable, kCFBooleanTrue,
740 kSecUseTombstones, kCFBooleanFalse,
743 result = do_delete(item);
750 do_keychain_delete_identities()
753 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
754 kSecClass, kSecClassKey,
755 kSecAttrSynchronizable, kCFBooleanTrue,
756 kSecUseTombstones, kCFBooleanFalse,
757 kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
760 result = do_delete(item);
767 do_keychain_delete_lakitu()
770 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
771 kSecClass, kSecClassGenericPassword,
772 kSecAttrSynchronizable, kCFBooleanTrue,
773 kSecUseTombstones, kCFBooleanFalse,
774 kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
775 kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
776 kSecAttrService, CFSTR("EscrowService"),
779 result = do_delete(item);
786 do_keychain_delete_sbd()
789 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
790 kSecClass, kSecClassGenericPassword,
791 kSecAttrSynchronizable, kCFBooleanTrue,
792 kSecUseTombstones, kCFBooleanFalse,
793 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
796 result = do_delete(item);
802 void SOSAccountSetToNew(SOSAccount* a)
804 secnotice("accountChange", "Setting Account to New");
807 /* remove all syncable items */
808 result = do_keychain_delete_aks_bags(); (void) result;
809 secdebug("set to new", "result for deleting aks bags: %d", result);
811 result = do_keychain_delete_identities(); (void) result;
812 secdebug("set to new", "result for deleting identities: %d", result);
814 result = do_keychain_delete_lakitu(); (void) result;
815 secdebug("set to new", "result for deleting lakitu: %d", result);
817 result = do_keychain_delete_sbd(); (void) result;
818 secdebug("set to new", "result for deleting sbd: %d", result);
820 a.accountKeyIsTrusted = false;
822 if (a.user_private_timer) {
823 dispatch_source_cancel(a.user_private_timer);
824 a.user_private_timer = NULL;
825 xpc_transaction_end();
828 if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) {
829 notify_cancel(a.lock_notification_token);
830 a.lock_notification_token = NOTIFY_TOKEN_INVALID;
837 // update_interest_block;
839 SOSUnregisterTransportKeyParameter(a.key_transport);
840 SOSUnregisterTransportMessage(a.ids_message_transport);
841 SOSUnregisterTransportMessage(a.kvs_message_transport);
842 SOSUnregisterTransportCircle(a.circle_transport);
844 a.circle_transport = NULL;
845 a.kvs_message_transport = NULL;
846 a.ids_message_transport = NULL;
849 a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
851 [a ensureFactoryCircles]; // Does rings too
853 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
854 SOSAccountEnsureUUID(a);
855 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew");
856 a.key_interests_need_updating = true;
859 bool SOSAccountIsNew(SOSAccount* account, CFErrorRef *error){
861 SOSAccountTrustClassic* trust = account.trust;
862 if(account.accountKeyIsTrusted != false || trust.departureCode != kSOSNeverAppliedToCircle ||
863 CFSetGetCount((__bridge CFSetRef)trust.retirees) != 0)
866 if(trust.retirees != nil)
868 if(trust.expansion != nil)
871 if(account.user_private_timer != NULL || account.lock_notification_token != NOTIFY_TOKEN_INVALID)
879 dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) {
880 return account.queue;
883 void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){
884 account.accountKeyIsTrusted = true;
887 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
889 SOSCCStatus circleStatus = [self.trust getCircleStatus:error];
890 if (!SOSAccountHasPublicKey(self, error)) {
891 if(circleStatus == kSOSCCInCircle) {
893 CFReleaseNull(*error);
894 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);
897 circleStatus = kSOSCCError;
902 bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) {
904 SOSAccountTrustClassic *trust = account.trust;
905 NSMutableSet* retirees = trust.retirees;
906 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
907 CFSetSetValue((__bridge CFMutableSetRef) retirees, peer);
908 CFErrorRef cleanupError = NULL;
909 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) {
910 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
912 CFReleaseSafe(cleanupError);
917 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) {
918 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
919 SOSPeerInfoRef me = account.peerInfo;
920 bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
922 SOSAccountTrustClassic *trust = account.trust;
923 NSMutableSet* retirees = trust.retirees;
925 if(!new_circle) return NULL;
926 __block bool workDone = false;
928 CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) {
929 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
930 if (isSOSPeerInfo(pi)) {
931 SOSCircleUpdatePeerInfo(new_circle, pi);
937 if(workDone && SOSCircleCountPeers(new_circle) == 0) {
938 SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
942 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
943 if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) ||
944 ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){
945 CFReleaseNull(new_circle);
949 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
950 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
951 // we have a chance to set things right with a SetCreds/Join sequence. This will cause
952 // handleUpdateCircle to return false.
953 CFReleaseNull(new_circle);
957 // This case is when we aren't an applicant and the circle is retirement-empty.
958 secnotice("circleOps", "Reset to empty with last retirement");
959 SOSCircleResetToEmpty(new_circle, NULL);
967 // MARK: Circle Membership change notificaion
970 void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
971 SOSAccountCircleMembershipChangeBlock copy = changeBlock;
972 [a.change_blocks addObject:copy];
975 void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
976 [a.change_blocks removeObject:changeBlock];
979 void SOSAccountPurgeIdentity(SOSAccount* account) {
980 SOSAccountTrustClassic *trust = account.trust;
981 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
984 // Purge private key but don't return error if we can't.
985 CFErrorRef purgeError = NULL;
986 if (!SOSFullPeerInfoPurgePersistentKey(identity, &purgeError)) {
987 secwarning("Couldn't purge persistent key for %@ [%@]", identity, purgeError);
989 CFReleaseNull(purgeError);
991 trust.fullPeerInfo = nil;
995 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) {
996 SOSAccountTrustClassic *trust = account.trust;
997 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
998 NSMutableSet* retirees = trust.retirees;
1000 SOSFullPeerInfoRef fpi = identity;
1001 if(!fpi) return false;
1003 CFErrorRef localError = NULL;
1005 bool retval = false;
1007 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
1009 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1011 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1012 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1013 // Remove our application if we have one.
1014 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1015 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1016 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1017 CFErrorRef cleanupError = NULL;
1018 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1019 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1021 CFReleaseSafe(cleanupError);
1025 // Store the retirement record locally.
1026 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1028 trust.retirees = retirees;
1030 // Write retirement to Transport
1031 CFErrorRef postError = NULL;
1032 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1033 secwarning("Couldn't post retirement (%@)", postError);
1035 if(![account.circle_transport flushChanges:&postError]){
1036 secwarning("Couldn't flush retirement data (%@)", postError);
1038 CFReleaseNull(postError);
1041 SOSAccountPurgeIdentity(account);
1045 CFReleaseNull(localError);
1046 CFReleaseNull(retire_peer);
1050 bool sosAccountLeaveRing(SOSAccount* account, SOSRingRef ring, CFErrorRef* error) {
1051 SOSAccountTrustClassic *trust = account.trust;
1052 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1054 SOSFullPeerInfoRef fpi = identity;
1055 if(!fpi) return false;
1056 SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
1057 CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
1059 CFErrorRef localError = NULL;
1061 bool retval = false;
1062 bool writeRing = false;
1063 bool writePeerInfo = false;
1065 if(SOSRingHasPeerID(ring, peerID)) {
1066 writePeerInfo = true;
1069 if(writePeerInfo || writeRing) {
1070 SOSRingWithdraw(ring, NULL, fpi, error);
1074 CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
1077 [account.circle_transport kvsRingPostRing:SOSRingGetName(ring) ring:ring_data err:NULL];
1079 CFReleaseNull(ring_data);
1082 CFReleaseNull(localError);
1086 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1087 bool result = false;
1088 if (account.circle_transport) {
1089 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1095 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1096 local value that has been overwritten by a distant value. If there is no
1097 conflict between the local and the distant values when doing the initial
1098 sync (e.g. if the cloud has no data stored or the client has not stored
1099 any data yet), you'll never see that notification.
1101 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1102 with server but initial round trip with server does not imply
1103 NSUbiquitousKeyValueStoreInitialSyncChange.
1108 // MARK: Status summary
1112 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1114 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1115 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1116 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1117 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1118 case kSOSCCError: return CFSTR("kSOSCCError");
1120 return CFSTR("kSOSCCError");
1122 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1123 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1124 return kSOSCCInCircle;
1125 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1126 return kSOSCCInCircle;
1127 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1128 return kSOSCCNotInCircle;
1129 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1130 return kSOSCCRequestPending;
1131 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1132 return kSOSCCCircleAbsent;
1133 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1140 // MARK: Account Reset Circles
1143 // This needs to be called within a [trust modifyCircle()] block
1146 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1147 bool retval = false;
1149 SOSAccountTrustClassic *trust = account.trust;
1150 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1152 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1154 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1155 if(SOSPeerInfoIsCloudIdentity(peer)) {
1156 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1158 CFSetAddValue(iCloud2Remove, peer);
1160 CFReleaseNull(icfpi);
1164 if(CFSetGetCount(iCloud2Remove) > 0) {
1166 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1168 CFReleaseNull(iCloud2Remove);
1173 // MARK: start backups
1177 bool SOSAccountEnsureInBackupRings(SOSAccount* account) {
1178 __block bool result = false;
1179 __block CFErrorRef error = NULL;
1180 secnotice("backup", "Ensuring in rings");
1182 NSData *backupKey = nil;
1184 if(!account.backup_key){
1189 backupKey = (__bridge_transfer NSData*)SOSPeerInfoV2DictionaryCopyData(account.peerInfo, sBackupKeyKey);
1191 bool updateBackupKey = ![backupKey isEqual:account.backup_key];
1193 if(updateBackupKey) {
1194 result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1195 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error);
1198 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1199 CFReleaseNull(error);
1203 if(!account.backup_key)
1206 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1208 CFReleaseNull(error);
1211 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){
1213 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1215 CFReleaseNull(error);
1219 CFDataRef recoveryKeyBackFromRing = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, &error);
1221 if(updateBackupKey || recoveryKeyBackFromRing) {
1222 // It's a good key, we're going with it. Stop backing up the old way.
1223 CFErrorRef localError = NULL;
1224 if (!SOSDeleteV0Keybag(&localError)) {
1225 secerror("Failed to delete v0 keybag: %@", localError);
1227 CFReleaseNull(localError);
1231 // Setup backups the new way.
1232 SOSAccountForEachBackupView(account, ^(const void *value) {
1233 CFStringRef viewName = (CFStringRef)value;
1234 if(updateBackupKey || (recoveryKeyBackFromRing && !SOSAccountRecoveryKeyIsInBackupAndCurrentInView(account, viewName))) {
1235 result &= SOSAccountNewBKSBForView(account, viewName, &error);
1241 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1243 CFReleaseNull(error);
1248 // MARK: Recovery Public Key Functions
1251 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1252 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1253 if(retval) secnotice("recovery", "successfully registered recovery public key");
1254 else secnotice("recovery", "could not register recovery public key: %@", *error);
1255 SOSClearErrorIfTrue(retval, error);
1259 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1260 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1261 if(retval) secnotice("recovery", "RK Cleared");
1262 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1263 SOSClearErrorIfTrue(retval, error);
1267 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1268 CFDataRef result = NULL;
1269 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1270 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1272 if (!isData(result)) {
1273 CFReleaseNull(result);
1275 SOSClearErrorIfTrue(result != NULL, error);
1285 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1286 bool use_cloud_peer, CFErrorRef* error) {
1287 SOSAccount* account = aTxn.account;
1288 SOSAccountTrustClassic *trust = account.trust;
1289 __block bool result = false;
1290 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1291 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1292 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1293 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1294 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1295 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1296 // this also clears initial sync data
1297 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1299 SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
1300 if (use_cloud_peer) {
1301 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1303 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1304 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1305 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1306 trust.departureCode = kSOSNeverLeftCircle;
1307 if(result && cloud_full_peer) {
1308 CFErrorRef localError = NULL;
1309 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1310 require_quiet(cloudid, finish);
1311 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1312 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1315 secerror("Failed to join with cloud identity: %@", localError);
1316 CFReleaseNull(localError);
1321 if (use_cloud_peer) {
1322 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1326 CFReleaseNull(cloud_full_peer);
1330 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) {
1331 SOSAccount* account = aTxn.account;
1332 SOSAccountTrustClassic *trust = account.trust;
1333 bool success = false;
1335 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1336 require_quiet(user_key, done); // Fail if we don't get one.
1338 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1340 if (trust.fullPeerInfo != NULL) {
1341 SOSPeerInfoRef myPeer = trust.peerInfo;
1342 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1343 require_quiet(!success, done);
1345 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1347 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1348 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1350 trust.fullPeerInfo = NULL;
1354 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
1356 require_quiet(success, done);
1358 trust.departureCode = kSOSNeverLeftCircle;
1364 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1365 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1366 return SOSAccountJoinCircles_internal(aTxn, false, error);
1369 CFStringRef SOSAccountCopyDeviceID(SOSAccount* account, CFErrorRef *error){
1370 CFStringRef result = NULL;
1371 SOSAccountTrustClassic *trust = account.trust;
1373 require_action_quiet(trust.fullPeerInfo, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
1375 result = SOSPeerInfoCopyDeviceID(trust.peerInfo);
1381 bool SOSAccountSetMyDSID(SOSAccountTransaction* txn, CFStringRef IDS, CFErrorRef* error){
1383 SOSAccount* account = txn.account;
1384 SOSAccountTrustClassic *trust = account.trust;
1386 secdebug("IDS Transport", "We are setting our device ID: %@", IDS);
1387 if(IDS != NULL && (CFStringGetLength(IDS) > 0)){
1388 if(!trust.fullPeerInfo){
1389 account.deviceID = [[NSString alloc] initWithString:(__bridge NSString * _Nonnull)(IDS)];
1390 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me"));
1393 result = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1395 SOSFullPeerInfoUpdateDeviceID(trust.fullPeerInfo, IDS, error);
1396 SOSFullPeerInfoUpdateTransportType(trust.fullPeerInfo, SOSTransportMessageTypeKVS, error);
1397 SOSFullPeerInfoUpdateTransportPreference(trust.fullPeerInfo, kCFBooleanFalse, error);
1398 SOSFullPeerInfoUpdateTransportFragmentationPreference(trust.fullPeerInfo, kCFBooleanTrue, error);
1399 SOSFullPeerInfoUpdateTransportAckModelPreference(trust.fullPeerInfo, kCFBooleanTrue, error);
1400 return SOSCircleHasPeer(circle, trust.peerInfo, NULL);
1406 // Initiate sync with all IDS peers, since we just learned we can talk that way.
1407 SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
1408 if (SOSPeerInfoShouldUseIDSTransport(account.peerInfo, peer)) {
1409 [txn requestSyncWith:(__bridge NSString*) SOSPeerInfoGetPeerID(peer)];
1413 account.deviceID = [[NSString alloc] initWithString:(__bridge NSString * _Nonnull)(IDS)];
1417 bool SOSAccountSendIDSTestMessage(SOSAccount* account, CFStringRef message, CFErrorRef *error){
1419 //construct message dictionary, circle -> peerID -> message
1421 CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
1423 CFStringRef operationString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kIDSSendOneMessage);
1424 CFDictionaryRef rawMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
1425 kIDSOperationType, operationString,
1426 kIDSMessageToSendKey, CFSTR("send IDS test message"),
1429 SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
1430 CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), rawMessage);
1433 result = [account.ids_message_transport SOSTransportMessageSendMessages:account.ids_message_transport pm:peerToMessage err:error];
1435 CFReleaseNull(peerToMessage);
1436 CFReleaseNull(operationString);
1437 CFReleaseNull(rawMessage);
1441 bool SOSAccountStartPingTest(SOSAccount* account, CFStringRef message, CFErrorRef *error){
1442 bool result = false;
1443 //construct message dictionary, circle -> peerID -> message
1444 SOSAccountTrustClassic *trust = account.trust;
1445 if(account.ids_message_transport == NULL)
1446 account.ids_message_transport = [[SOSMessageIDS alloc] initWithAccount:account andName:(__bridge NSString *)(SOSCircleGetName(trust.trustedCircle))];
1448 require_quiet(account.ids_message_transport, fail);
1449 CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
1451 CFStringRef operationString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kIDSStartPingTestMessage);
1452 CFDictionaryRef rawMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
1453 kIDSOperationType, operationString,
1454 kIDSMessageToSendKey, CFSTR("send IDS test message"),
1458 SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
1459 CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), rawMessage);
1462 result = [account.ids_message_transport SOSTransportMessageSendMessages:account.ids_message_transport pm:peerToMessage err:error];
1464 CFReleaseNull(peerToMessage);
1465 CFReleaseNull(rawMessage);
1466 CFReleaseNull(operationString);
1471 bool SOSAccountRetrieveDeviceIDFromKeychainSyncingOverIDSProxy(SOSAccount* account, CFErrorRef *error){
1474 __block bool success = true;
1475 __block CFErrorRef localError = NULL;
1476 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
1478 SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
1479 success = (sync_error == NULL);
1481 CFRetainAssign(localError, sync_error);
1484 dispatch_semaphore_signal(wait_for);
1487 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
1489 if(!success && localError != NULL && error != NULL){
1490 secerror("Could not ask KeychainSyncingOverIDSProxy for Device ID: %@", localError);
1491 *error = localError;
1495 secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
1500 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1501 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1502 return SOSAccountJoinCircles_internal(aTxn, true, error);
1505 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error)
1507 bool result = false;
1508 CFMutableSetRef peersToRemove = NULL;
1509 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1511 secnotice("circleOps", "Can't remove without userKey");
1514 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1515 SOSPeerInfoRef me = account.peerInfo;
1516 if(!(me_full && me))
1518 secnotice("circleOps", "Can't remove without being active peer");
1519 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1523 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1525 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1528 CFReleaseNull(peersToRemove);
1529 secnotice("circleOps", "No peerSet to remove");
1533 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1534 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1535 CFSetRemoveValue(peersToRemove, me);
1537 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1538 bool success = false;
1540 if(CFSetGetCount(peersToRemove) != 0) {
1541 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1542 success = SOSAccountGenerationSignatureUpdate(account, error);
1543 } else success = true;
1545 if (success && leaveCircle) {
1546 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1547 success = sosAccountLeaveCircle(account, circle, error);
1556 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1557 secnotice("circleOps", "Removed Peers from circle %@", description);
1561 CFReleaseNull(peersToRemove);
1566 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1567 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1568 dispatch_group_t group = dispatch_group_create();
1569 SOSAccountTrustClassic *trust = account.trust;
1570 __block bool result = false;
1571 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1572 // Add a task to the group
1573 dispatch_group_async(group, queue, ^{
1574 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1575 secnotice("circleOps", "Leaving circle by client request (Bail)");
1576 return sosAccountLeaveCircle(account, circle, error);
1579 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1580 dispatch_group_wait(group, milestone);
1582 trust.departureCode = kSOSWithdrewMembership;
1589 // MARK: Application
1592 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1593 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1594 SOSAccountTrustClassic *trust = account.trust;
1596 SOSPeerInfoRef me = trust.peerInfo;
1597 CFErrorRef peer_error = NULL;
1598 if (trust.trustedCircle && me &&
1599 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1600 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1601 __block bool modified = false;
1602 CFArrayForEach(peer_infos, ^(const void *value) {
1603 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1604 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1605 if (action(circle, trust.fullPeerInfo, peer)) {
1614 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1615 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1618 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1619 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1623 __block bool success = true;
1624 __block int64_t num_peers = 0;
1626 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1627 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1631 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1638 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1639 __block bool success = true;
1640 __block int64_t num_peers = 0;
1642 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1643 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1647 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1655 CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccount* account, CFErrorRef* error) {
1656 return CFSTR("We're compatible, go away");
1659 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1660 SOSAccountTrustClassic *trust = account.trust;
1661 return trust.departureCode;
1664 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1665 SOSAccountTrustClassic *trust = account.trust;
1666 trust.departureCode = reason;
1670 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1671 CFArrayRef result = NULL;
1672 CFNumberRef generation = NULL;
1673 SOSAccountTrustClassic *trust = account.trust;
1675 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1676 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1678 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1679 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1685 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1686 if (!SOSAccountHasPublicKey(account, error))
1689 return account.accountKeyIsTrusted;
1692 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1693 // TODO: this result is never set or used
1695 SOSAccountTrustClassic *trust = account.trust;
1697 secnotice("updates", "Ensuring peer registration.");
1699 if(!trust.trustedCircle || !trust.fullPeerInfo || !account.accountKeyIsTrusted)
1702 // If we are not in the circle, there is no point in setting up peers
1703 if(!SOSAccountIsMyPeerActive(account, NULL))
1706 // This code only uses the SOSFullPeerInfoRef for two things:
1707 // - Finding out if this device is in the trusted circle
1708 // - Using the peerID for this device to see if the current peer is "me"
1709 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
1711 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
1713 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
1714 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
1715 CFErrorRef localError = NULL;
1717 SOSMessage *messageTransport = SOSPeerInfoHasDeviceID(peer) ? account.ids_message_transport : account.kvs_message_transport;
1719 SOSEngineInitializePeerCoder((SOSEngineRef)[messageTransport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
1721 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
1722 CFReleaseSafe(localError);
1726 //Initialize our device ID
1727 [account.ids_message_transport SOSTransportMessageIDSGetIDSDeviceID:account];
1733 // Value manipulation
1736 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
1737 SOSAccountTrustClassic *trust = account.trust;
1738 if (!trust.expansion) {
1741 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
1744 bool SOSAccountAddEscrowRecords(SOSAccount* account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error){
1745 CFMutableDictionaryRef escrowRecords = (CFMutableDictionaryRef)SOSAccountGetValue(account, kSOSEscrowRecord, error);
1746 CFMutableDictionaryRef escrowCopied = NULL;
1747 bool success = false;
1749 if(isDictionary(escrowRecords) && escrowRecords != NULL)
1750 escrowCopied = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(escrowRecords), escrowRecords);
1752 escrowCopied = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
1754 CFDictionaryAddValue(escrowCopied, dsid, record);
1755 SOSAccountSetValue(account, kSOSEscrowRecord, escrowCopied, error);
1760 CFReleaseNull(escrowCopied);
1766 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
1767 bool success = false;
1769 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
1770 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
1775 bool SOSAccountCheckPeerAvailability(SOSAccount* account, CFErrorRef *error)
1777 CFStringRef operationString = NULL;
1778 CFDictionaryRef rawMessage = NULL;
1779 CFMutableSetRef peers = NULL;
1780 CFMutableDictionaryRef peerList = NULL;
1781 char* message = NULL;
1782 bool result = false;
1783 SOSAccountTrustClassic* trust = account.trust;
1784 if(account.ids_message_transport == NULL)
1785 account.ids_message_transport = [[SOSMessageIDS alloc] initWithAccount:account andName:(__bridge NSString *)SOSCircleGetName(trust.trustedCircle)];
1787 if(!account.ids_message_transport)
1790 //adding message type kIDSPeerAvailability so KeychainSyncingOverIDSProxy does not send this message as a keychain item
1792 operationString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kIDSPeerAvailability);
1793 rawMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
1794 kIDSOperationType, operationString,
1795 kIDSMessageToSendKey, CFSTR("checking peers"),
1798 peerList = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
1799 SOSCircleRef circle = trust.trustedCircle;
1801 //check each peer to make sure they have the right view set enabled
1802 CFSetRef mySubSet = SOSViewsGetV0SubviewSet();
1803 SOSCircleForEachValidPeer(circle, account.accountKey, ^(SOSPeerInfoRef peer) {
1804 if(!CFEqualSafe(peer, account.peerInfo)){
1805 CFMutableSetRef peerViews = SOSPeerInfoCopyEnabledViews(peer);
1806 CFSetRef intersectSets = CFSetCreateIntersection(kCFAllocatorDefault, mySubSet, peerViews);
1807 if(CFEqualSafe(intersectSets, mySubSet)){
1808 CFStringRef deviceID = SOSPeerInfoCopyDeviceID(peer);
1809 if(deviceID != NULL)
1810 CFDictionaryAddValue(peerList, SOSPeerInfoGetPeerID(peer), rawMessage);
1811 CFReleaseNull(deviceID);
1813 CFReleaseNull(peerViews);
1814 CFReleaseNull(intersectSets);
1818 require_quiet(CFDictionaryGetCount(peerList) > 0 , fail);
1820 result = [account.ids_message_transport SOSTransportMessageSendMessages:account.ids_message_transport pm:peerList err:error];
1823 CFReleaseNull(rawMessage);
1824 CFReleaseNull(operationString);
1825 CFReleaseNull(peerList);
1826 CFReleaseNull(peers);
1832 void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) {
1833 if (![account.trust isInCircle:NULL])
1835 SOSAccountTrustClassic *trust = account.trust;
1836 [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
1837 __block bool updated = false;
1838 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
1839 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
1841 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
1843 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
1844 CFErrorRef cleanupError = NULL;
1845 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
1846 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
1847 CFReleaseSafe(cleanupError);
1854 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
1856 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
1858 __block CFTypeRef object = NULL;
1860 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1861 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1863 CloudKeychainReplyBlock replyBlock =
1864 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
1866 object = returnedValues;
1871 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
1873 dispatch_semaphore_signal(waitSemaphore);
1876 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
1878 dispatch_semaphore_wait(waitSemaphore, finishTime);
1879 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
1884 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
1888 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
1890 CFStringRef uuid = SOSAccountCopyUUID(account);
1891 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1892 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1894 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1896 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
1898 dispatch_semaphore_signal(waitSemaphore);
1901 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
1902 dispatch_semaphore_wait(waitSemaphore, finishTime);
1903 CFReleaseNull(uuid);
1906 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
1908 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
1910 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
1911 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
1913 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
1914 CFStringAppend(timeDescription, decription);
1916 CFStringAppend(timeDescription, CFSTR("]"));
1918 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
1919 CFReleaseNull(timeDescription);
1921 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1922 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1923 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1925 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1927 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
1929 dispatch_semaphore_signal(waitSemaphore);
1932 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
1933 dispatch_semaphore_wait(waitSemaphore, finishTime);
1936 //Get all the key/values in KVS and remove old entries
1937 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
1939 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1941 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
1942 NSMutableArray *peerIDs = [NSMutableArray array];
1943 NSMutableArray *keysToRemove = [NSMutableArray array];
1945 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
1946 CFArrayForEach(peers, ^(const void *value) {
1947 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
1948 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
1950 //any peerid that is not ours gets added
1951 if(![[account.trust peerID] isEqualToString:peerID])
1952 [peerIDs addObject:peerID];
1954 CFReleaseNull(peers);
1956 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
1957 __block bool keyMatchesPeerID = false;
1959 //checks for full peer ids
1960 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
1961 //if key contains peerid of one active peer
1962 if([KVSKey containsString:PeerID]){
1963 secnotice("key-cleanup","key: %@", KVSKey);
1964 keyMatchesPeerID = true;
1967 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
1968 || [KVSKey hasPrefix:@"po"])
1969 [keysToRemove addObject:KVSKey];
1972 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
1973 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
1975 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
1977 //add last cleanup timestamp
1978 SOSAccountWriteLastCleanupTimestampToKVS(account);
1983 bool SOSAccountPopulateKVSWithBadKeys(SOSAccount* account, CFErrorRef* error) {
1985 NSMutableDictionary *testKeysAndValues = [NSMutableDictionary dictionary];
1986 [testKeysAndValues setObject:@"deadbeef" forKey:@"-ak|asdfjkl;asdfjk;"];
1987 [testKeysAndValues setObject:@"foobar" forKey:@"ak|asdfasdfasdf:qwerqwerqwer"];
1988 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"poak|asdfasdfasdfasdf"];
1989 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"po|asdfasdfasdfasdfasdfasdf"];
1990 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"k>KeyParm"];
1992 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1993 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1994 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1996 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1998 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2000 dispatch_semaphore_signal(waitSemaphore);
2003 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(testKeysAndValues), processQueue, replyBlock);
2004 dispatch_semaphore_wait(waitSemaphore, finishTime);
2009 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2010 SOSPeerInfoRef applicant = NULL;
2011 SOSAccountTrustClassic *trust = account.trust;
2012 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2013 if(!userKey) return false;
2014 if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
2016 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2018 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2025 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2027 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2028 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2029 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2030 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2032 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2035 if([parentUUID isEqualToString:viewUUID] || authoriative){
2037 /* check if we already have this entry */
2038 if ([seenUUID containsObject:viewUUID])
2041 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2042 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2047 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2049 NSMutableDictionary* strippedDown = [@{
2050 (id)kSecValueData : key,
2051 (id)kSecAttrServer : viewName,
2052 (id)kSecAttrAccount : viewUUID
2055 strippedDown[@"auth"] = @YES;
2057 [results addObject:strippedDown];
2058 [seenUUID addObject:viewUUID];
2064 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
2067 CKKSViewManager* manager = [CKKSViewManager manager];
2069 NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
2071 for (NSString *view in items) {
2072 NSString *uuid = items[view];
2073 NSDictionary *query = @{
2074 (id)kSecClass : (id)kSecClassInternetPassword,
2075 (id)kSecAttrNoLegacy : @YES,
2076 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2077 (id)kSecAttrAccount : uuid,
2078 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2079 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2080 (id)kSecReturnAttributes: @YES,
2081 (id)kSecReturnData: @YES,
2083 CFTypeRef result = NULL;
2084 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2085 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
2087 CFReleaseNull(result);
2094 SOSAccountGetAllTLKs(void)
2096 CFTypeRef result = NULL;
2097 NSMutableArray* results = [NSMutableArray array];
2098 NSMutableSet *seenUUID = [NSMutableSet set];
2100 // first use the TLK from the view manager
2101 AddViewManagerResults(results, seenUUID);
2103 //try to grab tlk-piggy items
2104 NSDictionary* query = @{
2105 (id)kSecClass : (id)kSecClassInternetPassword,
2106 (id)kSecAttrNoLegacy : @YES,
2107 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2108 (id)kSecAttrDescription: @"tlk",
2109 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2110 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2111 (id)kSecReturnAttributes: @YES,
2112 (id)kSecReturnData: @YES,
2115 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2116 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2118 CFReleaseNull(result);
2120 //try to grab tlk-piggy items
2122 (id)kSecClass : (id)kSecClassInternetPassword,
2123 (id)kSecAttrNoLegacy : @YES,
2124 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2125 (id)kSecAttrDescription: @"tlk-piggy",
2126 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2127 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2128 (id)kSecReturnAttributes: @YES,
2129 (id)kSecReturnData: @YES,
2132 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2133 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2135 CFReleaseNull(result);
2137 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2142 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2143 const uint8_t *der, uint8_t *der_end)
2145 if (type != kTLKUnknown) {
2146 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2147 piggy_encode_data(keychainData, der,
2148 piggy_encode_data(uuid, der,
2149 ccder_encode_uint64((uint64_t)type, der, der_end))));
2151 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2152 piggy_encode_data(keychainData, der,
2153 piggy_encode_data(uuid, der,
2154 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2158 static uint8_t* piggy_encode_data(NSData* data,
2159 const uint8_t *der, uint8_t *der_end)
2161 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2162 ccder_encode_body(data.length, data.bytes, der, der_end));
2167 name2type(NSString *view)
2169 if ([view isEqualToString:@"Manatee"])
2171 else if ([view isEqualToString:@"Engram"])
2173 else if ([view isEqualToString:@"AutoUnlock"])
2174 return kTLKAutoUnlock;
2175 if ([view isEqualToString:@"Health"])
2181 rank_type(NSString *view)
2183 if ([view isEqualToString:@"Manatee"])
2185 else if ([view isEqualToString:@"Engram"])
2187 else if ([view isEqualToString:@"AutoUnlock"])
2189 if ([view isEqualToString:@"Health"])
2195 parse_uuid(NSString *uuidString)
2197 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2199 [uuid getUUIDBytes:uuidblob];
2200 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2203 piggy_sizeof_data(NSData* data)
2205 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2208 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2209 if (type != kTLKUnknown) {
2210 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2211 piggy_sizeof_data(keychainData) +
2212 piggy_sizeof_data(uuid) +
2213 ccder_sizeof_uint64(type));
2215 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2216 piggy_sizeof_data(keychainData) +
2217 piggy_sizeof_data(uuid) +
2218 der_sizeof_string((__bridge CFStringRef)name, NULL));
2222 NSArray<NSDictionary*>*
2223 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2225 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2227 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2228 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2229 if (obj1[@"auth"] != NULL)
2231 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2232 if (obj2[@"auth"] != NULL)
2236 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2237 * since we are sorting backward, the Ascending/Descending looks wrong below.
2239 if (rank1 > rank2) {
2240 return NSOrderedAscending;
2241 } else if (rank1 < rank2) {
2242 return NSOrderedDescending;
2244 return NSOrderedSame;
2250 static NSArray<NSData *> *
2251 build_tlks(NSArray<NSDictionary*>* tlks)
2253 NSMutableArray *array = [NSMutableArray array];
2254 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2256 for (NSDictionary *item in sortedTLKs) {
2257 NSData* keychainData = item[(__bridge id)kSecValueData];
2258 NSString* name = item[(__bridge id)kSecAttrServer];
2259 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2260 NSData* uuid = parse_uuid(uuidString);
2262 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2264 unsigned char *der = [tlk mutableBytes];
2265 unsigned char *der_end = der + [tlk length];
2267 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2270 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2272 [array addObject:tlk];
2277 static NSArray<NSData *> *
2278 build_identities(NSArray<NSData *>* identities)
2280 NSMutableArray *array = [NSMutableArray array];
2281 for (NSData *item in identities) {
2282 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2284 unsigned char *der = [ident mutableBytes];
2285 unsigned char *der_end = der + [ident length];
2287 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2288 [array addObject:ident];
2295 static unsigned char *
2296 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2298 unsigned char *body_end = der_end;
2299 for (NSData *datum in data) {
2300 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2301 if (der_end == NULL)
2304 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2307 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2309 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2310 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2311 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2314 static NSData *encode_piggy(size_t IdentitiesBudget,
2316 NSArray<NSData*>* identities,
2317 NSArray<NSDictionary*>* tlks)
2319 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2320 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2321 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2322 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2323 size_t payloadSize = 0, identitiesSize = 0;
2324 NSMutableData *result = NULL;
2326 for (NSData *tlk in encodedTLKs) {
2327 if (TLKBudget - payloadSize < [tlk length])
2329 [budgetArray addObject:tlk];
2330 payloadSize += tlk.length;
2332 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2334 for (NSData *ident in encodedIdentities) {
2335 if (IdentitiesBudget - identitiesSize < [ident length])
2337 [identitiesArray addObject:ident];
2338 identitiesSize += ident.length;
2340 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2343 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2345 result = [NSMutableData dataWithLength:piggySize];
2347 unsigned char *der = [result mutableBytes];
2348 unsigned char *der_end = der + [result length];
2350 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2351 encode_data_array(identitiesArray, der,
2352 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2358 static const size_t SOSCCIdentitiesBudget = 120;
2359 static const size_t SOSCCTLKBudget = 500;
2362 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2364 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2367 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2369 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2371 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2372 if(SOSPeerInfoIsCloudIdentity(peer)) {
2373 CFArrayAppendValue(identities, peer);
2379 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, CFErrorRef *error) {
2380 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2381 secnotice("piggy", "identities: %@", identities);
2383 NSMutableArray *encodedIdenities = [NSMutableArray array];
2384 CFIndex i, count = CFArrayGetCount(identities);
2385 for (i = 0; i < count; i++) {
2386 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2387 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2389 [encodedIdenities addObject:data];
2391 CFRelease(identities);
2393 NSMutableArray* tlks = SOSAccountGetAllTLKs();
2395 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2398 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2399 SOSGenCountRef gencount = NULL;
2400 CFDataRef signature = NULL;
2401 SecKeyRef ourKey = NULL;
2403 CFDataRef pbblob = NULL;
2404 SOSCircleRef prunedCircle = NULL;
2406 secnotice("circleOps", "Making circle joining blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2408 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2409 require_quiet(userKey, errOut);
2411 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2412 require_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut);
2415 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2416 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2417 require_quiet(ourKey, errOut);
2420 SOSCircleRef currentCircle = [account.trust getCircle:error];
2421 require_quiet(currentCircle, errOut);
2423 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2424 require_quiet(prunedCircle, errOut);
2425 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2427 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2429 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2430 require_quiet(signature, errOut);
2432 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2435 CFReleaseNull(prunedCircle);
2436 CFReleaseNull(gencount);
2437 CFReleaseNull(signature);
2438 CFReleaseNull(ourKey);
2440 if(!pbblob && error != NULL) {
2441 secnotice("circleOps", "Failed to make circle joining blob as sponsor %@", *error);
2447 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2448 bool retval = false;
2449 SecKeyRef userKey = NULL;
2450 SOSAccountTrustClassic *trust = account.trust;
2451 SOSGenCountRef gencount = NULL;
2452 CFDataRef signature = NULL;
2453 SecKeyRef pubKey = NULL;
2454 bool setInitialSyncTimeoutToV0 = false;
2456 secnotice("circleOps", "Joining circles through piggy-back (SOSAccountCopyCircleJoiningBlob)");
2458 if (!isData(joiningBlob))
2461 userKey = SOSAccountGetPrivateCredential(account, error);
2465 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error))
2468 if(setInitialSyncTimeoutToV0){
2469 secnotice("piggy", "setting flag in account for piggybacking v0");
2470 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2473 secnotice("piggy", "setting flag in account for piggybacking v0");
2474 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2476 SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
2478 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2479 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2483 trust.fullPeerInfo, error);;
2487 CFReleaseNull(gencount);
2488 CFReleaseNull(pubKey);
2489 CFReleaseNull(signature);
2494 static char boolToChars(bool val, char truechar, char falsechar) {
2495 return val? truechar: falsechar;
2498 #define ACCOUNTLOGSTATE "accountLogState"
2499 void SOSAccountLogState(SOSAccount* account) {
2500 bool hasPubKey = account.accountKey != NULL;
2501 SOSAccountTrustClassic *trust = account.trust;
2502 bool pubTrusted = account.accountKeyIsTrusted;
2503 bool hasPriv = account.accountPrivateKey != NULL;
2504 SOSCCStatus stat = [account getCircleStatus:NULL];
2506 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2507 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2509 secnotice(ACCOUNTLOGSTATE, "Start");
2511 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2512 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2514 SOSAccountGetSOSCCStatusString(stat)
2516 CFReleaseNull(userPubKeyID);
2517 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2518 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2521 void SOSAccountLogViewState(SOSAccount* account) {
2522 bool isInCircle = [account.trust isInCircle:NULL];
2523 require_quiet(isInCircle, imOut);
2524 SOSPeerInfoRef mpi = account.peerInfo;
2525 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2526 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2528 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2529 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2530 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2531 boolToChars(isInitialComplete, 'I', 'i'),
2532 boolToChars(isBackupComplete, 'B', 'b'),
2535 CFReleaseNull(views);
2536 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2537 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2538 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2540 CFReleaseNull(unsyncedViews);
2543 secnotice(ACCOUNTLOGSTATE, "Finish");
2549 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2550 if(!isString(serial)) return;
2551 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2552 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2553 [account.trust updateV2Dictionary:account v2:newv2dict];
2556 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2558 secnotice("otrtimer", "timer fired!");
2559 CFErrorRef error = NULL;
2560 SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
2562 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2563 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2564 if(SOSCoderIsCoderInAwaitingState(coder)){
2565 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2566 CFErrorRef localError = NULL;
2567 SOSCoderReset(coder);
2568 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2569 secerror("Attempt to recover coder failed to restart: %@", localError);
2572 secnotice("otrtimer", "coder restarted!");
2573 SOSEngineSetCodersNeedSaving(engine, true);
2574 SOSPeerSetMustSendMessage(peer, true);
2575 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2577 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2578 SOSPeerRemoveOTRTimerEntry(peer);
2579 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2580 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2583 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2588 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2590 CFReleaseNull(error);
2593 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2595 __block SOSAccount* account = txn.account;
2596 CFErrorRef error = NULL;
2598 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2599 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2601 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2602 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2603 CFErrorRef error = NULL;
2604 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2607 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2608 bool sendResult = [account.ids_message_transport SOSTransportMessageSendMessage:account.ids_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2610 if(!sendResult || error){
2611 secnotice("ratelimit", "could not send message: %@", error);
2614 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2615 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2616 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2621 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2623 CFReleaseNull(error);