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 <Security/SecureObjectSync/SOSPeerInfo.h>
12 #include <Security/SecureObjectSync/SOSPeerInfoV2.h>
13 #include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
14 #include <Security/SecureObjectSync/SOSTransportCircle.h>
15 #include <Security/SecureObjectSync/SOSTransportMessage.h>
16 #include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
17 #include <Security/SecureObjectSync/SOSKVSKeys.h>
18 #include <Security/SecureObjectSync/SOSTransport.h>
19 #include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
20 #include <Security/SecureObjectSync/SOSTransportKeyParameterKVS.h>
21 #include <Security/SecureObjectSync/SOSPeerCoder.h>
22 #include <Security/SecureObjectSync/SOSInternal.h>
23 #include <Security/SecureObjectSync/SOSRing.h>
24 #include <Security/SecureObjectSync/SOSRingUtils.h>
25 #include <Security/SecureObjectSync/SOSRingRecovery.h>
26 #include <Security/SecureObjectSync/SOSPeerInfoSecurityProperties.h>
27 #include <Security/SecureObjectSync/SOSAccountTransaction.h>
28 #include <Security/SecureObjectSync/SOSAccountGhost.h>
30 #include <Security/SecItemInternal.h>
31 #include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
33 #include <utilities/SecCFWrappers.h>
34 #include <utilities/SecCFError.h>
36 CFGiblisWithCompareFor(SOSAccount
);
38 const CFStringRef SOSTransportMessageTypeIDS
= CFSTR("IDS");
39 const CFStringRef SOSTransportMessageTypeIDSV2
= CFSTR("IDS2.0");
40 const CFStringRef SOSTransportMessageTypeKVS
= CFSTR("KVS");
41 const CFStringRef kSOSDSIDKey
= CFSTR("AccountDSID");
42 const CFStringRef kSOSEscrowRecord
= CFSTR("EscrowRecord");
43 const CFStringRef kSOSUnsyncedViewsKey
= CFSTR("unsynced");
44 const CFStringRef kSOSPendingEnableViewsToBeSetKey
= CFSTR("pendingEnableViews");
45 const CFStringRef kSOSPendingDisableViewsToBeSetKey
= CFSTR("pendingDisableViews");
46 const CFStringRef kSOSTestV2Settings
= CFSTR("v2dictionary");
47 const CFStringRef kSOSRecoveryKey
= CFSTR("RecoveryKey");
48 const CFStringRef kSOSRecoveryRing
= CFSTR("RecoveryRing");
49 const CFStringRef kSOSAccountUUID
= CFSTR("UUID");
51 #define DATE_LENGTH 25
52 const CFStringRef kSOSAccountDebugScope
= CFSTR("Scope");
54 bool SOSAccountEnsureFactoryCircles(SOSAccountRef a
)
57 CFStringRef circle_name
= NULL
;
59 require_quiet(a
, xit
);
60 require_quiet(a
->factory
, xit
);
62 circle_name
= SOSDataSourceFactoryCopyName(a
->factory
);
63 require(circle_name
, xit
);
65 SOSAccountEnsureCircle(a
, circle_name
, NULL
);
70 // We don't own name, so don't release it.
71 CFReleaseNull(circle_name
);
76 SOSAccountRef
SOSAccountCreateBasic(CFAllocatorRef allocator
,
77 CFDictionaryRef gestalt
,
78 SOSDataSourceFactoryRef factory
) {
79 SOSAccountRef a
= CFTypeAllocate(SOSAccount
, struct __OpaqueSOSAccount
, allocator
);
81 a
->queue
= dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL
);
83 a
->gestalt
= CFRetainSafe(gestalt
);
85 a
->trusted_circle
= NULL
;
86 a
->backups
= CFDictionaryCreateMutableForCFTypes(allocator
);
87 a
->my_identity
= NULL
;
88 a
->retirees
= CFSetCreateMutableForSOSPeerInfosByID(allocator
);
90 a
->factory
= factory
; // We adopt the factory. kthanksbai.
92 a
->isListeningForSync
= false;
94 a
->_user_private
= NULL
;
95 a
->_password_tmp
= NULL
;
96 a
->user_private_timer
= NULL
;
97 a
->lock_notification_token
= NOTIFY_TOKEN_INVALID
;
99 a
->change_blocks
= CFArrayCreateMutableForCFTypes(allocator
);
100 a
->waitForInitialSync_blocks
= NULL
;
101 a
->departure_code
= kSOSNeverAppliedToCircle
;
103 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
104 a
->circle_transport
= NULL
;
105 a
->kvs_message_transport
= NULL
;
106 a
->ids_message_transport
= NULL
;
107 a
->expansion
= CFDictionaryCreateMutableForCFTypes(allocator
);
109 SOSAccountAddRingDictionary(a
);
112 a
->circle_rings_retirements_need_attention
= false;
113 a
->engine_peer_state_needs_repair
= false;
114 a
->key_interests_need_updating
= false;
121 // MARK: Transactional
124 void SOSAccountWithTransaction_Locked(SOSAccountRef account
, void (^action
)(SOSAccountRef account
, SOSAccountTransactionRef txn
)) {
125 SOSAccountTransactionRef at
= SOSAccountTransactionCreate(account
);
127 SOSAccountTransactionFinish(at
);
133 void SOSAccountWithTransaction(SOSAccountRef account
, bool sync
, void (^action
)(SOSAccountRef account
, SOSAccountTransactionRef txn
)) {
134 dispatch_block_t with_transaction
= ^{
135 SOSAccountWithTransaction_Locked(account
, action
);
139 dispatch_sync(SOSAccountGetQueue(account
), with_transaction
);
141 dispatch_async(SOSAccountGetQueue(account
), with_transaction
);
145 void SOSAccountWithTransactionSync(SOSAccountRef account
, void (^action
)(SOSAccountRef account
, SOSAccountTransactionRef txn
)) {
146 SOSAccountWithTransaction(account
, true, action
);
149 void SOSAccountWithTransactionAsync(SOSAccountRef account
, bool sync
, void (^action
)(SOSAccountRef account
, SOSAccountTransactionRef txn
)) {
150 SOSAccountWithTransaction(account
, false, action
);
157 void SOSAccountSetSaveBlock(SOSAccountRef account
, SOSAccountSaveBlock saveBlock
) {
158 CFAssignRetained(account
->saveBlock
, Block_copy(saveBlock
));
161 void SOSAccountFlattenToSaveBlock(SOSAccountRef account
) {
162 if (account
->saveBlock
) {
163 CFErrorRef localError
= NULL
;
164 CFDataRef saveData
= SOSAccountCopyEncodedData(account
, kCFAllocatorDefault
, &localError
);
166 (account
->saveBlock
)(saveData
, localError
);
168 CFReleaseNull(saveData
);
169 CFReleaseNull(localError
);
174 // MARK: Security Properties
177 SOSSecurityPropertyResultCode
SOSAccountUpdateSecurityProperty(SOSAccountRef account
, CFStringRef property
, SOSSecurityPropertyActionCode actionCode
, CFErrorRef
*error
) {
178 SOSSecurityPropertyResultCode retval
= kSOSCCGeneralSecurityPropertyError
;
179 bool updateCircle
= false;
180 require_action_quiet(account
->trusted_circle
, errOut
, SOSCreateError(kSOSErrorNoCircle
, CFSTR("No Trusted Circle"), NULL
, error
));
181 require_action_quiet(account
->my_identity
, errOut
, SOSCreateError(kSOSErrorPeerNotFound
, CFSTR("No Peer for Account"), NULL
, error
));
182 retval
= SOSFullPeerInfoUpdateSecurityProperty(account
->my_identity
, actionCode
, property
, error
);
184 if(actionCode
== kSOSCCSecurityPropertyEnable
&& retval
== kSOSCCSecurityPropertyValid
) {
186 } else if(actionCode
== kSOSCCSecurityPropertyDisable
&& retval
== kSOSCCSecurityPropertyNotValid
) {
188 } else if(actionCode
== kSOSCCSecurityPropertyPending
) {
193 SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle_to_change
) {
194 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for security property change");
195 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSFullPeerInfoGetPeerInfo(account
->my_identity
));
203 SOSSecurityPropertyResultCode
SOSAccountSecurityPropertyStatus(SOSAccountRef account
, CFStringRef property
, CFErrorRef
*error
) {
204 SOSSecurityPropertyResultCode retval
= kSOSCCGeneralViewError
;
205 require_action_quiet(account
->trusted_circle
, errOut
, SOSCreateError(kSOSErrorNoCircle
, CFSTR("No Trusted Circle"), NULL
, error
));
206 require_action_quiet(account
->my_identity
, errOut
, SOSCreateError(kSOSErrorPeerNotFound
, CFSTR("No Peer for Account"), NULL
, error
));
207 retval
= SOSFullPeerInfoSecurityPropertyStatus(account
->my_identity
, property
, error
);
212 bool SOSAccountUpdateGestalt(SOSAccountRef account
, CFDictionaryRef new_gestalt
)
214 if (CFEqualSafe(new_gestalt
, account
->gestalt
))
217 if (account
->trusted_circle
&& account
->my_identity
218 && SOSFullPeerInfoUpdateGestalt(account
->my_identity
, new_gestalt
, NULL
)) {
219 SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle_to_change
) {
220 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
221 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSAccountGetMyPeerInfo(account
));
225 CFRetainAssign(account
->gestalt
, new_gestalt
);
229 CFDictionaryRef
SOSAccountCopyGestalt(SOSAccountRef account
) {
230 return CFDictionaryCreateCopy(kCFAllocatorDefault
, account
->gestalt
);
233 bool SOSAccountUpdateV2Dictionary(SOSAccountRef account
, CFDictionaryRef newV2Dict
) {
234 if(!newV2Dict
) return true;
235 SOSAccountSetValue(account
, kSOSTestV2Settings
, newV2Dict
, NULL
);
236 if (account
->trusted_circle
&& account
->my_identity
237 && SOSFullPeerInfoUpdateV2Dictionary(account
->my_identity
, newV2Dict
, NULL
)) {
238 SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle_to_change
) {
239 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
240 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSAccountGetMyPeerInfo(account
));
246 CFDictionaryRef
SOSAccountCopyV2Dictionary(SOSAccountRef account
) {
247 CFDictionaryRef v2dict
= SOSAccountGetValue(account
, kSOSTestV2Settings
, NULL
);
248 return CFDictionaryCreateCopy(kCFAllocatorDefault
, v2dict
);
251 static bool SOSAccountUpdateDSID(SOSAccountRef account
, CFStringRef dsid
){
252 SOSAccountSetValue(account
, kSOSDSIDKey
, dsid
, NULL
);
253 //send new DSID over account changed
254 SOSTransportCircleSendOfficialDSID(account
->circle_transport
, dsid
, NULL
);
258 void SOSAccountAssertDSID(SOSAccountRef account
, CFStringRef dsid
) {
259 CFStringRef accountDSID
= SOSAccountGetValue(account
, kSOSDSIDKey
, NULL
);
260 if(accountDSID
== NULL
) {
261 secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid
);
263 SOSAccountUpdateDSID(account
, dsid
);
264 } else if(CFStringCompare(dsid
, accountDSID
, 0) != kCFCompareEqualTo
) {
265 secnotice("updates", "Changing DSID from: %@ to %@", accountDSID
, dsid
);
267 //DSID has changed, blast the account!
268 SOSAccountSetToNew(account
);
270 //update DSID to the new DSID
271 SOSAccountUpdateDSID(account
, dsid
);
273 secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID
, dsid
);
277 bool SOSAccountUpdateFullPeerInfo(SOSAccountRef account
, CFSetRef minimumViews
, CFSetRef excludedViews
) {
278 if (account
->trusted_circle
&& account
->my_identity
) {
279 if(SOSFullPeerInfoUpdateToCurrent(account
->my_identity
, minimumViews
, excludedViews
)) {
280 SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle_to_change
) {
281 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
282 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSFullPeerInfoGetPeerInfo(account
->my_identity
));
290 void SOSAccountPendEnableViewSet(SOSAccountRef account
, CFSetRef enabledViews
)
292 if(CFSetGetValue(enabledViews
, kSOSViewKeychainV0
) != NULL
) secnotice("viewChange", "Warning, attempting to Add KeychainV0");
294 SOSAccountValueUnionWith(account
, kSOSPendingEnableViewsToBeSetKey
, enabledViews
);
295 SOSAccountValueSubtractFrom(account
, kSOSPendingDisableViewsToBeSetKey
, enabledViews
);
299 void SOSAccountPendDisableViewSet(SOSAccountRef account
, CFSetRef disabledViews
)
301 SOSAccountValueUnionWith(account
, kSOSPendingDisableViewsToBeSetKey
, disabledViews
);
302 SOSAccountValueSubtractFrom(account
, kSOSPendingEnableViewsToBeSetKey
, disabledViews
);
305 static SOSViewResultCode
SOSAccountVirtualV0Behavior(SOSAccountRef account
, SOSViewActionCode actionCode
) {
306 SOSViewResultCode retval
= kSOSCCGeneralViewError
;
307 // The V0 view switches on and off all on it's own, we allow people the delusion
308 // of control and status if it's what we're stuck at., otherwise error.
309 if (SOSAccountSyncingV0(account
)) {
310 require_action_quiet(actionCode
== kSOSCCViewDisable
, errOut
, CFSTR("Can't disable V0 view and it's on right now"));
311 retval
= kSOSCCViewMember
;
313 require_action_quiet(actionCode
== kSOSCCViewEnable
, errOut
, CFSTR("Can't enable V0 and it's off right now"));
314 retval
= kSOSCCViewNotMember
;
321 SOSViewResultCode
SOSAccountUpdateView(SOSAccountRef account
, CFStringRef viewname
, SOSViewActionCode actionCode
, CFErrorRef
*error
) {
322 SOSViewResultCode retval
= kSOSCCGeneralViewError
;
323 SOSViewResultCode currentStatus
= kSOSCCGeneralViewError
;
324 bool alreadyInSync
= SOSAccountHasCompletedInitialSync(account
);
326 bool updateCircle
= false;
327 require_action_quiet(account
->trusted_circle
, errOut
, SOSCreateError(kSOSErrorNoCircle
, CFSTR("No Trusted Circle"), NULL
, error
));
328 require_action_quiet(account
->my_identity
, errOut
, SOSCreateError(kSOSErrorPeerNotFound
, CFSTR("No Peer for Account"), NULL
, error
));
329 require_action_quiet((actionCode
== kSOSCCViewEnable
) || (actionCode
== kSOSCCViewDisable
), errOut
, CFSTR("Invalid View Action"));
330 currentStatus
= SOSAccountViewStatus(account
, viewname
, error
);
331 require_action_quiet((currentStatus
== kSOSCCViewNotMember
) || (currentStatus
== kSOSCCViewMember
), errOut
, CFSTR("View Membership Not Actionable"));
333 if (CFEqualSafe(viewname
, kSOSViewKeychainV0
)) {
334 retval
= SOSAccountVirtualV0Behavior(account
, actionCode
);
335 } else if (SOSAccountSyncingV0(account
) && SOSViewsIsV0Subview(viewname
)) {
336 // Subviews of V0 syncing can't be turned off if V0 is on.
337 require_action_quiet(actionCode
= kSOSCCViewDisable
, errOut
, CFSTR("Have V0 peer can't disable"));
338 retval
= kSOSCCViewMember
;
340 CFMutableSetRef pendingSet
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
341 CFSetAddValue(pendingSet
, viewname
);
343 if(actionCode
== kSOSCCViewEnable
&& currentStatus
== kSOSCCViewNotMember
) {
345 retval
= SOSFullPeerInfoUpdateViews(account
->my_identity
, actionCode
, viewname
, error
);
346 if(retval
== kSOSCCViewMember
) updateCircle
= true;
348 SOSAccountPendEnableViewSet(account
, pendingSet
);
349 retval
= kSOSCCViewMember
;
350 updateCircle
= false;
352 } else if(actionCode
== kSOSCCViewDisable
&& currentStatus
== kSOSCCViewMember
) {
354 retval
= SOSFullPeerInfoUpdateViews(account
->my_identity
, actionCode
, viewname
, error
);
355 if(retval
== kSOSCCViewNotMember
) updateCircle
= true;
357 SOSAccountPendDisableViewSet(account
, pendingSet
);
358 retval
= kSOSCCViewNotMember
;
359 updateCircle
= false;
362 retval
= currentStatus
;
365 CFReleaseNull(pendingSet
);
368 SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle_to_change
) {
369 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views change");
370 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSFullPeerInfoGetPeerInfo(account
->my_identity
));
379 SOSViewResultCode
SOSAccountViewStatus(SOSAccountRef account
, CFStringRef viewname
, CFErrorRef
*error
) {
380 SOSViewResultCode retval
= kSOSCCGeneralViewError
;
381 require_action_quiet(account
->trusted_circle
, errOut
, SOSCreateError(kSOSErrorNoCircle
, CFSTR("No Trusted Circle"), NULL
, error
));
382 require_action_quiet(account
->my_identity
, errOut
, SOSCreateError(kSOSErrorPeerNotFound
, CFSTR("No Peer for Account"), NULL
, error
));
384 if (SOSAccountValueSetContainsValue(account
, kSOSPendingEnableViewsToBeSetKey
, viewname
)) {
385 retval
= kSOSCCViewMember
;
386 } else if (SOSAccountValueSetContainsValue(account
, kSOSPendingDisableViewsToBeSetKey
, viewname
)) {
387 retval
= kSOSCCViewNotMember
;
389 retval
= SOSFullPeerInfoViewStatus(account
->my_identity
, viewname
, error
);
392 // If that doesn't say we're a member and this view is a V0 subview, and we're syncing V0 views we are a member
393 if (retval
!= kSOSCCViewMember
) {
394 if ((CFEqualSafe(viewname
, kSOSViewKeychainV0
) || SOSViewsIsV0Subview(viewname
))
395 && SOSAccountSyncingV0(account
)) {
396 retval
= kSOSCCViewMember
;
400 // If we're only an applicant we report pending if we would be a view member
401 if (retval
== kSOSCCViewMember
) {
402 bool isApplicant
= SOSCircleHasApplicant(account
->trusted_circle
, SOSAccountGetMyPeerInfo(account
), error
);
404 retval
= kSOSCCViewPending
;
412 static void dumpViewSet(CFStringRef label
, CFSetRef views
) {
414 CFStringSetPerformWithDescription(views
, ^(CFStringRef description
) {
415 secnotice("circleChange", "%@ list: %@", label
, description
);
418 secnotice("circleChange", "No %@ list provided.", label
);
422 static bool SOSAccountScreenViewListForValidV0(SOSAccountRef account
, CFMutableSetRef viewSet
, SOSViewActionCode actionCode
) {
424 if(viewSet
&& CFSetContainsValue(viewSet
, kSOSViewKeychainV0
)) {
425 retval
= SOSAccountVirtualV0Behavior(account
, actionCode
) != kSOSCCGeneralViewError
;
426 CFSetRemoveValue(viewSet
, kSOSViewKeychainV0
);
431 bool SOSAccountUpdateViewSets(SOSAccountRef account
, CFSetRef origEnabledViews
, CFSetRef origDisabledViews
) {
433 bool updateCircle
= false;
434 SOSPeerInfoRef pi
= NULL
;
435 CFMutableSetRef enabledViews
= NULL
;
436 CFMutableSetRef disabledViews
= NULL
;
437 if(origEnabledViews
) enabledViews
= CFSetCreateMutableCopy(kCFAllocatorDefault
, 0, origEnabledViews
);
438 if(origDisabledViews
) disabledViews
= CFSetCreateMutableCopy(kCFAllocatorDefault
, 0, origDisabledViews
);
439 dumpViewSet(CFSTR("Enabled"), enabledViews
);
440 dumpViewSet(CFSTR("Disabled"), disabledViews
);
442 require_action_quiet(account
->trusted_circle
, errOut
, secnotice("views", "Attempt to set viewsets with no trusted circle"));
444 // Make sure we have a peerInfo capable of supporting views.
445 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInfo(account
);
446 require_action_quiet(fpi
, errOut
, secnotice("views", "Attempt to set viewsets with no fullPeerInfo"));
447 require_action_quiet(enabledViews
|| disabledViews
, errOut
, secnotice("views", "No work to do"));
449 pi
= SOSPeerInfoCreateCopy(kCFAllocatorDefault
, SOSFullPeerInfoGetPeerInfo(fpi
), NULL
);
451 require_action_quiet(pi
, errOut
, secnotice("views", "Couldn't copy PeerInfoRef"));
453 if(!SOSPeerInfoVersionIsCurrent(pi
)) {
454 CFErrorRef updateFailure
= NULL
;
455 require_action_quiet(SOSPeerInfoUpdateToV2(pi
, &updateFailure
), errOut
,
456 (secnotice("views", "Unable to update peer to V2- can't update views: %@", updateFailure
), (void) CFReleaseNull(updateFailure
)));
457 secnotice("V2update", "Updating PeerInfo to V2 within SOSAccountUpdateViewSets");
461 CFStringSetPerformWithDescription(enabledViews
, ^(CFStringRef description
) {
462 secnotice("viewChange", "Enabling %@", description
);
465 CFStringSetPerformWithDescription(disabledViews
, ^(CFStringRef description
) {
466 secnotice("viewChange", "Disabling %@", description
);
469 require_action_quiet(SOSAccountScreenViewListForValidV0(account
, enabledViews
, kSOSCCViewEnable
), errOut
, secnotice("viewChange", "Bad view change (enable) with kSOSViewKeychainV0"));
470 require_action_quiet(SOSAccountScreenViewListForValidV0(account
, disabledViews
, kSOSCCViewDisable
), errOut
, secnotice("viewChange", "Bad view change (disable) with kSOSViewKeychainV0"));
472 if(SOSAccountHasCompletedInitialSync(account
)) {
473 if(enabledViews
) updateCircle
|= SOSViewSetEnable(pi
, enabledViews
);
474 if(disabledViews
) updateCircle
|= SOSViewSetDisable(pi
, disabledViews
);
477 //hold on to the views and enable them later
478 if(enabledViews
) SOSAccountPendEnableViewSet(account
, enabledViews
);
479 if(disabledViews
) SOSAccountPendDisableViewSet(account
, disabledViews
);
484 /* UPDATE FULLPEERINFO VIEWS */
485 require_quiet(SOSFullPeerInfoUpdateToThisPeer(fpi
, pi
, NULL
), errOut
);
487 require_quiet(SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle_to_change
) {
488 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views or peerInfo change");
489 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSFullPeerInfoGetPeerInfo(account
->my_identity
));
492 // Make sure we update the engine
493 account
->circle_rings_retirements_need_attention
= true;
497 CFReleaseNull(enabledViews
);
498 CFReleaseNull(disabledViews
);
504 SOSAccountRef
SOSAccountCreate(CFAllocatorRef allocator
,
505 CFDictionaryRef gestalt
,
506 SOSDataSourceFactoryRef factory
) {
507 SOSAccountRef a
= SOSAccountCreateBasic(allocator
, gestalt
, factory
);
509 SOSAccountEnsureFactoryCircles(a
);
511 SOSAccountEnsureUUID(a
);
513 a
->key_interests_need_updating
= true;
518 static void SOSAccountDestroy(CFTypeRef aObj
) {
519 SOSAccountRef a
= (SOSAccountRef
) aObj
;
521 // We don't own the factory, merely have a reference to the singleton
525 SOSAccountCancelSyncChecking(a
);
527 SOSEngineRef engine
= SOSDataSourceFactoryGetEngineForDataSourceName(a
->factory
, SOSCircleGetName(a
->trusted_circle
), NULL
);
530 SOSEngineSetSyncCompleteListenerQueue(engine
, NULL
);
532 dispatch_sync(a
->queue
, ^{
533 CFReleaseNull(a
->gestalt
);
535 CFReleaseNull(a
->my_identity
);
536 CFReleaseNull(a
->trusted_circle
);
537 CFReleaseNull(a
->backups
);
538 CFReleaseNull(a
->retirees
);
540 a
->user_public_trusted
= false;
541 CFReleaseNull(a
->user_public
);
542 CFReleaseNull(a
->user_key_parameters
);
544 SOSAccountPurgePrivateCredential(a
);
545 CFReleaseNull(a
->previous_public
);
546 CFReleaseNull(a
->_user_private
);
547 CFReleaseNull(a
->_password_tmp
);
549 a
->departure_code
= kSOSNeverAppliedToCircle
;
550 CFReleaseNull(a
->kvs_message_transport
);
551 CFReleaseNull(a
->ids_message_transport
);
552 CFReleaseNull(a
->key_transport
);
553 CFReleaseNull(a
->circle_transport
);
554 dispatch_release(a
->queue
);
556 dispatch_release(a
->user_private_timer
);
557 CFReleaseNull(a
->change_blocks
);
558 CFReleaseNull(a
->waitForInitialSync_blocks
);
559 CFReleaseNull(a
->expansion
);
561 CFReleaseNull(a
->saveBlock
);
562 CFReleaseNull(a
->deviceID
);
566 static OSStatus
do_delete(CFDictionaryRef query
) {
569 result
= SecItemDelete(query
);
571 secerror("SecItemDelete: %d", (int)result
);
577 do_keychain_delete_aks_bags()
580 CFDictionaryRef item
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
581 kSecClass
, kSecClassGenericPassword
,
582 kSecAttrAccessGroup
, CFSTR("com.apple.sbd"),
583 kSecAttrAccount
, CFSTR("SecureBackupPublicKeybag"),
584 kSecAttrService
, CFSTR("SecureBackupService"),
585 kSecAttrSynchronizable
, kCFBooleanTrue
,
586 kSecUseTombstones
, kCFBooleanFalse
,
589 result
= do_delete(item
);
596 do_keychain_delete_identities()
599 CFDictionaryRef item
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
600 kSecClass
, kSecClassKey
,
601 kSecAttrSynchronizable
, kCFBooleanTrue
,
602 kSecUseTombstones
, kCFBooleanFalse
,
603 kSecAttrAccessGroup
, CFSTR("com.apple.security.sos"),
606 result
= do_delete(item
);
613 do_keychain_delete_lakitu()
616 CFDictionaryRef item
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
617 kSecClass
, kSecClassGenericPassword
,
618 kSecAttrSynchronizable
, kCFBooleanTrue
,
619 kSecUseTombstones
, kCFBooleanFalse
,
620 kSecAttrAccessGroup
, CFSTR("com.apple.lakitu"),
621 kSecAttrAccount
, CFSTR("EscrowServiceBypassToken"),
622 kSecAttrService
, CFSTR("EscrowService"),
625 result
= do_delete(item
);
632 do_keychain_delete_sbd()
635 CFDictionaryRef item
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
636 kSecClass
, kSecClassGenericPassword
,
637 kSecAttrSynchronizable
, kCFBooleanTrue
,
638 kSecUseTombstones
, kCFBooleanFalse
,
639 kSecAttrAccessGroup
, CFSTR("com.apple.sbd"),
642 result
= do_delete(item
);
648 void SOSAccountSetToNew(SOSAccountRef a
) {
649 secnotice("accountChange", "Setting Account to New");
652 CFReleaseNull(a
->my_identity
);
653 CFReleaseNull(a
->trusted_circle
);
654 CFReleaseNull(a
->backups
);
655 CFReleaseNull(a
->retirees
);
657 CFReleaseNull(a
->user_key_parameters
);
658 CFReleaseNull(a
->user_public
);
659 CFReleaseNull(a
->previous_public
);
660 CFReleaseNull(a
->_user_private
);
661 CFReleaseNull(a
->_password_tmp
);
663 CFReleaseNull(a
->key_transport
);
664 CFReleaseNull(a
->circle_transport
);
665 CFReleaseNull(a
->kvs_message_transport
);
666 CFReleaseNull(a
->ids_message_transport
);
667 CFReleaseNull(a
->expansion
);
668 CFReleaseNull(a
->deviceID
);
670 /* remove all syncable items */
671 result
= do_keychain_delete_aks_bags(); (void) result
;
672 secdebug("set to new", "result for deleting aks bags: %d", result
);
674 result
= do_keychain_delete_identities(); (void) result
;
675 secdebug("set to new", "result for deleting identities: %d", result
);
677 result
= do_keychain_delete_lakitu(); (void) result
;
678 secdebug("set to new", "result for deleting lakitu: %d", result
);
680 result
= do_keychain_delete_sbd(); (void) result
;
681 secdebug("set to new", "result for deleting sbd: %d", result
);
683 a
->user_public_trusted
= false;
684 a
->departure_code
= kSOSNeverAppliedToCircle
;
686 if (a
->user_private_timer
) {
687 dispatch_source_cancel(a
->user_private_timer
);
688 dispatch_release(a
->user_private_timer
);
689 a
->user_private_timer
= NULL
;
690 xpc_transaction_end();
693 if (a
->lock_notification_token
!= NOTIFY_TOKEN_INVALID
) {
694 notify_cancel(a
->lock_notification_token
);
695 a
->lock_notification_token
= NOTIFY_TOKEN_INVALID
;
702 // update_interest_block;
705 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
706 a
->circle_transport
= NULL
;
707 a
->kvs_message_transport
= NULL
;
708 a
->ids_message_transport
= NULL
;
710 a
->backups
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
712 a
->retirees
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
713 a
->expansion
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
714 SOSAccountAddRingDictionary(a
);
716 SOSAccountEnsureFactoryCircles(a
); // Does rings too
718 // By resetting our expansion dictionary we've reset our UUID, so we'll be notified properly
719 SOSAccountEnsureUUID(a
);
721 a
->key_interests_need_updating
= true;
724 bool SOSAccountIsNew(SOSAccountRef account
, CFErrorRef
*error
){
726 require_quiet(account
->user_public_trusted
== false, exit
);
727 require_quiet(account
->departure_code
== kSOSNeverAppliedToCircle
, exit
);
728 require_quiet(account
->user_private_timer
== NULL
, exit
);
729 require_quiet(account
->lock_notification_token
== NOTIFY_TOKEN_INVALID
, exit
);
730 require_quiet (CFDictionaryGetCount(account
->backups
) == 0, exit
);
731 require_quiet(CFSetGetCount(account
->retirees
) == 0, exit
);
738 static CFStringRef
SOSAccountCopyFormatDescription(CFTypeRef aObj
, CFDictionaryRef formatOptions
) {
739 SOSAccountRef a
= (SOSAccountRef
) aObj
;
741 CFStringRef gestaltDescription
= CFDictionaryCopyCompactDescription(a
->gestalt
);
743 CFStringRef result
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SOSAccount@%p: %c%c%c%c%c G: %@ Me: %@ C: %@ >"), a
,
744 a
->user_public
? 'P' : 'p',
745 a
->user_public_trusted
? 'T' : 't',
746 a
->isListeningForSync
? 'L' : 'l',
747 SOSAccountHasCompletedInitialSync(a
) ? 'C' : 'c',
748 SOSAccountHasCompletedRequiredBackupSync(a
) ? 'B' : 'b',
749 gestaltDescription
, a
->my_identity
, a
->trusted_circle
);
751 CFReleaseNull(gestaltDescription
);
756 CFStringRef
SOSAccountCreateCompactDescription(SOSAccountRef a
) {
758 CFStringRef gestaltDescription
= CFDictionaryCopySuperCompactDescription(a
->gestalt
);
760 CFStringRef result
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%@"), gestaltDescription
);
762 CFReleaseNull(gestaltDescription
);
767 static Boolean
SOSAccountCompare(CFTypeRef lhs
, CFTypeRef rhs
)
769 SOSAccountRef laccount
= (SOSAccountRef
) lhs
;
770 SOSAccountRef raccount
= (SOSAccountRef
) rhs
;
772 return CFEqualSafe(laccount
->gestalt
, raccount
->gestalt
)
773 && CFEqualSafe(laccount
->trusted_circle
, raccount
->trusted_circle
)
774 && CFEqualSafe(laccount
->expansion
, raccount
->expansion
)
775 && CFEqualSafe(laccount
->my_identity
, raccount
->my_identity
);
778 dispatch_queue_t
SOSAccountGetQueue(SOSAccountRef account
) {
779 return account
->queue
;
782 void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account
){
783 account
->user_public_trusted
= true;
786 SOSFullPeerInfoRef
SOSAccountCopyAccountIdentityPeerInfo(SOSAccountRef account
, CFAllocatorRef allocator
, CFErrorRef
* error
)
788 return CFRetainSafe(account
->my_identity
);
793 bool SOSAccountCleanupAfterPeer(SOSAccountRef account
, size_t seconds
, SOSCircleRef circle
,
794 SOSPeerInfoRef cleanupPeer
, CFErrorRef
* error
)
798 SOSPeerInfoRef myPeerInfo
= SOSFullPeerInfoGetPeerInfo(account
->my_identity
);
799 require_action_quiet(account
->my_identity
&& myPeerInfo
, xit
, SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("I have no peer")));
800 require_quiet(SOSCircleHasActivePeer(circle
, SOSFullPeerInfoGetPeerInfo(account
->my_identity
), error
), xit
);
802 CFStringRef cleanupPeerID
= SOSPeerInfoGetPeerID(cleanupPeer
);
804 CFStringRef circle_name
= SOSCircleGetName(circle
);
806 CFMutableDictionaryRef circleToPeerIDs
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
807 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs
, circle_name
), cleanupPeerID
);
809 CFErrorRef localError
= NULL
;
810 if (!(success
&= SOSTransportMessageCleanupAfterPeerMessages(account
->kvs_message_transport
, circleToPeerIDs
, &localError
))) {
811 secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID
, localError
);
814 if (account
->ids_message_transport
&& !SOSTransportMessageCleanupAfterPeerMessages(account
->ids_message_transport
, circleToPeerIDs
, &localError
)) {
815 secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID
, localError
);
818 CFReleaseNull(localError
);
820 if((success
&= SOSPeerInfoRetireRetirementTicket(seconds
, cleanupPeer
))) {
821 if (!(success
&= SOSTransportCircleExpireRetirementRecords(account
->circle_transport
, circleToPeerIDs
, &localError
))) {
822 secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID
, localError
);
825 CFReleaseNull(localError
);
826 CFReleaseNull(circleToPeerIDs
);
832 bool SOSAccountCleanupRetirementTickets(SOSAccountRef account
, size_t seconds
, CFErrorRef
* error
) {
833 CFMutableSetRef retirees_to_remove
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
835 __block
bool success
= true;
837 CFSetForEach(account
->retirees
, ^(const void *value
) {
838 SOSPeerInfoRef retiree
= (SOSPeerInfoRef
) value
;
841 // Remove the entry if it's not a retired peer or if it's retirment ticket has expired AND he's no longer in the circle.
842 if (!SOSPeerInfoIsRetirementTicket(retiree
) ||
843 (SOSPeerInfoRetireRetirementTicket(seconds
, retiree
) && !SOSCircleHasActivePeer(account
->trusted_circle
, retiree
, NULL
))) {
844 CFSetAddValue(retirees_to_remove
, retiree
);
849 CFMutableArrayRef retirees_to_cleanup
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
850 CFSetForEach(retirees_to_remove
, ^(const void *value
) {
851 CFArrayAppendValue(retirees_to_cleanup
, value
);
852 CFSetRemoveValue(account
->retirees
, value
);
855 CFReleaseNull(retirees_to_remove
);
857 CFDictionaryRef retirements_to_remove
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
858 SOSCircleGetName(account
->trusted_circle
), retirees_to_cleanup
,
861 CFReleaseNull(retirees_to_cleanup
);
863 success
= SOSTransportCircleExpireRetirementRecords(account
->circle_transport
, retirements_to_remove
, error
);
865 CFReleaseNull(retirements_to_remove
);
870 bool SOSAccountScanForRetired(SOSAccountRef account
, SOSCircleRef circle
, CFErrorRef
*error
) {
871 SOSCircleForEachRetiredPeer(circle
, ^(SOSPeerInfoRef peer
) {
872 CFSetSetValue(account
->retirees
, peer
);
873 CFErrorRef cleanupError
= NULL
;
874 if (!SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, peer
, &cleanupError
)) {
875 secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError
);
877 CFReleaseSafe(cleanupError
);
882 SOSCircleRef
SOSAccountCloneCircleWithRetirement(SOSAccountRef account
, SOSCircleRef starting_circle
, CFErrorRef
*error
) {
883 SOSCircleRef new_circle
= SOSCircleCopyCircle(NULL
, starting_circle
, error
);
884 SOSFullPeerInfoRef meFull
= SOSAccountGetMyFullPeerInfo(account
);
885 SOSPeerInfoRef me
= SOSFullPeerInfoGetPeerInfo(meFull
);
886 bool iAmApplicant
= me
&& SOSCircleHasApplicant(new_circle
, me
, NULL
);
888 if(!new_circle
) return NULL
;
889 __block
bool workDone
= false;
890 if (account
->retirees
) {
891 CFSetForEach(account
->retirees
, ^(const void* value
) {
892 SOSPeerInfoRef pi
= (SOSPeerInfoRef
) value
;
893 if (isSOSPeerInfo(pi
)) {
894 SOSCircleUpdatePeerInfo(new_circle
, pi
);
900 if(workDone
&& SOSCircleCountPeers(new_circle
) == 0) {
901 SecKeyRef userPrivKey
= SOSAccountGetPrivateCredential(account
, error
);
905 secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
906 if(!SOSCircleResetToOffering(new_circle
, userPrivKey
, meFull
, error
) ||
907 !SOSAccountAddiCloudIdentity(account
, new_circle
, userPrivKey
, error
)) {
908 CFReleaseNull(new_circle
);
912 // Do nothing. We can't resetToOffering without a userPrivKey. If we were to resetToEmpty
913 // we won't push the result later in handleUpdateCircle. If we leave the circle as it is
914 // we have a chance to set things right with a SetCreds/Join sequence. This will cause
915 // handleUpdateCircle to return false.
916 CFReleaseNull(new_circle
);
920 // This case is when we aren't an applicant and the circle is retirement-empty.
921 secnotice("resetToEmpty", "Reset to empty with last retirement");
922 SOSCircleResetToEmpty(new_circle
, NULL
);
930 // MARK: Circle Membership change notificaion
933 void SOSAccountAddChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
934 SOSAccountCircleMembershipChangeBlock copy
= Block_copy(changeBlock
);
935 CFArrayAppendValue(a
->change_blocks
, copy
);
939 void SOSAccountRemoveChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
940 CFArrayRemoveAllValue(a
->change_blocks
, changeBlock
);
943 void SOSAccountAddSyncablePeerBlock(SOSAccountRef a
, CFStringRef ds_name
, SOSAccountSyncablePeersBlock changeBlock
) {
944 if (!changeBlock
) return;
946 CFRetainSafe(ds_name
);
947 SOSAccountCircleMembershipChangeBlock block_to_register
= ^void (SOSCircleRef new_circle
,
948 CFSetRef added_peers
, CFSetRef removed_peers
,
949 CFSetRef added_applicants
, CFSetRef removed_applicants
) {
951 if (!CFEqualSafe(SOSCircleGetName(new_circle
), ds_name
))
954 SOSPeerInfoRef myPi
= SOSFullPeerInfoGetPeerInfo(a
->my_identity
);
955 CFStringRef myPi_id
= myPi
? SOSPeerInfoGetPeerID(myPi
) : NULL
;
957 CFMutableArrayRef peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
958 CFMutableArrayRef added_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
959 CFMutableArrayRef removed_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
961 if (SOSCircleHasPeer(new_circle
, myPi
, NULL
)) {
962 SOSCircleForEachPeer(new_circle
, ^(SOSPeerInfoRef peer
) {
963 CFArrayAppendValueIfNot(peer_ids
, SOSPeerInfoGetPeerID(peer
), myPi_id
);
966 CFSetForEach(added_peers
, ^(const void *value
) {
967 CFArrayAppendValueIfNot(added_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
970 CFSetForEach(removed_peers
, ^(const void *value
) {
971 CFArrayAppendValueIfNot(removed_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
975 if (CFArrayGetCount(peer_ids
) || CFSetContainsValue(removed_peers
, myPi
))
976 changeBlock(peer_ids
, added_ids
, removed_ids
);
978 CFReleaseSafe(peer_ids
);
979 CFReleaseSafe(added_ids
);
980 CFReleaseSafe(removed_ids
);
983 CFRetainSafe(changeBlock
);
984 SOSAccountAddChangeBlock(a
, block_to_register
);
986 CFSetRef empty
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
987 if (a
->trusted_circle
&& CFEqualSafe(ds_name
, SOSCircleGetName(a
->trusted_circle
))) {
988 block_to_register(a
->trusted_circle
, empty
, empty
, empty
, empty
);
990 CFReleaseSafe(empty
);
993 void SOSAccountPurgeIdentity(SOSAccountRef account
) {
994 if (account
->my_identity
) {
995 // Purge private key but don't return error if we can't.
996 CFErrorRef purgeError
= NULL
;
997 if (!SOSFullPeerInfoPurgePersistentKey(account
->my_identity
, &purgeError
)) {
998 secwarning("Couldn't purge persistent key for %@ [%@]", account
->my_identity
, purgeError
);
1000 CFReleaseNull(purgeError
);
1002 CFReleaseNull(account
->my_identity
);
1006 bool sosAccountLeaveCircle(SOSAccountRef account
, SOSCircleRef circle
, CFErrorRef
* error
) {
1007 SOSFullPeerInfoRef fpi
= account
->my_identity
;
1008 if(!fpi
) return false;
1010 CFErrorRef localError
= NULL
;
1012 bool retval
= false;
1014 SOSPeerInfoRef retire_peer
= SOSFullPeerInfoPromoteToRetiredAndCopy(fpi
, &localError
);
1016 secerror("Create ticket failed for peer %@: %@", fpi
, localError
);
1018 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
1019 if(SOSCircleHasApplicant(circle
, retire_peer
, NULL
)) {
1020 // Remove our application if we have one.
1021 SOSCircleWithdrawRequest(circle
, retire_peer
, NULL
);
1022 } else if (SOSCircleHasPeer(circle
, retire_peer
, NULL
)) {
1023 if (SOSCircleUpdatePeerInfo(circle
, retire_peer
)) {
1024 CFErrorRef cleanupError
= NULL
;
1025 if (!SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, retire_peer
, &cleanupError
)) {
1026 secerror("Error cleanup up after peer (%@): %@", retire_peer
, cleanupError
);
1028 CFReleaseSafe(cleanupError
);
1032 // Store the retirement record locally.
1033 CFSetAddValue(account
->retirees
, retire_peer
);
1035 // Write retirement to Transport
1036 CFErrorRef postError
= NULL
;
1037 if (!SOSTransportCirclePostRetirement(account
->circle_transport
, SOSCircleGetName(circle
), retire_peer
, &postError
)){
1038 secwarning("Couldn't post retirement (%@)", postError
);
1040 if(!SOSTransportCircleFlushChanges(account
->circle_transport
, &postError
)){
1041 secwarning("Couldn't flush retirement data (%@)", postError
);
1043 CFReleaseNull(postError
);
1046 SOSAccountPurgeIdentity(account
);
1050 CFReleaseNull(localError
);
1051 CFReleaseNull(retire_peer
);
1055 bool sosAccountLeaveRing(SOSAccountRef account
, SOSRingRef ring
, CFErrorRef
* error
) {
1056 SOSFullPeerInfoRef fpi
= account
->my_identity
;
1057 if(!fpi
) return false;
1058 SOSPeerInfoRef pi
= SOSFullPeerInfoGetPeerInfo(fpi
);
1059 CFStringRef peerID
= SOSPeerInfoGetPeerID(pi
);
1061 CFErrorRef localError
= NULL
;
1063 bool retval
= false;
1064 bool writeRing
= false;
1065 bool writePeerInfo
= false;
1067 if(SOSRingHasPeerID(ring
, peerID
)) {
1068 writePeerInfo
= true;
1072 // this was circle behavior - at some point
1073 if(SOSRingHasApplicant(ring
, peerID
)) {
1078 if(writePeerInfo
|| writeRing
) {
1079 SOSRingWithdraw(ring
, NULL
, fpi
, error
);
1082 // Write leave thing to Transport
1083 CFDataRef peerInfoData
= SOSFullPeerInfoCopyEncodedData(fpi
, kCFAllocatorDefault
, error
);
1084 SOSTransportCircleSendPeerInfo(account
->circle_transport
, peerID
, peerInfoData
, NULL
); // TODO: Handle errors?
1087 CFDataRef ring_data
= SOSRingCopyEncodedData(ring
, error
);
1090 SOSTransportCircleRingPostRing(account
->circle_transport
, SOSRingGetName(ring
), ring_data
, NULL
); // TODO: Handle errors?
1092 CFReleaseNull(ring_data
);
1095 CFReleaseNull(localError
);
1099 bool SOSAccountPostDebugScope(SOSAccountRef account
, CFTypeRef scope
, CFErrorRef
*error
) {
1100 bool result
= false;
1101 SOSTransportCircleRef transport
= account
->circle_transport
;
1103 result
= SOSTransportCircleSendDebugInfo(transport
, kSOSAccountDebugScope
, scope
, error
);
1109 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
1110 local value that has been overwritten by a distant value. If there is no
1111 conflict between the local and the distant values when doing the initial
1112 sync (e.g. if the cloud has no data stored or the client has not stored
1113 any data yet), you'll never see that notification.
1115 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
1116 with server but initial round trip with server does not imply
1117 NSUbiquitousKeyValueStoreInitialSyncChange.
1122 // MARK: Status summary
1125 static SOSCCStatus
SOSCCThisDeviceStatusInCircle(SOSCircleRef circle
, SOSPeerInfoRef this_peer
) {
1127 return kSOSCCNotInCircle
;
1129 if (circle
&& SOSCircleCountPeers(circle
) == 0)
1130 return kSOSCCCircleAbsent
;
1134 if(SOSPeerInfoIsRetirementTicket(this_peer
))
1135 return kSOSCCNotInCircle
;
1137 if (SOSCircleHasPeer(circle
, this_peer
, NULL
))
1138 return kSOSCCInCircle
;
1140 if (SOSCircleHasApplicant(circle
, this_peer
, NULL
))
1141 return kSOSCCRequestPending
;
1144 return kSOSCCNotInCircle
;
1147 CFStringRef
SOSAccountGetSOSCCStatusString(SOSCCStatus status
) {
1149 case kSOSCCInCircle
: return CFSTR("kSOSCCInCircle");
1150 case kSOSCCNotInCircle
: return CFSTR("kSOSCCNotInCircle");
1151 case kSOSCCRequestPending
: return CFSTR("kSOSCCRequestPending");
1152 case kSOSCCCircleAbsent
: return CFSTR("kSOSCCCircleAbsent");
1153 case kSOSCCError
: return CFSTR("kSOSCCError");
1155 return CFSTR("kSOSCCError");
1158 bool SOSAccountIsInCircle(SOSAccountRef account
, CFErrorRef
*error
) {
1159 SOSCCStatus result
= SOSAccountGetCircleStatus(account
, error
);
1161 if (result
!= kSOSCCInCircle
) {
1162 SOSErrorCreate(kSOSErrorNoCircle
, error
, NULL
, CFSTR("Not in circle"));
1169 SOSCCStatus
SOSAccountGetCircleStatus(SOSAccountRef account
, CFErrorRef
* error
) {
1170 if (!SOSAccountHasPublicKey(account
, error
)) {
1174 return SOSCCThisDeviceStatusInCircle(account
->trusted_circle
, SOSAccountGetMyPeerInfo(account
));
1178 // MARK: Account Reset Circles
1181 // This needs to be called within a SOSAccountModifyCircle() block
1183 bool SOSAccountAddiCloudIdentity(SOSAccountRef account
, SOSCircleRef circle
, SecKeyRef user_key
, CFErrorRef
*error
) {
1184 bool result
= false;
1185 SOSFullPeerInfoRef cloud_identity
= NULL
;
1186 SOSPeerInfoRef cloud_peer
= GenerateNewCloudIdentityPeerInfo(error
);
1187 require_quiet(cloud_peer
, err_out
);
1188 cloud_identity
= CopyCloudKeychainIdentity(cloud_peer
, error
);
1189 CFReleaseNull(cloud_peer
);
1190 require_quiet(cloud_identity
, err_out
);
1191 require_quiet(SOSCircleRequestAdmission(circle
, user_key
, cloud_identity
, error
), err_out
);
1192 require_quiet(SOSCircleAcceptRequest(circle
, user_key
, account
->my_identity
, SOSFullPeerInfoGetPeerInfo(cloud_identity
), error
), err_out
);
1198 bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccountRef account
, SOSCircleRef circle
, SecKeyRef privKey
, CFErrorRef
*error
) {
1199 bool retval
= false;
1200 CFMutableSetRef iCloud2Remove
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
1202 SOSCircleForEachActivePeer(circle
, ^(SOSPeerInfoRef peer
) {
1203 if(SOSPeerInfoIsCloudIdentity(peer
)) {
1204 SOSFullPeerInfoRef icfpi
= SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault
, peer
, NULL
);
1206 CFSetAddValue(iCloud2Remove
, peer
);
1208 CFReleaseNull(icfpi
);
1212 if(CFSetGetCount(iCloud2Remove
) > 0) {
1214 SOSCircleRemovePeers(circle
, privKey
, account
->my_identity
, iCloud2Remove
, error
);
1216 CFReleaseNull(iCloud2Remove
);
1220 static bool SOSAccountResetCircleToOffering(SOSAccountTransactionRef aTxn
, SecKeyRef user_key
, CFErrorRef
*error
) {
1221 SOSAccountRef account
= aTxn
->account
;
1222 bool result
= false;
1224 require(SOSAccountHasCircle(account
, error
), fail
);
1225 require(SOSAccountEnsureFullPeerAvailable(account
, error
), fail
);
1227 (void) SOSAccountResetAllRings(account
, error
);
1229 SOSAccountModifyCircle(account
, error
, ^(SOSCircleRef circle
) {
1230 bool result
= false;
1231 SOSFullPeerInfoRef cloud_identity
= NULL
;
1232 CFErrorRef localError
= NULL
;
1234 require_quiet(SOSCircleResetToOffering(circle
, user_key
, account
->my_identity
, &localError
), err_out
);
1236 account
->departure_code
= kSOSNeverLeftCircle
;
1237 require_quiet(SOSAccountAddEscrowToPeerInfo(account
, SOSAccountGetMyFullPeerInfo(account
), error
), err_out
);
1239 require_quiet(SOSAccountAddiCloudIdentity(account
, circle
, user_key
, error
), err_out
);
1241 SOSAccountPublishCloudParameters(account
, NULL
);
1244 if (result
== false)
1245 secerror("error resetting circle (%@) to offering: %@", circle
, localError
);
1246 if (localError
&& error
&& *error
== NULL
) {
1247 *error
= localError
;
1250 CFReleaseNull(localError
);
1251 CFReleaseNull(cloud_identity
);
1255 SOSAccountSetValue(account
, kSOSUnsyncedViewsKey
, kCFBooleanTrue
, NULL
);
1256 SOSAccountUpdateOutOfSyncViews(aTxn
, SOSViewsGetAllCurrent());
1265 bool SOSAccountResetToOffering(SOSAccountTransactionRef aTxn
, CFErrorRef
* error
) {
1266 SOSAccountRef account
= aTxn
->account
;
1267 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
1271 CFReleaseNull(account
->my_identity
);
1272 secnotice("resetToOffering", "Resetting circle to offering by request from client");
1274 return user_key
&& SOSAccountResetCircleToOffering(aTxn
, user_key
, error
);
1277 bool SOSAccountResetToEmpty(SOSAccountRef account
, CFErrorRef
* error
) {
1278 if (!SOSAccountHasPublicKey(account
, error
))
1280 __block
bool result
= true;
1282 result
&= SOSAccountResetAllRings(account
, error
);
1284 CFReleaseNull(account
->my_identity
);
1286 account
->departure_code
= kSOSWithdrewMembership
;
1287 secnotice("resetToEmpty", "Reset Circle to empty by client request");
1288 result
&= SOSAccountModifyCircle(account
, error
, ^(SOSCircleRef circle
) {
1289 result
= SOSCircleResetToEmpty(circle
, error
);
1294 secerror("error: %@", error
? *error
: NULL
);
1299 // MARK: start backups
1302 bool SOSAccountEnsureInBackupRings(SOSAccountRef account
) {
1303 __block
bool result
= false;
1304 __block CFErrorRef error
= NULL
;
1305 secnotice("backup", "Ensuring in rings");
1307 CFDataRef backupKey
= NULL
;
1309 require_action_quiet(account
->backup_key
, exit
, result
= true);
1311 backupKey
= SOSPeerInfoV2DictionaryCopyData(SOSAccountGetMyPeerInfo(account
), sBackupKeyKey
);
1313 bool updateBackupKey
= !CFEqualSafe(backupKey
, account
->backup_key
);
1315 if(updateBackupKey
) {
1316 require_quiet(SOSAccountUpdatePeerInfo(account
, CFSTR("Backup public key"), &error
, ^bool(SOSFullPeerInfoRef fpi
, CFErrorRef
*error
) {
1317 return SOSFullPeerInfoUpdateBackupKey(fpi
, account
->backup_key
, error
);
1320 require_quiet(account
->backup_key
, exit
); // If it went null, we're done now.
1322 require_quiet(SOSBSKBIsGoodBackupPublic(account
->backup_key
, &error
), exit
);
1324 CFDataRef recoveryKeyBackFromRing
= SOSAccountCopyRecoveryPublic(kCFAllocatorDefault
, account
, &error
);
1326 if(updateBackupKey
|| recoveryKeyBackFromRing
) {
1327 // It's a good key, we're going with it. Stop backing up the old way.
1328 CFErrorRef localError
= NULL
;
1329 if (!SOSDeleteV0Keybag(&localError
)) {
1330 secerror("Failed to delete v0 keybag: %@", localError
);
1332 CFReleaseNull(localError
);
1336 // Setup backups the new way.
1337 SOSAccountForEachBackupView(account
, ^(const void *value
) {
1338 CFStringRef viewName
= (CFStringRef
)value
;
1339 if(updateBackupKey
|| (recoveryKeyBackFromRing
&& !SOSAccountRecoveryKeyIsInBackupAndCurrentInView(account
, viewName
))) {
1340 result
&= SOSAccountNewBKSBForView(account
, viewName
, &error
);
1347 secnotice("backupkey", "Failed to setup backup public key: %@", error
? (CFTypeRef
) error
: (CFTypeRef
) CFSTR("No error space provided"));
1349 CFReleaseNull(backupKey
);
1354 // MARK: Recovery Public Key Functions
1357 bool SOSAccountRegisterRecoveryPublicKey(SOSAccountTransactionRef txn
, CFDataRef recovery_key
, CFErrorRef
*error
){
1358 bool retval
= SOSAccountSetRecoveryKey(txn
->account
, recovery_key
, error
);
1359 if(retval
) secnotice("recovery", "successfully registered recovery public key");
1360 else secnotice("recovery", "could not register recovery public key: %@", *error
);
1361 SOSClearErrorIfTrue(retval
, error
);
1365 bool SOSAccountClearRecoveryPublicKey(SOSAccountTransactionRef txn
, CFDataRef recovery_key
, CFErrorRef
*error
){
1366 bool retval
= SOSAccountRemoveRecoveryKey(txn
->account
, error
);
1367 SOSClearErrorIfTrue(retval
, error
);
1371 CFDataRef
SOSAccountCopyRecoveryPublicKey(SOSAccountTransactionRef txn
, CFErrorRef
*error
){
1372 CFDataRef result
= NULL
;
1373 result
= SOSAccountCopyRecoveryPublic(kCFAllocatorDefault
, txn
->account
, error
);
1374 if(!result
) secnotice("recovery", "Could not retrieve the recovery public key from the ring: %@", *error
);
1376 if (!isData(result
)) {
1377 CFReleaseNull(result
);
1379 SOSClearErrorIfTrue(result
!= NULL
, error
);
1388 static bool SOSAccountJoinCircle(SOSAccountTransactionRef aTxn
, SecKeyRef user_key
,
1389 bool use_cloud_peer
, CFErrorRef
* error
) {
1390 SOSAccountRef account
= aTxn
->account
;
1392 __block
bool result
= false;
1393 __block SOSFullPeerInfoRef cloud_full_peer
= NULL
;
1395 require_action_quiet(account
->trusted_circle
, fail
, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound
, NULL
, error
, NULL
, CFSTR("Don't have circle when joining???")));
1396 require_quiet(SOSAccountEnsureFullPeerAvailable(account
, error
), fail
);
1398 SOSFullPeerInfoRef myCirclePeer
= account
->my_identity
;
1400 if (SOSCircleCountPeers(account
->trusted_circle
) == 0 || SOSAccountGhostResultsInReset(account
)) {
1401 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1402 // this also clears initial sync data
1403 result
= SOSAccountResetCircleToOffering(aTxn
, user_key
, error
);
1405 SOSAccountSetValue(account
, kSOSUnsyncedViewsKey
, kCFBooleanTrue
, NULL
);
1407 if (use_cloud_peer
) {
1408 cloud_full_peer
= SOSCircleCopyiCloudFullPeerInfoRef(account
->trusted_circle
, NULL
);
1411 SOSAccountModifyCircle(account
, error
, ^(SOSCircleRef circle
) {
1412 result
= SOSAccountAddEscrowToPeerInfo(account
, myCirclePeer
, error
);
1413 result
&= SOSCircleRequestAdmission(circle
, user_key
, myCirclePeer
, error
);
1414 account
->departure_code
= kSOSNeverLeftCircle
;
1415 if(result
&& cloud_full_peer
) {
1416 CFErrorRef localError
= NULL
;
1417 CFStringRef cloudid
= SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer
));
1418 require_quiet(cloudid
, finish
);
1419 require_quiet(SOSCircleHasActivePeerWithID(circle
, cloudid
, &localError
), finish
);
1420 require_quiet(SOSCircleAcceptRequest(circle
, user_key
, cloud_full_peer
, SOSFullPeerInfoGetPeerInfo(myCirclePeer
), &localError
), finish
);
1424 secerror("Failed to join with cloud identity: %@", localError
);
1425 CFReleaseNull(localError
);
1431 if (use_cloud_peer
) {
1432 SOSAccountUpdateOutOfSyncViews(aTxn
, SOSViewsGetAllCurrent());
1437 CFReleaseNull(cloud_full_peer
);
1441 static bool SOSAccountJoinCircles_internal(SOSAccountTransactionRef aTxn
, bool use_cloud_identity
, CFErrorRef
* error
) {
1442 SOSAccountRef account
= aTxn
->account
;
1443 bool success
= false;
1445 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
1446 require_quiet(user_key
, done
); // Fail if we don't get one.
1448 require_action_quiet(account
->trusted_circle
, done
, SOSErrorCreate(kSOSErrorNoCircle
, error
, NULL
, CFSTR("No circle to join")));
1450 if (account
->my_identity
!= NULL
) {
1451 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(account
->my_identity
);
1452 success
= SOSCircleHasPeer(account
->trusted_circle
, myPeer
, NULL
);
1453 require_quiet(!success
, done
);
1455 SOSCircleRemoveRejectedPeer(account
->trusted_circle
, myPeer
, NULL
); // If we were rejected we should remove it now.
1457 if (!SOSCircleHasApplicant(account
->trusted_circle
, myPeer
, NULL
)) {
1458 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer
), SOSCircleGetName(account
->trusted_circle
));
1460 CFReleaseNull(account
->my_identity
);
1465 success
= SOSAccountJoinCircle(aTxn
, user_key
, use_cloud_identity
, error
);
1467 require_quiet(success
, done
);
1469 account
->departure_code
= kSOSNeverLeftCircle
;
1475 bool SOSAccountJoinCircles(SOSAccountTransactionRef aTxn
, CFErrorRef
* error
) {
1476 secnotice("circleJoin", "Normal path circle join (SOSAccountJoinCircles)");
1477 return SOSAccountJoinCircles_internal(aTxn
, false, error
);
1480 CFStringRef
SOSAccountCopyDeviceID(SOSAccountRef account
, CFErrorRef
*error
){
1481 CFStringRef result
= NULL
;
1483 require_action_quiet(account
->my_identity
, fail
, SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("No peer for me")));
1485 result
= SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account
->my_identity
));
1491 bool SOSAccountSetMyDSID(SOSAccountTransactionRef txn
, CFStringRef IDS
, CFErrorRef
* error
){
1493 SOSAccountRef account
= txn
->account
;
1495 secdebug("IDS Transport", "We are setting our device ID: %@", IDS
);
1496 if(IDS
!= NULL
&& (CFStringGetLength(IDS
) > 0)){
1497 require_action_quiet(account
->my_identity
, fail
, SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("No peer for me")));
1499 result
= SOSAccountModifyCircle(account
, error
, ^bool(SOSCircleRef circle
) {
1501 SOSFullPeerInfoUpdateDeviceID(account
->my_identity
, IDS
, error
);
1502 SOSFullPeerInfoUpdateTransportType(account
->my_identity
, SOSTransportMessageTypeIDSV2
, error
);
1503 SOSFullPeerInfoUpdateTransportPreference(account
->my_identity
, kCFBooleanFalse
, error
);
1504 SOSFullPeerInfoUpdateTransportFragmentationPreference(account
->my_identity
, kCFBooleanTrue
, error
);
1505 SOSFullPeerInfoUpdateTransportAckModelPreference(account
->my_identity
, kCFBooleanTrue
, error
);
1506 return SOSCircleHasPeer(circle
, SOSFullPeerInfoGetPeerInfo(account
->my_identity
), NULL
);
1512 // Initiate sync with all IDS peers, since we just learned we can talk that way.
1513 SOSAccountForEachCirclePeerExceptMe(account
, ^(SOSPeerInfoRef peer
) {
1514 if (SOSPeerInfoShouldUseIDSTransport(SOSAccountGetMyPeerInfo(account
), peer
)) {
1515 SOSAccountTransactionAddSyncRequestForPeerID(txn
, SOSPeerInfoGetPeerID(peer
));
1520 CFReleaseNull(account
->deviceID
);
1521 account
->deviceID
= CFRetainSafe(IDS
);
1525 bool SOSAccountSendIDSTestMessage(SOSAccountRef account
, CFStringRef message
, CFErrorRef
*error
){
1527 //construct message dictionary, circle -> peerID -> message
1529 CFMutableDictionaryRef peerToMessage
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1531 CFStringRef operationString
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%d"), kIDSSendOneMessage
);
1532 CFDictionaryRef rawMessage
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
1533 kIDSOperationType
, operationString
,
1534 kIDSMessageToSendKey
, CFSTR("send IDS test message"),
1537 SOSAccountForEachCirclePeerExceptMe(account
, ^(SOSPeerInfoRef peer
) {
1538 CFDictionaryAddValue(peerToMessage
, SOSPeerInfoGetPeerID(peer
), rawMessage
);
1541 result
= SOSTransportMessageSendMessages(account
->ids_message_transport
, peerToMessage
, error
);
1543 CFReleaseNull(peerToMessage
);
1544 CFReleaseNull(operationString
);
1545 CFReleaseNull(rawMessage
);
1549 bool SOSAccountStartPingTest(SOSAccountRef account
, CFStringRef message
, CFErrorRef
*error
){
1550 bool result
= false;
1551 //construct message dictionary, circle -> peerID -> message
1553 if(account
->ids_message_transport
== NULL
)
1554 account
->ids_message_transport
= (SOSTransportMessageRef
)SOSTransportMessageIDSCreate(account
, SOSCircleGetName(account
->trusted_circle
), error
);
1556 require_quiet(account
->ids_message_transport
, fail
);
1557 CFMutableDictionaryRef peerToMessage
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1559 CFStringRef operationString
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%d"), kIDSStartPingTestMessage
);
1560 CFDictionaryRef rawMessage
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
1561 kIDSOperationType
, operationString
,
1562 kIDSMessageToSendKey
, CFSTR("send IDS test message"),
1566 SOSAccountForEachCirclePeerExceptMe(account
, ^(SOSPeerInfoRef peer
) {
1567 CFDictionaryAddValue(peerToMessage
, SOSPeerInfoGetPeerID(peer
), rawMessage
);
1570 result
= SOSTransportMessageSendMessages(account
->ids_message_transport
, peerToMessage
, error
);
1572 CFReleaseNull(peerToMessage
);
1573 CFReleaseNull(rawMessage
);
1574 CFReleaseNull(operationString
);
1579 bool SOSAccountRetrieveDeviceIDFromKeychainSyncingOverIDSProxy(SOSAccountRef account
, CFErrorRef
*error
){
1582 __block
bool success
= true;
1583 __block CFErrorRef localError
= NULL
;
1584 dispatch_semaphore_t wait_for
= dispatch_semaphore_create(0);
1585 dispatch_retain(wait_for
); // Both this scope and the block own it
1587 SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues
, CFErrorRef sync_error
){
1588 success
= (sync_error
== NULL
);
1590 CFRetainAssign(localError
, sync_error
);
1593 dispatch_semaphore_signal(wait_for
);
1594 dispatch_release(wait_for
);
1597 dispatch_semaphore_wait(wait_for
, DISPATCH_TIME_FOREVER
);
1598 dispatch_release(wait_for
);
1600 if(!success
&& localError
!= NULL
&& error
!= NULL
){
1601 secerror("Could not ask KeychainSyncingOverIDSProxy for Device ID: %@", localError
);
1602 *error
= localError
;
1606 secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
1611 bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransactionRef aTxn
, CFErrorRef
* error
) {
1612 secnotice("circleJoin", "Joining after restore (SOSAccountJoinCirclesAfterRestore)");
1613 return SOSAccountJoinCircles_internal(aTxn
, true, error
);
1617 bool SOSAccountLeaveCircle(SOSAccountRef account
, CFErrorRef
* error
)
1621 secnotice("leaveCircle", "Leaving circle by client request");
1622 result
&= SOSAccountModifyCircle(account
, error
, ^(SOSCircleRef circle
) {
1623 return sosAccountLeaveCircle(account
, circle
, error
);
1626 account
->departure_code
= kSOSWithdrewMembership
;
1631 bool SOSAccountRemovePeersFromCircle(SOSAccountRef account
, CFArrayRef peers
, CFErrorRef
* error
)
1633 bool result
= false;
1634 CFMutableSetRef peersToRemove
= NULL
;
1635 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
1636 require_action_quiet(user_key
, errOut
, secnotice("removePeers", "Can't remove without userKey"));
1638 SOSFullPeerInfoRef me_full
= SOSAccountGetMyFullPeerInfo(account
);
1639 SOSPeerInfoRef me
= SOSAccountGetMyPeerInfo(account
);
1640 require_action_quiet(me_full
&& me
, errOut
, {
1641 SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("Can't remove without being active peer"));
1642 secnotice("removePeers", "Can't remove without being active peer");
1645 result
= true; // beyond this point failures would be rolled up in AccountModifyCircle.
1647 peersToRemove
= CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault
, peers
);
1648 require_action_quiet(peersToRemove
, errOut
, secnotice("removePeers", "No peerSet to remove"));
1650 // If we're one of the peers expected to leave - note that and then remove ourselves from the set (different handling).
1651 bool leaveCircle
= CFSetContainsValue(peersToRemove
, me
);
1652 CFSetRemoveValue(peersToRemove
, me
);
1654 result
&= SOSAccountModifyCircle(account
, error
, ^(SOSCircleRef circle
) {
1655 bool success
= false;
1657 if(CFSetGetCount(peersToRemove
) != 0) {
1658 require_quiet(SOSCircleRemovePeers(circle
, user_key
, me_full
, peersToRemove
, error
), done
);
1659 success
= SOSAccountGenerationSignatureUpdate(account
, error
);
1660 } else success
= true;
1662 if (success
&& leaveCircle
) {
1663 secnotice("leaveCircle", "Leaving circle by client request");
1664 success
= sosAccountLeaveCircle(account
, circle
, error
);
1673 CFReleaseNull(peersToRemove
);
1678 bool SOSAccountBail(SOSAccountRef account
, uint64_t limit_in_seconds
, CFErrorRef
* error
) {
1679 dispatch_queue_t queue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
1680 dispatch_group_t group
= dispatch_group_create();
1681 __block
bool result
= false;
1682 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds
);
1683 // Add a task to the group
1684 dispatch_group_async(group
, queue
, ^{
1685 SOSAccountModifyCircle(account
, error
, ^(SOSCircleRef circle
) {
1686 secnotice("leaveCircle", "Leaving circle by client request");
1687 return sosAccountLeaveCircle(account
, circle
, error
);
1690 dispatch_time_t milestone
= dispatch_time(DISPATCH_TIME_NOW
, limit_in_seconds
* NSEC_PER_SEC
);
1691 dispatch_group_wait(group
, milestone
);
1693 account
->departure_code
= kSOSWithdrewMembership
;
1695 dispatch_release(group
);
1701 // MARK: Application
1704 static void for_each_applicant_in_each_circle(SOSAccountRef account
, CFArrayRef peer_infos
,
1705 bool (^action
)(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
)) {
1706 SOSPeerInfoRef me
= SOSFullPeerInfoGetPeerInfo(account
->my_identity
);
1707 CFErrorRef peer_error
= NULL
;
1708 if (account
->trusted_circle
&& me
&&
1709 SOSCircleHasPeer(account
->trusted_circle
, me
, &peer_error
)) {
1710 SOSAccountModifyCircle(account
, NULL
, ^(SOSCircleRef circle
) {
1711 __block
bool modified
= false;
1712 CFArrayForEach(peer_infos
, ^(const void *value
) {
1713 SOSPeerInfoRef peer
= (SOSPeerInfoRef
) value
;
1714 if (isSOSPeerInfo(peer
) && SOSCircleHasApplicant(circle
, peer
, NULL
)) {
1715 if (action(circle
, account
->my_identity
, peer
)) {
1724 secerror("Got error in SOSCircleHasPeer: %@", peer_error
);
1725 CFReleaseSafe(peer_error
); // TODO: We should be accumulating errors here.
1728 bool SOSAccountAcceptApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
1729 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
1733 __block
bool success
= true;
1734 __block
int64_t num_peers
= 0;
1736 for_each_applicant_in_each_circle(account
, applicants
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
) {
1737 bool accepted
= SOSCircleAcceptRequest(circle
, user_key
, myCirclePeer
, peer
, error
);
1741 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
1748 bool SOSAccountRejectApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
1749 __block
bool success
= true;
1750 __block
int64_t num_peers
= 0;
1752 for_each_applicant_in_each_circle(account
, applicants
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
) {
1753 bool rejected
= SOSCircleRejectRequest(circle
, myCirclePeer
, peer
, error
);
1757 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
1765 CFStringRef
SOSAccountCopyIncompatibilityInfo(SOSAccountRef account
, CFErrorRef
* error
) {
1766 return CFSTR("We're compatible, go away");
1769 enum DepartureReason
SOSAccountGetLastDepartureReason(SOSAccountRef account
, CFErrorRef
* error
) {
1770 return account
->departure_code
;
1773 void SOSAccountSetLastDepartureReason(SOSAccountRef account
, enum DepartureReason reason
) {
1774 account
->departure_code
= reason
;
1778 CFArrayRef
SOSAccountCopyGeneration(SOSAccountRef account
, CFErrorRef
*error
) {
1779 CFArrayRef result
= NULL
;
1780 CFNumberRef generation
= NULL
;
1782 require_quiet(SOSAccountHasPublicKey(account
, error
), fail
);
1783 require_action_quiet(account
->trusted_circle
, fail
, SOSErrorCreate(kSOSErrorNoCircle
, error
, NULL
, CFSTR("No circle")));
1785 generation
= (CFNumberRef
)SOSCircleGetGeneration(account
->trusted_circle
);
1786 result
= CFArrayCreateForCFTypes(kCFAllocatorDefault
, generation
, NULL
);
1792 bool SOSValidateUserPublic(SOSAccountRef account
, CFErrorRef
*error
) {
1793 if (!SOSAccountHasPublicKey(account
, error
))
1796 return account
->user_public_trusted
;
1799 bool SOSAccountEnsurePeerRegistration(SOSAccountRef account
, CFErrorRef
*error
) {
1800 // TODO: this result is never set or used
1803 secnotice("updates", "Ensuring peer registration.");
1805 require_quiet(account
->trusted_circle
, done
);
1806 require_quiet(account
->my_identity
, done
);
1807 require_quiet(account
->user_public_trusted
, done
);
1809 // If we are not in the circle, there is no point in setting up peers
1810 require_quiet(SOSAccountIsMyPeerActive(account
, NULL
), done
);
1812 // This code only uses the SOSFullPeerInfoRef for two things:
1813 // - Finding out if this device is in the trusted circle
1814 // - Using the peerID for this device to see if the current peer is "me"
1815 // - It is used indirectly by passing account->my_identity to SOSEngineInitializePeerCoder
1817 CFStringRef my_id
= SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(account
->my_identity
));
1819 SOSCircleForEachValidSyncingPeer(account
->trusted_circle
, account
->user_public
, ^(SOSPeerInfoRef peer
) {
1820 if (!SOSPeerInfoPeerIDEqual(peer
, my_id
)) {
1821 CFErrorRef localError
= NULL
;
1822 SOSTransportMessageRef messageTransport
= NULL
;
1824 messageTransport
= SOSPeerInfoHasDeviceID(peer
) ? account
->ids_message_transport
: account
->kvs_message_transport
;
1826 SOSEngineInitializePeerCoder(messageTransport
->engine
, account
->my_identity
, peer
, &localError
);
1828 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer
, account
->my_identity
, localError
);
1829 CFReleaseSafe(localError
);
1833 //Initialize our device ID
1834 SOSTransportMessageIDSGetIDSDeviceID(account
);
1841 bool SOSAccountAddEscrowRecords(SOSAccountRef account
, CFStringRef dsid
, CFDictionaryRef record
, CFErrorRef
*error
){
1842 CFMutableDictionaryRef escrowRecords
= (CFMutableDictionaryRef
)SOSAccountGetValue(account
, kSOSEscrowRecord
, error
);
1843 CFMutableDictionaryRef escrowCopied
= NULL
;
1844 bool success
= false;
1846 if(isDictionary(escrowRecords
) && escrowRecords
!= NULL
)
1847 escrowCopied
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, CFDictionaryGetCount(escrowRecords
), escrowRecords
);
1849 escrowCopied
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1851 CFDictionaryAddValue(escrowCopied
, dsid
, record
);
1852 SOSAccountSetValue(account
, kSOSEscrowRecord
, escrowCopied
, error
);
1857 CFReleaseNull(escrowCopied
);
1863 bool SOSAccountAddEscrowToPeerInfo(SOSAccountRef account
, SOSFullPeerInfoRef myPeer
, CFErrorRef
*error
){
1864 bool success
= false;
1866 CFDictionaryRef escrowRecords
= SOSAccountGetValue(account
, kSOSEscrowRecord
, error
);
1867 success
= SOSFullPeerInfoReplaceEscrowRecords(myPeer
, escrowRecords
, error
);
1872 bool SOSAccountCheckPeerAvailability(SOSAccountRef account
, CFErrorRef
*error
)
1874 CFStringRef operationString
= NULL
;
1875 CFDictionaryRef rawMessage
= NULL
;
1876 CFMutableSetRef peers
= NULL
;
1877 CFMutableDictionaryRef peerList
= NULL
;
1878 char* message
= NULL
;
1879 bool result
= false;
1881 if(account
->ids_message_transport
== NULL
)
1882 account
->ids_message_transport
= (SOSTransportMessageRef
)SOSTransportMessageIDSCreate(account
, SOSCircleGetName(account
->trusted_circle
), error
);
1884 require_quiet(account
->ids_message_transport
, fail
);
1886 //adding message type kIDSPeerAvailability so KeychainSyncingOverIDSProxy does not send this message as a keychain item
1888 operationString
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%d"), kIDSPeerAvailability
);
1889 rawMessage
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
1890 kIDSOperationType
, operationString
,
1891 kIDSMessageToSendKey
, CFSTR("checking peers"),
1894 peerList
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1895 SOSCircleRef circle
= account
->trusted_circle
;
1897 //check each peer to make sure they have the right view set enabled
1898 CFSetRef mySubSet
= SOSViewsGetV0SubviewSet();
1899 SOSCircleForEachValidPeer(circle
, account
->user_public
, ^(SOSPeerInfoRef peer
) {
1900 if(!CFEqualSafe(peer
, SOSAccountGetMyPeerInfo(account
))){
1901 CFMutableSetRef peerViews
= SOSPeerInfoCopyEnabledViews(peer
);
1902 CFSetRef intersectSets
= CFSetCreateIntersection(kCFAllocatorDefault
, mySubSet
, peerViews
);
1903 if(CFEqualSafe(intersectSets
, mySubSet
)){
1904 CFStringRef deviceID
= SOSPeerInfoCopyDeviceID(peer
);
1905 if(deviceID
!= NULL
)
1906 CFDictionaryAddValue(peerList
, SOSPeerInfoGetPeerID(peer
), rawMessage
);
1907 CFReleaseNull(deviceID
);
1909 CFReleaseNull(peerViews
);
1910 CFReleaseNull(intersectSets
);
1914 require_quiet(CFDictionaryGetCount(peerList
) > 0 , fail
);
1915 result
= SOSTransportMessageSendMessages(account
->ids_message_transport
, peerList
, error
);
1918 CFReleaseNull(rawMessage
);
1919 CFReleaseNull(operationString
);
1920 CFReleaseNull(peerList
);
1921 CFReleaseNull(peers
);
1927 void SOSAccountRecordRetiredPeersInCircle(SOSAccountRef account
) {
1928 if (!SOSAccountIsInCircle(account
, NULL
))
1931 SOSAccountModifyCircle(account
, NULL
, ^bool (SOSCircleRef circle
) {
1932 __block
bool updated
= false;
1933 CFSetForEach(account
->retirees
, ^(CFTypeRef element
){
1934 SOSPeerInfoRef retiree
= asSOSPeerInfo(element
);
1936 if (retiree
&& SOSCircleUpdatePeerInfo(circle
, retiree
)) {
1938 secnotice("retirement", "Updated retired peer %@ in %@", retiree
, circle
);
1939 CFErrorRef cleanupError
= NULL
;
1940 if (!SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, retiree
, &cleanupError
))
1941 secerror("Error cleanup up after peer (%@): %@", retiree
, cleanupError
);
1942 CFReleaseSafe(cleanupError
);
1950 static size_t SOSPiggyBackBlobGetDEREncodedSize(SOSGenCountRef gencount
, SecKeyRef pubKey
, CFDataRef signature
, CFErrorRef
*error
) {
1951 size_t total_payload
= 0;
1953 CFDataRef publicBytes
= NULL
;
1954 OSStatus result
= SecKeyCopyPublicBytes(pubKey
, &publicBytes
);
1956 if (result
!= errSecSuccess
) {
1957 SOSCreateError(kSOSErrorBadKey
, CFSTR("Failed to export public bytes"), NULL
, error
);
1961 require_quiet(accumulate_size(&total_payload
, der_sizeof_number(gencount
, error
)), errOut
);
1962 require_quiet(accumulate_size(&total_payload
, der_sizeof_data_or_null(publicBytes
, error
)), errOut
);
1963 require_quiet(accumulate_size(&total_payload
, der_sizeof_data_or_null(signature
, error
)), errOut
);
1964 return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE
, total_payload
);
1967 SecCFDERCreateError(kSecDERErrorUnknownEncoding
, CFSTR("don't know how to encode"), NULL
, error
);
1971 static uint8_t* SOSPiggyBackBlobEncodeToDER(SOSGenCountRef gencount
, SecKeyRef pubKey
, CFDataRef signature
, CFErrorRef
* error
, const uint8_t* der
, uint8_t* der_end
) {
1972 CFDataRef publicBytes
= NULL
;
1974 OSStatus result
= SecKeyCopyPublicBytes(pubKey
, &publicBytes
);
1976 if (result
!= errSecSuccess
) {
1977 SOSCreateError(kSOSErrorBadKey
, CFSTR("Failed to export public bytes"), NULL
, error
);
1982 der_end
= ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE
, der_end
, der
,
1983 der_encode_number(gencount
, error
, der
,
1984 der_encode_data_or_null(publicBytes
, error
, der
,
1985 der_encode_data_or_null(signature
, error
, der
, der_end
))));
1989 static CFDataRef
SOSPiggyBackBlobCopyEncodedData(SOSGenCountRef gencount
, SecKeyRef pubKey
, CFDataRef signature
, CFAllocatorRef allocator
, CFErrorRef
*error
)
1991 return CFDataCreateWithDER(kCFAllocatorDefault
, SOSPiggyBackBlobGetDEREncodedSize(gencount
, pubKey
, signature
, error
), ^uint8_t*(size_t size
, uint8_t *buffer
) {
1992 return SOSPiggyBackBlobEncodeToDER(gencount
, pubKey
, signature
, error
, buffer
, (uint8_t *) buffer
+ size
);
1996 struct piggyBackBlob
{
1997 SOSGenCountRef gencount
;
1999 CFDataRef signature
;
2002 static struct piggyBackBlob
*SOSPiggyBackBlobCreateFromDER(CFAllocatorRef allocator
, CFErrorRef
*error
,
2003 const uint8_t** der_p
, const uint8_t *der_end
) {
2004 const uint8_t *sequence_end
;
2005 struct piggyBackBlob
*retval
= NULL
;
2006 SOSGenCountRef gencount
= NULL
;
2007 CFDataRef signature
= NULL
;
2008 CFDataRef publicBytes
= NULL
;
2010 *der_p
= ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE
, &sequence_end
, *der_p
, der_end
);
2011 require_action_quiet(sequence_end
!= NULL
, errOut
,
2012 SOSCreateError(kSOSErrorBadFormat
, CFSTR("Bad Blob DER"), (error
!= NULL
) ? *error
: NULL
, error
));
2013 *der_p
= der_decode_number(allocator
, 0, &gencount
, error
, *der_p
, sequence_end
);
2014 *der_p
= der_decode_data_or_null(kCFAllocatorDefault
, &publicBytes
, error
, *der_p
, der_end
);
2015 *der_p
= der_decode_data_or_null(kCFAllocatorDefault
, &signature
, error
, *der_p
, der_end
);
2016 require_action_quiet(*der_p
&& *der_p
== der_end
, errOut
,
2017 SOSCreateError(kSOSErrorBadFormat
, CFSTR("Didn't consume all bytes for pbblob"), (error
!= NULL
) ? *error
: NULL
, error
));
2018 retval
= malloc(sizeof(struct piggyBackBlob
));
2019 retval
->gencount
= gencount
;
2020 retval
->signature
= signature
;
2021 retval
->pubKey
= SecKeyCreateFromPublicData(kCFAllocatorDefault
, kSecECDSAAlgorithmID
, publicBytes
);
2025 CFReleaseNull(gencount
);
2026 CFReleaseNull(publicBytes
);
2027 CFReleaseNull(signature
);
2032 static struct piggyBackBlob
*SOSPiggyBackBlobCreateFromData(CFAllocatorRef allocator
, CFDataRef blobData
, CFErrorRef
*error
)
2034 size_t size
= CFDataGetLength(blobData
);
2035 const uint8_t *der
= CFDataGetBytePtr(blobData
);
2036 struct piggyBackBlob
*inflated
= SOSPiggyBackBlobCreateFromDER(allocator
, error
, &der
, der
+ size
);
2042 SOSPeerInfoRef
SOSAccountCopyApplication(SOSAccountRef account
, CFErrorRef
* error
) {
2043 SOSPeerInfoRef applicant
= NULL
;
2044 SecKeyRef userKey
= SOSAccountGetPrivateCredential(account
, error
);
2045 if(!userKey
) return false;
2046 require_quiet(SOSAccountEnsureFullPeerAvailable(account
, error
), errOut
);
2047 require(SOSFullPeerInfoPromoteToApplication(account
->my_identity
, userKey
, error
), errOut
);
2048 applicant
= SOSPeerInfoCreateCopy(kCFAllocatorDefault
, (SOSFullPeerInfoGetPeerInfo(account
->my_identity
)), error
);
2054 CFDataRef
SOSAccountCopyCircleJoiningBlob(SOSAccountRef account
, SOSPeerInfoRef applicant
, CFErrorRef
*error
) {
2055 SOSGenCountRef gencount
= NULL
;
2056 CFDataRef signature
= NULL
;
2057 SecKeyRef ourKey
= NULL
;
2059 CFDataRef pbblob
= NULL
;
2061 secnotice("circleJoin", "Making circle joining blob as sponsor (SOSAccountCopyCircleJoiningBlob)");
2063 SecKeyRef userKey
= SOSAccountGetTrustedPublicCredential(account
, error
);
2064 require_quiet(userKey
, errOut
);
2066 require_action_quiet(applicant
, errOut
, SOSCreateError(kSOSErrorProcessingFailure
, CFSTR("No applicant provided"), (error
!= NULL
) ? *error
: NULL
, error
));
2067 require_quiet(SOSPeerInfoApplicationVerify(applicant
, userKey
, error
), errOut
);
2070 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInfo(account
);
2071 ourKey
= SOSFullPeerInfoCopyDeviceKey(fpi
, error
);
2072 require_quiet(ourKey
, errOut
);
2075 SOSCircleRef currentCircle
= SOSAccountGetCircle(account
, error
);
2076 require_quiet(currentCircle
, errOut
);
2078 SOSCircleRef prunedCircle
= SOSCircleCopyCircle(NULL
, currentCircle
, error
);
2079 require_quiet(prunedCircle
, errOut
);
2080 require_quiet(SOSCirclePreGenerationSign(prunedCircle
, userKey
, error
), errOut
);
2082 gencount
= SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle
));
2084 signature
= SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle
, applicant
, ourKey
, error
);
2085 require_quiet(signature
, errOut
);
2087 pbblob
= SOSPiggyBackBlobCopyEncodedData(gencount
, ourKey
, signature
, kCFAllocatorDefault
, error
);
2090 CFReleaseNull(gencount
);
2091 CFReleaseNull(signature
);
2092 CFReleaseNull(ourKey
);
2095 secnotice("circleJoin", "Failed to make circle joining blob as sponsor %@", *error
);
2101 bool SOSAccountJoinWithCircleJoiningBlob(SOSAccountRef account
, CFDataRef joiningBlob
, CFErrorRef
*error
) {
2102 bool retval
= false;
2103 SecKeyRef userKey
= NULL
;
2104 struct piggyBackBlob
*pbb
= NULL
;
2106 secnotice("circleJoin", "Joining circles through piggy-back (SOSAccountCopyCircleJoiningBlob)");
2108 userKey
= SOSAccountGetPrivateCredential(account
, error
);
2109 require_quiet(userKey
, errOut
);
2110 pbb
= SOSPiggyBackBlobCreateFromData(kCFAllocatorDefault
, joiningBlob
, error
);
2111 require_quiet(pbb
, errOut
);
2113 SOSAccountSetValue(account
, kSOSUnsyncedViewsKey
, kCFBooleanTrue
, NULL
);
2115 retval
= SOSAccountModifyCircle(account
, error
, ^bool(SOSCircleRef copyOfCurrent
) {
2116 return SOSCircleAcceptPeerFromHSA2(copyOfCurrent
, userKey
,
2120 account
->my_identity
, error
);;
2126 CFReleaseNull(pbb
->gencount
);
2127 CFReleaseNull(pbb
->pubKey
);
2128 CFReleaseNull(pbb
->signature
);
2134 static char boolToChars(bool val
, char truechar
, char falsechar
) {
2135 return val
? truechar
: falsechar
;
2138 #define ACCOUNTLOGSTATE "accountLogState"
2139 void SOSAccountLogState(SOSAccountRef account
) {
2140 bool hasPubKey
= account
->user_public
!= NULL
;
2141 bool pubTrusted
= account
->user_public_trusted
;
2142 bool hasPriv
= account
->_user_private
!= NULL
;
2143 SOSCCStatus stat
= SOSAccountGetCircleStatus(account
, NULL
);
2144 CFStringRef userPubKeyID
= (account
->user_public
) ? SOSCopyIDOfKeyWithLength(account
->user_public
, 8, NULL
):
2145 CFStringCreateCopy(kCFAllocatorDefault
, CFSTR("*No Key*"));
2147 secnotice(ACCOUNTLOGSTATE
, "Start");
2149 secnotice(ACCOUNTLOGSTATE
, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
2150 boolToChars(hasPubKey
, 'U', 'u'), boolToChars(pubTrusted
, 'T', 't'), boolToChars(hasPriv
, 'I', 'i'),
2152 SOSAccountGetSOSCCStatusString(stat
)
2154 CFReleaseNull(userPubKeyID
);
2155 if(account
->trusted_circle
) SOSCircleLogState(ACCOUNTLOGSTATE
, account
->trusted_circle
, account
->user_public
, SOSAccountGetMyPeerID(account
));
2156 else secnotice(ACCOUNTLOGSTATE
, "ACCOUNT: No Circle");
2159 void SOSAccountLogViewState(SOSAccountRef account
) {
2160 bool isInCircle
= SOSAccountIsInCircle(account
, NULL
);
2161 require_quiet(isInCircle
, imOut
);
2162 SOSPeerInfoRef mpi
= SOSAccountGetMyPeerInfo(account
);
2163 bool isInitialComplete
= SOSAccountHasCompletedInitialSync(account
);
2164 bool isBackupComplete
= SOSAccountHasCompletedRequiredBackupSync(account
);
2166 CFSetRef views
= mpi
? SOSPeerInfoCopyEnabledViews(mpi
) : NULL
;
2167 CFStringSetPerformWithDescription(views
, ^(CFStringRef description
) {
2168 secnotice(ACCOUNTLOGSTATE
, "Sync: %c%c PeerViews: %@",
2169 boolToChars(isInitialComplete
, 'I', 'i'),
2170 boolToChars(isBackupComplete
, 'B', 'b'),
2173 CFReleaseNull(views
);
2174 CFSetRef unsyncedViews
= SOSAccountCopyOutstandingViews(account
);
2175 CFStringSetPerformWithDescription(views
, ^(CFStringRef description
) {
2176 secnotice(ACCOUNTLOGSTATE
, "outstanding views: %@", description
);
2178 CFReleaseNull(unsyncedViews
);
2181 secnotice(ACCOUNTLOGSTATE
, "Finish");
2187 void SOSAccountSetTestSerialNumber(SOSAccountRef account
, CFStringRef serial
) {
2188 if(!isString(serial
)) return;
2189 CFMutableDictionaryRef newv2dict
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
2190 CFDictionarySetValue(newv2dict
, sSerialNumberKey
, serial
);
2191 SOSAccountUpdateV2Dictionary(account
, newv2dict
);