]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSFixups.m
Security-58286.31.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 = nil;
43
44 if(lastfixup < CKKSFixupRefetchCurrentItemPointers) {
45 CKKSResultOperation* refetch = [[CKKSFixupRefetchAllCurrentItemPointers alloc] initWithCKKSKeychainView:keychainView
46 ckoperationGroup:fixupCKGroup];
47 [refetch addNullableSuccessDependency:previousOp];
48 [fixups runBeforeGroupFinished:refetch];
49 previousOp = refetch;
50 }
51
52 if(SecCKKSShareTLKs() && 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 return fixups;
61 }
62 @end
63
64 #pragma mark - CKKSFixupRefetchAllCurrentItemPointers
65
66 @interface CKKSFixupRefetchAllCurrentItemPointers ()
67 @property CKOperationGroup* group;
68 @end
69
70 // In <rdar://problem/34916549> CKKS: current item pointer CKRecord resurrection,
71 // We found that some devices could end up with multiple current item pointer records for a given record ID.
72 // This fixup will fetch all CKRecords matching any existing current item pointer records, and then trigger processing.
73 @implementation CKKSFixupRefetchAllCurrentItemPointers
74 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
75 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
76 {
77 if((self = [super init])) {
78 _ckks = keychainView;
79 _group = ckoperationGroup;
80 }
81 return self;
82 }
83
84 - (NSString*)description {
85 return [NSString stringWithFormat:@"<CKKSFixup:RefetchAllCurrentItemPointers (%@)>", self.ckks];
86 }
87 - (void)groupStart {
88 CKKSKeychainView* ckks = self.ckks;
89 if(!ckks) {
90 ckkserror("ckksfixup", ckks, "no CKKS object");
91 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
92 return;
93 }
94
95 [ckks dispatchSyncWithAccountKeys:^bool {
96 NSError* error = nil;
97
98 NSArray<CKKSCurrentItemPointer*>* cips = [CKKSCurrentItemPointer allInZone: ckks.zoneID error:&error];
99 if(error) {
100 ckkserror("ckksfixup", ckks, "Couldn't fetch current item pointers: %@", error);
101 return false;
102 }
103
104 NSMutableSet<CKRecordID*>* recordIDs = [NSMutableSet set];
105 for(CKKSCurrentItemPointer* cip in cips) {
106 CKRecordID* recordID = cip.storedCKRecord.recordID;
107 if(recordID) {
108 ckksnotice("ckksfixup", ckks, "Re-fetching %@ for %@", recordID, cip);
109 [recordIDs addObject:recordID];
110 } else {
111 ckkserror("ckksfixup", ckks, "No record ID for stored %@", cip);
112 }
113 }
114
115 if(recordIDs.count == 0) {
116 ckksnotice("ckksfixup", ckks, "No existing CIPs; fixup complete");
117 }
118
119 __weak __typeof(self) weakSelf = self;
120 NSBlockOperation* doneOp = [NSBlockOperation named:@"fetch-records-operation-complete" withBlock:^{}];
121 id<CKKSFetchRecordsOperation> fetch = [[ckks.fetchRecordsOperationClass alloc] initWithRecordIDs: [recordIDs allObjects]];
122 fetch.fetchRecordsCompletionBlock = ^(NSDictionary<CKRecordID *,CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable error) {
123 __strong __typeof(self) strongSelf = weakSelf;
124 CKKSKeychainView* strongCKKS = strongSelf.ckks;
125
126 [strongCKKS dispatchSync:^bool{
127 if(error) {
128 ckkserror("ckksfixup", strongCKKS, "Finished record fetch with error: %@", error);
129
130 NSDictionary<CKRecordID*,NSError*>* partialErrors = error.userInfo[CKPartialErrorsByItemIDKey];
131 if([error.domain isEqualToString:CKErrorDomain] && error.code == CKErrorPartialFailure && partialErrors) {
132 // Check if any of these records no longer exist on the server
133 for(CKRecordID* recordID in partialErrors.keyEnumerator) {
134 NSError* recordError = partialErrors[recordID];
135 if(recordError && [recordError.domain isEqualToString:CKErrorDomain] && recordError.code == CKErrorUnknownItem) {
136 ckkserror("ckksfixup", strongCKKS, "CloudKit believes %@ no longer exists", recordID);
137 [strongCKKS _onqueueCKRecordDeleted: recordID recordType:SecCKRecordCurrentItemType resync:true];
138 } else {
139 ckkserror("ckksfixup", strongCKKS, "Unknown error for %@: %@", recordID, error);
140 strongSelf.error = error;
141 }
142 }
143 } else {
144 strongSelf.error = error;
145 }
146 } else {
147 ckksnotice("ckksfixup", strongCKKS, "Finished record fetch successfully");
148 }
149
150 for(CKRecordID* recordID in recordsByRecordID) {
151 CKRecord* record = recordsByRecordID[recordID];
152 ckksnotice("ckksfixup", strongCKKS, "Recieved record %@", record);
153 [self.ckks _onqueueCKRecordChanged:record resync:true];
154 }
155
156 if(!strongSelf.error) {
157 // Now, update the zone state entry to be at this level
158 NSError* localerror = nil;
159 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
160 ckse.lastFixup = CKKSFixupRefetchCurrentItemPointers;
161 [ckse saveToDatabase:&localerror];
162 if(localerror) {
163 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
164 } else {
165 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupRefetchCurrentItemPointers");
166 }
167 }
168
169 [strongSelf runBeforeGroupFinished:doneOp];
170 return true;
171 }];
172 };
173 [ckks.database addOperation: fetch];
174 [self dependOnBeforeGroupFinished:fetch];
175 [self dependOnBeforeGroupFinished:doneOp];
176
177 return true;
178 }];
179 }
180 @end
181
182 #pragma mark - CKKSFixupFetchAllTLKShares
183
184 @interface CKKSFixupFetchAllTLKShares ()
185 @property CKOperationGroup* group;
186 @end
187
188 // In <rdar://problem/34901306> CKKSTLK: TLKShare CloudKit upload/download on TLK change, trust set addition
189 // We introduced TLKShare records.
190 // Older devices will throw them away, so on upgrade, they must refetch them
191 @implementation CKKSFixupFetchAllTLKShares
192 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
193 ckoperationGroup:(CKOperationGroup *)ckoperationGroup
194 {
195 if((self = [super init])) {
196 _ckks = keychainView;
197 _group = ckoperationGroup;
198 }
199 return self;
200 }
201
202 - (NSString*)description {
203 return [NSString stringWithFormat:@"<CKKSFixup:FetchAllTLKShares (%@)>", self.ckks];
204 }
205 - (void)groupStart {
206 CKKSKeychainView* ckks = self.ckks;
207 if(!ckks) {
208 ckkserror("ckksfixup", ckks, "no CKKS object");
209 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
210 return;
211 }
212
213 [ckks dispatchSyncWithAccountKeys:^bool {
214 __weak __typeof(self) weakSelf = self;
215 NSBlockOperation* doneOp = [NSBlockOperation named:@"fetch-records-operation-complete" withBlock:^{}];
216
217 NSPredicate *yes = [NSPredicate predicateWithValue:YES];
218 CKQuery *query = [[CKQuery alloc] initWithRecordType:SecCKRecordTLKShareType predicate:yes];
219
220 id<CKKSQueryOperation> fetch = [[ckks.queryOperationClass alloc] initWithQuery:query];
221 fetch.zoneID = ckks.zoneID;
222 fetch.desiredKeys = nil;
223
224 fetch.recordFetchedBlock = ^(CKRecord * _Nonnull record) {
225 __strong __typeof(self) strongSelf = weakSelf;
226 CKKSKeychainView* strongCKKS = strongSelf.ckks;
227 [strongCKKS dispatchSync:^bool{
228 ckksnotice("ckksfixup", strongCKKS, "Recieved tlk share record from query: %@", record);
229
230 [strongCKKS _onqueueCKRecordChanged:record resync:true];
231 return true;
232 }];
233 };
234
235 fetch.queryCompletionBlock = ^(CKQueryCursor * _Nullable cursor, NSError * _Nullable error) {
236 __strong __typeof(self) strongSelf = weakSelf;
237 CKKSKeychainView* strongCKKS = strongSelf.ckks;
238
239 [strongCKKS dispatchSync:^bool{
240 if(error) {
241 ckkserror("ckksfixup", strongCKKS, "Couldn't fetch all TLKShare records: %@", error);
242 strongSelf.error = error;
243 return false;
244 }
245
246 ckksnotice("ckksfixup", strongCKKS, "Successfully fetched TLKShare records (%@)", cursor);
247
248 NSError* localerror = nil;
249 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
250 ckse.lastFixup = CKKSFixupFetchTLKShares;
251 [ckse saveToDatabase:&localerror];
252 if(localerror) {
253 ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
254 } else {
255 ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupFetchTLKShares");
256 }
257 return true;
258 }];
259 [strongSelf runBeforeGroupFinished:doneOp];
260 };
261
262 [ckks.database addOperation: fetch];
263 [self dependOnBeforeGroupFinished:fetch];
264 [self dependOnBeforeGroupFinished:doneOp];
265
266 return true;
267 }];
268 }
269 @end
270
271
272 #endif // OCTAGON