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