]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTManager.m
Security-59306.101.1.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/OTManager.h"
36 #import "keychain/ot/OTDefines.h"
37 #import "keychain/ot/OTRamping.h"
38 #import "keychain/ot/OT.h"
39 #import "keychain/ot/OTConstants.h"
40 #import "keychain/ot/OTCuttlefishContext.h"
41 #import "keychain/ot/OTClientStateMachine.h"
42 #import "keychain/ot/OTStates.h"
43 #import "keychain/ot/OTJoiningConfiguration.h"
44 #import "keychain/ot/OTSOSAdapter.h"
45
46 #import "keychain/categories/NSError+UsefulConstructors.h"
47 #import "keychain/ckks/CKKSAnalytics.h"
48 #import "keychain/ckks/CKKSNearFutureScheduler.h"
49 #import "keychain/ckks/CKKS.h"
50 #import "keychain/ckks/CKKSViewManager.h"
51 #import "keychain/ckks/CKKSLockStateTracker.h"
52 #import "keychain/ckks/CKKSCloudKitClassDependencies.h"
53
54 #import <CloudKit/CloudKit.h>
55 #import <CloudKit/CloudKit_Private.h>
56
57 #import <SecurityFoundation/SFKey.h>
58 #import <SecurityFoundation/SFKey_Private.h>
59 #import "SecPasswordGenerate.h"
60
61 #import "keychain/categories/NSError+UsefulConstructors.h"
62 #include <CloudKit/CloudKit_Private.h>
63 #import <KeychainCircle/PairingChannel.h>
64
65 #import "keychain/escrowrequest/Framework/SecEscrowRequest.h"
66 #import "keychain/escrowrequest/EscrowRequestServer.h"
67
68 // If your callbacks might pass back a CK error, you should use XPCSanitizeError()
69 // Otherwise, XPC might crash on the other side if they haven't linked CloudKit.framework.
70 #define XPCSanitizeError CKXPCSuitableError
71
72 #import <Accounts/Accounts.h>
73 #import <Accounts/ACAccountStore_Private.h>
74 #import <Accounts/ACAccountType_Private.h>
75 #import <Accounts/ACAccountStore.h>
76 #import <AppleAccount/ACAccountStore+AppleAccount.h>
77 #import <AppleAccount/ACAccount+AppleAccount.h>
78
79 #import <CoreCDP/CDPFollowUpController.h>
80
81 #import "keychain/TrustedPeersHelper/TPHObjcTranslation.h"
82 #import "keychain/SecureObjectSync/SOSAccountTransaction.h"
83 #pragma clang diagnostic push
84 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
85 #import "keychain/SecureObjectSync/SOSAccount.h"
86 #pragma clang diagnostic pop
87
88 #import "utilities/SecTapToRadar.h"
89
90 static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
91 static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
92 static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";
93 static NSString* const kOTRampForGhostBustMIDName = @"metadata_rampstate_ghostBustMID";
94 static NSString* const kOTRampForghostBustSerialName = @"metadata_rampstate_ghostBustSerial";
95 static NSString* const kOTRampForghostBustAgeName = @"metadata_rampstate_ghostBustAge";
96 static NSString* const kOTRampZoneName = @"metadata_zone";
97 #define NUM_NSECS_IN_24_HRS (86400 * NSEC_PER_SEC)
98
99 #if OCTAGON
100 @interface OTManager (lockstateTracker) <CKKSLockStateNotification>
101 @end
102 #endif
103
104 @interface OTManager () <NSXPCListenerDelegate>
105 @property NSXPCListener *listener;
106
107 @property (nonatomic, strong) OTRamp *gbmidRamp;
108 @property (nonatomic, strong) OTRamp *gbserialRamp;
109 @property (nonatomic, strong) OTRamp *gbAgeRamp;
110 @property (nonatomic, strong) CKKSLockStateTracker *lockStateTracker;
111 @property (nonatomic, strong) id<OctagonFollowUpControllerProtocol> cdpd;
112
113 // Current contexts
114 @property NSMutableDictionary<NSString*, OTCuttlefishContext*>* contexts;
115 @property NSMutableDictionary<NSString*, OTClientStateMachine*>* clients;
116 @property dispatch_queue_t queue;
117
118 @property id<NSXPCProxyCreating> cuttlefishXPCConnection;
119
120 // Dependencies for injection
121 @property (readonly) id<OTSOSAdapter> sosAdapter;
122 @property (readonly) id<OTAuthKitAdapter> authKitAdapter;
123 @property (readonly) id<OTDeviceInformationAdapter> deviceInformationAdapter;
124 @property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
125 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
126 // If this is nil, all logging is disabled
127 @property (readonly, nullable) Class<SFAnalyticsProtocol> loggerClass;
128 @property (nonatomic) BOOL sosEnabledForPlatform;
129 @end
130
131 @implementation OTManager
132 @synthesize cuttlefishXPCConnection = _cuttlefishXPCConnection;
133 @synthesize sosAdapter = _sosAdapter;
134 @synthesize authKitAdapter = _authKitAdapter;
135 @synthesize deviceInformationAdapter = _deviceInformationAdapter;
136
137 - (instancetype)init
138 {
139 // Under Octagon, the sos adapter is not considered essential.
140 id<OTSOSAdapter> sosAdapter = (OctagonPlatformSupportsSOS() ?
141 [[OTSOSActualAdapter alloc] initAsEssential:NO] :
142 [[OTSOSMissingAdapter alloc] init]);
143
144 return [self initWithSOSAdapter:sosAdapter
145 authKitAdapter:[[OTAuthKitActualAdapter alloc] init]
146 deviceInformationAdapter:[[OTDeviceInformationActualAdapter alloc] init]
147 apsConnectionClass:[APSConnection class]
148 escrowRequestClass:[EscrowRequestServer class] // Use the server class here to skip the XPC layer
149 loggerClass:[CKKSAnalytics class]
150 lockStateTracker:[CKKSLockStateTracker globalTracker]
151 cloudKitClassDependencies:[CKKSCloudKitClassDependencies forLiveCloudKit]
152 cuttlefishXPCConnection:nil
153 cdpd:[[CDPFollowUpController alloc] init]];
154 }
155
156 - (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
157 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
158 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
159 apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
160 escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
161 loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
162 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
163 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
164 cuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
165 cdpd:(id<OctagonFollowUpControllerProtocol>)cdpd
166 {
167 if((self = [super init])) {
168 _sosAdapter = sosAdapter;
169 _authKitAdapter = authKitAdapter;
170 _deviceInformationAdapter = deviceInformationAdapter;
171 _loggerClass = loggerClass;
172 _lockStateTracker = lockStateTracker;
173 _sosEnabledForPlatform = OctagonPlatformSupportsSOS();
174 _cuttlefishXPCConnection = cuttlefishXPCConnection;
175
176 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
177 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
178 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
179
180 self.contexts = [NSMutableDictionary dictionary];
181 self.clients = [NSMutableDictionary dictionary];
182
183 self.queue = dispatch_queue_create("otmanager", DISPATCH_QUEUE_SERIAL);
184
185 _apsConnectionClass = apsConnectionClass;
186 _escrowRequestClass = escrowRequestClass;
187
188 _cdpd = cdpd;
189
190 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
191 sosAdapter:sosAdapter
192 accountStateTracker:_accountStateTracker
193 lockStateTracker:lockStateTracker
194 cloudKitClassDependencies:cloudKitClassDependencies];
195
196 // The default CuttlefishContext always exists:
197 (void) [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
198
199 secnotice("octagon", "otmanager init");
200 }
201 return self;
202 }
203
204 - (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
205 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
206 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
207 {
208 if((self = [super init])) {
209 _sosAdapter = sosAdapter;
210 _lockStateTracker = lockStateTracker;
211
212 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
213 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
214 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
215
216 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
217 sosAdapter:sosAdapter
218 accountStateTracker:_accountStateTracker
219 lockStateTracker:lockStateTracker
220 cloudKitClassDependencies:cloudKitClassDependencies];
221 }
222 return self;
223 }
224
225 - (void)initializeOctagon
226 {
227 secnotice("octagon", "Initializing Octagon...");
228
229 if(OctagonIsEnabled()) {
230 secnotice("octagon", "starting default state machine...");
231 OTCuttlefishContext* c = [self contextForContainerName:OTCKContainerName
232 contextID:OTDefaultContext];
233
234 [c startOctagonStateMachine];
235 [self registerForCircleChangedNotifications];
236 }
237 }
238
239 - (BOOL)waitForReady:(NSString* _Nullable)containerName context:(NSString*)context wait:(int64_t)wait
240 {
241 OTCuttlefishContext* c = [self contextForContainerName:containerName contextID:context];
242 return [c waitForReady:wait];
243 }
244
245 - (void) moveToCheckTrustedStateForContainer:(NSString* _Nullable)containerName context:(NSString*)context
246 {
247 OTCuttlefishContext* c = [self contextForContainerName:containerName
248 contextID:context];
249 [c startOctagonStateMachine];
250 [c moveToCheckTrustedState];
251 }
252
253 - (void)registerForCircleChangedNotifications
254 {
255 __weak __typeof(self) weakSelf = self;
256
257 // If we're not in the tests, go ahead and register for a notification
258 if(!SecCKKSTestsEnabled()) {
259 int token = 0;
260 notify_register_dispatch(kSOSCCCircleChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
261 secnotice("octagon", "circle changed notification called, checking trust state");
262 [weakSelf moveToCheckTrustedStateForContainer:OTCKContainerName context:OTDefaultContext];
263 });
264 }
265 }
266
267 + (instancetype _Nullable)manager {
268 if(!OctagonIsEnabled()) {
269 secerror("octagon: Attempt to fetch a manager while Octagon is disabled");
270 return nil;
271 }
272
273 return [self resetManager:false to:nil];
274 }
275
276 + (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj
277 {
278 static OTManager* manager = nil;
279
280 if(!manager || reset || obj) {
281 @synchronized([self class]) {
282 if(obj != nil) {
283 manager = obj;
284 } else {
285 if(reset) {
286 manager = nil;
287 } else if (manager == nil && OctagonIsEnabled()) {
288 manager = [[OTManager alloc] init];
289 }
290 }
291 }
292 }
293
294 return manager;
295 }
296
297 - (void)ensureRampsInitialized
298 {
299 CKContainer* container = [CKKSViewManager manager].container;
300 CKDatabase* database = [container privateCloudDatabase];
301 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
302
303 CKKSAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
304 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
305 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
306
307 if(!self.gbmidRamp) {
308 self.gbmidRamp = [[OTRamp alloc]initWithRecordName:kOTRampForGhostBustMIDName
309 localSettingName:@"ghostBustMID"
310 container:container
311 database:database
312 zoneID:zoneID
313 accountTracker:accountTracker
314 lockStateTracker:lockStateTracker
315 reachabilityTracker:reachabilityTracker
316 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
317 }
318
319 if(!self.gbserialRamp) {
320 self.gbserialRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustSerialName
321 localSettingName:@"ghostBustSerial"
322 container:container
323 database:database
324 zoneID:zoneID
325 accountTracker:accountTracker
326 lockStateTracker:lockStateTracker
327 reachabilityTracker:reachabilityTracker
328 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
329 }
330
331 if(!self.gbAgeRamp) {
332 self.gbAgeRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustAgeName
333 localSettingName:@"ghostBustAge"
334 container:container
335 database:database
336 zoneID:zoneID
337 accountTracker:accountTracker
338 lockStateTracker:lockStateTracker
339 reachabilityTracker:reachabilityTracker
340 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
341 }
342 }
343
344 ////
345 // MARK: SPI routines
346 ////
347
348
349 - (void)signIn:(NSString*)altDSID
350 container:(NSString* _Nullable)containerName
351 context:(NSString*)contextID
352 reply:(void (^)(NSError * _Nullable signedInError))reply
353 {
354 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountAvailable];
355
356 if(containerName == nil) {
357 containerName = OTCKContainerName;
358 }
359
360 NSError *error = nil;
361 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
362
363 secnotice("octagon","signing in %@ for altDSID: %@", context, altDSID);
364 [context accountAvailable:altDSID error:&error];
365
366 [tracker stopWithEvent:OctagonEventSignIn result:error];
367
368 reply(error);
369 }
370
371 - (void)signOut:(NSString* _Nullable)containerName
372 context:(NSString*)contextID
373 reply:(void (^)(NSError * _Nullable signedOutError))reply
374 {
375 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountNotAvailable];
376
377 if(containerName == nil) {
378 containerName = OTCKContainerName;
379 }
380
381 NSError* error = nil;
382
383 // TODO: should we compare DSIDs here?
384 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
385
386 secnotice("octagon", "signing out of octagon trust: %@", context);
387
388 [context accountNoLongerAvailable:&error];
389 if(error) {
390 secnotice("octagon", "signing out failed: %@", error);
391 }
392
393 [tracker stopWithEvent:OctagonEventSignOut result:error];
394
395 reply(error);
396 }
397
398 - (void)notifyIDMSTrustLevelChangeForContainer:(NSString* _Nullable)containerName
399 context:(NSString*)contextID
400 reply:(void (^)(NSError * _Nullable error))reply
401 {
402 if(containerName == nil) {
403 containerName = OTCKContainerName;
404 }
405
406 NSError *error = nil;
407 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
408 secnotice("octagon","received a notification of IDMS trust level change in %@", context);
409 [context idmsTrustLevelChanged:&error];
410
411 reply(error);
412 }
413
414 - (void)handleIdentityChangeForSigningKey:(SFECKeyPair*)peerSigningKey
415 ForEncryptionKey:(SFECKeyPair*)encryptionKey
416 ForPeerID:(NSString*)peerID
417 reply:(void (^)(BOOL result,
418 NSError* _Nullable error))reply
419 {
420 secnotice("octagon", "handleIdentityChangeForSigningKey: %@", peerID);
421 reply(NO,
422 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
423 code:errSecUnimplemented
424 userInfo:nil]);
425 }
426
427 - (void)preflightBottledPeer:(NSString*)contextID
428 dsid:(NSString*)dsid
429 reply:(void (^)(NSData* _Nullable entropy,
430 NSString* _Nullable bottleID,
431 NSData* _Nullable signingPublicKey,
432 NSError* _Nullable error))reply
433 {
434 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
435 reply(nil,
436 nil,
437 nil,
438 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
439 code:errSecUnimplemented
440 userInfo:nil]);
441 }
442
443 - (void)launchBottledPeer:(NSString*)contextID
444 bottleID:(NSString*)bottleID
445 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
446 {
447 secnotice("octagon", "launchBottledPeer");
448 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
449 code:errSecUnimplemented
450 userInfo:nil]);
451 }
452
453 - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
454 {
455 secnotice("octagon", "restore");
456 reply(nil,
457 nil,
458 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
459 code:errSecUnimplemented
460 userInfo:nil]);
461 }
462
463 - (void)scrubBottledPeer:(NSString*)contextID
464 bottleID:(NSString*)bottleID
465 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
466 {
467 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
468 code:errSecUnimplemented
469 userInfo:nil]);
470 }
471
472 ////
473 // MARK: OTCTL tool routines
474 ////
475
476 -(void)reset:(void (^)(BOOL result, NSError *))reply
477 {
478 reply(NO,
479 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
480 code:errSecUnimplemented
481 userInfo:nil]);
482 }
483
484 - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError * _Nullable))reply
485 {
486 reply(@[],
487 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
488 code:errSecUnimplemented
489 userInfo:nil]);
490 }
491
492 - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
493 {
494 reply(nil,
495 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
496 code:errSecUnimplemented
497 userInfo:nil]);
498 }
499
500 -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
501 {
502 reply(nil,
503 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
504 code:errSecUnimplemented
505 userInfo:nil]);
506 }
507
508 - (void)setCuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
509 {
510 _cuttlefishXPCConnection = cuttlefishXPCConnection;
511 }
512
513 - (id<NSXPCProxyCreating>)cuttlefishXPCConnection
514 {
515 if(!_cuttlefishXPCConnection) {
516 NSXPCConnection* xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.TrustedPeersHelper"];
517 xpcConnection.remoteObjectInterface = TrustedPeersHelperSetupProtocol([NSXPCInterface interfaceWithProtocol:@protocol(TrustedPeersHelperProtocol)]);
518 [xpcConnection resume];
519 _cuttlefishXPCConnection = xpcConnection;
520 }
521
522 return _cuttlefishXPCConnection;
523 }
524
525 - (OTClientStateMachine*)clientStateMachineForContainerName:(NSString* _Nullable)containerName
526 contextID:(NSString*)contextID
527 clientName:(NSString*)clientName
528 {
529 __block OTClientStateMachine* client = nil;
530
531 if(containerName == nil) {
532 containerName = SecCKKSContainerName;
533 }
534
535 dispatch_sync(self.queue, ^{
536 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
537 secnotice("octagon-client", "fetching context for key: %@", key);
538 client = self.clients[key];
539 if(!client) {
540 client = [[OTClientStateMachine alloc] initWithContainerName:containerName
541 contextID:contextID
542 clientName:clientName
543 cuttlefish:self.cuttlefishXPCConnection];
544
545 self.clients[key] = client;
546 }
547 });
548
549 return client;
550 }
551
552 - (void)removeClientContextForContainerName:(NSString* _Nullable)containerName
553 clientName:(NSString*)clientName
554 {
555 if(containerName == nil) {
556 containerName = SecCKKSContainerName;
557 }
558
559 dispatch_sync(self.queue, ^{
560 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
561 [self.clients removeObjectForKey:key];
562 secnotice("octagon", "removed client context with key: %@", key);
563 });
564 }
565
566 - (void)removeContextForContainerName:(NSString*)containerName
567 contextID:(NSString*)contextID
568 {
569 dispatch_sync(self.queue, ^{
570 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
571 self.contexts[key] = nil;
572 });
573 }
574
575 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
576 contextID:(NSString*)contextID
577 {
578 return [self contextForContainerName:containerName
579 contextID:contextID
580 sosAdapter:self.sosAdapter
581 authKitAdapter:self.authKitAdapter
582 lockStateTracker:self.lockStateTracker
583 accountStateTracker:self.accountStateTracker
584 deviceInformationAdapter:self.deviceInformationAdapter];
585 }
586
587 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
588 contextID:(NSString*)contextID
589 sosAdapter:(id<OTSOSAdapter>)sosAdapter
590 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
591 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
592 accountStateTracker:(CKKSAccountStateTracker*)accountStateTracker
593 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
594 {
595 __block OTCuttlefishContext* context = nil;
596
597 if(containerName == nil) {
598 containerName = SecCKKSContainerName;
599 }
600
601 dispatch_sync(self.queue, ^{
602 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
603
604 context = self.contexts[key];
605
606 // Right now, CKKS can only handle one session per address space (and SQL database).
607 // Therefore, only the primary OTCuttlefishContext gets to own the view manager.
608 CKKSViewManager* viewManager = nil;
609 if([containerName isEqualToString:SecCKKSContainerName] &&
610 [contextID isEqualToString:OTDefaultContext]) {
611 viewManager = self.viewManager;
612 }
613
614 if(!context) {
615 context = [[OTCuttlefishContext alloc] initWithContainerName:containerName
616 contextID:contextID
617 cuttlefish:self.cuttlefishXPCConnection
618 sosAdapter:sosAdapter
619 authKitAdapter:authKitAdapter
620 ckksViewManager:viewManager
621 lockStateTracker:lockStateTracker
622 accountStateTracker:accountStateTracker
623 deviceInformationAdapter:deviceInformationAdapter
624 apsConnectionClass:self.apsConnectionClass
625 escrowRequestClass:self.escrowRequestClass
626 cdpd:self.cdpd];
627 self.contexts[key] = context;
628 }
629 });
630
631 return context;
632 }
633
634 - (void)clearAllContexts
635 {
636 if(self.contexts) {
637 dispatch_sync(self.queue, ^{
638 [self.contexts removeAllObjects];
639 });
640 }
641 }
642
643 - (void)fetchEgoPeerID:(NSString* _Nullable)container
644 context:(NSString*)context
645 reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply
646 {
647 if(!container) {
648 container = OTCKContainerName;
649 }
650 secnotice("octagon", "Received a fetch peer ID for container (%@) and context (%@)", container, context);
651 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
652 [cfshContext rpcFetchEgoPeerID:^(NSString * _Nullable peerID,
653 NSError * _Nullable error) {
654 reply(peerID, XPCSanitizeError(error));
655 }];
656 }
657
658 - (void)fetchTrustStatus:(NSString *)container
659 context:(NSString *)context
660 configuration:(OTOperationConfiguration *)configuration
661 reply:(void (^)(CliqueStatus status,
662 NSString* _Nullable peerID,
663 NSNumber * _Nullable numberOfPeersInOctagon,
664 BOOL isExcluded,
665 NSError* _Nullable error))reply
666 {
667 if(!container) {
668 container = OTCKContainerName;
669 }
670 secnotice("octagon", "Received a trust status for container (%@) and context (%@)", container, context);
671 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
672
673 [cfshContext rpcTrustStatus:configuration reply:^(CliqueStatus status,
674 NSString * _Nullable peerID,
675 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
676 BOOL isExcluded,
677 NSError * _Nullable error) {
678 // Our clients don't need the whole breakout of peers, so just count for them
679 long peerCount = 0;
680 for(NSNumber* n in peerCountByModelID.allValues) {
681 peerCount += [n longValue];
682 }
683
684 reply(status, peerID, @(peerCount), isExcluded, error);
685 }];
686 }
687
688 - (void)fetchCliqueStatus:(NSString* _Nullable)container
689 context:(NSString*)contextID
690 configuration:(OTOperationConfiguration *)configuration
691 reply:(void (^)(CliqueStatus cliqueStatus, NSError* _Nullable error))reply
692 {
693 if(!container) {
694 container = OTCKContainerName;
695 }
696 if(configuration == nil) {
697 configuration = [[OTOperationConfiguration alloc] init];
698 }
699
700 __block OTCuttlefishContext* context = nil;
701 dispatch_sync(self.queue, ^{
702 NSString* key = [NSString stringWithFormat:@"%@-%@", container, contextID];
703
704 context = self.contexts[key];
705 });
706
707 if(!context) {
708 reply(-1, [NSError errorWithDomain:OctagonErrorDomain
709 code:OTErrorNoSuchContext
710 description:[NSString stringWithFormat:@"No context for (%@,%@)", container, contextID]]);
711 return;
712 }
713
714
715 [context rpcTrustStatus:configuration reply:^(CliqueStatus status,
716 NSString* egoPeerID,
717 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
718 BOOL isExcluded,
719 NSError * _Nullable error) {
720 reply(status, error);
721 }];
722 }
723
724 - (void)status:(NSString* _Nullable)containerName
725 context:(NSString*)contextID
726 reply:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
727 {
728 if(!containerName) {
729 containerName = OTCKContainerName;
730 }
731
732 secnotice("octagon", "Received a status RPC for container (%@) and context (%@)", containerName, contextID);
733
734 __block OTCuttlefishContext* context = nil;
735 dispatch_sync(self.queue, ^{
736 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
737
738 context = self.contexts[key];
739 });
740
741 if(!context) {
742 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
743 code:OTErrorNoSuchContext
744 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
745 return;
746 }
747
748 [context rpcStatus:reply];
749 }
750
751 - (void)startOctagonStateMachine:(NSString* _Nullable)container
752 context:(NSString*)context
753 reply:(void (^)(NSError* _Nullable error))reply
754 {
755 secnotice("octagon", "Received a start-state-machine RPC for container (%@) and context (%@)", container, context);
756
757 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
758 [cfshContext startOctagonStateMachine];
759 reply(nil);
760 }
761
762 - (void)resetAndEstablish:(NSString *)container
763 context:(NSString *)context
764 altDSID:(NSString*)altDSID
765 resetReason:(CuttlefishResetReason)resetReason
766 reply:(void (^)(NSError * _Nullable))reply
767 {
768 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityResetAndEstablish];
769
770 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
771 [cfshContext startOctagonStateMachine];
772 [cfshContext rpcResetAndEstablish:resetReason reply:^(NSError* resetAndEstablishError) {
773 [tracker stopWithEvent:OctagonEventResetAndEstablish result:resetAndEstablishError];
774 reply(resetAndEstablishError);
775 }];
776 }
777
778 - (void)establish:(NSString *)container
779 context:(NSString *)context
780 altDSID:(NSString*)altDSID
781 reply:(void (^)(NSError * _Nullable))reply
782 {
783 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityEstablish];
784
785 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
786 [cfshContext startOctagonStateMachine];
787 [cfshContext rpcEstablish:altDSID reply:^(NSError* establishError) {
788 [tracker stopWithEvent:OctagonEventEstablish result:establishError];
789 reply(establishError);
790 }];
791 }
792
793 - (void)leaveClique:(NSString* _Nullable)container
794 context:(NSString*)context
795 reply:(void (^)(NSError* _Nullable error))reply
796 {
797 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityLeaveClique];
798
799 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
800 [cfshContext startOctagonStateMachine];
801 [cfshContext rpcLeaveClique:^(NSError* leaveError) {
802 [tracker stopWithEvent:OctagonEventLeaveClique result:leaveError];
803 reply(leaveError);
804 }];
805 }
806
807 - (void)removeFriendsInClique:(NSString* _Nullable)container
808 context:(NSString*)context
809 peerIDs:(NSArray<NSString*>*)peerIDs
810 reply:(void (^)(NSError* _Nullable error))reply
811 {
812 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityRemoveFriendsInClique];
813
814 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
815 [cfshContext startOctagonStateMachine];
816 [cfshContext rpcRemoveFriendsInClique:peerIDs reply:^(NSError* removeFriendsError) {
817 [tracker stopWithEvent:OctagonEventRemoveFriendsInClique result:removeFriendsError];
818 reply(removeFriendsError);
819 }];
820 }
821
822 - (void)peerDeviceNamesByPeerID:(NSString* _Nullable)container
823 context:(NSString*)context
824 reply:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
825 {
826 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
827 [cfshContext rpcFetchDeviceNamesByPeerID:reply];
828 }
829
830 - (void)fetchAllViableBottles:(NSString* _Nullable)container
831 context:(NSString*)context
832 reply:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialBottleIDs, NSError* _Nullable error))reply
833 {
834 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchAllViableBottles];
835
836 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
837 [cfshContext rpcFetchAllViableBottles:^(NSArray<NSString *> * _Nullable sortedBottleIDs,
838 NSArray<NSString *> * _Nullable sortedPartialEscrowRecordIDs,
839 NSError * _Nullable error) {
840 [tracker stopWithEvent:OctagonEventFetchAllBottles result:error];
841 reply(sortedBottleIDs, sortedPartialEscrowRecordIDs, error);
842 }];
843 }
844
845 - (void)fetchEscrowContents:(NSString* _Nullable)containerName
846 contextID:(NSString *)contextID
847 reply:(void (^)(NSData* _Nullable entropy,
848 NSString* _Nullable bottleID,
849 NSData* _Nullable signingPublicKey,
850 NSError* _Nullable error))reply
851 {
852 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchEscrowContents];
853
854 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
855 [cfshContext fetchEscrowContents:^(NSData *entropy,
856 NSString *bottleID,
857 NSData *signingPublicKey,
858 NSError *error) {
859 [tracker stopWithEvent:OctagonEventFetchEscrowContents result:error];
860 reply(entropy, bottleID, signingPublicKey, error);
861 }];
862 }
863
864 ////
865 // MARK: Pairing Routines as Initiator
866 ////
867 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
868 reply:(void (^)(NSString * _Nullable peerID,
869 NSData * _Nullable permanentInfo,
870 NSData * _Nullable permanentInfoSig,
871 NSData * _Nullable stableInfo,
872 NSData * _Nullable stableInfoSig,
873 NSError * _Nullable error))reply
874 {
875 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
876 [cfshContext handlePairingRestart:config];
877 [cfshContext startOctagonStateMachine];
878 [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config
879 epoch:config.epoch
880 reply:^(NSString * _Nullable peerID,
881 NSData * _Nullable permanentInfo,
882 NSData * _Nullable permanentInfoSig,
883 NSData * _Nullable stableInfo,
884 NSData * _Nullable stableInfoSig,
885 NSError * _Nullable error) {
886 reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error);
887 }];
888 }
889
890 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
891 vouchData:(NSData*)vouchData
892 vouchSig:(NSData*)vouchSig
893 reply:(void (^)(NSError * _Nullable error))reply
894 {
895 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
896 [cfshContext handlePairingRestart:config];
897 [cfshContext startOctagonStateMachine];
898 [cfshContext rpcJoin:vouchData vouchSig:vouchSig reply:^(NSError * _Nullable error) {
899 reply(error);
900 }];
901 }
902
903 ////
904 // MARK: Pairing Routines as Acceptor
905 ////
906
907 - (void)rpcEpochWithConfiguration:(OTJoiningConfiguration*)config
908 reply:(void (^)(uint64_t epoch,
909 NSError * _Nullable error))reply
910 {
911 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
912 [acceptorCfshContext startOctagonStateMachine];
913
914 // 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
915 [acceptorCfshContext requestTrustedDeviceListRefresh];
916
917 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
918
919 [clientStateMachine startOctagonStateMachine];
920
921 [clientStateMachine rpcEpoch:acceptorCfshContext reply:^(uint64_t epoch, NSError * _Nullable error) {
922 reply(epoch, error);
923 }];
924 }
925
926 - (void)rpcVoucherWithConfiguration:(OTJoiningConfiguration*)config
927 peerID:(NSString*)peerID
928 permanentInfo:(NSData *)permanentInfo
929 permanentInfoSig:(NSData *)permanentInfoSig
930 stableInfo:(NSData *)stableInfo
931 stableInfoSig:(NSData *)stableInfoSig
932 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
933 {
934 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
935 [acceptorCfshContext startOctagonStateMachine];
936
937 // 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
938 [acceptorCfshContext requestTrustedDeviceListRefresh];
939 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
940
941 [clientStateMachine rpcVoucher:acceptorCfshContext peerID:peerID permanentInfo:permanentInfo permanentInfoSig:permanentInfoSig stableInfo:stableInfo stableInfoSig:stableInfoSig reply:^(NSData *voucher, NSData *voucherSig, NSError *error) {
942 reply(voucher, voucherSig, error);
943 }];
944 }
945
946 - (void)restore:(NSString * _Nullable)containerName
947 contextID:(nonnull NSString *)contextID
948 bottleSalt:(nonnull NSString *)bottleSalt
949 entropy:(nonnull NSData *)entropy
950 bottleID:(nonnull NSString *)bottleID
951 reply:(nonnull void (^)(NSError * _Nullable))reply {
952 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityBottledPeerRestore];
953
954 secnotice("octagon", "restore via bottle invoked for container: %@, context: %@", containerName, contextID);
955
956 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
957
958 [cfshContext startOctagonStateMachine];
959
960 [cfshContext joinWithBottle:bottleID entropy:entropy bottleSalt:bottleSalt reply:^(NSError *error) {
961 [tracker stopWithEvent:OctagonEventBottledPeerRestore result:error];
962 reply(error);
963 }];
964 }
965
966 ////
967 // MARK: Ghost busting using ramp records
968 ////
969
970 -(BOOL) ghostbustByMidEnabled {
971 [self ensureRampsInitialized];
972 return [self.gbmidRamp checkRampStateWithError:nil];
973 }
974
975 -(BOOL) ghostbustBySerialEnabled {
976 [self ensureRampsInitialized];
977 return [self.gbserialRamp checkRampStateWithError:nil];
978 }
979
980 -(BOOL) ghostbustByAgeEnabled {
981 [self ensureRampsInitialized];
982 return [self.gbAgeRamp checkRampStateWithError:nil];
983 }
984
985 ////
986 // MARK: Analytics
987 ////
988
989 - (void)setupAnalytics
990 {
991 WEAKIFY(self);
992
993 [[self.loggerClass logger] AddMultiSamplerForName:@"Octagon-healthSummary"
994 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
995 block:^NSDictionary<NSString *,NSNumber *> *{
996 STRONGIFY(self);
997
998 // We actually only care about the default context for the default container
999 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1000
1001 secnotice("octagon-analytics", "Reporting analytics for container: %@, context: %@", OTCKContainerName, OTDefaultContext);
1002
1003 NSMutableDictionary* values = [NSMutableDictionary dictionary];
1004
1005 NSError* error = nil;
1006 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&error];
1007 if(error) {
1008 secnotice("octagon-analytics", "Error fetching SOS status: %@", error);
1009 }
1010 values[OctagonAnalyticsSOSStatus] = @((int)sosStatus);
1011 NSDate* dateOfLastPPJ = [[CKKSAnalytics logger] datePropertyForKey:OctagonEventUpgradePreflightPreapprovedJoin];
1012 values[OctagonAnalyticsDateOfLastPreflightPreapprovedJoin] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastPPJ]);
1013
1014 values[OctagonAnalyticsStateMachineState] = OctagonStateMap()[cuttlefishContext.stateMachine.currentState];
1015
1016 NSError* metadataError = nil;
1017 OTAccountMetadataClassC* metadata = [cuttlefishContext.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
1018 if(!metadata || metadataError) {
1019 secnotice("octagon-analytics", "Error fetching Octagon metadata: %@", metadataError);
1020 }
1021 values[OctagonAnalyticIcloudAccountState] = metadata ? @(metadata.icloudAccountState) : nil;
1022 values[OctagonAnalyticCDPBitStatus] = metadata? @(metadata.cdpState) : nil;
1023 values[OctagonAnalyticsTrustState] = metadata ? @(metadata.trustState) : nil;
1024
1025 NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck];
1026 values[OctagonAnalyticsLastHealthCheck] = @([CKKSAnalytics fuzzyDaysSinceDate:healthCheck]);
1027
1028 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastKeystateReady];
1029 values[OctagonAnalyticsLastKeystateReady] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR]);
1030
1031 if(metadata && metadata.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1032 values[OctagonAnalyticsAttemptedJoin] = @(metadata.attemptedJoin);
1033
1034 NSError* machineIDError = nil;
1035 NSString* machineID = [cuttlefishContext.authKitAdapter machineID:&machineIDError];
1036 if(machineIDError) {
1037 secnotice("octagon-analytics", "Error fetching machine ID: %@", metadataError);
1038 }
1039
1040 values[OctagonAnalyticsHaveMachineID] = @(machineID != nil);
1041
1042 if(machineID) {
1043 NSError* midOnListError = nil;
1044 BOOL midOnList = [cuttlefishContext machineIDOnMemoizedList:machineID error:&midOnListError];
1045
1046 if(midOnListError) {
1047 secnotice("octagon-analytics", "Error fetching 'mid on list': %@", midOnListError);
1048 } else {
1049 values[OctagonAnalyticsMIDOnMemoizedList] = @(midOnList);
1050 }
1051
1052 NSError* peersWithMIDError = nil;
1053 NSNumber* peersWithMID = [cuttlefishContext numberOfPeersInModelWithMachineID:machineID error:&peersWithMIDError];
1054 if(peersWithMID && peersWithMIDError == nil) {
1055 values[OctagonAnalyticsPeersWithMID] = peersWithMID;
1056 } else {
1057 secnotice("octagon-analytics", "Error fetching how many peers have our MID: %@", midOnListError);
1058 }
1059 }
1060 }
1061
1062 // Track CFU usage and success/failure metrics
1063 // 1. Users in a good state will have no outstanding CFU, and will not have done a CFU
1064 // 2. Users in a bad state who have not repsonded to the CFU (repaired) will have a pending CFU.
1065 // 3. Users in a bad state who have acted on the CFU will have no pending CFU, but will have CFU failures.
1066 //
1067 // We also record the time window between the last followup completion and invocation.
1068 NSDate* dateOfLastFollowup = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastCoreFollowup];
1069 values[OctagonAnalyticsLastCoreFollowup] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastFollowup]);
1070 // We used to report this, but it was never set
1071 //values[OctagonAnalyticsCoreFollowupStatus] = @(followupHandler.followupStatus);
1072
1073 for (NSString *type in [self cdpContextTypes]) {
1074 NSString *metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1075 NSString *countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1076
1077 NSDate *lastFailure = [[CKKSAnalytics logger] datePropertyForKey:metricName];
1078 if (lastFailure) {
1079 values[metricName] = @([CKKSAnalytics fuzzyDaysSinceDate:lastFailure]);
1080 values[countName] = [[CKKSAnalytics logger] numberPropertyForKey:countName];
1081 }
1082 }
1083
1084 // SecEscrowRequest
1085 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
1086 if (request) {
1087 values[OctagonAnalyticsPrerecordPending] = @([request pendingEscrowUpload:&error]);
1088 if (error) {
1089 secnotice("octagon-analytics", "Error fetching pendingEscrowUpload status: %@", error);
1090 }
1091 } else {
1092 secnotice("octagon-analytics", "Error fetching escrowRequestClass: %@", error);
1093 }
1094
1095 {
1096 ACAccountStore *store = [[ACAccountStore alloc] init];
1097 ACAccount* primaryAccount = [store aa_primaryAppleAccount];
1098 if(primaryAccount) {
1099 values[OctagonAnalyticsKVSProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeyValue]);
1100 values[OctagonAnalyticsKVSEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeyValue]);
1101 values[OctagonAnalyticsKeychainSyncProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeychainSync]);
1102 values[OctagonAnalyticsKeychainSyncEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeychainSync]);
1103 values[OctagonAnalyticsCloudKitProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassCKDatabaseService]);
1104 values[OctagonAnalyticsCloudKitEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassCKDatabaseService]);
1105 }
1106 }
1107
1108 return values;
1109 }];
1110
1111 [[self.loggerClass logger] AddMultiSamplerForName:@"CFU-healthSummary"
1112 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1113 block:^NSDictionary<NSString *,NSNumber *> *{
1114 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1115 return [cuttlefishContext.followupHandler sfaStatus];
1116 }];
1117 }
1118
1119 - (NSArray<OTCliqueCDPContextType> *)cdpContextTypes
1120 {
1121 static NSArray<OTCliqueCDPContextType> *contextTypes = nil;
1122 static dispatch_once_t onceToken;
1123 dispatch_once(&onceToken, ^{
1124 contextTypes = @[OTCliqueCDPContextTypeNone,
1125 OTCliqueCDPContextTypeSignIn,
1126 OTCliqueCDPContextTypeRepair,
1127 OTCliqueCDPContextTypeFinishPasscodeChange,
1128 OTCliqueCDPContextTypeRecoveryKeyGenerate,
1129 OTCliqueCDPContextTypeRecoveryKeyNew,
1130 OTCliqueCDPContextTypeUpdatePasscode,
1131 ];
1132 });
1133 return contextTypes;
1134 }
1135
1136 ////
1137 // MARK: Recovery Key
1138 ////
1139
1140 - (void) createRecoveryKey:(NSString* _Nullable)containerName
1141 contextID:(NSString *)contextID
1142 recoveryKey:(NSString *)recoveryKey
1143 reply:(void (^)( NSError *error))reply
1144 {
1145 secnotice("octagon", "Setting recovery key for container: %@, context: %@", containerName, contextID);
1146
1147 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivitySetRecoveryKey];
1148
1149 if (!self.sosEnabledForPlatform) {
1150 secnotice("octagon-recovery", "Device is considered a limited peer, cannot enroll recovery key in Octagon");
1151 NSError* notFullPeerError = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorLimitedPeer userInfo:@{NSLocalizedDescriptionKey : @"Device is considered a limited peer, cannot enroll recovery key in Octagon"}];
1152 [tracker stopWithEvent:OctagonEventRecoveryKey result:notFullPeerError];
1153 reply(notFullPeerError);
1154 return;
1155 }
1156
1157 CFErrorRef validateError = NULL;
1158 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1159 if (!res) {
1160 NSError *validateErrorWrapper = nil;
1161 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1162 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1163 if(validateError) {
1164 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1165 }
1166
1167 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1168
1169 secerror("recovery failed validation with error:%@", validateError);
1170
1171 [tracker stopWithEvent:OctagonEventSetRecoveryKeyValidationFailed result:validateErrorWrapper];
1172 reply(validateErrorWrapper);
1173 return;
1174 }
1175
1176 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1177
1178 [cfshContext startOctagonStateMachine];
1179
1180 [cfshContext rpcSetRecoveryKey:recoveryKey reply:^(NSError * _Nullable error) {
1181 [tracker stopWithEvent:OctagonEventRecoveryKey result:error];
1182 reply(error);
1183 }];
1184 }
1185
1186 - (void) joinWithRecoveryKey:(NSString* _Nullable)containerName
1187 contextID:(NSString *)contextID
1188 recoveryKey:(NSString*)recoveryKey
1189 reply:(void (^)(NSError * _Nullable))reply
1190 {
1191 secnotice("octagon", "join with recovery key invoked for container: %@, context: %@", containerName, contextID);
1192
1193 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityJoinWithRecoveryKey];
1194
1195 CFErrorRef validateError = NULL;
1196 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1197 if (!res) {
1198 NSError *validateErrorWrapper = nil;
1199 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1200 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1201 if(validateError) {
1202 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1203 }
1204
1205 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1206
1207 secerror("recovery failed validation with error:%@", validateError);
1208
1209 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyValidationFailed result:validateErrorWrapper];
1210 reply(validateErrorWrapper);
1211 return;
1212 }
1213
1214 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1215
1216 [cfshContext startOctagonStateMachine];
1217
1218 [cfshContext joinWithRecoveryKey:recoveryKey reply:^(NSError *error) {
1219 if ((error.code == TrustedPeersHelperErrorCodeNotEnrolled || error.code == TrustedPeersHelperErrorCodeUntrustedRecoveryKeys)
1220 && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]){
1221 // If we hit either of these errors, let's reset and establish octagon then enroll this recovery key in the new circle
1222 secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle");
1223 [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error];
1224
1225 [cfshContext rpcResetAndEstablish:CuttlefishResetReasonRecoveryKey reply:^(NSError *resetError) {
1226 if (resetError) {
1227 secerror("octagon, failed to reset octagon");
1228 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:resetError];
1229 reply(resetError);
1230 return;
1231 } else {
1232 // Now enroll the recovery key
1233 secnotice("octagon", "attempting enrolling recovery key");
1234 if (self.sosEnabledForPlatform) {
1235 [self createRecoveryKey:containerName contextID:contextID recoveryKey:recoveryKey reply:^(NSError *enrollError) {
1236 if(enrollError){
1237 secerror("octagon, failed to enroll new recovery key: %@", enrollError);
1238 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyEnrollFailed result:enrollError];
1239 reply(enrollError);
1240 return;
1241 }else{
1242 secnotice("octagon", "successfully enrolled recovery key");
1243 [tracker stopWithEvent:OctagonEventRecoveryKey result:nil];
1244 reply (nil);
1245 return;
1246 }
1247 }];
1248 } else {
1249 secnotice("octagon", "Limited Peer, can't enroll recovery key");
1250 reply (nil);
1251 return;
1252 }
1253 }
1254 }];
1255 } else {
1256 secerror("octagon, join with recovery key failed: %d", (int)[error code]);
1257 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyFailed result:error];
1258 reply(error);
1259 }
1260 }];
1261 }
1262
1263 - (void)xpc24HrNotification
1264 {
1265 secnotice("octagon-health", "Received 24hr xpc notification");
1266
1267 [self healthCheck:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply:^(NSError * _Nullable error) {
1268 if(error) {
1269 secerror("octagon: error attempting to check octagon health: %@", error);
1270 } else {
1271 secnotice("octagon", "health check success");
1272 }
1273 }];
1274 }
1275
1276 - (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply
1277 {
1278 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1279
1280 secnotice("octagon", "notifying container of change");
1281
1282 [cfshContext notifyContainerChange:nil];
1283
1284 [cfshContext checkOctagonHealth:skipRateLimitingCheck reply:^(NSError *error) {
1285 if(error) {
1286 reply(error);
1287 } else {
1288 reply(nil);
1289 }
1290 }];
1291 }
1292
1293 - (void)setSOSEnabledForPlatformFlag:(bool) value
1294 {
1295 self.sosEnabledForPlatform = value;
1296 }
1297
1298 - (void)allContextsHalt
1299 {
1300 for(OTCuttlefishContext* context in self.contexts.allValues) {
1301 [context.stateMachine haltOperation];
1302
1303 // Also, clear the viewManager strong pointer
1304 [context clearCKKSViewManager];
1305 }
1306 }
1307
1308 - (void)allContextsDisablePendingFlags
1309 {
1310 for(OTCuttlefishContext* context in self.contexts.allValues) {
1311 [context.stateMachine disablePendingFlags];
1312 }
1313 }
1314
1315 - (bool)allContextsPause:(uint64_t)within
1316 {
1317 for(OTCuttlefishContext* context in self.contexts.allValues) {
1318 if(context.stateMachine.currentState != OctagonStateMachineNotStarted) {
1319 if([context.stateMachine.paused wait:within] != 0) {
1320 return false;
1321 }
1322 }
1323 }
1324 return true;
1325 }
1326
1327 - (void)attemptSosUpgrade:(NSString* _Nullable)containerName
1328 context:(NSString*)context
1329 reply:(void (^)(NSError* error))reply
1330
1331 {
1332 secnotice("octagon-sos", "Attempting sos upgrade");
1333 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1334
1335 [cfshContext startOctagonStateMachine];
1336
1337 [cfshContext attemptSOSUpgrade:^(NSError *error) {
1338 reply(error);
1339 }];
1340 }
1341
1342 - (void)waitForOctagonUpgrade:(NSString* _Nullable)containerName
1343 context:(NSString*)context
1344 reply:(void (^)(NSError* error))reply
1345
1346 {
1347 secnotice("octagon-sos", "waitForOctagonUpgrade");
1348 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1349
1350 [cfshContext startOctagonStateMachine];
1351
1352 [cfshContext waitForOctagonUpgrade:^(NSError * _Nonnull error) {
1353 reply(error);
1354 }];
1355 }
1356
1357 - (OTFollowupContextType)cliqueCDPTypeToFollowupContextType:(OTCliqueCDPContextType)type
1358 {
1359 if ([type isEqualToString:OTCliqueCDPContextTypeNone]) {
1360 return OTFollowupContextTypeNone;
1361 } else if ([type isEqualToString:OTCliqueCDPContextTypeSignIn]) {
1362 return OTFollowupContextTypeNone;
1363 } else if ([type isEqualToString:OTCliqueCDPContextTypeRepair]) {
1364 return OTFollowupContextTypeStateRepair;
1365 } else if ([type isEqualToString:OTCliqueCDPContextTypeFinishPasscodeChange]) {
1366 return OTFollowupContextTypeOfflinePasscodeChange;
1367 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyGenerate]) {
1368 return OTFollowupContextTypeRecoveryKeyRepair;
1369 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyNew]) {
1370 return OTFollowupContextTypeRecoveryKeyRepair;
1371 } else if ([type isEqualToString:OTCliqueCDPContextTypeUpdatePasscode]) {
1372 return OTFollowupContextTypeNone;
1373 } else {
1374 return OTFollowupContextTypeNone;
1375 }
1376 }
1377
1378 - (void)postCDPFollowupResult:(BOOL)success
1379 type:(OTCliqueCDPContextType)type
1380 error:(NSError * _Nullable)error
1381 containerName:(NSString* _Nullable)containerName
1382 contextName:(NSString *)contextName
1383 reply:(void (^)(NSError *error))reply
1384 {
1385 OTCuttlefishContext *cuttlefishContext = [self contextForContainerName:containerName contextID:contextName];
1386
1387 NSString* metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1388 NSString* countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1389
1390 [[CKKSAnalytics logger] logResultForEvent:metricName
1391 hardFailure:NO
1392 result:error];
1393 if (error) {
1394 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName];
1395 [[CKKSAnalytics logger] incrementIntegerPropertyForKey:countName];
1396 } else {
1397 [[CKKSAnalytics logger] setDateProperty:NULL forKey:metricName];
1398 [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName];
1399 }
1400
1401 // Always return without error
1402 reply(nil);
1403 }
1404
1405 - (void)tapToRadar:(NSString *)action
1406 description:(NSString *)description
1407 radar:(NSString *)radar
1408 reply:(void (^)(NSError * _Nullable))reply
1409 {
1410 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:action description:description radar:radar];
1411 [ttr trigger];
1412 reply(NULL);
1413 }
1414
1415 - (void)refetchCKKSPolicy:(NSString * _Nullable)containerName
1416 contextID:(nonnull NSString *)contextID
1417 reply:(nonnull void (^)(NSError * _Nullable))reply {
1418 secnotice("octagon-ckks", "refetch-ckks-policy");
1419
1420 if(!containerName) {
1421 containerName = OTCKContainerName;
1422 }
1423
1424 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1425
1426 if(!cfshContext) {
1427 reply([NSError errorWithDomain:OctagonErrorDomain
1428 code:OTErrorNoSuchContext
1429 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1430 return;
1431 }
1432
1433 [cfshContext rpcRefetchCKKSPolicy:^(NSError* error) {
1434 secnotice("octagon-ckks", "refetch-ckks-policy result: %@", error ?: @"no error");
1435 reply(error);
1436 }];
1437 }
1438
1439
1440 - (void)getCDPStatus:(NSString * _Nullable)containerName
1441 contextID:(nonnull NSString *)contextID
1442 reply:(nonnull void (^)(OTCDPStatus, NSError * _Nullable))reply {
1443 secnotice("octagon-cdp", "get-cdp-status");
1444
1445 if(!containerName) {
1446 containerName = OTCKContainerName;
1447 }
1448
1449 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1450
1451 if(!cfshContext) {
1452 reply(OTCDPStatusUnknown, [NSError errorWithDomain:OctagonErrorDomain
1453 code:OTErrorNoSuchContext
1454 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1455 return;
1456 }
1457
1458 NSError* error = nil;
1459 OTCDPStatus status = [cfshContext getCDPStatus:&error];
1460
1461 reply(status, error);
1462 }
1463
1464
1465 - (void)setCDPEnabled:(NSString * _Nullable)containerName
1466 contextID:(nonnull NSString *)contextID
1467 reply:(nonnull void (^)(NSError * _Nullable))reply {
1468 secnotice("octagon-cdp", "set-cdp-enabled");
1469 if(!containerName) {
1470 containerName = OTCKContainerName;
1471 }
1472
1473 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1474
1475 if(!cfshContext) {
1476 reply([NSError errorWithDomain:OctagonErrorDomain
1477 code:OTErrorNoSuchContext
1478 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1479 return;
1480 }
1481
1482 NSError* localError = nil;
1483 [cfshContext setCDPEnabled:&localError];
1484
1485 reply(localError);
1486 }
1487
1488 @end
1489
1490 #endif