]> git.saurik.com Git - apple/security.git/blob - Security/sec/SOSCircle/SecureObjectSync/SOSAccountUpdate.c
Security-57031.10.10.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 // 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);
275
276 SOSTransportCircleRef transport = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(prospective_circle));
277
278 SOSAccountScanForRetired(account, prospective_circle, error);
279 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
280 if(!newCircle) return false;
281
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.
284 }
285
286 typedef enum {
287 accept,
288 countersign,
289 leave,
290 revert,
291 ignore
292 } circle_action_t;
293
294 static const char *actionstring[] = {
295 "accept", "countersign", "leave", "revert", "ignore",
296 };
297
298 circle_action_t circle_action = ignore;
299 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
300
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;
305
306 SOSConcordanceStatus concstat =
307 SOSCircleConcordanceTrust(oldCircle, newCircle,
308 old_circle_key, account->user_public,
309 me, error);
310
311 CFStringRef concStr = NULL;
312 switch(concstat) {
313 case kSOSConcordanceTrusted:
314 circle_action = countersign;
315 concStr = CFSTR("Trusted");
316 break;
317 case kSOSConcordanceGenOld:
318 circle_action = userTrustedOldCircle ? revert : ignore;
319 concStr = CFSTR("Generation Old");
320 break;
321 case kSOSConcordanceBadUserSig:
322 case kSOSConcordanceBadPeerSig:
323 circle_action = userTrustedOldCircle ? revert : accept;
324 concStr = CFSTR("Bad Signature");
325 break;
326 case kSOSConcordanceNoUserSig:
327 circle_action = userTrustedOldCircle ? revert : accept;
328 concStr = CFSTR("No User Signature");
329 break;
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);
334 break;
335 case kSOSConcordanceNoPeer:
336 circle_action = leave;
337 leave_reason = kSOSLeftUntrustedCircle;
338 concStr = CFSTR("No trusted peer left");
339 break;
340 case kSOSConcordanceNoUserKey:
341 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
342 abort();
343 break;
344 default:
345 secerror("##### Bad Error Return from ConcordanceTrust");
346 abort();
347 break;
348 }
349
350 secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring[circle_action], concordstring[concstat], userTrustedOldCircle ? "trusted" : "untrusted");
351
352 SOSCircleRef circleToPush = NULL;
353
354 if (circle_action == leave) {
355 circle_action = ignore;
356
357 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
358 if (sosAccountLeaveCircle(account, newCircle, error)) {
359 circleToPush = newCircle;
360 } else {
361 secnotice("signing", "Can't leave circle %@, but dumping identities", oldCircle);
362 success = false;
363 }
364 account->departure_code = leave_reason;
365 circle_action = accept;
366 me = NULL;
367 me_full = NULL;
368 } else {
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;
372 }
373 }
374
375 if (circle_action == countersign) {
376 if (me && SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
377 CFErrorRef signing_error = NULL;
378
379 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
380 circleToPush = newCircle;
381 secnotice("signing", "Concurred with: %@", newCircle);
382 } else {
383 secerror("Failed to concurrence sign, error: %@ Old: %@ New: %@", signing_error, oldCircle, newCircle);
384 success = false;
385 }
386 CFReleaseSafe(signing_error);
387 }
388 circle_action = accept;
389 }
390
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;
395 }
396
397 if (me
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);
403 me = NULL;
404 me_full = NULL;
405 }
406
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);
412 me = NULL;
413 me_full = NULL;
414 } else {
415 SOSCircleRequestReadmission(newCircle, account->user_public, me, NULL);
416 writeUpdate = true;
417 }
418 }
419
420 CFRetain(oldCircle); // About to replace the oldCircle
421 CFDictionarySetValue(account->circles, newCircleName, newCircle);
422 SOSAccountSetPreviousPublic(account);
423
424 secnotice("signing", "%@, Accepting circle: %@", concStr, newCircle);
425
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.
433
434 if (SOSCircleRequestReadmission(newCircle, account->user_public, me, NULL))
435 writeUpdate = true;
436 }
437
438 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
439 SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL);
440 }
441
442 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
443
444 CFReleaseNull(oldCircle);
445
446 if (writeUpdate)
447 circleToPush = newCircle;
448 SOSUpdateKeyInterest();
449 }
450
451 /*
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.
455 */
456
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;
461 } else {
462 secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr, newCircle);
463 }
464 }
465
466
467 if (circleToPush != NULL) {
468 secnotice("signing", "Pushing:[%s] %@", local_remote, circleToPush);
469 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
470 if (circle_data) {
471 success &= SOSTransportCirclePostCircle(transport, SOSCircleGetName(circleToPush), circle_data, error);
472 } else {
473 success = false;
474 }
475 CFReleaseNull(circle_data);
476
477 success = (success && SOSTransportCircleFlushChanges(transport, error));
478 }
479
480 CFReleaseSafe(newCircle);
481 CFReleaseNull(emptyCircle);
482 return success;
483 }