]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountRingUpdate.c
Security-57337.20.44.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / SOSAccountRingUpdate.c
1 //
2 // SOSAccountRingUpdate.c
3 // sec
4 //
5 //
6
7 #include <stdio.h>
8
9 #include "SOSAccountPriv.h"
10 #include <Security/SecureObjectSync/SOSTransportCircle.h>
11 #include <Security/SecureObjectSync/SOSTransport.h>
12 #include <Security/SecureObjectSync/SOSViews.h>
13 #include <Security/SecureObjectSync/SOSRing.h>
14 #include <Security/SecureObjectSync/SOSRingUtils.h>
15 #include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
16
17 #if 0
18 static inline bool SOSAccountHasLeft(SOSAccountRef account) {
19 switch(account->departure_code) {
20 case kSOSWithdrewMembership: /* Fallthrough */
21 case kSOSMembershipRevoked: /* Fallthrough */
22 case kSOSLeftUntrustedCircle:
23 return true;
24 case kSOSNeverAppliedToCircle: /* Fallthrough */
25 case kSOSNeverLeftCircle: /* Fallthrough */
26 default:
27 return false;
28 }
29 }
30 #endif
31
32 static const char *concordstring[] = {
33 "kSOSConcordanceTrusted",
34 "kSOSConcordanceGenOld", // kSOSErrorReplay
35 "kSOSConcordanceNoUserSig", // kSOSErrorBadSignature
36 "kSOSConcordanceNoUserKey", // kSOSErrorNoKey
37 "kSOSConcordanceNoPeer", // kSOSErrorPeerNotFound
38 "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature
39 "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature
40 "kSOSConcordanceNoPeerSig",
41 "kSOSConcordanceWeSigned",
42 "kSOSConcordanceInvalidMembership",
43 "kSOSConcordanceMissingMe",
44 "kSOSConcordanceImNotWorthy",
45 };
46
47
48 static bool SOSAccountIsPeerRetired(SOSAccountRef account, CFSetRef peers){
49 CFMutableArrayRef peerInfos = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
50 bool result = false;
51
52 CFSetForEach(peers, ^(const void *value) {
53 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
54 if(SOSPeerInfoIsRetirementTicket(peer))
55 CFArrayAppendValue(peerInfos, peer);
56 });
57 if(CFArrayGetCount(peerInfos) > 0){
58 if(!SOSAccountRemoveBackupPeers(account, peerInfos, NULL))
59 secerror("Could not remove peers: %@, from the backup", peerInfos);
60 else
61 return true;
62 }
63 else
64 result = true;
65
66 return result;
67 }
68
69 static bool SOSAccountBackupSliceKeyBagNeedsFix(SOSAccountRef account, SOSBackupSliceKeyBagRef bskb) {
70
71 if (SOSBSKBIsDirect(bskb) || account->backup_key == NULL)
72 return false;
73
74 CFSetRef peers = SOSBSKBGetPeers(bskb);
75
76 /* first scan for retired peers, and kick'em out!*/
77 SOSAccountIsPeerRetired(account, peers);
78
79 SOSPeerInfoRef myPeer = SOSAccountGetMyPeerInfo(account);
80 bool needsFix = true;
81
82 if (myPeer) {
83 SOSPeerInfoRef meInBag = (SOSPeerInfoRef) CFSetGetValue(peers, myPeer);
84 CFDataRef myBK = SOSPeerInfoCopyBackupKey(myPeer);
85 CFDataRef meInBagBK = SOSPeerInfoCopyBackupKey(meInBag);
86 needsFix = !(meInBag && CFEqualSafe(myBK,
87 meInBagBK));
88 CFReleaseNull(myBK);
89 CFReleaseNull(meInBagBK);
90 }
91
92 return needsFix;
93 }
94
95
96 bool SOSAccountHandleUpdateRing(SOSAccountRef account, SOSRingRef prospectiveRing, bool writeUpdate, CFErrorRef *error) {
97 bool success = true;
98 bool haveOldRing = true;
99 const char *localRemote = writeUpdate ? "local": "remote";
100 SOSFullPeerInfoRef fpi = account->my_identity;
101 SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
102 CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
103 bool peerActive = (fpi && pi && peerID && SOSAccountIsInCircle(account, NULL));
104
105
106 secnotice("signing", "start:[%s] %@", localRemote, prospectiveRing);
107
108 require_quiet(SOSAccountHasPublicKey(account, error), errOut);
109
110 require_action_quiet(prospectiveRing, errOut,
111 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("No Ring to work with"), NULL, error));
112
113 // We should at least have a sane ring system in the account object
114 require_quiet(SOSAccountCheckForRings(account, error), errOut);
115
116 CFStringRef ringName = SOSRingGetName(prospectiveRing);
117 SOSRingRef oldRing = SOSAccountGetRing(account, ringName, NULL);
118
119 SOSTransportCircleRef transport = account->circle_transport;
120
121 SOSRingRef newRing = CFRetainSafe(prospectiveRing); // TODO: SOSAccountCloneRingWithRetirement(account, prospectiveRing, error);
122
123 typedef enum {
124 accept,
125 countersign,
126 leave,
127 revert,
128 modify,
129 ignore
130 } ringAction_t;
131
132 static const char *actionstring[] = {
133 "accept", "countersign", "leave", "revert", "modify", "ignore",
134 };
135
136 ringAction_t ringAction = ignore;
137
138 bool userTrustedoldRing = true;
139
140 SOSCircleRef circle = SOSAccountGetCircle(account, NULL);
141 CFSetRef peers = SOSCircleCopyPeers(circle, kCFAllocatorDefault);
142
143 SecKeyRef oldKey = account->user_public;
144
145 #if 0
146 // for now user keys aren't explored.
147 // we should ask the ring if it cares about it and then do the magic to find the right user keys.
148 SecKeyRef oldKey = account->user_public;
149
150 if(SOSRingPKTrusted(oldRing, account->user_public, NULL)) oldKey = account->user_public;
151 else if(account->previous_public && SOSRingPKTrusted(oldRing, account->previous_public, NULL)) oldKey = account->previous_public;
152 bool userTrustedoldRing = (oldKey != NULL) && haveOldRing;
153
154 #endif
155
156 if (!oldRing) {
157 oldRing = newRing;
158 }
159
160 SOSConcordanceStatus concstat = SOSRingConcordanceTrust(fpi, peers, oldRing, newRing, oldKey, account->user_public, peerID, error);
161 CFReleaseNull(peers);
162
163 CFStringRef concStr = NULL;
164 switch(concstat) {
165 case kSOSConcordanceTrusted:
166 ringAction = countersign;
167 concStr = CFSTR("Trusted");
168 break;
169 case kSOSConcordanceGenOld:
170 ringAction = userTrustedoldRing ? revert : ignore;
171 concStr = CFSTR("Generation Old");
172 break;
173 case kSOSConcordanceBadUserSig:
174 case kSOSConcordanceBadPeerSig:
175 ringAction = userTrustedoldRing ? revert : accept;
176 concStr = CFSTR("Bad Signature");
177 break;
178 case kSOSConcordanceNoUserSig:
179 ringAction = userTrustedoldRing ? revert : accept;
180 concStr = CFSTR("No User Signature");
181 break;
182 case kSOSConcordanceNoPeerSig:
183 ringAction = accept; // We might like this one eventually but don't countersign.
184 concStr = CFSTR("No trusted peer signature");
185 secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newRing);
186 break;
187 case kSOSConcordanceNoPeer:
188 ringAction = leave;
189 concStr = CFSTR("No trusted peer left");
190 break;
191 case kSOSConcordanceNoUserKey:
192 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
193 ringAction = ignore;
194 break;
195
196 case kSOSConcordanceMissingMe:
197 case kSOSConcordanceImNotWorthy:
198 ringAction = modify;
199 concStr = CFSTR("Incorrect membership for me");
200 break;
201 case kSOSConcordanceInvalidMembership:
202 ringAction = userTrustedoldRing ? revert : ignore;
203 concStr = CFSTR("Invalid Ring Membership");
204 break;
205 default:
206 secerror("##### Bad Error Return from ConcordanceTrust");
207 ringAction = ignore;
208 break;
209 }
210
211 secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring[ringAction], concordstring[concstat], userTrustedoldRing ? "trusted" : "untrusted");
212
213 SOSRingRef ringToPush = NULL;
214 bool iWasInOldRing = peerID && SOSRingHasPeerID(oldRing, peerID);
215 bool iAmInNewRing = peerID && SOSRingHasPeerID(newRing, peerID);
216 bool ringIsBackup = SOSRingGetType(newRing) == kSOSRingBackup;
217
218 if (ringIsBackup && peerActive) {
219 if (ringAction == accept || ringAction == countersign) {
220 CFErrorRef localError = NULL;
221 SOSBackupSliceKeyBagRef bskb = SOSRingCopyBackupSliceKeyBag(newRing, &localError);
222
223 if(!bskb) {
224 secnotice("signing", "Backup ring with no backup slice keybag (%@)", localError);
225 } else if (SOSAccountBackupSliceKeyBagNeedsFix(account, bskb)) {
226 ringAction = modify;
227 }
228 CFReleaseSafe(localError);
229 CFReleaseSafe(bskb);
230 }
231
232 if (ringAction == modify) {
233 CFErrorRef updateError = NULL;
234 CFDictionarySetValue(account->trusted_rings, ringName, newRing);
235
236 if(SOSAccountUpdateOurPeerInBackup(account, newRing, &updateError)) {
237 secdebug("signing", "Modified backup ring to include us");
238 } else {
239 secerror("Could not add ourselves to the backup: (%@)", updateError);
240 }
241 CFReleaseSafe(updateError);
242
243 // Fall through to normal modify handling.
244 }
245 }
246
247 if (ringAction == modify) {
248 ringAction = ignore;
249 }
250
251 if (ringAction == leave) {
252 if (iWasInOldRing) {
253 if (sosAccountLeaveRing(account, newRing, error)) {
254 ringToPush = newRing;
255 } else {
256 secnotice("signing", "Can't leave ring %@", oldRing);
257 success = false;
258 }
259 ringAction = accept;
260 } else {
261 // We are not in this ring, but we need to update account with it, since we got it from cloud
262 secnotice("signing", "We are not in this ring, but we need to update account with it");
263 ringAction = accept;
264 }
265 }
266
267 if (ringAction == countersign) {
268 if (iAmInNewRing) {
269 if (SOSRingPeerTrusted(newRing, fpi, NULL)) {
270 secinfo("signing", "Already concur with: %@", newRing);
271 } else {
272 CFErrorRef signingError = NULL;
273
274 if (fpi && SOSRingConcordanceSign(newRing, fpi, &signingError)) {
275 ringToPush = newRing;
276 secinfo("signing", "Concurred with: %@", newRing);
277 } else {
278 secerror("Failed to concurrence sign, error: %@ Old: %@ New: %@", signingError, oldRing, newRing);
279 success = false;
280 }
281 CFReleaseSafe(signingError);
282 }
283 } else {
284 secnotice("signing", "Not countersigning, not in ring: %@", newRing);
285 }
286 ringAction = accept;
287 }
288
289 if (ringAction == accept) {
290 if (iWasInOldRing && !iAmInNewRing) {
291
292 // Don't destroy evidence of other code determining reason for leaving.
293 //if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked;
294 // TODO: LeaveReason for rings
295 }
296
297 if (pi && SOSRingHasRejection(newRing, peerID)) {
298 // TODO: ReasonForLeaving for rings
299 SOSRingRemoveRejection(newRing, peerID);
300 }
301
302 CFRetainSafe(oldRing);
303 CFDictionarySetValue(account->trusted_rings, ringName, newRing);
304 // TODO: Why was this? SOSAccountSetPreviousPublic(account);
305
306 secnotice("signing", "%@, Accepting ring: %@", concStr, newRing);
307
308 if (pi && account->user_public_trusted
309 && SOSRingHasApplicant(oldRing, peerID)
310 && SOSRingCountPeers(newRing) > 0
311 && !iAmInNewRing && !SOSRingHasApplicant(newRing, peerID)) {
312 // We weren't rejected (above would have set me to NULL.
313 // We were applying and we weren't accepted.
314 // Our application is declared lost, let us reapply.
315
316 if (SOSRingApply(newRing, account->user_public, fpi, NULL))
317 if(peerActive) writeUpdate = true;
318 }
319
320 if (pi && SOSRingHasPeerID(oldRing, peerID)) {
321 SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL);
322 }
323
324 CFReleaseNull(oldRing);
325
326 account->circle_rings_retirements_need_attention = true;
327
328 if (writeUpdate)
329 ringToPush = newRing;
330 SOSUpdateKeyInterest(account);
331 }
332
333 /*
334 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new rings
335 * and pushing our current view of the ring (oldRing). We'll only do this if we actually
336 * are a member of oldRing - never for an empty ring.
337 */
338
339 if (ringAction == revert) {
340 if(haveOldRing && peerActive && SOSRingHasPeerID(oldRing, peerID)) {
341 secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr, newRing, oldRing);
342 ringToPush = oldRing;
343 } else {
344 secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr, newRing);
345 }
346 }
347
348
349 if (ringToPush != NULL) {
350 secnotice("signing", "Pushing:[%s] %@", localRemote, ringToPush);
351 CFDataRef ringData = SOSRingCopyEncodedData(ringToPush, error);
352 if (ringData) {
353 success &= SOSTransportCircleRingPostRing(transport, SOSRingGetName(ringToPush), ringData, error);
354 } else {
355 success = false;
356 }
357 CFReleaseNull(ringData);
358 }
359
360 CFReleaseSafe(newRing);
361 return success;
362 errOut:
363 return false;
364 }