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