]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSIncomingQueueOperation.m
Security-58286.251.4.tar.gz
[apple/security.git] / keychain / ckks / CKKSIncomingQueueOperation.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 #import "CKKSKeychainView.h"
25 #import "CKKSIncomingQueueOperation.h"
26 #import "CKKSIncomingQueueEntry.h"
27 #import "CKKSItemEncrypter.h"
28 #import "CKKSOutgoingQueueEntry.h"
29 #import "CKKSKey.h"
30 #import "CKKSManifest.h"
31 #import "CKKSAnalytics.h"
32 #import "CKKSPowerCollection.h"
33 #import "keychain/ckks/CKKSCurrentItemPointer.h"
34
35 #include <securityd/SecItemServer.h>
36 #include <securityd/SecItemDb.h>
37 #include <Security/SecItemPriv.h>
38
39 #include <utilities/SecADWrapper.h>
40
41 #if OCTAGON
42
43 @interface CKKSIncomingQueueOperation ()
44 @property bool newOutgoingEntries;
45 @property bool pendingClassAEntries;
46 @property bool missingKey;
47 @end
48
49 @implementation CKKSIncomingQueueOperation
50
51 - (instancetype)init {
52 return nil;
53 }
54 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks errorOnClassAFailure:(bool)errorOnClassAFailure {
55 if(self = [super init]) {
56 _ckks = ckks;
57
58 // Can't process unless we have a reasonable key hierarchy.
59 if(ckks.keyStateReadyDependency) {
60 [self addDependency: ckks.keyStateReadyDependency];
61 }
62
63 [self addNullableDependency: ckks.holdIncomingQueueOperation];
64
65 _errorOnClassAFailure = errorOnClassAFailure;
66 _pendingClassAEntries = false;
67
68 [self linearDependencies:ckks.incomingQueueOperations];
69
70 if ([CKKSManifest shouldSyncManifests]) {
71 __weak __typeof(self) weakSelf = self;
72 __weak CKKSKeychainView* weakCKKS = ckks;
73 CKKSResultOperation* updateManifestOperation = [CKKSResultOperation operationWithBlock:^{
74 __strong __typeof(self) strongSelf = weakSelf;
75 __strong CKKSKeychainView* strongCKKS = weakCKKS;
76 __block NSError* error = nil;
77 if (!strongCKKS || !strongSelf) {
78 ckkserror("ckksincoming", strongCKKS, "update manifest operation fired for released object");
79 return;
80 }
81
82 [strongCKKS dispatchSyncWithAccountKeys:^bool{
83 strongCKKS.latestManifest = [CKKSManifest latestTrustedManifestForZone:strongCKKS.zoneName error:&error];
84 if (error) {
85 strongSelf.error = error;
86 ckkserror("ckksincoming", strongCKKS, "failed to get latest manifest: %@", error);
87 return false;
88 }
89 else {
90 return true;
91 }
92 }];
93 }];
94 updateManifestOperation.name = @"update-manifest-operation";
95
96 [ckks scheduleOperation:updateManifestOperation];
97 [self addSuccessDependency:updateManifestOperation];
98 }
99 }
100 return self;
101 }
102
103 - (bool)processNewCurrentItemPointers:(NSArray<CKKSCurrentItemPointer*>*)queueEntries withManifest:(CKKSManifest*)manifest egoManifest:(CKKSEgoManifest*)egoManifest
104 {
105 CKKSKeychainView* ckks = self.ckks;
106
107 NSError* error = nil;
108 for(CKKSCurrentItemPointer* p in queueEntries) {
109 @autoreleasepool {
110 if ([CKKSManifest shouldSyncManifests]) {
111 if (![manifest validateCurrentItem:p withError:&error]) {
112 ckkserror("ckksincoming", ckks, "Unable to validate current item pointer (%@) against manifest (%@)", p, manifest);
113 if ([CKKSManifest shouldEnforceManifests]) {
114 return false;
115 }
116 }
117 }
118
119 p.state = SecCKKSProcessedStateLocal;
120
121 [p saveToDatabase:&error];
122 ckksnotice("ckkspointer", ckks, "Saving new current item pointer: %@", p);
123 if(error) {
124 ckkserror("ckksincoming", ckks, "Error saving new current item pointer: %@ %@", error, p);
125 }
126
127 // Schedule a view change notification
128 [ckks.notifyViewChangedScheduler trigger];
129 }
130 }
131
132 if(queueEntries.count > 0) {
133 // Schedule a view change notification
134 [ckks.notifyViewChangedScheduler trigger];
135 }
136
137 return (error == nil);
138 }
139
140 - (bool)processQueueEntries:(NSArray<CKKSIncomingQueueEntry*>*)queueEntries withManifest:(CKKSManifest*)manifest egoManifest:(CKKSEgoManifest*)egoManifest
141 {
142 CKKSKeychainView* ckks = self.ckks;
143
144 NSMutableArray* newOrChangedRecords = [[NSMutableArray alloc] init];
145 NSMutableArray* deletedRecordIDs = [[NSMutableArray alloc] init];
146
147 for(id entry in queueEntries) {
148 @autoreleasepool {
149 if(self.cancelled) {
150 ckksnotice("ckksincoming", ckks, "CKKSIncomingQueueOperation cancelled, quitting");
151 return false;
152 }
153
154 NSError* error = nil;
155
156 CKKSIncomingQueueEntry* iqe = (CKKSIncomingQueueEntry*) entry;
157 ckksnotice("ckksincoming", ckks, "ready to process an incoming queue entry: %@ %@ %@", iqe, iqe.uuid, iqe.action);
158
159 // Note that we currently unencrypt the item before deleting it, instead of just deleting it
160 // This finds the class, which is necessary for the deletion process. We could just try to delete
161 // across all classes, though...
162 NSMutableDictionary* attributes = [[CKKSItemEncrypter decryptItemToDictionary: iqe.item error:&error] mutableCopy];
163 if(!attributes || error) {
164 if([ckks.lockStateTracker isLockedError:error]) {
165 NSError* localerror = nil;
166 ckkserror("ckksincoming", ckks, "Keychain is locked; can't decrypt IQE %@", iqe);
167 CKKSKey* key = [CKKSKey tryFromDatabase:iqe.item.parentKeyUUID zoneID:ckks.zoneID error:&localerror];
168 if(localerror || ([key.keyclass isEqualToString:SecCKKSKeyClassA] && self.errorOnClassAFailure)) {
169 self.error = error;
170 }
171
172 // If this isn't an error, make sure it gets processed later.
173 if([key.keyclass isEqualToString:SecCKKSKeyClassA] && !self.errorOnClassAFailure) {
174 self.pendingClassAEntries = true;
175 }
176
177 } else if ([error.domain isEqualToString:@"securityd"] && error.code == errSecItemNotFound) {
178 ckkserror("ckksincoming", ckks, "Coudn't find key in keychain; will attempt to poke key hierarchy: %@", error)
179 self.missingKey = true;
180
181 } else {
182 ckkserror("ckksincoming", ckks, "Couldn't decrypt IQE %@ for some reason: %@", iqe, error);
183 self.error = error;
184 }
185 self.errorItemsProcessed += 1;
186 continue;
187 }
188
189 // Add the UUID (which isn't stored encrypted)
190 [attributes setValue: iqe.item.uuid forKey: (__bridge NSString*) kSecAttrUUID];
191
192 // Add the PCS plaintext fields, if they exist
193 if(iqe.item.plaintextPCSServiceIdentifier) {
194 [attributes setValue: iqe.item.plaintextPCSServiceIdentifier forKey: (__bridge NSString*) kSecAttrPCSPlaintextServiceIdentifier];
195 }
196 if(iqe.item.plaintextPCSPublicKey) {
197 [attributes setValue: iqe.item.plaintextPCSPublicKey forKey: (__bridge NSString*) kSecAttrPCSPlaintextPublicKey];
198 }
199 if(iqe.item.plaintextPCSPublicIdentity) {
200 [attributes setValue: iqe.item.plaintextPCSPublicIdentity forKey: (__bridge NSString*) kSecAttrPCSPlaintextPublicIdentity];
201 }
202
203 // This item is also synchronizable (by definition)
204 [attributes setValue: @(YES) forKey: (__bridge NSString*) kSecAttrSynchronizable];
205
206 NSString* classStr = [attributes objectForKey: (__bridge NSString*) kSecClass];
207 if(![classStr isKindOfClass: [NSString class]]) {
208 self.error = [NSError errorWithDomain:@"securityd"
209 code:errSecInternalError
210 userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Item did not have a reasonable class: %@", classStr]}];
211 ckkserror("ckksincoming", ckks, "Synced item seems wrong: %@", self.error);
212 self.errorItemsProcessed += 1;
213 continue;
214 }
215
216 const SecDbClass * classP = !classStr ? NULL : kc_class_with_name((__bridge CFStringRef) classStr);
217
218 if(!classP) {
219 ckkserror("ckksincoming", ckks, "unknown class in object: %@ %@", classStr, iqe);
220 iqe.state = SecCKKSStateError;
221 [iqe saveToDatabase:&error];
222 if(error) {
223 ckkserror("ckksincoming", ckks, "Couldn't save errored IQE to database: %@", error);
224 self.error = error;
225 }
226 self.errorItemsProcessed += 1;
227 continue;
228 }
229
230
231 // Now, filter: if this item was added locally, would it go to this view?
232 // In this release, this is based solely on viewhint
233 NSString* viewHint = attributes[(id)kSecAttrSyncViewHint];
234 bool itemIsForView = [viewHint isEqualToString: ckks.zoneName];
235 if(!itemIsForView) {
236
237 ckkserror("ckksincoming", ckks, "ViewHint in decrypted item (%@) does not match (%@), skipping item", viewHint, ckks.zoneName);
238 iqe.state = SecCKKSStateZoneMismatch;
239 [iqe saveToDatabase:&error];
240 if(error) {
241 ckkserror("ckksincoming", ckks, "Couldn't save zone mismatch IQE to database: %@", error);
242 self.error = error;
243 }
244 continue;
245 }
246
247 if([iqe.action isEqualToString: SecCKKSActionAdd] || [iqe.action isEqualToString: SecCKKSActionModify]) {
248 BOOL requireManifestValidation = [CKKSManifest shouldEnforceManifests];
249 BOOL manifestValidatesItem = [manifest validateItem:iqe.item withError:&error];
250
251 if (!requireManifestValidation || manifestValidatesItem) {
252 [self _onqueueHandleIQEChange: iqe attributes:attributes class:classP];
253 [newOrChangedRecords addObject:[iqe.item CKRecordWithZoneID:ckks.zoneID]];
254 }
255 else {
256 ckkserror("ckksincoming", ckks, "could not validate incoming item against manifest with error: %@", error);
257 if (![self _onqueueUpdateIQE:iqe withState:SecCKKSStateUnauthenticated error:&error]) {
258 ckkserror("ckksincoming", ckks, "failed to save incoming item back to database in unauthenticated state with error: %@", error);
259 return false;
260 }
261 self.errorItemsProcessed += 1;
262 continue;
263 }
264 } else if ([iqe.action isEqualToString: SecCKKSActionDelete]) {
265 BOOL requireManifestValidation = [CKKSManifest shouldEnforceManifests];
266 BOOL manifestValidatesDelete = ![manifest itemUUIDExistsInManifest:iqe.uuid];
267
268 if (!requireManifestValidation || manifestValidatesDelete) {
269 // if the item does not exist in the latest manifest, we're good to delete it
270 [self _onqueueHandleIQEDelete: iqe class:classP];
271 [deletedRecordIDs addObject:[[CKRecordID alloc] initWithRecordName:iqe.uuid zoneID:ckks.zoneID]];
272 }
273 else {
274 // if the item DOES exist in the manifest, we can't trust the deletion
275 ckkserror("ckksincoming", ckks, "could not validate incoming item deletion against manifest");
276 if (![self _onqueueUpdateIQE:iqe withState:SecCKKSStateUnauthenticated error:&error]) {
277 ckkserror("ckksincoming", ckks, "failed to save incoming item deletion back to database in unauthenticated state with error: %@", error);
278
279 self.errorItemsProcessed += 1;
280 return false;
281 }
282 }
283 }
284 }
285 }
286
287 if(newOrChangedRecords.count > 0 || deletedRecordIDs > 0) {
288 // Schedule a view change notification
289 [ckks.notifyViewChangedScheduler trigger];
290 }
291
292 if(self.missingKey) {
293 [ckks.pokeKeyStateMachineScheduler trigger];
294 }
295
296 if ([CKKSManifest shouldSyncManifests]) {
297 [egoManifest updateWithNewOrChangedRecords:newOrChangedRecords deletedRecordIDs:deletedRecordIDs];
298 }
299 return true;
300 }
301
302 - (bool)_onqueueUpdateIQE:(CKKSIncomingQueueEntry*)iqe withState:(NSString*)newState error:(NSError**)error
303 {
304 if (![iqe.state isEqualToString:newState]) {
305 NSMutableDictionary* oldWhereClause = iqe.whereClauseToFindSelf.mutableCopy;
306 oldWhereClause[@"state"] = iqe.state;
307 iqe.state = newState;
308 if ([iqe saveToDatabase:error]) {
309 if (![CKKSSQLDatabaseObject deleteFromTable:[iqe.class sqlTable] where:oldWhereClause connection:NULL error:error]) {
310 return false;
311 }
312 }
313 else {
314 return false;
315 }
316 }
317
318 return true;
319 }
320
321 - (void) main {
322 // Synchronous, on some thread. Get back on the CKKS queue for thread-safety.
323 CKKSKeychainView* ckks = self.ckks;
324 if(!ckks) {
325 ckkserror("ckksincoming", ckks, "no CKKS object");
326 return;
327 }
328
329 __weak __typeof(self) weakSelf = self;
330 self.completionBlock = ^(void) {
331 __strong __typeof(self) strongSelf = weakSelf;
332 if (!strongSelf) {
333 ckkserror("ckksincoming", ckks, "received callback for released object");
334 return;
335 }
336
337 CKKSAnalytics* logger = [CKKSAnalytics logger];
338
339 if (!strongSelf.error) {
340 [logger logSuccessForEvent:CKKSEventProcessIncomingQueueClassC inView:ckks];
341 if (!strongSelf.pendingClassAEntries) {
342 [logger logSuccessForEvent:CKKSEventProcessIncomingQueueClassA inView:ckks];
343 }
344 } else {
345 [logger logRecoverableError:strongSelf.error
346 forEvent:strongSelf.errorOnClassAFailure ? CKKSEventProcessIncomingQueueClassA : CKKSEventProcessIncomingQueueClassC
347 inView:ckks
348 withAttributes:NULL];
349 }
350 };
351
352 __block bool errored = false;
353 [ckks dispatchSync: ^bool{
354 if(self.cancelled) {
355 ckksnotice("ckksincoming", ckks, "CKKSIncomingQueueOperation cancelled, quitting");
356 return false;
357 }
358 ckks.lastIncomingQueueOperation = self;
359
360 ckksnotice("ckksincoming", ckks, "Processing incoming queue");
361
362 if ([CKKSManifest shouldSyncManifests]) {
363 if (!ckks.latestManifest) {
364 // Until we can make manifests in our unit tests, we can't abort here
365 ckkserror("ckksincoming", ckks, "no manifest in ckks");
366 }
367 if (!ckks.egoManifest) {
368 ckkserror("ckksincoming", ckks, "no ego manifest in ckks");
369 }
370 }
371
372 bool ok = true; // Should commit transaction?
373 __block NSError* error = nil;
374
375 if ([CKKSManifest shouldSyncManifests]) {
376 NSInteger unauthenticatedItemCount = [CKKSIncomingQueueEntry countByState:SecCKKSStateUnauthenticated zone:ckks.zoneID error:&error];
377 if (error || unauthenticatedItemCount < 0) {
378 ckkserror("ckksincoming", ckks, "Error fetching incoming queue state counts: %@", error);
379 self.error = error;
380 return false;
381 }
382
383 // take any existing unauthenticated entries and put them back in the new state
384 NSArray<CKKSIncomingQueueEntry*>* unauthenticatedEntries = nil;
385 NSString* lastMaxUUID = nil;
386 NSInteger numEntriesProcessed = 0;
387 while (numEntriesProcessed < unauthenticatedItemCount && (unauthenticatedEntries == nil || unauthenticatedEntries.count == SecCKKSIncomingQueueItemsAtOnce)) {
388 if(self.cancelled) {
389 ckksnotice("ckksincoming", ckks, "CKKSIncomingQueueOperation cancelled, quitting");
390 return false;
391 }
392
393 unauthenticatedEntries = [CKKSIncomingQueueEntry fetch:SecCKKSIncomingQueueItemsAtOnce
394 startingAtUUID:lastMaxUUID
395 state:SecCKKSStateUnauthenticated
396 zoneID:ckks.zoneID
397 error:&error];
398 if (error) {
399 ckkserror("ckksincoming", ckks, "Error fetching unauthenticated queue records: %@", error);
400 self.error = error;
401 return false;
402 }
403
404 if (unauthenticatedEntries.count == 0) {
405 ckksinfo("ckksincoming", ckks, "No unauthenticated entries in incoming queue to process");
406 break;
407 }
408
409 for (CKKSIncomingQueueEntry* unauthenticatedEntry in unauthenticatedEntries) {
410 if (![self _onqueueUpdateIQE:unauthenticatedEntry withState:SecCKKSStateNew error:&error]) {
411 ckkserror("ckksincoming", ckks, "Error saving unauthenticated entry back to new state: %@", error);
412 self.error = error;
413 return false;
414 }
415
416 lastMaxUUID = ([lastMaxUUID compare:unauthenticatedEntry.uuid] == NSOrderedDescending) ? lastMaxUUID : unauthenticatedEntry.uuid;
417 }
418 }
419 }
420 errored = !ok;
421 return ok;
422 }];
423
424 if(errored) {
425 ckksnotice("ckksincoming", ckks, "Early-exiting from IncomingQueueOperation");
426 return;
427 }
428
429 // Now for the tricky bit: take and drop the account queue for each batch of queue entries
430 // This is for peak memory concerns, but also to allow keychain API clients to make changes while we're processing many items
431 // Note that IncomingQueueOperations are no longer transactional: they can partially succeed. This might make them harder to reason about.
432 __block NSUInteger lastCount = SecCKKSIncomingQueueItemsAtOnce;
433 __block NSString* lastMaxUUID = nil;
434
435 while(lastCount == SecCKKSIncomingQueueItemsAtOnce) {
436 [ckks dispatchSync: ^bool{
437 NSArray<CKKSIncomingQueueEntry*> * queueEntries = nil;
438 if(self.cancelled) {
439 ckksnotice("ckksincoming", ckks, "CKKSIncomingQueueOperation cancelled, quitting");
440 errored = true;
441 return false;
442 }
443
444 NSError* error = nil;
445
446 queueEntries = [CKKSIncomingQueueEntry fetch: SecCKKSIncomingQueueItemsAtOnce
447 startingAtUUID:lastMaxUUID
448 state:SecCKKSStateNew
449 zoneID:ckks.zoneID
450 error: &error];
451
452 if(error != nil) {
453 ckkserror("ckksincoming", ckks, "Error fetching incoming queue records: %@", error);
454 self.error = error;
455 errored = true;
456 return false;
457 }
458
459 lastCount = queueEntries.count;
460
461 if([queueEntries count] == 0) {
462 // Nothing to do! exit.
463 ckksnotice("ckksincoming", ckks, "Nothing in incoming queue to process");
464 return true;
465 }
466
467 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventOutgoingQueue zone:ckks.zoneName count:[queueEntries count]];
468
469 if (![self processQueueEntries:queueEntries withManifest:ckks.latestManifest egoManifest:ckks.egoManifest]) {
470 ckksnotice("ckksincoming", ckks, "processQueueEntries didn't complete successfully");
471 errored = true;
472 return false;
473 }
474
475 // Find the highest UUID for the next fetch.
476 for(CKKSIncomingQueueEntry* iqe in queueEntries) {
477 lastMaxUUID = ([lastMaxUUID compare:iqe.uuid] == NSOrderedDescending) ? lastMaxUUID : iqe.uuid;
478 };
479 return true;
480 }];
481
482 if(errored) {
483 ckksnotice("ckksincoming", ckks, "Early-exiting from IncomingQueueOperation");
484 return;
485 }
486 }
487
488 ckksnotice("ckksincoming", ckks, "Processed %lu items in incoming queue (%lu errors)", (unsigned long)self.successfulItemsProcessed, (unsigned long)self.errorItemsProcessed);
489
490 [ckks dispatchSync: ^bool{
491 NSError* error = nil;
492
493 NSArray<CKKSCurrentItemPointer*>* newCIPs = [CKKSCurrentItemPointer remoteItemPointers:ckks.zoneID error:&error];
494 if(error || !newCIPs) {
495 ckkserror("ckksincoming", ckks, "Could not load remote item pointers: %@", error);
496 } else {
497 if (![self processNewCurrentItemPointers:newCIPs withManifest:ckks.latestManifest egoManifest:ckks.egoManifest]) {
498 return false;
499 }
500 ckksnotice("ckksincoming", ckks, "Processed %lu items in CIP queue", (unsigned long)newCIPs.count);
501 }
502
503 if(self.newOutgoingEntries) {
504 // No operation group
505 [ckks processOutgoingQueue:nil];
506 }
507
508 if(self.pendingClassAEntries) {
509 [self.ckks processIncomingQueueAfterNextUnlock];
510 }
511
512 return true;
513 }];
514 }
515
516 - (void)_onqueueHandleIQEChange: (CKKSIncomingQueueEntry*) iqe attributes:(NSDictionary*)attributes class:(const SecDbClass *)classP {
517 CKKSKeychainView* ckks = self.ckks;
518 if(!ckks) {
519 ckkserror("ckksincoming", ckks, "no CKKS object");
520 return;
521 }
522
523 dispatch_assert_queue(ckks.queue);
524
525 bool ok = false;
526 __block CFErrorRef cferror = NULL;
527 __block NSError* error = NULL;
528
529 SecDbItemRef item = SecDbItemCreateWithAttributes(NULL, classP, (__bridge CFDictionaryRef) attributes, KEYBAG_DEVICE, &cferror);
530
531 __block NSDate* moddate = (__bridge NSDate*) CFDictionaryGetValue(item->attributes, kSecAttrModificationDate);
532
533 ok = kc_with_dbt(true, &cferror, ^(SecDbConnectionRef dbt){
534 bool replaceok = SecDbItemInsertOrReplace(item, dbt, &cferror, ^(SecDbItemRef olditem, SecDbItemRef *replace) {
535 // If the UUIDs do not match, then select the item with the 'lower' UUID, and tell CKKS to
536 // delete the item with the 'higher' UUID.
537 // Otherwise, the cloud wins.
538
539 SecADAddValueForScalarKey((__bridge CFStringRef) SecCKKSAggdPrimaryKeyConflict,1);
540
541 // Note that SecDbItemInsertOrReplace CFReleases any replace pointer it's given, so, be careful
542
543 if(!CFDictionaryContainsKey(olditem->attributes, kSecAttrUUID)) {
544 // No UUID -> no good.
545 ckksnotice("ckksincoming", ckks, "Replacing item (it doesn't have a UUID) for %@", iqe.uuid);
546 if(replace) {
547 *replace = CFRetainSafe(item);
548 }
549 return;
550 }
551
552 CFStringRef itemUUID = CFDictionaryGetValue(item->attributes, kSecAttrUUID);
553 CFStringRef olditemUUID = CFDictionaryGetValue(olditem->attributes, kSecAttrUUID);
554
555 CFComparisonResult compare = CFStringCompare(itemUUID, olditemUUID, 0);
556 CKKSOutgoingQueueEntry* oqe = nil;
557 switch(compare) {
558 case kCFCompareLessThan:
559 // item wins; delete olditem
560 ckksnotice("ckksincoming", ckks, "Primary key conflict; replacing %@ with CK item %@", olditem, item);
561 if(replace) {
562 *replace = CFRetainSafe(item);
563 moddate = (__bridge NSDate*) CFDictionaryGetValue(item->attributes, kSecAttrModificationDate);
564 }
565
566 oqe = [CKKSOutgoingQueueEntry withItem:olditem action:SecCKKSActionDelete ckks:ckks error:&error];
567 [oqe saveToDatabase: &error];
568 self.newOutgoingEntries = true;
569 break;
570 case kCFCompareGreaterThan:
571 // olditem wins; don't change olditem; delete item
572 ckksnotice("ckksincoming", ckks, "Primary key conflict; dropping CK item %@", item);
573
574 oqe = [CKKSOutgoingQueueEntry withItem:item action:SecCKKSActionDelete ckks:ckks error:&error];
575 [oqe saveToDatabase: &error];
576 self.newOutgoingEntries = true;
577 moddate = nil;
578 break;
579
580 case kCFCompareEqualTo:
581 // remote item wins; this is the normal update case
582 ckksnotice("ckksincoming", ckks, "Primary key conflict; replacing %@ with CK item %@", olditem, item);
583 if(replace) {
584 *replace = CFRetainSafe(item);
585 moddate = (__bridge NSDate*) CFDictionaryGetValue(item->attributes, kSecAttrModificationDate);
586 }
587 break;
588 }
589 });
590
591 // SecDbItemInsertOrReplace returns an error even when it succeeds.
592 if(!replaceok && SecErrorIsSqliteDuplicateItemError(cferror)) {
593 CFReleaseNull(cferror);
594 replaceok = true;
595 }
596 return replaceok;
597 });
598
599 CFReleaseNull(item);
600
601 if(cferror) {
602 ckkserror("ckksincoming", ckks, "couldn't process item from IncomingQueue: %@", cferror);
603 SecTranslateError(&error, cferror);
604 self.error = error;
605
606 iqe.state = SecCKKSStateError;
607 [iqe saveToDatabase:&error];
608 if(error) {
609 ckkserror("ckksincoming", ckks, "Couldn't save errored IQE to database: %@", error);
610 self.error = error;
611 }
612 return;
613 }
614
615 if(error) {
616 ckkserror("ckksincoming", ckks, "Couldn't handle IQE, but why?: %@", error);
617 self.error = error;
618 return;
619 }
620
621 if(ok) {
622 ckksinfo("ckksincoming", ckks, "Correctly processed an IQE; deleting");
623 [iqe deleteFromDatabase: &error];
624
625 if(error) {
626 ckkserror("ckksincoming", ckks, "couldn't delete CKKSIncomingQueueEntry: %@", error);
627 self.error = error;
628 self.errorItemsProcessed += 1;
629 } else {
630 self.successfulItemsProcessed += 1;
631 }
632
633 if(moddate) {
634 // Log the number of seconds it took to propagate this change
635 uint64_t secondsDelay = (uint64_t) ([[NSDate date] timeIntervalSinceDate:moddate]);
636 SecADClientPushValueForDistributionKey((__bridge CFStringRef) SecCKKSAggdPropagationDelay, secondsDelay);
637 }
638
639 } else {
640 ckksnotice("ckksincoming", ckks, "IQE not correctly processed, but why? %@ %@", error, cferror);
641 self.error = error;
642
643 iqe.state = SecCKKSStateError;
644 [iqe saveToDatabase:&error];
645 if(error) {
646 ckkserror("ckksincoming", ckks, "Couldn't save errored IQE to database: %@", error);
647 self.error = error;
648 }
649
650 self.errorItemsProcessed += 1;
651 }
652 }
653
654 - (void)_onqueueHandleIQEDelete: (CKKSIncomingQueueEntry*) iqe class:(const SecDbClass *)classP {
655 CKKSKeychainView* ckks = self.ckks;
656 if(!ckks) {
657 ckkserror("ckksincoming", ckks, "no CKKS object");
658 return;
659 }
660
661 dispatch_assert_queue(ckks.queue);
662
663 bool ok = false;
664 __block CFErrorRef cferror = NULL;
665 NSError* error = NULL;
666 NSDictionary* queryAttributes = @{(__bridge NSString*) kSecClass: (__bridge NSString*) classP->name,
667 (__bridge NSString*) kSecAttrUUID: iqe.uuid,
668 (__bridge NSString*) kSecAttrSyncViewHint: ckks.zoneID.zoneName,
669 (__bridge NSString*) kSecAttrSynchronizable: @(YES)};
670 ckksnotice("ckksincoming", ckks, "trying to delete with query: %@", queryAttributes);
671 Query *q = query_create_with_limit( (__bridge CFDictionaryRef) queryAttributes, NULL, kSecMatchUnlimited, &cferror);
672
673
674 if(cferror) {
675 ckkserror("ckksincoming", ckks, "couldn't create query: %@", cferror);
676 SecTranslateError(&error, cferror);
677 self.error = error;
678 return;
679 }
680
681 ok = kc_with_dbt(true, &cferror, ^(SecDbConnectionRef dbt) {
682 return s3dl_query_delete(dbt, q, NULL, &cferror);
683 });
684
685 if(cferror) {
686 if(CFErrorGetCode(cferror) == errSecItemNotFound) {
687 ckkserror("ckksincoming", ckks, "couldn't delete item (as it's already gone); this is okay: %@", cferror);
688 ok = true;
689 CFReleaseNull(cferror);
690 } else {
691 ckkserror("ckksincoming", ckks, "couldn't delete item: %@", cferror);
692 SecTranslateError(&error, cferror);
693 self.error = error;
694 query_destroy(q, NULL);
695 return;
696 }
697 }
698
699
700 ok = query_notify_and_destroy(q, ok, &cferror);
701
702 if(cferror) {
703 ckkserror("ckksincoming", ckks, "couldn't delete query: %@", cferror);
704 SecTranslateError(&error, cferror);
705 self.error = error;
706 return;
707 }
708
709 if(ok) {
710 ckksnotice("ckksincoming", ckks, "Correctly processed an IQE; deleting");
711 [iqe deleteFromDatabase: &error];
712
713 if(error) {
714 ckkserror("ckksincoming", ckks, "couldn't delete CKKSIncomingQueueEntry: %@", error);
715 self.error = error;
716 self.errorItemsProcessed += 1;
717 } else {
718 self.successfulItemsProcessed += 1;
719 }
720 } else {
721 ckkserror("ckksincoming", ckks, "IQE not correctly processed, but why? %@ %@", error, cferror);
722 self.error = error;
723 self.errorItemsProcessed += 1;
724 }
725 }
726
727 @end;
728
729 #endif