6 #include "SOSAccountPriv.h"
7 #include <SecureObjectSync/SOSTransportCircle.h>
8 #include <SecureObjectSync/SOSTransport.h>
9 #include <SecureObjectSync/SOSPeerInfoCollections.h>
10 #include <CKBridge/SOSCloudKeychainClient.h>
12 static void DifferenceAndCall(CFSetRef old_members
, CFSetRef new_members
, void (^updatedCircle
)(CFSetRef additions
, CFSetRef removals
))
14 CFMutableSetRef additions
= CFSetCreateMutableCopy(kCFAllocatorDefault
, 0, new_members
);
15 CFMutableSetRef removals
= CFSetCreateMutableCopy(kCFAllocatorDefault
, 0, old_members
);
18 CFSetForEach(old_members
, ^(const void * value
) {
19 CFSetRemoveValue(additions
, value
);
22 CFSetForEach(new_members
, ^(const void * value
) {
23 CFSetRemoveValue(removals
, value
);
26 updatedCircle(additions
, removals
);
28 CFReleaseSafe(additions
);
29 CFReleaseSafe(removals
);
32 static void SOSAccountNotifyEngines(SOSAccountRef account
, SOSCircleRef new_circle
,
33 CFSetRef added_peers
, CFSetRef removed_peers
,
34 CFSetRef added_applicants
, CFSetRef removed_applicants
)
36 SOSPeerInfoRef myPi
= SOSAccountGetMyPeerInCircle(account
, new_circle
, NULL
);
37 CFStringRef myPi_id
= NULL
;
38 CFMutableArrayRef trusted_peer_ids
= NULL
;
39 CFMutableArrayRef untrusted_peer_ids
= NULL
;
41 if (myPi
&& SOSCircleHasPeer(new_circle
, myPi
, NULL
)) {
42 myPi_id
= SOSPeerInfoGetPeerID(myPi
);
43 trusted_peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
44 untrusted_peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
45 SOSCircleForEachPeer(new_circle
, ^(SOSPeerInfoRef peer
) {
46 CFMutableArrayRef arrayToAddTo
= SOSPeerInfoApplicationVerify(peer
, account
->user_public
, NULL
) ? trusted_peer_ids
: untrusted_peer_ids
;
47 CFArrayAppendValue(arrayToAddTo
, SOSPeerInfoGetPeerID(peer
));
51 CFArrayRef dsNames
= account
->factory
->copy_names(account
->factory
);
52 CFStringRef dsName
= NULL
;
53 CFArrayForEachC(dsNames
, dsName
) {
54 SOSEngineRef engine
= SOSDataSourceFactoryGetEngineForDataSourceName(account
->factory
, dsName
, NULL
);
56 SOSEngineCircleChanged(engine
, myPi_id
, trusted_peer_ids
, untrusted_peer_ids
);
58 CFReleaseSafe(dsNames
);
59 CFReleaseNull(trusted_peer_ids
);
60 CFReleaseNull(untrusted_peer_ids
);
63 static void SOSAccountNotifyOfChange(SOSAccountRef account
, SOSCircleRef oldCircle
, SOSCircleRef newCircle
)
65 CFMutableSetRef old_members
= SOSCircleCopyPeers(oldCircle
, kCFAllocatorDefault
);
66 CFMutableSetRef new_members
= SOSCircleCopyPeers(newCircle
, kCFAllocatorDefault
);
68 CFMutableSetRef old_applicants
= SOSCircleCopyApplicants(oldCircle
, kCFAllocatorDefault
);
69 CFMutableSetRef new_applicants
= SOSCircleCopyApplicants(newCircle
, kCFAllocatorDefault
);
71 DifferenceAndCall(old_members
, new_members
, ^(CFSetRef added_members
, CFSetRef removed_members
) {
72 DifferenceAndCall(old_applicants
, new_applicants
, ^(CFSetRef added_applicants
, CFSetRef removed_applicants
) {
73 SOSAccountNotifyEngines(account
, newCircle
, added_members
, removed_members
, added_applicants
, removed_applicants
);
74 CFArrayForEach(account
->change_blocks
, ^(const void * notificationBlock
) {
75 ((SOSAccountCircleMembershipChangeBlock
) notificationBlock
)(newCircle
, added_members
, removed_members
, added_applicants
, removed_applicants
);
80 CFReleaseNull(old_applicants
);
81 CFReleaseNull(new_applicants
);
83 CFReleaseNull(old_members
);
84 CFReleaseNull(new_members
);
87 void SOSAccountRecordRetiredPeerInCircleNamed(SOSAccountRef account
, CFStringRef circleName
, SOSPeerInfoRef retiree
)
89 // Replace Peer with RetiredPeer, if were a peer.
90 SOSAccountModifyCircle(account
, circleName
, NULL
, ^(SOSCircleRef circle
) {
91 SOSPeerInfoRef me
= SOSAccountGetMyPeerInCircleNamed(account
, circleName
, NULL
);
92 if(!me
|| !SOSCircleHasActivePeer(circle
, me
, NULL
)) return (bool) false;
94 bool updated
= SOSCircleUpdatePeerInfo(circle
, retiree
);
96 CFErrorRef cleanupError
= NULL
;
97 if (!SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, retiree
, &cleanupError
))
98 secerror("Error cleanup up after peer (%@): %@", retiree
, cleanupError
);
99 CFReleaseSafe(cleanupError
);
105 static SOSCircleRef
SOSAccountCreateCircleFrom(CFStringRef circleName
, CFTypeRef value
, CFErrorRef
*error
) {
106 if (value
&& !isData(value
) && !isNull(value
)) {
107 secnotice("circleCreat", "Value provided not appropriate for a circle");
108 CFStringRef description
= CFCopyTypeIDDescription(CFGetTypeID(value
));
109 SOSCreateErrorWithFormat(kSOSErrorUnexpectedType
, NULL
, error
, NULL
,
110 CFSTR("Expected data or NULL got %@"), description
);
111 CFReleaseSafe(description
);
115 SOSCircleRef circle
= NULL
;
116 if (!value
|| isNull(value
)) {
117 secnotice("circleCreat", "No circle found in data: %@", value
);
120 circle
= SOSCircleCreateFromData(NULL
, (CFDataRef
) value
, error
);
122 CFStringRef name
= SOSCircleGetName(circle
);
123 if (!CFEqualSafe(name
, circleName
)) {
124 secnotice("circleCreat", "Expected circle named %@, got %@", circleName
, name
);
125 SOSCreateErrorWithFormat(kSOSErrorNameMismatch
, NULL
, error
, NULL
,
126 CFSTR("Expected circle named %@, got %@"), circleName
, name
);
127 CFReleaseNull(circle
);
130 secnotice("circleCreat", "SOSCircleCreateFromData returned NULL.");
136 bool SOSAccountHandleCircleMessage(SOSAccountRef account
,
137 CFStringRef circleName
, CFDataRef encodedCircleMessage
, CFErrorRef
*error
) {
138 bool success
= false;
139 CFErrorRef localError
= NULL
;
140 SOSCircleRef circle
= SOSAccountCreateCircleFrom(circleName
, encodedCircleMessage
, &localError
);
142 success
= SOSAccountUpdateCircleFromRemote(account
, circle
, &localError
);
143 CFReleaseSafe(circle
);
145 secerror("NULL circle found, ignoring ...");
146 success
= true; // don't pend this NULL thing.
150 if (isSOSErrorCoded(localError
, kSOSErrorIncompatibleCircle
)) {
151 secerror("Incompatible circle found, abandoning membership: %@", circleName
);
152 SOSAccountDestroyCirclePeerInfoNamed(account
, circleName
, NULL
);
153 CFDictionarySetValue(account
->circles
, circleName
, kCFNull
);
163 CFReleaseNull(localError
);
168 bool SOSAccountHandleParametersChange(SOSAccountRef account
, CFDataRef parameters
, CFErrorRef
*error
){
170 SecKeyRef newKey
= NULL
;
171 CFDataRef newParameters
= NULL
;
172 bool success
= false;
174 if(SOSAccountRetrieveCloudParameters(account
, &newKey
, parameters
, &newParameters
, error
)) {
175 if (CFEqualSafe(account
->user_public
, newKey
)) {
176 secnotice("updates", "Got same public key sent our way. Ignoring.");
178 } else if (CFEqualSafe(account
->previous_public
, newKey
)) {
179 secnotice("updates", "Got previous public key repeated. Ignoring.");
182 CFReleaseNull(account
->user_public
);
183 SOSAccountPurgePrivateCredential(account
);
184 CFReleaseNull(account
->user_key_parameters
);
186 account
->user_public_trusted
= false;
188 account
->user_public
= newKey
;
191 account
->user_key_parameters
= newParameters
;
192 newParameters
= NULL
;
194 secnotice("updates", "Got new parameters for public key: %@", account
->user_public
);
195 debugDumpUserParameters(CFSTR("params"), account
->user_key_parameters
);
197 SOSUpdateKeyInterest();
203 CFReleaseNull(newKey
);
204 CFReleaseNull(newParameters
);
209 static inline bool SOSAccountHasLeft(SOSAccountRef account
) {
210 switch(account
->departure_code
) {
211 case kSOSWithdrewMembership
: /* Fallthrough */
212 case kSOSMembershipRevoked
: /* Fallthrough */
213 case kSOSLeftUntrustedCircle
:
215 case kSOSNeverAppliedToCircle
: /* Fallthrough */
216 case kSOSNeverLeftCircle
: /* Fallthrough */
222 static const char *concordstring
[] = {
223 "kSOSConcordanceTrusted",
224 "kSOSConcordanceGenOld", // kSOSErrorReplay
225 "kSOSConcordanceNoUserSig", // kSOSErrorBadSignature
226 "kSOSConcordanceNoUserKey", // kSOSErrorNoKey
227 "kSOSConcordanceNoPeer", // kSOSErrorPeerNotFound
228 "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature
229 "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature
230 "kSOSConcordanceNoPeerSig",
231 "kSOSConcordanceWeSigned",
234 bool SOSAccountHandleUpdateCircle(SOSAccountRef account
, SOSCircleRef prospective_circle
, bool writeUpdate
, CFErrorRef
*error
)
237 bool haveOldCircle
= true;
238 const char *local_remote
= writeUpdate
? "local": "remote";
240 secnotice("signing", "start:[%s] %@", local_remote
, prospective_circle
);
241 if (!account
->user_public
|| !account
->user_public_trusted
) {
242 SOSCreateError(kSOSErrorPublicKeyAbsent
, CFSTR("Can't handle updates with no trusted public key here"), NULL
, error
);
246 if (!prospective_circle
) {
247 secerror("##### Can't update to a NULL circle ######");
248 return false; // Can't update one we don't have.
251 CFStringRef newCircleName
= SOSCircleGetName(prospective_circle
);
252 SOSCircleRef oldCircle
= (SOSCircleRef
) CFDictionaryGetValue(account
->circles
, newCircleName
);
253 SOSCircleRef emptyCircle
= NULL
;
255 if(oldCircle
== NULL
) {
256 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle
, NULL
, error
, NULL
, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle
);
257 secerror("##### Can't replace circle - we don't care about %@ ######", prospective_circle
);
260 if (CFGetTypeID(oldCircle
) != SOSCircleGetTypeID()) {
261 secdebug("badcircle", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
262 // We don't know what is in our table, likely it was kCFNull indicating we didn't
263 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
264 emptyCircle
= SOSCircleCreate(kCFAllocatorDefault
, newCircleName
, NULL
);
265 oldCircle
= emptyCircle
;
266 haveOldCircle
= false;
267 // And we're paranoid, drop our old peer info if for some reason we didn't before.
268 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
271 SOSFullPeerInfoRef me_full
= SOSAccountGetMyFullPeerInCircle(account
, oldCircle
, NULL
);
272 SOSPeerInfoRef me
= SOSFullPeerInfoGetPeerInfo(me_full
);
274 SOSTransportCircleRef transport
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, SOSCircleGetName(prospective_circle
));
276 SOSAccountScanForRetired(account
, prospective_circle
, error
);
277 SOSCircleRef newCircle
= SOSAccountCloneCircleWithRetirement(account
, prospective_circle
, error
);
278 if(!newCircle
) return false;
280 if (me
&& SOSCircleUpdatePeerInfo(newCircle
, me
)) {
281 writeUpdate
= true; // If we update our peer in the new circle we should write it if we accept it.
292 static const char *actionstring
[] = {
293 "accept", "countersign", "leave", "revert", "ignore",
296 circle_action_t circle_action
= ignore
;
297 enum DepartureReason leave_reason
= kSOSNeverLeftCircle
;
299 SecKeyRef old_circle_key
= NULL
;
300 if(SOSCircleVerify(oldCircle
, account
->user_public
, NULL
)) old_circle_key
= account
->user_public
;
301 else if(account
->previous_public
&& SOSCircleVerify(oldCircle
, account
->previous_public
, NULL
)) old_circle_key
= account
->previous_public
;
302 bool userTrustedOldCircle
= (old_circle_key
!= NULL
) && haveOldCircle
;
304 SOSConcordanceStatus concstat
=
305 SOSCircleConcordanceTrust(oldCircle
, newCircle
,
306 old_circle_key
, account
->user_public
,
309 CFStringRef concStr
= NULL
;
311 case kSOSConcordanceTrusted
:
312 circle_action
= countersign
;
313 concStr
= CFSTR("Trusted");
315 case kSOSConcordanceGenOld
:
316 circle_action
= userTrustedOldCircle
? revert
: ignore
;
317 concStr
= CFSTR("Generation Old");
319 case kSOSConcordanceBadUserSig
:
320 case kSOSConcordanceBadPeerSig
:
321 circle_action
= userTrustedOldCircle
? revert
: accept
;
322 concStr
= CFSTR("Bad Signature");
324 case kSOSConcordanceNoUserSig
:
325 circle_action
= userTrustedOldCircle
? revert
: accept
;
326 concStr
= CFSTR("No User Signature");
328 case kSOSConcordanceNoPeerSig
:
329 circle_action
= accept
; // We might like this one eventually but don't countersign.
330 concStr
= CFSTR("No trusted peer signature");
331 secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newCircle
);
333 case kSOSConcordanceNoPeer
:
334 circle_action
= leave
;
335 leave_reason
= kSOSLeftUntrustedCircle
;
336 concStr
= CFSTR("No trusted peer left");
338 case kSOSConcordanceNoUserKey
:
339 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
343 secerror("##### Bad Error Return from ConcordanceTrust");
348 secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring
[circle_action
], concordstring
[concstat
], userTrustedOldCircle
? "trusted" : "untrusted");
350 SOSCircleRef circleToPush
= NULL
;
352 if (circle_action
== leave
) {
353 circle_action
= ignore
;
355 if (me
&& SOSCircleHasPeer(oldCircle
, me
, NULL
)) {
356 if (sosAccountLeaveCircle(account
, newCircle
, error
)) {
357 account
->departure_code
= leave_reason
;
358 circleToPush
= newCircle
;
359 circle_action
= accept
;
365 // We are not in this circle, but we need to update account with it, since we got it from cloud
366 secnotice("updatecircle", "We are not in this circle, but we need to update account with it");
367 circle_action
= accept
;
371 if (circle_action
== countersign
) {
372 if (me
&& SOSCircleHasPeer(newCircle
, me
, NULL
) && !SOSCircleVerifyPeerSigned(newCircle
, me
, NULL
)) {
373 CFErrorRef signing_error
= NULL
;
375 if (me_full
&& SOSCircleConcordanceSign(newCircle
, me_full
, &signing_error
)) {
376 circleToPush
= newCircle
;
377 secnotice("signing", "Concurred with: %@", newCircle
);
379 secerror("Failed to concurrence sign, error: %@ Old: %@ New: %@", signing_error
, oldCircle
, newCircle
);
382 CFReleaseSafe(signing_error
);
384 circle_action
= accept
;
387 if (circle_action
== accept
) {
388 if (me
&& SOSCircleHasActivePeer(oldCircle
, me
, NULL
) && !SOSCircleHasPeer(newCircle
, me
, NULL
)) {
389 // Don't destroy evidence of other code determining reason for leaving.
390 if(!SOSAccountHasLeft(account
)) account
->departure_code
= kSOSMembershipRevoked
;
394 && SOSCircleHasActivePeer(oldCircle
, me
, NULL
)
395 && !(SOSCircleCountPeers(oldCircle
) == 1 && SOSCircleHasPeer(oldCircle
, me
, NULL
)) // If it was our offering, don't change ID to avoid ghosts
396 && !SOSCircleHasPeer(newCircle
, me
, NULL
) && !SOSCircleHasApplicant(newCircle
, me
, NULL
)) {
397 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me
), SOSCircleGetName(oldCircle
));
398 SOSAccountDestroyCirclePeerInfo(account
, oldCircle
, NULL
);
403 if (me
&& SOSCircleHasRejectedApplicant(newCircle
, me
, NULL
)) {
404 SOSPeerInfoRef reject
= SOSCircleCopyRejectedApplicant(newCircle
, me
, NULL
);
405 if(CFEqualSafe(reject
, me
) && SOSPeerInfoApplicationVerify(me
, account
->user_public
, NULL
)) {
406 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me
), SOSCircleGetName(oldCircle
));
407 SOSAccountDestroyCirclePeerInfo(account
, oldCircle
, NULL
);
411 SOSCircleRequestReadmission(newCircle
, account
->user_public
, me_full
, NULL
);
416 CFRetain(oldCircle
); // About to replace the oldCircle
417 CFDictionarySetValue(account
->circles
, newCircleName
, newCircle
);
418 SOSAccountSetPreviousPublic(account
);
420 secnotice("signing", "%@, Accepting circle: %@", concStr
, newCircle
);
422 if (me_full
&& account
->user_public_trusted
423 && SOSCircleHasApplicant(oldCircle
, me
, NULL
)
424 && SOSCircleCountPeers(newCircle
) > 0
425 && !SOSCircleHasPeer(newCircle
, me
, NULL
) && !SOSCircleHasApplicant(newCircle
, me
, NULL
)) {
426 // We weren't rejected (above would have set me to NULL.
427 // We were applying and we weren't accepted.
428 // Our application is declared lost, let us reapply.
430 if (SOSCircleRequestReadmission(newCircle
, account
->user_public
, me_full
, NULL
))
434 if (me
&& SOSCircleHasActivePeer(oldCircle
, me
, NULL
)) {
435 SOSAccountCleanupRetirementTickets(account
, RETIREMENT_FINALIZATION_SECONDS
, NULL
);
438 SOSAccountNotifyOfChange(account
, oldCircle
, newCircle
);
440 CFReleaseNull(oldCircle
);
443 circleToPush
= newCircle
;
444 SOSUpdateKeyInterest();
448 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
449 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
450 * are a member of oldCircle - never for an empty circle.
453 if (circle_action
== revert
) {
454 if(haveOldCircle
&& me
&& SOSCircleHasActivePeer(oldCircle
, me
, NULL
)) {
455 secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr
, newCircle
, oldCircle
);
456 circleToPush
= oldCircle
;
458 secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr
, newCircle
);
463 if (circleToPush
!= NULL
) {
464 secnotice("circleUpdate", "Pushing:[%s] %@", local_remote
, circleToPush
);
465 CFDataRef circle_data
= SOSCircleCopyEncodedData(circleToPush
, kCFAllocatorDefault
, error
);
467 success
&= SOSTransportCirclePostCircle(transport
, SOSCircleGetName(circleToPush
), circle_data
, error
);
471 CFReleaseNull(circle_data
);
473 success
= (success
&& SOSTransportCircleFlushChanges(transport
, error
));
476 CFReleaseSafe(newCircle
);
477 CFReleaseNull(emptyCircle
);