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