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