]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccountUpdate.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / SecureObjectSync / SOSAccountUpdate.m
1 //
2 // SOSAccountUpdate.c
3 // sec
4 //
5
6 #include "SOSAccountPriv.h"
7 #include "SOSAccountLog.h"
8
9 #include "keychain/SecureObjectSync/SOSTransportCircleKVS.h"
10 #include "keychain/SecureObjectSync/SOSTransport.h"
11 #include <Security/SecureObjectSync/SOSViews.h>
12 #include "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
13 #include "keychain/SecureObjectSync/SOSPeerInfoPriv.h"
14 #include "keychain/SecureObjectSync/SOSPeerInfoV2.h"
15 #include "keychain/SecureObjectSync/SOSPeerInfoDER.h"
16 #include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>
17 #import "keychain/SecureObjectSync/SOSAccountGhost.h"
18
19 #import "keychain/SecureObjectSync/SOSAccountTrust.h"
20 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
21 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
22
23 static void DifferenceAndCall(CFSetRef old_members, CFSetRef new_members, void (^updatedCircle)(CFSetRef additions, CFSetRef removals))
24 {
25 CFMutableSetRef additions = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, new_members);
26 CFMutableSetRef removals = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, old_members);
27
28
29 CFSetForEach(old_members, ^(const void * value) {
30 CFSetRemoveValue(additions, value);
31 });
32
33 CFSetForEach(new_members, ^(const void * value) {
34 CFSetRemoveValue(removals, value);
35 });
36
37 updatedCircle(additions, removals);
38
39 CFReleaseSafe(additions);
40 CFReleaseSafe(removals);
41 }
42 static CFMutableSetRef SOSAccountCopyIntersectedViews(CFSetRef peerViews, CFSetRef myViews) {
43 __block CFMutableSetRef views = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
44 if (peerViews && myViews) CFSetForEach(peerViews, ^(const void *view) {
45 if (CFSetContainsValue(myViews, view)) {
46 CFSetAddValue(views, view);
47 }
48 });
49 return views;
50 }
51
52 static inline bool isSyncing(SOSPeerInfoRef peer, SecKeyRef upub) {
53 if(!SOSPeerInfoApplicationVerify(peer, upub, NULL)) return false;
54 if(SOSPeerInfoIsRetirementTicket(peer)) return false;
55 return true;
56 }
57
58 static bool isBackupSOSRing(SOSRingRef ring)
59 {
60 return isSOSRing(ring) && (kSOSRingBackup == SOSRingGetType(ring));
61 }
62
63 static void SOSAccountAppendPeerMetasForViewBackups(SOSAccount* account, CFSetRef views, CFMutableArrayRef appendTo)
64 {
65 CFMutableDictionaryRef ringToViewTable = NULL;
66
67 if([account getCircleStatus:NULL] != kSOSCCInCircle)
68 return;
69
70 if(!(SOSAccountHasCompletedInitialSync(account))){
71 secnotice("backup", "Haven't finished initial backup syncing, not registering backup metas with engine");
72 return;
73 }
74 if(!SOSPeerInfoV2DictionaryHasData(account.peerInfo, sBackupKeyKey)){
75 secnotice("backup", "No key to backup to, we don't enable individual view backups");
76 return;
77 }
78 ringToViewTable = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
79
80 CFSetForEach(views, ^(const void *value) {
81 CFStringRef viewName = value;
82 if (isString(viewName) && !CFEqualSafe(viewName, kSOSViewKeychainV0)) {
83 CFStringRef ringName = SOSBackupCopyRingNameForView(viewName);
84 viewName = ringName;
85 SOSRingRef ring = [account.trust copyRing:ringName err:NULL];
86 if (ring && isBackupSOSRing(ring)) {
87 CFTypeRef currentValue = (CFTypeRef) CFDictionaryGetValue(ringToViewTable, ring);
88
89 if (isSet(currentValue)) {
90 CFSetAddValue((CFMutableSetRef)currentValue, viewName);
91 } else {
92 CFMutableSetRef viewNameSet = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
93 CFSetAddValue(viewNameSet, viewName);
94
95 CFDictionarySetValue(ringToViewTable, ring, viewNameSet);
96 CFReleaseNull(viewNameSet);
97 }
98 } else {
99 secwarning("View '%@' not being backed up – ring %@:%@ not backup ring.", viewName, ringName, ring);
100 }
101 CFReleaseNull(ringName);
102 CFReleaseNull(ring);
103 }
104 });
105
106 CFDictionaryForEach(ringToViewTable, ^(const void *key, const void *value) {
107 SOSRingRef ring = (SOSRingRef) key;
108 CFSetRef viewNames = asSet(value, NULL);
109 if (isSOSRing(ring) && viewNames) {
110 if (SOSAccountIntersectsWithOutstanding(account, viewNames)) {
111 CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) {
112 secnotice("engine-notify", "Not ready, no peer meta: R: %@ Vs: %@", SOSRingGetName(ring), ringViews);
113 });
114 } else {
115 bool meta_added = false;
116 CFErrorRef create_error = NULL;
117 SOSBackupSliceKeyBagRef key_bag = NULL;
118 SOSPeerMetaRef newMeta = NULL;
119
120 CFDataRef ring_payload = SOSRingGetPayload(ring, NULL);
121 require_quiet(isData(ring_payload), skip);
122
123 key_bag = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, ring_payload, &create_error);
124 require_quiet(key_bag, skip);
125
126 newMeta = SOSPeerMetaCreateWithComponents(SOSRingGetName(ring), viewNames, ring_payload);
127 require_quiet(SecAllocationError(newMeta, &create_error, CFSTR("Didn't make peer meta for: %@"), ring), skip);
128 CFArrayAppendValue(appendTo, newMeta);
129
130 CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) {
131 secnotice("engine-notify", "Backup peer meta: R: %@ Vs: %@ VD: %@", SOSRingGetName(ring), ringViews, ring_payload);
132 });
133
134 meta_added = true;
135
136 skip:
137 if (!meta_added) {
138 CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) {
139 secerror("Failed to register backup meta from %@ for views %@. Error (%@)", ring, ringViews, create_error);
140 });
141 }
142 CFReleaseNull(newMeta);
143 CFReleaseNull(key_bag);
144 CFReleaseNull(create_error);
145 }
146 }
147 });
148
149 CFReleaseNull(ringToViewTable);
150 }
151
152 bool SOSAccountSyncingV0(SOSAccount* account) {
153 __block bool syncingV0 = false;
154 SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
155 if (SOSPeerInfoIsEnabledView(peer, kSOSViewKeychainV0)) {
156 syncingV0 = true;
157 }
158 });
159
160 return syncingV0;
161 }
162
163 void SOSAccountNotifyEngines(SOSAccount* account)
164 {
165 dispatch_assert_queue(account.queue);
166
167 SOSAccountTrustClassic *trust = account.trust;
168 SOSFullPeerInfoRef identity = trust.fullPeerInfo;
169 SOSCircleRef circle = trust.trustedCircle;
170
171 SOSPeerInfoRef myPi = SOSFullPeerInfoGetPeerInfo(identity);
172 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
173 CFMutableArrayRef syncing_peer_metas = NULL;
174 CFMutableArrayRef zombie_peer_metas = NULL;
175 CFErrorRef localError = NULL;
176 SOSPeerMetaRef myMeta = NULL;
177
178 if (myPi_id && isSyncing(myPi, account.accountKey) && SOSCircleHasPeer(circle, myPi, NULL)) {
179 CFMutableSetRef myViews = SOSPeerInfoCopyEnabledViews(myPi);
180
181 // We add V0 views to everyone if we see a V0 peer, or a peer with the view explicity enabled
182 // V2 peers shouldn't be explicity enabling the uber V0 view, though the seeds did.
183 __block bool addV0Views = SOSAccountSyncingV0(account);
184
185 bool selfSupportCKKSForAll = SOSPeerInfoSupportsCKKSForAll(myPi);
186 secnotice("engine-notify", "Self peer(%@) %@ CKKS For All", myPi_id, selfSupportCKKSForAll ? @"supports" : @"doesn't support");
187
188 syncing_peer_metas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
189 zombie_peer_metas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
190 SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
191 CFMutableArrayRef arrayToAddTo = isSyncing(peer, account.accountKey) ? syncing_peer_metas : zombie_peer_metas;
192
193 // Compute views each peer is in that we are also in ourselves
194 CFMutableSetRef peerEnabledViews = SOSPeerInfoCopyEnabledViews(peer);
195 CFMutableSetRef views = SOSAccountCopyIntersectedViews(peerEnabledViews, myViews);
196 CFReleaseNull(peerEnabledViews);
197
198 if(addV0Views) {
199 CFSetAddValue(views, kSOSViewKeychainV0);
200 }
201
202 // If this peer supports CKKS4All, let's sync zero views to it.
203 if(selfSupportCKKSForAll && SOSPeerInfoSupportsCKKSForAll(peer)) {
204 secnotice("engine-notify", "Peer %@ supports CKKS For All; ignoring in SOS syncing", SOSPeerInfoGetPeerID(peer));
205 CFSetRemoveAllValues(views);
206 }
207
208 CFStringSetPerformWithDescription(views, ^(CFStringRef viewsDescription) {
209 secnotice("engine-notify", "Meta: %@: %@", SOSPeerInfoGetPeerID(peer), viewsDescription);
210 });
211
212 SOSPeerMetaRef peerMeta = SOSPeerMetaCreateWithComponents(SOSPeerInfoGetPeerID(peer), views, NULL);
213 CFReleaseNull(views);
214
215 CFArrayAppendValue(arrayToAddTo, peerMeta);
216 CFReleaseNull(peerMeta);
217 });
218
219 // We don't make a backup peer meta for the magic V0 peer
220 // Set up all the rest before we munge the set
221 SOSAccountAppendPeerMetasForViewBackups(account, myViews, syncing_peer_metas);
222
223 // If we saw someone else needing V0, we sync V0, too!
224 if (addV0Views) {
225 CFSetAddValue(myViews, kSOSViewKeychainV0);
226 }
227
228 CFStringSetPerformWithDescription(myViews, ^(CFStringRef viewsDescription) {
229 secnotice("engine-notify", "My Meta: %@: %@", myPi_id, viewsDescription);
230 });
231 myMeta = SOSPeerMetaCreateWithComponents(myPi_id, myViews, NULL);
232 CFReleaseSafe(myViews);
233 }
234
235 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account.factory, SOSCircleGetName(circle), NULL);
236 if (engine) {
237 SOSEngineCircleChanged(engine, myMeta, syncing_peer_metas, zombie_peer_metas);
238 }
239
240 CFReleaseNull(myMeta);
241 CFReleaseSafe(localError);
242
243 CFReleaseNull(syncing_peer_metas);
244 CFReleaseNull(zombie_peer_metas);
245 }
246
247
248 // Upcoming call to View Changes Here
249 void SOSAccountNotifyOfChange(SOSAccount* account, SOSCircleRef oldCircle, SOSCircleRef newCircle)
250 {
251 account.circle_rings_retirements_need_attention = true;
252
253 CFMutableSetRef old_members = SOSCircleCopyPeers(oldCircle, kCFAllocatorDefault);
254 CFMutableSetRef new_members = SOSCircleCopyPeers(newCircle, kCFAllocatorDefault);
255
256 CFMutableSetRef old_applicants = SOSCircleCopyApplicants(oldCircle, kCFAllocatorDefault);
257 CFMutableSetRef new_applicants = SOSCircleCopyApplicants(newCircle, kCFAllocatorDefault);
258
259 SOSPeerInfoRef me = account.peerInfo;
260 if(me && CFSetContainsValue(new_members, me))
261 SOSAccountSetValue(account, kSOSEscrowRecord, kCFNull, NULL); //removing the escrow records from the account object
262
263 DifferenceAndCall(old_members, new_members, ^(CFSetRef added_members, CFSetRef removed_members) {
264 DifferenceAndCall(old_applicants, new_applicants, ^(CFSetRef added_applicants, CFSetRef removed_applicants) {
265 CFArrayForEach((__bridge CFArrayRef)(account.change_blocks), ^(const void * notificationBlock) {
266 secnotice("updates", "calling change block");
267 ((__bridge SOSAccountCircleMembershipChangeBlock) notificationBlock)(account, newCircle, added_members, removed_members, added_applicants, removed_applicants);
268 });
269 });
270 });
271
272 CFReleaseNull(old_applicants);
273 CFReleaseNull(new_applicants);
274
275 CFReleaseNull(old_members);
276 CFReleaseNull(new_members);
277 }
278
279 CF_RETURNS_RETAINED
280 CFDictionaryRef SOSAccountHandleRetirementMessages(SOSAccount* account, CFDictionaryRef circle_retirement_messages, CFErrorRef *error) {
281 SOSAccountTrustClassic* trust = account.trust;
282 CFStringRef circle_name = SOSCircleGetName(trust.trustedCircle);
283 CFMutableArrayRef handledRetirementIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
284 // We only handle one circle, look it up:
285
286 if(!trust.trustedCircle) // We don't fail, we intentionally handle nothing.
287 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, NULL);
288
289 CFDictionaryRef retirement_dictionary = asDictionary(CFDictionaryGetValue(circle_retirement_messages, circle_name), NULL);
290 if(!retirement_dictionary)
291 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, NULL);
292
293 CFDictionaryForEach(retirement_dictionary, ^(const void *key, const void *value) {
294 if(isData(value)) {
295 SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
296 if(pi && CFEqual(key, SOSPeerInfoGetPeerID(pi)) && SOSPeerInfoInspectRetirementTicket(pi, error)) {
297 [trust.retirees addObject: (__bridge id _Nonnull)(pi)];
298
299 account.circle_rings_retirements_need_attention = true; // Have to handle retirements.
300
301 CFArrayAppendValue(handledRetirementIDs, key);
302 }
303 CFReleaseNull(pi);
304 }
305 });
306
307 // If we are in the retiree list, we somehow got resurrected
308 // clearly we took care of proper departure before so leave
309 // and delcare that we withdrew this time.
310 SOSPeerInfoRef me = account.peerInfo;
311
312 if (me && [trust.retirees containsObject:(__bridge id _Nonnull)(me)]) {
313 SOSAccountPurgeIdentity(account);
314 trust.departureCode = kSOSDiscoveredRetirement;
315 }
316
317 CFDictionaryRef result = (CFArrayGetCount(handledRetirementIDs) == 0) ? CFDictionaryCreateForCFTypes(kCFAllocatorDefault, NULL)
318 : CFDictionaryCreateForCFTypes(kCFAllocatorDefault, circle_name, handledRetirementIDs, NULL);
319
320 CFReleaseNull(handledRetirementIDs);
321 return result;
322 }
323
324 static SOSCircleRef SOSAccountCreateCircleFrom(CFStringRef circleName, CFTypeRef value, CFErrorRef *error) {
325 if (value && !isData(value) && !isNull(value)) {
326 secnotice("circleOps", "Value provided not appropriate for a circle");
327 CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(value));
328 SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL,
329 CFSTR("Expected data or NULL got %@"), description);
330 CFReleaseSafe(description);
331 return NULL;
332 }
333
334 SOSCircleRef circle = NULL;
335 if (!value || isNull(value)) {
336 secnotice("circleOps", "No circle found in data: %@", value);
337 circle = NULL;
338 } else {
339 circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, error);
340 if (circle) {
341 CFStringRef name = SOSCircleGetName(circle);
342 if (!CFEqualSafe(name, circleName)) {
343 secnotice("circleOps", "Expected circle named %@, got %@", circleName, name);
344 SOSCreateErrorWithFormat(kSOSErrorNameMismatch, NULL, error, NULL,
345 CFSTR("Expected circle named %@, got %@"), circleName, name);
346 CFReleaseNull(circle);
347 }
348 } else {
349 secnotice("circleOps", "SOSCircleCreateFromData returned NULL.");
350 }
351 }
352 return circle;
353 }
354
355 bool SOSAccountHandleCircleMessage(SOSAccount* account,
356 CFStringRef circleName, CFDataRef encodedCircleMessage, CFErrorRef *error) {
357 bool success = false;
358 CFErrorRef localError = NULL;
359
360 if(account && account.accountIsChanging) {
361 secnotice("circleOps", "SOSAccountHandleCircleMessage called before signing in to new account");
362 return true; // we want to drop circle notifications when account is changing
363 }
364
365 SOSCircleRef circle = SOSAccountCreateCircleFrom(circleName, encodedCircleMessage, &localError);
366 if (circle) {
367 success = [account.trust updateCircleFromRemote:account.circle_transport newCircle:circle err:&localError];
368 CFReleaseSafe(circle);
369 } else {
370 secerror("NULL circle found, ignoring ...");
371 success = true; // don't pend this NULL thing.
372 }
373
374 if (!success) {
375 if (isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle)) {
376 secerror("Incompatible circle found, abandoning membership: %@", circleName);
377 }
378
379 if (error) {
380 *error = localError;
381 localError = NULL;
382 }
383
384 }
385
386 CFReleaseNull(localError);
387
388 return success;
389 }
390
391 bool SOSAccountHandleParametersChange(SOSAccount* account, CFDataRef parameters, CFErrorRef *error){
392 if(account && account.accountIsChanging) {
393 secnotice("circleOps", "SOSAccountHandleParametersChange called before signing in to new account");
394 return true; // we want to drop parm notifications when account is changing
395 }
396
397 SecKeyRef newKey = NULL;
398 CFDataRef pbkdfParams = NULL;
399 bool success = false;
400
401 if(SOSAccountRetrieveCloudParameters(account, &newKey, parameters, &pbkdfParams, error)) {
402 debugDumpUserParameters(CFSTR("SOSAccountHandleParametersChange got new user key parameters:"), pbkdfParams);
403 CFStringRef keyid = SOSCopyIDOfKeyWithLength(newKey, 8, NULL);
404 secnotice("circleOps", "SOSAccountHandleParametersChange got new public key: %@", keyid);
405 CFReleaseNull(keyid);
406
407 if (CFEqualSafe(account.accountKey, newKey)) {
408 secnotice("circleOps", "Got same public key sent our way. Ignoring.");
409 success = true;
410 } else if (CFEqualSafe(account.previousAccountKey, newKey)) {
411 secnotice("circleOps", "Got previous public key repeated. Ignoring.");
412 success = true;
413 } else {
414 SOSAccountSetUnTrustedUserPublicKey(account, newKey);
415 CFReleaseNull(newKey);
416 SOSAccountSetParameters(account, pbkdfParams);
417
418 if(SOSAccountRetryUserCredentials(account)) {
419 secnotice("circleOps", "Successfully used cached password with new parameters");
420 SOSAccountGenerationSignatureUpdate(account, error);
421 } else {
422 secnotice("circleOps", "Got new parameters for public key - could not find or use cached password");
423 SOSAccountPurgePrivateCredential(account);
424 }
425 secnotice("circleop", "Setting account.key_interests_need_updating to true in SOSAccountHandleParametersChange");
426 account.circle_rings_retirements_need_attention = true;
427 account.key_interests_need_updating = true;
428
429 success = true;
430 }
431 }
432
433 CFReleaseNull(newKey);
434 CFReleaseNull(pbkdfParams);
435
436 return success;
437 }
438