2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 #import <CloudKit/CloudKit.h>
27 #import <CloudKit/CloudKit_Private.h>
29 #import "keychain/ckks/CKKSKeychainView.h"
30 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
31 #import "keychain/ckks/CKKSKey.h"
32 #import "keychain/ckks/CKKSHealTLKSharesOperation.h"
33 #import "keychain/ckks/CKKSGroupOperation.h"
34 #import "keychain/ckks/CKKSTLKShare.h"
36 #import "CKKSPowerCollection.h"
38 @interface CKKSHealTLKSharesOperation ()
39 @property NSBlockOperation* cloudkitModifyOperationFinished;
40 @property CKOperationGroup* ckoperationGroup;
43 @implementation CKKSHealTLKSharesOperation
45 - (instancetype)init {
48 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
49 if(self = [super init]) {
51 _ckoperationGroup = ckoperationGroup;
58 * We've been invoked because something is wonky with the tlk shares.
60 * Attempt to figure out what it is, and what we can do about it.
63 __weak __typeof(self) weakSelf = self;
65 CKKSKeychainView* ckks = self.ckks;
67 ckkserror("ckksshare", ckks, "no CKKS object");
72 ckksnotice("ckksshare", ckks, "CKKSHealTLKSharesOperation cancelled, quitting");
76 [ckks dispatchSyncWithAccountKeys: ^bool{
78 ckksnotice("ckksshare", ckks, "CKKSHealTLKSharesOperation cancelled, quitting");
84 CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] initForZone:ckks.zoneID];
87 self.error = keyset.error;
88 ckkserror("ckksshare", ckks, "couldn't load current keys: can't fix TLK shares");
89 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
92 ckksnotice("ckksshare", ckks, "Key set is %@", keyset);
95 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventTLKShareProcessing zone:ckks.zoneName];
97 // Okay! Perform the checks.
98 if(![keyset.tlk loadKeyMaterialFromKeychain:&error] || error) {
99 // Well, that's no good. We can't share a TLK we don't have.
100 if([ckks.lockStateTracker isLockedError: error]) {
101 ckkserror("ckksshare", ckks, "Keychain is locked: can't fix shares yet: %@", error);
102 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReadyPendingUnlock withError:nil];
104 // TODO go to waitfortlk
105 ckkserror("ckksshare", ckks, "couldn't load current tlk from keychain: %@", error);
106 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
111 NSSet<CKKSTLKShare*>* newShares = [ckks _onqueueCreateMissingKeyShares:keyset.tlk
114 ckkserror("ckksshare", ckks, "Unable to create shares: %@", error);
118 if(newShares.count == 0u) {
119 ckksnotice("ckksshare", ckks, "Don't believe we need to change any TLKShares, stopping");
120 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReady withError:nil];
124 // Fire up our CloudKit operation!
126 NSMutableArray<CKRecord *>* recordsToSave = [[NSMutableArray alloc] init];
127 NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
128 NSMutableDictionary<CKRecordID*, CKRecord*>* attemptedRecords = [[NSMutableDictionary alloc] init];
130 for(CKKSTLKShare* share in newShares) {
131 CKRecord* record = [share CKRecordWithZoneID:ckks.zoneID];
132 [recordsToSave addObject: record];
133 attemptedRecords[record.recordID] = record;
136 // Use the spare operation trick to wait for the CKModifyRecordsOperation to complete
137 self.cloudkitModifyOperationFinished = [NSBlockOperation named:@"heal-tlkshares-modify-operation-finished" withBlock:^{}];
138 [self dependOnBeforeGroupFinished: self.cloudkitModifyOperationFinished];
141 // Get the CloudKit operation ready...
142 CKModifyRecordsOperation* modifyRecordsOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave
143 recordIDsToDelete:recordIDsToDelete];
144 modifyRecordsOp.atomic = YES;
145 modifyRecordsOp.longLived = NO;
147 // very important: get the TLKShares off-device ASAP
148 modifyRecordsOp.configuration.automaticallyRetryNetworkFailures = NO;
149 modifyRecordsOp.configuration.discretionaryNetworkBehavior = CKOperationDiscretionaryNetworkBehaviorNonDiscretionary;
151 modifyRecordsOp.group = self.ckoperationGroup;
152 ckksnotice("ckksshare", ckks, "Operation group is %@", self.ckoperationGroup);
154 modifyRecordsOp.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
155 __strong __typeof(weakSelf) strongSelf = weakSelf;
156 __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
158 // These should all fail or succeed as one. Do the hard work in the records completion block.
160 ckksinfo("ckksshare", blockCKKS, "Successfully completed upload for record %@", record.recordID.recordName);
162 ckkserror("ckksshare", blockCKKS, "error on row: %@ %@", record.recordID, error);
166 modifyRecordsOp.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error) {
167 __strong __typeof(weakSelf) strongSelf = weakSelf;
168 __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
170 secerror("ckks: received callback for released object");
174 [strongCKKS dispatchSyncWithAccountKeys: ^bool {
176 // Success. Persist the records to the CKKS database
177 ckksnotice("ckksshare", strongCKKS, "Completed TLK Share heal operation with success");
178 NSError* localerror = nil;
180 // Save the new CKRecords to the database
181 for(CKRecord* record in savedRecords) {
182 CKKSTLKShare* savedShare = [[CKKSTLKShare alloc] initWithCKRecord:record];
183 [savedShare saveToDatabase:&localerror];
186 // No recovery from this, really...
187 ckkserror("ckksshare", strongCKKS, "Couldn't save new TLKShare record to database: %@", localerror);
190 ckksnotice("ckksshare", strongCKKS, "Successfully completed upload for %@", savedShare);
194 // Successfully sharing TLKs means we're now in ready!
195 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateReady withError: nil];
197 ckkserror("ckksshare", strongCKKS, "Completed TLK Share heal operation with error: %@", error);
198 [strongCKKS _onqueueCKWriteFailed:error attemptedRecordsChanged:attemptedRecords];
199 // Send the key state machine into tlksharesfailed
200 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateHealTLKSharesFailed withError: nil];
205 // Notify that we're done
206 [strongSelf.operationQueue addOperation: strongSelf.cloudkitModifyOperationFinished];
209 [ckks.database addOperation: modifyRecordsOp];
215 [self.cloudkitModifyOperationFinished cancel];