2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
6 * SOSAccount.c - Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
10 #import <Foundation/Foundation.h>
12 #import "keychain/SecureObjectSync/SOSAccount.h"
13 #import <Security/SecureObjectSync/SOSPeerInfo.h>
14 #import "keychain/SecureObjectSync/SOSPeerInfoV2.h"
15 #import "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
16 #import "keychain/SecureObjectSync/SOSTransportCircle.h"
17 #import "keychain/SecureObjectSync/SOSTransportCircleKVS.h"
18 #import "keychain/SecureObjectSync/SOSTransportMessage.h"
19 #import "keychain/SecureObjectSync/SOSTransportMessageKVS.h"
20 #import "keychain/SecureObjectSync/SOSTransportKeyParameter.h"
21 #import "keychain/SecureObjectSync/SOSKVSKeys.h"
22 #import "keychain/SecureObjectSync/SOSTransport.h"
23 #import "keychain/SecureObjectSync/SOSPeerCoder.h"
24 #import "keychain/SecureObjectSync/SOSInternal.h"
25 #import "keychain/SecureObjectSync/SOSRing.h"
26 #import "keychain/SecureObjectSync/SOSRingUtils.h"
27 #import "keychain/SecureObjectSync/SOSRingRecovery.h"
28 #import "keychain/SecureObjectSync/SOSAccountTransaction.h"
29 #import "keychain/SecureObjectSync/SOSAccountGhost.h"
30 #import "keychain/SecureObjectSync/SOSPiggyback.h"
31 #import "keychain/SecureObjectSync/SOSControlHelper.h"
32 #import "keychain/SecureObjectSync/SOSAuthKitHelpers.h"
35 #import "keychain/SecureObjectSync/SOSAccountTrust.h"
36 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
37 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
38 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
39 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
40 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
41 #import "keychain/SecureObjectSync/SOSPeerOTRTimer.h"
42 #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h"
43 #import "keychain/SecureObjectSync/SOSTypes.h"
45 #import "keychain/ckks/CKKSViewManager.h"
46 #import "keychain/ckks/CKKSLockStateTracker.h"
47 #import "keychain/ot/OTManager.h"
49 #include <Security/SecItemInternal.h>
50 #include <Security/SecEntitlements.h>
51 #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h"
52 #include "keychain/securityd/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 kSOSAccountName = CFSTR("AccountName");
71 const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
72 const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
73 const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout");
74 const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
75 const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
76 const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary");
77 const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey");
78 const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing");
79 const CFStringRef kSOSAccountUUID = CFSTR("UUID");
80 const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters");
81 const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef>
82 const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef>
83 const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount");
84 const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion");
85 NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry";
86 NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount";
87 NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup";
89 const uint64_t max_packet_size_over_idms = 500;
92 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
94 #define DATE_LENGTH 25
95 const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
97 @implementation SOSAccount
99 // Auto synthesis for most fields works great.
100 // A few CF fields need retention work when assigning.
106 self.gestalt = [NSMutableDictionary dictionary];
107 self.backup_key = nil;
110 self.trust = [SOSAccountTrustClassic trustClassic];
113 self.user_private_timer = NULL;
116 self._password_tmp = nil;
117 self.isListeningForSync = false;
118 self.lock_notification_token = -1;
120 self.circle_transport = NULL;
122 self.circle_rings_retirements_need_attention = false;
123 self.engine_peer_state_needs_repair = false;
124 self.key_interests_need_updating = false;
126 self.change_blocks = [NSMutableArray array];
128 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
130 self.accountKeyIsTrusted = false;
131 self.accountKeyDerivationParamters = nil;
133 self.accountKey = NULL;
134 self.accountPrivateKey = NULL;
135 self.previousAccountKey = NULL;
137 self.saveBlock = nil;
139 self.notifyCircleChangeOnExit = false;
140 self.notifyViewChangeOnExit = false;
141 self.notifyBackupOnExit = false;
143 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
150 CFReleaseNull(self->_accountKey);
151 CFReleaseNull(self->_accountPrivateKey);
152 CFReleaseNull(self->_previousAccountKey);
156 @synthesize accountKey = _accountKey;
158 - (void) setAccountKey: (SecKeyRef) key {
159 CFRetainAssign(self->_accountKey, key);
162 @synthesize previousAccountKey = _previousAccountKey;
164 - (void) setPreviousAccountKey: (SecKeyRef) key {
165 CFRetainAssign(self->_previousAccountKey, key);
168 @synthesize accountPrivateKey = _accountPrivateKey;
170 - (void) setAccountPrivateKey: (SecKeyRef) key {
171 CFRetainAssign(self->_accountPrivateKey, key);
174 // Syntactic sugar getters
176 - (BOOL) hasPeerInfo {
177 return self.fullPeerInfo != nil;
180 - (SOSPeerInfoRef) peerInfo {
181 return self.trust.peerInfo;
184 - (SOSFullPeerInfoRef) fullPeerInfo {
185 return self.trust.fullPeerInfo;
188 - (NSString*) peerID {
189 return self.trust.peerID;
192 -(bool) ensureFactoryCircles
202 NSString* circle_name = (__bridge_transfer NSString*)SOSDataSourceFactoryCopyName(self.factory);
208 CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL));
210 return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL);
213 -(void)ensureOctagonPeerKeys
216 CKKSLockStateTracker *tracker = [CKKSViewManager manager].lockStateTracker;
217 if (tracker && tracker.isLocked == false) {
218 [self.trust ensureOctagonPeerKeys:self.circle_transport];
223 -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f
227 self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
229 self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)];
231 SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
234 self.factory = f; // We adopt the factory. kthanksbai.
236 self.isListeningForSync = false;
238 self.accountPrivateKey = NULL;
239 self._password_tmp = NULL;
240 self.user_private_timer = NULL;
241 self.lock_notification_token = NOTIFY_TOKEN_INVALID;
243 self.change_blocks = [NSMutableArray array];
244 self.waitForInitialSync_blocks = NULL;
246 self.key_transport = nil;
247 self.circle_transport = NULL;
248 self.ck_storage = nil;
249 self.kvs_message_transport = nil;
251 self.circle_rings_retirements_need_attention = false;
252 self.engine_peer_state_needs_repair = false;
253 self.key_interests_need_updating = false;
255 self.backup_key =nil;
258 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
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 sleep(1); // make up for keygen time in password based version - let syncdefaults catch up
460 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
461 SecKeyRef accountPrivateKey = NULL;
462 CFErrorRef error = NULL;
463 NSDictionary *attributes = @{
464 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
465 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
468 accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error);
469 if (accountPrivateKey == NULL) {
470 complete(false, (__bridge NSError *)error);
471 secnotice("pairing", "SecKeyCreateWithData failed: %@", error);
472 CFReleaseNull(error);
476 if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) {
477 CFReleaseNull(accountPrivateKey);
478 complete(false, (__bridge NSError *)error);
479 secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error);
480 CFReleaseNull(error);
484 secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded");
486 CFReleaseNull(accountPrivateKey);
487 complete(true, NULL);
490 // This makes getting the private key the same as Asserting the password - we read all the other things
491 // that we just expressed interest in.
492 CFErrorRef error = NULL;
493 if (!Flush(&error)) {
494 secnotice("pairing", "failed final flush: %@", error ? error : NULL);
497 CFReleaseNull(error);
500 - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete
502 __block CFErrorRef localError = NULL;
503 __block NSData *applicationBlob = NULL;
504 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
505 SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError);
507 applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError));
508 CFReleaseNull(application);
511 complete(applicationBlob, (__bridge NSError *)localError);
512 CFReleaseNull(localError);
515 - (void)circleHash:(void (^)(NSString *, NSError *))complete
517 __block CFErrorRef localError = NULL;
518 __block NSString *circleHash = NULL;
519 SecAKSDoWithUserBagLockAssertion(&localError, ^{
520 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
521 circleHash = CFBridgingRelease(SOSCircleCopyHashString(txn.account.trust.trustedCircle));
524 complete(circleHash, (__bridge NSError *)localError);
525 CFReleaseNull(localError);
530 #if TARGET_OS_OSX || TARGET_OS_IOS
533 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
534 OTManager *otm = [OTManager manager];
535 SOSAccountGhostBustingOptions options = ([otm ghostbustByMidEnabled] == YES ? SOSGhostBustByMID: 0) |
536 ([otm ghostbustBySerialEnabled] == YES ? SOSGhostBustBySerialNumber : 0) |
537 ([otm ghostbustByAgeEnabled] == YES ? SOSGhostBustSerialByAge: 0);
542 #define GHOSTBUSTDATE @"ghostbustdate"
544 - (NSDate *) ghostBustGetDate {
545 if(! self.settings) {
546 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
548 return [self.settings valueForKey:GHOSTBUSTDATE];
551 - (bool) ghostBustCheckDate {
552 NSDate *ghostBustDate = [self ghostBustGetDate];
553 if(ghostBustDate && ([ghostBustDate timeIntervalSinceNow] <= 0)) return true;
557 - (void) ghostBustFollowup {
558 if(! self.settings) {
559 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite];
561 NSTimeInterval earliestGB = 60*60*24*3; // wait at least 3 days
562 NSTimeInterval latestGB = 60*60*24*7; // wait at most 7 days
563 NSDate *ghostBustDate = SOSCreateRandomDateBetweenNowPlus(earliestGB, latestGB);
564 [self.settings setValue:ghostBustDate forKey:GHOSTBUSTDATE];
567 // GhostBusting initial scheduling
568 - (void)ghostBustSchedule {
569 NSDate *ghostBustDate = [self ghostBustGetDate];
571 [self ghostBustFollowup];
575 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
576 __block bool result = false;
577 __block CFErrorRef localError = NULL;
579 if([SOSAuthKitHelpers accountIsHSA2]) {
580 [SOSAuthKitHelpers activeMIDs:^(NSSet <SOSTrustedDeviceAttributes *> * _Nullable activeMIDs, NSError * _Nullable error) {
581 SOSAuthKitHelpers *akh = [[SOSAuthKitHelpers alloc] initWithActiveMIDS:activeMIDs];
583 SecAKSDoWithUserBagLockAssertion(&localError, ^{
584 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
585 result = SOSAccountGhostBustCircle(txn.account, akh, options, 1);
586 [self ghostBustFollowup];
590 complete(result, NULL);
593 complete(false, NULL);
597 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
598 NSDate *ghostBustDate = [self ghostBustGetDate];
599 if(([ghostBustDate timeIntervalSinceNow] <= 0)) {
601 [self ghostBust: options complete: complete];
603 complete(false, nil);
608 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
609 // if no particular options are presented use the ramp options.
610 // If TLKs haven't been set yet this will cause a deadlock. THis interface should only be used by the security tool for internal testing.
612 options = [SOSAccount ghostBustGetRampSettings];
614 [self ghostBust: options complete: complete];
617 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
618 // If TLKs haven't been set yet this will cause a deadlock. THis interface should only be used by the security tool for internal testing.
619 NSMutableDictionary *gbInfoDictionary = [NSMutableDictionary new];
620 SOSAccountGhostBustingOptions options = [SOSAccount ghostBustGetRampSettings];
621 NSString *ghostBustDate = [[self ghostBustGetDate] description];
623 gbInfoDictionary[@"SOSGhostBustBySerialNumber"] = (SOSGhostBustBySerialNumber & options) ? @"ON": @"OFF";
624 gbInfoDictionary[@"SOSGhostBustByMID"] = (SOSGhostBustByMID & options) ? @"ON": @"OFF";
625 gbInfoDictionary[@"SOSGhostBustSerialByAge"] = (SOSGhostBustSerialByAge & options) ? @"ON": @"OFF";
626 gbInfoDictionary[@"SOSAccountGhostBustDate"] = ghostBustDate;
629 NSData *json = [NSJSONSerialization dataWithJSONObject:gbInfoDictionary
630 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
633 secnotice("ghostbust", "Error during ghostBustInfo JSONification: %@", err.localizedDescription);
640 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings {
644 - (NSDate *) ghostBustGetDate {
648 - (void) ghostBustFollowup {
651 - (void)ghostBustSchedule {
654 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
655 complete(false, NULL);
658 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete {
659 complete(false, NULL);
662 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete {
663 complete(false, nil);
666 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete {
670 - (bool) ghostBustCheckDate {
676 - (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete
678 __block CFErrorRef localError = NULL;
679 __block NSData *blob = NULL;
680 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant);
682 complete(NULL, (__bridge NSError *)localError);
683 CFReleaseNull(localError);
687 SecAKSDoWithUserBagLockAssertionSoftly(^{
688 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
689 blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError));
695 complete(blob, (__bridge NSError *)localError);
696 CFReleaseNull(localError);
699 - (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete
701 __block CFErrorRef localError = NULL;
702 __block bool res = false;
704 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
705 res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError);
708 complete(res, (__bridge NSError *)localError);
709 CFReleaseNull(localError);
712 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete
714 CFErrorRef error = NULL;
715 uint32_t isflags = 0;
717 if (flags & SOSControlInitialSyncFlagTLK)
718 isflags |= SecServerInitialSyncCredentialFlagTLK;
719 if (flags & SOSControlInitialSyncFlagPCS)
720 isflags |= SecServerInitialSyncCredentialFlagPCS;
721 if (flags & SOSControlInitialSyncFlagPCSNonCurrent)
722 isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent;
723 if (flags & SOSControlInitialSyncFlagBluetoothMigration)
724 isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration;
727 NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error));
728 complete(array, (__bridge NSError *)error);
729 CFReleaseNull(error);
732 - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete
734 CFErrorRef error = NULL;
735 bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error);
736 complete(res, (__bridge NSError *)error);
737 CFReleaseNull(error);
740 - (void)triggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete
742 __block CFErrorRef localError = NULL;
743 __block bool res = false;
745 secnotice("sync", "trigger a forced sync for %@", peers);
747 SecAKSDoWithUserBagLockAssertion(&localError, ^{
748 [self performTransaction:^(SOSAccountTransaction *txn) {
750 NSSet *peersSet = [NSSet setWithArray:peers];
751 CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError);
752 if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) {
755 CFReleaseNull(handledPeers);
757 res = SOSAccountRequestSyncWithAllPeers(txn, &localError);
761 complete(res, (__bridge NSError *)localError);
762 CFReleaseNull(localError);
765 - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete
767 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
768 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
770 NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters];
771 complete(parameters, nil);
774 complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
778 - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete
780 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class
781 NSError* error = nil;
782 Class watchdogClass = NSClassFromString(@"SecdWatchdog");
784 [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error];
788 complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]);
796 - (void) flattenToSaveBlock {
797 if (self.saveBlock) {
798 NSError* error = nil;
799 NSData* saveData = [self encodedData:&error];
801 (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error);
805 CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) {
806 return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt);
809 CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) {
810 CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL);
811 return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict);
814 static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){
815 SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
816 //send new DSID over account changed
817 [account.circle_transport kvsSendOfficialDSID:dsid err:NULL];
821 void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) {
822 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
823 if(accountDSID == NULL) {
824 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
826 SOSAccountUpdateDSID(account, dsid);
827 } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
828 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
830 //DSID has changed, blast the account!
831 SOSAccountSetToNew(account);
833 //update DSID to the new DSID
834 SOSAccountUpdateDSID(account, dsid);
836 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
841 void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews)
843 [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews];
844 [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews];
847 #pragma clang diagnostic push
848 #pragma clang diagnostic ignored "-Wunused-value"
849 SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) {
850 SOSViewResultCode retval = kSOSCCGeneralViewError;
851 // The V0 view switches on and off all on it's own, we allow people the delusion
852 // of control and status if it's what we're stuck at., otherwise error.
853 if (SOSAccountSyncingV0(account)) {
854 require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
855 retval = kSOSCCViewMember;
857 require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
858 retval = kSOSCCViewNotMember;
863 #pragma clang diagnostic pop
865 SOSAccount* SOSAccountCreate(CFAllocatorRef allocator,
866 CFDictionaryRef gestalt,
867 SOSDataSourceFactoryRef factory) {
869 SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory];
870 [a ensureFactoryCircles];
871 SOSAccountEnsureUUID(a);
872 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate");
873 a.key_interests_need_updating = true;
878 static OSStatus do_delete(CFDictionaryRef query) {
881 result = SecItemDelete(query);
883 secerror("SecItemDelete: %d", (int)result);
889 do_keychain_delete_aks_bags()
892 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
893 kSecClass, kSecClassGenericPassword,
894 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
895 kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
896 kSecAttrService, CFSTR("SecureBackupService"),
897 kSecAttrSynchronizable, kCFBooleanTrue,
898 kSecUseTombstones, kCFBooleanFalse,
901 result = do_delete(item);
908 do_keychain_delete_identities()
911 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
912 kSecClass, kSecClassKey,
913 kSecAttrSynchronizable, kCFBooleanTrue,
914 kSecUseTombstones, kCFBooleanFalse,
915 kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
918 result = do_delete(item);
925 do_keychain_delete_lakitu()
928 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
929 kSecClass, kSecClassGenericPassword,
930 kSecAttrSynchronizable, kCFBooleanTrue,
931 kSecUseTombstones, kCFBooleanFalse,
932 kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
933 kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
934 kSecAttrService, CFSTR("EscrowService"),
937 result = do_delete(item);
944 do_keychain_delete_sbd()
947 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
948 kSecClass, kSecClassGenericPassword,
949 kSecAttrSynchronizable, kCFBooleanTrue,
950 kSecUseTombstones, kCFBooleanFalse,
951 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
954 result = do_delete(item);
960 void SOSAccountSetToNew(SOSAccount* a)
962 secnotice("accountChange", "Setting Account to New");
965 /* remove all syncable items */
966 result = do_keychain_delete_aks_bags(); (void) result;
967 secdebug("set to new", "result for deleting aks bags: %d", result);
969 result = do_keychain_delete_identities(); (void) result;
970 secdebug("set to new", "result for deleting identities: %d", result);
972 result = do_keychain_delete_lakitu(); (void) result;
973 secdebug("set to new", "result for deleting lakitu: %d", result);
975 result = do_keychain_delete_sbd(); (void) result;
976 secdebug("set to new", "result for deleting sbd: %d", result);
978 a.accountKeyIsTrusted = false;
980 if (a.user_private_timer) {
981 dispatch_source_cancel(a.user_private_timer);
982 a.user_private_timer = NULL;
983 xpc_transaction_end();
986 if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) {
987 notify_cancel(a.lock_notification_token);
988 a.lock_notification_token = NOTIFY_TOKEN_INVALID;
995 // update_interest_block;
997 SOSUnregisterTransportKeyParameter(a.key_transport);
998 SOSUnregisterTransportMessage(a.kvs_message_transport);
999 SOSUnregisterTransportCircle(a.circle_transport);
1001 a.circle_transport = NULL;
1002 a.kvs_message_transport = nil;
1005 a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]];
1007 [a ensureFactoryCircles]; // Does rings too
1009 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
1010 SOSAccountEnsureUUID(a);
1011 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew");
1012 a.key_interests_need_updating = true;
1015 bool SOSAccountIsNew(SOSAccount* account, CFErrorRef *error){
1016 bool result = false;
1017 SOSAccountTrustClassic* trust = account.trust;
1018 if(account.accountKeyIsTrusted != false || trust.departureCode != kSOSNeverAppliedToCircle ||
1019 CFSetGetCount((__bridge CFSetRef)trust.retirees) != 0)
1022 if(trust.retirees != nil)
1024 if(trust.expansion != nil)
1027 if(account.user_private_timer != NULL || account.lock_notification_token != NOTIFY_TOKEN_INVALID)
1035 dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) {
1036 return account.queue;
1039 void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){
1040 account.accountKeyIsTrusted = true;
1043 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
1045 SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error];
1046 if (!SOSAccountHasPublicKey(self, error)) {
1047 if(circleStatus == kSOSCCInCircle) {
1049 CFReleaseNull(*error);
1050 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);
1053 circleStatus = kSOSCCError;
1055 return circleStatus;
1058 -(bool) isInCircle:(CFErrorRef *)error
1060 SOSCCStatus result = [self getCircleStatus:error];
1061 if (result != kSOSCCInCircle) {
1062 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
1069 bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) {
1071 SOSAccountTrustClassic *trust = account.trust;
1072 NSMutableSet* retirees = trust.retirees;
1073 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
1074 CFSetSetValue((__bridge CFMutableSetRef) retirees, peer);
1075 CFErrorRef cleanupError = NULL;
1076 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) {
1077 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
1079 CFReleaseSafe(cleanupError);
1084 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) {
1085 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
1086 SOSPeerInfoRef me = account.peerInfo;
1087 bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
1089 SOSAccountTrustClassic *trust = account.trust;
1090 NSMutableSet* retirees = trust.retirees;
1092 if(!new_circle) return NULL;
1093 __block bool workDone = false;
1095 CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) {
1096 SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
1097 if (isSOSPeerInfo(pi)) {
1098 SOSCircleUpdatePeerInfo(new_circle, pi);
1104 if(workDone && SOSCircleCountPeers(new_circle) == 0) {
1105 SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
1109 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
1110 if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) ||
1111 ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){
1112 CFReleaseNull(new_circle);
1115 account.notifyBackupOnExit = true;
1117 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
1118 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
1119 // we have a chance to set things right with a SetCreds/Join sequence. This will cause
1120 // handleUpdateCircle to return false.
1121 CFReleaseNull(new_circle);
1125 // This case is when we aren't an applicant and the circle is retirement-empty.
1126 secnotice("circleOps", "Reset to empty with last retirement");
1127 SOSCircleResetToEmpty(new_circle, NULL);
1135 // MARK: Circle Membership change notificaion
1138 void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1139 SOSAccountCircleMembershipChangeBlock copy = changeBlock;
1140 [a.change_blocks addObject:copy];
1143 void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) {
1144 [a.change_blocks removeObject:changeBlock];
1147 void SOSAccountPurgeIdentity(SOSAccount* account) {
1148 SOSAccountTrustClassic *trust = account.trust;
1149 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1152 // Purge private key but don't return error if we can't.
1153 CFErrorRef purgeError = NULL;
1154 if (!SOSFullPeerInfoPurgePersistentKey(identity, &purgeError)) {
1155 secwarning("Couldn't purge persistent key for %@ [%@]", identity, purgeError);
1157 CFReleaseNull(purgeError);
1159 trust.fullPeerInfo = nil;
1163 bool sosAccountLeaveCircleWithAnalytics(SOSAccount* account, SOSCircleRef circle, NSData* parentData, CFErrorRef* error) {
1164 SOSAccountTrustClassic *trust = account.trust;
1165 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1166 NSMutableSet* retirees = trust.retirees;
1168 NSError* localError = nil;
1169 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentData error:&localError];
1171 SOSFullPeerInfoRef fpi = identity;
1172 if(!fpi) return false;
1174 CFErrorRef retiredError = NULL;
1176 bool retval = false;
1178 SFSignInAnalytics *promoteToRetiredEvent = [parent newSubTaskForEvent:@"promoteToRetiredEvent"];
1179 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
1181 [promoteToRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
1182 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
1184 *error = retiredError;
1186 CFReleaseNull(retiredError);
1189 [promoteToRetiredEvent stopWithAttributes:nil];
1192 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1194 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1195 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1196 // Remove our application if we have one.
1197 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1198 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1199 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1200 CFErrorRef cleanupError = NULL;
1201 SFSignInAnalytics *cleanupEvent = [parent newSubTaskForEvent:@"cleanupAfterPeerEvent"];
1202 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1203 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1205 [cleanupEvent stopWithAttributes:nil];
1206 CFReleaseSafe(cleanupError);
1210 // Store the retirement record locally.
1211 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1213 trust.retirees = retirees;
1215 // Write retirement to Transport
1216 CFErrorRef postError = NULL;
1217 SFSignInAnalytics *postRestirementEvent = [parent newSubTaskForEvent:@"postRestirementEvent"];
1218 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1219 [postRestirementEvent logRecoverableError:(__bridge NSError*)postError];
1220 secwarning("Couldn't post retirement (%@)", postError);
1222 [postRestirementEvent stopWithAttributes:nil];
1224 SFSignInAnalytics *flushChangesEvent = [parent newSubTaskForEvent:@"flushChangesEvent"];
1226 if(![account.circle_transport flushChanges:&postError]){
1227 [flushChangesEvent logRecoverableError:(__bridge NSError*)postError];
1228 secwarning("Couldn't flush retirement data (%@)", postError);
1230 [flushChangesEvent stopWithAttributes:nil];
1231 CFReleaseNull(postError);
1233 SFSignInAnalytics *purgeIdentityEvent = [parent newSubTaskForEvent:@"purgeIdentityEvent"];
1234 SOSAccountPurgeIdentity(account);
1235 [purgeIdentityEvent stopWithAttributes:nil];
1238 CFReleaseNull(retire_peer);
1242 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) {
1243 SOSAccountTrustClassic *trust = account.trust;
1244 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1245 NSMutableSet* retirees = trust.retirees;
1247 SOSFullPeerInfoRef fpi = identity;
1248 if(!fpi) return false;
1250 CFErrorRef localError = NULL;
1252 bool retval = false;
1254 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
1256 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1258 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1259 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1260 // Remove our application if we have one.
1261 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1262 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1263 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1264 CFErrorRef cleanupError = NULL;
1265 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1266 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1268 CFReleaseSafe(cleanupError);
1272 // Store the retirement record locally.
1273 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1275 trust.retirees = retirees;
1277 // Write retirement to Transport
1278 CFErrorRef postError = NULL;
1279 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1280 secwarning("Couldn't post retirement (%@)", postError);
1282 if(![account.circle_transport flushChanges:&postError]){
1283 secwarning("Couldn't flush retirement data (%@)", postError);
1285 CFReleaseNull(postError);
1288 SOSAccountPurgeIdentity(account);
1292 CFReleaseNull(localError);
1293 CFReleaseNull(retire_peer);
1297 bool sosAccountLeaveRing(SOSAccount* account, SOSRingRef ring, CFErrorRef* error) {
1298 SOSAccountTrustClassic *trust = account.trust;
1299 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1301 SOSFullPeerInfoRef fpi = identity;
1302 if(!fpi) return false;
1303 SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
1304 CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
1306 CFErrorRef localError = NULL;
1308 bool retval = false;
1309 bool writeRing = false;
1310 bool writePeerInfo = false;
1312 if(SOSRingHasPeerID(ring, peerID)) {
1313 writePeerInfo = true;
1316 if(writePeerInfo || writeRing) {
1317 SOSRingWithdraw(ring, NULL, fpi, error);
1321 CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
1324 [account.circle_transport kvsRingPostRing:SOSRingGetName(ring) ring:ring_data err:NULL];
1326 CFReleaseNull(ring_data);
1329 CFReleaseNull(localError);
1333 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1334 bool result = false;
1335 if (account.circle_transport) {
1336 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1342 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1343 local value that has been overwritten by a distant value. If there is no
1344 conflict between the local and the distant values when doing the initial
1345 sync (e.g. if the cloud has no data stored or the client has not stored
1346 any data yet), you'll never see that notification.
1348 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1349 with server but initial round trip with server does not imply
1350 NSUbiquitousKeyValueStoreInitialSyncChange.
1355 // MARK: Status summary
1359 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1361 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1362 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1363 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1364 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1365 case kSOSCCError: return CFSTR("kSOSCCError");
1367 return CFSTR("kSOSCCError");
1369 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1370 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1371 return kSOSCCInCircle;
1372 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1373 return kSOSCCInCircle;
1374 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1375 return kSOSCCNotInCircle;
1376 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1377 return kSOSCCRequestPending;
1378 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1379 return kSOSCCCircleAbsent;
1380 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1387 // MARK: Account Reset Circles
1390 // This needs to be called within a [trust modifyCircle()] block
1393 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1394 bool retval = false;
1396 SOSAccountTrustClassic *trust = account.trust;
1397 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1399 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1401 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1402 if(SOSPeerInfoIsCloudIdentity(peer)) {
1403 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1405 CFSetAddValue(iCloud2Remove, peer);
1407 CFReleaseNull(icfpi);
1411 if(CFSetGetCount(iCloud2Remove) > 0) {
1413 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1415 CFReleaseNull(iCloud2Remove);
1420 // MARK: start backups
1424 bool SOSAccountEnsureInBackupRings(SOSAccount* account) {
1425 __block bool result = false;
1426 __block CFErrorRef error = NULL;
1427 secnotice("backup", "Ensuring in rings");
1429 if(!account.backup_key){
1433 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){
1434 secnotice("backupkey", "account backup key isn't valid: %@", error);
1435 account.backup_key = nil;
1436 CFReleaseNull(error);
1440 NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(account.peerInfo);
1441 if(![peerBackupKey isEqual:account.backup_key]) {
1442 result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1443 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error);
1446 secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error);
1447 CFReleaseNull(error);
1452 // It's a good key, we're going with it. Stop backing up the old way.
1453 CFErrorRef localError = NULL;
1454 if (!SOSDeleteV0Keybag(&localError)) {
1455 secerror("Failed to delete v0 keybag: %@", localError);
1457 CFReleaseNull(localError);
1459 // Setup backups the new way.
1460 SOSAccountForEachBackupView(account, ^(const void *value) {
1461 CFStringRef viewName = asString(value, NULL);
1462 bool resetRing = SOSAccountValidateBackupRingForView(account, viewName, NULL);
1464 SOSAccountUpdateBackupRing(account, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
1465 SOSRingRef newRing = SOSAccountCreateBackupRingForView(account, viewName, error);
1472 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1474 CFReleaseNull(error);
1479 // MARK: Recovery Public Key Functions
1482 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1483 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1484 if(retval) secnotice("recovery", "successfully registered recovery public key");
1485 else secnotice("recovery", "could not register recovery public key: %@", *error);
1486 SOSClearErrorIfTrue(retval, error);
1490 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1491 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1492 if(retval) secnotice("recovery", "RK Cleared");
1493 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1494 SOSClearErrorIfTrue(retval, error);
1498 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1499 CFDataRef result = NULL;
1500 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1501 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1503 if (!isData(result)) {
1504 CFReleaseNull(result);
1506 SOSClearErrorIfTrue(result != NULL, error);
1515 static bool SOSAccountJoinCircleWithAnalytics(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1516 bool use_cloud_peer, NSData* parentEvent, CFErrorRef* error) {
1517 SOSAccount* account = aTxn.account;
1518 SOSAccountTrustClassic *trust = account.trust;
1519 __block bool result = false;
1520 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1521 SFSignInAnalytics *ensureFullPeerAvailableEvent = nil;
1522 NSError* localError = nil;
1523 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1525 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1526 ensureFullPeerAvailableEvent = [parent newSubTaskForEvent:@"ensureFullPeerAvailableEvent"];
1527 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1528 [ensureFullPeerAvailableEvent stopWithAttributes:nil];
1530 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1531 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1532 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1533 // this also clears initial sync data
1534 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1536 SOSAccountInitializeInitialSync(account);
1537 if (use_cloud_peer) {
1538 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1540 SFSignInAnalytics *acceptApplicantEvent = [parent newSubTaskForEvent:@"acceptApplicantEvent"];
1541 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1542 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1543 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1544 trust.departureCode = kSOSNeverLeftCircle;
1545 if(result && cloud_full_peer) {
1546 CFErrorRef localError = NULL;
1547 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1548 require_quiet(cloudid, finish);
1549 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1550 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1553 [acceptApplicantEvent logRecoverableError:(__bridge NSError *)(localError)];
1554 secerror("Failed to join with cloud identity: %@", localError);
1555 CFReleaseNull(localError);
1560 [acceptApplicantEvent stopWithAttributes:nil];
1561 if (use_cloud_peer) {
1562 SFSignInAnalytics *updateOutOfDateSyncViewsEvent = [acceptApplicantEvent newSubTaskForEvent:@"updateOutOfDateSyncViewsEvent"];
1563 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1564 [updateOutOfDateSyncViewsEvent stopWithAttributes:nil];
1568 CFReleaseNull(cloud_full_peer);
1572 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key,
1573 bool use_cloud_peer, CFErrorRef* error) {
1574 SOSAccount* account = aTxn.account;
1575 SOSAccountTrustClassic *trust = account.trust;
1576 __block bool result = false;
1577 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1578 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1579 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1580 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1581 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1582 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1583 // this also clears initial sync data
1584 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1586 SOSAccountInitializeInitialSync(account);
1587 if (use_cloud_peer) {
1588 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1590 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1591 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1592 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1593 trust.departureCode = kSOSNeverLeftCircle;
1594 if(result && cloud_full_peer) {
1595 CFErrorRef localError = NULL;
1596 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1597 require_quiet(cloudid, finish);
1598 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1599 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1602 secerror("Failed to join with cloud identity: %@", localError);
1603 CFReleaseNull(localError);
1608 if (use_cloud_peer) {
1609 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1613 CFReleaseNull(cloud_full_peer);
1617 static bool SOSAccountJoinCirclesWithAnalytics_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, NSData* parentEvent, CFErrorRef* error) {
1618 SOSAccount* account = aTxn.account;
1619 SOSAccountTrustClassic *trust = account.trust;
1620 bool success = false;
1622 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1623 require_quiet(user_key, done); // Fail if we don't get one.
1625 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1627 if (trust.fullPeerInfo != NULL) {
1628 SOSPeerInfoRef myPeer = trust.peerInfo;
1629 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1630 require_quiet(!success, done);
1632 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1634 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1635 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1637 trust.fullPeerInfo = NULL;
1641 success = SOSAccountJoinCircleWithAnalytics(aTxn, user_key, use_cloud_identity, parentEvent, error);
1643 require_quiet(success, done);
1645 trust.departureCode = kSOSNeverLeftCircle;
1651 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) {
1652 SOSAccount* account = aTxn.account;
1653 SOSAccountTrustClassic *trust = account.trust;
1654 bool success = false;
1656 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1657 require_quiet(user_key, done); // Fail if we don't get one.
1659 require_action_quiet(trust.trustedCircle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
1661 if (trust.fullPeerInfo != NULL) {
1662 SOSPeerInfoRef myPeer = trust.peerInfo;
1663 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1664 require_quiet(!success, done);
1666 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1668 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1669 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1671 trust.fullPeerInfo = NULL;
1675 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
1677 require_quiet(success, done);
1679 trust.departureCode = kSOSNeverLeftCircle;
1685 bool SOSAccountJoinCirclesWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1686 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCirclesWithAnalytics)");
1687 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, false, parentEvent, error);
1690 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1691 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1692 return SOSAccountJoinCircles_internal(aTxn, false, error);
1695 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) {
1696 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1697 return SOSAccountJoinCircles_internal(aTxn, true, error);
1700 bool SOSAccountJoinCirclesAfterRestoreWithAnalytics(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1701 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1702 return SOSAccountJoinCirclesWithAnalytics_internal(aTxn, true, parentEvent, error);
1705 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error)
1707 bool result = false;
1708 CFMutableSetRef peersToRemove = NULL;
1709 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1711 secnotice("circleOps", "Can't remove without userKey");
1714 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1715 SOSPeerInfoRef me = account.peerInfo;
1716 if(!(me_full && me))
1718 secnotice("circleOps", "Can't remove without being active peer");
1719 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1723 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1725 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1728 CFReleaseNull(peersToRemove);
1729 secnotice("circleOps", "No peerSet to remove");
1733 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1734 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1735 CFSetRemoveValue(peersToRemove, me);
1737 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1738 bool success = false;
1740 if(CFSetGetCount(peersToRemove) != 0) {
1741 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1742 success = SOSAccountGenerationSignatureUpdate(account, error);
1743 } else success = true;
1745 if (success && leaveCircle) {
1746 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1747 success = sosAccountLeaveCircle(account, circle, error);
1756 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1757 secnotice("circleOps", "Removed Peers from circle %@", description);
1761 CFReleaseNull(peersToRemove);
1766 bool SOSAccountRemovePeersFromCircleWithAnalytics(SOSAccount* account, CFArrayRef peers, NSData* parentEvent, CFErrorRef* error)
1769 NSError* localError = nil;
1770 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1772 bool result = false;
1773 CFMutableSetRef peersToRemove = NULL;
1774 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1776 secnotice("circleOps", "Can't remove without userKey");
1779 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1780 SOSPeerInfoRef me = account.peerInfo;
1781 if(!(me_full && me))
1783 secnotice("circleOps", "Can't remove without being active peer");
1784 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1788 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1790 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1793 CFReleaseNull(peersToRemove);
1794 secnotice("circleOps", "No peerSet to remove");
1798 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1799 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1800 CFSetRemoveValue(peersToRemove, me);
1802 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1803 bool success = false;
1805 if(CFSetGetCount(peersToRemove) != 0) {
1806 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1807 SFSignInAnalytics *generationSignatureUpdateEvent = [parent newSubTaskForEvent:@"generationSignatureUpdateEvent"];
1808 success = SOSAccountGenerationSignatureUpdate(account, error);
1809 if(error && *error){
1810 NSError* signatureUpdateError = (__bridge NSError*)*error;
1811 [generationSignatureUpdateEvent logUnrecoverableError:signatureUpdateError];
1813 [generationSignatureUpdateEvent stopWithAttributes:nil];
1814 } else success = true;
1816 if (success && leaveCircle) {
1817 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1818 success = sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
1827 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1828 secnotice("circleOps", "Removed Peers from circle %@", description);
1832 CFReleaseNull(peersToRemove);
1836 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1837 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1838 dispatch_group_t group = dispatch_group_create();
1839 SOSAccountTrustClassic *trust = account.trust;
1840 __block bool result = false;
1841 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1842 // Add a task to the group
1843 dispatch_group_async(group, queue, ^{
1844 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1845 secnotice("circleOps", "Leaving circle by client request (Bail)");
1846 return sosAccountLeaveCircle(account, circle, error);
1849 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1850 dispatch_group_wait(group, milestone);
1852 trust.departureCode = kSOSWithdrewMembership;
1859 // MARK: Application
1862 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1863 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1864 SOSAccountTrustClassic *trust = account.trust;
1866 SOSPeerInfoRef me = trust.peerInfo;
1867 CFErrorRef peer_error = NULL;
1868 if (trust.trustedCircle && me &&
1869 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1870 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1871 __block bool modified = false;
1872 CFArrayForEach(peer_infos, ^(const void *value) {
1873 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1874 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1875 if (action(circle, trust.fullPeerInfo, peer)) {
1884 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1885 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1888 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1889 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1893 __block int64_t acceptedPeers = 0;
1895 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1896 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1902 if (acceptedPeers == CFArrayGetCount(applicants))
1907 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1908 __block bool success = true;
1909 __block int64_t num_peers = 0;
1911 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1912 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1916 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1924 CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccount* account, CFErrorRef* error) {
1925 return CFSTR("We're compatible, go away");
1928 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1929 SOSAccountTrustClassic *trust = account.trust;
1930 return trust.departureCode;
1933 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1934 SOSAccountTrustClassic *trust = account.trust;
1935 trust.departureCode = reason;
1939 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1940 CFArrayRef result = NULL;
1941 CFNumberRef generation = NULL;
1942 SOSAccountTrustClassic *trust = account.trust;
1944 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1945 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1947 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1948 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1954 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1955 if (!SOSAccountHasPublicKey(account, error))
1958 return account.accountKeyIsTrusted;
1961 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1962 // TODO: this result is never set or used
1964 SOSAccountTrustClassic *trust = account.trust;
1966 secnotice("updates", "Ensuring peer registration.");
1969 secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
1973 if([account getCircleStatus: NULL] != kSOSCCInCircle) {
1977 // If we are not in the circle, there is no point in setting up peers
1978 if(!SOSAccountIsMyPeerActive(account, NULL)) {
1982 // This code only uses the SOSFullPeerInfoRef for two things:
1983 // - Finding out if this device is in the trusted circle
1984 // - Using the peerID for this device to see if the current peer is "me"
1985 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
1987 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
1989 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
1990 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
1991 CFErrorRef localError = NULL;
1993 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
1995 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
1996 CFReleaseSafe(localError);
2004 // Value manipulation
2007 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
2008 SOSAccountTrustClassic *trust = account.trust;
2009 if (!trust.expansion) {
2012 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
2015 bool SOSAccountAddEscrowRecords(SOSAccount* account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error){
2016 CFMutableDictionaryRef escrowRecords = (CFMutableDictionaryRef)SOSAccountGetValue(account, kSOSEscrowRecord, error);
2017 CFMutableDictionaryRef escrowCopied = NULL;
2018 bool success = false;
2020 if(isDictionary(escrowRecords) && escrowRecords != NULL)
2021 escrowCopied = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(escrowRecords), escrowRecords);
2023 escrowCopied = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2025 CFDictionaryAddValue(escrowCopied, dsid, record);
2026 SOSAccountSetValue(account, kSOSEscrowRecord, escrowCopied, error);
2031 CFReleaseNull(escrowCopied);
2037 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
2038 bool success = false;
2040 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
2041 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
2046 void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) {
2047 if (![account isInCircle:NULL]) {
2050 __block bool updateRings = false;
2051 SOSAccountTrustClassic *trust = account.trust;
2052 [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
2053 __block bool updated = false;
2054 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
2055 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
2057 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
2059 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
2060 CFErrorRef cleanupError = NULL;
2061 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
2062 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
2063 CFReleaseSafe(cleanupError);
2070 SOSAccountProcessBackupRings(account, NULL);
2074 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
2076 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
2078 __block CFTypeRef object = NULL;
2080 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2081 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2083 CloudKeychainReplyBlock replyBlock =
2084 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
2086 object = returnedValues;
2091 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
2093 dispatch_semaphore_signal(waitSemaphore);
2096 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
2098 dispatch_semaphore_wait(waitSemaphore, finishTime);
2099 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
2104 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
2108 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
2110 CFStringRef uuid = SOSAccountCopyUUID(account);
2111 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2112 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2114 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2116 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
2118 dispatch_semaphore_signal(waitSemaphore);
2121 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
2122 dispatch_semaphore_wait(waitSemaphore, finishTime);
2123 CFReleaseNull(uuid);
2126 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
2128 NSDate *now = [NSDate date];
2129 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
2131 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
2133 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
2135 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
2137 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
2138 CFStringAppend(timeDescription, decription);
2140 CFStringAppend(timeDescription, CFSTR("]"));
2142 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
2143 CFReleaseNull(timeDescription);
2145 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2146 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2147 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2149 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2151 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2153 dispatch_semaphore_signal(waitSemaphore);
2156 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
2157 dispatch_semaphore_wait(waitSemaphore, finishTime);
2160 // set the cleanup frequency to 3 days.
2161 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
2163 //Get all the key/values in KVS and remove old entries
2164 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
2166 // This should only happen on some number of days
2167 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
2168 NSDate *now = [NSDate date];
2169 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
2171 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
2175 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2177 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
2178 NSMutableArray *peerIDs = [NSMutableArray array];
2179 NSMutableArray *keysToRemove = [NSMutableArray array];
2181 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
2182 CFArrayForEach(peers, ^(const void *value) {
2183 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
2184 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
2186 //any peerid that is not ours gets added
2187 if(![[account.trust peerID] isEqualToString:peerID])
2188 [peerIDs addObject:peerID];
2190 CFReleaseNull(peers);
2192 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
2193 __block bool keyMatchesPeerID = false;
2195 //checks for full peer ids
2196 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
2197 //if key contains peerid of one active peer
2198 if([KVSKey containsString:PeerID]){
2199 secnotice("key-cleanup","key: %@", KVSKey);
2200 keyMatchesPeerID = true;
2203 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
2204 || [KVSKey hasPrefix:@"po"])
2205 [keysToRemove addObject:KVSKey];
2208 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
2209 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
2211 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
2213 //add last cleanup timestamp
2214 SOSAccountWriteLastCleanupTimestampToKVS(account);
2219 bool SOSAccountPopulateKVSWithBadKeys(SOSAccount* account, CFErrorRef* error) {
2221 NSMutableDictionary *testKeysAndValues = [NSMutableDictionary dictionary];
2222 [testKeysAndValues setObject:@"deadbeef" forKey:@"-ak|asdfjkl;asdfjk;"];
2223 [testKeysAndValues setObject:@"foobar" forKey:@"ak|asdfasdfasdf:qwerqwerqwer"];
2224 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"poak|asdfasdfasdfasdf"];
2225 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"po|asdfasdfasdfasdfasdfasdf"];
2226 [testKeysAndValues setObject:@"oldhistorycircle" forKey:@"k>KeyParm"];
2228 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
2229 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
2230 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2232 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
2234 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
2236 dispatch_semaphore_signal(waitSemaphore);
2239 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(testKeysAndValues), processQueue, replyBlock);
2240 dispatch_semaphore_wait(waitSemaphore, finishTime);
2245 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2246 SOSPeerInfoRef applicant = NULL;
2247 SOSAccountTrustClassic *trust = account.trust;
2248 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2249 if(!userKey) return false;
2250 if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
2252 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2254 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2261 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2263 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2264 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2265 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2266 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2268 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2271 if([parentUUID isEqualToString:viewUUID] || authoriative){
2273 /* check if we already have this entry */
2274 if ([seenUUID containsObject:viewUUID])
2277 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2278 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2283 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2285 NSMutableDictionary* strippedDown = [@{
2286 (id)kSecValueData : key,
2287 (id)kSecAttrServer : viewName,
2288 (id)kSecAttrAccount : viewUUID
2291 strippedDown[@"auth"] = @YES;
2293 [results addObject:strippedDown];
2294 [seenUUID addObject:viewUUID];
2300 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
2303 CKKSViewManager* manager = [CKKSViewManager manager];
2305 NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
2307 for (NSString *view in items) {
2308 NSString *uuid = items[view];
2309 NSDictionary *query = @{
2310 (id)kSecClass : (id)kSecClassInternetPassword,
2311 (id)kSecUseDataProtectionKeychain : @YES,
2312 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2313 (id)kSecAttrAccount : uuid,
2314 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2315 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2316 (id)kSecReturnAttributes: @YES,
2317 (id)kSecReturnData: @YES,
2319 CFTypeRef result = NULL;
2320 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2321 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
2323 CFReleaseNull(result);
2329 NSArray<NSDictionary *>*
2330 SOSAccountGetAllTLKs(void)
2332 CFTypeRef result = NULL;
2333 NSMutableArray<NSDictionary *>* results = [NSMutableArray array];
2334 NSMutableSet *seenUUID = [NSMutableSet set];
2336 // first use the TLK from the view manager
2337 AddViewManagerResults(results, seenUUID);
2339 //try to grab tlk-piggy items
2340 NSDictionary* query = @{
2341 (id)kSecClass : (id)kSecClassInternetPassword,
2342 (id)kSecUseDataProtectionKeychain : @YES,
2343 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2344 (id)kSecAttrDescription: @"tlk",
2345 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2346 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2347 (id)kSecReturnAttributes: @YES,
2348 (id)kSecReturnData: @YES,
2351 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2352 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2354 CFReleaseNull(result);
2356 //try to grab tlk-piggy items
2358 (id)kSecClass : (id)kSecClassInternetPassword,
2359 (id)kSecUseDataProtectionKeychain : @YES,
2360 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2361 (id)kSecAttrDescription: @"tlk-piggy",
2362 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2363 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2364 (id)kSecReturnAttributes: @YES,
2365 (id)kSecReturnData: @YES,
2368 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2369 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2371 CFReleaseNull(result);
2373 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2378 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2379 const uint8_t *der, uint8_t *der_end)
2381 if (type != kTLKUnknown) {
2382 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2383 piggy_encode_data(keychainData, der,
2384 piggy_encode_data(uuid, der,
2385 ccder_encode_uint64((uint64_t)type, der, der_end))));
2387 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2388 piggy_encode_data(keychainData, der,
2389 piggy_encode_data(uuid, der,
2390 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2394 static uint8_t* piggy_encode_data(NSData* data,
2395 const uint8_t *der, uint8_t *der_end)
2397 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2398 ccder_encode_body(data.length, data.bytes, der, der_end));
2402 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2404 name2type(NSString *view)
2406 if ([view isEqualToString:@"Manatee"])
2408 else if ([view isEqualToString:@"Engram"])
2410 else if ([view isEqualToString:@"AutoUnlock"])
2411 return kTLKAutoUnlock;
2412 if ([view isEqualToString:@"Health"])
2417 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2419 rank_type(NSString *view)
2421 if ([view isEqualToString:@"Manatee"])
2423 else if ([view isEqualToString:@"Engram"])
2425 else if ([view isEqualToString:@"AutoUnlock"])
2427 if ([view isEqualToString:@"Health"])
2433 parse_uuid(NSString *uuidString)
2435 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2437 [uuid getUUIDBytes:uuidblob];
2438 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2441 piggy_sizeof_data(NSData* data)
2443 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2446 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2447 if (type != kTLKUnknown) {
2448 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2449 piggy_sizeof_data(keychainData) +
2450 piggy_sizeof_data(uuid) +
2451 ccder_sizeof_uint64(type));
2453 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2454 piggy_sizeof_data(keychainData) +
2455 piggy_sizeof_data(uuid) +
2456 der_sizeof_string((__bridge CFStringRef)name, NULL));
2460 NSArray<NSDictionary*>*
2461 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2463 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2465 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2466 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2467 if (obj1[@"auth"] != NULL)
2469 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2470 if (obj2[@"auth"] != NULL)
2474 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2475 * since we are sorting backward, the Ascending/Descending looks wrong below.
2477 if (rank1 > rank2) {
2478 return NSOrderedAscending;
2479 } else if (rank1 < rank2) {
2480 return NSOrderedDescending;
2482 return NSOrderedSame;
2488 static NSArray<NSData *> *
2489 build_tlks(NSArray<NSDictionary*>* tlks)
2491 NSMutableArray *array = [NSMutableArray array];
2492 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2494 for (NSDictionary *item in sortedTLKs) {
2495 NSData* keychainData = item[(__bridge id)kSecValueData];
2496 NSString* name = item[(__bridge id)kSecAttrServer];
2497 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2498 NSData* uuid = parse_uuid(uuidString);
2500 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2502 unsigned char *der = [tlk mutableBytes];
2503 unsigned char *der_end = der + [tlk length];
2505 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2508 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2510 [array addObject:tlk];
2515 static NSArray<NSData *> *
2516 build_identities(NSArray<NSData *>* identities)
2518 NSMutableArray *array = [NSMutableArray array];
2519 for (NSData *item in identities) {
2520 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2522 unsigned char *der = [ident mutableBytes];
2523 unsigned char *der_end = der + [ident length];
2525 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2526 [array addObject:ident];
2533 static unsigned char *
2534 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2536 unsigned char *body_end = der_end;
2537 for (NSData *datum in data) {
2538 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2539 if (der_end == NULL)
2542 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2545 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2547 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2548 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2549 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2552 static NSData *encode_piggy(size_t IdentitiesBudget,
2554 NSArray<NSData*>* identities,
2555 NSArray<NSDictionary*>* tlks)
2557 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2558 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2559 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2560 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2561 size_t payloadSize = 0, identitiesSize = 0;
2562 NSMutableData *result = NULL;
2564 for (NSData *tlk in encodedTLKs) {
2565 if (TLKBudget - payloadSize < [tlk length])
2567 [budgetArray addObject:tlk];
2568 payloadSize += tlk.length;
2570 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2572 for (NSData *ident in encodedIdentities) {
2573 if (IdentitiesBudget - identitiesSize < [ident length])
2575 [identitiesArray addObject:ident];
2576 identitiesSize += ident.length;
2578 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2581 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2583 result = [NSMutableData dataWithLength:piggySize];
2585 unsigned char *der = [result mutableBytes];
2586 unsigned char *der_end = der + [result length];
2588 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2589 encode_data_array(identitiesArray, der,
2590 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2596 static const size_t SOSCCIdentitiesBudget = 120;
2597 static const size_t SOSCCTLKBudget = 500;
2600 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2602 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2605 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2607 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2609 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2610 if(SOSPeerInfoIsCloudIdentity(peer)) {
2611 CFArrayAppendValue(identities, peer);
2617 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) {
2619 NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array];
2620 NSArray<NSDictionary *>* tlks = nil;
2622 if (flags & kSOSInitialSyncFlagiCloudIdentity) {
2623 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2624 secnotice("piggy", "identities: %@", identities);
2626 CFIndex i, count = CFArrayGetCount(identities);
2627 for (i = 0; i < count; i++) {
2628 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2629 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2631 [encodedIdenities addObject:data];
2633 CFRelease(identities);
2636 if (flags & kSOSInitialSyncFlagTLKs) {
2637 tlks = SOSAccountGetAllTLKs();
2640 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2643 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
2644 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
2645 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
2646 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
2647 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
2648 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
2649 if(accountName == NULL) {
2650 accountName = CFSTR("Unavailable");
2652 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
2654 secnotice("circleOps",
2655 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
2656 operation, accountName, version, gencount, pkeyID, sigID, circleHash);
2657 CFReleaseNull(pkeyID);
2658 CFReleaseNull(sigID);
2659 CFReleaseNull(circleHash);
2662 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2663 SOSGenCountRef gencount = NULL;
2664 CFDataRef signature = NULL;
2665 SecKeyRef ourKey = NULL;
2667 CFDataRef pbblob = NULL;
2668 SOSCircleRef prunedCircle = NULL;
2670 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2672 SOSCCStatus circleStat = [account getCircleStatus:error];
2673 if(circleStat != kSOSCCInCircle) {
2674 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
2678 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2679 require_quiet(userKey, errOut);
2681 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2682 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
2683 secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
2686 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2687 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2688 require_quiet(ourKey, errOut);
2691 SOSCircleRef currentCircle = [account.trust getCircle:error];
2692 require_quiet(currentCircle, errOut);
2694 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2695 require_quiet(prunedCircle, errOut);
2696 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2698 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2700 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2701 require_quiet(signature, errOut);
2702 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
2703 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2706 CFReleaseNull(prunedCircle);
2707 CFReleaseNull(gencount);
2708 CFReleaseNull(signature);
2709 CFReleaseNull(ourKey);
2711 if(!pbblob && error != NULL) {
2712 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
2718 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2719 bool retval = false;
2720 SecKeyRef userKey = NULL;
2721 SOSAccountTrustClassic *trust = account.trust;
2722 SOSGenCountRef gencount = NULL;
2723 CFDataRef signature = NULL;
2724 SecKeyRef pubKey = NULL;
2725 bool setInitialSyncTimeoutToV0 = false;
2727 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
2729 if (!isData(joiningBlob)) {
2730 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
2734 userKey = SOSAccountGetPrivateCredential(account, error);
2736 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2740 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
2741 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2745 if(setInitialSyncTimeoutToV0){
2746 secnotice("circleOps", "setting flag in account for piggyback v0");
2747 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2749 secnotice("circleOps", "clearing flag in account for piggyback v0");
2750 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2752 SOSAccountInitializeInitialSync(account);
2754 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
2756 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2757 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2761 trust.fullPeerInfo, error);;
2764 CFReleaseNull(gencount);
2765 CFReleaseNull(pubKey);
2766 CFReleaseNull(signature);
2771 static char boolToChars(bool val, char truechar, char falsechar) {
2772 return val? truechar: falsechar;
2775 #define ACCOUNTLOGSTATE "accountLogState"
2776 void SOSAccountLogState(SOSAccount* account) {
2777 bool hasPubKey = account.accountKey != NULL;
2778 SOSAccountTrustClassic *trust = account.trust;
2779 bool pubTrusted = account.accountKeyIsTrusted;
2780 bool hasPriv = account.accountPrivateKey != NULL;
2781 SOSCCStatus stat = [account getCircleStatus:NULL];
2783 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2784 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2786 secnotice(ACCOUNTLOGSTATE, "Start");
2788 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2789 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2791 SOSAccountGetSOSCCStatusString(stat)
2793 CFReleaseNull(userPubKeyID);
2794 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2795 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2798 void SOSAccountLogViewState(SOSAccount* account) {
2799 bool isInCircle = [account.trust isInCircleOnly:NULL];
2800 require_quiet(isInCircle, imOut);
2801 SOSPeerInfoRef mpi = account.peerInfo;
2802 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2803 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2805 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2806 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2807 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2808 boolToChars(isInitialComplete, 'I', 'i'),
2809 boolToChars(isBackupComplete, 'B', 'b'),
2812 CFReleaseNull(views);
2813 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2814 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2815 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2817 CFReleaseNull(unsyncedViews);
2820 secnotice(ACCOUNTLOGSTATE, "Finish");
2826 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2827 if(!isString(serial)) return;
2828 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2829 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2830 [account.trust updateV2Dictionary:account v2:newv2dict];
2833 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2835 secnotice("otrtimer", "timer fired!");
2836 CFErrorRef error = NULL;
2837 SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
2839 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2840 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2841 if(SOSCoderIsCoderInAwaitingState(coder)){
2842 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2843 CFErrorRef localError = NULL;
2844 SOSCoderReset(coder);
2845 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2846 secerror("Attempt to recover coder failed to restart: %@", localError);
2849 secnotice("otrtimer", "coder restarted!");
2850 SOSEngineSetCodersNeedSaving(engine, true);
2851 SOSPeerSetMustSendMessage(peer, true);
2852 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2854 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2855 SOSPeerRemoveOTRTimerEntry(peer);
2856 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2857 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2860 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2865 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2867 CFReleaseNull(error);
2870 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2872 __block SOSAccount* account = txn.account;
2873 CFErrorRef error = NULL;
2875 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2876 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2878 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2879 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2880 CFErrorRef error = NULL;
2881 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2884 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2885 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2887 if(!sendResult || error){
2888 secnotice("ratelimit", "could not send message: %@", error);
2891 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2892 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2893 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2898 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2900 CFReleaseNull(error);