SOSAccount.m
1 /* 2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. 3 */ 4 5 /* 6 * SOSAccount.c - Implementation of the secure object syncing account. 7 * An account contains a SOSCircle for each protection domain synced. 8 */ 9 10 #import <Foundation/Foundation.h> 11 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" 33 34 #import "keychain/SecureObjectSync/SOSAccountTrust.h" 35 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h" 36 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h" 37 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h" 38 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h" 39 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Retirement.h" 40 #import "keychain/SecureObjectSync/SOSPeerOTRTimer.h" 41 #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h" 42 #import "keychain/SecureObjectSync/SOSTypes.h" 43 #if OCTAGON 44 #import "keychain/ckks/CKKSViewManager.h" 45 #import "keychain/ckks/CKKSLockStateTracker.h" 46 #import "keychain/ckks/CKKSNearFutureScheduler.h" 47 #import "keychain/ckks/CKKSPBFileStorage.h" 48 49 #import "keychain/ot/OTManager.h" 50 #import "keychain/ot/ObjCImprovements.h" 51 #import "keychain/ot/OctagonStateMachine.h" 52 #import "keychain/ot/OctagonStateMachineHelpers.h" 53 #endif 54 #include <Security/SecItemInternal.h> 55 #include <Security/SecEntitlements.h> 56 #include "keychain/securityd/SecItemServer.h" 57 58 #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h" 59 #include "keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h" 60 61 62 #import "ipc/SecdWatchdog.h" 63 #include <utilities/SecCFWrappers.h> 64 #include <utilities/SecCFError.h> 65 #include <utilities/SecFileLocations.h> 66 67 #include <os/activity.h> 68 #include <os/state_private.h> 69 70 #include <utilities/SecCoreCrypto.h> 71 72 #include <utilities/der_plist.h> 73 #include <utilities/der_plist_internal.h> 74 #include <corecrypto/ccder.h> 75 76 77 const CFStringRef kSOSAccountName = CFSTR("AccountName"); 78 const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord"); 79 const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced"); 80 const CFStringRef kSOSInitialSyncTimeoutV0 = CFSTR("initialsynctimeout"); 81 const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews"); 82 const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews"); 83 const CFStringRef kSOSTestV2Settings = CFSTR("v2dictionary"); 84 const CFStringRef kSOSRecoveryKey = CFSTR("RecoveryKey"); 85 const CFStringRef kSOSRecoveryRing = CFSTR("RecoveryRing"); 86 const CFStringRef kSOSAccountUUID = CFSTR("UUID"); 87 const CFStringRef kSOSRateLimitingCounters = CFSTR("RateLimitCounters"); 88 const CFStringRef kSOSAccountPeerNegotiationTimeouts = CFSTR("PeerNegotiationTimeouts"); //Dictionary<CFStringRef, CFNumberRef> 89 const CFStringRef kSOSAccountPeerLastSentTimestamp = CFSTR("kSOSAccountPeerLastSentTimestamp"); //Dictionary<CFStringRef, CFDateRef> 90 const CFStringRef kSOSAccountRenegotiationRetryCount = CFSTR("NegotiationRetryCount"); 91 const CFStringRef kOTRConfigVersion = CFSTR("OTRConfigVersion"); 92 NSString* const SecSOSAggdReattemptOTRNegotiation = @"com.apple.security.sos.otrretry"; 93 NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount"; 94 NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup"; 95 NSString* const kSOSIdentityStatusCompleteIdentity = @"completeIdentity"; 96 NSString* const kSOSIdentityStatusKeyOnly = @"keyOnly"; 97 NSString* const kSOSIdentityStatusPeerOnly = @"peerOnly"; 98 99 100 const uint64_t max_packet_size_over_idms = 500; 101 102 103 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable" 104 105 #define DATE_LENGTH 25 106 const CFStringRef kSOSAccountDebugScope = CFSTR("Scope"); 107 108 #if OCTAGON 109 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void); 110 111 @interface SOSAccount () <OctagonStateMachineEngine> 112 @property dispatch_queue_t stateMachineQueue; 113 @property (readwrite) OctagonStateMachine* stateMachine; 114 115 @property (readwrite) CKKSPBFileStorage<SOSAccountConfiguration*>* accountConfiguration; 116 117 @property CKKSNearFutureScheduler *performBackups; 118 @property CKKSNearFutureScheduler *performRingUpdates; 119 @end 120 #endif 121 122 @implementation SOSAccount 123 124 - (void)dealloc { 125 if(self) { 126 CFReleaseNull(self->_accountKey); 127 CFReleaseNull(self->_accountPrivateKey); 128 CFReleaseNull(self->_previousAccountKey); 129 CFReleaseNull(self->_peerPublicKey); 130 CFReleaseNull(self->_octagonSigningFullKeyRef); 131 CFReleaseNull(self->_octagonEncryptionFullKeyRef); 132 #if OCTAGON 133 [self.performBackups cancel]; 134 [self.performRingUpdates cancel]; 135 [self.stateMachine haltOperation]; 136 #endif 137 } 138 } 139 140 @synthesize accountKey = _accountKey; 141 142 - (void) setAccountKey: (SecKeyRef) key { 143 CFRetainAssign(self->_accountKey, key); 144 } 145 146 @synthesize accountPrivateKey = _accountPrivateKey; 147 148 - (void) setAccountPrivateKey: (SecKeyRef) key { 149 CFRetainAssign(self->_accountPrivateKey, key); 150 } 151 152 @synthesize previousAccountKey = _previousAccountKey; 153 154 - (void) setPreviousAccountKey: (SecKeyRef) key { 155 CFRetainAssign(self->_previousAccountKey, key); 156 } 157 158 @synthesize peerPublicKey = _peerPublicKey; 159 160 - (void) setPeerPublicKey: (SecKeyRef) key { 161 CFRetainAssign(self->_peerPublicKey, key); 162 } 163 164 // Syntactic sugar getters 165 166 - (BOOL) hasPeerInfo { 167 return self.fullPeerInfo != nil; 168 } 169 170 - (SOSPeerInfoRef) peerInfo { 171 return self.trust.peerInfo; 172 } 173 174 - (SOSFullPeerInfoRef) fullPeerInfo { 175 return self.trust.fullPeerInfo; 176 } 177 178 - (NSString*) peerID { 179 return self.trust.peerID; 180 } 181 182 -(bool) ensureFactoryCircles 183 { 184 if (self.factory == nil){ 185 return false; 186 } 187 188 NSString* circle_name = CFBridgingRelease(SOSDataSourceFactoryCopyName(self.factory)); 189 if (!circle_name){ 190 return false; 191 } 192 193 CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL)); 194 195 return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL); 196 } 197 198 -(void)ensureOctagonPeerKeys 199 { 200 #if OCTAGON 201 CKKSLockStateTracker *tracker = [CKKSLockStateTracker globalTracker]; 202 if (tracker && tracker.isLocked == false) { 203 [self.trust ensureOctagonPeerKeys:self.circle_transport]; 204 } 205 #endif 206 } 207 208 -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f 209 { 210 if ((self = [super init])) { 211 self.queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL); 212 213 self.gestalt = [[NSDictionary alloc] initWithDictionary:(__bridge NSDictionary * _Nonnull)(newGestalt)]; 214 215 SOSAccountTrustClassic *t = [[SOSAccountTrustClassic alloc] initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]]; 216 217 self.trust = t; 218 self.factory = f; // We adopt the factory. kthanksbai. 219 220 self.isListeningForSync = false; 221 222 self.accountPrivateKey = NULL; 223 self._password_tmp = NULL; 224 self.user_private_timer = NULL; 225 self.lock_notification_token = NOTIFY_TOKEN_INVALID; 226 227 self.change_blocks = [NSMutableArray array]; 228 229 self.key_transport = nil; 230 self.circle_transport = NULL; 231 self.ck_storage = nil; 232 self.kvs_message_transport = nil; 233 234 self.circle_rings_retirements_need_attention = false; 235 self.engine_peer_state_needs_repair = false; 236 self.key_interests_need_updating = false; 237 self.need_backup_peers_created_after_backup_key_set = false; 238 239 self.backup_key =nil; 240 self.deviceID = nil; 241 242 self.waitForInitialSync_blocks = [NSMutableDictionary dictionary]; 243 self.accountKeyIsTrusted = false; 244 self.accountKeyDerivationParameters = NULL; 245 self.accountKey = NULL; 246 self.previousAccountKey = NULL; 247 self.peerPublicKey = NULL; 248 249 self.saveBlock = nil; 250 251 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite]; 252 253 [self ensureFactoryCircles]; 254 SOSAccountEnsureUUID(self); 255 self.accountIsChanging = false; 256 257 #if OCTAGON 258 [self setupStateMachine]; 259 #endif 260 } 261 return self; 262 } 263 264 - (void)startStateMachine 265 { 266 #if OCTAGON 267 [self.stateMachine startOperation]; 268 #endif 269 } 270 271 -(BOOL)isEqual:(id) object 272 { 273 if(![object isKindOfClass:[SOSAccount class]]) 274 return NO; 275 276 SOSAccount* left = object; 277 return ([self.gestalt isEqual: left.gestalt] && 278 CFEqualSafe(self.trust.trustedCircle, left.trust.trustedCircle) && 279 [self.trust.expansion isEqual: left.trust.expansion] && 280 CFEqualSafe(self.trust.fullPeerInfo, left.trust.fullPeerInfo)); 281 282 } 283 284 - (void)userPublicKey:(void ((^))(BOOL trusted, NSData *spki, NSError *error))reply 285 { 286 dispatch_async(self.queue, ^{ 287 if (!self.accountKeyIsTrusted || self.accountKey == NULL) { 288 NSDictionary *userinfo = @{ 289 (id)kCFErrorDescriptionKey : @"User public key not trusted", 290 }; 291 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]); 292 return; 293 } 294 295 NSData *data = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(self.accountKey)); 296 if (data == NULL) { 297 NSDictionary *userinfo = @{ 298 (id)kCFErrorDescriptionKey : @"User public not available", 299 }; 300 reply(self.accountKeyIsTrusted, NULL, [NSError errorWithDomain:(__bridge NSString *)kSOSErrorDomain code:kSOSErrorPublicKeyAbsent userInfo:userinfo]); 301 return; 302 } 303 reply(self.accountKeyIsTrusted, data, NULL); 304 }); 305 } 306 307 - (void)kvsPerformanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *))reply 308 { 309 /* Need to collect performance counters from all subsystems, not just circle transport, don't have counters yet though */ 310 SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error) 311 { 312 reply((__bridge NSDictionary *)returnedValues); 313 }); 314 } 315 316 - (void)rateLimitingPerformanceCounters:(void(^)(NSDictionary <NSString *, NSString *> *))reply 317 { 318 CFErrorRef error = NULL; 319 CFDictionaryRef rateLimitingCounters = (CFDictionaryRef)SOSAccountGetValue(self, kSOSRateLimitingCounters, &error); 320 reply((__bridge NSDictionary*)rateLimitingCounters ? (__bridge NSDictionary*)rateLimitingCounters : [NSDictionary dictionary]); 321 } 322 323 - (void)stashedCredentialPublicKey:(void(^)(NSData *, NSError *error))reply 324 { 325 dispatch_async(self.queue, ^{ 326 CFErrorRef error = NULL; 327 328 SecKeyRef user_private = SOSAccountCopyStashedUserPrivateKey(self, &error); 329 if (user_private == NULL) { 330 reply(NULL, (__bridge NSError *)error); 331 CFReleaseNull(error); 332 return; 333 } 334 335 NSData *publicKey = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(user_private)); 336 CFReleaseNull(user_private); 337 reply(publicKey, NULL); 338 }); 339 } 340 341 - (void)assertStashedAccountCredential:(void(^)(BOOL result, NSError *error))complete 342 { 343 dispatch_async(self.queue, ^{ 344 CFErrorRef error = NULL; 345 bool result = SOSAccountAssertStashedAccountCredential(self, &error); 346 complete(result, (__bridge NSError *)error); 347 CFReleaseNull(error); 348 }); 349 } 350 351 static bool SyncKVSAndWait(CFErrorRef *error) { 352 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0); 353 354 __block bool success = false; 355 356 secnoticeq("fresh", "EFP calling SOSCloudKeychainSynchronizeAndWait"); 357 358 os_activity_initiate("CloudCircle EFRESH", OS_ACTIVITY_FLAG_DEFAULT, ^(void) { 359 SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) { 360 secnotice("fresh", "EFP returned, callback error: %@", sync_error); 361 362 success = (sync_error == NULL); 363 if (error) { 364 CFRetainAssign(*error, sync_error); 365 } 366 367 dispatch_semaphore_signal(wait_for); 368 }); 369 370 371 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER); 372 secnotice("fresh", "EFP complete: %s %@", success ? "success" : "failure", error ? *error : NULL); 373 }); 374 375 return success; 376 } 377 378 static bool Flush(CFErrorRef *error) { 379 __block bool success = false; 380 381 dispatch_semaphore_t wait_for = dispatch_semaphore_create(0); 382 secnotice("flush", "Starting"); 383 384 SOSCloudKeychainFlush(dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) { 385 success = (sync_error == NULL); 386 if (error) { 387 CFRetainAssign(*error, sync_error); 388 } 389 390 dispatch_semaphore_signal(wait_for); 391 }); 392 393 dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER); 394 395 secnotice("flush", "Returned %s", success? "Success": "Failure"); 396 397 return success; 398 } 399 400 - (bool)syncWaitAndFlush:(CFErrorRef *)error 401 { 402 secnotice("pairing", "sync and wait starting"); 403 404 if (!SyncKVSAndWait(error)) { 405 secnotice("pairing", "failed sync and wait: %@", error ? *error : NULL); 406 return false; 407 } 408 if (!Flush(error)) { 409 secnotice("pairing", "failed flush: %@", error ? *error : NULL); 410 return false; 411 } 412 secnotice("pairing", "finished sync and wait"); 413 return true; 414 } 415 416 - (void)validatedStashedAccountCredential:(void(^)(NSData *credential, NSError *error))complete 417 { 418 CFErrorRef syncerror = NULL; 419 420 if (![self syncWaitAndFlush:&syncerror]) { 421 complete(NULL, (__bridge NSError *)syncerror); 422 CFReleaseNull(syncerror); 423 return; 424 } 425 426 dispatch_async(self.queue, ^{ 427 CFErrorRef error = NULL; 428 SecKeyRef key = NULL; 429 key = SOSAccountCopyStashedUserPrivateKey(self, &error); 430 if (key == NULL) { 431 secnotice("pairing", "no stashed credential"); 432 complete(NULL, (__bridge NSError *)error); 433 CFReleaseNull(error); 434 return; 435 } 436 437 SecKeyRef publicKey = SecKeyCopyPublicKey(key); 438 if (publicKey) { 439 secnotice("pairing", "returning stash credential: %@", publicKey); 440 CFReleaseNull(publicKey); 441 } 442 443 NSData *keydata = CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error)); 444 CFReleaseNull(key); 445 complete(keydata, (__bridge NSError *)error); 446 CFReleaseNull(error); 447 }); 448 } 449 - (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete 450 { 451 452 dispatch_sync(SOSCCCredentialQueue(), ^{ 453 CFErrorRef syncerror = NULL; 454 455 if (![self syncWaitAndFlush:&syncerror]) { 456 complete(NULL, (__bridge NSError *)syncerror); 457 CFReleaseNull(syncerror); 458 } else { 459 __block bool success = false; 460 sleep(1); // make up for keygen time in password based version - let syncdefaults catch up 461 462 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 463 SecKeyRef accountPrivateKey = NULL; 464 CFErrorRef error = NULL; 465 NSDictionary *attributes = @{ 466 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate, 467 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC, 468 }; 469 470 accountPrivateKey = SecKeyCreateWithData((__bridge CFDataRef)credential, (__bridge CFDictionaryRef)attributes, &error); 471 if (accountPrivateKey == NULL) { 472 complete(false, (__bridge NSError *)error); 473 secnotice("pairing", "SecKeyCreateWithData failed: %@", error); 474 CFReleaseNull(error); 475 return; 476 } 477 478 if (!SOSAccountTryUserPrivateKey(self, accountPrivateKey, &error)) { 479 CFReleaseNull(accountPrivateKey); 480 complete(false, (__bridge NSError *)error); 481 secnotice("pairing", "SOSAccountTryUserPrivateKey failed: %@", error); 482 CFReleaseNull(error); 483 return; 484 } 485 486 success = true; 487 secnotice("pairing", "SOSAccountTryUserPrivateKey succeeded"); 488 489 CFReleaseNull(accountPrivateKey); 490 complete(true, NULL); 491 }]; 492 493 // This makes getting the private key the same as Asserting the password - we read all the other things 494 // that we just expressed interest in. 495 496 if(success) { 497 CFErrorRef localError = NULL; 498 if (!Flush(&localError)) { 499 // we're still setup with the private key - just report this. 500 secnotice("pairing", "failed final flush: %@", localError); 501 } 502 CFReleaseNull(localError); 503 } 504 } 505 }); 506 } 507 508 - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete 509 { 510 __block CFErrorRef localError = NULL; 511 __block NSData *applicationBlob = NULL; 512 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 513 SOSPeerInfoRef application = SOSAccountCopyApplication(txn.account, &localError); 514 if (application) { 515 applicationBlob = CFBridgingRelease(SOSPeerInfoCopyEncodedData(application, kCFAllocatorDefault, &localError)); 516 CFReleaseNull(application); 517 } 518 }]; 519 complete(applicationBlob, (__bridge NSError *)localError); 520 CFReleaseNull(localError); 521 } 522 523 - (void)circleHash:(void (^)(NSString *, NSError *))complete 524 { 525 __block CFErrorRef localError = NULL; 526 __block NSString *circleHash = NULL; 527 SecAKSDoWithUserBagLockAssertion(&localError, ^{ 528 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 529 circleHash = CFBridgingRelease(SOSCircleCopyHashString(txn.account.trust.trustedCircle)); 530 }]; 531 }); 532 complete(circleHash, (__bridge NSError *)localError); 533 CFReleaseNull(localError); 534 535 } 536 537 538 #if TARGET_OS_OSX || TARGET_OS_IOS 539 540 541 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings { 542 #if !defined(DARLING) || OCTAGON 543 OTManager *otm = [OTManager manager]; 544 SOSAccountGhostBustingOptions options = ([otm ghostbustByMidEnabled] == YES ? SOSGhostBustByMID: 0) | 545 ([otm ghostbustBySerialEnabled] == YES ? SOSGhostBustBySerialNumber : 0) | 546 ([otm ghostbustByAgeEnabled] == YES ? SOSGhostBustSerialByAge: 0); 547 #else 548 // Apple's original code (above) doesn't have a case for when `OCTAGON=0`, 549 // and tries to use Octagon code regardless (OTManager) 550 SOSAccountGhostBustingOptions options = 0; 551 #endif 552 return options; 553 } 554 555 556 #define GHOSTBUSTDATE @"ghostbustdate" 557 558 - (NSDate *) ghostBustGetDate { 559 if(! self.settings) { 560 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite]; 561 } 562 return [self.settings valueForKey:GHOSTBUSTDATE]; 563 } 564 565 - (bool) ghostBustCheckDate { 566 NSDate *ghostBustDate = [self ghostBustGetDate]; 567 if(ghostBustDate && ([ghostBustDate timeIntervalSinceNow] <= 0)) return true; 568 return false; 569 } 570 571 - (void) ghostBustFollowup { 572 if(! self.settings) { 573 self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite]; 574 } 575 NSTimeInterval earliestGB = 60*60*24*3; // wait at least 3 days 576 NSTimeInterval latestGB = 60*60*24*7; // wait at most 7 days 577 NSDate *ghostBustDate = SOSCreateRandomDateBetweenNowPlus(earliestGB, latestGB); 578 [self.settings setValue:ghostBustDate forKey:GHOSTBUSTDATE]; 579 } 580 581 // GhostBusting initial scheduling 582 - (void)ghostBustSchedule { 583 NSDate *ghostBustDate = [self ghostBustGetDate]; 584 if(!ghostBustDate) { 585 [self ghostBustFollowup]; 586 } 587 } 588 589 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete { 590 __block bool result = false; 591 __block CFErrorRef localError = NULL; 592 593 if([SOSAuthKitHelpers accountIsHSA2]) { 594 [SOSAuthKitHelpers activeMIDs:^(NSSet <SOSTrustedDeviceAttributes *> * _Nullable activeMIDs, NSError * _Nullable error) { 595 SOSAuthKitHelpers *akh = [[SOSAuthKitHelpers alloc] initWithActiveMIDS:activeMIDs]; 596 if(akh) { 597 SecAKSDoWithUserBagLockAssertion(&localError, ^{ 598 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 599 result = SOSAccountGhostBustCircle(txn.account, akh, options, 1); 600 [self ghostBustFollowup]; 601 }]; 602 }); 603 } 604 complete(result, NULL); 605 }]; 606 } else { 607 complete(false, NULL); 608 } 609 } 610 611 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete { 612 NSDate *ghostBustDate = [self ghostBustGetDate]; 613 if(([ghostBustDate timeIntervalSinceNow] <= 0)) { 614 if(options) { 615 [self ghostBust: options complete: complete]; 616 } else { 617 complete(false, nil); 618 } 619 } 620 } 621 622 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete { 623 // if no particular options are presented use the ramp options. 624 // 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. 625 if(options == 0) { 626 options = [SOSAccount ghostBustGetRampSettings]; 627 } 628 [self ghostBust: options complete: complete]; 629 } 630 631 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete { 632 // 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. 633 NSMutableDictionary *gbInfoDictionary = [NSMutableDictionary new]; 634 SOSAccountGhostBustingOptions options = [SOSAccount ghostBustGetRampSettings]; 635 NSString *ghostBustDate = [[self ghostBustGetDate] description]; 636 637 gbInfoDictionary[@"SOSGhostBustBySerialNumber"] = (SOSGhostBustBySerialNumber & options) ? @"ON": @"OFF"; 638 gbInfoDictionary[@"SOSGhostBustByMID"] = (SOSGhostBustByMID & options) ? @"ON": @"OFF"; 639 gbInfoDictionary[@"SOSGhostBustSerialByAge"] = (SOSGhostBustSerialByAge & options) ? @"ON": @"OFF"; 640 gbInfoDictionary[@"SOSAccountGhostBustDate"] = ghostBustDate; 641 642 NSError *err = nil; 643 NSData *json = [NSJSONSerialization dataWithJSONObject:gbInfoDictionary 644 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys) 645 error:&err]; 646 if (!json) { 647 secnotice("ghostbust", "Error during ghostBustInfo JSONification: %@", err.localizedDescription); 648 } 649 complete(json, err); 650 } 651 652 - (void) iCloudIdentityStatus_internal: (void(^)(NSDictionary *tableSpid, NSError *error))complete { 653 CFErrorRef localError = NULL; 654 NSMutableDictionary *tableSPID = [NSMutableDictionary new]; 655 656 if(![self isInCircle: &localError]) { 657 complete(tableSPID, (__bridge NSError *)localError); 658 return; 659 } 660 661 // Make set of SPIDs for iCloud Identity PeerInfos 662 NSMutableSet<NSString*> *peerInfoSPIDs = [[NSMutableSet alloc] init]; 663 SOSCircleForEachiCloudIdentityPeer(self.trust.trustedCircle , ^(SOSPeerInfoRef peer) { 664 NSString *peerID = CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, SOSPeerInfoGetPeerID(peer))); 665 if(peerID) { 666 [ peerInfoSPIDs addObject:peerID]; 667 } 668 }); 669 670 // Make set of SPIDs for iCloud Identity Private Keys 671 NSMutableSet<NSString*> *privateKeySPIDs = [[NSMutableSet alloc] init]; 672 SOSiCloudIdentityPrivateKeyForEach(^(SecKeyRef privKey) { 673 CFErrorRef localError = NULL; 674 CFStringRef keyID = SOSCopyIDOfKey(privKey, &localError); 675 if(keyID) { 676 NSString *peerID = CFBridgingRelease(keyID); 677 [ privateKeySPIDs addObject:peerID]; 678 } else { 679 secnotice("iCloudIdentity", "couldn't make ID from key (%@)", localError); 680 } 681 CFReleaseNull(localError); 682 }); 683 684 NSMutableSet<NSString*> *completeIdentity = [peerInfoSPIDs mutableCopy]; 685 if([peerInfoSPIDs count] > 0 && [privateKeySPIDs count] > 0) { 686 [ completeIdentity intersectSet:privateKeySPIDs]; 687 } else { 688 completeIdentity = nil; 689 } 690 691 NSMutableSet<NSString*> *keyOnly = [privateKeySPIDs mutableCopy]; 692 if([peerInfoSPIDs count] > 0 && [keyOnly count] > 0) { 693 [ keyOnly minusSet: peerInfoSPIDs ]; 694 } 695 696 NSMutableSet<NSString*> *peerOnly = [peerInfoSPIDs mutableCopy]; 697 if([peerOnly count] > 0 && [privateKeySPIDs count] > 0) { 698 [ peerOnly minusSet: privateKeySPIDs ]; 699 } 700 701 tableSPID[kSOSIdentityStatusCompleteIdentity] = [completeIdentity allObjects]; 702 tableSPID[kSOSIdentityStatusKeyOnly] = [keyOnly allObjects]; 703 tableSPID[kSOSIdentityStatusPeerOnly] = [peerOnly allObjects]; 704 705 complete(tableSPID, nil); 706 } 707 708 - (void)iCloudIdentityStatus: (void (^)(NSData *json, NSError *error))complete { 709 [self iCloudIdentityStatus_internal:^(NSDictionary *tableSpid, NSError *error) { 710 NSError *err = nil; 711 NSData *json = [NSJSONSerialization dataWithJSONObject:tableSpid 712 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys) 713 error:&err]; 714 if (!json) { 715 secnotice("iCloudIdentity", "Error during iCloudIdentityStatus JSONification: %@", err.localizedDescription); 716 } 717 complete(json, err); 718 }]; 719 } 720 721 #else 722 723 + (SOSAccountGhostBustingOptions) ghostBustGetRampSettings { 724 return 0; 725 } 726 727 - (NSDate *) ghostBustGetDate { 728 return nil; 729 } 730 731 - (void) ghostBustFollowup { 732 } 733 734 - (void)ghostBustSchedule { 735 } 736 737 - (void) ghostBust:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete { 738 complete(false, NULL); 739 } 740 741 - (void) ghostBustPeriodic:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool busted, NSError *error))complete { 742 complete(false, NULL); 743 } 744 745 - (void)ghostBustTriggerTimed:(SOSAccountGhostBustingOptions)options complete: (void(^)(bool ghostBusted, NSError *error))complete { 746 complete(false, nil); 747 } 748 749 - (void) ghostBustInfo: (void(^)(NSData *json, NSError *error))complete { 750 complete(nil, nil); 751 } 752 753 - (bool) ghostBustCheckDate { 754 return false; 755 } 756 757 - (void)iCloudIdentityStatus:(void (^)(NSData *, NSError *))complete { 758 complete(nil, nil); 759 } 760 761 762 - (void)iCloudIdentityStatus_internal:(void (^)(NSDictionary *, NSError *))complete { 763 complete(nil, nil); 764 } 765 766 #endif // !(TARGET_OS_OSX || TARGET_OS_IOS) 767 768 - (void)circleJoiningBlob:(NSData *)applicant complete:(void (^)(NSData *blob, NSError *))complete 769 { 770 __block CFErrorRef localError = NULL; 771 __block NSData *blob = NULL; 772 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(NULL, &localError, (__bridge CFDataRef)applicant); 773 if (peer == NULL) { 774 complete(NULL, (__bridge NSError *)localError); 775 CFReleaseNull(localError); 776 return; 777 } 778 779 SecAKSDoWithUserBagLockAssertionSoftly(^{ 780 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 781 blob = CFBridgingRelease(SOSAccountCopyCircleJoiningBlob(txn.account, peer, &localError)); 782 }]; 783 }); 784 785 CFReleaseNull(peer); 786 787 complete(blob, (__bridge NSError *)localError); 788 CFReleaseNull(localError); 789 } 790 791 - (void)joinCircleWithBlob:(NSData *)blob version:(PiggyBackProtocolVersion)version complete:(void (^)(bool success, NSError *))complete 792 { 793 __block CFErrorRef localError = NULL; 794 __block bool res = false; 795 796 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 797 res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError); 798 }]; 799 800 complete(res, (__bridge NSError *)localError); 801 CFReleaseNull(localError); 802 } 803 804 - (void)initialSyncCredentials:(uint32_t)flags complete:(void (^)(NSArray *, NSError *))complete 805 { 806 CFErrorRef error = NULL; 807 uint32_t isflags = 0; 808 809 if (flags & SOSControlInitialSyncFlagTLK) 810 isflags |= SecServerInitialSyncCredentialFlagTLK; 811 if (flags & SOSControlInitialSyncFlagPCS) 812 isflags |= SecServerInitialSyncCredentialFlagPCS; 813 if (flags & SOSControlInitialSyncFlagPCSNonCurrent) 814 isflags |= SecServerInitialSyncCredentialFlagPCSNonCurrent; 815 if (flags & SOSControlInitialSyncFlagBluetoothMigration) 816 isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration; 817 818 819 NSArray *array = CFBridgingRelease(_SecServerCopyInitialSyncCredentials(isflags, &error)); 820 complete(array, (__bridge NSError *)error); 821 CFReleaseNull(error); 822 } 823 824 - (void)importInitialSyncCredentials:(NSArray *)items complete:(void (^)(bool success, NSError *))complete 825 { 826 CFErrorRef error = NULL; 827 bool res = _SecServerImportInitialSyncCredentials((__bridge CFArrayRef)items, &error); 828 complete(res, (__bridge NSError *)error); 829 CFReleaseNull(error); 830 } 831 832 - (void)rpcTriggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete 833 { 834 __block CFErrorRef localError = NULL; 835 __block bool res = false; 836 837 secnotice("sync", "trigger a forced sync for %@", peers); 838 839 SecAKSDoWithUserBagLockAssertion(&localError, ^{ 840 [self performTransaction:^(SOSAccountTransaction *txn) { 841 if ([peers count]) { 842 NSSet *peersSet = [NSSet setWithArray:peers]; 843 CFMutableSetRef handledPeers = SOSAccountSyncWithPeers(txn, (__bridge CFSetRef)peersSet, &localError); 844 if (handledPeers && CFSetGetCount(handledPeers) == (CFIndex)[peersSet count]) { 845 res = true; 846 } 847 CFReleaseNull(handledPeers); 848 } else { 849 res = SOSAccountRequestSyncWithAllPeers(txn, &localError); 850 } 851 }]; 852 }); 853 complete(res, (__bridge NSError *)localError); 854 CFReleaseNull(localError); 855 } 856 857 - (void)rpcTriggerBackup:(NSArray<NSString *>* _Nullable)backupPeers complete:(void (^)(NSError *error))complete 858 { 859 __block CFErrorRef localError = NULL; 860 861 if (backupPeers.count == 0) { 862 SOSEngineRef engine = (SOSEngineRef) [self.kvs_message_transport SOSTransportMessageGetEngine]; 863 backupPeers = CFBridgingRelease(SOSEngineCopyBackupPeerNames(engine, &localError)); 864 } 865 866 #if OCTAGON 867 [self triggerBackupForPeers:backupPeers]; 868 #endif 869 870 complete((__bridge NSError *)localError); 871 CFReleaseNull(localError); 872 } 873 874 - (void)rpcTriggerRingUpdate:(void (^)(NSError *error))complete 875 { 876 #if OCTAGON 877 [self triggerRingUpdate]; 878 #endif 879 complete(NULL); 880 } 881 882 - (void)getWatchdogParameters:(void (^)(NSDictionary* parameters, NSError* error))complete 883 { 884 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class 885 Class watchdogClass = NSClassFromString(@"SecdWatchdog"); 886 if (watchdogClass) { 887 NSDictionary* parameters = [[watchdogClass watchdog] watchdogParameters]; 888 complete(parameters, nil); 889 } 890 else { 891 complete(nil, [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]); 892 } 893 } 894 895 - (void)setWatchdogParmeters:(NSDictionary*)parameters complete:(void (^)(NSError* error))complete 896 { 897 // SecdWatchdog is only available in the secd/securityd - no other binary will contain that class 898 NSError* error = nil; 899 Class watchdogClass = NSClassFromString(@"SecdWatchdog"); 900 if (watchdogClass) { 901 [[watchdogClass watchdog] setWatchdogParameters:parameters error:&error]; 902 complete(error); 903 } 904 else { 905 complete([NSError errorWithDomain:@"com.apple.securityd.watchdog" code:1 userInfo:@{NSLocalizedDescriptionKey : @"failed to lookup SecdWatchdog class from ObjC runtime"}]); 906 } 907 } 908 909 - (void)removeV0Peers:(void (^)(bool, NSError *))reply { 910 [self performTransaction:^(SOSAccountTransaction *txn) { 911 CFErrorRef localError = NULL; 912 bool result = SOSAccountRemoveV0Clients(txn.account, &localError); 913 reply(result, (__bridge NSError *)localError); 914 CFReleaseNull(localError); 915 }]; 916 } 917 918 919 // 920 // MARK: Save Block 921 // 922 923 - (void) flattenToSaveBlock { 924 if (self.saveBlock) { 925 NSError* error = nil; 926 NSData* saveData = [self encodedData:&error]; 927 928 (self.saveBlock)((__bridge CFDataRef) saveData, (__bridge CFErrorRef) error); 929 } 930 } 931 932 CFDictionaryRef SOSAccountCopyGestalt(SOSAccount* account) { 933 return CFDictionaryCreateCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)account.gestalt); 934 } 935 936 CFDictionaryRef SOSAccountCopyV2Dictionary(SOSAccount* account) { 937 CFDictionaryRef v2dict = SOSAccountGetValue(account, kSOSTestV2Settings, NULL); 938 return CFDictionaryCreateCopy(kCFAllocatorDefault, v2dict); 939 } 940 941 static bool SOSAccountUpdateDSID(SOSAccount* account, CFStringRef dsid){ 942 SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL); 943 //send new DSID over account changed 944 [account.circle_transport kvsSendOfficialDSID:dsid err:NULL]; 945 return true; 946 } 947 948 void SOSAccountAssertDSID(SOSAccount* account, CFStringRef dsid) { 949 CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL); 950 if(accountDSID == NULL) { 951 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid); 952 953 SOSAccountUpdateDSID(account, dsid); 954 } else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) { 955 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid); 956 957 //DSID has changed, blast the account! 958 SOSAccountSetToNew(account); 959 960 //update DSID to the new DSID 961 SOSAccountUpdateDSID(account, dsid); 962 } else { 963 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid); 964 } 965 } 966 967 SecKeyRef SOSAccountCopyDevicePrivateKey(SOSAccount* account, CFErrorRef *error) { 968 if(account.peerPublicKey) { 969 return SecKeyCopyMatchingPrivateKey(account.peerPublicKey, error); 970 } 971 return NULL; 972 } 973 974 SecKeyRef SOSAccountCopyDevicePublicKey(SOSAccount* account, CFErrorRef *error) { 975 return SecKeyCopyPublicKey(account.peerPublicKey); 976 } 977 978 979 void SOSAccountPendDisableViewSet(SOSAccount* account, CFSetRef disabledViews) 980 { 981 [account.trust valueUnionWith:kSOSPendingDisableViewsToBeSetKey valuesToUnion:disabledViews]; 982 [account.trust valueSubtractFrom:kSOSPendingEnableViewsToBeSetKey valuesToSubtract:disabledViews]; 983 } 984 985 #pragma clang diagnostic push 986 #pragma clang diagnostic ignored "-Wunused-value" 987 SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccount* account, SOSViewActionCode actionCode) { 988 SOSViewResultCode retval = kSOSCCGeneralViewError; 989 // The V0 view switches on and off all on it's own, we allow people the delusion 990 // of control and status if it's what we're stuck at., otherwise error. 991 if (SOSAccountSyncingV0(account)) { 992 require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now")); 993 retval = kSOSCCViewMember; 994 } else { 995 require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now")); 996 retval = kSOSCCViewNotMember; 997 } 998 errOut: 999 return retval; 1000 } 1001 #pragma clang diagnostic pop 1002 1003 SOSAccount* SOSAccountCreate(CFAllocatorRef allocator, 1004 CFDictionaryRef gestalt, 1005 SOSDataSourceFactoryRef factory) { 1006 1007 SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory]; 1008 dispatch_sync(a.queue, ^{ 1009 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountCreate"); 1010 a.key_interests_need_updating = true; 1011 }); 1012 1013 return a; 1014 } 1015 1016 static OSStatus do_delete(CFDictionaryRef query) { 1017 OSStatus result; 1018 1019 result = SecItemDelete(query); 1020 if (result) { 1021 secerror("SecItemDelete: %d", (int)result); 1022 } 1023 return result; 1024 } 1025 1026 static int 1027 do_keychain_delete_aks_bags() 1028 { 1029 OSStatus result; 1030 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, 1031 kSecClass, kSecClassGenericPassword, 1032 kSecAttrAccessGroup, CFSTR("com.apple.sbd"), 1033 kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"), 1034 kSecAttrService, CFSTR("SecureBackupService"), 1035 kSecAttrSynchronizable, kCFBooleanTrue, 1036 kSecUseTombstones, kCFBooleanFalse, 1037 NULL); 1038 1039 result = do_delete(item); 1040 CFReleaseSafe(item); 1041 1042 return result; 1043 } 1044 1045 static int 1046 do_keychain_delete_identities() 1047 { 1048 OSStatus result; 1049 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, 1050 kSecClass, kSecClassKey, 1051 kSecAttrSynchronizable, kCFBooleanTrue, 1052 kSecUseTombstones, kCFBooleanFalse, 1053 kSecAttrAccessGroup, CFSTR("com.apple.security.sos"), 1054 NULL); 1055 1056 result = do_delete(item); 1057 CFReleaseSafe(item); 1058 1059 return result; 1060 } 1061 1062 static int 1063 do_keychain_delete_lakitu() 1064 { 1065 OSStatus result; 1066 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, 1067 kSecClass, kSecClassGenericPassword, 1068 kSecAttrSynchronizable, kCFBooleanTrue, 1069 kSecUseTombstones, kCFBooleanFalse, 1070 kSecAttrAccessGroup, CFSTR("com.apple.lakitu"), 1071 kSecAttrAccount, CFSTR("EscrowServiceBypassToken"), 1072 kSecAttrService, CFSTR("EscrowService"), 1073 NULL); 1074 1075 result = do_delete(item); 1076 CFReleaseSafe(item); 1077 1078 return result; 1079 } 1080 1081 static int 1082 do_keychain_delete_sbd() 1083 { 1084 OSStatus result; 1085 CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, 1086 kSecClass, kSecClassGenericPassword, 1087 kSecAttrSynchronizable, kCFBooleanTrue, 1088 kSecUseTombstones, kCFBooleanFalse, 1089 kSecAttrAccessGroup, CFSTR("com.apple.sbd"), 1090 NULL); 1091 1092 result = do_delete(item); 1093 CFReleaseSafe(item); 1094 1095 return result; 1096 } 1097 1098 void SOSAccountSetToNew(SOSAccount* a) 1099 { 1100 secnotice("accountChange", "Setting Account to New"); 1101 int result = 0; 1102 1103 /* remove all syncable items */ 1104 result = do_keychain_delete_aks_bags(); (void) result; 1105 secdebug("set to new", "result for deleting aks bags: %d", result); 1106 1107 result = do_keychain_delete_identities(); (void) result; 1108 secdebug("set to new", "result for deleting identities: %d", result); 1109 1110 result = do_keychain_delete_lakitu(); (void) result; 1111 secdebug("set to new", "result for deleting lakitu: %d", result); 1112 1113 result = do_keychain_delete_sbd(); (void) result; 1114 secdebug("set to new", "result for deleting sbd: %d", result); 1115 1116 1117 if (a.user_private_timer) { 1118 dispatch_source_cancel(a.user_private_timer); 1119 a.user_private_timer = NULL; 1120 xpc_transaction_end(); 1121 1122 } 1123 if (a.lock_notification_token != NOTIFY_TOKEN_INVALID) { 1124 notify_cancel(a.lock_notification_token); 1125 a.lock_notification_token = NOTIFY_TOKEN_INVALID; 1126 } 1127 1128 // keeping gestalt; 1129 // keeping factory; 1130 // Live Notification 1131 // change_blocks; 1132 // update_interest_block; 1133 // update_block; 1134 SOSUnregisterTransportKeyParameter(a.key_transport); 1135 SOSUnregisterTransportMessage(a.kvs_message_transport); 1136 SOSUnregisterTransportCircle(a.circle_transport); 1137 1138 a.circle_transport = NULL; 1139 a.kvs_message_transport = nil; 1140 a._password_tmp = nil; 1141 a.circle_rings_retirements_need_attention = true; 1142 a.engine_peer_state_needs_repair = true; 1143 a.key_interests_need_updating = true; 1144 a.need_backup_peers_created_after_backup_key_set = true; 1145 1146 a.accountKeyIsTrusted = false; 1147 a.accountKeyDerivationParameters = nil; 1148 a.accountPrivateKey = NULL; 1149 a.accountKey = NULL; 1150 a.previousAccountKey = NULL; 1151 a.peerPublicKey = NULL; 1152 a.backup_key = nil; 1153 a.notifyCircleChangeOnExit = true; 1154 a.notifyViewChangeOnExit = true; 1155 a.notifyBackupOnExit = true; 1156 1157 a.octagonSigningFullKeyRef = NULL; 1158 a.octagonEncryptionFullKeyRef = NULL; 1159 1160 a.trust = nil; 1161 // setting a new trust object resets all the rings from this peer's point of view - they're in the SOSAccountTrustClassic dictionary 1162 a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]]; 1163 [a ensureFactoryCircles]; 1164 1165 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly 1166 SOSAccountEnsureUUID(a); 1167 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew"); 1168 a.key_interests_need_updating = true; 1169 } 1170 1171 dispatch_queue_t SOSAccountGetQueue(SOSAccount* account) { 1172 return account.queue; 1173 } 1174 1175 void SOSAccountSetUserPublicTrustedForTesting(SOSAccount* account){ 1176 account.accountKeyIsTrusted = true; 1177 } 1178 1179 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error 1180 { 1181 SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error]; 1182 if (!SOSAccountHasPublicKey(self, error)) { 1183 if(circleStatus == kSOSCCInCircle) { 1184 if(error) { 1185 CFReleaseNull(*error); 1186 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); 1187 } 1188 } 1189 circleStatus = kSOSCCError; 1190 } 1191 return circleStatus; 1192 } 1193 1194 -(bool) isInCircle:(CFErrorRef *)error 1195 { 1196 SOSCCStatus result = [self getCircleStatus:error]; 1197 if (result != kSOSCCInCircle) { 1198 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle")); 1199 return false; 1200 } 1201 return true; 1202 } 1203 1204 1205 bool SOSAccountScanForRetired(SOSAccount* account, SOSCircleRef circle, CFErrorRef *error) { 1206 1207 SOSAccountTrustClassic *trust = account.trust; 1208 NSMutableSet* retirees = trust.retirees; 1209 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) { 1210 CFSetSetValue((__bridge CFMutableSetRef) retirees, peer); 1211 CFErrorRef cleanupError = NULL; 1212 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:peer err:&cleanupError]) { 1213 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError); 1214 } 1215 CFReleaseSafe(cleanupError); 1216 }); 1217 return true; 1218 } 1219 1220 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccount* account, SOSCircleRef starting_circle, CFErrorRef *error) { 1221 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error); 1222 SOSPeerInfoRef me = account.peerInfo; 1223 bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL); 1224 1225 SOSAccountTrustClassic *trust = account.trust; 1226 NSMutableSet* retirees = trust.retirees; 1227 1228 if(!new_circle) return NULL; 1229 __block bool workDone = false; 1230 if (retirees) { 1231 CFSetForEach((__bridge CFSetRef)retirees, ^(const void* value) { 1232 SOSPeerInfoRef pi = (SOSPeerInfoRef) value; 1233 if (isSOSPeerInfo(pi)) { 1234 SOSCircleUpdatePeerInfo(new_circle, pi); 1235 workDone = true; 1236 } 1237 }); 1238 } 1239 1240 if(workDone && SOSCircleCountPeers(new_circle) == 0) { 1241 SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error); 1242 1243 if(iAmApplicant) { 1244 if(userPrivKey) { 1245 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant"); 1246 if(!SOSCircleResetToOffering(new_circle, userPrivKey, account.fullPeerInfo, error) || 1247 ![account.trust addiCloudIdentity:new_circle key:userPrivKey err:error]){ 1248 CFReleaseNull(new_circle); 1249 return NULL; 1250 } 1251 account.notifyBackupOnExit = true; 1252 } else { 1253 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty 1254 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is 1255 // we have a chance to set things right with a SetCreds/Join sequence. This will cause 1256 // handleUpdateCircle to return false. 1257 CFReleaseNull(new_circle); 1258 return NULL; 1259 } 1260 } else { 1261 // This case is when we aren't an applicant and the circle is retirement-empty. 1262 secnotice("circleOps", "Reset to empty with last retirement"); 1263 SOSCircleResetToEmpty(new_circle, NULL); 1264 } 1265 } 1266 1267 return new_circle; 1268 } 1269 1270 // 1271 // MARK: Circle Membership change notificaion 1272 // 1273 1274 void SOSAccountAddChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) { 1275 SOSAccountCircleMembershipChangeBlock copy = changeBlock; 1276 [a.change_blocks addObject:copy]; 1277 } 1278 1279 void SOSAccountRemoveChangeBlock(SOSAccount* a, SOSAccountCircleMembershipChangeBlock changeBlock) { 1280 [a.change_blocks removeObject:changeBlock]; 1281 } 1282 1283 void SOSAccountPurgeIdentity(SOSAccount* account) { 1284 SOSAccountTrustClassic *trust = account.trust; 1285 [trust purgeIdentity]; 1286 } 1287 1288 bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) { 1289 SOSAccountTrustClassic *trust = account.trust; 1290 SOSFullPeerInfoRef identity = trust.fullPeerInfo; 1291 NSMutableSet* retirees = trust.retirees; 1292 1293 NSError* localError = nil; 1294 SOSFullPeerInfoRef fpi = identity; 1295 if(!fpi) return false; 1296 1297 CFErrorRef retiredError = NULL; 1298 1299 bool retval = false; 1300 1301 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError); 1302 if(retiredError){ 1303 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError); 1304 if(error){ 1305 *error = retiredError; 1306 }else{ 1307 CFReleaseNull(retiredError); 1308 } 1309 } 1310 1311 if (!retire_peer) { 1312 secerror("Create ticket failed for peer %@: %@", fpi, localError); 1313 } else { 1314 // See if we need to repost the circle we could either be an applicant or a peer already in the circle 1315 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) { 1316 // Remove our application if we have one. 1317 SOSCircleWithdrawRequest(circle, retire_peer, NULL); 1318 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) { 1319 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) { 1320 CFErrorRef cleanupError = NULL; 1321 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) { 1322 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError); 1323 } 1324 CFReleaseSafe(cleanupError); 1325 } 1326 } 1327 1328 // Store the retirement record locally. 1329 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer); 1330 1331 trust.retirees = retirees; 1332 1333 // Write retirement to Transport 1334 CFErrorRef postError = NULL; 1335 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){ 1336 1337 secwarning("Couldn't post retirement (%@)", postError); 1338 } 1339 1340 1341 1342 if(![account.circle_transport flushChanges:&postError]){ 1343 secwarning("Couldn't flush retirement data (%@)", postError); 1344 } 1345 1346 CFReleaseNull(postError); 1347 } 1348 1349 SOSAccountPurgeIdentity(account); 1350 1351 retval = true; 1352 1353 CFReleaseNull(retire_peer); 1354 return retval; 1355 } 1356 1357 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) { 1358 bool result = false; 1359 if (account.circle_transport) { 1360 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error]; 1361 } 1362 return result; 1363 } 1364 1365 /* 1366 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any 1367 local value that has been overwritten by a distant value. If there is no 1368 conflict between the local and the distant values when doing the initial 1369 sync (e.g. if the cloud has no data stored or the client has not stored 1370 any data yet), you'll never see that notification. 1371 1372 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip 1373 with server but initial round trip with server does not imply 1374 NSUbiquitousKeyValueStoreInitialSyncChange. 1375 */ 1376 1377 1378 // 1379 // MARK: Status summary 1380 // 1381 1382 1383 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) { 1384 switch(status) { 1385 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle"); 1386 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle"); 1387 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending"); 1388 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent"); 1389 case kSOSCCError: return CFSTR("kSOSCCError"); 1390 } 1391 return CFSTR("kSOSCCError"); 1392 } 1393 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) { 1394 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) { 1395 return kSOSCCInCircle; 1396 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) { 1397 return kSOSCCInCircle; 1398 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) { 1399 return kSOSCCNotInCircle; 1400 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) { 1401 return kSOSCCRequestPending; 1402 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) { 1403 return kSOSCCCircleAbsent; 1404 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) { 1405 return kSOSCCError; 1406 } 1407 return kSOSCCError; 1408 } 1409 1410 // 1411 // MARK: Account Reset Circles 1412 // 1413 1414 // This needs to be called within a [trust modifyCircle()] block 1415 1416 1417 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) { 1418 bool retval = false; 1419 1420 SOSAccountTrustClassic *trust = account.trust; 1421 SOSFullPeerInfoRef identity = trust.fullPeerInfo; 1422 1423 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); 1424 1425 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { 1426 if(SOSPeerInfoIsCloudIdentity(peer)) { 1427 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL); 1428 if(!icfpi) { 1429 CFSetAddValue(iCloud2Remove, peer); 1430 } 1431 CFReleaseNull(icfpi); 1432 } 1433 }); 1434 1435 if(CFSetGetCount(iCloud2Remove) > 0) { 1436 retval = true; 1437 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error); 1438 } 1439 CFReleaseNull(iCloud2Remove); 1440 return retval; 1441 } 1442 1443 // 1444 // MARK: start backups 1445 // 1446 1447 // This needs to be called within a transaction. It only processes one view at a time 1448 // this will return true if work was done. 1449 - (bool)_onQueueEnsureInBackupRings: (CFStringRef) viewName { 1450 __block bool workDone = false; 1451 1452 dispatch_assert_queue(self.queue); 1453 1454 // Setup backups the new way. 1455 if(SOSAccountValidateBackupRingForView(self, viewName, NULL)) { 1456 workDone = SOSAccountUpdateBackupRing(self, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) { 1457 SOSRingRef newRing = SOSAccountCreateBackupRingForView(self, viewName, error); 1458 secnotice("backup", "Reset backup ring %@ %s", viewName, (newRing) ? "success": "failed"); 1459 return newRing; 1460 }); 1461 } 1462 return workDone; 1463 } 1464 1465 // 1466 // MARK: Recovery Public Key Functions 1467 // 1468 1469 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){ 1470 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error); 1471 if(retval) secnotice("recovery", "successfully registered recovery public key"); 1472 else secnotice("recovery", "could not register recovery public key: %@", *error); 1473 SOSClearErrorIfTrue(retval, error); 1474 return retval; 1475 } 1476 1477 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){ 1478 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error); 1479 if(retval) secnotice("recovery", "RK Cleared"); 1480 else secnotice("recovery", "Couldn't clear RK(%@)", *error); 1481 SOSClearErrorIfTrue(retval, error); 1482 return retval; 1483 } 1484 1485 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){ 1486 CFDataRef result = NULL; 1487 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error); 1488 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error); 1489 1490 if (!isData(result)) { 1491 CFReleaseNull(result); 1492 } 1493 SOSClearErrorIfTrue(result != NULL, error); 1494 1495 return result; 1496 } 1497 1498 // 1499 // MARK: Joining 1500 // 1501 1502 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key, bool use_cloud_peer, CFErrorRef* error) { 1503 SOSAccount* account = aTxn.account; 1504 SOSAccountTrustClassic *trust = account.trust; 1505 __block bool result = false; 1506 __block SOSFullPeerInfoRef cloud_full_peer = NULL; 1507 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???"))); 1508 1509 require_quiet([account.trust ensureFullPeerAvailable: account err:error], fail); 1510 1511 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo; 1512 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) { 1513 secnotice("resetToOffering", "Resetting circle to offering since there are no peers"); 1514 // this also clears initial sync data 1515 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error]; 1516 } else { 1517 SOSAccountInitializeInitialSync(account); 1518 if (use_cloud_peer) { 1519 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL); 1520 } 1521 1522 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) { 1523 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error); 1524 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error); 1525 trust.departureCode = kSOSNeverLeftCircle; 1526 if(result && cloud_full_peer) { 1527 CFErrorRef localError = NULL; 1528 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer)); 1529 require_quiet(cloudid, finish); 1530 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish); 1531 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish); 1532 finish: 1533 if (localError){ 1534 secerror("Failed to join with cloud identity: %@", localError); 1535 CFReleaseNull(localError); 1536 } 1537 } 1538 return result; 1539 }]; 1540 1541 if (use_cloud_peer) { 1542 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent()); 1543 } 1544 } 1545 fail: 1546 CFReleaseNull(cloud_full_peer); 1547 return result; 1548 } 1549 1550 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) { 1551 SOSAccount* account = aTxn.account; 1552 SOSAccountTrustClassic *trust = account.trust; 1553 bool success = false; 1554 1555 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error); 1556 require_quiet(user_key, done); // Fail if we don't get one. 1557 1558 if(!trust.trustedCircle || SOSCircleCountPeers(trust.trustedCircle) == 0 ) { 1559 secnotice("resetToOffering", "Resetting circle to offering because it's empty and we're joining"); 1560 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error]; 1561 } 1562 1563 if(SOSCircleHasPeerWithID(trust.trustedCircle, (__bridge CFStringRef)(account.peerID), NULL)) { 1564 // We shouldn't be at this point if we're already in circle. 1565 secnotice("circleops", "attempt to join a circle we're in - continuing."); 1566 return true; // let things above us think we're in circle - because we are. 1567 } 1568 1569 if(!SOSCircleVerify(trust.trustedCircle, account.accountKey, NULL)) { 1570 secnotice("resetToOffering", "Resetting circle to offering since we are new and it doesn't verify with current userKey"); 1571 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error]; 1572 } 1573 1574 if (trust.fullPeerInfo != NULL) { 1575 SOSPeerInfoRef myPeer = trust.peerInfo; 1576 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL); 1577 require_quiet(!success, done); 1578 1579 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now. 1580 1581 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) { 1582 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle)); 1583 1584 trust.fullPeerInfo = NULL; 1585 } 1586 } 1587 1588 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error); 1589 1590 require_quiet(success, done); 1591 1592 trust.departureCode = kSOSNeverLeftCircle; 1593 1594 done: 1595 return success; 1596 } 1597 1598 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) { 1599 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)"); 1600 return SOSAccountJoinCircles_internal(aTxn, false, error); 1601 } 1602 1603 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) { 1604 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)"); 1605 return SOSAccountJoinCircles_internal(aTxn, true, error); 1606 } 1607 1608 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error) 1609 { 1610 bool result = false; 1611 CFMutableSetRef peersToRemove = NULL; 1612 1613 CFStringArrayPerformWithDescription(peers, ^(CFStringRef description) { 1614 secnotice("circleOps", "Attempting to remove peer set %@", description); 1615 }); 1616 1617 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error); 1618 if (!user_key) { 1619 secnotice("circleOps", "Can't remove without userKey"); 1620 return result; 1621 } 1622 1623 SOSFullPeerInfoRef me_full = account.fullPeerInfo; 1624 SOSPeerInfoRef me = account.peerInfo; 1625 if (!(me_full && me)) { 1626 secnotice("circleOps", "Can't remove without being active peer"); 1627 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer")); 1628 return result; 1629 } 1630 1631 result = true; // beyond this point failures would be rolled up in AccountModifyCircle. 1632 1633 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers); 1634 if (!peersToRemove) { 1635 CFReleaseNull(peersToRemove); 1636 secnotice("circleOps", "No peerSet to remove"); 1637 return result; 1638 } 1639 1640 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling). 1641 bool leaveCircle = CFSetContainsValue(peersToRemove, me); 1642 CFSetRemoveValue(peersToRemove, me); 1643 1644 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) { 1645 bool success = false; 1646 1647 if (CFSetGetCount(peersToRemove) != 0) { 1648 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done); 1649 success = SOSAccountGenerationSignatureUpdate(account, error); 1650 } else { 1651 success = true; 1652 } 1653 1654 if (success && leaveCircle) { 1655 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)"); 1656 success = sosAccountLeaveCircle(account, circle, error); 1657 } 1658 1659 done: 1660 return success; 1661 1662 }]; 1663 1664 if (result) { 1665 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) { 1666 secnotice("circleOps", "Removed Peers from circle %@", description); 1667 }); 1668 } 1669 1670 CFReleaseNull(peersToRemove); 1671 return result; 1672 } 1673 1674 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) { 1675 dispatch_queue_t queue = dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0); 1676 dispatch_group_t group = dispatch_group_create(); 1677 SOSAccountTrustClassic *trust = account.trust; 1678 __block bool result = false; 1679 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds); 1680 // Add a task to the group 1681 dispatch_group_async(group, queue, ^{ 1682 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) { 1683 secnotice("circleOps", "Leaving circle by client request (Bail)"); 1684 return sosAccountLeaveCircle(account, circle, error); 1685 }]; 1686 }); 1687 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC); 1688 dispatch_group_wait(group, milestone); 1689 1690 trust.departureCode = kSOSWithdrewMembership; 1691 1692 return result; 1693 } 1694 1695 // 1696 // MARK: Application 1697 // 1698 1699 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos, 1700 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) { 1701 SOSAccountTrustClassic *trust = account.trust; 1702 1703 SOSPeerInfoRef me = trust.peerInfo; 1704 CFErrorRef peer_error = NULL; 1705 if (trust.trustedCircle && me && 1706 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) { 1707 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) { 1708 __block bool modified = false; 1709 CFArrayForEach(peer_infos, ^(const void *value) { 1710 SOSPeerInfoRef peer = (SOSPeerInfoRef) value; 1711 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) { 1712 if (action(circle, trust.fullPeerInfo, peer)) { 1713 modified = true; 1714 } 1715 } 1716 }); 1717 return modified; 1718 }]; 1719 } 1720 if (peer_error) 1721 secerror("Got error in SOSCircleHasPeer: %@", peer_error); 1722 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here. 1723 } 1724 1725 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) { 1726 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error); 1727 if (!user_key) 1728 return false; 1729 1730 __block int64_t acceptedPeers = 0; 1731 1732 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) { 1733 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error); 1734 if (accepted) 1735 acceptedPeers++; 1736 return accepted; 1737 }); 1738 1739 if (acceptedPeers == CFArrayGetCount(applicants)) 1740 return true; 1741 return false; 1742 } 1743 1744 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) { 1745 __block bool success = true; 1746 __block int64_t num_peers = 0; 1747 1748 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) { 1749 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error); 1750 if (!rejected) 1751 success = false; 1752 else 1753 num_peers = MAX(num_peers, SOSCircleCountPeers(circle)); 1754 return rejected; 1755 }); 1756 1757 return success; 1758 } 1759 1760 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) { 1761 SOSAccountTrustClassic *trust = account.trust; 1762 return trust.departureCode; 1763 } 1764 1765 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) { 1766 SOSAccountTrustClassic *trust = account.trust; 1767 trust.departureCode = reason; 1768 } 1769 1770 1771 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) { 1772 CFArrayRef result = NULL; 1773 CFNumberRef generation = NULL; 1774 SOSAccountTrustClassic *trust = account.trust; 1775 1776 require_quiet(SOSAccountHasPublicKey(account, error), fail); 1777 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle"))); 1778 1779 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle); 1780 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL); 1781 1782 fail: 1783 return result; 1784 } 1785 1786 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) { 1787 if (!SOSAccountHasPublicKey(account, error)) 1788 return NULL; 1789 1790 return account.accountKeyIsTrusted; 1791 } 1792 1793 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) { 1794 // TODO: this result is never set or used 1795 bool result = true; 1796 SOSAccountTrustClassic *trust = account.trust; 1797 1798 secnotice("updates", "Ensuring peer registration."); 1799 1800 if(!trust) { 1801 secnotice("updates", "Failed to get trust object in Ensuring peer registration."); 1802 return result; 1803 } 1804 1805 if([account getCircleStatus: NULL] != kSOSCCInCircle) { 1806 return result; 1807 } 1808 1809 // If we are not in the circle, there is no point in setting up peers 1810 if(!SOSAccountIsMyPeerActive(account, NULL)) { 1811 return result; 1812 } 1813 1814 // This code only uses the SOSFullPeerInfoRef for two things: 1815 // - Finding out if this device is in the trusted circle 1816 // - Using the peerID for this device to see if the current peer is "me" 1817 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder 1818 1819 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo); 1820 1821 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) { 1822 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) { 1823 CFErrorRef localError = NULL; 1824 1825 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError); 1826 if (localError) 1827 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError); 1828 CFReleaseNull(localError); 1829 } 1830 }); 1831 1832 return result; 1833 } 1834 1835 // 1836 // Value manipulation 1837 // 1838 1839 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) { 1840 SOSAccountTrustClassic *trust = account.trust; 1841 if (!trust.expansion) { 1842 return NULL; 1843 } 1844 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]); 1845 } 1846 1847 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){ 1848 bool success = false; 1849 1850 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error); 1851 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error); 1852 1853 return success; 1854 } 1855 1856 - (void)_onQueueRecordRetiredPeersInCircle { 1857 1858 dispatch_assert_queue(self.queue); 1859 1860 if (![self isInCircle:NULL]) { 1861 return; 1862 } 1863 __block bool updateRings = false; 1864 SOSAccountTrustClassic *trust = self.trust; 1865 [trust modifyCircle:self.circle_transport err:NULL action:^bool (SOSCircleRef circle) { 1866 __block bool updated = false; 1867 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){ 1868 SOSPeerInfoRef retiree = asSOSPeerInfo(element); 1869 1870 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) { 1871 updated = true; 1872 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle); 1873 CFErrorRef cleanupError = NULL; 1874 if (![self.trust cleanupAfterPeer:self.kvs_message_transport circleTransport:self.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError]) 1875 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError); 1876 CFReleaseSafe(cleanupError); 1877 updateRings = true; 1878 } 1879 }); 1880 return updated; 1881 }]; 1882 if(updateRings) { 1883 SOSAccountProcessBackupRings(self); 1884 } 1885 } 1886 1887 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC; 1888 1889 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error) 1890 { 1891 __block CFTypeRef object = NULL; 1892 1893 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); 1894 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); 1895 1896 CloudKeychainReplyBlock replyBlock = 1897 ^ (CFDictionaryRef returnedValues, CFErrorRef error) 1898 { 1899 object = returnedValues; 1900 if (object) 1901 CFRetain(object); 1902 if (error) 1903 { 1904 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error); 1905 } 1906 dispatch_semaphore_signal(waitSemaphore); 1907 }; 1908 1909 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock); 1910 1911 dispatch_semaphore_wait(waitSemaphore, finishTime); 1912 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull 1913 { 1914 CFRelease(object); 1915 object = NULL; 1916 } 1917 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors 1918 } 1919 1920 1921 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue) 1922 { 1923 CFStringRef uuid = SOSAccountCopyUUID(account); 1924 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); 1925 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); 1926 1927 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){ 1928 if (error){ 1929 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error); 1930 } 1931 dispatch_semaphore_signal(waitSemaphore); 1932 }; 1933 1934 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock); 1935 dispatch_semaphore_wait(waitSemaphore, finishTime); 1936 CFReleaseNull(uuid); 1937 } 1938 1939 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account) 1940 { 1941 NSDate *now = [NSDate date]; 1942 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup]; 1943 1944 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary]; 1945 1946 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("[")); 1947 1948 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent(); 1949 1950 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) { 1951 CFStringAppend(timeDescription, decription); 1952 }); 1953 CFStringAppend(timeDescription, CFSTR("]")); 1954 1955 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey]; 1956 CFReleaseNull(timeDescription); 1957 1958 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); 1959 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); 1960 dispatch_queue_t processQueue = dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0); 1961 1962 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){ 1963 if (error){ 1964 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error); 1965 } 1966 dispatch_semaphore_signal(waitSemaphore); 1967 }; 1968 1969 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock); 1970 dispatch_semaphore_wait(waitSemaphore, finishTime); 1971 } 1972 1973 // set the cleanup frequency to 3 days. 1974 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3 1975 1976 //Get all the key/values in KVS and remove old entries 1977 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error) 1978 { 1979 // This should only happen on some number of days 1980 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup]; 1981 NSDate *now = [NSDate date]; 1982 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup]; 1983 1984 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) { 1985 return true; 1986 } 1987 1988 dispatch_queue_t processQueue = dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0); 1989 1990 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error); 1991 NSMutableArray *peerIDs = [NSMutableArray array]; 1992 NSMutableArray *keysToRemove = [NSMutableArray array]; 1993 1994 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error); 1995 CFArrayForEach(peers, ^(const void *value) { 1996 SOSPeerInfoRef peer = (SOSPeerInfoRef)value; 1997 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer); 1998 1999 //any peerid that is not ours gets added 2000 if(![[account.trust peerID] isEqualToString:peerID]) 2001 [peerIDs addObject:peerID]; 2002 }); 2003 CFReleaseNull(peers); 2004 2005 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) { 2006 __block bool keyMatchesPeerID = false; 2007 2008 //checks for full peer ids 2009 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) { 2010 //if key contains peerid of one active peer 2011 if([KVSKey containsString:PeerID]){ 2012 secnotice("key-cleanup","key: %@", KVSKey); 2013 keyMatchesPeerID = true; 2014 } 2015 }]; 2016 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID) 2017 || [KVSKey hasPrefix:@"po"]) 2018 [keysToRemove addObject:KVSKey]; 2019 }]; 2020 2021 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove); 2022 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]); 2023 2024 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue); 2025 2026 //add last cleanup timestamp 2027 SOSAccountWriteLastCleanupTimestampToKVS(account); 2028 return true; 2029 2030 } 2031 2032 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) { 2033 SOSPeerInfoRef applicant = NULL; 2034 SOSAccountTrustClassic *trust = account.trust; 2035 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error); 2036 if(!userKey) return false; 2037 if(![trust ensureFullPeerAvailable:account err:error]) 2038 return applicant; 2039 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error)) 2040 return applicant; 2041 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error); 2042 2043 return applicant; 2044 } 2045 2046 #if OCTAGON 2047 static void 2048 AddStrippedTLKs(NSMutableArray* results, NSArray<CKKSKeychainBackedKey*>* tlks, NSMutableSet *seenUUID, bool authoritative) 2049 { 2050 for(CKKSKeychainBackedKey* tlk in tlks) { 2051 NSError* localerror = nil; 2052 CKKSAESSIVKey* keyBytes = [tlk ensureKeyLoaded:&localerror]; 2053 2054 if(!keyBytes || localerror) { 2055 secnotice("piggy", "Failed to load TLK %@: %@", tlk, localerror); 2056 continue; 2057 } 2058 2059 NSMutableDictionary* strippedDown = [@{ 2060 (id)kSecValueData : [keyBytes keyMaterial], 2061 (id)kSecAttrServer : tlk.zoneID.zoneName, 2062 (id)kSecAttrAccount : tlk.uuid, 2063 } mutableCopy]; 2064 2065 if (authoritative) { 2066 strippedDown[@"auth"] = @YES; 2067 } 2068 2069 secnotice("piggy", "sending TLK %@", tlk); 2070 2071 [results addObject:strippedDown]; 2072 [seenUUID addObject:tlk.uuid]; 2073 } 2074 } 2075 #endif 2076 2077 static void 2078 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative) 2079 { 2080 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) { 2081 NSString* parentUUID = keychainItem[(id)kSecAttrPath]; 2082 NSString* viewUUID = keychainItem[(id)kSecAttrAccount]; 2083 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer]; 2084 2085 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL) 2086 return; 2087 2088 if([parentUUID isEqualToString:viewUUID] || authoriative){ 2089 2090 /* check if we already have this entry */ 2091 if ([seenUUID containsObject:viewUUID]) 2092 return; 2093 2094 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData]; 2095 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0]; 2096 2097 if (key == NULL) 2098 return; 2099 2100 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID); 2101 2102 NSMutableDictionary* strippedDown = [@{ 2103 (id)kSecValueData : key, 2104 (id)kSecAttrServer : viewName, 2105 (id)kSecAttrAccount : viewUUID 2106 } mutableCopy]; 2107 if (authoriative) 2108 strippedDown[@"auth"] = @YES; 2109 2110 [results addObject:strippedDown]; 2111 [seenUUID addObject:viewUUID]; 2112 } 2113 }]; 2114 } 2115 2116 static void 2117 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID, bool selectedTLKsOnly) 2118 { 2119 #if OCTAGON 2120 NSError* localError = nil; 2121 NSArray<CKKSKeychainBackedKey*>* tlks = [[CKKSViewManager manager] currentTLKsFilteredByPolicy:selectedTLKsOnly error:&localError]; 2122 2123 if(localError) { 2124 secnotice("piggy", "unable to fetch TLKs: %@", localError); 2125 return; 2126 } 2127 2128 AddStrippedTLKs(results, tlks, seenUUID, true); 2129 #endif 2130 } 2131 2132 NSArray<NSDictionary *>* 2133 SOSAccountGetSelectedTLKs(void) 2134 { 2135 NSMutableArray<NSDictionary *>* results = [NSMutableArray array]; 2136 NSMutableSet *seenUUID = [NSMutableSet set]; 2137 2138 AddViewManagerResults(results, seenUUID, true); 2139 2140 return results; 2141 } 2142 2143 2144 NSArray<NSDictionary *>* 2145 SOSAccountGetAllTLKs(void) 2146 { 2147 CFTypeRef result = NULL; 2148 NSMutableArray<NSDictionary *>* results = [NSMutableArray array]; 2149 NSMutableSet *seenUUID = [NSMutableSet set]; 2150 2151 // first use all TLKs from the view manager 2152 AddViewManagerResults(results, seenUUID, false); 2153 2154 //try to grab tlk-piggy items 2155 NSDictionary* query = @{ 2156 (id)kSecClass : (id)kSecClassInternetPassword, 2157 (id)kSecUseDataProtectionKeychain : @YES, 2158 (id)kSecAttrAccessGroup : @"com.apple.security.ckks", 2159 (id)kSecAttrDescription: @"tlk", 2160 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue, 2161 (id)kSecMatchLimit : (id)kSecMatchLimitAll, 2162 (id)kSecReturnAttributes: @YES, 2163 (id)kSecReturnData: @YES, 2164 }; 2165 2166 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) { 2167 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false); 2168 } 2169 CFReleaseNull(result); 2170 2171 //try to grab tlk-piggy items 2172 query = @{ 2173 (id)kSecClass : (id)kSecClassInternetPassword, 2174 (id)kSecUseDataProtectionKeychain : @YES, 2175 (id)kSecAttrAccessGroup : @"com.apple.security.ckks", 2176 (id)kSecAttrDescription: @"tlk-piggy", 2177 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny, 2178 (id)kSecMatchLimit : (id)kSecMatchLimitAll, 2179 (id)kSecReturnAttributes: @YES, 2180 (id)kSecReturnData: @YES, 2181 }; 2182 2183 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) { 2184 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false); 2185 } 2186 CFReleaseNull(result); 2187 2188 secnotice("piggy", "Found %d TLKs", (int)[results count]); 2189 2190 return results; 2191 } 2192 2193 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid, 2194 const uint8_t *der, uint8_t *der_end) 2195 { 2196 if (type != kTLKUnknown) { 2197 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, 2198 piggy_encode_data(keychainData, der, 2199 piggy_encode_data(uuid, der, 2200 ccder_encode_uint64((uint64_t)type, der, der_end)))); 2201 } else { 2202 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, 2203 piggy_encode_data(keychainData, der, 2204 piggy_encode_data(uuid, der, 2205 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end)))); 2206 } 2207 } 2208 2209 static uint8_t* piggy_encode_data(NSData* data, 2210 const uint8_t *der, uint8_t *der_end) 2211 { 2212 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der, 2213 ccder_encode_body(data.length, data.bytes, der, der_end)); 2214 2215 } 2216 2217 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers 2218 static kTLKTypes 2219 name2type(NSString *view) 2220 { 2221 if ([view isEqualToString:@"Manatee"]) 2222 return kTLKManatee; 2223 else if ([view isEqualToString:@"Engram"]) 2224 return kTLKEngram; 2225 else if ([view isEqualToString:@"AutoUnlock"]) 2226 return kTLKAutoUnlock; 2227 if ([view isEqualToString:@"Health"]) 2228 return kTLKHealth; 2229 return kTLKUnknown; 2230 } 2231 2232 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers 2233 static unsigned 2234 rank_type(NSString *view) 2235 { 2236 if ([view isEqualToString:@"Manatee"]) 2237 return 5; 2238 else if ([view isEqualToString:@"Engram"]) 2239 return 4; 2240 else if ([view isEqualToString:@"AutoUnlock"]) 2241 return 3; 2242 if ([view isEqualToString:@"Health"]) 2243 return 2; 2244 return 0; 2245 } 2246 2247 static NSData * 2248 parse_uuid(NSString *uuidString) 2249 { 2250 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; 2251 uuid_t uuidblob; 2252 [uuid getUUIDBytes:uuidblob]; 2253 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)]; 2254 } 2255 static size_t 2256 piggy_sizeof_data(NSData* data) 2257 { 2258 return ccder_sizeof(CCDER_OCTET_STRING, [data length]); 2259 } 2260 2261 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) { 2262 if (type != kTLKUnknown) { 2263 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, 2264 piggy_sizeof_data(keychainData) + 2265 piggy_sizeof_data(uuid) + 2266 ccder_sizeof_uint64(type)); 2267 } else { 2268 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, 2269 piggy_sizeof_data(keychainData) + 2270 piggy_sizeof_data(uuid) + 2271 der_sizeof_string((__bridge CFStringRef)name, NULL)); 2272 } 2273 } 2274 2275 NSArray<NSDictionary*>* 2276 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks) 2277 { 2278 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy]; 2279 2280 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) { 2281 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]); 2282 if (obj1[@"auth"] != NULL) 2283 rank1 += 1000; 2284 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]); 2285 if (obj2[@"auth"] != NULL) 2286 rank2 += 1000; 2287 2288 /* 2289 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager), 2290 * since we are sorting backward, the Ascending/Descending looks wrong below. 2291 */ 2292 if (rank1 > rank2) { 2293 return NSOrderedAscending; 2294 } else if (rank1 < rank2) { 2295 return NSOrderedDescending; 2296 } 2297 return NSOrderedSame; 2298 }]; 2299 2300 return sortedTLKs; 2301 } 2302 2303 static NSArray<NSData *> * 2304 build_tlks(NSArray<NSDictionary*>* tlks) 2305 { 2306 NSMutableArray *array = [NSMutableArray array]; 2307 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks); 2308 2309 for (NSDictionary *item in sortedTLKs) { 2310 NSData* keychainData = item[(__bridge id)kSecValueData]; 2311 NSString* name = item[(__bridge id)kSecAttrServer]; 2312 NSString *uuidString = item[(__bridge id)kSecAttrAccount]; 2313 NSData* uuid = parse_uuid(uuidString); 2314 2315 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)]; 2316 2317 unsigned char *der = [tlk mutableBytes]; 2318 unsigned char *der_end = der + [tlk length]; 2319 2320 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL) 2321 return NULL; 2322 2323 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString); 2324 2325 [array addObject:tlk]; 2326 } 2327 return array; 2328 } 2329 2330 static NSArray<NSData *> * 2331 build_identities(NSArray<NSData *>* identities) 2332 { 2333 NSMutableArray *array = [NSMutableArray array]; 2334 for (NSData *item in identities) { 2335 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])]; 2336 2337 unsigned char *der = [ident mutableBytes]; 2338 unsigned char *der_end = der + [ident length]; 2339 2340 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end); 2341 [array addObject:ident]; 2342 } 2343 return array; 2344 } 2345 2346 2347 2348 static unsigned char * 2349 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end) 2350 { 2351 unsigned char *body_end = der_end; 2352 for (NSData *datum in data) { 2353 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end); 2354 if (der_end == NULL) 2355 return NULL; 2356 } 2357 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end); 2358 } 2359 2360 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size) 2361 { 2362 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, 2363 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) + 2364 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size)); 2365 } 2366 2367 static NSData *encode_piggy(size_t IdentitiesBudget, 2368 size_t TLKBudget, 2369 NSArray<NSData*>* identities, 2370 NSArray<NSDictionary*>* tlks) 2371 { 2372 NSArray<NSData *> *encodedTLKs = build_tlks(tlks); 2373 NSArray<NSData *> *encodedIdentities = build_identities(identities); 2374 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array]; 2375 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array]; 2376 size_t payloadSize = 0, identitiesSize = 0; 2377 NSMutableData *result = NULL; 2378 2379 for (NSData *tlk in encodedTLKs) { 2380 if (TLKBudget - payloadSize < [tlk length]) 2381 break; 2382 [budgetArray addObject:tlk]; 2383 payloadSize += tlk.length; 2384 } 2385 secnotice("piggy", "sending %d tlks", (int)budgetArray.count); 2386 2387 for (NSData *ident in encodedIdentities) { 2388 if (IdentitiesBudget - identitiesSize < [ident length]) 2389 break; 2390 [identitiesArray addObject:ident]; 2391 identitiesSize += ident.length; 2392 } 2393 secnotice("piggy", "sending %d identities", (int)identitiesArray.count); 2394 2395 2396 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize); 2397 2398 result = [NSMutableData dataWithLength:piggySize]; 2399 2400 unsigned char *der = [result mutableBytes]; 2401 unsigned char *der_end = der + [result length]; 2402 2403 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, 2404 encode_data_array(identitiesArray, der, 2405 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes]) 2406 return NULL; 2407 2408 return result; 2409 } 2410 2411 static const size_t SOSCCIdentitiesBudget = 120; 2412 static const size_t SOSCCTLKBudget = 500; 2413 2414 NSData * 2415 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks) 2416 { 2417 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks); 2418 } 2419 2420 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account) 2421 { 2422 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); 2423 2424 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) { 2425 if(SOSPeerInfoIsCloudIdentity(peer)) { 2426 CFArrayAppendValue(identities, peer); 2427 } 2428 }); 2429 return identities; 2430 } 2431 2432 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) { 2433 2434 NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array]; 2435 NSArray<NSDictionary *>* tlks = nil; 2436 2437 if (flags & kSOSInitialSyncFlagTLKsRequestOnly) { 2438 tlks = SOSAccountGetSelectedTLKs(); 2439 } else { 2440 if (flags & kSOSInitialSyncFlagiCloudIdentity) { 2441 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account); 2442 secnotice("piggy", "identities: %@", identities); 2443 2444 CFIndex i, count = CFArrayGetCount(identities); 2445 for (i = 0; i < count; i++) { 2446 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i); 2447 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error)); 2448 if (data) 2449 [encodedIdenities addObject:data]; 2450 } 2451 CFRelease(identities); 2452 } 2453 2454 2455 if (flags & kSOSInitialSyncFlagTLKs) { 2456 tlks = SOSAccountGetAllTLKs(); 2457 } 2458 } 2459 2460 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks)); 2461 } 2462 2463 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) { 2464 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL); 2465 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown")); 2466 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL); 2467 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature")); 2468 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL); 2469 if(accountName == NULL) { 2470 accountName = CFSTR("Unavailable"); 2471 } 2472 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle); 2473 CFStringRef genString = SOSGenerationCountCopyDescription(gencount); 2474 2475 secnotice("circleOps", 2476 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@", 2477 operation, accountName, version, genString, pkeyID, sigID, circleHash); 2478 CFReleaseNull(pkeyID); 2479 CFReleaseNull(sigID); 2480 CFReleaseNull(circleHash); 2481 CFReleaseNull(genString); 2482 } 2483 2484 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) { 2485 SOSGenCountRef gencount = NULL; 2486 CFDataRef signature = NULL; 2487 SecKeyRef ourKey = NULL; 2488 2489 CFDataRef pbblob = NULL; 2490 SOSCircleRef prunedCircle = NULL; 2491 2492 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)"); 2493 2494 SOSCCStatus circleStat = [account getCircleStatus:error]; 2495 if(circleStat != kSOSCCInCircle) { 2496 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat)); 2497 return NULL; 2498 } 2499 2500 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error); 2501 require_quiet(userKey, errOut); 2502 2503 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error)); 2504 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut, 2505 secnotice("circleOps", "Peer application wasn't signed with the correct userKey")); 2506 2507 { 2508 SOSFullPeerInfoRef fpi = account.fullPeerInfo; 2509 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error); 2510 require_quiet(ourKey, errOut); 2511 } 2512 2513 SOSCircleRef currentCircle = [account.trust getCircle:error]; 2514 require_quiet(currentCircle, errOut); 2515 2516 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error); 2517 require_quiet(prunedCircle, errOut); 2518 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut); 2519 2520 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle)); 2521 2522 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error); 2523 require_quiet(signature, errOut); 2524 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1); 2525 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error); 2526 2527 errOut: 2528 CFReleaseNull(prunedCircle); 2529 CFReleaseNull(gencount); 2530 CFReleaseNull(signature); 2531 CFReleaseNull(ourKey); 2532 2533 if(!pbblob && error != NULL) { 2534 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error); 2535 } 2536 2537 return pbblob; 2538 } 2539 2540 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) { 2541 bool retval = false; 2542 SecKeyRef userKey = NULL; 2543 SOSAccountTrustClassic *trust = account.trust; 2544 SOSGenCountRef gencount = NULL; 2545 CFDataRef signature = NULL; 2546 SecKeyRef pubKey = NULL; 2547 bool setInitialSyncTimeoutToV0 = false; 2548 2549 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)"); 2550 2551 if (!isData(joiningBlob)) { 2552 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)"); 2553 return false; 2554 } 2555 2556 userKey = SOSAccountGetPrivateCredential(account, error); 2557 if(!userKey) { 2558 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error); 2559 return retval; 2560 } 2561 2562 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) { 2563 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error); 2564 return retval; 2565 } 2566 2567 if(setInitialSyncTimeoutToV0){ 2568 secnotice("circleOps", "setting flag in account for piggyback v0"); 2569 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL); 2570 } else { 2571 secnotice("circleOps", "clearing flag in account for piggyback v0"); 2572 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL); 2573 } 2574 SOSAccountInitializeInitialSync(account); 2575 2576 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version); 2577 2578 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) { 2579 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey, 2580 gencount, 2581 pubKey, 2582 signature, 2583 trust.fullPeerInfo, error);; 2584 }]; 2585 2586 CFReleaseNull(gencount); 2587 CFReleaseNull(pubKey); 2588 CFReleaseNull(signature); 2589 2590 return retval; 2591 } 2592 2593 static char boolToChars(bool val, char truechar, char falsechar) { 2594 return val? truechar: falsechar; 2595 } 2596 2597 #define ACCOUNTLOGSTATE "accountLogState" 2598 void SOSAccountLogState(SOSAccount* account) { 2599 bool hasPubKey = account.accountKey != NULL; 2600 SOSAccountTrustClassic *trust = account.trust; 2601 bool pubTrusted = account.accountKeyIsTrusted; 2602 bool hasPriv = account.accountPrivateKey != NULL; 2603 SOSCCStatus stat = [account getCircleStatus:NULL]; 2604 2605 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL): 2606 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*")); 2607 2608 secnotice(ACCOUNTLOGSTATE, "Start"); 2609 2610 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@] [UID: %d EUID: %d]", 2611 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'), 2612 userPubKeyID, 2613 SOSAccountGetSOSCCStatusString(stat), getuid(), geteuid() 2614 ); 2615 CFReleaseNull(userPubKeyID); 2616 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID)); 2617 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle"); 2618 } 2619 2620 void SOSAccountLogViewState(SOSAccount* account) { 2621 bool isInCircle = [account.trust isInCircleOnly:NULL]; 2622 require_quiet(isInCircle, imOut); 2623 SOSPeerInfoRef mpi = account.peerInfo; 2624 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account); 2625 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account); 2626 2627 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL; 2628 CFStringSetPerformWithDescription(views, ^(CFStringRef description) { 2629 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@", 2630 boolToChars(isInitialComplete, 'I', 'i'), 2631 boolToChars(isBackupComplete, 'B', 'b'), 2632 description); 2633 }); 2634 CFReleaseNull(views); 2635 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account); 2636 CFStringSetPerformWithDescription(views, ^(CFStringRef description) { 2637 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description); 2638 }); 2639 CFReleaseNull(unsyncedViews); 2640 2641 imOut: 2642 secnotice(ACCOUNTLOGSTATE, "Finish"); 2643 2644 return; 2645 } 2646 2647 2648 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) { 2649 if(!isString(serial)) return; 2650 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); 2651 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial); 2652 [account.trust updateV2Dictionary:account v2:newv2dict]; 2653 } 2654 2655 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid) 2656 { 2657 secnotice("otrtimer", "timer fired!"); 2658 CFErrorRef error = NULL; 2659 2660 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL); 2661 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) { 2662 if(SOSCoderIsCoderInAwaitingState(coder)){ 2663 secnotice("otrtimer", "coder is in awaiting state, restarting coder"); 2664 CFErrorRef localError = NULL; 2665 SOSCoderReset(coder); 2666 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){ 2667 secerror("Attempt to recover coder failed to restart: %@", localError); 2668 } 2669 else{ 2670 secnotice("otrtimer", "coder restarted!"); 2671 SOSEngineSetCodersNeedSaving(engine, true); 2672 SOSPeerSetMustSendMessage(peer, true); 2673 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer)); 2674 } 2675 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer)); 2676 SOSPeerRemoveOTRTimerEntry(peer); 2677 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer)); 2678 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer)); 2679 } 2680 else{ 2681 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder"); 2682 } 2683 }); 2684 if(error) 2685 { 2686 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error); 2687 } 2688 CFReleaseNull(error); 2689 } 2690 2691 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup) 2692 { 2693 __block SOSAccount* account = txn.account; 2694 CFErrorRef error = NULL; 2695 2696 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL); 2697 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) { 2698 2699 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer); 2700 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer); 2701 CFErrorRef error = NULL; 2702 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup]; 2703 2704 if(message){ 2705 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id); 2706 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error]; 2707 2708 if(!sendResult || error){ 2709 secnotice("ratelimit", "could not send message: %@", error); 2710 } 2711 } 2712 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup]; 2713 [limiter.accessGroupToTimer removeObjectForKey:accessGroup]; 2714 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup]; 2715 }); 2716 2717 if(error) 2718 { 2719 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error); 2720 } 2721 CFReleaseNull(error); 2722 } 2723 2724 #if OCTAGON 2725 2726 /* 2727 * State machine 2728 */ 2729 2730 OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup"; 2731 OctagonFlag* SOSFlagTriggerRingUpdate = (OctagonFlag*)@"trigger_ring_update"; 2732 2733 OctagonState* SOSStateReady = (OctagonState*)@"ready"; 2734 OctagonState* SOSStateError = (OctagonState*)@"error"; 2735 OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup"; 2736 OctagonState* SOSStatePerformRingUpdate = (OctagonState*)@"perform_ring_update"; 2737 2738 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void) { 2739 static NSDictionary<OctagonState*, NSNumber*>* map = nil; 2740 static dispatch_once_t onceToken; 2741 dispatch_once(&onceToken, ^{ 2742 map = @{ 2743 SOSStateReady: @0U, 2744 SOSStateError: @1U, 2745 SOSStatePerformBackup: @2U, 2746 SOSStatePerformRingUpdate: @3U, 2747 }; 2748 }); 2749 return map; 2750 } 2751 2752 static NSSet<OctagonFlag*>* SOSFlagsSet(void) { 2753 static NSSet<OctagonFlag*>* set = nil; 2754 static dispatch_once_t onceToken; 2755 dispatch_once(&onceToken, ^{ 2756 set = [NSSet setWithArray:@[ 2757 SOSFlagTriggerBackup, 2758 SOSFlagTriggerRingUpdate, 2759 ]]; 2760 }); 2761 return set; 2762 } 2763 2764 2765 2766 + (NSURL *)urlForSOSAccountSettings { 2767 return (__bridge_transfer NSURL *)SecCopyURLForFileInKeychainDirectory(CFSTR("SOSAccountSettings.pb")); 2768 } 2769 2770 2771 - (void)setupStateMachine { 2772 WEAKIFY(self); 2773 2774 self.accountConfiguration = [[CKKSPBFileStorage alloc] initWithStoragePath:[[self class] urlForSOSAccountSettings] 2775 storageClass:[SOSAccountConfiguration class]]; 2776 2777 NSAssert(self.stateMachine == nil, @"can't bootstrap more than once"); 2778 2779 self.stateMachineQueue = dispatch_queue_create("SOS-statemachine", NULL); 2780 2781 self.stateMachine = [[OctagonStateMachine alloc] initWithName:@"sosaccount" 2782 states:[NSSet setWithArray:[SOSStateMap() allKeys]] 2783 flags:SOSFlagsSet() 2784 initialState:SOSStateReady 2785 queue:self.stateMachineQueue 2786 stateEngine:self 2787 lockStateTracker:[CKKSLockStateTracker globalTracker] 2788 reachabilityTracker:nil]; 2789 2790 2791 self.performBackups = [[CKKSNearFutureScheduler alloc] initWithName:@"performBackups" 2792 initialDelay:5*NSEC_PER_SEC 2793 continuingDelay:30*NSEC_PER_SEC 2794 keepProcessAlive:YES 2795 dependencyDescriptionCode:CKKSResultDescriptionNone 2796 block:^{ 2797 STRONGIFY(self); 2798 [self addBackupFlag]; 2799 }]; 2800 2801 self.performRingUpdates = [[CKKSNearFutureScheduler alloc] initWithName:@"performRingUpdates" 2802 initialDelay:1*NSEC_PER_SEC 2803 expontialBackoff:2.0 2804 maximumDelay:10*NSEC_PER_SEC 2805 keepProcessAlive:YES 2806 dependencyDescriptionCode:CKKSResultDescriptionNone 2807 block:^{ 2808 STRONGIFY(self); 2809 [self addRingUpdateFlag]; 2810 }]; 2811 2812 SOSAccountConfiguration *conf = self.accountConfiguration.storage; 2813 2814 if (conf.pendingBackupPeers.count) { 2815 [self addBackupFlag]; 2816 } 2817 if (conf.ringUpdateFlag) { 2818 [self addRingUpdateFlag]; 2819 } 2820 } 2821 2822 2823 /* 2824 * Flag adding to state machine 2825 */ 2826 2827 - (void)addBackupFlag { 2828 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup 2829 conditions:OctagonPendingConditionsDeviceUnlocked]; 2830 [self.stateMachine handlePendingFlag:pendingFlag]; 2831 } 2832 2833 - (void)addRingUpdateFlag { 2834 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerRingUpdate 2835 conditions:OctagonPendingConditionsDeviceUnlocked]; 2836 [self.stateMachine handlePendingFlag:pendingFlag]; 2837 } 2838 2839 //Mark: -- Set up state for state machine 2840 2841 2842 - (void)triggerBackupForPeers:(NSArray<NSString*>*)backupPeers 2843 { 2844 NSMutableSet *pending = [NSMutableSet set]; 2845 if (backupPeers) { 2846 [pending addObjectsFromArray:backupPeers]; 2847 } 2848 2849 WEAKIFY(self); 2850 dispatch_async(self.stateMachineQueue, ^{ 2851 STRONGIFY(self); 2852 2853 SOSAccountConfiguration *storage = self.accountConfiguration.storage; 2854 2855 if (storage.pendingBackupPeers) { 2856 [pending addObjectsFromArray:storage.pendingBackupPeers]; 2857 } 2858 storage.pendingBackupPeers = [[pending allObjects] mutableCopy]; 2859 [self.accountConfiguration setStorage:storage]; 2860 [self.performBackups trigger]; 2861 secnotice("sos-sm", "trigger backup for peers: %@ at %@", 2862 backupPeers, self.performBackups.nextFireTime); 2863 }); 2864 } 2865 2866 - (void)triggerRingUpdate 2867 { 2868 WEAKIFY(self); 2869 dispatch_async(self.stateMachineQueue, ^{ 2870 STRONGIFY(self); 2871 SOSAccountConfiguration *storage = self.accountConfiguration.storage; 2872 storage.ringUpdateFlag = YES; 2873 [self.accountConfiguration setStorage:storage]; 2874 [self.performRingUpdates trigger]; 2875 secnotice("sos-sm", "trigger ring update at %@", 2876 self.performRingUpdates.nextFireTime); 2877 }); 2878 } 2879 2880 //Mark: -- State machine and opertions 2881 2882 - (OctagonStateTransitionOperation *)performBackup { 2883 2884 WEAKIFY(self); 2885 return [OctagonStateTransitionOperation named:@"perform-backup-state" 2886 intending:SOSStateReady 2887 errorState:SOSStateError 2888 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) { 2889 STRONGIFY(self); 2890 SOSAccountConfiguration *storage = self.accountConfiguration.storage; 2891 2892 secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers); 2893 2894 if (storage.pendingBackupPeers.count) { 2895 SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers); 2896 [storage clearPendingBackupPeers]; 2897 } 2898 [self.accountConfiguration setStorage:storage]; 2899 2900 op.nextState = SOSStateReady; 2901 }]; 2902 } 2903 2904 - (OctagonStateTransitionOperation *)performRingUpdate { 2905 2906 WEAKIFY(self); 2907 return [OctagonStateTransitionOperation named:@"perform-ring-update" 2908 intending:SOSStateReady 2909 errorState:SOSStateError 2910 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) { 2911 STRONGIFY(self); 2912 __block bool goodPubKey = false; 2913 __block CFMutableSetRef myBackupViews = NULL; 2914 __block bool changesMade = false; 2915 2916 SOSAccountConfiguration *storage = self.accountConfiguration.storage; 2917 storage.ringUpdateFlag = NO; 2918 [self.accountConfiguration setStorage:storage]; 2919 2920 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 2921 if([self accountKeyIsTrusted] && [self isInCircle:NULL]) { 2922 [self _onQueueRecordRetiredPeersInCircle]; 2923 SOSAccountEnsureRecoveryRing(self); 2924 2925 CFErrorRef error = NULL; 2926 2927 // if we have a backup key but it isn't good clear it. goodPubKey becomes false. 2928 // if we have nil then we're resetting the pubkey. 2929 goodPubKey = SOSAccountBackupKeyConsistencyCheck(self, &error); 2930 2931 if(goodPubKey) { 2932 // It's a good key, we're going with it. Stop backing up the old way. 2933 CFErrorRef localError = NULL; 2934 if (!SOSDeleteV0Keybag(&localError)) { 2935 secerror("Failed to delete v0 keybag: %@", localError); 2936 } else { 2937 changesMade = true; 2938 } 2939 CFReleaseNull(localError); 2940 2941 if (self.peerInfo) { 2942 myBackupViews = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, SOSPeerInfoGetPermittedViews(self.peerInfo)); 2943 } 2944 } 2945 } 2946 }]; 2947 2948 if(goodPubKey && myBackupViews && !CFSetIsEmpty(myBackupViews)) { 2949 CFSetForEach(myBackupViews, ^(const void *value) { 2950 CFStringRef viewName = asString(value, NULL); 2951 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 2952 if([self _onQueueEnsureInBackupRings: viewName]) { 2953 changesMade = true; 2954 } 2955 }]; 2956 }); 2957 } 2958 CFReleaseNull(myBackupViews); 2959 2960 // This is where circle and ring flushes happen now. 2961 // If a circle or ring has been Pushed in handleUpdate routines we need them to get out now. 2962 // It isn't just the work up above that can do that push. It could simply be signing 2963 // an existing circle/ring. 2964 [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { 2965 CFErrorRef localError = NULL; 2966 if(![self.circle_transport flushChanges:&localError]){ 2967 secnotice("circleOps", "flush circles/rings failed %@", localError); 2968 } 2969 CFReleaseNull(localError); 2970 2971 if(!SecCKKSTestDisableSOS()) { 2972 SOSAccountNotifyEngines(self); 2973 } 2974 }]; 2975 2976 op.nextState = SOSStateReady; 2977 }]; 2978 2979 } 2980 2981 2982 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState 2983 flags:(nonnull OctagonFlags *)flags 2984 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler 2985 { 2986 dispatch_assert_queue(self.stateMachineQueue); 2987 2988 secnotice("sos-sm", "Entering state: %@ [flags: %@]", currentState, flags); 2989 2990 if ([currentState isEqualToString:SOSStateReady]) { 2991 if([flags _onqueueContains:SOSFlagTriggerBackup]) { 2992 [flags _onqueueRemoveFlag:SOSFlagTriggerBackup]; 2993 return [OctagonStateTransitionOperation named:@"perform-backup-flag" 2994 entering:SOSStatePerformBackup]; 2995 } 2996 2997 if ([flags _onqueueContains:SOSFlagTriggerRingUpdate]) { 2998 [flags _onqueueRemoveFlag:SOSFlagTriggerRingUpdate]; 2999 return [OctagonStateTransitionOperation named:@"perform-ring-update-flag" 3000 entering:SOSStatePerformRingUpdate]; 3001 } 3002 return nil; 3003 3004 } else if ([currentState isEqualToString:SOSStateError]) { 3005 return nil; 3006 } else if ([currentState isEqualToString:SOSStatePerformRingUpdate]) { 3007 return [self performRingUpdate]; 3008 3009 } else if ([currentState isEqualToString:SOSStatePerformBackup]) { 3010 return [self performBackup]; 3011 } 3012 3013 return nil; 3014 } 3015 #endif 3016 3017 @end 3018 3019