]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountTransaction.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / 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 <Security/SecureObjectSync/SOSAccount.h>
14 #include <Security/SecureObjectSync/SOSAccountPriv.h>
15 #include <Security/SecureObjectSync/SOSPeerInfoV2.h>
16 #import <Security/SecureObjectSync/SOSTransport.h>
17 #import <Security/SecureObjectSync/SOSTransportCircle.h>
18 #import <Security/SecureObjectSync/SOSTransportCircleKVS.h>
19 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
20 #import "Security/SecureObjectSync/SOSAccountTrustClassic.h"
21 #import "Security/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 static void SOSCircleSetCachedStatus(SOSAccount *account) {
58 static SOSCCStatus lastStatus = -2;
59 SOSCCStatus currentStatus = [account getCircleStatus:NULL];
60
61 if(lastStatus != currentStatus) {
62 lastStatus = currentStatus;
63 account.notifyCircleChangeOnExit = true;
64 }
65
66 if(account.notifyCircleChangeOnExit) {
67 __block uint64_t circleStatus = [account.trust getCircleStatusOnly:NULL] | CC_STATISVALID;
68 if(account.accountKeyIsTrusted) {
69 circleStatus |= CC_UKEY_TRUSTED;
70 if(account.accountPrivateKey) {
71 circleStatus |= CC_CAN_AUTH;
72 }
73 }
74 // we might be in circle, but invalid - let client see this in bitmask.
75 if([account.trust isInCircleOnly:NULL]) {
76 circleStatus |= CC_PEER_IS_IN;
77 }
78
79 SOSCachedNotificationOperation(kSOSCCCircleChangedNotification, ^bool(int token, bool gtg) {
80 if(gtg) {
81 uint32_t status = notify_set_state(token, circleStatus);
82 if(status == NOTIFY_STATUS_OK) {
83 notify_post(kSOSCCCircleChangedNotification);
84 account.notifyCircleChangeOnExit = false;
85 }
86 return true;
87 }
88 return false;
89 });
90 }
91 }
92
93 static void SOSViewsSetCachedStatus(SOSAccount *account) {
94 static uint64_t lastViewBitmask = 0;
95 __block uint64_t viewBitMask = ([account getCircleStatus:NULL] == kSOSCCInCircle) ? SOSPeerInfoViewBitMask(account.peerInfo) :0;
96
97 if(viewBitMask != lastViewBitmask) {
98 lastViewBitmask = viewBitMask;
99 account.notifyViewChangeOnExit = true; // this is also set within operations and might want the notification for other reasons.
100 }
101
102 if(account.notifyViewChangeOnExit) {
103 SOSCachedNotificationOperation(kSOSCCViewMembershipChangedNotification, ^bool(int token, bool gtg) {
104 if(gtg) {
105 uint32_t status = notify_set_state(token, viewBitMask);
106 if(status == NOTIFY_STATUS_OK) {
107 notify_post(kSOSCCViewMembershipChangedNotification);
108 account.notifyViewChangeOnExit = false;
109 }
110 return true;
111 }
112 return false;
113 });
114 }
115 }
116
117 - (NSString*) description {
118 return [NSString stringWithFormat:@"<SOSAccountTransaction*@%p %ld>",
119 self, (unsigned long)(self.initialViews ? [self.initialViews count] : 0)];
120 }
121
122 - (instancetype) initWithAccount:(SOSAccount *)account quiet:(bool)quiet {
123 if (self = [super init]) {
124 self.account = account;
125 _quiet = quiet;
126 [self start];
127 }
128 return self;
129 }
130
131 - (void) start {
132 SOSCircleSetCachedStatus(_account);
133 SOSViewsSetCachedStatus(_account);
134
135 self.initialInCircle = [self.account isInCircle:NULL];
136 self.initialTrusted = self.account.accountKeyIsTrusted;
137 self.initialCirclePeerCount = 0;
138 if(self.initialInCircle) {
139 self.initialCirclePeerCount = SOSCircleCountPeers(self.account.trust.trustedCircle);
140 }
141
142 if (self.initialInCircle) {
143 SOSAccountEnsureSyncChecking(self.account);
144 }
145
146 self.initialUnsyncedViews = (__bridge_transfer NSMutableSet<NSString*>*)SOSAccountCopyOutstandingViews(self.account);
147 self.initialKeyParameters = self.account.accountKeyDerivationParamters ? [NSData dataWithData:self.account.accountKeyDerivationParamters] : nil;
148
149 SOSPeerInfoRef mpi = self.account.peerInfo;
150 if (mpi) {
151 self.initialViews = CFBridgingRelease(SOSPeerInfoCopyEnabledViews(mpi));
152 [self.account ensureOctagonPeerKeys];
153 }
154 self.peersToRequestSync = nil;
155
156 if(!self.quiet) {
157 CFStringSetPerformWithDescription((__bridge CFSetRef) self.initialViews, ^(CFStringRef description) {
158 secnotice("acct-txn", "Starting as:%s v:%@", self.initialInCircle ? "member" : "non-member", description);
159 });
160 }
161 }
162
163 - (void) restart {
164 [self finish];
165 [self start];
166 }
167
168
169 - (void) finish {
170 static int do_account_state_at_zero = 0;
171 bool doCircleChanged = false;
172 bool doViewChanged = false;
173
174
175 CFErrorRef localError = NULL;
176 bool notifyEngines = false;
177
178 SOSPeerInfoRef mpi = self.account.peerInfo;
179
180 bool isInCircle = [self.account isInCircle:NULL];
181
182 if (isInCircle && self.peersToRequestSync) {
183 SOSCCRequestSyncWithPeers((__bridge CFSetRef)(self.peersToRequestSync));
184 }
185 self.peersToRequestSync = nil;
186
187 if (isInCircle) {
188 SOSAccountEnsureSyncChecking(self.account);
189 } else {
190 SOSAccountCancelSyncChecking(self.account);
191 }
192
193 // If our identity changed our inital set should be everything.
194 if ([self.initialID isEqualToString: (__bridge NSString *)(SOSPeerInfoGetPeerID(mpi))]) {
195 self.initialUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSViewCopyViewSet(kViewSetAll);
196 }
197
198 NSSet<NSString*>* finalUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSAccountCopyOutstandingViews(self.account);
199 if (!NSIsEqualSafe(self.initialUnsyncedViews, finalUnsyncedViews)) {
200 if (SOSAccountHandleOutOfSyncUpdate(self.account,
201 (__bridge CFSetRef)(self.initialUnsyncedViews),
202 (__bridge CFSetRef)(finalUnsyncedViews))) {
203 notifyEngines = true;
204 }
205
206 secnotice("initial-sync", "Unsynced was: %@", [self.initialUnsyncedViews shortDescription]);
207 secnotice("initial-sync", "Unsynced is: %@", [finalUnsyncedViews shortDescription]);
208 }
209
210 if (self.account.engine_peer_state_needs_repair) {
211 // We currently only get here from a failed syncwithallpeers, so
212 // that will retry. If this logic changes, force a syncwithallpeers
213 if (!SOSAccountEnsurePeerRegistration(self.account, &localError)) {
214 secerror("Ensure peer registration while repairing failed: %@", localError);
215 }
216 CFReleaseNull(localError);
217
218 notifyEngines = true;
219 }
220
221 if(self.account.circle_rings_retirements_need_attention){
222 SOSAccountRecordRetiredPeersInCircle(self.account);
223
224 SOSAccountEnsureRecoveryRing(self.account);
225 SOSAccountEnsureInBackupRings(self.account);
226
227 CFErrorRef localError = NULL;
228 if(![self.account.circle_transport flushChanges:&localError]){
229 secerror("flush circle failed %@", localError);
230 }
231 CFReleaseSafe(localError);
232
233 notifyEngines = true;
234 }
235
236 if (notifyEngines) {
237 #if OCTAGON
238 if(!SecCKKSTestDisableSOS()) {
239 #endif
240 SOSAccountNotifyEngines(self.account);
241 #if OCTAGON
242 }
243 #endif
244 }
245
246 if(self.account.key_interests_need_updating){
247 SOSUpdateKeyInterest(self.account);
248 }
249
250 self.account.key_interests_need_updating = false;
251 self.account.circle_rings_retirements_need_attention = false;
252 self.account.engine_peer_state_needs_repair = false;
253
254 [self.account flattenToSaveBlock];
255
256 // Refresh isInCircle since we could have changed our mind
257 isInCircle = [self.account isInCircle:NULL];
258
259 uint finalCirclePeerCount = 0;
260 if(isInCircle) {
261 finalCirclePeerCount = SOSCircleCountPeers(self.account.trust.trustedCircle);
262 }
263
264 if(isInCircle && (finalCirclePeerCount < self.initialCirclePeerCount)) {
265 (void) SOSAccountCleanupAllKVSKeys(_account, NULL);
266 }
267
268 mpi = self.account.peerInfo;
269 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
270
271 if(!self.quiet) {
272 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
273 secnotice("acct-txn", "Finished as:%s v:%@", isInCircle ? "member" : "non-member", description);
274 });
275 }
276
277 // This is the logic to detect a new userKey:
278 bool userKeyChanged = !NSIsEqualSafe(self.initialKeyParameters, self.account.accountKeyDerivationParamters);
279
280 // This indicates we initiated a password change.
281 bool weInitiatedKeyChange = (self.initialTrusted &&
282 self.initialInCircle &&
283 userKeyChanged && isInCircle &&
284 self.account.accountKeyIsTrusted);
285
286 if(self.initialInCircle != isInCircle) {
287 doCircleChanged = true;
288 doViewChanged = true;
289 do_account_state_at_zero = 0;
290 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification && kSOSCCViewMembershipChangedNotification for circle/view change");
291 } else if(isInCircle && !NSIsEqualSafe(self.initialViews, (__bridge NSSet*)views)) {
292 doViewChanged = true;
293 do_account_state_at_zero = 0;
294 secnotice("secdNotify", "Notified clients of kSOSCCViewMembershipChangedNotification for viewchange(only)");
295 } else if(weInitiatedKeyChange) { // We consider this a circleChange so (PCS) can tell the userkey trust changed.
296 doCircleChanged = true;
297 do_account_state_at_zero = 0;
298 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification for userKey change");
299 }
300
301
302
303 // This is the case of we used to trust the key, were in the circle, the key changed, we don't trust it now.
304 bool fellOutOfTrust = (self.initialTrusted &&
305 self.initialInCircle &&
306 userKeyChanged &&
307 !self.account.accountKeyIsTrusted);
308
309 if(fellOutOfTrust) {
310 secnotice("userKeyTrust", "No longer trust user public key - prompting for password.");
311 notify_post(kPublicKeyNotAvailable);
312 doCircleChanged = true;
313 do_account_state_at_zero = 0;
314 }
315
316 bool userKeyTrustChangedToTrueAndNowInCircle = (!self.initialTrusted && self.account.accountKeyIsTrusted && isInCircle);
317
318 if(userKeyTrustChangedToTrueAndNowInCircle) {
319 secnotice("userKeyTrust", "UserKey is once again trusted and we're valid in circle.");
320 doCircleChanged = true;
321 doViewChanged = true;
322 }
323
324 if(doCircleChanged) {
325 SOSCircleSetCachedStatus(_account);
326 }
327 if(doViewChanged) {
328 SOSViewsSetCachedStatus(_account);
329 }
330 if(self.account.notifyBackupOnExit) {
331 notify_post(kSecItemBackupNotification);
332 self.account.notifyBackupOnExit = false;
333 }
334
335
336 if(do_account_state_at_zero <= 0) {
337 SOSAccountLogState(self.account);
338 SOSAccountLogViewState(self.account);
339 do_account_state_at_zero = ACCOUNT_STATE_INTERVAL;
340 }
341 do_account_state_at_zero--;
342
343 CFReleaseNull(views);
344 }
345
346 - (void) requestSyncWith: (NSString*) peerID {
347 if (self.peersToRequestSync == nil) {
348 self.peersToRequestSync = [NSMutableSet<NSString*> set];
349 }
350 [self.peersToRequestSync addObject: peerID];
351 }
352
353 - (void) requestSyncWithPeers: (NSSet<NSString*>*) peerList {
354 if (self.peersToRequestSync == nil) {
355 self.peersToRequestSync = [NSMutableSet<NSString*> set];
356 }
357 [self.peersToRequestSync unionSet: peerList];
358 }
359
360 @end
361
362
363
364
365 //
366 // MARK: Transactional
367 //
368
369 @implementation SOSAccount (Transaction)
370
371 __thread bool __hasAccountQueue = false;
372
373 + (void)performWhileHoldingAccountQueue:(void (^)(void))action
374 {
375 bool hadAccountQueue = __hasAccountQueue;
376 __hasAccountQueue = true;
377 action();
378 __hasAccountQueue = hadAccountQueue;
379 }
380
381 + (void)performOnQuietAccountQueue:(void (^)(void))action
382 {
383 SOSAccount* account = (__bridge SOSAccount*)GetSharedAccountRef();
384 [account performTransaction:true action:^(SOSAccountTransaction * _Nonnull txn) {
385 action();
386 }];
387 }
388
389 - (void) performTransaction_Locked: (void (^)(SOSAccountTransaction* txn)) action {
390 [self performTransaction_Locked:false action:action];
391 }
392
393 - (void) performTransaction_Locked:(bool)quiet action:(void (^)(SOSAccountTransaction* txn))action {
394 @autoreleasepool {
395 SOSAccountTransaction* transaction = [[SOSAccountTransaction new] initWithAccount:self quiet:quiet];
396 action(transaction);
397 [transaction finish];
398 }
399 }
400
401 - (void) performTransaction: (void (^)(SOSAccountTransaction* txn)) action {
402 [self performTransaction:false action:action];
403 }
404
405 - (void)performTransaction:(bool)quiet action:(void (^)(SOSAccountTransaction* txn))action {
406
407 if (__hasAccountQueue) {
408 // Be quiet; we're already in a transaction
409 [self performTransaction_Locked:true action:action];
410 }
411 else {
412 dispatch_sync(self.queue, ^{
413 __hasAccountQueue = true;
414 [self performTransaction_Locked:quiet action:action];
415 __hasAccountQueue = false;
416 });
417 }
418 }
419
420
421 @end