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