]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSKeychainView.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSKeychainView.m
1 /*
2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #import "CKKSKeychainView.h"
25
26
27
28 #if OCTAGON
29 #import "CloudKitDependencies.h"
30 #import <CloudKit/CloudKit.h>
31 #import <CloudKit/CloudKit_Private.h>
32 #endif
33
34 #import "CKKS.h"
35 #import "CKKSAPSReceiver.h"
36 #import "CKKSIncomingQueueEntry.h"
37 #import "CKKSOutgoingQueueEntry.h"
38 #import "CKKSCurrentKeyPointer.h"
39 #import "CKKSKey.h"
40 #import "CKKSMirrorEntry.h"
41 #import "CKKSZoneStateEntry.h"
42 #import "CKKSItemEncrypter.h"
43 #import "CKKSIncomingQueueOperation.h"
44 #import "CKKSNewTLKOperation.h"
45 #import "CKKSProcessReceivedKeysOperation.h"
46 #import "CKKSZone.h"
47 #import "CKKSFetchAllRecordZoneChangesOperation.h"
48 #import "CKKSHealKeyHierarchyOperation.h"
49 #import "CKKSReencryptOutgoingItemsOperation.h"
50 #import "CKKSScanLocalItemsOperation.h"
51 #import "CKKSSynchronizeOperation.h"
52 #import "CKKSRateLimiter.h"
53 #import "CKKSManifest.h"
54 #import "CKKSManifestLeafRecord.h"
55 #import "CKKSZoneChangeFetcher.h"
56 #import "CKKSAnalytics.h"
57 #import "keychain/ckks/CKKSDeviceStateEntry.h"
58 #import "keychain/ckks/CKKSNearFutureScheduler.h"
59 #import "keychain/ckks/CKKSCurrentItemPointer.h"
60 #import "keychain/ckks/CKKSUpdateCurrentItemPointerOperation.h"
61 #import "keychain/ckks/CKKSUpdateDeviceStateOperation.h"
62 #import "keychain/ckks/CKKSNotifier.h"
63 #import "keychain/ckks/CloudKitCategories.h"
64 #import "keychain/ckks/CKKSTLKShare.h"
65 #import "keychain/ckks/CKKSHealTLKSharesOperation.h"
66 #import "keychain/ckks/CKKSLocalSynchronizeOperation.h"
67 #import "keychain/categories/NSError+UsefulConstructors.h"
68
69 #include <utilities/SecCFWrappers.h>
70 #include <utilities/SecDb.h>
71 #include <securityd/SecDbItem.h>
72 #include <securityd/SecItemDb.h>
73 #include <securityd/SecItemSchema.h>
74 #include <securityd/SecItemServer.h>
75 #include <utilities/debugging.h>
76 #include <Security/SecItemPriv.h>
77 #include <Security/SecureObjectSync/SOSAccountTransaction.h>
78 #include <utilities/SecADWrapper.h>
79 #include <utilities/SecPLWrappers.h>
80 #include <os/transaction_private.h>
81
82 #if OCTAGON
83 @interface CKKSKeychainView()
84 @property bool keyStateFetchRequested;
85 @property bool keyStateFullRefetchRequested;
86 @property bool keyStateProcessRequested;
87 @property bool trustedPeersSetChanged;
88
89 @property bool keyStateCloudKitDeleteRequested;
90 @property NSHashTable<CKKSResultOperation*>* cloudkitDeleteZoneOperations;
91
92 @property bool keyStateLocalResetRequested;
93 @property NSHashTable<CKKSResultOperation*>* localResetOperations;
94
95 @property (atomic) NSString *activeTLK;
96
97 @property (readonly) Class<CKKSNotifier> notifierClass;
98
99 @property CKKSNearFutureScheduler* initializeScheduler;
100
101 // Slows down all outgoing queue operations
102 @property CKKSNearFutureScheduler* outgoingQueueOperationScheduler;
103
104 @property CKKSResultOperation* processIncomingQueueAfterNextUnlockOperation;
105 @property CKKSResultOperation* resultsOfNextIncomingQueueOperationOperation;
106
107 @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
108
109 @property id<CKKSPeerProvider> currentPeerProvider;
110
111 // An extra queue for semaphore-waiting-based NSOperations
112 @property NSOperationQueue* waitingQueue;
113
114 // Make these readwrite
115 @property (nonatomic, readwrite) CKKSSelves* currentSelfPeers;
116 @property (nonatomic, readwrite) NSError* currentSelfPeersError;
117 @property (nonatomic, readwrite) NSSet<id<CKKSPeer>>* currentTrustedPeers;
118 @property (nonatomic, readwrite) NSError* currentTrustedPeersError;
119 @end
120 #endif
121
122 @implementation CKKSKeychainView
123 #if OCTAGON
124
125 - (instancetype)initWithContainer: (CKContainer*) container
126 zoneName: (NSString*) zoneName
127 accountTracker:(CKKSCKAccountStateTracker*) accountTracker
128 lockStateTracker:(CKKSLockStateTracker*) lockStateTracker
129 reachabilityTracker:(CKKSReachabilityTracker *)reachabilityTracker
130 changeFetcher:(CKKSZoneChangeFetcher*)fetcher
131 savedTLKNotifier:(CKKSNearFutureScheduler*) savedTLKNotifier
132 peerProvider:(id<CKKSPeerProvider>)peerProvider
133 fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
134 fetchRecordsOperationClass: (Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
135 queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
136 modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
137 modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
138 apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass
139 notifierClass: (Class<CKKSNotifier>) notifierClass
140 {
141
142 if(self = [super initWithContainer:container
143 zoneName:zoneName
144 accountTracker:accountTracker
145 reachabilityTracker:reachabilityTracker
146 fetchRecordZoneChangesOperationClass:fetchRecordZoneChangesOperationClass
147 fetchRecordsOperationClass:fetchRecordsOperationClass
148 queryOperationClass:queryOperationClass
149 modifySubscriptionsOperationClass:modifySubscriptionsOperationClass
150 modifyRecordZonesOperationClass:modifyRecordZonesOperationClass
151 apsConnectionClass:apsConnectionClass]) {
152 __weak __typeof(self) weakSelf = self;
153
154 _loggedIn = [[CKKSCondition alloc] init];
155 _loggedOut = [[CKKSCondition alloc] init];
156 _accountStateKnown = [[CKKSCondition alloc] init];
157
158 _incomingQueueOperations = [NSHashTable weakObjectsHashTable];
159 _outgoingQueueOperations = [NSHashTable weakObjectsHashTable];
160 _cloudkitDeleteZoneOperations = [NSHashTable weakObjectsHashTable];
161 _localResetOperations = [NSHashTable weakObjectsHashTable];
162
163 _zoneChangeFetcher = fetcher;
164 [fetcher registerClient:self];
165
166 _notifierClass = notifierClass;
167 _notifyViewChangedScheduler = [[CKKSNearFutureScheduler alloc] initWithName:[NSString stringWithFormat: @"%@-notify-scheduler", self.zoneName]
168 initialDelay:250*NSEC_PER_MSEC
169 continuingDelay:1*NSEC_PER_SEC
170 keepProcessAlive:true
171 dependencyDescriptionCode:CKKSResultDescriptionPendingViewChangedScheduling
172 block:^{
173 __strong __typeof(self) strongSelf = weakSelf;
174 [strongSelf.notifierClass post:[NSString stringWithFormat:@"com.apple.security.view-change.%@", strongSelf.zoneName]];
175
176 // Ugly, but: the Manatee and Engram views need to send a fake 'PCS' view change.
177 // TODO: make this data-driven somehow
178 if([strongSelf.zoneName isEqualToString:@"Manatee"] ||
179 [strongSelf.zoneName isEqualToString:@"Engram"] ||
180 [strongSelf.zoneName isEqualToString:@"ApplePay"] ||
181 [strongSelf.zoneName isEqualToString:@"LimitedPeersAllowed"]) {
182 [strongSelf.notifierClass post:@"com.apple.security.view-change.PCS"];
183 }
184 }];
185
186 _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
187
188 _lockStateTracker = lockStateTracker;
189 _savedTLKNotifier = savedTLKNotifier;
190 _currentPeerProvider = peerProvider;
191 [_currentPeerProvider registerForPeerChangeUpdates:self];
192
193 _keyHierarchyConditions = [[NSMutableDictionary alloc] init];
194 [CKKSZoneKeyStateMap() enumerateKeysAndObjectsUsingBlock:^(CKKSZoneKeyState * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) {
195 [self.keyHierarchyConditions setObject: [[CKKSCondition alloc] init] forKey:key];
196 }];
197
198 // Use the keyHierarchyState setter to modify the zone key state map
199 self.keyHierarchyState = SecCKKSZoneKeyStateLoggedOut;
200
201 _keyHierarchyError = nil;
202 _keyHierarchyOperationGroup = nil;
203 _keyStateMachineOperation = nil;
204 _keyStateFetchRequested = false;
205 _keyStateProcessRequested = false;
206
207 _waitingQueue = [[NSOperationQueue alloc] init];
208 _waitingQueue.maxConcurrentOperationCount = 5;
209
210 _keyStateReadyDependency = [self createKeyStateReadyDependency: @"Key state has become ready for the first time." ckoperationGroup:[CKOperationGroup CKKSGroupWithName:@"initial-key-state-ready-scan"]];
211
212 _keyStateNonTransientDependency = [self createKeyStateNontransientDependency];
213
214 dispatch_time_t initializeDelay = SecCKKSReduceRateLimiting() ? NSEC_PER_MSEC * 600 : NSEC_PER_SEC * 30;
215 _initializeScheduler = [[CKKSNearFutureScheduler alloc] initWithName:[NSString stringWithFormat: @"%@-zone-initializer", self.zoneName]
216 initialDelay:0
217 continuingDelay:initializeDelay
218 keepProcessAlive:false
219 dependencyDescriptionCode:CKKSResultDescriptionPendingZoneInitializeScheduling
220 block:^{}];
221
222 dispatch_time_t initialOutgoingQueueDelay = SecCKKSReduceRateLimiting() ? NSEC_PER_MSEC * 200 : NSEC_PER_SEC * 1;
223 dispatch_time_t continuingOutgoingQueueDelay = SecCKKSReduceRateLimiting() ? NSEC_PER_MSEC * 200 : NSEC_PER_SEC * 30;
224 _outgoingQueueOperationScheduler = [[CKKSNearFutureScheduler alloc] initWithName:[NSString stringWithFormat: @"%@-outgoing-queue-scheduler", self.zoneName]
225 initialDelay:initialOutgoingQueueDelay
226 continuingDelay:continuingOutgoingQueueDelay
227 keepProcessAlive:false
228 dependencyDescriptionCode:CKKSResultDescriptionPendingOutgoingQueueScheduling
229 block:^{}];
230
231
232 dispatch_time_t initialKeyHierachyPokeDelay = SecCKKSReduceRateLimiting() ? NSEC_PER_MSEC * 100 : NSEC_PER_MSEC * 500;
233 dispatch_time_t continuingKeyHierachyPokeDelay = SecCKKSReduceRateLimiting() ? NSEC_PER_MSEC * 200 : NSEC_PER_SEC * 5;
234 _pokeKeyStateMachineScheduler = [[CKKSNearFutureScheduler alloc] initWithName:[NSString stringWithFormat: @"%@-reprocess-scheduler", self.zoneName]
235 initialDelay:initialKeyHierachyPokeDelay
236 continuingDelay:continuingKeyHierachyPokeDelay
237 keepProcessAlive:true
238 dependencyDescriptionCode:CKKSResultDescriptionPendingKeyHierachyPokeScheduling
239 block:^{
240 __strong __typeof(self) strongSelf = weakSelf;
241 [strongSelf dispatchSyncWithAccountKeys: ^bool{
242 __strong __typeof(weakSelf) strongBlockSelf = weakSelf;
243
244 [strongBlockSelf _onqueueAdvanceKeyStateMachineToState:nil withError:nil];
245 return true;
246 }];
247 }];
248 }
249 return self;
250 }
251
252 - (NSString*)description {
253 return [NSString stringWithFormat:@"<%@: %@ (%@)>", NSStringFromClass([self class]), self.zoneName, self.keyHierarchyState];
254 }
255
256 - (NSString*)debugDescription {
257 return [NSString stringWithFormat:@"<%@: %@ (%@) %p>", NSStringFromClass([self class]), self.zoneName, self.keyHierarchyState, self];
258 }
259
260 - (CKKSZoneKeyState*)keyHierarchyState {
261 return _keyHierarchyState;
262 }
263
264 - (void)setKeyHierarchyState:(CKKSZoneKeyState *)keyHierarchyState {
265 if((keyHierarchyState == nil && _keyHierarchyState == nil) || ([keyHierarchyState isEqualToString:_keyHierarchyState])) {
266 // No change, do nothing.
267 } else {
268 // Fixup the condition variables as part of setting this state
269 if(_keyHierarchyState) {
270 self.keyHierarchyConditions[_keyHierarchyState] = [[CKKSCondition alloc] init];
271 }
272
273 _keyHierarchyState = keyHierarchyState;
274
275 if(keyHierarchyState) {
276 [self.keyHierarchyConditions[keyHierarchyState] fulfill];
277 }
278 }
279 }
280
281 - (NSString *)lastActiveTLKUUID
282 {
283 return self.activeTLK;
284 }
285
286 - (void)_onqueueResetSetup:(CKKSZoneKeyState*)newState resetMessage:(NSString*)resetMessage ckoperationGroup:(CKOperationGroup*)group {
287 [super resetSetup];
288
289 self.keyHierarchyState = newState;
290 self.keyHierarchyError = nil;
291
292 [self.keyStateMachineOperation cancel];
293 self.keyStateMachineOperation = nil;
294
295 self.keyStateFetchRequested = false;
296 self.keyStateProcessRequested = false;
297
298 self.keyHierarchyOperationGroup = group;
299
300 NSOperation* oldKSRD = self.keyStateReadyDependency;
301 self.keyStateReadyDependency = [self createKeyStateReadyDependency:resetMessage ckoperationGroup:self.keyHierarchyOperationGroup];
302 if(oldKSRD) {
303 [oldKSRD addDependency:self.keyStateReadyDependency];
304 [self.waitingQueue addOperation:oldKSRD];
305 }
306
307 NSOperation* oldKSNTD = self.keyStateNonTransientDependency;
308 self.keyStateNonTransientDependency = [self createKeyStateNontransientDependency];
309 if(oldKSNTD) {
310 [oldKSNTD addDependency:self.keyStateNonTransientDependency];
311 [self.waitingQueue addOperation:oldKSNTD];
312 }
313 }
314
315 - (CKKSResultOperation*)createPendingInitializationOperation {
316
317 __weak __typeof(self) weakSelf = self;
318 CKKSResultOperation* initializationOp = [CKKSGroupOperation named:@"view-initialization" withBlockTakingSelf:^(CKKSGroupOperation * _Nonnull strongOp) {
319 __strong __typeof(weakSelf) strongSelf = weakSelf;
320
321 __block CKKSResultOperation* zoneCreationOperation = nil;
322 [strongSelf dispatchSync:^bool {
323 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: self.zoneName];
324 zoneCreationOperation = [self handleCKLogin:ckse.ckzonecreated zoneSubscribed:ckse.ckzonesubscribed];
325 return true;
326 }];
327
328 CKKSResultOperation* viewInitializationOperation = [CKKSResultOperation named:@"view-initialization" withBlockTakingSelf:^(CKKSResultOperation * _Nonnull strongInternalOp) {
329 __strong __typeof(weakSelf) strongSelf = weakSelf;
330 if(!strongSelf) {
331 ckkserror("ckks", strongSelf, "received callback for released object");
332 return;
333 }
334
335 [strongSelf dispatchSyncWithAccountKeys: ^bool {
336 ckksnotice("ckks", strongSelf, "Zone setup progress: %@ %d %@ %d %@",
337 [CKKSCKAccountStateTracker stringFromAccountStatus:strongSelf.accountStatus],
338 strongSelf.zoneCreated, strongSelf.zoneCreatedError, strongSelf.zoneSubscribed, strongSelf.zoneSubscribedError);
339
340 NSError* error = nil;
341 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: strongSelf.zoneName];
342 ckse.ckzonecreated = strongSelf.zoneCreated;
343 ckse.ckzonesubscribed = strongSelf.zoneSubscribed;
344
345 // Although, if the zone subscribed error says there's no zone, mark down that there's no zone
346 if(strongSelf.zoneSubscribedError &&
347 [strongSelf.zoneSubscribedError.domain isEqualToString:CKErrorDomain] && strongSelf.zoneSubscribedError.code == CKErrorPartialFailure) {
348 NSError* subscriptionError = strongSelf.zoneSubscribedError.userInfo[CKPartialErrorsByItemIDKey][strongSelf.zoneID];
349 if(subscriptionError && [subscriptionError.domain isEqualToString:CKErrorDomain] && subscriptionError.code == CKErrorZoneNotFound) {
350
351 ckkserror("ckks", strongSelf, "zone subscription error appears to say the zone doesn't exist, fixing status: %@", strongSelf.zoneSubscribedError);
352 ckse.ckzonecreated = false;
353 }
354 }
355
356 [ckse saveToDatabase: &error];
357 if(error) {
358 ckkserror("ckks", strongSelf, "couldn't save zone creation status for %@: %@", strongSelf.zoneName, error);
359 }
360
361 if(!strongSelf.zoneCreated || !strongSelf.zoneSubscribed) {
362 // Go into 'zonecreationfailed'
363 strongInternalOp.error = strongSelf.zoneCreatedError ? strongSelf.zoneCreatedError : strongSelf.zoneSubscribedError;
364 [strongSelf _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateZoneCreationFailed withError:strongInternalOp.error];
365
366 return true;
367 } else {
368 [strongSelf _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateInitialized withError:nil];
369 }
370
371 return true;
372 }];
373 }];
374
375 [viewInitializationOperation addDependency:zoneCreationOperation];
376 [strongOp runBeforeGroupFinished:viewInitializationOperation];
377 }];
378
379 return initializationOp;
380 }
381
382 - (void)_onqueuePerformKeyStateInitialized:(CKKSZoneStateEntry*)ckse {
383
384 // Check if we believe we've synced this zone before.
385 if(ckse.changeToken == nil) {
386 self.keyHierarchyOperationGroup = [CKOperationGroup CKKSGroupWithName:@"initial-setup"];
387
388 ckksnotice("ckks", self, "No existing change token; going to try to match local items with CloudKit ones.");
389
390 // Onboard this keychain: there's likely items in it that we haven't synced yet.
391 // But, there might be items in The Cloud that correspond to these items, with UUIDs that we don't know yet.
392 // First, fetch all remote items.
393 CKKSResultOperation* fetch = [self.zoneChangeFetcher requestSuccessfulFetch:CKKSFetchBecauseInitialStart];
394 fetch.name = @"initial-fetch";
395
396 // Next, try to process them (replacing local entries)
397 CKKSIncomingQueueOperation* initialProcess = [self processIncomingQueue:true after:fetch];
398 initialProcess.name = @"initial-process-incoming-queue";
399
400 // If all that succeeds, iterate through all keychain items and find the ones which need to be uploaded
401 self.initialScanOperation = [self scanLocalItems:@"initial-scan-operation"
402 ckoperationGroup:self.keyHierarchyOperationGroup
403 after:initialProcess];
404
405 } else {
406 // Likely a restart of securityd!
407
408 // First off, are there any in-flight queue entries? If so, put them back into New.
409 // If they're truly in-flight, we'll "conflict" with ourselves, but that should be fine.
410 NSError* error = nil;
411 [self _onqueueResetAllInflightOQE:&error];
412 if(error) {
413 ckkserror("ckks", self, "Couldn't reset in-flight OQEs, bad behavior ahead: %@", error);
414 }
415
416 // Are there any fixups to run first?
417 self.lastFixupOperation = [CKKSFixups fixup:ckse.lastFixup for:self];
418 if(self.lastFixupOperation) {
419 ckksnotice("ckksfixup", self, "We have a fixup to perform: %@", self.lastFixupOperation);
420 [self scheduleOperation:self.lastFixupOperation];
421 }
422
423 self.keyHierarchyOperationGroup = [CKOperationGroup CKKSGroupWithName:@"restart-setup"];
424
425 if ([CKKSManifest shouldSyncManifests]) {
426 self.egoManifest = [CKKSEgoManifest tryCurrentEgoManifestForZone:self.zoneName];
427 }
428
429 // If it's been more than 24 hours since the last fetch, fetch and process everything.
430 // Otherwise, just kick off the local queue processing.
431
432 NSDate* now = [NSDate date];
433 NSDateComponents* offset = [[NSDateComponents alloc] init];
434 [offset setHour:-24];
435 NSDate* deadline = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:now options:0];
436
437 NSOperation* initialProcess = nil;
438 if(ckse.lastFetchTime == nil || [ckse.lastFetchTime compare: deadline] == NSOrderedAscending) {
439 initialProcess = [self fetchAndProcessCKChanges:CKKSFetchBecauseSecuritydRestart after:self.lastFixupOperation];
440
441 // Also, kick off a scan local items: it'll find any out-of-sync issues in the local keychain
442 self.initialScanOperation = [self scanLocalItems:@"24-hr-scan-operation"
443 ckoperationGroup:self.keyHierarchyOperationGroup
444 after:initialProcess];
445 } else {
446 initialProcess = [self processIncomingQueue:false after:self.lastFixupOperation];
447 }
448
449 if([CKKSManifest shouldSyncManifests]) {
450 if (!self.egoManifest && !self.initialScanOperation) {
451 ckksnotice("ckksmanifest", self, "No ego manifest on restart; rescanning");
452 self.initialScanOperation = [self scanLocalItems:@"initial-scan-operation"
453 ckoperationGroup:self.keyHierarchyOperationGroup
454 after:initialProcess];
455 }
456 }
457
458 // Process outgoing queue after re-start
459 [self processOutgoingQueueAfter:self.lastFixupOperation ckoperationGroup:self.keyHierarchyOperationGroup];
460 }
461 }
462
463 - (bool)_onqueueResetLocalData: (NSError * __autoreleasing *) error {
464 dispatch_assert_queue(self.queue);
465
466 NSError* localerror = nil;
467 bool setError = false; // Ugly, but this is the only way to return the first error given
468
469 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: self.zoneName];
470 ckse.ckzonecreated = false;
471 ckse.ckzonesubscribed = false; // I'm actually not sure about this: can you be subscribed to a non-existent zone?
472 ckse.changeToken = NULL;
473 [ckse saveToDatabase: &localerror];
474 if(localerror) {
475 ckkserror("ckks", self, "couldn't reset zone status for %@: %@", self.zoneName, localerror);
476 if(error && !setError) {
477 *error = localerror; setError = true;
478 }
479 }
480
481 [CKKSMirrorEntry deleteAll:self.zoneID error: &localerror];
482 if(localerror) {
483 ckkserror("ckks", self, "couldn't delete all CKKSMirrorEntry: %@", localerror);
484 if(error && !setError) {
485 *error = localerror; setError = true;
486 }
487 }
488
489 [CKKSOutgoingQueueEntry deleteAll:self.zoneID error: &localerror];
490 if(localerror) {
491 ckkserror("ckks", self, "couldn't delete all CKKSOutgoingQueueEntry: %@", localerror);
492 if(error && !setError) {
493 *error = localerror; setError = true;
494 }
495 }
496
497 [CKKSIncomingQueueEntry deleteAll:self.zoneID error: &localerror];
498 if(localerror) {
499 ckkserror("ckks", self, "couldn't delete all CKKSIncomingQueueEntry: %@", localerror);
500 if(error && !setError) {
501 *error = localerror; setError = true;
502 }
503 }
504
505 [CKKSKey deleteAll:self.zoneID error: &localerror];
506 if(localerror) {
507 ckkserror("ckks", self, "couldn't delete all CKKSKey: %@", localerror);
508 if(error && !setError) {
509 *error = localerror; setError = true;
510 }
511 }
512
513 [CKKSTLKShare deleteAll:self.zoneID error: &localerror];
514 if(localerror) {
515 ckkserror("ckks", self, "couldn't delete all CKKSTLKShare: %@", localerror);
516 if(error && !setError) {
517 *error = localerror; setError = true;
518 }
519 }
520
521 [CKKSCurrentKeyPointer deleteAll:self.zoneID error: &localerror];
522 if(localerror) {
523 ckkserror("ckks", self, "couldn't delete all CKKSCurrentKeyPointer: %@", localerror);
524 if(error && !setError) {
525 *error = localerror; setError = true;
526 }
527 }
528
529 [CKKSCurrentItemPointer deleteAll:self.zoneID error: &localerror];
530 if(localerror) {
531 ckkserror("ckks", self, "couldn't delete all CKKSCurrentItemPointer: %@", localerror);
532 if(error && !setError) {
533 *error = localerror; setError = true;
534 }
535 }
536
537 [CKKSDeviceStateEntry deleteAll:self.zoneID error:&localerror];
538 if(localerror) {
539 ckkserror("ckks", self, "couldn't delete all CKKSDeviceStateEntry: %@", localerror);
540 if(error && !setError) {
541 *error = localerror; setError = true;
542 }
543 }
544
545 return (localerror == nil && !setError);
546 }
547
548 - (CKKSResultOperation*)createPendingResetLocalDataOperation {
549 @synchronized(self.localResetOperations) {
550 CKKSResultOperation* pendingResetLocalOperation = (CKKSResultOperation*) [self findFirstPendingOperation:self.localResetOperations];
551 if(!pendingResetLocalOperation) {
552 __weak __typeof(self) weakSelf = self;
553 pendingResetLocalOperation = [CKKSResultOperation named:@"reset-local" withBlockTakingSelf:^(CKKSResultOperation * _Nonnull strongOp) {
554 __strong __typeof(self) strongSelf = weakSelf;
555 __block NSError* error = nil;
556
557 [strongSelf dispatchSync: ^bool{
558 [strongSelf _onqueueResetLocalData: &error];
559 return true;
560 }];
561
562 strongOp.error = error;
563 }];
564 [pendingResetLocalOperation linearDependencies:self.localResetOperations];
565 }
566 return pendingResetLocalOperation;
567 }
568 }
569
570 - (CKKSResultOperation*)resetLocalData {
571 // Not overly thread-safe, but a single read is okay
572 CKKSAccountStatus accountStatus = self.accountStatus;
573 ckksnotice("ckksreset", self, "Requesting local data reset");
574
575 // If we're currently signed in, the reset operation will be handled by the CKKS key state machine, and a reset should end up in 'ready'
576 if(accountStatus == CKKSAccountStatusAvailable) {
577 __weak __typeof(self) weakSelf = self;
578 CKKSGroupOperation* resetOperationGroup = [CKKSGroupOperation named:@"local-reset" withBlockTakingSelf:^(CKKSGroupOperation *strongOp) {
579 __strong __typeof(self) strongSelf = weakSelf;
580
581 __block CKKSResultOperation* resetOperation = nil;
582
583 [strongSelf dispatchSyncWithAccountKeys:^bool {
584 strongSelf.keyStateLocalResetRequested = true;
585 resetOperation = [strongSelf createPendingResetLocalDataOperation];
586 [strongSelf _onqueueAdvanceKeyStateMachineToState:nil withError:nil];
587 return true;
588 }];
589
590 [strongOp dependOnBeforeGroupFinished:resetOperation];
591 }];
592 [self scheduleOperationWithoutDependencies:resetOperationGroup];
593
594 CKKSGroupOperation* viewReset = [CKKSGroupOperation named:@"local-data-reset" withBlockTakingSelf:^(CKKSGroupOperation *strongOp) {
595 __strong __typeof(weakSelf) strongSelf = weakSelf;
596 // Now that the local reset finished, wait for the key hierarchy state machine to churn
597 ckksnotice("ckksreset", strongSelf, "waiting for key hierarchy to become ready (after local reset)");
598 CKKSResultOperation* waitOp = [CKKSResultOperation named:@"waiting-for-local-reset" withBlock:^{}];
599 [waitOp timeout: 60*NSEC_PER_SEC];
600 [waitOp addNullableDependency:strongSelf.keyStateReadyDependency];
601
602 [strongOp runBeforeGroupFinished:waitOp];
603 }];
604 [viewReset addSuccessDependency:resetOperationGroup];
605
606 [self scheduleOperationWithoutDependencies:viewReset];
607 return viewReset;
608 } else {
609 // Since we're logged out, we must run the reset ourselves
610 __weak __typeof(self) weakSelf = self;
611 CKKSResultOperation* pendingResetLocalOperation = [CKKSResultOperation named:@"reset-local"
612 withBlockTakingSelf:^(CKKSResultOperation * _Nonnull strongOp) {
613 __strong __typeof(self) strongSelf = weakSelf;
614 __block NSError* error = nil;
615
616 [strongSelf dispatchSync: ^bool{
617 [strongSelf _onqueueResetLocalData: &error];
618 return true;
619 }];
620
621 strongOp.error = error;
622 }];
623 [self scheduleOperationWithoutDependencies:pendingResetLocalOperation];
624 return pendingResetLocalOperation;
625 }
626 }
627
628 - (CKKSResultOperation*)createPendingDeleteZoneOperation:(CKOperationGroup*)operationGroup {
629 @synchronized(self.cloudkitDeleteZoneOperations) {
630 CKKSResultOperation* pendingDeleteOperation = (CKKSResultOperation*) [self findFirstPendingOperation:self.cloudkitDeleteZoneOperations];
631 if(!pendingDeleteOperation) {
632 pendingDeleteOperation = [self deleteCloudKitZoneOperation:operationGroup];
633 [pendingDeleteOperation linearDependencies:self.cloudkitDeleteZoneOperations];
634 }
635 return pendingDeleteOperation;
636 }
637 }
638
639 - (CKKSResultOperation*)resetCloudKitZone:(CKOperationGroup*)operationGroup {
640 // Not overly thread-safe, but a single read is okay
641 if(self.accountStatus == CKKSAccountStatusAvailable) {
642 // Actually running the delete operation will be handled by the CKKS key state machine
643 ckksnotice("ckksreset", self, "Requesting reset of CK zone (logged in)");
644
645 __block CKKSResultOperation* deleteOperation = nil;
646 [self dispatchSyncWithAccountKeys:^bool {
647 self.keyStateCloudKitDeleteRequested = true;
648 deleteOperation = [self createPendingDeleteZoneOperation:operationGroup];
649 [self _onqueueAdvanceKeyStateMachineToState:nil withError:nil];
650 return true;
651 }];
652
653 __weak __typeof(self) weakSelf = self;
654 CKKSGroupOperation* viewReset = [CKKSGroupOperation named:[NSString stringWithFormat:@"cloudkit-view-reset-%@", self.zoneName]
655 withBlockTakingSelf:^(CKKSGroupOperation *strongOp) {
656 __strong __typeof(self) strongSelf = weakSelf;
657 // Now that the delete finished, wait for the key hierarchy state machine
658 ckksnotice("ckksreset", strongSelf, "waiting for key hierarchy to become ready (after cloudkit reset)");
659 CKKSResultOperation* waitOp = [CKKSResultOperation named:@"waiting-for-reset" withBlock:^{}];
660 [waitOp timeout: 60*NSEC_PER_SEC];
661 [waitOp addNullableDependency:strongSelf.keyStateReadyDependency];
662
663 [strongOp runBeforeGroupFinished:waitOp];
664 }];
665
666 [viewReset addDependency:deleteOperation];
667 [self.waitingQueue addOperation:viewReset];
668
669 return viewReset;
670 } else {
671 // Since we're logged out, we just need to run this ourselves
672 ckksnotice("ckksreset", self, "Requesting reset of CK zone (logged out)");
673 CKKSResultOperation* deleteOperation = [self createPendingDeleteZoneOperation:operationGroup];
674 [self scheduleOperationWithoutDependencies:deleteOperation];
675 return deleteOperation;
676 }
677 }
678
679 - (void)_onqueueKeyStateMachineRequestFetch {
680 dispatch_assert_queue(self.queue);
681
682 // We're going to set this flag, then nudge the key state machine.
683 // If it was idle, then it should launch a fetch. If there was an active process, this flag will stay high
684 // and the fetch will be launched later.
685
686 self.keyStateFetchRequested = true;
687 [self _onqueueAdvanceKeyStateMachineToState: nil withError: nil];
688 }
689
690 - (void)keyStateMachineRequestProcess {
691 // Since bools are atomic, we don't need to get on-queue here
692 // Just set the flag high and hope
693 self.keyStateProcessRequested = true;
694 [self.pokeKeyStateMachineScheduler trigger];
695 }
696
697 - (void)_onqueueKeyStateMachineRequestProcess {
698 dispatch_assert_queue(self.queue);
699
700 // Set the request flag, then nudge the key state machine.
701 // If it was idle, then it should launch a process. If there was an active process, this flag will stay high
702 // and the process will be launched later.
703
704 self.keyStateProcessRequested = true;
705 [self _onqueueAdvanceKeyStateMachineToState: nil withError: nil];
706 }
707
708 - (CKKSResultOperation*)createKeyStateReadyDependency:(NSString*)message ckoperationGroup:(CKOperationGroup*)group {
709 __weak __typeof(self) weakSelf = self;
710 CKKSResultOperation* keyStateReadyDependency = [CKKSResultOperation operationWithBlock:^{
711 __strong __typeof(self) strongSelf = weakSelf;
712 if(!strongSelf) {
713 return;
714 }
715 ckksnotice("ckkskey", strongSelf, "%@", message);
716
717 [strongSelf dispatchSync:^bool {
718 if(strongSelf.droppedItems) {
719 // While we weren't in 'ready', keychain modifications might have come in and were dropped on the floor. Find them!
720 ckksnotice("ckkskey", strongSelf, "Launching scan operation for missed items");
721 [self scanLocalItems:@"ready-again-scan" ckoperationGroup:group after:nil];
722 }
723 return true;
724 }];
725 }];
726 keyStateReadyDependency.name = [NSString stringWithFormat: @"%@-key-state-ready", self.zoneName];
727 keyStateReadyDependency.descriptionErrorCode = CKKSResultDescriptionPendingKeyReady;
728 return keyStateReadyDependency;
729 }
730
731 - (CKKSResultOperation*)createKeyStateNontransientDependency {
732 __weak __typeof(self) weakSelf = self;
733 return [CKKSResultOperation named:[NSString stringWithFormat: @"%@-key-state-nontransient", self.zoneName] withBlock:^{
734 __strong __typeof(self) strongSelf = weakSelf;
735 ckksnotice("ckkskey", strongSelf, "Key state is now non-transient");
736 }];
737 }
738
739 // The operations suggested by this state machine should call _onqueueAdvanceKeyStateMachineToState once they are complete.
740 // At no other time should keyHierarchyState be modified.
741
742 // Note that this function cannot rely on doing any database work; it might get rolled back, especially in an error state
743 - (void)_onqueueAdvanceKeyStateMachineToState: (CKKSZoneKeyState*) state withError: (NSError*) error {
744 dispatch_assert_queue(self.queue);
745 __weak __typeof(self) weakSelf = self;
746
747 // Resetting back to 'loggedout' takes all precedence.
748 if([state isEqual:SecCKKSZoneKeyStateLoggedOut]) {
749 ckksnotice("ckkskey", self, "Resetting the key hierarchy state machine back to '%@'", state);
750
751 [self _onqueueResetSetup:SecCKKSZoneKeyStateLoggedOut
752 resetMessage:@"Key state has become ready for the first time (after reset)."
753 ckoperationGroup:[CKOperationGroup CKKSGroupWithName:@"key-state-after-logout"]];
754
755 [self _onqueueHandleKeyStateNonTransientDependency];
756 return;
757 }
758
759 // Resetting back to 'initialized' also takes precedence
760 if([state isEqual:SecCKKSZoneKeyStateInitializing]) {
761 ckksnotice("ckkskey", self, "Resetting the key hierarchy state machine back to '%@'", state);
762
763 [self _onqueueResetSetup:SecCKKSZoneKeyStateInitializing
764 resetMessage:@"Key state has become ready for the first time (after re-initializing)."
765 ckoperationGroup:[CKOperationGroup CKKSGroupWithName:@"key-state-reset-to-initializing"]];
766
767 // Begin initialization, but rate-limit it
768 self.keyStateMachineOperation = [self createPendingInitializationOperation];
769 [self.keyStateMachineOperation addNullableDependency:self.initializeScheduler.operationDependency];
770 [self.initializeScheduler trigger];
771 [self scheduleOperation:self.keyStateMachineOperation];
772
773 [self _onqueueHandleKeyStateNonTransientDependency];
774 return;
775 }
776
777 // Cancels and error states take precedence
778 if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateError] ||
779 [self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateCancelled] ||
780 self.keyHierarchyError != nil) {
781 // Error state: nowhere to go. Early-exit.
782 ckkserror("ckkskey", self, "Asked to advance state machine from non-exit state %@ (to %@): %@", self.keyHierarchyState, state, self.keyHierarchyError);
783 return;
784 }
785
786 if([state isEqual: SecCKKSZoneKeyStateError]) {
787 // But wait! Is this a "we're locked" error?
788 if(error && [self.lockStateTracker isLockedError:error]) {
789 ckkserror("ckkskey", self, "advised of 'keychain locked' error, ignoring: coming from state (%@): %@", self.keyHierarchyState, error);
790 // After the next unlock, fake that we received the last zone transition
791 CKKSZoneKeyState* lastState = self.keyHierarchyState;
792 self.keyStateMachineOperation = [NSBlockOperation named:@"key-state-after-unlock" withBlock:^{
793 __strong __typeof(self) strongSelf = weakSelf;
794 if(!strongSelf) {
795 return;
796 }
797 [strongSelf dispatchSync:^bool{
798 [strongSelf _onqueueAdvanceKeyStateMachineToState:lastState withError:nil];
799 return true;
800 }];
801 }];
802 state = nil;
803
804 self.keyHierarchyState = SecCKKSZoneKeyStateWaitForUnlock;
805
806 [self.keyStateMachineOperation addNullableDependency:self.lockStateTracker.unlockDependency];
807 [self scheduleOperation:self.keyStateMachineOperation];
808
809 [self _onqueueHandleKeyStateNonTransientDependency];
810 return;
811
812 } else {
813 // Error state: record the error and exit early
814 ckkserror("ckkskey", self, "advised of error: coming from state (%@): %@", self.keyHierarchyState, error);
815
816 [[CKKSAnalytics logger] logUnrecoverableError:error
817 forEvent:CKKSEventStateError
818 inView:self
819 withAttributes:@{ @"previousKeyHierarchyState" : self.keyHierarchyState }];
820
821
822 self.keyHierarchyState = SecCKKSZoneKeyStateError;
823 self.keyHierarchyError = error;
824
825 [self _onqueueHandleKeyStateNonTransientDependency];
826 return;
827 }
828 }
829
830 if([state isEqual: SecCKKSZoneKeyStateCancelled]) {
831 ckkserror("ckkskey", self, "advised of cancel: coming from state (%@): %@", self.keyHierarchyState, error);
832 self.keyHierarchyState = SecCKKSZoneKeyStateCancelled;
833 self.keyHierarchyError = error;
834
835 // Cancel the key ready dependency. Strictly Speaking, this will cause errors down the line, but we're in a cancel state: those operations should be canceled anyway.
836 self.keyHierarchyOperationGroup = nil;
837 [self.keyStateReadyDependency cancel];
838 self.keyStateReadyDependency = nil;
839
840 [self.keyStateNonTransientDependency cancel];
841 self.keyStateNonTransientDependency = nil;
842 return;
843 }
844
845 // Now that the current or new state isn't an error or a cancel, proceed.
846 if(self.keyStateMachineOperation && ![self.keyStateMachineOperation isFinished]) {
847 if(state == nil) {
848 // we started this operation to move the state machine. Since you aren't asking for a state transition, and there's an active operation, no need to do anything
849 ckksnotice("ckkskey", self, "Not advancing state machine: waiting for %@", self.keyStateMachineOperation);
850 return;
851 }
852 }
853
854 if(state) {
855 ckksnotice("ckkskey", self, "Preparing to advance key hierarchy state machine from %@ to %@", self.keyHierarchyState, state);
856 self.keyStateMachineOperation = nil;
857 } else {
858 ckksnotice("ckkskey", self, "Key hierarchy state machine is being poked; currently %@", self.keyHierarchyState);
859 state = self.keyHierarchyState;
860 }
861
862 #if DEBUG
863 // During testing, keep the developer honest: this function should always have the self identities
864 if(self.currentSelfPeersError) {
865 NSAssert(self.currentSelfPeersError.code != CKKSNoPeersAvailable, @"Must have viable (or errored) self peers to advance key state");
866 }
867 #endif
868
869 // Do any of these state transitions below want to change which state we're in?
870 CKKSZoneKeyState* nextState = nil;
871 NSError* nextError = nil;
872
873 // Many of our decisions below will be based on what keys exist. Help them out.
874 CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] initForZone:self.zoneID];
875 NSError* localerror = nil;
876 NSArray<CKKSKey*>* localKeys = [CKKSKey localKeys:self.zoneID error:&localerror];
877 NSArray<CKKSKey*>* remoteKeys = [CKKSKey remoteKeys:self.zoneID error: &localerror];
878
879 // We also are checking for OutgoingQueueEntries in the reencrypt state; this is a sign that our key hierarchy is out of date.
880 NSInteger outdatedOQEs = [CKKSOutgoingQueueEntry countByState:SecCKKSStateReencrypt zone:self.zoneID error:&localerror];
881
882 SecADSetValueForScalarKey((__bridge CFStringRef) SecCKKSAggdViewKeyCount, [localKeys count]);
883
884 if(localerror) {
885 ckkserror("ckkskey", self, "couldn't fetch keys and OQEs from local database, entering error state: %@", localerror);
886 self.keyHierarchyState = SecCKKSZoneKeyStateError;
887 self.keyHierarchyError = localerror;
888 [self _onqueueHandleKeyStateNonTransientDependency];
889 return;
890 }
891
892 #if !defined(NDEBUG)
893 NSArray<CKKSKey*>* allKeys = [CKKSKey allKeys:self.zoneID error:&localerror];
894 ckksdebug("ckkskey", self, "All keys: %@", allKeys);
895 #endif
896
897 NSError* hierarchyError = nil;
898
899 if(self.keyStateCloudKitDeleteRequested || [state isEqualToString:SecCKKSZoneKeyStateResettingZone]) {
900 // CloudKit reset requests take precedence over all other state transitions
901 ckksnotice("ckkskey", self, "Deleting the CloudKit Zone");
902 CKKSGroupOperation* op = [[CKKSGroupOperation alloc] init];
903
904 CKKSResultOperation* deleteOp = [self createPendingDeleteZoneOperation:self.keyHierarchyOperationGroup];
905 [op runBeforeGroupFinished: deleteOp];
906
907 NSOperation* nextStateOp = [self operationToEnterState:SecCKKSZoneKeyStateResettingLocalData keyStateError:nil named:@"state-resetting-local"];
908 [nextStateOp addDependency:deleteOp];
909 [op runBeforeGroupFinished:nextStateOp];
910
911 self.keyStateMachineOperation = op;
912 self.keyStateCloudKitDeleteRequested = false;
913
914 // Also, pending operations should be cancelled
915 [self cancelPendingOperations];
916
917 } else if(self.keyStateLocalResetRequested || [state isEqualToString:SecCKKSZoneKeyStateResettingLocalData]) {
918 // Local reset requests take precedence over all other state transitions
919 ckksnotice("ckkskey", self, "Resetting local data");
920 CKKSGroupOperation* op = [[CKKSGroupOperation alloc] init];
921
922 CKKSResultOperation* resetOp = [self createPendingResetLocalDataOperation];
923 [op runBeforeGroupFinished: resetOp];
924
925 NSOperation* nextStateOp = [self operationToEnterState:SecCKKSZoneKeyStateInitializing keyStateError:nil named:@"state-resetting-initialize"];
926 [nextStateOp addDependency:resetOp];
927 [op runBeforeGroupFinished:nextStateOp];
928
929 self.keyStateMachineOperation = op;
930 self.keyStateLocalResetRequested = false;
931
932
933 } else if([state isEqualToString:SecCKKSZoneKeyStateZoneCreationFailed]) {
934 //Prepare to go back into initializing, as soon as the initializeScheduler is happy
935 self.keyStateMachineOperation = [self operationToEnterState:SecCKKSZoneKeyStateInitializing keyStateError:nil named:@"recover-from-cloudkit-failure"];
936 [self.keyStateMachineOperation addNullableDependency:self.initializeScheduler.operationDependency];
937 [self.initializeScheduler trigger];
938
939 } else if([state isEqualToString: SecCKKSZoneKeyStateReady]) {
940 if(self.keyStateProcessRequested || [remoteKeys count] > 0) {
941 // We've either received some remote keys from the last fetch, or someone has requested a reprocess.
942 ckksnotice("ckkskey", self, "Kicking off a key reprocess based on request:%d and remote key count %lu", self.keyStateProcessRequested, (unsigned long)[remoteKeys count]);
943 nextState = SecCKKSZoneKeyStateProcess;
944
945 } else if(self.keyStateFullRefetchRequested) {
946 // In ready, but someone has requested a full fetch. Kick it off.
947 ckksnotice("ckkskey", self, "Kicking off a full key refetch based on request:%d", self.keyStateFullRefetchRequested);
948 nextState = SecCKKSZoneKeyStateNeedFullRefetch;
949
950 } else if(self.keyStateFetchRequested) {
951 // In ready, but someone has requested a fetch. Kick it off.
952 ckksnotice("ckkskey", self, "Kicking off a key refetch based on request:%d", self.keyStateFetchRequested);
953 nextState = SecCKKSZoneKeyStateFetch; // Don't go to 'ready', go to 'initialized', since we want to fetch again
954 }
955 // TODO: kick off a key roll if one has been requested
956
957 if(!self.keyStateMachineOperation) {
958 // We think we're ready. Double check.
959 CKKSZoneKeyState* checkedstate = [self _onqueueEnsureKeyHierarchyHealth:keyset error:&hierarchyError];
960 if(![checkedstate isEqualToString:SecCKKSZoneKeyStateReady] || hierarchyError) {
961 // Things is bad. Kick off a heal to fix things up.
962 ckksnotice("ckkskey", self, "Thought we were ready, but the key hierarchy is %@: %@", checkedstate, hierarchyError);
963 nextState = checkedstate;
964 if([nextState isEqualToString:SecCKKSZoneKeyStateError]) {
965 nextError = hierarchyError;
966 }
967 }
968 }
969
970 } else if([state isEqualToString: SecCKKSZoneKeyStateInitialized]) {
971 // We're initialized and CloudKit is ready. See what needs done...
972
973 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state:self.zoneName];
974 [self _onqueuePerformKeyStateInitialized:ckse];
975
976 // We need to either:
977 // Wait for the fixup operation to occur
978 // Go into 'ready'
979 // Or start a key state fetch
980 if(self.lastFixupOperation && ![self.lastFixupOperation isFinished]) {
981 nextState = SecCKKSZoneKeyStateWaitForFixupOperation;
982 } else {
983 // Check if we have an existing key hierarchy in keyset
984 if(keyset.error && !([keyset.error.domain isEqual: @"securityd"] && keyset.error.code == errSecItemNotFound)) {
985 ckkserror("ckkskey", self, "Error examining existing key hierarchy: %@", error);
986 }
987
988 if(keyset.tlk && keyset.classA && keyset.classC && !keyset.error) {
989 // This is likely a restart of securityd, and we think we're ready. Double check.
990
991 CKKSZoneKeyState* checkedstate = [self _onqueueEnsureKeyHierarchyHealth:keyset error:&hierarchyError];
992 if([checkedstate isEqualToString:SecCKKSZoneKeyStateReady] && !hierarchyError) {
993 ckksnotice("ckkskey", self, "Already have existing key hierarchy for %@; using it.", self.zoneID.zoneName);
994 } else {
995 ckksnotice("ckkskey", self, "Initial scan shows key hierarchy is %@: %@", checkedstate, hierarchyError);
996 }
997 nextState = checkedstate;
998
999 } else {
1000 // We have no local key hierarchy. One might exist in CloudKit, or it might not.
1001 ckksnotice("ckkskey", self, "No existing key hierarchy for %@. Check if there's one in CloudKit...", self.zoneID.zoneName);
1002 nextState = SecCKKSZoneKeyStateFetch;
1003 }
1004 }
1005
1006 } else if([state isEqualToString:SecCKKSZoneKeyStateFetch]) {
1007 ckksnotice("ckkskey", self, "Starting a key hierarchy fetch");
1008 [self _onqueueKeyHierarchyFetch];
1009
1010 } else if([state isEqualToString: SecCKKSZoneKeyStateNeedFullRefetch]) {
1011 ckksnotice("ckkskey", self, "Starting a key hierarchy full refetch");
1012 [self _onqueueKeyHierarchyRefetch];
1013
1014 } else if([state isEqualToString:SecCKKSZoneKeyStateWaitForFixupOperation]) {
1015 // We should enter 'initialized' when the fixup operation completes
1016 ckksnotice("ckkskey", self, "Waiting for the fixup operation: %@", self.lastFixupOperation);
1017
1018 self.keyStateMachineOperation = [NSBlockOperation named:@"key-state-after-fixup" withBlock:^{
1019 __strong __typeof(self) strongSelf = weakSelf;
1020 [strongSelf dispatchSyncWithAccountKeys:^bool{
1021 ckksnotice("ckkskey", self, "Fixup operation complete! Restarting key hierarchy machinery");
1022 [strongSelf _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateInitialized withError:nil];
1023 return true;
1024 }];
1025 }];
1026 [self.keyStateMachineOperation addNullableDependency:self.lastFixupOperation];
1027
1028 } else if([state isEqualToString: SecCKKSZoneKeyStateFetchComplete]) {
1029 // We've just completed a fetch of everything. Are there any remote keys?
1030 if(remoteKeys.count > 0u) {
1031 // Process the keys we received.
1032 self.keyStateMachineOperation = [[CKKSProcessReceivedKeysOperation alloc] initWithCKKSKeychainView: self];
1033 } else if( (keyset.currentTLKPointer || keyset.currentClassAPointer || keyset.currentClassCPointer) &&
1034 !(keyset.tlk && keyset.classA && keyset.classC)) {
1035 // Huh. We appear to have current key pointers, but the keys themselves don't exist. That's weird.
1036 // Transfer to the "unhealthy" state to request a fix
1037 ckksnotice("ckkskey", self, "We appear to have current key pointers but no keys to match them. Moving to 'unhealthy'");
1038 nextState = SecCKKSZoneKeyStateUnhealthy;
1039 } else {
1040 // No remote keys, and the pointers look sane? Do we have an existing key hierarchy?
1041 CKKSZoneKeyState* checkedstate = [self _onqueueEnsureKeyHierarchyHealth:keyset error:&hierarchyError];
1042 if([checkedstate isEqualToString:SecCKKSZoneKeyStateReady] && !hierarchyError) {
1043 ckksnotice("ckkskey", self, "After fetch, everything looks good.");
1044 nextState = checkedstate;
1045 } else if(localKeys.count == 0 && remoteKeys.count == 0) {
1046 ckksnotice("ckkskey", self, "After fetch, we don't have any key hierarchy. Making a new one: %@", hierarchyError);
1047 self.keyStateMachineOperation = [[CKKSNewTLKOperation alloc] initWithCKKSKeychainView: self ckoperationGroup:self.keyHierarchyOperationGroup];
1048 } else {
1049 ckksnotice("ckkskey", self, "After fetch, we have a possibly unhealthy key hierarchy. Moving to %@: %@", checkedstate, hierarchyError);
1050 nextState = checkedstate;
1051 }
1052 }
1053
1054 } else if([state isEqualToString: SecCKKSZoneKeyStateWaitForTLK]) {
1055 // We're in a hold state: waiting for the TLK bytes to arrive.
1056
1057 if(self.keyStateProcessRequested) {
1058 // Someone has requsted a reprocess! Go to the correct state.
1059 ckksnotice("ckkskey", self, "Received a nudge that our TLK might be here! Reprocessing.");
1060 nextState = SecCKKSZoneKeyStateProcess;
1061
1062 } else if(self.trustedPeersSetChanged) {
1063 // Hmm, maybe this trust set change will cause us to recover this TLK (due to a previously-untrusted share becoming trusted). Worth a shot!
1064 ckksnotice("ckkskey", self, "Received a nudge that the trusted peers set might have changed! Reprocessing.");
1065 nextState = SecCKKSZoneKeyStateProcess;
1066 self.trustedPeersSetChanged = false;
1067
1068 } else {
1069 // Should we nuke this zone?
1070 if([self _onqueueOtherDevicesReportHavingTLKs:keyset]) {
1071 ckksnotice("ckkskey", self, "Other devices report having TLK(%@). Entering a waiting state", keyset.currentTLKPointer);
1072 } else {
1073 ckksnotice("ckkskey", self, "No other devices have TLK(%@). Beginning zone reset...", keyset.currentTLKPointer);
1074 nextState = SecCKKSZoneKeyStateResettingZone;
1075 }
1076 }
1077
1078 } else if([state isEqualToString: SecCKKSZoneKeyStateWaitForUnlock]) {
1079 ckksnotice("ckkskey", self, "Requested to enter waitforunlock");
1080 self.keyStateMachineOperation = [self operationToEnterState:SecCKKSZoneKeyStateInitialized keyStateError:nil named:@"key-state-after-unlock"];
1081 [self.keyStateMachineOperation addNullableDependency: self.lockStateTracker.unlockDependency];
1082
1083 } else if([state isEqualToString: SecCKKSZoneKeyStateReadyPendingUnlock]) {
1084 ckksnotice("ckkskey", self, "Believe we're ready, but rechecking after unlock");
1085 self.keyStateMachineOperation = [self operationToEnterState:SecCKKSZoneKeyStateInitialized keyStateError:nil named:@"key-state-after-unlock"];
1086 [self.keyStateMachineOperation addNullableDependency: self.lockStateTracker.unlockDependency];
1087
1088 } else if([state isEqualToString: SecCKKSZoneKeyStateBadCurrentPointers]) {
1089 // The current key pointers are broken, but we're not sure why.
1090 ckksnotice("ckkskey", self, "Our current key pointers are reported broken. Attempting a fix!");
1091 self.keyStateMachineOperation = [[CKKSHealKeyHierarchyOperation alloc] initWithCKKSKeychainView: self ckoperationGroup:self.keyHierarchyOperationGroup];
1092
1093 } else if([state isEqualToString: SecCKKSZoneKeyStateNewTLKsFailed]) {
1094 ckksnotice("ckkskey", self, "Creating new TLKs didn't work. Attempting to refetch!");
1095 [self _onqueueKeyHierarchyFetch];
1096
1097 } else if([state isEqualToString: SecCKKSZoneKeyStateHealTLKSharesFailed]) {
1098 ckksnotice("ckkskey", self, "Creating new TLK shares didn't work. Attempting to refetch!");
1099 [self _onqueueKeyHierarchyFetch];
1100
1101 } else if([state isEqualToString:SecCKKSZoneKeyStateUnhealthy]) {
1102 ckksnotice("ckkskey", self, "Looks like the key hierarchy is unhealthy. Launching fix.");
1103 self.keyStateMachineOperation = [[CKKSHealKeyHierarchyOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:self.keyHierarchyOperationGroup];
1104
1105 } else if([state isEqualToString:SecCKKSZoneKeyStateHealTLKShares]) {
1106 ckksnotice("ckksshare", self, "Key hierarchy is okay, but not shared appropriately. Launching fix.");
1107 self.keyStateMachineOperation = [[CKKSHealTLKSharesOperation alloc] initWithCKKSKeychainView:self
1108 ckoperationGroup:self.keyHierarchyOperationGroup];
1109
1110 } else if([state isEqualToString:SecCKKSZoneKeyStateProcess]) {
1111 ckksnotice("ckksshare", self, "Launching key state process");
1112 self.keyStateMachineOperation = [[CKKSProcessReceivedKeysOperation alloc] initWithCKKSKeychainView: self];
1113
1114 // Since we're starting a reprocess, this is answering all previous requests.
1115 self.keyStateProcessRequested = false;
1116
1117 } else {
1118 ckkserror("ckks", self, "asked to advance state machine to unknown state: %@", state);
1119 self.keyHierarchyState = state;
1120 [self _onqueueHandleKeyStateNonTransientDependency];
1121 return;
1122 }
1123
1124 // Handle the key state ready dependency
1125 // If we're in ready and not entering a non-ready state, we should activate the ready dependency. Otherwise, we should create it.
1126 if(([state isEqualToString:SecCKKSZoneKeyStateReady] || [state isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock]) &&
1127 (nextState == nil || [nextState isEqualToString:SecCKKSZoneKeyStateReady] || [nextState isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock])) {
1128
1129 // Ready enough!
1130 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastKeystateReady inView:self];
1131 if(self.keyStateReadyDependency) {
1132 [self scheduleOperation: self.keyStateReadyDependency];
1133 self.keyStateReadyDependency = nil;
1134 }
1135
1136 // If there are any OQEs waiting to be encrypted, launch an op to fix them
1137 if(outdatedOQEs > 0) {
1138 ckksnotice("ckksreencrypt", self, "Reencrypting outgoing items as the key hierarchy is ready");
1139 CKKSReencryptOutgoingItemsOperation* op = [[CKKSReencryptOutgoingItemsOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:self.keyHierarchyOperationGroup];
1140 [self scheduleOperation:op];
1141 }
1142 } else {
1143 // Not in ready: we need a key state ready dependency
1144 if(self.keyStateReadyDependency == nil || [self.keyStateReadyDependency isFinished]) {
1145 self.keyHierarchyOperationGroup = [CKOperationGroup CKKSGroupWithName:@"key-state-broken"];
1146 self.keyStateReadyDependency = [self createKeyStateReadyDependency:@"Key state has become ready again." ckoperationGroup:self.keyHierarchyOperationGroup];
1147 }
1148 }
1149
1150 NSAssert(!((self.keyStateMachineOperation != nil) &&
1151 (nextState != nil)),
1152 @"Should have a machine operation or a next state, not both");
1153
1154 // Start any operations, or log that we aren't
1155 if(self.keyStateMachineOperation) {
1156 [self scheduleOperation: self.keyStateMachineOperation];
1157 ckksnotice("ckkskey", self, "Now in key state: %@", state);
1158 self.keyHierarchyState = state;
1159
1160 } else if([state isEqualToString:SecCKKSZoneKeyStateError]) {
1161 ckksnotice("ckkskey", self, "Entering key state 'error'");
1162 self.keyHierarchyState = state;
1163
1164 } else if(nextState == nil) {
1165 ckksnotice("ckkskey", self, "Entering key state: %@", state);
1166 self.keyHierarchyState = state;
1167
1168 } else if(![state isEqualToString: nextState]) {
1169 ckksnotice("ckkskey", self, "Staying in state %@, but proceeding to %@ as soon as possible", self.keyHierarchyState, nextState);
1170 self.keyStateMachineOperation = [self operationToEnterState:nextState keyStateError:nextError named:@"next-key-state"];
1171 [self scheduleOperation: self.keyStateMachineOperation];
1172
1173 } else {
1174 // Nothing to do and not in a waiting state? This is likely a bug, but, hey: pretend to be in ready!
1175 if(!([state isEqualToString:SecCKKSZoneKeyStateReady] || [state isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock])) {
1176 ckkserror("ckkskey", self, "No action to take in state %@; BUG, but: maybe we're ready?", state);
1177 nextState = SecCKKSZoneKeyStateReady;
1178 self.keyStateMachineOperation = [self operationToEnterState:nextState keyStateError:nil named:@"next-key-state"];
1179 [self scheduleOperation: self.keyStateMachineOperation];
1180 }
1181 }
1182
1183 [self _onqueueHandleKeyStateNonTransientDependency];
1184 }
1185
1186 - (void)_onqueueHandleKeyStateNonTransientDependency {
1187 dispatch_assert_queue(self.queue);
1188
1189 if(CKKSKeyStateTransient(self.keyHierarchyState)) {
1190 if(self.keyStateNonTransientDependency == nil || [self.keyStateNonTransientDependency isFinished]) {
1191 self.keyStateNonTransientDependency = [self createKeyStateNontransientDependency];
1192 }
1193 } else {
1194 // Nontransient: go for it
1195 if(self.keyStateNonTransientDependency) {
1196 [self scheduleOperation: self.keyStateNonTransientDependency];
1197 self.keyStateNonTransientDependency = nil;
1198 }
1199 }
1200 }
1201
1202 - (NSOperation*)operationToEnterState:(CKKSZoneKeyState*)state keyStateError:(NSError* _Nullable)keyStateError named:(NSString*)name {
1203 __weak __typeof(self) weakSelf = self;
1204
1205 return [NSBlockOperation named:name withBlock:^{
1206 __strong __typeof(self) strongSelf = weakSelf;
1207 if(!strongSelf) {
1208 return;
1209 }
1210 [strongSelf dispatchSyncWithAccountKeys:^bool{
1211 [strongSelf _onqueueAdvanceKeyStateMachineToState:state withError:keyStateError];
1212 return true;
1213 }];
1214 }];
1215 }
1216
1217 - (bool)_onqueueOtherDevicesReportHavingTLKs:(CKKSCurrentKeySet*)keyset
1218 {
1219 dispatch_assert_queue(self.queue);
1220
1221 //Has there been any activity indicating that other trusted devices have keys in the past 45 days, or untrusted devices in the past 4?
1222 // (We chose 4 as devices attempt to upload their device state every 3 days. If a device is unceremoniously kicked out of circle, we normally won't immediately reset.)
1223 NSDate* now = [NSDate date];
1224 NSDateComponents* trustedOffset = [[NSDateComponents alloc] init];
1225 [trustedOffset setDay:-45];
1226 NSDate* trustedDeadline = [[NSCalendar currentCalendar] dateByAddingComponents:trustedOffset toDate:now options:0];
1227
1228 NSDateComponents* untrustedOffset = [[NSDateComponents alloc] init];
1229 [untrustedOffset setDay:-4];
1230 NSDate* untrustedDeadline = [[NSCalendar currentCalendar] dateByAddingComponents:untrustedOffset toDate:now options:0];
1231
1232 NSMutableSet<NSString*>* trustedPeerIDs = [NSMutableSet set];
1233 for(id<CKKSPeer> peer in self.currentTrustedPeers) {
1234 [trustedPeerIDs addObject:peer.peerID];
1235 }
1236
1237 NSError* localerror = nil;
1238
1239 NSArray<CKKSDeviceStateEntry*>* allDeviceStates = [CKKSDeviceStateEntry allInZone:self.zoneID error:&localerror];
1240 if(localerror) {
1241 ckkserror("ckkskey", self, "Error fetching device states: %@", localerror);
1242 localerror = nil;
1243 return true;
1244 }
1245 for(CKKSDeviceStateEntry* device in allDeviceStates) {
1246 if(device.octagonPeerID != nil) {
1247 ckksnotice("ckkskey", self, "An Octagon-capable device has been in this account; not resetting: (%@)", device);
1248 return true;
1249 }
1250
1251 if([trustedPeerIDs containsObject:device.circlePeerID] || [trustedPeerIDs containsObject:device.octagonPeerID]) {
1252 // Is this a recent DSE? If it's older than the deadline, skip it
1253 if([device.storedCKRecord.modificationDate compare:trustedDeadline] == NSOrderedAscending) {
1254 ckksnotice("ckkskey", self, "Trusted device state (%@) is too old; ignoring", device);
1255 continue;
1256 }
1257 } else {
1258 // Device is untrusted. How does it fare with the untrustedDeadline?
1259 if([device.storedCKRecord.modificationDate compare:untrustedDeadline] == NSOrderedAscending) {
1260 ckksnotice("ckkskey", self, "Device (%@) is not trusted and from too long ago; ignoring device state (%@)", device.circlePeerID, device);
1261 continue;
1262 } else {
1263 ckksnotice("ckkskey", self, "Device (%@) is not trusted, but very recent. Including in heuristic: %@", device.circlePeerID, device);
1264 }
1265 }
1266
1267 if([device.keyState isEqualToString:SecCKKSZoneKeyStateReady] ||
1268 [device.keyState isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock]) {
1269 ckksnotice("ckkskey", self, "Other device (%@) has keys; it should send them to us", device);
1270 return true;
1271 }
1272 }
1273
1274 NSArray<CKKSTLKShare*>* tlkShares = [CKKSTLKShare allForUUID:keyset.currentTLKPointer.currentKeyUUID
1275 zoneID:self.zoneID
1276 error:&localerror];
1277 if(localerror) {
1278 ckkserror("ckkskey", self, "Error fetching device states: %@", localerror);
1279 localerror = nil;
1280 return false;
1281 }
1282
1283 for(CKKSTLKShare* tlkShare in tlkShares) {
1284 if([trustedPeerIDs containsObject:tlkShare.senderPeerID] &&
1285 [tlkShare.storedCKRecord.modificationDate compare:trustedDeadline] == NSOrderedDescending) {
1286 ckksnotice("ckkskey", self, "Trusted TLK Share (%@) created recently; other devices have keys and should send them to us", tlkShare);
1287 return true;
1288 }
1289 }
1290
1291 // Okay, how about the untrusted deadline?
1292 for(CKKSTLKShare* tlkShare in tlkShares) {
1293 if([tlkShare.storedCKRecord.modificationDate compare:untrustedDeadline] == NSOrderedDescending) {
1294 ckksnotice("ckkskey", self, "Untrusted TLK Share (%@) created very recently; other devices might have keys and should rejoin the circle (and send them to us)", tlkShare);
1295 return true;
1296 }
1297 }
1298
1299 return false;
1300 }
1301
1302 // For this key, who doesn't yet have a valid CKKSTLKShare for it?
1303 // Note that we really want a record sharing the TLK to ourselves, so this function might return
1304 // a non-empty set even if all peers have the TLK: it wants us to make a record for ourself.
1305 - (NSSet<id<CKKSPeer>>*)_onqueueFindPeersMissingShare:(CKKSKey*)key error:(NSError* __autoreleasing*)error {
1306 dispatch_assert_queue(self.queue);
1307
1308 if(!key) {
1309 ckkserror("ckksshare", self, "Attempting to find missing shares for nil key");
1310 return [NSSet set];
1311 }
1312
1313 if(self.currentTrustedPeersError) {
1314 ckkserror("ckksshare", self, "Couldn't find missing shares because trusted peers aren't available: %@", self.currentTrustedPeersError);
1315 if(error) {
1316 *error = self.currentTrustedPeersError;
1317 }
1318 return [NSSet set];
1319 }
1320 if(self.currentSelfPeersError) {
1321 ckkserror("ckksshare", self, "Couldn't find missing shares because self peers aren't available: %@", self.currentSelfPeersError);
1322 if(error) {
1323 *error = self.currentSelfPeersError;
1324 }
1325 return [NSSet set];
1326 }
1327
1328 NSMutableSet<id<CKKSPeer>>* peersMissingShares = [NSMutableSet set];
1329
1330 NSMutableSet<NSString*>* trustedPeerIDs = [NSMutableSet set];
1331 for(id<CKKSPeer> peer in self.currentTrustedPeers) {
1332 [trustedPeerIDs addObject:peer.peerID];
1333 }
1334
1335 for(id<CKKSPeer> peer in self.currentTrustedPeers) {
1336 NSError* peerError = nil;
1337 // Find all the shares for this peer for this key
1338 NSArray<CKKSTLKShare*>* currentPeerShares = [CKKSTLKShare allFor:peer.peerID
1339 keyUUID:key.uuid
1340 zoneID:self.zoneID
1341 error:&peerError];
1342
1343 if(peerError) {
1344 ckkserror("ckksshare", self, "Couldn't load shares for peer %@: %@", peer, peerError);
1345 if(error) {
1346 *error = peerError;
1347 }
1348 return nil;
1349 }
1350
1351 // Determine if we think this peer has enough things shared to them
1352 bool alreadyShared = false;
1353 for(CKKSTLKShare* existingPeerShare in currentPeerShares) {
1354 // If an SOS Peer sent this share, is its signature still valid? Or did the signing key change?
1355 if([existingPeerShare.senderPeerID hasPrefix:CKKSSOSPeerPrefix]) {
1356 NSError* signatureError = nil;
1357 if(![existingPeerShare signatureVerifiesWithPeerSet:self.currentTrustedPeers error:&signatureError]) {
1358 ckksnotice("ckksshare", self, "Existing TLKShare's signature doesn't verify with current peer set: %@ %@", signatureError, existingPeerShare);
1359 continue;
1360 }
1361 }
1362
1363 if([existingPeerShare.tlkUUID isEqualToString: key.uuid] && [trustedPeerIDs containsObject:existingPeerShare.senderPeerID]) {
1364
1365 // Was this shared to us?
1366 if([peer.peerID isEqualToString: self.currentSelfPeers.currentSelf.peerID]) {
1367 // We only count this as 'found' if we did the sharing and it's to our current keys
1368 if([existingPeerShare.senderPeerID isEqualToString:self.currentSelfPeers.currentSelf.peerID] &&
1369 [existingPeerShare.receiver.publicEncryptionKey isEqual:self.currentSelfPeers.currentSelf.publicEncryptionKey]) {
1370 ckksnotice("ckksshare", self, "Local peer %@ is shared %@ via self: %@", peer, key, existingPeerShare);
1371 alreadyShared = true;
1372 break;
1373 } else {
1374 ckksnotice("ckksshare", self, "Local peer %@ is shared %@ via trusted %@, but that's not good enough", peer, key, existingPeerShare);
1375 }
1376
1377 } else {
1378 // Was this shared to the remote peer's current keys?
1379 if([peer.publicEncryptionKey isEqual: existingPeerShare.receiver.publicEncryptionKey]) {
1380 // Some other peer has a trusted share. Cool!
1381 ckksnotice("ckksshare", self, "Peer %@ is shared %@ via trusted %@", peer, key, existingPeerShare);
1382 alreadyShared = true;
1383 break;
1384 } else {
1385 ckksnotice("ckksshare", self, "Peer %@ has a share for %@, but to old keys: %@", peer, key, existingPeerShare);
1386 }
1387 }
1388 }
1389 }
1390
1391 if(!alreadyShared) {
1392 // Add this peer to our set, if it has an encryption key to receive the share
1393 if(peer.publicEncryptionKey) {
1394 [peersMissingShares addObject:peer];
1395 }
1396 }
1397 }
1398
1399 if(peersMissingShares.count > 0u) {
1400 // Log each and every one of the things
1401 ckksnotice("ckksshare", self, "Missing TLK shares for %lu peers: %@", (unsigned long)peersMissingShares.count, peersMissingShares);
1402 ckksnotice("ckksshare", self, "Self peers are (%@) %@", self.currentSelfPeersError ?: @"no error", self.currentSelfPeers);
1403 ckksnotice("ckksshare", self, "Trusted peers are (%@) %@", self.currentTrustedPeersError ?: @"no error", self.currentTrustedPeers);
1404 }
1405
1406 return peersMissingShares;
1407 }
1408
1409 - (NSSet<CKKSTLKShare*>*)_onqueueCreateMissingKeyShares:(CKKSKey*)key error:(NSError* __autoreleasing*)error {
1410 dispatch_assert_queue(self.queue);
1411
1412 if(self.currentTrustedPeersError) {
1413 ckkserror("ckksshare", self, "Couldn't create missing shares because trusted peers aren't available: %@", self.currentTrustedPeersError);
1414 if(error) {
1415 *error = self.currentTrustedPeersError;
1416 }
1417 return nil;
1418 }
1419 if(self.currentSelfPeersError) {
1420 ckkserror("ckksshare", self, "Couldn't create missing shares because self peers aren't available: %@", self.currentSelfPeersError);
1421 if(error) {
1422 *error = self.currentSelfPeersError;
1423 }
1424 return nil;
1425 }
1426
1427 NSSet<id<CKKSPeer>>* remainingPeers = [self _onqueueFindPeersMissingShare:key error:error];
1428 NSMutableSet<CKKSTLKShare*>* newShares = [NSMutableSet set];
1429
1430 if(!remainingPeers) {
1431 return nil;
1432 }
1433
1434 NSError* localerror = nil;
1435
1436 if(![key ensureKeyLoaded:error]) {
1437 return nil;
1438 }
1439
1440 for(id<CKKSPeer> peer in remainingPeers) {
1441 if(!peer.publicEncryptionKey) {
1442 ckksnotice("ckksshare", self, "No need to make TLK for %@; they don't have any encryption keys", peer);
1443 continue;
1444 }
1445
1446 // Create a share for this peer.
1447 ckksnotice("ckksshare", self, "Creating share of %@ as %@ for %@", key, self.currentSelfPeers.currentSelf, peer);
1448 CKKSTLKShare* newShare = [CKKSTLKShare share:key
1449 as:self.currentSelfPeers.currentSelf
1450 to:peer
1451 epoch:-1
1452 poisoned:0
1453 error:&localerror];
1454
1455 if(localerror) {
1456 ckkserror("ckksshare", self, "Couldn't create new share for %@: %@", peer, localerror);
1457 if(error) {
1458 *error = localerror;
1459 }
1460 return nil;
1461 }
1462
1463 [newShares addObject: newShare];
1464 }
1465
1466 return newShares;
1467 }
1468
1469 - (CKKSZoneKeyState*)_onqueueEnsureKeyHierarchyHealth:(CKKSCurrentKeySet*)set error:(NSError* __autoreleasing *)error {
1470 dispatch_assert_queue(self.queue);
1471
1472 // Check keyset
1473 if(!set.tlk || !set.classA || !set.classC) {
1474 ckkserror("ckkskey", self, "Error examining existing key hierarchy (missing at least one key): %@", set);
1475 if(error) {
1476 *error = set.error;
1477 }
1478 return SecCKKSZoneKeyStateUnhealthy;
1479 }
1480
1481 NSError* localerror = nil;
1482 bool probablyOkIfUnlocked = false;
1483
1484 // keychain being locked is not a fatal error here
1485 [set.tlk loadKeyMaterialFromKeychain:&localerror];
1486 if(localerror && !([localerror.domain isEqual: @"securityd"] && localerror.code == errSecInteractionNotAllowed)) {
1487 ckkserror("ckkskey", self, "Error loading TLK(%@): %@", set.tlk, localerror);
1488 if(error) {
1489 *error = localerror;
1490 }
1491 return SecCKKSZoneKeyStateUnhealthy;
1492 } else if(localerror) {
1493 ckkserror("ckkskey", self, "Soft error loading TLK(%@), maybe locked: %@", set.tlk, localerror);
1494 probablyOkIfUnlocked = true;
1495 }
1496 localerror = nil;
1497
1498 // keychain being locked is not a fatal error here
1499 [set.classA loadKeyMaterialFromKeychain:&localerror];
1500 if(localerror && !([localerror.domain isEqual: @"securityd"] && localerror.code == errSecInteractionNotAllowed)) {
1501 ckkserror("ckkskey", self, "Error loading classA key(%@): %@", set.classA, localerror);
1502 if(error) {
1503 *error = localerror;
1504 }
1505 return SecCKKSZoneKeyStateUnhealthy;
1506 } else if(localerror) {
1507 ckkserror("ckkskey", self, "Soft error loading classA key(%@), maybe locked: %@", set.classA, localerror);
1508 probablyOkIfUnlocked = true;
1509 }
1510 localerror = nil;
1511
1512 // keychain being locked is a fatal error here, since this is class C
1513 [set.classC loadKeyMaterialFromKeychain:&localerror];
1514 if(localerror) {
1515 ckkserror("ckkskey", self, "Error loading classC(%@): %@", set.classC, localerror);
1516 if(error) {
1517 *error = localerror;
1518 }
1519 return SecCKKSZoneKeyStateUnhealthy;
1520 }
1521
1522 // Check that the classA and classC keys point to the current TLK
1523 if(![set.classA.parentKeyUUID isEqualToString: set.tlk.uuid]) {
1524 localerror = [NSError errorWithDomain:CKKSServerExtensionErrorDomain
1525 code:CKKSServerUnexpectedSyncKeyInChain
1526 userInfo:@{
1527 NSLocalizedDescriptionKey: @"Current class A key does not wrap to current TLK",
1528 }];
1529 ckkserror("ckkskey", self, "Key hierarchy unhealthy: %@", localerror);
1530 if(error) {
1531 *error = localerror;
1532 }
1533 return SecCKKSZoneKeyStateUnhealthy;
1534 }
1535 if(![set.classC.parentKeyUUID isEqualToString: set.tlk.uuid]) {
1536 localerror = [NSError errorWithDomain:CKKSServerExtensionErrorDomain
1537 code:CKKSServerUnexpectedSyncKeyInChain
1538 userInfo:@{
1539 NSLocalizedDescriptionKey: @"Current class C key does not wrap to current TLK",
1540 }];
1541 ckkserror("ckkskey", self, "Key hierarchy unhealthy: %@", localerror);
1542 if(error) {
1543 *error = localerror;
1544 }
1545 return SecCKKSZoneKeyStateUnhealthy;
1546 }
1547
1548 self.activeTLK = [set.tlk uuid];
1549
1550 // Now that we're pretty sure we have the keys, are they shared appropriately?
1551 // Check that every trusted peer has at least one TLK share
1552 NSSet<id<CKKSPeer>>* missingShares = [self _onqueueFindPeersMissingShare:set.tlk error:&localerror];
1553 if(localerror && [self.lockStateTracker isLockedError: localerror]) {
1554 ckkserror("ckkskey", self, "Couldn't find missing TLK shares due to lock state: %@", localerror);
1555 probablyOkIfUnlocked = true;
1556 } else if([localerror.domain isEqualToString:CKKSErrorDomain] && localerror.code == CKKSNoPeersAvailable) {
1557 ckkserror("ckkskey", self, "Couldn't find missing TLK shares due to missing peers, likely due to lock state: %@", localerror);
1558 probablyOkIfUnlocked = true;
1559
1560 } else if(localerror) {
1561 if(error) {
1562 *error = localerror;
1563 }
1564 ckkserror("ckkskey", self, "Error finding missing TLK shares: %@", localerror);
1565 return SecCKKSZoneKeyStateError;
1566 }
1567
1568 if(!missingShares || missingShares.count != 0u) {
1569 localerror = [NSError errorWithDomain:CKKSErrorDomain code:CKKSMissingTLKShare
1570 description:[NSString stringWithFormat:@"Missing shares for %lu peers", (unsigned long)missingShares.count]];
1571 if(error) {
1572 *error = localerror;
1573 }
1574 return SecCKKSZoneKeyStateHealTLKShares;
1575 } else {
1576 ckksnotice("ckksshare", self, "TLK (%@) is shared correctly", set.tlk);
1577 }
1578
1579 // Got to the bottom? Cool! All keys are present and accounted for.
1580 return probablyOkIfUnlocked ? SecCKKSZoneKeyStateReadyPendingUnlock : SecCKKSZoneKeyStateReady;
1581 }
1582
1583 - (void)_onqueueKeyHierarchyFetch {
1584 dispatch_assert_queue(self.queue);
1585
1586 __weak __typeof(self) weakSelf = self;
1587 self.keyStateMachineOperation = [NSBlockOperation blockOperationWithBlock: ^{
1588 __strong __typeof(weakSelf) strongSelf = weakSelf;
1589 if(!strongSelf) {
1590 ckkserror("ckks", strongSelf, "received callback for released object");
1591 return;
1592 }
1593
1594 [strongSelf dispatchSyncWithAccountKeys: ^bool{
1595 [strongSelf _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateFetchComplete withError: nil];
1596 return true;
1597 }];
1598 }];
1599 self.keyStateMachineOperation.name = @"waiting-for-fetch";
1600
1601 NSOperation* fetchOp = [self.zoneChangeFetcher requestSuccessfulFetch: CKKSFetchBecauseKeyHierarchy];
1602 [self.keyStateMachineOperation addDependency: fetchOp];
1603
1604 self.keyStateFetchRequested = false;
1605 }
1606
1607 - (void)_onqueueKeyHierarchyRefetch {
1608 dispatch_assert_queue(self.queue);
1609
1610 __weak __typeof(self) weakSelf = self;
1611 self.keyStateMachineOperation = [NSBlockOperation blockOperationWithBlock: ^{
1612 __strong __typeof(weakSelf) strongSelf = weakSelf;
1613 if(!strongSelf) {
1614 ckkserror("ckks", strongSelf, "received callback for released object");
1615 return;
1616 }
1617
1618 [strongSelf dispatchSyncWithAccountKeys: ^bool{
1619 [strongSelf _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateFetchComplete withError: nil];
1620 return true;
1621 }];
1622 }];
1623 self.keyStateMachineOperation.name = @"waiting-for-refetch";
1624
1625 NSOperation* fetchOp = [self.zoneChangeFetcher requestSuccessfulFetchForManyReasons:[NSSet setWithObjects:CKKSFetchBecauseKeyHierarchy, CKKSFetchBecauseResync, nil]];
1626 [self.keyStateMachineOperation addDependency: fetchOp];
1627
1628 self.keyStateMachineRefetched = true;
1629 self.keyStateFullRefetchRequested = false;
1630 self.keyStateFetchRequested = false;
1631 }
1632
1633 - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn
1634 added: (SecDbItemRef) added
1635 deleted: (SecDbItemRef) deleted
1636 rateLimiter: (CKKSRateLimiter*) rateLimiter
1637 syncCallback: (SecBoolNSErrorCallback) syncCallback {
1638 if(!SecCKKSIsEnabled()) {
1639 ckksnotice("ckks", self, "Skipping handleKeychainEventDbConnection due to disabled CKKS");
1640 return;
1641 }
1642
1643 __block NSError* error = nil;
1644
1645 // Tombstones come in as item modifications or item adds. Handle modifications here.
1646 bool addedTombstone = added && SecDbItemIsTombstone(added);
1647 bool deletedTombstone = deleted && SecDbItemIsTombstone(deleted);
1648
1649 bool addedSync = added && SecDbItemIsSyncable(added);
1650 bool deletedSync = deleted && SecDbItemIsSyncable(deleted);
1651
1652 bool isAdd = ( added && !deleted) || (added && deleted && !addedTombstone && deletedTombstone) || (added && deleted && addedSync && !deletedSync);
1653 bool isDelete = (!added && deleted) || (added && deleted && addedTombstone && !deletedTombstone) || (added && deleted && !addedSync && deletedSync);
1654 bool isModify = ( added && deleted) && (!isAdd) && (!isDelete);
1655
1656 // On an update that changes an item's primary key, SecDb modifies the existing item, then adds a new tombstone to replace the old primary key.
1657 // Therefore, we might receive an added tombstone here with no deleted item to accompany it. This should be considered a deletion.
1658 if(addedTombstone && !deleted) {
1659 isAdd = false;
1660 isDelete = true;
1661 isModify = false;
1662
1663 // Passed to withItem: below
1664 deleted = added;
1665 }
1666
1667 // If neither item is syncable, don't proceed further in the syncing system
1668 bool proceed = addedSync || deletedSync;
1669
1670 if(!proceed) {
1671 ckksnotice("ckks", self, "skipping sync of non-sync item (%d, %d)", addedSync, deletedSync);
1672 return;
1673 }
1674
1675 // Only synchronize items which can transfer between devices
1676 NSString* protection = (__bridge NSString*)SecDbItemGetCachedValueWithName(added ? added : deleted, kSecAttrAccessible);
1677 if(! ([protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleWhenUnlocked] ||
1678 [protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAfterFirstUnlock] ||
1679 [protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAlwaysPrivate])) {
1680 ckksnotice("ckks", self, "skipping sync of device-bound(%@) item", protection);
1681 return;
1682 }
1683
1684 // Our caller gave us a database connection. We must get on the local queue to ensure atomicity
1685 // Note that we're at the mercy of the surrounding db transaction, so don't try to rollback here
1686 [self dispatchSyncWithConnection: dbconn block: ^bool {
1687 // Schedule a "view changed" notification
1688 [self.notifyViewChangedScheduler trigger];
1689
1690 if(self.accountStatus == CKKSAccountStatusNoAccount) {
1691 // No account; CKKS shouldn't attempt anything.
1692 self.droppedItems = true;
1693
1694 if(syncCallback) {
1695 // We're positively not logged into CloudKit, and therefore don't expect this item to be synced anytime particularly soon.
1696 [self callSyncCallbackWithErrorNoAccount: syncCallback];
1697 }
1698 return true;
1699 }
1700
1701 // Always record the callback, even if we can't encrypt the item right now. Maybe we'll get to it soon!
1702 if(syncCallback) {
1703 CFErrorRef cferror = NULL;
1704 NSString* uuid = (__bridge_transfer NSString*) CFRetain(SecDbItemGetValue(added, &v10itemuuid, &cferror));
1705 if(!cferror && uuid) {
1706 self.pendingSyncCallbacks[uuid] = syncCallback;
1707 }
1708 CFReleaseNull(cferror);
1709 }
1710
1711 CKKSOutgoingQueueEntry* oqe = nil;
1712 if (isAdd) {
1713 oqe = [CKKSOutgoingQueueEntry withItem: added action: SecCKKSActionAdd ckks:self error: &error];
1714 } else if(isDelete) {
1715 oqe = [CKKSOutgoingQueueEntry withItem: deleted action: SecCKKSActionDelete ckks:self error: &error];
1716 } else if(isModify) {
1717 oqe = [CKKSOutgoingQueueEntry withItem: added action: SecCKKSActionModify ckks:self error: &error];
1718 } else {
1719 ckkserror("ckks", self, "processKeychainEventItemAdded given garbage: %@ %@", added, deleted);
1720 return true;
1721 }
1722
1723 CKOperationGroup* operationGroup = [CKOperationGroup CKKSGroupWithName:@"keychain-api-use"];
1724
1725 if(error) {
1726 ckkserror("ckks", self, "Couldn't create outgoing queue entry: %@", error);
1727 self.droppedItems = true;
1728
1729 // If the problem is 'no UUID', launch a scan operation to find and fix it
1730 // We don't want to fix it up here, in the closing moments of a transaction
1731 if([error.domain isEqualToString:CKKSErrorDomain] && error.code == CKKSNoUUIDOnItem) {
1732 ckksnotice("ckks", self, "Launching scan operation to find UUID");
1733 [self scanLocalItems:@"uuid-find-scan" ckoperationGroup:operationGroup after:nil];
1734 }
1735
1736 // If the problem is 'couldn't load key', tell the key hierarchy state machine to fix it
1737 if([error.domain isEqualToString:CKKSErrorDomain] && error.code == errSecItemNotFound) {
1738 [self.pokeKeyStateMachineScheduler trigger];
1739 }
1740
1741 return true;
1742 }
1743
1744 if(rateLimiter) {
1745 NSDate* limit = nil;
1746 NSInteger value = [rateLimiter judge:oqe at:[NSDate date] limitTime:&limit];
1747 if(limit) {
1748 oqe.waitUntil = limit;
1749 SecPLLogRegisteredEvent(@"CKKSSyncing", @{ @"ratelimit" : @(value), @"accessgroup" : oqe.accessgroup});
1750 }
1751 }
1752
1753 [oqe saveToDatabaseWithConnection: dbconn error: &error];
1754 if(error) {
1755 ckkserror("ckks", self, "Couldn't save outgoing queue entry to database: %@", error);
1756 return true;
1757 } else {
1758 ckksnotice("ckks", self, "Saved %@ to outgoing queue", oqe);
1759 }
1760
1761 // This update supercedes all other local modifications to this item (_except_ those in-flight).
1762 // Delete all items in reencrypt or error.
1763 CKKSOutgoingQueueEntry* reencryptOQE = [CKKSOutgoingQueueEntry tryFromDatabase:oqe.uuid state:SecCKKSStateReencrypt zoneID:self.zoneID error:&error];
1764 if(error) {
1765 ckkserror("ckks", self, "Couldn't load reencrypt OQE sibling for %@: %@", oqe, error);
1766 }
1767 if(reencryptOQE) {
1768 [reencryptOQE deleteFromDatabase:&error];
1769 if(error) {
1770 ckkserror("ckks", self, "Couldn't delete reencrypt OQE sibling(%@) for %@: %@", reencryptOQE, oqe, error);
1771 }
1772 error = nil;
1773 }
1774
1775 CKKSOutgoingQueueEntry* errorOQE = [CKKSOutgoingQueueEntry tryFromDatabase:oqe.uuid state:SecCKKSStateError zoneID:self.zoneID error:&error];
1776 if(error) {
1777 ckkserror("ckks", self, "Couldn't load error OQE sibling for %@: %@", oqe, error);
1778 }
1779 if(errorOQE) {
1780 [errorOQE deleteFromDatabase:&error];
1781 if(error) {
1782 ckkserror("ckks", self, "Couldn't delete error OQE sibling(%@) for %@: %@", reencryptOQE, oqe, error);
1783 }
1784 }
1785
1786 [self processOutgoingQueue:operationGroup];
1787
1788 return true;
1789 }];
1790 }
1791
1792 -(void)setCurrentItemForAccessGroup:(NSData* _Nonnull)newItemPersistentRef
1793 hash:(NSData*)newItemSHA1
1794 accessGroup:(NSString*)accessGroup
1795 identifier:(NSString*)identifier
1796 replacing:(NSData* _Nullable)oldCurrentItemPersistentRef
1797 hash:(NSData*)oldItemSHA1
1798 complete:(void (^) (NSError* operror)) complete
1799 {
1800 if(accessGroup == nil || identifier == nil) {
1801 NSError* error = [NSError errorWithDomain:CKKSErrorDomain
1802 code:errSecParam
1803 description:@"No access group or identifier given"];
1804 ckkserror("ckkscurrent", self, "Cancelling request: %@", error);
1805 complete(error);
1806 return;
1807 }
1808
1809 // Not being in a CloudKit account is an automatic failure.
1810 // But, wait a good long while for the CloudKit account state to be known (in the case of daemon startup)
1811 [self.accountStateKnown wait:(SecCKKSTestsEnabled() ? 1*NSEC_PER_SEC : 30*NSEC_PER_SEC)];
1812
1813 if(self.accountStatus != CKKSAccountStatusAvailable) {
1814 NSError* error = [NSError errorWithDomain:CKKSErrorDomain
1815 code:CKKSNotLoggedIn
1816 description:@"User is not signed into iCloud."];
1817 ckksnotice("ckkscurrent", self, "Rejecting current item pointer set since we don't have an iCloud account.");
1818 complete(error);
1819 return;
1820 }
1821
1822 ckksnotice("ckkscurrent", self, "Starting change current pointer operation for %@-%@", accessGroup, identifier);
1823 CKKSUpdateCurrentItemPointerOperation* ucipo = [[CKKSUpdateCurrentItemPointerOperation alloc] initWithCKKSKeychainView:self
1824 newItem:newItemPersistentRef
1825 hash:newItemSHA1
1826 accessGroup:accessGroup
1827 identifier:identifier
1828 replacing:oldCurrentItemPersistentRef
1829 hash:oldItemSHA1
1830 ckoperationGroup:[CKOperationGroup CKKSGroupWithName:@"currentitem-api"]];
1831
1832 __weak __typeof(self) weakSelf = self;
1833 CKKSResultOperation* returnCallback = [CKKSResultOperation operationWithBlock:^{
1834 __strong __typeof(self) strongSelf = weakSelf;
1835
1836 if(ucipo.error) {
1837 ckkserror("ckkscurrent", strongSelf, "Failed setting a current item pointer for %@ with %@", ucipo.currentPointerIdentifier, ucipo.error);
1838 } else {
1839 ckksnotice("ckkscurrent", strongSelf, "Finished setting a current item pointer for %@", ucipo.currentPointerIdentifier);
1840 }
1841 complete(ucipo.error);
1842 }];
1843 returnCallback.name = @"setCurrentItem-return-callback";
1844 [returnCallback addDependency: ucipo];
1845 [self scheduleOperation: returnCallback];
1846
1847 // Now, schedule ucipo. It modifies the CloudKit zone, so it should insert itself into the list of OutgoingQueueOperations.
1848 // Then, we won't have simultaneous zone-modifying operations.
1849 [ucipo linearDependencies:self.outgoingQueueOperations];
1850
1851 // If this operation hasn't started within 60 seconds, cancel it and return a "timed out" error.
1852 [ucipo timeout:60*NSEC_PER_SEC];
1853
1854 [self scheduleOperation:ucipo];
1855 return;
1856 }
1857
1858 -(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
1859 identifier:(NSString*)identifier
1860 fetchCloudValue:(bool)fetchCloudValue
1861 complete:(void (^) (NSString* uuid, NSError* operror)) complete
1862 {
1863 if(accessGroup == nil || identifier == nil) {
1864 ckksnotice("ckkscurrent", self, "Rejecting current item pointer get since no access group(%@) or identifier(%@) given", accessGroup, identifier);
1865 complete(NULL, [NSError errorWithDomain:CKKSErrorDomain
1866 code:errSecParam
1867 description:@"No access group or identifier given"]);
1868 return;
1869 }
1870
1871 // Not being in a CloudKit account is an automatic failure.
1872 // But, wait a good long while for the CloudKit account state to be known (in the case of daemon startup)
1873 [self.accountStateKnown wait:(SecCKKSTestsEnabled() ? 1*NSEC_PER_SEC : 30*NSEC_PER_SEC)];
1874
1875 if(self.accountStatus != CKKSAccountStatusAvailable) {
1876 ckksnotice("ckkscurrent", self, "Rejecting current item pointer get since we don't have an iCloud account.");
1877 complete(NULL, [NSError errorWithDomain:CKKSErrorDomain
1878 code:CKKSNotLoggedIn
1879 description:@"User is not signed into iCloud."]);
1880 return;
1881 }
1882
1883 CKKSResultOperation* fetchAndProcess = nil;
1884 if(fetchCloudValue) {
1885 fetchAndProcess = [self fetchAndProcessCKChanges:CKKSFetchBecauseCurrentItemFetchRequest];
1886 }
1887
1888 __weak __typeof(self) weakSelf = self;
1889 CKKSResultOperation* getCurrentItem = [CKKSResultOperation named:@"get-current-item-pointer" withBlock:^{
1890 if(fetchAndProcess.error) {
1891 ckksnotice("ckkscurrent", self, "Rejecting current item pointer get since fetch failed: %@", fetchAndProcess.error);
1892 complete(NULL, fetchAndProcess.error);
1893 return;
1894 }
1895
1896 __strong __typeof(self) strongSelf = weakSelf;
1897
1898 [strongSelf dispatchSync: ^bool {
1899 NSError* error = nil;
1900 NSString* currentIdentifier = [NSString stringWithFormat:@"%@-%@", accessGroup, identifier];
1901
1902 CKKSCurrentItemPointer* cip = [CKKSCurrentItemPointer fromDatabase:currentIdentifier
1903 state:SecCKKSProcessedStateLocal
1904 zoneID:strongSelf.zoneID
1905 error:&error];
1906 if(!cip || error) {
1907 ckkserror("ckkscurrent", strongSelf, "No current item pointer for %@", currentIdentifier);
1908 complete(nil, error);
1909 return false;
1910 }
1911
1912 if(!cip.currentItemUUID) {
1913 ckkserror("ckkscurrent", strongSelf, "Current item pointer is empty %@", cip);
1914 complete(nil, [NSError errorWithDomain:CKKSErrorDomain
1915 code:errSecInternalError
1916 description:@"Current item pointer is empty"]);
1917 return false;
1918 }
1919
1920 ckksinfo("ckkscurrent", strongSelf, "Retrieved current item pointer: %@", cip);
1921 complete(cip.currentItemUUID, NULL);
1922 return true;
1923 }];
1924 }];
1925
1926 [getCurrentItem addNullableDependency:fetchAndProcess];
1927 [self scheduleOperation: getCurrentItem];
1928 }
1929
1930 - (CKKSKey*) keyForItem: (SecDbItemRef) item error: (NSError * __autoreleasing *) error {
1931 CKKSKeyClass* class = nil;
1932
1933 NSString* protection = (__bridge NSString*)SecDbItemGetCachedValueWithName(item, kSecAttrAccessible);
1934 if([protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleWhenUnlocked]) {
1935 class = SecCKKSKeyClassA;
1936 } else if([protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAlwaysPrivate] ||
1937 [protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAfterFirstUnlock]) {
1938 class = SecCKKSKeyClassC;
1939 } else {
1940 NSError* localError = [NSError errorWithDomain:CKKSErrorDomain
1941 code:CKKSInvalidKeyClass
1942 description:[NSString stringWithFormat:@"can't pick key class for protection %@", protection]];
1943 ckkserror("ckks", self, "can't pick key class: %@ %@", localError, item);
1944 if(error) {
1945 *error = localError;
1946 }
1947
1948 return nil;
1949 }
1950
1951 NSError* currentKeyError = nil;
1952 CKKSKey* key = [CKKSKey currentKeyForClass: class zoneID:self.zoneID error:&currentKeyError];
1953 if(!key || currentKeyError) {
1954 ckkserror("ckks", self, "Couldn't find current key for %@: %@", class, currentKeyError);
1955
1956 if(error) {
1957 *error = currentKeyError;
1958 }
1959 return nil;
1960 }
1961
1962 // and make sure it's unwrapped.
1963 NSError* loadedError = nil;
1964 if(![key ensureKeyLoaded:&loadedError]) {
1965 ckkserror("ckks", self, "Couldn't load key(%@): %@", key, loadedError);
1966 if(error) {
1967 *error = loadedError;
1968 }
1969 return nil;
1970 }
1971
1972 return key;
1973 }
1974
1975 // Use the following method to find the first pending operation in a weak collection
1976 - (NSOperation*)findFirstPendingOperation: (NSHashTable*) table {
1977 return [self findFirstPendingOperation:table ofClass:nil];
1978 }
1979
1980 // Use the following method to find the first pending operation in a weak collection
1981 - (NSOperation*)findFirstPendingOperation: (NSHashTable*) table ofClass:(Class)class {
1982 @synchronized(table) {
1983 for(NSOperation* op in table) {
1984 if(op != nil && [op isPending] && (class == nil || [op isKindOfClass: class])) {
1985 return op;
1986 }
1987 }
1988 return nil;
1989 }
1990 }
1991
1992 // Use the following method to count the pending operations in a weak collection
1993 - (int64_t)countPendingOperations: (NSHashTable*) table {
1994 @synchronized(table) {
1995 int count = 0;
1996 for(NSOperation* op in table) {
1997 if(op != nil && !([op isExecuting] || [op isFinished])) {
1998 count++;
1999 }
2000 }
2001 return count;
2002 }
2003 }
2004
2005 - (CKKSOutgoingQueueOperation*)processOutgoingQueue:(CKOperationGroup*)ckoperationGroup {
2006 return [self processOutgoingQueueAfter:nil ckoperationGroup:ckoperationGroup];
2007 }
2008
2009 - (CKKSOutgoingQueueOperation*)processOutgoingQueueAfter:(CKKSResultOperation*)after ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
2010 CKKSOutgoingQueueOperation* outgoingop =
2011 (CKKSOutgoingQueueOperation*) [self findFirstPendingOperation:self.outgoingQueueOperations
2012 ofClass:[CKKSOutgoingQueueOperation class]];
2013 if(outgoingop) {
2014 if(after) {
2015 [outgoingop addDependency: after];
2016 }
2017 if([outgoingop isPending]) {
2018 if(!outgoingop.ckoperationGroup && ckoperationGroup) {
2019 outgoingop.ckoperationGroup = ckoperationGroup;
2020 } else if(ckoperationGroup) {
2021 ckkserror("ckks", self, "Throwing away CKOperationGroup(%@) in favor of (%@)", ckoperationGroup.name, outgoingop.ckoperationGroup.name);
2022 }
2023
2024 // Will log any pending dependencies as well
2025 ckksnotice("ckksoutgoing", self, "Returning existing %@", outgoingop);
2026
2027 // Shouldn't be necessary, but can't hurt
2028 [self.outgoingQueueOperationScheduler trigger];
2029 return outgoingop;
2030 }
2031 }
2032
2033 CKKSOutgoingQueueOperation* op = [[CKKSOutgoingQueueOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:ckoperationGroup];
2034 op.name = @"outgoing-queue-operation";
2035 [op addNullableDependency:after];
2036 [op addNullableDependency:self.outgoingQueueOperationScheduler.operationDependency];
2037 [self.outgoingQueueOperationScheduler trigger];
2038
2039 [self scheduleOperation: op];
2040 ckksnotice("ckksoutgoing", self, "Scheduled %@", op);
2041 return op;
2042 }
2043
2044 - (void)processIncomingQueueAfterNextUnlock {
2045 // Thread races aren't so important here; we might end up with two or three copies of this operation, but that's okay.
2046 if(![self.processIncomingQueueAfterNextUnlockOperation isPending]) {
2047 __weak __typeof(self) weakSelf = self;
2048
2049 CKKSResultOperation* restartIncomingQueueOperation = [CKKSResultOperation operationWithBlock:^{
2050 __strong __typeof(self) strongSelf = weakSelf;
2051 // This IQO shouldn't error if the keybag has locked again. It will simply try again later.
2052 [strongSelf processIncomingQueue:false];
2053 }];
2054
2055 restartIncomingQueueOperation.name = @"reprocess-incoming-queue-after-unlock";
2056 self.processIncomingQueueAfterNextUnlockOperation = restartIncomingQueueOperation;
2057
2058 [restartIncomingQueueOperation addNullableDependency:self.lockStateTracker.unlockDependency];
2059 [self scheduleOperation: restartIncomingQueueOperation];
2060 }
2061 }
2062
2063 - (CKKSResultOperation*)resultsOfNextProcessIncomingQueueOperation {
2064 if(self.resultsOfNextIncomingQueueOperationOperation && [self.resultsOfNextIncomingQueueOperationOperation isPending]) {
2065 return self.resultsOfNextIncomingQueueOperationOperation;
2066 }
2067
2068 // Else, make a new one.
2069 self.resultsOfNextIncomingQueueOperationOperation = [CKKSResultOperation named:[NSString stringWithFormat:@"wait-for-next-incoming-queue-operation-%@", self.zoneName] withBlock:^{}];
2070 return self.resultsOfNextIncomingQueueOperationOperation;
2071 }
2072
2073 - (CKKSIncomingQueueOperation*)processIncomingQueue:(bool)failOnClassA {
2074 return [self processIncomingQueue:failOnClassA after: nil];
2075 }
2076
2077 - (CKKSIncomingQueueOperation*) processIncomingQueue:(bool)failOnClassA after: (CKKSResultOperation*) after {
2078 CKKSIncomingQueueOperation* incomingop = (CKKSIncomingQueueOperation*) [self findFirstPendingOperation:self.incomingQueueOperations];
2079 if(incomingop) {
2080 ckksinfo("ckks", self, "Skipping processIncomingQueue due to at least one pending instance");
2081 if(after) {
2082 [incomingop addNullableDependency: after];
2083 }
2084
2085 // check (again) for race condition; if the op has started we need to add another (for the dependency)
2086 if([incomingop isPending]) {
2087 incomingop.errorOnClassAFailure |= failOnClassA;
2088 return incomingop;
2089 }
2090 }
2091
2092 CKKSIncomingQueueOperation* op = [[CKKSIncomingQueueOperation alloc] initWithCKKSKeychainView:self errorOnClassAFailure:failOnClassA];
2093 op.name = @"incoming-queue-operation";
2094 if(after != nil) {
2095 [op addSuccessDependency: after];
2096 }
2097
2098 if(self.resultsOfNextIncomingQueueOperationOperation) {
2099 [self.resultsOfNextIncomingQueueOperationOperation addSuccessDependency:op];
2100 [self scheduleOperation:self.resultsOfNextIncomingQueueOperationOperation];
2101 }
2102
2103 [self scheduleOperation: op];
2104 return op;
2105 }
2106
2107 - (CKKSScanLocalItemsOperation*)scanLocalItems:(NSString*)operationName {
2108 return [self scanLocalItems:operationName ckoperationGroup:nil after:nil];
2109 }
2110
2111 - (CKKSScanLocalItemsOperation*)scanLocalItems:(NSString*)operationName ckoperationGroup:(CKOperationGroup*)operationGroup after:(NSOperation*)after {
2112 CKKSScanLocalItemsOperation* scanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:operationGroup];
2113 scanOperation.name = operationName;
2114
2115 [scanOperation addNullableDependency:self.lastFixupOperation];
2116 [scanOperation addNullableDependency:self.lockStateTracker.unlockDependency];
2117 [scanOperation addNullableDependency:self.keyStateReadyDependency];
2118 [scanOperation addNullableDependency:after];
2119
2120 [self scheduleOperation: scanOperation];
2121 return scanOperation;
2122 }
2123
2124 - (CKKSUpdateDeviceStateOperation*)updateDeviceState:(bool)rateLimit
2125 waitForKeyHierarchyInitialization:(uint64_t)timeout
2126 ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
2127
2128 __weak __typeof(self) weakSelf = self;
2129
2130 // If securityd just started, the key state might be in some transient early state. Wait a bit.
2131 CKKSResultOperation* waitForKeyReady = [CKKSResultOperation named:@"device-state-wait" withBlock:^{
2132 __strong __typeof(self) strongSelf = weakSelf;
2133 ckksnotice("ckksdevice", strongSelf, "Finished waiting for key hierarchy transient state, currently %@", strongSelf.keyHierarchyState);
2134 }];
2135
2136 [waitForKeyReady addNullableDependency:self.keyStateNonTransientDependency];
2137 [waitForKeyReady timeout:timeout];
2138 [self.waitingQueue addOperation:waitForKeyReady];
2139
2140 CKKSUpdateDeviceStateOperation* op = [[CKKSUpdateDeviceStateOperation alloc] initWithCKKSKeychainView:self rateLimit:rateLimit ckoperationGroup:ckoperationGroup];
2141 op.name = @"device-state-operation";
2142
2143 [op addDependency: waitForKeyReady];
2144
2145 // op modifies the CloudKit zone, so it should insert itself into the list of OutgoingQueueOperations.
2146 // Then, we won't have simultaneous zone-modifying operations and confuse ourselves.
2147 // However, since we might have pending OQOs, it should try to insert itself at the beginning of the linearized list
2148 [op linearDependenciesWithSelfFirst:self.outgoingQueueOperations];
2149
2150 // CKKSUpdateDeviceStateOperations are special: they should fire even if we don't believe we're in an iCloud account.
2151 // They also shouldn't block or be blocked by any other operation; our wait operation above will handle that
2152 [self scheduleOperationWithoutDependencies:op];
2153 return op;
2154 }
2155
2156 // There are some errors which won't be reported but will be reflected in the CDSE; any error coming out of here is fatal
2157 - (CKKSDeviceStateEntry*)_onqueueCurrentDeviceStateEntry: (NSError* __autoreleasing*)error {
2158 NSError* localerror = nil;
2159
2160 CKKSCKAccountStateTracker* accountTracker = self.accountTracker;
2161
2162 // We must have an iCloud account (with d2de on) to even create one of these
2163 if(accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable || accountTracker.currentCKAccountInfo.supportsDeviceToDeviceEncryption != YES) {
2164 ckkserror("ckksdevice", self, "No iCloud account active: %@", accountTracker.currentCKAccountInfo);
2165 localerror = [NSError errorWithDomain:@"securityd"
2166 code:errSecInternalError
2167 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No active HSA2 iCloud account: %@", accountTracker.currentCKAccountInfo]}];
2168 if(error) {
2169 *error = localerror;
2170 }
2171 return nil;
2172 }
2173
2174 CKKSDeviceStateEntry* oldcdse = [CKKSDeviceStateEntry tryFromDatabase:accountTracker.ckdeviceID zoneID:self.zoneID error:&localerror];
2175 if(localerror) {
2176 ckkserror("ckksdevice", self, "Couldn't read old CKKSDeviceStateEntry from database: %@", localerror);
2177 if(error) {
2178 *error = localerror;
2179 }
2180 return nil;
2181 }
2182
2183 // Find out what we think the current keys are
2184 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassTLK zoneID:self.zoneID error:&localerror];
2185 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassA zoneID:self.zoneID error:&localerror];
2186 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassC zoneID:self.zoneID error:&localerror];
2187 if(localerror) {
2188 // Things is broken, but the whole point of this record is to share the brokenness. Continue.
2189 ckkserror("ckksdevice", self, "Couldn't read current key pointers from database: %@; proceeding", localerror);
2190 localerror = nil;
2191 }
2192
2193 CKKSKey* suggestedTLK = currentTLKPointer.currentKeyUUID ? [CKKSKey tryFromDatabase:currentTLKPointer.currentKeyUUID zoneID:self.zoneID error:&localerror] : nil;
2194 CKKSKey* suggestedClassAKey = currentClassAPointer.currentKeyUUID ? [CKKSKey tryFromDatabase:currentClassAPointer.currentKeyUUID zoneID:self.zoneID error:&localerror] : nil;
2195 CKKSKey* suggestedClassCKey = currentClassCPointer.currentKeyUUID ? [CKKSKey tryFromDatabase:currentClassCPointer.currentKeyUUID zoneID:self.zoneID error:&localerror] : nil;
2196
2197 if(localerror) {
2198 // Things is broken, but the whole point of this record is to share the brokenness. Continue.
2199 ckkserror("ckksdevice", self, "Couldn't read keys from database: %@; proceeding", localerror);
2200 localerror = nil;
2201 }
2202
2203 // Check if we posess the keys in the keychain
2204 [suggestedTLK ensureKeyLoaded:&localerror];
2205 if(localerror && [self.lockStateTracker isLockedError:localerror]) {
2206 ckkserror("ckksdevice", self, "Device is locked; couldn't read TLK from keychain. Assuming it is present and continuing; error was %@", localerror);
2207 localerror = nil;
2208 } else if(localerror) {
2209 ckkserror("ckksdevice", self, "Couldn't read TLK from keychain. We do not have a current TLK. Error was %@", localerror);
2210 suggestedTLK = nil;
2211 }
2212
2213 [suggestedClassAKey ensureKeyLoaded:&localerror];
2214 if(localerror && [self.lockStateTracker isLockedError:localerror]) {
2215 ckkserror("ckksdevice", self, "Device is locked; couldn't read ClassA key from keychain. Assuming it is present and continuing; error was %@", localerror);
2216 localerror = nil;
2217 } else if(localerror) {
2218 ckkserror("ckksdevice", self, "Couldn't read ClassA key from keychain. We do not have a current ClassA key. Error was %@", localerror);
2219 suggestedClassAKey = nil;
2220 }
2221
2222 [suggestedClassCKey ensureKeyLoaded:&localerror];
2223 // class C keys are stored class C, so uh, don't check lock state.
2224 if(localerror) {
2225 ckkserror("ckksdevice", self, "Couldn't read ClassC key from keychain. We do not have a current ClassC key. Error was %@", localerror);
2226 suggestedClassCKey = nil;
2227 }
2228
2229 // We'd like to have the circle peer ID. Give the account state tracker a fighting chance, but not having it is not an error
2230 if([accountTracker.accountCirclePeerIDInitialized wait:500*NSEC_PER_MSEC] != 0 && !accountTracker.accountCirclePeerID) {
2231 ckkserror("ckksdevice", self, "No peer ID available");
2232 }
2233
2234 // Reset the last unlock time to 'day' granularity in UTC
2235 NSCalendar* calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
2236 calendar.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
2237 NSDate* lastUnlockDay = self.lockStateTracker.lastUnlockTime;
2238 lastUnlockDay = lastUnlockDay ? [calendar startOfDayForDate:lastUnlockDay] : nil;
2239
2240 // We only really want the oldcdse for its encodedCKRecord, so make a new cdse here
2241 CKKSDeviceStateEntry* newcdse = [[CKKSDeviceStateEntry alloc] initForDevice:accountTracker.ckdeviceID
2242 osVersion:SecCKKSHostOSVersion()
2243 lastUnlockTime:lastUnlockDay
2244 octagonPeerID:nil
2245 octagonStatus:nil
2246 circlePeerID:accountTracker.accountCirclePeerID
2247 circleStatus:accountTracker.currentCircleStatus.status
2248 keyState:self.keyHierarchyState
2249 currentTLKUUID:suggestedTLK.uuid
2250 currentClassAUUID:suggestedClassAKey.uuid
2251 currentClassCUUID:suggestedClassCKey.uuid
2252 zoneID:self.zoneID
2253 encodedCKRecord:oldcdse.encodedCKRecord];
2254 return newcdse;
2255 }
2256
2257 - (CKKSSynchronizeOperation*) resyncWithCloud {
2258 CKKSSynchronizeOperation* op = [[CKKSSynchronizeOperation alloc] initWithCKKSKeychainView: self];
2259 [self scheduleOperation: op];
2260 return op;
2261 }
2262
2263 - (CKKSLocalSynchronizeOperation*)resyncLocal {
2264 CKKSLocalSynchronizeOperation* op = [[CKKSLocalSynchronizeOperation alloc] initWithCKKSKeychainView:self];
2265 [self scheduleOperation: op];
2266 return op;
2267 }
2268
2269 - (CKKSResultOperation*)fetchAndProcessCKChanges:(CKKSFetchBecause*)because {
2270 return [self fetchAndProcessCKChanges:because after:nil];
2271 }
2272
2273 - (CKKSResultOperation*)fetchAndProcessCKChanges:(CKKSFetchBecause*)because after:(CKKSResultOperation*)after {
2274 if(!SecCKKSIsEnabled()) {
2275 ckksinfo("ckks", self, "Skipping fetchAndProcessCKChanges due to disabled CKKS");
2276 return nil;
2277 }
2278
2279 if(after) {
2280 [self.zoneChangeFetcher holdFetchesUntil:after];
2281 }
2282
2283 // We fetched some changes; try to process them!
2284 return [self processIncomingQueue:false after:[self.zoneChangeFetcher requestSuccessfulFetch:because]];
2285 }
2286
2287 - (CKKSResultOperation*)fetchAndProcessCKChangesDueToAPNS:(CKRecordZoneNotification*)notification {
2288 if(!SecCKKSIsEnabled()) {
2289 ckksinfo("ckks", self, "Skipping fetchAndProcessCKChanges due to disabled CKKS");
2290 return nil;
2291 }
2292
2293 // We fetched some changes; try to process them!
2294 return [self processIncomingQueue:false after:[self.zoneChangeFetcher requestSuccessfulFetchDueToAPNS:notification]];
2295 }
2296
2297 // Lets the view know about a failed CloudKit write. If the error is "already have one of these records", it will
2298 // store the new records and kick off the new processing
2299 //
2300 // Note that you need to tell this function the records you wanted to save, so it can determine what needs deletion
2301 - (bool)_onqueueCKWriteFailed:(NSError*)ckerror attemptedRecordsChanged:(NSDictionary<CKRecordID*, CKRecord*>*)savedRecords {
2302 dispatch_assert_queue(self.queue);
2303
2304 NSDictionary<CKRecordID*,NSError*>* partialErrors = ckerror.userInfo[CKPartialErrorsByItemIDKey];
2305 if([ckerror.domain isEqual:CKErrorDomain] && ckerror.code == CKErrorPartialFailure && partialErrors) {
2306 // Check if this error was "you're out of date"
2307 bool recordChanged = true;
2308
2309 for(NSError* error in partialErrors.allValues) {
2310 if((![error.domain isEqual:CKErrorDomain]) || (error.code != CKErrorBatchRequestFailed && error.code != CKErrorServerRecordChanged && error.code != CKErrorUnknownItem)) {
2311 // There's an error in there that isn't CKErrorServerRecordChanged, CKErrorBatchRequestFailed, or CKErrorUnknownItem. Don't handle nicely...
2312 recordChanged = false;
2313 }
2314 }
2315
2316 if(recordChanged) {
2317 ckksnotice("ckks", self, "Received a ServerRecordChanged error, attempting to update new records and delete unknown ones");
2318
2319 bool updatedRecord = false;
2320
2321 for(CKRecordID* recordID in partialErrors.allKeys) {
2322 NSError* error = partialErrors[recordID];
2323 if([error.domain isEqual:CKErrorDomain] && error.code == CKErrorServerRecordChanged) {
2324 CKRecord* newRecord = error.userInfo[CKRecordChangedErrorServerRecordKey];
2325 ckksnotice("ckks", self, "On error: updating our idea of: %@", newRecord);
2326
2327 updatedRecord |= [self _onqueueCKRecordChanged:newRecord resync:true];
2328 } else if([error.domain isEqual:CKErrorDomain] && error.code == CKErrorUnknownItem) {
2329 CKRecord* record = savedRecords[recordID];
2330 ckksnotice("ckks", self, "On error: handling an unexpected delete of: %@ %@", recordID, record);
2331
2332 updatedRecord |= [self _onqueueCKRecordDeleted:recordID recordType:record.recordType resync:true];
2333 }
2334 }
2335
2336 if(updatedRecord) {
2337 [self processIncomingQueue:false];
2338 return true;
2339 }
2340 }
2341
2342 // Check if this error was the CKKS server extension rejecting the write
2343 for(CKRecordID* recordID in partialErrors.allKeys) {
2344 NSError* error = partialErrors[recordID];
2345
2346 NSError* underlyingError = error.userInfo[NSUnderlyingErrorKey];
2347 NSError* thirdLevelError = underlyingError.userInfo[NSUnderlyingErrorKey];
2348 ckksnotice("ckks", self, "Examining 'write failed' error: %@ %@ %@", error, underlyingError, thirdLevelError);
2349
2350 if([error.domain isEqualToString:CKErrorDomain] && error.code == CKErrorServerRejectedRequest &&
2351 underlyingError && [underlyingError.domain isEqualToString:CKInternalErrorDomain] && underlyingError.code == CKErrorInternalPluginError &&
2352 thirdLevelError && [thirdLevelError.domain isEqualToString:@"CloudkitKeychainService"]) {
2353
2354 if(thirdLevelError.code == CKKSServerUnexpectedSyncKeyInChain) {
2355 // The server thinks the classA/C synckeys don't wrap directly the to top TLK, but we don't (otherwise, we would have fixed it).
2356 // Issue a key hierarchy fetch and see what's what.
2357 ckkserror("ckks", self, "CKKS Server extension has told us about %@ for record %@; requesting refetch and reprocess of key hierarchy", thirdLevelError, recordID);
2358 [self _onqueueKeyStateMachineRequestFetch];
2359 } else {
2360 ckkserror("ckks", self, "CKKS Server extension has told us about %@ for record %@, but we don't currently handle this error", thirdLevelError, recordID);
2361 }
2362 }
2363 }
2364 }
2365
2366 return false;
2367 }
2368
2369 - (bool)_onqueueCKRecordDeleted:(CKRecordID*)recordID recordType:(NSString*)recordType resync:(bool)resync {
2370 dispatch_assert_queue(self.queue);
2371
2372 // TODO: resync doesn't really mean much here; what does it mean for a record to be 'deleted' if you're fetching from scratch?
2373
2374 if([recordType isEqual: SecCKRecordItemType]) {
2375 ckksinfo("ckks", self, "CloudKit notification: deleted record(%@): %@", recordType, recordID);
2376 NSError* error = nil;
2377 NSError* iqeerror = nil;
2378 CKKSMirrorEntry* ckme = [CKKSMirrorEntry fromDatabase: [recordID recordName] zoneID:self.zoneID error: &error];
2379
2380 // Deletes always succeed, not matter the generation count
2381 if(ckme) {
2382 [ckme deleteFromDatabase:&error];
2383
2384 CKKSIncomingQueueEntry* iqe = [[CKKSIncomingQueueEntry alloc] initWithCKKSItem:ckme.item action:SecCKKSActionDelete state:SecCKKSStateNew];
2385 [iqe saveToDatabase:&iqeerror];
2386 if(iqeerror) {
2387 ckkserror("ckks", self, "Couldn't save incoming queue entry: %@", iqeerror);
2388 }
2389 }
2390 ckksinfo("ckks", self, "CKKSMirrorEntry was deleted: %@ %@ error: %@", recordID, ckme, error);
2391 // TODO: actually pass error back up
2392 return (error == nil);
2393
2394 } else if([recordType isEqual: SecCKRecordCurrentItemType]) {
2395 ckksinfo("ckks", self, "CloudKit notification: deleted current item pointer(%@): %@", recordType, recordID);
2396 NSError* error = nil;
2397
2398 [[CKKSCurrentItemPointer tryFromDatabase:[recordID recordName] state:SecCKKSProcessedStateRemote zoneID:self.zoneID error:&error] deleteFromDatabase:&error];
2399 [[CKKSCurrentItemPointer fromDatabase:[recordID recordName] state:SecCKKSProcessedStateLocal zoneID:self.zoneID error:&error] deleteFromDatabase:&error];
2400
2401 ckksinfo("ckks", self, "CKKSCurrentItemPointer was deleted: %@ error: %@", recordID, error);
2402 return (error == nil);
2403
2404 } else if([recordType isEqual: SecCKRecordIntermediateKeyType]) {
2405 // TODO: handle in some interesting way
2406 return true;
2407 } else if([recordType isEqual: SecCKRecordTLKShareType]) {
2408 NSError* error = nil;
2409 ckksinfo("ckks", self, "CloudKit notification: deleted tlk share record(%@): %@", recordType, recordID);
2410 CKKSTLKShare* share = [CKKSTLKShare tryFromDatabaseFromCKRecordID:recordID error:&error];
2411 [share deleteFromDatabase:&error];
2412
2413 if(error) {
2414 ckkserror("ckks", self, "CK notification: Couldn't delete deleted TLKShare: %@ %@", recordID, error);
2415 }
2416 return (error == nil);
2417
2418 } else if([recordType isEqual: SecCKRecordDeviceStateType]) {
2419 NSError* error = nil;
2420 ckksinfo("ckks", self, "CloudKit notification: deleted device state record(%@): %@", recordType, recordID);
2421
2422 CKKSDeviceStateEntry* cdse = [CKKSDeviceStateEntry tryFromDatabaseFromCKRecordID:recordID error:&error];
2423 [cdse deleteFromDatabase: &error];
2424 ckksinfo("ckks", self, "CKKSCurrentItemPointer(%@) was deleted: %@ error: %@", cdse, recordID, error);
2425
2426 return (error == nil);
2427
2428 } else if ([recordType isEqualToString:SecCKRecordManifestType]) {
2429 ckksinfo("ckks", self, "CloudKit notification: deleted manifest record (%@): %@", recordType, recordID);
2430
2431 NSError* error = nil;
2432 CKKSManifest* manifest = [CKKSManifest manifestForRecordName:recordID.recordName error:&error];
2433 if (manifest) {
2434 [manifest deleteFromDatabase:&error];
2435 }
2436
2437 ckksinfo("ckks", self, "CKKSManifest was deleted: %@ %@ error: %@", recordID, manifest, error);
2438 // TODO: actually pass error back up
2439 return error == nil;
2440 }
2441
2442 else {
2443 ckkserror("ckksfetch", self, "unknown record type: %@ %@", recordType, recordID);
2444 return false;
2445 }
2446 }
2447
2448 - (bool)_onqueueCKRecordChanged:(CKRecord*)record resync:(bool)resync {
2449 dispatch_assert_queue(self.queue);
2450
2451 @autoreleasepool {
2452 ckksnotice("ckksfetch", self, "Processing record modification(%@): %@", record.recordType, record);
2453
2454 if([[record recordType] isEqual: SecCKRecordItemType]) {
2455 [self _onqueueCKRecordItemChanged:record resync:resync];
2456 return true;
2457 } else if([[record recordType] isEqual: SecCKRecordCurrentItemType]) {
2458 [self _onqueueCKRecordCurrentItemPointerChanged:record resync:resync];
2459 return true;
2460 } else if([[record recordType] isEqual: SecCKRecordIntermediateKeyType]) {
2461 [self _onqueueCKRecordKeyChanged:record resync:resync];
2462 return true;
2463 } else if ([[record recordType] isEqual: SecCKRecordTLKShareType]) {
2464 [self _onqueueCKRecordTLKShareChanged:record resync:resync];
2465 return true;
2466 } else if([[record recordType] isEqualToString: SecCKRecordCurrentKeyType]) {
2467 [self _onqueueCKRecordCurrentKeyPointerChanged:record resync:resync];
2468 return true;
2469 } else if ([[record recordType] isEqualToString:SecCKRecordManifestType]) {
2470 [self _onqueueCKRecordManifestChanged:record resync:resync];
2471 return true;
2472 } else if ([[record recordType] isEqualToString:SecCKRecordManifestLeafType]) {
2473 [self _onqueueCKRecordManifestLeafChanged:record resync:resync];
2474 return true;
2475 } else if ([[record recordType] isEqualToString:SecCKRecordDeviceStateType]) {
2476 [self _onqueueCKRecordDeviceStateChanged:record resync:resync];
2477 return true;
2478 } else {
2479 ckkserror("ckksfetch", self, "unknown record type: %@ %@", [record recordType], record);
2480 return false;
2481 }
2482 }
2483 }
2484
2485 - (void)_onqueueCKRecordItemChanged:(CKRecord*)record resync:(bool)resync {
2486 dispatch_assert_queue(self.queue);
2487
2488 NSError* error = nil;
2489 // Find if we knew about this record in the past
2490 bool update = false;
2491 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase: [[record recordID] recordName] zoneID:self.zoneID error:&error];
2492
2493 if(error) {
2494 ckkserror("ckks", self, "error loading a CKKSMirrorEntry from database: %@", error);
2495 // TODO: quit?
2496 }
2497
2498 if(resync) {
2499 if(!ckme) {
2500 ckkserror("ckksresync", self, "BUG: No local item matching resynced CloudKit record: %@", record);
2501 } else if(![ckme matchesCKRecord:record]) {
2502 ckkserror("ckksresync", self, "BUG: Local item doesn't match resynced CloudKit record: %@ %@", ckme, record);
2503 } else {
2504 ckksnotice("ckksresync", self, "Already know about this item record, updating anyway: %@", record.recordID);
2505 }
2506 }
2507
2508 if(ckme && ckme.item && ckme.item.generationCount > [record[SecCKRecordGenerationCountKey] unsignedLongLongValue]) {
2509 ckkserror("ckks", self, "received a record from CloudKit with a bad generation count: %@ (%ld > %@)", ckme.uuid,
2510 (long) ckme.item.generationCount,
2511 record[SecCKRecordGenerationCountKey]);
2512 // Abort processing this record.
2513 return;
2514 }
2515
2516 // If we found an old version in the database; this might be an update
2517 if(ckme) {
2518 if([ckme matchesCKRecord:record] && !resync) {
2519 // This is almost certainly a record we uploaded; CKFetchChanges sends them back as new records
2520 ckksnotice("ckks", self, "CloudKit has told us of record we already know about; skipping update");
2521 return;
2522 }
2523
2524 update = true;
2525 // Set the CKKSMirrorEntry's fields to be whatever this record holds
2526 [ckme setFromCKRecord: record];
2527 } else {
2528 // Have to make a new CKKSMirrorEntry
2529 ckme = [[CKKSMirrorEntry alloc] initWithCKRecord: record];
2530 }
2531
2532 [ckme saveToDatabase: &error];
2533
2534 if(error) {
2535 ckkserror("ckks", self, "couldn't save new CKRecord to database: %@ %@", record, error);
2536 } else {
2537 ckksdebug("ckks", self, "CKKSMirrorEntry was created: %@", ckme);
2538 }
2539
2540 NSError* iqeerror = nil;
2541 CKKSIncomingQueueEntry* iqe = [[CKKSIncomingQueueEntry alloc] initWithCKKSItem:ckme.item
2542 action:(update ? SecCKKSActionModify : SecCKKSActionAdd)
2543 state:SecCKKSStateNew];
2544 [iqe saveToDatabase:&iqeerror];
2545 if(iqeerror) {
2546 ckkserror("ckks", self, "Couldn't save modified incoming queue entry: %@", iqeerror);
2547 } else {
2548 ckksdebug("ckks", self, "CKKSIncomingQueueEntry was created: %@", iqe);
2549 }
2550
2551 // A remote change has occured for this record. Delete any pending local changes; they will be overwritten.
2552 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry tryFromDatabase:ckme.uuid state: SecCKKSStateNew zoneID:self.zoneID error: &error];
2553 if(error) {
2554 ckkserror("ckks", self, "Couldn't load OutgoingQueueEntry: %@", error);
2555 }
2556 if(oqe) {
2557 [self _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateDeleted error:&error];
2558 }
2559
2560 // Reencryptions are pending changes too
2561 oqe = [CKKSOutgoingQueueEntry tryFromDatabase:ckme.uuid state: SecCKKSStateReencrypt zoneID:self.zoneID error: &error];
2562 if(error) {
2563 ckkserror("ckks", self, "Couldn't load reencrypted OutgoingQueueEntry: %@", error);
2564 }
2565 if(oqe) {
2566 [oqe deleteFromDatabase:&error];
2567 if(error) {
2568 ckkserror("ckks", self, "Couldn't delete reencrypted oqe(%@): %@", oqe, error);
2569 }
2570 }
2571 }
2572
2573 - (void)_onqueueCKRecordKeyChanged:(CKRecord*)record resync:(bool)resync {
2574 dispatch_assert_queue(self.queue);
2575
2576 NSError* error = nil;
2577
2578 if(resync) {
2579 NSError* resyncerror = nil;
2580
2581 CKKSKey* key = [CKKSKey tryFromDatabaseAnyState:record.recordID.recordName zoneID:self.zoneID error:&resyncerror];
2582 if(resyncerror) {
2583 ckkserror("ckksresync", self, "error loading key: %@", resyncerror);
2584 }
2585 if(!key) {
2586 ckkserror("ckksresync", self, "BUG: No sync key matching resynced CloudKit record: %@", record);
2587 } else if(![key matchesCKRecord:record]) {
2588 ckkserror("ckksresync", self, "BUG: Local sync key doesn't match resynced CloudKit record(s): %@ %@", key, record);
2589 } else {
2590 ckksnotice("ckksresync", self, "Already know about this sync key, skipping update: %@", record);
2591 return;
2592 }
2593 }
2594
2595 CKKSKey* remotekey = [[CKKSKey alloc] initWithCKRecord: record];
2596
2597 // Do we already know about this key?
2598 CKKSKey* possibleLocalKey = [CKKSKey tryFromDatabase:remotekey.uuid zoneID:self.zoneID error:&error];
2599 if(error) {
2600 ckkserror("ckkskey", self, "Error findibg exsiting local key for %@: %@", remotekey, error);
2601 // Go on, assuming there isn't a local key
2602 } else if(possibleLocalKey && [possibleLocalKey matchesCKRecord:record]) {
2603 // Okay, nothing new here. Update the CKRecord and move on.
2604 // Note: If the new record doesn't match the local copy, we have to go through the whole dance below
2605 possibleLocalKey.storedCKRecord = record;
2606 [possibleLocalKey saveToDatabase:&error];
2607
2608 if(error) {
2609 ckkserror("ckkskey", self, "Couldn't update existing key: %@: %@", possibleLocalKey, error);
2610 }
2611 return;
2612 }
2613
2614 // Drop into the synckeys table as a 'remote' key, then ask for a rekey operation.
2615 remotekey.state = SecCKKSProcessedStateRemote;
2616 remotekey.currentkey = false;
2617
2618 [remotekey saveToDatabase:&error];
2619 if(error) {
2620 ckkserror("ckkskey", self, "Couldn't save key record to database: %@: %@", remotekey, error);
2621 ckksinfo("ckkskey", self, "CKRecord was %@", record);
2622 }
2623
2624 // We've saved a new key in the database; trigger a rekey operation.
2625 [self _onqueueKeyStateMachineRequestProcess];
2626 }
2627
2628 - (void)_onqueueCKRecordTLKShareChanged:(CKRecord*)record resync:(bool)resync {
2629 dispatch_assert_queue(self.queue);
2630
2631 NSError* error = nil;
2632 if(resync) {
2633 // TODO fill in
2634 }
2635
2636 // CKKSTLKShares get saved with no modification
2637 CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:record];
2638 [share saveToDatabase:&error];
2639 if(error) {
2640 ckkserror("ckksshare", self, "Couldn't save new TLK share to database: %@ %@", share, error);
2641 }
2642
2643 [self _onqueueKeyStateMachineRequestProcess];
2644 }
2645
2646 - (void)_onqueueCKRecordCurrentKeyPointerChanged:(CKRecord*)record resync:(bool)resync {
2647 dispatch_assert_queue(self.queue);
2648
2649 // Pull out the old CKP, if it exists
2650 NSError* ckperror = nil;
2651 CKKSCurrentKeyPointer* oldckp = [CKKSCurrentKeyPointer tryFromDatabase:((CKKSKeyClass*) record.recordID.recordName) zoneID:self.zoneID error:&ckperror];
2652 if(ckperror) {
2653 ckkserror("ckkskey", self, "error loading ckp: %@", ckperror);
2654 }
2655
2656 if(resync) {
2657 if(!oldckp) {
2658 ckkserror("ckksresync", self, "BUG: No current key pointer matching resynced CloudKit record: %@", record);
2659 } else if(![oldckp matchesCKRecord:record]) {
2660 ckkserror("ckksresync", self, "BUG: Local current key pointer doesn't match resynced CloudKit record: %@ %@", oldckp, record);
2661 } else {
2662 ckksnotice("ckksresync", self, "Current key pointer has 'changed', but it matches our local copy: %@", record);
2663 }
2664 }
2665
2666 NSError* error = nil;
2667 CKKSCurrentKeyPointer* currentkey = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: record];
2668
2669 [currentkey saveToDatabase: &error];
2670 if(error) {
2671 ckkserror("ckkskey", self, "Couldn't save current key pointer to database: %@: %@", currentkey, error);
2672 ckksinfo("ckkskey", self, "CKRecord was %@", record);
2673 }
2674
2675 if([oldckp matchesCKRecord:record]) {
2676 ckksnotice("ckkskey", self, "Current key pointer modification doesn't change anything interesting; skipping reprocess: %@", record);
2677 } else {
2678 // We've saved a new key in the database; trigger a rekey operation.
2679 [self _onqueueKeyStateMachineRequestProcess];
2680 }
2681 }
2682
2683 - (void)_onqueueCKRecordCurrentItemPointerChanged:(CKRecord*)record resync:(bool)resync {
2684 dispatch_assert_queue(self.queue);
2685
2686 if(resync) {
2687 NSError* ciperror = nil;
2688 CKKSCurrentItemPointer* localcip = [CKKSCurrentItemPointer tryFromDatabase:record.recordID.recordName state:SecCKKSProcessedStateLocal zoneID:self.zoneID error:&ciperror];
2689 CKKSCurrentItemPointer* remotecip = [CKKSCurrentItemPointer tryFromDatabase:record.recordID.recordName state:SecCKKSProcessedStateRemote zoneID:self.zoneID error:&ciperror];
2690 if(ciperror) {
2691 ckkserror("ckksresync", self, "error loading cip: %@", ciperror);
2692 }
2693 if(!(localcip || remotecip)) {
2694 ckkserror("ckksresync", self, "BUG: No current item pointer matching resynced CloudKit record: %@", record);
2695 } else if(! ([localcip matchesCKRecord:record] || [remotecip matchesCKRecord:record]) ) {
2696 ckkserror("ckksresync", self, "BUG: Local current item pointer doesn't match resynced CloudKit record(s): %@ %@ %@", localcip, remotecip, record);
2697 } else {
2698 ckksnotice("ckksresync", self, "Already know about this current item pointer, skipping update: %@", record);
2699 return;
2700 }
2701 }
2702
2703 NSError* error = nil;
2704 CKKSCurrentItemPointer* cip = [[CKKSCurrentItemPointer alloc] initWithCKRecord: record];
2705 cip.state = SecCKKSProcessedStateRemote;
2706
2707 [cip saveToDatabase: &error];
2708 if(error) {
2709 ckkserror("currentitem", self, "Couldn't save current item pointer to database: %@: %@ %@", cip, error, record);
2710 }
2711 }
2712
2713 - (void)_onqueueCKRecordManifestChanged:(CKRecord*)record resync:(bool)resync
2714 {
2715 NSError* error = nil;
2716 CKKSPendingManifest* manifest = [[CKKSPendingManifest alloc] initWithCKRecord:record];
2717 [manifest saveToDatabase:&error];
2718 if (error) {
2719 ckkserror("CKKS", self, "Failed to save fetched manifest record to database: %@: %@", manifest, error);
2720 ckksinfo("CKKS", self, "manifest CKRecord was %@", record);
2721 }
2722 }
2723
2724 - (void)_onqueueCKRecordManifestLeafChanged:(CKRecord*)record resync:(bool)resync
2725 {
2726 NSError* error = nil;
2727 CKKSManifestLeafRecord* manifestLeaf = [[CKKSManifestPendingLeafRecord alloc] initWithCKRecord:record];
2728 [manifestLeaf saveToDatabase:&error];
2729 if (error) {
2730 ckkserror("CKKS", self, "Failed to save fetched manifest leaf record to database: %@: %@", manifestLeaf, error);
2731 ckksinfo("CKKS", self, "manifest leaf CKRecord was %@", record);
2732 }
2733 }
2734
2735 - (void)_onqueueCKRecordDeviceStateChanged:(CKRecord*)record resync:(bool)resync {
2736 if(resync) {
2737 NSError* dserror = nil;
2738 CKKSDeviceStateEntry* cdse = [CKKSDeviceStateEntry tryFromDatabase:record.recordID.recordName zoneID:self.zoneID error:&dserror];
2739 if(dserror) {
2740 ckkserror("ckksresync", self, "error loading cdse: %@", dserror);
2741 }
2742 if(!cdse) {
2743 ckkserror("ckksresync", self, "BUG: No current device state entry matching resynced CloudKit record: %@", record);
2744 } else if(![cdse matchesCKRecord:record]) {
2745 ckkserror("ckksresync", self, "BUG: Local current device state entry doesn't match resynced CloudKit record(s): %@ %@", cdse, record);
2746 } else {
2747 ckksnotice("ckksresync", self, "Already know about this current item pointer, skipping update: %@", record);
2748 return;
2749 }
2750 }
2751
2752 NSError* error = nil;
2753 CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initWithCKRecord:record];
2754 [cdse saveToDatabase:&error];
2755 if (error) {
2756 ckkserror("ckksdevice", self, "Failed to save device record to database: %@: %@ %@", cdse, error, record);
2757 }
2758 }
2759
2760 - (bool)_onqueueResetAllInflightOQE:(NSError**)error {
2761 NSError* localError = nil;
2762
2763 while(true) {
2764 NSArray<CKKSOutgoingQueueEntry*> * inflightQueueEntries = [CKKSOutgoingQueueEntry fetch:SecCKKSOutgoingQueueItemsAtOnce
2765 state:SecCKKSStateInFlight
2766 zoneID:self.zoneID
2767 error:&localError];
2768
2769 if(localError != nil) {
2770 ckkserror("ckks", self, "Error finding inflight outgoing queue records: %@", localError);
2771 if(error) {
2772 *error = localError;
2773 }
2774 return false;
2775 }
2776
2777 if([inflightQueueEntries count] == 0u) {
2778 break;
2779 }
2780
2781 for(CKKSOutgoingQueueEntry* oqe in inflightQueueEntries) {
2782 [self _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateNew error:&localError];
2783
2784 if(localError) {
2785 ckkserror("ckks", self, "Error fixing up inflight OQE(%@): %@", oqe, localError);
2786 if(error) {
2787 *error = localError;
2788 }
2789 return false;
2790 }
2791 }
2792 }
2793
2794 return true;
2795 }
2796
2797 - (bool)_onqueueChangeOutgoingQueueEntry: (CKKSOutgoingQueueEntry*) oqe toState: (NSString*) state error: (NSError* __autoreleasing*) error {
2798 dispatch_assert_queue(self.queue);
2799
2800 NSError* localerror = nil;
2801
2802 if([state isEqualToString: SecCKKSStateDeleted]) {
2803 // Hurray, this must be a success
2804 SecBoolNSErrorCallback callback = self.pendingSyncCallbacks[oqe.uuid];
2805 if(callback) {
2806 callback(true, nil);
2807 self.pendingSyncCallbacks[oqe.uuid] = nil;
2808 }
2809
2810 [oqe deleteFromDatabase: &localerror];
2811 if(localerror) {
2812 ckkserror("ckks", self, "Couldn't delete %@: %@", oqe, localerror);
2813 }
2814
2815 } else if([oqe.state isEqualToString:SecCKKSStateInFlight] && [state isEqualToString:SecCKKSStateNew]) {
2816 // An in-flight OQE is moving to new? See if it's been superceded
2817 CKKSOutgoingQueueEntry* newOQE = [CKKSOutgoingQueueEntry tryFromDatabase:oqe.uuid state:SecCKKSStateNew zoneID:self.zoneID error:&localerror];
2818 if(localerror) {
2819 ckkserror("ckksoutgoing", self, "Couldn't fetch an overwriting OQE, assuming one doesn't exist: %@", localerror);
2820 newOQE = nil;
2821 }
2822
2823 if(newOQE) {
2824 ckksnotice("ckksoutgoing", self, "New modification has come in behind inflight %@; dropping failed change", oqe);
2825 // recurse for that lovely code reuse
2826 [self _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateDeleted error:&localerror];
2827 if(localerror) {
2828 ckkserror("ckksoutgoing", self, "Couldn't delete in-flight OQE: %@", localerror);
2829 if(error) {
2830 *error = localerror;
2831 }
2832 }
2833 } else {
2834 oqe.state = state;
2835 [oqe saveToDatabase: &localerror];
2836 if(localerror) {
2837 ckkserror("ckks", self, "Couldn't save %@ as %@: %@", oqe, state, localerror);
2838 }
2839 }
2840
2841 } else {
2842 oqe.state = state;
2843 [oqe saveToDatabase: &localerror];
2844 if(localerror) {
2845 ckkserror("ckks", self, "Couldn't save %@ as %@: %@", oqe, state, localerror);
2846 }
2847 }
2848
2849 if(error && localerror) {
2850 *error = localerror;
2851 }
2852 return localerror == nil;
2853 }
2854
2855 - (bool)_onqueueErrorOutgoingQueueEntry: (CKKSOutgoingQueueEntry*) oqe itemError: (NSError*) itemError error: (NSError* __autoreleasing*) error {
2856 dispatch_assert_queue(self.queue);
2857
2858 SecBoolNSErrorCallback callback = self.pendingSyncCallbacks[oqe.uuid];
2859 if(callback) {
2860 callback(false, itemError);
2861 self.pendingSyncCallbacks[oqe.uuid] = nil;
2862 }
2863 NSError* localerror = nil;
2864
2865 // Now, delete the OQE: it's never coming back
2866 [oqe deleteFromDatabase:&localerror];
2867 if(localerror) {
2868 ckkserror("ckks", self, "Couldn't delete %@ (due to error %@): %@", oqe, itemError, localerror);
2869 }
2870
2871 if(error && localerror) {
2872 *error = localerror;
2873 }
2874 return localerror == nil;
2875 }
2876
2877 - (bool)_onqueueUpdateLatestManifestWithError:(NSError**)error
2878 {
2879 dispatch_assert_queue(self.queue);
2880 CKKSManifest* manifest = [CKKSManifest latestTrustedManifestForZone:self.zoneName error:error];
2881 if (manifest) {
2882 self.latestManifest = manifest;
2883 return true;
2884 }
2885 else {
2886 return false;
2887 }
2888 }
2889
2890 - (bool)_onqueueWithAccountKeysCheckTLK:(CKKSKey*)proposedTLK error:(NSError* __autoreleasing *)error {
2891 dispatch_assert_queue(self.queue);
2892 // First, if we have a local identity, check for any TLK shares
2893 NSError* localerror = nil;
2894
2895 if(![proposedTLK wrapsSelf]) {
2896 ckkserror("ckksshare", self, "Potential TLK %@ does not wrap self; skipping TLK share checking", proposedTLK);
2897 } else {
2898 bool tlkShares = [self _onqueueWithAccountKeysCheckTLKFromShares:proposedTLK error:&localerror];
2899 // We only want to error out if a positive error occurred. "No shares" is okay.
2900 if(!tlkShares || localerror) {
2901 bool noTrustedTLKShares = [localerror.domain isEqualToString:CKKSErrorDomain] && localerror.code == CKKSNoTrustedTLKShares;
2902 bool noSelfPeer = [localerror.domain isEqualToString:CKKSErrorDomain] && localerror.code == CKKSNoEncryptionKey;
2903
2904 // If this error was something worse than 'couldn't unwrap for reasons including there not being data', report it
2905 if(!(noTrustedTLKShares || noSelfPeer)) {
2906 if(error) {
2907 *error = localerror;
2908 }
2909 ckkserror("ckksshare", self, "Errored unwrapping TLK with TLKShares: %@", localerror);
2910 return false;
2911 } else {
2912 ckkserror("ckksshare", self, "Non-fatal error unwrapping TLK with TLKShares: %@", localerror);
2913 }
2914 }
2915 }
2916
2917 if([proposedTLK loadKeyMaterialFromKeychain:error]) {
2918 // Hurray!
2919 return true;
2920 } else {
2921 return false;
2922 }
2923 }
2924
2925 // This version only examines if this TLK is recoverable from TLK shares
2926 - (bool)_onqueueWithAccountKeysCheckTLKFromShares:(CKKSKey*)proposedTLK error:(NSError* __autoreleasing *)error {
2927 NSError* localerror = NULL;
2928 if(!self.currentSelfPeers.currentSelf || self.currentSelfPeersError) {
2929 ckkserror("ckksshare", self, "Couldn't fetch self peers: %@", self.currentSelfPeersError);
2930 if(error) {
2931 if([self.lockStateTracker isLockedError:self.currentSelfPeersError]) {
2932 // Locked error should propagate
2933 *error = self.currentSelfPeersError;
2934 } else {
2935 *error = [NSError errorWithDomain:CKKSErrorDomain
2936 code:CKKSNoEncryptionKey
2937 description:@"No current self peer"
2938 underlying:self.currentSelfPeersError];
2939 }
2940 }
2941 return false;
2942 }
2943
2944 if(!self.currentTrustedPeers || self.currentTrustedPeersError) {
2945 ckkserror("ckksshare", self, "Couldn't fetch trusted peers: %@", self.currentTrustedPeersError);
2946 if(error) {
2947 *error = [NSError errorWithDomain:CKKSErrorDomain
2948 code:CKKSNoPeersAvailable
2949 description:@"No trusted peers"
2950 underlying:self.currentTrustedPeersError];
2951 }
2952 return false;
2953 }
2954
2955 NSError* lastShareError = nil;
2956
2957 for(id<CKKSSelfPeer> selfPeer in self.currentSelfPeers.allSelves) {
2958 NSArray<CKKSTLKShare*>* possibleShares = [CKKSTLKShare allFor:selfPeer.peerID
2959 keyUUID:proposedTLK.uuid
2960 zoneID:self.zoneID
2961 error:&localerror];
2962 if(localerror) {
2963 ckkserror("ckksshare", self, "Error fetching CKKSTLKShares for %@: %@", selfPeer, localerror);
2964 }
2965
2966 if(possibleShares.count == 0) {
2967 ckksnotice("ckksshare", self, "No CKKSTLKShares to %@ for %@", selfPeer, proposedTLK);
2968 continue;
2969 }
2970
2971 for(CKKSTLKShare* possibleShare in possibleShares) {
2972 NSError* possibleShareError = nil;
2973 ckksnotice("ckksshare", self, "Checking possible TLK share %@ as %@", possibleShare, selfPeer);
2974
2975 CKKSKey* possibleKey = [possibleShare recoverTLK:selfPeer
2976 trustedPeers:self.currentTrustedPeers
2977 error:&possibleShareError];
2978
2979 if(possibleShareError) {
2980 ckkserror("ckksshare", self, "Unable to unwrap TLKShare(%@) as %@: %@",
2981 possibleShare, selfPeer, possibleShareError);
2982 ckkserror("ckksshare", self, "Current trust set: %@", self.currentTrustedPeers);
2983 lastShareError = possibleShareError;
2984 continue;
2985 }
2986
2987 bool result = [proposedTLK trySelfWrappedKeyCandidate:possibleKey.aessivkey error:&possibleShareError];
2988 if(possibleShareError) {
2989 ckkserror("ckksshare", self, "Unwrapped TLKShare(%@) does not unwrap proposed TLK(%@) as %@: %@",
2990 possibleShare, proposedTLK, self.currentSelfPeers.currentSelf, possibleShareError);
2991 lastShareError = possibleShareError;
2992 continue;
2993 }
2994
2995 if(result) {
2996 ckksnotice("ckksshare", self, "TLKShare(%@) unlocked TLK(%@) as %@",
2997 possibleShare, proposedTLK, selfPeer);
2998
2999 // The proposed TLK is trusted key material. Persist it as a "trusted" key.
3000 [proposedTLK saveKeyMaterialToKeychain:true error:&possibleShareError];
3001 if(possibleShareError) {
3002 ckkserror("ckksshare", self, "Couldn't store the new TLK(%@) to the keychain: %@", proposedTLK, possibleShareError);
3003 if(error) {
3004 *error = possibleShareError;
3005 }
3006 return false;
3007 }
3008
3009 return true;
3010 }
3011 }
3012 }
3013
3014 if(error) {
3015 *error = [NSError errorWithDomain:CKKSErrorDomain
3016 code:CKKSNoTrustedTLKShares
3017 description:[NSString stringWithFormat:@"No trusted TLKShares for %@", proposedTLK]
3018 underlying:lastShareError];
3019 }
3020 return false;
3021 }
3022
3023 - (bool)dispatchSyncWithConnection:(SecDbConnectionRef _Nonnull)dbconn block:(bool (^)(void))block {
3024 CFErrorRef cferror = NULL;
3025
3026 // Take the DB transaction, then get on the local queue.
3027 // In the case of exclusive DB transactions, we don't really _need_ the local queue, but, it's here for future use.
3028 bool ret = kc_transaction_type(dbconn, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, ^bool{
3029 __block bool ok = false;
3030
3031 dispatch_sync(self.queue, ^{
3032 ok = block();
3033 });
3034
3035 return ok;
3036 });
3037
3038 if(cferror) {
3039 ckkserror("ckks", self, "error doing database transaction, major problems ahead: %@", cferror);
3040 }
3041 return ret;
3042 }
3043
3044 - (void)dispatchSync: (bool (^)(void)) block {
3045 // important enough to block this thread. Must get a connection first, though!
3046
3047 // Please don't jetsam us...
3048 os_transaction_t transaction = os_transaction_create([[NSString stringWithFormat:@"com.apple.securityd.ckks.%@", self.zoneName] UTF8String]);
3049
3050 CFErrorRef cferror = NULL;
3051 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
3052 return [self dispatchSyncWithConnection:dbt block:block];
3053 });
3054 if(cferror) {
3055 ckkserror("ckks", self, "error getting database connection, major problems ahead: %@", cferror);
3056 }
3057
3058 (void)transaction;
3059 }
3060
3061 - (void)dispatchSyncWithAccountKeys:(bool (^)(void))block
3062 {
3063 [SOSAccount performOnQuietAccountQueue: ^{
3064 NSError* selfPeersError = nil;
3065 CKKSSelves* currentSelfPeers = [self.currentPeerProvider fetchSelfPeers:&selfPeersError];
3066
3067 NSError* trustedPeersError = nil;
3068 NSSet<id<CKKSPeer>>* currentTrustedPeers = [self.currentPeerProvider fetchTrustedPeers:&trustedPeersError];
3069
3070 [self dispatchSync:^bool{
3071 self.currentSelfPeers = currentSelfPeers;
3072 self.currentSelfPeersError = selfPeersError;
3073
3074 self.currentTrustedPeers = currentTrustedPeers;
3075 self.currentTrustedPeersError = trustedPeersError;
3076
3077 __block bool result = false;
3078 [SOSAccount performWhileHoldingAccountQueue:^{ // so any calls through SOS account will know they can perform their work without dispatching to the account queue, which we already hold
3079 result = block();
3080 }];
3081
3082 // Forget the peers; they might have class A key material
3083 self.currentSelfPeers = nil;
3084 self.currentSelfPeersError = [NSError errorWithDomain:CKKSErrorDomain code:CKKSNoPeersAvailable description:@"No current self peer available"];
3085 self.currentTrustedPeers = nil;
3086 self.currentTrustedPeersError = [NSError errorWithDomain:CKKSErrorDomain code:CKKSNoPeersAvailable description:@"No current trusted peers available"];
3087
3088 return result;
3089 }];
3090 }];
3091 }
3092
3093 #pragma mark - CKKSZoneUpdateReceiver
3094
3095 - (void)notifyZoneChange: (CKRecordZoneNotification*) notification {
3096 ckksnotice("ckks", self, "received a zone change notification for %@ %@", self, notification);
3097
3098 [self fetchAndProcessCKChangesDueToAPNS:notification];
3099 }
3100
3101 - (void)superHandleCKLogin {
3102 [super handleCKLogin];
3103 }
3104
3105 - (void)handleCKLogin {
3106 ckksnotice("ckks", self, "received a notification of CK login");
3107 if(!SecCKKSIsEnabled()) {
3108 ckksnotice("ckks", self, "Skipping CloudKit initialization due to disabled CKKS");
3109 return;
3110 }
3111
3112 __weak __typeof(self) weakSelf = self;
3113 CKKSResultOperation* login = [CKKSResultOperation named:@"ckks-login" withBlock:^{
3114 __strong __typeof(self) strongSelf = weakSelf;
3115
3116 [strongSelf dispatchSyncWithAccountKeys:^bool{
3117 [strongSelf superHandleCKLogin];
3118
3119 // Reset key hierarchy state machine to initializing
3120 [strongSelf _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateInitializing withError:nil];
3121 return true;
3122 }];
3123
3124 // Change our condition variables to reflect that we think we're logged in
3125 strongSelf.loggedOut = [[CKKSCondition alloc] initToChain:strongSelf.loggedOut];
3126 [strongSelf.loggedIn fulfill];
3127 [strongSelf.accountStateKnown fulfill];
3128 }];
3129
3130 [self scheduleAccountStatusOperation:login];
3131 }
3132
3133 - (void)superHandleCKLogout {
3134 [super handleCKLogout];
3135 }
3136
3137 - (void)handleCKLogout {
3138 __weak __typeof(self) weakSelf = self;
3139 CKKSResultOperation* logout = [CKKSResultOperation named:@"ckks-logout" withBlock: ^{
3140 __strong __typeof(self) strongSelf = weakSelf;
3141 if(!strongSelf) {
3142 return;
3143 }
3144 [strongSelf dispatchSync:^bool {
3145 ckksnotice("ckks", strongSelf, "received a notification of CK logout");
3146 [strongSelf superHandleCKLogout];
3147
3148 NSError* error = nil;
3149 [strongSelf _onqueueResetLocalData: &error];
3150 if(error) {
3151 ckkserror("ckks", strongSelf, "error while resetting local data: %@", error);
3152 }
3153
3154 [self _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateLoggedOut withError:nil];
3155
3156 strongSelf.loggedIn = [[CKKSCondition alloc] initToChain: strongSelf.loggedIn];
3157 [strongSelf.loggedOut fulfill];
3158 [strongSelf.accountStateKnown fulfill];
3159
3160 // Tell all pending sync clients that we don't expect to ever sync
3161 for(NSString* callbackUUID in strongSelf.pendingSyncCallbacks.allKeys) {
3162 [strongSelf callSyncCallbackWithErrorNoAccount:strongSelf.pendingSyncCallbacks[callbackUUID]];
3163 strongSelf.pendingSyncCallbacks[callbackUUID] = nil;
3164 }
3165
3166 return true;
3167 }];
3168 }];
3169
3170 [self scheduleAccountStatusOperation: logout];
3171 }
3172
3173 - (void)callSyncCallbackWithErrorNoAccount:(SecBoolNSErrorCallback)syncCallback {
3174 CKKSAccountStatus accountStatus = self.accountStatus;
3175 dispatch_async(self.queue, ^{
3176 syncCallback(false, [NSError errorWithDomain:@"securityd"
3177 code:errSecNotLoggedIn
3178 userInfo:@{NSLocalizedDescriptionKey:
3179 [NSString stringWithFormat: @"No iCloud account available(%d); item is not expected to sync", (int)accountStatus]}]);
3180 });
3181 }
3182
3183 #pragma mark - CKKSChangeFetcherClient
3184
3185 - (CKKSCloudKitFetchRequest*)participateInFetch
3186 {
3187 __block CKKSCloudKitFetchRequest* request = [[CKKSCloudKitFetchRequest alloc] init];
3188 [self dispatchSync: ^bool {
3189 if(self.accountStatus != CKKSAccountStatusAvailable) {
3190 ckksnotice("ckksfetch", self, "Not participating in fetch: not logged in");
3191 request.participateInFetch = false;
3192 return false;
3193 }
3194
3195 if(!self.zoneCreated) {
3196 ckksnotice("ckksfetch", self, "Not participating in fetch: zone not created yet");
3197 request.participateInFetch = false;
3198 return false;
3199 }
3200
3201 request.participateInFetch = true;
3202
3203 if([self.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateNeedFullRefetch]) {
3204 // We want to return a nil change tag (to force a resync)
3205 ckksnotice("ckksfetch", self, "Beginning refetch");
3206 request.changeToken = nil;
3207 } else {
3208 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state:self.zoneName];
3209 if(!ckse) {
3210 ckkserror("ckksfetch", self, "couldn't fetch zone change token for %@", self.zoneName);
3211 return false;
3212 }
3213 request.changeToken = ckse.changeToken;
3214 }
3215 return true;
3216 }];
3217
3218 return request;
3219 }
3220
3221 - (void)changesFetched:(NSArray<CKRecord*>*)changedRecords
3222 deletedRecordIDs:(NSArray<CKKSCloudKitDeletion*>*)deletedRecords
3223 oldChangeToken:(CKServerChangeToken*)oldChangeToken
3224 newChangeToken:(CKServerChangeToken*)newChangeToken
3225 {
3226 [self dispatchSyncWithAccountKeys:^bool{
3227 // This is a resync if we already have a change token, but this fetch didn't have one
3228 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: self.zoneName];
3229 bool resync = ckse.changeToken && (oldChangeToken == nil);
3230
3231 for (CKRecord* record in changedRecords) {
3232 [self _onqueueCKRecordChanged:record resync:resync];
3233 }
3234
3235 for (CKKSCloudKitDeletion* deletion in deletedRecords) {
3236 [self _onqueueCKRecordDeleted:deletion.recordID recordType:deletion.recordType resync:resync];
3237 }
3238
3239 NSError* error = nil;
3240 if(resync) {
3241 // Scan through all CKMirrorEntries and determine if any exist that CloudKit didn't tell us about
3242 ckksnotice("ckksresync", self, "Comparing local UUIDs against the CloudKit list");
3243 NSMutableArray<NSString*>* uuids = [[CKKSMirrorEntry allUUIDs:self.zoneID error:&error] mutableCopy];
3244
3245 for(NSString* uuid in uuids) {
3246 CKRecord* record = nil;
3247 CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName:uuid zoneID:self.zoneID];
3248 for(CKRecord* r in changedRecords) {
3249 if([r.recordID isEqual:recordID]) {
3250 record = r;
3251 break;
3252 }
3253 }
3254
3255 if(record) {
3256 ckksnotice("ckksresync", self, "UUID %@ is still in CloudKit; carry on.", uuid);
3257 } else {
3258 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:uuid zoneID:self.zoneID error:&error];
3259 if(error != nil) {
3260 ckkserror("ckksresync", self, "Couldn't read an item from the database, but it used to be there: %@ %@", uuid, error);
3261 continue;
3262 }
3263 if(!ckme) {
3264 ckkserror("ckksresync", self, "Couldn't read ckme(%@) from database; continuing", uuid);
3265 continue;
3266 }
3267
3268 ckkserror("ckksresync", self, "BUG: Local item %@ not found in CloudKit, deleting", uuid);
3269 [self _onqueueCKRecordDeleted:ckme.item.storedCKRecord.recordID recordType:ckme.item.storedCKRecord.recordType resync:resync];
3270 }
3271 }
3272 }
3273
3274 error = nil;
3275
3276 CKKSZoneStateEntry* state = [CKKSZoneStateEntry state:self.zoneName];
3277 state.lastFetchTime = [NSDate date]; // The last fetch happened right now!
3278 state.changeToken = newChangeToken;
3279 [state saveToDatabase:&error];
3280 if(error) {
3281 ckkserror("ckksfetch", self, "Couldn't save new server change token: %@", error);
3282 }
3283
3284 // Might as well kick off a IQO!
3285 [self processIncomingQueue:false];
3286
3287 ckksnotice("ckksfetch", self, "Finished processing changes for %@", self.zoneID);
3288
3289 return true;
3290 }];
3291 }
3292
3293 // Return false if this is a 'fatal' error and we don't want another fetch to be tried
3294 - (bool)notifyFetchError: (NSError*) error {
3295 __weak __typeof(self) weakSelf = self;
3296
3297 bool isChangeTokenExpiredError = false;
3298 if([error.domain isEqualToString:CKErrorDomain] && (error.code == CKErrorChangeTokenExpired)) {
3299 isChangeTokenExpiredError = true;
3300 } else if([error.domain isEqualToString:CKErrorDomain] && (error.code == CKErrorPartialFailure)) {
3301 NSDictionary* partialErrors = error.userInfo[CKPartialErrorsByItemIDKey];
3302 for(CKRecordZoneID* zoneID in partialErrors) {
3303 NSError* partialError = partialErrors[zoneID];
3304 if([zoneID isEqual:self.zoneID] && [partialError.domain isEqualToString:CKErrorDomain] && (partialError.code == CKErrorChangeTokenExpired)) {
3305 isChangeTokenExpiredError = true;
3306 }
3307 }
3308 }
3309
3310 if(isChangeTokenExpiredError) {
3311 ckkserror("ckks", self, "Received notice that our change token is out of date (for %@). Resetting local data...", self.zoneID);
3312 CKKSResultOperation* resetOp = [self resetLocalData];
3313 CKKSResultOperation* resetHandler = [CKKSResultOperation named:@"local-reset-handler" withBlock:^{
3314 __strong __typeof(self) strongSelf = weakSelf;
3315 if(!strongSelf) {
3316 ckkserror("ckks", strongSelf, "received callback for released object");
3317 return;
3318 }
3319
3320 if(resetOp.error) {
3321 ckksnotice("ckksreset", strongSelf, "CloudKit-inspired local reset of %@ ended with error: %@", strongSelf.zoneID, error);
3322 } else {
3323 ckksnotice("ckksreset", strongSelf, "CloudKit-inspired local reset of %@ ended successfully", strongSelf.zoneID);
3324 }
3325 }];
3326
3327 [resetHandler addDependency:resetOp];
3328 [self scheduleOperation:resetHandler];
3329 return false;
3330 }
3331
3332 bool isDeletedZoneError = false;
3333 if([error.domain isEqualToString:CKErrorDomain] && ((error.code == CKErrorUserDeletedZone) || (error.code == CKErrorZoneNotFound))) {
3334 isDeletedZoneError = true;
3335 } else if([error.domain isEqualToString:CKErrorDomain] && (error.code == CKErrorPartialFailure)) {
3336 NSDictionary* partialErrors = error.userInfo[CKPartialErrorsByItemIDKey];
3337 for(CKRecordZoneID* zoneID in partialErrors) {
3338 NSError* partialError = partialErrors[zoneID];
3339 if([self.zoneID isEqual:zoneID] && [partialError.domain isEqualToString:CKErrorDomain] && ((partialError.code == CKErrorUserDeletedZone) || (partialError.code == CKErrorZoneNotFound))) {
3340 isDeletedZoneError = true;
3341 }
3342 }
3343 }
3344
3345 if(isDeletedZoneError) {
3346 ckkserror("ckks", self, "Received notice that our zone(%@) does not exist. Resetting local data.", self.zoneID);
3347 CKKSResultOperation* resetOp = [self resetLocalData];
3348 CKKSResultOperation* resetHandler = [CKKSResultOperation named:@"reset-handler" withBlock:^{
3349 __strong __typeof(self) strongSelf = weakSelf;
3350 if(!strongSelf) {
3351 ckkserror("ckksreset", strongSelf, "received callback for released object");
3352 return;
3353 }
3354
3355 if(resetOp.error) {
3356 ckksnotice("ckksreset", strongSelf, "CloudKit-inspired local reset of %@ ended with error: %@", strongSelf.zoneID, resetOp.error);
3357 } else {
3358 ckksnotice("ckksreset", strongSelf, "CloudKit-inspired local reset of %@ ended successfully", strongSelf.zoneID);
3359 }
3360 }];
3361
3362 [resetHandler addDependency:resetOp];
3363 [self scheduleOperation:resetHandler];
3364 return false;
3365 }
3366
3367 if([error.domain isEqualToString:CKErrorDomain] && (error.code == CKErrorBadContainer)) {
3368 ckkserror("ckks", self, "Received notice that our container does not exist. Nothing to do.");
3369 return false;
3370 }
3371
3372 return true;
3373 }
3374
3375 #pragma mark CKKSPeerUpdateListener
3376
3377 - (void)selfPeerChanged {
3378 // Currently, we have no idea what to do with this. Kick off a key reprocess?
3379 ckkserror("ckks", self, "Received update that our self identity has changed");
3380 [self keyStateMachineRequestProcess];
3381 }
3382
3383 - (void)trustedPeerSetChanged {
3384 // We might need to share the TLK to some new people, or we might now trust the TLKs we have.
3385 // The key state machine should handle that, so poke it.
3386 ckkserror("ckks", self, "Received update that the trust set has changed");
3387
3388 self.trustedPeersSetChanged = true;
3389 [self.pokeKeyStateMachineScheduler trigger];
3390 }
3391
3392 #pragma mark - Test Support
3393
3394 - (bool) outgoingQueueEmpty: (NSError * __autoreleasing *) error {
3395 __block bool ret = false;
3396 [self dispatchSync: ^bool{
3397 NSArray* queueEntries = [CKKSOutgoingQueueEntry all: error];
3398 ret = queueEntries && ([queueEntries count] == 0);
3399 return true;
3400 }];
3401
3402 return ret;
3403 }
3404
3405 - (CKKSResultOperation*)waitForFetchAndIncomingQueueProcessing {
3406 CKKSResultOperation* op = [self fetchAndProcessCKChanges:CKKSFetchBecauseTesting];
3407 [op waitUntilFinished];
3408 return op;
3409 }
3410
3411 - (void)waitForKeyHierarchyReadiness {
3412 if(self.keyStateReadyDependency) {
3413 [self.keyStateReadyDependency waitUntilFinished];
3414 }
3415 }
3416
3417 - (void)cancelPendingOperations {
3418 @synchronized(self.outgoingQueueOperations) {
3419 for(NSOperation* op in self.outgoingQueueOperations) {
3420 [op cancel];
3421 }
3422 [self.outgoingQueueOperations removeAllObjects];
3423 }
3424
3425 @synchronized(self.incomingQueueOperations) {
3426 for(NSOperation* op in self.incomingQueueOperations) {
3427 [op cancel];
3428 }
3429 [self.incomingQueueOperations removeAllObjects];
3430 }
3431
3432 [super cancelAllOperations];
3433 }
3434
3435 - (void)cancelAllOperations {
3436 [self.zoneSetupOperation cancel];
3437 [self.keyStateMachineOperation cancel];
3438 [self.keyStateReadyDependency cancel];
3439 [self.keyStateNonTransientDependency cancel];
3440 [self.zoneChangeFetcher cancel];
3441 [self.notifyViewChangedScheduler cancel];
3442
3443 [self cancelPendingOperations];
3444
3445 [self dispatchSync:^bool{
3446 [self _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateCancelled withError: nil];
3447 return true;
3448 }];
3449 }
3450
3451 - (void)halt {
3452 [super halt];
3453
3454 // Don't send any more notifications, either
3455 _notifierClass = nil;
3456 }
3457
3458 - (NSDictionary*)status {
3459 #define stringify(obj) CKKSNilToNSNull([obj description])
3460 #define boolstr(obj) (!!(obj) ? @"yes" : @"no")
3461 __block NSMutableDictionary* ret = nil;
3462 __block NSError* error = nil;
3463 CKKSManifest* manifest = nil;
3464
3465 ret = [[self fastStatus] mutableCopy];
3466
3467 manifest = [CKKSManifest latestTrustedManifestForZone:self.zoneName error:&error];
3468 [self dispatchSync: ^bool {
3469
3470 CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] initForZone:self.zoneID];
3471 if(keyset.error) {
3472 error = keyset.error;
3473 }
3474
3475 NSString* manifestGeneration = manifest ? [NSString stringWithFormat:@"%lu", (unsigned long)manifest.generationCount] : nil;
3476
3477 if(error) {
3478 ckkserror("ckks", self, "error during status: %@", error);
3479 }
3480 // We actually don't care about this error, especially if it's "no current key pointers"...
3481 error = nil;
3482
3483 // Map deviceStates to strings to avoid NSXPC issues. Obj-c, why is this so hard?
3484 NSArray* deviceStates = [CKKSDeviceStateEntry allInZone:self.zoneID error:&error];
3485 NSMutableArray<NSString*>* mutDeviceStates = [[NSMutableArray alloc] init];
3486 [deviceStates enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
3487 [mutDeviceStates addObject: [obj description]];
3488 }];
3489
3490 NSArray* tlkShares = [CKKSTLKShare allForUUID:keyset.currentTLKPointer.currentKeyUUID zoneID:self.zoneID error:&error];
3491 NSMutableArray<NSString*>* mutTLKShares = [[NSMutableArray alloc] init];
3492 [tlkShares enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
3493 [mutTLKShares addObject: [obj description]];
3494 }];
3495
3496 [ret addEntriesFromDictionary:@{
3497 @"statusError": stringify(error),
3498 @"oqe": CKKSNilToNSNull([CKKSOutgoingQueueEntry countsByStateInZone:self.zoneID error:&error]),
3499 @"iqe": CKKSNilToNSNull([CKKSIncomingQueueEntry countsByStateInZone:self.zoneID error:&error]),
3500 @"ckmirror": CKKSNilToNSNull([CKKSMirrorEntry countsByParentKey:self.zoneID error:&error]),
3501 @"devicestates": CKKSNilToNSNull(mutDeviceStates),
3502 @"tlkshares": CKKSNilToNSNull(mutTLKShares),
3503 @"keys": CKKSNilToNSNull([CKKSKey countsByClass:self.zoneID error:&error]),
3504 @"currentTLK": CKKSNilToNSNull(keyset.tlk.uuid),
3505 @"currentClassA": CKKSNilToNSNull(keyset.classA.uuid),
3506 @"currentClassC": CKKSNilToNSNull(keyset.classC.uuid),
3507 @"currentTLKPtr": CKKSNilToNSNull(keyset.currentTLKPointer.currentKeyUUID),
3508 @"currentClassAPtr": CKKSNilToNSNull(keyset.currentClassAPointer.currentKeyUUID),
3509 @"currentClassCPtr": CKKSNilToNSNull(keyset.currentClassCPointer.currentKeyUUID),
3510 @"currentManifestGen": CKKSNilToNSNull(manifestGeneration),
3511 }];
3512 return false;
3513 }];
3514 return ret;
3515 }
3516
3517 - (NSDictionary*)fastStatus {
3518
3519 __block NSDictionary* ret = nil;
3520
3521 [self dispatchSync: ^bool {
3522
3523 ret = @{
3524 @"view": CKKSNilToNSNull(self.zoneName),
3525 @"ckaccountstatus": self.accountStatus == CKAccountStatusCouldNotDetermine ? @"could not determine" :
3526 self.accountStatus == CKAccountStatusAvailable ? @"logged in" :
3527 self.accountStatus == CKAccountStatusRestricted ? @"restricted" :
3528 self.accountStatus == CKAccountStatusNoAccount ? @"logged out" : @"unknown",
3529 @"lockstatetracker": stringify(self.lockStateTracker),
3530 @"accounttracker": stringify(self.accountTracker),
3531 @"fetcher": stringify(self.zoneChangeFetcher),
3532 @"zoneCreated": boolstr(self.zoneCreated),
3533 @"zoneCreatedError": stringify(self.zoneCreatedError),
3534 @"zoneSubscribed": boolstr(self.zoneSubscribed),
3535 @"zoneSubscribedError": stringify(self.zoneSubscribedError),
3536 @"zoneInitializeScheduler": stringify(self.initializeScheduler),
3537 @"keystate": CKKSNilToNSNull(self.keyHierarchyState),
3538 @"keyStateError": stringify(self.keyHierarchyError),
3539 @"statusError": [NSNull null],
3540
3541 @"zoneSetupOperation": stringify(self.zoneSetupOperation),
3542 @"keyStateOperation": stringify(self.keyStateMachineOperation),
3543 @"lastIncomingQueueOperation": stringify(self.lastIncomingQueueOperation),
3544 @"lastNewTLKOperation": stringify(self.lastNewTLKOperation),
3545 @"lastOutgoingQueueOperation": stringify(self.lastOutgoingQueueOperation),
3546 @"lastProcessReceivedKeysOperation": stringify(self.lastProcessReceivedKeysOperation),
3547 @"lastReencryptOutgoingItemsOperation":stringify(self.lastReencryptOutgoingItemsOperation),
3548 @"lastScanLocalItemsOperation": stringify(self.lastScanLocalItemsOperation),
3549 };
3550 return false;
3551 }];
3552
3553 return ret;
3554 }
3555
3556 #endif /* OCTAGON */
3557 @end