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