]> git.saurik.com Git - apple/security.git/blob - Security/sec/SOSCircle/SecureObjectSync/SOSAccountUpdate.c
Security-57031.1.35.tar.gz
[apple/security.git] / Security / sec / SOSCircle / SecureObjectSync / SOSAccountUpdate.c
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
12 static 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
32 static 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
63 static 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
87 void 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
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);
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
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);
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
168 bool 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
209 static 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
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",
232 };
233
234 bool 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 }