]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccount.m
Security-59306.120.7.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 sosAccountLeaveCircle(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 = nil;
1145
1146 if(parentData) {
1147 parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentData error:&localError];
1148 }
1149
1150 SOSFullPeerInfoRef fpi = identity;
1151 if(!fpi) return false;
1152
1153 CFErrorRef retiredError = NULL;
1154
1155 bool retval = false;
1156
1157 SFSignInAnalytics *promoteToRetiredEvent = nil;
1158
1159 if(parent) {
1160 promoteToRetiredEvent = [parent newSubTaskForEvent:@"promoteToRetiredEvent"];
1161 }
1162
1163 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError);
1164 if(retiredError){
1165 if(promoteToRetiredEvent) {
1166 [promoteToRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
1167 }
1168 secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError);
1169 if(error){
1170 *error = retiredError;
1171 }else{
1172 CFReleaseNull(retiredError);
1173 }
1174 }
1175 if(promoteToRetiredEvent) {
1176 [promoteToRetiredEvent stopWithAttributes:nil];
1177 }
1178
1179 if (!retire_peer) {
1180 secerror("Create ticket failed for peer %@: %@", fpi, localError);
1181 } else {
1182 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1183 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
1184 // Remove our application if we have one.
1185 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
1186 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
1187 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
1188 CFErrorRef cleanupError = NULL;
1189 SFSignInAnalytics *cleanupEvent = nil;
1190
1191 if(parent) {
1192 cleanupEvent = [parent newSubTaskForEvent:@"cleanupAfterPeerEvent"];
1193 }
1194 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retire_peer err:&cleanupError]) {
1195 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
1196 }
1197 if(cleanupEvent) {
1198 [cleanupEvent stopWithAttributes:nil];
1199 }
1200 CFReleaseSafe(cleanupError);
1201 }
1202 }
1203
1204 // Store the retirement record locally.
1205 CFSetAddValue((__bridge CFMutableSetRef)retirees, retire_peer);
1206
1207 trust.retirees = retirees;
1208
1209 // Write retirement to Transport
1210 CFErrorRef postError = NULL;
1211 SFSignInAnalytics *postRetirementEvent = nil;
1212
1213 if(parent) {
1214 postRetirementEvent = [parent newSubTaskForEvent:@"postRestirementEvent"];
1215 }
1216
1217 if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){
1218 if(postRetirementEvent) {
1219 [postRetirementEvent logRecoverableError:(__bridge NSError*)postError];
1220 }
1221 secwarning("Couldn't post retirement (%@)", postError);
1222 }
1223 if(postRetirementEvent) {
1224 [postRetirementEvent stopWithAttributes:nil];
1225 }
1226
1227 SFSignInAnalytics *flushChangesEvent = nil;
1228 if(parent) {
1229 flushChangesEvent = [parent newSubTaskForEvent:@"flushChangesEvent"];
1230 }
1231
1232 if(![account.circle_transport flushChanges:&postError]){
1233 if(flushChangesEvent) {
1234 [flushChangesEvent logRecoverableError:(__bridge NSError*)postError];
1235 }
1236 secwarning("Couldn't flush retirement data (%@)", postError);
1237 }
1238 if(flushChangesEvent) {
1239 [flushChangesEvent stopWithAttributes:nil];
1240 }
1241 CFReleaseNull(postError);
1242 }
1243 SFSignInAnalytics *purgeIdentityEvent = nil;
1244 if(parent) {
1245 purgeIdentityEvent = [parent newSubTaskForEvent:@"purgeIdentityEvent"];
1246 }
1247 SOSAccountPurgeIdentity(account);
1248 if(purgeIdentityEvent) {
1249 [purgeIdentityEvent stopWithAttributes:nil];
1250 }
1251 retval = true;
1252
1253 CFReleaseNull(retire_peer);
1254 return retval;
1255 }
1256
1257 bool SOSAccountPostDebugScope(SOSAccount* account, CFTypeRef scope, CFErrorRef *error) {
1258 bool result = false;
1259 if (account.circle_transport) {
1260 result = [account.circle_transport kvssendDebugInfo:kSOSAccountDebugScope debug:scope err:error];
1261 }
1262 return result;
1263 }
1264
1265 /*
1266 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1267 local value that has been overwritten by a distant value. If there is no
1268 conflict between the local and the distant values when doing the initial
1269 sync (e.g. if the cloud has no data stored or the client has not stored
1270 any data yet), you'll never see that notification.
1271
1272 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1273 with server but initial round trip with server does not imply
1274 NSUbiquitousKeyValueStoreInitialSyncChange.
1275 */
1276
1277
1278 //
1279 // MARK: Status summary
1280 //
1281
1282
1283 CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
1284 switch(status) {
1285 case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
1286 case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
1287 case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
1288 case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
1289 case kSOSCCError: return CFSTR("kSOSCCError");
1290 }
1291 return CFSTR("kSOSCCError");
1292 }
1293 SOSCCStatus SOSAccountGetSOSCCStatusFromString(CFStringRef status) {
1294 if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1295 return kSOSCCInCircle;
1296 } else if(CFEqualSafe(status, CFSTR("kSOSCCInCircle"))) {
1297 return kSOSCCInCircle;
1298 } else if(CFEqualSafe(status, CFSTR("kSOSCCNotInCircle"))) {
1299 return kSOSCCNotInCircle;
1300 } else if(CFEqualSafe(status, CFSTR("kSOSCCRequestPending"))) {
1301 return kSOSCCRequestPending;
1302 } else if(CFEqualSafe(status, CFSTR("kSOSCCCircleAbsent"))) {
1303 return kSOSCCCircleAbsent;
1304 } else if(CFEqualSafe(status, CFSTR("kSOSCCError"))) {
1305 return kSOSCCError;
1306 }
1307 return kSOSCCError;
1308 }
1309
1310 //
1311 // MARK: Account Reset Circles
1312 //
1313
1314 // This needs to be called within a [trust modifyCircle()] block
1315
1316
1317 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccount* account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
1318 bool retval = false;
1319
1320 SOSAccountTrustClassic *trust = account.trust;
1321 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
1322
1323 CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
1324
1325 SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
1326 if(SOSPeerInfoIsCloudIdentity(peer)) {
1327 SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
1328 if(!icfpi) {
1329 CFSetAddValue(iCloud2Remove, peer);
1330 }
1331 CFReleaseNull(icfpi);
1332 }
1333 });
1334
1335 if(CFSetGetCount(iCloud2Remove) > 0) {
1336 retval = true;
1337 SOSCircleRemovePeers(circle, privKey, identity, iCloud2Remove, error);
1338 }
1339 CFReleaseNull(iCloud2Remove);
1340 return retval;
1341 }
1342
1343 //
1344 // MARK: start backups
1345 //
1346
1347
1348 bool SOSAccountEnsureInBackupRings(SOSAccount* account) {
1349 __block bool result = false;
1350 __block CFErrorRef error = NULL;
1351 secnotice("backup", "Ensuring in rings");
1352
1353 if(!account.backup_key){
1354 return true;
1355 }
1356
1357 if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)account.backup_key, &error)){
1358 secnotice("backupkey", "account backup key isn't valid: %@", error);
1359 account.backup_key = nil;
1360 CFReleaseNull(error);
1361 return false;
1362 }
1363
1364 NSData *peerBackupKey = (__bridge_transfer NSData*)SOSPeerInfoCopyBackupKey(account.peerInfo);
1365 if(![peerBackupKey isEqual:account.backup_key]) {
1366 result = SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
1367 return SOSFullPeerInfoUpdateBackupKey(fpi, (__bridge CFDataRef)(account.backup_key), error);
1368 });
1369 if (!result) {
1370 secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error);
1371 CFReleaseNull(error);
1372 return result;
1373 }
1374 }
1375
1376 // It's a good key, we're going with it. Stop backing up the old way.
1377 CFErrorRef localError = NULL;
1378 if (!SOSDeleteV0Keybag(&localError)) {
1379 secerror("Failed to delete v0 keybag: %@", localError);
1380 }
1381 CFReleaseNull(localError);
1382
1383 // Setup backups the new way.
1384 SOSAccountForEachBackupView(account, ^(const void *value) {
1385 CFStringRef viewName = asString(value, NULL);
1386 bool resetRing = SOSAccountValidateBackupRingForView(account, viewName, NULL);
1387 if(resetRing) {
1388 SOSAccountUpdateBackupRing(account, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) {
1389 SOSRingRef newRing = SOSAccountCreateBackupRingForView(account, viewName, error);
1390 return newRing;
1391 });
1392 }
1393 });
1394
1395 if (!result) {
1396 secnotice("backupkey", "Failed to setup backup public key: %@", error);
1397 }
1398 CFReleaseNull(error);
1399 return result;
1400 }
1401
1402 //
1403 // MARK: Recovery Public Key Functions
1404 //
1405
1406 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1407 bool retval = SOSAccountSetRecoveryKey(txn.account, recovery_key, error);
1408 if(retval) secnotice("recovery", "successfully registered recovery public key");
1409 else secnotice("recovery", "could not register recovery public key: %@", *error);
1410 SOSClearErrorIfTrue(retval, error);
1411 return retval;
1412 }
1413
1414 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransaction* txn, CFDataRef recovery_key, CFErrorRef *error){
1415 bool retval = SOSAccountRemoveRecoveryKey(txn.account, error);
1416 if(retval) secnotice("recovery", "RK Cleared");
1417 else secnotice("recovery", "Couldn't clear RK(%@)", *error);
1418 SOSClearErrorIfTrue(retval, error);
1419 return retval;
1420 }
1421
1422 CFDataRef SOSAccountCopyRecoveryPublicKey(SOSAccountTransaction* txn, CFErrorRef *error){
1423 CFDataRef result = NULL;
1424 result = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, txn.account, error);
1425 if(!result) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error);
1426
1427 if (!isData(result)) {
1428 CFReleaseNull(result);
1429 }
1430 SOSClearErrorIfTrue(result != NULL, error);
1431
1432 return result;
1433 }
1434
1435 //
1436 // MARK: Joining
1437 //
1438
1439 static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key, bool use_cloud_peer, NSData* parentEvent, CFErrorRef* error) {
1440 SOSAccount* account = aTxn.account;
1441 SOSAccountTrustClassic *trust = account.trust;
1442 __block bool result = false;
1443 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1444 SFSignInAnalytics *ensureFullPeerAvailableEvent = nil;
1445 NSError* localError = nil;
1446 SFSignInAnalytics* parent = nil;
1447
1448 if(parentEvent) {
1449 parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1450 }
1451
1452 require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1453 if(parent) {
1454 ensureFullPeerAvailableEvent = [parent newSubTaskForEvent:@"ensureFullPeerAvailableEvent"];
1455 }
1456 require_quiet([account.trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1457 if(ensureFullPeerAvailableEvent) {
1458 [ensureFullPeerAvailableEvent stopWithAttributes:nil];
1459 }
1460
1461 SOSFullPeerInfoRef myCirclePeer = trust.fullPeerInfo;
1462 if (SOSCircleCountPeers(trust.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1463 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1464 // this also clears initial sync data
1465 result = [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1466 } else {
1467 SFSignInAnalytics *acceptApplicantEvent = nil;
1468
1469 SOSAccountInitializeInitialSync(account);
1470 if (use_cloud_peer) {
1471 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL);
1472 }
1473 if(parent) {
1474 acceptApplicantEvent = [parent newSubTaskForEvent:@"acceptApplicantEvent"];
1475 }
1476 [account.trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1477 result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
1478 result &= SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
1479 trust.departureCode = kSOSNeverLeftCircle;
1480 if(result && cloud_full_peer) {
1481 CFErrorRef localError = NULL;
1482 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1483 require_quiet(cloudid, finish);
1484 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1485 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
1486 finish:
1487 if (localError){
1488 if(acceptApplicantEvent) {
1489 [acceptApplicantEvent logRecoverableError:(__bridge NSError *)(localError)];
1490 }
1491 secerror("Failed to join with cloud identity: %@", localError);
1492 CFReleaseNull(localError);
1493 }
1494 }
1495 return result;
1496 }];
1497 if(acceptApplicantEvent) {
1498 [acceptApplicantEvent stopWithAttributes:nil];
1499 }
1500 if (use_cloud_peer) {
1501 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1502 if(acceptApplicantEvent) {
1503 SFSignInAnalytics *updateOutOfDateSyncViewsEvent = [acceptApplicantEvent newSubTaskForEvent:@"updateOutOfDateSyncViewsEvent"];
1504 [updateOutOfDateSyncViewsEvent stopWithAttributes:nil];
1505 }
1506 }
1507 }
1508 fail:
1509 CFReleaseNull(cloud_full_peer);
1510 return result;
1511 }
1512
1513 static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, NSData* parentEvent, CFErrorRef* error) {
1514 SOSAccount* account = aTxn.account;
1515 SOSAccountTrustClassic *trust = account.trust;
1516 bool success = false;
1517
1518 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1519 require_quiet(user_key, done); // Fail if we don't get one.
1520
1521 if(!trust.trustedCircle || SOSCircleCountPeers(trust.trustedCircle) == 0 ) {
1522 secnotice("resetToOffering", "Resetting circle to offering because it's empty and we're joining");
1523 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1524 }
1525
1526 if(SOSCircleHasPeerWithID(trust.trustedCircle, (__bridge CFStringRef)(account.peerID), NULL)) {
1527 // We shouldn't be at this point if we're already in circle.
1528 secnotice("circleops", "attempt to join a circle we're in - continuing.");
1529 return true; // let things above us think we're in circle - because we are.
1530 }
1531
1532 if(!SOSCircleVerify(trust.trustedCircle, account.accountKey, NULL)) {
1533 secnotice("resetToOffering", "Resetting circle to offering since we are new and it doesn't verify with current userKey");
1534 return [account.trust resetCircleToOffering:aTxn userKey:user_key err:error];
1535 }
1536
1537 if (trust.fullPeerInfo != NULL) {
1538 SOSPeerInfoRef myPeer = trust.peerInfo;
1539 success = SOSCircleHasPeer(trust.trustedCircle, myPeer, NULL);
1540 require_quiet(!success, done);
1541
1542 SOSCircleRemoveRejectedPeer(trust.trustedCircle, myPeer, NULL); // If we were rejected we should remove it now.
1543
1544 if (!SOSCircleHasApplicant(trust.trustedCircle, myPeer, NULL)) {
1545 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle));
1546
1547 trust.fullPeerInfo = NULL;
1548 }
1549 }
1550
1551 success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, parentEvent, error);
1552
1553 require_quiet(success, done);
1554
1555 trust.departureCode = kSOSNeverLeftCircle;
1556
1557 done:
1558 return success;
1559 }
1560
1561 bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1562 secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)");
1563 return SOSAccountJoinCircles_internal(aTxn, false, parentEvent, error);
1564 }
1565
1566 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, NSData* parentEvent, CFErrorRef* error) {
1567 secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1568 return SOSAccountJoinCircles_internal(aTxn, true, parentEvent, error);
1569 }
1570
1571 bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, NSData* parentEvent, CFErrorRef* error)
1572 {
1573
1574 NSError* localError = nil;
1575 SFSignInAnalytics* parent = nil;
1576
1577 if(parentEvent) {
1578 parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
1579 }
1580
1581 bool result = false;
1582 CFMutableSetRef peersToRemove = NULL;
1583 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1584 if(!user_key){
1585 secnotice("circleOps", "Can't remove without userKey");
1586 return result;
1587 }
1588 SOSFullPeerInfoRef me_full = account.fullPeerInfo;
1589 SOSPeerInfoRef me = account.peerInfo;
1590 if(!(me_full && me))
1591 {
1592 secnotice("circleOps", "Can't remove without being active peer");
1593 SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer"));
1594 return result;
1595 }
1596
1597 result = true; // beyond this point failures would be rolled up in AccountModifyCircle.
1598
1599 peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
1600 if(!peersToRemove)
1601 {
1602 CFReleaseNull(peersToRemove);
1603 secnotice("circleOps", "No peerSet to remove");
1604 return result;
1605 }
1606
1607 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1608 bool leaveCircle = CFSetContainsValue(peersToRemove, me);
1609 CFSetRemoveValue(peersToRemove, me);
1610
1611 result &= [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1612 bool success = false;
1613
1614 if(CFSetGetCount(peersToRemove) != 0) {
1615 require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done);
1616
1617 SFSignInAnalytics *generationSignatureUpdateEvent = nil;
1618 if(parent) {
1619 generationSignatureUpdateEvent = [parent newSubTaskForEvent:@"generationSignatureUpdateEvent"];
1620 }
1621
1622 success = SOSAccountGenerationSignatureUpdate(account, error);
1623 if(error && *error){
1624 NSError* signatureUpdateError = (__bridge NSError*)*error;
1625 if(generationSignatureUpdateEvent) {
1626 [generationSignatureUpdateEvent logUnrecoverableError:signatureUpdateError];
1627 }
1628 }
1629 if(generationSignatureUpdateEvent) {
1630 [generationSignatureUpdateEvent stopWithAttributes:nil];
1631 }
1632 } else success = true;
1633
1634 if (success && leaveCircle) {
1635 secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)");
1636 success = sosAccountLeaveCircle(account, circle, parentEvent, error);
1637 }
1638
1639 done:
1640 return success;
1641
1642 }];
1643
1644 if(result) {
1645 CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) {
1646 secnotice("circleOps", "Removed Peers from circle %@", description);
1647 });
1648 }
1649
1650 CFReleaseNull(peersToRemove);
1651 return result;
1652 }
1653
1654 bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) {
1655 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1656 dispatch_group_t group = dispatch_group_create();
1657 SOSAccountTrustClassic *trust = account.trust;
1658 __block bool result = false;
1659 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
1660 // Add a task to the group
1661 dispatch_group_async(group, queue, ^{
1662 [trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1663 secnotice("circleOps", "Leaving circle by client request (Bail)");
1664 return sosAccountLeaveCircle(account, circle, nil, error);
1665 }];
1666 });
1667 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
1668 dispatch_group_wait(group, milestone);
1669
1670 trust.departureCode = kSOSWithdrewMembership;
1671
1672 return result;
1673 }
1674
1675
1676 //
1677 // MARK: Application
1678 //
1679
1680 static void for_each_applicant_in_each_circle(SOSAccount* account, CFArrayRef peer_infos,
1681 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
1682 SOSAccountTrustClassic *trust = account.trust;
1683
1684 SOSPeerInfoRef me = trust.peerInfo;
1685 CFErrorRef peer_error = NULL;
1686 if (trust.trustedCircle && me &&
1687 SOSCircleHasPeer(trust.trustedCircle, me, &peer_error)) {
1688 [account.trust modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1689 __block bool modified = false;
1690 CFArrayForEach(peer_infos, ^(const void *value) {
1691 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
1692 if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
1693 if (action(circle, trust.fullPeerInfo, peer)) {
1694 modified = true;
1695 }
1696 }
1697 });
1698 return modified;
1699 }];
1700 }
1701 if (peer_error)
1702 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
1703 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
1704 }
1705
1706 bool SOSAccountAcceptApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1707 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
1708 if (!user_key)
1709 return false;
1710
1711 __block int64_t acceptedPeers = 0;
1712
1713 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1714 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
1715 if (accepted)
1716 acceptedPeers++;
1717 return accepted;
1718 });
1719
1720 if (acceptedPeers == CFArrayGetCount(applicants))
1721 return true;
1722 return false;
1723 }
1724
1725 bool SOSAccountRejectApplicants(SOSAccount* account, CFArrayRef applicants, CFErrorRef* error) {
1726 __block bool success = true;
1727 __block int64_t num_peers = 0;
1728
1729 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
1730 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
1731 if (!rejected)
1732 success = false;
1733 else
1734 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
1735 return rejected;
1736 });
1737
1738 return success;
1739 }
1740
1741 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccount* account, CFErrorRef* error) {
1742 SOSAccountTrustClassic *trust = account.trust;
1743 return trust.departureCode;
1744 }
1745
1746 void SOSAccountSetLastDepartureReason(SOSAccount* account, enum DepartureReason reason) {
1747 SOSAccountTrustClassic *trust = account.trust;
1748 trust.departureCode = reason;
1749 }
1750
1751
1752 CFArrayRef SOSAccountCopyGeneration(SOSAccount* account, CFErrorRef *error) {
1753 CFArrayRef result = NULL;
1754 CFNumberRef generation = NULL;
1755 SOSAccountTrustClassic *trust = account.trust;
1756
1757 require_quiet(SOSAccountHasPublicKey(account, error), fail);
1758 require_action_quiet(trust.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
1759
1760 generation = (CFNumberRef)SOSCircleGetGeneration(trust.trustedCircle);
1761 result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
1762
1763 fail:
1764 return result;
1765 }
1766
1767 bool SOSValidateUserPublic(SOSAccount* account, CFErrorRef *error) {
1768 if (!SOSAccountHasPublicKey(account, error))
1769 return NULL;
1770
1771 return account.accountKeyIsTrusted;
1772 }
1773
1774 bool SOSAccountEnsurePeerRegistration(SOSAccount* account, CFErrorRef *error) {
1775 // TODO: this result is never set or used
1776 bool result = true;
1777 SOSAccountTrustClassic *trust = account.trust;
1778
1779 secnotice("updates", "Ensuring peer registration.");
1780
1781 if(!trust) {
1782 secnotice("updates", "Failed to get trust object in Ensuring peer registration.");
1783 return result;
1784 }
1785
1786 if([account getCircleStatus: NULL] != kSOSCCInCircle) {
1787 return result;
1788 }
1789
1790 // If we are not in the circle, there is no point in setting up peers
1791 if(!SOSAccountIsMyPeerActive(account, NULL)) {
1792 return result;
1793 }
1794
1795 // This code only uses the SOSFullPeerInfoRef for two things:
1796 // - Finding out if this device is in the trusted circle
1797 // - Using the peerID for this device to see if the current peer is "me"
1798 // - It is used indirectly by passing trust.fullPeerInfo to SOSEngineInitializePeerCoder
1799
1800 CFStringRef my_id = SOSPeerInfoGetPeerID(trust.peerInfo);
1801
1802 SOSCircleForEachValidSyncingPeer(trust.trustedCircle, account.accountKey, ^(SOSPeerInfoRef peer) {
1803 if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
1804 CFErrorRef localError = NULL;
1805
1806 SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError);
1807 if (localError)
1808 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError);
1809 CFReleaseSafe(localError);
1810 }
1811 });
1812
1813 return result;
1814 }
1815
1816 //
1817 // Value manipulation
1818 //
1819
1820 CFTypeRef SOSAccountGetValue(SOSAccount* account, CFStringRef key, CFErrorRef *error) {
1821 SOSAccountTrustClassic *trust = account.trust;
1822 if (!trust.expansion) {
1823 return NULL;
1824 }
1825 return (__bridge CFTypeRef)([trust.expansion objectForKey:(__bridge NSString* _Nonnull)(key)]);
1826 }
1827
1828
1829
1830 bool SOSAccountAddEscrowToPeerInfo(SOSAccount* account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
1831 bool success = false;
1832
1833 CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
1834 success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
1835
1836 return success;
1837 }
1838
1839 void SOSAccountRecordRetiredPeersInCircle(SOSAccount* account) {
1840 if (![account isInCircle:NULL]) {
1841 return;
1842 }
1843 __block bool updateRings = false;
1844 SOSAccountTrustClassic *trust = account.trust;
1845 [trust modifyCircle:account.circle_transport err:NULL action:^bool (SOSCircleRef circle) {
1846 __block bool updated = false;
1847 CFSetForEach((__bridge CFMutableSetRef)trust.retirees, ^(CFTypeRef element){
1848 SOSPeerInfoRef retiree = asSOSPeerInfo(element);
1849
1850 if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
1851 updated = true;
1852 secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
1853 CFErrorRef cleanupError = NULL;
1854 if (![account.trust cleanupAfterPeer:account.kvs_message_transport circleTransport:account.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError])
1855 secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
1856 CFReleaseSafe(cleanupError);
1857 updateRings = true;
1858 }
1859 });
1860 return updated;
1861 }];
1862 if(updateRings) {
1863 SOSAccountProcessBackupRings(account, NULL);
1864 }
1865 }
1866
1867 static const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
1868
1869 static CFDictionaryRef SOSAccountGetObjectsFromCloud(dispatch_queue_t processQueue, CFErrorRef *error)
1870 {
1871 __block CFTypeRef object = NULL;
1872
1873 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1874 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1875
1876 CloudKeychainReplyBlock replyBlock =
1877 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
1878 {
1879 object = returnedValues;
1880 if (object)
1881 CFRetain(object);
1882 if (error)
1883 {
1884 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
1885 }
1886 dispatch_semaphore_signal(waitSemaphore);
1887 };
1888
1889 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
1890
1891 dispatch_semaphore_wait(waitSemaphore, finishTime);
1892 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
1893 {
1894 CFRelease(object);
1895 object = NULL;
1896 }
1897 return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors
1898 }
1899
1900
1901 static void SOSAccountRemoveKVSKeys(SOSAccount* account, NSArray* keysToRemove, dispatch_queue_t processQueue)
1902 {
1903 CFStringRef uuid = SOSAccountCopyUUID(account);
1904 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1905 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1906
1907 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1908 if (error){
1909 secerror("SOSCloudKeychainRemoveKeys returned error: %@", error);
1910 }
1911 dispatch_semaphore_signal(waitSemaphore);
1912 };
1913
1914 SOSCloudKeychainRemoveKeys((__bridge CFArrayRef)(keysToRemove), uuid, processQueue, replyBlock);
1915 dispatch_semaphore_wait(waitSemaphore, finishTime);
1916 CFReleaseNull(uuid);
1917 }
1918
1919 static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account)
1920 {
1921 NSDate *now = [NSDate date];
1922 [account.settings setObject:now forKey:SOSAccountLastKVSCleanup];
1923
1924 NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary];
1925
1926 CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("["));
1927
1928 CFAbsoluteTime currentTimeAndDate = CFAbsoluteTimeGetCurrent();
1929
1930 withStringOfAbsoluteTime(currentTimeAndDate, ^(CFStringRef decription) {
1931 CFStringAppend(timeDescription, decription);
1932 });
1933 CFStringAppend(timeDescription, CFSTR("]"));
1934
1935 [writeTimestamp setObject:(__bridge NSString*)(timeDescription) forKey:(__bridge NSString*)kSOSKVSLastCleanupTimestampKey];
1936 CFReleaseNull(timeDescription);
1937
1938 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
1939 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
1940 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1941
1942 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error){
1943 if (error){
1944 secerror("SOSCloudKeychainPutObjectsInCloud returned error: %@", error);
1945 }
1946 dispatch_semaphore_signal(waitSemaphore);
1947 };
1948
1949 SOSCloudKeychainPutObjectsInCloud((__bridge CFDictionaryRef)(writeTimestamp), processQueue, replyBlock);
1950 dispatch_semaphore_wait(waitSemaphore, finishTime);
1951 }
1952
1953 // set the cleanup frequency to 3 days.
1954 #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3
1955
1956 //Get all the key/values in KVS and remove old entries
1957 bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error)
1958 {
1959 // This should only happen on some number of days
1960 NSDate *lastKVSCleanup = [account.settings objectForKey:SOSAccountLastKVSCleanup];
1961 NSDate *now = [NSDate date];
1962 NSTimeInterval timeSinceCleanup = [now timeIntervalSinceDate:lastKVSCleanup];
1963
1964 if(timeSinceCleanup < KVS_CLEANUP_FREQUENCY_LIMIT) {
1965 return true;
1966 }
1967
1968 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1969
1970 NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error);
1971 NSMutableArray *peerIDs = [NSMutableArray array];
1972 NSMutableArray *keysToRemove = [NSMutableArray array];
1973
1974 CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error);
1975 CFArrayForEach(peers, ^(const void *value) {
1976 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
1977 NSString* peerID = (__bridge NSString*) SOSPeerInfoGetPeerID(peer);
1978
1979 //any peerid that is not ours gets added
1980 if(![[account.trust peerID] isEqualToString:peerID])
1981 [peerIDs addObject:peerID];
1982 });
1983 CFReleaseNull(peers);
1984
1985 [keysAndValues enumerateKeysAndObjectsUsingBlock:^(NSString * KVSKey, NSNumber * KVSValue, BOOL *stop) {
1986 __block bool keyMatchesPeerID = false;
1987
1988 //checks for full peer ids
1989 [peerIDs enumerateObjectsUsingBlock:^(id _Nonnull PeerID, NSUInteger idx, BOOL * _Nonnull stop) {
1990 //if key contains peerid of one active peer
1991 if([KVSKey containsString:PeerID]){
1992 secnotice("key-cleanup","key: %@", KVSKey);
1993 keyMatchesPeerID = true;
1994 }
1995 }];
1996 if((([KVSKey hasPrefix:@"ak"] || [KVSKey hasPrefix:@"-ak"]) && !keyMatchesPeerID)
1997 || [KVSKey hasPrefix:@"po"])
1998 [keysToRemove addObject:KVSKey];
1999 }];
2000
2001 secnotice("key-cleanup", "message keys that we should remove! %@", keysToRemove);
2002 secnotice("key-cleanup", "total keys: %lu, cleaning up %lu", (unsigned long)[keysAndValues count], (unsigned long)[keysToRemove count]);
2003
2004 SOSAccountRemoveKVSKeys(account, keysToRemove, processQueue);
2005
2006 //add last cleanup timestamp
2007 SOSAccountWriteLastCleanupTimestampToKVS(account);
2008 return true;
2009
2010 }
2011
2012 SOSPeerInfoRef SOSAccountCopyApplication(SOSAccount* account, CFErrorRef* error) {
2013 SOSPeerInfoRef applicant = NULL;
2014 SOSAccountTrustClassic *trust = account.trust;
2015 SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
2016 if(!userKey) return false;
2017 if(![trust ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
2018 return applicant;
2019 if(!SOSFullPeerInfoPromoteToApplication(trust.fullPeerInfo, userKey, error))
2020 return applicant;
2021 applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, trust.peerInfo, error);
2022
2023 return applicant;
2024 }
2025
2026
2027 static void
2028 AddStrippedResults(NSMutableArray *results, NSArray *keychainItems, NSMutableSet *seenUUID, bool authoriative)
2029 {
2030 [keychainItems enumerateObjectsUsingBlock:^(NSDictionary* keychainItem, NSUInteger idx, BOOL * _Nonnull stop) {
2031 NSString* parentUUID = keychainItem[(id)kSecAttrPath];
2032 NSString* viewUUID = keychainItem[(id)kSecAttrAccount];
2033 NSString *viewName = [keychainItem objectForKey:(id)kSecAttrServer];
2034
2035 if (parentUUID == NULL || viewUUID == NULL || viewName == NULL)
2036 return;
2037
2038 if([parentUUID isEqualToString:viewUUID] || authoriative){
2039
2040 /* check if we already have this entry */
2041 if ([seenUUID containsObject:viewUUID])
2042 return;
2043
2044 NSData* v_data = [keychainItem objectForKey:(id)kSecValueData];
2045 NSData *key = [[NSData alloc] initWithBase64EncodedData:v_data options:0];
2046
2047 if (key == NULL)
2048 return;
2049
2050 secnotice("piggy", "fetched TLK %@ with name %@", viewName, viewUUID);
2051
2052 NSMutableDictionary* strippedDown = [@{
2053 (id)kSecValueData : key,
2054 (id)kSecAttrServer : viewName,
2055 (id)kSecAttrAccount : viewUUID
2056 } mutableCopy];
2057 if (authoriative)
2058 strippedDown[@"auth"] = @YES;
2059
2060 [results addObject:strippedDown];
2061 [seenUUID addObject:viewUUID];
2062 }
2063 }];
2064 }
2065
2066 static void
2067 AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID)
2068 {
2069 #if OCTAGON
2070 CKKSViewManager* manager = [CKKSViewManager manager];
2071
2072 NSDictionary<NSString *,NSString *> *items = [manager activeTLKs];
2073
2074 for (NSString *view in items) {
2075 NSString *uuid = items[view];
2076 NSDictionary *query = @{
2077 (id)kSecClass : (id)kSecClassInternetPassword,
2078 (id)kSecUseDataProtectionKeychain : @YES,
2079 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2080 (id)kSecAttrAccount : uuid,
2081 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2082 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2083 (id)kSecReturnAttributes: @YES,
2084 (id)kSecReturnData: @YES,
2085 };
2086 CFTypeRef result = NULL;
2087 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2088 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, true);
2089 }
2090 CFReleaseNull(result);
2091 }
2092 #endif
2093 }
2094
2095
2096 NSArray<NSDictionary *>*
2097 SOSAccountGetAllTLKs(void)
2098 {
2099 CFTypeRef result = NULL;
2100 NSMutableArray<NSDictionary *>* results = [NSMutableArray array];
2101 NSMutableSet *seenUUID = [NSMutableSet set];
2102
2103 // first use the TLK from the view manager
2104 AddViewManagerResults(results, seenUUID);
2105
2106 //try to grab tlk-piggy items
2107 NSDictionary* query = @{
2108 (id)kSecClass : (id)kSecClassInternetPassword,
2109 (id)kSecUseDataProtectionKeychain : @YES,
2110 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2111 (id)kSecAttrDescription: @"tlk",
2112 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
2113 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2114 (id)kSecReturnAttributes: @YES,
2115 (id)kSecReturnData: @YES,
2116 };
2117
2118 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2119 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2120 }
2121 CFReleaseNull(result);
2122
2123 //try to grab tlk-piggy items
2124 query = @{
2125 (id)kSecClass : (id)kSecClassInternetPassword,
2126 (id)kSecUseDataProtectionKeychain : @YES,
2127 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
2128 (id)kSecAttrDescription: @"tlk-piggy",
2129 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
2130 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
2131 (id)kSecReturnAttributes: @YES,
2132 (id)kSecReturnData: @YES,
2133 };
2134
2135 if (SecItemCopyMatching((__bridge CFDictionaryRef)query, &result) == 0) {
2136 AddStrippedResults(results, (__bridge NSArray*)result, seenUUID, false);
2137 }
2138 CFReleaseNull(result);
2139
2140 secnotice("piggy", "Found %d TLKs", (int)[results count]);
2141
2142 return results;
2143 }
2144
2145 static uint8_t* encode_tlk(kTLKTypes type, NSString *name, NSData *keychainData, NSData* uuid,
2146 const uint8_t *der, uint8_t *der_end)
2147 {
2148 if (type != kTLKUnknown) {
2149 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2150 piggy_encode_data(keychainData, der,
2151 piggy_encode_data(uuid, der,
2152 ccder_encode_uint64((uint64_t)type, der, der_end))));
2153 } else {
2154 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2155 piggy_encode_data(keychainData, der,
2156 piggy_encode_data(uuid, der,
2157 der_encode_string((__bridge CFStringRef)name, NULL, der, der_end))));
2158 }
2159 }
2160
2161 static uint8_t* piggy_encode_data(NSData* data,
2162 const uint8_t *der, uint8_t *der_end)
2163 {
2164 return ccder_encode_tl(CCDER_OCTET_STRING, data.length, der,
2165 ccder_encode_body(data.length, data.bytes, der, der_end));
2166
2167 }
2168
2169 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2170 static kTLKTypes
2171 name2type(NSString *view)
2172 {
2173 if ([view isEqualToString:@"Manatee"])
2174 return kTLKManatee;
2175 else if ([view isEqualToString:@"Engram"])
2176 return kTLKEngram;
2177 else if ([view isEqualToString:@"AutoUnlock"])
2178 return kTLKAutoUnlock;
2179 if ([view isEqualToString:@"Health"])
2180 return kTLKHealth;
2181 return kTLKUnknown;
2182 }
2183
2184 // you can not add more items here w/o also adding a version, older clients wont understand newer numbers
2185 static unsigned
2186 rank_type(NSString *view)
2187 {
2188 if ([view isEqualToString:@"Manatee"])
2189 return 5;
2190 else if ([view isEqualToString:@"Engram"])
2191 return 4;
2192 else if ([view isEqualToString:@"AutoUnlock"])
2193 return 3;
2194 if ([view isEqualToString:@"Health"])
2195 return 2;
2196 return 0;
2197 }
2198
2199 static NSData *
2200 parse_uuid(NSString *uuidString)
2201 {
2202 NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
2203 uuid_t uuidblob;
2204 [uuid getUUIDBytes:uuidblob];
2205 return [NSData dataWithBytes:uuidblob length:sizeof(uuid_t)];
2206 }
2207 static size_t
2208 piggy_sizeof_data(NSData* data)
2209 {
2210 return ccder_sizeof(CCDER_OCTET_STRING, [data length]);
2211 }
2212
2213 static size_t sizeof_keychainitem(kTLKTypes type, NSString *name, NSData* keychainData, NSData* uuid) {
2214 if (type != kTLKUnknown) {
2215 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2216 piggy_sizeof_data(keychainData) +
2217 piggy_sizeof_data(uuid) +
2218 ccder_sizeof_uint64(type));
2219 } else {
2220 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2221 piggy_sizeof_data(keychainData) +
2222 piggy_sizeof_data(uuid) +
2223 der_sizeof_string((__bridge CFStringRef)name, NULL));
2224 }
2225 }
2226
2227 NSArray<NSDictionary*>*
2228 SOSAccountSortTLKS(NSArray<NSDictionary*>* tlks)
2229 {
2230 NSMutableArray<NSDictionary*>* sortedTLKs = [tlks mutableCopy];
2231
2232 [sortedTLKs sortUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2) {
2233 unsigned rank1 = rank_type(obj1[(__bridge id)kSecAttrServer]);
2234 if (obj1[@"auth"] != NULL)
2235 rank1 += 1000;
2236 unsigned rank2 = rank_type(obj2[(__bridge id)kSecAttrServer]);
2237 if (obj2[@"auth"] != NULL)
2238 rank2 += 1000;
2239
2240 /*
2241 * Sort by rank (higher better), but prefer TLK that are authoriative (ie used by CKKSViewManager),
2242 * since we are sorting backward, the Ascending/Descending looks wrong below.
2243 */
2244 if (rank1 > rank2) {
2245 return NSOrderedAscending;
2246 } else if (rank1 < rank2) {
2247 return NSOrderedDescending;
2248 }
2249 return NSOrderedSame;
2250 }];
2251
2252 return sortedTLKs;
2253 }
2254
2255 static NSArray<NSData *> *
2256 build_tlks(NSArray<NSDictionary*>* tlks)
2257 {
2258 NSMutableArray *array = [NSMutableArray array];
2259 NSArray<NSDictionary*>* sortedTLKs = SOSAccountSortTLKS(tlks);
2260
2261 for (NSDictionary *item in sortedTLKs) {
2262 NSData* keychainData = item[(__bridge id)kSecValueData];
2263 NSString* name = item[(__bridge id)kSecAttrServer];
2264 NSString *uuidString = item[(__bridge id)kSecAttrAccount];
2265 NSData* uuid = parse_uuid(uuidString);
2266
2267 NSMutableData *tlk = [NSMutableData dataWithLength:sizeof_keychainitem(name2type(name), name, keychainData, uuid)];
2268
2269 unsigned char *der = [tlk mutableBytes];
2270 unsigned char *der_end = der + [tlk length];
2271
2272 if (encode_tlk(name2type(name), name, keychainData, uuid, der, der_end) == NULL)
2273 return NULL;
2274
2275 secnotice("piggy", "preparing TLK in order: %@: %@", name, uuidString);
2276
2277 [array addObject:tlk];
2278 }
2279 return array;
2280 }
2281
2282 static NSArray<NSData *> *
2283 build_identities(NSArray<NSData *>* identities)
2284 {
2285 NSMutableArray *array = [NSMutableArray array];
2286 for (NSData *item in identities) {
2287 NSMutableData *ident = [NSMutableData dataWithLength:ccder_sizeof_raw_octet_string([item length])];
2288
2289 unsigned char *der = [ident mutableBytes];
2290 unsigned char *der_end = der + [ident length];
2291
2292 ccder_encode_raw_octet_string([item length], [item bytes], der, der_end);
2293 [array addObject:ident];
2294 }
2295 return array;
2296 }
2297
2298
2299
2300 static unsigned char *
2301 encode_data_array(NSArray<NSData*>* data, unsigned char *der, unsigned char *der_end)
2302 {
2303 unsigned char *body_end = der_end;
2304 for (NSData *datum in data) {
2305 der_end = ccder_encode_body([datum length], [datum bytes], der, der_end);
2306 if (der_end == NULL)
2307 return NULL;
2308 }
2309 return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, body_end, der, der_end);
2310 }
2311
2312 static size_t sizeof_piggy(size_t identities_size, size_t tlk_size)
2313 {
2314 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE,
2315 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, identities_size) +
2316 ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, tlk_size));
2317 }
2318
2319 static NSData *encode_piggy(size_t IdentitiesBudget,
2320 size_t TLKBudget,
2321 NSArray<NSData*>* identities,
2322 NSArray<NSDictionary*>* tlks)
2323 {
2324 NSArray<NSData *> *encodedTLKs = build_tlks(tlks);
2325 NSArray<NSData *> *encodedIdentities = build_identities(identities);
2326 NSMutableArray<NSData *> *budgetArray = [NSMutableArray array];
2327 NSMutableArray<NSData *> *identitiesArray = [NSMutableArray array];
2328 size_t payloadSize = 0, identitiesSize = 0;
2329 NSMutableData *result = NULL;
2330
2331 for (NSData *tlk in encodedTLKs) {
2332 if (TLKBudget - payloadSize < [tlk length])
2333 break;
2334 [budgetArray addObject:tlk];
2335 payloadSize += tlk.length;
2336 }
2337 secnotice("piggy", "sending %d tlks", (int)budgetArray.count);
2338
2339 for (NSData *ident in encodedIdentities) {
2340 if (IdentitiesBudget - identitiesSize < [ident length])
2341 break;
2342 [identitiesArray addObject:ident];
2343 identitiesSize += ident.length;
2344 }
2345 secnotice("piggy", "sending %d identities", (int)identitiesArray.count);
2346
2347
2348 size_t piggySize = sizeof_piggy(identitiesSize, payloadSize);
2349
2350 result = [NSMutableData dataWithLength:piggySize];
2351
2352 unsigned char *der = [result mutableBytes];
2353 unsigned char *der_end = der + [result length];
2354
2355 if (ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
2356 encode_data_array(identitiesArray, der,
2357 encode_data_array(budgetArray, der, der_end))) != [result mutableBytes])
2358 return NULL;
2359
2360 return result;
2361 }
2362
2363 static const size_t SOSCCIdentitiesBudget = 120;
2364 static const size_t SOSCCTLKBudget = 500;
2365
2366 NSData *
2367 SOSPiggyCreateInitialSyncData(NSArray<NSData*>* identities, NSArray<NSDictionary *>* tlks)
2368 {
2369 return encode_piggy(SOSCCIdentitiesBudget, SOSCCTLKBudget, identities, tlks);
2370 }
2371
2372 CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account)
2373 {
2374 CFMutableArrayRef identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2375
2376 SOSCircleForEachActivePeer(account.trust.trustedCircle, ^(SOSPeerInfoRef peer) {
2377 if(SOSPeerInfoIsCloudIdentity(peer)) {
2378 CFArrayAppendValue(identities, peer);
2379 }
2380 });
2381 return identities;
2382 }
2383
2384 CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) {
2385
2386 NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array];
2387 NSArray<NSDictionary *>* tlks = nil;
2388
2389 if (flags & kSOSInitialSyncFlagiCloudIdentity) {
2390 CFMutableArrayRef identities = SOSAccountCopyiCloudIdentities(account);
2391 secnotice("piggy", "identities: %@", identities);
2392
2393 CFIndex i, count = CFArrayGetCount(identities);
2394 for (i = 0; i < count; i++) {
2395 SOSPeerInfoRef fpi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(identities, i);
2396 NSData *data = CFBridgingRelease(SOSPeerInfoCopyData(fpi, error));
2397 if (data)
2398 [encodedIdenities addObject:data];
2399 }
2400 CFRelease(identities);
2401 }
2402
2403 if (flags & kSOSInitialSyncFlagTLKs) {
2404 tlks = SOSAccountGetAllTLKs();
2405 }
2406
2407 return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks));
2408 }
2409
2410 static void pbNotice(CFStringRef operation, SOSAccount* account, SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, PiggyBackProtocolVersion version) {
2411 CFStringRef pkeyID = SOSCopyIDOfKey(pubKey, NULL);
2412 if(pkeyID == NULL) pkeyID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("Unknown"));
2413 CFStringRef sigID = SOSCopyIDOfDataBuffer(signature, NULL);
2414 if(sigID == NULL) sigID = CFStringCreateCopy(kCFAllocatorDefault, CFSTR("No Signature"));
2415 CFStringRef accountName = SOSAccountGetValue(account, kSOSAccountName, NULL);
2416 if(accountName == NULL) {
2417 accountName = CFSTR("Unavailable");
2418 }
2419 CFStringRef circleHash = SOSCircleCopyHashString(account.trust.trustedCircle);
2420
2421 secnotice("circleOps",
2422 "%@: Joining blob for account: %@ for piggyback (V%d) gencount: %@ pubkey: %@ signatureID: %@ starting circle hash: %@",
2423 operation, accountName, version, gencount, pkeyID, sigID, circleHash);
2424 CFReleaseNull(pkeyID);
2425 CFReleaseNull(sigID);
2426 CFReleaseNull(circleHash);
2427 }
2428
2429 CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccount* account, SOSPeerInfoRef applicant, CFErrorRef *error) {
2430 SOSGenCountRef gencount = NULL;
2431 CFDataRef signature = NULL;
2432 SecKeyRef ourKey = NULL;
2433
2434 CFDataRef pbblob = NULL;
2435 SOSCircleRef prunedCircle = NULL;
2436
2437 secnotice("circleOps", "Making circle joining piggyback blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2438
2439 SOSCCStatus circleStat = [account getCircleStatus:error];
2440 if(circleStat != kSOSCCInCircle) {
2441 secnotice("circleOps", "Invalid circle status: %@ to accept piggyback as sponsor (SOSAccountCopyCircleJoiningBlob)", SOSCCGetStatusDescription(circleStat));
2442 return NULL;
2443 }
2444
2445 SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
2446 require_quiet(userKey, errOut);
2447
2448 require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
2449 require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut,
2450 secnotice("circleOps", "Peer application wasn't signed with the correct userKey"));
2451
2452 {
2453 SOSFullPeerInfoRef fpi = account.fullPeerInfo;
2454 ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
2455 require_quiet(ourKey, errOut);
2456 }
2457
2458 SOSCircleRef currentCircle = [account.trust getCircle:error];
2459 require_quiet(currentCircle, errOut);
2460
2461 prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
2462 require_quiet(prunedCircle, errOut);
2463 require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
2464
2465 gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
2466
2467 signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
2468 require_quiet(signature, errOut);
2469 pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1);
2470 pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error);
2471
2472 errOut:
2473 CFReleaseNull(prunedCircle);
2474 CFReleaseNull(gencount);
2475 CFReleaseNull(signature);
2476 CFReleaseNull(ourKey);
2477
2478 if(!pbblob && error != NULL) {
2479 secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error);
2480 }
2481
2482 return pbblob;
2483 }
2484
2485 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccount* account, CFDataRef joiningBlob, PiggyBackProtocolVersion version, CFErrorRef *error) {
2486 bool retval = false;
2487 SecKeyRef userKey = NULL;
2488 SOSAccountTrustClassic *trust = account.trust;
2489 SOSGenCountRef gencount = NULL;
2490 CFDataRef signature = NULL;
2491 SecKeyRef pubKey = NULL;
2492 bool setInitialSyncTimeoutToV0 = false;
2493
2494 secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)");
2495
2496 if (!isData(joiningBlob)) {
2497 secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)");
2498 return false;
2499 }
2500
2501 userKey = SOSAccountGetPrivateCredential(account, error);
2502 if(!userKey) {
2503 secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2504 return retval;
2505 }
2506
2507 if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) {
2508 secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error);
2509 return retval;
2510 }
2511
2512 if(setInitialSyncTimeoutToV0){
2513 secnotice("circleOps", "setting flag in account for piggyback v0");
2514 SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL);
2515 } else {
2516 secnotice("circleOps", "clearing flag in account for piggyback v0");
2517 SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL);
2518 }
2519 SOSAccountInitializeInitialSync(account);
2520
2521 pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version);
2522
2523 retval = [trust modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef copyOfCurrent) {
2524 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
2525 gencount,
2526 pubKey,
2527 signature,
2528 trust.fullPeerInfo, error);;
2529 }];
2530
2531 CFReleaseNull(gencount);
2532 CFReleaseNull(pubKey);
2533 CFReleaseNull(signature);
2534
2535 return retval;
2536 }
2537
2538 static char boolToChars(bool val, char truechar, char falsechar) {
2539 return val? truechar: falsechar;
2540 }
2541
2542 #define ACCOUNTLOGSTATE "accountLogState"
2543 void SOSAccountLogState(SOSAccount* account) {
2544 bool hasPubKey = account.accountKey != NULL;
2545 SOSAccountTrustClassic *trust = account.trust;
2546 bool pubTrusted = account.accountKeyIsTrusted;
2547 bool hasPriv = account.accountPrivateKey != NULL;
2548 SOSCCStatus stat = [account getCircleStatus:NULL];
2549
2550 CFStringRef userPubKeyID = (account.accountKey) ? SOSCopyIDOfKeyWithLength(account.accountKey, 8, NULL):
2551 CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
2552
2553 secnotice(ACCOUNTLOGSTATE, "Start");
2554
2555 secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2556 boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
2557 userPubKeyID,
2558 SOSAccountGetSOSCCStatusString(stat)
2559 );
2560 CFReleaseNull(userPubKeyID);
2561 if(trust.trustedCircle) SOSCircleLogState(ACCOUNTLOGSTATE, trust.trustedCircle, account.accountKey, (__bridge CFStringRef)(account.peerID));
2562 else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
2563 }
2564
2565 void SOSAccountLogViewState(SOSAccount* account) {
2566 bool isInCircle = [account.trust isInCircleOnly:NULL];
2567 require_quiet(isInCircle, imOut);
2568 SOSPeerInfoRef mpi = account.peerInfo;
2569 bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
2570 bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
2571
2572 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
2573 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2574 secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
2575 boolToChars(isInitialComplete, 'I', 'i'),
2576 boolToChars(isBackupComplete, 'B', 'b'),
2577 description);
2578 });
2579 CFReleaseNull(views);
2580 CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
2581 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
2582 secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
2583 });
2584 CFReleaseNull(unsyncedViews);
2585
2586 imOut:
2587 secnotice(ACCOUNTLOGSTATE, "Finish");
2588
2589 return;
2590 }
2591
2592
2593 void SOSAccountSetTestSerialNumber(SOSAccount* account, CFStringRef serial) {
2594 if(!isString(serial)) return;
2595 CFMutableDictionaryRef newv2dict = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
2596 CFDictionarySetValue(newv2dict, sSerialNumberKey, serial);
2597 [account.trust updateV2Dictionary:account v2:newv2dict];
2598 }
2599
2600 void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid)
2601 {
2602 secnotice("otrtimer", "timer fired!");
2603 CFErrorRef error = NULL;
2604 SecADAddValueForScalarKey((__bridge CFStringRef) SecSOSAggdReattemptOTRNegotiation,1);
2605
2606 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2607 SOSEngineWithPeerID(engine, peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2608 if(SOSCoderIsCoderInAwaitingState(coder)){
2609 secnotice("otrtimer", "coder is in awaiting state, restarting coder");
2610 CFErrorRef localError = NULL;
2611 SOSCoderReset(coder);
2612 if(SOSCoderStart(coder, &localError) == kSOSCoderFailure){
2613 secerror("Attempt to recover coder failed to restart: %@", localError);
2614 }
2615 else{
2616 secnotice("otrtimer", "coder restarted!");
2617 SOSEngineSetCodersNeedSaving(engine, true);
2618 SOSPeerSetMustSendMessage(peer, true);
2619 SOSCCRequestSyncWithPeer(SOSPeerGetID(peer));
2620 }
2621 SOSPeerOTRTimerIncreaseOTRNegotiationRetryCount(account, (__bridge NSString*)SOSPeerGetID(peer));
2622 SOSPeerRemoveOTRTimerEntry(peer);
2623 SOSPeerOTRTimerRemoveRTTTimeoutForPeer(account, (__bridge NSString*)SOSPeerGetID(peer));
2624 SOSPeerOTRTimerRemoveLastSentMessageTimestamp(account, (__bridge NSString*)SOSPeerGetID(peer));
2625 }
2626 else{
2627 secnotice("otrtimer", "time fired but out of negotiation! Not restarting coder");
2628 }
2629 });
2630 if(error)
2631 {
2632 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2633 }
2634 CFReleaseNull(error);
2635 }
2636
2637 void SOSAccountTimerFiredSendNextMessage(SOSAccountTransaction* txn, NSString* peerid, NSString* accessGroup)
2638 {
2639 __block SOSAccount* account = txn.account;
2640 CFErrorRef error = NULL;
2641
2642 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(txn.account.factory, SOSCircleGetName(account.trust.trustedCircle), NULL);
2643 SOSEngineWithPeerID(engine, (__bridge CFStringRef)peerid, &error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState) {
2644
2645 NSString *peer_id = (__bridge NSString*)SOSPeerGetID(peer);
2646 PeerRateLimiter *limiter = (__bridge PeerRateLimiter*)SOSPeerGetRateLimiter(peer);
2647 CFErrorRef error = NULL;
2648 NSData* message = [limiter.accessGroupToNextMessageToSend objectForKey:accessGroup];
2649
2650 if(message){
2651 secnotice("ratelimit","SOSPeerRateLimiter timer went off! sending:%@ \n to peer:%@", message, peer_id);
2652 bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error];
2653
2654 if(!sendResult || error){
2655 secnotice("ratelimit", "could not send message: %@", error);
2656 }
2657 }
2658 [limiter.accessGroupRateLimitState setObject:[[NSNumber alloc]initWithLong:RateLimitStateCanSend] forKey:accessGroup];
2659 [limiter.accessGroupToTimer removeObjectForKey:accessGroup];
2660 [limiter.accessGroupToNextMessageToSend removeObjectForKey:accessGroup];
2661 });
2662
2663 if(error)
2664 {
2665 secnotice("otrtimer","error grabbing engine for peer id: %@, error:%@", peerid, error);
2666 }
2667 CFReleaseNull(error);
2668 }
2669
2670 #if OCTAGON
2671
2672 /*
2673 * State machine
2674 */
2675
2676 OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup";
2677
2678 OctagonState* SOSStateReady = (OctagonState*)@"ready";
2679 OctagonState* SOSStateError = (OctagonState*)@"error";
2680 OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup";
2681
2682 static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void) {
2683 static NSDictionary<OctagonState*, NSNumber*>* map = nil;
2684 static dispatch_once_t onceToken;
2685 dispatch_once(&onceToken, ^{
2686 map = @{
2687 SOSStateReady: @0U,
2688 SOSStateError: @1U,
2689 SOSStatePerformBackup: @2U,
2690 };
2691 });
2692 return map;
2693 }
2694
2695 static NSSet<OctagonFlag*>* SOSFlagsSet(void) {
2696 static NSSet<OctagonFlag*>* set = nil;
2697 static dispatch_once_t onceToken;
2698 dispatch_once(&onceToken, ^{
2699 set = [NSSet setWithArray:@[
2700 SOSFlagTriggerBackup
2701 ]];
2702 });
2703 return set;
2704 }
2705
2706
2707
2708 + (NSURL *)urlForSOSAccountSettings {
2709 return (__bridge NSURL *)SecCopyURLForFileInKeychainDirectory(CFSTR("SOSAccountSettings.pb"));
2710 }
2711
2712
2713 - (void)setupStateMachine {
2714 WEAKIFY(self);
2715
2716 self.accountConfiguration = [[CKKSPBFileStorage alloc] initWithStoragePath:[[self class] urlForSOSAccountSettings]
2717 storageClass:[SOSAccountConfiguration class]];
2718
2719 NSAssert(self.stateMachine == nil, @"cant bootstrap more the once");
2720
2721 self.stateMachineQueue = dispatch_queue_create("SOS-statemachine", NULL);
2722
2723 self.stateMachine = [[OctagonStateMachine alloc] initWithName:@"sosaccount"
2724 states:[NSSet setWithArray:[SOSStateMap() allKeys]]
2725 flags:SOSFlagsSet()
2726 initialState:SOSStateReady
2727 queue:self.stateMachineQueue
2728 stateEngine:self
2729 lockStateTracker:[CKKSLockStateTracker globalTracker]];
2730
2731
2732 self.performBackups = [[CKKSNearFutureScheduler alloc] initWithName:@"performBackups"
2733 initialDelay:5*NSEC_PER_SEC
2734 continuingDelay:30*NSEC_PER_SEC
2735 keepProcessAlive:YES
2736 dependencyDescriptionCode:CKKSResultDescriptionNone
2737 block:^{
2738 STRONGIFY(self);
2739 [self addBackupFlag];
2740 }];
2741
2742 SOSAccountConfiguration *conf = self.accountConfiguration.storage;
2743
2744 if (conf.pendingBackupPeers.count) {
2745 [self addBackupFlag];
2746 }
2747
2748 [self.stateMachine startOperation];
2749 }
2750
2751
2752 - (void)addBackupFlag {
2753 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup
2754 conditions:OctagonPendingConditionsDeviceUnlocked];
2755 [self.stateMachine handlePendingFlag:pendingFlag];
2756 }
2757
2758 - (void)triggerBackupForPeers:(NSArray<NSString*>*)backupPeers
2759 {
2760 dispatch_sync(self.stateMachineQueue, ^{
2761 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2762
2763 NSMutableSet *pending = [NSMutableSet set];
2764 if (storage.pendingBackupPeers) {
2765 [pending addObjectsFromArray:storage.pendingBackupPeers];
2766 }
2767 if (backupPeers) {
2768 [pending addObjectsFromArray:backupPeers];
2769 }
2770 storage.pendingBackupPeers = [[pending allObjects] mutableCopy];
2771 [self.accountConfiguration setStorage:storage];
2772 [self.performBackups trigger];
2773 secnotice("sos-sm", "trigger backup for peers: %@", backupPeers);
2774 });
2775 }
2776
2777 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
2778 flags:(nonnull OctagonFlags *)flags
2779 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
2780 {
2781 dispatch_assert_queue(self.stateMachineQueue);
2782
2783 WEAKIFY(self);
2784
2785 secnotice("sos-sm", "currentState: %@ [flags: %@]", currentState, flags);
2786
2787 if ([currentState isEqualToString:SOSStateReady]) {
2788 if([flags _onqueueContains:SOSFlagTriggerBackup]) {
2789 [flags _onqueueRemoveFlag:SOSFlagTriggerBackup];
2790 return [OctagonStateTransitionOperation named:@"perform-backup-flag"
2791 entering:SOSStatePerformBackup];
2792 }
2793 secnotice("sos-sm", "Entering state ready");
2794 return nil;
2795 } else if ([currentState isEqualToString:SOSStateError]) {
2796 secnotice("sos-sm", "Entering state error");
2797 return nil;
2798 } else if ([currentState isEqualToString:SOSStatePerformBackup]) {
2799
2800 return [OctagonStateTransitionOperation named:@"perform-backup-state"
2801 intending:SOSStateReady
2802 errorState:SOSStateError
2803 withBlockTakingSelf:^void(OctagonStateTransitionOperation * _Nonnull op) {
2804 STRONGIFY(self);
2805 SOSAccountConfiguration *storage = self.accountConfiguration.storage;
2806
2807 secnotice("sos-sm", "performing backup for %@", storage.pendingBackupPeers);
2808
2809 if (storage.pendingBackupPeers.count) {
2810 SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)storage.pendingBackupPeers);
2811 [storage clearPendingBackupPeers];
2812 }
2813 [self.accountConfiguration setStorage:storage];
2814
2815 op.nextState = SOSStateReady;
2816 }];
2817 }
2818
2819 return nil;
2820 }
2821 #endif
2822
2823 @end
2824
2825