2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 #import "SecEntitlements.h"
26 #import <Foundation/NSXPCConnection.h>
27 #import <Foundation/NSXPCConnection_Private.h>
31 #import "keychain/ot/OTControlProtocol.h"
32 #import "keychain/ot/OTControl.h"
33 #import "keychain/ot/OTContext.h"
34 #import "keychain/ot/OTManager.h"
35 #import "keychain/ot/OTDefines.h"
36 #import "keychain/ot/OTRamping.h"
37 #import "keychain/ot/SFPublicKey+SPKI.h"
38 #import "keychain/ot/OT.h"
39 #import "keychain/ot/OTConstants.h"
41 #import "keychain/ckks/CloudKitCategories.h"
42 #import "keychain/ckks/CKKSAnalytics.h"
43 #import "keychain/ckks/CKKSNearFutureScheduler.h"
44 #import "keychain/ckks/CKKS.h"
45 #import "keychain/ckks/CKKSViewManager.h"
46 #import "keychain/ckks/CKKSLockStateTracker.h"
48 #import <CloudKit/CloudKit.h>
49 #import <CloudKit/CloudKit_Private.h>
51 #import <SecurityFoundation/SFKey.h>
52 #import <SecurityFoundation/SFKey_Private.h>
54 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
55 #import <Accounts/Accounts.h>
56 #import <Accounts/ACAccountStore_Private.h>
57 #import <Accounts/ACAccountType_Private.h>
58 #import <Accounts/ACAccountStore.h>
59 #import <AppleAccount/ACAccountStore+AppleAccount.h>
60 #import <AppleAccount/ACAccount+AppleAccount.h>
62 #import <Accounts/Accounts.h>
63 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
64 #import <AOSAccounts/MobileMePrefsCore.h>
65 #import <AOSAccounts/ACAccountStore+iCloudAccount.h>
66 #import <AOSAccounts/ACAccount+iCloudAccount.h>
67 #import <AOSAccounts/iCloudAccount.h>
70 #import <Security/SecureObjectSync/SOSAccountTransaction.h>
71 #pragma clang diagnostic push
72 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
73 #import <Security/SecureObjectSync/SOSAccount.h>
74 #pragma clang diagnostic pop
76 static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
77 static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
78 static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";
79 static NSString* const kOTRampZoneName = @"metadata_zone";
80 #define NUM_NSECS_IN_24_HRS (86400 * NSEC_PER_SEC)
82 @interface OTManager () <NSXPCListenerDelegate, OTContextIdentityProvider>
83 @property NSXPCListener *listener;
84 @property (nonatomic, strong) OTContext* context;
85 @property (nonatomic, strong) OTLocalStore *localStore;
86 @property (nonatomic, strong) OTRamp *enrollRamp;
87 @property (nonatomic, strong) OTRamp *restoreRamp;
88 @property (nonatomic, strong) OTRamp *cfuRamp;
89 @property (nonatomic, strong) CKKSNearFutureScheduler *cfuScheduler;
90 @property (nonatomic, strong) NSDate *lastPostedCoreFollowUp;
93 @implementation OTManager
97 OTLocalStore* localStore = nil;
98 OTContext* context = nil;
100 NSString* dsid = [self askAccountsForDSID];
102 localStore = [[OTLocalStore alloc]initWithContextID:OTDefaultContext dsid:dsid path:nil error:nil];
103 context = [[OTContext alloc]initWithContextID:OTDefaultContext dsid:dsid localStore:self.localStore cloudStore:nil identityProvider:self error:nil];
105 //initialize our scheduler
106 CKKSNearFutureScheduler *cfuScheduler = [[CKKSNearFutureScheduler alloc] initWithName:@"scheduling-cfu" initialDelay:NUM_NSECS_IN_24_HRS continuingDelay:NUM_NSECS_IN_24_HRS keepProcessAlive:true dependencyDescriptionCode:CKKSResultDescriptionNone block:^{
107 secnotice("octagon", "running scheduled cfu block");
108 NSError* error = nil;
109 [self scheduledCloudKitRampCheck:&error];
112 //initialize our ramp objects
115 return [self initWithContext:context
116 localStore:localStore
117 enroll:self.enrollRamp
118 restore:self.restoreRamp
120 cfuScheduler:cfuScheduler];
123 -(instancetype) initWithContext:(OTContext*)context
124 localStore:(OTLocalStore*)localStore
125 enroll:(OTRamp*)enroll
126 restore:(OTRamp*)restore
128 cfuScheduler:(CKKSNearFutureScheduler*)cfuScheduler
132 self.context = context;
133 self.localStore = localStore;
135 self.enrollRamp = enroll;
136 self.restoreRamp = restore;
137 self.cfuScheduler = cfuScheduler;
139 secnotice("octagon", "otmanager init");
144 -(NSString*) askAccountsForDSID
146 NSString *dsid = nil;
147 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
149 #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
150 ACAccount *account = [accountStore aa_primaryAppleAccount];
151 dsid = [account aa_personID];
153 ACAccount *primaryiCloudAccount = nil;
154 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
155 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
157 dsid = [primaryiCloudAccount icaPersonID];
162 + (instancetype _Nullable)manager {
163 static OTManager* manager = nil;
165 if(!SecOTIsEnabled()) {
166 secerror("octagon: Attempt to fetch a manager while Octagon is disabled");
170 static dispatch_once_t onceToken;
171 dispatch_once(&onceToken, ^{
172 manager = [[OTManager alloc]init];
181 BOOL initResult = NO;
183 CKContainer* container = [CKKSViewManager manager].container;
184 CKDatabase* database = [container privateCloudDatabase];
185 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
187 CKKSCKAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
188 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
189 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
191 self.cfuRamp = [[OTRamp alloc]initWithRecordName:kOTRampForCFURecordName
196 accountTracker:accountTracker
197 lockStateTracker:lockStateTracker
198 reachabilityTracker:reachabilityTracker
199 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
201 self.enrollRamp = [[OTRamp alloc]initWithRecordName:kOTRampForEnrollmentRecordName
202 featureName:@"enroll"
206 accountTracker:accountTracker
207 lockStateTracker:lockStateTracker
208 reachabilityTracker:reachabilityTracker
209 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
212 self.restoreRamp = [[OTRamp alloc]initWithRecordName:kOTRampForRestoreRecordName
213 featureName:@"restore"
217 accountTracker:accountTracker
218 lockStateTracker:lockStateTracker
219 reachabilityTracker:reachabilityTracker
220 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
222 if(self.cfuRamp && self.enrollRamp && self.restoreRamp){
228 -(BOOL) initializeManagerPropertiesForContext:(NSString*)dsid error:(NSError**)error
230 CKKSAnalytics* logger = [CKKSAnalytics logger];
231 NSError *localError = nil;
232 BOOL initialized = YES;
235 dsid = [self askAccountsForDSID];
239 self.localStore = [[OTLocalStore alloc] initWithContextID:OTDefaultContext dsid:dsid path:nil error:&localError];
240 if(!self.localStore){
241 secerror("octagon: could not create localStore: %@", localError);
242 [logger logUnrecoverableError:localError forEvent:OctagonEventSignIn withAttributes:@{
243 OctagonEventAttributeFailureReason : @"creating local store",
249 self.context = [[OTContext alloc]initWithContextID:OTDefaultContext dsid:dsid localStore:self.localStore cloudStore:nil identityProvider:self error:&localError];
251 secerror("octagon: could not create context: %@", localError);
252 [logger logUnrecoverableError:localError forEvent:OctagonEventSignIn withAttributes:@{
253 OctagonEventAttributeFailureReason : @"creating context",
255 self.localStore = nil;
259 //just in case, init the ramp objects
262 if(localError && error){
272 - (void)signIn:(NSString*)dsid reply:(void (^)(BOOL result, NSError * _Nullable signedInError))reply
274 CKKSAnalytics* logger = [CKKSAnalytics logger];
275 SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonSignIn withAction:nil];
278 NSError *error = nil;
279 if(![self initializeManagerPropertiesForContext:dsid error:&error]){
286 [logger logSuccessForEventNamed:OctagonEventSignIn];
288 secnotice("octagon","created context and local store on manager for:%@", dsid);
293 - (void)signOut:(void (^)(BOOL result, NSError * _Nullable signedOutError))reply
295 CKKSAnalytics* logger = [CKKSAnalytics logger];
297 NSError* error = nil;
298 NSError *bottledPeerError = nil;
299 NSError *localContextError = nil;
301 secnotice("octagon", "signing out of octagon trust: dsid: %@ contextID: %@",
303 self.context.contextID);
305 NSString* contextAndDSID = [NSString stringWithFormat:@"%@-%@", self.context.contextID, self.context.dsid];
307 //remove all locally stored context
308 BOOL result1 = [self.localStore deleteLocalContext:contextAndDSID error:&localContextError];
310 secerror("octagon: could not delete local context: %@: %@", self.context.contextID, localContextError);
311 [logger logUnrecoverableError:localContextError forEvent:OctagonEventSignOut withAttributes:@{
312 OctagonEventAttributeFailureReason : @"deleting local context",
314 error = localContextError;
317 BOOL result2 = [self.localStore deleteBottledPeersForContextAndDSID:contextAndDSID error:&bottledPeerError];
319 secerror("octagon: could not delete bottle peer records: %@: %@", self.context.contextID, bottledPeerError);
320 [logger logUnrecoverableError:bottledPeerError forEvent:OctagonEventSignOut withAttributes:@{
321 OctagonEventAttributeFailureReason : @"deleting local bottled peers",
323 error = bottledPeerError;
326 //free context & local store
328 self.localStore = nil;
330 BOOL result = (result1 && result2);
332 [logger logSuccessForEventNamed:OctagonEventSignOut];
335 reply(result, error);
337 - (void)preflightBottledPeer:(NSString*)contextID
339 reply:(void (^)(NSData* _Nullable entropy,
340 NSString* _Nullable bottleID,
341 NSData* _Nullable signingPublicKey,
342 NSError* _Nullable error))reply
344 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
345 NSError* error = nil;
346 CKKSAnalytics* logger = [CKKSAnalytics logger];
347 SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonPreflightBottle withAction:nil];
351 if(!self.context || !self.localStore){
352 if(![self initializeManagerPropertiesForContext:dsid error:&error]){
353 secerror("octagon: could not init manager obejcts: %@", error);
354 reply(nil,nil,nil,error);
360 NSInteger retryDelayInSeconds = 0;
361 BOOL isFeatureOn = [self.enrollRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error];
363 //got an error from ramp check, we should log it
365 [logger logRecoverableError:error
366 forEvent:OctagonEventRamp
367 zoneName:kOTRampZoneName
369 OctagonEventAttributeFailureReason : @"ramp check for preflight bottle"
373 if(!isFeatureOn){ //cloud kit has not asked us to come back and the feature is off for this device
374 secnotice("octagon", "bottled peers is not on");
376 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
378 reply(nil, nil, nil, error);
382 NSData* entropy = [self.context makeMeSomeEntropy:OTMasterSecretLength];
384 secerror("octagon: entropy creation failed: %@", error);
385 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEntropyCreationFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to create entropy"}];
386 [logger logUnrecoverableError:error forEvent:OctagonEventPreflightBottle withAttributes:@{
387 OctagonEventAttributeFailureReason : @"preflight bottle, entropy failure"}
390 reply(nil, nil, nil, error);
394 OTPreflightInfo* result = [self.context preflightBottledPeer:contextID entropy:entropy error:&error];
395 if(!result || error){
396 secerror("octagon: preflight failed: %@", error);
397 [logger logUnrecoverableError:error forEvent:OctagonEventPreflightBottle withAttributes:@{ OctagonEventAttributeFailureReason : @"preflight bottle"}];
398 reply(nil, nil, nil, error);
404 [logger logSuccessForEventNamed:OctagonEventPreflightBottle];
406 secnotice("octagon", "preflightBottledPeer completed, created: %@", result.bottleID);
408 reply(entropy, result.bottleID, result.escrowedSigningSPKI, error);
411 - (void)launchBottledPeer:(NSString*)contextID
412 bottleID:(NSString*)bottleID
413 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
415 secnotice("octagon", "launchBottledPeer");
416 NSError* error = nil;
417 CKKSAnalytics* logger = [CKKSAnalytics logger];
418 SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonLaunchBottle withAction:nil];
422 if(!self.context || !self.localStore){
423 if(![self initializeManagerPropertiesForContext:nil error:&error]){
430 NSInteger retryDelayInSeconds = 0;
431 BOOL isFeatureOn = [self.enrollRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error];
433 //got an error from ramp check, we should log it
435 [logger logRecoverableError:error
436 forEvent:OctagonEventRamp
437 zoneName:kOTRampZoneName
439 OctagonEventAttributeFailureReason : @"ramp state check for launch bottle"
444 secnotice("octagon", "bottled peers is not on");
446 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
452 OTBottledPeerRecord* bprecord = [self.localStore readLocalBottledPeerRecordWithRecordID:bottleID error:&error];
453 if(!bprecord || error){
454 secerror("octagon: could not retrieve record for: %@, error: %@", bottleID, error);
455 [logger logUnrecoverableError:error forEvent:OctagonEventLaunchBottle withAttributes:@{
456 OctagonEventAttributeFailureReason : @"reading bottle from local store"
462 BOOL result = [self.context.cloudStore uploadBottledPeerRecord:bprecord escrowRecordID:bprecord.escrowRecordID error:&error];
463 if(!result || error){
464 secerror("octagon: could not upload record for bottleID %@, error: %@", bottleID, error);
465 [logger logUnrecoverableError:error forEvent:OctagonEventLaunchBottle withAttributes:@{
466 OctagonEventAttributeFailureReason : @"upload bottle to cloud kit"
474 [logger logSuccessForEventNamed:OctagonEventLaunchBottle];
476 secnotice("octagon", "successfully launched: %@", bprecord.recordName);
481 - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
483 //check if configuration zone allows restore
484 NSError* error = nil;
485 CKKSAnalytics* logger = [CKKSAnalytics logger];
486 SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonRestore withAction:nil];
490 if(!self.context || !self.localStore){
491 if(![self initializeManagerPropertiesForContext:dsid error:&error]){
492 secerror("octagon: could not init manager obejcts: %@", error);
493 reply(nil,nil,error);
499 NSInteger retryDelayInSeconds = 0;
500 BOOL isFeatureOn = [self.restoreRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error];
502 //got an error from ramp check, we should log it
504 [logger logRecoverableError:error
505 forEvent:OctagonEventRamp
506 zoneName:kOTRampZoneName
508 OctagonEventAttributeFailureReason : @"checking ramp state for restore"
513 secnotice("octagon", "bottled peers is not on");
515 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
518 reply(nil, nil, error);
522 if(!escrowRecordID || [escrowRecordID length] == 0){
523 secerror("octagon: missing escrowRecordID");
524 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEmptyEscrowRecordID userInfo:@{NSLocalizedDescriptionKey: @"Escrow Record ID is empty or missing"}];
526 [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{
527 OctagonEventAttributeFailureReason : @"escrow record id missing",
531 reply(nil, nil, error);
534 if(!dsid || [dsid length] == 0){
535 secerror("octagon: missing dsid");
536 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEmptyDSID userInfo:@{NSLocalizedDescriptionKey: @"DSID is empty or missing"}];
538 [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{
539 OctagonEventAttributeFailureReason : @"dsid missing",
542 reply(nil, nil, error);
545 if(!secret || [secret length] == 0){
546 secerror("octagon: missing secret");
547 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorEmptySecret userInfo:@{NSLocalizedDescriptionKey: @"Secret is empty or missing"}];
550 [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{
551 OctagonEventAttributeFailureReason : @"secret missing",
555 reply(nil, nil, error);
559 OTBottledPeerSigned *bps = [_context restoreFromEscrowRecordID:escrowRecordID secret:secret error:&error];
560 if(!bps || error != nil){
561 secerror("octagon: failed to restore bottled peer: %@", error);
563 [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{
564 OctagonEventAttributeFailureReason : @"restore failed",
567 reply(nil, nil, error);
571 NSData *encryptionKeyData = bps.bp.peerEncryptionKey.publicKey.keyData; // FIXME
572 if(!encryptionKeyData){
573 secerror("octagon: restored octagon encryption key is nil: %@", error);
574 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorRestoredPeerEncryptionKeyFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to retrieve restored Octagon Peer Encryption Key"}];
576 [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{
577 OctagonEventAttributeFailureReason : @"restored octagon encryption key"
580 reply(nil,nil,error);
584 NSData *signingKeyData = bps.bp.peerSigningKey.publicKey.keyData; // FIXME
586 secerror("octagon: restored octagon signing key is nil: %@", error);
587 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorRestoredPeerSigningKeyFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to retrieve restored Octagon Peer Signing Key"}];
589 [logger logUnrecoverableError:error forEvent:OctagonEventRestoreBottle withAttributes:@{
590 OctagonEventAttributeFailureReason : @"restored octagon signing key"
593 reply(nil,nil,error);
598 [logger logSuccessForEventNamed:OctagonEventRestoreBottle];
600 secnotice("octagon", "restored bottled peer: %@", escrowRecordID);
602 reply(signingKeyData, encryptionKeyData, error);
605 - (void)scrubBottledPeer:(NSString*)contextID
606 bottleID:(NSString*)bottleID
607 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
609 NSError* error = nil;
611 CKKSAnalytics* logger = [CKKSAnalytics logger];
612 SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityScrubBottle withAction:nil];
614 if(!self.context || !self.localStore){
615 if(![self initializeManagerPropertiesForContext:nil error:&error]){
623 NSInteger retryDelayInSeconds = 0;
624 BOOL isFeatureOn = [self.enrollRamp checkRampState:&retryDelayInSeconds qos:NSQualityOfServiceUserInitiated error:&error];
626 //got an error from ramp check, we should log it
628 [logger logRecoverableError:error
629 forEvent:OctagonEventRamp
630 zoneName:kOTRampZoneName
632 OctagonEventAttributeFailureReason : @"ramp check for scrubbing bottled peer"
637 secnotice("octagon", "bottled peers is not on");
639 error = [NSError errorWithDomain:octagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
646 BOOL result = [self.context scrubBottledPeer:contextID bottleID:bottleID error:&error];
647 if(!result || error){
648 secerror("octagon: could not scrub record for bottleID %@, error: %@", bottleID, error);
649 [logger logUnrecoverableError:error forEvent:OctagonEventScrubBottle withAttributes:@{
650 OctagonEventAttributeFailureReason : @"could not scrub bottle",
656 [logger logSuccessForEventNamed:OctagonEventScrubBottle];
658 secnotice("octagon", "scrubbed bottled peer: %@", bottleID);
664 * OTCTL tool routines
667 -(void) reset:(void (^)(BOOL result, NSError *))reply
669 NSError* error = nil;
671 if(self.context.lockStateTracker.isLocked){
672 secnotice("octagon","device is locked! can't check ramp state");
673 error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
674 code:errSecInteractionNotAllowed
675 userInfo:@{NSLocalizedDescriptionKey: @"device is locked"}];
680 if(self.context.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){
681 secnotice("octagon","not signed in! can't check ramp state");
682 error = [NSError errorWithDomain:octagonErrorDomain
683 code:OTErrorNotSignedIn
684 userInfo:@{NSLocalizedDescriptionKey: @"not signed in"}];
689 if(!self.context.reachabilityTracker.currentReachability){
690 secnotice("octagon","no network! can't check ramp state");
691 error = [NSError errorWithDomain:octagonErrorDomain
692 code:OTErrorNoNetwork
693 userInfo:@{NSLocalizedDescriptionKey: @"no network"}];
698 NSError* bottledPeerError = nil;
700 BOOL result = [_context.cloudStore performReset:&bottledPeerError];
701 if(!result || bottledPeerError != nil){
702 secerror("octagon: resetting octagon trust zone failed: %@", bottledPeerError);
705 NSString* contextAndDSID = [NSString stringWithFormat:@"%@-%@", self.context.contextID, self.context.dsid];
707 result = [self.localStore deleteBottledPeersForContextAndDSID:contextAndDSID error:&bottledPeerError];
709 secerror("octagon: could not delete bottle peer records: %@: %@", self.context.contextID, bottledPeerError);
712 reply(result, bottledPeerError);
715 - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError *))reply
717 NSError* error = nil;
719 if(self.context.lockStateTracker.isLocked){
720 secnotice("octagon","device is locked! can't check ramp state");
721 error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
722 code:errSecInteractionNotAllowed
723 userInfo:@{NSLocalizedDescriptionKey: @"device is locked"}];
728 if(self.context.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){
729 secnotice("octagon","not signed in! can't check ramp state");
730 error = [NSError errorWithDomain:octagonErrorDomain
731 code:OTErrorNotSignedIn
732 userInfo:@{NSLocalizedDescriptionKey: @"not signed in"}];
736 if(!self.context.reachabilityTracker.currentReachability){
737 secnotice("octagon","no network! can't check ramp state");
738 error = [NSError errorWithDomain:octagonErrorDomain
739 code:OTErrorNoNetwork
740 userInfo:@{NSLocalizedDescriptionKey: @"no network"}];
745 NSArray* list = [_context.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error];
746 if(!list || error !=nil){
747 secerror("octagon: there are not eligible bottle peer records: %@", error);
754 - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
756 __block NSData *encryptionKey = NULL;
757 __block NSError* localError = nil;
759 SOSCCPerformWithOctagonEncryptionPublicKey(^(SecKeyRef octagonPrivKey, CFErrorRef error) {
761 SecKeyCopyPublicBytes(octagonPrivKey, &key);
762 encryptionKey = CFBridgingRelease(key);
764 localError = (__bridge NSError*)error;
767 if(!encryptionKey || localError != nil){
768 reply(nil, localError);
769 secerror("octagon: retrieving the octagon encryption public key failed: %@", localError);
772 reply(encryptionKey, localError);
775 -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
777 __block NSData *signingKey = NULL;
778 __block NSError* localError = nil;
780 SOSCCPerformWithOctagonSigningPublicKey(^(SecKeyRef octagonPrivKey, CFErrorRef error) {
782 SecKeyCopyPublicBytes(octagonPrivKey, &key);
783 signingKey = CFBridgingRelease(key);
785 localError = (__bridge NSError*)error;
788 if(!signingKey || localError != nil){
789 reply(nil, localError);
790 secerror("octagon: retrieving the octagon signing public key failed: %@", localError);
793 reply(signingKey, localError);
800 -(BOOL)scheduledCloudKitRampCheck:(NSError**)error
802 secnotice("octagon", "scheduling a CloudKit ramping check");
803 NSInteger retryAfterInSeconds = 0;
804 NSError* localError = nil;
805 BOOL cancelScheduler = YES;
807 CKKSAnalytics* logger = [CKKSAnalytics logger];
810 BOOL canCFU = [self.cfuRamp checkRampState:&retryAfterInSeconds qos:NSQualityOfServiceUserInitiated error:&localError];
813 secerror("octagon: checking ramp state for CFU error'd: %@", localError);
814 [logger logUnrecoverableError:localError forEvent:OctagonEventRamp withAttributes:@{
815 OctagonEventAttributeFailureReason : @"ramp check failed",
820 secnotice("octagon", "CFU is enabled, checking if this device has a bottle");
821 OctagonBottleCheckState bottleStatus = [self.context doesThisDeviceHaveABottle:&localError];
823 if(bottleStatus == NOBOTTLE){
824 //time to post a follow up!
825 secnotice("octagon", "device does not have a bottle, posting a follow up");
826 if(!SecCKKSTestsEnabled()){
827 [self.context postFollowUp];
829 NSInteger timeDiff = -1;
831 NSDate *currentDate = [NSDate date];
832 if(self.lastPostedCoreFollowUp){
833 timeDiff = [currentDate timeIntervalSinceDate:self.lastPostedCoreFollowUp];
836 //log how long we last posted a followup, if any
837 [logger logRecoverableError:localError
838 forEvent:OctagonEventCoreFollowUp
839 zoneName:kOTRampZoneName
841 OctagonEventAttributeFailureReason : @"No bottle for peer",
842 OctagonEventAttributeTimeSinceLastPostedFollowUp: [NSNumber numberWithInteger:timeDiff],
845 self.lastPostedCoreFollowUp = currentDate;
846 //if the followup failed or succeeded, we should continue the scheduler until we have a bottle.
847 cancelScheduler = NO;
848 }else if(bottleStatus == BOTTLE){
849 secnotice("octagon", "device has a bottle");
850 [logger logSuccessForEventNamed:OctagonEventBottleCheck];
854 [logger logRecoverableError:localError
855 forEvent:OctagonEventBottleCheck
856 zoneName:kOTRampZoneName
858 OctagonEventAttributeFailureReason : @"bottle check",
863 if(cancelScheduler == NO){
864 secnotice("octagon", "requesting bottle check again");
865 [self.cfuScheduler trigger];
868 if(error && localError){
871 return cancelScheduler;
874 -(void)scheduleCFUForFuture
876 secnotice("octagon", "scheduling a query to cloudkit to see if this device can post a core follow up");
878 [self.cfuScheduler trigger];
881 - (nullable OTIdentity *) currentIdentity:(NSError**)error
883 return [OTIdentity currentIdentityFromSOS:error];