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