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