]>
Commit | Line | Data |
---|---|---|
427c49bc A |
1 | // |
2 | // SOSCloudCircleServer.c | |
3 | // sec | |
4 | // | |
5 | // Created by Mitch Adler on 11/15/12. | |
6 | // | |
7 | // | |
8 | ||
9 | #include <AssertMacros.h> | |
10 | #include <CoreFoundation/CFURL.h> | |
11 | ||
12 | #include <securityd/SOSCloudCircleServer.h> | |
13 | #include <SecureObjectSync/SOSCloudCircle.h> | |
14 | #include <SecureObjectSync/SOSCloudCircleInternal.h> | |
15 | #include <SecureObjectSync/SOSCircle.h> | |
16 | #include <SecureObjectSync/SOSAccount.h> | |
17 | #include <SecureObjectSync/SOSFullPeerInfo.h> | |
18 | #include <SecureObjectSync/SOSPeerInfoInternal.h> | |
19 | #include <SecureObjectSync/SOSInternal.h> | |
20 | #include <SecureObjectSync/SOSUserKeygen.h> | |
21 | ||
22 | #include <utilities/SecCFWrappers.h> | |
23 | #include <utilities/SecCFRelease.h> | |
24 | #include <utilities/debugging.h> | |
25 | #include "SOSCloudKeychainClient.h" | |
26 | ||
27 | #include <corecrypto/ccrng.h> | |
28 | #include <corecrypto/ccrng_pbkdf2_prng.h> | |
29 | #include <corecrypto/ccec.h> | |
30 | #include <corecrypto/ccdigest.h> | |
31 | #include <corecrypto/ccsha2.h> | |
32 | #include <CommonCrypto/CommonRandomSPI.h> | |
33 | #include <Security/SecKeyPriv.h> | |
34 | #include <Security/SecFramework.h> | |
35 | ||
36 | #include <utilities/SecFileLocations.h> | |
37 | #include <utilities/SecAKSWrappers.h> | |
38 | #include <SecItemServer.h> | |
39 | #include <SecItemPriv.h> | |
40 | ||
41 | #include <TargetConditionals.h> | |
42 | ||
43 | #include <utilities/iCloudKeychainTrace.h> | |
44 | ||
45 | #if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR | |
46 | #include <MobileGestalt.h> | |
47 | #else | |
48 | #include <AppleSystemInfo/AppleSystemInfo.h> | |
49 | ||
50 | // We need authorization, but that doesn't exist | |
51 | // on sec built for desktop (iOS in a process) | |
52 | // Define AuthorizationRef here to make SystemConfiguration work | |
53 | // as if it's on iOS. | |
54 | typedef const struct AuthorizationOpaqueRef * AuthorizationRef; | |
55 | #endif | |
56 | ||
57 | #define SOSCKCSCOPE "sync" | |
58 | ||
59 | #define USE_SYSTEMCONFIGURATION_PRIVATE_HEADERS | |
60 | #import <SystemConfiguration/SystemConfiguration.h> | |
61 | ||
62 | #include <notify.h> | |
63 | ||
64 | CloudKeychainReplyBlock log_error = ^(CFDictionaryRef returnedValues __unused, CFErrorRef error) { | |
65 | if (error) { | |
66 | secerror("Error putting: %@", error); | |
67 | CFReleaseSafe(error); | |
68 | } | |
69 | }; | |
70 | ||
71 | static SOSCCAccountDataSourceFactoryBlock accountDataSourceOverride = NULL; | |
72 | ||
73 | bool SOSKeychainAccountSetFactoryForAccount(SOSCCAccountDataSourceFactoryBlock block) | |
74 | { | |
75 | accountDataSourceOverride = Block_copy(block); | |
4d3cab3d | 76 | |
427c49bc A |
77 | return true; |
78 | } | |
79 | ||
80 | static void do_with_account(void (^action)(SOSAccountRef account)); | |
81 | ||
82 | ||
83 | // | |
84 | // Constants | |
85 | // | |
86 | CFStringRef kSOSInternalAccessGroup = CFSTR("com.apple.security.sos"); | |
87 | ||
88 | CFStringRef kSOSAccountLabel = CFSTR("iCloud Keychain Account Meta-data"); | |
89 | CFStringRef kSOSPeerDataLabel = CFSTR("iCloud Peer Data Meta-data"); | |
90 | ||
91 | static CFStringRef accountFileName = CFSTR("PersistedAccount.plist"); | |
92 | ||
93 | static CFDictionaryRef SOSItemCopyQueryForSyncItems(CFStringRef service, bool returnData) | |
94 | { | |
95 | return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, | |
96 | kSecClass, kSecClassGenericPassword, | |
97 | kSecAttrService, service, | |
98 | kSecAttrAccessGroup, kSOSInternalAccessGroup, | |
99 | kSecReturnData, returnData ? kCFBooleanTrue : kCFBooleanFalse, | |
100 | NULL); | |
101 | } | |
102 | ||
103 | CFDataRef SOSItemGet(CFStringRef service, CFErrorRef* error) | |
104 | { | |
105 | CFDictionaryRef query = SOSItemCopyQueryForSyncItems(service, true); | |
106 | ||
107 | CFDataRef result = NULL; | |
108 | ||
109 | OSStatus copyResult = SecItemCopyMatching(query, (CFTypeRef*) &result); | |
110 | ||
111 | CFReleaseNull(query); | |
112 | ||
113 | if (copyResult != noErr) { | |
114 | SecError(copyResult, error, CFSTR("Error %@ reading for service '%@'"), result, service); | |
115 | CFReleaseNull(result); | |
116 | return NULL; | |
117 | } | |
118 | ||
119 | if (!isData(result)) { | |
120 | SOSCreateErrorWithFormat(kSOSErrorProcessingFailure, NULL, error, NULL, CFSTR("SecItemCopyMatching returned non-data in '%@'"), service); | |
121 | return NULL; | |
122 | } | |
123 | ||
124 | return result; | |
125 | } | |
126 | ||
127 | static CFDataRef SOSKeychainCopySavedAccountData() | |
128 | { | |
129 | CFErrorRef error = NULL; | |
130 | CFDataRef accountData = SOSItemGet(kSOSAccountLabel, &error); | |
131 | if (!accountData) | |
132 | secnotice("account", "Failed to load account: %@", error); | |
133 | CFReleaseNull(error); | |
134 | ||
135 | return accountData; | |
136 | } | |
137 | ||
138 | ||
139 | bool SOSItemUpdateOrAdd(CFStringRef service, CFStringRef accessibility, CFDataRef data, CFErrorRef *error) | |
140 | { | |
141 | CFDictionaryRef query = SOSItemCopyQueryForSyncItems(service, false); | |
142 | ||
143 | CFDictionaryRef update = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, | |
144 | kSecValueData, data, | |
145 | kSecAttrAccessible, accessibility, | |
146 | NULL); | |
147 | OSStatus saveStatus = SecItemUpdate(query, update); | |
148 | ||
149 | if (errSecItemNotFound == saveStatus) { | |
150 | CFMutableDictionaryRef add = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, query); | |
151 | CFDictionaryForEach(update, ^(const void *key, const void *value) { | |
152 | CFDictionaryAddValue(add, key, value); | |
153 | }); | |
154 | saveStatus = SecItemAdd(add, NULL); | |
155 | CFReleaseNull(add); | |
156 | } | |
157 | ||
158 | CFReleaseNull(query); | |
159 | CFReleaseNull(update); | |
160 | ||
161 | return SecError(saveStatus, error, CFSTR("Error saving %@ to service '%@'"), data, service); | |
162 | } | |
163 | ||
164 | static CFStringRef accountStatusFileName = CFSTR("accountStatus.plist"); | |
165 | #include <utilities/der_plist.h> | |
166 | #include <utilities/der_plist_internal.h> | |
167 | #include <corecrypto/ccder.h> | |
168 | ||
169 | static const uint8_t* ccder_decode_bool(bool* boolean, const uint8_t* der, const uint8_t *der_end) | |
170 | { | |
171 | if (NULL == der) | |
172 | return NULL; | |
4d3cab3d | 173 | |
427c49bc A |
174 | size_t payload_size = 0; |
175 | const uint8_t *payload = ccder_decode_tl(CCDER_BOOLEAN, &payload_size, der, der_end); | |
4d3cab3d | 176 | |
427c49bc A |
177 | if (NULL == payload || (der_end - payload) < 1 || payload_size != 1) { |
178 | return NULL; | |
179 | } | |
4d3cab3d | 180 | |
427c49bc A |
181 | if (boolean) |
182 | *boolean = (*payload != 0); | |
4d3cab3d | 183 | |
427c49bc A |
184 | return payload + payload_size; |
185 | } | |
186 | ||
187 | bool SOSCCCircleIsOn_Artifact(void) { | |
188 | CFURLRef accountStatusFileURL = SecCopyURLForFileInKeychainDirectory(accountStatusFileName); | |
189 | CFDataRef accountStatus = (CFDataRef) CFPropertyListReadFromFile(accountStatusFileURL); | |
190 | CFReleaseSafe(accountStatusFileURL); | |
191 | bool circle_on = false; | |
4d3cab3d | 192 | |
427c49bc A |
193 | if (accountStatus && !isData(accountStatus)) { |
194 | CFReleaseNull(accountStatus); | |
195 | } else if(accountStatus) { | |
196 | size_t size = CFDataGetLength(accountStatus); | |
197 | const uint8_t *der = CFDataGetBytePtr(accountStatus); | |
198 | const uint8_t *der_p = der; | |
4d3cab3d | 199 | |
427c49bc A |
200 | const uint8_t *sequence_end; |
201 | der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, der_p, der_p + size); | |
202 | der_p = ccder_decode_bool(&circle_on, der_p, sequence_end); | |
203 | } else { | |
4d3cab3d | 204 | |
427c49bc A |
205 | } |
206 | return circle_on; | |
207 | } | |
208 | ||
209 | ||
210 | static size_t ccder_sizeof_bool(bool value __unused, CFErrorRef *error) | |
211 | { | |
212 | return ccder_sizeof(CCDER_BOOLEAN, 1); | |
213 | } | |
214 | ||
215 | ||
216 | static uint8_t* ccder_encode_bool(bool value, const uint8_t *der, uint8_t *der_end) | |
217 | { | |
218 | uint8_t value_byte = value; | |
4d3cab3d | 219 | |
427c49bc A |
220 | return ccder_encode_tl(CCDER_BOOLEAN, 1, der, |
221 | ccder_encode_body(1, &value_byte, der, der_end)); | |
222 | } | |
223 | ||
224 | static void SOSCCCircleIsOn_SetArtifact(bool account_on) { | |
225 | static CFDataRef sLastSavedAccountStatus = NULL; | |
226 | CFErrorRef saveError = NULL; | |
227 | size_t der_size = ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, ccder_sizeof_bool(account_on, NULL)); | |
228 | uint8_t der[der_size]; | |
229 | uint8_t *der_end = der + der_size; | |
230 | der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, | |
231 | ccder_encode_bool(account_on, der, der_end)); | |
232 | ||
233 | CFDataRef accountStatusAsData = CFDataCreate(kCFAllocatorDefault, der_end, der_size); | |
4d3cab3d | 234 | |
427c49bc A |
235 | require_quiet(accountStatusAsData, exit); |
236 | if (sLastSavedAccountStatus && CFEqual(sLastSavedAccountStatus, accountStatusAsData)) goto exit; | |
4d3cab3d | 237 | |
427c49bc A |
238 | CFURLRef accountStatusFileURL = SecCopyURLForFileInKeychainDirectory(accountStatusFileName); |
239 | CFPropertyListWriteToFile((CFPropertyListRef) accountStatusAsData, accountStatusFileURL); | |
240 | CFReleaseSafe(accountStatusFileURL); | |
4d3cab3d | 241 | |
427c49bc A |
242 | CFReleaseNull(sLastSavedAccountStatus); |
243 | sLastSavedAccountStatus = accountStatusAsData; | |
244 | accountStatusAsData = NULL; | |
4d3cab3d | 245 | |
427c49bc A |
246 | exit: |
247 | CFReleaseNull(saveError); | |
248 | CFReleaseNull(accountStatusAsData); | |
249 | } | |
250 | ||
4d3cab3d A |
251 | static void SOSCCCircleIsOn_UpdateArtifact(SOSCCStatus status) |
252 | { | |
253 | switch (status) { | |
254 | case kSOSCCCircleAbsent: | |
255 | case kSOSCCNotInCircle: | |
256 | SOSCCCircleIsOn_SetArtifact(false); | |
257 | break; | |
258 | case kSOSCCInCircle: | |
259 | case kSOSCCRequestPending: | |
260 | SOSCCCircleIsOn_SetArtifact(true); | |
261 | break; | |
262 | case kSOSCCError: | |
263 | default: | |
264 | // do nothing | |
265 | break; | |
266 | } | |
267 | } | |
427c49bc A |
268 | |
269 | static void SOSKeychainAccountEnsureSaved(SOSAccountRef account) | |
270 | { | |
271 | static CFDataRef sLastSavedAccountData = NULL; | |
4d3cab3d | 272 | |
427c49bc | 273 | CFErrorRef saveError = NULL; |
4d3cab3d A |
274 | SOSCCCircleIsOn_UpdateArtifact(SOSAccountIsInCircles(account, NULL)); |
275 | ||
427c49bc | 276 | CFDataRef accountAsData = SOSAccountCopyEncodedData(account, kCFAllocatorDefault, &saveError); |
4d3cab3d | 277 | |
427c49bc A |
278 | require_action_quiet(accountAsData, exit, secerror("Failed to transform account into data, error: %@", saveError)); |
279 | require_quiet(!CFEqualSafe(sLastSavedAccountData, accountAsData), exit); | |
4d3cab3d | 280 | |
427c49bc A |
281 | if (!SOSItemUpdateOrAdd(kSOSAccountLabel, kSecAttrAccessibleAlwaysThisDeviceOnly, accountAsData, &saveError)) { |
282 | secerror("Can't save account: %@", saveError); | |
283 | goto exit; | |
284 | } | |
4d3cab3d | 285 | |
427c49bc A |
286 | CFReleaseNull(sLastSavedAccountData); |
287 | sLastSavedAccountData = accountAsData; | |
288 | accountAsData = NULL; | |
4d3cab3d | 289 | |
427c49bc A |
290 | exit: |
291 | CFReleaseNull(saveError); | |
292 | CFReleaseNull(accountAsData); | |
293 | } | |
294 | ||
295 | ||
296 | /* | |
297 | Stolen from keychain_sync.c | |
298 | */ | |
299 | ||
300 | static bool clearAllKVS(CFErrorRef *error) | |
301 | { | |
302 | return true; | |
303 | __block bool result = false; | |
304 | const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC; | |
305 | dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); | |
306 | dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); | |
307 | dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); | |
308 | ||
309 | SOSCloudKeychainClearAll(processQueue, ^(CFDictionaryRef returnedValues, CFErrorRef cerror) | |
310 | { | |
311 | result = (cerror != NULL); | |
312 | dispatch_semaphore_signal(waitSemaphore); | |
313 | }); | |
314 | ||
315 | dispatch_semaphore_wait(waitSemaphore, finishTime); | |
316 | dispatch_release(waitSemaphore); | |
317 | ||
318 | return result; | |
319 | } | |
320 | ||
321 | static SOSAccountRef SOSKeychainAccountCreateSharedAccount(CFDictionaryRef our_gestalt) | |
322 | { | |
323 | SOSAccountKeyInterestBlock updateKVSKeys = ^(bool getNewKeysOnly, CFArrayRef alwaysKeys, CFArrayRef afterFirstUnlockKeys, CFArrayRef unlockedKeys) { | |
324 | CFErrorRef error = NULL; | |
325 | ||
326 | if (!SOSCloudKeychainUpdateKeys(getNewKeysOnly, alwaysKeys, afterFirstUnlockKeys, unlockedKeys, &error)) | |
327 | { | |
328 | secerror("Error updating keys: %@", error); | |
329 | // TODO: propagate error(s) to callers. | |
330 | } else { | |
331 | if (CFArrayGetCount(unlockedKeys) == 0) { | |
332 | secnotice(SOSCKCSCOPE, "Unlocked keys were empty!"); | |
333 | } | |
334 | // This leaks 3 CFStringRefs in DEBUG builds. | |
335 | CFStringRef alwaysKeysDesc = SOSInterestListCopyDescription(alwaysKeys); | |
336 | CFStringRef afterFirstUnlockKeysDesc = SOSInterestListCopyDescription(afterFirstUnlockKeys); | |
337 | CFStringRef unlockedKeysDesc = SOSInterestListCopyDescription(unlockedKeys); | |
338 | secdebug(SOSCKCSCOPE, "Updating interest: always: %@,\nfirstUnlock: %@,\nunlockedKeys: %@", | |
339 | alwaysKeysDesc, | |
340 | afterFirstUnlockKeysDesc, | |
341 | unlockedKeysDesc); | |
342 | CFReleaseNull(alwaysKeysDesc); | |
343 | CFReleaseNull(afterFirstUnlockKeysDesc); | |
344 | CFReleaseNull(unlockedKeysDesc); | |
345 | } | |
346 | ||
347 | CFReleaseNull(error); | |
348 | }; | |
4d3cab3d | 349 | |
427c49bc A |
350 | SOSAccountDataUpdateBlock updateKVS = ^ bool (CFDictionaryRef changes, CFErrorRef *error) { |
351 | SOSCloudKeychainPutObjectsInCloud(changes, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), log_error); | |
352 | ||
353 | CFStringRef changeDescription = SOSChangesCopyDescription(changes, true); | |
354 | secnotice("account", "Keys Sent: %@", changeDescription); | |
355 | CFReleaseSafe(changeDescription); | |
356 | ||
357 | return true; | |
358 | }; | |
359 | ||
360 | secdebug("account", "Created account"); | |
4d3cab3d | 361 | |
427c49bc | 362 | CFDataRef savedAccount = SOSKeychainCopySavedAccountData(); |
4d3cab3d | 363 | |
427c49bc A |
364 | // At this point we might have an account structure from keychain that may or may not match the account we're building this for |
365 | // murf ZZZ we should probably make sure this is a good thing before using it. | |
366 | ||
367 | SOSAccountRef account = NULL; | |
4d3cab3d | 368 | |
427c49bc A |
369 | if (savedAccount) { |
370 | CFErrorRef inflationError = NULL; | |
371 | SOSDataSourceFactoryRef factory = accountDataSourceOverride ? accountDataSourceOverride() : SecItemDataSourceFactoryCreateDefault(); | |
372 | ||
373 | account = SOSAccountCreateFromData(kCFAllocatorDefault, savedAccount, factory, updateKVSKeys, updateKVS, &inflationError); | |
4d3cab3d | 374 | |
427c49bc A |
375 | if (account) |
376 | SOSAccountUpdateGestalt(account, our_gestalt); | |
377 | else | |
378 | secerror("Got error inflating account: %@", inflationError); | |
379 | CFReleaseNull(inflationError); | |
380 | } | |
381 | ||
382 | CFReleaseSafe(savedAccount); | |
4d3cab3d | 383 | |
427c49bc A |
384 | if (!account) { |
385 | // If we get here then we are creating a new accout and so increment the peer count for ourselves. | |
386 | SOSDataSourceFactoryRef factory = accountDataSourceOverride ? accountDataSourceOverride() : SecItemDataSourceFactoryCreateDefault(); | |
387 | ||
388 | account = SOSAccountCreate(kCFAllocatorDefault, our_gestalt, factory, updateKVSKeys, updateKVS); | |
389 | ||
390 | if (!account) | |
391 | secerror("Got NULL creating account"); | |
392 | } | |
393 | ||
394 | return account; | |
395 | } | |
396 | ||
397 | // | |
398 | // Mark: Gestalt Handling | |
399 | // | |
400 | ||
401 | static CFStringRef CopyModelName(void) | |
402 | { | |
403 | static dispatch_once_t once; | |
404 | static CFStringRef modelName = NULL; | |
405 | dispatch_once(&once, ^{ | |
406 | #if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR | |
407 | modelName = MGCopyAnswer(kMGQDeviceName, NULL); | |
408 | #else | |
409 | modelName = ASI_CopyComputerModelName(FALSE); | |
410 | #endif | |
411 | if (modelName == NULL) | |
412 | modelName = CFSTR("Unknown model"); | |
413 | }); | |
414 | return CFStringCreateCopy(kCFAllocatorDefault, modelName); | |
415 | } | |
416 | ||
417 | static CFStringRef CopyComputerName(SCDynamicStoreRef store) | |
418 | { | |
419 | CFStringRef deviceName = SCDynamicStoreCopyComputerName(store, NULL); | |
420 | if (deviceName == NULL) { | |
421 | deviceName = CFSTR("Unknown name"); | |
422 | } | |
423 | return deviceName; | |
424 | } | |
425 | ||
426 | static CFDictionaryRef GatherDeviceGestalt(SCDynamicStoreRef store, CFArrayRef keys, void *context) | |
427 | { | |
428 | CFStringRef modelName = CopyModelName(); | |
429 | CFStringRef computerName = CopyComputerName(store); | |
430 | ||
431 | CFDictionaryRef gestalt = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, | |
432 | kPIUserDefinedDeviceName, computerName, | |
433 | kPIDeviceModelName, modelName, | |
434 | NULL); | |
435 | CFRelease(modelName); | |
436 | CFRelease(computerName); | |
437 | ||
438 | return gestalt; | |
439 | } | |
440 | ||
441 | static void SOSCCProcessGestaltUpdate(SCDynamicStoreRef store, CFArrayRef keys, void *context) | |
442 | { | |
443 | do_with_account(^(SOSAccountRef account) { | |
444 | CFDictionaryRef gestalt = GatherDeviceGestalt(store, keys, context); | |
445 | if (SOSAccountUpdateGestalt(account, gestalt)) { | |
446 | notify_post(kSOSCCCircleChangedNotification); | |
447 | } | |
448 | CFReleaseSafe(gestalt); | |
449 | }); | |
450 | } | |
451 | ||
452 | ||
453 | static CFDictionaryRef RegisterForGestaltUpdate(dispatch_queue_t queue, void *info) | |
454 | { | |
455 | SCDynamicStoreContext context = { .info = info }; | |
456 | SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("com.apple.securityd.cloudcircleserver"), SOSCCProcessGestaltUpdate, &context); | |
457 | CFStringRef computerKey = SCDynamicStoreKeyCreateComputerName(NULL); | |
458 | CFArrayRef keys = NULL; | |
459 | CFDictionaryRef gestalt = NULL; | |
460 | ||
461 | if (store == NULL || computerKey == NULL) { | |
462 | goto done; | |
463 | } | |
464 | keys = CFArrayCreate(NULL, (const void **)&computerKey, 1, &kCFTypeArrayCallBacks); | |
465 | if (keys == NULL) { | |
466 | goto done; | |
467 | } | |
468 | gestalt = GatherDeviceGestalt(store, keys, info); | |
469 | SCDynamicStoreSetNotificationKeys(store, keys, NULL); | |
470 | SCDynamicStoreSetDispatchQueue(store, queue); | |
471 | ||
472 | done: | |
473 | if (store) CFRelease(store); | |
474 | if (computerKey) CFRelease(computerKey); | |
475 | if (keys) CFRelease(keys); | |
476 | return gestalt; | |
477 | } | |
478 | ||
479 | static void do_with_account(void (^action)(SOSAccountRef account)); | |
480 | static void do_with_account_async(void (^action)(SOSAccountRef account)); | |
481 | ||
482 | static void do_with_account_dynamic(void (^action)(SOSAccountRef account), bool sync) { | |
483 | static SOSAccountRef sSharedAccount; | |
484 | static dispatch_once_t onceToken; | |
4d3cab3d | 485 | |
427c49bc A |
486 | dispatch_once(&onceToken, ^{ |
487 | secdebug(SOSCKCSCOPE, "Account Creation start"); | |
4d3cab3d | 488 | |
427c49bc | 489 | CFDictionaryRef gestalt = RegisterForGestaltUpdate(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NULL); |
4d3cab3d | 490 | |
427c49bc A |
491 | if (!gestalt) { |
492 | #if TARGET_OS_IPHONE && TARGET_IPHONE_SIMULATOR | |
493 | gestalt = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, NULL); | |
494 | #else | |
495 | secerror("Didn't get machine gestalt! This is going to be ugly."); | |
496 | #endif | |
497 | } | |
498 | ||
499 | sSharedAccount = SOSKeychainAccountCreateSharedAccount(gestalt); | |
500 | ||
501 | CFReleaseSafe(gestalt); | |
4d3cab3d | 502 | |
427c49bc | 503 | SOSCCSetThisDeviceDefinitelyNotActiveInCircle(SOSAccountIsInCircles(sSharedAccount, NULL)); |
4d3cab3d | 504 | |
427c49bc A |
505 | SOSAccountAddChangeBlock(sSharedAccount, ^(SOSCircleRef circle, |
506 | CFArrayRef peer_additions, CFArrayRef peer_removals, | |
507 | CFArrayRef applicant_additions, CFArrayRef applicant_removals) { | |
508 | CFErrorRef pi_error = NULL; | |
509 | SOSPeerInfoRef me = SOSAccountGetMyPeerInCircle(sSharedAccount, circle, &pi_error); | |
510 | if (!me) { | |
511 | secerror("Error finding me for change: %@", pi_error); | |
512 | CFReleaseNull(pi_error); | |
513 | } else { | |
514 | CFReleaseSafe(pi_error); | |
4d3cab3d | 515 | |
427c49bc A |
516 | if (CFArrayContainsValue(peer_additions, CFRangeMake(0, CFArrayGetCount(peer_additions)), me)) { |
517 | SOSCCSyncWithAllPeers(); | |
518 | } | |
519 | } | |
520 | ||
521 | if (CFArrayGetCount(peer_additions) != 0 || | |
522 | CFArrayGetCount(peer_removals) != 0 || | |
523 | CFArrayGetCount(applicant_additions) != 0 || | |
524 | CFArrayGetCount(applicant_removals) != 0) { | |
4d3cab3d | 525 | |
427c49bc A |
526 | SOSCCSetThisDeviceDefinitelyNotActiveInCircle(SOSAccountIsInCircles(sSharedAccount, NULL)); |
527 | notify_post(kSOSCCCircleChangedNotification); | |
528 | } | |
529 | }); | |
4d3cab3d | 530 | |
427c49bc A |
531 | SOSCloudKeychainSetItemsChangedBlock(^(CFDictionaryRef changes) { |
532 | CFRetainSafe(changes); | |
533 | do_with_account_async(^(SOSAccountRef account) { | |
534 | CFStringRef changeDescription = SOSChangesCopyDescription(changes, false); | |
535 | secdebug(SOSCKCSCOPE, "Received: %@", changeDescription); | |
536 | CFReleaseSafe(changeDescription); | |
4d3cab3d | 537 | |
427c49bc A |
538 | CFErrorRef error = NULL; |
539 | if (!SOSAccountHandleUpdates(account, changes, &error)) { | |
540 | secerror("Error handling updates: %@", error); | |
541 | CFReleaseNull(error); | |
542 | return; | |
543 | } | |
544 | CFReleaseNull(error); | |
545 | CFReleaseSafe(changes); | |
546 | }); | |
547 | }); | |
548 | ||
549 | }); | |
4d3cab3d | 550 | |
427c49bc A |
551 | dispatch_block_t do_action_and_save = ^{ |
552 | action(sSharedAccount); | |
553 | SOSKeychainAccountEnsureSaved(sSharedAccount); | |
554 | }; | |
555 | ||
556 | if (sync) { | |
557 | dispatch_sync(SOSAccountGetQueue(sSharedAccount), do_action_and_save); | |
558 | } else { | |
559 | dispatch_async(SOSAccountGetQueue(sSharedAccount), do_action_and_save); | |
560 | } | |
561 | } | |
562 | ||
563 | static void do_with_account_async(void (^action)(SOSAccountRef account)) { | |
564 | do_with_account_dynamic(action, false); | |
565 | } | |
566 | ||
567 | static void do_with_account(void (^action)(SOSAccountRef account)) { | |
568 | do_with_account_dynamic(action, true); | |
569 | } | |
570 | ||
571 | #if TARGET_IPHONE_SIMULATOR | |
572 | #define MKBDeviceUnlockedSinceBoot() true | |
573 | #endif | |
574 | ||
575 | static bool do_if_after_first_unlock(CFErrorRef *error, dispatch_block_t action) | |
576 | { | |
577 | bool beenUnlocked = false; | |
578 | require_quiet(SecAKSGetHasBeenUnlocked(&beenUnlocked, error), fail); | |
579 | ||
580 | require_action_quiet(beenUnlocked, fail, | |
581 | SOSCreateErrorWithFormat(kSOSErrorNotReady, NULL, error, NULL, | |
582 | CFSTR("Keybag never unlocked, ask after first unlock"))); | |
583 | ||
584 | action(); | |
585 | return true; | |
586 | ||
587 | fail: | |
588 | return false; | |
589 | } | |
590 | ||
591 | static bool do_with_account_if_after_first_unlock(CFErrorRef *error, bool (^action)(SOSAccountRef account, CFErrorRef* error)) | |
592 | { | |
593 | __block bool action_result = false; | |
594 | ||
595 | return do_if_after_first_unlock(error, ^{ | |
596 | do_with_account(^(SOSAccountRef account) { | |
597 | action_result = action(account, error); | |
598 | }); | |
599 | ||
600 | }) && action_result; | |
601 | } | |
602 | ||
603 | static bool do_with_account_while_unlocked(CFErrorRef *error, bool (^action)(SOSAccountRef account, CFErrorRef* error)) | |
604 | { | |
605 | __block bool action_result = false; | |
606 | ||
607 | return SecAKSDoWhileUserBagLocked(error, ^{ | |
608 | do_with_account(^(SOSAccountRef account) { | |
609 | action_result = action(account, error); | |
610 | }); | |
611 | ||
612 | }) && action_result; | |
613 | } | |
614 | ||
615 | SOSAccountRef SOSKeychainAccountGetSharedAccount() | |
616 | { | |
617 | __block SOSAccountRef result = NULL; | |
4d3cab3d | 618 | |
427c49bc A |
619 | do_with_account(^(SOSAccountRef account) { |
620 | result = account; | |
621 | }); | |
4d3cab3d | 622 | |
427c49bc A |
623 | return result; |
624 | } | |
625 | ||
626 | // | |
627 | // Mark: Credential processing | |
628 | // | |
629 | ||
630 | ||
631 | bool SOSCCTryUserCredentials_Server(CFStringRef user_label, CFDataRef user_password, CFErrorRef *error) | |
632 | { | |
633 | return do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
634 | return SOSAccountTryUserCredentials(account, user_label, user_password, block_error); | |
635 | }); | |
636 | } | |
637 | ||
638 | ||
639 | static bool EnsureFreshParameters(SOSAccountRef account, CFErrorRef *error) { | |
640 | dispatch_semaphore_t wait_for = dispatch_semaphore_create(0); | |
641 | dispatch_retain(wait_for); // Both this scope and the block own it. | |
4d3cab3d | 642 | |
427c49bc A |
643 | CFMutableArrayRef keysToGet = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); |
644 | CFArrayAppendValue(keysToGet, kSOSKVSKeyParametersKey); | |
4d3cab3d | 645 | |
427c49bc A |
646 | __block CFDictionaryRef valuesToUpdate = NULL; |
647 | __block bool success = false; | |
4d3cab3d | 648 | |
427c49bc A |
649 | SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { |
650 | CFStringRef circle_key = SOSCircleKeyCreateWithName(SOSCircleGetName(circle), NULL); | |
651 | CFArrayAppendValue(keysToGet, circle_key); | |
652 | CFReleaseNull(circle_key); | |
653 | }); | |
4d3cab3d | 654 | |
427c49bc A |
655 | secnotice("updates", "***** EnsureFreshParameters *****"); |
656 | ||
657 | SOSCloudKeychainSynchronizeAndWait(keysToGet, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) { | |
658 | ||
659 | if (sync_error) { | |
660 | secerror("SOSCloudKeychainSynchronizeAndWait: %@", sync_error); | |
661 | if (error) { | |
662 | *error = sync_error; | |
663 | CFRetainSafe(*error); | |
664 | } | |
665 | } else { | |
666 | secnotice("updates", "SOSCloudKeychainSynchronizeAndWait: results: %@", returnedValues); | |
667 | valuesToUpdate = returnedValues; | |
668 | CFRetainSafe(valuesToUpdate); | |
669 | success = true; | |
670 | } | |
4d3cab3d | 671 | |
427c49bc A |
672 | dispatch_semaphore_signal(wait_for); |
673 | dispatch_release(wait_for); | |
674 | }); | |
675 | ||
676 | dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER); | |
677 | // TODO: Maybe we timeout here... used to dispatch_time(DISPATCH_TIME_NOW, 30ull * NSEC_PER_SEC)); | |
678 | dispatch_release(wait_for); | |
4d3cab3d | 679 | |
427c49bc A |
680 | if ((valuesToUpdate) && (account)) { |
681 | if (!SOSAccountHandleUpdates(account, valuesToUpdate, error)) { | |
682 | secerror("Freshness update failed: %@", *error); | |
4d3cab3d | 683 | |
427c49bc A |
684 | success = false; |
685 | } | |
686 | } | |
687 | ||
688 | CFReleaseNull(valuesToUpdate); | |
689 | CFReleaseNull(keysToGet); | |
690 | ||
691 | return success; | |
692 | } | |
693 | ||
694 | static bool EnsureFreshParameters_once(SOSAccountRef account, CFErrorRef *error) { | |
695 | static dispatch_once_t once; | |
696 | __block bool retval = false; | |
697 | dispatch_once(&once, ^{ | |
698 | retval = EnsureFreshParameters(account, error); | |
699 | }); | |
700 | return retval; | |
701 | } | |
702 | ||
703 | bool SOSCCSetUserCredentials_Server(CFStringRef user_label, CFDataRef user_password, CFErrorRef *error) | |
704 | { | |
4d3cab3d | 705 | return do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { |
427c49bc A |
706 | if (!EnsureFreshParameters(account, block_error)) { |
707 | secnotice("updates", "EnsureFreshParameters error: %@", *block_error); | |
708 | return false; | |
709 | } | |
710 | if (!SOSAccountAssertUserCredentials(account, user_label, user_password, block_error)) { | |
711 | secnotice("updates", "EnsureFreshParameters/SOSAccountAssertUserCredentials error: %@", *block_error); | |
712 | return false; | |
713 | } | |
714 | ||
715 | return true; | |
716 | }); | |
717 | ||
718 | } | |
719 | ||
720 | bool SOSCCCanAuthenticate_Server(CFErrorRef *error) | |
721 | { | |
722 | bool result = do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
723 | return SOSAccountGetPrivateCredential(account, block_error) != NULL; | |
724 | }); | |
4d3cab3d | 725 | |
427c49bc A |
726 | if (!result && error && *error && CFErrorGetDomain(*error) == kSOSErrorDomain) { |
727 | CFIndex code = CFErrorGetCode(*error); | |
728 | if (code == kSOSErrorPrivateKeyAbsent || code == kSOSErrorPublicKeyAbsent) { | |
729 | CFReleaseNull(*error); | |
730 | } | |
731 | } | |
732 | ||
733 | return result; | |
734 | } | |
735 | ||
736 | bool SOSCCPurgeUserCredentials_Server(CFErrorRef *error) | |
737 | { | |
738 | return do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
739 | SOSAccountPurgePrivateCredential(account); | |
740 | return true; | |
741 | }); | |
742 | } | |
743 | ||
744 | ||
745 | #if USE_BETTER | |
746 | static bool sAccountInCircleCache = false; | |
747 | ||
748 | static void do_with_not_in_circle_bool_queue(bool start_account, dispatch_block_t action) | |
749 | { | |
750 | static dispatch_queue_t account_start_queue; | |
751 | static dispatch_queue_t not_in_circle_queue; | |
752 | static bool account_started = false; | |
753 | ||
754 | static dispatch_once_t onceToken; | |
755 | dispatch_once(&onceToken, ^{ | |
756 | not_in_circle_queue = dispatch_queue_create("nis queue", DISPATCH_QUEUE_SERIAL); | |
757 | account_start_queue = dispatch_queue_create("init nis queue", DISPATCH_QUEUE_SERIAL);; | |
758 | account_started = false; | |
759 | }); | |
4d3cab3d | 760 | |
427c49bc A |
761 | __block bool done = false; |
762 | dispatch_sync(not_in_circle_queue, ^{ | |
763 | if (account_started) { | |
764 | done = true; | |
765 | action(); | |
766 | } | |
767 | }); | |
4d3cab3d | 768 | |
427c49bc A |
769 | if (!done && start_account) { |
770 | dispatch_sync(account_start_queue, ^{ | |
771 | __block bool do_start = false; | |
772 | dispatch_sync(not_in_circle_queue, ^{ | |
773 | do_start = !account_started; | |
774 | account_started = true; | |
775 | }); | |
776 | if (do_start) | |
777 | SOSCCThisDeviceIsInCircle(NULL); // Inflate account. | |
778 | }); | |
779 | ||
780 | dispatch_sync(not_in_circle_queue, action); | |
781 | } | |
782 | } | |
783 | #endif | |
784 | ||
785 | bool SOSCCThisDeviceDefinitelyNotActiveInCircle() | |
786 | { | |
787 | return !SOSCCCircleIsOn_Artifact(); | |
788 | #if USE_BETTER | |
789 | __block bool result = false; | |
790 | do_with_not_in_circle_bool_queue(true, ^{ | |
791 | result = sAccountInCircleCache; | |
792 | }); | |
4d3cab3d | 793 | |
427c49bc A |
794 | return result; |
795 | #endif | |
796 | } | |
797 | ||
798 | void SOSCCSetThisDeviceDefinitelyNotActiveInCircle(SOSCCStatus currentStatus) | |
799 | { | |
4d3cab3d | 800 | SOSCCCircleIsOn_UpdateArtifact(currentStatus); |
427c49bc A |
801 | #if USE_BETTER |
802 | do_with_not_in_circle_bool_queue(false, ^{ | |
803 | sAccountInCircleCache = notActive; | |
804 | }); | |
805 | #endif | |
806 | } | |
807 | ||
808 | ||
809 | SOSCCStatus SOSCCThisDeviceIsInCircle_Server(CFErrorRef *error) | |
810 | { | |
811 | __block SOSCCStatus status; | |
4d3cab3d | 812 | |
427c49bc A |
813 | return do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { |
814 | EnsureFreshParameters_once(account, NULL); | |
815 | status = SOSAccountIsInCircles(account, block_error); | |
816 | return true; | |
817 | }) ? status : kSOSCCError; | |
818 | } | |
819 | ||
820 | bool SOSCCRequestToJoinCircle_Server(CFErrorRef* error) | |
821 | { | |
822 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
823 | EnsureFreshParameters_once(account, NULL); | |
824 | return SOSAccountJoinCircles(account, block_error); | |
825 | }); | |
826 | } | |
827 | ||
828 | bool SOSCCRequestToJoinCircleAfterRestore_Server(CFErrorRef* error) | |
829 | { | |
830 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
831 | EnsureFreshParameters_once(account, NULL); | |
832 | return SOSAccountJoinCirclesAfterRestore(account, block_error); | |
833 | }); | |
834 | } | |
835 | ||
836 | bool SOSCCResetToOffering_Server(CFErrorRef* error) | |
837 | { | |
838 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
839 | EnsureFreshParameters_once(account, NULL); | |
840 | clearAllKVS(NULL); | |
841 | return SOSAccountResetToOffering(account, block_error); | |
842 | }); | |
843 | } | |
844 | ||
845 | bool SOSCCResetToEmpty_Server(CFErrorRef* error) | |
846 | { | |
847 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
848 | return SOSAccountResetToEmpty(account, block_error); | |
849 | }); | |
850 | } | |
851 | ||
852 | bool SOSCCRemoveThisDeviceFromCircle_Server(CFErrorRef* error) | |
853 | { | |
854 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
855 | bool result = SOSAccountLeaveCircles(account, block_error); | |
856 | return result; | |
857 | }); | |
858 | } | |
859 | ||
860 | bool SOSCCBailFromCircle_Server(uint64_t limit_in_seconds, CFErrorRef* error) | |
861 | { | |
862 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
863 | return SOSAccountBail(account, limit_in_seconds, block_error); | |
864 | }); | |
865 | } | |
866 | ||
867 | CFArrayRef SOSCCCopyApplicantPeerInfo_Server(CFErrorRef* error) | |
868 | { | |
869 | __block CFArrayRef result = NULL; | |
870 | ||
871 | (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
872 | EnsureFreshParameters_once(account, NULL); | |
873 | result = SOSAccountCopyApplicants(account, block_error); | |
874 | return result != NULL; | |
875 | }); | |
4d3cab3d | 876 | |
427c49bc A |
877 | return result; |
878 | } | |
879 | ||
880 | ||
881 | bool SOSCCAcceptApplicants_Server(CFArrayRef applicants, CFErrorRef* error) | |
882 | { | |
883 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
884 | EnsureFreshParameters_once(account, NULL); | |
885 | return SOSAccountAcceptApplicants(account, applicants, block_error); | |
886 | }); | |
887 | } | |
888 | ||
889 | bool SOSCCRejectApplicants_Server(CFArrayRef applicants, CFErrorRef* error) | |
890 | { | |
891 | return do_with_account_while_unlocked(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
892 | EnsureFreshParameters_once(account, NULL); | |
893 | return SOSAccountRejectApplicants(account, applicants, block_error); | |
894 | }); | |
895 | } | |
896 | ||
897 | CFArrayRef SOSCCCopyPeerPeerInfo_Server(CFErrorRef* error) | |
898 | { | |
899 | __block CFArrayRef result = NULL; | |
4d3cab3d | 900 | |
427c49bc A |
901 | (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { |
902 | EnsureFreshParameters_once(account, NULL); | |
903 | result = SOSAccountCopyPeers(account, block_error); | |
904 | return result != NULL; | |
905 | }); | |
4d3cab3d | 906 | |
427c49bc A |
907 | return result; |
908 | } | |
909 | ||
910 | CFArrayRef SOSCCCopyConcurringPeerPeerInfo_Server(CFErrorRef* error) | |
911 | { | |
912 | __block CFArrayRef result = NULL; | |
4d3cab3d | 913 | |
427c49bc A |
914 | (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { |
915 | EnsureFreshParameters_once(account, NULL); | |
916 | result = SOSAccountCopyConcurringPeers(account, block_error); | |
917 | return result != NULL; | |
918 | }); | |
4d3cab3d | 919 | |
427c49bc A |
920 | return result; |
921 | } | |
922 | ||
923 | CFStringRef SOSCCCopyIncompatibilityInfo_Server(CFErrorRef* error) | |
924 | { | |
925 | __block CFStringRef result = NULL; | |
4d3cab3d | 926 | |
427c49bc A |
927 | (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { |
928 | EnsureFreshParameters_once(account, NULL); | |
929 | result = SOSAccountCopyIncompatibilityInfo(account, block_error); | |
930 | return result != NULL; | |
931 | }); | |
4d3cab3d | 932 | |
427c49bc A |
933 | return result; |
934 | } | |
935 | ||
936 | enum DepartureReason SOSCCGetLastDepartureReason_Server(CFErrorRef* error) | |
937 | { | |
938 | __block enum DepartureReason result = kSOSDepartureReasonError; | |
4d3cab3d | 939 | |
427c49bc A |
940 | (void) do_with_account_if_after_first_unlock(error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { |
941 | EnsureFreshParameters_once(account, NULL); | |
942 | result = SOSAccountGetLastDepartureReason(account, block_error); | |
943 | return result != kSOSDepartureReasonError; | |
944 | }); | |
4d3cab3d | 945 | |
427c49bc A |
946 | return result; |
947 | } | |
948 | ||
949 | SyncWithAllPeersReason SOSCCProcessSyncWithAllPeers_Server(CFErrorRef* error) | |
950 | { | |
951 | /* | |
952 | #define kIOReturnLockedRead iokit_common_err(0x2c3) // device read locked | |
953 | #define kIOReturnLockedWrite iokit_common_err(0x2c4) // device write locked | |
954 | */ | |
955 | __block SyncWithAllPeersReason result = kSyncWithAllPeersSuccess; | |
956 | CFErrorRef action_error = NULL; | |
957 | ||
958 | if (!do_with_account_while_unlocked(&action_error, ^bool (SOSAccountRef account, CFErrorRef* block_error) { | |
959 | CFErrorRef localError = NULL; | |
960 | if (!SOSAccountSyncWithAllPeers(account, &localError)) { | |
961 | secerror("sync with all peers failed: %@", localError); | |
962 | CFReleaseSafe(localError); | |
963 | // This isn't a device-locked error, but returning false will | |
964 | // have CloudKeychainProxy ask us to try sync again after next unlock | |
965 | result = kSyncWithAllPeersOtherFail; | |
966 | return false; | |
967 | } | |
968 | return true; | |
969 | })) { | |
970 | if (action_error) { | |
971 | if (SecErrorGetOSStatus(action_error) == errSecInteractionNotAllowed) { | |
972 | secnotice("updates", "SOSAccountSyncWithAllPeers failed because device is locked; letting CloudKeychainProxy know"); | |
973 | result = kSyncWithAllPeersLocked; // tell CloudKeychainProxy to call us back when device unlocks | |
974 | CFReleaseNull(action_error); | |
975 | } else { | |
976 | secerror("Unexpected error: %@", action_error); | |
977 | } | |
978 | ||
979 | if (error && *error == NULL) { | |
980 | *error = action_error; | |
981 | action_error = NULL; | |
982 | } | |
983 | ||
984 | CFReleaseNull(action_error); | |
985 | } | |
986 | } | |
987 | ||
988 | return result; | |
989 | } | |
990 | ||
991 | void SOSCCSyncWithAllPeers(void) | |
992 | { | |
993 | SOSCloudKeychainRequestSyncWithAllPeers(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NULL); | |
994 | } | |
995 | ||
996 | void SOSCCHandleUpdate(CFDictionaryRef updates) | |
997 | { | |
998 | SOSCloudKeychainHandleUpdate(updates); | |
999 | } | |
1000 |