]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSOutgoingQueueOperation.m
Security-58286.31.2.tar.gz
[apple/security.git] / keychain / ckks / CKKSOutgoingQueueOperation.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 "CKKSCurrentKeyPointer.h"
26 #import "CKKSOutgoingQueueOperation.h"
27 #import "CKKSIncomingQueueEntry.h"
28 #import "CKKSItemEncrypter.h"
29 #import "CKKSOutgoingQueueEntry.h"
30 #import "CKKSReencryptOutgoingItemsOperation.h"
31 #import "CKKSManifest.h"
32 #import "CKKSAnalyticsLogger.h"
33
34 #include <securityd/SecItemServer.h>
35 #include <securityd/SecItemDb.h>
36 #include <Security/SecItemPriv.h>
37 #include <utilities/SecADWrapper.h>
38 #import "CKKSPowerCollection.h"
39
40 #if OCTAGON
41
42 @interface CKKSOutgoingQueueOperation()
43 @property CKModifyRecordsOperation* modifyRecordsOperation;
44 @end
45
46 @implementation CKKSOutgoingQueueOperation
47
48 - (instancetype)init {
49 if(self = [super init]) {
50 }
51 return nil;
52 }
53 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
54 if(self = [super init]) {
55 _ckks = ckks;
56 _ckoperationGroup = ckoperationGroup;
57
58 [self addNullableDependency:ckks.viewSetupOperation];
59 [self addNullableDependency:ckks.holdOutgoingQueueOperation];
60
61 // Depend on all previous CKKSOutgoingQueueOperations
62 [self linearDependencies:ckks.outgoingQueueOperations];
63
64 // We also depend on the view being setup and the key hierarchy being reasonable
65 [self addNullableDependency:ckks.viewSetupOperation];
66 [self addNullableDependency:ckks.keyStateReadyDependency];
67 }
68 return self;
69 }
70
71 - (void) groupStart {
72 // Synchronous, on some thread. Get back on the CKKS queue for thread-safety.
73 __weak __typeof(self) weakSelf = self;
74
75 CKKSKeychainView* ckks = self.ckks;
76 if(!ckks) {
77 ckkserror("ckksoutgoing", ckks, "no ckks object");
78 return;
79 }
80
81 [ckks dispatchSyncWithAccountKeys: ^bool{
82 ckks.lastOutgoingQueueOperation = self;
83 if(self.cancelled) {
84 ckksnotice("ckksoutgoing", ckks, "CKKSOutgoingQueueOperation cancelled, quitting");
85 return false;
86 }
87
88 NSError* error = nil;
89
90 // We only actually care about queue items in the 'new' state
91 NSArray<CKKSOutgoingQueueEntry*> * queueEntries = [CKKSOutgoingQueueEntry fetch:SecCKKSOutgoingQueueItemsAtOnce state: SecCKKSStateNew zoneID:ckks.zoneID error:&error];
92
93 if(error != nil) {
94 ckkserror("ckksoutgoing", ckks, "Error fetching outgoing queue records: %@", error);
95 self.error = error;
96 return false;
97 }
98
99 ckksinfo("ckksoutgoing", ckks, "processing outgoing queue: %@", queueEntries);
100
101 NSMutableDictionary<CKRecordID*, CKRecord*>* recordsToSave = [[NSMutableDictionary alloc] init];
102 NSMutableSet<CKRecordID*>* oqesModified = [[NSMutableSet alloc] init];
103 NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
104
105 CKKSCurrentKeyPointer* currentClassAKeyPointer = [CKKSCurrentKeyPointer fromDatabase: SecCKKSKeyClassA zoneID:ckks.zoneID error: &error];
106 CKKSCurrentKeyPointer* currentClassCKeyPointer = [CKKSCurrentKeyPointer fromDatabase: SecCKKSKeyClassC zoneID:ckks.zoneID error: &error];
107 NSMutableDictionary<CKKSKeyClass*, CKKSCurrentKeyPointer*>* currentKeysToSave = [[NSMutableDictionary alloc] init];
108 bool needsReencrypt = false;
109
110 if(error != nil) {
111 ckkserror("ckksoutgoing", ckks, "Couldn't load current class keys: %@", error);
112 return false;
113 }
114
115 for(CKKSOutgoingQueueEntry* oqe in queueEntries) {
116 if(self.cancelled) {
117 secdebug("ckksoutgoing", "CKKSOutgoingQueueOperation cancelled, quitting");
118 return false;
119 }
120
121 CKKSOutgoingQueueEntry* inflight = [CKKSOutgoingQueueEntry tryFromDatabase: oqe.uuid state:SecCKKSStateInFlight zoneID:ckks.zoneID error: &error];
122 if(!error && inflight) {
123 // There is an inflight request with this UUID. Leave this request in-queue until CloudKit returns and we resolve the inflight request.
124 continue;
125 }
126
127 // If this item is not a delete, check the encryption status of this item.
128 if(![oqe.action isEqualToString: SecCKKSActionDelete]) {
129 // Check if this item is encrypted under a current key
130 if([oqe.item.parentKeyUUID isEqualToString: currentClassAKeyPointer.currentKeyUUID]) {
131 // Excellent.
132 currentKeysToSave[SecCKKSKeyClassA] = currentClassAKeyPointer;
133
134 } else if([oqe.item.parentKeyUUID isEqualToString: currentClassCKeyPointer.currentKeyUUID]) {
135 // Works for us!
136 currentKeysToSave[SecCKKSKeyClassC] = currentClassCKeyPointer;
137
138 } else {
139 // This item is encrypted under an old key. Set it up for reencryption and move on.
140 ckksnotice("ckksoutgoing", ckks, "Item's encryption key (%@ %@) is neither %@ or %@", oqe, oqe.item.parentKeyUUID, currentClassAKeyPointer, currentClassCKeyPointer);
141 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateReencrypt error:&error];
142 if(error) {
143 ckkserror("ckksoutgoing", ckks, "couldn't save oqe to database: %@", error);
144 self.error = error;
145 error = nil;
146 }
147 needsReencrypt = true;
148 continue;
149 }
150 }
151
152 if([oqe.action isEqualToString: SecCKKSActionAdd]) {
153 CKRecord* record = [oqe.item CKRecordWithZoneID: ckks.zoneID];
154 recordsToSave[record.recordID] = record;
155 [oqesModified addObject: record.recordID];
156
157 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateInFlight error:&error];
158 if(error) {
159 ckkserror("ckksoutgoing", ckks, "couldn't save state for CKKSOutgoingQueueEntry: %@", error);
160 self.error = error;
161 }
162
163 } else if ([oqe.action isEqualToString: SecCKKSActionDelete]) {
164 [recordIDsToDelete addObject: [[CKRecordID alloc] initWithRecordName: oqe.item.uuid zoneID: ckks.zoneID]];
165
166 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateInFlight error:&error];
167 if(error) {
168 ckkserror("ckksoutgoing", ckks, "couldn't save state for CKKSOutgoingQueueEntry: %@", error);
169 }
170
171 } else if ([oqe.action isEqualToString: SecCKKSActionModify]) {
172 // Load the existing item from the ckmirror.
173 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase: oqe.item.uuid zoneID:ckks.zoneID error:&error];
174 if(!ckme) {
175 // This is a problem: we have an update to an item that doesn't exist.
176 // Either: an Add operation we launched failed due to a CloudKit error (conflict?) and this is a follow-on update
177 // Or: ?
178 ckkserror("ckksoutgoing", ckks, "update to a record that doesn't exist? %@", oqe.item.uuid);
179 // treat as an add.
180 CKRecord* record = [oqe.item CKRecordWithZoneID: ckks.zoneID];
181 recordsToSave[record.recordID] = record;
182 [oqesModified addObject: record.recordID];
183
184 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateInFlight error:&error];
185 if(error) {
186 ckkserror("ckksoutgoing", ckks, "couldn't save state for CKKSOutgoingQueueEntry: %@", error);
187 self.error = error;
188 }
189 } else {
190 if(![oqe.item.storedCKRecord.recordChangeTag isEqual: ckme.item.storedCKRecord.recordChangeTag]) {
191 // The mirror entry has updated since this item was created. If we proceed, we might end up with
192 // a badly-authenticated record.
193 ckksnotice("ckksoutgoing", ckks, "Record (%@)'s change tag doesn't match ckmirror's change tag, reencrypting", oqe);
194 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateReencrypt error:&error];
195 if(error) {
196 ckkserror("ckksoutgoing", ckks, "couldn't save oqe to database: %@", error);
197 self.error = error;
198 error = nil;
199 }
200 needsReencrypt = true;
201 continue;
202 }
203 // Grab the old ckrecord and update it
204 CKRecord* record = [oqe.item updateCKRecord: ckme.item.storedCKRecord zoneID: ckks.zoneID];
205 recordsToSave[record.recordID] = record;
206 [oqesModified addObject: record.recordID];
207
208 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateInFlight error:&error];
209 if(error) {
210 ckkserror("ckksoutgoing", ckks, "couldn't save state for CKKSOutgoingQueueEntry: %@", error);
211 }
212 }
213 }
214 }
215
216 if(needsReencrypt) {
217 ckksnotice("ckksoutgoing", ckks, "An item needs reencryption!");
218
219 CKKSReencryptOutgoingItemsOperation* op = [[CKKSReencryptOutgoingItemsOperation alloc] initWithCKKSKeychainView:ckks ckoperationGroup:self.ckoperationGroup];
220 [ckks scheduleOperation: op];
221 }
222
223 if([recordsToSave count] == 0 && [recordIDsToDelete count] == 0) {
224 // Nothing to do! exit.
225 ckksnotice("ckksoutgoing", ckks, "Nothing in outgoing queue to process");
226 if(self.ckoperationGroup) {
227 ckksnotice("ckksoutgoing", ckks, "End of operation group: %@", self.ckoperationGroup);
228 }
229 return true;
230 }
231
232 self.itemsProcessed = recordsToSave.count;
233
234 NSBlockOperation* modifyComplete = [[NSBlockOperation alloc] init];
235 modifyComplete.name = @"modifyRecordsComplete";
236 [self dependOnBeforeGroupFinished: modifyComplete];
237
238 if ([CKKSManifest shouldSyncManifests]) {
239 if (ckks.egoManifest) {
240 [ckks.egoManifest updateWithNewOrChangedRecords:recordsToSave.allValues deletedRecordIDs:recordIDsToDelete];
241 for(CKRecord* record in [ckks.egoManifest allCKRecordsWithZoneID:ckks.zoneID]) {
242 recordsToSave[record.recordID] = record;
243 }
244 NSError* saveError = nil;
245 if (![ckks.egoManifest saveToDatabase:&saveError]) {
246 self.error = saveError;
247 ckkserror("ckksoutgoing", ckks, "could not save ego manifest with error: %@", saveError);
248 }
249 }
250 else {
251 ckkserror("ckksoutgoing", ckks, "could not get current ego manifest to update");
252 }
253 }
254
255 void (^modifyRecordsCompletionBlock)(NSArray<CKRecord*>*, NSArray<CKRecordID*>*, NSError*) = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *ckerror) {
256 __strong __typeof(weakSelf) strongSelf = weakSelf;
257 __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
258 if(!strongSelf || !strongCKKS) {
259 ckkserror("ckksoutgoing", strongCKKS, "received callback for released object");
260 return;
261 }
262
263 [strongCKKS dispatchSync: ^bool{
264 if(ckerror) {
265 ckkserror("ckksoutgoing", strongCKKS, "error processing outgoing queue: %@", ckerror);
266
267 // Tell CKKS about any out-of-date records
268 [strongCKKS _onqueueCKWriteFailed:ckerror attemptedRecordsChanged:recordsToSave];
269
270 // Check if these are due to key records being out of date. We'll see a CKErrorBatchRequestFailed, with a bunch of errors inside
271 if([ckerror.domain isEqualToString:CKErrorDomain] && (ckerror.code == CKErrorPartialFailure)) {
272 NSMutableDictionary<CKRecordID*, NSError*>* failedRecords = ckerror.userInfo[CKPartialErrorsByItemIDKey];
273 ckksnotice("ckksoutgoing", strongCKKS, "failed records %@", failedRecords);
274 for(CKRecordID* recordID in failedRecords.allKeys) {
275 NSError* recordError = failedRecords[recordID];
276
277 if(recordError.code == CKErrorServerRecordChanged) {
278 if([recordID.recordName isEqualToString: SecCKKSKeyClassA] ||
279 [recordID.recordName isEqualToString: SecCKKSKeyClassC]) {
280 // The current key pointers have updated without our knowledge, so CloudKit failed this operation. Mark all records as 'needs reencryption' and kick that off.
281 [strongSelf _onqueueModifyAllRecordsAsReencrypt: failedRecords.allKeys];
282
283 // Note that _onqueueCKWriteFailed is responsible for kicking the key state machine, so we don't need to do it here.
284 // This will wait for the key hierarchy to become 'ready'
285 CKKSReencryptOutgoingItemsOperation* op = [[CKKSReencryptOutgoingItemsOperation alloc] initWithCKKSKeychainView:strongCKKS ckoperationGroup:strongSelf.ckoperationGroup];
286 [strongCKKS scheduleOperation: op];
287
288 // Quit the loop so we only do this once
289 break;
290 } else {
291 // CKErrorServerRecordChanged on an item update means that we've been overwritten.
292 if([oqesModified containsObject:recordID]) {
293 [strongSelf _onqueueModifyRecordAsError:recordID recordError:recordError];
294 }
295 }
296 } else if(recordError.code == CKErrorBatchRequestFailed) {
297 // Also fine. This record only didn't succeed because something else failed.
298 // OQEs should be placed back into the 'new' state, unless they've been overwritten by a new OQE. Other records should be ignored.
299
300 if([oqesModified containsObject:recordID]) {
301 NSError* error = nil;
302 CKKSOutgoingQueueEntry* inflightOQE = [CKKSOutgoingQueueEntry tryFromDatabase:recordID.recordName state:SecCKKSStateInFlight zoneID:recordID.zoneID error:&error];
303 CKKSOutgoingQueueEntry* newOQE = [CKKSOutgoingQueueEntry tryFromDatabase:recordID.recordName state:SecCKKSStateNew zoneID:recordID.zoneID error:&error];
304 if(error) {
305 ckkserror("ckksoutgoing", strongCKKS, "Couldn't try to fetch an overwriting OQE: %@", error);
306 }
307
308 if(newOQE) {
309 ckksnotice("ckksoutgoing", strongCKKS, "New modification has come in behind failed change for %@; dropping failed change", inflightOQE);
310 [strongCKKS _onqueueChangeOutgoingQueueEntry:inflightOQE toState:SecCKKSStateDeleted error:&error];
311 if(error) {
312 ckkserror("ckksoutgoing", strongCKKS, "Couldn't delete in-flight OQE: %@", error);
313 }
314 } else {
315 [strongCKKS _onqueueChangeOutgoingQueueEntry:inflightOQE toState:SecCKKSStateNew error:&error];
316 }
317 }
318
319 } else if ([recordID.recordName hasPrefix:@"Manifest:-:"] || [recordID.recordName hasPrefix:@"ManifestLeafRecord:-:"]) {
320 [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"ManifestUpload" withAttributes:@{CKKSManifestZoneKey : strongCKKS.zoneID.zoneName, CKKSManifestSignerIDKey : strongCKKS.egoManifest.signerID, CKKSManifestGenCountKey : @(strongCKKS.egoManifest.generationCount)}];
321 } else {
322 // Some unknown error occurred on this record. If it's an OQE, move it to the error state.
323 ckkserror("ckksoutgoing", strongCKKS, "Unknown error on row: %@ %@", recordID, recordError);
324 if([oqesModified containsObject:recordID]) {
325 [strongSelf _onqueueModifyRecordAsError:recordID recordError:recordError];
326 }
327 }
328 }
329 }
330
331 strongSelf.error = error;
332 return true;
333 }
334
335 ckksnotice("ckksoutgoing", strongCKKS, "Completed processing outgoing queue");
336 NSError* error = NULL;
337 CKKSPowerCollection *plstats = [[CKKSPowerCollection alloc] init];
338
339 for(CKRecord* record in savedRecords) {
340 // Save the item records
341 if([record.recordType isEqualToString: SecCKRecordItemType]) {
342 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry fromDatabase: record.recordID.recordName state: SecCKKSStateInFlight zoneID:strongCKKS.zoneID error:&error];
343 [strongCKKS _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateDeleted error:&error];
344 if(error) {
345 ckkserror("ckksoutgoing", strongCKKS, "Couldn't update %@ in outgoingqueue: %@", record.recordID.recordName, error);
346 strongSelf.error = error;
347 }
348 error = nil;
349 CKKSMirrorEntry* ckme = [[CKKSMirrorEntry alloc] initWithCKRecord: record];
350 [ckme saveToDatabase: &error];
351 if(error) {
352 ckkserror("ckksoutgoing", strongCKKS, "Couldn't save %@ to ckmirror: %@", record.recordID.recordName, error);
353 strongSelf.error = error;
354 }
355
356 [plstats storedOQE:oqe];
357
358 // And the CKCurrentKeyRecords (do we need to do this? Will the server update the change tag on a save which saves nothing?)
359 } else if([record.recordType isEqualToString: SecCKRecordCurrentKeyType]) {
360 CKKSCurrentKeyPointer* currentkey = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: record];
361 [currentkey saveToDatabase: &error];
362 if(error) {
363 ckkserror("ckksoutgoing", strongCKKS, "Couldn't save %@ to currentkey: %@", record.recordID.recordName, error);
364 strongSelf.error = error;
365 }
366
367 } else if ([record.recordType isEqualToString:SecCKRecordDeviceStateType]) {
368 CKKSDeviceStateEntry* newcdse = [[CKKSDeviceStateEntry alloc] initWithCKRecord:record];
369 [newcdse saveToDatabase:&error];
370 if(error) {
371 ckkserror("ckksoutgoing", strongCKKS, "Couldn't save %@ to ckdevicestate: %@", record.recordID.recordName, error);
372 strongSelf.error = error;
373 }
374
375 } else if ([record.recordType isEqualToString:SecCKRecordManifestType]) {
376 [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"ManifestUpload"];
377 } else if (![record.recordType isEqualToString:SecCKRecordManifestLeafType]) {
378 ckkserror("ckksoutgoing", strongCKKS, "unknown record type in results: %@", record);
379 }
380 }
381
382 // Delete the deleted record IDs
383 for(CKRecordID* ckrecordID in deletedRecordIDs) {
384
385 NSError* error = nil;
386 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry fromDatabase: ckrecordID.recordName state: SecCKKSStateInFlight zoneID:strongCKKS.zoneID error:&error];
387 [strongCKKS _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateDeleted error:&error];
388 if(error) {
389 ckkserror("ckksoutgoing", strongCKKS, "Couldn't delete %@ from outgoingqueue: %@", ckrecordID.recordName, error);
390 strongSelf.error = error;
391 }
392 error = nil;
393 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase: ckrecordID.recordName zoneID:strongCKKS.zoneID error:&error];
394 [ckme deleteFromDatabase: &error];
395 if(error) {
396 ckkserror("ckksoutgoing", strongCKKS, "Couldn't delete %@ from ckmirror: %@", ckrecordID.recordName, error);
397 strongSelf.error = error;
398 }
399
400 [plstats deletedOQE:oqe];
401 }
402
403 [plstats commit];
404
405 if(strongSelf.error) {
406 ckkserror("ckksoutgoing", strongCKKS, "Operation failed; rolling back: %@", strongSelf.error);
407 return false;
408 }
409 return true;
410 }];
411
412
413 [strongSelf.operationQueue addOperation: modifyComplete];
414 // Kick off another queue process. We expect it to exit instantly, but who knows!
415 [strongCKKS processOutgoingQueue:strongSelf.ckoperationGroup];
416 };
417
418 ckksinfo("ckksoutgoing", ckks, "Current keys to update: %@", currentKeysToSave);
419 for(CKKSCurrentKeyPointer* keypointer in currentKeysToSave.allValues) {
420 CKRecord* record = [keypointer CKRecordWithZoneID: ckks.zoneID];
421 recordsToSave[record.recordID] = record;
422 }
423
424 // Piggyback on this operation to update our device state
425 NSError* cdseError = nil;
426 CKKSDeviceStateEntry* cdse = [ckks _onqueueCurrentDeviceStateEntry:&cdseError];
427 CKRecord* cdseRecord = [cdse CKRecordWithZoneID: ckks.zoneID];
428 if(cdseError) {
429 ckkserror("ckksoutgoing", ckks, "Can't make current device state: %@", cdseError);
430 } else if(!cdseRecord) {
431 ckkserror("ckksoutgoing", ckks, "Can't make current device state cloudkit record, but no reason why");
432 } else {
433 // Add the CDSE to the outgoing records
434 // TODO: maybe only do this every few hours?
435 ckksnotice("ckksoutgoing", ckks, "Updating device state: %@", cdse);
436 recordsToSave[cdseRecord.recordID] = cdseRecord;
437 }
438
439 ckksinfo("ckksoutgoing", ckks, "Saving records %@ to CloudKit zone %@", recordsToSave, ckks.zoneID);
440
441 self.modifyRecordsOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave.allValues recordIDsToDelete:recordIDsToDelete];
442 self.modifyRecordsOperation.atomic = TRUE;
443 self.modifyRecordsOperation.timeoutIntervalForRequest = 2;
444 self.modifyRecordsOperation.qualityOfService = NSQualityOfServiceUtility;
445 self.modifyRecordsOperation.savePolicy = CKRecordSaveIfServerRecordUnchanged;
446 self.modifyRecordsOperation.group = self.ckoperationGroup;
447 ckksnotice("ckksoutgoing", ckks, "Operation group is %@", self.ckoperationGroup);
448
449 self.modifyRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
450 __strong __typeof(weakSelf) strongSelf = weakSelf;
451 __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
452
453 if(!error) {
454 ckksnotice("ckksoutgoing", blockCKKS, "Record upload successful for %@", record.recordID.recordName);
455 } else {
456 ckkserror("ckksoutgoing", blockCKKS, "error on row: %@ %@", error, record);
457 }
458 };
459
460 self.modifyRecordsOperation.modifyRecordsCompletionBlock = modifyRecordsCompletionBlock;
461 [self dependOnBeforeGroupFinished: self.modifyRecordsOperation];
462 [ckks.database addOperation: self.modifyRecordsOperation];
463
464 return true;
465 }];
466 }
467
468 - (void)_onqueueModifyRecordAsError:(CKRecordID*)recordID recordError:(NSError*)itemerror {
469 CKKSKeychainView* ckks = self.ckks;
470 if(!ckks) {
471 ckkserror("ckksoutgoing", ckks, "no CKKS object");
472 return;
473 }
474
475 dispatch_assert_queue(ckks.queue);
476
477 NSError* error = nil;
478 uint64_t count = 0;
479
480 // At this stage, cloudkit doesn't give us record types
481 if([recordID.recordName isEqualToString: SecCKKSKeyClassA] ||
482 [recordID.recordName isEqualToString: SecCKKSKeyClassC] ||
483 [recordID.recordName hasPrefix:@"Manifest:-:"] ||
484 [recordID.recordName hasPrefix:@"ManifestLeafRecord:-:"]) {
485 // Nothing to do here. We need a whole key refetch and synchronize.
486 } else {
487 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry fromDatabase:recordID.recordName state: SecCKKSStateInFlight zoneID:ckks.zoneID error:&error];
488 [ckks _onqueueErrorOutgoingQueueEntry: oqe itemError: itemerror error:&error];
489 if(error) {
490 ckkserror("ckksoutgoing", ckks, "Couldn't set OQE %@ as error: %@", recordID.recordName, error);
491 self.error = error;
492 }
493 count ++;
494 }
495 }
496
497
498 - (void)_onqueueModifyAllRecordsAsReencrypt: (NSArray<CKRecordID*>*) recordIDs {
499 CKKSKeychainView* ckks = self.ckks;
500 if(!ckks) {
501 ckkserror("ckksoutgoing", ckks, "no CKKS object");
502 return;
503 }
504
505 dispatch_assert_queue(ckks.queue);
506
507 NSError* error = nil;
508 uint64_t count = 0;
509
510 for(CKRecordID* recordID in recordIDs) {
511 // At this stage, cloudkit doesn't give us record types
512 if([recordID.recordName isEqualToString: SecCKKSKeyClassA] ||
513 [recordID.recordName isEqualToString: SecCKKSKeyClassC]) {
514 // Nothing to do here. We need a whole key refetch and synchronize.
515 } else {
516 CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry fromDatabase:recordID.recordName state: SecCKKSStateInFlight zoneID:ckks.zoneID error:&error];
517 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateReencrypt error:&error];
518 if(error) {
519 ckkserror("ckksoutgoing", ckks, "Couldn't set OQE %@ as reencrypt: %@", recordID.recordName, error);
520 self.error = error;
521 }
522 count ++;
523 }
524 }
525
526 SecADAddValueForScalarKey((__bridge CFStringRef) SecCKKSAggdItemReencryption, count);
527 }
528
529 @end;
530
531 #endif