]> git.saurik.com Git - apple/security.git/blob - Security/sec/SOSCircle/SecureObjectSync/SOSAccount.c
Security-57031.30.12.tar.gz
[apple/security.git] / Security / sec / SOSCircle / SecureObjectSync / SOSAccount.c
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 #include "SOSAccountPriv.h"
11 #include <SecureObjectSync/SOSPeerInfoCollections.h>
12 #include <SecureObjectSync/SOSTransportCircle.h>
13 #include <SecureObjectSync/SOSTransportMessage.h>
14 #include <SecureObjectSync/SOSKVSKeys.h>
15 #include <SecureObjectSync/SOSTransportKeyParameter.h>
16 #include <SecureObjectSync/SOSTransportKeyParameterKVS.h>
17 #include <SecureObjectSync/SOSEngine.h>
18 #include <SecureObjectSync/SOSPeerCoder.h>
19
20 CFGiblisWithCompareFor(SOSAccount);
21
22
23 bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
24 {
25 bool result = false;
26 if (a)
27 {
28 require(a->factory, xit);
29 CFArrayRef circle_names = a->factory->copy_names(a->factory);
30 require(circle_names, xit);
31 CFArrayForEach(circle_names, ^(const void*name) {
32 if (isString(name))
33 SOSAccountEnsureCircle(a, (CFStringRef)name, NULL);
34 });
35
36 CFReleaseNull(circle_names);
37 result = true;
38 }
39 xit:
40 return result;
41 }
42
43
44 SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator,
45 CFDictionaryRef gestalt,
46 SOSDataSourceFactoryRef factory) {
47 SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator);
48
49 a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
50
51 a->gestalt = CFRetainSafe(gestalt);
52
53 a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
54 a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
55
56 a->factory = factory; // We adopt the factory. kthanksbai.
57
58 a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);
59
60 a->departure_code = kSOSNeverAppliedToCircle;
61
62 a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
63 a->circle_transports = CFDictionaryCreateMutableForCFTypes(allocator);
64 a->message_transports = CFDictionaryCreateMutableForCFTypes(allocator);
65
66 return a;
67 }
68
69
70
71
72 bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
73 {
74 if (CFEqual(new_gestalt, account->gestalt))
75 return false;
76 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
77 if (SOSFullPeerInfoUpdateGestalt(full_peer, new_gestalt, NULL)) {
78 SOSAccountModifyCircle(account, SOSCircleGetName(circle),
79 NULL, ^(SOSCircleRef circle_to_change) {
80 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
81 return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(full_peer));
82 });
83 };
84 });
85
86 CFRetainAssign(account->gestalt, new_gestalt);
87 return true;
88 }
89
90 SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
91 CFDictionaryRef gestalt,
92 SOSDataSourceFactoryRef factory) {
93 SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
94
95 a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
96
97 SOSAccountEnsureFactoryCircles(a);
98
99 return a;
100 }
101
102 static void SOSAccountDestroy(CFTypeRef aObj) {
103 SOSAccountRef a = (SOSAccountRef) aObj;
104
105 // We don't own the factory, meerly have a reference to the singleton
106 // don't free it.
107 // a->factory
108
109 CFReleaseNull(a->gestalt);
110 CFReleaseNull(a->circle_identities);
111 CFReleaseNull(a->circles);
112 CFReleaseNull(a->retired_peers);
113
114 a->user_public_trusted = false;
115 CFReleaseNull(a->user_public);
116 CFReleaseNull(a->user_key_parameters);
117
118 SOSAccountPurgePrivateCredential(a);
119 CFReleaseNull(a->previous_public);
120
121 a->departure_code = kSOSNeverAppliedToCircle;
122 CFReleaseNull(a->message_transports);
123 CFReleaseNull(a->key_transport);
124 CFReleaseNull(a->circle_transports);
125 dispatch_release(a->queue);
126 }
127
128 void SOSAccountSetToNew(SOSAccountRef a) {
129 secnotice("accountChange", "Setting Account to New");
130 CFAllocatorRef allocator = CFGetAllocator(a);
131 CFReleaseNull(a->circle_identities);
132 CFReleaseNull(a->circles);
133 CFReleaseNull(a->retired_peers);
134
135 CFReleaseNull(a->user_key_parameters);
136 CFReleaseNull(a->user_public);
137 CFReleaseNull(a->previous_public);
138 CFReleaseNull(a->_user_private);
139
140 CFReleaseNull(a->key_transport);
141 CFReleaseNull(a->circle_transports);
142 CFReleaseNull(a->message_transports);
143
144 a->user_public_trusted = false;
145 a->departure_code = kSOSNeverAppliedToCircle;
146 a->user_private_timer = 0;
147 a->lock_notification_token = 0;
148
149 // keeping gestalt;
150 // keeping factory;
151 // Live Notification
152 // change_blocks;
153 // update_interest_block;
154 // update_block;
155
156 a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
157 a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
158 a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
159
160 a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
161 a->circle_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
162 a->message_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
163
164 SOSAccountEnsureFactoryCircles(a);
165 }
166
167
168 static CFStringRef SOSAccountCopyFormatDescription(CFTypeRef aObj, CFDictionaryRef formatOptions) {
169 SOSAccountRef a = (SOSAccountRef) aObj;
170
171 return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a, a->gestalt, a->circles, a->circle_identities);
172 }
173
174 static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
175 {
176 SOSAccountRef laccount = (SOSAccountRef) lhs;
177 SOSAccountRef raccount = (SOSAccountRef) rhs;
178
179 return CFEqual(laccount->gestalt, raccount->gestalt)
180 && CFEqual(laccount->circles, raccount->circles)
181 && CFEqual(laccount->circle_identities, raccount->circle_identities);
182 // ??? retired_peers
183 }
184
185 dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
186 return account->queue;
187 }
188
189 CFDictionaryRef SOSAccountGetMessageTransports(SOSAccountRef account){
190 return account->message_transports;
191 }
192
193
194 void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
195 account->user_public_trusted = true;
196 }
197
198 CFArrayRef SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
199 {
200 CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator);
201
202 CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) {
203 SOSFullPeerInfoRef fpi = (SOSFullPeerInfoRef) value;
204
205 CFArrayAppendValue(result, SOSFullPeerInfoGetPeerInfo(fpi));
206 });
207
208 return result;
209 }
210
211 static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account, SOSCircleRef circle) {
212 CFErrorRef error = NULL;
213
214 if (!SOSAccountHasPublicKey(account, &error))
215 return false;
216
217 SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), &error);
218 SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
219 CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
220 return SOSCircleHasPeerWithID(circle, myPeerID, &error);
221 }
222
223 static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef circleName, CFStringRef peerID) {
224 CFErrorRef error = NULL;
225 SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, circleName, &error);
226 if (!myfpi) {
227 return false;
228 }
229 SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
230 CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
231 return CFEqualSafe(myPeerID, peerID);
232 }
233
234 bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
235 {
236 __block bool result = true;
237
238 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
239
240 if (SOSAccountThisDeviceCanSyncWithCircle(account, circle)) {
241 CFStringRef circleName = SOSCircleGetName(circle);
242 SOSTransportMessageRef thisPeerTransport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
243 ;
244 CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
245
246 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
247 // Figure out transport for peer; for now we always use KVS
248 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
249 if (!SOSAccountIsThisPeerIDMe(account, circleName, peerID)) {
250 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circleName), peerID);
251 }
252 });
253
254 result &= SOSTransportMessageSyncWithPeers(thisPeerTransport, circleToPeerIDs, error);
255
256 CFReleaseNull(circleToPeerIDs);
257 }
258 });
259
260 // Tell each transport to sync with its collection of peers we know we should sync with.
261
262
263 if (result)
264 SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
265
266
267 return result;
268 }
269
270 bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
271 SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
272 {
273 bool success = false;
274 if(!SOSAccountIsMyPeerActiveInCircle(account, circle, NULL)) return true;
275
276 SOSPeerInfoRef myPeerInfo = SOSAccountGetMyPeerInCircle(account, circle, error);
277 require(myPeerInfo, xit);
278
279 CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);
280 CFStringRef circle_name = SOSCircleGetName(circle);
281
282 if (CFEqual(cleanupPeerID, SOSPeerInfoGetPeerID(myPeerInfo))) {
283 CFErrorRef destroyError = NULL;
284 if (!SOSAccountDestroyCirclePeerInfo(account, circle, &destroyError)) {
285 secerror("Unable to destroy peer info: %@", destroyError);
286 }
287 CFReleaseSafe(destroyError);
288
289 account->departure_code = kSOSWithdrewMembership;
290
291 return true;
292 }
293
294 CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
295 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);
296
297
298 CFErrorRef localError = NULL;
299 SOSTransportMessageRef tMessage = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
300 if (!SOSTransportMessageCleanupAfterPeerMessages(tMessage, circleToPeerIDs, &localError)) {
301 secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
302 }
303 CFReleaseNull(localError);
304 SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
305 if(SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer)) {
306 if (!SOSTransportCircleExpireRetirementRecords(tCircle, circleToPeerIDs, &localError)) {
307 secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID, localError);
308 }
309 }
310 CFReleaseNull(localError);
311
312 CFReleaseNull(circleToPeerIDs);
313
314 xit:
315 return success;
316 }
317
318 bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) {
319 CFMutableDictionaryRef retirements_to_remove = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
320 CFDictionaryRef original_retired_peers = account->retired_peers;
321 __block bool success = true;
322 account->retired_peers = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
323
324 CFDictionaryForEach(original_retired_peers, ^(const void *key, const void *value) {
325 if (isString(key) && isDictionary(value)) {
326 CFStringRef circle_name = key;
327 __block CFMutableDictionaryRef still_active_circle_retirements = NULL;
328 CFDictionaryForEach((CFMutableDictionaryRef) value, ^(const void *key, const void *value) {
329 if (isString(key) && isData(value)) {
330 CFStringRef retired_peer_id = (CFStringRef) key;
331 SOSPeerInfoRef retired_peer = SOSPeerInfoCreateFromData(kCFAllocatorDefault, NULL, (CFDataRef) value);
332 if (retired_peer && SOSPeerInfoIsRetirementTicket(retired_peer) && CFEqual(retired_peer_id, SOSPeerInfoGetPeerID(retired_peer))) {
333 // He's a retired peer all right, if he's active or not yet expired we keep a record of his retirement.
334 // if not, clear any recordings of his retirement from our transport.
335 if (SOSAccountIsActivePeerInCircleNamed(account, circle_name, retired_peer_id, NULL) ||
336 !SOSPeerInfoRetireRetirementTicket(seconds, retired_peer)) {
337 // He's still around or not expired. Keep record.
338 if (still_active_circle_retirements == NULL) {
339 still_active_circle_retirements = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, circle_name);
340 }
341 CFDictionarySetValue(still_active_circle_retirements, retired_peer_id, value);
342 } else {
343 CFMutableArrayRef retirements = CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove, circle_name);
344 CFArrayAppendValue(retirements, retired_peer_id);
345 }
346 }
347 CFReleaseNull(retired_peer);
348 }
349 });
350
351 SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, circle_name);
352 success &= SOSTransportCircleExpireRetirementRecords(tCircle, retirements_to_remove, error);
353 }
354 });
355
356 CFReleaseNull(original_retired_peers);
357 CFReleaseNull(retirements_to_remove);
358
359 return success;
360 }
361
362 bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
363 __block CFMutableDictionaryRef circle_retirees = (CFMutableDictionaryRef) CFDictionaryGetValue(account->retired_peers, SOSCircleGetName(circle));
364
365 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
366 CFStringRef peer_id = SOSPeerInfoGetPeerID(peer);
367 if(!circle_retirees || !CFDictionaryGetValueIfPresent(circle_retirees, peer_id, NULL)) {
368 if (!circle_retirees) {
369 circle_retirees = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, SOSCircleGetName(circle));
370 }
371 CFDataRef value = SOSPeerInfoCopyEncodedData(peer, NULL, NULL);
372 if(value) {
373 CFDictionarySetValue(circle_retirees, peer_id, value);
374 SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, error);
375 }
376 CFReleaseSafe(value);
377 }
378 });
379 return true;
380 }
381
382 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
383 CFStringRef circle_to_mod = SOSCircleGetName(starting_circle);
384
385 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
386 if(!new_circle) return NULL;
387
388 CFDictionaryRef circle_retirements = CFDictionaryGetValue(account->retired_peers, circle_to_mod);
389
390 if (isDictionary(circle_retirements)) {
391 CFDictionaryForEach(circle_retirements, ^(const void* id, const void* value) {
392 if (isData(value)) {
393 SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
394 if (pi && CFEqualSafe(id, SOSPeerInfoGetPeerID(pi))) {
395 SOSCircleUpdatePeerInfo(new_circle, pi);
396 }
397 CFReleaseSafe(pi);
398 }
399 });
400 }
401
402 if(SOSCircleCountPeers(new_circle) == 0) {
403 SOSCircleResetToEmpty(new_circle, NULL);
404 }
405
406 return new_circle;
407 }
408
409 //
410 // MARK: Circle Membership change notificaion
411 //
412
413 void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
414 CFArrayAppendValue(a->change_blocks, changeBlock);
415 }
416
417 void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
418 CFArrayRemoveAllValue(a->change_blocks, changeBlock);
419 }
420
421 void SOSAccountAddSyncablePeerBlock(SOSAccountRef a, CFStringRef ds_name, SOSAccountSyncablePeersBlock changeBlock) {
422 if (!changeBlock) return;
423
424 CFRetainSafe(ds_name);
425 SOSAccountCircleMembershipChangeBlock block_to_register = ^void (SOSCircleRef new_circle,
426 CFSetRef added_peers, CFSetRef removed_peers,
427 CFSetRef added_applicants, CFSetRef removed_applicants) {
428
429 if (!CFEqualSafe(SOSCircleGetName(new_circle), ds_name))
430 return;
431
432 SOSPeerInfoRef myPi = SOSAccountGetMyPeerInCircle(a, new_circle, NULL);
433 CFStringRef myPi_id = myPi ? SOSPeerInfoGetPeerID(myPi) : NULL;
434
435 CFMutableArrayRef peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
436 CFMutableArrayRef added_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
437 CFMutableArrayRef removed_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
438
439 if (SOSCircleHasPeer(new_circle, myPi, NULL)) {
440 SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) {
441 CFArrayAppendValueIfNot(peer_ids, SOSPeerInfoGetPeerID(peer), myPi_id);
442 });
443
444 CFSetForEach(added_peers, ^(const void *value) {
445 CFArrayAppendValueIfNot(added_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
446 });
447
448 CFSetForEach(removed_peers, ^(const void *value) {
449 CFArrayAppendValueIfNot(removed_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
450 });
451 }
452
453 if (CFArrayGetCount(peer_ids) || CFSetContainsValue(removed_peers, myPi))
454 changeBlock(peer_ids, added_ids, removed_ids);
455
456 CFReleaseSafe(peer_ids);
457 CFReleaseSafe(added_ids);
458 CFReleaseSafe(removed_ids);
459 };
460
461 CFRetainSafe(changeBlock);
462 SOSAccountAddChangeBlock(a, Block_copy(block_to_register));
463
464 CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
465 SOSCircleRef circle = (SOSCircleRef) CFDictionaryGetValue(a->circles, ds_name);
466 if (circle) {
467 block_to_register(circle, empty, empty, empty, empty);
468 }
469 CFReleaseSafe(empty);
470 }
471
472
473 bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
474 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL);
475 if(!fpi) return false;
476 if(!SOSFullPeerInfoValidate(fpi, NULL)) return false;
477 CFErrorRef localError = NULL;
478 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
479 CFStringRef retire_id = SOSPeerInfoGetPeerID(retire_peer);
480
481 // Account should move away from a dictionary of KVS keys to a Circle -> Peer -> Retirement ticket storage soonish.
482 CFStringRef retire_key = SOSRetirementKeyCreateWithCircleAndPeer(circle, retire_id);
483 CFDataRef retire_value = NULL;
484 bool retval = false;
485 bool writeCircle = false;
486
487 // Create a Retirement Ticket and store it in the retired_peers of the account.
488 require_action_quiet(retire_peer, errout, secerror("Create ticket failed for peer %@: %@", fpi, localError));
489 retire_value = SOSPeerInfoCopyEncodedData(retire_peer, NULL, &localError);
490 require_action_quiet(retire_value, errout, secerror("Failed to encode retirement peer %@: %@", retire_peer, localError));
491
492 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
493 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
494 // Remove our application if we have one.
495 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
496 writeCircle = true;
497 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
498 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
499 CFErrorRef cleanupError = NULL;
500 if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError))
501 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
502 CFReleaseSafe(cleanupError);
503 }
504 writeCircle = true;
505 }
506
507 // Store the retirement record locally.
508 CFDictionarySetValue(account->retired_peers, retire_key, retire_value);
509
510 // Write retirement to Transport
511 SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
512 SOSTransportCirclePostRetirement(tCircle, SOSCircleGetName(circle), retire_id, retire_value, NULL); // TODO: Handle errors?
513
514 // Kill peer key but don't return error if we can't.
515 if(!SOSAccountDestroyCirclePeerInfo(account, circle, &localError))
516 secerror("Couldn't purge key for peer %@ on retirement: %@", fpi, localError);
517
518 if (writeCircle) {
519 CFDataRef circle_data = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, error);
520
521 if (circle_data) {
522 SOSTransportCirclePostCircle(tCircle, SOSCircleGetName(circle), circle_data, NULL); // TODO: Handle errors?
523 }
524 CFReleaseNull(circle_data);
525 }
526 retval = true;
527
528 errout:
529 CFReleaseNull(localError);
530 CFReleaseNull(retire_peer);
531 CFReleaseNull(retire_key);
532 CFReleaseNull(retire_value);
533 return retval;
534 }
535
536 /*
537 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
538 local value that has been overwritten by a distant value. If there is no
539 conflict between the local and the distant values when doing the initial
540 sync (e.g. if the cloud has no data stored or the client has not stored
541 any data yet), you'll never see that notification.
542
543 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
544 with server but initial round trip with server does not imply
545 NSUbiquitousKeyValueStoreInitialSyncChange.
546 */
547
548
549 //
550 // MARK: Status summary
551 //
552
553 static SOSCCStatus SOSCCCircleStatus(SOSCircleRef circle) {
554 if (SOSCircleCountPeers(circle) == 0)
555 return kSOSCCCircleAbsent;
556
557 return kSOSCCNotInCircle;
558 }
559
560 static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
561 if (SOSCircleCountPeers(circle) == 0)
562 return kSOSCCCircleAbsent;
563
564 if (SOSCircleHasPeer(circle, this_peer, NULL))
565 return kSOSCCInCircle;
566
567 if (SOSCircleHasApplicant(circle, this_peer, NULL))
568 return kSOSCCRequestPending;
569
570 return kSOSCCNotInCircle;
571 }
572
573 static SOSCCStatus UnionStatus(SOSCCStatus accumulated_status, SOSCCStatus additional_circle_status) {
574 switch (additional_circle_status) {
575 case kSOSCCInCircle:
576 return accumulated_status;
577 case kSOSCCRequestPending:
578 return (accumulated_status == kSOSCCInCircle) ?
579 kSOSCCRequestPending :
580 accumulated_status;
581 case kSOSCCNotInCircle:
582 return (accumulated_status == kSOSCCInCircle ||
583 accumulated_status == kSOSCCRequestPending) ?
584 kSOSCCNotInCircle :
585 accumulated_status;
586 case kSOSCCCircleAbsent:
587 return (accumulated_status == kSOSCCInCircle ||
588 accumulated_status == kSOSCCRequestPending ||
589 accumulated_status == kSOSCCNotInCircle) ?
590 kSOSCCCircleAbsent :
591 accumulated_status;
592 default:
593 return additional_circle_status;
594 };
595
596 }
597
598 SOSCCStatus SOSAccountIsInCircles(SOSAccountRef account, CFErrorRef* error) {
599 if (!SOSAccountHasPublicKey(account, error)) {
600 return kSOSCCError;
601 }
602
603 __block bool set_once = false;
604 __block SOSCCStatus status = kSOSCCInCircle;
605
606 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
607 set_once = true;
608 status = UnionStatus(status, kSOSCCNotInCircle);
609 }, ^(SOSCircleRef circle) {
610 set_once = true;
611 status = UnionStatus(status, SOSCCCircleStatus(circle));
612 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
613 set_once = true;
614 SOSCCStatus circle_status = SOSCCThisDeviceStatusInCircle(circle, SOSFullPeerInfoGetPeerInfo(full_peer));
615 status = UnionStatus(status, circle_status);
616 });
617
618 if (!set_once)
619 status = kSOSCCCircleAbsent;
620
621 return status;
622 }
623
624 //
625 // MARK: Account Reset Circles
626 //
627
628 static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) {
629 SOSFullPeerInfoRef myCirclePeer = SOSAccountMakeMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error);
630 if (!myCirclePeer)
631 return false;
632 if(!SOSFullPeerInfoValidate(myCirclePeer, NULL)) return false;
633
634
635 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
636 bool result = false;
637 SOSFullPeerInfoRef cloud_identity = NULL;
638 CFErrorRef localError = NULL;
639
640 require_quiet(SOSCircleResetToOffering(circle, user_key, myCirclePeer, &localError), err_out);
641
642 {
643 SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error);
644 require_quiet(cloud_peer, err_out);
645 cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error);
646 CFReleaseNull(cloud_peer);
647 require_quiet(cloud_identity, err_out);
648 }
649
650 account->departure_code = kSOSNeverLeftCircle;
651 require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, &localError), err_out);
652 require_quiet(SOSCircleAcceptRequest(circle, user_key, myCirclePeer, SOSFullPeerInfoGetPeerInfo(cloud_identity), &localError), err_out);
653 result = true;
654 SOSAccountPublishCloudParameters(account, NULL);
655
656 err_out:
657 if (result == false)
658 secerror("error resetting circle (%@) to offering: %@", circle, localError);
659 if (localError && error && *error == NULL) {
660 *error = localError;
661 localError = NULL;
662 }
663 CFReleaseNull(localError);
664 CFReleaseNull(cloud_identity);
665 return result;
666 });
667
668 return true;
669 }
670
671
672 bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
673 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
674 if (!user_key)
675 return false;
676
677 __block bool result = true;
678
679 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
680 SOSCircleRef circle = SOSCircleCreate(NULL, name, NULL);
681 if (circle)
682 CFDictionaryAddValue(account->circles, name, circle);
683
684 SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
685 }, ^(SOSCircleRef circle) {
686 SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
687 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
688 SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
689 });
690
691 return result;
692 }
693
694 bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
695 if (!SOSAccountHasPublicKey(account, error))
696 return false;
697
698 __block bool result = true;
699 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
700 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
701 if (!SOSCircleResetToEmpty(circle, error))
702 {
703 secerror("error: %@", *error);
704 result = false;
705 }
706 account->departure_code = kSOSWithdrewMembership;
707 return result;
708 });
709 });
710
711 return result;
712 }
713
714
715 //
716 // MARK: Joining
717 //
718
719 static bool SOSAccountJoinThisCircle(SOSAccountRef account, SecKeyRef user_key,
720 SOSCircleRef circle, bool use_cloud_peer, CFErrorRef* error) {
721 __block bool result = false;
722 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
723
724 SOSFullPeerInfoRef myCirclePeer = SOSAccountMakeMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error);
725
726 require_action_quiet(myCirclePeer, fail,
727 SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Can't find/create peer for circle: %@"), circle));
728 if (use_cloud_peer) {
729 cloud_full_peer = SOSCircleGetiCloudFullPeerInfoRef(circle);
730 }
731
732 if (SOSCircleCountPeers(circle) == 0) {
733 result = SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
734 } else {
735 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
736 result = SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
737 account->departure_code = kSOSNeverLeftCircle;
738 if(result && cloud_full_peer) {
739 CFErrorRef localError = NULL;
740 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
741 require_quiet(cloudid, finish);
742 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
743 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
744 finish:
745 if (localError){
746 secerror("Failed to join with cloud identity: %@", localError);
747 CFReleaseNull(localError);
748 }
749 }
750 return result;
751 });
752 }
753
754 fail:
755 CFReleaseNull(cloud_full_peer);
756 return result;
757 }
758
759 static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) {
760 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
761 if (!user_key)
762 return false;
763
764 __block bool success = true;
765
766 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { // Incompatible
767 success = false;
768 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error);
769 }, ^(SOSCircleRef circle) { //no peer
770 success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
771 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { // Have Peer
772 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(full_peer);
773 if(SOSCircleHasPeer(circle, myPeer, NULL)) goto already_present;
774 if(SOSCircleHasApplicant(circle, myPeer, NULL)) goto already_applied;
775 if(SOSCircleHasRejectedApplicant(circle, myPeer, NULL)) {
776 SOSCircleRemoveRejectedPeer(circle, myPeer, NULL);
777 }
778
779 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(circle));
780 CFErrorRef localError = NULL;
781 if (!SOSAccountDestroyCirclePeerInfo(account, circle, &localError)) {
782 secerror("Failed to destroy peer (%@) during application, error=%@", myPeer, localError);
783 CFReleaseNull(localError);
784 }
785 already_applied:
786 success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
787 return;
788 already_present:
789 success = true;
790 return;
791 });
792
793 if(success) account->departure_code = kSOSNeverLeftCircle;
794 return success;
795 }
796
797 bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
798 return SOSAccountJoinCircles_internal(account, false, error);
799 }
800
801 CFStringRef SOSAccountGetDeviceID(SOSAccountRef account, CFErrorRef *error){
802 __block CFStringRef result = NULL;
803 __block CFStringRef temp = NULL;
804 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
805 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
806 if(fpi){
807 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
808 if(myPeer){
809 temp = SOSPeerInfoGetDeviceID(myPeer);
810 if(!isNull(temp)){
811 result = CFStringCreateCopy(kCFAllocatorDefault, temp);
812 }
813 }
814 else{
815 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
816 }
817 }
818 else{
819 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
820 }
821 });
822 return result;
823 }
824
825 bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
826 __block bool result = false;
827
828 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
829 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
830 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
831 if(fpi){
832 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
833 if(myPeer){
834 SOSPeerInfoSetDeviceID(myPeer, IDS);
835 result = true;
836 }
837 else{
838 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
839 result = false;
840 }
841 }
842 else{
843 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
844 result = false;
845 }
846 return result;
847 });
848 });
849 return result;
850
851 }
852 bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
853 return SOSAccountJoinCircles_internal(account, true, error);
854 }
855
856
857 bool SOSAccountLeaveCircles(SOSAccountRef account, CFErrorRef* error)
858 {
859 __block bool result = true;
860 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
861 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
862 result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
863 return result;
864 });
865 });
866
867 account->departure_code = kSOSWithdrewMembership;
868 return result;
869 }
870
871 bool SOSAccountBail(SOSAccountRef account, uint64_t limit_in_seconds, CFErrorRef* error) {
872 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
873 dispatch_group_t group = dispatch_group_create();
874 __block bool result = false;
875 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
876 // Add a task to the group
877 dispatch_group_async(group, queue, ^{
878 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
879 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
880 result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
881 return result;
882 });
883 });
884
885 account->departure_code = kSOSWithdrewMembership;
886 });
887 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
888
889 dispatch_group_wait(group, milestone);
890 dispatch_release(group);
891 return result;
892 }
893
894
895 //
896 // MARK: Application
897 //
898
899 static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
900 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
901
902 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
903 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(full_peer);
904 CFErrorRef peer_error = NULL;
905 if (SOSCircleHasPeer(circle, me, &peer_error)) {
906 CFArrayForEach(peer_infos, ^(const void *value) {
907 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
908 if (SOSCircleHasApplicant(circle, peer, NULL)) {
909 SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) {
910 return action(circle, full_peer, peer);
911 });
912 }
913 });
914 }
915 if (peer_error)
916 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
917 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
918 });
919 }
920
921 bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
922 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
923 if (!user_key)
924 return false;
925
926 __block bool success = true;
927 __block int64_t num_peers = 0;
928
929 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
930 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
931 if (!accepted)
932 success = false;
933 else
934 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
935 return accepted;
936 });
937
938 return success;
939 }
940
941 bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
942 __block bool success = true;
943 __block int64_t num_peers = 0;
944
945 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
946 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
947 if (!rejected)
948 success = false;
949 else
950 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
951 return rejected;
952 });
953
954 return success;
955 }
956
957
958
959 CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) {
960 return CFSTR("We're compatible, go away");
961 }
962
963 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) {
964 return account->departure_code;
965 }
966
967
968 CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
969 if (!SOSAccountHasPublicKey(account, error))
970 return NULL;
971 CFMutableArrayRef generations = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
972
973 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
974 CFNumberRef generation = (CFNumberRef)SOSCircleGetGeneration(circle);
975 CFArrayAppendValue(generations, SOSCircleGetName(circle));
976 CFArrayAppendValue(generations, generation);
977 });
978
979 return generations;
980
981 }
982
983 bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
984 if (!SOSAccountHasPublicKey(account, error))
985 return NULL;
986
987 return account->user_public_trusted;
988 }
989
990
991 bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
992 __block bool result = true;
993
994 secnotice("updates", "Ensuring peer registration.");
995
996 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
997 }, ^(SOSCircleRef circle) {
998 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
999 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
1000 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(fpi);
1001 CFMutableArrayRef trusted_peer_ids = NULL;
1002 CFMutableArrayRef untrusted_peer_ids = NULL;
1003 CFStringRef my_id = NULL;
1004 if (SOSCircleHasPeer(circle, me, NULL)) {
1005 my_id = SOSPeerInfoGetPeerID(me);
1006 trusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
1007 untrusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
1008 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1009 CFMutableArrayRef arrayToAddTo = SOSPeerInfoApplicationVerify(peer, account->user_public, NULL) ? trusted_peer_ids : untrusted_peer_ids;
1010
1011 CFArrayAppendValueIfNot(arrayToAddTo, SOSPeerInfoGetPeerID(peer), my_id);
1012 });
1013 }
1014
1015 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(circle), NULL);
1016 if (engine)
1017 SOSEngineCircleChanged(engine, my_id, trusted_peer_ids, untrusted_peer_ids);
1018
1019 CFReleaseNull(trusted_peer_ids);
1020 CFReleaseNull(untrusted_peer_ids);
1021
1022 SOSTransportMessageRef transport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
1023 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1024 if (!CFEqualSafe(me, peer)) {
1025 CFErrorRef localError = NULL;
1026 SOSPeerCoderInitializeForPeer(transport, full_peer, peer, &localError);
1027 if (localError)
1028 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, full_peer, localError);
1029 CFReleaseSafe(localError);
1030 }
1031 });
1032 });
1033
1034 return result;
1035 }