]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSScanLocalItemsOperation.m
Security-58286.51.6.tar.gz
[apple/security.git] / keychain / ckks / CKKSScanLocalItemsOperation.m
1 /*
2 * Copyright (c) 2016 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/CKKSAnalytics.h"
27 #import "keychain/ckks/CKKSKeychainView.h"
28 #import "keychain/ckks/CKKSNearFutureScheduler.h"
29 #import "keychain/ckks/CKKSScanLocalItemsOperation.h"
30 #import "keychain/ckks/CKKSMirrorEntry.h"
31 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
32 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
33 #import "keychain/ckks/CKKSGroupOperation.h"
34 #import "keychain/ckks/CKKSKey.h"
35 #import "keychain/ckks/CKKSViewManager.h"
36 #import "keychain/ckks/CKKSManifest.h"
37
38 #import "CKKSPowerCollection.h"
39
40 #include <securityd/SecItemSchema.h>
41 #include <securityd/SecItemServer.h>
42 #include <securityd/SecItemDb.h>
43 #include <Security/SecItemPriv.h>
44 #include <utilities/SecInternalReleasePriv.h>
45 #import <IMCore/IMCore_Private.h>
46 #import <IMCore/IMCloudKitHooks.h>
47
48 @interface CKKSScanLocalItemsOperation ()
49 @property CKOperationGroup* ckoperationGroup;
50 @property (assign) NSUInteger processedItems;
51 @end
52
53 @implementation CKKSScanLocalItemsOperation
54
55 - (instancetype)init {
56 return nil;
57 }
58 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
59 if(self = [super init]) {
60 _ckks = ckks;
61 _ckoperationGroup = ckoperationGroup;
62 _recordsFound = 0;
63 _recordsAdded = 0;
64 }
65 return self;
66 }
67
68 - (void) main {
69 // Take a strong reference.
70 CKKSKeychainView* ckks = self.ckks;
71 if(!ckks) {
72 ckkserror("ckksscan", ckks, "no CKKS object");
73 return;
74 }
75
76 [ckks dispatchSyncWithAccountKeys: ^bool{
77 if(self.cancelled) {
78 ckksnotice("ckksscan", ckks, "CKKSScanLocalItemsOperation cancelled, quitting");
79 return false;
80 }
81 ckks.lastScanLocalItemsOperation = self;
82
83 NSMutableArray* itemsForManifest = [NSMutableArray array];
84
85 // First, query for all synchronizable items
86 __block CFErrorRef cferror = NULL;
87 __block NSError* error = nil;
88 __block bool newEntries = false;
89
90 // We want this set to be empty after scanning, or else the keychain (silently) dropped something on the floor
91 NSMutableSet<NSString*>* mirrorUUIDs = [NSMutableSet setWithArray:[CKKSMirrorEntry allUUIDs:ckks.zoneID error:&error]];
92
93 // Must query per-class, so:
94 const SecDbSchema *newSchema = current_schema();
95 for (const SecDbClass *const *class = newSchema->classes; *class != NULL; class++) {
96 cferror = NULL;
97
98 if(!((*class)->itemclass)) {
99 // Don't try to scan non-item 'classes'
100 continue;
101 }
102
103 NSDictionary* queryAttributes = @{(__bridge NSString*) kSecClass: (__bridge NSString*) (*class)->name,
104 (__bridge NSString*) kSecReturnRef: @(YES),
105 (__bridge NSString*) kSecAttrSynchronizable: @(YES),
106 (__bridge NSString*) kSecAttrTombstone: @(NO),
107 // This works ~as long as~ item views are chosen by view hint only. It's a significant perf win, though.
108 // <rdar://problem/32269541> SpinTracer: CKKSScanLocalItemsOperation expensive on M8 machines
109 (__bridge NSString*) kSecAttrSyncViewHint: ckks.zoneName,
110 };
111 ckksinfo("ckksscan", ckks, "Scanning all synchronizable items for: %@", queryAttributes);
112
113 Query *q = query_create_with_limit( (__bridge CFDictionaryRef) queryAttributes, NULL, kSecMatchUnlimited, &cferror);
114 bool ok = false;
115
116 if(cferror) {
117 ckkserror("ckksscan", ckks, "couldn't create query: %@", cferror);
118 SecTranslateError(&error, cferror);
119 self.error = error;
120 continue;
121 }
122
123 ok = kc_with_dbt(true, &cferror, ^(SecDbConnectionRef dbt) {
124 return SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
125 ckksnotice("ckksscan", ckks, "scanning item: %@", item);
126
127 self.processedItems += 1;
128
129 SecDbItemRef itemToSave = NULL;
130
131 // First check: is this a tombstone? If so, skip with prejudice.
132 if(SecDbItemIsTombstone(item)) {
133 ckksinfo("ckksscan", ckks, "Skipping tombstone %@", item);
134 return;
135 }
136
137 // Second check: is this item even for this view? If not, skip.
138 NSString* viewForItem = [[CKKSViewManager manager] viewNameForItem:item];
139 if(![viewForItem isEqualToString: ckks.zoneName]) {
140 ckksinfo("ckksscan", ckks, "Scanned item is for view %@, skipping", viewForItem);
141 return;
142 }
143
144 // Third check: is this item one of our keys for a view? If not, skip.
145 if([CKKSKey isItemKeyForKeychainView: item] != nil) {
146 ckksinfo("ckksscan", ckks, "Scanned item is a CKKS internal key, skipping");
147 return;
148 }
149
150 // Fourth check: does this item have a UUID? If not, ONBOARD!
151 NSString* uuid = (__bridge_transfer NSString*) CFRetain(SecDbItemGetValue(item, &v10itemuuid, &cferror));
152 if(!uuid || [uuid isEqual: [NSNull null]]) {
153 ckksnotice("ckksscan", ckks, "making new UUID for item %@", item);
154
155 uuid = [[NSUUID UUID] UUIDString];
156 NSDictionary* updates = @{(id) kSecAttrUUID: uuid};
157
158 SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, (__bridge CFDictionaryRef) updates, &cferror);
159 if(SecErrorGetOSStatus(cferror) != errSecSuccess) {
160 ckkserror("ckksscan", ckks, "couldn't update item with new UUID: %@", cferror);
161 SecTranslateError(&error, cferror);
162 self.error = error;
163 CFReleaseNull(new_item);
164 return;
165 }
166
167 if (new_item) {
168 bool ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, ^{
169 return SecDbItemUpdate(item, new_item, dbt, kCFBooleanFalse, q->q_uuid_from_primary_key, &cferror);
170 });
171
172 if(!ok || SecErrorGetOSStatus(cferror) != errSecSuccess) {
173 ckkserror("ckksscan", ckks, "couldn't update item with new UUID: %@", cferror);
174 SecTranslateError(&error, cferror);
175 self.error = error;
176 CFReleaseNull(new_item);
177 return;
178 }
179 }
180 itemToSave = CFRetainSafe(new_item);
181 CFReleaseNull(new_item);
182
183 } else {
184 // Is there a known sync item with this UUID?
185 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase: uuid zoneID:ckks.zoneID error: &error];
186 if(ckme != nil) {
187 if ([CKKSManifest shouldSyncManifests]) {
188 [itemsForManifest addObject:ckme.item];
189 }
190 [mirrorUUIDs removeObject:uuid];
191 ckksinfo("ckksscan", ckks, "Existing mirror entry with UUID %@", uuid);
192 return;
193 }
194
195 // We don't care about the oqe state here, just that one exists
196 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry tryFromDatabase: uuid zoneID:ckks.zoneID error: &error];
197 if(oqe != nil) {
198 ckksnotice("ckksscan", ckks, "Existing outgoing queue entry with UUID %@", uuid);
199 // If its state is 'new', mark down that we've seen new entries that need processing
200 newEntries |= !![oqe.state isEqualToString: SecCKKSStateNew];
201 return;
202 }
203
204 itemToSave = CFRetainSafe(item);
205 }
206
207 // Hurray, we can help!
208 self.recordsFound += 1;
209
210 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry withItem: itemToSave action: SecCKKSActionAdd ckks:ckks error: &error];
211
212 if(error) {
213 ckkserror("ckksscan", ckks, "Need to upload %@, but can't create outgoing entry: %@", item, error);
214 self.error = error;
215 CFReleaseNull(itemToSave);
216 return;
217 }
218
219 ckksnotice("ckksscan", ckks, "Syncing new item: %@", oqe);
220 CFReleaseNull(itemToSave);
221
222 [oqe saveToDatabase: &error];
223 if(error) {
224 ckkserror("ckksscan", ckks, "Need to upload %@, but can't save to database: %@", oqe, error);
225 self.error = error;
226 return;
227 }
228 newEntries = true;
229 if ([CKKSManifest shouldSyncManifests]) {
230 [itemsForManifest addObject:oqe.item];
231 }
232
233 self.recordsAdded += 1;
234 });
235 });
236
237 if(cferror || !ok) {
238 ckkserror("ckksscan", ckks, "error processing or finding items: %@", cferror);
239 SecTranslateError(&error, cferror);
240 self.error = error;
241 query_destroy(q, NULL);
242 continue;
243 }
244
245 ok = query_notify_and_destroy(q, ok, &cferror);
246
247 if(cferror || !ok) {
248 ckkserror("ckksscan", ckks, "couldn't delete query: %@", cferror);
249 SecTranslateError(&error, cferror);
250 self.error = error;
251 continue;
252 }
253 }
254
255 // We're done checking local keychain for extra items, now let's make sure the mirror doesn't have extra items, either
256 if (mirrorUUIDs.count > 0) {
257 ckksnotice("ckksscan", ckks, "keychain missing %lu items from mirror, proceeding with queue scanning", mirrorUUIDs.count);
258 [mirrorUUIDs minusSet:[NSSet setWithArray:[CKKSIncomingQueueEntry allUUIDs:ckks.zoneID error:&error]]];
259 if (error) {
260 ckkserror("ckksscan", ckks, "unable to inspect incoming queue: %@", error);
261 self.error = error;
262 return false;
263 }
264
265 [mirrorUUIDs minusSet:[NSSet setWithArray:[CKKSOutgoingQueueEntry allUUIDs:ckks.zoneID error:&error]]];
266 if (error) {
267 ckkserror("ckksscan", ckks, "unable to inspect outgoing queue: %@", error);
268 self.error = error;
269 return false;
270 }
271
272 if (mirrorUUIDs.count > 0) {
273 ckkserror("ckksscan", ckks, "BUG: keychain missing %lu items from mirror and/or queues: %@", mirrorUUIDs.count, mirrorUUIDs);
274 self.missingLocalItemsFound = mirrorUUIDs.count;
275
276 [[CKKSAnalytics logger] logMetric:[NSNumber numberWithUnsignedInteger:mirrorUUIDs.count] withName:CKKSEventMissingLocalItemsFound];
277
278 for (NSString* uuid in mirrorUUIDs) {
279 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:uuid zoneID:ckks.zoneID error:&error];
280 [ckks _onqueueCKRecordChanged:ckme.item.storedCKRecord resync:true];
281 }
282
283 // And, if you're not in the tests, try to collect a sysdiagnose I guess?
284 // <rdar://problem/36166435> Re-enable IMCore autosysdiagnose capture to securityd
285 //if(SecIsInternalRelease() && !SecCKKSTestsEnabled()) {
286 // [[IMCloudKitHooks sharedInstance] tryToAutoCollectLogsWithErrorString:@"35810558" sendLogsTo:@"rowdy_bot@icloud.com"];
287 //}
288 } else {
289 ckksnotice("ckksscan", ckks, "No missing local items found");
290 }
291 }
292
293 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventScanLocalItems zone:ckks.zoneName count:self.processedItems];
294
295 if ([CKKSManifest shouldSyncManifests]) {
296 // TODO: this manifest needs to incorporate peer manifests
297 CKKSEgoManifest* manifest = [CKKSEgoManifest newManifestForZone:ckks.zoneName withItems:itemsForManifest peerManifestIDs:@[] currentItems:@{} error:&error];
298 if (!manifest || error) {
299 ckkserror("ckksscan", ckks, "could not create manifest: %@", error);
300 self.error = error;
301 return false;
302 }
303
304 [manifest saveToDatabase:&error];
305 if (error) {
306 ckkserror("ckksscan", ckks, "could not save manifest to database: %@", error);
307 self.error = error;
308 return false;
309 }
310
311 ckks.egoManifest = manifest;
312 }
313
314 if(newEntries) {
315 // Schedule a "view changed" notification
316 [ckks.notifyViewChangedScheduler trigger];
317
318 // notify CKKS that it should process these new entries
319 [ckks processOutgoingQueue:self.ckoperationGroup];
320 }
321
322 if(self.missingLocalItemsFound > 0) {
323 [ckks processIncomingQueue:false];
324 }
325
326 ckksnotice("ckksscan", ckks, "Completed scan");
327 ckks.droppedItems = false;
328 return true;
329 }];
330 }
331
332 @end;
333
334 #endif