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