]>
Commit | Line | Data |
---|---|---|
866f8763 A |
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> | |
866f8763 | 11 | |
b54c578e A |
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 | ||
b54c578e A |
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" | |
ecaf5866 | 43 | #if OCTAGON |
866f8763 | 44 | #import "keychain/ckks/CKKSViewManager.h" |
ecaf5866 | 45 | #import "keychain/ckks/CKKSLockStateTracker.h" |
805875f8 A |
46 | #import "keychain/ckks/CKKSNearFutureScheduler.h" |
47 | #import "keychain/ckks/CKKSPBFileStorage.h" | |
48 | ||
b54c578e | 49 | #import "keychain/ot/OTManager.h" |
805875f8 A |
50 | #import "keychain/ot/ObjCImprovements.h" |
51 | #import "keychain/ot/OctagonStateMachine.h" | |
52 | #import "keychain/ot/OctagonStateMachineHelpers.h" | |
ecaf5866 | 53 | #endif |
866f8763 A |
54 | #include <Security/SecItemInternal.h> |
55 | #include <Security/SecEntitlements.h> | |
7fb2cbd2 | 56 | #include "keychain/securityd/SecItemServer.h" |
866f8763 | 57 | |
805875f8 A |
58 | #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h" |
59 | #include "keychain/SecureObjectSync/generated_source/SOSAccountConfiguration.h" | |
60 | ||
866f8763 A |
61 | |
62 | #import "SecdWatchdog.h" | |
63 | ||
64 | #include <utilities/SecCFWrappers.h> | |
65 | #include <utilities/SecCFError.h> | |
805875f8 | 66 | #include <utilities/SecFileLocations.h> |
866f8763 A |
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 | ||
805875f8 | 77 | |
79b9da22 | 78 | const CFStringRef kSOSAccountName = CFSTR("AccountName"); |
866f8763 A |
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"; | |
79b9da22 A |
94 | NSString* const SOSAccountUserDefaultsSuite = @"com.apple.security.sosaccount"; |
95 | NSString* const SOSAccountLastKVSCleanup = @"lastKVSCleanup"; | |
d64be36e A |
96 | NSString* const kSOSIdentityStatusCompleteIdentity = @"completeIdentity"; |
97 | NSString* const kSOSIdentityStatusKeyOnly = @"keyOnly"; | |
98 | NSString* const kSOSIdentityStatusPeerOnly = @"peerOnly"; | |
99 | ||
866f8763 A |
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 | ||
805875f8 A |
109 | #if OCTAGON |
110 | static NSDictionary<OctagonState*, NSNumber*>* SOSStateMap(void); | |
866f8763 | 111 | |
805875f8 A |
112 | @interface SOSAccount () <OctagonStateMachineEngine> |
113 | @property dispatch_queue_t stateMachineQueue; | |
114 | @property (readwrite) OctagonStateMachine* stateMachine; | |
866f8763 | 115 | |
805875f8 | 116 | @property (readwrite) CKKSPBFileStorage<SOSAccountConfiguration*>* accountConfiguration; |
79b9da22 | 117 | |
805875f8 | 118 | @property CKKSNearFutureScheduler *performBackups; |
d64be36e | 119 | @property CKKSNearFutureScheduler *performRingUpdates; |
805875f8 A |
120 | @end |
121 | #endif | |
79b9da22 | 122 | |
805875f8 | 123 | @implementation SOSAccount |
866f8763 | 124 | |
ecaf5866 A |
125 | - (void)dealloc { |
126 | if(self) { | |
127 | CFReleaseNull(self->_accountKey); | |
128 | CFReleaseNull(self->_accountPrivateKey); | |
129 | CFReleaseNull(self->_previousAccountKey); | |
d64be36e A |
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 | |
ecaf5866 | 138 | } |
866f8763 A |
139 | } |
140 | ||
141 | @synthesize accountKey = _accountKey; | |
142 | ||
143 | - (void) setAccountKey: (SecKeyRef) key { | |
144 | CFRetainAssign(self->_accountKey, key); | |
145 | } | |
146 | ||
d64be36e A |
147 | @synthesize accountPrivateKey = _accountPrivateKey; |
148 | ||
149 | - (void) setAccountPrivateKey: (SecKeyRef) key { | |
150 | CFRetainAssign(self->_accountPrivateKey, key); | |
151 | } | |
152 | ||
866f8763 A |
153 | @synthesize previousAccountKey = _previousAccountKey; |
154 | ||
155 | - (void) setPreviousAccountKey: (SecKeyRef) key { | |
156 | CFRetainAssign(self->_previousAccountKey, key); | |
157 | } | |
158 | ||
d64be36e | 159 | @synthesize peerPublicKey = _peerPublicKey; |
866f8763 | 160 | |
d64be36e A |
161 | - (void) setPeerPublicKey: (SecKeyRef) key { |
162 | CFRetainAssign(self->_peerPublicKey, key); | |
866f8763 A |
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 | ||
866f8763 A |
183 | -(bool) ensureFactoryCircles |
184 | { | |
d64be36e | 185 | if (self.factory == nil){ |
866f8763 A |
186 | return false; |
187 | } | |
188 | ||
d64be36e | 189 | NSString* circle_name = CFBridgingRelease(SOSDataSourceFactoryCopyName(self.factory)); |
866f8763 A |
190 | if (!circle_name){ |
191 | return false; | |
192 | } | |
193 | ||
ecaf5866 | 194 | CFReleaseSafe(SOSAccountEnsureCircle(self, (__bridge CFStringRef) circle_name, NULL)); |
866f8763 A |
195 | |
196 | return SOSAccountInflateTransports(self, (__bridge CFStringRef) circle_name, NULL); | |
197 | } | |
198 | ||
8a50f688 A |
199 | -(void)ensureOctagonPeerKeys |
200 | { | |
ecaf5866 A |
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 | |
8a50f688 A |
207 | } |
208 | ||
866f8763 A |
209 | -(id) initWithGestalt:(CFDictionaryRef)newGestalt factory:(SOSDataSourceFactoryRef)f |
210 | { | |
d64be36e | 211 | if ((self = [super init])) { |
866f8763 A |
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]; | |
866f8763 A |
229 | |
230 | self.key_transport = nil; | |
231 | self.circle_transport = NULL; | |
232 | self.ck_storage = nil; | |
233 | self.kvs_message_transport = nil; | |
866f8763 A |
234 | |
235 | self.circle_rings_retirements_need_attention = false; | |
236 | self.engine_peer_state_needs_repair = false; | |
237 | self.key_interests_need_updating = false; | |
d64be36e | 238 | self.need_backup_peers_created_after_backup_key_set = false; |
866f8763 A |
239 | |
240 | self.backup_key =nil; | |
241 | self.deviceID = nil; | |
242 | ||
243 | self.waitForInitialSync_blocks = [NSMutableDictionary dictionary]; | |
866f8763 | 244 | self.accountKeyIsTrusted = false; |
d64be36e | 245 | self.accountKeyDerivationParameters = NULL; |
866f8763 A |
246 | self.accountKey = NULL; |
247 | self.previousAccountKey = NULL; | |
d64be36e | 248 | self.peerPublicKey = NULL; |
866f8763 A |
249 | |
250 | self.saveBlock = nil; | |
805875f8 A |
251 | |
252 | self.settings = [[NSUserDefaults alloc] initWithSuiteName:SOSAccountUserDefaultsSuite]; | |
253 | ||
d64be36e A |
254 | [self ensureFactoryCircles]; |
255 | SOSAccountEnsureUUID(self); | |
256 | self.accountIsChanging = false; | |
257 | ||
805875f8 A |
258 | #if OCTAGON |
259 | [self setupStateMachine]; | |
260 | #endif | |
866f8763 A |
261 | } |
262 | return self; | |
263 | } | |
264 | ||
d64be36e A |
265 | - (void)startStateMachine |
266 | { | |
267 | #if OCTAGON | |
268 | [self.stateMachine startOperation]; | |
269 | #endif | |
270 | } | |
271 | ||
866f8763 A |
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 */ | |
d64be36e | 311 | SOSCloudKeychainRequestPerfCounters(dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error) |
866f8763 A |
312 | { |
313 | reply((__bridge NSDictionary *)returnedValues); | |
314 | }); | |
315 | } | |
866f8763 A |
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) { | |
d64be36e | 360 | SOSCloudKeychainSynchronizeAndWait(dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0), ^(__unused CFDictionaryRef returnedValues, CFErrorRef sync_error) { |
866f8763 A |
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 | ||
d64be36e | 385 | SOSCloudKeychainFlush(dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) { |
866f8763 A |
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 | } | |
866f8763 A |
450 | - (void)stashAccountCredential:(NSData *)credential complete:(void(^)(bool success, NSError *error))complete |
451 | { | |
866f8763 | 452 | |
d64be36e A |
453 | dispatch_sync(SOSCCCredentialQueue(), ^{ |
454 | CFErrorRef syncerror = NULL; | |
b54c578e | 455 | |
d64be36e A |
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 | } | |
866f8763 | 478 | |
d64be36e A |
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"); | |
866f8763 | 489 | |
d64be36e A |
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 | } | |
866f8763 | 505 | } |
d64be36e | 506 | }); |
866f8763 A |
507 | } |
508 | ||
509 | - (void)myPeerInfo:(void (^)(NSData *, NSError *))complete | |
510 | { | |
511 | __block CFErrorRef localError = NULL; | |
512 | __block NSData *applicationBlob = NULL; | |
b54c578e A |
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, ^{ | |
866f8763 | 529 | [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { |
b54c578e | 530 | circleHash = CFBridgingRelease(SOSCircleCopyHashString(txn.account.trust.trustedCircle)); |
866f8763 A |
531 | }]; |
532 | }); | |
b54c578e | 533 | complete(circleHash, (__bridge NSError *)localError); |
866f8763 | 534 | CFReleaseNull(localError); |
b54c578e A |
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; | |
866f8763 A |
548 | } |
549 | ||
b54c578e A |
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 | ||
d64be36e A |
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 | ||
b54c578e A |
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 | ||
d64be36e A |
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) | |
b54c578e | 762 | |
866f8763 A |
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 | } | |
b54c578e A |
773 | |
774 | SecAKSDoWithUserBagLockAssertionSoftly(^{ | |
866f8763 A |
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 | ||
b54c578e A |
791 | [self performTransaction:^(SOSAccountTransaction * _Nonnull txn) { |
792 | res = SOSAccountJoinWithCircleJoiningBlob(txn.account, (__bridge CFDataRef)blob, version, &localError); | |
793 | }]; | |
866f8763 A |
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; | |
dd5fb164 A |
810 | if (flags & SOSControlInitialSyncFlagBluetoothMigration) |
811 | isflags |= SecServerInitialSyncCredentialFlagBluetoothMigration; | |
812 | ||
866f8763 A |
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 | ||
d64be36e | 827 | - (void)rpcTriggerSync:(NSArray <NSString *> *)peers complete:(void(^)(bool success, NSError *))complete |
866f8763 A |
828 | { |
829 | __block CFErrorRef localError = NULL; | |
830 | __block bool res = false; | |
831 | ||
832 | secnotice("sync", "trigger a forced sync for %@", peers); | |
833 | ||
b54c578e | 834 | SecAKSDoWithUserBagLockAssertion(&localError, ^{ |
866f8763 A |
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 | ||
d64be36e | 852 | - (void)rpcTriggerBackup:(NSArray<NSString *>* _Nullable)backupPeers complete:(void (^)(NSError *error))complete |
805875f8 A |
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 | ||
d64be36e A |
869 | - (void)rpcTriggerRingUpdate:(void (^)(NSError *error))complete |
870 | { | |
871 | #if OCTAGON | |
872 | [self triggerRingUpdate]; | |
873 | #endif | |
874 | complete(NULL); | |
875 | } | |
876 | ||
866f8763 A |
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 | ||
866f8763 A |
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 | ||
d64be36e A |
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 | ||
866f8763 A |
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 | ||
79b9da22 A |
970 | #pragma clang diagnostic push |
971 | #pragma clang diagnostic ignored "-Wunused-value" | |
866f8763 A |
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 | } | |
79b9da22 | 986 | #pragma clang diagnostic pop |
866f8763 A |
987 | |
988 | SOSAccount* SOSAccountCreate(CFAllocatorRef allocator, | |
989 | CFDictionaryRef gestalt, | |
990 | SOSDataSourceFactoryRef factory) { | |
991 | ||
992 | SOSAccount* a = [[SOSAccount alloc] initWithGestalt:gestalt factory:factory]; | |
d64be36e A |
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 | }); | |
866f8763 A |
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 | ||
866f8763 A |
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); | |
866f8763 A |
1120 | SOSUnregisterTransportMessage(a.kvs_message_transport); |
1121 | SOSUnregisterTransportCircle(a.circle_transport); | |
1122 | ||
1123 | a.circle_transport = NULL; | |
79b9da22 | 1124 | a.kvs_message_transport = nil; |
d64be36e A |
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; | |
866f8763 A |
1144 | |
1145 | a.trust = nil; | |
d64be36e | 1146 | // setting a new trust object resets all the rings from this peer's point of view - they're in the SOSAccountTrustClassic dictionary |
866f8763 | 1147 | a.trust = [[SOSAccountTrustClassic alloc]initWithRetirees:[NSMutableSet set] fpi:NULL circle:NULL departureCode:kSOSDepartureReasonError peerExpansion:[NSMutableDictionary dictionary]]; |
d64be36e | 1148 | [a ensureFactoryCircles]; |
866f8763 A |
1149 | |
1150 | // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly | |
1151 | SOSAccountEnsureUUID(a); | |
ecaf5866 | 1152 | secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountSetToNew"); |
866f8763 A |
1153 | a.key_interests_need_updating = true; |
1154 | } | |
1155 | ||
866f8763 A |
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 | { | |
79b9da22 | 1166 | SOSCCStatus circleStatus = [self.trust getCircleStatusOnly:error]; |
ecaf5866 A |
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; | |
866f8763 A |
1175 | } |
1176 | return circleStatus; | |
1177 | } | |
ecaf5866 | 1178 | |
79b9da22 A |
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 | ||
866f8763 A |
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 | } | |
79b9da22 | 1236 | account.notifyBackupOnExit = true; |
866f8763 A |
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. | |
ecaf5866 | 1247 | secnotice("circleOps", "Reset to empty with last retirement"); |
866f8763 A |
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; | |
d64be36e | 1270 | [trust purgeIdentity]; |
866f8763 A |
1271 | } |
1272 | ||
d64be36e | 1273 | bool sosAccountLeaveCircle(SOSAccount* account, SOSCircleRef circle, CFErrorRef* error) { |
79b9da22 A |
1274 | SOSAccountTrustClassic *trust = account.trust; |
1275 | SOSFullPeerInfoRef identity = trust.fullPeerInfo; | |
1276 | NSMutableSet* retirees = trust.retirees; | |
1277 | ||
1278 | NSError* localError = nil; | |
79b9da22 A |
1279 | SOSFullPeerInfoRef fpi = identity; |
1280 | if(!fpi) return false; | |
1281 | ||
1282 | CFErrorRef retiredError = NULL; | |
1283 | ||
1284 | bool retval = false; | |
650c69f3 | 1285 | |
79b9da22 A |
1286 | SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &retiredError); |
1287 | if(retiredError){ | |
79b9da22 A |
1288 | secerror("SOSFullPeerInfoPromoteToRetiredAndCopy error: %@", retiredError); |
1289 | if(error){ | |
1290 | *error = retiredError; | |
1291 | }else{ | |
1292 | CFReleaseNull(retiredError); | |
1293 | } | |
1294 | } | |
79b9da22 A |
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; | |
79b9da22 A |
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 | } | |
79b9da22 A |
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; | |
79b9da22 | 1320 | if(![account.circle_transport postRetirement:SOSCircleGetName(circle) peer:retire_peer err:&postError]){ |
d64be36e | 1321 | |
79b9da22 A |
1322 | secwarning("Couldn't post retirement (%@)", postError); |
1323 | } | |
79b9da22 | 1324 | |
d64be36e | 1325 | |
866f8763 | 1326 | |
866f8763 A |
1327 | if(![account.circle_transport flushChanges:&postError]){ |
1328 | secwarning("Couldn't flush retirement data (%@)", postError); | |
1329 | } | |
d64be36e | 1330 | |
866f8763 A |
1331 | CFReleaseNull(postError); |
1332 | } | |
d64be36e | 1333 | |
866f8763 | 1334 | SOSAccountPurgeIdentity(account); |
d64be36e | 1335 | |
866f8763 A |
1336 | retval = true; |
1337 | ||
866f8763 A |
1338 | CFReleaseNull(retire_peer); |
1339 | return retval; | |
1340 | } | |
1341 | ||
866f8763 A |
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 | ||
d64be36e | 1433 | - (bool)_onQueueEnsureInBackupRings { |
866f8763 A |
1434 | __block bool result = false; |
1435 | __block CFErrorRef error = NULL; | |
1436 | secnotice("backup", "Ensuring in rings"); | |
1437 | ||
d64be36e A |
1438 | dispatch_assert_queue(self.queue); |
1439 | ||
1440 | if(!self.backup_key){ | |
b54c578e | 1441 | return true; |
866f8763 A |
1442 | } |
1443 | ||
d64be36e | 1444 | if(!SOSBSKBIsGoodBackupPublic((__bridge CFDataRef)self.backup_key, &error)){ |
b54c578e | 1445 | secnotice("backupkey", "account backup key isn't valid: %@", error); |
d64be36e | 1446 | self.backup_key = nil; |
b54c578e A |
1447 | CFReleaseNull(error); |
1448 | return false; | |
1449 | } | |
866f8763 | 1450 | |
d64be36e A |
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); | |
866f8763 A |
1455 | }); |
1456 | if (!result) { | |
b54c578e | 1457 | secnotice("backupkey", "Failed to setup backup public key in peerInfo from account: %@", error); |
ecaf5866 | 1458 | CFReleaseNull(error); |
866f8763 A |
1459 | return result; |
1460 | } | |
1461 | } | |
b54c578e A |
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); | |
866f8763 | 1467 | } |
b54c578e | 1468 | CFReleaseNull(localError); |
866f8763 | 1469 | |
b54c578e | 1470 | // Setup backups the new way. |
d64be36e | 1471 | SOSAccountForEachBackupView(self, ^(const void *value) { |
b54c578e | 1472 | CFStringRef viewName = asString(value, NULL); |
d64be36e | 1473 | bool resetRing = SOSAccountValidateBackupRingForView(self, viewName, NULL); |
b54c578e | 1474 | if(resetRing) { |
d64be36e A |
1475 | SOSAccountUpdateBackupRing(self, viewName, NULL, ^SOSRingRef(SOSRingRef existing, CFErrorRef *error) { |
1476 | SOSRingRef newRing = SOSAccountCreateBackupRingForView(self, viewName, error); | |
b54c578e A |
1477 | return newRing; |
1478 | }); | |
866f8763 | 1479 | } |
b54c578e | 1480 | }); |
866f8763 A |
1481 | |
1482 | if (!result) { | |
ecaf5866 | 1483 | secnotice("backupkey", "Failed to setup backup public key: %@", error); |
866f8763 | 1484 | } |
ecaf5866 | 1485 | CFReleaseNull(error); |
866f8763 A |
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 | ||
d64be36e | 1526 | static bool SOSAccountJoinCircle(SOSAccountTransaction* aTxn, SecKeyRef user_key, bool use_cloud_peer, CFErrorRef* error) { |
79b9da22 A |
1527 | SOSAccount* account = aTxn.account; |
1528 | SOSAccountTrustClassic *trust = account.trust; | |
1529 | __block bool result = false; | |
1530 | __block SOSFullPeerInfoRef cloud_full_peer = NULL; | |
866f8763 | 1531 | require_action_quiet(trust.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???"))); |
d64be36e A |
1532 | |
1533 | require_quiet([account.trust ensureFullPeerAvailable: account err:error], fail); | |
650c69f3 | 1534 | |
866f8763 A |
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 { | |
b54c578e | 1541 | SOSAccountInitializeInitialSync(account); |
866f8763 A |
1542 | if (use_cloud_peer) { |
1543 | cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(trust.trustedCircle, NULL); | |
1544 | } | |
d64be36e | 1545 | |
866f8763 A |
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 | }]; | |
d64be36e | 1564 | |
866f8763 A |
1565 | if (use_cloud_peer) { |
1566 | SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent()); | |
1567 | } | |
1568 | } | |
1569 | fail: | |
1570 | CFReleaseNull(cloud_full_peer); | |
1571 | return result; | |
1572 | } | |
1573 | ||
d64be36e | 1574 | static bool SOSAccountJoinCircles_internal(SOSAccountTransaction* aTxn, bool use_cloud_identity, CFErrorRef* error) { |
866f8763 A |
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 | ||
650c69f3 A |
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 | } | |
79b9da22 | 1597 | |
866f8763 A |
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)) { | |
79b9da22 A |
1606 | secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(trust.trustedCircle)); |
1607 | ||
866f8763 A |
1608 | trust.fullPeerInfo = NULL; |
1609 | } | |
1610 | } | |
79b9da22 | 1611 | |
d64be36e | 1612 | success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error); |
866f8763 A |
1613 | |
1614 | require_quiet(success, done); | |
79b9da22 | 1615 | |
866f8763 A |
1616 | trust.departureCode = kSOSNeverLeftCircle; |
1617 | ||
1618 | done: | |
1619 | return success; | |
1620 | } | |
1621 | ||
d64be36e | 1622 | bool SOSAccountJoinCircles(SOSAccountTransaction* aTxn, CFErrorRef* error) { |
650c69f3 | 1623 | secnotice("circleOps", "Normal path circle join (SOSAccountJoinCircles)"); |
d64be36e | 1624 | return SOSAccountJoinCircles_internal(aTxn, false, error); |
866f8763 A |
1625 | } |
1626 | ||
d64be36e | 1627 | bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransaction* aTxn, CFErrorRef* error) { |
79b9da22 | 1628 | secnotice("circleOps", "Joining after restore (SOSAccountJoinCirclesAfterRestore)"); |
d64be36e | 1629 | return SOSAccountJoinCircles_internal(aTxn, true, error); |
866f8763 A |
1630 | } |
1631 | ||
d64be36e | 1632 | bool SOSAccountRemovePeersFromCircle(SOSAccount* account, CFArrayRef peers, CFErrorRef* error) |
79b9da22 | 1633 | { |
866f8763 A |
1634 | bool result = false; |
1635 | CFMutableSetRef peersToRemove = NULL; | |
1636 | SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error); | |
d64be36e | 1637 | if (!user_key) { |
ecaf5866 | 1638 | secnotice("circleOps", "Can't remove without userKey"); |
866f8763 A |
1639 | return result; |
1640 | } | |
d64be36e | 1641 | |
866f8763 A |
1642 | SOSFullPeerInfoRef me_full = account.fullPeerInfo; |
1643 | SOSPeerInfoRef me = account.peerInfo; | |
d64be36e | 1644 | if (!(me_full && me)) { |
ecaf5866 | 1645 | secnotice("circleOps", "Can't remove without being active peer"); |
866f8763 A |
1646 | SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Can't remove without being active peer")); |
1647 | return result; | |
1648 | } | |
79b9da22 | 1649 | |
866f8763 A |
1650 | result = true; // beyond this point failures would be rolled up in AccountModifyCircle. |
1651 | ||
1652 | peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers); | |
d64be36e | 1653 | if (!peersToRemove) { |
866f8763 | 1654 | CFReleaseNull(peersToRemove); |
ecaf5866 | 1655 | secnotice("circleOps", "No peerSet to remove"); |
866f8763 A |
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 | ||
d64be36e | 1666 | if (CFSetGetCount(peersToRemove) != 0) { |
866f8763 A |
1667 | require_quiet(SOSCircleRemovePeers(circle, user_key, me_full, peersToRemove, error), done); |
1668 | success = SOSAccountGenerationSignatureUpdate(account, error); | |
d64be36e A |
1669 | } else { |
1670 | success = true; | |
1671 | } | |
866f8763 A |
1672 | |
1673 | if (success && leaveCircle) { | |
ecaf5866 | 1674 | secnotice("circleOps", "Leaving circle by client request (SOSAccountRemovePeersFromCircle)"); |
d64be36e | 1675 | success = sosAccountLeaveCircle(account, circle, error); |
866f8763 A |
1676 | } |
1677 | ||
1678 | done: | |
1679 | return success; | |
1680 | ||
1681 | }]; | |
79b9da22 | 1682 | |
d64be36e | 1683 | if (result) { |
ecaf5866 A |
1684 | CFStringSetPerformWithDescription(peersToRemove, ^(CFStringRef description) { |
1685 | secnotice("circleOps", "Removed Peers from circle %@", description); | |
1686 | }); | |
1687 | } | |
866f8763 A |
1688 | |
1689 | CFReleaseNull(peersToRemove); | |
1690 | return result; | |
1691 | } | |
1692 | ||
866f8763 | 1693 | bool SOSAccountBail(SOSAccount* account, uint64_t limit_in_seconds, CFErrorRef* error) { |
d64be36e | 1694 | dispatch_queue_t queue = dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0); |
866f8763 A |
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) { | |
ecaf5866 | 1702 | secnotice("circleOps", "Leaving circle by client request (Bail)"); |
d64be36e | 1703 | return sosAccountLeaveCircle(account, circle, error); |
866f8763 A |
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 | ||
866f8763 A |
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 | ||
79b9da22 | 1749 | __block int64_t acceptedPeers = 0; |
866f8763 A |
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); | |
79b9da22 A |
1753 | if (accepted) |
1754 | acceptedPeers++; | |
866f8763 A |
1755 | return accepted; |
1756 | }); | |
1757 | ||
79b9da22 A |
1758 | if (acceptedPeers == CFArrayGetCount(applicants)) |
1759 | return true; | |
1760 | return false; | |
866f8763 A |
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 | ||
866f8763 A |
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 | ||
79b9da22 A |
1819 | if(!trust) { |
1820 | secnotice("updates", "Failed to get trust object in Ensuring peer registration."); | |
866f8763 | 1821 | return result; |
79b9da22 A |
1822 | } |
1823 | ||
1824 | if([account getCircleStatus: NULL] != kSOSCCInCircle) { | |
1825 | return result; | |
1826 | } | |
1827 | ||
866f8763 | 1828 | // If we are not in the circle, there is no point in setting up peers |
79b9da22 | 1829 | if(!SOSAccountIsMyPeerActive(account, NULL)) { |
866f8763 | 1830 | return result; |
79b9da22 | 1831 | } |
866f8763 A |
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; | |
866f8763 | 1843 | |
79b9da22 | 1844 | SOSEngineInitializePeerCoder((SOSEngineRef)[account.kvs_message_transport SOSTransportMessageGetEngine], trust.fullPeerInfo, peer, &localError); |
866f8763 A |
1845 | if (localError) |
1846 | secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, trust.fullPeerInfo, localError); | |
d64be36e | 1847 | CFReleaseNull(localError); |
866f8763 A |
1848 | } |
1849 | }); | |
866f8763 A |
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 | ||
866f8763 A |
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 | ||
d64be36e A |
1875 | - (void)_onQueueRecordRetiredPeersInCircle { |
1876 | ||
1877 | dispatch_assert_queue(self.queue); | |
1878 | ||
1879 | if (![self isInCircle:NULL]) { | |
866f8763 | 1880 | return; |
79b9da22 | 1881 | } |
b54c578e | 1882 | __block bool updateRings = false; |
d64be36e A |
1883 | SOSAccountTrustClassic *trust = self.trust; |
1884 | [trust modifyCircle:self.circle_transport err:NULL action:^bool (SOSCircleRef circle) { | |
866f8763 A |
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; | |
d64be36e | 1893 | if (![self.trust cleanupAfterPeer:self.kvs_message_transport circleTransport:self.circle_transport seconds:RETIREMENT_FINALIZATION_SECONDS circle:circle cleanupPeer:retiree err:&cleanupError]) |
866f8763 A |
1894 | secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError); |
1895 | CFReleaseSafe(cleanupError); | |
b54c578e | 1896 | updateRings = true; |
866f8763 A |
1897 | } |
1898 | }); | |
1899 | return updated; | |
1900 | }]; | |
b54c578e | 1901 | if(updateRings) { |
d64be36e | 1902 | SOSAccountProcessBackupRings(self); |
b54c578e | 1903 | } |
866f8763 A |
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 | { | |
866f8763 A |
1918 | object = returnedValues; |
1919 | if (object) | |
1920 | CFRetain(object); | |
1921 | if (error) | |
1922 | { | |
1923 | secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error); | |
1924 | } | |
866f8763 A |
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 | } | |
ecaf5866 | 1936 | return asDictionary(object, NULL); // don't propogate "NULL is not a dictionary" errors |
866f8763 A |
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); | |
ecaf5866 | 1955 | CFReleaseNull(uuid); |
866f8763 A |
1956 | } |
1957 | ||
1958 | static void SOSAccountWriteLastCleanupTimestampToKVS(SOSAccount* account) | |
1959 | { | |
79b9da22 A |
1960 | NSDate *now = [NSDate date]; |
1961 | [account.settings setObject:now forKey:SOSAccountLastKVSCleanup]; | |
1962 | ||
866f8763 A |
1963 | NSMutableDictionary *writeTimestamp = [NSMutableDictionary dictionary]; |
1964 | ||
1965 | CFMutableStringRef timeDescription = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("[")); | |
79b9da22 | 1966 | |
866f8763 A |
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]; | |
ecaf5866 | 1975 | CFReleaseNull(timeDescription); |
866f8763 A |
1976 | |
1977 | dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); | |
1978 | dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); | |
d64be36e | 1979 | dispatch_queue_t processQueue = dispatch_get_global_queue(SOS_ACCOUNT_PRIORITY, 0); |
866f8763 A |
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 | ||
79b9da22 A |
1992 | // set the cleanup frequency to 3 days. |
1993 | #define KVS_CLEANUP_FREQUENCY_LIMIT 60*60*24*3 | |
1994 | ||
866f8763 A |
1995 | //Get all the key/values in KVS and remove old entries |
1996 | bool SOSAccountCleanupAllKVSKeys(SOSAccount* account, CFErrorRef* error) | |
1997 | { | |
79b9da22 A |
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 | ||
d64be36e | 2007 | dispatch_queue_t processQueue = dispatch_get_global_queue(SOS_TRANSPORT_PRIORITY, 0); |
866f8763 A |
2008 | |
2009 | NSDictionary *keysAndValues = (__bridge_transfer NSDictionary*)SOSAccountGetObjectsFromCloud(processQueue, error); | |
2010 | NSMutableArray *peerIDs = [NSMutableArray array]; | |
2011 | NSMutableArray *keysToRemove = [NSMutableArray array]; | |
ecaf5866 A |
2012 | |
2013 | CFArrayRef peers = SOSAccountCopyActiveValidPeers(account, error); | |
2014 | CFArrayForEach(peers, ^(const void *value) { | |
866f8763 A |
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 | }); | |
ecaf5866 | 2022 | CFReleaseNull(peers); |
866f8763 A |
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 | ||
866f8763 A |
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; | |
d64be36e | 2056 | if(![trust ensureFullPeerAvailable:account err:error]) |
866f8763 A |
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 | ||
d64be36e A |
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 | |
866f8763 A |
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 | |
d64be36e | 2136 | AddViewManagerResults(NSMutableArray *results, NSMutableSet *seenUUID, bool selectedTLKsOnly) |
866f8763 A |
2137 | { |
2138 | #if OCTAGON | |
d64be36e A |
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; | |
866f8763 | 2145 | } |
d64be36e A |
2146 | |
2147 | AddStrippedTLKs(results, tlks, seenUUID, true); | |
866f8763 A |
2148 | #endif |
2149 | } | |
2150 | ||
d64be36e A |
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 | ||
866f8763 | 2162 | |
7fb2cbd2 | 2163 | NSArray<NSDictionary *>* |
866f8763 A |
2164 | SOSAccountGetAllTLKs(void) |
2165 | { | |
2166 | CFTypeRef result = NULL; | |
7fb2cbd2 | 2167 | NSMutableArray<NSDictionary *>* results = [NSMutableArray array]; |
866f8763 A |
2168 | NSMutableSet *seenUUID = [NSMutableSet set]; |
2169 | ||
d64be36e A |
2170 | // first use all TLKs from the view manager |
2171 | AddViewManagerResults(results, seenUUID, false); | |
866f8763 A |
2172 | |
2173 | //try to grab tlk-piggy items | |
2174 | NSDictionary* query = @{ | |
2175 | (id)kSecClass : (id)kSecClassInternetPassword, | |
b54c578e | 2176 | (id)kSecUseDataProtectionKeychain : @YES, |
866f8763 A |
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, | |
b54c578e | 2193 | (id)kSecUseDataProtectionKeychain : @YES, |
866f8763 A |
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 | ||
7fb2cbd2 | 2236 | // you can not add more items here w/o also adding a version, older clients wont understand newer numbers |
866f8763 A |
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 | ||
7fb2cbd2 | 2251 | // you can not add more items here w/o also adding a version, older clients wont understand newer numbers |
866f8763 A |
2252 | static unsigned |
2253 | rank_type(NSString *view) | |
2254 | { | |
2255 | if ([view isEqualToString:@"Manatee"]) | |
79b9da22 | 2256 | return 5; |
866f8763 | 2257 | else if ([view isEqualToString:@"Engram"]) |
79b9da22 | 2258 | return 4; |
866f8763 | 2259 | else if ([view isEqualToString:@"AutoUnlock"]) |
79b9da22 | 2260 | return 3; |
866f8763 | 2261 | if ([view isEqualToString:@"Health"]) |
79b9da22 | 2262 | return 2; |
866f8763 A |
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 | ||
ecaf5866 | 2439 | CF_RETURNS_RETAINED CFMutableArrayRef SOSAccountCopyiCloudIdentities(SOSAccount* account) |
866f8763 A |
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 | ||
7fb2cbd2 | 2451 | CFDataRef SOSAccountCopyInitialSyncData(SOSAccount* account, SOSInitialSyncFlags flags, CFErrorRef *error) { |
866f8763 | 2452 | |
7fb2cbd2 A |
2453 | NSMutableArray<NSData *>* encodedIdenities = [NSMutableArray array]; |
2454 | NSArray<NSDictionary *>* tlks = nil; | |
2455 | ||
d64be36e A |
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); | |
7fb2cbd2 | 2471 | } |
7fb2cbd2 | 2472 | |
d64be36e A |
2473 | |
2474 | if (flags & kSOSInitialSyncFlagTLKs) { | |
2475 | tlks = SOSAccountGetAllTLKs(); | |
2476 | } | |
866f8763 | 2477 | } |
866f8763 A |
2478 | |
2479 | return CFBridgingRetain(SOSPiggyCreateInitialSyncData(encodedIdenities, tlks)); | |
2480 | } | |
2481 | ||
79b9da22 A |
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 | ||
866f8763 A |
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; | |
ecaf5866 | 2507 | SOSCircleRef prunedCircle = NULL; |
866f8763 | 2508 | |
79b9da22 A |
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 | } | |
866f8763 A |
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)); | |
79b9da22 A |
2521 | require_action_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut, |
2522 | secnotice("circleOps", "Peer application wasn't signed with the correct userKey")); | |
866f8763 A |
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 | ||
ecaf5866 | 2533 | prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error); |
866f8763 A |
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); | |
79b9da22 | 2541 | pbNotice(CFSTR("Accepting"), account, gencount, ourKey, signature, kPiggyV1); |
866f8763 A |
2542 | pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, error); |
2543 | ||
2544 | errOut: | |
ecaf5866 | 2545 | CFReleaseNull(prunedCircle); |
866f8763 A |
2546 | CFReleaseNull(gencount); |
2547 | CFReleaseNull(signature); | |
2548 | CFReleaseNull(ourKey); | |
2549 | ||
2550 | if(!pbblob && error != NULL) { | |
79b9da22 | 2551 | secnotice("circleOps", "Failed to make circle joining piggyback blob as sponsor %@", *error); |
866f8763 A |
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 | ||
79b9da22 | 2566 | secnotice("circleOps", "Joining circles through piggyback (SOSAccountCopyCircleJoiningBlob)"); |
866f8763 | 2567 | |
79b9da22 A |
2568 | if (!isData(joiningBlob)) { |
2569 | secnotice("circleOps", "Bad data blob: piggyback (SOSAccountCopyCircleJoiningBlob)"); | |
866f8763 | 2570 | return false; |
79b9da22 | 2571 | } |
866f8763 A |
2572 | |
2573 | userKey = SOSAccountGetPrivateCredential(account, error); | |
79b9da22 A |
2574 | if(!userKey) { |
2575 | secnotice("circleOps", "Failed - no private credential %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error); | |
866f8763 | 2576 | return retval; |
79b9da22 | 2577 | } |
866f8763 | 2578 | |
79b9da22 A |
2579 | if (!SOSPiggyBackBlobCreateFromData(&gencount, &pubKey, &signature, joiningBlob, version, &setInitialSyncTimeoutToV0, error)) { |
2580 | secnotice("circleOps", "Failed - decoding blob %@: piggyback (SOSAccountCopyCircleJoiningBlob)", *error); | |
866f8763 | 2581 | return retval; |
79b9da22 | 2582 | } |
866f8763 A |
2583 | |
2584 | if(setInitialSyncTimeoutToV0){ | |
79b9da22 | 2585 | secnotice("circleOps", "setting flag in account for piggyback v0"); |
866f8763 | 2586 | SOSAccountSetValue(account, kSOSInitialSyncTimeoutV0, kCFBooleanTrue, NULL); |
79b9da22 A |
2587 | } else { |
2588 | secnotice("circleOps", "clearing flag in account for piggyback v0"); | |
866f8763 A |
2589 | SOSAccountClearValue(account, kSOSInitialSyncTimeoutV0, NULL); |
2590 | } | |
b54c578e | 2591 | SOSAccountInitializeInitialSync(account); |
866f8763 | 2592 | |
79b9da22 A |
2593 | pbNotice(CFSTR("Joining"), account, gencount, pubKey, signature, version); |
2594 | ||
866f8763 A |
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);; | |
866f8763 A |
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 | ||
d64be36e | 2627 | secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@] [UID: %d EUID: %d]", |
866f8763 A |
2628 | boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'), |
2629 | userPubKeyID, | |
d64be36e | 2630 | SOSAccountGetSOSCCStatusString(stat), getuid(), geteuid() |
866f8763 A |
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) { | |
79b9da22 | 2638 | bool isInCircle = [account.trust isInCircleOnly:NULL]; |
866f8763 A |
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 | ||
ecaf5866 | 2672 | void SOSAccountResetOTRNegotiationCoder(SOSAccount* account, CFStringRef peerid) |
866f8763 A |
2673 | { |
2674 | secnotice("otrtimer", "timer fired!"); | |
2675 | CFErrorRef error = NULL; | |
ecaf5866 | 2676 | |
866f8763 A |
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); | |
79b9da22 | 2723 | bool sendResult = [account.kvs_message_transport SOSTransportMessageSendMessage:account.kvs_message_transport id:(__bridge CFStringRef)peer_id messageToSend:(__bridge CFDataRef)message err:&error]; |
866f8763 A |
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 | ||
805875f8 A |
2741 | #if OCTAGON |
2742 | ||
2743 | /* | |
2744 | * State machine | |
2745 | */ | |
2746 | ||
2747 | OctagonFlag* SOSFlagTriggerBackup = (OctagonFlag*)@"trigger_backup"; | |
d64be36e | 2748 | OctagonFlag* SOSFlagTriggerRingUpdate = (OctagonFlag*)@"trigger_ring_update"; |
805875f8 A |
2749 | |
2750 | OctagonState* SOSStateReady = (OctagonState*)@"ready"; | |
2751 | OctagonState* SOSStateError = (OctagonState*)@"error"; | |
2752 | OctagonState* SOSStatePerformBackup = (OctagonState*)@"perform_backup"; | |
d64be36e | 2753 | OctagonState* SOSStatePerformRingUpdate = (OctagonState*)@"perform_ring_update"; |
805875f8 A |
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, | |
d64be36e | 2763 | SOSStatePerformRingUpdate: @3U, |
805875f8 A |
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:@[ | |
d64be36e A |
2774 | SOSFlagTriggerBackup, |
2775 | SOSFlagTriggerRingUpdate, | |
805875f8 A |
2776 | ]]; |
2777 | }); | |
2778 | return set; | |
2779 | } | |
2780 | ||
2781 | ||
2782 | ||
2783 | + (NSURL *)urlForSOSAccountSettings { | |
d64be36e | 2784 | return (__bridge_transfer NSURL *)SecCopyURLForFileInKeychainDirectory(CFSTR("SOSAccountSettings.pb")); |
805875f8 A |
2785 | } |
2786 | ||
2787 | ||
2788 | - (void)setupStateMachine { | |
2789 | WEAKIFY(self); | |
2790 | ||
2791 | self.accountConfiguration = [[CKKSPBFileStorage alloc] initWithStoragePath:[[self class] urlForSOSAccountSettings] | |
d64be36e | 2792 | storageClass:[SOSAccountConfiguration class]]; |
805875f8 | 2793 | |
d64be36e | 2794 | NSAssert(self.stateMachine == nil, @"can't bootstrap more than once"); |
805875f8 A |
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 | ||
d64be36e A |
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 | ||
805875f8 A |
2828 | SOSAccountConfiguration *conf = self.accountConfiguration.storage; |
2829 | ||
2830 | if (conf.pendingBackupPeers.count) { | |
2831 | [self addBackupFlag]; | |
2832 | } | |
d64be36e A |
2833 | if (conf.ringUpdateFlag) { |
2834 | [self addRingUpdateFlag]; | |
2835 | } | |
b3971512 | 2836 | } |
805875f8 | 2837 | |
805875f8 | 2838 | |
d64be36e A |
2839 | /* |
2840 | * Flag adding to state machine | |
2841 | */ | |
2842 | ||
805875f8 A |
2843 | - (void)addBackupFlag { |
2844 | OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:SOSFlagTriggerBackup | |
2845 | conditions:OctagonPendingConditionsDeviceUnlocked]; | |
2846 | [self.stateMachine handlePendingFlag:pendingFlag]; | |
2847 | } | |
2848 | ||
d64be36e A |
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 | ||
805875f8 A |
2858 | - (void)triggerBackupForPeers:(NSArray<NSString*>*)backupPeers |
2859 | { | |
d64be36e A |
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 | ||
805875f8 A |
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]; | |
d64be36e A |
2877 | secnotice("sos-sm", "trigger backup for peers: %@ at %@", |
2878 | backupPeers, self.performBackups.nextFireTime); | |
805875f8 A |
2879 | }); |
2880 | } | |
2881 | ||
d64be36e A |
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 | ||
805875f8 A |
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 | ||
d64be36e | 2965 | secnotice("sos-sm", "Entering state: %@ [flags: %@]", currentState, flags); |
805875f8 A |
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 | } | |
d64be36e A |
2973 | |
2974 | if ([flags _onqueueContains:SOSFlagTriggerRingUpdate]) { | |
2975 | [flags _onqueueRemoveFlag:SOSFlagTriggerRingUpdate]; | |
2976 | return [OctagonStateTransitionOperation named:@"perform-ring-update-flag" | |
2977 | entering:SOSStatePerformRingUpdate]; | |
2978 | } | |
805875f8 | 2979 | return nil; |
d64be36e | 2980 | |
805875f8 A |
2981 | } else if ([currentState isEqualToString:SOSStateError]) { |
2982 | return nil; | |
d64be36e A |
2983 | } else if ([currentState isEqualToString:SOSStatePerformRingUpdate]) { |
2984 | return [self performRingUpdate]; | |
b3971512 | 2985 | |
d64be36e A |
2986 | } else if ([currentState isEqualToString:SOSStatePerformBackup]) { |
2987 | return [self performBackup]; | |
805875f8 A |
2988 | } |
2989 | ||
2990 | return nil; | |
2991 | } | |
2992 | #endif | |
2993 | ||
866f8763 A |
2994 | @end |
2995 | ||
2996 |