]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSHealTLKSharesOperation.m
Security-59754.80.3.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 @end
42
43 @implementation CKKSHealTLKSharesOperation
44 @synthesize intendedState = _intendedState;
45 @synthesize nextState = _nextState;
46
47 - (instancetype)init {
48 return nil;
49 }
50
51 - (instancetype)initWithOperationDependencies:(CKKSOperationDependencies*)operationDependencies
52 ckks:(CKKSKeychainView*)ckks
53 {
54 if(self = [super init]) {
55 _ckks = ckks;
56 _deps = operationDependencies;
57
58 _nextState = SecCKKSZoneKeyStateHealTLKSharesFailed;
59 _intendedState = SecCKKSZoneKeyStateBecomeReady;
60 }
61 return self;
62 }
63
64 - (void)groupStart {
65 /*
66 * We've been invoked because something is wonky with the tlk shares.
67 *
68 * Attempt to figure out what it is, and what we can do about it.
69 */
70
71 WEAKIFY(self);
72
73 if(self.cancelled) {
74 ckksnotice("ckksshare", self.deps.zoneID, "CKKSHealTLKSharesOperation cancelled, quitting");
75 return;
76 }
77
78 NSArray<CKKSPeerProviderState*>* trustStates = [self.deps currentTrustStates];
79
80 NSError* error = nil;
81 __block CKKSCurrentKeySet* keyset = nil;
82
83 [self.deps.databaseProvider dispatchSyncWithReadOnlySQLTransaction:^{
84 keyset = [CKKSCurrentKeySet loadForZone:self.deps.zoneID];
85 }];
86
87 if(keyset.error) {
88 self.nextState = SecCKKSZoneKeyStateUnhealthy;
89 self.error = keyset.error;
90 ckkserror("ckksshare", self.deps.zoneID, "couldn't load current keys: can't fix TLK shares");
91 return;
92 } else {
93 ckksnotice("ckksshare", self.deps.zoneID, "Key set is %@", keyset);
94 }
95
96 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventTLKShareProcessing zone:self.deps.zoneID.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([self.deps.lockStateTracker isLockedError: error]) {
102 ckkserror("ckksshare", self.deps.zoneID, "Keychain is locked: can't fix shares yet: %@", error);
103 self.nextState = SecCKKSZoneKeyStateBecomeReady;
104 } else {
105 ckkserror("ckksshare", self.deps.zoneID, "couldn't load current tlk from keychain: %@", error);
106 self.nextState = SecCKKSZoneKeyStateUnhealthy;
107 }
108 return;
109 }
110
111 NSSet<CKKSTLKShareRecord*>* newShares = [CKKSHealTLKSharesOperation createMissingKeyShares:keyset
112 trustStates:trustStates
113 error:&error];
114 if(error) {
115 ckkserror("ckksshare", self.deps.zoneID, "Unable to create shares: %@", error);
116 self.nextState = SecCKKSZoneKeyStateUnhealthy;
117 return;
118 }
119
120 if(newShares.count == 0u) {
121 ckksnotice("ckksshare", self.deps.zoneID, "Don't believe we need to change any TLKShares, stopping");
122 self.nextState = self.intendedState;
123 return;
124 }
125
126 keyset.pendingTLKShares = [newShares allObjects];
127
128 // Let's double-check: if we upload these TLKShares, will the world be right?
129 BOOL newSharesSufficient = [CKKSHealTLKSharesOperation areNewSharesSufficient:keyset
130 trustStates:trustStates
131 error:&error];
132 if(!newSharesSufficient || error) {
133 ckksnotice("ckksshare", self.deps.zoneID, "New shares won't resolve the share issue; erroring to avoid infinite loops");
134 self.nextState = SecCKKSZoneKeyStateError;
135 return;
136 }
137
138 // Fire up our CloudKit operation!
139
140 NSMutableArray<CKRecord *>* recordsToSave = [[NSMutableArray alloc] init];
141 NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
142 NSMutableDictionary<CKRecordID*, CKRecord*>* attemptedRecords = [[NSMutableDictionary alloc] init];
143
144 ckksnotice("ckksshare", self.deps.zoneID, "Uploading %d new TLKShares", (unsigned int)newShares.count);
145 for(CKKSTLKShareRecord* share in newShares) {
146 ckksnotice("ckksshare", self.deps.zoneID, "Uploading TLKShare to %@ (as %@)", share.share.receiverPeerID, share.senderPeerID);
147
148 CKRecord* record = [share CKRecordWithZoneID:self.deps.zoneID];
149 [recordsToSave addObject: record];
150 attemptedRecords[record.recordID] = record;
151 }
152
153 // Use the spare operation trick to wait for the CKModifyRecordsOperation to complete
154 self.cloudkitModifyOperationFinished = [NSBlockOperation named:@"heal-tlkshares-modify-operation-finished" withBlock:^{}];
155 [self dependOnBeforeGroupFinished: self.cloudkitModifyOperationFinished];
156
157 // Get the CloudKit operation ready...
158 CKModifyRecordsOperation* modifyRecordsOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave
159 recordIDsToDelete:recordIDsToDelete];
160 modifyRecordsOp.atomic = YES;
161 modifyRecordsOp.longLived = NO;
162
163 // very important: get the TLKShares off-device ASAP
164 modifyRecordsOp.configuration.automaticallyRetryNetworkFailures = NO;
165 modifyRecordsOp.configuration.discretionaryNetworkBehavior = CKOperationDiscretionaryNetworkBehaviorNonDiscretionary;
166 modifyRecordsOp.configuration.isCloudKitSupportOperation = YES;
167
168 modifyRecordsOp.group = self.deps.ckoperationGroup;
169 ckksnotice("ckksshare", self.deps.zoneID, "Operation group is %@", self.deps.ckoperationGroup);
170
171 modifyRecordsOp.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
172 STRONGIFY(self);
173
174 // These should all fail or succeed as one. Do the hard work in the records completion block.
175 if(!error) {
176 ckksnotice("ckksshare", self.deps.zoneID, "Successfully completed upload for record %@", record.recordID.recordName);
177 } else {
178 ckkserror("ckksshare", self.deps.zoneID, "error on row: %@ %@", record.recordID, error);
179 }
180 };
181
182 modifyRecordsOp.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error) {
183 STRONGIFY(self);
184
185 CKKSKeychainView* strongCKKS = self.ckks;
186
187 [self.deps.databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
188 if(error == nil) {
189 // Success. Persist the records to the CKKS database
190 ckksnotice("ckksshare", self.deps.zoneID, "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", self.deps.zoneID, "Couldn't save new TLKShare record to database: %@", localerror);
202 self.error = localerror;
203 self.nextState = SecCKKSZoneKeyStateError;
204 return CKKSDatabaseTransactionCommit;
205
206 } else {
207 ckksnotice("ckksshare", self.deps.zoneID, "Successfully completed upload for %@", savedShare);
208 }
209 }
210
211 // Successfully sharing TLKs means we're now in ready!
212 self.nextState = SecCKKSZoneKeyStateBecomeReady;
213 } else {
214 ckkserror("ckksshare", self.deps.zoneID, "Completed TLK Share heal operation with error: %@", error);
215 [strongCKKS _onqueueCKWriteFailed:error attemptedRecordsChanged:attemptedRecords];
216 // Send the key state machine into tlksharesfailed
217 self.nextState = SecCKKSZoneKeyStateHealTLKSharesFailed;
218 }
219 return CKKSDatabaseTransactionCommit;
220 }];
221
222 // Notify that we're done
223 [self.operationQueue addOperation: self.cloudkitModifyOperationFinished];
224 };
225
226 [self.ckks.database addOperation:modifyRecordsOp];
227 }
228
229 - (void)cancel {
230 [self.cloudkitModifyOperationFinished cancel];
231 [super cancel];
232 }
233
234 + (BOOL)areNewSharesSufficient:(CKKSCurrentKeySet*)keyset
235 trustStates:(NSArray<CKKSPeerProviderState*>*)trustStates
236 error:(NSError* __autoreleasing*)error
237 {
238 for(CKKSPeerProviderState* trustState in trustStates) {
239 NSError* localError = nil;
240 NSSet<id<CKKSPeer>>* peersMissingShares = [trustState findPeersMissingTLKSharesFor:keyset
241 error:&localError];
242 if(peersMissingShares == nil || localError) {
243 if(trustState.essential) {
244 if(error) {
245 *error = localError;
246 }
247 return NO;
248 } else {
249 ckksnotice("ckksshare", keyset.tlk, "Failed to find peers for nonessential system: %@", trustState);
250 // Not a hard failure.
251 }
252 }
253
254 if(peersMissingShares.count > 0) {
255 ckksnotice("ckksshare", keyset.tlk, "New share set is missing shares for peers: %@", peersMissingShares);
256 return NO;
257 }
258 }
259
260 return YES;
261 }
262
263
264 + (NSSet<CKKSTLKShareRecord*>* _Nullable)createMissingKeyShares:(CKKSCurrentKeySet*)keyset
265 trustStates:(NSArray<CKKSPeerProviderState*>*)trustStates
266 error:(NSError* __autoreleasing*)error
267 {
268 NSError* localerror = nil;
269 NSSet<CKKSTLKShareRecord*>* newShares = nil;
270
271 // If any one of our trust states succeed, this function doesn't have an error
272 for(CKKSPeerProviderState* trustState in trustStates) {
273 NSError* stateError = nil;
274
275 NSSet<CKKSTLKShareRecord*>* newTrustShares = [self createMissingKeyShares:keyset
276 peers:trustState
277 error:&stateError];
278
279
280 if(newTrustShares && !stateError) {
281 newShares = newShares ? [newShares setByAddingObjectsFromSet:newTrustShares] : newTrustShares;
282 } else {
283 ckksnotice("ckksshare", keyset.tlk, "Unable to create shares for trust set %@: %@", trustState, stateError);
284 if(localerror == nil) {
285 localerror = stateError;
286 }
287 }
288 }
289
290 // Only report an error if none of the trust states were able to succeed
291 if(newShares) {
292 return newShares;
293 } else {
294 if(error && localerror) {
295 *error = localerror;
296 }
297 return nil;
298 }
299 }
300
301 + (NSSet<CKKSTLKShareRecord*>*)createMissingKeyShares:(CKKSCurrentKeySet*)keyset
302 peers:(CKKSPeerProviderState*)trustState
303 error:(NSError* __autoreleasing*)error
304 {
305 NSError* localerror = nil;
306 if(![keyset.tlk ensureKeyLoaded:&localerror]) {
307 ckkserror("ckksshare", keyset.tlk, "TLK not loaded; cannot make shares for peers: %@", localerror);
308 if(error) {
309 *error = localerror;
310 }
311 return nil;
312 }
313
314 NSSet<id<CKKSPeer>>* remainingPeers = [trustState findPeersMissingTLKSharesFor:keyset
315 error:&localerror];
316 if(!remainingPeers) {
317 ckkserror("ckksshare", keyset.tlk, "Unable to find peers missing TLKShares: %@", localerror);
318 if(error) {
319 *error = localerror;
320 }
321 return nil;
322 }
323
324 NSMutableSet<CKKSTLKShareRecord*>* newShares = [NSMutableSet set];
325
326 for(id<CKKSPeer> peer in remainingPeers) {
327 if(!peer.publicEncryptionKey) {
328 ckksnotice("ckksshare", keyset.tlk, "No need to make TLK for %@; they don't have any encryption keys", peer);
329 continue;
330 }
331
332 // Create a share for this peer.
333 ckksnotice("ckksshare", keyset.tlk, "Creating share of %@ as %@ for %@", keyset.tlk, trustState.currentSelfPeers.currentSelf, peer);
334 CKKSTLKShareRecord* newShare = [CKKSTLKShareRecord share:keyset.tlk
335 as:trustState.currentSelfPeers.currentSelf
336 to:peer
337 epoch:-1
338 poisoned:0
339 error:&localerror];
340
341 if(localerror) {
342 ckkserror("ckksshare", keyset.tlk, "Couldn't create new share for %@: %@", peer, localerror);
343 if(error) {
344 *error = localerror;
345 }
346 return nil;
347 }
348
349 [newShares addObject: newShare];
350 }
351
352 return newShares;
353 }
354
355 @end;
356
357 #endif