]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSFixups.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSFixups.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 "keychain/ckks/CKKSFixups.h"
27 #import "keychain/ckks/CloudKitCategories.h"
28 #import "keychain/ckks/CKKSCurrentItemPointer.h"
29 #import "keychain/ckks/CKKSZoneStateEntry.h"
30 #import "keychain/categories/NSError+UsefulConstructors.h"
31 #import "keychain/ot/ObjCImprovements.h"
32
33 @implementation CKKSFixups
34 +(nullable CKKSGroupOperation*)fixup:(CKKSFixup)lastfixup for:(CKKSKeychainView*)keychainView
35 {
36 if(lastfixup == CKKSCurrentFixupNumber) {
37 return nil;
38 }
39
40 CKOperationGroup* fixupCKGroup = [CKOperationGroup CKKSGroupWithName:@"fixup"];
41 CKKSGroupOperation* fixups = [[CKKSGroupOperation alloc] init];
42 fixups.name = @"ckks-fixups";
43
44 CKKSResultOperation* previousOp = keychainView.holdFixupOperation;
45
46 if(lastfixup < CKKSFixupRefetchCurrentItemPointers) {
47 CKKSResultOperation* refetch = [[CKKSFixupRefetchAllCurrentItemPointers alloc] initWithCKKSKeychainView:keychainView
48 ckoperationGroup:fixupCKGroup];
49 [refetch addNullableDependency:previousOp];
50 [fixups runBeforeGroupFinished:refetch];
51 previousOp = refetch;
52 }
53
54 if(lastfixup < CKKSFixupFetchTLKShares) {
55 CKKSResultOperation* fetchShares = [[CKKSFixupFetchAllTLKShares alloc] initWithCKKSKeychainView:keychainView
56 ckoperationGroup:fixupCKGroup];
57 [fetchShares addNullableSuccessDependency:previousOp];
58 [fixups runBeforeGroupFinished:fetchShares];
59 previousOp = fetchShares;
60 }
61
62 if(lastfixup < CKKSFixupLocalReload) {
63 CKKSResultOperation* localSync = [[CKKSFixupLocalReloadOperation alloc] initWithCKKSKeychainView:keychainView
64 fixupNumber:CKKSFixupLocalReload
65 ckoperationGroup:fixupCKGroup];
66 [localSync addNullableSuccessDependency:previousOp];
67 [fixups runBeforeGroupFinished:localSync];
68 previousOp = localSync;
69 }
70
71 if(lastfixup < CKKSFixupResaveDeviceStateEntries) {
72 CKKSResultOperation* resave = [[CKKSFixupResaveDeviceStateEntriesOperation alloc] initWithCKKSKeychainView:keychainView];
73
74 [resave addNullableSuccessDependency:previousOp];
75 [fixups runBeforeGroupFinished:resave];
76 previousOp = resave;
77 }
78
79 if(lastfixup < CKKSFixupDeleteAllCKKSTombstones) {
80 CKKSResultOperation* localSync = [[CKKSFixupLocalReloadOperation alloc] initWithCKKSKeychainView:keychainView
81 fixupNumber:CKKSFixupDeleteAllCKKSTombstones
82 ckoperationGroup:fixupCKGroup];
83 [localSync addNullableSuccessDependency:previousOp];
84 [fixups runBeforeGroupFinished:localSync];
85 previousOp = localSync;
86 }
87
88 (void)previousOp;
89 return fixups;
90 }
91 @end
92
93 #pragma mark - CKKSFixupRefetchAllCurrentItemPointers
94
95 @interface CKKSFixupRefetchAllCurrentItemPointers ()
96 @property CKOperationGroup* group;
97 @end
98
99 // In <rdar://problem/34916549> CKKS: current item pointer CKRecord resurrection,
100 // We found that some devices could end up with multiple current item pointer records for a given record ID.
101 // This fixup will fetch all CKRecords matching any existing current item pointer records, and then trigger processing.
102 @implementation CKKSFixupRefetchAllCurrentItemPointers
103 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
104 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
105 {
106 if((self = [super init])) {
107 _ckks = keychainView;
108 _group = ckoperationGroup;
109 }
110 return self;
111 }
112
113 - (NSString*)description {
114 return [NSString stringWithFormat:@"<CKKSFixup:RefetchAllCurrentItemPointers (%@)>", self.ckks];
115 }
116 - (void)groupStart {
117 CKKSKeychainView* ckks = self.ckks;
118 if(!ckks) {
119 ckkserror("ckksfixup", ckks, "no CKKS object");
120 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
121 return;
122 }
123
124 [ckks dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
125 NSError* error = nil;
126
127 NSArray<CKKSCurrentItemPointer*>* cips = [CKKSCurrentItemPointer allInZone: ckks.zoneID error:&error];
128 if(error) {
129 ckkserror("ckksfixup", ckks, "Couldn't fetch current item pointers: %@", error);
130 return CKKSDatabaseTransactionRollback;
131 }
132
133 NSMutableSet<CKRecordID*>* recordIDs = [NSMutableSet set];
134 for(CKKSCurrentItemPointer* cip in cips) {
135 CKRecordID* recordID = cip.storedCKRecord.recordID;
136 if(recordID) {
137 ckksnotice("ckksfixup", ckks, "Re-fetching %@ for %@", recordID, cip);
138 [recordIDs addObject:recordID];
139 } else {
140 ckkserror("ckksfixup", ckks, "No record ID for stored %@", cip);
141 }
142 }
143
144 if(recordIDs.count == 0) {
145 ckksnotice("ckksfixup", ckks, "No existing CIPs; fixup complete");
146 }
147
148 WEAKIFY(self);
149 NSBlockOperation* doneOp = [NSBlockOperation named:@"fetch-records-operation-complete" withBlock:^{}];
150 CKDatabaseOperation<CKKSFetchRecordsOperation>* fetch = [[ckks.cloudKitClassDependencies.fetchRecordsOperationClass alloc] initWithRecordIDs: [recordIDs allObjects]];
151 fetch.fetchRecordsCompletionBlock = ^(NSDictionary<CKRecordID *,CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable error) {
152 STRONGIFY(self);
153 CKKSKeychainView* strongCKKS = self.ckks;
154
155 [strongCKKS dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
156 if(error) {
157 ckkserror("ckksfixup", strongCKKS, "Finished record fetch with error: %@", error);
158
159 NSDictionary<CKRecordID*,NSError*>* partialErrors = error.userInfo[CKPartialErrorsByItemIDKey];
160 if([error.domain isEqualToString:CKErrorDomain] && error.code == CKErrorPartialFailure && partialErrors) {
161 // Check if any of these records no longer exist on the server
162 for(CKRecordID* recordID in partialErrors.keyEnumerator) {
163 NSError* recordError = partialErrors[recordID];
164 if(recordError && [recordError.domain isEqualToString:CKErrorDomain] && recordError.code == CKErrorUnknownItem) {
165 ckkserror("ckksfixup", strongCKKS, "CloudKit believes %@ no longer exists", recordID);
166 [strongCKKS _onqueueCKRecordDeleted: recordID recordType:SecCKRecordCurrentItemType resync:true];
167 } else {
168 ckkserror("ckksfixup", strongCKKS, "Unknown error for %@: %@", recordID, error);
169 self.error = error;
170 }
171 }
172 } else {
173 self.error = error;
174 }
175 } else {
176 ckksnotice("ckksfixup", strongCKKS, "Finished record fetch successfully");
177 }
178
179 for(CKRecordID* recordID in recordsByRecordID) {
180 CKRecord* record = recordsByRecordID[recordID];
181 ckksnotice("ckksfixup", strongCKKS, "Recieved record %@", record);
182 [self.ckks _onqueueCKRecordChanged:record resync:true];
183 }
184
185 if(!self.error) {
186 // Now, update the zone state entry to be at this level
187 NSError* localerror = nil;
188 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
189 ckse.lastFixup = CKKSFixupRefetchCurrentItemPointers;
190 [ckse saveToDatabase:&localerror];
191 if(localerror) {
192 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
193 } else {
194 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupRefetchCurrentItemPointers");
195 }
196 }
197
198 [self runBeforeGroupFinished:doneOp];
199 return CKKSDatabaseTransactionCommit;
200 }];
201 };
202 [ckks.database addOperation: fetch];
203 [self dependOnBeforeGroupFinished:fetch];
204 [self dependOnBeforeGroupFinished:doneOp];
205
206 return CKKSDatabaseTransactionCommit;
207 }];
208 }
209 @end
210
211 #pragma mark - CKKSFixupFetchAllTLKShares
212
213 @interface CKKSFixupFetchAllTLKShares ()
214 @property CKOperationGroup* group;
215 @end
216
217 // In <rdar://problem/34901306> CKKSTLK: TLKShare CloudKit upload/download on TLK change, trust set addition
218 // We introduced TLKShare records.
219 // Older devices will throw them away, so on upgrade, they must refetch them
220 @implementation CKKSFixupFetchAllTLKShares
221 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
222 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
223 {
224 if((self = [super init])) {
225 _ckks = keychainView;
226 _group = ckoperationGroup;
227 }
228 return self;
229 }
230
231 - (NSString*)description {
232 return [NSString stringWithFormat:@"<CKKSFixup:FetchAllTLKShares (%@)>", self.ckks];
233 }
234 - (void)groupStart {
235 CKKSKeychainView* ckks = self.ckks;
236 if(!ckks) {
237 ckkserror("ckksfixup", ckks, "no CKKS object");
238 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
239 return;
240 }
241
242 [ckks dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
243 WEAKIFY(self);
244 NSBlockOperation* doneOp = [NSBlockOperation named:@"fetch-records-operation-complete" withBlock:^{}];
245
246 NSPredicate *yes = [NSPredicate predicateWithValue:YES];
247 CKQuery *query = [[CKQuery alloc] initWithRecordType:SecCKRecordTLKShareType predicate:yes];
248
249 CKDatabaseOperation<CKKSQueryOperation>* fetch = [[ckks.cloudKitClassDependencies.queryOperationClass alloc] initWithQuery:query];
250 fetch.zoneID = ckks.zoneID;
251 fetch.desiredKeys = nil;
252
253 fetch.recordFetchedBlock = ^(CKRecord * _Nonnull record) {
254 STRONGIFY(self);
255 CKKSKeychainView* strongCKKS = self.ckks;
256 [strongCKKS dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
257 ckksnotice("ckksfixup", strongCKKS, "Recieved tlk share record from query: %@", record);
258
259 [strongCKKS _onqueueCKRecordChanged:record resync:true];
260 return CKKSDatabaseTransactionCommit;
261 }];
262 };
263
264 fetch.queryCompletionBlock = ^(CKQueryCursor * _Nullable cursor, NSError * _Nullable error) {
265 STRONGIFY(self);
266 CKKSKeychainView* strongCKKS = self.ckks;
267
268 [strongCKKS dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
269 if(error) {
270 ckkserror("ckksfixup", strongCKKS, "Couldn't fetch all TLKShare records: %@", error);
271 self.error = error;
272 return CKKSDatabaseTransactionRollback;
273 }
274
275 ckksnotice("ckksfixup", strongCKKS, "Successfully fetched TLKShare records (%@)", cursor);
276
277 NSError* localerror = nil;
278 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
279 ckse.lastFixup = CKKSFixupFetchTLKShares;
280 [ckse saveToDatabase:&localerror];
281 if(localerror) {
282 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
283 } else {
284 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupFetchTLKShares");
285 }
286 return CKKSDatabaseTransactionCommit;
287 }];
288 [self runBeforeGroupFinished:doneOp];
289 };
290
291 [ckks.database addOperation: fetch];
292 [self dependOnBeforeGroupFinished:fetch];
293 [self dependOnBeforeGroupFinished:doneOp];
294
295 return CKKSDatabaseTransactionCommit;
296 }];
297 }
298 @end
299
300 #pragma mark - CKKSFixupLocalReloadOperation
301
302 @interface CKKSFixupLocalReloadOperation ()
303 @property CKOperationGroup* group;
304 @property CKKSFixup fixupNumber;
305 @end
306
307 // In <rdar://problem/35540228> Server Generated CloudKit "Manatee Identity Lost"
308 // items could be deleted from the local keychain after CKKS believed they were already synced, and therefore wouldn't resync
309 // Perform a local resync operation
310 //
311 // In <rdar://problem/60650208> CKKS: adjust UUID and tombstone handling
312 // some CKKS participants uploaded entries with tomb=1 to CKKS
313 // Performing a local reload operation should find and delete those entries
314 @implementation CKKSFixupLocalReloadOperation
315 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
316 fixupNumber:(CKKSFixup)fixupNumber
317 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
318 {
319 if((self = [super init])) {
320 _ckks = keychainView;
321 _fixupNumber = fixupNumber;
322 _group = ckoperationGroup;
323 }
324 return self;
325 }
326
327 - (NSString*)description {
328 return [NSString stringWithFormat:@"<CKKSFixup:LocalReload (%d)(%@)>", (int)self.fixupNumber, self.ckks];
329 }
330 - (void)groupStart {
331 CKKSKeychainView* ckks = self.ckks;
332 WEAKIFY(self);
333 if(!ckks) {
334 ckkserror("ckksfixup", ckks, "no CKKS object");
335 self.error = [NSError errorWithDomain:CKKSErrorDomain code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
336 return;
337 }
338
339 CKKSResultOperation* reload = [[CKKSReloadAllItemsOperation alloc] initWithCKKSKeychainView:ckks];
340 [self runBeforeGroupFinished:reload];
341
342 CKKSResultOperation* cleanup = [CKKSResultOperation named:@"local-reload-cleanup" withBlock:^{
343 STRONGIFY(self);
344 __strong __typeof(self.ckks) strongCKKS = self.ckks;
345 [strongCKKS dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
346 if(reload.error) {
347 ckkserror("ckksfixup", strongCKKS, "Couldn't perform a reload: %@", reload.error);
348 self.error = reload.error;
349 return CKKSDatabaseTransactionRollback;
350 }
351
352 ckksnotice("ckksfixup", strongCKKS, "Successfully performed a reload fixup. New fixup number is %d",
353 (int)self.fixupNumber);
354
355 NSError* localerror = nil;
356 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
357 ckse.lastFixup = self.fixupNumber;
358 [ckse saveToDatabase:&localerror];
359 if(localerror) {
360 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
361 } else {
362 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupLocalReload");
363 }
364 return CKKSDatabaseTransactionCommit;
365 }];
366 }];
367 [cleanup addNullableDependency:reload];
368 [self runBeforeGroupFinished:cleanup];
369 }
370 @end
371
372 #pragma mark - CKKSFixupResaveDeviceStateEntriesOperation
373
374 @implementation CKKSFixupResaveDeviceStateEntriesOperation: CKKSGroupOperation
375
376 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
377 {
378 if((self = [super init])) {
379 _ckks = keychainView;
380 }
381 return self;
382 }
383
384 - (NSString*)description {
385 return [NSString stringWithFormat:@"<CKKSFixup:ResaveCDSE (%@)>", self.ckks];
386 }
387
388 - (void)groupStart {
389 CKKSKeychainView* ckks = self.ckks;
390
391 if(!ckks) {
392 ckkserror("ckksfixup", ckks, "no CKKS object");
393 self.error = [NSError errorWithDomain:CKKSErrorDomain code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
394 return;
395 }
396
397 // This operation simply loads all CDSEs, remakes them from their CKRecord, and resaves them
398 [ckks dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
399 NSError* error = nil;
400 NSArray<CKKSDeviceStateEntry*>* cdses = [CKKSDeviceStateEntry allInZone:ckks.zoneID
401 error:&error];
402
403 if(error) {
404 ckkserror("ckksfixup", ckks, "Unable to fetch all CDSEs: %@", error);
405 self.error = error;
406 return CKKSDatabaseTransactionRollback;
407 }
408
409 for(CKKSDeviceStateEntry* cdse in cdses) {
410 CKRecord* record = cdse.storedCKRecord;
411
412 if(record) {
413 [cdse setFromCKRecord:record];
414 [cdse saveToDatabase:&error];
415 if(error) {
416 ckkserror("ckksfixup", ckks, "Unable to save CDSE: %@", error);
417 self.error = error;
418 return CKKSDatabaseTransactionRollback;
419 }
420 } else {
421 ckksnotice("ckksfixup", ckks, "Saved CDSE has no stored record: %@", cdse);
422 }
423 }
424
425 ckksnotice("ckksfixup", ckks, "Successfully performed a ResaveDeviceState fixup");
426
427 NSError* localerror = nil;
428 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:ckks.zoneName error:&localerror];
429 ckse.lastFixup = CKKSFixupResaveDeviceStateEntries;
430 [ckse saveToDatabase:&localerror];
431 if(localerror) {
432 ckkserror("ckksfixup", ckks, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
433 self.error = localerror;
434 return CKKSDatabaseTransactionRollback;
435 }
436
437 ckksnotice("ckksfixup", ckks, "Updated zone fixup state to CKKSFixupResaveDeviceStateEntries");
438
439 return CKKSDatabaseTransactionCommit;
440 }];
441 }
442
443
444 @end
445
446 #endif // OCTAGON