1//
2//  SOSAccountUpdate.c
3//  sec
4//
5
6#include "SOSAccountPriv.h"
7#include <SecureObjectSync/SOSTransportCircle.h>
8#include <SecureObjectSync/SOSTransport.h>
9#include <SecureObjectSync/SOSPeerInfoCollections.h>
10#include <CKBridge/SOSCloudKeychainClient.h>
11
12static void DifferenceAndCall(CFSetRef old_members, CFSetRef new_members, void (^updatedCircle)(CFSetRef additions, CFSetRef removals))
13{
14    CFMutableSetRef additions = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, new_members);
15    CFMutableSetRef removals = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, old_members);
16
17
18    CFSetForEach(old_members, ^(const void * value) {
19        CFSetRemoveValue(additions, value);
20    });
21
22    CFSetForEach(new_members, ^(const void * value) {
23        CFSetRemoveValue(removals, value);
24    });
25
26    updatedCircle(additions, removals);
27
28    CFReleaseSafe(additions);
29    CFReleaseSafe(removals);
30}
31
32static void SOSAccountNotifyEngines(SOSAccountRef account, SOSCircleRef new_circle,
33                                    CFSetRef added_peers, CFSetRef removed_peers,
34                                    CFSetRef added_applicants, CFSetRef removed_applicants)
35{
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;
40
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));
48        });
49    }
50
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);
55        if (engine)
56            SOSEngineCircleChanged(engine, myPi_id, trusted_peer_ids, untrusted_peer_ids);
57    }
58    CFReleaseSafe(dsNames);
59    CFReleaseNull(trusted_peer_ids);
60    CFReleaseNull(untrusted_peer_ids);
61}
62
63static void SOSAccountNotifyOfChange(SOSAccountRef account, SOSCircleRef oldCircle, SOSCircleRef newCircle)
64{
65    CFMutableSetRef old_members = SOSCircleCopyPeers(oldCircle, kCFAllocatorDefault);
66    CFMutableSetRef new_members = SOSCircleCopyPeers(newCircle, kCFAllocatorDefault);
67
68    CFMutableSetRef old_applicants = SOSCircleCopyApplicants(oldCircle, kCFAllocatorDefault);
69    CFMutableSetRef new_applicants = SOSCircleCopyApplicants(newCircle, kCFAllocatorDefault);
70
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);
76            });
77        });
78    });
79
80    CFReleaseNull(old_applicants);
81    CFReleaseNull(new_applicants);
82
83    CFReleaseNull(old_members);
84    CFReleaseNull(new_members);
85}
86
87void SOSAccountRecordRetiredPeerInCircleNamed(SOSAccountRef account, CFStringRef circleName, SOSPeerInfoRef retiree)
88{
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;
93
94        bool updated = SOSCircleUpdatePeerInfo(circle, retiree);
95        if (updated) {
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);
100        }
101        return updated;
102    });
103}
104
105static 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);
112        return NULL;
113    }
114
115    SOSCircleRef circle = NULL;
116    if (!value || isNull(value)) {
117        secnotice("circleCreat", "No circle found in data: %@", value);
118        circle = NULL;
119    } else {
120        circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, error);
121        if (circle) {
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);
128            }
129        } else {
130            secnotice("circleCreat", "SOSCircleCreateFromData returned NULL.");
131        }
132    }
133    return circle;
134}
135
136bool 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);
141    if (circle) {
142        success = SOSAccountUpdateCircleFromRemote(account, circle, &localError);
143        CFReleaseSafe(circle);
144    } else {
145        secerror("NULL circle found, ignoring ...");
146        success = true;  // don't pend this NULL thing.
147    }
148
149    if (!success) {
150        if (isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle)) {
151            secerror("Incompatible circle found, abandoning membership: %@", circleName);
152            SOSAccountDestroyCirclePeerInfoNamed(account, circleName, NULL);
153            CFDictionarySetValue(account->circles, circleName, kCFNull);
154        }
155
156        if (error) {
157            *error = localError;
158            localError = NULL;
159        }
160
161    }
162
163    CFReleaseNull(localError);
164
165    return success;
166}
167
168bool SOSAccountHandleParametersChange(SOSAccountRef account, CFDataRef parameters, CFErrorRef *error){
169
170    SecKeyRef newKey = NULL;
171    CFDataRef newParameters = NULL;
172    bool success = false;
173
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.");
177            success = true;
178        } else if (CFEqualSafe(account->previous_public, newKey)) {
179            secnotice("updates", "Got previous public key repeated. Ignoring.");
180            success = true;
181        } else {
182            CFReleaseNull(account->user_public);
183            SOSAccountPurgePrivateCredential(account);
184            CFReleaseNull(account->user_key_parameters);
185
186            account->user_public_trusted = false;
187
188            account->user_public = newKey;
189            newKey = NULL;
190
191            account->user_key_parameters = newParameters;
192            newParameters = NULL;
193
194            secnotice("updates", "Got new parameters for public key: %@", account->user_public);
195            debugDumpUserParameters(CFSTR("params"), account->user_key_parameters);
196
197            SOSUpdateKeyInterest();
198
199            success = true;
200        }
201    }
202
203    CFReleaseNull(newKey);
204    CFReleaseNull(newParameters);
205
206    return success;
207}
208
209static inline bool SOSAccountHasLeft(SOSAccountRef account) {
210    switch(account->departure_code) {
211        case kSOSWithdrewMembership: /* Fallthrough */
212        case kSOSMembershipRevoked: /* Fallthrough */
213        case kSOSLeftUntrustedCircle:
214            return true;
215        case kSOSNeverAppliedToCircle: /* Fallthrough */
216        case kSOSNeverLeftCircle: /* Fallthrough */
217        default:
218            return false;
219    }
220}
221
222static 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",
232};
233
234bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef prospective_circle, bool writeUpdate, CFErrorRef *error)
235{
236    bool success = true;
237    bool haveOldCircle = true;
238    const char *local_remote = writeUpdate ? "local": "remote";
239
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);
243        return false;
244    }
245
246    if (!prospective_circle) {
247        secerror("##### Can't update to a NULL circle ######");
248        return false; // Can't update one we don't have.
249    }
250
251    CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
252    SOSCircleRef oldCircle = (SOSCircleRef) CFDictionaryGetValue(account->circles, newCircleName);
253    SOSCircleRef emptyCircle = NULL;
254
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);
258        return false;
259    }
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);
269    }
270
271    SOSFullPeerInfoRef me_full = SOSAccountGetMyFullPeerInCircle(account, oldCircle, NULL);
272    SOSPeerInfoRef     me = SOSFullPeerInfoGetPeerInfo(me_full);
273
274    SOSTransportCircleRef transport = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(prospective_circle));
275
276    SOSAccountScanForRetired(account, prospective_circle, error);
277    SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
278    if(!newCircle) return false;
279
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.
282    }
283
284    typedef enum {
285        accept,
286        countersign,
287        leave,
288        revert,
289        ignore
290    } circle_action_t;
291
292    static const char *actionstring[] = {
293        "accept", "countersign", "leave", "revert", "ignore",
294    };
295
296    circle_action_t circle_action = ignore;
297    enum DepartureReason leave_reason = kSOSNeverLeftCircle;
298
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;
303
304    SOSConcordanceStatus concstat =
305    SOSCircleConcordanceTrust(oldCircle, newCircle,
306                              old_circle_key, account->user_public,
307                              me, error);
308
309    CFStringRef concStr = NULL;
310    switch(concstat) {
311        case kSOSConcordanceTrusted:
312            circle_action = countersign;
313            concStr = CFSTR("Trusted");
314            break;
315        case kSOSConcordanceGenOld:
316            circle_action = userTrustedOldCircle ? revert : ignore;
317            concStr = CFSTR("Generation Old");
318            break;
319        case kSOSConcordanceBadUserSig:
320        case kSOSConcordanceBadPeerSig:
321            circle_action = userTrustedOldCircle ? revert : accept;
322            concStr = CFSTR("Bad Signature");
323            break;
324        case kSOSConcordanceNoUserSig:
325            circle_action = userTrustedOldCircle ? revert : accept;
326            concStr = CFSTR("No User Signature");
327            break;
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);
332            break;
333        case kSOSConcordanceNoPeer:
334            circle_action = leave;
335            leave_reason = kSOSLeftUntrustedCircle;
336            concStr = CFSTR("No trusted peer left");
337            break;
338        case kSOSConcordanceNoUserKey:
339            secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
340            abort();
341            break;
342        default:
343            secerror("##### Bad Error Return from ConcordanceTrust");
344            abort();
345            break;
346    }
347
348    secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring[circle_action], concordstring[concstat], userTrustedOldCircle ? "trusted" : "untrusted");
349
350    SOSCircleRef circleToPush = NULL;
351
352    if (circle_action == leave) {
353        circle_action = ignore;
354
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;
360                me = NULL;
361                me_full = NULL;
362            }
363        }
364        else {
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;
368        }
369    }
370
371    if (circle_action == countersign) {
372        if (me && SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
373            CFErrorRef signing_error = NULL;
374
375            if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
376                circleToPush = newCircle;
377                secnotice("signing", "Concurred with: %@", newCircle);
378            } else {
379                secerror("Failed to concurrence sign, error: %@  Old: %@ New: %@", signing_error, oldCircle, newCircle);
380                success = false;
381            }
382            CFReleaseSafe(signing_error);
383        }
384        circle_action = accept;
385    }
386
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;
391        }
392
393        if (me
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);
399            me = NULL;
400            me_full = NULL;
401        }
402
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);
408                me = NULL;
409                me_full = NULL;
410            } else {
411                SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL);
412                writeUpdate = true;
413            }
414        }
415
416        CFRetain(oldCircle); // About to replace the oldCircle
417        CFDictionarySetValue(account->circles, newCircleName, newCircle);
418        SOSAccountSetPreviousPublic(account);
419
420        secnotice("signing", "%@, Accepting circle: %@", concStr, newCircle);
421
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.
429
430            if (SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL))
431                writeUpdate = true;
432        }
433
434        if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
435            SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL);
436        }
437
438        SOSAccountNotifyOfChange(account, oldCircle, newCircle);
439
440        CFReleaseNull(oldCircle);
441
442        if (writeUpdate)
443            circleToPush = newCircle;
444        SOSUpdateKeyInterest();
445    }
446
447    /*
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.
451     */
452
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;
457        } else {
458            secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr, newCircle);
459        }
460    }
461
462
463    if (circleToPush != NULL) {
464        secnotice("circleUpdate", "Pushing:[%s] %@", local_remote, circleToPush);
465        CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
466        if (circle_data) {
467            success &= SOSTransportCirclePostCircle(transport, SOSCircleGetName(circleToPush), circle_data, error);
468        } else {
469            success = false;
470        }
471        CFReleaseNull(circle_data);
472
473        success = (success && SOSTransportCircleFlushChanges(transport, error));
474    }
475
476    CFReleaseSafe(newCircle);
477    CFReleaseNull(emptyCircle);
478    return success;
479}
480