]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSNewTLKOperation.m
Security-58286.70.7.tar.gz
[apple/security.git] / keychain / ckks / CKKSNewTLKOperation.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 #import "CKKSCurrentKeyPointer.h"
26 #import "CKKSKey.h"
27 #import "CKKSNewTLKOperation.h"
28 #import "CKKSGroupOperation.h"
29 #import "CKKSNearFutureScheduler.h"
30 #import "keychain/ckks/CloudKitCategories.h"
31
32 #if OCTAGON
33
34 #import "keychain/ckks/CKKSTLKShare.h"
35
36 @interface CKKSNewTLKOperation ()
37 @property NSBlockOperation* cloudkitModifyOperationFinished;
38 @property CKOperationGroup* ckoperationGroup;
39 @end
40
41 @implementation CKKSNewTLKOperation
42
43 - (instancetype)init {
44 return nil;
45 }
46 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
47 if(self = [super init]) {
48 _ckks = ckks;
49 _ckoperationGroup = ckoperationGroup;
50 }
51 return self;
52 }
53
54 - (void)groupStart {
55 /*
56 * Rolling keys is an essential operation, and must be transactional: either completing successfully or
57 * failing entirely. Also, in the case of failure, some other peer has beaten us to CloudKit and changed
58 * the keys stored there (which we must now fetch and handle): the keys we attempted to upload are useless.
59
60 * Therefore, we'll skip the normal OutgoingQueue behavior, and persist keys in-memory until such time as
61 * CloudKit tells us the operation succeeds or fails, at which point we'll commit them or throw them away.
62 *
63 * Note that this means edge cases in the case of secd dying in the middle of this operation; our normal
64 * retry mechanisms won't work. We'll have to make the policy decision to re-roll the keys if needed upon
65 * the next launch of secd (or, the write will succeed after we die, and we'll handle receiving the CK
66 * items as if a different peer uploaded them).
67 */
68
69 __weak __typeof(self) weakSelf = self;
70
71 CKKSKeychainView* ckks = self.ckks;
72
73 if(self.cancelled) {
74 ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting");
75 return;
76 }
77
78 if(!ckks) {
79 ckkserror("ckkstlk", ckks, "no CKKS object");
80 return;
81 }
82
83 // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety.
84 [ckks dispatchSyncWithAccountKeys: ^bool{
85 if(self.cancelled) {
86 ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting");
87 return false;
88 }
89
90 ckks.lastNewTLKOperation = self;
91
92 if(ckks.currentSelfPeersError) {
93 if([ckks.lockStateTracker isLockedError: ckks.currentSelfPeersError]) {
94 ckkserror("ckksshare", ckks, "Can't create new TLKs: keychain is locked so self peers are unavailable: %@", ckks.currentSelfPeersError);
95 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
96 } else {
97 ckkserror("ckkstlk", ckks, "Couldn't create new TLKs because self peers aren't available: %@", ckks.currentSelfPeersError);
98 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateNewTLKsFailed withError: ckks.currentSelfPeersError];
99 }
100 self.error = ckks.currentSelfPeersError;
101 return false;
102 }
103 if(ckks.currentTrustedPeersError) {
104 if([ckks.lockStateTracker isLockedError: ckks.currentTrustedPeersError]) {
105 ckkserror("ckksshare", ckks, "Can't create new TLKs: keychain is locked so trusted peers are unavailable: %@", ckks.currentTrustedPeersError);
106 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
107 } else {
108 ckkserror("ckkstlk", ckks, "Couldn't create new TLKs because trusted peers aren't available: %@", ckks.currentTrustedPeersError);
109 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateNewTLKsFailed withError: ckks.currentTrustedPeersError];
110 }
111 self.error = ckks.currentTrustedPeersError;
112 return false;
113 }
114
115 NSError* error = nil;
116
117 ckksinfo("ckkstlk", ckks, "Generating new TLK");
118
119 // Promote to strong reference
120 CKKSKeychainView* ckks = self.ckks;
121
122 CKKSKey* newTLK = nil;
123 CKKSKey* newClassAKey = nil;
124 CKKSKey* newClassCKey = nil;
125 CKKSKey* wrappedOldTLK = nil;
126
127 NSMutableArray<CKRecord *>* recordsToSave = [[NSMutableArray alloc] init];
128 NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
129
130 // Now, prepare data for the operation:
131
132 // We must find the current TLK (to wrap it to the new TLK).
133 NSError* localerror = nil;
134 CKKSKey* oldTLK = [CKKSKey currentKeyForClass: SecCKKSKeyClassTLK zoneID:ckks.zoneID error: &localerror];
135 if(localerror) {
136 ckkserror("ckkstlk", ckks, "couldn't load the current TLK: %@", localerror);
137 // TODO: not loading the old TLK is fine, but only if there aren't any TLKs
138 }
139
140 [oldTLK ensureKeyLoaded: &error];
141
142 ckksnotice("ckkstlk", ckks, "Old TLK is: %@ %@", oldTLK, error);
143 if(error != nil) {
144 ckkserror("ckkstlk", ckks, "Couldn't fetch and unwrap old TLK: %@", error);
145 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
146 return false;
147 }
148
149 // Generate new hierarchy:
150 // newTLK
151 // / | \
152 // / | \
153 // / | \
154 // oldTLK classA classC
155
156 newTLK = [[CKKSKey alloc] initSelfWrappedWithAESKey:[CKKSAESSIVKey randomKey]
157 uuid:[[NSUUID UUID] UUIDString]
158 keyclass:SecCKKSKeyClassTLK
159 state:SecCKKSProcessedStateLocal
160 zoneID:ckks.zoneID
161 encodedCKRecord:nil
162 currentkey:true];
163
164 newClassAKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassA error: &error];
165 newClassCKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassC error: &error];
166
167 if(error != nil) {
168 ckkserror("ckkstlk", ckks, "couldn't make new key hierarchy: %@", error);
169 // TODO: this really isn't the error state, but a 'retry'.
170 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
171 return false;
172 }
173
174 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassTLK withKeyUUID:newTLK.uuid zoneID:ckks.zoneID error: &error];
175 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassA withKeyUUID:newClassAKey.uuid zoneID:ckks.zoneID error: &error];
176 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassC withKeyUUID:newClassCKey.uuid zoneID:ckks.zoneID error: &error];
177
178 if(error != nil) {
179 ckkserror("ckkstlk", ckks, "couldn't make current key records: %@", error);
180 // TODO: this really isn't the error state, but a 'retry'.
181 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
182 return false;
183 }
184
185 // Wrap old TLK under the new TLK
186 wrappedOldTLK = [oldTLK copy];
187 if(wrappedOldTLK) {
188 [wrappedOldTLK ensureKeyLoaded: &error];
189 if(error != nil) {
190 ckkserror("ckkstlk", ckks, "couldn't unwrap TLK, aborting new TLK operation: %@", error);
191 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
192 return false;
193 }
194
195 [wrappedOldTLK wrapUnder: newTLK error:&error];
196 // TODO: should we continue in this error state? Might be required to fix broken TLKs/argue over which TLK should be used
197 if(error != nil) {
198 ckkserror("ckkstlk", ckks, "couldn't wrap oldTLK, aborting new TLK operation: %@", error);
199 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
200 return false;
201 }
202
203 wrappedOldTLK.currentkey = false;
204 }
205
206 [recordsToSave addObject: [newTLK CKRecordWithZoneID: ckks.zoneID]];
207 [recordsToSave addObject: [newClassAKey CKRecordWithZoneID: ckks.zoneID]];
208 [recordsToSave addObject: [newClassCKey CKRecordWithZoneID: ckks.zoneID]];
209
210 [recordsToSave addObject: [currentTLKPointer CKRecordWithZoneID: ckks.zoneID]];
211 [recordsToSave addObject: [currentClassAPointer CKRecordWithZoneID: ckks.zoneID]];
212 [recordsToSave addObject: [currentClassCPointer CKRecordWithZoneID: ckks.zoneID]];
213
214 if(wrappedOldTLK) {
215 [recordsToSave addObject: [wrappedOldTLK CKRecordWithZoneID: ckks.zoneID]];
216 }
217
218 // Save the proposed keys to the keychain. Note that we might reject this TLK later, but in that case, this TLK is just orphaned. No worries!
219 ckksnotice("ckkstlk", ckks, "Saving new keys %@ to database %@", recordsToSave, ckks.database);
220
221 [newTLK saveKeyMaterialToKeychain: &error];
222 [newClassAKey saveKeyMaterialToKeychain: &error];
223 [newClassCKey saveKeyMaterialToKeychain: &error];
224 if(error) {
225 self.error = error;
226 ckkserror("ckkstlk", ckks, "couldn't save new key material to keychain, aborting new TLK operation: %@", error);
227 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
228 return false;
229 }
230
231 // Generate the TLK sharing records for all trusted peers
232 NSMutableSet<CKKSTLKShare*>* tlkShares = [NSMutableSet set];
233 for(id<CKKSPeer> trustedPeer in ckks.currentTrustedPeers) {
234 if(!trustedPeer.publicEncryptionKey) {
235 ckksnotice("ckkstlk", ckks, "No need to make TLK for %@; they don't have any encryption keys", trustedPeer);
236 continue;
237 }
238
239 ckksnotice("ckkstlk", ckks, "Generating TLK(%@) share for %@", newTLK, trustedPeer);
240 CKKSTLKShare* share = [CKKSTLKShare share:newTLK as:ckks.currentSelfPeers.currentSelf to:trustedPeer epoch:-1 poisoned:0 error:&error];
241
242 [tlkShares addObject:share];
243 [recordsToSave addObject: [share CKRecordWithZoneID: ckks.zoneID]];
244 }
245
246 // Use the spare operation trick to wait for the CKModifyRecordsOperation to complete
247 self.cloudkitModifyOperationFinished = [NSBlockOperation named:@"newtlk-cloudkit-modify-operation-finished" withBlock:^{}];
248 [self dependOnBeforeGroupFinished: self.cloudkitModifyOperationFinished];
249
250 CKModifyRecordsOperation* modifyRecordsOp = nil;
251
252 // Get the CloudKit operation ready...
253 modifyRecordsOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave recordIDsToDelete:recordIDsToDelete];
254 modifyRecordsOp.atomic = YES;
255 modifyRecordsOp.longLived = NO; // The keys are only in memory; mark this explicitly not long-lived
256 modifyRecordsOp.qualityOfService = NSQualityOfServiceUserInitiated; // This needs to happen before CKKS is available for PCS/CloudKit use.
257 modifyRecordsOp.group = self.ckoperationGroup;
258 ckksnotice("ckkstlk", ckks, "Operation group is %@", self.ckoperationGroup);
259
260 NSMutableDictionary<CKRecordID*, CKRecord*>* attemptedRecords = [[NSMutableDictionary alloc] init];
261 for(CKRecord* record in recordsToSave) {
262 attemptedRecords[record] = record;
263 }
264
265 modifyRecordsOp.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
266 __strong __typeof(weakSelf) strongSelf = weakSelf;
267 __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
268
269 // These should all fail or succeed as one. Do the hard work in the records completion block.
270 if(!error) {
271 ckksnotice("ckkstlk", blockCKKS, "Successfully completed upload for %@", record.recordID.recordName);
272 } else {
273 ckkserror("ckkstlk", blockCKKS, "error on row: %@ %@", error, record);
274 }
275 };
276
277
278 modifyRecordsOp.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *ckerror) {
279 __strong __typeof(weakSelf) strongSelf = weakSelf;
280 __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
281 if(!strongSelf || !strongCKKS) {
282 ckkserror("ckkstlk", strongCKKS, "received callback for released object");
283 return;
284 }
285
286 [strongCKKS dispatchSyncWithAccountKeys: ^bool{
287 if(ckerror == nil) {
288 ckksnotice("ckkstlk", strongCKKS, "Completed TLK CloudKit operation");
289
290 // Success. Persist the keys to the CKKS database.
291 NSError* localerror = nil;
292
293 // Save the new CKRecords to the before persisting to database
294 for(CKRecord* record in savedRecords) {
295 if([newTLK matchesCKRecord: record]) {
296 newTLK.storedCKRecord = record;
297 } else if([newClassAKey matchesCKRecord: record]) {
298 newClassAKey.storedCKRecord = record;
299 } else if([newClassCKey matchesCKRecord: record]) {
300 newClassCKey.storedCKRecord = record;
301 } else if([wrappedOldTLK matchesCKRecord: record]) {
302 wrappedOldTLK.storedCKRecord = record;
303
304 } else if([currentTLKPointer matchesCKRecord: record]) {
305 currentTLKPointer.storedCKRecord = record;
306 } else if([currentClassAPointer matchesCKRecord: record]) {
307 currentClassAPointer.storedCKRecord = record;
308 } else if([currentClassCPointer matchesCKRecord: record]) {
309 currentClassCPointer.storedCKRecord = record;
310 }
311
312 for(CKKSTLKShare* share in tlkShares) {
313 if([share matchesCKRecord: record]) {
314 share.storedCKRecord = record;
315 }
316 }
317 }
318
319 [newTLK saveToDatabaseAsOnlyCurrentKeyForClassAndState: &localerror];
320 [newClassAKey saveToDatabaseAsOnlyCurrentKeyForClassAndState: &localerror];
321 [newClassCKey saveToDatabaseAsOnlyCurrentKeyForClassAndState: &localerror];
322
323 [currentTLKPointer saveToDatabase: &localerror];
324 [currentClassAPointer saveToDatabase: &localerror];
325 [currentClassCPointer saveToDatabase: &localerror];
326
327 [wrappedOldTLK saveToDatabase: &localerror];
328
329 for(CKKSTLKShare* share in tlkShares) {
330 [share saveToDatabase:&localerror];
331 }
332
333 // TLKs are already saved in the local keychain; fire off a backup
334 CKKSNearFutureScheduler* tlkNotifier = strongCKKS.savedTLKNotifier;
335 ckksnotice("ckkstlk", strongCKKS, "triggering new TLK notification: %@", tlkNotifier);
336 [tlkNotifier trigger];
337
338 if(localerror != nil) {
339 ckkserror("ckkstlk", strongCKKS, "couldn't save new key hierarchy to database; this is very bad: %@", localerror);
340 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: localerror];
341 return false;
342 } else {
343 // Everything is groovy.
344 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateReady withError: nil];
345 }
346 } else {
347 ckkserror("ckkstlk", strongCKKS, "couldn't save new key hierarchy to CloudKit: %@", ckerror);
348 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateNewTLKsFailed withError: nil];
349
350 [strongCKKS _onqueueCKWriteFailed:ckerror attemptedRecordsChanged:attemptedRecords];
351
352 // Delete these keys from the keychain only if CloudKit positively told us the write failed.
353 // Other failures _might_ leave the writes written to cloudkit; who can say?
354 if([ckerror ckksIsCKErrorRecordChangedError]) {
355 NSError* localerror = nil;
356 [newTLK deleteKeyMaterialFromKeychain: &localerror];
357 [newClassAKey deleteKeyMaterialFromKeychain: &localerror];
358 [newClassCKey deleteKeyMaterialFromKeychain: &localerror];
359 if(localerror) {
360 ckkserror("ckkstlk", strongCKKS, "couldn't delete now-useless key material from keychain: %@", localerror);
361 }
362 } else {
363 ckksnotice("ckkstlk", strongCKKS, "Error is too scary; not deleting likely-useless key material from keychain");
364 }
365 }
366 return true;
367 }];
368
369 // Notify that we're done
370 [strongSelf.operationQueue addOperation: strongSelf.cloudkitModifyOperationFinished];
371 };
372
373 [ckks.database addOperation: modifyRecordsOp];
374 return true;
375 }];
376 }
377
378 - (void)cancel {
379 [self.cloudkitModifyOperationFinished cancel];
380 [super cancel];
381 }
382
383 @end;
384
385 #endif