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