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"
17 #import "keychain/otctl/OTControlCLI.h"
20 #import <AuthKit/AKAppleIDAuthenticationController.h>
21 #import <AuthKit/AKAppleIDAuthenticationContext.h>
22 #import <AuthKit/AKAppleIDAuthenticationContext_Private.h>
24 static NSString* fetch_pet(NSString* appleID, NSString* dsid)
26 if(!appleID && !dsid) {
27 NSLog(@"Must provide either an AppleID or a DSID to fetch a PET");
31 AKAppleIDAuthenticationContext* authContext = [[AKAppleIDAuthenticationContext alloc] init];
32 authContext.username = appleID;
34 authContext.authenticationType = AKAppleIDAuthenticationTypeSilent;
35 authContext.isUsernameEditable = NO;
37 __block NSString* pet = nil;
39 dispatch_semaphore_t s = dispatch_semaphore_create(0);
41 AKAppleIDAuthenticationController *authenticationController = [[AKAppleIDAuthenticationController alloc] init];
42 [authenticationController authenticateWithContext:authContext
43 completion:^(AKAuthenticationResults authenticationResults, NSError *error) {
45 NSLog(@"error fetching PET: %@", error);
49 pet = authenticationResults[AKAuthenticationPasswordKey];
50 dispatch_semaphore_signal(s);
52 dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
57 // Mutual recursion to set up an object for jsonification
58 static NSDictionary* cleanDictionaryForJSON(NSDictionary* dict);
60 static id cleanObjectForJSON(id obj) {
64 if([obj isKindOfClass:[NSError class]]) {
65 NSError* obje = (NSError*) obj;
66 NSMutableDictionary* newErrorDict = [@{@"code": @(obje.code), @"domain": obje.domain} mutableCopy];
67 newErrorDict[@"userInfo"] = cleanDictionaryForJSON(obje.userInfo);
69 } else if([NSJSONSerialization isValidJSONObject:obj]) {
72 } else if ([obj isKindOfClass: [NSNumber class]]) {
75 } else if([obj isKindOfClass: [NSData class]]) {
76 NSData* dataObj = (NSData*)obj;
77 return [dataObj base64EncodedStringWithOptions:0];
79 } else if ([obj isKindOfClass: [NSDictionary class]]) {
80 return cleanDictionaryForJSON((NSDictionary*) obj);
82 } else if ([obj isKindOfClass: [NSArray class]]) {
83 NSArray* arrayObj = (NSArray*)obj;
84 NSMutableArray* cleanArray = [NSMutableArray arrayWithCapacity:arrayObj.count];
86 for(id x in arrayObj) {
87 [cleanArray addObject: cleanObjectForJSON(x)];
92 return [obj description];
96 static NSDictionary* cleanDictionaryForJSON(NSDictionary* dict) {
100 NSMutableDictionary* mutDict = [dict mutableCopy];
101 for(id key in mutDict.allKeys) {
102 id obj = mutDict[key];
103 mutDict[key] = cleanObjectForJSON(obj);
108 static void print_json(NSDictionary* dict)
112 NSData* json = [NSJSONSerialization dataWithJSONObject:cleanDictionaryForJSON(dict)
113 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
116 NSLog(@"error during JSONification: %@", err.localizedDescription);
118 printf("%s\n", [[[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding] UTF8String]);
123 @implementation OTControlCLI
125 - (instancetype)initWithOTControl:(OTControl*)control {
126 if((self = [super init])) {
133 - (long)startOctagonStateMachine:(NSString*)container context:(NSString*)contextID {
135 __block long ret = -1;
137 [self.control startOctagonStateMachine:container
139 reply:^(NSError* _Nullable error) {
141 printf("Error starting state machine: %s\n", [[error description] UTF8String]);
144 printf("state machine started.\n");
150 printf("Unimplemented.\n");
155 - (long)signIn:(NSString*)altDSID container:(NSString* _Nullable)container context:(NSString*)contextID {
157 __block long ret = -1;
159 [self.control signIn:altDSID
162 reply:^(NSError* _Nullable error) {
164 printf("Error signing in: %s\n", [[error description] UTF8String]);
166 printf("Sign in complete.\n");
173 printf("Unimplemented.\n");
178 - (long)signOut:(NSString* _Nullable)container context:(NSString*)contextID {
180 __block long ret = -1;
181 [self.control signOut:container
183 reply:^(NSError* _Nullable error) {
185 printf("Error signing out: %s\n", [[error description] UTF8String]);
187 printf("Sign out complete.\n");
193 printf("Unimplemented.\n");
198 - (long)depart:(NSString* _Nullable)container context:(NSString*)contextID {
200 __block long ret = -1;
202 [self.control leaveClique:container
204 reply:^(NSError* _Nullable error) {
206 printf("Error departing clique: %s\n", [[error description] UTF8String]);
208 printf("Departing clique completed.\n");
214 printf("Unimplemented.\n");
219 - (long)resetOctagon:(NSString*)container context:(NSString*)contextID altDSID:(NSString*)altDSID {
221 __block long ret = -1;
223 [self.control resetAndEstablish:container
226 resetReason:CuttlefishResetReasonUserInitiatedReset
227 reply:^(NSError* _Nullable error) {
229 printf("Error resetting: %s\n", [[error description] UTF8String]);
231 printf("reset and establish:\n");
238 printf("Unimplemented.\n");
244 - (long)resetProtectedData:(NSString*)container context:(NSString*)contextID altDSID:(NSString*)altDSID appleID:(NSString*)appleID dsid:(NSString*)dsid
247 __block long ret = -1;
249 NSError* error = nil;
250 OTConfigurationContext *data = [[OTConfigurationContext alloc] init];
251 data.passwordEquivalentToken = fetch_pet(appleID, dsid);
252 data.authenticationAppleID = appleID;
253 data.altDSID = altDSID;
254 data.context = contextID;
256 OTClique* clique = [OTClique resetProtectedData:data error:&error];
257 if(clique != nil && error == nil) {
262 printf("Unimplemented.\n");
267 - (void)printPeer:(NSDictionary*)peerInformation prefix:(NSString* _Nullable)prefix {
268 NSString* peerID = peerInformation[@"peerID"];
269 NSString* model = peerInformation[@"permanentInfo"][@"model_id"];
270 NSNumber* epoch = peerInformation[@"permanentInfo"][@"epoch"];
271 NSString* deviceName = peerInformation[@"stableInfo"][@"device_name"];
272 NSString* serialNumber = peerInformation[@"stableInfo"][@"serial_number"];
273 NSString* os = peerInformation[@"stableInfo"][@"os_version"];
275 printf("%s%s hw:'%s' name:'%s' serial: '%s' os:'%s' epoch:%d\n",
276 (prefix ? [prefix UTF8String] : ""),
279 [deviceName UTF8String],
280 [serialNumber UTF8String],
285 - (void)printPeers:(NSArray<NSString*>*)peerIDs
286 egoPeerID:(NSString* _Nullable)egoPeerID
287 informationOnPeers:(NSDictionary<NSString*, NSDictionary*>*)informationOnPeers {
288 for(NSString* peerID in peerIDs) {
289 NSDictionary* peerInformation = informationOnPeers[peerID];
291 if(!peerInformation) {
292 printf(" Peer: %s; further information missing\n", [peerID UTF8String]);
296 if([peerID isEqualToString:egoPeerID]) {
297 [self printPeer:peerInformation prefix:@" Self: "];
299 [self printPeer:peerInformation prefix:@" Peer: "];
304 - (long)status:(NSString* _Nullable)container context:(NSString*)contextID json:(bool)json {
306 __block long ret = 0;
308 [self.control status:container
310 reply:^(NSDictionary* result, NSError* _Nullable error) {
313 print_json(@{@"error" : [error description]});
315 printf("Error fetching status: %s\n", [[error description] UTF8String]);
322 printf("Status for %s,%s:\n", [result[@"containerName"] UTF8String], [result[@"contextID"] UTF8String]);
325 printf("State: %s\n", [[result[@"state"] description] UTF8String]);
326 printf("Flags: %s; Flags Pending: %s\n\n",
327 ([result[@"stateFlags"] count] == 0u) ? "none" : [[result[@"stateFlags"] description] UTF8String],
328 ([result[@"statePendingFlags"] count] == 0u) ? "none" : [[result[@"statePendingFlags"] description] UTF8String]);
330 NSDictionary* contextDump = result[@"contextDump"];
332 // Make it easy to find peer information
333 NSMutableDictionary<NSString*, NSDictionary*>* peers = [NSMutableDictionary dictionary];
334 NSMutableArray<NSString*>* allPeerIDs = [NSMutableArray array];
335 for(NSDictionary* peerInformation in contextDump[@"peers"]) {
336 NSString* peerID = peerInformation[@"peerID"];
338 peers[peerID] = peerInformation;
339 [allPeerIDs addObject:peerID];
343 NSDictionary* egoInformation = contextDump[@"self"];
344 NSString* egoPeerID = egoInformation[@"peerID"];
345 NSDictionary* egoDynamicInfo = egoInformation[@"dynamicInfo"];
348 NSMutableArray *otherPeers = [allPeerIDs mutableCopy];
349 [self printPeer:egoInformation prefix:@" Self: "];
352 // The self peer is technically a peer, so, shove it on in there
353 peers[egoPeerID] = egoInformation;
355 NSArray<NSString*>* includedPeers = egoDynamicInfo[@"included"];
356 printf("Trusted peers (by me):\n");
357 if(includedPeers && includedPeers.count > 0) {
358 [self printPeers:includedPeers egoPeerID:egoPeerID informationOnPeers:peers];
359 [otherPeers removeObjectsInArray:includedPeers];
361 printf(" No trusted peers.\n");
365 NSArray<NSString*>* excludedPeers = egoDynamicInfo[@"excluded"];
366 printf("Excluded peers (by me):\n");
367 if(excludedPeers && excludedPeers.count > 0) {
368 [self printPeers:excludedPeers egoPeerID:egoPeerID informationOnPeers:peers];
369 [otherPeers removeObjectsInArray:excludedPeers];
371 printf(" No excluded peers.\n");
375 printf("Other peers (included/excluded by others):\n");
376 if(otherPeers.count > 0) {
377 [self printPeers:otherPeers egoPeerID:egoPeerID informationOnPeers:peers];
379 printf(" No other peers.\n");
384 printf("No current identity for this device.\n\n");
386 if(allPeerIDs.count > 0) {
387 printf("All peers currently in this account:\n");
388 [self printPeers:allPeerIDs egoPeerID:nil informationOnPeers:peers];
390 printf("No peers currently exist for this account.\n");
401 printf("Unimplemented.\n");
406 - (long)recoverUsingBottleID:(NSString*)bottleID
407 entropy:(NSData*)entropy
408 altDSID:(NSString*)altDSID
409 containerName:(NSString*)containerName
410 context:(NSString*)context
411 control:(OTControl*)control {
412 __block long ret = 0;
415 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
417 [control restore:containerName
422 reply:^(NSError* _Nullable error) {
425 printf("Error recovering: %s\n", [[error description] UTF8String]);
427 printf("Succeeded recovering bottled peer %s\n", [[bottleID description] UTF8String]);
429 dispatch_semaphore_signal(sema);
432 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
433 printf("timed out waiting for restore/recover\n");
444 - (long)fetchAllBottles:(NSString*)altDSID
445 containerName:(NSString*)containerName
446 context:(NSString*)context
447 control:(OTControl*)control {
448 __block long ret = 0;
451 __block NSError* localError = nil;
453 __block NSArray<NSString*>* localViableBottleIDs = nil;
454 __block NSArray<NSString*>* localPartiallyViableBottleIDs = nil;
456 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
458 [control fetchAllViableBottles:containerName
460 reply:^(NSArray<NSString*>* _Nullable sortedBottleIDs,
461 NSArray<NSString*>* _Nullable sortedPartialBottleIDs,
462 NSError* _Nullable controlError) {
464 secnotice("clique", "findOptimalBottleIDsWithContextData errored: %@\n", controlError);
466 secnotice("clique", "findOptimalBottleIDsWithContextData succeeded: %@, %@\n", sortedBottleIDs, sortedPartialBottleIDs);
468 localError = controlError;
469 localViableBottleIDs = sortedBottleIDs;
470 localPartiallyViableBottleIDs = sortedPartialBottleIDs;
471 dispatch_semaphore_signal(sema);
474 if(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 60)) != 0) {
475 secnotice("clique", "findOptimalBottleIDsWithContextData failed to fetch bottles\n");
479 [localViableBottleIDs enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL* stop) {
480 printf("preferred bottleID: %s\n", [obj UTF8String]);
483 [localPartiallyViableBottleIDs enumerateObjectsUsingBlock:^(NSString* obj, NSUInteger idx, BOOL* stop) {
484 printf("partial recovery bottleID: %s\n", [obj UTF8String]);
494 - (long)healthCheck:(NSString* _Nullable)container context:(NSString*)contextID skipRateLimitingCheck:(BOOL)skipRateLimitingCheck
497 __block long ret = -1;
499 [self.control healthCheck:container
501 skipRateLimitingCheck:skipRateLimitingCheck
502 reply:^(NSError* _Nullable error) {
504 printf("Error checking health: %s\n", [[error description] UTF8String]);
506 printf("Checking Octagon Health completed.\n");
512 printf("Unimplemented.\n");
517 - (long)refetchCKKSPolicy:(NSString*)container context:(NSString*)contextID
520 __block long ret = 1;
522 [self.control refetchCKKSPolicy:container
524 reply:^(NSError * _Nullable error) {
526 printf("Error refetching CKKS policy: %s\n", [[error description] UTF8String]);
528 printf("CKKS refetch completed.\n");
534 printf("Unimplemented.\n");
539 - (long)tapToRadar:(NSString *)action description:(NSString *)description radar:(NSString *)radar
542 __block long ret = 1;
544 [self.control tapToRadar:action
545 description:description
547 reply:^(NSError* _Nullable error) {
549 printf("Error trigger TTR: %s\n", [[error description] UTF8String]);
551 printf("Trigger TTR completed.\n");
557 printf("Unimplemented.\n");