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