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