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