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