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