]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
1 | // |
2 | // SOSAccountUpdate.c | |
3 | // sec | |
4 | // | |
5 | ||
6 | #include "SOSAccountPriv.h" | |
fa7225c8 A |
7 | #include "SOSAccountLog.h" |
8 | ||
5c19dc3a A |
9 | #include <Security/SecureObjectSync/SOSAccountHSAJoin.h> |
10 | #include <Security/SecureObjectSync/SOSTransportCircle.h> | |
11 | #include <Security/SecureObjectSync/SOSTransport.h> | |
12 | #include <Security/SecureObjectSync/SOSViews.h> | |
13 | #include <Security/SecureObjectSync/SOSPeerInfoCollections.h> | |
14 | #include <Security/SecureObjectSync/SOSPeerInfoPriv.h> | |
15 | #include <Security/SecureObjectSync/SOSPeerInfoV2.h> | |
16 | #include <Security/SecureObjectSync/SOSPeerInfoDER.h> | |
17 | #include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h> | |
6b200bc3 A |
18 | #include <Security/SecureObjectSync/SOSAccountGhost.h> |
19 | ||
d8f41ccd A |
20 | |
21 | static void DifferenceAndCall(CFSetRef old_members, CFSetRef new_members, void (^updatedCircle)(CFSetRef additions, CFSetRef removals)) | |
22 | { | |
23 | CFMutableSetRef additions = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, new_members); | |
24 | CFMutableSetRef removals = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, old_members); | |
25 | ||
26 | ||
27 | CFSetForEach(old_members, ^(const void * value) { | |
28 | CFSetRemoveValue(additions, value); | |
29 | }); | |
30 | ||
31 | CFSetForEach(new_members, ^(const void * value) { | |
32 | CFSetRemoveValue(removals, value); | |
33 | }); | |
34 | ||
35 | updatedCircle(additions, removals); | |
36 | ||
37 | CFReleaseSafe(additions); | |
38 | CFReleaseSafe(removals); | |
39 | } | |
40 | ||
5c19dc3a A |
41 | static CFMutableSetRef SOSAccountCopyIntersectedViews(CFSetRef peerViews, CFSetRef myViews) { |
42 | __block CFMutableSetRef views = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); | |
43 | if (peerViews && myViews) CFSetForEach(peerViews, ^(const void *view) { | |
44 | if (CFSetContainsValue(myViews, view)) { | |
45 | CFSetAddValue(views, view); | |
46 | } | |
47 | }); | |
48 | return views; | |
49 | } | |
50 | ||
51 | static inline bool isSyncing(SOSPeerInfoRef peer, SecKeyRef upub) { | |
52 | if(!SOSPeerInfoApplicationVerify(peer, upub, NULL)) return false; | |
53 | if(SOSPeerInfoIsRetirementTicket(peer)) return false; | |
54 | return true; | |
55 | } | |
56 | ||
57 | static bool isBackupSOSRing(SOSRingRef ring) | |
58 | { | |
59 | return isSOSRing(ring) && (kSOSRingBackup == SOSRingGetType(ring)); | |
60 | } | |
61 | ||
5c19dc3a A |
62 | static void SOSAccountAppendPeerMetasForViewBackups(SOSAccountRef account, CFSetRef views, CFMutableArrayRef appendTo) |
63 | { | |
fa7225c8 A |
64 | CFMutableDictionaryRef ringToViewTable = NULL; |
65 | ||
66 | require_quiet(SOSAccountIsInCircle(account, NULL), done); | |
67 | ||
68 | require_action_quiet(SOSAccountHasCompletedRequiredBackupSync(account), done, | |
69 | secnotice("backup", "Haven't finished initial backup syncing, not registering backup metas with engine")); | |
5c19dc3a | 70 | |
fa7225c8 A |
71 | require_action_quiet(SOSPeerInfoV2DictionaryHasData(SOSAccountGetMyPeerInfo(account), sBackupKeyKey), done, |
72 | secnotice("backup", "No key to backup to, we don't enable individual view backups")); | |
73 | ||
74 | ringToViewTable = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
5c19dc3a A |
75 | |
76 | CFSetForEach(views, ^(const void *value) { | |
77 | CFStringRef viewName = value; | |
78 | if (isString(viewName) && !CFEqualSafe(viewName, kSOSViewKeychainV0)) { | |
79 | CFStringRef ringName = SOSBackupCopyRingNameForView(viewName); | |
80 | viewName = ringName; | |
fa7225c8 A |
81 | SOSRingRef ring = SOSAccountCopyRing(account, ringName, NULL); |
82 | if (ring && isBackupSOSRing(ring)) { | |
5c19dc3a A |
83 | CFTypeRef currentValue = (CFTypeRef) CFDictionaryGetValue(ringToViewTable, ring); |
84 | ||
85 | if (isSet(currentValue)) { | |
86 | CFSetAddValue((CFMutableSetRef)currentValue, viewName); | |
87 | } else { | |
88 | CFMutableSetRef viewNameSet = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); | |
89 | CFSetAddValue(viewNameSet, viewName); | |
90 | ||
91 | CFDictionarySetValue(ringToViewTable, ring, viewNameSet); | |
92 | CFReleaseNull(viewNameSet); | |
93 | } | |
94 | } else { | |
95 | secwarning("View '%@' not being backed up – ring %@:%@ not backup ring.", viewName, ringName, ring); | |
96 | } | |
97 | CFReleaseNull(ringName); | |
fa7225c8 | 98 | CFReleaseNull(ring); |
5c19dc3a A |
99 | } |
100 | }); | |
101 | ||
5c19dc3a A |
102 | CFDictionaryForEach(ringToViewTable, ^(const void *key, const void *value) { |
103 | SOSRingRef ring = (SOSRingRef) key; | |
fa7225c8 A |
104 | CFSetRef viewNames = asSet(value, NULL); |
105 | if (isSOSRing(ring) && viewNames) { | |
106 | if (SOSAccountIntersectsWithOutstanding(account, viewNames)) { | |
107 | CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) { | |
108 | secnotice("engine-notify", "Not ready, no peer meta: R: %@ Vs: %@", SOSRingGetName(ring), ringViews); | |
109 | }); | |
5c19dc3a A |
110 | } else { |
111 | bool meta_added = false; | |
112 | CFErrorRef create_error = NULL; | |
113 | SOSBackupSliceKeyBagRef key_bag = NULL; | |
114 | SOSPeerMetaRef newMeta = NULL; | |
115 | ||
116 | CFDataRef ring_payload = SOSRingGetPayload(ring, NULL); | |
117 | require_quiet(isData(ring_payload), skip); | |
118 | ||
119 | key_bag = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, ring_payload, &create_error); | |
120 | require_quiet(key_bag, skip); | |
121 | ||
122 | newMeta = SOSPeerMetaCreateWithComponents(SOSRingGetName(ring), viewNames, ring_payload); | |
123 | require_quiet(SecAllocationError(newMeta, &create_error, CFSTR("Didn't make peer meta for: %@"), ring), skip); | |
124 | CFArrayAppendValue(appendTo, newMeta); | |
125 | ||
fa7225c8 A |
126 | CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) { |
127 | secnotice("engine-notify", "Backup peer meta: R: %@ Vs: %@ VD: %@", SOSRingGetName(ring), ringViews, ring_payload); | |
128 | }); | |
5c19dc3a A |
129 | |
130 | meta_added = true; | |
131 | ||
132 | skip: | |
133 | if (!meta_added) { | |
fa7225c8 A |
134 | CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) { |
135 | secerror("Failed to register backup meta from %@ for views %@. Error (%@)", ring, ringViews, create_error); | |
136 | }); | |
5c19dc3a A |
137 | } |
138 | CFReleaseNull(newMeta); | |
139 | CFReleaseNull(key_bag); | |
140 | CFReleaseNull(create_error); | |
141 | } | |
142 | } | |
143 | }); | |
144 | ||
fa7225c8 | 145 | done: |
5c19dc3a A |
146 | CFReleaseNull(ringToViewTable); |
147 | } | |
148 | ||
149 | bool SOSAccountSyncingV0(SOSAccountRef account) { | |
150 | __block bool syncingV0 = false; | |
151 | SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) { | |
152 | if (SOSPeerInfoIsEnabledView(peer, kSOSViewKeychainV0)) { | |
153 | syncingV0 = true; | |
154 | } | |
155 | }); | |
156 | ||
157 | return syncingV0; | |
158 | } | |
159 | ||
160 | void SOSAccountNotifyEngines(SOSAccountRef account) | |
d8f41ccd | 161 | { |
5c19dc3a A |
162 | SOSPeerInfoRef myPi = SOSFullPeerInfoGetPeerInfo(account->my_identity); |
163 | CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi); | |
164 | CFMutableArrayRef syncing_peer_metas = NULL; | |
165 | CFMutableArrayRef zombie_peer_metas = NULL; | |
166 | CFErrorRef localError = NULL; | |
167 | SOSPeerMetaRef myMeta = NULL; | |
168 | ||
169 | if (myPi_id && isSyncing(myPi, account->user_public) && SOSCircleHasPeer(account->trusted_circle, myPi, NULL)) { | |
170 | CFMutableSetRef myViews = SOSPeerInfoCopyEnabledViews(myPi); | |
171 | ||
172 | // We add V0 views to everyone if we see a V0 peer, or a peer with the view explicity enabled | |
173 | // V2 peers shouldn't be explicity enabling the uber V0 view, though the seeds did. | |
174 | __block bool addV0Views = SOSAccountSyncingV0(account); | |
175 | ||
176 | syncing_peer_metas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
177 | zombie_peer_metas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
178 | SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) { | |
179 | CFMutableArrayRef arrayToAddTo = isSyncing(peer, account->user_public) ? syncing_peer_metas : zombie_peer_metas; | |
180 | ||
181 | // Compute views each peer is in that we are also in ourselves | |
182 | CFMutableSetRef peerEnabledViews = SOSPeerInfoCopyEnabledViews(peer); | |
183 | CFMutableSetRef views = SOSAccountCopyIntersectedViews(peerEnabledViews, myViews); | |
184 | CFReleaseNull(peerEnabledViews); | |
185 | ||
186 | if(addV0Views) { | |
187 | CFSetAddValue(views, kSOSViewKeychainV0); | |
188 | } | |
189 | ||
fa7225c8 A |
190 | CFStringSetPerformWithDescription(views, ^(CFStringRef viewsDescription) { |
191 | secnotice("engine-notify", "Meta: %@: %@", SOSPeerInfoGetPeerID(peer), viewsDescription); | |
192 | }); | |
193 | ||
5c19dc3a A |
194 | SOSPeerMetaRef peerMeta = SOSPeerMetaCreateWithComponents(SOSPeerInfoGetPeerID(peer), views, NULL); |
195 | CFReleaseNull(views); | |
196 | ||
197 | CFArrayAppendValue(arrayToAddTo, peerMeta); | |
198 | CFReleaseNull(peerMeta); | |
d8f41ccd | 199 | }); |
5c19dc3a | 200 | |
fa7225c8 A |
201 | // We don't make a backup peer meta for the magic V0 peer |
202 | // Set up all the rest before we munge the set | |
5c19dc3a | 203 | SOSAccountAppendPeerMetasForViewBackups(account, myViews, syncing_peer_metas); |
5c19dc3a A |
204 | |
205 | // If we saw someone else needing V0, we sync V0, too! | |
206 | if (addV0Views) { | |
207 | CFSetAddValue(myViews, kSOSViewKeychainV0); | |
208 | } | |
209 | ||
fa7225c8 A |
210 | CFStringSetPerformWithDescription(myViews, ^(CFStringRef viewsDescription) { |
211 | secnotice("engine-notify", "My Meta: %@: %@", myPi_id, viewsDescription); | |
212 | }); | |
5c19dc3a A |
213 | myMeta = SOSPeerMetaCreateWithComponents(myPi_id, myViews, NULL); |
214 | CFReleaseSafe(myViews); | |
d8f41ccd A |
215 | } |
216 | ||
5c19dc3a A |
217 | SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL); |
218 | if (engine) { | |
219 | SOSEngineCircleChanged(engine, myMeta, syncing_peer_metas, zombie_peer_metas); | |
d8f41ccd | 220 | } |
5c19dc3a A |
221 | |
222 | CFReleaseNull(myMeta); | |
223 | CFReleaseSafe(localError); | |
224 | CFReleaseNull(syncing_peer_metas); | |
225 | CFReleaseNull(zombie_peer_metas); | |
d8f41ccd A |
226 | } |
227 | ||
fa7225c8 | 228 | // Upcoming call to View Changes Here |
d8f41ccd A |
229 | static void SOSAccountNotifyOfChange(SOSAccountRef account, SOSCircleRef oldCircle, SOSCircleRef newCircle) |
230 | { | |
5c19dc3a A |
231 | account->circle_rings_retirements_need_attention = true; |
232 | ||
d8f41ccd A |
233 | CFMutableSetRef old_members = SOSCircleCopyPeers(oldCircle, kCFAllocatorDefault); |
234 | CFMutableSetRef new_members = SOSCircleCopyPeers(newCircle, kCFAllocatorDefault); | |
235 | ||
236 | CFMutableSetRef old_applicants = SOSCircleCopyApplicants(oldCircle, kCFAllocatorDefault); | |
237 | CFMutableSetRef new_applicants = SOSCircleCopyApplicants(newCircle, kCFAllocatorDefault); | |
5c19dc3a | 238 | |
e0e0d90e A |
239 | SOSPeerInfoRef me = SOSAccountGetMyPeerInfo(account); |
240 | if(me && CFSetContainsValue(new_members, me)) | |
241 | SOSAccountSetValue(account, kSOSEscrowRecord, kCFNull, NULL); //removing the escrow records from the account object | |
242 | ||
d8f41ccd A |
243 | DifferenceAndCall(old_members, new_members, ^(CFSetRef added_members, CFSetRef removed_members) { |
244 | DifferenceAndCall(old_applicants, new_applicants, ^(CFSetRef added_applicants, CFSetRef removed_applicants) { | |
d8f41ccd | 245 | CFArrayForEach(account->change_blocks, ^(const void * notificationBlock) { |
5c19dc3a | 246 | secnotice("updates", "calling change block"); |
d8f41ccd A |
247 | ((SOSAccountCircleMembershipChangeBlock) notificationBlock)(newCircle, added_members, removed_members, added_applicants, removed_applicants); |
248 | }); | |
249 | }); | |
250 | }); | |
5c19dc3a | 251 | |
d8f41ccd A |
252 | CFReleaseNull(old_applicants); |
253 | CFReleaseNull(new_applicants); | |
254 | ||
255 | CFReleaseNull(old_members); | |
256 | CFReleaseNull(new_members); | |
257 | } | |
258 | ||
5c19dc3a A |
259 | CF_RETURNS_RETAINED |
260 | CFDictionaryRef SOSAccountHandleRetirementMessages(SOSAccountRef account, CFDictionaryRef circle_retirement_messages, CFErrorRef *error) { | |
261 | CFStringRef circle_name = SOSCircleGetName(account->trusted_circle); | |
262 | CFMutableArrayRef handledRetirementIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
263 | // We only handle one circle, look it up: | |
264 | ||
265 | require_quiet(account->trusted_circle, finish); // We don't fail, we intentionally handle nothing. | |
fa7225c8 A |
266 | CFDictionaryRef retirement_dictionary = asDictionary(CFDictionaryGetValue(circle_retirement_messages, circle_name), error); |
267 | require_quiet(retirement_dictionary, finish); | |
268 | CFDictionaryForEach(retirement_dictionary, ^(const void *key, const void *value) { | |
5c19dc3a A |
269 | if(isData(value)) { |
270 | SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value); | |
271 | if(pi && CFEqual(key, SOSPeerInfoGetPeerID(pi)) && SOSPeerInfoInspectRetirementTicket(pi, error)) { | |
272 | CFSetAddValue(account->retirees, pi); | |
273 | ||
e0e0d90e | 274 | account->circle_rings_retirements_need_attention = true; // Have to handle retirements. |
5c19dc3a A |
275 | |
276 | CFArrayAppendValue(handledRetirementIDs, key); | |
277 | } | |
278 | CFReleaseNull(pi); | |
279 | } | |
280 | }); | |
281 | ||
282 | // If we are in the retiree list, we somehow got resurrected | |
283 | // clearly we took care of proper departure before so leave | |
284 | // and delcare that we withdrew this time. | |
285 | SOSPeerInfoRef me = SOSAccountGetMyPeerInfo(account); | |
286 | if (me && CFSetContainsValue(account->retirees, me)) { | |
287 | SOSAccountPurgeIdentity(account); | |
288 | account->departure_code = kSOSDiscoveredRetirement; | |
289 | } | |
290 | ||
291 | finish: | |
292 | { | |
293 | CFDictionaryRef result = (CFArrayGetCount(handledRetirementIDs) == 0) ? CFDictionaryCreateForCFTypes(kCFAllocatorDefault, NULL) | |
294 | : CFDictionaryCreateForCFTypes(kCFAllocatorDefault, circle_name, handledRetirementIDs, NULL); | |
295 | ||
296 | CFReleaseNull(handledRetirementIDs); | |
297 | return result; | |
298 | } | |
299 | } | |
300 | ||
d8f41ccd A |
301 | static SOSCircleRef SOSAccountCreateCircleFrom(CFStringRef circleName, CFTypeRef value, CFErrorRef *error) { |
302 | if (value && !isData(value) && !isNull(value)) { | |
303 | secnotice("circleCreat", "Value provided not appropriate for a circle"); | |
304 | CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(value)); | |
305 | SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL, | |
306 | CFSTR("Expected data or NULL got %@"), description); | |
307 | CFReleaseSafe(description); | |
308 | return NULL; | |
309 | } | |
310 | ||
311 | SOSCircleRef circle = NULL; | |
312 | if (!value || isNull(value)) { | |
313 | secnotice("circleCreat", "No circle found in data: %@", value); | |
314 | circle = NULL; | |
315 | } else { | |
316 | circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, error); | |
317 | if (circle) { | |
318 | CFStringRef name = SOSCircleGetName(circle); | |
319 | if (!CFEqualSafe(name, circleName)) { | |
320 | secnotice("circleCreat", "Expected circle named %@, got %@", circleName, name); | |
321 | SOSCreateErrorWithFormat(kSOSErrorNameMismatch, NULL, error, NULL, | |
322 | CFSTR("Expected circle named %@, got %@"), circleName, name); | |
323 | CFReleaseNull(circle); | |
324 | } | |
325 | } else { | |
326 | secnotice("circleCreat", "SOSCircleCreateFromData returned NULL."); | |
327 | } | |
328 | } | |
329 | return circle; | |
330 | } | |
331 | ||
332 | bool SOSAccountHandleCircleMessage(SOSAccountRef account, | |
333 | CFStringRef circleName, CFDataRef encodedCircleMessage, CFErrorRef *error) { | |
334 | bool success = false; | |
335 | CFErrorRef localError = NULL; | |
336 | SOSCircleRef circle = SOSAccountCreateCircleFrom(circleName, encodedCircleMessage, &localError); | |
337 | if (circle) { | |
338 | success = SOSAccountUpdateCircleFromRemote(account, circle, &localError); | |
339 | CFReleaseSafe(circle); | |
340 | } else { | |
341 | secerror("NULL circle found, ignoring ..."); | |
342 | success = true; // don't pend this NULL thing. | |
343 | } | |
344 | ||
345 | if (!success) { | |
346 | if (isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle)) { | |
347 | secerror("Incompatible circle found, abandoning membership: %@", circleName); | |
5c19dc3a A |
348 | CFReleaseNull(account->my_identity); |
349 | CFReleaseNull(account->trusted_circle); | |
d8f41ccd A |
350 | } |
351 | ||
352 | if (error) { | |
353 | *error = localError; | |
354 | localError = NULL; | |
355 | } | |
356 | ||
357 | } | |
358 | ||
359 | CFReleaseNull(localError); | |
360 | ||
361 | return success; | |
362 | } | |
363 | ||
364 | bool SOSAccountHandleParametersChange(SOSAccountRef account, CFDataRef parameters, CFErrorRef *error){ | |
365 | ||
366 | SecKeyRef newKey = NULL; | |
367 | CFDataRef newParameters = NULL; | |
368 | bool success = false; | |
369 | ||
370 | if(SOSAccountRetrieveCloudParameters(account, &newKey, parameters, &newParameters, error)) { | |
fa7225c8 A |
371 | debugDumpUserParameters(CFSTR("SOSAccountHandleParametersChange got new user key parameters:"), parameters); |
372 | secnotice("keygen", "SOSAccountHandleParametersChange got new public key: %@", newKey); | |
373 | ||
d8f41ccd A |
374 | if (CFEqualSafe(account->user_public, newKey)) { |
375 | secnotice("updates", "Got same public key sent our way. Ignoring."); | |
376 | success = true; | |
377 | } else if (CFEqualSafe(account->previous_public, newKey)) { | |
378 | secnotice("updates", "Got previous public key repeated. Ignoring."); | |
379 | success = true; | |
380 | } else { | |
5c19dc3a | 381 | SOSAccountSetUnTrustedUserPublicKey(account, newKey); |
e0e0d90e | 382 | SOSAccountSetParameters(account, newParameters); |
5c19dc3a | 383 | newKey = NULL; |
5c19dc3a A |
384 | |
385 | if(SOSAccountRetryUserCredentials(account)) { | |
fa7225c8 | 386 | secnotice("keygen", "Successfully used cached password with new parameters"); |
5c19dc3a A |
387 | SOSAccountGenerationSignatureUpdate(account, error); |
388 | } else { | |
389 | SOSAccountPurgePrivateCredential(account); | |
fa7225c8 | 390 | secnotice("keygen", "Got new parameters for public key - could not find or use cached password"); |
5c19dc3a | 391 | } |
d8f41ccd | 392 | |
e0e0d90e | 393 | account->circle_rings_retirements_need_attention = true; |
fa7225c8 | 394 | account->key_interests_need_updating = true; |
d8f41ccd A |
395 | |
396 | success = true; | |
397 | } | |
398 | } | |
399 | ||
400 | CFReleaseNull(newKey); | |
401 | CFReleaseNull(newParameters); | |
402 | ||
403 | return success; | |
404 | } | |
405 | ||
406 | static inline bool SOSAccountHasLeft(SOSAccountRef account) { | |
407 | switch(account->departure_code) { | |
5c19dc3a A |
408 | case kSOSDiscoveredRetirement: /* Fallthrough */ |
409 | case kSOSLostPrivateKey: /* Fallthrough */ | |
d8f41ccd A |
410 | case kSOSWithdrewMembership: /* Fallthrough */ |
411 | case kSOSMembershipRevoked: /* Fallthrough */ | |
412 | case kSOSLeftUntrustedCircle: | |
413 | return true; | |
414 | case kSOSNeverAppliedToCircle: /* Fallthrough */ | |
415 | case kSOSNeverLeftCircle: /* Fallthrough */ | |
416 | default: | |
417 | return false; | |
418 | } | |
419 | } | |
420 | ||
421 | static const char *concordstring[] = { | |
422 | "kSOSConcordanceTrusted", | |
423 | "kSOSConcordanceGenOld", // kSOSErrorReplay | |
424 | "kSOSConcordanceNoUserSig", // kSOSErrorBadSignature | |
425 | "kSOSConcordanceNoUserKey", // kSOSErrorNoKey | |
426 | "kSOSConcordanceNoPeer", // kSOSErrorPeerNotFound | |
427 | "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature | |
428 | "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature | |
429 | "kSOSConcordanceNoPeerSig", | |
430 | "kSOSConcordanceWeSigned", | |
431 | }; | |
432 | ||
fa7225c8 | 433 | |
d8f41ccd A |
434 | bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef prospective_circle, bool writeUpdate, CFErrorRef *error) |
435 | { | |
436 | bool success = true; | |
437 | bool haveOldCircle = true; | |
438 | const char *local_remote = writeUpdate ? "local": "remote"; | |
439 | ||
fa7225c8 | 440 | secnotice("signing", "start:[%s]", local_remote); |
d8f41ccd A |
441 | if (!account->user_public || !account->user_public_trusted) { |
442 | SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error); | |
443 | return false; | |
444 | } | |
445 | ||
446 | if (!prospective_circle) { | |
447 | secerror("##### Can't update to a NULL circle ######"); | |
448 | return false; // Can't update one we don't have. | |
449 | } | |
450 | ||
451 | CFStringRef newCircleName = SOSCircleGetName(prospective_circle); | |
5c19dc3a | 452 | SOSCircleRef oldCircle = account->trusted_circle; |
d8f41ccd A |
453 | SOSCircleRef emptyCircle = NULL; |
454 | ||
455 | if(oldCircle == NULL) { | |
456 | SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle); | |
fa7225c8 | 457 | secerror("##### Can't replace circle - we don't care about it ######"); |
d8f41ccd A |
458 | return false; |
459 | } | |
460 | if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) { | |
5c19dc3a | 461 | secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); |
d8f41ccd A |
462 | // We don't know what is in our table, likely it was kCFNull indicating we didn't |
463 | // understand a circle that came by. We seem to like this one lets make our entry be empty circle | |
464 | emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL); | |
465 | oldCircle = emptyCircle; | |
466 | haveOldCircle = false; | |
467 | // And we're paranoid, drop our old peer info if for some reason we didn't before. | |
468 | // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL); | |
469 | } | |
470 | ||
d8f41ccd | 471 | |
5c19dc3a | 472 | SOSTransportCircleRef transport = account->circle_transport; |
d8f41ccd A |
473 | |
474 | SOSAccountScanForRetired(account, prospective_circle, error); | |
475 | SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error); | |
476 | if(!newCircle) return false; | |
6b200bc3 A |
477 | |
478 | SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle); | |
479 | if(ghostCleaned) { | |
480 | CFRetainAssign(newCircle, ghostCleaned); | |
481 | writeUpdate = true; | |
482 | } | |
d8f41ccd | 483 | |
fa7225c8 A |
484 | SOSFullPeerInfoRef me_full = account->my_identity; |
485 | SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full); | |
486 | CFStringRef myPeerID = SOSPeerInfoGetPeerID(me); | |
487 | myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer"); | |
488 | ||
d8f41ccd A |
489 | if (me && SOSCircleUpdatePeerInfo(newCircle, me)) { |
490 | writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it. | |
491 | } | |
492 | ||
493 | typedef enum { | |
494 | accept, | |
495 | countersign, | |
496 | leave, | |
497 | revert, | |
498 | ignore | |
499 | } circle_action_t; | |
500 | ||
501 | static const char *actionstring[] = { | |
502 | "accept", "countersign", "leave", "revert", "ignore", | |
503 | }; | |
504 | ||
505 | circle_action_t circle_action = ignore; | |
506 | enum DepartureReason leave_reason = kSOSNeverLeftCircle; | |
507 | ||
508 | SecKeyRef old_circle_key = NULL; | |
509 | if(SOSCircleVerify(oldCircle, account->user_public, NULL)) old_circle_key = account->user_public; | |
510 | else if(account->previous_public && SOSCircleVerify(oldCircle, account->previous_public, NULL)) old_circle_key = account->previous_public; | |
511 | bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle; | |
512 | ||
513 | SOSConcordanceStatus concstat = | |
514 | SOSCircleConcordanceTrust(oldCircle, newCircle, | |
515 | old_circle_key, account->user_public, | |
516 | me, error); | |
517 | ||
518 | CFStringRef concStr = NULL; | |
519 | switch(concstat) { | |
520 | case kSOSConcordanceTrusted: | |
521 | circle_action = countersign; | |
522 | concStr = CFSTR("Trusted"); | |
523 | break; | |
524 | case kSOSConcordanceGenOld: | |
525 | circle_action = userTrustedOldCircle ? revert : ignore; | |
526 | concStr = CFSTR("Generation Old"); | |
527 | break; | |
528 | case kSOSConcordanceBadUserSig: | |
529 | case kSOSConcordanceBadPeerSig: | |
530 | circle_action = userTrustedOldCircle ? revert : accept; | |
531 | concStr = CFSTR("Bad Signature"); | |
532 | break; | |
533 | case kSOSConcordanceNoUserSig: | |
534 | circle_action = userTrustedOldCircle ? revert : accept; | |
535 | concStr = CFSTR("No User Signature"); | |
536 | break; | |
537 | case kSOSConcordanceNoPeerSig: | |
538 | circle_action = accept; // We might like this one eventually but don't countersign. | |
539 | concStr = CFSTR("No trusted peer signature"); | |
fa7225c8 | 540 | secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later"); |
d8f41ccd A |
541 | break; |
542 | case kSOSConcordanceNoPeer: | |
543 | circle_action = leave; | |
544 | leave_reason = kSOSLeftUntrustedCircle; | |
545 | concStr = CFSTR("No trusted peer left"); | |
546 | break; | |
547 | case kSOSConcordanceNoUserKey: | |
548 | secerror("##### No User Public Key Available, this shouldn't ever happen!!!"); | |
549 | abort(); | |
550 | break; | |
551 | default: | |
552 | secerror("##### Bad Error Return from ConcordanceTrust"); | |
553 | abort(); | |
554 | break; | |
555 | } | |
556 | ||
fa7225c8 | 557 | secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle. My PeerID is %@", actionstring[circle_action], concordstring[concstat], userTrustedOldCircle ? "trusted" : "untrusted", myPeerID); |
d8f41ccd A |
558 | |
559 | SOSCircleRef circleToPush = NULL; | |
560 | ||
561 | if (circle_action == leave) { | |
d87e1158 | 562 | circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store. |
d8f41ccd A |
563 | |
564 | if (me && SOSCircleHasPeer(oldCircle, me, NULL)) { | |
5c19dc3a A |
565 | secnotice("account", "Leaving circle with peer %@", me); |
566 | debugDumpCircle(CFSTR("oldCircle"), oldCircle); | |
567 | debugDumpCircle(CFSTR("newCircle"), newCircle); | |
568 | debugDumpCircle(CFSTR("prospective_circle"), prospective_circle); | |
569 | secnotice("account", "Key state: user_public %@, previous_public %@, old_circle_key %@", | |
570 | account->user_public, account->previous_public, old_circle_key); | |
571 | ||
d8f41ccd | 572 | if (sosAccountLeaveCircle(account, newCircle, error)) { |
fa7225c8 | 573 | secnotice("leaveCircle", "Leaving circle by newcircle state"); |
d8f41ccd | 574 | circleToPush = newCircle; |
949d2ff0 | 575 | } else { |
fa7225c8 | 576 | secnotice("signing", "Can't leave circle, but dumping identities"); |
949d2ff0 | 577 | success = false; |
d8f41ccd | 578 | } |
949d2ff0 A |
579 | account->departure_code = leave_reason; |
580 | circle_action = accept; | |
581 | me = NULL; | |
582 | me_full = NULL; | |
583 | } else { | |
d8f41ccd | 584 | // We are not in this circle, but we need to update account with it, since we got it from cloud |
949d2ff0 | 585 | secnotice("signing", "We are not in this circle, but we need to update account with it"); |
5c19dc3a A |
586 | debugDumpCircle(CFSTR("oldCircle"), oldCircle); |
587 | debugDumpCircle(CFSTR("newCircle"), newCircle); | |
588 | debugDumpCircle(CFSTR("prospective_circle"), prospective_circle); | |
d8f41ccd A |
589 | circle_action = accept; |
590 | } | |
591 | } | |
592 | ||
593 | if (circle_action == countersign) { | |
5c19dc3a A |
594 | if (me && SOSCircleHasPeer(newCircle, me, NULL)) { |
595 | if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) { | |
fa7225c8 | 596 | secnotice("signing", "Already concur with the new circle"); |
5c19dc3a A |
597 | } else { |
598 | CFErrorRef signing_error = NULL; | |
599 | ||
600 | if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) { | |
601 | circleToPush = newCircle; | |
fa7225c8 | 602 | secnotice("signing", "Concurred with new circle"); |
5c19dc3a | 603 | } else { |
fa7225c8 | 604 | secerror("Failed to concurrence sign, error: %@", signing_error); |
5c19dc3a A |
605 | success = false; |
606 | } | |
607 | CFReleaseSafe(signing_error); | |
608 | } | |
d8f41ccd | 609 | |
5c19dc3a | 610 | if(SOSAccountVerifyAndAcceptHSAApplicants(account, newCircle, error)) { |
d8f41ccd | 611 | circleToPush = newCircle; |
5c19dc3a | 612 | writeUpdate = true; |
d8f41ccd | 613 | } |
5c19dc3a | 614 | } else { |
fa7225c8 | 615 | secnotice("signing", "Not countersigning, not in new circle"); |
5c19dc3a | 616 | debugDumpCircle(CFSTR("circle to countersign"), newCircle); |
d8f41ccd A |
617 | } |
618 | circle_action = accept; | |
619 | } | |
620 | ||
621 | if (circle_action == accept) { | |
622 | if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) { | |
623 | // Don't destroy evidence of other code determining reason for leaving. | |
624 | if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked; | |
5c19dc3a A |
625 | secnotice("account", "Member of old circle but not of new circle"); |
626 | debugDumpCircle(CFSTR("oldCircle"), oldCircle); | |
627 | debugDumpCircle(CFSTR("newCircle"), newCircle); | |
d8f41ccd A |
628 | } |
629 | ||
630 | if (me | |
631 | && SOSCircleHasActivePeer(oldCircle, me, NULL) | |
632 | && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts | |
633 | && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) { | |
634 | secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle)); | |
5c19dc3a A |
635 | if (account->my_identity) |
636 | SOSFullPeerInfoPurgePersistentKey(account->my_identity, NULL); | |
637 | CFReleaseNull(account->my_identity); | |
d8f41ccd A |
638 | me = NULL; |
639 | me_full = NULL; | |
640 | } | |
641 | ||
642 | if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) { | |
643 | SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL); | |
644 | if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account->user_public, NULL)) { | |
645 | secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle)); | |
5c19dc3a A |
646 | debugDumpCircle(CFSTR("oldCircle"), oldCircle); |
647 | debugDumpCircle(CFSTR("newCircle"), newCircle); | |
648 | if (account->my_identity) | |
649 | SOSFullPeerInfoPurgePersistentKey(account->my_identity, NULL); | |
650 | CFReleaseNull(account->my_identity); | |
d8f41ccd A |
651 | me = NULL; |
652 | me_full = NULL; | |
653 | } else { | |
5c19dc3a A |
654 | secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle)); |
655 | debugDumpCircle(CFSTR("oldCircle"), oldCircle); | |
656 | debugDumpCircle(CFSTR("newCircle"), newCircle); | |
949d2ff0 | 657 | SOSCircleRequestReadmission(newCircle, account->user_public, me, NULL); |
d8f41ccd A |
658 | writeUpdate = true; |
659 | } | |
660 | } | |
661 | ||
5c19dc3a A |
662 | CFRetainSafe(oldCircle); |
663 | CFRetainAssign(account->trusted_circle, newCircle); | |
d8f41ccd A |
664 | SOSAccountSetPreviousPublic(account); |
665 | ||
fa7225c8 | 666 | secnotice("signing", "%@, Accepting new circle", concStr); |
d8f41ccd | 667 | |
949d2ff0 | 668 | if (me && account->user_public_trusted |
d8f41ccd A |
669 | && SOSCircleHasApplicant(oldCircle, me, NULL) |
670 | && SOSCircleCountPeers(newCircle) > 0 | |
671 | && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) { | |
672 | // We weren't rejected (above would have set me to NULL. | |
673 | // We were applying and we weren't accepted. | |
674 | // Our application is declared lost, let us reapply. | |
675 | ||
fa7225c8 | 676 | secnotice("signing", "requesting readmission to new circle"); |
949d2ff0 | 677 | if (SOSCircleRequestReadmission(newCircle, account->user_public, me, NULL)) |
d8f41ccd A |
678 | writeUpdate = true; |
679 | } | |
680 | ||
681 | if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) { | |
682 | SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL); | |
683 | } | |
5c19dc3a | 684 | |
d8f41ccd A |
685 | SOSAccountNotifyOfChange(account, oldCircle, newCircle); |
686 | ||
687 | CFReleaseNull(oldCircle); | |
688 | ||
689 | if (writeUpdate) | |
690 | circleToPush = newCircle; | |
fa7225c8 | 691 | account->key_interests_need_updating = true; |
d8f41ccd A |
692 | } |
693 | ||
694 | /* | |
695 | * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles | |
696 | * and pushing our current view of the circle (oldCircle). We'll only do this if we actually | |
697 | * are a member of oldCircle - never for an empty circle. | |
698 | */ | |
699 | ||
700 | if (circle_action == revert) { | |
701 | if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) { | |
fa7225c8 | 702 | secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr); |
5c19dc3a A |
703 | debugDumpCircle(CFSTR("oldCircle"), oldCircle); |
704 | debugDumpCircle(CFSTR("newCircle"), newCircle); | |
d8f41ccd A |
705 | circleToPush = oldCircle; |
706 | } else { | |
fa7225c8 | 707 | secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr); |
d8f41ccd A |
708 | } |
709 | } | |
710 | ||
711 | ||
712 | if (circleToPush != NULL) { | |
fa7225c8 | 713 | secnotice("signing", "Pushing:[%s]", local_remote); |
d8f41ccd | 714 | CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error); |
5c19dc3a | 715 | |
d8f41ccd | 716 | if (circle_data) { |
fa7225c8 A |
717 | // Ensure we flush changes |
718 | account->circle_rings_retirements_need_attention = true; | |
719 | ||
5c19dc3a A |
720 | //recording circle we are pushing in KVS |
721 | success &= SOSTransportCircleRecordLastCirclePushedInKVS(transport, SOSCircleGetName(circleToPush), circle_data); | |
722 | //posting new circle to peers | |
d8f41ccd A |
723 | success &= SOSTransportCirclePostCircle(transport, SOSCircleGetName(circleToPush), circle_data, error); |
724 | } else { | |
725 | success = false; | |
726 | } | |
727 | CFReleaseNull(circle_data); | |
d8f41ccd A |
728 | } |
729 | ||
730 | CFReleaseSafe(newCircle); | |
731 | CFReleaseNull(emptyCircle); | |
fa7225c8 | 732 | |
d8f41ccd A |
733 | return success; |
734 | } |