]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccountTransaction.m
Security-59306.41.2.tar.gz
[apple/security.git] / keychain / SecureObjectSync / SOSAccountTransaction.m
1 //
2 // SOSAccountTransaction.c
3 // sec
4 //
5 //
6
7 #include "SOSAccountTransaction.h"
8
9 #include <utilities/SecCFWrappers.h>
10 #import <utilities/SecNSAdditions.h>
11 #include <CoreFoundation/CoreFoundation.h>
12
13 #include "keychain/SecureObjectSync/SOSAccount.h"
14 #include "keychain/SecureObjectSync/SOSAccountPriv.h"
15 #include "keychain/SecureObjectSync/SOSPeerInfoV2.h"
16 #import "keychain/SecureObjectSync/SOSTransport.h"
17 #import "keychain/SecureObjectSync/SOSTransportCircle.h"
18 #import "keychain/SecureObjectSync/SOSTransportCircleKVS.h"
19 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
20 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
21 #import "keychain/SecureObjectSync/SOSTransportMessageKVS.h"
22 #import "Security/SecItemBackup.h"
23
24 #include <keychain/ckks/CKKS.h>
25
26 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
27
28 // Account dumping state stuff
29
30 #define ACCOUNT_STATE_INTERVAL 200
31
32
33 @interface SOSAccountTransaction ()
34
35 @property BOOL initialInCircle;
36 @property NSSet<NSString*>* initialViews;
37 @property NSSet<NSString*>* initialUnsyncedViews;
38 @property NSString* initialID;
39
40 @property BOOL initialTrusted;
41 @property NSData* initialKeyParameters;
42
43 @property uint initialCirclePeerCount;
44
45 @property bool quiet;
46
47 @property NSMutableSet<NSString*>* peersToRequestSync;
48
49 - (void) start;
50
51 @end
52
53
54
55 @implementation SOSAccountTransaction
56
57 - (uint64_t)currentTrustBitmask {
58 dispatch_assert_queue(_account.queue);
59
60 // A couple of assumptions here, that circle status is 32 bit (it an int), and that we can store
61 // that in th lower half without sign extending it
62 uint64_t circleStatus = (((uint32_t)[_account.trust getCircleStatusOnly:NULL]) & 0xffffffff) | CC_STATISVALID;
63 if(_account.accountKeyIsTrusted) {
64 circleStatus |= CC_UKEY_TRUSTED;
65 if(_account.accountPrivateKey) {
66 circleStatus |= CC_CAN_AUTH;
67 }
68 }
69 // we might be in circle, but invalid - let client see this in bitmask.
70 if([_account.trust isInCircleOnly:NULL]) {
71 circleStatus |= CC_PEER_IS_IN;
72 }
73 return circleStatus;
74 }
75
76 - (void) updateSOSCircleCachedStatus {
77 // clearly invalid, overwritten by cached value in notify_get_state()
78 // if its the second time we are launchded
79 static uint64_t lastStatus = 0;
80
81 dispatch_assert_queue(_account.queue);
82
83 static dispatch_once_t onceToken;
84 dispatch_once(&onceToken, ^{
85 // pull out what previous instance of securityd though the trust status
86 SOSCachedNotificationOperation(kSOSCCCircleChangedNotification, ^bool(int token, bool gtg) {
87 uint64_t state64;
88 if (notify_get_state(token, &state64) == NOTIFY_STATUS_OK) {
89 if (state64 & CC_STATISVALID) {
90 lastStatus = state64;
91 }
92 }
93 return true;
94 });
95 secnotice("sosnotify", "initial last circle status is: %llu", (unsigned long long)lastStatus);
96 });
97
98 uint64_t currentStatus = [self currentTrustBitmask];
99 if (lastStatus & CC_STATISVALID) {
100 if(lastStatus != currentStatus) {
101 _account.notifyCircleChangeOnExit = true;
102 }
103 } else {
104 _account.notifyCircleChangeOnExit = true;
105 }
106
107 if(_account.notifyCircleChangeOnExit) {
108 // notify if last cache status was invalid, or it have changed, clients don't get update on changes in
109 // the metadata stored in the upper bits of above CC_MASK (at least not though this path)
110 bool firstLaunch = (lastStatus & CC_STATISVALID) == 0;
111 bool circleChanged = ((lastStatus & CC_MASK) != (currentStatus & CC_MASK));
112
113 bool circleStatusChanged = firstLaunch || circleChanged;
114
115 lastStatus = currentStatus;
116
117 secnotice("sosnotify", "new last circle status is: %llu (notify: %s)",
118 (unsigned long long)lastStatus,
119 circleStatusChanged ? "yes" : "no");
120
121 SOSCachedNotificationOperation(kSOSCCCircleChangedNotification, ^bool(int token, bool gtg) {
122 if(gtg) {
123 uint32_t status = notify_set_state(token, currentStatus);
124 if(status == NOTIFY_STATUS_OK) {
125 self->_account.notifyCircleChangeOnExit = false;
126
127 if (circleStatusChanged) {
128 secnotice("sosnotify", "posting kSOSCCCircleChangedNotification");
129 notify_post(kSOSCCCircleChangedNotification);
130 }
131 }
132 return true;
133 }
134 return false;
135 });
136
137 // preserve behavior from previous, this should be merged into SOSViewsSetCachedStatus()
138 if (firstLaunch) {
139 _account.notifyViewChangeOnExit = true;
140 }
141 }
142 }
143
144 static void SOSViewsSetCachedStatus(SOSAccount *account) {
145 static uint64_t lastViewBitmask = 0;
146 CFSetRef piViews = SOSAccountCopyEnabledViews(account);
147
148 __block uint64_t viewBitMask = (([account getCircleStatus:NULL] == kSOSCCInCircle) && piViews) ? SOSViewBitmaskFromSet(piViews) :0;
149 CFReleaseNull(piViews);
150
151 if(viewBitMask != lastViewBitmask) {
152 lastViewBitmask = viewBitMask;
153 account.notifyViewChangeOnExit = true; // this is also set within operations and might want the notification for other reasons.
154 }
155
156 if(account.notifyViewChangeOnExit) {
157 SOSCachedNotificationOperation(kSOSCCViewMembershipChangedNotification, ^bool(int token, bool gtg) {
158 if(gtg) {
159 uint32_t status = notify_set_state(token, viewBitMask);
160 if(status == NOTIFY_STATUS_OK) {
161 notify_post(kSOSCCViewMembershipChangedNotification);
162 account.notifyViewChangeOnExit = false;
163 }
164 return true;
165 }
166 return false;
167 });
168 }
169 }
170
171 - (NSString*) description {
172 return [NSString stringWithFormat:@"<SOSAccountTransaction*@%p %ld>",
173 self, (unsigned long)(self.initialViews ? [self.initialViews count] : 0)];
174 }
175
176 - (instancetype) initWithAccount:(SOSAccount *)account quiet:(bool)quiet {
177 if (self = [super init]) {
178 self.account = account;
179 _quiet = quiet;
180 [self start];
181 }
182 return self;
183 }
184
185 - (void) start {
186 [self updateSOSCircleCachedStatus];
187 SOSViewsSetCachedStatus(_account);
188
189 self.initialInCircle = [self.account isInCircle:NULL];
190 self.initialTrusted = self.account.accountKeyIsTrusted;
191 self.initialCirclePeerCount = 0;
192 if(self.initialInCircle) {
193 self.initialCirclePeerCount = SOSCircleCountPeers(self.account.trust.trustedCircle);
194 }
195
196 if (self.initialInCircle) {
197 SOSAccountEnsureSyncChecking(self.account);
198 }
199
200 self.initialUnsyncedViews = (__bridge_transfer NSMutableSet<NSString*>*)SOSAccountCopyOutstandingViews(self.account);
201 self.initialKeyParameters = self.account.accountKeyDerivationParamters ? [NSData dataWithData:self.account.accountKeyDerivationParamters] : nil;
202
203 SOSPeerInfoRef mpi = self.account.peerInfo;
204 if (mpi) {
205 self.initialViews = CFBridgingRelease(SOSPeerInfoCopyEnabledViews(mpi));
206 [self.account ensureOctagonPeerKeys];
207 }
208 self.peersToRequestSync = nil;
209
210 if(self.account.key_interests_need_updating) {
211 SOSUpdateKeyInterest(self.account);
212 }
213
214 if(!self.quiet) {
215 CFStringSetPerformWithDescription((__bridge CFSetRef) self.initialViews, ^(CFStringRef description) {
216 secnotice("acct-txn", "Starting as:%s v:%@", self.initialInCircle ? "member" : "non-member", description);
217 });
218 }
219 }
220
221 - (void) restart {
222 [self finish];
223 [self start];
224 }
225
226
227 - (void) finish {
228 static int do_account_state_at_zero = 0;
229 bool doCircleChanged = self.account.notifyCircleChangeOnExit;
230 bool doViewChanged = false;
231
232
233 CFErrorRef localError = NULL;
234 bool notifyEngines = false;
235
236 SOSPeerInfoRef mpi = self.account.peerInfo;
237
238 bool isInCircle = [self.account isInCircle:NULL];
239
240 if (isInCircle && self.peersToRequestSync) {
241 SOSCCRequestSyncWithPeers((__bridge CFSetRef)(self.peersToRequestSync));
242 }
243 self.peersToRequestSync = nil;
244
245 if (isInCircle) {
246 SOSAccountEnsureSyncChecking(self.account);
247 } else {
248 SOSAccountCancelSyncChecking(self.account);
249 }
250
251 // If our identity changed our inital set should be everything.
252 if ([self.initialID isEqualToString: (__bridge NSString *)(SOSPeerInfoGetPeerID(mpi))]) {
253 self.initialUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSViewCopyViewSet(kViewSetAll);
254 }
255
256 NSSet<NSString*>* finalUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSAccountCopyOutstandingViews(self.account);
257 if (!NSIsEqualSafe(self.initialUnsyncedViews, finalUnsyncedViews)) {
258 if (SOSAccountHandleOutOfSyncUpdate(self.account,
259 (__bridge CFSetRef)(self.initialUnsyncedViews),
260 (__bridge CFSetRef)(finalUnsyncedViews))) {
261 notifyEngines = true;
262 }
263
264 secnotice("initial-sync", "Unsynced was: %@", [self.initialUnsyncedViews shortDescription]);
265 secnotice("initial-sync", "Unsynced is: %@", [finalUnsyncedViews shortDescription]);
266 }
267
268 if (self.account.engine_peer_state_needs_repair) {
269 // We currently only get here from a failed syncwithallpeers, so
270 // that will retry. If this logic changes, force a syncwithallpeers
271 if (!SOSAccountEnsurePeerRegistration(self.account, &localError)) {
272 secerror("Ensure peer registration while repairing failed: %@", localError);
273 }
274 CFReleaseNull(localError);
275
276 notifyEngines = true;
277 }
278
279 if(self.account.circle_rings_retirements_need_attention){
280 SOSAccountRecordRetiredPeersInCircle(self.account);
281
282 SOSAccountEnsureRecoveryRing(self.account);
283 SOSAccountEnsureInBackupRings(self.account);
284
285 CFErrorRef localError = NULL;
286 if(![self.account.circle_transport flushChanges:&localError]){
287 secerror("flush circle failed %@", localError);
288 }
289 CFReleaseSafe(localError);
290
291 notifyEngines = true;
292 }
293
294 if (notifyEngines) {
295 #if OCTAGON
296 if(!SecCKKSTestDisableSOS()) {
297 #endif
298 SOSAccountNotifyEngines(self.account);
299 #if OCTAGON
300 }
301 #endif
302 }
303
304 if(self.account.key_interests_need_updating) {
305 SOSUpdateKeyInterest(self.account);
306 }
307
308 self.account.circle_rings_retirements_need_attention = false;
309 self.account.engine_peer_state_needs_repair = false;
310
311 [self.account flattenToSaveBlock];
312
313 // Refresh isInCircle since we could have changed our mind
314 isInCircle = [self.account isInCircle:NULL];
315
316 uint finalCirclePeerCount = 0;
317 if(isInCircle) {
318 finalCirclePeerCount = SOSCircleCountPeers(self.account.trust.trustedCircle);
319 }
320
321 if(isInCircle && (finalCirclePeerCount < self.initialCirclePeerCount)) {
322 (void) SOSAccountCleanupAllKVSKeys(_account, NULL);
323 }
324
325 mpi = self.account.peerInfo;
326 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
327
328 if(!self.quiet) {
329 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
330 secnotice("acct-txn", "Finished as:%s v:%@", isInCircle ? "member" : "non-member", description);
331 });
332 }
333
334 // This is the logic to detect a new userKey:
335 bool userKeyChanged = !NSIsEqualSafe(self.initialKeyParameters, self.account.accountKeyDerivationParamters);
336
337 // This indicates we initiated a password change.
338 bool weInitiatedKeyChange = (self.initialTrusted &&
339 self.initialInCircle &&
340 userKeyChanged && isInCircle &&
341 self.account.accountKeyIsTrusted);
342
343 if(self.initialInCircle != isInCircle) {
344 doCircleChanged = true;
345 doViewChanged = true;
346 do_account_state_at_zero = 0;
347 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification && kSOSCCViewMembershipChangedNotification for circle/view change");
348 } else if(isInCircle && !NSIsEqualSafe(self.initialViews, (__bridge NSSet*)views)) {
349 doViewChanged = true;
350 do_account_state_at_zero = 0;
351 secnotice("secdNotify", "Notified clients of kSOSCCViewMembershipChangedNotification for viewchange(only)");
352 } else if(weInitiatedKeyChange) { // We consider this a circleChange so (PCS) can tell the userkey trust changed.
353 doCircleChanged = true;
354 do_account_state_at_zero = 0;
355 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification for userKey change");
356 }
357
358
359
360 // This is the case of we used to trust the key, were in the circle, the key changed, we don't trust it now.
361 bool fellOutOfTrust = (self.initialTrusted &&
362 self.initialInCircle &&
363 userKeyChanged &&
364 !self.account.accountKeyIsTrusted);
365
366 if(fellOutOfTrust) {
367 secnotice("userKeyTrust", "No longer trust user public key - prompting for password.");
368 notify_post(kPublicKeyNotAvailable);
369 doCircleChanged = true;
370 do_account_state_at_zero = 0;
371 }
372
373 bool userKeyTrustChangedToTrueAndNowInCircle = (!self.initialTrusted && self.account.accountKeyIsTrusted && isInCircle);
374
375 if(userKeyTrustChangedToTrueAndNowInCircle) {
376 secnotice("userKeyTrust", "UserKey is once again trusted and we're valid in circle.");
377 doCircleChanged = true;
378 doViewChanged = true;
379 }
380
381 if(doCircleChanged) {
382 [self updateSOSCircleCachedStatus];
383 }
384 if(doViewChanged) {
385 SOSViewsSetCachedStatus(_account);
386 }
387 if(self.account.notifyBackupOnExit) {
388 notify_post(kSecItemBackupNotification);
389 self.account.notifyBackupOnExit = false;
390 }
391
392
393 if(do_account_state_at_zero <= 0) {
394 SOSAccountLogState(self.account);
395 SOSAccountLogViewState(self.account);
396 do_account_state_at_zero = ACCOUNT_STATE_INTERVAL;
397 }
398 do_account_state_at_zero--;
399
400 CFReleaseNull(views);
401 }
402
403 - (void) requestSyncWith: (NSString*) peerID {
404 if (self.peersToRequestSync == nil) {
405 self.peersToRequestSync = [NSMutableSet<NSString*> set];
406 }
407 [self.peersToRequestSync addObject: peerID];
408 }
409
410 - (void) requestSyncWithPeers: (NSSet<NSString*>*) peerList {
411 if (self.peersToRequestSync == nil) {
412 self.peersToRequestSync = [NSMutableSet<NSString*> set];
413 }
414 [self.peersToRequestSync unionSet: peerList];
415 }
416
417 @end
418
419
420
421
422 //
423 // MARK: Transactional
424 //
425
426 @implementation SOSAccount (Transaction)
427
428 __thread bool __hasAccountQueue = false;
429
430 + (void)performOnQuietAccountQueue:(void (^)(void))action
431 {
432 SOSAccount* account = (__bridge SOSAccount*)GetSharedAccountRef();
433 if(account) {
434 [account performTransaction:true action:^(SOSAccountTransaction * _Nonnull txn) {
435 action();
436 }];
437 } else {
438 secnotice("acct-txn", "No account; running block on local thread");
439 action();
440 }
441 }
442
443 - (void) performTransaction_Locked: (void (^)(SOSAccountTransaction* txn)) action {
444 [self performTransaction_Locked:false action:action];
445 }
446
447 - (void) performTransaction_Locked:(bool)quiet action:(void (^)(SOSAccountTransaction* txn))action {
448 @autoreleasepool {
449 SOSAccountTransaction* transaction = [[SOSAccountTransaction alloc] initWithAccount:self quiet:quiet];
450 action(transaction);
451 [transaction finish];
452 }
453 }
454
455 - (void) performTransaction: (void (^)(SOSAccountTransaction* txn)) action {
456 [self performTransaction:false action:action];
457 }
458
459 - (void)performTransaction:(bool)quiet action:(void (^)(SOSAccountTransaction* txn))action {
460
461 if (__hasAccountQueue) {
462 // Be quiet; we're already in a transaction
463 [self performTransaction_Locked:true action:action];
464 }
465 else {
466 dispatch_sync(self.queue, ^{
467 __hasAccountQueue = true;
468 [self performTransaction_Locked:quiet action:action];
469 __hasAccountQueue = false;
470 });
471 }
472 }
473
474
475 @end