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