]>
Commit | Line | Data |
---|---|---|
866f8763 A |
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 |