2 // SOSAccountTransaction.c
7 #include "SOSAccountTransaction.h"
9 #include <utilities/SecCFWrappers.h>
10 #import <utilities/SecNSAdditions.h>
11 #include <CoreFoundation/CoreFoundation.h>
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"
23 #include <keychain/ckks/CKKS.h>
24 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
26 // Account dumping state stuff
28 #define ACCOUNT_STATE_INTERVAL 200
31 @interface SOSAccountTransaction ()
33 @property BOOL initialInCircle;
34 @property NSSet<NSString*>* initialViews;
35 @property NSSet<NSString*>* initialUnsyncedViews;
36 @property NSString* initialID;
38 @property BOOL initialTrusted;
39 @property NSData* initialKeyParameters;
41 @property NSMutableSet<NSString*>* peersToRequestSync;
49 @implementation SOSAccountTransaction
51 + (instancetype) transactionWithAccount: (SOSAccount*) account {
52 return [[SOSAccountTransaction new] initWithAccount: account];
55 - (NSString*) description {
56 return [NSString stringWithFormat:@"<SOSAccountTransaction*@%p %ld>",
57 self, (unsigned long)(self.initialViews ? [self.initialViews count] : 0)];
60 - (instancetype) initWithAccount:(SOSAccount *)account {
61 if (self = [super init]) {
62 self.account = account;
69 self.initialInCircle = [self.account.trust isInCircle:NULL];
70 self.initialTrusted = self.account.accountKeyIsTrusted;
72 if (self.initialInCircle) {
73 SOSAccountEnsureSyncChecking(self.account);
76 self.initialUnsyncedViews = (__bridge_transfer NSMutableSet<NSString*>*)SOSAccountCopyOutstandingViews(self.account);
77 self.initialKeyParameters = self.account.accountKeyDerivationParamters ? [NSData dataWithData:self.account.accountKeyDerivationParamters] : nil;
79 SOSPeerInfoRef mpi = self.account.peerInfo;
80 self.initialViews = mpi ? (__bridge_transfer NSSet*) SOSPeerInfoCopyEnabledViews(mpi) : nil;
82 self.peersToRequestSync = nil;
84 CFStringSetPerformWithDescription((__bridge CFSetRef) self.initialViews, ^(CFStringRef description) {
85 secnotice("acct-txn", "Starting as:%s v:%@", self.initialInCircle ? "member" : "non-member", description);
96 static int do_account_state_at_zero = 0;
98 CFErrorRef localError = NULL;
99 bool notifyEngines = false;
101 SOSPeerInfoRef mpi = self.account.peerInfo;
103 bool isInCircle = [self.account.trust isInCircle:NULL];
105 if (isInCircle && self.peersToRequestSync) {
106 SOSCCRequestSyncWithPeers((__bridge CFSetRef)(self.peersToRequestSync));
108 self.peersToRequestSync = nil;
111 SOSAccountEnsureSyncChecking(self.account);
113 SOSAccountCancelSyncChecking(self.account);
116 // If our identity changed our inital set should be everything.
117 if ([self.initialID isEqualToString: (__bridge NSString *)(SOSPeerInfoGetPeerID(mpi))]) {
118 self.initialUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSViewCopyViewSet(kViewSetAll);
121 NSSet<NSString*>* finalUnsyncedViews = (__bridge_transfer NSSet<NSString*>*) SOSAccountCopyOutstandingViews(self.account);
122 if (!NSIsEqualSafe(self.initialUnsyncedViews, finalUnsyncedViews)) {
123 if (SOSAccountHandleOutOfSyncUpdate(self.account,
124 (__bridge CFSetRef)(self.initialUnsyncedViews),
125 (__bridge CFSetRef)(finalUnsyncedViews))) {
126 notifyEngines = true;
129 secnotice("initial-sync", "Unsynced was: %@", [self.initialUnsyncedViews shortDescription]);
130 secnotice("initial-sync", "Unsynced is: %@", [finalUnsyncedViews shortDescription]);
133 if (self.account.engine_peer_state_needs_repair) {
134 // We currently only get here from a failed syncwithallpeers, so
135 // that will retry. If this logic changes, force a syncwithallpeers
136 if (!SOSAccountEnsurePeerRegistration(self.account, &localError)) {
137 secerror("Ensure peer registration while repairing failed: %@", localError);
139 CFReleaseNull(localError);
141 notifyEngines = true;
144 if(self.account.circle_rings_retirements_need_attention){
145 SOSAccountRecordRetiredPeersInCircle(self.account);
147 SOSAccountEnsureRecoveryRing(self.account);
148 SOSAccountEnsureInBackupRings(self.account);
150 CFErrorRef localError = NULL;
151 if(![self.account.circle_transport flushChanges:&localError]){
152 secerror("flush circle failed %@", localError);
154 CFReleaseSafe(localError);
156 notifyEngines = true;
161 if(!SecCKKSTestDisableSOS()) {
163 SOSAccountNotifyEngines(self.account);
169 if(self.account.key_interests_need_updating){
170 SOSUpdateKeyInterest(self.account);
173 self.account.key_interests_need_updating = false;
174 self.account.circle_rings_retirements_need_attention = false;
175 self.account.engine_peer_state_needs_repair = false;
177 [self.account flattenToSaveBlock];
179 // Refresh isInCircle since we could have changed our mind
180 isInCircle = [self.account.trust isInCircle:NULL];
182 mpi = self.account.peerInfo;
183 CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
185 CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
186 secnotice("acct-txn", "Finished as:%s v:%@", isInCircle ? "member" : "non-member", description);
189 // This is the logic to detect a new userKey:
190 bool userKeyChanged = !NSIsEqualSafe(self.initialKeyParameters, self.account.accountKeyDerivationParamters);
192 // This indicates we initiated a password change.
193 bool weInitiatedKeyChange = (self.initialTrusted &&
194 self.initialInCircle &&
195 userKeyChanged && isInCircle &&
196 self.account.accountKeyIsTrusted);
198 if(self.initialInCircle != isInCircle) {
199 notify_post(kSOSCCCircleChangedNotification);
200 notify_post(kSOSCCViewMembershipChangedNotification);
201 do_account_state_at_zero = 0;
202 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification && kSOSCCViewMembershipChangedNotification for circle/view change");
203 } else if(isInCircle && !NSIsEqualSafe(self.initialViews, (__bridge NSSet*)views)) {
204 notify_post(kSOSCCViewMembershipChangedNotification);
205 do_account_state_at_zero = 0;
206 secnotice("secdNotify", "Notified clients of kSOSCCViewMembershipChangedNotification for viewchange(only)");
207 } else if(weInitiatedKeyChange) { // We consider this a circleChange so (PCS) can tell the userkey trust changed.
208 notify_post(kSOSCCCircleChangedNotification);
209 do_account_state_at_zero = 0;
210 secnotice("secdNotify", "Notified clients of kSOSCCCircleChangedNotification for userKey change");
213 // This is the case of we used to trust the key, were in the circle, the key changed, we don't trust it now.
214 bool fellOutOfTrust = (self.initialTrusted &&
215 self.initialInCircle &&
217 !self.account.accountKeyIsTrusted);
220 secnotice("userKeyTrust", "No longer trust user public key - prompting for password.");
221 notify_post(kPublicKeyNotAvailable);
222 do_account_state_at_zero = 0;
225 if(do_account_state_at_zero <= 0) {
226 SOSAccountLogState(self.account);
227 SOSAccountLogViewState(self.account);
228 do_account_state_at_zero = ACCOUNT_STATE_INTERVAL;
230 do_account_state_at_zero--;
232 CFReleaseNull(views);
235 - (void) requestSyncWith: (NSString*) peerID {
236 if (self.peersToRequestSync == nil) {
237 self.peersToRequestSync = [NSMutableSet<NSString*> set];
239 [self.peersToRequestSync addObject: peerID];
242 - (void) requestSyncWithPeers: (NSSet<NSString*>*) peerList {
243 if (self.peersToRequestSync == nil) {
244 self.peersToRequestSync = [NSMutableSet<NSString*> set];
246 [self.peersToRequestSync unionSet: peerList];
255 // MARK: Transactional
258 @implementation SOSAccount (Transaction)
260 __thread bool __hasAccountQueue = false;
262 + (void)performWhileHoldingAccountQueue:(void (^)(void))action
264 bool hadAccountQueue = __hasAccountQueue;
265 __hasAccountQueue = true;
267 __hasAccountQueue = hadAccountQueue;
270 + (void)performOnAccountQueue:(void (^)(void))action
272 SOSAccount* account = (__bridge SOSAccount*)GetSharedAccountRef();
273 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
278 - (void) performTransaction_Locked: (void (^)(SOSAccountTransaction* txn)) action {
279 SOSAccountTransaction* transaction = [SOSAccountTransaction transactionWithAccount:self];
281 [transaction finish];
284 - (void) performTransaction: (void (^)(SOSAccountTransaction* txn)) action {
285 if (__hasAccountQueue) {
286 [self performTransaction_Locked:action];
289 dispatch_sync(self.queue, ^{
290 __hasAccountQueue = true;
291 [self performTransaction_Locked:action];
292 __hasAccountQueue = false;