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