]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTManager.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / ot / OTManager.m
1 /*
2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #include <Security/SecEntitlements.h>
26 #import <Foundation/NSXPCConnection.h>
27 #import <Foundation/NSXPCConnection_Private.h>
28
29 #if OCTAGON
30 #import <TargetConditionals.h>
31 #import "keychain/ot/ObjCImprovements.h"
32 #import "keychain/ot/OTControlProtocol.h"
33 #import "keychain/ot/OTControl.h"
34 #import "keychain/ot/OTClique.h"
35 #import "keychain/ot/OTContext.h"
36 #import "keychain/ot/OTManager.h"
37 #import "keychain/ot/OTDefines.h"
38 #import "keychain/ot/OTRamping.h"
39 #import "keychain/ot/OT.h"
40 #import "keychain/ot/OTConstants.h"
41 #import "keychain/ot/OTBottledPeerState.h"
42 #import "keychain/ot/OTCuttlefishContext.h"
43 #import "keychain/ot/OTClientStateMachine.h"
44 #import "keychain/ot/OTStates.h"
45 #import "keychain/ot/OTJoiningConfiguration.h"
46 #import "keychain/ot/OTSOSAdapter.h"
47
48 #import "keychain/categories/NSError+UsefulConstructors.h"
49 #import "keychain/ckks/CKKSAnalytics.h"
50 #import "keychain/ckks/CKKSNearFutureScheduler.h"
51 #import "keychain/ckks/CKKS.h"
52 #import "keychain/ckks/CKKSViewManager.h"
53 #import "keychain/ckks/CKKSLockStateTracker.h"
54
55 #import <CloudKit/CloudKit.h>
56 #import <CloudKit/CloudKit_Private.h>
57
58 #import <SecurityFoundation/SFKey.h>
59 #import <SecurityFoundation/SFKey_Private.h>
60 #import "SecPasswordGenerate.h"
61
62 #import "keychain/categories/NSError+UsefulConstructors.h"
63 #include <CloudKit/CloudKit_Private.h>
64 #import <KeychainCircle/PairingChannel.h>
65
66 #import "keychain/escrowrequest/Framework/SecEscrowRequest.h"
67 #import "keychain/escrowrequest/EscrowRequestServer.h"
68
69 // If your callbacks might pass back a CK error, you should use XPCSanitizeError()
70 // Otherwise, XPC might crash on the other side if they haven't linked CloudKit.framework.
71 #define XPCSanitizeError CKXPCSuitableError
72
73 #import <Accounts/Accounts.h>
74 #import <Accounts/ACAccountStore_Private.h>
75 #import <Accounts/ACAccountType_Private.h>
76 #import <Accounts/ACAccountStore.h>
77 #import <AppleAccount/ACAccountStore+AppleAccount.h>
78 #import <AppleAccount/ACAccount+AppleAccount.h>
79
80 #import <CoreCDP/CDPFollowUpController.h>
81
82 #import "keychain/TrustedPeersHelper/TPHObjcTranslation.h"
83 #import "keychain/SecureObjectSync/SOSAccountTransaction.h"
84 #pragma clang diagnostic push
85 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
86 #import "keychain/SecureObjectSync/SOSAccount.h"
87 #pragma clang diagnostic pop
88
89 #import "utilities/SecTapToRadar.h"
90
91 static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
92 static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
93 static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";
94 static NSString* const kOTRampForGhostBustMIDName = @"metadata_rampstate_ghostBustMID";
95 static NSString* const kOTRampForghostBustSerialName = @"metadata_rampstate_ghostBustSerial";
96 static NSString* const kOTRampForghostBustAgeName = @"metadata_rampstate_ghostBustAge";
97 static NSString* const kOTRampZoneName = @"metadata_zone";
98 #define NUM_NSECS_IN_24_HRS (86400 * NSEC_PER_SEC)
99
100 #if OCTAGON
101 @interface OTManager (lockstateTracker) <CKKSLockStateNotification>
102 @end
103 #endif
104
105 @interface OTManager () <NSXPCListenerDelegate, OTContextIdentityProvider>
106 @property NSXPCListener *listener;
107 @property (nonatomic, strong) OTContext* context;
108 @property (nonatomic, strong) OTLocalStore *localStore;
109 @property (nonatomic, strong) OTRamp *enrollRamp;
110 @property (nonatomic, strong) OTRamp *restoreRamp;
111 @property (nonatomic, strong) OTRamp *cfuRamp;
112 @property (nonatomic, strong) OTRamp *gbmidRamp;
113 @property (nonatomic, strong) OTRamp *gbserialRamp;
114 @property (nonatomic, strong) OTRamp *gbAgeRamp;
115 @property (nonatomic, strong) CKKSNearFutureScheduler *cfuScheduler;
116 @property (nonatomic, strong) NSDate *lastPostedCoreFollowUp;
117 @property (nonatomic, strong) OTBottledPeerState *bottleState;
118 @property (nonatomic, strong) CKKSLockStateTracker *lockStateTracker;
119 @property (nonatomic, strong) id<OctagonFollowUpControllerProtocol> cdpd;
120
121 // Current contexts
122 @property NSMutableDictionary<NSString*, OTCuttlefishContext*>* contexts;
123 @property NSMutableDictionary<NSString*, OTClientStateMachine*>* clients;
124 @property dispatch_queue_t queue;
125
126 @property id<NSXPCProxyCreating> cuttlefishXPCConnection;
127
128 // Dependencies for injection
129 @property (readonly) id<OTSOSAdapter> sosAdapter;
130 @property (readonly) id<OTAuthKitAdapter> authKitAdapter;
131 @property (readonly) id<OTDeviceInformationAdapter> deviceInformationAdapter;
132 @property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
133 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
134 // If this is nil, all logging is disabled
135 @property (readonly, nullable) Class<SFAnalyticsProtocol> loggerClass;
136 @property (nonatomic) BOOL sosEnabledForPlatform;
137 @end
138
139 @implementation OTManager
140 @synthesize cuttlefishXPCConnection = _cuttlefishXPCConnection;
141 @synthesize sosAdapter = _sosAdapter;
142 @synthesize authKitAdapter = _authKitAdapter;
143 @synthesize deviceInformationAdapter = _deviceInformationAdapter;
144
145 -(instancetype)init
146 {
147 OTLocalStore* localStore = nil;
148 OTContext* context = nil;
149
150 NSString* dsid = [self askAccountsForDSID];
151 if(dsid){
152 localStore = [[OTLocalStore alloc]initWithContextID:OTDefaultContext dsid:dsid path:nil error:nil];
153 context = [[OTContext alloc]initWithContextID:OTDefaultContext dsid:dsid localStore:self.localStore cloudStore:nil identityProvider:self error:nil];
154 }
155 //initialize our scheduler
156 CKKSNearFutureScheduler *cfuScheduler = [[CKKSNearFutureScheduler alloc] initWithName:@"scheduling-cfu" initialDelay:NUM_NSECS_IN_24_HRS continuingDelay:NUM_NSECS_IN_24_HRS keepProcessAlive:true dependencyDescriptionCode:CKKSResultDescriptionNone block:^{
157 secnotice("octagon", "running scheduled cfu block");
158 NSError* error = nil;
159 [self scheduledCloudKitRampCheck:&error];
160 }];
161
162 //initialize our ramp objects
163 [self initRamps];
164
165 // Under Octagon, the sos adatper is not considered essential.
166 id<OTSOSAdapter> sosAdapter = (OctagonPlatformSupportsSOS() ?
167 [[OTSOSActualAdapter alloc] initAsEssential:NO] :
168 [[OTSOSMissingAdapter alloc] init]);
169
170 return [self initWithContext:context
171 localStore:localStore
172 enroll:self.enrollRamp
173 restore:self.restoreRamp
174 cfu:self.cfuRamp
175 cfuScheduler:cfuScheduler
176 sosAdapter:sosAdapter
177 authKitAdapter:[[OTAuthKitActualAdapter alloc] init]
178 deviceInformationAdapter:[[OTDeviceInformationActualAdapter alloc] init]
179 apsConnectionClass:[APSConnection class]
180 escrowRequestClass:[EscrowRequestServer class] // Use the server class here to skip the XPC layer
181 loggerClass:[CKKSAnalytics class]
182 lockStateTracker:[CKKSLockStateTracker globalTracker]
183 // The use of CKKS's account tracker here is an inversion, and will be fixed with CKKS-for-all when we
184 // have Octagon own the CKKS objects
185 accountStateTracker:[CKKSViewManager manager].accountTracker
186 cuttlefishXPCConnection:nil
187 cdpd:[[CDPFollowUpController alloc] init]];
188 }
189
190 -(instancetype) initWithContext:(OTContext*)context
191 localStore:(OTLocalStore*)localStore
192 enroll:(OTRamp*)enroll
193 restore:(OTRamp*)restore
194 cfu:(OTRamp*)cfu
195 cfuScheduler:(CKKSNearFutureScheduler*)cfuScheduler
196 sosAdapter:(id<OTSOSAdapter>)sosAdapter
197 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
198 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
199 apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
200 escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
201 loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
202 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
203 accountStateTracker:(id<CKKSCloudKitAccountStateTrackingProvider>)accountStateTracker
204 cuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
205 cdpd:(id<OctagonFollowUpControllerProtocol>)cdpd
206 {
207 self = [super init];
208 if(self){
209 self.context = context;
210 self.localStore = localStore;
211 self.cfuRamp = cfu;
212 self.enrollRamp = enroll;
213 self.restoreRamp = restore;
214 _sosAdapter = sosAdapter;
215 _authKitAdapter = authKitAdapter;
216 _deviceInformationAdapter = deviceInformationAdapter;
217 _loggerClass = loggerClass;
218 _lockStateTracker = lockStateTracker;
219 _accountStateTracker = accountStateTracker;
220 self.cfuScheduler = cfuScheduler;
221 _sosEnabledForPlatform = OctagonPlatformSupportsSOS();
222 _cuttlefishXPCConnection = cuttlefishXPCConnection;
223
224 self.contexts = [NSMutableDictionary dictionary];
225 self.clients = [NSMutableDictionary dictionary];
226
227 self.queue = dispatch_queue_create("otmanager", DISPATCH_QUEUE_SERIAL);
228
229 _apsConnectionClass = apsConnectionClass;
230 _escrowRequestClass = escrowRequestClass;
231
232 _cdpd = cdpd;
233
234 // The default CuttlefishContext always exists:
235 (void) [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
236
237 // Tell SFA to expect us
238 if(OctagonIsEnabled()){
239 [self setupAnalytics];
240 }
241
242 secnotice("octagon", "otmanager init");
243 }
244 return self;
245 }
246
247 - (void)initializeOctagon
248 {
249 secnotice("octagon", "Initializing Octagon...");
250
251 if(OctagonIsEnabled()) {
252 secnotice("octagon", "starting default state machine...");
253 OTCuttlefishContext* c = [self contextForContainerName:OTCKContainerName
254 contextID:OTDefaultContext];
255
256 [c startOctagonStateMachine];
257 [self registerForCircleChangedNotifications];
258 }
259 }
260
261 - (void) moveToCheckTrustedStateForContainer:(NSString* _Nullable)containerName context:(NSString*)context
262 {
263 OTCuttlefishContext* c = [self contextForContainerName:containerName
264 contextID:context];
265 [c startOctagonStateMachine];
266 [c moveToCheckTrustedState];
267 }
268
269 - (void)registerForCircleChangedNotifications
270 {
271 __weak __typeof(self) weakSelf = self;
272
273 // If we're not in the tests, go ahead and register for a notification
274 if(!SecCKKSTestsEnabled()) {
275 int token = 0;
276 notify_register_dispatch(kSOSCCCircleChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
277 secnotice("octagon", "circle changed notification called, checking trust state");
278 [weakSelf moveToCheckTrustedStateForContainer:OTCKContainerName context:OTDefaultContext];
279 });
280 }
281 }
282
283 -(NSString*) askAccountsForDSID
284 {
285 NSString *dsid = nil;
286 @try {
287 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
288
289 ACAccount *account = [accountStore aa_primaryAppleAccount];
290 dsid = [account aa_personID];
291 } @catch (NSException * e) {
292 secerror("octagon: failed to retrieve account: %@", e);
293 }
294 return dsid;
295 }
296
297 + (instancetype _Nullable)manager {
298 if(!OctagonIsEnabled()) {
299 secerror("octagon: Attempt to fetch a manager while Octagon is disabled");
300 return nil;
301 }
302
303 return [self resetManager:false to:nil];
304 }
305
306 + (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj
307 {
308 static OTManager* manager = nil;
309
310 if(!manager || reset || obj) {
311 @synchronized([self class]) {
312 if(obj != nil) {
313 manager = obj;
314 } else {
315 if(reset) {
316 manager = nil;
317 } else if (manager == nil && OctagonIsEnabled()) {
318 manager = [[OTManager alloc] init];
319 }
320 }
321 }
322 }
323
324 return manager;
325 }
326
327 -(BOOL) initRamps
328 {
329 BOOL initResult = NO;
330
331 CKContainer* container = [CKKSViewManager manager].container;
332 CKDatabase* database = [container privateCloudDatabase];
333 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
334
335 CKKSAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
336 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
337 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
338
339 self.cfuRamp = [[OTRamp alloc]initWithRecordName:kOTRampForCFURecordName
340 localSettingName:@"cfu"
341 container:container
342 database:database
343 zoneID:zoneID
344 accountTracker:accountTracker
345 lockStateTracker:lockStateTracker
346 reachabilityTracker:reachabilityTracker
347 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
348
349 self.enrollRamp = [[OTRamp alloc]initWithRecordName:kOTRampForEnrollmentRecordName
350 localSettingName:@"enroll"
351 container:container
352 database:database
353 zoneID:zoneID
354 accountTracker:accountTracker
355 lockStateTracker:lockStateTracker
356 reachabilityTracker:reachabilityTracker
357 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
358
359
360 self.restoreRamp = [[OTRamp alloc]initWithRecordName:kOTRampForRestoreRecordName
361 localSettingName:@"restore"
362 container:container
363 database:database
364 zoneID:zoneID
365 accountTracker:accountTracker
366 lockStateTracker:lockStateTracker
367 reachabilityTracker:reachabilityTracker
368 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
369
370 self.gbmidRamp = [[OTRamp alloc]initWithRecordName:kOTRampForGhostBustMIDName
371 localSettingName:@"ghostBustMID"
372 container:container
373 database:database
374 zoneID:zoneID
375 accountTracker:accountTracker
376 lockStateTracker:lockStateTracker
377 reachabilityTracker:reachabilityTracker
378 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
379
380 self.gbserialRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustSerialName
381 localSettingName:@"ghostBustSerial"
382 container:container
383 database:database
384 zoneID:zoneID
385 accountTracker:accountTracker
386 lockStateTracker:lockStateTracker
387 reachabilityTracker:reachabilityTracker
388 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
389
390 self.gbAgeRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustAgeName
391 localSettingName:@"ghostBustAge"
392 container:container
393 database:database
394 zoneID:zoneID
395 accountTracker:accountTracker
396 lockStateTracker:lockStateTracker
397 reachabilityTracker:reachabilityTracker
398 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
399
400 if(self.cfuRamp && self.enrollRamp && self.restoreRamp && self.gbmidRamp && self.gbserialRamp && self.gbAgeRamp){
401 initResult = YES;
402 }
403 return initResult;
404 }
405
406 -(BOOL) initializeManagerPropertiesForContext:(NSString*)dsid error:(NSError**)error
407 {
408 NSError *localError = nil;
409 BOOL initialized = YES;
410
411 if(dsid == nil){
412 dsid = [self askAccountsForDSID];
413 }
414
415 //create local store
416 self.localStore = [[OTLocalStore alloc] initWithContextID:OTDefaultContext dsid:dsid path:nil error:&localError];
417 if(!self.localStore){
418 secerror("octagon: could not create localStore: %@", localError);
419 initialized = NO;
420 }
421
422 //create context
423 self.context = [[OTContext alloc]initWithContextID:OTDefaultContext dsid:dsid localStore:self.localStore cloudStore:nil identityProvider:self error:&localError];
424 if(!self.context){
425 secerror("octagon: could not create context: %@", localError);
426 self.localStore = nil;
427 initialized = NO;
428 }
429
430 //just in case, init the ramp objects
431 [self initRamps];
432
433 if(localError && error){
434 *error = localError;
435 }
436 return initialized;
437 }
438
439 ////
440 // MARK: SPI routines
441 ////
442
443
444 - (void)signIn:(NSString*)altDSID
445 container:(NSString* _Nullable)containerName
446 context:(NSString*)contextID
447 reply:(void (^)(NSError * _Nullable signedInError))reply
448 {
449 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountAvailable];
450
451 if(containerName == nil) {
452 containerName = OTCKContainerName;
453 }
454
455 NSError *error = nil;
456 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
457
458 secnotice("octagon","signing in %@ for altDSID: %@", context, altDSID);
459 [context accountAvailable:altDSID error:&error];
460
461 [tracker stopWithEvent:OctagonEventSignIn result:error];
462
463 reply(error);
464 }
465
466 - (void)signOut:(NSString* _Nullable)containerName
467 context:(NSString*)contextID
468 reply:(void (^)(NSError * _Nullable signedOutError))reply
469 {
470 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountNotAvailable];
471
472 if(containerName == nil) {
473 containerName = OTCKContainerName;
474 }
475
476 NSError* error = nil;
477
478 // TODO: should we compare DSIDs here?
479 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
480
481 secnotice("octagon", "signing out of octagon trust: %@", context);
482
483 [context accountNoLongerAvailable:&error];
484 if(error) {
485 secnotice("octagon", "signing out failed: %@", error);
486 }
487
488 [tracker stopWithEvent:OctagonEventSignOut result:error];
489
490 reply(error);
491 }
492
493 - (void)notifyIDMSTrustLevelChangeForContainer:(NSString* _Nullable)containerName
494 context:(NSString*)contextID
495 reply:(void (^)(NSError * _Nullable error))reply
496 {
497 if(containerName == nil) {
498 containerName = OTCKContainerName;
499 }
500
501 NSError *error = nil;
502 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
503 secnotice("octagon","received a notification of IDMS trust level change in %@", context);
504 [context idmsTrustLevelChanged:&error];
505
506 reply(error);
507 }
508
509 - (void)handleIdentityChangeForSigningKey:(SFECKeyPair*)peerSigningKey
510 ForEncryptionKey:(SFECKeyPair*)encryptionKey
511 ForPeerID:(NSString*)peerID
512 reply:(void (^)(BOOL result,
513 NSError* _Nullable error))reply
514 {
515 secnotice("octagon", "handleIdentityChangeForSigningKey: %@", peerID);
516 NSError* error = nil;
517
518 if(self.context.lockStateTracker.isLocked){
519 secnotice("octagon","device is locked! can't check ramp state");
520 error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
521 code:errSecInteractionNotAllowed
522 userInfo:@{NSLocalizedDescriptionKey: @"device is locked"}];
523
524 reply(NO,error);
525 return;
526 }
527
528 // Wait until the account tracker has had a chance to figure out the state
529 [self.context.accountTracker.ckAccountInfoInitialized wait:5*NSEC_PER_SEC];
530 if(self.context.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){
531 secnotice("octagon","not signed in! can't check ramp state");
532 error = [NSError errorWithDomain:OctagonErrorDomain
533 code:OTErrorNotSignedIn
534 userInfo:@{NSLocalizedDescriptionKey: @"not signed in"}];
535 reply(NO,error);
536 return;
537
538 }
539 if(!self.context.reachabilityTracker.currentReachability){
540 secnotice("octagon","no network! can't check ramp state");
541 error = [NSError errorWithDomain:OctagonErrorDomain
542 code:OTErrorNoNetwork
543 userInfo:@{NSLocalizedDescriptionKey: @"no network"}];
544 reply(NO,error);
545 return;
546 }
547
548 CKKSAnalytics* logger = [CKKSAnalytics logger];
549 SFAnalyticsActivityTracker *tracker = [logger logSystemMetricsForActivityNamed:CKKSActivityOctagonUpdateBottle withAction:nil];
550
551 [tracker start];
552
553 if(!self.context || !self.localStore){
554 if(![self initializeManagerPropertiesForContext:nil error:&error]){
555 secerror("octagon: could not init manager obejcts: %@", error);
556 reply(NO,error);
557 [tracker cancel];
558 return;
559 }
560 }
561
562 BOOL isFeatureOn = [self.enrollRamp checkRampStateWithError:&error];
563
564 //got an error from ramp check, we should log it
565 if(error){
566 [logger logRecoverableError:error
567 forEvent:OctagonEventRamp
568 zoneName:kOTRampZoneName
569 withAttributes:@{
570 OctagonEventAttributeFailureReason : @"ramp check for updating bottle"
571 }];
572 }
573
574 if(!isFeatureOn){ //the feature is off for this device
575 secnotice("octagon", "bottled peers is not on");
576 if(!error){
577 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
578 }
579 [tracker cancel];
580 reply(NO, error);
581 return;
582 }
583
584 BOOL updated = [self.context updateAllBottlesForPeerID:peerID newSigningKey:peerSigningKey newEncryptionKey:encryptionKey error:&error];
585
586 if(!updated || error != nil) {
587 secerror("octagon: failed to update bottles for %@, error: %@", peerID, error);
588 [logger logUnrecoverableError:error forEvent:OctagonEventUpdateBottle withAttributes:@{ OctagonEventAttributeFailureReason : @"failed to update bottles"}];
589 [tracker cancel];
590 reply(NO, error);
591 return;
592 }
593
594 [tracker stop];
595 [logger logSuccessForEventNamed:OctagonEventUpdateBottle];
596
597 secnotice("octagon", "handleIdentityChangeForSigningKey completed, updated bottles for: %@", peerID);
598
599 reply(YES, error);
600 }
601
602 - (void)preflightBottledPeer:(NSString*)contextID
603 dsid:(NSString*)dsid
604 reply:(void (^)(NSData* _Nullable entropy,
605 NSString* _Nullable bottleID,
606 NSData* _Nullable signingPublicKey,
607 NSError* _Nullable error))reply
608 {
609 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
610 NSError* error = nil;
611
612 if(!self.context || !self.localStore){
613 if(![self initializeManagerPropertiesForContext:dsid error:&error]){
614 secerror("octagon: could not init manager obejcts: %@", error);
615 reply(nil,nil,nil,error);
616 return;
617 }
618 }
619
620 BOOL isFeatureOn = [self.enrollRamp checkRampStateWithError:&error];
621
622 if(!isFeatureOn){ //feature is off for this device
623 secnotice("octagon", "bottled peers is not on");
624 if(!error){
625 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
626 }
627 reply(nil, nil, nil, error);
628 return;
629 }
630
631 NSData* entropy = [self.context makeMeSomeEntropy:OTMasterSecretLength];
632 if(!entropy){
633 secerror("octagon: entropy creation failed: %@", error);
634 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorEntropyCreationFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to create entropy"}];
635 reply(nil, nil, nil, error);
636 return;
637 }
638
639 OTPreflightInfo* result = [self.context preflightBottledPeer:contextID entropy:entropy error:&error];
640 if(!result || error){
641 secerror("octagon: preflight failed: %@", error);
642 reply(nil, nil, nil, error);
643 return;
644 }
645
646 secnotice("octagon", "preflightBottledPeer completed, created: %@", result.bottleID);
647
648 reply(entropy, result.bottleID, result.escrowedSigningSPKI, error);
649 }
650
651 - (void)launchBottledPeer:(NSString*)contextID
652 bottleID:(NSString*)bottleID
653 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
654 {
655 secnotice("octagon", "launchBottledPeer");
656 NSError* error = nil;
657
658 if(!self.context || !self.localStore){
659 if(![self initializeManagerPropertiesForContext:nil error:&error]){
660 reply(error);
661 return;
662 }
663 }
664
665 BOOL isFeatureOn = [self.enrollRamp checkRampStateWithError:&error];
666
667 if(!isFeatureOn){
668 secnotice("octagon", "bottled peers is not on");
669 if(!error){
670 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
671 }
672 reply(error);
673 return;
674 }
675
676 OTBottledPeerRecord* bprecord = [self.localStore readLocalBottledPeerRecordWithRecordID:bottleID error:&error];
677 if(!bprecord || error){
678 secerror("octagon: could not retrieve record for: %@, error: %@", bottleID, error);
679 reply(error);
680 return;
681 }
682 BOOL result = [self.context.cloudStore uploadBottledPeerRecord:bprecord escrowRecordID:bprecord.escrowRecordID error:&error];
683 if(!result || error){
684 secerror("octagon: could not upload record for bottleID %@, error: %@", bottleID, error);
685 reply(error);
686 return;
687 }
688
689 secnotice("octagon", "successfully launched: %@", bprecord.recordName);
690
691 reply(error);
692 }
693
694 - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
695 {
696 //check if configuration zone allows restore
697 NSError* error = nil;
698
699 if(!self.context || !self.localStore){
700 if(![self initializeManagerPropertiesForContext:dsid error:&error]){
701 secerror("octagon: could not init manager obejcts: %@", error);
702 reply(nil,nil,error);
703 return;
704 }
705 }
706
707 BOOL isFeatureOn = [self.restoreRamp checkRampStateWithError:&error];
708
709 if(!isFeatureOn){
710 secnotice("octagon", "bottled peers is not on");
711 if(!error){
712 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
713 }
714 reply(nil, nil, error);
715 return;
716 }
717
718 if(!escrowRecordID || [escrowRecordID length] == 0){
719 secerror("octagon: missing escrowRecordID");
720 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorEmptyEscrowRecordID userInfo:@{NSLocalizedDescriptionKey: @"Escrow Record ID is empty or missing"}];
721
722 reply(nil, nil, error);
723 return;
724 }
725 if(!dsid || [dsid length] == 0){
726 secerror("octagon: missing dsid");
727 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorEmptyDSID userInfo:@{NSLocalizedDescriptionKey: @"DSID is empty or missing"}];
728 reply(nil, nil, error);
729 return;
730 }
731 if(!secret || [secret length] == 0){
732 secerror("octagon: missing secret");
733 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorEmptySecret userInfo:@{NSLocalizedDescriptionKey: @"Secret is empty or missing"}];
734 reply(nil, nil, error);
735 return;
736 }
737
738 OTBottledPeerSigned *bps = [_context restoreFromEscrowRecordID:escrowRecordID secret:secret error:&error];
739 if(!bps || error != nil){
740 secerror("octagon: failed to restore bottled peer: %@", error);
741 reply(nil, nil, error);
742 return;
743 }
744
745 SFECKeyPair *encryptionKey = bps.bp.peerEncryptionKey;
746 SFPublicKey *encryptionPublicKey = encryptionKey.publicKey;
747 NSData* encryptionKeyData = encryptionPublicKey.keyData;// FIXME
748
749 if(!encryptionKeyData){
750 secerror("octagon: restored octagon encryption key is nil: %@", error);
751 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRestoredPeerEncryptionKeyFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to retrieve restored Octagon Peer Encryption Key"}];
752 reply(nil,nil,error);
753 return;
754 }
755
756 SFECKeyPair *signingKey = bps.bp.peerSigningKey;
757 SFPublicKey *signingKeyPublicKey = signingKey.publicKey;
758 NSData* signingKeyData = signingKeyPublicKey.keyData;// FIXME
759
760 if(!signingKeyData){
761 secerror("octagon: restored octagon signing key is nil: %@", error);
762 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRestoredPeerSigningKeyFailure userInfo:@{NSLocalizedDescriptionKey: @"Failed to retrieve restored Octagon Peer Signing Key"}];
763 reply(nil,nil,error);
764 return;
765 }
766
767 secnotice("octagon", "restored bottled peer: %@", escrowRecordID);
768
769 reply(signingKeyData, encryptionKeyData, error);
770 }
771
772 - (void)scrubBottledPeer:(NSString*)contextID
773 bottleID:(NSString*)bottleID
774 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
775 {
776 NSError* error = nil;
777
778 if(!self.context || !self.localStore){
779 if(![self initializeManagerPropertiesForContext:nil error:&error]){
780 reply(error);
781 return;
782 }
783 }
784
785 BOOL isFeatureOn = [self.enrollRamp checkRampStateWithError:&error];
786
787 if(!isFeatureOn){
788 secnotice("octagon", "bottled peers is not on");
789 if(!error){
790 error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorFeatureNotEnabled userInfo:@{NSLocalizedDescriptionKey: @"Feature not enabled"}];
791 }
792 reply(error);
793 return;
794 }
795
796 BOOL result = [self.context scrubBottledPeer:contextID bottleID:bottleID error:&error];
797 if(!result || error){
798 secerror("octagon: could not scrub record for bottleID %@, error: %@", bottleID, error);
799 reply(error);
800 return;
801 }
802
803 secnotice("octagon", "scrubbed bottled peer: %@", bottleID);
804
805 reply(error);
806 }
807
808 ////
809 // MARK: OTCTL tool routines
810 ////
811
812 -(void)reset:(void (^)(BOOL result, NSError *))reply
813 {
814 NSError* error = nil;
815
816 if(!self.context || !self.localStore){
817 if(![self initializeManagerPropertiesForContext:nil error:&error]){
818 secerror("octagon: could not init manager obejcts: %@", error);
819 reply(NO,error);
820 return;
821 }
822 }
823
824 if(self.context.lockStateTracker.isLocked){
825 secnotice("octagon","device is locked! can't check ramp state");
826 error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
827 code:errSecInteractionNotAllowed
828 userInfo:@{NSLocalizedDescriptionKey: @"device is locked"}];
829
830 reply(NO,error);
831 return;
832 }
833
834 // Wait until the account tracker has had a chance to figure out the state
835 [self.context.accountTracker.ckAccountInfoInitialized wait:5*NSEC_PER_SEC];
836 if(self.context.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){
837 secnotice("octagon","not signed in! can't check ramp state");
838 error = [NSError errorWithDomain:OctagonErrorDomain
839 code:OTErrorNotSignedIn
840 userInfo:@{NSLocalizedDescriptionKey: @"not signed in"}];
841 reply(NO,error);
842 return;
843
844 }
845 if(!self.context.reachabilityTracker.currentReachability){
846 secnotice("octagon","no network! can't check ramp state");
847 error = [NSError errorWithDomain:OctagonErrorDomain
848 code:OTErrorNoNetwork
849 userInfo:@{NSLocalizedDescriptionKey: @"no network"}];
850 reply(NO,error);
851 return;
852 }
853
854 NSError* bottledPeerError = nil;
855
856 BOOL result = [_context.cloudStore performReset:&bottledPeerError];
857 if(!result || bottledPeerError != nil){
858 secerror("octagon: resetting octagon trust zone failed: %@", bottledPeerError);
859 }
860
861 NSString* contextAndDSID = [NSString stringWithFormat:@"%@-%@", self.context.contextID, self.context.dsid];
862
863 result = [self.localStore deleteBottledPeersForContextAndDSID:contextAndDSID error:&bottledPeerError];
864 if(!result){
865 secerror("octagon: could not delete bottle peer records: %@: %@", self.context.contextID, bottledPeerError);
866 }
867
868 reply(result, bottledPeerError);
869 }
870
871 - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError * _Nullable))reply
872 {
873 NSError* error = nil;
874 if(!self.context || !self.localStore){
875 if(![self initializeManagerPropertiesForContext:nil error:&error]){
876 secerror("octagon: could not init manager obejcts: %@", error);
877 reply(nil,error);
878 return;
879 }
880 }
881
882 if(self.context.lockStateTracker.isLocked){
883 secnotice("octagon","device is locked! can't check ramp state");
884 error = [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
885 code:errSecInteractionNotAllowed
886 userInfo:@{NSLocalizedDescriptionKey: @"device is locked"}];
887
888 reply(nil,error);
889 return;
890 }
891
892 // Wait until the account tracker has had a chance to figure out the state
893 [self.context.accountTracker.ckAccountInfoInitialized wait:5*NSEC_PER_SEC];
894 if(self.context.accountTracker.currentCKAccountInfo.accountStatus != CKAccountStatusAvailable){
895 secnotice("octagon","not signed in! can't check ramp state");
896 error = [NSError errorWithDomain:OctagonErrorDomain
897 code:OTErrorNotSignedIn
898 userInfo:@{NSLocalizedDescriptionKey: @"not signed in"}];
899 reply(nil,error);
900 return;
901 }
902 if(!self.context.reachabilityTracker.currentReachability){
903 secnotice("octagon","no network! can't check ramp state");
904 error = [NSError errorWithDomain:OctagonErrorDomain
905 code:OTErrorNoNetwork
906 userInfo:@{NSLocalizedDescriptionKey: @"no network"}];
907 reply(nil,error);
908 return;
909 }
910
911 NSArray* list = [_context.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error];
912 if(!list || error !=nil){
913 secerror("octagon: there are not eligible bottle peer records: %@", error);
914 reply(nil,error);
915 return;
916 }
917 reply(list, error);
918 }
919
920 - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
921 {
922 __block NSData *encryptionKey = NULL;
923 __block NSError* localError = nil;
924
925 SOSCCPerformWithOctagonEncryptionPublicKey(^(SecKeyRef octagonPrivKey, CFErrorRef error) {
926 CFDataRef key;
927 SecKeyCopyPublicBytes(octagonPrivKey, &key);
928 encryptionKey = CFBridgingRelease(key);
929 if(error){
930 localError = (__bridge NSError*)error;
931 }
932 });
933 if(!encryptionKey || localError != nil){
934 reply(nil, localError);
935 secerror("octagon: retrieving the octagon encryption public key failed: %@", localError);
936 return;
937 }
938 reply(encryptionKey, localError);
939 }
940
941 -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
942 {
943 __block NSData *signingKey = NULL;
944 __block NSError* localError = nil;
945
946 SOSCCPerformWithOctagonSigningPublicKey(^(SecKeyRef octagonPrivKey, CFErrorRef error) {
947 CFDataRef key;
948 SecKeyCopyPublicBytes(octagonPrivKey, &key);
949 signingKey = CFBridgingRelease(key);
950 if(error){
951 localError = (__bridge NSError*)error;
952 }
953 });
954 if(!signingKey || localError != nil){
955 reply(nil, localError);
956 secerror("octagon: retrieving the octagon signing public key failed: %@", localError);
957 return;
958 }
959 reply(signingKey, localError);
960 }
961
962 ////
963 // MARK: OT Helpers
964 ////
965
966 -(BOOL)scheduledCloudKitRampCheck:(NSError**)error
967 {
968 secnotice("octagon", "scheduling a CloudKit ramping check");
969 NSError* localError = nil;
970 BOOL cancelScheduler = YES;
971
972 CKKSAnalytics* logger = [CKKSAnalytics logger];
973
974 if(self.cfuRamp){
975 BOOL canCFU = [self.cfuRamp checkRampStateWithError:&localError];
976
977 if(localError){
978 secerror("octagon: checking ramp state for CFU error'd: %@", localError);
979 [logger logUnrecoverableError:localError forEvent:OctagonEventRamp withAttributes:@{
980 OctagonEventAttributeFailureReason : @"ramp check failed",
981 }];
982 }
983
984 if(canCFU){
985 secnotice("octagon", "CFU is enabled, checking if this device has a bottle");
986 OctagonBottleCheckState bottleStatus = [self.context doesThisDeviceHaveABottle:&localError];
987
988 if(bottleStatus == NOBOTTLE){
989 //time to post a follow up!
990 secnotice("octagon", "device does not have a bottle, posting a follow up");
991 if(!SecCKKSTestsEnabled()){
992 //40347954 removing cfu invocations until we can add CDP without creating a cycle.
993 //[self.context postFollowUp];
994 secnotice("octagon", "would have posted a follow up");
995 }
996 NSInteger timeDiff = -1;
997
998 NSDate *currentDate = [NSDate date];
999 if(self.lastPostedCoreFollowUp){
1000 timeDiff = [currentDate timeIntervalSinceDate:self.lastPostedCoreFollowUp];
1001 }
1002
1003 //log how long we last posted a followup, if any
1004 [logger logRecoverableError:localError
1005 forEvent:OctagonEventCoreFollowUp
1006 zoneName:kOTRampZoneName
1007 withAttributes:@{
1008 OctagonEventAttributeFailureReason : @"No bottle for peer",
1009 OctagonEventAttributeTimeSinceLastPostedFollowUp: [NSNumber numberWithInteger:timeDiff],
1010 }];
1011
1012 self.lastPostedCoreFollowUp = currentDate;
1013 //if the followup failed or succeeded, we should continue the scheduler until we have a bottle.
1014 cancelScheduler = NO;
1015 }else if(bottleStatus == BOTTLE){
1016 secnotice("octagon", "device has a bottle");
1017 [logger logSuccessForEventNamed:OctagonEventBottleCheck];
1018 }
1019
1020 if(localError){
1021 [logger logRecoverableError:localError
1022 forEvent:OctagonEventBottleCheck
1023 zoneName:kOTRampZoneName
1024 withAttributes:@{
1025 OctagonEventAttributeFailureReason : @"bottle check",
1026 }];
1027 }
1028 }
1029 }
1030 if(cancelScheduler == NO){
1031 secnotice("octagon", "requesting bottle check again");
1032 [self.cfuScheduler trigger];
1033 }
1034
1035 if(error && localError){
1036 *error = localError;
1037 }
1038 return cancelScheduler;
1039 }
1040
1041 -(void)scheduleCFUForFuture
1042 {
1043 secnotice("octagon", "scheduling a query to cloudkit to see if this device can post a core follow up");
1044
1045 [self.cfuScheduler trigger];
1046 }
1047
1048 - (nullable OTIdentity *) currentIdentity:(NSError**)error
1049 {
1050 return [OTIdentity currentIdentityFromSOS:error];
1051 }
1052
1053 - (void)setCuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
1054 {
1055 _cuttlefishXPCConnection = cuttlefishXPCConnection;
1056 }
1057
1058 - (id<NSXPCProxyCreating>)cuttlefishXPCConnection
1059 {
1060 if(!_cuttlefishXPCConnection) {
1061 NSXPCConnection* xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.TrustedPeersHelper"];
1062 xpcConnection.remoteObjectInterface = TrustedPeersHelperSetupProtocol([NSXPCInterface interfaceWithProtocol:@protocol(TrustedPeersHelperProtocol)]);
1063 [xpcConnection resume];
1064 _cuttlefishXPCConnection = xpcConnection;
1065 }
1066
1067 return _cuttlefishXPCConnection;
1068 }
1069
1070 - (OTClientStateMachine*)clientStateMachineForContainerName:(NSString* _Nullable)containerName
1071 contextID:(NSString*)contextID
1072 clientName:(NSString*)clientName
1073 {
1074 __block OTClientStateMachine* client = nil;
1075
1076 if(containerName == nil) {
1077 containerName = SecCKKSContainerName;
1078 }
1079
1080 dispatch_sync(self.queue, ^{
1081 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
1082 secnotice("octagon-client", "fetching context for key: %@", key);
1083 client = self.clients[key];
1084 if(!client) {
1085 client = [[OTClientStateMachine alloc] initWithContainerName:containerName
1086 contextID:contextID
1087 clientName:clientName
1088 cuttlefish:self.cuttlefishXPCConnection];
1089
1090 self.clients[key] = client;
1091 }
1092 });
1093
1094 return client;
1095 }
1096
1097 - (void)removeClientContextForContainerName:(NSString* _Nullable)containerName
1098 clientName:(NSString*)clientName
1099 {
1100 if(containerName == nil) {
1101 containerName = SecCKKSContainerName;
1102 }
1103
1104 dispatch_sync(self.queue, ^{
1105 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
1106 [self.clients removeObjectForKey:key];
1107 secnotice("octagon", "removed client context with key: %@", key);
1108 });
1109 }
1110
1111 - (void)removeContextForContainerName:(NSString*)containerName
1112 contextID:(NSString*)contextID
1113 {
1114 dispatch_sync(self.queue, ^{
1115 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
1116 self.contexts[key] = nil;
1117 });
1118 }
1119
1120 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
1121 contextID:(NSString*)contextID
1122 {
1123 return [self contextForContainerName:containerName
1124 contextID:contextID
1125 sosAdapter:self.sosAdapter
1126 authKitAdapter:self.authKitAdapter
1127 lockStateTracker:self.lockStateTracker
1128 accountStateTracker:self.accountStateTracker
1129 deviceInformationAdapter:self.deviceInformationAdapter];
1130 }
1131
1132 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
1133 contextID:(NSString*)contextID
1134 sosAdapter:(id<OTSOSAdapter>)sosAdapter
1135 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
1136 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
1137 accountStateTracker:(CKKSAccountStateTracker*)accountStateTracker
1138 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
1139 {
1140 __block OTCuttlefishContext* context = nil;
1141
1142 if(containerName == nil) {
1143 containerName = SecCKKSContainerName;
1144 }
1145
1146 dispatch_sync(self.queue, ^{
1147 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
1148
1149 context = self.contexts[key];
1150
1151 // Right now, CKKS can only handle one session per address space (and SQL database).
1152 // Therefore, only the primary OTCuttlefishContext gets to own the view manager.
1153 CKKSViewManager* viewManager = nil;
1154 if([containerName isEqualToString:SecCKKSContainerName] &&
1155 [contextID isEqualToString:OTDefaultContext]) {
1156 viewManager = [CKKSViewManager manager];
1157 }
1158
1159 if(!context) {
1160 context = [[OTCuttlefishContext alloc] initWithContainerName:containerName
1161 contextID:contextID
1162 cuttlefish:self.cuttlefishXPCConnection
1163 sosAdapter:sosAdapter
1164 authKitAdapter:authKitAdapter
1165 ckksViewManager:viewManager
1166 lockStateTracker:lockStateTracker
1167 accountStateTracker:accountStateTracker
1168 deviceInformationAdapter:deviceInformationAdapter
1169 apsConnectionClass:self.apsConnectionClass
1170 escrowRequestClass:self.escrowRequestClass
1171 cdpd:self.cdpd];
1172 self.contexts[key] = context;
1173 }
1174 });
1175
1176 return context;
1177 }
1178
1179 - (void)fetchEgoPeerID:(NSString* _Nullable)container
1180 context:(NSString*)context
1181 reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply
1182 {
1183 if(!container) {
1184 container = OTCKContainerName;
1185 }
1186 secnotice("octagon", "Received a fetch peer ID for container (%@) and context (%@)", container, context);
1187 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1188 [cfshContext rpcFetchEgoPeerID:^(NSString * _Nullable peerID,
1189 NSError * _Nullable error) {
1190 reply(peerID, XPCSanitizeError(error));
1191 }];
1192 }
1193
1194 - (void)fetchTrustStatus:(NSString *)container
1195 context:(NSString *)context
1196 configuration:(OTOperationConfiguration *)configuration
1197 reply:(void (^)(CliqueStatus status,
1198 NSString* _Nullable peerID,
1199 NSNumber * _Nullable numberOfPeersInOctagon,
1200 BOOL isExcluded,
1201 NSError* _Nullable error))reply
1202 {
1203 if(!container) {
1204 container = OTCKContainerName;
1205 }
1206 secnotice("octagon", "Received a trust status for container (%@) and context (%@)", container, context);
1207 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1208
1209 [cfshContext rpcTrustStatus:configuration reply:^(CliqueStatus status,
1210 NSString * _Nullable peerID,
1211 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
1212 BOOL isExcluded,
1213 NSError * _Nullable error) {
1214 // Our clients don't need the whole breakout of peers, so just count for them
1215 long peerCount = 0;
1216 for(NSNumber* n in peerCountByModelID.allValues) {
1217 peerCount += [n longValue];
1218 }
1219
1220 reply(status, peerID, @(peerCount), isExcluded, error);
1221 }];
1222 }
1223
1224 - (void)fetchCliqueStatus:(NSString* _Nullable)container
1225 context:(NSString*)contextID
1226 configuration:(OTOperationConfiguration *)configuration
1227 reply:(void (^)(CliqueStatus cliqueStatus, NSError* _Nullable error))reply
1228 {
1229 if(!container) {
1230 container = OTCKContainerName;
1231 }
1232 if(configuration == nil) {
1233 configuration = [[OTOperationConfiguration alloc] init];
1234 }
1235
1236 __block OTCuttlefishContext* context = nil;
1237 dispatch_sync(self.queue, ^{
1238 NSString* key = [NSString stringWithFormat:@"%@-%@", container, contextID];
1239
1240 context = self.contexts[key];
1241 });
1242
1243 if(!context) {
1244 reply(-1, [NSError errorWithDomain:OctagonErrorDomain
1245 code:OTErrorNoSuchContext
1246 description:[NSString stringWithFormat:@"No context for (%@,%@)", container, contextID]]);
1247 return;
1248 }
1249
1250
1251 [context rpcTrustStatus:configuration reply:^(CliqueStatus status,
1252 NSString* egoPeerID,
1253 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
1254 BOOL isExcluded,
1255 NSError * _Nullable error) {
1256 reply(status, error);
1257 }];
1258 }
1259
1260 - (void)status:(NSString* _Nullable)containerName
1261 context:(NSString*)contextID
1262 reply:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
1263 {
1264 if(!containerName) {
1265 containerName = OTCKContainerName;
1266 }
1267
1268 secnotice("octagon", "Received a status RPC for container (%@) and context (%@)", containerName, contextID);
1269
1270 __block OTCuttlefishContext* context = nil;
1271 dispatch_sync(self.queue, ^{
1272 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
1273
1274 context = self.contexts[key];
1275 });
1276
1277 if(!context) {
1278 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
1279 code:OTErrorNoSuchContext
1280 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1281 return;
1282 }
1283
1284 [context rpcStatus:reply];
1285 }
1286
1287 - (void)startOctagonStateMachine:(NSString* _Nullable)container
1288 context:(NSString*)context
1289 reply:(void (^)(NSError* _Nullable error))reply
1290 {
1291 secnotice("octagon", "Received a start-state-machine RPC for container (%@) and context (%@)", container, context);
1292
1293 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1294 [cfshContext startOctagonStateMachine];
1295 reply(nil);
1296 }
1297
1298 - (void)resetAndEstablish:(NSString *)container
1299 context:(NSString *)context
1300 altDSID:(NSString*)altDSID
1301 reply:(void (^)(NSError * _Nullable))reply
1302 {
1303 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityResetAndEstablish];
1304
1305 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1306 [cfshContext startOctagonStateMachine];
1307 [cfshContext rpcResetAndEstablish:^(NSError* resetAndEstablishError) {
1308 [tracker stopWithEvent:OctagonEventResetAndEstablish result:resetAndEstablishError];
1309 reply(resetAndEstablishError);
1310 }];
1311 }
1312
1313 - (void)establish:(NSString *)container
1314 context:(NSString *)context
1315 altDSID:(NSString*)altDSID
1316 reply:(void (^)(NSError * _Nullable))reply
1317 {
1318 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityEstablish];
1319
1320 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1321 [cfshContext startOctagonStateMachine];
1322 [cfshContext rpcEstablish:altDSID reply:^(NSError* establishError) {
1323 [tracker stopWithEvent:OctagonEventEstablish result:establishError];
1324 reply(establishError);
1325 }];
1326 }
1327
1328 - (void)leaveClique:(NSString* _Nullable)container
1329 context:(NSString*)context
1330 reply:(void (^)(NSError* _Nullable error))reply
1331 {
1332 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityLeaveClique];
1333
1334 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1335 [cfshContext startOctagonStateMachine];
1336 [cfshContext rpcLeaveClique:^(NSError* leaveError) {
1337 [tracker stopWithEvent:OctagonEventLeaveClique result:leaveError];
1338 reply(leaveError);
1339 }];
1340 }
1341
1342 - (void)removeFriendsInClique:(NSString* _Nullable)container
1343 context:(NSString*)context
1344 peerIDs:(NSArray<NSString*>*)peerIDs
1345 reply:(void (^)(NSError* _Nullable error))reply
1346 {
1347 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityRemoveFriendsInClique];
1348
1349 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1350 [cfshContext startOctagonStateMachine];
1351 [cfshContext rpcRemoveFriendsInClique:peerIDs reply:^(NSError* removeFriendsError) {
1352 [tracker stopWithEvent:OctagonEventRemoveFriendsInClique result:removeFriendsError];
1353 reply(removeFriendsError);
1354 }];
1355 }
1356
1357 - (void)peerDeviceNamesByPeerID:(NSString* _Nullable)container
1358 context:(NSString*)context
1359 reply:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
1360 {
1361 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1362 [cfshContext rpcFetchDeviceNamesByPeerID:reply];
1363 }
1364
1365 - (void)fetchAllViableBottles:(NSString* _Nullable)container
1366 context:(NSString*)context
1367 reply:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialBottleIDs, NSError* _Nullable error))reply
1368 {
1369 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchAllViableBottles];
1370
1371 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1372 [cfshContext rpcFetchAllViableBottles:^(NSArray<NSString *> * _Nullable sortedBottleIDs,
1373 NSArray<NSString *> * _Nullable sortedPartialEscrowRecordIDs,
1374 NSError * _Nullable error) {
1375 [tracker stopWithEvent:OctagonEventFetchAllBottles result:error];
1376 reply(sortedBottleIDs, sortedPartialEscrowRecordIDs, error);
1377 }];
1378 }
1379
1380 - (void)fetchEscrowContents:(NSString* _Nullable)containerName
1381 contextID:(NSString *)contextID
1382 reply:(void (^)(NSData* _Nullable entropy,
1383 NSString* _Nullable bottleID,
1384 NSData* _Nullable signingPublicKey,
1385 NSError* _Nullable error))reply
1386 {
1387 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchEscrowContents];
1388
1389 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1390 [cfshContext fetchEscrowContents:^(NSData *entropy,
1391 NSString *bottleID,
1392 NSData *signingPublicKey,
1393 NSError *error) {
1394 [tracker stopWithEvent:OctagonEventFetchEscrowContents result:error];
1395 reply(entropy, bottleID, signingPublicKey, error);
1396 }];
1397 }
1398
1399 ////
1400 // MARK: Pairing Routines as Initiator
1401 ////
1402 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
1403 reply:(void (^)(NSString * _Nullable peerID,
1404 NSData * _Nullable permanentInfo,
1405 NSData * _Nullable permanentInfoSig,
1406 NSData * _Nullable stableInfo,
1407 NSData * _Nullable stableInfoSig,
1408 NSError * _Nullable error))reply
1409 {
1410 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
1411 [cfshContext handlePairingRestart:config];
1412 [cfshContext startOctagonStateMachine];
1413 [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config epoch:config.epoch reply:^(NSString * _Nullable peerID, NSData * _Nullable permanentInfo, NSData * _Nullable permanentInfoSig, NSData * _Nullable stableInfo, NSData * _Nullable stableInfoSig, NSError * _Nullable error) {
1414 reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error);
1415 }];
1416 }
1417
1418 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
1419 vouchData:(NSData*)vouchData
1420 vouchSig:(NSData*)vouchSig
1421 preapprovedKeys:(NSArray<NSData*>* _Nullable)preapprovedKeys
1422 reply:(void (^)(NSError * _Nullable error))reply
1423 {
1424 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
1425 [cfshContext handlePairingRestart:config];
1426 [cfshContext startOctagonStateMachine];
1427 [cfshContext rpcJoin:vouchData vouchSig:vouchSig preapprovedKeys:preapprovedKeys reply:^(NSError * _Nullable error) {
1428 reply(error);
1429 }];
1430 }
1431
1432 ////
1433 // MARK: Pairing Routines as Acceptor
1434 ////
1435
1436 - (void)rpcEpochWithConfiguration:(OTJoiningConfiguration*)config
1437 reply:(void (^)(uint64_t epoch,
1438 NSError * _Nullable error))reply
1439 {
1440 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
1441 [acceptorCfshContext startOctagonStateMachine];
1442
1443 // Let's assume that the new device's machine ID has made it to the IDMS list by now, and let's refresh our idea of that list
1444 [acceptorCfshContext requestTrustedDeviceListRefresh];
1445
1446 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
1447
1448 [clientStateMachine startOctagonStateMachine];
1449
1450 [clientStateMachine rpcEpoch:acceptorCfshContext reply:^(uint64_t epoch, NSError * _Nullable error) {
1451 reply(epoch, error);
1452 }];
1453 }
1454
1455 - (void)rpcVoucherWithConfiguration:(OTJoiningConfiguration*)config
1456 peerID:(NSString*)peerID
1457 permanentInfo:(NSData *)permanentInfo
1458 permanentInfoSig:(NSData *)permanentInfoSig
1459 stableInfo:(NSData *)stableInfo
1460 stableInfoSig:(NSData *)stableInfoSig
1461 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
1462 {
1463 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
1464 [acceptorCfshContext startOctagonStateMachine];
1465
1466 // Let's assume that the new device's machine ID has made it to the IDMS list by now, and let's refresh our idea of that list
1467 [acceptorCfshContext requestTrustedDeviceListRefresh];
1468 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
1469
1470 [clientStateMachine rpcVoucher:acceptorCfshContext peerID:peerID permanentInfo:permanentInfo permanentInfoSig:permanentInfoSig stableInfo:stableInfo stableInfoSig:stableInfoSig reply:^(NSData *voucher, NSData *voucherSig, NSError *error) {
1471 reply(voucher, voucherSig, error);
1472 }];
1473 }
1474
1475 - (void)restore:(NSString * _Nullable)containerName
1476 contextID:(nonnull NSString *)contextID
1477 bottleSalt:(nonnull NSString *)bottleSalt
1478 entropy:(nonnull NSData *)entropy
1479 bottleID:(nonnull NSString *)bottleID
1480 reply:(nonnull void (^)(NSError * _Nullable))reply {
1481 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityBottledPeerRestore];
1482
1483 secnotice("octagon", "restore via bottle invoked for container: %@, context: %@", containerName, contextID);
1484
1485 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1486
1487 [cfshContext startOctagonStateMachine];
1488
1489 [cfshContext joinWithBottle:bottleID entropy:entropy bottleSalt:bottleSalt reply:^(NSError *error) {
1490 [tracker stopWithEvent:OctagonEventBottledPeerRestore result:error];
1491 reply(error);
1492 }];
1493 }
1494
1495 ////
1496 // MARK: Ghost busting using ramp records
1497 ////
1498
1499 -(BOOL) ghostbustByMidEnabled {
1500 return [self.gbmidRamp checkRampStateWithError:nil];
1501 }
1502
1503 -(BOOL) ghostbustBySerialEnabled {
1504 return [self.gbserialRamp checkRampStateWithError:nil];
1505 }
1506
1507 -(BOOL) ghostbustByAgeEnabled {
1508 return [self.gbAgeRamp checkRampStateWithError:nil];
1509 }
1510
1511 ////
1512 // MARK: Analytics
1513 ////
1514
1515 - (void)setupAnalytics
1516 {
1517 WEAKIFY(self);
1518
1519 [[self.loggerClass logger] AddMultiSamplerForName:@"Octagon-healthSummary"
1520 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1521 block:^NSDictionary<NSString *,NSNumber *> *{
1522 STRONGIFY(self);
1523
1524 // We actually only care about the default context for the default container
1525 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1526
1527 secnotice("octagon-analytics", "Reporting analytics for container: %@, context: %@", OTCKContainerName, OTDefaultContext);
1528
1529 NSMutableDictionary* values = [NSMutableDictionary dictionary];
1530
1531 NSError* error = nil;
1532 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&error];
1533 if(error) {
1534 secnotice("octagon-analytics", "Error fetching SOS status: %@", error);
1535 }
1536 values[OctagonAnalyticsSOSStatus] = @((int)sosStatus);
1537 NSDate* dateOfLastPPJ = [[CKKSAnalytics logger] datePropertyForKey:OctagonEventUpgradePreflightPreapprovedJoin];
1538 values[OctagonAnalyticsDateOfLastPreflightPreapprovedJoin] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastPPJ]);
1539
1540 values[OctagonAnalyticsStateMachineState] = OctagonStateMap()[cuttlefishContext.stateMachine.currentState];
1541
1542 values[OctagonAnalyticIcloudAccountState] = @([cuttlefishContext currentMemoizedAccountState]);
1543 values[OctagonAnalyticsTrustState] = @([cuttlefishContext currentMemoizedTrustState]);
1544 NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck];
1545 values[OctagonAnalyticsLastHealthCheck] = @([CKKSAnalytics fuzzyDaysSinceDate:healthCheck]);
1546
1547 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastKeystateReady];
1548 values[OctagonAnalyticsLastKeystateReady] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR]);
1549
1550 // Track CFU usage and success/failure metrics
1551 // 1. Users in a good state will have no outstanding CFU, and will not have done a CFU
1552 // 2. Users in a bad state who have not repsonded to the CFU (repaired) will have a pending CFU.
1553 // 3. Users in a bad state who have acted on the CFU will have no pending CFU, but will have CFU failures.
1554 //
1555 // We also record the time window between the last followup completion and invocation.
1556 OTFollowup *followupHandler = cuttlefishContext.followupHandler;
1557 NSDate* dateOfLastFollowup = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastCoreFollowup];
1558 values[OctagonAnalyticsLastCoreFollowup] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastFollowup]);
1559 values[OctagonAnalyticsCoreFollowupStatus] = @(followupHandler.followupStatus);
1560
1561 for (NSString *type in [self cdpContextTypes]) {
1562 NSString *metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1563 NSString *countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1564
1565 NSDate *lastFailure = [[CKKSAnalytics logger] datePropertyForKey:metricName];
1566 if (lastFailure) {
1567 values[metricName] = @([CKKSAnalytics fuzzyDaysSinceDate:lastFailure]);
1568 values[countName] = [[CKKSAnalytics logger] numberPropertyForKey:countName];
1569 }
1570 }
1571
1572 // SecEscrowRequest
1573 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
1574 if (request) {
1575 values[OctagonAnalyticsPrerecordPending] = @([request pendingEscrowUpload:&error]);
1576 if (error) {
1577 secnotice("octagon-analytics", "Error fetching pendingEscrowUpload status: %@", error);
1578 }
1579 } else {
1580 secnotice("octagon-analytics", "Error fetching escrowRequestClass: %@", error);
1581 }
1582
1583 {
1584 ACAccountStore *store = [[ACAccountStore alloc] init];
1585 ACAccount* primaryAccount = [store aa_primaryAppleAccount];
1586 if(primaryAccount) {
1587 values[OctagonAnalyticsKVSProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeyValue]);
1588 values[OctagonAnalyticsKVSEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeyValue]);
1589 values[OctagonAnalyticsKeychainSyncProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeychainSync]);
1590 values[OctagonAnalyticsKeychainSyncEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeychainSync]);
1591 values[OctagonAnalyticsCloudKitProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassCKDatabaseService]);
1592 values[OctagonAnalyticsCloudKitEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassCKDatabaseService]);
1593 }
1594 }
1595
1596 return values;
1597 }];
1598
1599 [[self.loggerClass logger] AddMultiSamplerForName:@"CFU-healthSummary"
1600 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1601 block:^NSDictionary<NSString *,NSNumber *> *{
1602 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1603 return [cuttlefishContext.followupHandler sfaStatus];
1604 }];
1605 }
1606
1607 - (NSArray<OTCliqueCDPContextType> *)cdpContextTypes
1608 {
1609 static NSArray<OTCliqueCDPContextType> *contextTypes = nil;
1610 static dispatch_once_t onceToken;
1611 dispatch_once(&onceToken, ^{
1612 contextTypes = @[OTCliqueCDPContextTypeNone,
1613 OTCliqueCDPContextTypeSignIn,
1614 OTCliqueCDPContextTypeRepair,
1615 OTCliqueCDPContextTypeFinishPasscodeChange,
1616 OTCliqueCDPContextTypeRecoveryKeyGenerate,
1617 OTCliqueCDPContextTypeRecoveryKeyNew,
1618 OTCliqueCDPContextTypeUpdatePasscode,
1619 ];
1620 });
1621 return contextTypes;
1622 }
1623
1624 ////
1625 // MARK: Recovery Key
1626 ////
1627
1628 - (void) createRecoveryKey:(NSString* _Nullable)containerName
1629 contextID:(NSString *)contextID
1630 recoveryKey:(NSString *)recoveryKey
1631 reply:(void (^)( NSError *error))reply
1632 {
1633 secnotice("octagon", "Setting recovery key for container: %@, context: %@", containerName, contextID);
1634
1635 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivitySetRecoveryKey];
1636
1637 if (!self.sosEnabledForPlatform) {
1638 secnotice("octagon-recovery", "Device is considered a limited peer, cannot enroll recovery key in Octagon");
1639 NSError* notFullPeerError = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorLimitedPeer userInfo:@{NSLocalizedDescriptionKey : @"Device is considered a limited peer, cannot enroll recovery key in Octagon"}];
1640 [tracker stopWithEvent:OctagonEventRecoveryKey result:notFullPeerError];
1641 reply(notFullPeerError);
1642 return;
1643 }
1644
1645 CFErrorRef validateError = NULL;
1646 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, CFBridgingRetain(recoveryKey), &validateError);
1647 if (!res) {
1648 secerror("recovery failed validation with error:%@", validateError);
1649
1650 NSError *validateErrorWrapper = (__bridge_transfer NSError *)validateError;
1651 [tracker stopWithEvent:OctagonEventSetRecoveryKeyValidationFailed result:validateErrorWrapper];
1652
1653 if (validateErrorWrapper) {
1654 reply(validateErrorWrapper);
1655 return;
1656 }
1657 }
1658
1659 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1660
1661 [cfshContext startOctagonStateMachine];
1662
1663 [cfshContext rpcSetRecoveryKey:recoveryKey reply:^(NSError * _Nullable error) {
1664 [tracker stopWithEvent:OctagonEventRecoveryKey result:error];
1665 reply(error);
1666 }];
1667 }
1668
1669 - (void) joinWithRecoveryKey:(NSString* _Nullable)containerName
1670 contextID:(NSString *)contextID
1671 recoveryKey:(NSString*)recoveryKey
1672 reply:(void (^)(NSError * _Nullable))reply
1673 {
1674 secnotice("octagon", "join with recovery key invoked for container: %@, context: %@", containerName, contextID);
1675
1676 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityJoinWithRecoveryKey];
1677
1678 CFErrorRef validateError = NULL;
1679 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, CFBridgingRetain(recoveryKey), &validateError);
1680 if (!res) {
1681 secerror("recovery failed validation with error:%@", validateError);
1682
1683 NSError *validateErrorWrapper = (__bridge_transfer NSError *)validateError;
1684 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyValidationFailed result:validateErrorWrapper];
1685
1686 if (validateErrorWrapper) {
1687 reply(validateErrorWrapper);
1688 return;
1689 }
1690 }
1691
1692 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1693
1694 [cfshContext startOctagonStateMachine];
1695
1696 [cfshContext joinWithRecoveryKey:recoveryKey reply:^(NSError *error) {
1697 if (error.code == TrustedPeersHelperErrorCodeNotEnrolled && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]) {
1698 // If we hit this error, let's reset and establish octagon then enroll this recovery key in the new circle
1699 secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle");
1700 [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error];
1701
1702 [cfshContext rpcResetAndEstablish:^(NSError *resetError) {
1703 if (resetError) {
1704 secerror("octagon, failed to reset octagon");
1705 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:resetError];
1706 reply(resetError);
1707 return;
1708 } else {
1709 // Now enroll the recovery key
1710 secnotice("octagon", "attempting enrolling recovery key");
1711 if (self.sosEnabledForPlatform) {
1712 [self createRecoveryKey:containerName contextID:contextID recoveryKey:recoveryKey reply:^(NSError *enrollError) {
1713 if(enrollError){
1714 secerror("octagon, failed to enroll new recovery key: %@", enrollError);
1715 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyEnrollFailed result:enrollError];
1716 reply(enrollError);
1717 return;
1718 }else{
1719 secnotice("octagon", "successfully enrolled recovery key");
1720 [tracker stopWithEvent:OctagonEventRecoveryKey result:nil];
1721 reply (nil);
1722 return;
1723 }
1724 }];
1725 } else {
1726 secnotice("octagon", "Limited Peer, can't enroll recovery key");
1727 reply (nil);
1728 return;
1729 }
1730 }
1731 }];
1732 } else {
1733 secerror("octagon, join with recovery key failed: %d", (int)[error code]);
1734 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyFailed result:error];
1735 reply(error);
1736 }
1737 }];
1738 }
1739
1740 - (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply
1741 {
1742 [self xpc24HrNotification:container context:context skipRateLimitingCheck:skipRateLimitingCheck reply:reply];
1743 }
1744
1745
1746 - (void)xpc24HrNotification:(NSString* _Nullable)containerName context:(NSString*)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *error))reply
1747 {
1748 secnotice("octagon-health", "Received 24 xpc notification");
1749
1750 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1751
1752 secnotice("octagon", "notifying container of change");
1753
1754 [cfshContext notifyContainerChange:nil];
1755
1756 [cfshContext checkOctagonHealth:skipRateLimitingCheck reply:^(NSError *error) {
1757 if(error) {
1758 reply(error);
1759 } else {
1760 reply(nil);
1761 }
1762 }];
1763 }
1764
1765 - (void)setSOSEnabledForPlatformFlag:(bool) value
1766 {
1767 self.sosEnabledForPlatform = value;
1768 }
1769
1770 - (void)allContextsHalt
1771 {
1772 for(OTCuttlefishContext* context in self.contexts.allValues) {
1773 [context.stateMachine haltOperation];
1774 }
1775 }
1776
1777 - (void)allContextsDisablePendingFlags
1778 {
1779 for(OTCuttlefishContext* context in self.contexts.allValues) {
1780 [context.stateMachine disablePendingFlags];
1781 }
1782 }
1783
1784 - (bool)allContextsPause:(uint64_t)within
1785 {
1786 for(OTCuttlefishContext* context in self.contexts.allValues) {
1787 if(context.stateMachine.currentState != OctagonStateMachineNotStarted) {
1788 if([context.stateMachine.paused wait:within] != 0) {
1789 return false;
1790 }
1791 }
1792 }
1793 return true;
1794 }
1795
1796 - (void)attemptSosUpgrade:(NSString* _Nullable)containerName
1797 context:(NSString*)context
1798 reply:(void (^)(NSError* error))reply
1799
1800 {
1801 secnotice("octagon-sos", "Attempting sos upgrade");
1802 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1803
1804 [cfshContext startOctagonStateMachine];
1805
1806 [cfshContext attemptSOSUpgrade:^(NSError *error) {
1807 reply(error);
1808 }];
1809 }
1810
1811 - (void)waitForOctagonUpgrade:(NSString* _Nullable)containerName
1812 context:(NSString*)context
1813 reply:(void (^)(NSError* error))reply
1814
1815 {
1816 secnotice("octagon-sos", "waitForOctagonUpgrade");
1817 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1818
1819 [cfshContext startOctagonStateMachine];
1820
1821 [cfshContext waitForOctagonUpgrade:^(NSError * _Nonnull error) {
1822 reply(error);
1823 }];
1824 }
1825
1826 - (OTFollowupContextType)cliqueCDPTypeToFollowupContextType:(OTCliqueCDPContextType)type
1827 {
1828 if ([type isEqualToString:OTCliqueCDPContextTypeNone]) {
1829 return OTFollowupContextTypeNone;
1830 } else if ([type isEqualToString:OTCliqueCDPContextTypeSignIn]) {
1831 return OTFollowupContextTypeNone;
1832 } else if ([type isEqualToString:OTCliqueCDPContextTypeRepair]) {
1833 return OTFollowupContextTypeStateRepair;
1834 } else if ([type isEqualToString:OTCliqueCDPContextTypeFinishPasscodeChange]) {
1835 return OTFollowupContextTypeOfflinePasscodeChange;
1836 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyGenerate]) {
1837 return OTFollowupContextTypeRecoveryKeyRepair;
1838 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyNew]) {
1839 return OTFollowupContextTypeRecoveryKeyRepair;
1840 } else if ([type isEqualToString:OTCliqueCDPContextTypeUpdatePasscode]) {
1841 return OTFollowupContextTypeNone;
1842 } else {
1843 return OTFollowupContextTypeNone;
1844 }
1845 }
1846
1847 - (void)postCDPFollowupResult:(BOOL)success
1848 type:(OTCliqueCDPContextType)type
1849 error:(NSError * _Nullable)error
1850 containerName:(NSString* _Nullable)containerName
1851 contextName:(NSString *)contextName
1852 reply:(void (^)(NSError *error))reply
1853 {
1854 OTCuttlefishContext *cuttlefishContext = [self contextForContainerName:containerName contextID:contextName];
1855
1856 NSString* metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1857 NSString* countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1858
1859 [[CKKSAnalytics logger] logResultForEvent:metricName
1860 hardFailure:NO
1861 result:error];
1862 if (error) {
1863 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName];
1864 [[CKKSAnalytics logger] incrementIntegerPropertyForKey:countName];
1865 } else {
1866 [[CKKSAnalytics logger] setDateProperty:NULL forKey:metricName];
1867 [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName];
1868 }
1869
1870 // Clear all CFU state variables, too
1871 [cuttlefishContext clearPendingCFUFlags];
1872
1873 // Always return without error
1874 reply(nil);
1875 }
1876
1877 - (void)tapToRadar:(NSString *)action
1878 description:(NSString *)description
1879 radar:(NSString *)radar
1880 reply:(void (^)(NSError * _Nullable))reply
1881 {
1882 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:action description:description radar:radar];
1883 [ttr trigger];
1884 reply(NULL);
1885 }
1886
1887 @end
1888
1889 #endif