]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSHealTLKSharesOperation.m
Security-59306.61.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSHealTLKSharesOperation.m
1 /*
2 * Copyright (c) 2017 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 #if OCTAGON
25
26 #import <CloudKit/CloudKit.h>
27 #import <CloudKit/CloudKit_Private.h>
28
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/CKKSTLKShareRecord.h"
35 #import "keychain/ot/ObjCImprovements.h"
36
37 #import "CKKSPowerCollection.h"
38
39 @interface CKKSHealTLKSharesOperation ()
40 @property NSBlockOperation* cloudkitModifyOperationFinished;
41 @property CKOperationGroup* ckoperationGroup;
42 @end
43
44 @implementation CKKSHealTLKSharesOperation
45
46 - (instancetype)init {
47 return nil;
48 }
49 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
50 if(self = [super init]) {
51 _ckks = ckks;
52 _ckoperationGroup = ckoperationGroup;
53 }
54 return self;
55 }
56
57 - (void)groupStart {
58 /*
59 * We've been invoked because something is wonky with the tlk shares.
60 *
61 * Attempt to figure out what it is, and what we can do about it.
62 */
63
64 WEAKIFY(self);
65
66 CKKSKeychainView* ckks = self.ckks;
67 if(!ckks) {
68 ckkserror("ckksshare", ckks, "no CKKS object");
69 return;
70 }
71
72 if(self.cancelled) {
73 ckksnotice("ckksshare", ckks, "CKKSHealTLKSharesOperation cancelled, quitting");
74 return;
75 }
76
77 [ckks dispatchSyncWithAccountKeys: ^bool{
78 if(self.cancelled) {
79 ckksnotice("ckksshare", ckks, "CKKSHealTLKSharesOperation cancelled, quitting");
80 return false;
81 }
82
83 NSError* error = nil;
84
85 CKKSCurrentKeySet* keyset = [CKKSCurrentKeySet loadForZone:ckks.zoneID];
86
87 if(keyset.error) {
88 self.error = keyset.error;
89 ckkserror("ckksshare", ckks, "couldn't load current keys: can't fix TLK shares");
90 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
91 return true;
92 } else {
93 ckksnotice("ckksshare", ckks, "Key set is %@", keyset);
94 }
95
96 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventTLKShareProcessing zone:ckks.zoneName];
97
98 // Okay! Perform the checks.
99 if(![keyset.tlk loadKeyMaterialFromKeychain:&error] || error) {
100 // Well, that's no good. We can't share a TLK we don't have.
101 if([ckks.lockStateTracker isLockedError: error]) {
102 ckkserror("ckksshare", ckks, "Keychain is locked: can't fix shares yet: %@", error);
103 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReadyPendingUnlock withError:nil];
104 } else {
105 // TODO go to waitfortlk
106 ckkserror("ckksshare", ckks, "couldn't load current tlk from keychain: %@", error);
107 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
108 }
109 return true;
110 }
111
112 NSSet<CKKSTLKShareRecord*>* newShares = [ckks _onqueueCreateMissingKeyShares:keyset.tlk
113 error:&error];
114 if(error) {
115 ckkserror("ckksshare", ckks, "Unable to create shares: %@", error);
116 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
117 return false;
118 }
119
120 if(newShares.count == 0u) {
121 ckksnotice("ckksshare", ckks, "Don't believe we need to change any TLKShares, stopping");
122 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReady withError:nil];
123 return true;
124 }
125
126 // Let's double-check: if we upload these TLKShares, will the world be right?
127 BOOL newSharesSufficient = [ckks _onqueueAreNewSharesSufficient:newShares
128 currentTLK:keyset.tlk
129 error:&error];
130 if(!newSharesSufficient || error) {
131 ckksnotice("ckksshare", ckks, "New shares won't resolve the share issue; erroring to avoid infinite loops");
132 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:error];
133 return true;
134 }
135
136 // Fire up our CloudKit operation!
137
138 NSMutableArray<CKRecord *>* recordsToSave = [[NSMutableArray alloc] init];
139 NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
140 NSMutableDictionary<CKRecordID*, CKRecord*>* attemptedRecords = [[NSMutableDictionary alloc] init];
141
142 for(CKKSTLKShareRecord* share in newShares) {
143 CKRecord* record = [share CKRecordWithZoneID:ckks.zoneID];
144 [recordsToSave addObject: record];
145 attemptedRecords[record.recordID] = record;
146 }
147
148 // Use the spare operation trick to wait for the CKModifyRecordsOperation to complete
149 self.cloudkitModifyOperationFinished = [NSBlockOperation named:@"heal-tlkshares-modify-operation-finished" withBlock:^{}];
150 [self dependOnBeforeGroupFinished: self.cloudkitModifyOperationFinished];
151
152
153 // Get the CloudKit operation ready...
154 CKModifyRecordsOperation* modifyRecordsOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave
155 recordIDsToDelete:recordIDsToDelete];
156 modifyRecordsOp.atomic = YES;
157 modifyRecordsOp.longLived = NO;
158
159 // very important: get the TLKShares off-device ASAP
160 modifyRecordsOp.configuration.automaticallyRetryNetworkFailures = NO;
161 modifyRecordsOp.configuration.discretionaryNetworkBehavior = CKOperationDiscretionaryNetworkBehaviorNonDiscretionary;
162 modifyRecordsOp.configuration.isCloudKitSupportOperation = YES;
163
164 modifyRecordsOp.group = self.ckoperationGroup;
165 ckksnotice("ckksshare", ckks, "Operation group is %@", self.ckoperationGroup);
166
167 modifyRecordsOp.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
168 STRONGIFY(self);
169 CKKSKeychainView* blockCKKS = self.ckks;
170
171 // These should all fail or succeed as one. Do the hard work in the records completion block.
172 if(!error) {
173 ckksnotice("ckksshare", blockCKKS, "Successfully completed upload for record %@", record.recordID.recordName);
174 } else {
175 ckkserror("ckksshare", blockCKKS, "error on row: %@ %@", record.recordID, error);
176 }
177 };
178
179 modifyRecordsOp.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error) {
180 STRONGIFY(self);
181 CKKSKeychainView* strongCKKS = self.ckks;
182 if(!self) {
183 secerror("ckks: received callback for released object");
184 return;
185 }
186
187 [strongCKKS dispatchSyncWithAccountKeys: ^bool {
188 if(error == nil) {
189 // Success. Persist the records to the CKKS database
190 ckksnotice("ckksshare", strongCKKS, "Completed TLK Share heal operation with success");
191 NSError* localerror = nil;
192
193 // Save the new CKRecords to the database
194 for(CKRecord* record in savedRecords) {
195 CKKSTLKShareRecord* savedShare = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
196 bool saved = [savedShare saveToDatabase:&localerror];
197
198 if(!saved || localerror != nil) {
199 // erroring means we were unable to save the new TLKShare records to the database. This will cause us to try to reupload them. Fail.
200 // No recovery from this, really...
201 ckkserror("ckksshare", strongCKKS, "Couldn't save new TLKShare record to database: %@", localerror);
202 [strongCKKS _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError: localerror];
203 return true;
204
205 } else {
206 ckksnotice("ckksshare", strongCKKS, "Successfully completed upload for %@", savedShare);
207 }
208 }
209
210 // Successfully sharing TLKs means we're now in ready!
211 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateReady withError: nil];
212 } else {
213 ckkserror("ckksshare", strongCKKS, "Completed TLK Share heal operation with error: %@", error);
214 [strongCKKS _onqueueCKWriteFailed:error attemptedRecordsChanged:attemptedRecords];
215 // Send the key state machine into tlksharesfailed
216 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateHealTLKSharesFailed withError: nil];
217 }
218 return true;
219 }];
220
221 // Notify that we're done
222 [self.operationQueue addOperation: self.cloudkitModifyOperationFinished];
223 };
224
225 [ckks.database addOperation: modifyRecordsOp];
226 return true;
227 }];
228 }
229
230 - (void)cancel {
231 [self.cloudkitModifyOperationFinished cancel];
232 [super cancel];
233 }
234
235 @end;
236
237 #endif