]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTManager.m
Security-59754.41.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 if([CKDatabase class] == nil) {
274 // CloudKit is not linked. We cannot bring Octagon up.
275 secerror("Octagon: CloudKit.framework appears to not be linked. Cannot create an Octagon manager (on pain of crash).");
276 return nil;
277 }
278
279 return [self resetManager:false to:nil];
280 }
281
282 + (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj
283 {
284 static OTManager* manager = nil;
285
286 if(!manager || reset || obj) {
287 @synchronized([self class]) {
288 if(obj != nil) {
289 manager = obj;
290 } else {
291 if(reset) {
292 manager = nil;
293 } else if (manager == nil && OctagonIsEnabled()) {
294 manager = [[OTManager alloc] init];
295 }
296 }
297 }
298 }
299
300 return manager;
301 }
302
303 - (void)ensureRampsInitialized
304 {
305 CKContainer* container = [CKKSViewManager manager].container;
306 CKDatabase* database = [container privateCloudDatabase];
307 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
308
309 CKKSAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
310 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
311 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
312
313 if(!self.gbmidRamp) {
314 self.gbmidRamp = [[OTRamp alloc]initWithRecordName:kOTRampForGhostBustMIDName
315 localSettingName:@"ghostBustMID"
316 container:container
317 database:database
318 zoneID:zoneID
319 accountTracker:accountTracker
320 lockStateTracker:lockStateTracker
321 reachabilityTracker:reachabilityTracker
322 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
323 }
324
325 if(!self.gbserialRamp) {
326 self.gbserialRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustSerialName
327 localSettingName:@"ghostBustSerial"
328 container:container
329 database:database
330 zoneID:zoneID
331 accountTracker:accountTracker
332 lockStateTracker:lockStateTracker
333 reachabilityTracker:reachabilityTracker
334 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
335 }
336
337 if(!self.gbAgeRamp) {
338 self.gbAgeRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustAgeName
339 localSettingName:@"ghostBustAge"
340 container:container
341 database:database
342 zoneID:zoneID
343 accountTracker:accountTracker
344 lockStateTracker:lockStateTracker
345 reachabilityTracker:reachabilityTracker
346 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
347 }
348 }
349
350 ////
351 // MARK: SPI routines
352 ////
353
354
355 - (void)signIn:(NSString*)altDSID
356 container:(NSString* _Nullable)containerName
357 context:(NSString*)contextID
358 reply:(void (^)(NSError * _Nullable signedInError))reply
359 {
360 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountAvailable];
361
362 if(containerName == nil) {
363 containerName = OTCKContainerName;
364 }
365
366 NSError *error = nil;
367 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
368
369 secnotice("octagon","signing in %@ for altDSID: %@", context, altDSID);
370 [context accountAvailable:altDSID error:&error];
371
372 [tracker stopWithEvent:OctagonEventSignIn result:error];
373
374 reply(error);
375 }
376
377 - (void)signOut:(NSString* _Nullable)containerName
378 context:(NSString*)contextID
379 reply:(void (^)(NSError * _Nullable signedOutError))reply
380 {
381 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountNotAvailable];
382
383 if(containerName == nil) {
384 containerName = OTCKContainerName;
385 }
386
387 NSError* error = nil;
388
389 // TODO: should we compare DSIDs here?
390 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
391
392 secnotice("octagon", "signing out of octagon trust: %@", context);
393
394 [context accountNoLongerAvailable:&error];
395 if(error) {
396 secnotice("octagon", "signing out failed: %@", error);
397 }
398
399 [tracker stopWithEvent:OctagonEventSignOut result:error];
400
401 reply(error);
402 }
403
404 - (void)notifyIDMSTrustLevelChangeForContainer:(NSString* _Nullable)containerName
405 context:(NSString*)contextID
406 reply:(void (^)(NSError * _Nullable error))reply
407 {
408 if(containerName == nil) {
409 containerName = OTCKContainerName;
410 }
411
412 NSError *error = nil;
413 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
414 secnotice("octagon","received a notification of IDMS trust level change in %@", context);
415 [context idmsTrustLevelChanged:&error];
416
417 reply(error);
418 }
419
420 - (void)handleIdentityChangeForSigningKey:(SFECKeyPair*)peerSigningKey
421 ForEncryptionKey:(SFECKeyPair*)encryptionKey
422 ForPeerID:(NSString*)peerID
423 reply:(void (^)(BOOL result,
424 NSError* _Nullable error))reply
425 {
426 secnotice("octagon", "handleIdentityChangeForSigningKey: %@", peerID);
427 reply(NO,
428 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
429 code:errSecUnimplemented
430 userInfo:nil]);
431 }
432
433 - (void)preflightBottledPeer:(NSString*)contextID
434 dsid:(NSString*)dsid
435 reply:(void (^)(NSData* _Nullable entropy,
436 NSString* _Nullable bottleID,
437 NSData* _Nullable signingPublicKey,
438 NSError* _Nullable error))reply
439 {
440 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
441 reply(nil,
442 nil,
443 nil,
444 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
445 code:errSecUnimplemented
446 userInfo:nil]);
447 }
448
449 - (void)launchBottledPeer:(NSString*)contextID
450 bottleID:(NSString*)bottleID
451 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
452 {
453 secnotice("octagon", "launchBottledPeer");
454 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
455 code:errSecUnimplemented
456 userInfo:nil]);
457 }
458
459 - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
460 {
461 secnotice("octagon", "restore");
462 reply(nil,
463 nil,
464 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
465 code:errSecUnimplemented
466 userInfo:nil]);
467 }
468
469 - (void)scrubBottledPeer:(NSString*)contextID
470 bottleID:(NSString*)bottleID
471 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
472 {
473 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
474 code:errSecUnimplemented
475 userInfo:nil]);
476 }
477
478 ////
479 // MARK: OTCTL tool routines
480 ////
481
482 -(void)reset:(void (^)(BOOL result, NSError *))reply
483 {
484 reply(NO,
485 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
486 code:errSecUnimplemented
487 userInfo:nil]);
488 }
489
490 - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError * _Nullable))reply
491 {
492 reply(@[],
493 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
494 code:errSecUnimplemented
495 userInfo:nil]);
496 }
497
498 - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
499 {
500 reply(nil,
501 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
502 code:errSecUnimplemented
503 userInfo:nil]);
504 }
505
506 -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
507 {
508 reply(nil,
509 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
510 code:errSecUnimplemented
511 userInfo:nil]);
512 }
513
514 - (void)setCuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
515 {
516 _cuttlefishXPCConnection = cuttlefishXPCConnection;
517 }
518
519 - (id<NSXPCProxyCreating>)cuttlefishXPCConnection
520 {
521 if(!_cuttlefishXPCConnection) {
522 NSXPCConnection* xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.TrustedPeersHelper"];
523 xpcConnection.remoteObjectInterface = TrustedPeersHelperSetupProtocol([NSXPCInterface interfaceWithProtocol:@protocol(TrustedPeersHelperProtocol)]);
524 [xpcConnection resume];
525 _cuttlefishXPCConnection = xpcConnection;
526 }
527
528 return _cuttlefishXPCConnection;
529 }
530
531 - (OTClientStateMachine*)clientStateMachineForContainerName:(NSString* _Nullable)containerName
532 contextID:(NSString*)contextID
533 clientName:(NSString*)clientName
534 {
535 __block OTClientStateMachine* client = nil;
536
537 if(containerName == nil) {
538 containerName = SecCKKSContainerName;
539 }
540
541 dispatch_sync(self.queue, ^{
542 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
543 secnotice("octagon-client", "fetching context for key: %@", key);
544 client = self.clients[key];
545 if(!client) {
546 client = [[OTClientStateMachine alloc] initWithContainerName:containerName
547 contextID:contextID
548 clientName:clientName
549 cuttlefish:self.cuttlefishXPCConnection];
550
551 self.clients[key] = client;
552 }
553 });
554
555 return client;
556 }
557
558 - (void)removeClientContextForContainerName:(NSString* _Nullable)containerName
559 clientName:(NSString*)clientName
560 {
561 if(containerName == nil) {
562 containerName = SecCKKSContainerName;
563 }
564
565 dispatch_sync(self.queue, ^{
566 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
567 [self.clients removeObjectForKey:key];
568 secnotice("octagon", "removed client context with key: %@", key);
569 });
570 }
571
572 - (void)removeContextForContainerName:(NSString*)containerName
573 contextID:(NSString*)contextID
574 {
575 dispatch_sync(self.queue, ^{
576 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
577 self.contexts[key] = nil;
578 });
579 }
580
581 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
582 contextID:(NSString*)contextID
583 {
584 return [self contextForContainerName:containerName
585 contextID:contextID
586 sosAdapter:self.sosAdapter
587 authKitAdapter:self.authKitAdapter
588 lockStateTracker:self.lockStateTracker
589 accountStateTracker:self.accountStateTracker
590 deviceInformationAdapter:self.deviceInformationAdapter];
591 }
592
593 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
594 contextID:(NSString*)contextID
595 sosAdapter:(id<OTSOSAdapter>)sosAdapter
596 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
597 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
598 accountStateTracker:(CKKSAccountStateTracker*)accountStateTracker
599 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
600 {
601 __block OTCuttlefishContext* context = nil;
602
603 if(containerName == nil) {
604 containerName = SecCKKSContainerName;
605 }
606
607 dispatch_sync(self.queue, ^{
608 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
609
610 context = self.contexts[key];
611
612 // Right now, CKKS can only handle one session per address space (and SQL database).
613 // Therefore, only the primary OTCuttlefishContext gets to own the view manager.
614 CKKSViewManager* viewManager = nil;
615 if([containerName isEqualToString:SecCKKSContainerName] &&
616 [contextID isEqualToString:OTDefaultContext]) {
617 viewManager = self.viewManager;
618 }
619
620 if(!context) {
621 context = [[OTCuttlefishContext alloc] initWithContainerName:containerName
622 contextID:contextID
623 cuttlefish:self.cuttlefishXPCConnection
624 sosAdapter:sosAdapter
625 authKitAdapter:authKitAdapter
626 ckksViewManager:viewManager
627 lockStateTracker:lockStateTracker
628 accountStateTracker:accountStateTracker
629 deviceInformationAdapter:deviceInformationAdapter
630 apsConnectionClass:self.apsConnectionClass
631 escrowRequestClass:self.escrowRequestClass
632 cdpd:self.cdpd];
633 self.contexts[key] = context;
634 }
635 });
636
637 return context;
638 }
639
640 - (void)clearAllContexts
641 {
642 if(self.contexts) {
643 dispatch_sync(self.queue, ^{
644 [self.contexts removeAllObjects];
645 });
646 }
647 }
648
649 - (void)fetchEgoPeerID:(NSString* _Nullable)container
650 context:(NSString*)context
651 reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply
652 {
653 if(!container) {
654 container = OTCKContainerName;
655 }
656 secnotice("octagon", "Received a fetch peer ID for container (%@) and context (%@)", container, context);
657 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
658 [cfshContext rpcFetchEgoPeerID:^(NSString * _Nullable peerID,
659 NSError * _Nullable error) {
660 reply(peerID, XPCSanitizeError(error));
661 }];
662 }
663
664 - (void)fetchTrustStatus:(NSString *)container
665 context:(NSString *)context
666 configuration:(OTOperationConfiguration *)configuration
667 reply:(void (^)(CliqueStatus status,
668 NSString* _Nullable peerID,
669 NSNumber * _Nullable numberOfPeersInOctagon,
670 BOOL isExcluded,
671 NSError* _Nullable error))reply
672 {
673 if(!container) {
674 container = OTCKContainerName;
675 }
676 secnotice("octagon", "Received a trust status for container (%@) and context (%@)", container, context);
677 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
678
679 [cfshContext rpcTrustStatus:configuration reply:^(CliqueStatus status,
680 NSString * _Nullable peerID,
681 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
682 BOOL isExcluded,
683 BOOL isLocked,
684 NSError * _Nullable error) {
685 // Our clients don't need the whole breakout of peers, so just count for them
686 long peerCount = 0;
687 for(NSNumber* n in peerCountByModelID.allValues) {
688 peerCount += [n longValue];
689 }
690
691 reply(status, peerID, @(peerCount), isExcluded, error);
692 }];
693 }
694
695 - (void)fetchCliqueStatus:(NSString* _Nullable)container
696 context:(NSString*)contextID
697 configuration:(OTOperationConfiguration *)configuration
698 reply:(void (^)(CliqueStatus cliqueStatus, NSError* _Nullable error))reply
699 {
700 if(!container) {
701 container = OTCKContainerName;
702 }
703 if(configuration == nil) {
704 configuration = [[OTOperationConfiguration alloc] init];
705 }
706
707 __block OTCuttlefishContext* context = nil;
708 dispatch_sync(self.queue, ^{
709 NSString* key = [NSString stringWithFormat:@"%@-%@", container, contextID];
710
711 context = self.contexts[key];
712 });
713
714 if(!context) {
715 reply(-1, [NSError errorWithDomain:OctagonErrorDomain
716 code:OTErrorNoSuchContext
717 description:[NSString stringWithFormat:@"No context for (%@,%@)", container, contextID]]);
718 return;
719 }
720
721
722 [context rpcTrustStatus:configuration reply:^(CliqueStatus status,
723 NSString* egoPeerID,
724 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
725 BOOL isExcluded,
726 BOOL isLocked,
727 NSError * _Nullable error) {
728 reply(status, error);
729 }];
730 }
731
732 - (void)status:(NSString* _Nullable)containerName
733 context:(NSString*)contextID
734 reply:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
735 {
736 if(!containerName) {
737 containerName = OTCKContainerName;
738 }
739
740 secnotice("octagon", "Received a status RPC for container (%@) and context (%@)", containerName, contextID);
741
742 __block OTCuttlefishContext* context = nil;
743 dispatch_sync(self.queue, ^{
744 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
745
746 context = self.contexts[key];
747 });
748
749 if(!context) {
750 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
751 code:OTErrorNoSuchContext
752 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
753 return;
754 }
755
756 [context rpcStatus:reply];
757 }
758
759 - (void)startOctagonStateMachine:(NSString* _Nullable)container
760 context:(NSString*)context
761 reply:(void (^)(NSError* _Nullable error))reply
762 {
763 secnotice("octagon", "Received a start-state-machine RPC for container (%@) and context (%@)", container, context);
764
765 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
766 [cfshContext startOctagonStateMachine];
767 reply(nil);
768 }
769
770 - (void)resetAndEstablish:(NSString *)container
771 context:(NSString *)context
772 altDSID:(NSString*)altDSID
773 resetReason:(CuttlefishResetReason)resetReason
774 reply:(void (^)(NSError * _Nullable))reply
775 {
776 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityResetAndEstablish];
777
778 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
779 [cfshContext startOctagonStateMachine];
780 [cfshContext rpcResetAndEstablish:resetReason reply:^(NSError* resetAndEstablishError) {
781 [tracker stopWithEvent:OctagonEventResetAndEstablish result:resetAndEstablishError];
782 reply(resetAndEstablishError);
783 }];
784 }
785
786 - (void)establish:(NSString *)container
787 context:(NSString *)context
788 altDSID:(NSString*)altDSID
789 reply:(void (^)(NSError * _Nullable))reply
790 {
791 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityEstablish];
792
793 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
794 [cfshContext startOctagonStateMachine];
795 [cfshContext rpcEstablish:altDSID reply:^(NSError* establishError) {
796 [tracker stopWithEvent:OctagonEventEstablish result:establishError];
797 reply(establishError);
798 }];
799 }
800
801 - (void)leaveClique:(NSString* _Nullable)container
802 context:(NSString*)context
803 reply:(void (^)(NSError* _Nullable error))reply
804 {
805 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityLeaveClique];
806
807 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
808 [cfshContext startOctagonStateMachine];
809 [cfshContext rpcLeaveClique:^(NSError* leaveError) {
810 [tracker stopWithEvent:OctagonEventLeaveClique result:leaveError];
811 reply(leaveError);
812 }];
813 }
814
815 - (void)removeFriendsInClique:(NSString* _Nullable)container
816 context:(NSString*)context
817 peerIDs:(NSArray<NSString*>*)peerIDs
818 reply:(void (^)(NSError* _Nullable error))reply
819 {
820 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityRemoveFriendsInClique];
821
822 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
823 [cfshContext startOctagonStateMachine];
824 [cfshContext rpcRemoveFriendsInClique:peerIDs reply:^(NSError* removeFriendsError) {
825 [tracker stopWithEvent:OctagonEventRemoveFriendsInClique result:removeFriendsError];
826 reply(removeFriendsError);
827 }];
828 }
829
830 - (void)peerDeviceNamesByPeerID:(NSString* _Nullable)container
831 context:(NSString*)context
832 reply:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
833 {
834 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
835 [cfshContext rpcFetchDeviceNamesByPeerID:reply];
836 }
837
838 - (void)fetchAllViableBottles:(NSString* _Nullable)container
839 context:(NSString*)context
840 reply:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialBottleIDs, NSError* _Nullable error))reply
841 {
842 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchAllViableBottles];
843
844 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
845 [cfshContext rpcFetchAllViableBottles:^(NSArray<NSString *> * _Nullable sortedBottleIDs,
846 NSArray<NSString *> * _Nullable sortedPartialEscrowRecordIDs,
847 NSError * _Nullable error) {
848 [tracker stopWithEvent:OctagonEventFetchAllBottles result:error];
849 reply(sortedBottleIDs, sortedPartialEscrowRecordIDs, error);
850 }];
851 }
852
853 - (void)fetchEscrowContents:(NSString* _Nullable)containerName
854 contextID:(NSString *)contextID
855 reply:(void (^)(NSData* _Nullable entropy,
856 NSString* _Nullable bottleID,
857 NSData* _Nullable signingPublicKey,
858 NSError* _Nullable error))reply
859 {
860 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchEscrowContents];
861
862 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
863 [cfshContext fetchEscrowContents:^(NSData *entropy,
864 NSString *bottleID,
865 NSData *signingPublicKey,
866 NSError *error) {
867 [tracker stopWithEvent:OctagonEventFetchEscrowContents result:error];
868 reply(entropy, bottleID, signingPublicKey, error);
869 }];
870 }
871
872 ////
873 // MARK: Pairing Routines as Initiator
874 ////
875 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
876 reply:(void (^)(NSString * _Nullable peerID,
877 NSData * _Nullable permanentInfo,
878 NSData * _Nullable permanentInfoSig,
879 NSData * _Nullable stableInfo,
880 NSData * _Nullable stableInfoSig,
881 NSError * _Nullable error))reply
882 {
883 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
884 [cfshContext handlePairingRestart:config];
885 [cfshContext startOctagonStateMachine];
886 [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config
887 epoch:config.epoch
888 reply:^(NSString * _Nullable peerID,
889 NSData * _Nullable permanentInfo,
890 NSData * _Nullable permanentInfoSig,
891 NSData * _Nullable stableInfo,
892 NSData * _Nullable stableInfoSig,
893 NSError * _Nullable error) {
894 reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error);
895 }];
896 }
897
898 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
899 vouchData:(NSData*)vouchData
900 vouchSig:(NSData*)vouchSig
901 reply:(void (^)(NSError * _Nullable error))reply
902 {
903 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
904 [cfshContext handlePairingRestart:config];
905 [cfshContext startOctagonStateMachine];
906 [cfshContext rpcJoin:vouchData vouchSig:vouchSig reply:^(NSError * _Nullable error) {
907 reply(error);
908 }];
909 }
910
911 ////
912 // MARK: Pairing Routines as Acceptor
913 ////
914
915 - (void)rpcEpochWithConfiguration:(OTJoiningConfiguration*)config
916 reply:(void (^)(uint64_t epoch,
917 NSError * _Nullable error))reply
918 {
919 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
920 [acceptorCfshContext startOctagonStateMachine];
921
922 // 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
923 [acceptorCfshContext requestTrustedDeviceListRefresh];
924
925 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
926
927 [clientStateMachine startOctagonStateMachine];
928
929 [clientStateMachine rpcEpoch:acceptorCfshContext reply:^(uint64_t epoch, NSError * _Nullable error) {
930 reply(epoch, error);
931 }];
932 }
933
934 - (void)rpcVoucherWithConfiguration:(OTJoiningConfiguration*)config
935 peerID:(NSString*)peerID
936 permanentInfo:(NSData *)permanentInfo
937 permanentInfoSig:(NSData *)permanentInfoSig
938 stableInfo:(NSData *)stableInfo
939 stableInfoSig:(NSData *)stableInfoSig
940 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
941 {
942 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
943 [acceptorCfshContext startOctagonStateMachine];
944
945 // 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
946 [acceptorCfshContext requestTrustedDeviceListRefresh];
947 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
948
949 [clientStateMachine rpcVoucher:acceptorCfshContext peerID:peerID permanentInfo:permanentInfo permanentInfoSig:permanentInfoSig stableInfo:stableInfo stableInfoSig:stableInfoSig reply:^(NSData *voucher, NSData *voucherSig, NSError *error) {
950 reply(voucher, voucherSig, error);
951 }];
952 }
953
954 - (void)restore:(NSString * _Nullable)containerName
955 contextID:(nonnull NSString *)contextID
956 bottleSalt:(nonnull NSString *)bottleSalt
957 entropy:(nonnull NSData *)entropy
958 bottleID:(nonnull NSString *)bottleID
959 reply:(nonnull void (^)(NSError * _Nullable))reply {
960 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityBottledPeerRestore];
961
962 secnotice("octagon", "restore via bottle invoked for container: %@, context: %@", containerName, contextID);
963
964 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
965
966 [cfshContext startOctagonStateMachine];
967
968 [cfshContext joinWithBottle:bottleID entropy:entropy bottleSalt:bottleSalt reply:^(NSError *error) {
969 [tracker stopWithEvent:OctagonEventBottledPeerRestore result:error];
970 reply(error);
971 }];
972 }
973
974 ////
975 // MARK: Ghost busting using ramp records
976 ////
977
978 -(BOOL) ghostbustByMidEnabled {
979 [self ensureRampsInitialized];
980 return [self.gbmidRamp checkRampStateWithError:nil];
981 }
982
983 -(BOOL) ghostbustBySerialEnabled {
984 [self ensureRampsInitialized];
985 return [self.gbserialRamp checkRampStateWithError:nil];
986 }
987
988 -(BOOL) ghostbustByAgeEnabled {
989 [self ensureRampsInitialized];
990 return [self.gbAgeRamp checkRampStateWithError:nil];
991 }
992
993 ////
994 // MARK: Analytics
995 ////
996
997 - (void)setupAnalytics
998 {
999 WEAKIFY(self);
1000
1001 [[self.loggerClass logger] AddMultiSamplerForName:@"Octagon-healthSummary"
1002 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1003 block:^NSDictionary<NSString *,NSNumber *> *{
1004 STRONGIFY(self);
1005
1006 // We actually only care about the default context for the default container
1007 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1008
1009 secnotice("octagon-analytics", "Reporting analytics for container: %@, context: %@", OTCKContainerName, OTDefaultContext);
1010
1011 NSMutableDictionary* values = [NSMutableDictionary dictionary];
1012
1013 NSError* error = nil;
1014 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&error];
1015 if(error) {
1016 secnotice("octagon-analytics", "Error fetching SOS status: %@", error);
1017 }
1018 values[OctagonAnalyticsSOSStatus] = @((int)sosStatus);
1019 NSDate* dateOfLastPPJ = [[CKKSAnalytics logger] datePropertyForKey:OctagonEventUpgradePreflightPreapprovedJoin];
1020 values[OctagonAnalyticsDateOfLastPreflightPreapprovedJoin] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastPPJ]);
1021
1022 values[OctagonAnalyticsStateMachineState] = OctagonStateMap()[cuttlefishContext.stateMachine.currentState];
1023
1024 NSError* metadataError = nil;
1025 OTAccountMetadataClassC* metadata = [cuttlefishContext.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
1026 if(!metadata || metadataError) {
1027 secnotice("octagon-analytics", "Error fetching Octagon metadata: %@", metadataError);
1028 }
1029 values[OctagonAnalyticIcloudAccountState] = metadata ? @(metadata.icloudAccountState) : nil;
1030 values[OctagonAnalyticCDPBitStatus] = metadata? @(metadata.cdpState) : nil;
1031 values[OctagonAnalyticsTrustState] = metadata ? @(metadata.trustState) : nil;
1032
1033 TPSyncingPolicy* syncingPolicy = [metadata getTPSyncingPolicy];
1034 values[OctagonAnalyticsUserControllableViewsSyncing] = syncingPolicy ? @(syncingPolicy.syncUserControllableViewsAsBoolean) : nil;
1035
1036 NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck];
1037 values[OctagonAnalyticsLastHealthCheck] = @([CKKSAnalytics fuzzyDaysSinceDate:healthCheck]);
1038
1039 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastKeystateReady];
1040 values[OctagonAnalyticsLastKeystateReady] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR]);
1041
1042 if(metadata && metadata.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1043 values[OctagonAnalyticsAttemptedJoin] = @(metadata.attemptedJoin);
1044
1045 NSError* machineIDError = nil;
1046 NSString* machineID = [cuttlefishContext.authKitAdapter machineID:&machineIDError];
1047 if(machineIDError) {
1048 secnotice("octagon-analytics", "Error fetching machine ID: %@", metadataError);
1049 }
1050
1051 values[OctagonAnalyticsHaveMachineID] = @(machineID != nil);
1052
1053 if(machineID) {
1054 NSError* midOnListError = nil;
1055 BOOL midOnList = [cuttlefishContext machineIDOnMemoizedList:machineID error:&midOnListError];
1056
1057 if(midOnListError) {
1058 secnotice("octagon-analytics", "Error fetching 'mid on list': %@", midOnListError);
1059 } else {
1060 values[OctagonAnalyticsMIDOnMemoizedList] = @(midOnList);
1061 }
1062
1063 NSError* peersWithMIDError = nil;
1064 NSNumber* peersWithMID = [cuttlefishContext numberOfPeersInModelWithMachineID:machineID error:&peersWithMIDError];
1065 if(peersWithMID && peersWithMIDError == nil) {
1066 values[OctagonAnalyticsPeersWithMID] = peersWithMID;
1067 } else {
1068 secnotice("octagon-analytics", "Error fetching how many peers have our MID: %@", midOnListError);
1069 }
1070 }
1071 }
1072
1073 // Track CFU usage and success/failure metrics
1074 // 1. Users in a good state will have no outstanding CFU, and will not have done a CFU
1075 // 2. Users in a bad state who have not repsonded to the CFU (repaired) will have a pending CFU.
1076 // 3. Users in a bad state who have acted on the CFU will have no pending CFU, but will have CFU failures.
1077 //
1078 // We also record the time window between the last followup completion and invocation.
1079 NSDate* dateOfLastFollowup = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastCoreFollowup];
1080 values[OctagonAnalyticsLastCoreFollowup] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastFollowup]);
1081 // We used to report this, but it was never set
1082 //values[OctagonAnalyticsCoreFollowupStatus] = @(followupHandler.followupStatus);
1083
1084 for (NSString *type in [self cdpContextTypes]) {
1085 NSString *metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1086 NSString *countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1087
1088 NSDate *lastFailure = [[CKKSAnalytics logger] datePropertyForKey:metricName];
1089 if (lastFailure) {
1090 values[metricName] = @([CKKSAnalytics fuzzyDaysSinceDate:lastFailure]);
1091 values[countName] = [[CKKSAnalytics logger] numberPropertyForKey:countName];
1092 }
1093 }
1094
1095 // SecEscrowRequest
1096 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
1097 if (request) {
1098 values[OctagonAnalyticsPrerecordPending] = @([request pendingEscrowUpload:&error]);
1099 if (error) {
1100 secnotice("octagon-analytics", "Error fetching pendingEscrowUpload status: %@", error);
1101 }
1102 } else {
1103 secnotice("octagon-analytics", "Error fetching escrowRequestClass: %@", error);
1104 }
1105
1106 {
1107 ACAccountStore *store = [[ACAccountStore alloc] init];
1108 ACAccount* primaryAccount = [store aa_primaryAppleAccount];
1109 if(primaryAccount) {
1110 values[OctagonAnalyticsKVSProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeyValue]);
1111 values[OctagonAnalyticsKVSEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeyValue]);
1112 values[OctagonAnalyticsKeychainSyncProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeychainSync]);
1113 values[OctagonAnalyticsKeychainSyncEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeychainSync]);
1114 values[OctagonAnalyticsCloudKitProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassCKDatabaseService]);
1115 values[OctagonAnalyticsCloudKitEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassCKDatabaseService]);
1116 }
1117 }
1118
1119 return values;
1120 }];
1121
1122 [[self.loggerClass logger] AddMultiSamplerForName:@"CFU-healthSummary"
1123 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1124 block:^NSDictionary<NSString *,NSNumber *> *{
1125 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1126 return [cuttlefishContext.followupHandler sfaStatus];
1127 }];
1128 }
1129
1130 - (NSArray<OTCliqueCDPContextType> *)cdpContextTypes
1131 {
1132 static NSArray<OTCliqueCDPContextType> *contextTypes = nil;
1133 static dispatch_once_t onceToken;
1134 dispatch_once(&onceToken, ^{
1135 contextTypes = @[OTCliqueCDPContextTypeNone,
1136 OTCliqueCDPContextTypeSignIn,
1137 OTCliqueCDPContextTypeRepair,
1138 OTCliqueCDPContextTypeFinishPasscodeChange,
1139 OTCliqueCDPContextTypeRecoveryKeyGenerate,
1140 OTCliqueCDPContextTypeRecoveryKeyNew,
1141 OTCliqueCDPContextTypeUpdatePasscode,
1142 ];
1143 });
1144 return contextTypes;
1145 }
1146
1147 ////
1148 // MARK: Recovery Key
1149 ////
1150
1151 - (void) createRecoveryKey:(NSString* _Nullable)containerName
1152 contextID:(NSString *)contextID
1153 recoveryKey:(NSString *)recoveryKey
1154 reply:(void (^)( NSError *error))reply
1155 {
1156 secnotice("octagon", "Setting recovery key for container: %@, context: %@", containerName, contextID);
1157
1158 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivitySetRecoveryKey];
1159
1160 if (!self.sosEnabledForPlatform) {
1161 secnotice("octagon-recovery", "Device does not participate in SOS; cannot enroll recovery key in Octagon");
1162 NSError* notFullPeerError = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorLimitedPeer userInfo:@{NSLocalizedDescriptionKey : @"Device is considered a limited peer, cannot enroll recovery key in Octagon"}];
1163 [tracker stopWithEvent:OctagonEventRecoveryKey result:notFullPeerError];
1164 reply(notFullPeerError);
1165 return;
1166 }
1167
1168 CFErrorRef validateError = NULL;
1169 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1170 if (!res) {
1171 NSError *validateErrorWrapper = nil;
1172 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1173 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1174 if(validateError) {
1175 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1176 }
1177
1178 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1179
1180 secerror("recovery failed validation with error:%@", validateError);
1181
1182 [tracker stopWithEvent:OctagonEventSetRecoveryKeyValidationFailed result:validateErrorWrapper];
1183 reply(validateErrorWrapper);
1184 return;
1185 }
1186
1187 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1188
1189 [cfshContext startOctagonStateMachine];
1190
1191 [cfshContext rpcSetRecoveryKey:recoveryKey reply:^(NSError * _Nullable error) {
1192 [tracker stopWithEvent:OctagonEventRecoveryKey result:error];
1193 reply(error);
1194 }];
1195 }
1196
1197 - (void) joinWithRecoveryKey:(NSString* _Nullable)containerName
1198 contextID:(NSString *)contextID
1199 recoveryKey:(NSString*)recoveryKey
1200 reply:(void (^)(NSError * _Nullable))reply
1201 {
1202 secnotice("octagon", "join with recovery key invoked for container: %@, context: %@", containerName, contextID);
1203
1204 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityJoinWithRecoveryKey];
1205
1206 CFErrorRef validateError = NULL;
1207 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1208 if (!res) {
1209 NSError *validateErrorWrapper = nil;
1210 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1211 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1212 if(validateError) {
1213 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1214 }
1215
1216 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1217
1218 secerror("recovery failed validation with error:%@", validateError);
1219
1220 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyValidationFailed result:validateErrorWrapper];
1221 reply(validateErrorWrapper);
1222 return;
1223 }
1224
1225 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1226
1227 [cfshContext startOctagonStateMachine];
1228
1229 [cfshContext joinWithRecoveryKey:recoveryKey reply:^(NSError *error) {
1230 if ((error.code == TrustedPeersHelperErrorCodeNotEnrolled || error.code == TrustedPeersHelperErrorCodeUntrustedRecoveryKeys)
1231 && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]){
1232 // If we hit either of these errors, and the local device thinks it should be able to set a recovery key,
1233 // let's reset and establish octagon then enroll this recovery key in the new circle
1234
1235 if (!self.sosEnabledForPlatform) {
1236 secerror("octagon: recovery key is not enrolled in octagon, and current device can't set recovery keys");
1237 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:error];
1238 reply(error);
1239 return;
1240 }
1241
1242 secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle");
1243 [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error];
1244
1245 [cfshContext rpcResetAndEstablish:CuttlefishResetReasonRecoveryKey reply:^(NSError *resetError) {
1246 if (resetError) {
1247 secerror("octagon, failed to reset octagon");
1248 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:resetError];
1249 reply(resetError);
1250 return;
1251 } else {
1252 // Now enroll the recovery key
1253 secnotice("octagon", "attempting enrolling recovery key");
1254 [self createRecoveryKey:containerName contextID:contextID recoveryKey:recoveryKey reply:^(NSError *enrollError) {
1255 if(enrollError){
1256 secerror("octagon, failed to enroll new recovery key: %@", enrollError);
1257 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyEnrollFailed result:enrollError];
1258 reply(enrollError);
1259 return;
1260 }else{
1261 secnotice("octagon", "successfully enrolled recovery key");
1262 [tracker stopWithEvent:OctagonEventRecoveryKey result:nil];
1263 reply (nil);
1264 return;
1265 }
1266 }];
1267 }
1268 }];
1269 } else {
1270 secerror("octagon, join with recovery key failed: %d", (int)[error code]);
1271 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyFailed result:error];
1272 reply(error);
1273 }
1274 }];
1275 }
1276
1277 - (void)xpc24HrNotification
1278 {
1279 secnotice("octagon-health", "Received 24hr xpc notification");
1280
1281 [self healthCheck:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply:^(NSError * _Nullable error) {
1282 if(error) {
1283 secerror("octagon: error attempting to check octagon health: %@", error);
1284 } else {
1285 secnotice("octagon", "health check success");
1286 }
1287 }];
1288 }
1289
1290 - (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply
1291 {
1292 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1293
1294 secnotice("octagon", "notifying container of change");
1295
1296 [cfshContext notifyContainerChange:nil];
1297
1298 [cfshContext checkOctagonHealth:skipRateLimitingCheck reply:^(NSError *error) {
1299 if(error) {
1300 reply(error);
1301 } else {
1302 reply(nil);
1303 }
1304 }];
1305 }
1306
1307 - (void)setSOSEnabledForPlatformFlag:(bool) value
1308 {
1309 self.sosEnabledForPlatform = value;
1310 }
1311
1312 - (void)allContextsHalt
1313 {
1314 for(OTCuttlefishContext* context in self.contexts.allValues) {
1315 [context.stateMachine haltOperation];
1316
1317 // Also, clear the viewManager strong pointer
1318 [context clearCKKSViewManager];
1319 }
1320 }
1321
1322 - (void)allContextsDisablePendingFlags
1323 {
1324 for(OTCuttlefishContext* context in self.contexts.allValues) {
1325 [context.stateMachine disablePendingFlags];
1326 }
1327 }
1328
1329 - (bool)allContextsPause:(uint64_t)within
1330 {
1331 for(OTCuttlefishContext* context in self.contexts.allValues) {
1332 if(context.stateMachine.currentState != OctagonStateMachineNotStarted) {
1333 if([context.stateMachine.paused wait:within] != 0) {
1334 return false;
1335 }
1336 }
1337 }
1338 return true;
1339 }
1340
1341 - (void)waitForOctagonUpgrade:(NSString* _Nullable)containerName
1342 context:(NSString*)context
1343 reply:(void (^)(NSError* error))reply
1344
1345 {
1346 secnotice("octagon-sos", "Attempting wait for octagon upgrade");
1347 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1348
1349 [cfshContext startOctagonStateMachine];
1350
1351 [cfshContext waitForOctagonUpgrade:^(NSError * _Nonnull error) {
1352 reply(error);
1353 }];
1354 }
1355
1356 - (OTFollowupContextType)cliqueCDPTypeToFollowupContextType:(OTCliqueCDPContextType)type
1357 {
1358 if ([type isEqualToString:OTCliqueCDPContextTypeNone]) {
1359 return OTFollowupContextTypeNone;
1360 } else if ([type isEqualToString:OTCliqueCDPContextTypeSignIn]) {
1361 return OTFollowupContextTypeNone;
1362 } else if ([type isEqualToString:OTCliqueCDPContextTypeRepair]) {
1363 return OTFollowupContextTypeStateRepair;
1364 } else if ([type isEqualToString:OTCliqueCDPContextTypeFinishPasscodeChange]) {
1365 return OTFollowupContextTypeOfflinePasscodeChange;
1366 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyGenerate]) {
1367 return OTFollowupContextTypeRecoveryKeyRepair;
1368 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyNew]) {
1369 return OTFollowupContextTypeRecoveryKeyRepair;
1370 } else if ([type isEqualToString:OTCliqueCDPContextTypeUpdatePasscode]) {
1371 return OTFollowupContextTypeNone;
1372 } else {
1373 return OTFollowupContextTypeNone;
1374 }
1375 }
1376
1377 - (void)postCDPFollowupResult:(BOOL)success
1378 type:(OTCliqueCDPContextType)type
1379 error:(NSError * _Nullable)error
1380 containerName:(NSString* _Nullable)containerName
1381 contextName:(NSString *)contextName
1382 reply:(void (^)(NSError *error))reply
1383 {
1384 NSString* metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1385 NSString* countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1386
1387 [[CKKSAnalytics logger] logResultForEvent:metricName
1388 hardFailure:NO
1389 result:error];
1390 if (error) {
1391 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName];
1392 [[CKKSAnalytics logger] incrementIntegerPropertyForKey:countName];
1393 } else {
1394 [[CKKSAnalytics logger] setDateProperty:NULL forKey:metricName];
1395 [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName];
1396 }
1397
1398 // Always return without error
1399 reply(nil);
1400 }
1401
1402 - (void)tapToRadar:(NSString *)action
1403 description:(NSString *)description
1404 radar:(NSString *)radar
1405 reply:(void (^)(NSError * _Nullable))reply
1406 {
1407 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:action description:description radar:radar];
1408 [ttr trigger];
1409 reply(NULL);
1410 }
1411
1412 - (void)refetchCKKSPolicy:(NSString * _Nullable)containerName
1413 contextID:(nonnull NSString *)contextID
1414 reply:(nonnull void (^)(NSError * _Nullable))reply {
1415 secnotice("octagon-ckks", "refetch-ckks-policy");
1416
1417 if(!containerName) {
1418 containerName = OTCKContainerName;
1419 }
1420
1421 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1422
1423 if(!cfshContext) {
1424 reply([NSError errorWithDomain:OctagonErrorDomain
1425 code:OTErrorNoSuchContext
1426 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1427 return;
1428 }
1429
1430 [cfshContext rpcRefetchCKKSPolicy:^(NSError* error) {
1431 secnotice("octagon-ckks", "refetch-ckks-policy result: %@", error ?: @"no error");
1432 reply(error);
1433 }];
1434 }
1435
1436 - (void)getCDPStatus:(NSString * _Nullable)containerName
1437 contextID:(nonnull NSString *)contextID
1438 reply:(nonnull void (^)(OTCDPStatus, NSError * _Nullable))reply {
1439 secnotice("octagon-cdp", "get-cdp-status");
1440
1441 if(!containerName) {
1442 containerName = OTCKContainerName;
1443 }
1444
1445 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1446
1447 if(!cfshContext) {
1448 reply(OTCDPStatusUnknown, [NSError errorWithDomain:OctagonErrorDomain
1449 code:OTErrorNoSuchContext
1450 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1451 return;
1452 }
1453
1454 NSError* error = nil;
1455 OTCDPStatus status = [cfshContext getCDPStatus:&error];
1456
1457 reply(status, error);
1458 }
1459
1460
1461 - (void)setCDPEnabled:(NSString * _Nullable)containerName
1462 contextID:(nonnull NSString *)contextID
1463 reply:(nonnull void (^)(NSError * _Nullable))reply {
1464 secnotice("octagon-cdp", "set-cdp-enabled");
1465 if(!containerName) {
1466 containerName = OTCKContainerName;
1467 }
1468
1469 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1470
1471 if(!cfshContext) {
1472 reply([NSError errorWithDomain:OctagonErrorDomain
1473 code:OTErrorNoSuchContext
1474 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1475 return;
1476 }
1477
1478 NSError* localError = nil;
1479 [cfshContext setCDPEnabled:&localError];
1480
1481 reply(localError);
1482 }
1483
1484 - (void)fetchEscrowRecords:(NSString * _Nullable)containerName
1485 contextID:(NSString*)contextID
1486 forceFetch:(BOOL)forceFetch
1487 reply:(nonnull void (^)(NSArray<NSData *> * _Nullable records,
1488 NSError * _Nullable error))reply {
1489
1490 secnotice("octagon-fetch-escrow-records", "fetching records");
1491 if(!containerName) {
1492 containerName = OTCKContainerName;
1493 }
1494
1495 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1496
1497 if(!cfshContext) {
1498 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
1499 code:OTErrorNoSuchContext
1500 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1501 return;
1502 }
1503
1504 [cfshContext rpcFetchAllViableEscrowRecords:forceFetch reply:^(NSArray<NSData *> * _Nullable records, NSError * _Nullable error) {
1505 if(error) {
1506 secerror("octagon-fetch-escrow-records: error fetching records: %@", error);
1507 reply(nil, error);
1508 return;
1509 }
1510 secnotice("octagon-fetch-escrow-records", "successfully fetched records");
1511 reply(records, nil);
1512 }];
1513 }
1514
1515 - (void)invalidateEscrowCache:(NSString * _Nullable)containerName
1516 contextID:(NSString*)contextID
1517 reply:(nonnull void (^)(NSError * _Nullable error))reply {
1518
1519 secnotice("octagon-remove-escrow-cache", "beginning removing escrow cache!");
1520 if(!containerName) {
1521 containerName = OTCKContainerName;
1522 }
1523
1524 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1525
1526 if(!cfshContext) {
1527 reply([NSError errorWithDomain:OctagonErrorDomain
1528 code:OTErrorNoSuchContext
1529 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1530 return;
1531 }
1532
1533 [cfshContext rpcInvalidateEscrowCache:^(NSError * _Nullable invalidateError) {
1534 if(invalidateError) {
1535 secerror("octagon-remove-escrow-cache: error invalidating escrow cache: %@", invalidateError);
1536 reply(invalidateError);
1537 return;
1538 }
1539 secnotice("octagon-remove-escrow-caches", "successfully invalidated escrow cache");
1540 reply(nil);
1541 }];
1542 }
1543
1544 - (void)setUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1545 contextID:(NSString*)contextID
1546 enabled:(BOOL)enabled
1547 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1548 {
1549 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1550
1551 if(!cfshContext) {
1552 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1553 code:OTErrorNoSuchContext
1554 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1555 return;
1556 }
1557
1558 [cfshContext rpcSetUserControllableViewsSyncingStatus:enabled reply:^(BOOL areSyncing, NSError * _Nullable error) {
1559 if(error) {
1560 secerror("octagon-user-controllable-views: error setting status: %@", error);
1561 reply(NO, error);
1562 return;
1563 }
1564
1565 secnotice("octagon-user-controllable-views", "successfully set status to: %@", areSyncing ? @"enabled" : @"paused");
1566 reply(areSyncing, nil);
1567 }];
1568 }
1569
1570 - (void)fetchUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1571 contextID:(NSString*)contextID
1572 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1573 {
1574 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1575
1576 if(!cfshContext) {
1577 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1578 code:OTErrorNoSuchContext
1579 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1580 return;
1581 }
1582
1583 [cfshContext rpcFetchUserControllableViewsSyncingStatus:^(BOOL areSyncing, NSError * _Nullable error) {
1584 if(error) {
1585 secerror("octagon-user-controllable-views: error fetching status: %@", error);
1586 reply(NO, error);
1587 return;
1588 }
1589
1590 secerror("octagon-user-controllable-views: succesfully fetched status as: %@", areSyncing ? @"enabled" : @"paused");
1591 reply(areSyncing, nil);
1592 }];
1593 }
1594
1595 @end
1596
1597 #endif