]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSUpdateCurrentItemPointerOperation.m
Security-58286.20.16.tar.gz
[apple/security.git] / keychain / ckks / CKKSUpdateCurrentItemPointerOperation.m
1 /*
2 * Copyright (c) 2017 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 #if OCTAGON
25
26 #import "keychain/ckks/CKKSKeychainView.h"
27 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
28 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
29 #import "keychain/ckks/CKKSCurrentItemPointer.h"
30 #import "keychain/ckks/CKKSUpdateCurrentItemPointerOperation.h"
31 #import "keychain/ckks/CKKSManifest.h"
32
33 #import <CloudKit/CloudKit.h>
34
35 @interface CKKSUpdateCurrentItemPointerOperation ()
36 @property CKModifyRecordsOperation* modifyRecordsOperation;
37 @property CKOperationGroup* ckoperationGroup;
38
39 @property NSString* currentPointerIdentifier;
40 @property NSString* oldCurrentItemUUID;
41 @property NSString* currentItemUUID;
42 @end
43
44 @implementation CKKSUpdateCurrentItemPointerOperation
45
46 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*) ckks
47 currentPointer:(NSString*)identifier
48 oldItemUUID:(NSString*)oldItemUUID
49 newItemUUID:(NSString*)newItemUUID
50 ckoperationGroup:(CKOperationGroup*)ckoperationGroup
51 {
52 if((self = [super init])) {
53 _ckks = ckks;
54
55 _currentPointerIdentifier = identifier;
56 _oldCurrentItemUUID = oldItemUUID;
57 _currentItemUUID = newItemUUID;
58 _ckoperationGroup = ckoperationGroup;
59 }
60 return self;
61 }
62
63 - (void)groupStart {
64 CKKSKeychainView* ckks = self.ckks;
65 if(!ckks) {
66 ckkserror("ckkscurrent", ckks, "no CKKS object");
67 self.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
68 return;
69 }
70
71 __weak __typeof(self) weakSelf = self;
72
73 [ckks dispatchSyncWithAccountQueue:^bool {
74 if(self.cancelled) {
75 ckksnotice("ckksscan", ckks, "CKKSUpdateCurrentItemPointerOperation cancelled, quitting");
76 return false;
77 }
78
79 NSError* error = nil;
80
81 // Ensure that there's no pending pointer update
82 CKKSCurrentItemPointer* cipPending = [CKKSCurrentItemPointer tryFromDatabase:self.currentPointerIdentifier state:SecCKKSProcessedStateRemote zoneID:ckks.zoneID error:&error];
83 if(cipPending) {
84 self.error = [NSError errorWithDomain:@"securityd" code:errSecItemInvalidValue
85 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Update to current item pointer is pending."]}];
86 ckkserror("ckkscurrent", ckks, "Attempt to set a new current item pointer when one exists: %@", self.error);
87 return false;
88 }
89
90 CKKSCurrentItemPointer* cip = [CKKSCurrentItemPointer tryFromDatabase:self.currentPointerIdentifier state:SecCKKSProcessedStateLocal zoneID:ckks.zoneID error:&error];
91
92 if(cip) {
93 // Ensure that the itempointer matches the old item
94 if(![cip.currentItemUUID isEqualToString: self.oldCurrentItemUUID]) {
95 ckksnotice("ckkscurrent", ckks, "Caller's idea of the current item pointer for %@ doesn't match; rejecting change of current", cip);
96 self.error = [NSError errorWithDomain:@"securityd" code:errSecItemInvalidValue
97 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Current pointer does not match given value of '%@', aborting", self.oldCurrentItemUUID]}];
98 return false;
99 }
100 // Cool. Since you know what you're updating, you're allowed to update!
101 cip.currentItemUUID = self.currentItemUUID;
102
103 } else if(self.oldCurrentItemUUID) {
104 // Error case: the client thinks there's a current pointer, but we don't have one
105 ckksnotice("ckkscurrent", ckks, "Requested to update a current item pointer but one doesn't exist at %@; rejecting change of current", self.currentPointerIdentifier);
106 self.error = [NSError errorWithDomain:@"securityd" code:errSecItemInvalidValue
107 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Current pointer does not match given value of '%@', aborting", self.oldCurrentItemUUID]}];
108 return false;
109 } else {
110 // No current item pointer? How exciting! Let's make you a nice new one.
111 cip = [[CKKSCurrentItemPointer alloc] initForIdentifier:self.currentPointerIdentifier currentItemUUID:self.currentItemUUID state:SecCKKSProcessedStateLocal zoneID:ckks.zoneID encodedCKRecord:nil];
112 ckksnotice("ckkscurrent", ckks, "Creating a new current item pointer: %@", cip);
113 }
114
115 // Check if either item is currently in any sync queue, and fail if so
116 NSArray* oqes = [CKKSOutgoingQueueEntry allUUIDs:&error];
117 NSArray* iqes = [CKKSIncomingQueueEntry allUUIDs:&error];
118 if([oqes containsObject:self.currentItemUUID] || [iqes containsObject:self.currentItemUUID]) {
119 error = [NSError errorWithDomain:@"securityd" code:errSecItemInvalidValue
120 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"New item(%@) is being synced; can't set current pointer.", self.currentItemUUID]}];
121 }
122 if([oqes containsObject: self.oldCurrentItemUUID] || [iqes containsObject:self.oldCurrentItemUUID]) {
123 error = [NSError errorWithDomain:@"securityd" code:errSecItemInvalidValue
124 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Old item(%@) is being synced; can't set current pointer.", self.oldCurrentItemUUID]}];
125 }
126
127 if(error) {
128 ckkserror("ckkscurrent", ckks, "Error attempting to update current item pointer %@: %@", self.currentPointerIdentifier, error);
129 self.error = error;
130 return false;
131 }
132
133 // Make sure the item is synced, though!
134 CKKSMirrorEntry* ckme = [CKKSMirrorEntry fromDatabase:cip.currentItemUUID zoneID:ckks.zoneID error:&error];
135 if(!ckme || error) {
136 ckkserror("ckkscurrent", ckks, "Error attempting to set a current item pointer to an item that isn't synced: %@ %@", cip, ckme);
137 // Why can't you put nulls in dictionary literals?
138 if(error) {
139 error = [NSError errorWithDomain:@"securityd"
140 code:errSecItemNotFound
141 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"No synced item matching (%@); can't set current pointer.", cip.currentItemUUID],
142 NSUnderlyingErrorKey:error,
143 }];
144 } else {
145 error = [NSError errorWithDomain:@"securityd"
146 code:errSecItemNotFound
147 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"No synced item matching (%@); can't set current pointer.", cip.currentItemUUID],
148 }];
149 }
150 self.error = error;
151 return false;
152 }
153
154 if ([CKKSManifest shouldSyncManifests]) {
155 [ckks.egoManifest setCurrentItemUUID:self.currentItemUUID forIdentifier:self.currentPointerIdentifier];
156 }
157
158 ckksnotice("ckkscurrent", ckks, "Saving new current item pointer %@", cip);
159
160 NSMutableDictionary<CKRecordID*, CKRecord*>* recordsToSave = [[NSMutableDictionary alloc] init];
161 CKRecord* record = [cip CKRecordWithZoneID:ckks.zoneID];
162 recordsToSave[record.recordID] = record;
163
164 if([CKKSManifest shouldSyncManifests]) {
165 for(CKRecord* record in [ckks.egoManifest allCKRecordsWithZoneID:ckks.zoneID]) {
166 recordsToSave[record.recordID] = record;
167 }
168 }
169
170 // Start a CKModifyRecordsOperation to save this new/updated record.
171 NSBlockOperation* modifyComplete = [[NSBlockOperation alloc] init];
172 modifyComplete.name = @"updateCurrentItemPointer-modifyRecordsComplete";
173 [self dependOnBeforeGroupFinished: modifyComplete];
174
175 self.modifyRecordsOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave.allValues recordIDsToDelete:nil];
176 self.modifyRecordsOperation.atomic = TRUE;
177 self.modifyRecordsOperation.timeoutIntervalForRequest = 2;
178 self.modifyRecordsOperation.qualityOfService = NSQualityOfServiceUtility;
179 self.modifyRecordsOperation.savePolicy = CKRecordSaveIfServerRecordUnchanged;
180 self.modifyRecordsOperation.group = self.ckoperationGroup;
181
182 self.modifyRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
183 __strong __typeof(weakSelf) strongSelf = weakSelf;
184 __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
185
186 if(!error) {
187 ckksnotice("ckkscurrent", blockCKKS, "Current pointer upload successful for %@: %@", record.recordID.recordName, record);
188 } else {
189 ckkserror("ckkscurrent", blockCKKS, "error on row: %@ %@", error, record);
190 }
191 };
192
193 self.modifyRecordsOperation.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *ckerror) {
194 __strong __typeof(weakSelf) strongSelf = weakSelf;
195 __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
196 if(!strongSelf || !strongCKKS) {
197 ckkserror("ckkscurrent", strongCKKS, "received callback for released object");
198 strongSelf.error = [NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
199 [strongCKKS scheduleOperation: modifyComplete];
200 return;
201 }
202
203 if(ckerror) {
204 ckkserror("ckkscurrent", strongCKKS, "CloudKit returned an error: %@", ckerror);
205 strongSelf.error = ckerror;
206
207 [ckks dispatchSync:^bool {
208 return [ckks _onqueueCKWriteFailed:ckerror attemptedRecordsChanged:recordsToSave];
209 }];
210
211 [strongCKKS scheduleOperation: modifyComplete];
212 return;
213 }
214
215 __block NSError* error = nil;
216
217 [strongCKKS dispatchSync: ^bool{
218 for(CKRecord* record in savedRecords) {
219 // Save the item records
220 if([record.recordType isEqualToString: SecCKRecordCurrentItemType]) {
221 if([cip matchesCKRecord: record]) {
222 cip.storedCKRecord = record;
223 [cip saveToDatabase:&error];
224 if(error) {
225 ckkserror("ckkscurrent", strongCKKS, "Couldn't save new current pointer to database: %@", error);
226 }
227 } else {
228 ckkserror("ckkscurrent", strongCKKS, "CloudKit record does not match saved record, ignoring: %@ %@", record, cip);
229 }
230 }
231 else if ([CKKSManifest shouldSyncManifests] && [record.recordType isEqualToString:SecCKRecordManifestType]) {
232 CKKSManifest* manifest = [[CKKSManifest alloc] initWithCKRecord:record];
233 [manifest saveToDatabase:&error];
234 if (error) {
235 ckkserror("ckkscurrent", strongCKKS, "Couldn't save %@ to manifest: %@", record.recordID.recordName, error);
236 strongSelf.error = error;
237 }
238 }
239
240 // Schedule a 'view changed' notification
241 [strongCKKS.notifyViewChangedScheduler trigger];
242 }
243 return true;
244 }];
245
246 strongSelf.error = error;
247 [strongCKKS scheduleOperation: modifyComplete];
248 };
249
250 [self dependOnBeforeGroupFinished: self.modifyRecordsOperation];
251 [ckks.database addOperation: self.modifyRecordsOperation];
252
253 return true;
254 }];
255 }
256
257 @end
258
259 #endif // OCTAGON