3 #import <Foundation/Foundation.h>
4 #import <Foundation/NSXPCConnection_Private.h>
5 #import <Security/SecItemPriv.h>
6 #import <Security/Security.h>
9 #include "utilities/SecCFWrappers.h"
10 #include "utilities/SecInternalReleasePriv.h"
11 #import "utilities/debugging.h"
13 #import "keychain/ot/OTClique.h"
14 #import "keychain/ot/OT.h"
15 #import "keychain/ot/OTConstants.h"
16 #import "keychain/ot/OTControl.h"
18 #import "keychain/otctl/OTControlCLI.h"
20 #import "keychain/OctagonTrust/OctagonTrust.h"
22 #import <AuthKit/AKAppleIDAuthenticationController.h>
23 #import <AuthKit/AKAppleIDAuthenticationContext.h>
24 #import <AuthKit/AKAppleIDAuthenticationContext_Private.h>
26 static NSString * fetch_pet(NSString * appleID, NSString * dsid)
28 if(!appleID && !dsid) {
29 NSLog(@"Must provide either an AppleID or a DSID to fetch a PET");
33 AKAppleIDAuthenticationContext* authContext = [[AKAppleIDAuthenticationContext alloc] init];
34 authContext.username = appleID;
36 authContext.authenticationType = AKAppleIDAuthenticationTypeSilent;
37 authContext.isUsernameEditable = NO;
39 __block NSString * pet = nil;
41 dispatch_semaphore_t s = dispatch_semaphore_create(0);
43 AKAppleIDAuthenticationController *authenticationController = [[AKAppleIDAuthenticationController alloc] init];
44 [authenticationController authenticateWithContext:authContext
45 completion:^(AKAuthenticationResults authenticationResults, NSError *error) {
47 NSLog(@"error fetching PET: %@", error);
51 pet = authenticationResults[AKAuthenticationPasswordKey];
52 dispatch_semaphore_signal(s);
54 dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
59 // Mutual recursion to set up an object for jsonification
60 static NSDictionary* cleanDictionaryForJSON(NSDictionary* dict);
62 static id cleanObjectForJSON(id obj) {
66 if([obj isKindOfClass:[NSError class]]) {
67 NSError* obje = (NSError*) obj;
68 NSMutableDictionary* newErrorDict = [@{@"code": @(obje.code), @"domain": obje.domain} mutableCopy];
69 newErrorDict[@"userInfo"] = cleanDictionaryForJSON(obje.userInfo);
71 } else if([NSJSONSerialization isValidJSONObject:obj]) {
74 } else if ([obj isKindOfClass: [NSNumber class]]) {
77 } else if([obj isKindOfClass: [NSData class]]) {
78 NSData* dataObj = (NSData*)obj;
79 return [dataObj base64EncodedStringWithOptions:0];
81 } else if ([obj isKindOfClass: [NSDictionary class]]) {
82 return cleanDictionaryForJSON((NSDictionary*) obj);
84 } else if ([obj isKindOfClass: [NSArray class]]) {
85 NSArray* arrayObj = (NSArray*)obj;
86 NSMutableArray* cleanArray = [NSMutableArray arrayWithCapacity:arrayObj.count];
88 for(id x in arrayObj) {
89 [cleanArray addObject: cleanObjectForJSON(x)];
94 return [obj description];
98 static NSDictionary* cleanDictionaryForJSON(NSDictionary* dict) {
102 NSMutableDictionary* mutDict = [dict mutableCopy];
103 for(id key in mutDict.allKeys) {
104 id obj = mutDict[key];
105 mutDict[key] = cleanObjectForJSON(obj);
110 static void print_json(NSDictionary* dict)
114 NSData* json = [NSJSONSerialization dataWithJSONObject:cleanDictionaryForJSON(dict)
115 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
118 NSLog(@"error during JSONification: %@", err.localizedDescription);
120 printf("%s\n", [[[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding] UTF8String]);
125 @implementation OTControlCLI
127 - (instancetype)initWithOTControl:(OTControl*)control {
128 if((self = [super init])) {
135 - (long)startOctagonStateMachine:(NSString *)container context:(NSString *)contextID {
137 __block long ret = -1;
139 [self.control startOctagonStateMachine:container
141 reply:^(NSError* _Nullable error) {
143 printf("Error starting state machine: %s\n", [[error description] UTF8String]);
146 printf("state machine started.\n");
152 printf("Unimplemented.\n");
157 - (long)signIn:(NSString *)altDSID container:(NSString * _Nullable)container context:(NSString *)contextID {
159 __block long ret = -1;
161 [self.control signIn:altDSID
164 reply:^(NSError* _Nullable error) {
166 printf("Error signing in: %s\n", [[error description] UTF8String]);
168 printf("Sign in complete.\n");
175 printf("Unimplemented.\n");
180 - (long)signOut:(NSString * _Nullable)container context:(NSString *)contextID {
182 __block long ret = -1;
183 [self.control signOut:container
185 reply:^(NSError* _Nullable error) {
187 printf("Error signing out: %s\n", [[error description] UTF8String]);
189 printf("Sign out complete.\n");
195 printf("Unimplemented.\n");
200 - (long)depart:(NSString * _Nullable)container context:(NSString *)contextID {
202 __block long ret = -1;
204 [self.control leaveClique:container
206 reply:^(NSError* _Nullable error) {
208 printf("Error departing clique: %s\n", [[error description] UTF8String]);
210 printf("Departing clique completed.\n");
216 printf("Unimplemented.\n");
221 - (long)resetOctagon:(NSString *)container context:(NSString *)contextID altDSID:(NSString *)altDSID {
223 __block long ret = -1;
225 [self.control resetAndEstablish:container
228 resetReason:CuttlefishResetReasonUserInitiatedReset
229 reply:^(NSError* _Nullable error) {
231 printf("Error resetting: %s\n", [[error description] UTF8String]);
233 printf("reset and establish:\n");
240 printf("Unimplemented.\n");
246 - (long)resetProtectedData:(NSString *)container context:(NSString *)contextID altDSID:(NSString *)altDSID appleID:(NSString *)appleID dsid:(NSString *)dsid
249 __block long ret = -1;
251 NSError* error = nil;
252 OTConfigurationContext *data = [[OTConfigurationContext alloc] init];
253 data.passwordEquivalentToken = fetch_pet(appleID, dsid);
254 data.authenticationAppleID = appleID;
255 data.altDSID = altDSID;
256 data.context = contextID;
257 data.containerName = container;
259 OTClique* clique = [OTClique resetProtectedData:data error:&error];
260 if(clique != nil && error == nil) {
265 printf("Unimplemented.\n");
270 - (void)printPeer:(NSDictionary*)peerInformation prefix:(NSString * _Nullable)prefix {
271 NSString * peerID = peerInformation[@"peerID"];
272 NSString * model = peerInformation[@"permanentInfo"][@"model_id"];
273 NSNumber* epoch = peerInformation[@"permanentInfo"][@"epoch"];
274 NSString * deviceName = peerInformation[@"stableInfo"][@"device_name"];
275 NSString * serialNumber = peerInformation[@"stableInfo"][@"serial_number"];
276 NSString * os = peerInformation[@"stableInfo"][@"os_version"];
278 printf("%s%s hw:'%s' name:'%s' serial: '%s' os:'%s' epoch:%d\n",
279 (prefix ? [prefix UTF8String] : ""),
282 [deviceName UTF8String],
283 [serialNumber UTF8String],
288 - (void)printPeers:(NSArray<NSString *>*)peerIDs
289 egoPeerID:(NSString * _Nullable)egoPeerID
290 informationOnPeers:(NSDictionary<NSString *, NSDictionary*>*)informationOnPeers {
291 for(NSString * peerID in peerIDs) {
292 NSDictionary* peerInformation = informationOnPeers[peerID];
294 if(!peerInformation) {
295 printf(" Peer: %s; further information missing\n", [peerID UTF8String]);
299 if([peerID isEqualToString:egoPeerID]) {
300 [self printPeer:peerInformation prefix:@" Self: "];
302 [self printPeer:peerInformation prefix:@" Peer: "];
307 - (long)fetchEscrowRecords:(NSString * _Nullable)container context:(NSString *)contextID {
309 __block long ret = -1;
311 NSError* error = nil;
312 OTConfigurationContext *data = [[OTConfigurationContext alloc] init];
313 data.context = contextID;
314 data.containerName = container;
316 NSArray<OTEscrowRecord*>* records = [OTClique fetchEscrowRecords:data error:&error];
317 if(records != nil && error == nil) {
318 printf("Successfully fetched %lu records.\n", (unsigned long)records.count);
320 for(OTEscrowRecord* record in records){
321 CFErrorRef* localError = NULL;
322 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(kCFAllocatorDefault, localError, (__bridge CFDataRef)record.escrowInformationMetadata.peerInfo);
323 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
324 printf("fetched record id: %s\n", [(__bridge NSString *)peerID UTF8String]);
329 printf("Unimplemented.\n");
334 - (long)fetchAllEscrowRecords:(NSString* _Nullable)container context:(NSString*)contextID {
336 __block long ret = -1;
338 NSError* error = nil;
339 OTConfigurationContext *data = [[OTConfigurationContext alloc] init];
340 data.context = contextID;
341 data.containerName = container;
343 NSArray<OTEscrowRecord*>* records = [OTClique fetchAllEscrowRecords:data error:&error];
344 if(records != nil && error == nil) {
345 printf("Successfully fetched %lu records.\n", (unsigned long)records.count);
347 for(OTEscrowRecord* record in records){
348 CFErrorRef* localError = NULL;
349 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(kCFAllocatorDefault, localError, (__bridge CFDataRef)record.escrowInformationMetadata.peerInfo);
350 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
351 printf("fetched record id: %s\n", [(__bridge NSString*)peerID UTF8String]);
356 printf("Unimplemented.\n");
361 - (long)performEscrowRecovery:(NSString * _Nullable)container context:(NSString *)contextID recordID:(NSString *)recordID appleID:(NSString *)appleID secret:(NSString *)secret
364 __block long ret = -1;
366 NSError* error = nil;
367 OTConfigurationContext *data = [[OTConfigurationContext alloc] init];
368 data.context = contextID;
370 OTICDPRecordContext* cdpContext = [[OTICDPRecordContext alloc] init];
371 cdpContext.cdpInfo = [[OTCDPRecoveryInformation alloc] init];
372 cdpContext.cdpInfo.recoverySecret = secret;
373 cdpContext.cdpInfo.containsIcdpData = true;
374 cdpContext.cdpInfo.usesMultipleIcsc = true;
375 cdpContext.authInfo = [[OTEscrowAuthenticationInformation alloc] init];
376 cdpContext.authInfo.authenticationAppleid = appleID;
377 cdpContext.authInfo.authenticationPassword = fetch_pet(appleID, nil);
379 NSArray<OTEscrowRecord*>* escrowRecords = [OTClique fetchEscrowRecords:data error:&error];
380 if (escrowRecords == nil || error != nil) {
381 printf("Failed to fetch escrow records.\n");
384 OTEscrowRecord* record = nil;
386 for (OTEscrowRecord* r in escrowRecords) {
387 CFErrorRef* localError = NULL;
388 SOSPeerInfoRef peer = SOSPeerInfoCreateFromData(kCFAllocatorDefault, localError, (__bridge CFDataRef)r.escrowInformationMetadata.peerInfo);
389 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
391 if ([(__bridge NSString *)peerID isEqualToString:recordID]) {
397 printf("Failed to find escrow record to restore. \n");
401 OTClique* clique = [OTClique performEscrowRecovery:data cdpContext:cdpContext escrowRecord:record error:&error];
402 if (clique != nil && error == nil) {
403 printf("Successfully performed escrow recovery.\n");
406 fprintf(stderr, "Escrow recovery failed: %s\n", error.description.UTF8String);
410 printf("Unimplemented.\n");
415 - (long)performSilentEscrowRecovery:(NSString * _Nullable)container context:(NSString *)contextID appleID:(NSString *)appleID secret:(NSString *)secret {
417 __block long ret = -1;
419 NSError* error = nil;
420 OTConfigurationContext *data = [[OTConfigurationContext alloc] init];
421 data.context = contextID;
423 OTICDPRecordContext* cdpContext = [[OTICDPRecordContext alloc] init];
424 cdpContext.cdpInfo = [[OTCDPRecoveryInformation alloc] init];
426 cdpContext.cdpInfo.recoverySecret = secret;
427 cdpContext.cdpInfo.containsIcdpData = true;
428 cdpContext.cdpInfo.silentRecoveryAttempt = true;
429 cdpContext.cdpInfo.usesMultipleIcsc = true;
431 cdpContext.authInfo = [[OTEscrowAuthenticationInformation alloc] init];
432 cdpContext.authInfo.authenticationAppleid = appleID;
433 cdpContext.authInfo.authenticationPassword = fetch_pet(appleID, nil);
436 NSArray<OTEscrowRecord*>* records = [OTClique fetchEscrowRecords:data error:&error];
437 if (records == nil || error != nil) {
438 printf("Failed to fetch escrow records.\n");
441 OTClique* clique = [OTClique performSilentEscrowRecovery:data cdpContext:cdpContext allRecords:records error:&error];
442 if (clique != nil && error == nil) {
443 printf("Successfully performed escrow recovery.\n");
446 fprintf(stderr, "Escrow recovery failed: %s\n", error.description.UTF8String);
450 printf("Unimplemented.\n");
456 - (long)status:(NSString * _Nullable)container context:(NSString *)contextID json:(bool)json {
458 __block long ret = 0;
460 [self.control status:container
462 reply:^(NSDictionary* result, NSError* _Nullable error) {
465 print_json(@{@"error" : [error description]});
467 printf("Error fetching status: %s\n", [[error description] UTF8String]);
474 printf("Status for %s,%s:\n", [result[@"containerName"] UTF8String], [result[@"contextID"] UTF8String]);
477 printf("State: %s\n", [[result[@"state"] description] UTF8String]);
478 printf("Flags: %s; Flags Pending: %s\n\n",
479 ([result[@"stateFlags"] count] == 0u) ? "none" : [[result[@"stateFlags"] description] UTF8String],
480 ([result[@"statePendingFlags"] count] == 0u) ? "none" : [[result[@"statePendingFlags"] description] UTF8String]);
482 NSDictionary* contextDump = result[@"contextDump"];
484 // Make it easy to find peer information
485 NSMutableDictionary<NSString *, NSDictionary*>* peers = [NSMutableDictionary dictionary];
486 NSMutableArray<NSString *>* allPeerIDs = [NSMutableArray array];
487 for(NSDictionary* peerInformation in contextDump[@"peers"]) {
488 NSString * peerID = peerInformation[@"peerID"];
490 peers[peerID] = peerInformation;
491 [allPeerIDs addObject:peerID];
495 NSDictionary* egoInformation = contextDump[@"self"];
496 NSString * egoPeerID = egoInformation[@"peerID"];
497 NSDictionary* egoDynamicInfo = egoInformation[@"dynamicInfo"];
500 NSMutableArray *otherPeers = [allPeerIDs mutableCopy];
501 [self printPeer:egoInformation prefix:@" Self: "];
504 // The self peer is technically a peer, so, shove it on in there
505 peers[egoPeerID] = egoInformation;
507 NSArray<NSString *>* includedPeers = egoDynamicInfo[@"included"];
508 printf("Trusted peers (by me):\n");
509 if(includedPeers && includedPeers.count > 0) {
510 [self printPeers:includedPeers egoPeerID:egoPeerID informationOnPeers:peers];
511 [otherPeers removeObjectsInArray:includedPeers];
513 printf(" No trusted peers.\n");
517 NSArray<NSString *>* excludedPeers = egoDynamicInfo[@"excluded"];
518 printf("Excluded peers (by me):\n");
519 if(excludedPeers && excludedPeers.count > 0) {
520 [self printPeers:excludedPeers egoPeerID:egoPeerID informationOnPeers:peers];
521 [otherPeers removeObjectsInArray:excludedPeers];
523 printf(" No excluded peers.\n");
527 printf("Other peers (included/excluded by others):\n");
528 if(otherPeers.count > 0) {
529 [self printPeers:otherPeers egoPeerID:egoPeerID informationOnPeers:peers];
531 printf(" No other peers.\n");
536 printf("No current identity for this device.\n\n");
538 if(allPeerIDs.count > 0) {
539 printf("All peers currently in this account:\n");
540 [self printPeers:allPeerIDs egoPeerID:nil informationOnPeers:peers];
542 printf("No peers currently exist for this account.\n");
553 printf("Unimplemented.\n");
558 - (long)recoverUsingBottleID:(NSString *)bottleID
559 entropy:(NSData*)entropy
560 altDSID:(NSString *)altDSID
561 containerName:(NSString *)containerName
562 context:(NSString *)context
563 control:(OTControl*)control {
564 __block long ret = 0;
567 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
569 [control restore:containerName
574 reply:^(NSError* _Nullable error) {
577 printf("Error recovering: %s\n", [[error description] UTF8String]);
579 printf("Succeeded recovering bottled peer %s\n", [[bottleID description] UTF8String]);
581 dispatch_semaphore_signal(sema);
584 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
585 printf("timed out waiting for restore/recover\n");
596 - (long)fetchAllBottles:(NSString *)altDSID
597 containerName:(NSString *)containerName
598 context:(NSString *)context
599 control:(OTControl*)control {
600 __block long ret = 0;
603 __block NSError* localError = nil;
605 __block NSArray<NSString *>* localViableBottleIDs = nil;
606 __block NSArray<NSString *>* localPartiallyViableBottleIDs = nil;
608 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
610 [control fetchAllViableBottles:containerName
612 reply:^(NSArray<NSString *>* _Nullable sortedBottleIDs,
613 NSArray<NSString *>* _Nullable sortedPartialBottleIDs,
614 NSError* _Nullable controlError) {
616 secnotice("clique", "findOptimalBottleIDsWithContextData errored: %@\n", controlError);
618 secnotice("clique", "findOptimalBottleIDsWithContextData succeeded: %@, %@\n", sortedBottleIDs, sortedPartialBottleIDs);
620 localError = controlError;
621 localViableBottleIDs = sortedBottleIDs;
622 localPartiallyViableBottleIDs = sortedPartialBottleIDs;
623 dispatch_semaphore_signal(sema);
626 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
627 secnotice("clique", "findOptimalBottleIDsWithContextData failed to fetch bottles\n");
631 [localViableBottleIDs enumerateObjectsUsingBlock:^(NSString * obj, NSUInteger idx, BOOL* stop) {
632 printf("preferred bottleID: %s\n", [obj UTF8String]);
635 [localPartiallyViableBottleIDs enumerateObjectsUsingBlock:^(NSString * obj, NSUInteger idx, BOOL* stop) {
636 printf("partial recovery bottleID: %s\n", [obj UTF8String]);
646 - (long)healthCheck:(NSString * _Nullable)container context:(NSString *)contextID skipRateLimitingCheck:(BOOL)skipRateLimitingCheck
649 __block long ret = -1;
651 [self.control healthCheck:container
653 skipRateLimitingCheck:skipRateLimitingCheck
654 reply:^(NSError* _Nullable error) {
656 printf("Error checking health: %s\n", [[error description] UTF8String]);
658 printf("Checking Octagon Health completed.\n");
664 printf("Unimplemented.\n");
669 - (long)refetchCKKSPolicy:(NSString *)container context:(NSString *)contextID
672 __block long ret = 1;
674 [self.control refetchCKKSPolicy:container
676 reply:^(NSError * _Nullable error) {
678 printf("Error refetching CKKS policy: %s\n", [[error description] UTF8String]);
680 printf("CKKS refetch completed.\n");
686 printf("Unimplemented.\n");
691 - (long)tapToRadar:(NSString *)action description:(NSString *)description radar:(NSString *)radar
694 __block long ret = 1;
696 [self.control tapToRadar:action
697 description:description
699 reply:^(NSError* _Nullable error) {
701 printf("Error trigger TTR: %s\n", [[error description] UTF8String]);
703 printf("Trigger TTR completed.\n");
709 printf("Unimplemented.\n");
714 - (long)setUserControllableViewsSyncStatus:(NSString * _Nullable)containerName
715 contextID:(NSString *)contextID
716 enabled:(BOOL)enabled
719 __block long ret = 1;
721 [self.control setUserControllableViewsSyncStatus:containerName
724 reply:^(BOOL nowSyncing, NSError * _Nullable error) {
726 printf("Error setting user controllable views: %s\n", [[error description] UTF8String]);
728 printf("User controllable views are now %s.", [(nowSyncing ? @"enabled" : @"paused") UTF8String]);
734 printf("Unimplemented.\n");
739 - (long)fetchUserControllableViewsSyncStatus:(NSString * _Nullable)containerName
740 contextID:(NSString *)contextID
743 __block long ret = 1;
745 [self.control fetchUserControllableViewsSyncStatus:containerName
747 reply:^(BOOL nowSyncing, NSError * _Nullable error) {
749 printf("Error setting user controllable views: %s\n", [[error description] UTF8String]);
751 printf("User controllable views are currently %s.", [(nowSyncing ? @"enabled" : @"paused") UTF8String]);
757 printf("Unimplemented.\n");