]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountViewSync.m
Security-58286.220.15.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / SOSAccountViewSync.m
1 //
2 // SOSAccountViews.c
3 // sec
4 //
5 // Created by Mitch Adler on 6/10/16.
6 //
7 //
8
9
10 #include <CoreFoundation/CoreFoundation.h>
11
12 #include <Security/SecureObjectSync/SOSAccount.h>
13 #include "SOSViews.h"
14 #include "SOSAccountPriv.h"
15
16 #include <utilities/SecCFWrappers.h>
17 #import <Security/SecureObjectSync/SOSAccountTrustClassic.h>
18 #import <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
19 #import <Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h>
20 #import <Security/SecureObjectSync/SOSAccountTrustClassic+Identity.h>
21
22 //
23 // MARK: Helpers
24 //
25
26 static CFMutableSetRef SOSAccountCopyOtherPeersViews(SOSAccount* account) {
27 __block CFMutableSetRef otherPeersViews = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
28 SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
29 SOSPeerInfoWithEnabledViewSet(peer, ^(CFSetRef enabled) {
30 CFSetUnion(otherPeersViews, enabled);
31 });
32 });
33
34 return otherPeersViews;
35 }
36
37 //
38 // MARK: Outstanding tracking
39 //
40 CFMutableSetRef SOSAccountCopyOutstandingViews(SOSAccount* account) {
41 CFSetRef initialSyncViews = SOSViewCopyViewSet(kViewSetAll);
42 CFMutableSetRef result = SOSAccountCopyIntersectionWithOustanding(account, initialSyncViews);
43 CFReleaseNull(initialSyncViews);
44 return result;
45 }
46 bool SOSAccountIsViewOutstanding(SOSAccount* account, CFStringRef view) {
47 bool isOutstandingView;
48 require_action_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done, isOutstandingView = true);
49 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
50 require_action_quiet(unsyncedObject, done, isOutstandingView = false);
51 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
52 if (unsyncedBool) {
53 isOutstandingView = CFBooleanGetValue(unsyncedBool);
54 } else {
55 CFSetRef unsyncedSet = asSet(unsyncedObject, NULL);
56 isOutstandingView = unsyncedSet && CFSetContainsValue(unsyncedSet, view);
57 }
58 done:
59 return isOutstandingView;
60 }
61 CFMutableSetRef SOSAccountCopyIntersectionWithOustanding(SOSAccount* account, CFSetRef inSet) {
62 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
63 CFMutableSetRef result = NULL;
64 require_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done);
65 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
66 if (unsyncedBool) {
67 if (!CFBooleanGetValue(unsyncedBool)) {
68 result = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
69 }
70 } else {
71 CFSetRef unsyncedSet = asSet(unsyncedObject, NULL);
72 if (unsyncedSet) {
73 result = CFSetCreateIntersection(kCFAllocatorDefault, unsyncedSet, inSet);
74 } else {
75 result = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
76 }
77 }
78 done:
79 if (result == NULL) {
80 result = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, inSet);
81 }
82 return result;
83 }
84 bool SOSAccountIntersectsWithOutstanding(SOSAccount* account, CFSetRef views) {
85 CFSetRef nonInitiallySyncedViews = SOSAccountCopyIntersectionWithOustanding(account, views);
86 bool intersects = !CFSetIsEmpty(nonInitiallySyncedViews);
87 CFReleaseNull(nonInitiallySyncedViews);
88 return intersects;
89 }
90 bool SOSAccountHasOustandingViews(SOSAccount* account) {
91 bool hasOutstandingViews;
92 require_action_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done, hasOutstandingViews = true);
93 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
94 require_action_quiet(unsyncedObject, done, hasOutstandingViews = false);
95 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
96 if (unsyncedBool) {
97 hasOutstandingViews = CFBooleanGetValue(unsyncedBool);
98 } else {
99 hasOutstandingViews = isSet(unsyncedBool);
100 }
101 done:
102 return hasOutstandingViews;
103 }
104 //
105 // MARK: Initial sync functions
106 //
107 static bool SOSAccountHasCompletedInitialySyncWithSetKind(SOSAccount* account, ViewSetKind setKind) {
108 CFSetRef viewSet = SOSViewCopyViewSet(setKind);
109 bool completedSync = !SOSAccountIntersectsWithOutstanding(account, viewSet);
110 CFReleaseNull(viewSet);
111 return completedSync;
112 }
113 bool SOSAccountHasCompletedInitialSync(SOSAccount* account) {
114 return SOSAccountHasCompletedInitialySyncWithSetKind(account, kViewSetInitial);
115 }
116 bool SOSAccountHasCompletedRequiredBackupSync(SOSAccount* account) {
117 return SOSAccountHasCompletedInitialySyncWithSetKind(account, kViewSetRequiredForBackup);
118 }
119
120
121 //
122 // MARK: Handling initial sync being done
123 //
124
125 static bool SOSAccountResolvePendingViewSets(SOSAccount* account, CFErrorRef *error) {
126 bool status = [account.trust updateViewSets:account enabled:asSet(SOSAccountGetValue(account, kSOSPendingEnableViewsToBeSetKey, NULL), NULL) disabled:asSet(SOSAccountGetValue(account, kSOSPendingDisableViewsToBeSetKey, NULL), NULL)];
127 if(status){
128 SOSAccountClearValue(account, kSOSPendingEnableViewsToBeSetKey, NULL);
129 SOSAccountClearValue(account, kSOSPendingDisableViewsToBeSetKey, NULL);
130
131 secnotice("views","updated view sets!");
132 }
133 else{
134 secerror("Could not update view sets");
135 }
136 return status;
137 }
138
139 static void SOSAccountCallInitialSyncBlocks(SOSAccount* account) {
140 CFDictionaryRef syncBlocks = NULL;
141 syncBlocks = CFBridgingRetain(account.waitForInitialSync_blocks);
142 account.waitForInitialSync_blocks = nil;
143
144 if (syncBlocks) {
145 CFDictionaryForEach(syncBlocks, ^(const void *key, const void *value) {
146 secnotice("updates", "calling in sync block [%@]", key);
147 ((__bridge SOSAccountWaitForInitialSyncBlock)value)(account);
148 });
149 }
150 CFReleaseNull(syncBlocks);
151 }
152
153
154 static void SOSAccountHandleRequiredBackupSyncDone(SOSAccount* account) {
155 secnotice("initial-sync", "Handling Required Backup Sync done");
156 }
157
158 static void SOSAccountHandleInitialSyncDone(SOSAccount* account) {
159 secnotice("initial-sync", "Handling initial sync done.");
160
161 if(!SOSAccountResolvePendingViewSets(account, NULL))
162 secnotice("initial-sync", "Account could not add the pending view sets");
163
164 SOSAccountCallInitialSyncBlocks(account);
165 }
166
167
168
169 //
170 // MARK: Waiting for in-sync
171 //
172 static CFStringRef CreateUUIDString() {
173 CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
174 CFStringRef result = CFUUIDCreateString(kCFAllocatorDefault, uuid);
175 CFReleaseNull(uuid);
176 return result;
177 }
178
179 CFStringRef SOSAccountCallWhenInSync(SOSAccount* account, SOSAccountWaitForInitialSyncBlock syncBlock) {
180 //if we are not initially synced
181 CFStringRef id = NULL;
182 CFTypeRef unSyncedViews = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
183 if (unSyncedViews != NULL) {
184 id = CreateUUIDString();
185 secnotice("initial-sync", "adding sync block [%@] to array!", id);
186 SOSAccountWaitForInitialSyncBlock copy = syncBlock;
187 if (account.waitForInitialSync_blocks == NULL) {
188 account.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
189 }
190 [account.waitForInitialSync_blocks setObject:copy forKey:(__bridge NSString*)id];
191 } else {
192 syncBlock(account);
193 }
194
195 return id;
196 }
197
198 bool SOSAccountUnregisterCallWhenInSync(SOSAccount* account, CFStringRef id) {
199 if (account.waitForInitialSync_blocks == NULL) return false;
200
201 [account.waitForInitialSync_blocks removeObjectForKey: (__bridge NSString*)id];
202 return true;
203 }
204
205 static void performWithInitialSyncDescription(CFTypeRef object, void (^action)(CFStringRef description)) {
206 CFSetRef setObject = asSet(object, NULL);
207 if (setObject) {
208 CFStringSetPerformWithDescription(setObject, action);
209 } else {
210 CFStringRef format = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), object);
211 action(format);
212 CFReleaseNull(format);
213 }
214 }
215
216 static bool CFSetIntersectionWentEmpty(CFSetRef interestingSet, CFSetRef before, CFSetRef after) {
217 return ((before != NULL) && !CFSetIntersectionIsEmpty(interestingSet, before)) &&
218 ((after == NULL) || CFSetIntersectionIsEmpty(interestingSet, after));
219 }
220
221 static bool SOSViewIntersectionWentEmpty(ViewSetKind kind, CFSetRef before, CFSetRef after) {
222 CFSetRef kindSet = SOSViewCopyViewSet(kind);
223 bool result = CFSetIntersectionWentEmpty(kindSet, before, after);
224 CFReleaseNull(kindSet);
225 return result;
226 }
227
228 bool SOSAccountHandleOutOfSyncUpdate(SOSAccount* account, CFSetRef oldOOSViews, CFSetRef newOOSViews) {
229 bool actionTaken = false;
230
231 if (SOSViewIntersectionWentEmpty(kViewSetInitial, oldOOSViews, newOOSViews)) {
232 SOSAccountHandleInitialSyncDone(account);
233 actionTaken = true;
234 }
235
236 if (SOSViewIntersectionWentEmpty(kViewSetRequiredForBackup, oldOOSViews, newOOSViews)) {
237 SOSAccountHandleRequiredBackupSyncDone(account);
238 actionTaken = true;
239 }
240 return actionTaken;
241 }
242
243 void SOSAccountUpdateOutOfSyncViews(SOSAccountTransaction* aTxn, CFSetRef viewsInSync) {
244 SOSAccount* account = aTxn.account;
245 SOSCCStatus circleStatus = [account getCircleStatus:NULL];
246
247 bool inOrApplying = (circleStatus == kSOSCCInCircle) || (circleStatus == kSOSCCRequestPending);
248
249 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
250 __block CFTypeRef newUnsyncedObject = CFRetainSafe(unsyncedObject);
251
252 CFSetRef unsyncedSet = NULL;
253 CFMutableSetRef newUnsyncedSet = NULL;
254
255 performWithInitialSyncDescription(viewsInSync, ^(CFStringRef viewsInSyncDescription) {
256 secnotice("initial-sync", "Views in sync: %@", viewsInSyncDescription);
257 });
258
259
260 if (!inOrApplying) {
261 if (unsyncedObject != NULL) {
262 secnotice("initial-sync", "not in circle nor applying: clearing pending");
263 CFReleaseNull(newUnsyncedObject);
264 }
265 } else if (circleStatus == kSOSCCInCircle) {
266 if (unsyncedObject == kCFBooleanTrue) {
267 unsyncedSet = SOSViewCopyViewSet(kViewSetAll);
268 CFAssignRetained(newUnsyncedObject, CFSetCreateCopy(kCFAllocatorDefault, unsyncedSet));
269
270 secnotice("initial-sync", "Pending views setting to all we can expect.");
271 } else if (isSet(unsyncedObject)) {
272 unsyncedSet = (CFSetRef) CFRetainSafe(unsyncedObject);
273 }
274
275 if (unsyncedSet) {
276 CFSetRef otherPeersViews = SOSAccountCopyOtherPeersViews(account);
277
278 newUnsyncedSet = CFSetCreateIntersection(kCFAllocatorDefault, unsyncedSet, otherPeersViews);
279
280 if (viewsInSync) {
281 CFSetSubtract(newUnsyncedSet, viewsInSync);
282 }
283
284 CFRetainAssign(newUnsyncedObject, newUnsyncedSet);
285 CFReleaseNull(otherPeersViews);
286 }
287
288 performWithInitialSyncDescription(newUnsyncedSet, ^(CFStringRef unsynced) {
289 secnotice("initial-sync", "Unsynced: %@", unsynced);
290 });
291 }
292
293
294 if (isSet(newUnsyncedObject) && CFSetIsEmpty((CFSetRef) newUnsyncedObject)) {
295 secnotice("initial-sync", "Empty set, using NULL instead");
296 CFReleaseNull(newUnsyncedObject);
297 }
298
299 CFErrorRef localError = NULL;
300 if (!SOSAccountSetValue(account, kSOSUnsyncedViewsKey, newUnsyncedObject, &localError)) {
301 secnotice("initial-sync", "Failure saving new unsynced value: %@ value: %@", localError, newUnsyncedObject);
302 }
303 CFReleaseNull(localError);
304
305 CFReleaseNull(newUnsyncedObject);
306 CFReleaseNull(newUnsyncedSet);
307 CFReleaseNull(unsyncedSet);
308 }
309
310 void SOSAccountPeerGotInSync(SOSAccountTransaction* aTxn, CFStringRef peerID, CFSetRef views) {
311 SOSAccount* account = aTxn.account;
312 secnotice("initial-sync", "Peer %@ synced views: %@", peerID, views);
313 SOSCircleRef circle = NULL;
314 SOSAccountTrustClassic* trust = account.trust;
315 circle = trust.trustedCircle;
316 if (circle && [account isInCircle:NULL] && SOSCircleHasActivePeerWithID(circle, peerID, NULL)) {
317 SOSAccountUpdateOutOfSyncViews(aTxn, views);
318 }
319 }
320
321 void SOSAccountEnsureSyncChecking(SOSAccount* account) {
322 if (!account.isListeningForSync) {
323 SOSEngineRef engine = [account.trust getDataSourceEngine:account.factory];
324
325 if (engine) {
326 secnotice("initial-sync", "Setting up notifications to monitor in-sync");
327 SOSEngineSetSyncCompleteListenerQueue(engine, account.queue);
328 SOSEngineSetSyncCompleteListener(engine, ^(CFStringRef peerID, CFSetRef views) {
329 [account performTransaction_Locked:^(SOSAccountTransaction * _Nonnull txn) {
330 SOSAccountPeerGotInSync(txn, peerID, views);
331 }];
332 });
333 account.isListeningForSync = true;
334 } else {
335 secerror("Couldn't find engine to setup notifications!!!");
336 }
337 }
338 }
339
340 void SOSAccountCancelSyncChecking(SOSAccount* account) {
341 if (account.isListeningForSync) {
342 SOSEngineRef engine = [account.trust getDataSourceEngine:account.factory];
343
344 if (engine) {
345 secnotice("initial-sync", "Cancelling notifications to monitor in-sync");
346 SOSEngineSetSyncCompleteListenerQueue(engine, NULL);
347 SOSEngineSetSyncCompleteListener(engine, NULL);
348 } else {
349 secnotice("initial-sync", "No engine to cancel notification from.");
350 }
351 account.isListeningForSync = false;
352 }
353 }
354
355 bool SOSAccountCheckForAlwaysOnViews(SOSAccount* account) {
356 bool changed = false;
357 SOSPeerInfoRef myPI = account.peerInfo;
358 require_quiet(myPI, done);
359 require_quiet([account isInCircle:NULL], done);
360 require_quiet(SOSAccountHasCompletedInitialSync(account), done);
361 CFMutableSetRef viewsToEnsure = SOSViewCopyViewSet(kViewSetAlwaysOn);
362 // Previous version PeerInfo if we were syncing legacy keychain, ensure we include those legacy views.
363 if(!SOSPeerInfoVersionIsCurrent(myPI)) {
364 CFSetRef V0toAdd = SOSViewCopyViewSet(kViewSetV0);
365 CFSetUnion(viewsToEnsure, V0toAdd);
366 CFReleaseNull(V0toAdd);
367 }
368 changed = [account.trust updateFullPeerInfo:account minimum:viewsToEnsure excluded:SOSViewsGetV0ViewSet()]; // We don't permit V0 view proper, only sub-views
369 CFReleaseNull(viewsToEnsure);
370 done:
371 return changed;
372 }
373