]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTCloudStore.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / ot / OTCloudStore.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 #if OCTAGON
24
25 #import <Foundation/Foundation.h>
26 #import <Foundation/NSKeyedArchiver_Private.h>
27 #import <CloudKit/CloudKit.h>
28 #import <CloudKit/CloudKit_Private.h>
29
30 #import "keychain/ot/OTConstants.h"
31 #import "keychain/ot/OTCloudStore.h"
32 #import "keychain/ot/OTCloudStoreState.h"
33 #import "keychain/ckks/CKKSZoneStateEntry.h"
34 #import "keychain/ckks/CKKS.h"
35 #import "keychain/ot/OTDefines.h"
36 #import "keychain/ckks/CKKSReachabilityTracker.h"
37 #import <utilities/debugging.h>
38
39 #import "keychain/ckks/CKKSZoneModifier.h"
40
41
42 NS_ASSUME_NONNULL_BEGIN
43
44 /* Octagon Trust Local Context Record Constants */
45 static NSString* OTCKRecordContextID = @"contextID";
46 static NSString* OTCKRecordDSID = @"accountDSID";
47 static NSString* OTCKRecordContextName = @"contextName";
48 static NSString* OTCKRecordZoneCreated = @"zoneCreated";
49 static NSString* OTCKRecordSubscribedToChanges = @"subscribedToChanges";
50 static NSString* OTCKRecordChangeToken = @"changeToken";
51 static NSString* OTCKRecordEgoPeerID = @"egoPeerID";
52 static NSString* OTCKRecordEgoPeerCreationDate = @"egoPeerCreationDate";
53 static NSString* OTCKRecordRecoverySigningSPKI = @"recoverySigningSPKI";
54 static NSString* OTCKRecordRecoveryEncryptionSPKI = @"recoveryEncryptionSPKI";
55 static NSString* OTCKRecordBottledPeerTableEntry = @"bottledPeer";
56
57 /* Octagon Trust Local Peer Record */
58 static NSString* OTCKRecordPeerID = @"peerID";
59 static NSString* OTCKRecordPermanentInfo = @"permanentInfo";
60 static NSString* OTCKRecordStableInfo = @"stableInfo";
61 static NSString* OTCKRecordDynamicInfo = @"dynamicInfo";
62 static NSString* OTCKRecordRecoveryVoucher = @"recoveryVoucher";
63 static NSString* OTCKRecordIsEgoPeer = @"isEgoPeer";
64
65 /* Octagon Trust BottledPeerSchema */
66 static NSString* OTCKRecordEscrowRecordID = @"escrowRecordID";
67 static NSString* OTCKRecordBottle = @"bottle";
68 static NSString* OTCKRecordSPID = @"spID";
69 static NSString* OTCKRecordEscrowSigningSPKI = @"escrowSigningSPKI";
70 static NSString* OTCKRecordPeerSigningSPKI = @"peerSigningSPKI";
71 static NSString* OTCKRecordSignatureFromEscrow = @"signatureUsingEscrow";
72 static NSString* OTCKRecordSignatureFromPeerKey = @"signatureUsingPeerKey";
73 static NSString* OTCKRecordEncodedRecord = @"encodedRecord";
74
75 /* Octagon Table Names */
76 static NSString* const contextTable = @"context";
77 static NSString* const peerTable = @"peer";
78 static NSString* const bottledPeerTable = @"bp";
79
80 /* Octagon Trust Schemas */
81 static NSString* const octagonZoneName = @"OctagonTrustZone";
82
83 /* Octagon Cloud Kit defines */
84 static NSString* OTCKZoneName = @"OctagonTrust";
85 static NSString* OTCKRecordName = @"bp-";
86 static NSString* OTCKRecordBottledPeerType = @"OTBottledPeer";
87
88 @interface OTCloudStore ()
89 @property (nonatomic, strong) NSString* dsid;
90 @property (nonatomic, strong) NSString* containerName;
91 @property (nonatomic, strong) CKModifyRecordsOperation* modifyRecordsOperation;
92 @property (nonatomic, strong) CKDatabaseOperation<CKKSFetchRecordZoneChangesOperation>* fetchRecordZoneChangesOperation;
93 @property (nonatomic, strong) NSOperationQueue *operationQueue;
94 @property (nonatomic, strong) OTLocalStore* localStore;
95 @property (nonatomic, strong) CKKSResultOperation* viewSetupOperation;
96 @property (nonatomic, strong) NSError* error;
97 @end
98
99 @class OctagonAPSReceiver;
100
101 @interface OTCloudStore()
102
103 @property CKDatabaseOperation<CKKSModifyRecordZonesOperation>* zoneCreationOperation;
104 @property CKDatabaseOperation<CKKSModifyRecordZonesOperation>* zoneDeletionOperation;
105 @property CKDatabaseOperation<CKKSModifySubscriptionsOperation>* zoneSubscriptionOperation;
106
107 @property NSOperation* accountLoggedInDependency;
108
109 @property NSHashTable<NSOperation*>* accountOperations;
110 @end
111
112 @implementation OTCloudStore
113
114 - (instancetype) initWithContainer:(CKContainer*) container
115 zoneName:(NSString*)zoneName
116 accountTracker:(nullable CKKSAccountStateTracker*)accountTracker
117 reachabilityTracker:(nullable CKKSReachabilityTracker*)reachabilityTracker
118 localStore:(OTLocalStore*)localStore
119 contextID:(NSString*)contextID
120 dsid:(NSString*)dsid
121 zoneModifier:(CKKSZoneModifier*)zoneModifier
122 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
123 operationQueue:(nullable NSOperationQueue *)operationQueue
124 {
125
126 self = [super initWithContainer:container
127 zoneName:zoneName
128 accountTracker:accountTracker
129 reachabilityTracker:reachabilityTracker
130 zoneModifier:zoneModifier
131 cloudKitClassDependencies:cloudKitClassDependencies];
132
133 if(self){
134 if (!operationQueue) {
135 operationQueue = [[NSOperationQueue alloc] init];
136 }
137 _contextID = [contextID copy];
138 _localStore = localStore;
139 _containerName = OTCKContainerName;
140 _dsid = [dsid copy];
141 _operationQueue = operationQueue;
142 self.queue = dispatch_queue_create([[NSString stringWithFormat:@"OctagonTrustQueue.%@.zone.%@", container.containerIdentifier, zoneName] UTF8String], DISPATCH_QUEUE_SERIAL);
143 [self beginCloudKitOperation];
144 }
145 return self;
146
147 }
148
149 -(CKKSResultOperation*) otFetchAndProcessUpdates
150 {
151 CKKSResultOperation* fetchOp = [CKKSResultOperation named:@"fetch-and-process-updates-watcher" withBlock:^{}];
152
153 __weak __typeof(self) weakSelf = self;
154
155 [self dispatchSync: ^bool{
156
157 OTCloudStoreState* state = [OTCloudStoreState state: self.zoneName];
158
159 CKFetchRecordZoneChangesConfiguration* options = [[CKFetchRecordZoneChangesConfiguration alloc] init];
160 options.previousServerChangeToken = state.changeToken;
161
162 self.fetchRecordZoneChangesOperation = [[[self.cloudKitClassDependencies.fetchRecordZoneChangesOperationClass class] alloc] initWithRecordZoneIDs:@[self.zoneID] configurationsByRecordZoneID:@{self.zoneID : options}];
163
164 self.fetchRecordZoneChangesOperation.recordChangedBlock = ^(CKRecord *record) {
165 secinfo("octagon", "CloudKit notification: record changed(%@): %@", [record recordType], record);
166 __strong __typeof(weakSelf) strongSelf = weakSelf;
167
168 if(!strongSelf) {
169 secnotice("octagon", "received callback for released object");
170 fetchOp.error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTCloudStore userInfo:@{NSLocalizedDescriptionKey: @"received callback for released object"}];
171
172 fetchOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerFetchRecords;
173
174 return;
175 }
176 if ([record.recordType isEqualToString:OTCKRecordBottledPeerType]) {
177 NSError* localError = nil;
178
179 //write to localStore
180 OTBottledPeerRecord *rec = [[OTBottledPeerRecord alloc] init];
181 rec.bottle = record[OTCKRecordBottle];
182 rec.spID = record[OTCKRecordSPID];
183 rec.escrowRecordID = record[OTCKRecordEscrowRecordID];
184 rec.escrowedSigningSPKI = record[OTCKRecordEscrowSigningSPKI];
185 rec.peerSigningSPKI = record[OTCKRecordPeerSigningSPKI];
186 rec.signatureUsingEscrowKey = record[OTCKRecordSignatureFromEscrow];
187 rec.signatureUsingPeerKey = record[OTCKRecordSignatureFromPeerKey];
188 rec.encodedRecord = [strongSelf recordToData:record];
189 rec.launched = @"YES";
190 BOOL result = [strongSelf.localStore insertBottledPeerRecord:rec escrowRecordID:record[OTCKRecordEscrowRecordID] error:&localError];
191 if(!result || localError){
192 secerror("Could not write bottled peer record:%@ to database: %@", record.recordID.recordName, localError);
193 fetchOp.error = localError;
194 fetchOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerFetchRecords;
195
196 }
197 secnotice("octagon", "fetched changes: %@", record);
198 }
199 };
200
201 self.fetchRecordZoneChangesOperation.recordWithIDWasDeletedBlock = ^(CKRecordID *RecordID, NSString *recordType) {
202 secinfo("octagon", "CloudKit notification: deleted record(%@): %@", recordType, RecordID);
203 };
204
205 self.fetchRecordZoneChangesOperation.recordZoneChangeTokensUpdatedBlock = ^(CKRecordZoneID *recordZoneID, CKServerChangeToken *serverChangeToken, NSData *clientChangeTokenData) {
206 __strong __typeof(weakSelf) strongSelf = weakSelf;
207 NSError* error = nil;
208 OTCloudStoreState* state = [OTCloudStoreState state: strongSelf.zoneName];
209 secdebug("octagon", "Received a new server change token: %@ %@", serverChangeToken, clientChangeTokenData);
210 state.changeToken = serverChangeToken;
211
212 if(error) {
213 secerror("octagon: Couldn't save new server change token: %@", error);
214 fetchOp.error = error;
215 fetchOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerFetchRecords;
216 }
217 };
218
219 // Completion blocks don't count for dependencies. Use this intermediate operation hack instead.
220 NSBlockOperation* recordZoneChangesCompletedOperation = [[NSBlockOperation alloc] init];
221 self.fetchRecordZoneChangesOperation.recordZoneFetchCompletionBlock = ^(CKRecordZoneID *recordZoneID, CKServerChangeToken *serverChangeToken, NSData *clientChangeTokenData, BOOL moreComing, NSError * recordZoneError) {
222 __strong __typeof(weakSelf) strongSelf = weakSelf;
223 if(!strongSelf) {
224 secnotice("octagon", "received callback for released object");
225 return;
226 }
227 if(recordZoneError) {
228 secerror("octagon: FetchRecordZoneChanges(%@) error: %@", strongSelf.zoneName, recordZoneError);
229 fetchOp.error = recordZoneError;
230 fetchOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerFetchRecords;
231 }
232
233 // TODO: fetch state here
234 if(serverChangeToken) {
235 NSError* error = nil;
236 secdebug("octagon", "Zone change fetch complete: received a new server change token: %@ %@", serverChangeToken, clientChangeTokenData);
237 state.changeToken = serverChangeToken;
238 if(error) {
239 secerror("octagon: Couldn't save new server change token: %@", error);
240 fetchOp.error = error;
241 fetchOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerFetchRecords;
242 }
243 }
244 secdebug("octagon", "Record zone fetch complete: changeToken=%@ error=%@", serverChangeToken, recordZoneError);
245
246 [strongSelf.operationQueue addOperation: recordZoneChangesCompletedOperation];
247 [strongSelf.operationQueue addOperation: fetchOp];
248
249 };
250 self.fetchRecordZoneChangesOperation.fetchRecordZoneChangesCompletionBlock = ^(NSError * _Nullable operationError) {
251 __strong __typeof(weakSelf) strongSelf = weakSelf;
252 if(!strongSelf) {
253 secnotice("octagon", "received callback for released object");
254 fetchOp.error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTCloudStore userInfo:@{NSLocalizedDescriptionKey: @"received callback for released object"}];
255 fetchOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerFetchRecords;
256 return;
257 }
258 secnotice("octagon", "Record zone changes fetch complete: error=%@", operationError);
259 };
260 return true;
261 }];
262 [self.database addOperation: self.fetchRecordZoneChangesOperation];
263
264 return fetchOp;
265 }
266
267
268 - (void)notifyZoneChange:(CKRecordZoneNotification* _Nullable)notification
269 {
270 secnotice("octagon", "received notify zone change. notification: %@", notification);
271
272 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-fetch-and-process-changes" withBlock:^{}];
273
274 [op addSuccessDependency: [self otFetchAndProcessUpdates]];
275
276 [op timeout:(SecCKKSTestsEnabled() ? 2*NSEC_PER_SEC : 120*NSEC_PER_SEC)];
277 [self.operationQueue addOperation: op];
278
279 [op waitUntilFinished];
280 if(op.error != nil) {
281 secerror("octagon: failed to fetch changes error:%@", op.error);
282 }
283 else{
284 secnotice("octagon", "downloaded bottled peer records");
285 }
286 }
287
288 -(BOOL) downloadBottledPeerRecord:(NSError**)error
289 {
290 secnotice("octagon", "downloadBottledPeerRecord");
291 BOOL result = NO;
292 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-fetch-and-process-changes" withBlock:^{}];
293
294 [op addSuccessDependency: [self otFetchAndProcessUpdates]];
295
296 [op timeout:(SecCKKSTestsEnabled() ? 2*NSEC_PER_SEC : 120*NSEC_PER_SEC)];
297 [self.operationQueue addOperation: op];
298
299 [op waitUntilFinished];
300 if(op.error != nil) {
301 secerror("octagon: failed to fetch changes error:%@", op.error);
302 if(error){
303 *error = op.error;
304 }
305 }
306 else{
307 result = YES;
308 secnotice("octagon", "downloaded bottled peer records");
309 }
310 return result;
311 }
312
313 - (nullable NSArray*) retrieveListOfEligibleEscrowRecordIDs:(NSError**)error
314 {
315 NSError* localError = nil;
316
317 NSMutableArray* recordIDs = [NSMutableArray array];
318
319 //fetch any recent changes first before gathering escrow record ids
320 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-fetch-and-process-changes" withBlock:^{}];
321
322 secnotice("octagon", "Beginning CloudKit fetch");
323 [op addSuccessDependency: [self otFetchAndProcessUpdates]];
324
325 [op timeout:(SecCKKSTestsEnabled() ? 2*NSEC_PER_SEC : 120*NSEC_PER_SEC)];
326 [self.operationQueue addOperation: op];
327
328 [op waitUntilFinished];
329 if(op.error != nil) {
330 secnotice("octagon", "failed to fetch changes error:%@", op.error);
331 }
332
333 secnotice("octagon", "checking local store for bottles");
334
335 //check localstore for bottles
336 NSArray* localStoreBottledPeerRecords = [self.localStore readAllLocalBottledPeerRecords:&localError];
337 if(!localStoreBottledPeerRecords)
338 {
339 secerror("octagon: local store contains no bottled peer entries: %@", localError);
340 if(error){
341 *error = localError;
342 }
343 return nil;
344 }
345 for(OTBottledPeerRecord* entry in localStoreBottledPeerRecords){
346 NSString* escrowID = entry.escrowRecordID;
347 if(escrowID && ![recordIDs containsObject:escrowID]){
348 [recordIDs addObject:escrowID];
349 }
350 }
351
352 return recordIDs;
353 }
354
355 -(CKRecord*) dataToRecord:(NSData*)encodedRecord
356 {
357 NSKeyedUnarchiver *coder = [[NSKeyedUnarchiver alloc] initForReadingFromData:encodedRecord error:nil];
358 CKRecord* record = [[CKRecord alloc] initWithCoder:coder];
359 [coder finishDecoding];
360 return record;
361 }
362
363 -(NSData*) recordToData:(CKRecord*)record
364 {
365 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
366 [record encodeWithCoder:archiver];
367 [archiver finishEncoding];
368
369 return archiver.encodedData;
370 }
371
372 -( CKRecord* _Nullable ) CKRecordFromMirror:(CKRecordID*)recordID bpRecord:(OTBottledPeerRecord*)bprecord escrowRecordID:(NSString*)escrowRecordID error:(NSError**)error
373 {
374 CKRecord* record = nil;
375
376 OTBottledPeerRecord* recordFromDB = [self.localStore readLocalBottledPeerRecordWithRecordID:recordID.recordName error:error];
377 if(recordFromDB && recordFromDB.encodedRecord != nil){
378 record = [self dataToRecord:recordFromDB.encodedRecord];
379 }
380 else{
381 record = [[CKRecord alloc] initWithRecordType:OTCKRecordBottledPeerType recordID:recordID];
382 }
383
384 if(record == nil){
385 secerror("octagon: failed to create cloud kit record");
386 if(error){
387 *error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTCloudStore userInfo:@{NSLocalizedDescriptionKey: @"failed to create cloud kit record"}];
388 }
389 return nil;
390 }
391 record[OTCKRecordPeerID] = bprecord.peerID;
392 record[OTCKRecordSPID] = bprecord.spID;
393 record[OTCKRecordEscrowSigningSPKI] = bprecord.escrowedSigningSPKI;
394 record[OTCKRecordPeerSigningSPKI] = bprecord.peerSigningSPKI;
395 record[OTCKRecordEscrowRecordID] = escrowRecordID;
396 record[OTCKRecordBottle] = bprecord.bottle;
397 record[OTCKRecordSignatureFromEscrow] = bprecord.signatureUsingEscrowKey;
398 record[OTCKRecordSignatureFromPeerKey] = bprecord.signatureUsingPeerKey;
399
400 return record;
401 }
402
403 -(CKKSResultOperation*) modifyRecords:(NSArray<CKRecord *>*) recordsToSave deleteRecordIDs:(NSArray<CKRecordID*>*) recordIDsToDelete
404 {
405 __weak __typeof(self) weakSelf = self;
406 CKKSResultOperation* modifyOp = [CKKSResultOperation named:@"modify-records-watcher" withBlock:^{}];
407
408 [self dispatchSync: ^bool{
409 self.modifyRecordsOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave recordIDsToDelete:recordIDsToDelete];
410
411 self.modifyRecordsOperation.atomic = YES;
412 self.modifyRecordsOperation.longLived = NO; // The keys are only in memory; mark this explicitly not long-lived
413
414 // Currently done during buddy. User is waiting.
415 self.modifyRecordsOperation.configuration.automaticallyRetryNetworkFailures = NO;
416 self.modifyRecordsOperation.configuration.discretionaryNetworkBehavior = CKOperationDiscretionaryNetworkBehaviorNonDiscretionary;
417 self.modifyRecordsOperation.configuration.isCloudKitSupportOperation = YES;
418
419 self.modifyRecordsOperation.savePolicy = CKRecordSaveIfServerRecordUnchanged;
420
421 self.modifyRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
422 // These should all fail or succeed as one. Do the hard work in the records completion block.
423 if(!error) {
424 secnotice("octagon", "Successfully completed upload for %@", record.recordID.recordName);
425
426 } else {
427 secerror("octagon: error on row: %@ %@", record.recordID.recordName, error);
428 modifyOp.error = error;
429 modifyOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerModifyRecords;
430 [weakSelf.operationQueue addOperation:modifyOp];
431 }
432 };
433 self.modifyRecordsOperation.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error) {
434 secnotice("octagon", "Completed trust update");
435 __strong __typeof(weakSelf) strongSelf = weakSelf;
436
437 if(error){
438 modifyOp.error = error;
439 modifyOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerModifyRecords;
440 secerror("octagon: received error from cloudkit: %@", error);
441 if([error.domain isEqualToString:CKErrorDomain] && (error.code == CKErrorPartialFailure)) {
442 NSMutableDictionary<CKRecordID*, NSError*>* failedRecords = error.userInfo[CKPartialErrorsByItemIDKey];
443 ckksnotice("octagon", strongSelf, "failed records %@", failedRecords);
444 }
445 return;
446 }
447 if(!strongSelf) {
448 secerror("octagon: received callback for released object");
449 modifyOp.error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorOTCloudStore userInfo:@{NSLocalizedDescriptionKey: @"received callback for released object"}];
450 modifyOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerModifyRecords;
451 [strongSelf.operationQueue addOperation:modifyOp];
452 return;
453 }
454
455 if(savedRecords && [savedRecords count] > 0){
456 for(CKRecord* record in savedRecords){
457 NSError* localError = nil;
458 secnotice("octagon", "saving recordID: %@ changeToken:%@", record.recordID.recordName, record.recordChangeTag);
459
460 //write to localStore
461 OTBottledPeerRecord *rec = [[OTBottledPeerRecord alloc] init];
462 rec.bottle = record[OTCKRecordBottle];
463 rec.spID = record[OTCKRecordSPID];
464 rec.escrowRecordID = record[OTCKRecordEscrowRecordID];
465 rec.signatureUsingEscrowKey = record[OTCKRecordSignatureFromEscrow];
466 rec.signatureUsingPeerKey = record[OTCKRecordSignatureFromPeerKey];
467 rec.encodedRecord = [strongSelf recordToData:record];
468 rec.launched = @"YES";
469 rec.escrowedSigningSPKI = record[OTCKRecordEscrowSigningSPKI];
470 rec.peerSigningSPKI = record[OTCKRecordPeerSigningSPKI];
471
472 BOOL result = [strongSelf.localStore insertBottledPeerRecord:rec escrowRecordID:record[OTCKRecordEscrowRecordID] error:&localError];
473
474 if(!result || localError){
475 secerror("Could not write bottled peer record:%@ to database: %@", record.recordID.recordName, localError);
476 }
477
478 if(localError){
479 secerror("octagon: could not save to database: %@", localError);
480 modifyOp.error = localError;
481 modifyOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerModifyRecords;
482 }
483 }
484 }
485 else if(deletedRecordIDs && [deletedRecordIDs count] >0){
486 for(CKRecordID* recordID in deletedRecordIDs){
487 secnotice("octagon", "removed recordID: %@", recordID);
488 NSError* localError = nil;
489 BOOL result = [strongSelf.localStore deleteBottledPeer:recordID.recordName error:&localError];
490 if(!result){
491 secerror("octagon: could not remove record id: %@, error:%@", recordID, localError);
492 modifyOp.error = localError;
493 modifyOp.descriptionErrorCode = CKKSResultDescriptionPendingBottledPeerModifyRecords;
494 }
495 }
496 }
497 [strongSelf.operationQueue addOperation:modifyOp];
498 };
499 return true;
500 }];
501
502 [self.database addOperation: self.modifyRecordsOperation];
503 return modifyOp;
504 }
505
506 - (BOOL) uploadBottledPeerRecord:(OTBottledPeerRecord *)bprecord
507 escrowRecordID:(NSString *)escrowRecordID
508 error:(NSError**)error
509 {
510 secnotice("octagon", "sending bottled peer to cloudkit");
511 BOOL result = YES;
512
513 CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName:bprecord.recordName zoneID:self.zoneID];
514 CKRecord *record = [self CKRecordFromMirror:recordID bpRecord:bprecord escrowRecordID:escrowRecordID error:error];
515
516 if(!record){
517 return NO;
518 }
519 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-modify-changes" withBlock:^{}];
520
521 secnotice("octagon", "Beginning CloudKit ModifyRecords");
522 [op addSuccessDependency: [self modifyRecords:@[ record ] deleteRecordIDs:@[]]];
523
524 [op timeout:(SecCKKSTestsEnabled() ? 2*NSEC_PER_SEC : 120*NSEC_PER_SEC)];
525 [self.operationQueue addOperation: op];
526
527 [op waitUntilFinished];
528 if(op.error != nil) {
529 secerror("octagon: failed to commit record changes error:%@", op.error);
530 if(error){
531 *error = op.error;
532 }
533 return NO;
534 }
535 secnotice("octagon", "successfully uploaded record: %@", bprecord.recordName);
536 return result;
537 }
538
539 -(BOOL) removeBottledPeerRecordID:(CKRecordID*)recordID error:(NSError**)error
540 {
541 secnotice("octagon", "removing bottled peer from cloudkit");
542 BOOL result = YES;
543
544 NSMutableArray<CKRecordID*>* recordIDsToRemove = [[NSMutableArray alloc] init];
545 [recordIDsToRemove addObject:recordID];
546
547 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-modify-changes" withBlock:^{}];
548
549 secnotice("octagon", "Beginning CloudKit ModifyRecords");
550 [op addSuccessDependency: [self modifyRecords:[NSMutableArray array] deleteRecordIDs:recordIDsToRemove]];
551
552 [op timeout:(SecCKKSTestsEnabled() ? 2*NSEC_PER_SEC : 120*NSEC_PER_SEC)];
553 [self.operationQueue addOperation: op];
554
555 [op waitUntilFinished];
556 if(op.error != nil) {
557 secerror("octagon: ailed to commit record changes error:%@", op.error);
558 if(error){
559 *error = op.error;
560 }
561 return NO;
562 }
563
564 return result;
565 }
566
567 - (void)_onqueueHandleCKLogin {
568 if(!SecCKKSIsEnabled()) {
569 ckksnotice("ckks", self, "Skipping CloudKit initialization due to disabled CKKS");
570 return;
571 }
572
573 dispatch_assert_queue(self.queue);
574
575 __weak __typeof(self) weakSelf = self;
576
577 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: self.zoneName];
578 [self handleCKLogin:ckse.ckzonecreated zoneSubscribed:ckse.ckzonesubscribed];
579
580 self.viewSetupOperation = [CKKSResultOperation operationWithBlock: ^{
581 __strong __typeof(weakSelf) strongSelf = weakSelf;
582 if(!strongSelf) {
583 ckkserror("ckks", strongSelf, "received callback for released object");
584 return;
585 }
586
587 __block bool quit = false;
588
589 [strongSelf dispatchSync: ^bool {
590 ckksnotice("octagon", strongSelf, "Zone setup progress: %@ %d %@ %d %@",
591 [CKKSAccountStateTracker stringFromAccountStatus:strongSelf.accountStatus],
592 strongSelf.zoneCreated, strongSelf.zoneCreatedError, strongSelf.zoneSubscribed, strongSelf.zoneSubscribedError);
593
594 NSError* error = nil;
595 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: strongSelf.zoneName];
596 ckse.ckzonecreated = strongSelf.zoneCreated;
597 ckse.ckzonesubscribed = strongSelf.zoneSubscribed;
598
599 // Although, if the zone subscribed error says there's no zone, mark down that there's no zone
600 if(strongSelf.zoneSubscribedError &&
601 [strongSelf.zoneSubscribedError.domain isEqualToString:CKErrorDomain] && strongSelf.zoneSubscribedError.code == CKErrorPartialFailure) {
602 NSError* subscriptionError = strongSelf.zoneSubscribedError.userInfo[CKPartialErrorsByItemIDKey][strongSelf.zoneID];
603 if(subscriptionError && [subscriptionError.domain isEqualToString:CKErrorDomain] && subscriptionError.code == CKErrorZoneNotFound) {
604
605 ckkserror("octagon", strongSelf, "zone subscription error appears to say the zone doesn't exist, fixing status: %@", strongSelf.zoneSubscribedError);
606 ckse.ckzonecreated = false;
607 }
608 }
609
610 [ckse saveToDatabase: &error];
611 if(error) {
612 ckkserror("octagon", strongSelf, "couldn't save zone creation status for %@: %@", strongSelf.zoneName, error);
613 }
614
615 if(!strongSelf.zoneCreated || !strongSelf.zoneSubscribed || strongSelf.accountStatus != CKAccountStatusAvailable) {
616 // Something has gone very wrong. Error out and maybe retry.
617 quit = true;
618
619 // Note that CKKSZone has probably called [handleLogout]; which means we have a key hierarchy reset queued up. Error here anyway.
620 NSError* realReason = strongSelf.zoneCreatedError ? strongSelf.zoneCreatedError : strongSelf.zoneSubscribedError;
621 strongSelf.viewSetupOperation.error = realReason;
622
623
624 return true;
625 }
626
627 return true;
628 }];
629
630 if(quit) {
631 ckkserror("octagon", strongSelf, "Quitting setup.");
632 return;
633 }
634 }];
635 self.viewSetupOperation.name = @"zone-setup";
636
637 [self.viewSetupOperation addNullableDependency: self.zoneSetupOperation];
638 [self scheduleAccountStatusOperation: self.viewSetupOperation];
639 }
640
641 - (void)handleCKLogin
642 {
643 ckksinfo("octagon", self, "received a notification of CK login");
644
645 __weak __typeof(self) weakSelf = self;
646 CKKSResultOperation* login = [CKKSResultOperation named:@"octagon-login" withBlock:^{
647 __strong __typeof(self) strongSelf = weakSelf;
648
649 [strongSelf dispatchSync:^bool{
650 strongSelf.accountStatus = CKKSAccountStatusAvailable;
651 [strongSelf _onqueueHandleCKLogin];
652 return true;
653 }];
654 }];
655
656 [self scheduleAccountStatusOperation:login];
657 }
658
659 - (bool)_onqueueResetLocalData: (NSError * __autoreleasing *) error {
660 dispatch_assert_queue(self.queue);
661
662 NSError* localerror = nil;
663 bool setError = false;
664
665 CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: self.zoneName];
666 ckse.ckzonecreated = false;
667 ckse.ckzonesubscribed = false;
668 ckse.changeToken = NULL;
669 [ckse saveToDatabase: &localerror];
670 if(localerror) {
671 ckkserror("ckks", self, "couldn't reset zone status for %@: %@", self.zoneName, localerror);
672 if(error && !setError) {
673 *error = localerror; setError = true;
674 }
675 }
676
677 BOOL result = [_localStore removeAllBottledPeerRecords:&localerror];
678 if(!result){
679 *error = localerror;
680 secerror("octagon: failed to move all bottled peer entries for context: %@ error: %@", self.contextID, localerror);
681 }
682 return (localerror == nil && !setError);
683 }
684
685 -(CKKSResultOperation*) resetOctagonTrustZone:(NSError**)error
686 {
687 // On a reset, we should cancel all existing operations
688 [self cancelAllOperations];
689 CKKSResultOperation* reset = [super deleteCloudKitZoneOperation:nil];
690 [self scheduleOperationWithoutDependencies:reset];
691
692 __weak __typeof(self) weakSelf = self;
693 CKKSGroupOperation* resetFollowUp = [[CKKSGroupOperation alloc] init];
694 resetFollowUp.name = @"cloudkit-reset-follow-up-group";
695
696 [resetFollowUp runBeforeGroupFinished: [CKKSResultOperation named:@"cloudkit-reset-follow-up" withBlock: ^{
697 __strong __typeof(weakSelf) strongSelf = weakSelf;
698 if(!strongSelf) {
699 ckkserror("octagon", strongSelf, "received callback for released object");
700 return;
701 }
702
703 if(!reset.error) {
704 ckksnotice("octagon", strongSelf, "Successfully deleted zone %@", strongSelf.zoneName);
705 __block NSError* error = nil;
706
707 [strongSelf dispatchSync: ^bool{
708 [strongSelf _onqueueResetLocalData: &error];
709 return true;
710 }];
711 } else {
712 // Shouldn't ever happen, since reset is a successDependency
713 ckkserror("ckks", strongSelf, "Couldn't reset zone %@: %@", strongSelf.zoneName, reset.error);
714 }
715 }]];
716
717 [resetFollowUp addSuccessDependency:reset];
718 [self scheduleOperationWithoutDependencies:resetFollowUp];
719
720 return reset;
721 }
722
723 -(BOOL) performReset:(NSError**)error
724 {
725 BOOL result = NO;
726 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlock:^{}];
727
728 secnotice("octagon", "Beginning CloudKit reset for Octagon Trust");
729 [op addSuccessDependency:[self resetOctagonTrustZone:error]];
730
731 [op timeout:(SecCKKSTestsEnabled() ? 2*NSEC_PER_SEC : 120*NSEC_PER_SEC)];
732 [self.operationQueue addOperation: op];
733
734 [op waitUntilFinished];
735 if(!op.error) {
736 secnotice("octagon", "Completed rpcResetCloudKit");
737 __weak __typeof(self) weakSelf = self;
738 CKKSResultOperation* login = [CKKSResultOperation named:@"octagon-login" withBlock:^{
739 __strong __typeof(self) strongSelf = weakSelf;
740
741 [strongSelf dispatchSync:^bool{
742 strongSelf.accountStatus = CKKSAccountStatusAvailable;
743 [strongSelf handleCKLogin:false zoneSubscribed:false];
744 return true;
745 }];
746 }];
747
748 [self.operationQueue addOperation:login];
749 result = YES;
750 } else {
751 secnotice("octagon", "Completed rpcResetCloudKit with error: %@", op.error);
752 if(error){
753 *error = op.error;
754 }
755 }
756
757 return result;
758 }
759
760 @end
761
762 NS_ASSUME_NONNULL_END
763 #endif
764