]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSFixups.m
Security-58286.41.2.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
31 @implementation CKKSFixups
32 +(CKKSGroupOperation*)fixup:(CKKSFixup)lastfixup for:(CKKSKeychainView*)keychainView
33 {
34 if(lastfixup == CKKSCurrentFixupNumber) {
35 return nil;
36 }
37
38 CKOperationGroup* fixupCKGroup = [CKOperationGroup CKKSGroupWithName:@"fixup"];
39 CKKSGroupOperation* fixups = [[CKKSGroupOperation alloc] init];
40 fixups.name = @"ckks-fixups";
41
42 CKKSResultOperation* previousOp = keychainView.holdFixupOperation;
43
44 if(lastfixup < CKKSFixupRefetchCurrentItemPointers) {
45 CKKSResultOperation* refetch = [[CKKSFixupRefetchAllCurrentItemPointers alloc] initWithCKKSKeychainView:keychainView
46 ckoperationGroup:fixupCKGroup];
47 [refetch addNullableDependency:previousOp];
48 [fixups runBeforeGroupFinished:refetch];
49 previousOp = refetch;
50 }
51
52 if(lastfixup < CKKSFixupFetchTLKShares) {
53 CKKSResultOperation* fetchShares = [[CKKSFixupFetchAllTLKShares alloc] initWithCKKSKeychainView:keychainView
54 ckoperationGroup:fixupCKGroup];
55 [fetchShares addNullableSuccessDependency:previousOp];
56 [fixups runBeforeGroupFinished:fetchShares];
57 previousOp = fetchShares;
58 }
59
60 if(lastfixup < CKKSFixupLocalReload) {
61 CKKSResultOperation* localSync = [[CKKSFixupLocalReloadOperation alloc] initWithCKKSKeychainView:keychainView
62 ckoperationGroup:fixupCKGroup];
63 [localSync addNullableSuccessDependency:previousOp];
64 [fixups runBeforeGroupFinished:localSync];
65 previousOp = localSync;
66 }
67
68 (void)previousOp;
69 return fixups;
70 }
71 @end
72
73 #pragma mark - CKKSFixupRefetchAllCurrentItemPointers
74
75 @interface CKKSFixupRefetchAllCurrentItemPointers ()
76 @property CKOperationGroup* group;
77 @end
78
79 // In <rdar://problem/34916549> CKKS: current item pointer CKRecord resurrection,
80 // We found that some devices could end up with multiple current item pointer records for a given record ID.
81 // This fixup will fetch all CKRecords matching any existing current item pointer records, and then trigger processing.
82 @implementation CKKSFixupRefetchAllCurrentItemPointers
83 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
84 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
85 {
86 if((self = [super init])) {
87 _ckks = keychainView;
88 _group = ckoperationGroup;
89 }
90 return self;
91 }
92
93 - (NSString*)description {
94 return [NSString stringWithFormat:@"<CKKSFixup:RefetchAllCurrentItemPointers (%@)>", self.ckks];
95 }
96 - (void)groupStart {
97 CKKSKeychainView* ckks = self.ckks;
98 if(!ckks) {
99 ckkserror("ckksfixup", ckks, "no CKKS object");
100 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
101 return;
102 }
103
104 [ckks dispatchSyncWithAccountKeys:^bool {
105 NSError* error = nil;
106
107 NSArray<CKKSCurrentItemPointer*>* cips = [CKKSCurrentItemPointer allInZone: ckks.zoneID error:&error];
108 if(error) {
109 ckkserror("ckksfixup", ckks, "Couldn't fetch current item pointers: %@", error);
110 return false;
111 }
112
113 NSMutableSet<CKRecordID*>* recordIDs = [NSMutableSet set];
114 for(CKKSCurrentItemPointer* cip in cips) {
115 CKRecordID* recordID = cip.storedCKRecord.recordID;
116 if(recordID) {
117 ckksnotice("ckksfixup", ckks, "Re-fetching %@ for %@", recordID, cip);
118 [recordIDs addObject:recordID];
119 } else {
120 ckkserror("ckksfixup", ckks, "No record ID for stored %@", cip);
121 }
122 }
123
124 if(recordIDs.count == 0) {
125 ckksnotice("ckksfixup", ckks, "No existing CIPs; fixup complete");
126 }
127
128 __weak __typeof(self) weakSelf = self;
129 NSBlockOperation* doneOp = [NSBlockOperation named:@"fetch-records-operation-complete" withBlock:^{}];
130 id<CKKSFetchRecordsOperation> fetch = [[ckks.fetchRecordsOperationClass alloc] initWithRecordIDs: [recordIDs allObjects]];
131 fetch.fetchRecordsCompletionBlock = ^(NSDictionary<CKRecordID *,CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable error) {
132 __strong __typeof(self) strongSelf = weakSelf;
133 CKKSKeychainView* strongCKKS = strongSelf.ckks;
134
135 [strongCKKS dispatchSync:^bool{
136 if(error) {
137 ckkserror("ckksfixup", strongCKKS, "Finished record fetch with error: %@", error);
138
139 NSDictionary<CKRecordID*,NSError*>* partialErrors = error.userInfo[CKPartialErrorsByItemIDKey];
140 if([error.domain isEqualToString:CKErrorDomain] && error.code == CKErrorPartialFailure && partialErrors) {
141 // Check if any of these records no longer exist on the server
142 for(CKRecordID* recordID in partialErrors.keyEnumerator) {
143 NSError* recordError = partialErrors[recordID];
144 if(recordError && [recordError.domain isEqualToString:CKErrorDomain] && recordError.code == CKErrorUnknownItem) {
145 ckkserror("ckksfixup", strongCKKS, "CloudKit believes %@ no longer exists", recordID);
146 [strongCKKS _onqueueCKRecordDeleted: recordID recordType:SecCKRecordCurrentItemType resync:true];
147 } else {
148 ckkserror("ckksfixup", strongCKKS, "Unknown error for %@: %@", recordID, error);
149 strongSelf.error = error;
150 }
151 }
152 } else {
153 strongSelf.error = error;
154 }
155 } else {
156 ckksnotice("ckksfixup", strongCKKS, "Finished record fetch successfully");
157 }
158
159 for(CKRecordID* recordID in recordsByRecordID) {
160 CKRecord* record = recordsByRecordID[recordID];
161 ckksnotice("ckksfixup", strongCKKS, "Recieved record %@", record);
162 [self.ckks _onqueueCKRecordChanged:record resync:true];
163 }
164
165 if(!strongSelf.error) {
166 // Now, update the zone state entry to be at this level
167 NSError* localerror = nil;
168 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
169 ckse.lastFixup = CKKSFixupRefetchCurrentItemPointers;
170 [ckse saveToDatabase:&localerror];
171 if(localerror) {
172 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
173 } else {
174 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupRefetchCurrentItemPointers");
175 }
176 }
177
178 [strongSelf runBeforeGroupFinished:doneOp];
179 return true;
180 }];
181 };
182 [ckks.database addOperation: fetch];
183 [self dependOnBeforeGroupFinished:fetch];
184 [self dependOnBeforeGroupFinished:doneOp];
185
186 return true;
187 }];
188 }
189 @end
190
191 #pragma mark - CKKSFixupFetchAllTLKShares
192
193 @interface CKKSFixupFetchAllTLKShares ()
194 @property CKOperationGroup* group;
195 @end
196
197 // In <rdar://problem/34901306> CKKSTLK: TLKShare CloudKit upload/download on TLK change, trust set addition
198 // We introduced TLKShare records.
199 // Older devices will throw them away, so on upgrade, they must refetch them
200 @implementation CKKSFixupFetchAllTLKShares
201 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
202 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
203 {
204 if((self = [super init])) {
205 _ckks = keychainView;
206 _group = ckoperationGroup;
207 }
208 return self;
209 }
210
211 - (NSString*)description {
212 return [NSString stringWithFormat:@"<CKKSFixup:FetchAllTLKShares (%@)>", self.ckks];
213 }
214 - (void)groupStart {
215 CKKSKeychainView* ckks = self.ckks;
216 if(!ckks) {
217 ckkserror("ckksfixup", ckks, "no CKKS object");
218 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
219 return;
220 }
221
222 [ckks dispatchSyncWithAccountKeys:^bool {
223 __weak __typeof(self) weakSelf = self;
224 NSBlockOperation* doneOp = [NSBlockOperation named:@"fetch-records-operation-complete" withBlock:^{}];
225
226 NSPredicate *yes = [NSPredicate predicateWithValue:YES];
227 CKQuery *query = [[CKQuery alloc] initWithRecordType:SecCKRecordTLKShareType predicate:yes];
228
229 id<CKKSQueryOperation> fetch = [[ckks.queryOperationClass alloc] initWithQuery:query];
230 fetch.zoneID = ckks.zoneID;
231 fetch.desiredKeys = nil;
232
233 fetch.recordFetchedBlock = ^(CKRecord * _Nonnull record) {
234 __strong __typeof(self) strongSelf = weakSelf;
235 CKKSKeychainView* strongCKKS = strongSelf.ckks;
236 [strongCKKS dispatchSync:^bool{
237 ckksnotice("ckksfixup", strongCKKS, "Recieved tlk share record from query: %@", record);
238
239 [strongCKKS _onqueueCKRecordChanged:record resync:true];
240 return true;
241 }];
242 };
243
244 fetch.queryCompletionBlock = ^(CKQueryCursor * _Nullable cursor, NSError * _Nullable error) {
245 __strong __typeof(self) strongSelf = weakSelf;
246 CKKSKeychainView* strongCKKS = strongSelf.ckks;
247
248 [strongCKKS dispatchSync:^bool{
249 if(error) {
250 ckkserror("ckksfixup", strongCKKS, "Couldn't fetch all TLKShare records: %@", error);
251 strongSelf.error = error;
252 return false;
253 }
254
255 ckksnotice("ckksfixup", strongCKKS, "Successfully fetched TLKShare records (%@)", cursor);
256
257 NSError* localerror = nil;
258 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
259 ckse.lastFixup = CKKSFixupFetchTLKShares;
260 [ckse saveToDatabase:&localerror];
261 if(localerror) {
262 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
263 } else {
264 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupFetchTLKShares");
265 }
266 return true;
267 }];
268 [strongSelf runBeforeGroupFinished:doneOp];
269 };
270
271 [ckks.database addOperation: fetch];
272 [self dependOnBeforeGroupFinished:fetch];
273 [self dependOnBeforeGroupFinished:doneOp];
274
275 return true;
276 }];
277 }
278 @end
279
280 #pragma mark - CKKSFixupLocalReloadOperation
281
282 @interface CKKSFixupLocalReloadOperation ()
283 @property CKOperationGroup* group;
284 @end
285
286 // In <rdar://problem/35540228> Server Generated CloudKit "Manatee Identity Lost"
287 // items could be deleted from the local keychain after CKKS believed they were already synced, and therefore wouldn't resync
288 // Perform a local resync operation
289 @implementation CKKSFixupLocalReloadOperation
290 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
291 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
292 {
293 if((self = [super init])) {
294 _ckks = keychainView;
295 _group = ckoperationGroup;
296 }
297 return self;
298 }
299
300 - (NSString*)description {
301 return [NSString stringWithFormat:@"<CKKSFixup:LocalReload (%@)>", self.ckks];
302 }
303 - (void)groupStart {
304 CKKSKeychainView* ckks = self.ckks;
305 __weak __typeof(self) weakSelf = self;
306 if(!ckks) {
307 ckkserror("ckksfixup", ckks, "no CKKS object");
308 self.error = [NSError errorWithDomain:CKKSErrorDomain code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
309 return;
310 }
311
312 CKKSResultOperation* reload = [[CKKSReloadAllItemsOperation alloc] initWithCKKSKeychainView:ckks];
313 [self runBeforeGroupFinished:reload];
314
315 CKKSResultOperation* cleanup = [CKKSResultOperation named:@"local-reload-cleanup" withBlock:^{
316 __strong __typeof(self) strongSelf = weakSelf;
317 __strong __typeof(self.ckks) strongCKKS = strongSelf.ckks;
318 [strongCKKS dispatchSync:^bool{
319 if(reload.error) {
320 ckkserror("ckksfixup", strongCKKS, "Couldn't perform a reload: %@", reload.error);
321 strongSelf.error = reload.error;
322 return false;
323 }
324
325 ckksnotice("ckksfixup", strongCKKS, "Successfully performed a reload fixup");
326
327 NSError* localerror = nil;
328 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
329 ckse.lastFixup = CKKSFixupLocalReload;
330 [ckse saveToDatabase:&localerror];
331 if(localerror) {
332 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
333 } else {
334 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupLocalReload");
335 }
336 return true;
337 }];
338 }];
339 [cleanup addNullableDependency:reload];
340 [self runBeforeGroupFinished:cleanup];
341 }
342 @end
343
344 #endif // OCTAGON