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 // Changed to just get the fullpeerinfo if present. We don't want to make up FPIs here.
272 SOSPeerInfoRef me
= NULL
;
273 SOSFullPeerInfoRef me_full
= SOSAccountGetMyFullPeerInCircleNamedIfPresent(account
, SOSCircleGetName(oldCircle
), NULL
);
274 if(me_full
) me
= SOSFullPeerInfoGetPeerInfo(me_full
);
276 SOSTransportCircleRef transport
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, SOSCircleGetName(prospective_circle
));
278 SOSAccountScanForRetired(account
, prospective_circle
, error
);
279 SOSCircleRef newCircle
= SOSAccountCloneCircleWithRetirement(account
, prospective_circle
, error
);
280 if(!newCircle
) return false;
282 if (me
&& SOSCircleUpdatePeerInfo(newCircle
, me
)) {
283 writeUpdate
= true; // If we update our peer in the new circle we should write it if we accept it.
294 static const char *actionstring
[] = {
295 "accept", "countersign", "leave", "revert", "ignore",
298 circle_action_t circle_action
= ignore
;
299 enum DepartureReason leave_reason
= kSOSNeverLeftCircle
;
301 SecKeyRef old_circle_key
= NULL
;
302 if(SOSCircleVerify(oldCircle
, account
->user_public
, NULL
)) old_circle_key
= account
->user_public
;
303 else if(account
->previous_public
&& SOSCircleVerify(oldCircle
, account
->previous_public
, NULL
)) old_circle_key
= account
->previous_public
;
304 bool userTrustedOldCircle
= (old_circle_key
!= NULL
) && haveOldCircle
;
306 SOSConcordanceStatus concstat
=
307 SOSCircleConcordanceTrust(oldCircle
, newCircle
,
308 old_circle_key
, account
->user_public
,
311 CFStringRef concStr
= NULL
;
313 case kSOSConcordanceTrusted
:
314 circle_action
= countersign
;
315 concStr
= CFSTR("Trusted");
317 case kSOSConcordanceGenOld
:
318 circle_action
= userTrustedOldCircle
? revert
: ignore
;
319 concStr
= CFSTR("Generation Old");
321 case kSOSConcordanceBadUserSig
:
322 case kSOSConcordanceBadPeerSig
:
323 circle_action
= userTrustedOldCircle
? revert
: accept
;
324 concStr
= CFSTR("Bad Signature");
326 case kSOSConcordanceNoUserSig
:
327 circle_action
= userTrustedOldCircle
? revert
: accept
;
328 concStr
= CFSTR("No User Signature");
330 case kSOSConcordanceNoPeerSig
:
331 circle_action
= accept
; // We might like this one eventually but don't countersign.
332 concStr
= CFSTR("No trusted peer signature");
333 secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newCircle
);
335 case kSOSConcordanceNoPeer
:
336 circle_action
= leave
;
337 leave_reason
= kSOSLeftUntrustedCircle
;
338 concStr
= CFSTR("No trusted peer left");
340 case kSOSConcordanceNoUserKey
:
341 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
345 secerror("##### Bad Error Return from ConcordanceTrust");
350 secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring
[circle_action
], concordstring
[concstat
], userTrustedOldCircle
? "trusted" : "untrusted");
352 SOSCircleRef circleToPush
= NULL
;
354 if (circle_action
== leave
) {
355 circle_action
= ignore
;
357 if (me
&& SOSCircleHasPeer(oldCircle
, me
, NULL
)) {
358 if (sosAccountLeaveCircle(account
, newCircle
, error
)) {
359 circleToPush
= newCircle
;
361 secnotice("signing", "Can't leave circle %@, but dumping identities", oldCircle
);
364 account
->departure_code
= leave_reason
;
365 circle_action
= accept
;
369 // We are not in this circle, but we need to update account with it, since we got it from cloud
370 secnotice("signing", "We are not in this circle, but we need to update account with it");
371 circle_action
= accept
;
375 if (circle_action
== countersign
) {
376 if (me
&& SOSCircleHasPeer(newCircle
, me
, NULL
) && !SOSCircleVerifyPeerSigned(newCircle
, me
, NULL
)) {
377 CFErrorRef signing_error
= NULL
;
379 if (me_full
&& SOSCircleConcordanceSign(newCircle
, me_full
, &signing_error
)) {
380 circleToPush
= newCircle
;
381 secnotice("signing", "Concurred with: %@", newCircle
);
383 secerror("Failed to concurrence sign, error: %@ Old: %@ New: %@", signing_error
, oldCircle
, newCircle
);
386 CFReleaseSafe(signing_error
);
388 circle_action
= accept
;
391 if (circle_action
== accept
) {
392 if (me
&& SOSCircleHasActivePeer(oldCircle
, me
, NULL
) && !SOSCircleHasPeer(newCircle
, me
, NULL
)) {
393 // Don't destroy evidence of other code determining reason for leaving.
394 if(!SOSAccountHasLeft(account
)) account
->departure_code
= kSOSMembershipRevoked
;
398 && SOSCircleHasActivePeer(oldCircle
, me
, NULL
)
399 && !(SOSCircleCountPeers(oldCircle
) == 1 && SOSCircleHasPeer(oldCircle
, me
, NULL
)) // If it was our offering, don't change ID to avoid ghosts
400 && !SOSCircleHasPeer(newCircle
, me
, NULL
) && !SOSCircleHasApplicant(newCircle
, me
, NULL
)) {
401 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me
), SOSCircleGetName(oldCircle
));
402 SOSAccountDestroyCirclePeerInfo(account
, oldCircle
, NULL
);
407 if (me
&& SOSCircleHasRejectedApplicant(newCircle
, me
, NULL
)) {
408 SOSPeerInfoRef reject
= SOSCircleCopyRejectedApplicant(newCircle
, me
, NULL
);
409 if(CFEqualSafe(reject
, me
) && SOSPeerInfoApplicationVerify(me
, account
->user_public
, NULL
)) {
410 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me
), SOSCircleGetName(oldCircle
));
411 SOSAccountDestroyCirclePeerInfo(account
, oldCircle
, NULL
);
415 SOSCircleRequestReadmission(newCircle
, account
->user_public
, me
, NULL
);
420 CFRetain(oldCircle
); // About to replace the oldCircle
421 CFDictionarySetValue(account
->circles
, newCircleName
, newCircle
);
422 SOSAccountSetPreviousPublic(account
);
424 secnotice("signing", "%@, Accepting circle: %@", concStr
, newCircle
);
426 if (me
&& account
->user_public_trusted
427 && SOSCircleHasApplicant(oldCircle
, me
, NULL
)
428 && SOSCircleCountPeers(newCircle
) > 0
429 && !SOSCircleHasPeer(newCircle
, me
, NULL
) && !SOSCircleHasApplicant(newCircle
, me
, NULL
)) {
430 // We weren't rejected (above would have set me to NULL.
431 // We were applying and we weren't accepted.
432 // Our application is declared lost, let us reapply.
434 if (SOSCircleRequestReadmission(newCircle
, account
->user_public
, me
, NULL
))
438 if (me
&& SOSCircleHasActivePeer(oldCircle
, me
, NULL
)) {
439 SOSAccountCleanupRetirementTickets(account
, RETIREMENT_FINALIZATION_SECONDS
, NULL
);
442 SOSAccountNotifyOfChange(account
, oldCircle
, newCircle
);
444 CFReleaseNull(oldCircle
);
447 circleToPush
= newCircle
;
448 SOSUpdateKeyInterest();
452 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
453 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
454 * are a member of oldCircle - never for an empty circle.
457 if (circle_action
== revert
) {
458 if(haveOldCircle
&& me
&& SOSCircleHasActivePeer(oldCircle
, me
, NULL
)) {
459 secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr
, newCircle
, oldCircle
);
460 circleToPush
= oldCircle
;
462 secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr
, newCircle
);
467 if (circleToPush
!= NULL
) {
468 secnotice("signing", "Pushing:[%s] %@", local_remote
, circleToPush
);
469 CFDataRef circle_data
= SOSCircleCopyEncodedData(circleToPush
, kCFAllocatorDefault
, error
);
471 success
&= SOSTransportCirclePostCircle(transport
, SOSCircleGetName(circleToPush
), circle_data
, error
);
475 CFReleaseNull(circle_data
);
477 success
= (success
&& SOSTransportCircleFlushChanges(transport
, error
));
480 CFReleaseSafe(newCircle
);
481 CFReleaseNull(emptyCircle
);