]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountTransaction.m
Security-58286.60.28.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
23 #include <keychain/ckks/CKKS.h>
24
25 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
26
27 // Account dumping state stuff
28
29 #define ACCOUNT_STATE_INTERVAL 200
30
31
32 @interface SOSAccountTransaction ()
33
34 @property BOOL initialInCircle;
35 @property NSSet<NSString*>* initialViews;
36 @property NSSet<NSString*>* initialUnsyncedViews;
37 @property NSString* initialID;
38
39 @property BOOL initialTrusted;
40 @property NSData* initialKeyParameters;
41
42 @property bool quiet;
43
44 @property NSMutableSet<NSString*>* peersToRequestSync;
45
46 - (void) start;
47
48 @end
49
50
51
52 @implementation SOSAccountTransaction
53
54 - (NSString*) description {
55 return [NSString stringWithFormat:@"<SOSAccountTransaction*@%p %ld>",
56 self, (unsigned long)(self.initialViews ? [self.initialViews count] : 0)];
57 }
58
59 - (instancetype) initWithAccount:(SOSAccount *)account quiet:(bool)quiet {
60 if (self = [super init]) {
61 self.account = account;
62 _quiet = quiet;
63 [self start];
64 }
65 return self;
66 }
67
68 - (void) start {
69 self.initialInCircle = [self.account.trust isInCircle:NULL];
70 self.initialTrusted = self.account.accountKeyIsTrusted;
71
72 if (self.initialInCircle) {
73 SOSAccountEnsureSyncChecking(self.account);
74 }
75
76 self.initialUnsyncedViews = (__bridge_transfer NSMutableSet<NSString*>*)SOSAccountCopyOutstandingViews(self.account);
77 self.initialKeyParameters = self.account.accountKeyDerivationParamters ? [NSData dataWithData:self.account.accountKeyDerivationParamters] : nil;
78
79 SOSPeerInfoRef mpi = self.account.peerInfo;
80 if (mpi) {
81 self.initialViews = CFBridgingRelease(SOSPeerInfoCopyEnabledViews(mpi));
82 [self.account ensureOctagonPeerKeys];
83 }
84 self.peersToRequestSync = nil;
85
86 if(!self.quiet) {
87 CFStringSetPerformWithDescription((__bridge CFSetRef) self.initialViews, ^(CFStringRef description) {
88 secnotice("acct-txn", "Starting as:%s v:%@", self.initialInCircle ? "member" : "non-member", description);
89 });
90 }
91 }
92
93 - (void) restart {
94 [self finish];
95 [self start];
96 }
97
98
99 - (void) finish {
100 static int do_account_state_at_zero = 0;
101 bool doCircleChanged = false;
102 bool doViewChanged = false;
103
104
105 CFErrorRef localError = NULL;
106 bool notifyEngines = false;
107
108 SOSPeerInfoRef mpi = self.account.peerInfo;
109
110 bool isInCircle = [self.account.trust isInCircle:NULL];
111
112 if (isInCircle && self.peersToRequestSync) {
113 SOSCCRequestSyncWithPeers((__bridge CFSetRef)(self.peersToRequestSync));
114 }
115 self.peersToRequestSync = nil;
116
117 if (isInCircle) {
118 SOSAccountEnsureSyncChecking(self.account);
119 } else {
120 SOSAccountCancelSyncChecking(self.account);
121 }
122
123 // If our identity changed our inital set should be everything.
124 if ([self.initialID isEqualToString: (__bridge NSString *)(SOSPeerInfoGetPeerID(mpi))]) {
125 self.initialUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSViewCopyViewSet(kViewSetAll);
126 }
127
128 NSSet<NSString*>* finalUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSAccountCopyOutstandingViews(self.account);
129 if (!NSIsEqualSafe(self.initialUnsyncedViews, finalUnsyncedViews)) {
130 if (SOSAccountHandleOutOfSyncUpdate(self.account,
131 (__bridge CFSetRef)(self.initialUnsyncedViews),
132 (__bridge CFSetRef)(finalUnsyncedViews))) {
133 notifyEngines = true;
134 }
135
136 secnotice("initial-sync", "Unsynced was: %@", [self.initialUnsyncedViews shortDescription]);
137 secnotice("initial-sync", "Unsynced is: %@", [finalUnsyncedViews shortDescription]);
138 }
139
140 if (self.account.engine_peer_state_needs_repair) {
141 // We currently only get here from a failed syncwithallpeers, so
142 // that will retry. If this logic changes, force a syncwithallpeers
143 if (!SOSAccountEnsurePeerRegistration(self.account, &localError)) {
144 secerror("Ensure peer registration while repairing failed: %@", localError);
145 }
146 CFReleaseNull(localError);
147
148 notifyEngines = true;
149 }
150
151 if(self.account.circle_rings_retirements_need_attention){
152 SOSAccountRecordRetiredPeersInCircle(self.account);
153
154 SOSAccountEnsureRecoveryRing(self.account);
155 SOSAccountEnsureInBackupRings(self.account);
156
157 CFErrorRef localError = NULL;
158 if(![self.account.circle_transport flushChanges:&localError]){
159 secerror("flush circle failed %@", localError);
160 }
161 CFReleaseSafe(localError);
162
163 notifyEngines = true;
164 }
165
166 if (notifyEngines) {
167 #if OCTAGON
168 if(!SecCKKSTestDisableSOS()) {
169 #endif
170 SOSAccountNotifyEngines(self.account);
171 #if OCTAGON
172 }
173 #endif
174 }
175
176 if(self.account.key_interests_need_updating){
177 SOSUpdateKeyInterest(self.account);
178 }
179
180 self.account.key_interests_need_updating = false;
181 self.account.circle_rings_retirements_need_attention = false;
182 self.account.engine_peer_state_needs_repair = false;
183
184 [self.account flattenToSaveBlock];
185
186 // Refresh isInCircle since we could have changed our mind
187 isInCircle = [self.account.trust isInCircle:NULL];
188
189 mpi = self.account.peerInfo;
190 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
191
192 if(!self.quiet) {
193 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
194 secnotice("acct-txn", "Finished as:%s v:%@", isInCircle ? "member" : "non-member", description);
195 });
196 }
197
198 // This is the logic to detect a new userKey:
199 bool userKeyChanged = !NSIsEqualSafe(self.initialKeyParameters, self.account.accountKeyDerivationParamters);
200
201 // This indicates we initiated a password change.
202 bool weInitiatedKeyChange = (self.initialTrusted &&
203 self.initialInCircle &&
204 userKeyChanged && isInCircle &&
205 self.account.accountKeyIsTrusted);
206
207 if(self.initialInCircle != isInCircle) {
208 doCircleChanged = true;
209 doViewChanged = true;
210 do_account_state_at_zero = 0;
211 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification && kSOSCCViewMembershipChangedNotification for circle/view change");
212 } else if(isInCircle && !NSIsEqualSafe(self.initialViews, (__bridge NSSet*)views)) {
213 doViewChanged = true;
214 do_account_state_at_zero = 0;
215 secnotice("secdNotify", "Notified clients of kSOSCCViewMembershipChangedNotification for viewchange(only)");
216 } else if(weInitiatedKeyChange) { // We consider this a circleChange so (PCS) can tell the userkey trust changed.
217 doCircleChanged = true;
218 do_account_state_at_zero = 0;
219 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification for userKey change");
220 }
221
222
223
224 // This is the case of we used to trust the key, were in the circle, the key changed, we don't trust it now.
225 bool fellOutOfTrust = (self.initialTrusted &&
226 self.initialInCircle &&
227 userKeyChanged &&
228 !self.account.accountKeyIsTrusted);
229
230 if(fellOutOfTrust) {
231 secnotice("userKeyTrust", "No longer trust user public key - prompting for password.");
232 notify_post(kPublicKeyNotAvailable);
233 doCircleChanged = true;
234 do_account_state_at_zero = 0;
235 }
236
237 bool userKeyTrustChangedToTrueAndNowInCircle = (!self.initialTrusted && self.account.accountKeyIsTrusted && isInCircle);
238
239 if(userKeyTrustChangedToTrueAndNowInCircle) {
240 secnotice("userKeyTrust", "UserKey is once again trusted and we're valid in circle.");
241 doCircleChanged = true;
242 doViewChanged = true;
243 }
244
245 if(doCircleChanged) notify_post(kSOSCCCircleChangedNotification);
246 if(doViewChanged) notify_post(kSOSCCViewMembershipChangedNotification);
247
248 if(do_account_state_at_zero <= 0) {
249 SOSAccountLogState(self.account);
250 SOSAccountLogViewState(self.account);
251 do_account_state_at_zero = ACCOUNT_STATE_INTERVAL;
252 }
253 do_account_state_at_zero--;
254
255 CFReleaseNull(views);
256 }
257
258 - (void) requestSyncWith: (NSString*) peerID {
259 if (self.peersToRequestSync == nil) {
260 self.peersToRequestSync = [NSMutableSet<NSString*> set];
261 }
262 [self.peersToRequestSync addObject: peerID];
263 }
264
265 - (void) requestSyncWithPeers: (NSSet<NSString*>*) peerList {
266 if (self.peersToRequestSync == nil) {
267 self.peersToRequestSync = [NSMutableSet<NSString*> set];
268 }
269 [self.peersToRequestSync unionSet: peerList];
270 }
271
272 @end
273
274
275
276
277 //
278 // MARK: Transactional
279 //
280
281 @implementation SOSAccount (Transaction)
282
283 __thread bool __hasAccountQueue = false;
284
285 + (void)performWhileHoldingAccountQueue:(void (^)(void))action
286 {
287 bool hadAccountQueue = __hasAccountQueue;
288 __hasAccountQueue = true;
289 action();
290 __hasAccountQueue = hadAccountQueue;
291 }
292
293 + (void)performOnQuietAccountQueue:(void (^)(void))action
294 {
295 SOSAccount* account = (__bridge SOSAccount*)GetSharedAccountRef();
296 [account performTransaction:true action:^(SOSAccountTransaction * _Nonnull txn) {
297 action();
298 }];
299 }
300
301 - (void) performTransaction_Locked: (void (^)(SOSAccountTransaction* txn)) action {
302 [self performTransaction_Locked:false action:action];
303 }
304
305 - (void) performTransaction_Locked:(bool)quiet action:(void (^)(SOSAccountTransaction* txn))action {
306 @autoreleasepool {
307 SOSAccountTransaction* transaction = [[SOSAccountTransaction new] initWithAccount:self quiet:quiet];
308 action(transaction);
309 [transaction finish];
310 }
311 }
312
313 - (void) performTransaction: (void (^)(SOSAccountTransaction* txn)) action {
314 [self performTransaction:false action:action];
315 }
316
317 - (void)performTransaction:(bool)quiet action:(void (^)(SOSAccountTransaction* txn))action {
318
319 if (__hasAccountQueue) {
320 // Be quiet; we're already in a transaction
321 [self performTransaction_Locked:true action:action];
322 }
323 else {
324 dispatch_sync(self.queue, ^{
325 __hasAccountQueue = true;
326 [self performTransaction_Locked:quiet action:action];
327 __hasAccountQueue = false;
328 });
329 }
330 }
331
332
333 @end