2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
6 * SOSAccount.c - Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
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>
20 CFGiblisWithCompareFor(SOSAccount
);
23 bool SOSAccountEnsureFactoryCircles(SOSAccountRef a
)
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
) {
33 SOSAccountEnsureCircle(a
, (CFStringRef
)name
, NULL
);
36 CFReleaseNull(circle_names
);
44 SOSAccountRef
SOSAccountCreateBasic(CFAllocatorRef allocator
,
45 CFDictionaryRef gestalt
,
46 SOSDataSourceFactoryRef factory
) {
47 SOSAccountRef a
= CFTypeAllocate(SOSAccount
, struct __OpaqueSOSAccount
, allocator
);
49 a
->queue
= dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL
);
51 a
->gestalt
= CFRetainSafe(gestalt
);
53 a
->circles
= CFDictionaryCreateMutableForCFTypes(allocator
);
54 a
->circle_identities
= CFDictionaryCreateMutableForCFTypes(allocator
);
56 a
->factory
= factory
; // We adopt the factory. kthanksbai.
58 a
->change_blocks
= CFArrayCreateMutableForCFTypes(allocator
);
60 a
->departure_code
= kSOSNeverAppliedToCircle
;
62 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
63 a
->circle_transports
= CFDictionaryCreateMutableForCFTypes(allocator
);
64 a
->message_transports
= CFDictionaryCreateMutableForCFTypes(allocator
);
72 bool SOSAccountUpdateGestalt(SOSAccountRef account
, CFDictionaryRef new_gestalt
)
74 if (CFEqual(new_gestalt
, account
->gestalt
))
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
));
86 CFRetainAssign(account
->gestalt
, new_gestalt
);
90 SOSAccountRef
SOSAccountCreate(CFAllocatorRef allocator
,
91 CFDictionaryRef gestalt
,
92 SOSDataSourceFactoryRef factory
) {
93 SOSAccountRef a
= SOSAccountCreateBasic(allocator
, gestalt
, factory
);
95 a
->retired_peers
= CFDictionaryCreateMutableForCFTypes(allocator
);
97 SOSAccountEnsureFactoryCircles(a
);
102 static void SOSAccountDestroy(CFTypeRef aObj
) {
103 SOSAccountRef a
= (SOSAccountRef
) aObj
;
105 // We don't own the factory, meerly have a reference to the singleton
109 CFReleaseNull(a
->gestalt
);
110 CFReleaseNull(a
->circle_identities
);
111 CFReleaseNull(a
->circles
);
112 CFReleaseNull(a
->retired_peers
);
114 a
->user_public_trusted
= false;
115 CFReleaseNull(a
->user_public
);
116 CFReleaseNull(a
->user_key_parameters
);
118 SOSAccountPurgePrivateCredential(a
);
119 CFReleaseNull(a
->previous_public
);
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
);
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
);
135 CFReleaseNull(a
->user_key_parameters
);
136 CFReleaseNull(a
->user_public
);
137 CFReleaseNull(a
->previous_public
);
138 CFReleaseNull(a
->_user_private
);
140 CFReleaseNull(a
->key_transport
);
141 CFReleaseNull(a
->circle_transports
);
142 CFReleaseNull(a
->message_transports
);
144 a
->user_public_trusted
= false;
145 a
->departure_code
= kSOSNeverAppliedToCircle
;
146 a
->user_private_timer
= 0;
147 a
->lock_notification_token
= 0;
153 // update_interest_block;
156 a
->circles
= CFDictionaryCreateMutableForCFTypes(allocator
);
157 a
->circle_identities
= CFDictionaryCreateMutableForCFTypes(allocator
);
158 a
->retired_peers
= CFDictionaryCreateMutableForCFTypes(allocator
);
160 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
161 a
->circle_transports
= (CFMutableDictionaryRef
)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
162 a
->message_transports
= (CFMutableDictionaryRef
)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
164 SOSAccountEnsureFactoryCircles(a
);
168 static CFStringRef
SOSAccountCopyFormatDescription(CFTypeRef aObj
, CFDictionaryRef formatOptions
) {
169 SOSAccountRef a
= (SOSAccountRef
) aObj
;
171 return CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a
, a
->gestalt
, a
->circles
, a
->circle_identities
);
174 static Boolean
SOSAccountCompare(CFTypeRef lhs
, CFTypeRef rhs
)
176 SOSAccountRef laccount
= (SOSAccountRef
) lhs
;
177 SOSAccountRef raccount
= (SOSAccountRef
) rhs
;
179 return CFEqual(laccount
->gestalt
, raccount
->gestalt
)
180 && CFEqual(laccount
->circles
, raccount
->circles
)
181 && CFEqual(laccount
->circle_identities
, raccount
->circle_identities
);
185 dispatch_queue_t
SOSAccountGetQueue(SOSAccountRef account
) {
186 return account
->queue
;
189 CFDictionaryRef
SOSAccountGetMessageTransports(SOSAccountRef account
){
190 return account
->message_transports
;
194 void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account
){
195 account
->user_public_trusted
= true;
198 CFArrayRef
SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account
, CFAllocatorRef allocator
, CFErrorRef
* error
)
200 CFMutableArrayRef result
= CFArrayCreateMutableForCFTypes(allocator
);
202 CFDictionaryForEach(account
->circle_identities
, ^(const void *key
, const void *value
) {
203 SOSFullPeerInfoRef fpi
= (SOSFullPeerInfoRef
) value
;
205 CFArrayAppendValue(result
, SOSFullPeerInfoGetPeerInfo(fpi
));
211 static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account
, SOSCircleRef circle
) {
212 CFErrorRef error
= NULL
;
214 if (!SOSAccountHasPublicKey(account
, &error
))
217 SOSFullPeerInfoRef myfpi
= SOSAccountGetMyFullPeerInCircleNamedIfPresent(account
, SOSCircleGetName(circle
), &error
);
218 SOSPeerInfoRef mypi
= SOSFullPeerInfoGetPeerInfo(myfpi
);
219 CFStringRef myPeerID
= SOSPeerInfoGetPeerID(mypi
);
220 return SOSCircleHasPeerWithID(circle
, myPeerID
, &error
);
223 static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account
, CFStringRef circleName
, CFStringRef peerID
) {
224 CFErrorRef error
= NULL
;
225 SOSFullPeerInfoRef myfpi
= SOSAccountGetMyFullPeerInCircleNamedIfPresent(account
, circleName
, &error
);
229 SOSPeerInfoRef mypi
= SOSFullPeerInfoGetPeerInfo(myfpi
);
230 CFStringRef myPeerID
= SOSPeerInfoGetPeerID(mypi
);
231 return CFEqualSafe(myPeerID
, peerID
);
234 bool SOSAccountSyncWithAllPeers(SOSAccountRef account
, CFErrorRef
*error
)
236 __block
bool result
= true;
238 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
240 if (SOSAccountThisDeviceCanSyncWithCircle(account
, circle
)) {
241 CFStringRef circleName
= SOSCircleGetName(circle
);
242 SOSTransportMessageRef thisPeerTransport
= (SOSTransportMessageRef
)CFDictionaryGetValue(account
->message_transports
, SOSCircleGetName(circle
));
244 CFMutableDictionaryRef circleToPeerIDs
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
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
);
254 result
&= SOSTransportMessageSyncWithPeers(thisPeerTransport
, circleToPeerIDs
, error
);
256 CFReleaseNull(circleToPeerIDs
);
260 // Tell each transport to sync with its collection of peers we know we should sync with.
264 SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers
, 1);
270 bool SOSAccountCleanupAfterPeer(SOSAccountRef account
, size_t seconds
, SOSCircleRef circle
,
271 SOSPeerInfoRef cleanupPeer
, CFErrorRef
* error
)
273 bool success
= false;
274 if(!SOSAccountIsMyPeerActiveInCircle(account
, circle
, NULL
)) return true;
276 SOSPeerInfoRef myPeerInfo
= SOSAccountGetMyPeerInCircle(account
, circle
, error
);
277 require(myPeerInfo
, xit
);
279 CFStringRef cleanupPeerID
= SOSPeerInfoGetPeerID(cleanupPeer
);
280 CFStringRef circle_name
= SOSCircleGetName(circle
);
282 if (CFEqual(cleanupPeerID
, SOSPeerInfoGetPeerID(myPeerInfo
))) {
283 CFErrorRef destroyError
= NULL
;
284 if (!SOSAccountDestroyCirclePeerInfo(account
, circle
, &destroyError
)) {
285 secerror("Unable to destroy peer info: %@", destroyError
);
287 CFReleaseSafe(destroyError
);
289 account
->departure_code
= kSOSWithdrewMembership
;
294 CFMutableDictionaryRef circleToPeerIDs
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
295 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs
, circle_name
), cleanupPeerID
);
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
);
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
);
310 CFReleaseNull(localError
);
312 CFReleaseNull(circleToPeerIDs
);
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
);
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
);
341 CFDictionarySetValue(still_active_circle_retirements
, retired_peer_id
, value
);
343 CFMutableArrayRef retirements
= CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove
, circle_name
);
344 CFArrayAppendValue(retirements
, retired_peer_id
);
347 CFReleaseNull(retired_peer
);
351 SOSTransportCircleRef tCircle
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, circle_name
);
352 success
&= SOSTransportCircleExpireRetirementRecords(tCircle
, retirements_to_remove
, error
);
356 CFReleaseNull(original_retired_peers
);
357 CFReleaseNull(retirements_to_remove
);
362 bool SOSAccountScanForRetired(SOSAccountRef account
, SOSCircleRef circle
, CFErrorRef
*error
) {
363 __block CFMutableDictionaryRef circle_retirees
= (CFMutableDictionaryRef
) CFDictionaryGetValue(account
->retired_peers
, SOSCircleGetName(circle
));
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
));
371 CFDataRef value
= SOSPeerInfoCopyEncodedData(peer
, NULL
, NULL
);
373 CFDictionarySetValue(circle_retirees
, peer_id
, value
);
374 SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, peer
, error
);
376 CFReleaseSafe(value
);
382 SOSCircleRef
SOSAccountCloneCircleWithRetirement(SOSAccountRef account
, SOSCircleRef starting_circle
, CFErrorRef
*error
) {
383 CFStringRef circle_to_mod
= SOSCircleGetName(starting_circle
);
385 SOSCircleRef new_circle
= SOSCircleCopyCircle(NULL
, starting_circle
, error
);
386 if(!new_circle
) return NULL
;
388 CFDictionaryRef circle_retirements
= CFDictionaryGetValue(account
->retired_peers
, circle_to_mod
);
390 if (isDictionary(circle_retirements
)) {
391 CFDictionaryForEach(circle_retirements
, ^(const void* id
, const void* value
) {
393 SOSPeerInfoRef pi
= SOSPeerInfoCreateFromData(NULL
, error
, (CFDataRef
) value
);
394 if (pi
&& CFEqualSafe(id
, SOSPeerInfoGetPeerID(pi
))) {
395 SOSCircleUpdatePeerInfo(new_circle
, pi
);
402 if(SOSCircleCountPeers(new_circle
) == 0) {
403 SOSCircleResetToEmpty(new_circle
, NULL
);
410 // MARK: Circle Membership change notificaion
413 void SOSAccountAddChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
414 CFArrayAppendValue(a
->change_blocks
, changeBlock
);
417 void SOSAccountRemoveChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
418 CFArrayRemoveAllValue(a
->change_blocks
, changeBlock
);
421 void SOSAccountAddSyncablePeerBlock(SOSAccountRef a
, CFStringRef ds_name
, SOSAccountSyncablePeersBlock changeBlock
) {
422 if (!changeBlock
) return;
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
) {
429 if (!CFEqualSafe(SOSCircleGetName(new_circle
), ds_name
))
432 SOSPeerInfoRef myPi
= SOSAccountGetMyPeerInCircle(a
, new_circle
, NULL
);
433 CFStringRef myPi_id
= myPi
? SOSPeerInfoGetPeerID(myPi
) : NULL
;
435 CFMutableArrayRef peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
436 CFMutableArrayRef added_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
437 CFMutableArrayRef removed_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
439 if (SOSCircleHasPeer(new_circle
, myPi
, NULL
)) {
440 SOSCircleForEachPeer(new_circle
, ^(SOSPeerInfoRef peer
) {
441 CFArrayAppendValueIfNot(peer_ids
, SOSPeerInfoGetPeerID(peer
), myPi_id
);
444 CFSetForEach(added_peers
, ^(const void *value
) {
445 CFArrayAppendValueIfNot(added_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
448 CFSetForEach(removed_peers
, ^(const void *value
) {
449 CFArrayAppendValueIfNot(removed_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
453 if (CFArrayGetCount(peer_ids
) || CFSetContainsValue(removed_peers
, myPi
))
454 changeBlock(peer_ids
, added_ids
, removed_ids
);
456 CFReleaseSafe(peer_ids
);
457 CFReleaseSafe(added_ids
);
458 CFReleaseSafe(removed_ids
);
461 CFRetainSafe(changeBlock
);
462 SOSAccountAddChangeBlock(a
, Block_copy(block_to_register
));
464 CFSetRef empty
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
465 SOSCircleRef circle
= (SOSCircleRef
) CFDictionaryGetValue(a
->circles
, ds_name
);
467 block_to_register(circle
, empty
, empty
, empty
, empty
);
469 CFReleaseSafe(empty
);
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
);
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
;
485 bool writeCircle
= false;
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
));
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
);
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
);
507 // Store the retirement record locally.
508 CFDictionarySetValue(account
->retired_peers
, retire_key
, retire_value
);
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?
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
);
519 CFDataRef circle_data
= SOSCircleCopyEncodedData(circle
, kCFAllocatorDefault
, error
);
522 SOSTransportCirclePostCircle(tCircle
, SOSCircleGetName(circle
), circle_data
, NULL
); // TODO: Handle errors?
524 CFReleaseNull(circle_data
);
529 CFReleaseNull(localError
);
530 CFReleaseNull(retire_peer
);
531 CFReleaseNull(retire_key
);
532 CFReleaseNull(retire_value
);
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.
543 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
544 with server but initial round trip with server does not imply
545 NSUbiquitousKeyValueStoreInitialSyncChange.
550 // MARK: Status summary
553 static SOSCCStatus
SOSCCCircleStatus(SOSCircleRef circle
) {
554 if (SOSCircleCountPeers(circle
) == 0)
555 return kSOSCCCircleAbsent
;
557 return kSOSCCNotInCircle
;
560 static SOSCCStatus
SOSCCThisDeviceStatusInCircle(SOSCircleRef circle
, SOSPeerInfoRef this_peer
) {
561 if (SOSCircleCountPeers(circle
) == 0)
562 return kSOSCCCircleAbsent
;
564 if (SOSCircleHasPeer(circle
, this_peer
, NULL
))
565 return kSOSCCInCircle
;
567 if (SOSCircleHasApplicant(circle
, this_peer
, NULL
))
568 return kSOSCCRequestPending
;
570 return kSOSCCNotInCircle
;
573 static SOSCCStatus
UnionStatus(SOSCCStatus accumulated_status
, SOSCCStatus additional_circle_status
) {
574 switch (additional_circle_status
) {
576 return accumulated_status
;
577 case kSOSCCRequestPending
:
578 return (accumulated_status
== kSOSCCInCircle
) ?
579 kSOSCCRequestPending
:
581 case kSOSCCNotInCircle
:
582 return (accumulated_status
== kSOSCCInCircle
||
583 accumulated_status
== kSOSCCRequestPending
) ?
586 case kSOSCCCircleAbsent
:
587 return (accumulated_status
== kSOSCCInCircle
||
588 accumulated_status
== kSOSCCRequestPending
||
589 accumulated_status
== kSOSCCNotInCircle
) ?
593 return additional_circle_status
;
598 SOSCCStatus
SOSAccountIsInCircles(SOSAccountRef account
, CFErrorRef
* error
) {
599 if (!SOSAccountHasPublicKey(account
, error
)) {
603 __block
bool set_once
= false;
604 __block SOSCCStatus status
= kSOSCCInCircle
;
606 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
608 status
= UnionStatus(status
, kSOSCCNotInCircle
);
609 }, ^(SOSCircleRef circle
) {
611 status
= UnionStatus(status
, SOSCCCircleStatus(circle
));
612 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
614 SOSCCStatus circle_status
= SOSCCThisDeviceStatusInCircle(circle
, SOSFullPeerInfoGetPeerInfo(full_peer
));
615 status
= UnionStatus(status
, circle_status
);
619 status
= kSOSCCCircleAbsent
;
625 // MARK: Account Reset Circles
628 static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account
, SOSCircleRef circle
, SecKeyRef user_key
, CFErrorRef
*error
) {
629 SOSFullPeerInfoRef myCirclePeer
= SOSAccountMakeMyFullPeerInCircleNamed(account
, SOSCircleGetName(circle
), error
);
632 if(!SOSFullPeerInfoValidate(myCirclePeer
, NULL
)) return false;
635 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
637 SOSFullPeerInfoRef cloud_identity
= NULL
;
638 CFErrorRef localError
= NULL
;
640 require_quiet(SOSCircleResetToOffering(circle
, user_key
, myCirclePeer
, &localError
), err_out
);
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
);
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
);
654 SOSAccountPublishCloudParameters(account
, NULL
);
658 secerror("error resetting circle (%@) to offering: %@", circle
, localError
);
659 if (localError
&& error
&& *error
== NULL
) {
663 CFReleaseNull(localError
);
664 CFReleaseNull(cloud_identity
);
672 bool SOSAccountResetToOffering(SOSAccountRef account
, CFErrorRef
* error
) {
673 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
677 __block
bool result
= true;
679 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
680 SOSCircleRef circle
= SOSCircleCreate(NULL
, name
, NULL
);
682 CFDictionaryAddValue(account
->circles
, name
, circle
);
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
);
694 bool SOSAccountResetToEmpty(SOSAccountRef account
, CFErrorRef
* error
) {
695 if (!SOSAccountHasPublicKey(account
, error
))
698 __block
bool result
= true;
699 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
700 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
701 if (!SOSCircleResetToEmpty(circle
, error
))
703 secerror("error: %@", *error
);
706 account
->departure_code
= kSOSWithdrewMembership
;
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
;
724 SOSFullPeerInfoRef myCirclePeer
= SOSAccountMakeMyFullPeerInCircleNamed(account
, SOSCircleGetName(circle
), error
);
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
);
732 if (SOSCircleCountPeers(circle
) == 0) {
733 result
= SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
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
);
746 secerror("Failed to join with cloud identity: %@", localError
);
747 CFReleaseNull(localError
);
755 CFReleaseNull(cloud_full_peer
);
759 static bool SOSAccountJoinCircles_internal(SOSAccountRef account
, bool use_cloud_identity
, CFErrorRef
* error
) {
760 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
764 __block
bool success
= true;
766 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) { // Incompatible
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
);
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
);
786 success
= SOSAccountJoinThisCircle(account
, user_key
, circle
, use_cloud_identity
, error
) && success
;
793 if(success
) account
->departure_code
= kSOSNeverLeftCircle
;
797 bool SOSAccountJoinCircles(SOSAccountRef account
, CFErrorRef
* error
) {
798 return SOSAccountJoinCircles_internal(account
, false, error
);
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
);
807 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(fpi
);
809 temp
= SOSPeerInfoGetDeviceID(myPeer
);
811 result
= CFStringCreateCopy(kCFAllocatorDefault
, temp
);
815 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle
));
819 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle
));
825 bool SOSAccountSetMyDSID(SOSAccountRef account
, CFStringRef IDS
, CFErrorRef
* error
){
826 __block
bool result
= false;
828 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
829 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
830 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
832 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(fpi
);
834 SOSPeerInfoSetDeviceID(myPeer
, IDS
);
838 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle
));
843 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle
));
852 bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account
, CFErrorRef
* error
) {
853 return SOSAccountJoinCircles_internal(account
, true, error
);
857 bool SOSAccountLeaveCircles(SOSAccountRef account
, CFErrorRef
* error
)
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!
867 account
->departure_code
= kSOSWithdrewMembership
;
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!
885 account
->departure_code
= kSOSWithdrewMembership
;
887 dispatch_time_t milestone
= dispatch_time(DISPATCH_TIME_NOW
, limit_in_seconds
* NSEC_PER_SEC
);
889 dispatch_group_wait(group
, milestone
);
890 dispatch_release(group
);
899 static void for_each_applicant_in_each_circle(SOSAccountRef account
, CFArrayRef peer_infos
,
900 bool (^action
)(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
)) {
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
);
916 secerror("Got error in SOSCircleHasPeer: %@", peer_error
);
917 CFReleaseSafe(peer_error
); // TODO: We should be accumulating errors here.
921 bool SOSAccountAcceptApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
922 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
926 __block
bool success
= true;
927 __block
int64_t num_peers
= 0;
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
);
934 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
941 bool SOSAccountRejectApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
942 __block
bool success
= true;
943 __block
int64_t num_peers
= 0;
945 for_each_applicant_in_each_circle(account
, applicants
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
) {
946 bool rejected
= SOSCircleRejectRequest(circle
, myCirclePeer
, peer
, error
);
950 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
959 CFStringRef
SOSAccountCopyIncompatibilityInfo(SOSAccountRef account
, CFErrorRef
* error
) {
960 return CFSTR("We're compatible, go away");
963 enum DepartureReason
SOSAccountGetLastDepartureReason(SOSAccountRef account
, CFErrorRef
* error
) {
964 return account
->departure_code
;
968 CFArrayRef
SOSAccountCopyGeneration(SOSAccountRef account
, CFErrorRef
*error
) {
969 if (!SOSAccountHasPublicKey(account
, error
))
971 CFMutableArrayRef generations
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
973 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
974 CFNumberRef generation
= (CFNumberRef
)SOSCircleGetGeneration(circle
);
975 CFArrayAppendValue(generations
, SOSCircleGetName(circle
));
976 CFArrayAppendValue(generations
, generation
);
983 bool SOSValidateUserPublic(SOSAccountRef account
, CFErrorRef
*error
) {
984 if (!SOSAccountHasPublicKey(account
, error
))
987 return account
->user_public_trusted
;
991 bool SOSAccountEnsurePeerRegistration(SOSAccountRef account
, CFErrorRef
*error
) {
992 __block
bool result
= true;
994 secnotice("updates", "Ensuring peer registration.");
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
;
1011 CFArrayAppendValueIfNot(arrayToAddTo
, SOSPeerInfoGetPeerID(peer
), my_id
);
1015 SOSEngineRef engine
= SOSDataSourceFactoryGetEngineForDataSourceName(account
->factory
, SOSCircleGetName(circle
), NULL
);
1017 SOSEngineCircleChanged(engine
, my_id
, trusted_peer_ids
, untrusted_peer_ids
);
1019 CFReleaseNull(trusted_peer_ids
);
1020 CFReleaseNull(untrusted_peer_ids
);
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
);
1028 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer
, full_peer
, localError
);
1029 CFReleaseSafe(localError
);