]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSScanLocalItemsOperation.m
Security-59754.80.3.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 #import <TrustedPeers/TPSyncingPolicy.h>
26 #import <TrustedPeers/TPPBPolicyKeyViewMapping.h>
27 #import <TrustedPeers/TPDictionaryMatchingRules.h>
28
29 #import "keychain/ckks/CKKSAnalytics.h"
30 #import "keychain/ckks/CKKSKeychainView.h"
31 #import "keychain/ckks/CKKSNearFutureScheduler.h"
32 #import "keychain/ckks/CKKSScanLocalItemsOperation.h"
33 #import "keychain/ckks/CKKSMirrorEntry.h"
34 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
35 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
36 #import "keychain/ckks/CKKSGroupOperation.h"
37 #import "keychain/ckks/CKKSKey.h"
38 #import "keychain/ckks/CKKSViewManager.h"
39 #import "keychain/ckks/CKKSItemEncrypter.h"
40 #import "keychain/ckks/CKKSStates.h"
41 #import "keychain/ckks/CKKSZoneStateEntry.h"
42
43 #import "CKKSPowerCollection.h"
44
45 #include "keychain/securityd/SecItemSchema.h"
46 #include "keychain/securityd/SecItemServer.h"
47 #include "keychain/securityd/SecItemDb.h"
48 #include <Security/SecItemPriv.h>
49 #include <utilities/SecInternalReleasePriv.h>
50 #import <IMCore/IMCore_Private.h>
51 #import <IMCore/IMCloudKitHooks.h>
52
53 @interface CKKSScanLocalItemsOperation ()
54 @property (assign) NSUInteger processedItems;
55
56 @property BOOL newCKKSEntries;
57 @end
58
59 @implementation CKKSScanLocalItemsOperation
60 @synthesize nextState = _nextState;
61 @synthesize intendedState = _intendedState;
62
63 - (instancetype)init {
64 return nil;
65 }
66 - (instancetype)initWithDependencies:(CKKSOperationDependencies*)dependencies
67 ckks:(CKKSKeychainView*)ckks
68 intending:(OctagonState*)intendedState
69 errorState:(OctagonState*)errorState
70 ckoperationGroup:(CKOperationGroup*)ckoperationGroup
71 {
72 if((self = [super init])) {
73 _deps = dependencies;
74 _ckks = ckks;
75 _ckoperationGroup = ckoperationGroup;
76
77 _nextState = errorState;
78 _intendedState = intendedState;
79
80 _recordsFound = 0;
81 _recordsAdded = 0;
82 }
83 return self;
84 }
85
86 - (NSDictionary*)queryPredicatesForViewMapping {
87 TPPBPolicyKeyViewMapping* viewRule = nil;
88
89 // If there's more than one rule matching this view, then exit with an empty dictionary: the language doesn't support ORs.
90 for(TPPBPolicyKeyViewMapping* mapping in [CKKSViewManager manager].policy.keyViewMapping) {
91 if([mapping.view isEqualToString:self.deps.zoneID.zoneName]) {
92 if(viewRule == nil) {
93 viewRule = mapping;
94 } else {
95 // Too many rules for this view! Don't perform optimization.
96 ckksnotice("ckksscan", self.deps.zoneID, "Too many policy rules for view %@", self.deps.zoneID.zoneName);
97 return @{};
98 }
99 }
100 }
101
102 if(viewRule.hasMatchingRule &&
103 viewRule.matchingRule.andsCount == 0 &&
104 viewRule.matchingRule.orsCount == 0 &&
105 !viewRule.matchingRule.hasNot &&
106 !viewRule.matchingRule.hasExists &&
107 viewRule.matchingRule.hasMatch) {
108 if([((id)kSecAttrSyncViewHint) isEqualToString:viewRule.matchingRule.match.fieldName] &&
109 [viewRule.matchingRule.match.regex isEqualToString:[NSString stringWithFormat:@"^%@$", self.deps.zoneID.zoneName]]) {
110 return @{
111 (id)kSecAttrSyncViewHint: self.deps.zoneID.zoneName,
112 };
113 } else if([((id)kSecAttrAccessGroup) isEqualToString:viewRule.matchingRule.match.fieldName] &&
114 [viewRule.matchingRule.match.regex isEqualToString:@"^com\\.apple\\.cfnetwork$"]) {
115 // We can't match on any regex agrp match, because it might be some actually difficult regex. But, we know about this one!
116 return @{
117 (id)kSecAttrAccessGroup: @"com.apple.cfnetwork",
118 };
119
120 } else if([((id)kSecAttrAccessGroup) isEqualToString:viewRule.matchingRule.match.fieldName] &&
121 [viewRule.matchingRule.match.regex isEqualToString:@"^com\\.apple\\.safari\\.credit-cards$"]) {
122 // We can't match on any regex agrp match, because it might be some actually difficult regex. But, we know about this one!
123 return @{
124 (id)kSecAttrAccessGroup: @"com.apple.safari.credit-cards",
125 };
126
127 } else {
128 ckksnotice("ckksscan", self.deps.zoneID, "Policy view rule is not a match against viewhint: %@", viewRule);
129 }
130 } else {
131 ckksnotice("ckksscan", self.deps.zoneID, "Policy view rule is complex: %@", viewRule);
132 }
133
134 return @{};
135 }
136
137 - (BOOL)executeQuery:(NSDictionary*)queryPredicates readWrite:(bool)readWrite error:(NSError**)error block:(void (^_Nonnull)(SecDbItemRef item))block
138 {
139 __block CFErrorRef cferror = NULL;
140 __block bool ok = false;
141
142 Query *q = query_create_with_limit((__bridge CFDictionaryRef)queryPredicates, NULL, kSecMatchUnlimited, NULL, &cferror);
143
144 if(cferror) {
145 ckkserror("ckksscan", self.deps.zoneID, "couldn't create query: %@", cferror);
146 SecTranslateError(error, cferror);
147 return NO;
148 }
149
150 ok = kc_with_dbt(readWrite, &cferror, ^(SecDbConnectionRef dbt) {
151 return SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
152 block(item);
153 });
154 });
155
156 if(readWrite) {
157 ok = query_notify_and_destroy(q, ok, &cferror);
158 } else {
159 ok = query_destroy(q, &cferror);
160 }
161
162 if(cferror || !ok) {
163 ckkserror("ckksscan", self.deps.zoneID, "couldn't execute query: %@", cferror);
164 SecTranslateError(error, cferror);
165 return NO;
166 }
167
168 return YES;
169 }
170
171 - (BOOL)onboardItemToCKKS:(SecDbItemRef)item error:(NSError**)error
172 {
173 NSError* itemSaveError = nil;
174
175 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry withItem:item
176 action:SecCKKSActionAdd
177 zoneID:self.deps.zoneID
178 error:&itemSaveError];
179
180 if(itemSaveError) {
181 ckkserror("ckksscan", self.deps.zoneID, "Need to upload %@, but can't create outgoing entry: %@", item, itemSaveError);
182 if(error) {
183 *error = itemSaveError;
184 }
185 return NO;
186 }
187
188 ckksnotice("ckksscan", self.deps.zoneID, "Syncing new item: %@", oqe);
189
190 [oqe saveToDatabase:&itemSaveError];
191 if(itemSaveError) {
192 ckkserror("ckksscan", self.deps.zoneID, "Need to upload %@, but can't save to database: %@", oqe, itemSaveError);
193 self.error = itemSaveError;
194 return NO;
195 }
196
197 self.newCKKSEntries = true;
198 self.recordsAdded += 1;
199
200 return YES;
201 }
202
203 - (void)onboardItemsWithUUIDs:(NSSet<NSString*>*)uuids itemClass:(NSString*)itemClass databaseProvider:(id<CKKSDatabaseProviderProtocol>)databaseProvider
204 {
205 ckksnotice("ckksscan", self.deps.zoneID, "Found %d missing %@ items", (int)uuids.count, itemClass);
206 // Use one transaction for each item to allow for SecItem API calls to interleave
207 for(NSString* itemUUID in uuids) {
208 [databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult {
209 NSDictionary* queryAttributes = @{
210 (id)kSecClass: itemClass,
211 (id)kSecReturnRef: @(YES),
212 (id)kSecAttrSynchronizable: @(YES),
213 (id)kSecAttrTombstone: @(NO),
214 (id)kSecAttrUUID: itemUUID,
215 };
216
217 ckksnotice("ckksscan", self.deps.zoneID, "Onboarding %@ %@", itemClass, itemUUID);
218
219 __block NSError* itemSaveError = nil;
220
221 [self executeQuery:queryAttributes readWrite:false error:&itemSaveError block:^(SecDbItemRef itemToSave) {
222 [self onboardItemToCKKS:itemToSave error:&itemSaveError];
223 }];
224
225 if(itemSaveError) {
226 ckkserror("ckksscan", self.deps.zoneID, "Need to upload %@, but can't save to database: %@", itemUUID, itemSaveError);
227 self.error = itemSaveError;
228 return CKKSDatabaseTransactionRollback;
229 }
230
231 return CKKSDatabaseTransactionCommit;
232 }];
233 }
234 }
235
236 - (void)fixUUIDlessItemsWithPrimaryKeys:(NSMutableSet<NSDictionary*>*)primaryKeys databaseProvider:(id<CKKSDatabaseProviderProtocol>)databaseProvider
237 {
238 ckksnotice("ckksscan", self.deps.zoneID, "Found %d items missing UUIDs", (int)primaryKeys.count);
239
240 if([primaryKeys count] == 0) {
241 return;
242 }
243
244 [databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
245 __block NSError* itemError = nil;
246
247 for(NSDictionary* primaryKey in primaryKeys) {
248 ckksnotice("ckksscan", self.deps.zoneID, "Found item with no uuid: %@", primaryKey);
249
250 __block CFErrorRef cferror = NULL;
251
252 bool connectionSuccess = kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
253 Query *q = query_create_with_limit((__bridge CFDictionaryRef)primaryKey, NULL, kSecMatchUnlimited, NULL, &cferror);
254
255 if(!q || cferror) {
256 ckkserror("ckksscan", self.deps.zoneID, "couldn't create query: %@", cferror);
257 return false;
258 }
259
260 __block bool ok = true;
261
262 ok &= SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef uuidlessItem, bool *stop) {
263 NSString* uuid = [[NSUUID UUID] UUIDString];
264 NSDictionary* updates = @{(id)kSecAttrUUID: uuid};
265
266 ckksnotice("ckksscan", self.deps.zoneID, "Assigning new UUID %@ for item %@", uuid, uuidlessItem);
267
268 SecDbItemRef new_item = SecDbItemCopyWithUpdates(uuidlessItem, (__bridge CFDictionaryRef)updates, &cferror);
269
270 if(!new_item) {
271 SecTranslateError(&itemError, cferror);
272 self.error = itemError;
273 ckksnotice("ckksscan", self.deps.zoneID, "Unable to copy item with new UUID: %@", cferror);
274 return;
275 }
276
277 bool updateSuccess = kc_transaction_type(dbt, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, ^{
278 return SecDbItemUpdate(uuidlessItem, new_item, dbt, kCFBooleanFalse, q->q_uuid_from_primary_key, &cferror);
279 });
280
281 if(updateSuccess) {
282 [self onboardItemToCKKS:new_item error:&itemError];
283 } else {
284 ckksnotice("ckksscan", self.deps.zoneID, "Unable to update item with new UUID: %@", cferror);
285 }
286
287 ok &= updateSuccess;
288 });
289
290 ok &= query_notify_and_destroy(q, ok, &cferror);
291
292 return true;
293 });
294
295 if(!connectionSuccess) {
296 ckkserror("ckksscan", self.deps.zoneID, "couldn't execute query: %@", cferror);
297 SecTranslateError(&itemError, cferror);
298 self.error = itemError;
299 return CKKSDatabaseTransactionRollback;
300 }
301 }
302
303 return CKKSDatabaseTransactionCommit;
304 }];
305 }
306
307 - (void)retriggerMissingMirrorEntires:(NSSet<NSString*>*)mirrorUUIDs
308 ckks:(CKKSKeychainView*)ckks
309 databaseProvider:(id<CKKSDatabaseProviderProtocol>)databaseProvider
310 {
311 if (mirrorUUIDs.count > 0) {
312 [databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
313 NSError* error = nil;
314 ckkserror("ckksscan", self.deps.zoneID, "BUG: keychain missing %lu items from mirror and/or queues: %@", (unsigned long)mirrorUUIDs.count, mirrorUUIDs);
315 self.missingLocalItemsFound = mirrorUUIDs.count;
316
317 [[CKKSAnalytics logger] logMetric:[NSNumber numberWithUnsignedInteger:mirrorUUIDs.count] withName:CKKSEventMissingLocalItemsFound];
318
319 for (NSString* uuid in mirrorUUIDs) {
320 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:uuid zoneID:self.deps.zoneID error:&error];
321
322 if(!ckme || error) {
323 ckkserror("ckksscan", self.deps.zoneID, "BUG: error fetching previously-extant CKME (uuid: %@) from database: %@", uuid, error);
324 self.error = error;
325 } else {
326 [ckks _onqueueCKRecordChanged:ckme.item.storedCKRecord resync:true];
327 }
328 }
329
330 // And, if you're not in the tests, try to collect a sysdiagnose I guess?
331 // <rdar://problem/36166435> Re-enable IMCore autosysdiagnose capture to securityd
332 //if(SecIsInternalRelease() && !SecCKKSTestsEnabled()) {
333 // [[IMCloudKitHooks sharedInstance] tryToAutoCollectLogsWithErrorString:@"35810558" sendLogsTo:@"rowdy_bot@icloud.com"];
334 //}
335 return CKKSDatabaseTransactionCommit;
336 }];
337 } else {
338 ckksnotice("ckksscan", self.deps.zoneID,"No missing local items found");
339 }
340 }
341
342 - (void)main
343 {
344 if(SecCKKSTestsEnabled() && SecCKKSTestSkipScan()) {
345 ckksnotice("ckksscan", self.deps.zoneID, "Scan cancelled by test request");
346 return;
347 }
348
349 // We need to not be jetsamed while running this
350 os_transaction_t transaction = os_transaction_create([[NSString stringWithFormat:@"com.apple.securityd.ckks.scan.%@", self.deps.zoneID] UTF8String]);
351
352 id<CKKSDatabaseProviderProtocol> databaseProvider = self.deps.databaseProvider;
353 CKKSKeychainView* ckks = self.ckks;
354
355 [self.deps.launch addEvent:@"scan-local-items"];
356
357 // A map of ItemClass -> Set of found UUIDs
358 NSMutableDictionary<NSString*, NSMutableSet<NSString*>*>* itemUUIDsNotYetInCKKS = [NSMutableDictionary dictionary];
359
360 // A list of primary keys of items that fit in this view, but have no UUIDs
361 NSMutableSet<NSDictionary*>* primaryKeysWithNoUUIDs = [NSMutableSet set];
362
363 // We want this set to be empty after scanning, or else the keychain (silently) dropped something on the floor
364 NSMutableSet<NSString*>* mirrorUUIDs = [NSMutableSet set];
365
366 [databaseProvider dispatchSyncWithReadOnlySQLTransaction:^{
367 // First, query for all synchronizable items
368 __block NSError* error = nil;
369
370 [mirrorUUIDs addObjectsFromArray:[CKKSMirrorEntry allUUIDs:self.deps.zoneID error:&error]];
371
372 // Must query per-class, so:
373 const SecDbSchema *newSchema = current_schema();
374 for (const SecDbClass *const *class = newSchema->classes; *class != NULL; class++) {
375 if(!((*class)->itemclass)) {
376 // Don't try to scan non-item 'classes'
377 continue;
378 }
379
380 NSString* itemClass = (__bridge NSString*)(*class)->name;
381
382 NSMutableDictionary* queryAttributes = [
383 @{(__bridge NSString*)kSecClass: itemClass,
384 (__bridge NSString*)kSecReturnRef: @(YES),
385 (__bridge NSString*)kSecAttrSynchronizable: @(YES),
386 (__bridge NSString*)kSecAttrTombstone: @(NO),
387 } mutableCopy];
388
389 NSDictionary* extraQueryPredicates = [self queryPredicatesForViewMapping];
390 [queryAttributes addEntriesFromDictionary:extraQueryPredicates];
391
392 ckksnotice("ckksscan", self.deps.zoneID, "Scanning all synchronizable %@ items(%@) for: %@", itemClass, self.name, queryAttributes);
393
394 [self executeQuery:queryAttributes readWrite:false error:&error block:^(SecDbItemRef item) {
395 ckksnotice("ckksscan", self.deps.zoneID, "scanning item: %@", item);
396
397 self.processedItems += 1;
398
399 // First check: is this a tombstone? If so, skip with prejudice.
400 if(SecDbItemIsTombstone(item)) {
401 ckksinfo("ckksscan", self.deps.zoneID, "Skipping tombstone %@", item);
402 return;
403 }
404
405 // Second check: is this item a CKKS key for a view? If so, skip.
406 if([CKKSKey isItemKeyForKeychainView:item] != nil) {
407 ckksinfo("ckksscan", self.deps.zoneID, "Scanned item is a CKKS internal key, skipping");
408 return;
409 }
410
411 // Third check: What view is this for?
412 NSString* viewForItem = [[CKKSViewManager manager] viewNameForItem:item];
413 if(![viewForItem isEqualToString:self.deps.zoneID.zoneName]) {
414 ckksinfo("ckksscan", self.deps.zoneID, "Scanned item is for view %@, skipping", viewForItem);
415 return;
416 }
417
418 // Fourth check: does this item have a UUID? If not, mark for later onboarding.
419 CFErrorRef cferror = NULL;
420
421 NSString* uuid = (__bridge_transfer NSString*) CFRetain(SecDbItemGetValue(item, &v10itemuuid, &cferror));
422 if(!uuid || [uuid isEqual: [NSNull null]]) {
423 ckksnotice("ckksscan", self.deps.zoneID, "making new UUID for item %@: %@", item, cferror);
424
425 NSMutableDictionary* primaryKey = [(NSDictionary*)CFBridgingRelease(SecDbItemCopyPListWithMask(item, kSecDbPrimaryKeyFlag, &cferror)) mutableCopy];
426
427 // Class is an important part of a primary key, SecDb
428 primaryKey[(id)kSecClass] = itemClass;
429
430 if(SecErrorGetOSStatus(cferror) != errSecSuccess) {
431 ckkserror("ckksscan", self.deps.zoneID, "couldn't copy UUID-less item's primary key: %@", cferror);
432 SecTranslateError(&error, cferror);
433 self.error = error;
434 return;
435 }
436
437 [primaryKeysWithNoUUIDs addObject:primaryKey];
438 return;
439 }
440
441 // Is there a known sync item with this UUID?
442 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:uuid
443 zoneID:self.deps.zoneID
444 error:&error];
445 if(ckme != nil) {
446 [mirrorUUIDs removeObject:uuid];
447 ckksinfo("ckksscan", self.deps.zoneID, "Existing mirror entry with UUID %@", uuid);
448
449 if([self areEquivalent:item ckksItem:ckme.item]) {
450 // Fair enough.
451 return;
452 } else {
453 ckksnotice("ckksscan", self.deps.zoneID, "Existing mirror entry with UUID %@ does not match local item", uuid);
454 }
455 }
456
457 // We don't care about the oqe state here, just that one exists
458 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry tryFromDatabase:uuid
459 zoneID:self.deps.zoneID
460 error:&error];
461 if(oqe != nil) {
462 ckksnotice("ckksscan", self.deps.zoneID, "Existing outgoing queue entry with UUID %@", uuid);
463 // If its state is 'new', mark down that we've seen new entries that need processing
464 self.newCKKSEntries |= !![oqe.state isEqualToString:SecCKKSStateNew];
465 return;
466 }
467
468 // Hurray, we can help!
469 ckksnotice("ckksscan", self.deps.zoneID, "Item(%@) is new; will attempt to add to CKKS", uuid);
470 self.recordsFound += 1;
471
472 NSMutableSet<NSString*>* classUUIDs = itemUUIDsNotYetInCKKS[itemClass];
473 if(!classUUIDs) {
474 classUUIDs = [NSMutableSet set];
475 itemUUIDsNotYetInCKKS[itemClass] = classUUIDs;
476 }
477 [classUUIDs addObject:uuid];
478 }];
479 }
480
481 // We're done checking local keychain for extra items, now let's make sure the mirror doesn't have extra items that the keychain doesn't have, either
482 if (mirrorUUIDs.count > 0) {
483 ckksnotice("ckksscan", self.deps.zoneID, "keychain missing %lu items from mirror, proceeding with queue scanning", (unsigned long)mirrorUUIDs.count);
484 [mirrorUUIDs minusSet:[NSSet setWithArray:[CKKSIncomingQueueEntry allUUIDs:self.deps.zoneID error:&error]]];
485 if (error) {
486 ckkserror("ckksscan", self.deps.zoneID, "unable to inspect incoming queue: %@", error);
487 self.error = error;
488 return;
489 }
490
491 [mirrorUUIDs minusSet:[NSSet setWithArray:[CKKSOutgoingQueueEntry allUUIDs:self.deps.zoneID error:&error]]];
492 if (error) {
493 ckkserror("ckksscan", self.deps.zoneID, "unable to inspect outgoing queue: %@", error);
494 self.error = error;
495 return;
496 }
497 }
498
499 // Drop off of read-only transaction
500 }];
501
502 if(self.error) {
503 ckksnotice("ckksscan", self.deps.zoneID, "Exiting due to previous error: %@", self.error);
504 return;
505 }
506
507 ckksnotice("ckksscan", self.deps.zoneID, "Found %d item classes with missing items", (int)itemUUIDsNotYetInCKKS.count);
508
509 for(NSString* itemClass in [itemUUIDsNotYetInCKKS allKeys]) {
510 [self onboardItemsWithUUIDs:itemUUIDsNotYetInCKKS[itemClass] itemClass:itemClass databaseProvider:databaseProvider];
511 }
512
513 [self fixUUIDlessItemsWithPrimaryKeys:primaryKeysWithNoUUIDs databaseProvider:databaseProvider];
514
515 [self retriggerMissingMirrorEntires:mirrorUUIDs
516 ckks:ckks
517 databaseProvider:databaseProvider];
518
519 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventScanLocalItems zone:self.deps.zoneID.zoneName count:self.processedItems];
520
521 // Write down that a scan occurred
522 [databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
523 CKKSZoneStateEntry* zoneState = [CKKSZoneStateEntry state:self.deps.zoneID.zoneName];
524
525 zoneState.lastLocalKeychainScanTime = [NSDate now];
526
527 NSError* saveError = nil;
528 [zoneState saveToDatabase:&saveError];
529
530 if(saveError) {
531 ckkserror("ckksscan", self.deps.zoneID, "Unable to save 'scanned' bit: %@", saveError);
532 } else {
533 ckksnotice("ckksscan", self.deps.zoneID, "Saved scanned status.");
534 }
535
536 return CKKSDatabaseTransactionCommit;
537 }];
538
539 if(self.newCKKSEntries) {
540 // Schedule a "view changed" notification
541 [self.deps.notifyViewChangedScheduler trigger];
542
543 // notify CKKS that it should process these new entries
544 [ckks processOutgoingQueue:self.ckoperationGroup];
545 // TODO: self.nextState = SecCKKSZoneKeyStateProcessOutgoingQueue;
546 } else {
547 self.nextState = self.intendedState;
548 }
549
550 if(self.missingLocalItemsFound > 0) {
551 [ckks processIncomingQueue:false];
552 // TODO [self.deps.flagHandler _onqueueHandleFlag:CKKSFlagProcessIncomingQueue];
553 }
554
555 ckksnotice("ckksscan", self.deps.zoneID, "Completed scan");
556 (void)transaction;
557 }
558
559 - (BOOL)areEquivalent:(SecDbItemRef)item ckksItem:(CKKSItem*)ckksItem
560 {
561 NSError* localerror = nil;
562 NSDictionary* attributes = [CKKSIncomingQueueOperation decryptCKKSItemToAttributes:ckksItem error:&localerror];
563 if(!attributes || localerror) {
564 ckksnotice("ckksscan", self.deps.zoneID, "Could not decrypt item for comparison: %@", localerror);
565 return YES;
566 }
567
568 CFErrorRef cferror = NULL;
569 NSDictionary* objdict = (NSMutableDictionary*)CFBridgingRelease(SecDbItemCopyPListWithMask(item, kSecDbSyncFlag, &cferror));
570 localerror = (NSError*)CFBridgingRelease(cferror);
571
572 if(!objdict || localerror) {
573 ckksnotice("ckksscan", self.deps.zoneID, "Could not get item contents for comparison: %@", localerror);
574
575 // Fail open: assert that this item doesn't match
576 return NO;
577 }
578
579 for(id key in objdict) {
580 // Okay, but seriously storing dates as floats was a mistake.
581 // Don't compare cdat and mdat, as they'll usually be different.
582 // Also don't compare the sha1, as it hashes that double.
583 if([key isEqual:(__bridge id)kSecAttrCreationDate] ||
584 [key isEqual:(__bridge id)kSecAttrModificationDate] ||
585 [key isEqual:(__bridge id)kSecAttrSHA1]) {
586 continue;
587 }
588
589 id value = objdict[key];
590 id attributesValue = attributes[key];
591
592 if(![value isEqual:attributesValue]) {
593 return NO;
594 }
595 }
596
597 return YES;
598 }
599
600 @end;
601
602 #endif